You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ji...@apache.org on 2025/05/30 16:19:51 UTC

(sedona) branch prepare-1.7.2 created (now 6ba5b8bcf0)

This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a change to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git


      at 6ba5b8bcf0 [GH-1945] Shade Jiffle and its dependencies (#1964)

This branch includes the following new commits:

     new 6b5b65e4a2 [SEDONA-722] Fix precision loss problems caused by casting world coordinates from double to float (#1860)
     new 8bee92d784 [SEDONA-704] Optimize STAC reader and fix few issues (#1861)
     new fb11f4f49a [DOCS] Bump Sedona versions in doc (#1862)
     new 8383a9a28a [SEDONA-715] Unify Zeppelin and Jupyter setting in Docker (#1850)
     new 98a99cb5ce [DOCS] add missing useSpheroid argument to ST_DBSCAN docs (#1870)
     new 7a8a41f7d8 [GH-1868] Fix spark sql extension load failure when parser failed to load (#1872)
     new 637fd5f744 [SEDONA-726] Fix ST_Force_2D and add ST_Force2D (#1876)
     new 657325d650 [DOCS] Update the Databricks doc to reflect issues on DBR 16 (#1877)
     new 173f28ce96 [SEDONA-704] Add the grid extension to the stac reader (#1883)
     new b8fee2b960 [SEDONA-727] Relocate ST_LabelPoint to alphabetical order in docs (#1896)
     new 8998ea05cd [CI] pre-commit(scala): auto add and standardize license headers (#1880)
     new ae44ede009 [DOCS] Fix case / spelling (#1882)
     new 5cae18cd35 [DOCS] Format Python Markdown codeblocks with black (#1881)
     new ff6ed393de [CI] pre-commit(insert-license): fix bug in comment markers for .c and .h (#1884)
     new 2b34fa8683 [DOCS] Fix spelling in Java code
     new 8bfc6aee7b [CI] Update CODEOWNERS add @jbampton for pre-commit config (#1891)
     new 8d4d4aac1e chore: standardize spelling in Java code (#1892)
     new 8935ca7a9e [CI] Clean up the ignored words list `codespell.txt` (#1890)
     new fe836bdfff [CI] Update CODEOWNERS add reviewers for linters (#1894)
     new f6ef7732da [CI] pre-commit: add `requirements-docs.txt` to `requirements-txt-fixer` (#1889)
     new 1e8b70ffdf [SEDONA-724] Fix RS_ZonalStats and RS_ZonalStatsAll edge case bug (#1871)
     new 64d2ffbaa2 [SEDONA-728] Fix Rasterization clamping bug (#1897)
     new bf240dd0c5 [DOC] Fix Referer of matomo tracking pixel (#1907)
     new 86f0fc2c26 [GH-1905] Increment Spark minor version in Databricks setup guide (#1912)
     new d930769adf [SEDONA-690] Set default metric to use Haversine for KNN join and code refactoring (#1909)
     new 13f6b2e87f [SEDONA-731] add osm nodes parser (#1920)
     new e55714fe8f [GH-1922] ST_X/Y/Z ON null geometries (#1923)
     new f827cdfb53 [GH-1761] error when invalid ST_Subdivide maxVertices argument (#1926)
     new cc5b6f1498 [DOCS] Fix the wrong discord link
     new ed5df81ca3 [CI] Fix failed Python build extension CI (#1928)
     new 2a19d9410b [GH-1910] Import geopandas only when type checking (#1929)
     new 02e57ef541 [GH-1931] Move packaging module import to geopandas try-except block (#1932)
     new 9831713622 [SEDONA-734] Fix relation parsing in OSM reader (#1947)
     new 3f16ee4a68 [SEDONA-735] Fix RS_Clip bug caused by AOI geometries smaller than pixel size (#1952)
     new 6ba5b8bcf0 [GH-1945] Shade Jiffle and its dependencies (#1964)

The 35 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(sedona) 07/35: [SEDONA-726] Fix ST_Force_2D and add ST_Force2D (#1876)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 637fd5f74475d3b75062fb47d3d7a5f0b0a80142
Author: Furqaan Khan <46...@users.noreply.github.com>
AuthorDate: Tue Mar 25 15:04:46 2025 -0400

    [SEDONA-726] Fix ST_Force_2D and add ST_Force2D (#1876)
---
 .../java/org/apache/sedona/common/Functions.java   |  2 +-
 .../common/utils/GeometryForce2DTransformer.java   | 44 +++++++++++++
 .../org/apache/sedona/common/FunctionsTest.java    | 75 ++++++++++++++++++++++
 docs/api/flink/Function.md                         | 22 ++++++-
 docs/api/snowflake/vector-data/Function.md         | 26 +++++++-
 docs/api/sql/Function.md                           | 22 ++++++-
 .../main/java/org/apache/sedona/flink/Catalog.java |  1 +
 .../apache/sedona/flink/expressions/Functions.java | 10 +++
 .../java/org/apache/sedona/flink/FunctionTest.java | 13 +++-
 python/sedona/sql/st_functions.py                  | 12 ++++
 python/tests/sql/test_dataframe_api.py             |  2 +
 python/tests/sql/test_function.py                  | 15 ++++-
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |  1 +
 .../sql/sedona_sql/expressions/Functions.scala     | 13 ++++
 .../sql/sedona_sql/expressions/st_functions.scala  |  3 +
 15 files changed, 255 insertions(+), 6 deletions(-)

diff --git a/common/src/main/java/org/apache/sedona/common/Functions.java b/common/src/main/java/org/apache/sedona/common/Functions.java
index c4bcb67086..97e08b7b0f 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -885,7 +885,7 @@ public class Functions {
   }
 
   public static Geometry force2D(Geometry geometry) {
-    return GeomUtils.get2dGeom(geometry);
+    return GeometryForce2DTransformer.transform2D(geometry);
   }
 
   public static boolean isEmpty(Geometry geometry) {
diff --git a/common/src/main/java/org/apache/sedona/common/utils/GeometryForce2DTransformer.java b/common/src/main/java/org/apache/sedona/common/utils/GeometryForce2DTransformer.java
new file mode 100644
index 0000000000..78bc16fa51
--- /dev/null
+++ b/common/src/main/java/org/apache/sedona/common/utils/GeometryForce2DTransformer.java
@@ -0,0 +1,44 @@
+/*
+ * 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.sedona.common.utils;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.util.GeometryTransformer;
+
+public class GeometryForce2DTransformer extends GeometryTransformer {
+
+  @Override
+  protected CoordinateSequence transformCoordinates(CoordinateSequence coords, Geometry parent) {
+    Coordinate[] newCoords = new Coordinate[coords.size()];
+    for (int i = 0; i < coords.size(); i++) {
+      Coordinate coordinate = coords.getCoordinate(i);
+      newCoords[i] = new Coordinate(coordinate.getX(), coordinate.getY());
+    }
+
+    return createCoordinateSequence(newCoords);
+  }
+
+  public static Geometry transform2D(Geometry geometry) {
+    if (geometry.getCoordinates().length == 0) return geometry;
+    GeometryForce2DTransformer transformer = new GeometryForce2DTransformer();
+    return transformer.transform(geometry);
+  }
+}
diff --git a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
index 642efe0216..4819384e70 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -2133,6 +2133,81 @@ public class FunctionsTest extends TestBase {
     assertEquals(expectedWKT, actualWKT);
   }
 
+  @Test
+  public void force2D() throws ParseException {
+    Geometry geom =
+        Constructors.geomFromWKT(
+            "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (3 5, 5 7, 5 3, 3 5), (5 7, 11 5, 5 3, 5 7))",
+            0);
+    byte[] polyBinary = Functions.asEWKB(geom);
+    Geometry polyFromBinary = Constructors.geomFromWKB(polyBinary);
+    Geometry forcedPoly = Functions.force2D(Functions.makeValid(polyFromBinary, false));
+    String actual = Functions.asWKT(forcedPoly);
+    String expected =
+        "POLYGON ((0 10, 10 10, 10 5.333333333333333, 5 7, 3 5, 5 3, 10 4.666666666666667, 10 0, 0 0, 0 10))";
+    assertEquals(expected, actual);
+
+    geom = Constructors.geomFromWKT("POINT ZM(1 2 2 5)", 0);
+    actual = Functions.asWKT(Functions.force2D(geom));
+    expected = "POINT (1 2)";
+    assertEquals(expected, actual);
+
+    geom = Constructors.geomFromWKT("MULTIPOINT ZM((1 2 2 5), (2 3 2 5))", 0);
+    actual = Functions.asWKT(Functions.force2D(geom));
+    expected = "MULTIPOINT ((1 2), (2 3))";
+    assertEquals(expected, actual);
+
+    geom = Constructors.geomFromWKT("LINESTRING (1 2 5, 2 3 5, 3 4 5)", 0);
+    actual = Functions.asWKT(Functions.force2D(geom));
+    expected = "LINESTRING (1 2, 2 3, 3 4)";
+    assertEquals(expected, actual);
+
+    // return 2D as is
+    geom =
+        Constructors.geomFromWKT(
+            "MULTILINESTRING ((10 10, 20 20, 30 30), (15 15, 25 25, 35 35))", 0);
+    Geometry actualGeom = Functions.force2D(geom);
+    assertTrue(Predicates.equals(actualGeom, geom));
+
+    geom = Constructors.geomFromWKT("LINEARRING M(30 10 5, 40 40 5, 20 40 5, 10 20 5, 30 10 5)", 0);
+    actual = Functions.asWKT(Functions.force2D(geom));
+    expected = "LINEARRING (30 10, 40 40, 20 40, 10 20, 30 10)";
+    assertEquals(expected, actual);
+
+    geom =
+        Constructors.geomFromWKT(
+            "POLYGON ZM((0 0 0 0, 10 0 0 0, 10 10 0 0, 0 10 0 0, 0 0 0 0), (4 4 0 0, 4 6 0 0, 6 6 0 0, 6 4 0 0, 4 4 0 0))",
+            0);
+    actual = Functions.asWKT(Functions.force2D(geom));
+    expected = "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (4 4, 4 6, 6 6, 6 4, 4 4))";
+    assertEquals(expected, actual);
+
+    geom =
+        Constructors.geomFromWKT(
+            "POLYGON ZM((0 0 0 0, 10 0 0 0, 10 10 0 0, 0 0 0 0), (4 4 0 0, 4 6 0 0, 6 6 0 0, 4 4 0 0))",
+            0);
+    actual = Functions.asWKT(Functions.force2D(geom));
+    expected = "POLYGON ((0 0, 10 0, 10 10, 0 0), (4 4, 4 6, 6 6, 4 4))";
+    assertEquals(expected, actual);
+
+    geom =
+        Constructors.geomFromWKT(
+            "MULTIPOLYGON M(((30 10 5, 40 40 5, 20 40 5, 10 20 5, 30 10 5)), ((15 5 5, 10 20 5, 20 30 5, 15 5 5)))",
+            0);
+    actual = Functions.asWKT(Functions.force2D(geom));
+    expected = "MULTIPOLYGON (((30 10, 40 40, 20 40, 10 20, 30 10)), ((15 5, 10 20, 20 30, 15 5)))";
+    assertEquals(expected, actual);
+
+    geom =
+        Constructors.geomFromWKT(
+            "GEOMETRYCOLLECTION ZM(POINT ZM(10 10 2 5), LINESTRING ZM(15 15 2 5, 25 25 2 5, 35 35 2 5), POLYGON ZM((30 10 2 5, 40 40 2 5, 20 40 2 5, 10 20 2 5, 30 10 2 5)))",
+            0);
+    actual = Functions.asWKT(Functions.force2D(geom));
+    expected =
+        "GEOMETRYCOLLECTION (POINT (10 10), LINESTRING (15 15, 25 25, 35 35), POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10)))";
+    assertEquals(expected, actual);
+  }
+
   @Test
   public void testForce3DM() throws ParseException {
     Geometry geom = Constructors.geomFromWKT("POINT (1 2)", 0);
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 8446bc5a69..d5960a2284 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -1439,7 +1439,7 @@ POINT (2 1)
 
 ## ST_Force_2D
 
-Introduction: Forces the geometries into a "2-dimensional mode" so that all output representations will only have the X and Y coordinates
+Introduction: Forces the geometries into a "2-dimensional mode" so that all output representations will only have the X and Y coordinates. This function is an alias of [ST_Force2D](#st_force2d).
 
 Format: `ST_Force_2D (A: Geometry)`
 
@@ -1457,6 +1457,26 @@ Output:
 POLYGON((0 0,0 5,5 0,0 0),(1 1,3 1,1 3,1 1))
 ```
 
+## ST_Force2D
+
+Introduction: Forces the geometries into a "2-dimensional mode" so that all output representations will only have the X and Y coordinates. This function is an alias of [ST_Force_2D](#st_force_2d).
+
+Format: `ST_Force2D (A: Geometry)`
+
+Since: `v1.8.0`
+
+Example:
+
+```sql
+SELECT ST_Force2D(ST_GeomFromText('POLYGON((0 0 2,0 5 2,5 0 2,0 0 2),(1 1 2,3 1 2,1 3 2,1 1 2))'))
+```
+
+Output:
+
+```
+POLYGON((0 0,0 5,5 0,0 0),(1 1,3 1,1 3,1 1))
+```
+
 ## ST_Force3D
 
 Introduction: Forces the geometry into a 3-dimensional model so that all output representations will have X, Y and Z coordinates.
diff --git a/docs/api/snowflake/vector-data/Function.md b/docs/api/snowflake/vector-data/Function.md
index f6b8c447f2..08bfb3b30a 100644
--- a/docs/api/snowflake/vector-data/Function.md
+++ b/docs/api/snowflake/vector-data/Function.md
@@ -1162,7 +1162,7 @@ Output: `POINT (2 1)`
 
 ## ST_Force_2D
 
-Introduction: Forces the geometries into a "2-dimensional mode" so that all output representations will only have the X and Y coordinates
+Introduction: Forces the geometries into a "2-dimensional mode" so that all output representations will only have the X and Y coordinates. This function is an alias of [ST_Force2D](#st_force2d).
 
 Format: `ST_Force_2D (A:geometry)`
 
@@ -1184,6 +1184,30 @@ Result:
 +---------------------------------------------------------------+
 ```
 
+## ST_Force2D
+
+Introduction: Forces the geometries into a "2-dimensional mode" so that all output representations will only have the X and Y coordinates. This function is an alias of [ST_Force_2D](#st_force_2d).
+
+Format: `ST_Force2D (A:geometry)`
+
+Example:
+
+```sql
+SELECT ST_AsText(
+    ST_Force2D(ST_GeomFromText('POLYGON((0 0 2,0 5 2,5 0 2,0 0 2),(1 1 2,3 1 2,1 3 2,1 1 2))'))
+) AS geom
+```
+
+Result:
+
+```
++---------------------------------------------------------------+
+|geom                                                           |
++---------------------------------------------------------------+
+|POLYGON((0 0,0 5,5 0,0 0),(1 1,3 1,1 3,1 1))                   |
++---------------------------------------------------------------+
+```
+
 ## ST_Force3D
 
 Introduction: Forces the geometry into a 3-dimensional model so that all output representations will have X, Y and Z coordinates.
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index d4bda5c44a..0cf40f4f2a 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -1495,7 +1495,7 @@ POINT (2 1)
 
 ## ST_Force_2D
 
-Introduction: Forces the geometries into a "2-dimensional mode" so that all output representations will only have the X and Y coordinates
+Introduction: Forces the geometries into a "2-dimensional mode" so that all output representations will only have the X and Y coordinates. This function is an alias of [ST_Force2D](#st_force2d).
 
 Format: `ST_Force_2D (A: Geometry)`
 
@@ -1513,6 +1513,26 @@ Output:
 POLYGON((0 0,0 5,5 0,0 0),(1 1,3 1,1 3,1 1))
 ```
 
+## ST_Force2D
+
+Introduction: Forces the geometries into a "2-dimensional mode" so that all output representations will only have the X and Y coordinates. This function is an alias of [ST_Force_2D](#st_force_2d).
+
+Format: `ST_Force2D (A: Geometry)`
+
+Since: `v1.8.0`
+
+SQL Example
+
+```sql
+SELECT ST_Force2D(ST_GeomFromText('POLYGON((0 0 2,0 5 2,5 0 2,0 0 2),(1 1 2,3 1 2,1 3 2,1 1 2))'))
+```
+
+Output:
+
+```
+POLYGON((0 0,0 5,5 0,0 0),(1 1,3 1,1 3,1 1))
+```
+
 ## ST_Force3D
 
 Introduction: Forces the geometry into a 3-dimensional model so that all output representations will have X, Y and Z coordinates.
diff --git a/flink/src/main/java/org/apache/sedona/flink/Catalog.java b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
index 497aaa5fe6..8654e3bf2a 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -128,6 +128,7 @@ public class Catalog {
       new Functions.ST_AsGML(),
       new Functions.ST_AsKML(),
       new Functions.ST_Force_2D(),
+      new Functions.ST_Force2D(),
       new Functions.ST_IsEmpty(),
       new Functions.ST_X(),
       new Functions.ST_Y(),
diff --git a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
index 86562860fe..af90fabbe6 100644
--- a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
+++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
@@ -865,6 +865,16 @@ public class Functions {
     }
   }
 
+  public static class ST_Force2D extends ScalarFunction {
+    @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
+    public Geometry eval(
+        @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
+            Object o) {
+      Geometry geom = (Geometry) o;
+      return org.apache.sedona.common.Functions.force2D(geom);
+    }
+  }
+
   public static class ST_IsEmpty extends ScalarFunction {
     @DataTypeHint("Boolean")
     public boolean eval(
diff --git a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
index 838f39db26..b85a9f0ae5 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -1126,7 +1126,7 @@ public class FunctionTest extends TestBase {
   }
 
   @Test
-  public void testForce2D() {
+  public void testForce_2D() {
     Table polygonTable = createPolygonTable(1);
     Table Forced2DTable =
         polygonTable.select(
@@ -1136,6 +1136,17 @@ public class FunctionTest extends TestBase {
         "POLYGON ((-0.5 -0.5, -0.5 0.5, 0.5 0.5, 0.5 -0.5, -0.5 -0.5))", result.toString());
   }
 
+  @Test
+  public void testForce2D() {
+    Table polygonTable = createPolygonTable(1);
+    Table Forced2DTable =
+        polygonTable.select(
+            call(Functions.ST_Force2D.class.getSimpleName(), $(polygonColNames[0])));
+    Geometry result = (Geometry) first(Forced2DTable).getField(0);
+    assertEquals(
+        "POLYGON ((-0.5 -0.5, -0.5 0.5, 0.5 0.5, 0.5 -0.5, -0.5 -0.5))", result.toString());
+  }
+
   @Test
   public void testIsEmpty() {
     Table polygonTable = createPolygonTable(testDataSize);
diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py
index 684ffa8e93..82bead1db2 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -654,6 +654,18 @@ def ST_Force_2D(geometry: ColumnOrName) -> Column:
     return _call_st_function("ST_Force_2D", geometry)
 
 
+@validate_argument_types
+def ST_Force2D(geometry: ColumnOrName) -> Column:
+    """Force the geometry column to only output two dimensional representations.
+
+    :param geometry: Geometry column to force to be 2D.
+    :type geometry: ColumnOrName
+    :return: Geometry column identical to geometry except with only X and Y coordinates.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Force2D", geometry)
+
+
 @validate_argument_types
 def ST_GeneratePoints(
     geometry: ColumnOrName,
diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py
index 99d1f17754..22ee40c799 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -543,6 +543,7 @@ test_configurations = [
     ),
     (stf.ST_FlipCoordinates, ("point",), "point_geom", "", "POINT (1 0)"),
     (stf.ST_Force_2D, ("point",), "point_geom", "", "POINT (0 1)"),
+    (stf.ST_Force2D, ("point",), "point_geom", "", "POINT (0 1)"),
     (stf.ST_Force3D, ("point", 1.0), "point_geom", "", "POINT Z (0 1 1)"),
     (
         stf.ST_Force3DM,
@@ -1304,6 +1305,7 @@ wrong_type_configurations = [
     (stf.ST_ExteriorRing, (None,)),
     (stf.ST_FlipCoordinates, (None,)),
     (stf.ST_Force_2D, (None,)),
+    (stf.ST_Force2D, (None,)),
     (stf.ST_Force3DM, (None,)),
     (stf.ST_Force3DZ, (None,)),
     (stf.ST_Force4D, (None,)),
diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py
index 96f31e4d94..da9f2bc253 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -2125,7 +2125,7 @@ class TestPredicateJoin(TestBase):
             )
             assert point.take(1)[0][0] == test[2]
 
-    def test_st_force2d(self):
+    def test_st_force_2d(self):
         tests1 = {
             "'POINT(0 5)'": "POINT (0 5)",
             "'POLYGON((0 0 2, 0 5 2, 5 0 2, 0 0 2), (1 1 2, 3 1 2, 1 3 2, 1 1 2))'": "POLYGON ((0 0, 0 5, 5 0, 0 0), (1 1, 3 1, 1 3, 1 1))",
@@ -2138,6 +2138,19 @@ class TestPredicateJoin(TestBase):
             )
             assert geom_2d.take(1)[0][0] == expected_geom
 
+    def test_st_force2d(self):
+        tests1 = {
+            "'POINT(0 5)'": "POINT (0 5)",
+            "'POLYGON((0 0 2, 0 5 2, 5 0 2, 0 0 2), (1 1 2, 3 1 2, 1 3 2, 1 1 2))'": "POLYGON ((0 0, 0 5, 5 0, 0 0), (1 1, 3 1, 1 3, 1 1))",
+            "'LINESTRING(0 5 1, 0 0 1, 0 10 2)'": "LINESTRING (0 5, 0 0, 0 10)",
+        }
+
+        for input_geom, expected_geom in tests1.items():
+            geom_2d = self.spark.sql(
+                "select ST_AsText(ST_Force2D(ST_GeomFromText({})))".format(input_geom)
+            )
+            assert geom_2d.take(1)[0][0] == expected_geom
+
     def test_st_buildarea(self):
         tests = {
             "'MULTILINESTRING((0 0, 10 0, 10 10, 0 10, 0 0),(10 10, 20 10, 20 20, 10 20, 10 10))'": "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0)), ((10 10, 10 20, 20 20, 20 10, 10 10)))",
diff --git a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index 57fe3893e0..e0e3503012 100644
--- a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -187,6 +187,7 @@ object Catalog extends AbstractCatalog {
     function[ST_PointN](),
     function[ST_AsEWKT](),
     function[ST_Force_2D](),
+    function[ST_Force2D](),
     function[ST_ForcePolygonCW](),
     function[ST_ForceRHR](),
     function[ST_ZMax](),
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index 8fb84b3b61..b594d8a43d 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -1241,6 +1241,19 @@ case class ST_Force_2D(inputExpressions: Seq[Expression])
   }
 }
 
+/*
+ * Forces the geometries into a "2-dimensional mode" so that all output representations will only have the X and Y coordinates.
+ *
+ * @param inputExpressions
+ */
+case class ST_Force2D(inputExpressions: Seq[Expression])
+    extends InferredExpression(Functions.force2D _) {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+    copy(inputExpressions = newChildren)
+  }
+}
+
 /**
  * Returns the geometry in EWKT format
  *
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
index 84d555ff64..637579e8c2 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
@@ -218,6 +218,9 @@ object st_functions extends DataFrameAPI {
   def ST_Force_2D(geometry: Column): Column = wrapExpression[ST_Force_2D](geometry)
   def ST_Force_2D(geometry: String): Column = wrapExpression[ST_Force_2D](geometry)
 
+  def ST_Force2D(geometry: Column): Column = wrapExpression[ST_Force2D](geometry)
+  def ST_Force2D(geometry: String): Column = wrapExpression[ST_Force2D](geometry)
+
   def ST_GeoHash(geometry: Column, precision: Column): Column =
     wrapExpression[ST_GeoHash](geometry, precision)
   def ST_GeoHash(geometry: String, precision: Int): Column =


(sedona) 10/35: [SEDONA-727] Relocate ST_LabelPoint to alphabetical order in docs (#1896)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit b8fee2b9609bcc6398e462117365d55a57d221d4
Author: Pranav Toggi <pr...@gmail.com>
AuthorDate: Sun Mar 30 21:52:43 2025 -0700

    [SEDONA-727] Relocate ST_LabelPoint to alphabetical order in docs (#1896)
    
    * profiling1
    
    * profiling4
    
    * profiling...
    
    * profiling 8
    
    * relocate docs
    
    * undo pom changes
---
 docs/api/flink/Function.md                 | 148 ++++++++++++++---------------
 docs/api/snowflake/vector-data/Function.md | 144 ++++++++++++++--------------
 docs/api/sql/Function.md                   | 148 ++++++++++++++---------------
 3 files changed, 220 insertions(+), 220 deletions(-)

diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index d5960a2284..fffb917684 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -183,80 +183,6 @@ Input: `POLYGON ((1 0 1, 1 1 1, 2 2 2, 1 0 1))`
 
 Output: `POLYGON Z((2 3 1, 4 5 1, 7 8 2, 2 3 1))`
 
-## ST_LabelPoint
-
-Introduction: `ST_LabelPoint` computes and returns a label point for a given polygon or geometry collection. The label point is chosen to be sufficiently far from boundaries of the geometry. For a regular Polygon this will be the
-centroid.
-
-The algorithm is derived from Tippecanoe’s `polygon_to_anchor`, an approximate solution for label point generation, designed to be faster than optimal algorithms like `polylabel`. It searches for a “good enough” label point within a limited number of iterations. For geometry collections, only the largest Polygon by area is considered. While `ST_Centroid` is a fast algorithm to calculate the center of mass of a (Multi)Polygon, it may place the point outside of the Polygon or near a bounda [...]
-
-`ST_LabelPoint` takes up to 3 arguments,
-
-- `geometry`: input geometry (e.g., a Polygon or GeometryCollection) for which the anchor point is to be calculated.
-- `gridResolution` (Optional, default is 16): Controls the resolution of the search grid for refining the label point. A higher resolution increases the grid density, providing a higher chance of finding a good enough result at the cost of runtime. For example, a gridResolution of 16 divides the bounding box of the polygon into a 16x16 grid.
-- `goodnessThreshold` (Optional, default is 0.2): Determines the minimum acceptable “goodness” value for the anchor point. Higher thresholds prioritize points farther from boundaries but may require more computation.
-
-!!!note
-    - `ST_LabelPoint` throws an `IllegalArgumentException` if the input geometry has an area of zero or less.
-    - Holes within polygons are respected. Points within a hole are given a goodness of 0.
-    - For GeometryCollections, only the largest polygon by area is considered.
-
-!!!tip
-    - Use `ST_LabelPoint` for tasks such as label placement, identifying representative points for polygons, or other spatial analyses where an internal reference point is preferred but not required. If intersection of the point and the original geometry is required, use of an algorithm like `polylabel` should be considered.
-    - `ST_LabelPoint` offers a faster, approximate solution for label point generation, making it ideal for large datasets or real-time applications.
-
-Format:
-
-```sql
-ST_LabelPoint(geometry: Geometry)
-```
-
-```sql
-ST_LabelPoint(geometry: Geometry, gridResolution: Integer)
-```
-
-```sql
-ST_LabelPoint(geometry: Geometry, gridResolution: Integer, goodnessThreshold: Double)
-```
-
-Since: `v1.7.1`
-
-SQL Example:
-
-```
-SELECT ST_LabelPoint(ST_GeomFromWKT('POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))'))
-```
-
-Output:
-
-```
-POINT (2 2)
-```
-
-SQL Example:
-
-```
-SELECT ST_LabelPoint(ST_GeomFromWKT('GEOMETRYCOLLECTION(POLYGON ((-112.840785 33.435962, -112.840785 33.708284, -112.409597 33.708284, -112.409597 33.435962, -112.840785 33.435962)), POLYGON ((-112.309264 33.398167, -112.309264 33.746007, -111.787444 33.746007, -111.787444 33.398167, -112.309264 33.398167)))'))
-```
-
-Output:
-
-```
-POINT (-112.04835399999999 33.57208699999999)
-```
-
-SQL Example:
-
-```
-SELECT ST_LabelPoint(ST_GeomFromWKT('POLYGON ((-112.654072 33.114485, -112.313516 33.653431, -111.63515 33.314399, -111.497829 33.874913, -111.692825 33.431378, -112.376684 33.788215, -112.654072 33.114485))', 4326))
-```
-
-Output:
-
-```
-SRID=4326;POINT (-112.0722602222832 33.53914975012836)
-```
-
 ## ST_Angle
 
 Introduction: Compute and return the angle between two vectors represented by the provided points or linestrings.
@@ -2453,6 +2379,80 @@ Output:
 false
 ```
 
+## ST_LabelPoint
+
+Introduction: `ST_LabelPoint` computes and returns a label point for a given polygon or geometry collection. The label point is chosen to be sufficiently far from boundaries of the geometry. For a regular Polygon this will be the
+centroid.
+
+The algorithm is derived from Tippecanoe’s `polygon_to_anchor`, an approximate solution for label point generation, designed to be faster than optimal algorithms like `polylabel`. It searches for a “good enough” label point within a limited number of iterations. For geometry collections, only the largest Polygon by area is considered. While `ST_Centroid` is a fast algorithm to calculate the center of mass of a (Multi)Polygon, it may place the point outside of the Polygon or near a bounda [...]
+
+`ST_LabelPoint` takes up to 3 arguments,
+
+- `geometry`: input geometry (e.g., a Polygon or GeometryCollection) for which the anchor point is to be calculated.
+- `gridResolution` (Optional, default is 16): Controls the resolution of the search grid for refining the label point. A higher resolution increases the grid density, providing a higher chance of finding a good enough result at the cost of runtime. For example, a gridResolution of 16 divides the bounding box of the polygon into a 16x16 grid.
+- `goodnessThreshold` (Optional, default is 0.2): Determines the minimum acceptable “goodness” value for the anchor point. Higher thresholds prioritize points farther from boundaries but may require more computation.
+
+!!!note
+    - `ST_LabelPoint` throws an `IllegalArgumentException` if the input geometry has an area of zero or less.
+    - Holes within polygons are respected. Points within a hole are given a goodness of 0.
+    - For GeometryCollections, only the largest polygon by area is considered.
+
+!!!tip
+    - Use `ST_LabelPoint` for tasks such as label placement, identifying representative points for polygons, or other spatial analyses where an internal reference point is preferred but not required. If intersection of the point and the original geometry is required, use of an algorithm like `polylabel` should be considered.
+    - `ST_LabelPoint` offers a faster, approximate solution for label point generation, making it ideal for large datasets or real-time applications.
+
+Format:
+
+```sql
+ST_LabelPoint(geometry: Geometry)
+```
+
+```sql
+ST_LabelPoint(geometry: Geometry, gridResolution: Integer)
+```
+
+```sql
+ST_LabelPoint(geometry: Geometry, gridResolution: Integer, goodnessThreshold: Double)
+```
+
+Since: `v1.7.1`
+
+SQL Example:
+
+```
+SELECT ST_LabelPoint(ST_GeomFromWKT('POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))'))
+```
+
+Output:
+
+```
+POINT (2 2)
+```
+
+SQL Example:
+
+```
+SELECT ST_LabelPoint(ST_GeomFromWKT('GEOMETRYCOLLECTION(POLYGON ((-112.840785 33.435962, -112.840785 33.708284, -112.409597 33.708284, -112.409597 33.435962, -112.840785 33.435962)), POLYGON ((-112.309264 33.398167, -112.309264 33.746007, -111.787444 33.746007, -111.787444 33.398167, -112.309264 33.398167)))'))
+```
+
+Output:
+
+```
+POINT (-112.04835399999999 33.57208699999999)
+```
+
+SQL Example:
+
+```
+SELECT ST_LabelPoint(ST_GeomFromWKT('POLYGON ((-112.654072 33.114485, -112.313516 33.653431, -111.63515 33.314399, -111.497829 33.874913, -111.692825 33.431378, -112.376684 33.788215, -112.654072 33.114485))', 4326))
+```
+
+Output:
+
+```
+SRID=4326;POINT (-112.0722602222832 33.53914975012836)
+```
+
 ## ST_Length
 
 Introduction: Returns the perimeter of A.
diff --git a/docs/api/snowflake/vector-data/Function.md b/docs/api/snowflake/vector-data/Function.md
index 08bfb3b30a..d545a2695a 100644
--- a/docs/api/snowflake/vector-data/Function.md
+++ b/docs/api/snowflake/vector-data/Function.md
@@ -146,78 +146,6 @@ Input: `POLYGON ((1 0 1, 1 1 1, 2 2 2, 1 0 1))`
 
 Output: `POLYGON Z((2 3 1, 4 5 1, 7 8 2, 2 3 1))`
 
-## ST_LabelPoint
-
-Introduction: `ST_LabelPoint` computes and returns a label point for a given polygon or geometry collection. The label point is chosen to be sufficiently far from boundaries of the geometry. For a regular Polygon this will be the
-centroid.
-
-The algorithm is derived from Tippecanoe’s `polygon_to_anchor`, an approximate solution for label point generation, designed to be faster than optimal algorithms like `polylabel`. It searches for a “good enough” label point within a limited number of iterations. For geometry collections, only the largest Polygon by area is considered. While `ST_Centroid` is a fast algorithm to calculate the center of mass of a (Multi)Polygon, it may place the point outside of the Polygon or near a bounda [...]
-
-`ST_LabelPoint` takes up to 3 arguments,
-
-- `geometry`: input geometry (e.g., a Polygon or GeometryCollection) for which the anchor point is to be calculated.
-- `gridResolution` (Optional, default is 16): Controls the resolution of the search grid for refining the label point. A higher resolution increases the grid density, providing a higher chance of finding a good enough result at the cost of runtime. For example, a gridResolution of 16 divides the bounding box of the polygon into a 16x16 grid.
-- `goodnessThreshold` (Optional, default is 0.2): Determines the minimum acceptable “goodness” value for the anchor point. Higher thresholds prioritize points farther from boundaries but may require more computation.
-
-!!!note
-    - `ST_LabelPoint` throws an `IllegalArgumentException` if the input geometry has an area of zero or less.
-    - Holes within polygons are respected. Points within a hole are given a goodness of 0.
-    - For GeometryCollections, only the largest polygon by area is considered.
-
-!!!tip
-    - Use `ST_LabelPoint` for tasks such as label placement, identifying representative points for polygons, or other spatial analyses where an internal reference point is preferred but not required. If intersection of the point and the original geometry is required, use of an algorithm like `polylabel` should be considered.
-    - `ST_LabelPoint` offers a faster, approximate solution for label point generation, making it ideal for large datasets or real-time applications.
-
-Format:
-
-```sql
-ST_LabelPoint(geometry: Geometry)
-```
-
-```sql
-ST_LabelPoint(geometry: Geometry, gridResolution: Integer)
-```
-
-```sql
-ST_LabelPoint(geometry: Geometry, gridResolution: Integer, goodnessThreshold: Double)
-```
-
-SQL Example:
-
-```
-SELECT ST_LabelPoint(ST_GeomFromWKT('POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))'))
-```
-
-Output:
-
-```
-POINT (2 2)
-```
-
-SQL Example:
-
-```
-SELECT ST_LabelPoint(ST_GeomFromWKT('GEOMETRYCOLLECTION(POLYGON ((-112.840785 33.435962, -112.840785 33.708284, -112.409597 33.708284, -112.409597 33.435962, -112.840785 33.435962)), POLYGON ((-112.309264 33.398167, -112.309264 33.746007, -111.787444 33.746007, -111.787444 33.398167, -112.309264 33.398167)))'))
-```
-
-Output:
-
-```
-POINT (-112.04835399999999 33.57208699999999)
-```
-
-SQL Example:
-
-```
-SELECT ST_LabelPoint(ST_GeomFromWKT('POLYGON ((-112.654072 33.114485, -112.313516 33.653431, -111.63515 33.314399, -111.497829 33.874913, -111.692825 33.431378, -112.376684 33.788215, -112.654072 33.114485))', 4326))
-```
-
-Output:
-
-```
-SRID=4326;POINT (-112.0722602222832 33.53914975012836)
-```
-
 ## ST_Angle
 
 Introduction: Computes and returns the angle between two vectors represented by the provided points or linestrings.
@@ -1817,6 +1745,78 @@ gid  |                  validity_info
 
 ```
 
+## ST_LabelPoint
+
+Introduction: `ST_LabelPoint` computes and returns a label point for a given polygon or geometry collection. The label point is chosen to be sufficiently far from boundaries of the geometry. For a regular Polygon this will be the
+centroid.
+
+The algorithm is derived from Tippecanoe’s `polygon_to_anchor`, an approximate solution for label point generation, designed to be faster than optimal algorithms like `polylabel`. It searches for a “good enough” label point within a limited number of iterations. For geometry collections, only the largest Polygon by area is considered. While `ST_Centroid` is a fast algorithm to calculate the center of mass of a (Multi)Polygon, it may place the point outside of the Polygon or near a bounda [...]
+
+`ST_LabelPoint` takes up to 3 arguments,
+
+- `geometry`: input geometry (e.g., a Polygon or GeometryCollection) for which the anchor point is to be calculated.
+- `gridResolution` (Optional, default is 16): Controls the resolution of the search grid for refining the label point. A higher resolution increases the grid density, providing a higher chance of finding a good enough result at the cost of runtime. For example, a gridResolution of 16 divides the bounding box of the polygon into a 16x16 grid.
+- `goodnessThreshold` (Optional, default is 0.2): Determines the minimum acceptable “goodness” value for the anchor point. Higher thresholds prioritize points farther from boundaries but may require more computation.
+
+!!!note
+    - `ST_LabelPoint` throws an `IllegalArgumentException` if the input geometry has an area of zero or less.
+    - Holes within polygons are respected. Points within a hole are given a goodness of 0.
+    - For GeometryCollections, only the largest polygon by area is considered.
+
+!!!tip
+    - Use `ST_LabelPoint` for tasks such as label placement, identifying representative points for polygons, or other spatial analyses where an internal reference point is preferred but not required. If intersection of the point and the original geometry is required, use of an algorithm like `polylabel` should be considered.
+    - `ST_LabelPoint` offers a faster, approximate solution for label point generation, making it ideal for large datasets or real-time applications.
+
+Format:
+
+```sql
+ST_LabelPoint(geometry: Geometry)
+```
+
+```sql
+ST_LabelPoint(geometry: Geometry, gridResolution: Integer)
+```
+
+```sql
+ST_LabelPoint(geometry: Geometry, gridResolution: Integer, goodnessThreshold: Double)
+```
+
+SQL Example:
+
+```
+SELECT ST_LabelPoint(ST_GeomFromWKT('POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))'))
+```
+
+Output:
+
+```
+POINT (2 2)
+```
+
+SQL Example:
+
+```
+SELECT ST_LabelPoint(ST_GeomFromWKT('GEOMETRYCOLLECTION(POLYGON ((-112.840785 33.435962, -112.840785 33.708284, -112.409597 33.708284, -112.409597 33.435962, -112.840785 33.435962)), POLYGON ((-112.309264 33.398167, -112.309264 33.746007, -111.787444 33.746007, -111.787444 33.398167, -112.309264 33.398167)))'))
+```
+
+Output:
+
+```
+POINT (-112.04835399999999 33.57208699999999)
+```
+
+SQL Example:
+
+```
+SELECT ST_LabelPoint(ST_GeomFromWKT('POLYGON ((-112.654072 33.114485, -112.313516 33.653431, -111.63515 33.314399, -111.497829 33.874913, -111.692825 33.431378, -112.376684 33.788215, -112.654072 33.114485))', 4326))
+```
+
+Output:
+
+```
+SRID=4326;POINT (-112.0722602222832 33.53914975012836)
+```
+
 ## ST_Length
 
 Introduction: Returns the perimeter of A.
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 0cf40f4f2a..2c7bd21fc6 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -179,80 +179,6 @@ Input: `POLYGON ((1 0 1, 1 1 1, 2 2 2, 1 0 1))`
 
 Output: `POLYGON Z((2 3 1, 4 5 1, 7 8 2, 2 3 1))`
 
-## ST_LabelPoint
-
-Introduction: `ST_LabelPoint` computes and returns a label point for a given polygon or geometry collection. The label point is chosen to be sufficiently far from boundaries of the geometry. For a regular Polygon this will be the
-centroid.
-
-The algorithm is derived from Tippecanoe’s `polygon_to_anchor`, an approximate solution for label point generation, designed to be faster than optimal algorithms like `polylabel`. It searches for a “good enough” label point within a limited number of iterations. For geometry collections, only the largest Polygon by area is considered. While `ST_Centroid` is a fast algorithm to calculate the center of mass of a (Multi)Polygon, it may place the point outside of the Polygon or near a bounda [...]
-
-`ST_LabelPoint` takes up to 3 arguments,
-
-- `geometry`: input geometry (e.g., a Polygon or GeometryCollection) for which the anchor point is to be calculated.
-- `gridResolution` (Optional, default is 16): Controls the resolution of the search grid for refining the label point. A higher resolution increases the grid density, providing a higher chance of finding a good enough result at the cost of runtime. For example, a gridResolution of 16 divides the bounding box of the polygon into a 16x16 grid.
-- `goodnessThreshold` (Optional, default is 0.2): Determines the minimum acceptable “goodness” value for the anchor point. Higher thresholds prioritize points farther from boundaries but may require more computation.
-
-!!!note
-    - `ST_LabelPoint` throws an `IllegalArgumentException` if the input geometry has an area of zero or less.
-    - Holes within polygons are respected. Points within a hole are given a goodness of 0.
-    - For GeometryCollections, only the largest polygon by area is considered.
-
-!!!tip
-    - Use `ST_LabelPoint` for tasks such as label placement, identifying representative points for polygons, or other spatial analyses where an internal reference point is preferred but not required. If intersection of the point and the original geometry is required, use of an algorithm like `polylabel` should be considered.
-    - `ST_LabelPoint` offers a faster, approximate solution for label point generation, making it ideal for large datasets or real-time applications.
-
-Format:
-
-```sql
-ST_LabelPoint(geometry: Geometry)
-```
-
-```sql
-ST_LabelPoint(geometry: Geometry, gridResolution: Integer)
-```
-
-```sql
-ST_LabelPoint(geometry: Geometry, gridResolution: Integer, goodnessThreshold: Double)
-```
-
-Since: `v1.7.1`
-
-SQL Example:
-
-```
-SELECT ST_LabelPoint(ST_GeomFromWKT('POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))'))
-```
-
-Output:
-
-```
-POINT (2 2)
-```
-
-SQL Example:
-
-```
-SELECT ST_LabelPoint(ST_GeomFromWKT('GEOMETRYCOLLECTION(POLYGON ((-112.840785 33.435962, -112.840785 33.708284, -112.409597 33.708284, -112.409597 33.435962, -112.840785 33.435962)), POLYGON ((-112.309264 33.398167, -112.309264 33.746007, -111.787444 33.746007, -111.787444 33.398167, -112.309264 33.398167)))'))
-```
-
-Output:
-
-```
-POINT (-112.04835399999999 33.57208699999999)
-```
-
-SQL Example:
-
-```
-SELECT ST_LabelPoint(ST_GeomFromWKT('POLYGON ((-112.654072 33.114485, -112.313516 33.653431, -111.63515 33.314399, -111.497829 33.874913, -111.692825 33.431378, -112.376684 33.788215, -112.654072 33.114485))', 4326))
-```
-
-Output:
-
-```
-SRID=4326;POINT (-112.0722602222832 33.53914975012836)
-```
-
 ## ST_Angle
 
 Introduction: Computes and returns the angle between two vectors represented by the provided points or linestrings.
@@ -2574,6 +2500,80 @@ Output:
 false
 ```
 
+## ST_LabelPoint
+
+Introduction: `ST_LabelPoint` computes and returns a label point for a given polygon or geometry collection. The label point is chosen to be sufficiently far from boundaries of the geometry. For a regular Polygon this will be the
+centroid.
+
+The algorithm is derived from Tippecanoe’s `polygon_to_anchor`, an approximate solution for label point generation, designed to be faster than optimal algorithms like `polylabel`. It searches for a “good enough” label point within a limited number of iterations. For geometry collections, only the largest Polygon by area is considered. While `ST_Centroid` is a fast algorithm to calculate the center of mass of a (Multi)Polygon, it may place the point outside of the Polygon or near a bounda [...]
+
+`ST_LabelPoint` takes up to 3 arguments,
+
+- `geometry`: input geometry (e.g., a Polygon or GeometryCollection) for which the anchor point is to be calculated.
+- `gridResolution` (Optional, default is 16): Controls the resolution of the search grid for refining the label point. A higher resolution increases the grid density, providing a higher chance of finding a good enough result at the cost of runtime. For example, a gridResolution of 16 divides the bounding box of the polygon into a 16x16 grid.
+- `goodnessThreshold` (Optional, default is 0.2): Determines the minimum acceptable “goodness” value for the anchor point. Higher thresholds prioritize points farther from boundaries but may require more computation.
+
+!!!note
+    - `ST_LabelPoint` throws an `IllegalArgumentException` if the input geometry has an area of zero or less.
+    - Holes within polygons are respected. Points within a hole are given a goodness of 0.
+    - For GeometryCollections, only the largest polygon by area is considered.
+
+!!!tip
+    - Use `ST_LabelPoint` for tasks such as label placement, identifying representative points for polygons, or other spatial analyses where an internal reference point is preferred but not required. If intersection of the point and the original geometry is required, use of an algorithm like `polylabel` should be considered.
+    - `ST_LabelPoint` offers a faster, approximate solution for label point generation, making it ideal for large datasets or real-time applications.
+
+Format:
+
+```sql
+ST_LabelPoint(geometry: Geometry)
+```
+
+```sql
+ST_LabelPoint(geometry: Geometry, gridResolution: Integer)
+```
+
+```sql
+ST_LabelPoint(geometry: Geometry, gridResolution: Integer, goodnessThreshold: Double)
+```
+
+Since: `v1.7.1`
+
+SQL Example:
+
+```
+SELECT ST_LabelPoint(ST_GeomFromWKT('POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))'))
+```
+
+Output:
+
+```
+POINT (2 2)
+```
+
+SQL Example:
+
+```
+SELECT ST_LabelPoint(ST_GeomFromWKT('GEOMETRYCOLLECTION(POLYGON ((-112.840785 33.435962, -112.840785 33.708284, -112.409597 33.708284, -112.409597 33.435962, -112.840785 33.435962)), POLYGON ((-112.309264 33.398167, -112.309264 33.746007, -111.787444 33.746007, -111.787444 33.398167, -112.309264 33.398167)))'))
+```
+
+Output:
+
+```
+POINT (-112.04835399999999 33.57208699999999)
+```
+
+SQL Example:
+
+```
+SELECT ST_LabelPoint(ST_GeomFromWKT('POLYGON ((-112.654072 33.114485, -112.313516 33.653431, -111.63515 33.314399, -111.497829 33.874913, -111.692825 33.431378, -112.376684 33.788215, -112.654072 33.114485))', 4326))
+```
+
+Output:
+
+```
+SRID=4326;POINT (-112.0722602222832 33.53914975012836)
+```
+
 ## ST_Length
 
 Introduction: Returns the perimeter of A.


(sedona) 25/35: [SEDONA-690] Set default metric to use Haversine for KNN join and code refactoring (#1909)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit d930769adfaa69ec49f59deb92719325b67f6478
Author: Feng Zhang <fe...@wherobots.com>
AuthorDate: Thu Apr 10 09:52:58 2025 -0700

    [SEDONA-690] Set default metric to use Haversine for KNN join and code refactoring (#1909)
    
    * [SEDONA-690] Set default metric to use Haversine for KNN join and some code refactor
    
    * fix unit tests
    
    * clean up join params
---
 .../joinJudgement/InMemoryKNNJoinIterator.java     | 155 ++++++++++++++++
 .../core/joinJudgement/KnnJoinIndexJudgement.java  | 200 +++++++--------------
 .../sedona/core/spatialOperator/JoinQuery.java     | 169 ++++++++---------
 .../join/BroadcastObjectSideKNNJoinExec.scala      |   4 +-
 .../join/BroadcastQuerySideKNNJoinExec.scala       |   4 +-
 .../sql/sedona_sql/strategy/join/KNNJoinExec.scala |   4 +-
 .../scala/org/apache/sedona/sql/KnnJoinSuite.scala |  28 +++
 7 files changed, 329 insertions(+), 235 deletions(-)

diff --git a/spark/common/src/main/java/org/apache/sedona/core/joinJudgement/InMemoryKNNJoinIterator.java b/spark/common/src/main/java/org/apache/sedona/core/joinJudgement/InMemoryKNNJoinIterator.java
new file mode 100644
index 0000000000..54ba42485e
--- /dev/null
+++ b/spark/common/src/main/java/org/apache/sedona/core/joinJudgement/InMemoryKNNJoinIterator.java
@@ -0,0 +1,155 @@
+/*
+ * 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.sedona.core.joinJudgement;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.sedona.core.enums.DistanceMetric;
+import org.apache.sedona.core.wrapper.UniqueGeometry;
+import org.apache.spark.util.LongAccumulator;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.index.strtree.ItemDistance;
+import org.locationtech.jts.index.strtree.STRtree;
+
+public class InMemoryKNNJoinIterator<T extends Geometry, U extends Geometry>
+    implements Iterator<Pair<T, U>> {
+  private final Iterator<T> querySideIterator;
+  private final STRtree strTree;
+
+  private final int k;
+  private final DistanceMetric distanceMetric;
+  private final boolean includeTies;
+  private final ItemDistance itemDistance;
+
+  private final LongAccumulator streamCount;
+  private final LongAccumulator resultCount;
+
+  private final List<Pair<T, U>> currentResults = new ArrayList<>();
+  private int currentResultIndex = 0;
+
+  public InMemoryKNNJoinIterator(
+      Iterator<T> querySideIterator,
+      STRtree strTree,
+      int k,
+      DistanceMetric distanceMetric,
+      boolean includeTies,
+      LongAccumulator streamCount,
+      LongAccumulator resultCount) {
+    this.querySideIterator = querySideIterator;
+    this.strTree = strTree;
+
+    this.k = k;
+    this.distanceMetric = distanceMetric;
+    this.includeTies = includeTies;
+    this.itemDistance = KnnJoinIndexJudgement.getItemDistance(distanceMetric);
+
+    this.streamCount = streamCount;
+    this.resultCount = resultCount;
+  }
+
+  @Override
+  public boolean hasNext() {
+    if (currentResultIndex < currentResults.size()) {
+      return true;
+    }
+
+    currentResultIndex = 0;
+    currentResults.clear();
+    while (querySideIterator.hasNext()) {
+      populateNextBatch();
+      if (!currentResults.isEmpty()) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  @Override
+  public Pair<T, U> next() {
+    if (!hasNext()) {
+      throw new NoSuchElementException();
+    }
+
+    return currentResults.get(currentResultIndex++);
+  }
+
+  private void populateNextBatch() {
+    T queryItem = querySideIterator.next();
+    Geometry queryGeom;
+    if (queryItem instanceof UniqueGeometry) {
+      queryGeom = (Geometry) ((UniqueGeometry<?>) queryItem).getOriginalGeometry();
+    } else {
+      queryGeom = queryItem;
+    }
+    streamCount.add(1);
+
+    Object[] localK =
+        strTree.nearestNeighbour(queryGeom.getEnvelopeInternal(), queryGeom, itemDistance, k);
+    if (includeTies) {
+      localK = getUpdatedLocalKWithTies(queryGeom, localK, strTree);
+    }
+
+    for (Object obj : localK) {
+      U candidate = (U) obj;
+      Pair<T, U> pair = Pair.of(queryItem, candidate);
+      currentResults.add(pair);
+      resultCount.add(1);
+    }
+  }
+
+  private Object[] getUpdatedLocalKWithTies(
+      Geometry streamShape, Object[] localK, STRtree strTree) {
+    Envelope searchEnvelope = streamShape.getEnvelopeInternal();
+    // get the maximum distance from the k nearest neighbors
+    double maxDistance = 0.0;
+    LinkedHashSet<U> uniqueCandidates = new LinkedHashSet<>();
+    for (Object obj : localK) {
+      U candidate = (U) obj;
+      uniqueCandidates.add(candidate);
+      double distance = streamShape.distance(candidate);
+      if (distance > maxDistance) {
+        maxDistance = distance;
+      }
+    }
+    searchEnvelope.expandBy(maxDistance);
+    List<U> candidates = strTree.query(searchEnvelope);
+    if (!candidates.isEmpty()) {
+      // update localK with all candidates that are within the maxDistance
+      List<Object> tiedResults = new ArrayList<>();
+      // add all localK
+      Collections.addAll(tiedResults, localK);
+
+      for (U candidate : candidates) {
+        double distance = streamShape.distance(candidate);
+        if (distance == maxDistance && !uniqueCandidates.contains(candidate)) {
+          tiedResults.add(candidate);
+        }
+      }
+      localK = tiedResults.toArray();
+    }
+    return localK;
+  }
+}
diff --git a/spark/common/src/main/java/org/apache/sedona/core/joinJudgement/KnnJoinIndexJudgement.java b/spark/common/src/main/java/org/apache/sedona/core/joinJudgement/KnnJoinIndexJudgement.java
index f5375009ed..0dda586986 100644
--- a/spark/common/src/main/java/org/apache/sedona/core/joinJudgement/KnnJoinIndexJudgement.java
+++ b/spark/common/src/main/java/org/apache/sedona/core/joinJudgement/KnnJoinIndexJudgement.java
@@ -19,19 +19,18 @@
 package org.apache.sedona.core.joinJudgement;
 
 import java.io.Serializable;
-import java.util.*;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.sedona.core.enums.DistanceMetric;
 import org.apache.sedona.core.knnJudgement.EuclideanItemDistance;
 import org.apache.sedona.core.knnJudgement.HaversineItemDistance;
 import org.apache.sedona.core.knnJudgement.SpheroidDistance;
-import org.apache.sedona.core.wrapper.UniqueGeometry;
 import org.apache.spark.api.java.function.FlatMapFunction2;
 import org.apache.spark.broadcast.Broadcast;
 import org.apache.spark.util.LongAccumulator;
-import org.locationtech.jts.geom.Envelope;
 import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.index.SpatialIndex;
 import org.locationtech.jts.index.strtree.GeometryItemDistance;
 import org.locationtech.jts.index.strtree.ItemDistance;
 import org.locationtech.jts.index.strtree.STRtree;
@@ -45,19 +44,17 @@ import org.locationtech.jts.index.strtree.STRtree;
  */
 public class KnnJoinIndexJudgement<T extends Geometry, U extends Geometry>
     extends JudgementBase<T, U>
-    implements FlatMapFunction2<Iterator<T>, Iterator<SpatialIndex>, Pair<U, T>>, Serializable {
+    implements FlatMapFunction2<Iterator<T>, Iterator<U>, Pair<T, U>>, Serializable {
   private final int k;
-  private final Double searchRadius;
   private final DistanceMetric distanceMetric;
   private final boolean includeTies;
-  private final Broadcast<List> broadcastQueryObjects;
+  private final Broadcast<List<T>> broadcastQueryObjects;
   private final Broadcast<STRtree> broadcastObjectsTreeIndex;
 
   /**
    * Constructor for the KnnJoinIndexJudgement class.
    *
    * @param k the number of nearest neighbors to find
-   * @param searchRadius
    * @param distanceMetric the distance metric to use
    * @param broadcastQueryObjects the broadcast geometries on queries
    * @param broadcastObjectsTreeIndex the broadcast spatial index on objects
@@ -68,10 +65,9 @@ public class KnnJoinIndexJudgement<T extends Geometry, U extends Geometry>
    */
   public KnnJoinIndexJudgement(
       int k,
-      Double searchRadius,
       DistanceMetric distanceMetric,
       boolean includeTies,
-      Broadcast<List> broadcastQueryObjects,
+      Broadcast<List<T>> broadcastQueryObjects,
       Broadcast<STRtree> broadcastObjectsTreeIndex,
       LongAccumulator buildCount,
       LongAccumulator streamCount,
@@ -79,7 +75,6 @@ public class KnnJoinIndexJudgement<T extends Geometry, U extends Geometry>
       LongAccumulator candidateCount) {
     super(null, buildCount, streamCount, resultCount, candidateCount);
     this.k = k;
-    this.searchRadius = searchRadius;
     this.distanceMetric = distanceMetric;
     this.includeTies = includeTies;
     this.broadcastQueryObjects = broadcastQueryObjects;
@@ -91,15 +86,15 @@ public class KnnJoinIndexJudgement<T extends Geometry, U extends Geometry>
    * and uses the spatial index to find the k nearest neighbors for each geometry. The method
    * returns an iterator over the join results.
    *
-   * @param streamShapes iterator over the geometries in the stream side
-   * @param treeIndexes iterator over the spatial indexes
+   * @param queryShapes iterator over the geometries in the query side
+   * @param objectShapes iterator over the geometries in the object side
    * @return an iterator over the join results
    * @throws Exception if the spatial index is not of type STRtree
    */
   @Override
-  public Iterator<Pair<U, T>> call(Iterator<T> streamShapes, Iterator<SpatialIndex> treeIndexes)
+  public Iterator<Pair<T, U>> call(Iterator<T> queryShapes, Iterator<U> objectShapes)
       throws Exception {
-    if (!treeIndexes.hasNext() || (streamShapes != null && !streamShapes.hasNext())) {
+    if (!objectShapes.hasNext() || (queryShapes != null && !queryShapes.hasNext())) {
       buildCount.add(0);
       streamCount.add(0);
       resultCount.add(0);
@@ -107,91 +102,64 @@ public class KnnJoinIndexJudgement<T extends Geometry, U extends Geometry>
       return Collections.emptyIterator();
     }
 
-    STRtree strTree;
-    if (broadcastObjectsTreeIndex != null) {
-      // get the broadcast spatial index on objects side if available
-      strTree = broadcastObjectsTreeIndex.getValue();
-    } else {
-      // get the spatial index from the iterator
-      SpatialIndex treeIndex = treeIndexes.next();
-      if (!(treeIndex instanceof STRtree)) {
-        throw new Exception(
-            "[KnnJoinIndexJudgement][Call] Only STRtree index supports KNN search.");
-      }
-      strTree = (STRtree) treeIndex;
-    }
-
-    // TODO: For future improvement, instead of using a list to store the results,
-    // we can use lazy evaluation to avoid storing all the results in memory.
-    List<Pair<U, T>> result = new ArrayList<>();
-
-    List queryItems;
-    if (broadcastQueryObjects != null) {
-      // get the broadcast spatial index on queries side if available
-      queryItems = broadcastQueryObjects.getValue();
-      for (Object item : queryItems) {
-        T queryGeom;
-        if (item instanceof UniqueGeometry) {
-          queryGeom = (T) ((UniqueGeometry) item).getOriginalGeometry();
-        } else {
-          queryGeom = (T) item;
-        }
-        streamCount.add(1);
-
-        Object[] localK =
-            strTree.nearestNeighbour(
-                queryGeom.getEnvelopeInternal(), queryGeom, getItemDistance(), k);
-        if (includeTies) {
-          localK = getUpdatedLocalKWithTies(queryGeom, localK, strTree);
-        }
-        if (searchRadius != null) {
-          localK = getInSearchRadius(localK, queryGeom);
-        }
+    STRtree strTree = buildSTRtree(objectShapes);
+    return new InMemoryKNNJoinIterator<>(
+        queryShapes, strTree, k, distanceMetric, includeTies, streamCount, resultCount);
+  }
 
-        for (Object obj : localK) {
-          T candidate = (T) obj;
-          Pair<U, T> pair = Pair.of((U) item, candidate);
-          result.add(pair);
-          resultCount.add(1);
-        }
-      }
-      return result.iterator();
-    } else {
-      while (streamShapes.hasNext()) {
-        T streamShape = streamShapes.next();
-        streamCount.add(1);
+  /**
+   * This method performs the KNN join operation using the broadcast spatial index built using all
+   * geometries in the object side.
+   *
+   * @param queryShapes iterator over the geometries in the query side
+   * @return an iterator over the join results
+   */
+  public Iterator<Pair<T, U>> callUsingBroadcastObjectIndex(Iterator<T> queryShapes) {
+    if (!queryShapes.hasNext()) {
+      buildCount.add(0);
+      streamCount.add(0);
+      resultCount.add(0);
+      candidateCount.add(0);
+      return Collections.emptyIterator();
+    }
 
-        Object[] localK =
-            strTree.nearestNeighbour(
-                streamShape.getEnvelopeInternal(), streamShape, getItemDistance(), k);
-        if (includeTies) {
-          localK = getUpdatedLocalKWithTies(streamShape, localK, strTree);
-        }
-        if (searchRadius != null) {
-          localK = getInSearchRadius(localK, streamShape);
-        }
+    // There's no need to use external spatial index, since the object side is small enough to be
+    // broadcasted, the STRtree built from the broadcasted object should be able to fit into memory.
+    STRtree strTree = broadcastObjectsTreeIndex.getValue();
+    return new InMemoryKNNJoinIterator<>(
+        queryShapes, strTree, k, distanceMetric, includeTies, streamCount, resultCount);
+  }
 
-        for (Object obj : localK) {
-          T candidate = (T) obj;
-          Pair<U, T> pair = Pair.of((U) streamShape, candidate);
-          result.add(pair);
-          resultCount.add(1);
-        }
-      }
-      return result.iterator();
+  /**
+   * This method performs the KNN join operation using the broadcast query geometries.
+   *
+   * @param objectShapes iterator over the geometries in the object side
+   * @return an iterator over the join results
+   */
+  public Iterator<Pair<T, U>> callUsingBroadcastQueryList(Iterator<U> objectShapes) {
+    if (!objectShapes.hasNext()) {
+      buildCount.add(0);
+      streamCount.add(0);
+      resultCount.add(0);
+      candidateCount.add(0);
+      return Collections.emptyIterator();
     }
+
+    List<T> queryItems = broadcastQueryObjects.getValue();
+    STRtree strTree = buildSTRtree(objectShapes);
+    return new InMemoryKNNJoinIterator<>(
+        queryItems.iterator(), strTree, k, distanceMetric, includeTies, streamCount, resultCount);
   }
 
-  private Object[] getInSearchRadius(Object[] localK, T queryGeom) {
-    localK =
-        Arrays.stream(localK)
-            .filter(
-                candidate -> {
-                  Geometry candidateGeom = (Geometry) candidate;
-                  return distanceByMetric(queryGeom, candidateGeom, distanceMetric) <= searchRadius;
-                })
-            .toArray();
-    return localK;
+  private STRtree buildSTRtree(Iterator<U> objectShapes) {
+    STRtree strTree = new STRtree();
+    while (objectShapes.hasNext()) {
+      U spatialObject = objectShapes.next();
+      strTree.insert(spatialObject.getEnvelopeInternal(), spatialObject);
+      buildCount.add(1);
+    }
+    strTree.build();
+    return strTree;
   }
 
   /**
@@ -219,12 +187,6 @@ public class KnnJoinIndexJudgement<T extends Geometry, U extends Geometry>
     }
   }
 
-  private ItemDistance getItemDistance() {
-    ItemDistance itemDistance;
-    itemDistance = getItemDistanceByMetric(distanceMetric);
-    return itemDistance;
-  }
-
   /**
    * This method returns the ItemDistance object based on the specified distance metric.
    *
@@ -250,38 +212,6 @@ public class KnnJoinIndexJudgement<T extends Geometry, U extends Geometry>
     return itemDistance;
   }
 
-  private Object[] getUpdatedLocalKWithTies(T streamShape, Object[] localK, STRtree strTree) {
-    Envelope searchEnvelope = streamShape.getEnvelopeInternal();
-    // get the maximum distance from the k nearest neighbors
-    double maxDistance = 0.0;
-    LinkedHashSet<T> uniqueCandidates = new LinkedHashSet<>();
-    for (Object obj : localK) {
-      T candidate = (T) obj;
-      uniqueCandidates.add(candidate);
-      double distance = streamShape.distance(candidate);
-      if (distance > maxDistance) {
-        maxDistance = distance;
-      }
-    }
-    searchEnvelope.expandBy(maxDistance);
-    List<T> candidates = strTree.query(searchEnvelope);
-    if (!candidates.isEmpty()) {
-      // update localK with all candidates that are within the maxDistance
-      List<Object> tiedResults = new ArrayList<>();
-      // add all localK
-      Collections.addAll(tiedResults, localK);
-
-      for (T candidate : candidates) {
-        double distance = streamShape.distance(candidate);
-        if (distance == maxDistance && !uniqueCandidates.contains(candidate)) {
-          tiedResults.add(candidate);
-        }
-      }
-      localK = tiedResults.toArray();
-    }
-    return localK;
-  }
-
   public static <U extends Geometry, T extends Geometry> double distance(
       U key, T value, DistanceMetric distanceMetric) {
     switch (distanceMetric) {
@@ -295,4 +225,10 @@ public class KnnJoinIndexJudgement<T extends Geometry, U extends Geometry>
         return new EuclideanItemDistance().distance(key, value);
     }
   }
+
+  public static ItemDistance getItemDistance(DistanceMetric distanceMetric) {
+    ItemDistance itemDistance;
+    itemDistance = getItemDistanceByMetric(distanceMetric);
+    return itemDistance;
+  }
 }
diff --git a/spark/common/src/main/java/org/apache/sedona/core/spatialOperator/JoinQuery.java b/spark/common/src/main/java/org/apache/sedona/core/spatialOperator/JoinQuery.java
index a5665726e0..7b55dd0763 100644
--- a/spark/common/src/main/java/org/apache/sedona/core/spatialOperator/JoinQuery.java
+++ b/spark/common/src/main/java/org/apache/sedona/core/spatialOperator/JoinQuery.java
@@ -37,13 +37,11 @@ import org.apache.spark.SparkContext;
 import org.apache.spark.api.java.JavaPairRDD;
 import org.apache.spark.api.java.JavaRDD;
 import org.apache.spark.api.java.JavaSparkContext;
-import org.apache.spark.api.java.function.FlatMapFunction;
 import org.apache.spark.api.java.function.Function2;
 import org.apache.spark.api.java.function.PairFunction;
 import org.apache.spark.broadcast.Broadcast;
 import org.apache.spark.util.LongAccumulator;
 import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.index.SpatialIndex;
 import org.locationtech.jts.index.strtree.STRtree;
 import scala.Tuple2;
 
@@ -401,7 +399,7 @@ public class JoinQuery {
       DistanceMetric distanceMetric)
       throws Exception {
     final JoinParams joinParams =
-        new JoinParams(true, null, IndexType.RTREE, null, k, distanceMetric, null);
+        new JoinParams(true, null, IndexType.RTREE, null, k, distanceMetric);
 
     final JavaPairRDD<U, T> joinResults = knnJoin(queryRDD, objectRDD, joinParams, false, false);
     return collectGeometriesByKey(joinResults);
@@ -785,7 +783,7 @@ public class JoinQuery {
     LongAccumulator candidateCount = Metrics.createMetric(sparkContext, "candidateCount");
 
     final Broadcast<STRtree> broadcastObjectsTreeIndex;
-    final Broadcast<List> broadcastQueryObjects;
+    final Broadcast<List<UniqueGeometry<U>>> broadcastQueryObjects;
     if (broadcastJoin && objectRDD.indexedRawRDD != null && objectRDD.indexedRDD == null) {
       // If broadcastJoin is true and rawIndex is created on object side
       // we will broadcast queryRDD to objectRDD
@@ -816,10 +814,9 @@ public class JoinQuery {
     final JavaRDD<Pair<U, T>> joinResult;
     if (broadcastObjectsTreeIndex == null && broadcastQueryObjects == null) {
       // no broadcast join
-      final KnnJoinIndexJudgement judgement =
-          new KnnJoinIndexJudgement(
+      final KnnJoinIndexJudgement<U, T> judgement =
+          new KnnJoinIndexJudgement<>(
               joinParams.k,
-              joinParams.searchRadius,
               joinParams.distanceMetric,
               includeTies,
               null,
@@ -828,13 +825,13 @@ public class JoinQuery {
               streamCount,
               resultCount,
               candidateCount);
-      joinResult = queryRDD.spatialPartitionedRDD.zipPartitions(objectRDD.indexedRDD, judgement);
+      joinResult =
+          queryRDD.spatialPartitionedRDD.zipPartitions(objectRDD.spatialPartitionedRDD, judgement);
     } else if (broadcastObjectsTreeIndex != null) {
       // broadcast join with objectRDD as broadcast side
-      final KnnJoinIndexJudgement judgement =
-          new KnnJoinIndexJudgement(
+      final KnnJoinIndexJudgement<U, T> judgement =
+          new KnnJoinIndexJudgement<>(
               joinParams.k,
-              joinParams.searchRadius,
               joinParams.distanceMetric,
               includeTies,
               null,
@@ -844,13 +841,12 @@ public class JoinQuery {
               resultCount,
               candidateCount);
       // won't need inputs from the shapes in the objectRDD
-      joinResult = queryRDD.rawSpatialRDD.zipPartitions(queryRDD.rawSpatialRDD, judgement);
-    } else if (broadcastQueryObjects != null) {
+      joinResult = queryRDD.rawSpatialRDD.mapPartitions(judgement::callUsingBroadcastObjectIndex);
+    } else {
       // broadcast join with queryRDD as broadcast side
-      final KnnJoinIndexJudgement judgement =
-          new KnnJoinIndexJudgement(
+      final KnnJoinIndexJudgement<UniqueGeometry<U>, T> judgement =
+          new KnnJoinIndexJudgement<>(
               joinParams.k,
-              joinParams.searchRadius,
               joinParams.distanceMetric,
               includeTies,
               broadcastQueryObjects,
@@ -860,8 +856,6 @@ public class JoinQuery {
               resultCount,
               candidateCount);
       joinResult = querySideBroadcastKNNJoin(objectRDD, joinParams, judgement, includeTies);
-    } else {
-      throw new IllegalArgumentException("No index found on the input RDDs.");
     }
 
     return joinResult.mapToPair(
@@ -891,22 +885,11 @@ public class JoinQuery {
       JavaRDD<Pair<U, T>> querySideBroadcastKNNJoin(
           SpatialRDD<T> objectRDD,
           JoinParams joinParams,
-          KnnJoinIndexJudgement judgement,
+          KnnJoinIndexJudgement<UniqueGeometry<U>, T> judgement,
           boolean includeTies) {
     final JavaRDD<Pair<U, T>> joinResult;
-    JavaRDD<Pair<U, T>> joinResultMapped =
-        objectRDD.indexedRawRDD.mapPartitions(
-            iterator -> {
-              List<Pair<U, T>> results = new ArrayList<>();
-              if (iterator.hasNext()) {
-                SpatialIndex spatialIndex = iterator.next();
-                // the broadcast join won't need inputs from the query's shape stream
-                Iterator<Pair<U, T>> callResult =
-                    judgement.call(null, Collections.singletonList(spatialIndex).iterator());
-                callResult.forEachRemaining(results::add);
-              }
-              return results.iterator();
-            });
+    JavaRDD<Pair<UniqueGeometry<U>, T>> joinResultMapped =
+        objectRDD.rawSpatialRDD.mapPartitions(judgement::callUsingBroadcastQueryList);
     // this is to avoid serializable issues with the broadcast variable
     int k = joinParams.k;
     DistanceMetric distanceMetric = joinParams.distanceMetric;
@@ -915,72 +898,67 @@ public class JoinQuery {
     // (based on a grouping key and distance)
     joinResult =
         joinResultMapped
-            .groupBy(pair -> pair.getKey()) // Group by the first geometry
+            .groupBy(pair -> pair.getKey().getUniqueId())
             .flatMap(
-                (FlatMapFunction<Tuple2<U, Iterable<Pair<U, T>>>, Pair<U, T>>)
-                    pair -> {
-                      Iterable<Pair<U, T>> values = pair._2;
-
-                      // Extract and sort values by distance
-                      List<Pair<U, T>> sortedPairs = new ArrayList<>();
-                      for (Pair<U, T> p : values) {
-                        Pair<U, T> newPair =
-                            Pair.of(
-                                (U) ((UniqueGeometry<?>) p.getKey()).getOriginalGeometry(),
-                                p.getValue());
-                        sortedPairs.add(newPair);
-                      }
-
-                      // Sort pairs based on the distance function between the two geometries
-                      sortedPairs.sort(
-                          (p1, p2) -> {
-                            double distance1 =
-                                KnnJoinIndexJudgement.distance(
-                                    p1.getKey(), p1.getValue(), distanceMetric);
-                            double distance2 =
-                                KnnJoinIndexJudgement.distance(
-                                    p2.getKey(), p2.getValue(), distanceMetric);
-                            return Double.compare(
-                                distance1, distance2); // Sort ascending by distance
-                          });
-
-                      if (includeTies) {
-                        // Keep the top k pairs, including ties
-                        List<Pair<U, T>> topPairs = new ArrayList<>();
-                        double kthDistance = -1;
-                        for (int i = 0; i < sortedPairs.size(); i++) {
-                          if (i < k) {
-                            topPairs.add(sortedPairs.get(i));
-                            if (i == k - 1) {
-                              kthDistance =
-                                  KnnJoinIndexJudgement.distance(
-                                      sortedPairs.get(i).getKey(),
-                                      sortedPairs.get(i).getValue(),
-                                      distanceMetric);
-                            }
-                          } else {
-                            double currentDistance =
-                                KnnJoinIndexJudgement.distance(
-                                    sortedPairs.get(i).getKey(),
-                                    sortedPairs.get(i).getValue(),
-                                    distanceMetric);
-                            if (currentDistance == kthDistance) {
-                              topPairs.add(sortedPairs.get(i));
-                            } else {
-                              break;
-                            }
-                          }
+                pair -> {
+                  Iterable<Pair<UniqueGeometry<U>, T>> values = pair._2;
+
+                  // Extract and sort values by distance
+                  List<Pair<U, T>> sortedPairs = new ArrayList<>();
+                  for (Pair<UniqueGeometry<U>, T> p : values) {
+                    Pair<U, T> newPair = Pair.of(p.getKey().getOriginalGeometry(), p.getValue());
+                    sortedPairs.add(newPair);
+                  }
+
+                  // Sort pairs based on the distance function between the two geometries
+                  sortedPairs.sort(
+                      (p1, p2) -> {
+                        double distance1 =
+                            KnnJoinIndexJudgement.distance(
+                                p1.getKey(), p1.getValue(), distanceMetric);
+                        double distance2 =
+                            KnnJoinIndexJudgement.distance(
+                                p2.getKey(), p2.getValue(), distanceMetric);
+                        return Double.compare(distance1, distance2); // Sort ascending by distance
+                      });
+
+                  if (includeTies) {
+                    // Keep the top k pairs, including ties
+                    List<Pair<U, T>> topPairs = new ArrayList<>();
+                    double kthDistance = -1;
+                    for (int i = 0; i < sortedPairs.size(); i++) {
+                      if (i < k) {
+                        topPairs.add(sortedPairs.get(i));
+                        if (i == k - 1) {
+                          kthDistance =
+                              KnnJoinIndexJudgement.distance(
+                                  sortedPairs.get(i).getKey(),
+                                  sortedPairs.get(i).getValue(),
+                                  distanceMetric);
                         }
-                        return topPairs.iterator();
                       } else {
-                        // Keep the top k pairs without ties
-                        List<Pair<U, T>> topPairs = new ArrayList<>();
-                        for (int i = 0; i < Math.min(k, sortedPairs.size()); i++) {
+                        double currentDistance =
+                            KnnJoinIndexJudgement.distance(
+                                sortedPairs.get(i).getKey(),
+                                sortedPairs.get(i).getValue(),
+                                distanceMetric);
+                        if (currentDistance == kthDistance) {
                           topPairs.add(sortedPairs.get(i));
+                        } else {
+                          break;
                         }
-                        return topPairs.iterator();
                       }
-                    });
+                    }
+                    return topPairs.iterator();
+                  } else {
+                    // Keep the top k pairs without ties
+                    List<Pair<U, T>> topPairs = new ArrayList<>();
+                    for (int i = 0; i < Math.min(k, sortedPairs.size()); i++) {
+                      topPairs.add(sortedPairs.get(i));
+                    }
+                    return topPairs.iterator();
+                  }
+                });
 
     return joinResult;
   }
@@ -994,14 +972,13 @@ public class JoinQuery {
     // KNN specific parameters
     public final int k;
     public final DistanceMetric distanceMetric;
-    public final Double searchRadius;
 
     public JoinParams(
         boolean useIndex,
         SpatialPredicate spatialPredicate,
         IndexType polygonIndexType,
         JoinBuildSide joinBuildSide) {
-      this(useIndex, spatialPredicate, polygonIndexType, joinBuildSide, -1, null, null);
+      this(useIndex, spatialPredicate, polygonIndexType, joinBuildSide, -1, null);
     }
 
     public JoinParams(
@@ -1010,15 +987,13 @@ public class JoinQuery {
         IndexType polygonIndexType,
         JoinBuildSide joinBuildSide,
         int k,
-        DistanceMetric distanceMetric,
-        Double searchRadius) {
+        DistanceMetric distanceMetric) {
       this.useIndex = useIndex;
       this.spatialPredicate = spatialPredicate;
       this.indexType = polygonIndexType;
       this.joinBuildSide = joinBuildSide;
       this.k = k;
       this.distanceMetric = distanceMetric;
-      this.searchRadius = searchRadius;
     }
 
     public JoinParams(boolean useIndex, SpatialPredicate spatialPredicate) {
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/BroadcastObjectSideKNNJoinExec.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/BroadcastObjectSideKNNJoinExec.scala
index c5777be3c1..f4bdae40d5 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/BroadcastObjectSideKNNJoinExec.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/BroadcastObjectSideKNNJoinExec.scala
@@ -138,9 +138,9 @@ case class BroadcastObjectSideKNNJoinExec(
     // Number of neighbors to find
     val kValue: Int = this.k.eval().asInstanceOf[Int]
     // Metric to use in the join to calculate the distance, only Euclidean and Spheroid are supported
-    val distanceMetric = if (isGeography) DistanceMetric.SPHEROID else DistanceMetric.EUCLIDEAN
+    val distanceMetric = if (isGeography) DistanceMetric.HAVERSINE else DistanceMetric.EUCLIDEAN
     val joinParams =
-      new JoinParams(true, null, IndexType.RTREE, null, kValue, distanceMetric, null)
+      new JoinParams(true, null, IndexType.RTREE, null, kValue, distanceMetric)
     joinParams
   }
 
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/BroadcastQuerySideKNNJoinExec.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/BroadcastQuerySideKNNJoinExec.scala
index 9ce40c6d42..575ded9125 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/BroadcastQuerySideKNNJoinExec.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/BroadcastQuerySideKNNJoinExec.scala
@@ -149,9 +149,9 @@ case class BroadcastQuerySideKNNJoinExec(
     // Number of neighbors to find
     val kValue: Int = this.k.eval().asInstanceOf[Int]
     // Metric to use in the join to calculate the distance, only Euclidean and Spheroid are supported
-    val distanceMetric = if (isGeography) DistanceMetric.SPHEROID else DistanceMetric.EUCLIDEAN
+    val distanceMetric = if (isGeography) DistanceMetric.HAVERSINE else DistanceMetric.EUCLIDEAN
     val joinParams =
-      new JoinParams(true, null, IndexType.RTREE, null, kValue, distanceMetric, null)
+      new JoinParams(true, null, IndexType.RTREE, null, kValue, distanceMetric)
     joinParams
   }
 
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/KNNJoinExec.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/KNNJoinExec.scala
index fdc53d13ce..a879447d40 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/KNNJoinExec.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/KNNJoinExec.scala
@@ -209,9 +209,9 @@ case class KNNJoinExec(
     // Number of neighbors to find
     val kValue: Int = this.k.eval().asInstanceOf[Int]
     // Metric to use in the join to calculate the distance, only Euclidean and Spheroid are supported
-    val distanceMetric = if (isGeography) DistanceMetric.SPHEROID else DistanceMetric.EUCLIDEAN
+    val distanceMetric = if (isGeography) DistanceMetric.HAVERSINE else DistanceMetric.EUCLIDEAN
     val joinParams =
-      new JoinParams(true, null, IndexType.RTREE, null, kValue, distanceMetric, null)
+      new JoinParams(true, null, IndexType.RTREE, null, kValue, distanceMetric)
     joinParams
   }
 }
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/KnnJoinSuite.scala b/spark/common/src/test/scala/org/apache/sedona/sql/KnnJoinSuite.scala
index ab2c64898a..50696337f7 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/KnnJoinSuite.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/KnnJoinSuite.scala
@@ -458,6 +458,34 @@ class KnnJoinSuite extends TestBaseScala with TableDrivenPropertyChecks {
       df2.cache()
       df1.join(df2, expr("ST_KNN(geom1, geom2, 1)")).count() should be(0)
     }
+
+    it("KNN Join using spider data source") {
+      val dfRandomSquares = sparkSession.read
+        .format("spider")
+        .option("n", "10000")
+        .option("distribution", "parcel")
+        .option("dither", "0.5")
+        .option("splitRange", "0.5")
+        .load()
+
+      dfRandomSquares.createOrReplaceTempView("df_random_squares")
+
+      val dfRandomPoints = sparkSession.read
+        .format("spider")
+        .option("n", "1000")
+        .option("distribution", "uniform")
+        .load()
+
+      dfRandomPoints.createOrReplaceTempView("df_random_points")
+
+      // Execute a KNN join query: attribute points to the nearest square
+      val knnJoined = sparkSession.sql("""SELECT sq.id, pt.id
+          |FROM df_random_squares sq
+          |JOIN df_random_points pt
+          |ON ST_KNN(sq.geometry, pt.geometry, 1, TRUE)""".stripMargin)
+
+      assert(knnJoined.count() > 0)
+    }
   }
 
   private def withOptimizationMode(mode: String)(body: => Unit): Unit = {


(sedona) 22/35: [SEDONA-728] Fix Rasterization clamping bug (#1897)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 64d2ffbaa2c6d95787e0f811d8a2e725fa1e97fe
Author: Pranav Toggi <pr...@gmail.com>
AuthorDate: Mon Mar 31 11:57:08 2025 -0700

    [SEDONA-728] Fix Rasterization clamping bug (#1897)
    
    * profiling1
    
    * profiling4
    
    * profiling...
    
    * profiling 8
    
    * [BUG-918] Fix clamping issue in Rasterization (#403)
    
    ## Did you read the Contributor Guide?
    
    - Yes, I have read the [Contributor
    Rules](https://sedona.apache.org/latest/community/rule/) and
    [Contributor Development
    Guide](https://sedona.apache.org/latest/community/develop/)
    
    ## Is this PR related to a ticket?
    
    - Yes, and the PR name follows the format `[SEDONA-XXX] my subject`.
    
    ## What changes were proposed in this PR?
    
    - Fixes clamping issue in scanLine intersection calculation
    
    
    ## How was this patch tested?
    
    - Passes new and existing tests
    
    
    ## Did this PR include necessary documentation updates?
    
    - Yes, I am adding a new API. I am using the [current SNAPSHOT version
    number](https://github.com/apache/sedona/blob/99239524f17389fc4ae9548ea88756f8ea538bb9/pom.xml#L29)
    in `vX.Y.Z` format.
    - Yes, I have updated the documentation.
    - No, this PR does not affect any public API so no need to change the
    documentation.
    
    * undo pom changes
---
 .../apache/sedona/common/raster/Rasterization.java    |  4 ++--
 .../sedona/common/raster/FunctionEditorsTest.java     |  6 +++---
 .../sedona/common/raster/RasterBandAccessorsTest.java | 19 +++++++++++++++++--
 .../org/apache/sedona/sql/rasteralgebraTest.scala     |  9 +++++----
 4 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/common/src/main/java/org/apache/sedona/common/raster/Rasterization.java b/common/src/main/java/org/apache/sedona/common/raster/Rasterization.java
index 652e822e92..19530441bb 100644
--- a/common/src/main/java/org/apache/sedona/common/raster/Rasterization.java
+++ b/common/src/main/java/org/apache/sedona/common/raster/Rasterization.java
@@ -574,7 +574,7 @@ public class Rasterization {
           // calculating slope
           for (double y = yStart; y >= yEnd; y--) {
             double xIntercept = p1X; // Vertical line, xIntercept is constant
-            if (xIntercept < 0 || xIntercept > params.writableRaster.getWidth()) {
+            if (xIntercept < 0 || xIntercept >= params.writableRaster.getWidth()) {
               continue; // Skip xIntercepts outside geomExtent
             }
             scanlineIntersections.computeIfAbsent(y, k -> new TreeSet<>()).add(xIntercept);
@@ -585,7 +585,7 @@ public class Rasterization {
 
           for (double y = yStart; y >= yEnd; y--) {
             double xIntercept = p1X + ((p1Y - y) / slope);
-            if ((xIntercept < 0) || (xIntercept > params.writableRaster.getWidth())) {
+            if ((xIntercept < 0) || (xIntercept >= params.writableRaster.getWidth())) {
               continue; // skip xIntercepts outside geomExtent
             }
             if (!scanlineIntersections.containsKey(y)) {
diff --git a/common/src/test/java/org/apache/sedona/common/raster/FunctionEditorsTest.java b/common/src/test/java/org/apache/sedona/common/raster/FunctionEditorsTest.java
index ef34a5060d..93aa474588 100644
--- a/common/src/test/java/org/apache/sedona/common/raster/FunctionEditorsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/raster/FunctionEditorsTest.java
@@ -68,8 +68,8 @@ public class FunctionEditorsTest extends RasterTestBase {
     expected =
         new double[] {
           0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0,
-          10.0, 0.0, 0.0, 0.0, 10.0, 10.0, 0.0, 0.0, 0.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0,
-          10.0, 0.0, 10.0, 10.0, 0.0, 0.0, 0.0, 10.0, 0.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+          10.0, 0.0, 0.0, 0.0, 10.0, 10.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0,
+          10.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
           0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 10.0, 10.0, 0.0,
           0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 10.0, 10.0,
           10.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
@@ -239,7 +239,7 @@ public class FunctionEditorsTest extends RasterTestBase {
     actual = MapAlgebra.bandAsArray(raster, 1);
     expected =
         new double[] {
-          255.0, 255.0, 255.0, 0.0, 0.0, 255.0, 255.0, 255.0, 0.0, 0.0, 255.0, 255.0, 0.0, 0.0, 0.0,
+          255.0, 255.0, 255.0, 0.0, 0.0, 255.0, 255.0, 255.0, 0.0, 0.0, 255.0, 0.0, 0.0, 0.0, 0.0,
           255.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
         };
     assertArrayEquals(expected, actual, 0.1d);
diff --git a/common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java b/common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java
index 21f4e446ae..e0efb21897 100644
--- a/common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java
@@ -100,14 +100,29 @@ public class RasterBandAccessorsTest extends RasterTestBase {
             0);
 
     Double actualZonalStats = RasterBandAccessors.getZonalStats(raster, extent, "mode");
-    assertNull(actualZonalStats);
+    assertEquals(10.0, actualZonalStats, FP_TOLERANCE);
 
     String actualZonalStatsAll =
         Arrays.toString(RasterBandAccessors.getZonalStatsAll(raster, extent));
-    String expectedZonalStatsAll = "[0.0, null, null, null, null, null, null, null, null]";
+    String expectedZonalStatsAll = "[1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 0.0, 10.0, 10.0]";
     assertEquals(expectedZonalStatsAll, actualZonalStatsAll);
   }
 
+  @Test
+  public void testZonalStatsForVerticalLineBug() throws Exception {
+    String wkt =
+        "POLYGON ((989675.3 221207.04013636176, 989675.3 221109.675, 989435.2175433404 221109.675, 989433.6550697018 221110.53743814293, 989419.3 221118.46099022447, 989419.3 221137.08165200218, 989449.9606422258 221192.9473339209, 989465.7613533186 221221.73707975633, 989508.9887178271 221300.49928501665, 989630.3976442496 221232.27312269452, 989639.3630089872 221227.23492283348, 989675.3 221207.04013636176))";
+
+    Geometry geom = Constructors.geomFromWKT(wkt, 2263);
+
+    GridCoverage2D raster =
+        RasterConstructors.makeEmptyRaster(1, 256, 256, 989419.3, 221365.675, 1, -1, 0, 0, 2263);
+
+    Double actual = RasterBandAccessors.getZonalStats(raster, geom, 1, "mean", true);
+    Double expected = 0.0;
+    assertEquals(expected, actual, FP_TOLERANCE);
+  }
+
   @Test
   public void testZonalStats() throws FactoryException, ParseException, IOException {
     GridCoverage2D raster =
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala b/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
index 5832e54077..ff5dc7109b 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
@@ -1633,12 +1633,13 @@ class rasteralgebraTest extends TestBaseScala with BeforeAndAfter with GivenWhen
           |SELECT RS_SetSRID(RS_AddBandFromArray(RS_MakeEmptyRaster(1, "D", 5, 5, 1, -1, 1), pixels, 1), 4326) as raster, geom FROM data
           |""".stripMargin)
 
-      val actual = df.selectExpr("RS_ZonalStats(raster, geom, 1, 'mode')").first().get(0)
-      assertNull(actual)
+      var actual = df.selectExpr("RS_ZonalStats(raster, geom, 1, 'mode')").first().get(0)
+      assertEquals(10.0, actual)
 
       val statsDf = df.selectExpr("RS_ZonalStatsAll(raster, geom) as stats")
-      val actualBoolean = statsDf.selectExpr("isNull(stats.mode)").first().getAs[Boolean](0)
-      assertTrue(actualBoolean)
+      actual = statsDf.first().getStruct(0).toSeq.slice(0, 9)
+      val expected = Seq(1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 0.0, 10.0, 10.0)
+      assertTrue(expected.equals(actual))
     }
 
     it("Passed RS_ZonalStats") {


(sedona) 34/35: [SEDONA-735] Fix RS_Clip bug caused by AOI geometries smaller than pixel size (#1952)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 3f16ee4a685e29ac16894325f2f7b6ffa7477af6
Author: Pranav Toggi <pr...@gmail.com>
AuthorDate: Wed May 21 12:02:17 2025 -0700

    [SEDONA-735] Fix RS_Clip bug caused by AOI geometries smaller than pixel size (#1952)
    
    * profiling1
    
    * profiling4
    
    * profiling...
    
    * profiling 8
    
    * remove redundant cropping
    
    * fix lenient mode
    
    * Use rasterizeGeomExtent from Rasterization class
    
    * add comment
    
    * add test for small AOI geometries
    
    * make rasterizeGeomExtent a protected method
    
    * add test geotif
    
    * undo pom changes
    
    * Address comments
    
    * spotless fix
---
 .../sedona/common/raster/RasterBandEditors.java    | 11 +++++++++-
 .../apache/sedona/common/raster/Rasterization.java |  2 +-
 .../common/raster/RasterBandEditorsTest.java       | 24 +++++++++++++++++++++-
 3 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/common/src/main/java/org/apache/sedona/common/raster/RasterBandEditors.java b/common/src/main/java/org/apache/sedona/common/raster/RasterBandEditors.java
index a05872722b..3abd56869e 100644
--- a/common/src/main/java/org/apache/sedona/common/raster/RasterBandEditors.java
+++ b/common/src/main/java/org/apache/sedona/common/raster/RasterBandEditors.java
@@ -31,7 +31,10 @@ import org.geotools.coverage.GridSampleDimension;
 import org.geotools.coverage.grid.GridCoverage2D;
 import org.geotools.coverage.processing.CannotCropException;
 import org.geotools.coverage.processing.operation.Crop;
+import org.geotools.geometry.Envelope2D;
+import org.geotools.geometry.jts.JTS;
 import org.locationtech.jts.geom.Geometry;
+import org.opengis.geometry.BoundingBox;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.FactoryException;
 import org.opengis.referencing.operation.TransformException;
@@ -298,13 +301,19 @@ public class RasterBandEditors {
     singleBandRaster = pair.getLeft();
     geometry = pair.getRight();
 
+    // Use rasterizeGeomExtent for AOI geometries smaller than a pixel
+    double[] metadata = RasterAccessors.metadata(singleBandRaster);
+    Envelope2D geomEnvelope =
+        Rasterization.rasterizeGeomExtent(geometry, singleBandRaster, metadata, allTouched);
+    Geometry geomExtent = JTS.toGeometry((BoundingBox) geomEnvelope);
+
     // Crop the raster
     // this will shrink the extent of the raster to the geometry
     Crop cropObject = new Crop();
     ParameterValueGroup parameters = cropObject.getParameters();
     parameters.parameter("Source").setValue(singleBandRaster);
     parameters.parameter(Crop.PARAMNAME_DEST_NODATA).setValue(new double[] {noDataValue});
-    parameters.parameter(Crop.PARAMNAME_ROI).setValue(geometry);
+    parameters.parameter(Crop.PARAMNAME_ROI).setValue(geomExtent);
 
     GridCoverage2D newRaster;
     try {
diff --git a/common/src/main/java/org/apache/sedona/common/raster/Rasterization.java b/common/src/main/java/org/apache/sedona/common/raster/Rasterization.java
index 19530441bb..5d15990d6f 100644
--- a/common/src/main/java/org/apache/sedona/common/raster/Rasterization.java
+++ b/common/src/main/java/org/apache/sedona/common/raster/Rasterization.java
@@ -303,7 +303,7 @@ public class Rasterization {
     return x >= minX && x <= maxX && y >= minY && y <= maxY;
   }
 
-  private static Envelope2D rasterizeGeomExtent(
+  protected static Envelope2D rasterizeGeomExtent(
       Geometry geom, GridCoverage2D raster, double[] metadata, boolean allTouched) {
 
     if (Objects.equals(geom.getGeometryType(), "MultiLineString")) {
diff --git a/common/src/test/java/org/apache/sedona/common/raster/RasterBandEditorsTest.java b/common/src/test/java/org/apache/sedona/common/raster/RasterBandEditorsTest.java
index 73f81e8ed1..45de7c7e30 100644
--- a/common/src/test/java/org/apache/sedona/common/raster/RasterBandEditorsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/raster/RasterBandEditorsTest.java
@@ -201,6 +201,28 @@ public class RasterBandEditorsTest extends RasterTestBase {
     assertTrue(Arrays.equals(expectedValues, actualValues));
   }
 
+  @Test
+  public void testClip_smallAOI() throws FactoryException, TransformException, ParseException {
+    // Test for AOI geometries smaller than a pixel
+    GridCoverage2D raster =
+        RasterConstructors.makeEmptyRaster(1, "F", 4, 4, 401805.039562261, 2095852.150947876, 30);
+    raster = RasterEditors.setSrid(raster, 5070);
+    double[] bandValues = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+    raster = MapAlgebra.addBandFromArray(raster, bandValues, 1);
+    String polygon =
+        "POLYGON ((401866.1071465613 2095803.8989834636, 401850.9983055725 2095803.1375789386, 401850.039562261 2095822.150947876, 401865.14840359957 2095822.9124532426, 401880.2572475523 2095823.6738547424, 401881.2159852499 2095804.6604855175, 401866.1071465613 2095803.8989834636))";
+    Geometry geom = Constructors.geomFromWKT(polygon, 5070);
+
+    GridCoverage2D clippedRaster = RasterBandEditors.clip(raster, 1, geom, false, 0, true);
+    double bandNoDataValue = RasterBandAccessors.getBandNoDataValue(clippedRaster);
+    double expectedBandNoDataValue = 0.0;
+    double[] actualValues = MapAlgebra.bandAsArray(clippedRaster, 1);
+    double[] expectedValues = {2.0, 3.0, 6.0, 7.0};
+
+    assertEquals(expectedBandNoDataValue, bandNoDataValue, FP_TOLERANCE);
+    assertTrue(Arrays.equals(expectedValues, actualValues));
+  }
+
   @Test
   public void testClip()
       throws IOException, FactoryException, TransformException, ParseException,
@@ -244,7 +266,7 @@ public class RasterBandEditorsTest extends RasterTestBase {
     points.add(Constructors.geomFromWKT("POINT(237919 4.20357e+06)", 26918));
     points.add(Constructors.geomFromWKT("POINT(223802 4.20465e+06)", 26918));
     actualValues = PixelFunctions.values(croppedRaster, points, 1).toArray(new Double[0]);
-    expectedValues = new Double[] {0.0, 0.0, 0.0, 0.0, null};
+    expectedValues = new Double[] {0.0, 0.0, 0.0, 0.0, 255.0};
     assertTrue(Arrays.equals(expectedValues, actualValues));
   }
 


(sedona) 33/35: [SEDONA-734] Fix relation parsing in OSM reader (#1947)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 98317136228ae5d8373eb01eedccdecc5e23f253
Author: Paweł Tokaj <pa...@gmail.com>
AuthorDate: Wed May 21 18:44:34 2025 +0200

    [SEDONA-734] Fix relation parsing in OSM reader (#1947)
    
    * SEDONA-734 fix osm relation parsing
    
    * SEDONA-734 fix osm relation parsing
---
 .../osmpbf/extractors/RelationExtractor.java          |  7 ++++---
 .../scala/org/apache/sedona/sql/OsmReaderTest.scala   | 19 ++++++++++++++++---
 2 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/RelationExtractor.java b/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/RelationExtractor.java
index 19edd837c1..aa04189caa 100644
--- a/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/RelationExtractor.java
+++ b/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/RelationExtractor.java
@@ -72,12 +72,13 @@ public class RelationExtractor {
     long[] memberIds = new long[relation.getMemidsCount()];
 
     if (relation.getMemidsCount() != 0) {
-      long firstId = relation.getMemids(0);
+      long idValue = relation.getMemids(0);
 
-      memberIds[0] = firstId;
+      memberIds[0] = idValue;
 
       for (int i = 1; i < relation.getMemidsCount(); i++) {
-        memberIds[i] = relation.getMemids(i) + firstId;
+        idValue += relation.getMemids(i);
+        memberIds[i] = idValue;
       }
     }
 
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/OsmReaderTest.scala b/spark/common/src/test/scala/org/apache/sedona/sql/OsmReaderTest.scala
index d3f9a52c1c..6bedc8f97a 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/OsmReaderTest.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/OsmReaderTest.scala
@@ -175,7 +175,7 @@ class OsmReaderTest extends TestBaseScala with Matchers {
         .join(nodes, col("r.ref") === nodes("id"))
         .groupBy("r.id")
         .count()
-        .count() shouldEqual (113)
+        .count() shouldEqual (162)
 
       relations
         .selectExpr("explode(refs) AS ref", "id")
@@ -183,7 +183,7 @@ class OsmReaderTest extends TestBaseScala with Matchers {
         .join(ways, col("r.ref") === ways("id"))
         .groupBy("r.id")
         .count()
-        .count() shouldEqual (162)
+        .count() shouldEqual (261)
 
       relations
         .selectExpr("explode(refs) AS ref", "id")
@@ -191,7 +191,20 @@ class OsmReaderTest extends TestBaseScala with Matchers {
         .join(relations.as("r2"), col("r1.ref") === col("r2.id"))
         .groupBy("r1.id")
         .count()
-        .count() shouldEqual (31)
+        .count() shouldEqual (54)
+
+      val relationsList = relations
+        .where("id == 7360676")
+        .selectExpr("refs")
+        .as[Seq[String]]
+        .collect()
+        .head
+
+      val expectedRelationsList =
+        Seq("252356770", "503642591", "4939150452", "1373711177", "4939150459", "503642592")
+
+      relationsList.length shouldEqual (expectedRelationsList.length)
+      relationsList should contain theSameElementsAs expectedRelationsList
     }
   }
 


(sedona) 12/35: [DOCS] Fix case / spelling (#1882)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit ae44ede0096e5561e928c2f636bfaf3e517ddbf5
Author: John Bampton <jb...@users.noreply.github.com>
AuthorDate: Fri Mar 28 14:57:59 2025 +1000

    [DOCS] Fix case / spelling (#1882)
---
 docs/community/contact.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/community/contact.md b/docs/community/contact.md
index 8268c2e089..7d81ae1e86 100644
--- a/docs/community/contact.md
+++ b/docs/community/contact.md
@@ -55,7 +55,7 @@ You can ask for help or contribute to the project. Before submitting an issue, p
 Enhancement requests for new features are also welcome. The more concrete and rationale the request is, the greater the chance it will be incorporated into future releases.
 
 * [Sedona JIRA](https://issues.apache.org/jira/projects/SEDONA): bug reports and feature requests
-* [Sedona Github Issues](https://github.com/apache/sedona/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen): bug reports and feature requests
+* [Sedona GitHub Issues](https://github.com/apache/sedona/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen): bug reports and feature requests
 * [Sedona GitHub Discussion](https://github.com/apache/sedona/discussions): project development and general questions
 * [Sedona Mailing Lists](https://lists.apache.org/list.html?sedona.apache.org): [dev@sedona.apache.org](https://lists.apache.org/list.html?dev@sedona.apache.org): project development and general questions.
 


(sedona) 16/35: [CI] Update CODEOWNERS add @jbampton for pre-commit config (#1891)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 8bfc6aee7b90eedbb7775121079dbfc31cdbeeb9
Author: John Bampton <jb...@users.noreply.github.com>
AuthorDate: Sat Mar 29 15:21:49 2025 +1000

    [CI] Update CODEOWNERS add @jbampton for pre-commit config (#1891)
    
    People with write permissions for the repository can create or edit the CODEOWNERS file and be listed as code owners.
    
    https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
    
    See the link below for an example of how a CODEOWNERS file works:
    
    https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#example-of-a-codeowners-file
---
 .github/CODEOWNERS | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 225e4e65d5..94230756ed 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1 +1,2 @@
-*   @jiayuasu
+*                           @jiayuasu
+.pre-commit-config.yaml     @jbampton @jiayuasu


(sedona) 28/35: [GH-1761] error when invalid ST_Subdivide maxVertices argument (#1926)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit f827cdfb53ec6ddeded6840f47bc7bfac49939b1
Author: Javier Goday <jg...@gmail.com>
AuthorDate: Sat Apr 26 06:45:49 2025 +0200

    [GH-1761] error when invalid ST_Subdivide maxVertices argument (#1926)
---
 common/src/main/java/org/apache/sedona/common/Functions.java   |  3 +++
 .../src/test/java/org/apache/sedona/common/FunctionsTest.java  |  8 ++++++++
 docs/api/flink/Function.md                                     |  2 ++
 docs/api/snowflake/vector-data/Function.md                     |  4 ++++
 docs/api/sql/Function.md                                       |  4 ++++
 .../test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala   |  2 +-
 .../test/scala/org/apache/sedona/sql/functionTestScala.scala   | 10 ++++++++++
 7 files changed, 32 insertions(+), 1 deletion(-)

diff --git a/common/src/main/java/org/apache/sedona/common/Functions.java b/common/src/main/java/org/apache/sedona/common/Functions.java
index 4e14055d8c..f5e1eb0eba 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -1866,6 +1866,9 @@ public class Functions {
   }
 
   public static Geometry[] subDivide(Geometry geometry, int maxVertices) {
+    if (maxVertices < 5) {
+      throw new IllegalArgumentException("ST_Subdivide needs 5 or more max vertices");
+    }
     return GeometrySubDivider.subDivide(geometry, maxVertices);
   }
 
diff --git a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
index 748c0b6c12..8cafb9c648 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -4420,4 +4420,12 @@ public class FunctionsTest extends TestBase {
     assertFalse(Functions.hasM(emptyPoint));
     assertFalse(Functions.hasZ(emptyPoint));
   }
+
+  @Test
+  public void subdivideInvalidMaxVertices() {
+    LineString lineString = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 99, 99));
+    IllegalArgumentException e =
+        assertThrows(IllegalArgumentException.class, () -> Functions.subDivide(lineString, 4));
+    assertEquals("ST_Subdivide needs 5 or more max vertices", e.getMessage());
+  }
 }
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index fffb917684..199316538a 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -4041,6 +4041,8 @@ POINT(100 150)
 
 Introduction: Returns list of geometries divided based of given maximum number of vertices.
 
+A minimum of 5 vertices is required for maxVertices parameter to form a closed box.
+
 Format: `ST_SubDivide(geom: Geometry, maxVertices: Integer)`
 
 Since: `v1.5.0`
diff --git a/docs/api/snowflake/vector-data/Function.md b/docs/api/snowflake/vector-data/Function.md
index d545a2695a..37e12e81b8 100644
--- a/docs/api/snowflake/vector-data/Function.md
+++ b/docs/api/snowflake/vector-data/Function.md
@@ -3169,6 +3169,8 @@ Output: `POINT(100 150)`
 
 Introduction: Returns a multi-geometry divided based of given maximum number of vertices.
 
+A minimum of 5 vertices is required for maxVertices parameter to form a closed box.
+
 Format: `ST_SubDivide(geom: geometry, maxVertices: int)`
 
 SQL example:
@@ -3188,6 +3190,8 @@ MULTILINESTRING ((0 0, 5 5), (5 5, 10 10), (10 10, 21 21), (21 21, 60 60), (60 6
 
 Introduction: It works the same as ST_SubDivide but returns new rows with geometries instead of a multi-geometry.
 
+A minimum of 5 vertices is required for maxVertices parameter to form a closed box.
+
 Format: `
 SELECT SEDONA.ST_AsText(GEOM)
 FROM table(SEDONA.ST_SubDivideExplode(geom: geometry, maxVertices: int))`
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 2c7bd21fc6..fd7cc881d6 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -4253,6 +4253,8 @@ POINT(100 150)
 
 Introduction: Returns list of geometries divided based of given maximum number of vertices.
 
+A minimum of 5 vertices is required for maxVertices parameter to form a closed box.
+
 Format: `ST_SubDivide(geom: Geometry, maxVertices: Integer)`
 
 Since: `v1.1.0`
@@ -4309,6 +4311,8 @@ Output:
 
 Introduction: It works the same as ST_SubDivide but returns new rows with geometries instead of list.
 
+A minimum of 5 vertices is required for maxVertices parameter to form a closed box.
+
 Format: `ST_SubDivideExplode(geom: Geometry, maxVertices: Integer)`
 
 Since: `v1.1.0`
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala b/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala
index 02fdb3149b..37723b75fe 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala
@@ -75,7 +75,7 @@ class PreserveSRIDSuite extends TestBaseScala with TableDrivenPropertyChecks {
       ("ST_SetPoint(geom3, 1, ST_Point(0.5, 0.5))", 1000),
       ("ST_ClosestPoint(geom1, geom2)", 1000),
       ("ST_FlipCoordinates(geom1)", 1000),
-      ("ST_SubDivide(geom4, 4)", 1000),
+      ("ST_SubDivide(geom4, 5)", 1000),
       ("ST_MakeEnvelope(0, 1, 2, 3, 1000)", 1000),
       ("ST_MakeLine(geom3, geom3)", 1000),
       ("ST_Points(geom1)", 1000),
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index 665304662e..1f6140e991 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -3770,4 +3770,14 @@ class functionTestScala
       "The Line has SRID 4326 and Point has SRID 3857. The Line and Point should be in the same SRID.")
   }
 
+  it("should raise an error when using ST_SubDivide with not enough vertices") {
+    val invalidDf = sparkSession.sql("""
+        |SELECT ST_Subdivide(ST_GeomFromWKT('LINESTRING(0 0, 99 99)'), 4) as result
+    """.stripMargin)
+
+    val exception = intercept[Exception] {
+      invalidDf.collect()
+    }
+    exception.getMessage should include("ST_Subdivide needs 5 or more max vertices")
+  }
 }


(sedona) 21/35: [SEDONA-724] Fix RS_ZonalStats and RS_ZonalStatsAll edge case bug (#1871)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 1e8b70ffdf09f1b7fe58488b48e6630f9ca056bd
Author: Furqaan Khan <46...@users.noreply.github.com>
AuthorDate: Thu Mar 20 15:19:58 2025 -0400

    [SEDONA-724] Fix RS_ZonalStats and RS_ZonalStatsAll edge case bug (#1871)
    
    * fix: RS_ZonalStats and RS_ZonalStatsAll edge case
    
    * fix: spotless
    
    * change NaN to null
    
    * fix spotless
    
    * change all NaNs to nulls
---
 .../sedona/common/raster/RasterBandAccessors.java  | 76 +++++++++++++++-------
 .../common/raster/RasterBandAccessorsTest.java     | 56 ++++++++++++----
 .../org/apache/sedona/sql/rasteralgebraTest.scala  | 18 +++++
 3 files changed, 117 insertions(+), 33 deletions(-)

diff --git a/common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java b/common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java
index b843ce924c..f5011d5a1c 100644
--- a/common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java
+++ b/common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java
@@ -99,7 +99,7 @@ public class RasterBandAccessors {
    * @return An array with all the stats for the region
    * @throws FactoryException
    */
-  public static double[] getZonalStatsAll(
+  public static Double[] getZonalStatsAll(
       GridCoverage2D raster,
       Geometry roi,
       int band,
@@ -114,18 +114,35 @@ public class RasterBandAccessors {
     DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
     double[] pixelData = (double[]) objects.get(1);
 
+    // Shortcut for an edge case where ROI barely intersects with raster's extent, but it doesn't
+    // intersect with the centroid of the pixel.
+    // This happens when allTouched parameter is false.
+    if (pixelData.length == 0) {
+      return new Double[] {0.0, null, null, null, null, null, null, null, null};
+    }
+
     // order of stats
     // count, sum, mean, median, mode, stddev, variance, min, max
-    double[] result = new double[9];
-    result[0] = stats.getN();
-    result[1] = stats.getSum();
-    result[2] = stats.getMean();
-    result[3] = stats.getPercentile(50);
+    Double[] result = new Double[9];
+    result[0] = (double) stats.getN();
+    if (stats.getN() == 0) {
+      result[1] = null;
+    } else {
+      result[1] = stats.getSum();
+    }
+    double mean = stats.getMean();
+    result[2] = Double.isNaN(mean) ? null : mean;
+    double median = stats.getPercentile(50);
+    result[3] = Double.isNaN(median) ? null : median;
     result[4] = zonalMode(pixelData);
-    result[5] = stats.getStandardDeviation();
-    result[6] = stats.getVariance();
-    result[7] = stats.getMin();
-    result[8] = stats.getMax();
+    double stdDev = stats.getStandardDeviation();
+    result[5] = Double.isNaN(stdDev) ? null : stats.getStandardDeviation();
+    double variance = stats.getVariance();
+    result[6] = Double.isNaN(variance) ? null : variance;
+    double min = stats.getMin();
+    result[7] = Double.isNaN(min) ? null : min;
+    double max = stats.getMax();
+    result[8] = Double.isNaN(max) ? null : max;
 
     return result;
   }
@@ -139,7 +156,7 @@ public class RasterBandAccessors {
    * @return An array with all the stats for the region
    * @throws FactoryException
    */
-  public static double[] getZonalStatsAll(
+  public static Double[] getZonalStatsAll(
       GridCoverage2D raster, Geometry roi, int band, boolean allTouched, boolean excludeNoData)
       throws FactoryException {
     return getZonalStatsAll(raster, roi, band, allTouched, excludeNoData, true);
@@ -153,7 +170,7 @@ public class RasterBandAccessors {
    * @return An array with all the stats for the region, excludeNoData is set to true
    * @throws FactoryException
    */
-  public static double[] getZonalStatsAll(
+  public static Double[] getZonalStatsAll(
       GridCoverage2D raster, Geometry roi, int band, boolean allTouched) throws FactoryException {
     return getZonalStatsAll(raster, roi, band, allTouched, true);
   }
@@ -165,7 +182,7 @@ public class RasterBandAccessors {
    * @return An array with all the stats for the region, excludeNoData is set to true
    * @throws FactoryException
    */
-  public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band)
+  public static Double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band)
       throws FactoryException {
     return getZonalStatsAll(raster, roi, band, false);
   }
@@ -177,7 +194,7 @@ public class RasterBandAccessors {
    *     set to 1
    * @throws FactoryException
    */
-  public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi)
+  public static Double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi)
       throws FactoryException {
     return getZonalStatsAll(raster, roi, 1);
   }
@@ -213,26 +230,36 @@ public class RasterBandAccessors {
 
     switch (statType.toLowerCase()) {
       case "sum":
-        return stats.getSum();
+        if (pixelData.length == 0) {
+          return null;
+        } else {
+          return stats.getSum();
+        }
       case "average":
       case "avg":
       case "mean":
-        return stats.getMean();
+        double mean = stats.getMean();
+        return Double.isNaN(mean) ? null : mean;
       case "count":
         return (double) stats.getN();
       case "max":
-        return stats.getMax();
+        double max = stats.getMax();
+        return Double.isNaN(max) ? null : max;
       case "min":
-        return stats.getMin();
+        double min = stats.getMin();
+        return Double.isNaN(min) ? null : min;
       case "stddev":
       case "sd":
-        return stats.getStandardDeviation();
+        double stdDev = stats.getStandardDeviation();
+        return Double.isNaN(stdDev) ? null : stdDev;
       case "median":
-        return stats.getPercentile(50);
+        double median = stats.getPercentile(50);
+        return Double.isNaN(median) ? null : median;
       case "mode":
         return zonalMode(pixelData);
       case "variance":
-        return stats.getVariance();
+        double variance = stats.getVariance();
+        return Double.isNaN(variance) ? null : variance;
       default:
         throw new IllegalArgumentException(
             "Please select from the accepted options. Some of the valid options are sum, mean, stddev, etc.");
@@ -310,8 +337,13 @@ public class RasterBandAccessors {
    * @return Mode of the pixel values. If there is multiple with same occurrence, then the largest
    *     value will be returned.
    */
-  private static double zonalMode(double[] pixelData) {
+  private static Double zonalMode(double[] pixelData) {
     double[] modes = StatUtils.mode(pixelData);
+    // Return NaN when ROI and raster's extent overlap, but there's no pixel data.
+    // This behavior only happens when allTouched parameter is false.
+    if (modes.length == 0) {
+      return null;
+    }
     return modes[modes.length - 1];
   }
 
diff --git a/common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java b/common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java
index 94986d039a..21f4e446ae 100644
--- a/common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java
@@ -28,7 +28,6 @@ import org.junit.Test;
 import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.io.ParseException;
 import org.opengis.referencing.FactoryException;
-import org.opengis.referencing.operation.TransformException;
 
 public class RasterBandAccessorsTest extends RasterTestBase {
 
@@ -84,6 +83,31 @@ public class RasterBandAccessorsTest extends RasterTestBase {
     assertEquals("Provided band index 2 is not present in the raster", exception.getMessage());
   }
 
+  @Test
+  public void testZonalStatsIntersectingNoPixelData() throws FactoryException, ParseException {
+    double[][] pixelsValues =
+        new double[][] {
+          new double[] {
+            3, 7, 5, 40, 61, 70, 60, 80, 27, 55, 35, 44, 21, 36, 53, 54, 86, 28, 45, 24, 99, 22, 18,
+            98, 10
+          }
+        };
+    GridCoverage2D raster =
+        RasterConstructors.makeNonEmptyRaster(1, "", 5, 5, 1, -1, 1, -1, 0, 0, 0, pixelsValues);
+    Geometry extent =
+        Constructors.geomFromWKT(
+            "POLYGON ((5.822754 -6.620957, 6.965332 -6.620957, 6.965332 -5.834616, 5.822754 -5.834616, 5.822754 -6.620957))",
+            0);
+
+    Double actualZonalStats = RasterBandAccessors.getZonalStats(raster, extent, "mode");
+    assertNull(actualZonalStats);
+
+    String actualZonalStatsAll =
+        Arrays.toString(RasterBandAccessors.getZonalStatsAll(raster, extent));
+    String expectedZonalStatsAll = "[0.0, null, null, null, null, null, null, null, null]";
+    assertEquals(expectedZonalStatsAll, actualZonalStatsAll);
+  }
+
   @Test
   public void testZonalStats() throws FactoryException, ParseException, IOException {
     GridCoverage2D raster =
@@ -182,15 +206,17 @@ public class RasterBandAccessorsTest extends RasterTestBase {
   }
 
   @Test
-  public void testZonalStatsAll()
-      throws IOException, FactoryException, ParseException, TransformException {
+  public void testZonalStatsAll() throws IOException, FactoryException, ParseException {
     GridCoverage2D raster =
         rasterFromGeoTiff(resourceFolder + "raster_geotiff_color/FAA_UTM18N_NAD83.tif");
     String polygon =
         "POLYGON ((-8673439.6642 4572993.5327, -8673155.5737 4563873.2099, -8701890.3259 4562931.7093, -8682522.8735 4572703.8908, -8673439.6642 4572993.5327))";
     Geometry geom = Constructors.geomFromWKT(polygon, 3857);
 
-    double[] actual = RasterBandAccessors.getZonalStatsAll(raster, geom, 1, false, false, false);
+    double[] actual =
+        Arrays.stream(RasterBandAccessors.getZonalStatsAll(raster, geom, 1, false, false, false))
+            .mapToDouble(Double::doubleValue)
+            .toArray();
     double[] expected =
         new double[] {
           185953.0,
@@ -209,16 +235,19 @@ public class RasterBandAccessorsTest extends RasterTestBase {
         Constructors.geomFromWKT(
             "POLYGON ((-77.96672569800863073 37.91971182746296876, -77.9688630154902711 37.89620133516485367, -77.93936803424354309 37.90517806858776595, -77.96672569800863073 37.91971182746296876))",
             0);
-    actual = RasterBandAccessors.getZonalStatsAll(raster, geom, 1, false, false, false);
+    actual =
+        Arrays.stream(RasterBandAccessors.getZonalStatsAll(raster, geom, 1, false, false, false))
+            .mapToDouble(Double::doubleValue)
+            .toArray();
     assertNotNull(actual);
 
     Geometry nonIntersectingGeom =
         Constructors.geomFromWKT(
             "POLYGON ((-78.22106647832458748 37.76411511479908967, -78.20183062098976734 37.72863564460374874, -78.18088490966962922 37.76753482276972562, -78.22106647832458748 37.76411511479908967))",
             0);
-    actual =
+    Double[] actualNull =
         RasterBandAccessors.getZonalStatsAll(raster, nonIntersectingGeom, 1, false, false, true);
-    assertNull(actual);
+    assertNull(actualNull);
     assertThrows(
         IllegalArgumentException.class,
         () ->
@@ -227,15 +256,17 @@ public class RasterBandAccessorsTest extends RasterTestBase {
   }
 
   @Test
-  public void testZonalStatsAllWithNoData()
-      throws IOException, FactoryException, ParseException, TransformException {
+  public void testZonalStatsAllWithNoData() throws IOException, FactoryException, ParseException {
     GridCoverage2D raster =
         rasterFromGeoTiff(resourceFolder + "raster/raster_with_no_data/test5.tiff");
     String polygon =
         "POLYGON((-167.750000 87.750000, -155.250000 87.750000, -155.250000 40.250000, -180.250000 40.250000, -167.750000 87.750000))";
     Geometry geom = Constructors.geomFromWKT(polygon, RasterAccessors.srid(raster));
 
-    double[] actual = RasterBandAccessors.getZonalStatsAll(raster, geom, 1, false, true);
+    double[] actual =
+        Arrays.stream(RasterBandAccessors.getZonalStatsAll(raster, geom, 1, false, true))
+            .mapToDouble(Double::doubleValue)
+            .toArray();
     double[] expected =
         new double[] {
           14249.0,
@@ -265,7 +296,10 @@ public class RasterBandAccessorsTest extends RasterTestBase {
     // Testing implicit CRS transformation
     Geometry geom = Constructors.geomFromWKT("POLYGON((2 -2, 2 -6, 6 -6, 6 -2, 2 -2))", 0);
 
-    double[] actual = RasterBandAccessors.getZonalStatsAll(raster, geom, 1, false, true);
+    double[] actual =
+        Arrays.stream(RasterBandAccessors.getZonalStatsAll(raster, geom, 1, false, true))
+            .mapToDouble(Double::doubleValue)
+            .toArray();
     double[] expected = new double[] {13.0, 114.0, 8.7692, 9.0, 11.0, 4.7285, 22.3589, 1.0, 16.0};
     assertArrayEquals(expected, actual, FP_TOLERANCE);
   }
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala b/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
index 3e77f8f509..5832e54077 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
@@ -1623,6 +1623,24 @@ class rasteralgebraTest extends TestBaseScala with BeforeAndAfter with GivenWhen
       assertTrue(expectedSummary4.equals(actualSummary4))
     }
 
+    it("Passed RS_ZonalStats edge case") {
+      val df = sparkSession.sql("""
+          |with data as (
+          | SELECT array(3, 7, 5, 40, 61, 70, 60, 80, 27, 55, 35, 44, 21, 36, 53, 54, 86, 28, 45, 24, 99, 22, 18, 98, 10) as pixels,
+          |   ST_GeomFromWKT('POLYGON ((5.822754 -6.620957, 6.965332 -6.620957, 6.965332 -5.834616, 5.822754 -5.834616, 5.822754 -6.620957))', 4326) as geom
+          |)
+          |
+          |SELECT RS_SetSRID(RS_AddBandFromArray(RS_MakeEmptyRaster(1, "D", 5, 5, 1, -1, 1), pixels, 1), 4326) as raster, geom FROM data
+          |""".stripMargin)
+
+      val actual = df.selectExpr("RS_ZonalStats(raster, geom, 1, 'mode')").first().get(0)
+      assertNull(actual)
+
+      val statsDf = df.selectExpr("RS_ZonalStatsAll(raster, geom) as stats")
+      val actualBoolean = statsDf.selectExpr("isNull(stats.mode)").first().getAs[Boolean](0)
+      assertTrue(actualBoolean)
+    }
+
     it("Passed RS_ZonalStats") {
       var df = sparkSession.read
         .format("binaryFile")


(sedona) 30/35: [CI] Fix failed Python build extension CI (#1928)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit ed5df81ca36920db0ada10bb08449a512fbaa56a
Author: Jia Yu <ji...@apache.org>
AuthorDate: Fri Apr 25 22:14:02 2025 -0700

    [CI] Fix failed Python build extension CI (#1928)
---
 .github/workflows/python-extension.yml | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/python-extension.yml b/.github/workflows/python-extension.yml
index 59025cfb7b..0e29734073 100644
--- a/.github/workflows/python-extension.yml
+++ b/.github/workflows/python-extension.yml
@@ -62,8 +62,13 @@ jobs:
       - name: Install dependencies
         run: |
           cd python
-          pipenv --python ${{ matrix.python }}
-          pipenv install --dev
+          if [[ "$RUNNER_OS" == "Windows" ]]; then
+              PYTHON_EXE_PATH="$pythonLocation/python.exe"
+          else
+              PYTHON_EXE_PATH="$pythonLocation/python"
+          fi
+          echo "Using Python executable at: $PYTHON_EXE_PATH"
+          pipenv install --dev --python "$PYTHON_EXE_PATH"
       - name: Build extension
         run: |
           cd python


(sedona) 19/35: [CI] Update CODEOWNERS add reviewers for linters (#1894)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit fe836bdfff17e6c49d1721daca375f8e652c4838
Author: John Bampton <jb...@users.noreply.github.com>
AuthorDate: Mon Mar 31 14:39:51 2025 +1000

    [CI] Update CODEOWNERS add reviewers for linters (#1894)
    
    The linter config files work with the pre-commit config.
    
    So adding @jbampton as one of the default reviewers.
    
    Added reviewers for the Makefile as well.
    
    An example of how a CODEOWNERS file works:
    
    https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#example-of-a-codeowners-file
---
 .github/CODEOWNERS | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 94230756ed..3a1ffe02fb 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,2 +1,8 @@
 *                           @jiayuasu
+.editorconfig               @jbampton @jiayuasu
 .pre-commit-config.yaml     @jbampton @jiayuasu
+.prettierignore             @jbampton @jiayuasu
+.prettierrc                 @jbampton @jiayuasu
+.shellcheckrc               @jbampton @jiayuasu
+Makefile                    @jbampton @jiayuasu
+/.github/linters/           @jbampton @jiayuasu


(sedona) 14/35: [CI] pre-commit(insert-license): fix bug in comment markers for .c and .h (#1884)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit ff6ed393dec559a129e2eda17edaacdef7e4e770
Author: John Bampton <jb...@users.noreply.github.com>
AuthorDate: Fri Mar 28 14:59:25 2025 +1000

    [CI] pre-commit(insert-license): fix bug in comment markers for .c and .h (#1884)
---
 .pre-commit-config.yaml | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 00a6c1e621..a1defa0e6c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -33,11 +33,20 @@ repos:
     rev: v1.5.5
     hooks:
       - id: insert-license
-        name: add license for all .c and .h files
-        files: \.(c|h)$
+        name: add license for all .c files
+        files: \.c$
         args:
           - --comment-style
-          - "/*|*|*/"
+          - "/*| *| */"
+          - --license-filepath
+          - .github/workflows/license-templates/LICENSE.txt
+          - --fuzzy-match-generates-todo
+      - id: insert-license
+        name: add license for all .h files
+        files: \.h$
+        args:
+          - --comment-style
+          - "/*| *| */"
           - --license-filepath
           - .github/workflows/license-templates/LICENSE.txt
           - --fuzzy-match-generates-todo


(sedona) 17/35: chore: standardize spelling in Java code (#1892)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 8d4d4aac1ec8863377f41ae77a271a106984f88f
Author: John Bampton <jb...@users.noreply.github.com>
AuthorDate: Mon Mar 31 14:12:31 2025 +1000

    chore: standardize spelling in Java code (#1892)
    
    We use "splitted" a lot more than "splited".
    
    So this PR just makes the usage consistent.
---
 .../main/java/org/apache/sedona/common/utils/FormatUtils.java    | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/common/src/main/java/org/apache/sedona/common/utils/FormatUtils.java b/common/src/main/java/org/apache/sedona/common/utils/FormatUtils.java
index 494f97bbb8..dcbf1cfb2a 100644
--- a/common/src/main/java/org/apache/sedona/common/utils/FormatUtils.java
+++ b/common/src/main/java/org/apache/sedona/common/utils/FormatUtils.java
@@ -141,14 +141,15 @@ public class FormatUtils<T extends Geometry> implements Serializable {
     geoJSONReader = new GeoJSONReader();
   }
 
-  private void handleNonSpatialDataToGeometry(Geometry geometry, List<String> splitedGeometryData) {
-    LinkedList<String> splitedGeometryDataList = new LinkedList<String>(splitedGeometryData);
+  private void handleNonSpatialDataToGeometry(
+      Geometry geometry, List<String> splittedGeometryData) {
+    LinkedList<String> splittedGeometryDataList = new LinkedList<String>(splittedGeometryData);
     if (carryInputData) {
       if (this.splitter != FileDataSplitter.GEOJSON) {
         // remove spatial data position
-        splitedGeometryDataList.remove(this.startOffset);
+        splittedGeometryDataList.remove(this.startOffset);
       }
-      geometry.setUserData(String.join("\t", splitedGeometryDataList));
+      geometry.setUserData(String.join("\t", splittedGeometryDataList));
     }
   }
 


(sedona) 24/35: [GH-1905] Increment Spark minor version in Databricks setup guide (#1912)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 86f0fc2c26d9d39f215b3df1abe1d67ed2fa3104
Author: Kristin Cowalcijk <ko...@apache.org>
AuthorDate: Tue Apr 8 00:21:45 2025 +0800

    [GH-1905] Increment Spark minor version in Databricks setup guide (#1912)
---
 docs/setup/databricks.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/setup/databricks.md b/docs/setup/databricks.md
index ff51a9eb70..75bdc1beb7 100644
--- a/docs/setup/databricks.md
+++ b/docs/setup/databricks.md
@@ -19,7 +19,7 @@
 
 In Databricks advanced editions, you need to install Sedona via [cluster init-scripts](https://docs.databricks.com/clusters/init-scripts.html) as described below. Sedona is not guaranteed to be 100% compatible with `Databricks photon acceleration`. Sedona requires Spark internal APIs to inject many optimization strategies, which sometimes is not accessible in `Photon`.
 
-The following steps use DBR including Apache Spark 3.4.x as an example. Please change the Spark version according to your DBR version. Please pay attention to the Spark version postfix and Scala version postfix on our [Maven Coordinate page](maven-coordinates.md). Databricks Spark and Apache Spark's compatibility can be found [here](https://docs.databricks.com/en/release-notes/runtime/index.html).
+The following steps use DBR including Apache Spark 3.5.x as an example. Please change the Spark version according to your DBR version. Please pay attention to the Spark version postfix and Scala version postfix on our [Maven Coordinate page](maven-coordinates.md). Databricks Spark and Apache Spark's compatibility can be found [here](https://docs.databricks.com/en/release-notes/runtime/index.html).
 
 !!! bug
     Databricks Runtime 16.2 (non-LTS) introduces a change in the json4s dependency, which may lead to compatibility issues with Apache Sedona. We recommend using a currently supported LTS version, such as Databricks Runtime 15.4 LTS or 14.3 LTS, to ensure stability. A patch will be provided once an official Databricks Runtime 16 LTS version is released.
@@ -36,7 +36,7 @@ mkdir -p /Workspace/Shared/sedona/{{ sedona.current_version }}
 # Download the dependencies from Maven into DBFS
 curl -o /Workspace/Shared/sedona/{{ sedona.current_version }}/geotools-wrapper-{{ sedona.current_geotools }}.jar "https://repo1.maven.org/maven2/org/datasyslab/geotools-wrapper/{{ sedona.current_geotools }}/geotools-wrapper-{{ sedona.current_geotools }}.jar"
 
-curl -o /Workspace/Shared/sedona/{{ sedona.current_version }}/sedona-spark-shaded-3.4_2.12-{{ sedona.current_version }}.jar "https://repo1.maven.org/maven2/org/apache/sedona/sedona-spark-shaded-3.4_2.12/{{ sedona.current_version }}/sedona-spark-shaded-3.4_2.12-{{ sedona.current_version }}.jar"
+curl -o /Workspace/Shared/sedona/{{ sedona.current_version }}/sedona-spark-shaded-3.5_2.12-{{ sedona.current_version }}.jar "https://repo1.maven.org/maven2/org/apache/sedona/sedona-spark-shaded-3.5_2.12/{{ sedona.current_version }}/sedona-spark-shaded-3.5_2.12-{{ sedona.current_version }}.jar"
 ```
 
 Of course, you can also do the steps above manually.


(sedona) 02/35: [SEDONA-704] Optimize STAC reader and fix few issues (#1861)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 8bee92d784e457116c3342d8256bf9d2d04b9a98
Author: Feng Zhang <fe...@wherobots.com>
AuthorDate: Mon Mar 17 14:30:18 2025 -0700

    [SEDONA-704] Optimize STAC reader and fix few issues (#1861)
    
    * [SEDONA-704] Optimize STAC reader and fix few issues
    
    * fix formating issue
    
    * fix compiling error
    
    * another fix
---
 docs/tutorial/files/stac-sedona-spark.md           |   4 +-
 python/sedona/stac/collection_client.py            |  93 ++++++--------
 python/tests/stac/test_client.py                   |   5 +-
 python/tests/stac/test_collection_client.py        |   6 +-
 .../spark/sql/sedona_sql/io/stac/StacBatch.scala   |  36 +++++-
 .../sql/sedona_sql/io/stac/StacDataSource.scala    |  17 ++-
 .../sedona_sql/io/stac/StacPartitionReader.scala   |   8 +-
 .../spark/sql/sedona_sql/io/stac/StacScan.scala    |  26 +++-
 .../sql/sedona_sql/io/stac/StacScanBuilder.scala   |  10 +-
 .../spark/sql/sedona_sql/io/stac/StacTable.scala   |   9 +-
 .../spark/sql/sedona_sql/io/stac/StacUtils.scala   | 134 ++++++++++++++-------
 .../SpatialTemporalFilterPushDownForStacScan.scala |  17 ++-
 .../sql/sedona_sql/io/stac/StacBatchTest.scala     |  81 +++++++++++--
 .../sedona_sql/io/stac/StacDataSourceTest.scala    |  26 ++--
 .../io/stac/StacPartitionReaderTest.scala          |   5 +
 .../sql/sedona_sql/io/stac/StacUtilsTest.scala     | 128 +++++++++++++++++++-
 16 files changed, 450 insertions(+), 155 deletions(-)

diff --git a/docs/tutorial/files/stac-sedona-spark.md b/docs/tutorial/files/stac-sedona-spark.md
index 062e6c5f55..aff0e29f0f 100644
--- a/docs/tutorial/files/stac-sedona-spark.md
+++ b/docs/tutorial/files/stac-sedona-spark.md
@@ -116,7 +116,7 @@ The STAC data source supports predicate pushdown for spatial and temporal filter
 
 ### Spatial Filter Pushdown
 
-Spatial filter pushdown allows the data source to apply spatial predicates (e.g., st_contains, st_intersects) directly at the data source level, reducing the amount of data transferred and processed.
+Spatial filter pushdown allows the data source to apply spatial predicates (e.g., st_intersects) directly at the data source level, reducing the amount of data transferred and processed.
 
 ### Temporal Filter Pushdown
 
@@ -147,7 +147,7 @@ In this example, the data source will push down the temporal filter to the under
 ```sql
   SELECT id, geometry
   FROM STAC_TABLE
-  WHERE st_contains(ST_GeomFromText('POLYGON((17 10, 18 10, 18 11, 17 11, 17 10))'), geometry)
+  WHERE st_intersects(ST_GeomFromText('POLYGON((17 10, 18 10, 18 11, 17 11, 17 10))'), geometry)
 ```
 
 In this example, the data source will push down the spatial filter to the underlying data source.
diff --git a/python/sedona/stac/collection_client.py b/python/sedona/stac/collection_client.py
index b1cae6df39..7670c6291d 100644
--- a/python/sedona/stac/collection_client.py
+++ b/python/sedona/stac/collection_client.py
@@ -119,7 +119,7 @@ class CollectionClient:
         - DataFrame: The filtered Spark DataFrame.
 
         The function constructs SQL conditions for spatial and temporal filters and applies them to the DataFrame.
-        If bbox is provided, it constructs spatial conditions using st_contains and ST_GeomFromText.
+        If bbox is provided, it constructs spatial conditions using st_intersects and ST_GeomFromText.
         If datetime is provided, it constructs temporal conditions using the datetime column.
         The conditions are combined using OR logic.
         """
@@ -131,7 +131,7 @@ class CollectionClient:
                     f"{bbox[2]} {bbox[3]}, {bbox[0]} {bbox[3]}, {bbox[0]} {bbox[1]}))"
                 )
                 bbox_conditions.append(
-                    f"st_contains(ST_GeomFromText('{polygon_wkt}'), geometry)"
+                    f"st_intersects(ST_GeomFromText('{polygon_wkt}'), geometry)"
                 )
             bbox_sql_condition = " OR ".join(bbox_conditions)
             df = df.filter(bbox_sql_condition)
@@ -217,34 +217,7 @@ class CollectionClient:
           is raised with a message indicating the failure.
         """
         try:
-            # Load the collection data from the specified collection URL
-            df = self.spark.read.format("stac").load(self.collection_url)
-
-            # Apply ID filters if provided
-            if ids:
-                if isinstance(ids, tuple):
-                    ids = list(ids)
-                if isinstance(ids, str):
-                    ids = [ids]
-                df = df.filter(df.id.isin(ids))
-
-            # Ensure bbox is a list of lists
-            if bbox and isinstance(bbox[0], float):
-                bbox = [bbox]
-
-            # Handle datetime parameter
-            if datetime:
-                if isinstance(datetime, (str, python_datetime.datetime)):
-                    datetime = [self._expand_date(str(datetime))]
-                elif isinstance(datetime, list) and isinstance(datetime[0], str):
-                    datetime = [datetime]
-
-            # Apply spatial and temporal filters
-            df = self._apply_spatial_temporal_filters(df, bbox, datetime)
-
-            # Limit the number of items if max_items is specified
-            if max_items is not None:
-                df = df.limit(max_items)
+            df = self.load_items_df(bbox, datetime, ids, max_items)
 
             # Collect the filtered rows and convert them to PyStacItem objects
             items = []
@@ -291,32 +264,7 @@ class CollectionClient:
           is raised with a message indicating the failure.
         """
         try:
-            df = self.spark.read.format("stac").load(self.collection_url)
-
-            # Apply ID filters if provided
-            if ids:
-                if isinstance(ids, tuple):
-                    ids = list(ids)
-                if isinstance(ids, str):
-                    ids = [ids]
-                df = df.filter(df.id.isin(ids))
-
-            # Ensure bbox is a list of lists
-            if bbox and isinstance(bbox[0], float):
-                bbox = [bbox]
-
-            # Handle datetime parameter
-            if datetime:
-                if isinstance(datetime, (str, python_datetime.datetime)):
-                    datetime = [[str(datetime), str(datetime)]]
-                elif isinstance(datetime, list) and isinstance(datetime[0], str):
-                    datetime = [datetime]
-
-            df = self._apply_spatial_temporal_filters(df, bbox, datetime)
-
-            # Limit the number of items if max_items is specified
-            if max_items is not None:
-                df = df.limit(max_items)
+            df = self.load_items_df(bbox, datetime, ids, max_items)
 
             return df
         except Exception as e:
@@ -394,5 +342,38 @@ class CollectionClient:
 
         return df
 
+    def load_items_df(self, bbox, datetime, ids, max_items):
+        # Load the collection data from the specified collection URL
+        if not ids and not bbox and not datetime and max_items is not None:
+            df = (
+                self.spark.read.format("stac")
+                .option("itemsLimitMax", max_items)
+                .load(self.collection_url)
+            )
+        else:
+            df = self.spark.read.format("stac").load(self.collection_url)
+            # Apply ID filters if provided
+            if ids:
+                if isinstance(ids, tuple):
+                    ids = list(ids)
+                if isinstance(ids, str):
+                    ids = [ids]
+                df = df.filter(df.id.isin(ids))
+            # Ensure bbox is a list of lists
+            if bbox and isinstance(bbox[0], float):
+                bbox = [bbox]
+            # Handle datetime parameter
+            if datetime:
+                if isinstance(datetime, (str, python_datetime.datetime)):
+                    datetime = [self._expand_date(str(datetime))]
+                elif isinstance(datetime, list) and isinstance(datetime[0], str):
+                    datetime = [datetime]
+            # Apply spatial and temporal filters
+            df = self._apply_spatial_temporal_filters(df, bbox, datetime)
+        # Limit the number of items if max_items is specified
+        if max_items is not None:
+            df = df.limit(max_items)
+        return df
+
     def __str__(self):
         return f"<CollectionClient id={self.collection_id}>"
diff --git a/python/tests/stac/test_client.py b/python/tests/stac/test_client.py
index 4f9919e1c5..b8b2beed8a 100644
--- a/python/tests/stac/test_client.py
+++ b/python/tests/stac/test_client.py
@@ -36,7 +36,7 @@ class TestStacClient(TestBase):
             return_dataframe=False,
         )
         assert items is not None
-        assert len(list(items)) == 2
+        assert len(list(items)) > 0
 
     def test_search_with_ids(self) -> None:
         client = Client.open(STAC_URLS["PLANETARY-COMPUTER"])
@@ -82,7 +82,7 @@ class TestStacClient(TestBase):
             return_dataframe=False,
         )
         assert items is not None
-        assert len(list(items)) == 4
+        assert len(list(items)) > 0
 
     def test_search_with_bbox_and_non_overlapping_intervals(self) -> None:
         client = Client.open(STAC_URLS["PLANETARY-COMPUTER"])
@@ -144,6 +144,7 @@ class TestStacClient(TestBase):
             datetime=["2006-01-01T00:00:00Z", "2007-01-01T00:00:00Z"],
         )
         assert df is not None
+        assert df.count() == 20
         assert isinstance(df, DataFrame)
 
     def test_search_with_catalog_url(self) -> None:
diff --git a/python/tests/stac/test_collection_client.py b/python/tests/stac/test_collection_client.py
index 24226f86ca..aa9a15f4eb 100644
--- a/python/tests/stac/test_collection_client.py
+++ b/python/tests/stac/test_collection_client.py
@@ -71,7 +71,7 @@ class TestStacReader(TestBase):
         bbox = [[-100.0, -72.0, 105.0, -69.0]]
         items = list(collection.get_items(bbox=bbox))
         assert items is not None
-        assert len(items) == 2
+        assert len(items) > 0
 
     def test_get_items_with_temporal_extent(self) -> None:
         client = Client.open(STAC_URLS["PLANETARY-COMPUTER"])
@@ -88,7 +88,7 @@ class TestStacReader(TestBase):
         datetime = [["2006-12-01T00:00:00Z", "2006-12-27T03:00:00Z"]]
         items = list(collection.get_items(bbox=bbox, datetime=datetime))
         assert items is not None
-        assert len(items) == 4
+        assert len(items) > 0
 
     def test_get_items_with_multiple_bboxes_and_interval(self) -> None:
         client = Client.open(STAC_URLS["PLANETARY-COMPUTER"])
@@ -111,7 +111,7 @@ class TestStacReader(TestBase):
         datetime = [["2006-12-01T00:00:00Z", "2006-12-27T03:00:00Z"]]
         items = list(collection.get_items(bbox=bbox, datetime=datetime))
         assert items is not None
-        assert len(items) == 4
+        assert len(items) > 0
 
     def test_get_items_with_ids(self) -> None:
         client = Client.open(STAC_URLS["PLANETARY-COMPUTER"])
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacBatch.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacBatch.scala
index 5994c463fd..4ef53756d2 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacBatch.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacBatch.scala
@@ -19,16 +19,19 @@
 package org.apache.spark.sql.sedona_sql.io.stac
 
 import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper}
+import org.apache.spark.broadcast.Broadcast
 import org.apache.spark.sql.connector.read.{Batch, InputPartition, PartitionReaderFactory}
 import org.apache.spark.sql.execution.datasource.stac.TemporalFilter
 import org.apache.spark.sql.execution.datasources.parquet.{GeoParquetSpatialFilter, GeometryFieldMetaData}
 import org.apache.spark.sql.sedona_sql.io.stac.StacUtils.getNumPartitions
 import org.apache.spark.sql.types.StructType
+import org.apache.spark.util.SerializableConfiguration
 
 import java.time.LocalDateTime
 import java.time.format.DateTimeFormatterBuilder
 import java.time.temporal.ChronoField
 import scala.jdk.CollectionConverters._
+import scala.util.Random
 import scala.util.control.Breaks.breakable
 
 /**
@@ -40,12 +43,14 @@ import scala.util.control.Breaks.breakable
  * which are necessary for batch data processing.
  */
 case class StacBatch(
+    broadcastConf: Broadcast[SerializableConfiguration],
     stacCollectionUrl: String,
     stacCollectionJson: String,
     schema: StructType,
     opts: Map[String, String],
     spatialFilter: Option[GeoParquetSpatialFilter],
-    temporalFilter: Option[TemporalFilter])
+    temporalFilter: Option[TemporalFilter],
+    limitFilter: Option[Int])
     extends Batch {
 
   private val defaultItemsLimitPerRequest: Int = {
@@ -82,10 +87,16 @@ case class StacBatch(
     // Initialize the itemLinks array
     val itemLinks = scala.collection.mutable.ArrayBuffer[String]()
 
-    // Start the recursive collection of item links
-    val itemsLimitMax = opts.getOrElse("itemsLimitMax", "-1").toInt
+    // Get the maximum number of items to process
+    val itemsLimitMax = limitFilter match {
+      case Some(limit) if limit >= 0 => limit
+      case _ => opts.getOrElse("itemsLimitMax", "-1").toInt
+    }
     val checkItemsLimitMax = itemsLimitMax > 0
+
+    // Start the recursive collection of item links
     setItemMaxLeft(itemsLimitMax)
+
     collectItemLinks(stacCollectionBasePath, stacCollectionJson, itemLinks, checkItemsLimitMax)
 
     // Handle when the number of items is less than 1
@@ -109,8 +120,9 @@ case class StacBatch(
     // Determine how many items to put in each partition
     val partitionSize = Math.ceil(itemLinks.length.toDouble / numPartitions).toInt
 
-    // Group the item links into partitions
-    itemLinks
+    // Group the item links into partitions, but randomize first for better load balancing
+    Random
+      .shuffle(itemLinks)
       .grouped(partitionSize)
       .zipWithIndex
       .map { case (items, index) =>
@@ -229,7 +241,7 @@ case class StacBatch(
           } else if (rel == "items" && href.startsWith("http")) {
             // iterate through the items and check if the limit is reached (if needed)
             if (iterateItemsWithLimit(
-                itemUrl + "?limit=" + defaultItemsLimitPerRequest,
+                getItemLink(itemUrl, defaultItemsLimitPerRequest, spatialFilter, temporalFilter),
                 needCountNextItems)) return
           }
         }
@@ -256,6 +268,17 @@ case class StacBatch(
     }
   }
 
+  /** Adds an item link to the list of item links. */
+  def getItemLink(
+      itemUrl: String,
+      defaultItemsLimitPerRequest: Int,
+      spatialFilter: Option[GeoParquetSpatialFilter],
+      temporalFilter: Option[TemporalFilter]): String = {
+    val baseUrl = itemUrl + "?limit=" + defaultItemsLimitPerRequest
+    val urlWithFilters = StacUtils.addFiltersToUrl(baseUrl, spatialFilter, temporalFilter)
+    urlWithFilters
+  }
+
   /**
    * Filters a collection based on the provided spatial and temporal filters.
    *
@@ -361,6 +384,7 @@ case class StacBatch(
   override def createReaderFactory(): PartitionReaderFactory = { (partition: InputPartition) =>
     {
       new StacPartitionReader(
+        broadcastConf,
         partition.asInstanceOf[StacPartition],
         schema,
         opts,
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacDataSource.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacDataSource.scala
index dc2dc3e6dd..154adf8eca 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacDataSource.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacDataSource.scala
@@ -18,15 +18,16 @@
  */
 package org.apache.spark.sql.sedona_sql.io.stac
 
-import StacUtils.{inferStacSchema, updatePropertiesPromotedSchema}
 import org.apache.spark.sql.SparkSession
 import org.apache.spark.sql.connector.catalog.{Table, TableProvider}
 import org.apache.spark.sql.connector.expressions.Transform
 import org.apache.spark.sql.sedona_sql.UDT.GeometryUDT
 import org.apache.spark.sql.sedona_sql.io.geojson.GeoJSONUtils
+import org.apache.spark.sql.sedona_sql.io.stac.StacUtils.{inferStacSchema, updatePropertiesPromotedSchema}
 import org.apache.spark.sql.sources.DataSourceRegister
 import org.apache.spark.sql.types.StructType
 import org.apache.spark.sql.util.CaseInsensitiveStringMap
+import org.apache.spark.util.SerializableConfiguration
 
 import java.util
 import java.util.concurrent.ConcurrentHashMap
@@ -100,6 +101,7 @@ class StacDataSource() extends TableProvider with DataSourceRegister {
       partitioning: Array[Transform],
       properties: util.Map[String, String]): Table = {
     val opts = new CaseInsensitiveStringMap(properties)
+    val sparkSession = SparkSession.active
 
     val optsMap: Map[String, String] = opts.asCaseSensitiveMap().asScala.toMap ++ Map(
       "sessionLocalTimeZone" -> SparkSession.active.sessionState.conf.sessionLocalTimeZone,
@@ -107,17 +109,20 @@ class StacDataSource() extends TableProvider with DataSourceRegister {
       "defaultParallelism" -> SparkSession.active.sparkContext.defaultParallelism.toString,
       "maxPartitionItemFiles" -> SparkSession.active.conf
         .get("spark.sedona.stac.load.maxPartitionItemFiles", "0"),
-      "numPartitions" -> SparkSession.active.conf
+      "numPartitions" -> sparkSession.conf
         .get("spark.sedona.stac.load.numPartitions", "-1"),
       "itemsLimitMax" -> opts
         .asCaseSensitiveMap()
         .asScala
         .toMap
-        .get("itemsLimitMax")
-        .filter(_.toInt > 0)
-        .getOrElse(SparkSession.active.conf.get("spark.sedona.stac.load.itemsLimitMax", "-1")))
+        .getOrElse(
+          "itemsLimitMax",
+          sparkSession.conf.get("spark.sedona.stac.load.itemsLimitMax", "-1")))
     val stacCollectionJsonString = StacUtils.loadStacCollectionToJson(optsMap)
+    val hadoopConf = sparkSession.sessionState.newHadoopConfWithOptions(opts.asScala.toMap)
+    val broadcastedConf =
+      sparkSession.sparkContext.broadcast(new SerializableConfiguration(hadoopConf))
 
-    new StacTable(stacCollectionJson = stacCollectionJsonString, opts = optsMap)
+    new StacTable(stacCollectionJson = stacCollectionJsonString, opts = optsMap, broadcastedConf)
   }
 }
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacPartitionReader.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacPartitionReader.scala
index a545eb232f..b72ac526ca 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacPartitionReader.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacPartitionReader.scala
@@ -20,6 +20,7 @@ package org.apache.spark.sql.sedona_sql.io.stac
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.apache.hadoop.conf.Configuration
+import org.apache.spark.broadcast.Broadcast
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.catalyst.json.JSONOptionsInRead
 import org.apache.spark.sql.connector.read.PartitionReader
@@ -28,14 +29,16 @@ import org.apache.spark.sql.execution.datasources.PartitionedFile
 import org.apache.spark.sql.execution.datasources.json.JsonDataSource
 import org.apache.spark.sql.execution.datasources.parquet.GeoParquetSpatialFilter
 import org.apache.spark.sql.sedona_sql.io.geojson.{GeoJSONUtils, SparkCompatUtil}
-import org.apache.spark.sql.sedona_sql.io.stac.StacUtils.{buildOutDbRasterFields, promotePropertiesToTop}
+import org.apache.spark.sql.sedona_sql.io.stac.StacUtils.promotePropertiesToTop
 import org.apache.spark.sql.types.{StringType, StructType}
+import org.apache.spark.util.SerializableConfiguration
 
 import java.io.{File, PrintWriter}
 import java.lang.reflect.Constructor
 import scala.io.Source
 
 class StacPartitionReader(
+    broadcast: Broadcast[SerializableConfiguration],
     partition: StacPartition,
     schema: StructType,
     opts: Map[String, String],
@@ -120,8 +123,7 @@ class StacPartitionReader(
 
         rows.map(row => {
           val geometryConvertedRow = GeoJSONUtils.convertGeoJsonToGeometry(row, alteredSchema)
-          val rasterAddedRow = buildOutDbRasterFields(geometryConvertedRow, alteredSchema)
-          val propertiesPromotedRow = promotePropertiesToTop(rasterAddedRow, alteredSchema)
+          val propertiesPromotedRow = promotePropertiesToTop(geometryConvertedRow, alteredSchema)
           propertiesPromotedRow
         })
       } else {
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacScan.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacScan.scala
index 2edf082912..9ccc89fba8 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacScan.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacScan.scala
@@ -18,14 +18,19 @@
  */
 package org.apache.spark.sql.sedona_sql.io.stac
 
+import org.apache.spark.broadcast.Broadcast
 import org.apache.spark.sql.connector.read.{Batch, Scan}
 import org.apache.spark.sql.execution.datasource.stac.TemporalFilter
 import org.apache.spark.sql.execution.datasources.parquet.GeoParquetSpatialFilter
 import org.apache.spark.sql.internal.connector.SupportsMetadata
 import org.apache.spark.sql.sedona_sql.io.stac.StacUtils.{getFullCollectionUrl, inferStacSchema}
 import org.apache.spark.sql.types.StructType
+import org.apache.spark.util.SerializableConfiguration
 
-class StacScan(stacCollectionJson: String, opts: Map[String, String])
+class StacScan(
+    stacCollectionJson: String,
+    opts: Map[String, String],
+    broadcastConf: Broadcast[SerializableConfiguration])
     extends Scan
     with SupportsMetadata {
 
@@ -35,6 +40,8 @@ class StacScan(stacCollectionJson: String, opts: Map[String, String])
   // The temporal filter to be pushed down to the data source
   var temporalFilter: Option[TemporalFilter] = None
 
+  var limit: Option[Int] = None
+
   /**
    * Returns the schema of the data to be read.
    *
@@ -62,12 +69,14 @@ class StacScan(stacCollectionJson: String, opts: Map[String, String])
   override def toBatch: Batch = {
     val stacCollectionUrl = getFullCollectionUrl(opts)
     StacBatch(
+      broadcastConf,
       stacCollectionUrl,
       stacCollectionJson,
       readSchema(),
       opts,
       spatialFilter,
-      temporalFilter)
+      temporalFilter,
+      limit)
   }
 
   /**
@@ -90,6 +99,16 @@ class StacScan(stacCollectionJson: String, opts: Map[String, String])
     temporalFilter = Some(combineTemporalFilter)
   }
 
+  /**
+   * Sets the limit on the number of items to be read.
+   *
+   * @param n
+   *   The limit on the number of items to be read.
+   */
+  def setLimit(n: Int) = {
+    limit = Some(n)
+  }
+
   /**
    * Returns metadata about the data to be read.
    *
@@ -101,7 +120,8 @@ class StacScan(stacCollectionJson: String, opts: Map[String, String])
   override def getMetaData(): Map[String, String] = {
     Map(
       "PushedSpatialFilters" -> spatialFilter.map(_.toString).getOrElse("None"),
-      "PushedTemporalFilters" -> temporalFilter.map(_.toString).getOrElse("None"))
+      "PushedTemporalFilters" -> temporalFilter.map(_.toString).getOrElse("None"),
+      "Pushedimit" -> limit.map(_.toString).getOrElse("None"))
   }
 
   /**
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacScanBuilder.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacScanBuilder.scala
index ebaab87dda..0fcbde9b4d 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacScanBuilder.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacScanBuilder.scala
@@ -18,7 +18,9 @@
  */
 package org.apache.spark.sql.sedona_sql.io.stac
 
+import org.apache.spark.broadcast.Broadcast
 import org.apache.spark.sql.connector.read.{Scan, ScanBuilder}
+import org.apache.spark.util.SerializableConfiguration
 
 /**
  * The `StacScanBuilder` class represents the builder for creating a `Scan` instance in the
@@ -28,7 +30,11 @@ import org.apache.spark.sql.connector.read.{Scan, ScanBuilder}
  * bridge between Spark's data source API and the specific implementation of the STAC data read
  * operation.
  */
-class StacScanBuilder(stacCollectionJson: String, opts: Map[String, String]) extends ScanBuilder {
+class StacScanBuilder(
+    stacCollectionJson: String,
+    opts: Map[String, String],
+    broadcastConf: Broadcast[SerializableConfiguration])
+    extends ScanBuilder {
 
   /**
    * Builds and returns a `Scan` instance. The `Scan` defines the schema and batch reading methods
@@ -37,5 +43,5 @@ class StacScanBuilder(stacCollectionJson: String, opts: Map[String, String]) ext
    * @return
    *   A `Scan` instance that defines how to read STAC data.
    */
-  override def build(): Scan = new StacScan(stacCollectionJson, opts)
+  override def build(): Scan = new StacScan(stacCollectionJson, opts, broadcastConf)
 }
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacTable.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacTable.scala
index bd536f6de6..ca5f32663c 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacTable.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacTable.scala
@@ -18,6 +18,7 @@
  */
 package org.apache.spark.sql.sedona_sql.io.stac
 
+import org.apache.spark.broadcast.Broadcast
 import org.apache.spark.sql.connector.catalog.{SupportsRead, Table, TableCapability}
 import org.apache.spark.sql.connector.read.ScanBuilder
 import org.apache.spark.sql.sedona_sql.UDT.GeometryUDT
@@ -25,6 +26,7 @@ import org.apache.spark.sql.sedona_sql.io.geojson.GeoJSONUtils
 import org.apache.spark.sql.sedona_sql.io.stac.StacUtils.{inferStacSchema, updatePropertiesPromotedSchema}
 import org.apache.spark.sql.types._
 import org.apache.spark.sql.util.CaseInsensitiveStringMap
+import org.apache.spark.util.SerializableConfiguration
 
 import java.util.concurrent.ConcurrentHashMap
 
@@ -38,7 +40,10 @@ import java.util.concurrent.ConcurrentHashMap
  * @constructor
  *   Creates a new instance of the `StacTable` class.
  */
-class StacTable(stacCollectionJson: String, opts: Map[String, String])
+class StacTable(
+    stacCollectionJson: String,
+    opts: Map[String, String],
+    broadcastConf: Broadcast[SerializableConfiguration])
     extends Table
     with SupportsRead {
 
@@ -84,7 +89,7 @@ class StacTable(stacCollectionJson: String, opts: Map[String, String])
    *   A new instance of ScanBuilder.
    */
   override def newScanBuilder(options: CaseInsensitiveStringMap): ScanBuilder =
-    new StacScanBuilder(stacCollectionJson, opts)
+    new StacScanBuilder(stacCollectionJson, opts, broadcastConf)
 }
 
 object StacTable {
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacUtils.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacUtils.scala
index 508d6986b5..95c86c24c0 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacUtils.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacUtils.scala
@@ -22,9 +22,13 @@ import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.scala.DefaultScalaModule
 import org.apache.spark.sql.SparkSession
 import org.apache.spark.sql.catalyst.InternalRow
-import org.apache.spark.sql.catalyst.util.ArrayBasedMapData
-import org.apache.spark.sql.types.{MapType, StringType, StructField, StructType}
+import org.apache.spark.sql.execution.datasource.stac.TemporalFilter
+import org.apache.spark.sql.execution.datasources.parquet.GeoParquetSpatialFilter
+import org.apache.spark.sql.types.{StructField, StructType}
+import org.locationtech.jts.geom.Envelope
 
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
 import scala.io.Source
 
 object StacUtils {
@@ -133,9 +137,6 @@ object StacUtils {
     }
   }
 
-  /**
-   * Promote the properties field to the top level of the row.
-   */
   def promotePropertiesToTop(row: InternalRow, schema: StructType): InternalRow = {
     val propertiesIndex = schema.fieldIndex("properties")
     val propertiesStruct = schema("properties").dataType.asInstanceOf[StructType]
@@ -167,47 +168,6 @@ object StacUtils {
     StructType(newFields)
   }
 
-  /**
-   * Builds the output row with the raster field in the assets map.
-   *
-   * @param row
-   *   The input row.
-   * @param schema
-   *   The schema of the input row.
-   * @return
-   *   The output row with the raster field in the assets map.
-   */
-  def buildOutDbRasterFields(row: InternalRow, schema: StructType): InternalRow = {
-    val newValues = new Array[Any](schema.fields.length)
-
-    schema.fields.zipWithIndex.foreach {
-      case (StructField("assets", MapType(StringType, valueType: StructType, _), _, _), index) =>
-        val assetsMap = row.getMap(index)
-        if (assetsMap != null) {
-          val updatedAssets = assetsMap
-            .keyArray()
-            .array
-            .zip(assetsMap.valueArray().array)
-            .map { case (key, value) =>
-              val assetRow = value.asInstanceOf[InternalRow]
-              if (assetRow != null) {
-                key -> assetRow
-              } else {
-                key -> null
-              }
-            }
-            .toMap
-          newValues(index) = ArrayBasedMapData(updatedAssets)
-        } else {
-          newValues(index) = null
-        }
-      case (_, index) =>
-        newValues(index) = row.get(index, schema.fields(index).dataType)
-    }
-
-    InternalRow.fromSeq(newValues)
-  }
-
   /**
    * Returns the number of partitions to use for reading the data.
    *
@@ -241,4 +201,86 @@ object StacUtils {
       Math.max(1, Math.ceil(itemCount.toDouble / maxSplitFiles).toInt)
     }
   }
+
+  /** Returns the temporal filter string based on the temporal filter. */
+  def getFilterBBox(filter: GeoParquetSpatialFilter): String = {
+    def calculateUnionBBox(filter: GeoParquetSpatialFilter): Envelope = {
+      filter match {
+        case GeoParquetSpatialFilter.AndFilter(left, right) =>
+          val leftEnvelope = calculateUnionBBox(left)
+          val rightEnvelope = calculateUnionBBox(right)
+          leftEnvelope.expandToInclude(rightEnvelope)
+          leftEnvelope
+        case GeoParquetSpatialFilter.OrFilter(left, right) =>
+          val leftEnvelope = calculateUnionBBox(left)
+          val rightEnvelope = calculateUnionBBox(right)
+          leftEnvelope.expandToInclude(rightEnvelope)
+          leftEnvelope
+        case leaf: GeoParquetSpatialFilter.LeafFilter =>
+          leaf.queryWindow.getEnvelopeInternal
+      }
+    }
+
+    val unionEnvelope = calculateUnionBBox(filter)
+    s"bbox=${unionEnvelope.getMinX}%2C${unionEnvelope.getMinY}%2C${unionEnvelope.getMaxX}%2C${unionEnvelope.getMaxY}"
+  }
+
+  /** Returns the temporal filter string based on the temporal filter. */
+  def getFilterTemporal(filter: TemporalFilter): String = {
+    val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
+
+    def formatDateTime(dateTime: LocalDateTime): String = {
+      if (dateTime == null) ".." else dateTime.format(formatter)
+    }
+
+    def calculateUnionTemporal(filter: TemporalFilter): (LocalDateTime, LocalDateTime) = {
+      filter match {
+        case TemporalFilter.AndFilter(left, right) =>
+          val (leftStart, leftEnd) = calculateUnionTemporal(left)
+          val (rightStart, rightEnd) = calculateUnionTemporal(right)
+          val start =
+            if (leftStart == null || (rightStart != null && rightStart.isBefore(leftStart)))
+              rightStart
+            else leftStart
+          val end =
+            if (leftEnd == null || (rightEnd != null && rightEnd.isAfter(leftEnd))) rightEnd
+            else leftEnd
+          (start, end)
+        case TemporalFilter.OrFilter(left, right) =>
+          val (leftStart, leftEnd) = calculateUnionTemporal(left)
+          val (rightStart, rightEnd) = calculateUnionTemporal(right)
+          val start =
+            if (leftStart == null || (rightStart != null && rightStart.isBefore(leftStart)))
+              rightStart
+            else leftStart
+          val end =
+            if (leftEnd == null || (rightEnd != null && rightEnd.isAfter(leftEnd))) rightEnd
+            else leftEnd
+          (start, end)
+        case TemporalFilter.LessThanFilter(_, value) =>
+          (null, value)
+        case TemporalFilter.GreaterThanFilter(_, value) =>
+          (value, null)
+        case TemporalFilter.EqualFilter(_, value) =>
+          (value, value)
+      }
+    }
+
+    val (start, end) = calculateUnionTemporal(filter)
+    if (end == null) s"datetime=${formatDateTime(start)}/.."
+    else s"datetime=${formatDateTime(start)}/${formatDateTime(end)}"
+  }
+
+  /** Adds the spatial and temporal filters to the base URL. */
+  def addFiltersToUrl(
+      baseUrl: String,
+      spatialFilter: Option[GeoParquetSpatialFilter],
+      temporalFilter: Option[TemporalFilter]): String = {
+    val spatialFilterStr = spatialFilter.map(StacUtils.getFilterBBox).getOrElse("")
+    val temporalFilterStr = temporalFilter.map(StacUtils.getFilterTemporal).getOrElse("")
+
+    val filters = Seq(spatialFilterStr, temporalFilterStr).filter(_.nonEmpty).mkString("&")
+    val urlWithFilters = if (filters.nonEmpty) s"&$filters" else ""
+    s"$baseUrl$urlWithFilters"
+  }
 }
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/optimization/SpatialTemporalFilterPushDownForStacScan.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/optimization/SpatialTemporalFilterPushDownForStacScan.scala
index 566d368d69..c32af552b3 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/optimization/SpatialTemporalFilterPushDownForStacScan.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/optimization/SpatialTemporalFilterPushDownForStacScan.scala
@@ -20,7 +20,7 @@ package org.apache.spark.sql.sedona_sql.optimization
 
 import org.apache.spark.sql.SparkSession
 import org.apache.spark.sql.catalyst.expressions.{And, EqualTo, Expression, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, Literal, Or, SubqueryExpression}
-import org.apache.spark.sql.catalyst.plans.logical.{Filter, LogicalPlan}
+import org.apache.spark.sql.catalyst.plans.logical.{Filter, GlobalLimit, LocalLimit, LogicalPlan}
 import org.apache.spark.sql.connector.catalog.CatalogV2Implicits.parseColumnPath
 import org.apache.spark.sql.execution.datasource.stac.TemporalFilter
 import org.apache.spark.sql.execution.datasource.stac.TemporalFilter.{AndFilter => TemporalAndFilter}
@@ -79,10 +79,25 @@ class SpatialTemporalFilterPushDownForStacScan(sparkSession: SparkSession)
             filter.copy()
           }
           filter.copy()
+        case lr: DataSourceV2ScanRelation if isStacScanRelation(lr) =>
+          val scan = lr.scan.asInstanceOf[StacScan]
+          val limit = extractLimit(plan)
+          limit match {
+            case Some(n) => scan.setLimit(n)
+            case None =>
+          }
+          lr
       }
     }
   }
 
+  def extractLimit(plan: LogicalPlan): Option[Int] = {
+    plan.collectFirst {
+      case GlobalLimit(Literal(limit: Int, _), _) => limit
+      case LocalLimit(Literal(limit: Int, _), _) => limit
+    }
+  }
+
   private def isStacScanRelation(lr: DataSourceV2ScanRelation): Boolean =
     lr.scan.isInstanceOf[StacScan]
 
diff --git a/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacBatchTest.scala b/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacBatchTest.scala
index 0765b3950f..612ce2bb52 100644
--- a/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacBatchTest.scala
+++ b/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacBatchTest.scala
@@ -22,6 +22,8 @@ import org.apache.sedona.sql.TestBaseScala
 import org.apache.spark.sql.connector.read.InputPartition
 import org.apache.spark.sql.types.StructType
 
+import java.time.format.DateTimeFormatter
+import java.time.{LocalDate, ZoneOffset}
 import scala.io.Source
 import scala.collection.mutable
 
@@ -40,6 +42,41 @@ class StacBatchTest extends TestBaseScala {
     }
   }
 
+  it("collectItemLinks should collect correct item links") {
+    val collectionUrl =
+      "https://earth-search.aws.element84.com/v1/collections/sentinel-2-pre-c1-l2a"
+    val stacCollectionJson = StacUtils.loadStacCollectionToJson(collectionUrl)
+    val opts = mutable
+      .Map(
+        "itemsLimitMax" -> "1000",
+        "itemsLimitPerRequest" -> "200",
+        "itemsLoadProcessReportThreshold" -> "1000000")
+      .toMap
+
+    val stacBatch =
+      StacBatch(
+        null,
+        collectionUrl,
+        stacCollectionJson,
+        StructType(Seq()),
+        opts,
+        None,
+        None,
+        None)
+    stacBatch.setItemMaxLeft(1000)
+    val itemLinks = mutable.ArrayBuffer[String]()
+    val needCountNextItems = true
+
+    val startTime = System.nanoTime()
+    stacBatch.collectItemLinks(collectionUrl, stacCollectionJson, itemLinks, needCountNextItems)
+    val endTime = System.nanoTime()
+    val duration = (endTime - startTime) / 1e6 // Convert to milliseconds
+
+    assert(itemLinks.nonEmpty)
+    assert(itemLinks.length == 5)
+    assert(duration > 0)
+  }
+
   it("planInputPartitions should create correct number of partitions") {
     val stacCollectionJson =
       """
@@ -48,18 +85,26 @@ class StacBatchTest extends TestBaseScala {
         |  "id": "sample-collection",
         |  "description": "A sample STAC collection",
         |  "links": [
-        |    {"rel": "item", "href": "https://path/to/item1.json"},
-        |    {"rel": "item", "href": "https://path/to/item2.json"},
-        |    {"rel": "item", "href": "https://path/to/item3.json"}
+        |    {"rel": "item", "href": "https://storage.googleapis.com/cfo-public/vegetation/California-Vegetation-CanopyBaseHeight-2016-Summer-00010m.json"},
+        |    {"rel": "item", "href": "https://storage.googleapis.com/cfo-public/vegetation/California-Vegetation-CanopyBaseHeight-2016-Summer-00010m.json"},
+        |    {"rel": "item", "href": "https://storage.googleapis.com/cfo-public/vegetation/California-Vegetation-CanopyBaseHeight-2016-Summer-00010m.json"}
         |  ]
         |}
       """.stripMargin
 
-    val opts = mutable.Map("numPartitions" -> "2").toMap
-    val collectionUrl = "https://path/to/collection.json"
+    val opts = mutable.Map("numPartitions" -> "2", "itemsLimitMax" -> "20").toMap
+    val collectionUrl = "https://storage.googleapis.com/cfo-public/vegetation/collection.json"
 
     val stacBatch =
-      StacBatch(collectionUrl, stacCollectionJson, StructType(Seq()), opts, None, None)
+      StacBatch(
+        null,
+        collectionUrl,
+        stacCollectionJson,
+        StructType(Seq()),
+        opts,
+        None,
+        None,
+        None)
     val partitions: Array[InputPartition] = stacBatch.planInputPartitions()
 
     assert(partitions.length == 2)
@@ -75,11 +120,19 @@ class StacBatchTest extends TestBaseScala {
         |}
       """.stripMargin
 
-    val opts = mutable.Map("numPartitions" -> "2").toMap
+    val opts = mutable.Map("numPartitions" -> "2", "itemsLimitMax" -> "20").toMap
     val collectionUrl = "https://path/to/collection.json"
 
     val stacBatch =
-      StacBatch(collectionUrl, stacCollectionJson, StructType(Seq()), opts, None, None)
+      StacBatch(
+        null,
+        collectionUrl,
+        stacCollectionJson,
+        StructType(Seq()),
+        opts,
+        None,
+        None,
+        None)
     val partitions: Array[InputPartition] = stacBatch.planInputPartitions()
 
     assert(partitions.isEmpty)
@@ -88,11 +141,19 @@ class StacBatchTest extends TestBaseScala {
   it("planInputPartitions should create correct number of partitions with real collection.json") {
     val rootJsonFile = "datasource_stac/collection.json"
     val stacCollectionJson = loadJsonFromResource(rootJsonFile)
-    val opts = mutable.Map("numPartitions" -> "3").toMap
+    val opts = mutable.Map("numPartitions" -> "3", "itemsLimitMax" -> "20").toMap
     val collectionUrl = getAbsolutePathOfResource(rootJsonFile)
 
     val stacBatch =
-      StacBatch(collectionUrl, stacCollectionJson, StructType(Seq()), opts, None, None)
+      StacBatch(
+        null,
+        collectionUrl,
+        stacCollectionJson,
+        StructType(Seq()),
+        opts,
+        None,
+        None,
+        None)
     val partitions: Array[InputPartition] = stacBatch.planInputPartitions()
 
     assert(partitions.length == 3)
diff --git a/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacDataSourceTest.scala b/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacDataSourceTest.scala
index 2cab18b694..5603b3e551 100644
--- a/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacDataSourceTest.scala
+++ b/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacDataSourceTest.scala
@@ -19,8 +19,8 @@
 package org.apache.spark.sql.sedona_sql.io.stac
 
 import org.apache.sedona.sql.TestBaseScala
-import org.apache.spark.sql.sedona_sql.UDT.GeometryUDT
-import org.apache.spark.sql.types.{ArrayType, DoubleType, MapType, StringType, StructField, StructType, TimestampType}
+import org.apache.spark.sql.sedona_sql.UDT.{GeometryUDT, RasterUDT}
+import org.apache.spark.sql.types._
 
 class StacDataSourceTest extends TestBaseScala {
 
@@ -88,7 +88,7 @@ class StacDataSourceTest extends TestBaseScala {
     val dfSelect = sparkSession.sql(
       "SELECT id, geometry " +
         "FROM STACTBL " +
-        "WHERE st_contains(ST_GeomFromText('POLYGON((17 10, 18 10, 18 11, 17 11, 17 10))'), geometry)")
+        "WHERE st_intersects(ST_GeomFromText('POLYGON((17 10, 18 10, 18 11, 17 11, 17 10))'), geometry)")
 
     val physicalPlan = dfSelect.queryExecution.executedPlan.toString()
     assert(physicalPlan.contains(
@@ -102,10 +102,11 @@ class StacDataSourceTest extends TestBaseScala {
     val dfStac = sparkSession.read.format("stac").load(STAC_COLLECTION_LOCAL)
     dfStac.createOrReplaceTempView("STACTBL")
 
-    val dfSelect = sparkSession.sql("SELECT id, datetime as dt, geometry, bbox " +
-      "FROM STACTBL " +
-      "WHERE datetime BETWEEN '2020-01-01T00:00:00Z' AND '2020-12-13T00:00:00Z' " +
-      "AND st_contains(ST_GeomFromText('POLYGON((17 10, 18 10, 18 11, 17 11, 17 10))'), geometry)")
+    val dfSelect = sparkSession.sql(
+      "SELECT id, datetime as dt, geometry, bbox " +
+        "FROM STACTBL " +
+        "WHERE datetime BETWEEN '2020-01-01T00:00:00Z' AND '2020-12-13T00:00:00Z' " +
+        "AND st_intersects(ST_GeomFromText('POLYGON((17 10, 18 10, 18 11, 17 11, 17 10))'), geometry)")
 
     val physicalPlan = dfSelect.queryExecution.executedPlan.toString()
     assert(physicalPlan.contains(
@@ -137,11 +138,12 @@ class StacDataSourceTest extends TestBaseScala {
     val dfStac = sparkSession.read.format("stac").load(STAC_COLLECTION_LOCAL)
     dfStac.createOrReplaceTempView("STACTBL")
 
-    val dfSelect = sparkSession.sql("SELECT id, datetime as dt, geometry, bbox " +
-      "FROM STACTBL " +
-      "WHERE id = 'some-id' " +
-      "AND datetime BETWEEN '2020-01-01T00:00:00Z' AND '2020-12-13T00:00:00Z' " +
-      "AND st_contains(ST_GeomFromText('POLYGON((17 10, 18 10, 18 11, 17 11, 17 10))'), geometry)")
+    val dfSelect = sparkSession.sql(
+      "SELECT id, datetime as dt, geometry, bbox " +
+        "FROM STACTBL " +
+        "WHERE id = 'some-id' " +
+        "AND datetime BETWEEN '2020-01-01T00:00:00Z' AND '2020-12-13T00:00:00Z' " +
+        "AND st_intersects(ST_GeomFromText('POLYGON((17 10, 18 10, 18 11, 17 11, 17 10))'), geometry)")
 
     val physicalPlan = dfSelect.queryExecution.executedPlan.toString()
     assert(physicalPlan.contains(
diff --git a/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacPartitionReaderTest.scala b/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacPartitionReaderTest.scala
index fc6f8dcfdc..428327e7cc 100644
--- a/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacPartitionReaderTest.scala
+++ b/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacPartitionReaderTest.scala
@@ -18,8 +18,10 @@
  */
 package org.apache.spark.sql.sedona_sql.io.stac
 
+import org.apache.hadoop.conf.Configuration
 import org.apache.sedona.sql.TestBaseScala
 import org.apache.spark.sql.catalyst.InternalRow
+import org.apache.spark.util.SerializableConfiguration
 
 import scala.jdk.CollectionConverters._
 
@@ -40,6 +42,7 @@ class StacPartitionReaderTest extends TestBaseScala {
     val partition = StacPartition(0, jsonFiles, Map.empty[String, String].asJava)
     val reader =
       new StacPartitionReader(
+        sparkSession.sparkContext.broadcast(new SerializableConfiguration(new Configuration())),
         partition,
         StacTable.SCHEMA_V1_1_0,
         Map.empty[String, String],
@@ -61,6 +64,7 @@ class StacPartitionReaderTest extends TestBaseScala {
     val partition = StacPartition(0, jsonFiles, Map.empty[String, String].asJava)
     val reader =
       new StacPartitionReader(
+        sparkSession.sparkContext.broadcast(new SerializableConfiguration(new Configuration())),
         partition,
         StacTable.SCHEMA_V1_1_0,
         Map.empty[String, String],
@@ -82,6 +86,7 @@ class StacPartitionReaderTest extends TestBaseScala {
     val partition = StacPartition(0, jsonFiles, Map.empty[String, String].asJava)
     val reader =
       new StacPartitionReader(
+        sparkSession.sparkContext.broadcast(new SerializableConfiguration(new Configuration())),
         partition,
         StacTable.SCHEMA_V1_1_0,
         Map.empty[String, String],
diff --git a/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacUtilsTest.scala b/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacUtilsTest.scala
index 75542c760d..d2940abf41 100644
--- a/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacUtilsTest.scala
+++ b/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacUtilsTest.scala
@@ -21,16 +21,25 @@ package org.apache.spark.sql.sedona_sql.io.stac
 import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper}
 import com.fasterxml.jackson.module.scala.DefaultScalaModule
 import org.apache.hadoop.fs.{FileSystem, Path}
+import org.apache.sedona.core.spatialOperator.SpatialPredicate
 import org.apache.spark.sql.SparkSession
-import org.apache.spark.sql.sedona_sql.io.stac.StacUtils.getNumPartitions
+import org.apache.spark.sql.execution.datasource.stac.TemporalFilter
+import org.apache.spark.sql.execution.datasources.parquet.GeoParquetSpatialFilter
+import org.apache.spark.sql.sedona_sql.io.stac.StacUtils.{getFilterBBox, getFilterTemporal, getNumPartitions}
+import org.locationtech.jts.geom.{Envelope, GeometryFactory, Polygon}
+import org.locationtech.jts.io.WKTReader
 import org.scalatest.funsuite.AnyFunSuite
 
 import java.io.{File, PrintWriter}
+import java.time.LocalDateTime
 import scala.io.Source
 import scala.jdk.CollectionConverters._
 
 class StacUtilsTest extends AnyFunSuite {
 
+  val geometryFactory = new GeometryFactory()
+  val wktReader = new WKTReader(geometryFactory)
+
   test("getStacCollectionBasePath should return base URL for HTTP URL") {
     val opts = Map("path" -> "https://service_url/collections/collection.json")
     val result = StacUtils.getStacCollectionBasePath(opts)
@@ -591,4 +600,121 @@ class StacUtilsTest extends AnyFunSuite {
       writer.close()
     }
   }
+
+  test("getFilterBBox with LeafFilter") {
+    val queryWindow: Polygon =
+      wktReader.read("POLYGON((10 10, 20 10, 20 20, 10 20, 10 10))").asInstanceOf[Polygon]
+    val leafFilter =
+      GeoParquetSpatialFilter.LeafFilter("geometry", SpatialPredicate.INTERSECTS, queryWindow)
+    val bbox = getFilterBBox(leafFilter)
+    assert(bbox == "bbox=10.0%2C10.0%2C20.0%2C20.0")
+  }
+
+  test("getFilterBBox with AndFilter") {
+    val queryWindow1: Polygon =
+      wktReader.read("POLYGON((10 10, 20 10, 20 20, 10 20, 10 10))").asInstanceOf[Polygon]
+    val queryWindow2: Polygon =
+      wktReader.read("POLYGON((30 30, 40 30, 40 40, 30 40, 30 30))").asInstanceOf[Polygon]
+    val leafFilter1 =
+      GeoParquetSpatialFilter.LeafFilter("geometry", SpatialPredicate.INTERSECTS, queryWindow1)
+    val leafFilter2 =
+      GeoParquetSpatialFilter.LeafFilter("geometry", SpatialPredicate.INTERSECTS, queryWindow2)
+    val andFilter = GeoParquetSpatialFilter.AndFilter(leafFilter1, leafFilter2)
+    val bbox = getFilterBBox(andFilter)
+    assert(bbox == "bbox=10.0%2C10.0%2C40.0%2C40.0")
+  }
+
+  test("getFilterBBox with OrFilter") {
+    val queryWindow1: Polygon =
+      wktReader.read("POLYGON((10 10, 20 10, 20 20, 10 20, 10 10))").asInstanceOf[Polygon]
+    val queryWindow2: Polygon =
+      wktReader.read("POLYGON((30 30, 40 30, 40 40, 30 40, 30 30))").asInstanceOf[Polygon]
+    val leafFilter1 =
+      GeoParquetSpatialFilter.LeafFilter("geometry", SpatialPredicate.INTERSECTS, queryWindow1)
+    val leafFilter2 =
+      GeoParquetSpatialFilter.LeafFilter("geometry", SpatialPredicate.INTERSECTS, queryWindow2)
+    val orFilter = GeoParquetSpatialFilter.OrFilter(leafFilter1, leafFilter2)
+    val bbox = getFilterBBox(orFilter)
+    assert(bbox == "bbox=10.0%2C10.0%2C40.0%2C40.0")
+  }
+
+  test("getFilterTemporal with LessThanFilter") {
+    val dateTime = LocalDateTime.parse("2025-03-07T00:00:00")
+    val filter = TemporalFilter.LessThanFilter("timestamp", dateTime)
+    val result = getFilterTemporal(filter)
+    assert(result == "datetime=../2025-03-07T00:00:00.000Z")
+  }
+
+  test("getFilterTemporal with GreaterThanFilter") {
+    val dateTime = LocalDateTime.parse("2025-03-06T00:00:00")
+    val filter = TemporalFilter.GreaterThanFilter("timestamp", dateTime)
+    val result = getFilterTemporal(filter)
+    assert(result == "datetime=2025-03-06T00:00:00.000Z/..")
+  }
+
+  test("getFilterTemporal with EqualFilter") {
+    val dateTime = LocalDateTime.parse("2025-03-06T00:00:00")
+    val filter = TemporalFilter.EqualFilter("timestamp", dateTime)
+    val result = getFilterTemporal(filter)
+    assert(result == "datetime=2025-03-06T00:00:00.000Z/2025-03-06T00:00:00.000Z")
+  }
+
+  test("getFilterTemporal with AndFilter") {
+    val dateTime1 = LocalDateTime.parse("2025-03-06T00:00:00")
+    val dateTime2 = LocalDateTime.parse("2025-03-07T00:00:00")
+    val filter1 = TemporalFilter.GreaterThanFilter("timestamp", dateTime1)
+    val filter2 = TemporalFilter.LessThanFilter("timestamp", dateTime2)
+    val andFilter = TemporalFilter.AndFilter(filter1, filter2)
+    val result = getFilterTemporal(andFilter)
+    assert(result == "datetime=2025-03-06T00:00:00.000Z/2025-03-07T00:00:00.000Z")
+  }
+
+  test("getFilterTemporal with OrFilter") {
+    val dateTime1 = LocalDateTime.parse("2025-03-06T00:00:00")
+    val dateTime2 = LocalDateTime.parse("2025-03-07T00:00:00")
+    val filter1 = TemporalFilter.GreaterThanFilter("timestamp", dateTime1)
+    val filter2 = TemporalFilter.LessThanFilter("timestamp", dateTime2)
+    val orFilter = TemporalFilter.OrFilter(filter1, filter2)
+    val result = getFilterTemporal(orFilter)
+    assert(result == "datetime=2025-03-06T00:00:00.000Z/2025-03-07T00:00:00.000Z")
+  }
+
+  test("addFiltersToUrl with no filters") {
+    val baseUrl = "http://example.com/stac"
+    val result = StacUtils.addFiltersToUrl(baseUrl, None, None)
+    assert(result == "http://example.com/stac")
+  }
+
+  test("addFiltersToUrl with spatial filter") {
+    val baseUrl = "http://example.com/stac"
+    val envelope = new Envelope(1.0, 2.0, 3.0, 4.0)
+    val queryWindow = geometryFactory.toGeometry(envelope)
+    val spatialFilter = Some(
+      GeoParquetSpatialFilter.LeafFilter("geometry", SpatialPredicate.INTERSECTS, queryWindow))
+    val result = StacUtils.addFiltersToUrl(baseUrl, spatialFilter, None)
+    val expectedUrl = s"$baseUrl&bbox=1.0%2C3.0%2C2.0%2C4.0"
+    assert(result == expectedUrl)
+  }
+
+  test("addFiltersToUrl with temporal filter") {
+    val baseUrl = "http://example.com/stac"
+    val temporalFilter = Some(
+      TemporalFilter.GreaterThanFilter("timestamp", LocalDateTime.parse("2025-03-06T00:00:00")))
+    val result = StacUtils.addFiltersToUrl(baseUrl, None, temporalFilter)
+    val expectedUrl = s"$baseUrl&datetime=2025-03-06T00:00:00.000Z/.."
+    assert(result == expectedUrl)
+  }
+
+  test("addFiltersToUrl with both spatial and temporal filters") {
+    val baseUrl = "http://example.com/stac"
+    val envelope = new Envelope(1.0, 2.0, 3.0, 4.0)
+    val queryWindow = geometryFactory.toGeometry(envelope)
+    val spatialFilter = Some(
+      GeoParquetSpatialFilter.LeafFilter("geometry", SpatialPredicate.INTERSECTS, queryWindow))
+    val temporalFilter = Some(
+      TemporalFilter.GreaterThanFilter("timestamp", LocalDateTime.parse("2025-03-06T00:00:00")))
+    val result = StacUtils.addFiltersToUrl(baseUrl, spatialFilter, temporalFilter)
+    val expectedUrl = s"$baseUrl&bbox=1.0%2C3.0%2C2.0%2C4.0&datetime=2025-03-06T00:00:00.000Z/.."
+    assert(result == expectedUrl)
+  }
 }


(sedona) 09/35: [SEDONA-704] Add the grid extension to the stac reader (#1883)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 173f28ce96ac7e9cdc687984d4604b40d92f6ee8
Author: Feng Zhang <fe...@wherobots.com>
AuthorDate: Thu Mar 27 21:56:44 2025 -0700

    [SEDONA-704] Add the grid extension to the stac reader (#1883)
    
    * [SEDONA-704] Add the grid extension to the stac reader
    
    * In Scala 2.13, the standard library collections were restructured, and scala.collection.Seq is now different from Seq.
    
    * Update spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacDataSourceTest.scala
    
    ---------
    
    Co-authored-by: Jia Yu <ji...@wherobots.com>
---
 docs/tutorial/files/stac-sedona-spark.md           |   1 +
 .../sql/sedona_sql/io/stac/StacExtension.scala     |  57 ++++++++++
 .../spark/sql/sedona_sql/io/stac/StacUtils.scala   |  69 +++++++++++-
 .../resources/datasource_stac/extended-item.json   |   3 +-
 .../sedona_sql/io/stac/StacDataSourceTest.scala    | 120 ++++++++++++++-------
 5 files changed, 206 insertions(+), 44 deletions(-)

diff --git a/docs/tutorial/files/stac-sedona-spark.md b/docs/tutorial/files/stac-sedona-spark.md
index aff0e29f0f..864b6ef9d6 100644
--- a/docs/tutorial/files/stac-sedona-spark.md
+++ b/docs/tutorial/files/stac-sedona-spark.md
@@ -77,6 +77,7 @@ root
  |    |-- element: string (containsNull = true)
  |-- constellation: string (nullable = true)
  |-- mission: string (nullable = true)
+ |-- grid:code: string (nullable = true)
  |-- gsd: double (nullable = true)
  |-- collection: string (nullable = true)
  |-- links: array (nullable = true)
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacExtension.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacExtension.scala
new file mode 100644
index 0000000000..c1f78268ac
--- /dev/null
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacExtension.scala
@@ -0,0 +1,57 @@
+/*
+ * 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.spark.sql.sedona_sql.io.stac
+
+import org.apache.spark.sql.types.{StringType, StructField, StructType}
+
+/**
+ * Defines a STAC extension with its schema and property mappings
+ *
+ * @param name
+ *   The name of the STAC extension (e.g., "eo", "proj")
+ * @param schema
+ *   The schema for the extension properties
+ * @param propertyMappings
+ *   Mapping from original property path to top-level property name
+ */
+case class StacExtension(name: String, schema: StructType)
+
+object StacExtension {
+
+  /**
+   * Returns an array of STAC extension definitions, each containing:
+   *   - Extension name
+   *   - Extension schema
+   *   - Mapping from original property paths to promoted top-level property names
+   *
+   * @return
+   *   Array of StacExtension definitions
+   */
+  def getStacExtensionDefinitions(): Array[StacExtension] = {
+    Array(
+      // Grid extension - https://stac-extensions.github.io/grid/v1.1.0/schema.json
+      StacExtension(
+        name = "grid",
+        // Schema for the grid extension, add all required fields here
+        schema = StructType(Seq(StructField("grid:code", StringType, nullable = true))))
+
+      // Add other extensions here...
+    )
+  }
+}
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacUtils.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacUtils.scala
index 95c86c24c0..cfb0d33abc 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacUtils.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/io/stac/StacUtils.scala
@@ -24,7 +24,7 @@ import org.apache.spark.sql.SparkSession
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.execution.datasource.stac.TemporalFilter
 import org.apache.spark.sql.execution.datasources.parquet.GeoParquetSpatialFilter
-import org.apache.spark.sql.types.{StructField, StructType}
+import org.apache.spark.sql.types.{MetadataBuilder, StructField, StructType}
 import org.locationtech.jts.geom.Envelope
 
 import java.time.LocalDateTime
@@ -129,14 +129,79 @@ object StacUtils {
     val stacVersion = collection.get("stac_version").asText()
 
     // Return the corresponding schema based on the stac_version
-    stacVersion match {
+    val coreSchema = stacVersion match {
       case "1.0.0" => StacTable.SCHEMA_V1_0_0
       case version if version.matches("1\\.[1-9]\\d*\\.\\d*") => StacTable.SCHEMA_V1_1_0
       // Add more cases here for other versions if needed
       case _ => throw new IllegalArgumentException(s"Unsupported STAC version: $stacVersion")
     }
+    val extensions = StacExtension.getStacExtensionDefinitions()
+    val schemaWithExtensions = addExtensionFieldsToSchema(coreSchema, extensions)
+    schemaWithExtensions
   }
 
+  /**
+   * Adds STAC extension fields to the properties field in the schema
+   *
+   * @param schema
+   *   The base STAC schema to enhance
+   * @param extensions
+   *   Array of STAC extension definitions
+   * @return
+   *   Enhanced schema with extension fields added to the properties struct
+   */
+  def addExtensionFieldsToSchema(
+      schema: StructType,
+      extensions: Array[StacExtension]): StructType = {
+    // Find the properties field in the schema
+    val propertiesFieldOpt = schema.fields.find(_.name == "properties")
+
+    if (propertiesFieldOpt.isEmpty) {
+      // If there's no properties field, return the original schema
+      return schema
+    }
+
+    // Get the properties field and its struct type
+    val propertiesField = propertiesFieldOpt.get
+    val propertiesStruct = propertiesField.dataType.asInstanceOf[StructType]
+
+    // Create extension fields with metadata indicating their source
+    val extensionFields = extensions.flatMap { extension =>
+      extension.schema.fields.map { field =>
+        StructField(
+          field.name,
+          field.dataType,
+          field.nullable,
+          new MetadataBuilder()
+            .withMetadata(field.metadata)
+            .putString("stac_extension", extension.name)
+            .build())
+      }
+    }
+
+    // Create a new properties struct that includes the extension fields
+    val updatedPropertiesStruct = StructType(propertiesStruct.fields ++ extensionFields)
+
+    // Create a new properties field with the updated struct
+    val updatedPropertiesField = StructField(
+      propertiesField.name,
+      updatedPropertiesStruct,
+      propertiesField.nullable,
+      propertiesField.metadata)
+
+    // Replace the properties field in the schema
+    val updatedFields = schema.fields.map {
+      case field if field.name == "properties" => updatedPropertiesField
+      case field => field
+    }
+
+    // Return the schema with the updated properties field
+    StructType(updatedFields)
+  }
+
+  /**
+   * Promote the properties field to the top level of the row.
+   */
   def promotePropertiesToTop(row: InternalRow, schema: StructType): InternalRow = {
     val propertiesIndex = schema.fieldIndex("properties")
     val propertiesStruct = schema("properties").dataType.asInstanceOf[StructType]
diff --git a/spark/common/src/test/resources/datasource_stac/extended-item.json b/spark/common/src/test/resources/datasource_stac/extended-item.json
index b5f3a0a9df..14857da6b1 100644
--- a/spark/common/src/test/resources/datasource_stac/extended-item.json
+++ b/spark/common/src/test/resources/datasource_stac/extended-item.json
@@ -58,6 +58,7 @@
       "cool_sensor_v2"
     ],
     "gsd": 0.66,
+    "grid:code": "MSIN-2506",
     "eo:cloud_cover": 1.2,
     "eo:snow_cover": 0,
     "statistics": {
@@ -207,4 +208,4 @@
       "title": "Satellite Ephemeris Metadata"
     }
   }
-}
\ No newline at end of file
+}
diff --git a/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacDataSourceTest.scala b/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacDataSourceTest.scala
index 5603b3e551..da9ff573e3 100644
--- a/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacDataSourceTest.scala
+++ b/spark/common/src/test/scala/org/apache/spark/sql/sedona_sql/io/stac/StacDataSourceTest.scala
@@ -19,7 +19,7 @@
 package org.apache.spark.sql.sedona_sql.io.stac
 
 import org.apache.sedona.sql.TestBaseScala
-import org.apache.spark.sql.sedona_sql.UDT.{GeometryUDT, RasterUDT}
+import org.apache.spark.sql.sedona_sql.UDT.GeometryUDT
 import org.apache.spark.sql.types._
 
 class StacDataSourceTest extends TestBaseScala {
@@ -41,6 +41,14 @@ class StacDataSourceTest extends TestBaseScala {
     assert(rowCount > 0)
   }
 
+  it("basic df load from local file with extensions should work") {
+    val dfStac = sparkSession.read.format("stac").load(STAC_COLLECTION_LOCAL)
+    // Filter rows where grid:code equals "MSIN-2506"
+    val filteredDf = dfStac.filter(dfStac.col("grid:code") === "MSIN-2506")
+    val rowCount = filteredDf.count()
+    assert(rowCount > 0)
+  }
+
   it("basic df load from remote service endpoints should work") {
     STAC_COLLECTION_REMOTE.foreach { endpoint =>
       val dfStac = sparkSession.read.format("stac").load(endpoint)
@@ -156,54 +164,84 @@ class StacDataSourceTest extends TestBaseScala {
   }
 
   def assertSchema(actualSchema: StructType): Unit = {
-    val expectedSchema = StructType(
-      Seq(
-        StructField("stac_version", StringType, nullable = false),
-        StructField(
-          "stac_extensions",
-          ArrayType(StringType, containsNull = true),
-          nullable = true),
-        StructField("type", StringType, nullable = false),
-        StructField("id", StringType, nullable = false),
-        StructField("bbox", ArrayType(DoubleType, containsNull = true), nullable = true),
-        StructField("geometry", new GeometryUDT(), nullable = true),
-        StructField("title", StringType, nullable = true),
-        StructField("description", StringType, nullable = true),
-        StructField("datetime", TimestampType, nullable = true),
-        StructField("start_datetime", TimestampType, nullable = true),
-        StructField("end_datetime", TimestampType, nullable = true),
-        StructField("created", TimestampType, nullable = true),
-        StructField("updated", TimestampType, nullable = true),
-        StructField("platform", StringType, nullable = true),
-        StructField("instruments", ArrayType(StringType, containsNull = true), nullable = true),
-        StructField("constellation", StringType, nullable = true),
-        StructField("mission", StringType, nullable = true),
-        StructField("gsd", DoubleType, nullable = true),
-        StructField("collection", StringType, nullable = true),
-        StructField(
-          "links",
-          ArrayType(
-            StructType(Seq(
+    // Base STAC fields that should always be present
+    val baseFields = Seq(
+      StructField("stac_version", StringType, nullable = false),
+      StructField("stac_extensions", ArrayType(StringType, containsNull = true), nullable = true),
+      StructField("type", StringType, nullable = false),
+      StructField("id", StringType, nullable = false),
+      StructField("bbox", ArrayType(DoubleType, containsNull = true), nullable = true),
+      StructField("geometry", new GeometryUDT(), nullable = true),
+      StructField("title", StringType, nullable = true),
+      StructField("description", StringType, nullable = true),
+      StructField("datetime", TimestampType, nullable = true),
+      StructField("start_datetime", TimestampType, nullable = true),
+      StructField("end_datetime", TimestampType, nullable = true),
+      StructField("created", TimestampType, nullable = true),
+      StructField("updated", TimestampType, nullable = true),
+      StructField("platform", StringType, nullable = true),
+      StructField("instruments", ArrayType(StringType, containsNull = true), nullable = true),
+      StructField("constellation", StringType, nullable = true),
+      StructField("mission", StringType, nullable = true),
+      StructField("gsd", DoubleType, nullable = true),
+      StructField("collection", StringType, nullable = true),
+      StructField(
+        "links",
+        ArrayType(
+          StructType(
+            Seq(
               StructField("rel", StringType, nullable = true),
               StructField("href", StringType, nullable = true),
               StructField("type", StringType, nullable = true),
               StructField("title", StringType, nullable = true))),
-            containsNull = true),
-          nullable = true),
-        StructField(
-          "assets",
-          MapType(
-            StringType,
-            StructType(Seq(
+          containsNull = true),
+        nullable = true),
+      StructField(
+        "assets",
+        MapType(
+          StringType,
+          StructType(
+            Seq(
               StructField("href", StringType, nullable = true),
               StructField("type", StringType, nullable = true),
               StructField("title", StringType, nullable = true),
               StructField("roles", ArrayType(StringType, containsNull = true), nullable = true))),
-            valueContainsNull = true),
-          nullable = true)))
+          valueContainsNull = true),
+        nullable = true))
+
+    // Extension fields that may be present
+    val extensionFields = Seq(
+      // Grid extension fields
+      StructField("grid:code", StringType, nullable = true)
+      // Add other extension fields as needed
+    )
+
+    // Check that all base fields are present with correct types
+    baseFields.foreach { expectedField =>
+      val actualField = actualSchema.fields.find(_.name == expectedField.name)
+      assert(actualField.isDefined, s"Required field ${expectedField.name} not found in schema")
+      assert(
+        actualField.get.dataType == expectedField.dataType,
+        s"Field ${expectedField.name} has wrong type. Expected: ${expectedField.dataType}, Actual: ${actualField.get.dataType}")
+      assert(
+        actualField.get.nullable == expectedField.nullable,
+        s"Field ${expectedField.name} has wrong nullability. Expected: ${expectedField.nullable}, Actual: ${actualField.get.nullable}")
+    }
+
+    // Check extension fields if they are present
+    val actualFieldNames = actualSchema.fields.map(_.name).toSet
+    extensionFields.foreach { extensionField =>
+      if (actualFieldNames.contains(extensionField.name)) {
+        val actualField = actualSchema.fields.find(_.name == extensionField.name).get
+        assert(
+          actualField.dataType == extensionField.dataType,
+          s"Extension field ${extensionField.name} has wrong type. Expected: ${extensionField.dataType}, Actual: ${actualField.dataType}")
+      }
+    }
 
-    assert(
-      actualSchema == expectedSchema,
-      s"Schema does not match. Expected: $expectedSchema, Actual: $actualSchema")
+    // Check that there are no unexpected fields
+    val expectedFieldNames = (baseFields ++ extensionFields).map(_.name).toSet
+    val unexpectedFields = actualFieldNames.diff(expectedFieldNames)
+    assert(unexpectedFields.isEmpty, s"Schema contains unexpected fields: $unexpectedFields")
   }
 }


(sedona) 26/35: [SEDONA-731] add osm nodes parser (#1920)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 13f6b2e87f267a6ccad68b5b2450fc90254422d4
Author: Paweł Tokaj <pa...@gmail.com>
AuthorDate: Mon Apr 21 18:29:37 2025 +0200

    [SEDONA-731] add osm nodes parser (#1920)
    
    * SEDONA-731 add osm nodes parser
    
    * SEDONA-731 update docs
    
    * SEDONA-731 update docs
---
 docs/tutorial/sql.md                               |   2 +-
 .../osmpbf/extractors/DenseNodeExtractor.java      |   2 +
 ...{DenseNodeExtractor.java => NodeExtractor.java} |  67 +++++++--------------
 .../datasources/osmpbf/iterators/BlobIterator.java |  15 ++++-
 spark/common/src/test/resources/osmpbf/dense.pbf   | Bin 0 -> 282 bytes
 spark/common/src/test/resources/osmpbf/nodes.pbf   | Bin 0 -> 242 bytes
 .../org/apache/sedona/sql/OsmReaderTest.scala      |  38 ++++++++++++
 7 files changed, 77 insertions(+), 47 deletions(-)

diff --git a/docs/tutorial/sql.md b/docs/tutorial/sql.md
index 821ea37d9b..eb7fbe3082 100644
--- a/docs/tutorial/sql.md
+++ b/docs/tutorial/sql.md
@@ -480,7 +480,7 @@ Since v1.7.1, Sedona supports loading OSM PBF file format as a DataFrame.
 	```
 
 OSM PBF files can contain nodes, ways, and relations. Currently Sedona support
-DenseNodes, Ways and Relations. When you load the data you get a DataFrame with the following schema.
+Nodes, DenseNodes, Ways and Relations. When you load the data you get a DataFrame with the following schema.
 
 ```
 root
diff --git a/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/DenseNodeExtractor.java b/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/DenseNodeExtractor.java
index f850682be8..0b9ca93748 100644
--- a/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/DenseNodeExtractor.java
+++ b/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/DenseNodeExtractor.java
@@ -81,6 +81,8 @@ public class DenseNodeExtractor {
       keyIndex = keyIndex + 2;
     }
 
+    keyIndex = keyIndex + 1;
+
     return tags;
   }
 }
diff --git a/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/DenseNodeExtractor.java b/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/NodeExtractor.java
similarity index 52%
copy from spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/DenseNodeExtractor.java
copy to spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/NodeExtractor.java
index f850682be8..39e98537cd 100644
--- a/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/DenseNodeExtractor.java
+++ b/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/NodeExtractor.java
@@ -20,29 +20,18 @@ package org.apache.sedona.sql.datasources.osmpbf.extractors;
 
 import java.util.HashMap;
 import org.apache.sedona.sql.datasources.osmpbf.build.Osmformat;
+import org.apache.sedona.sql.datasources.osmpbf.features.TagsResolver;
 import org.apache.sedona.sql.datasources.osmpbf.model.OsmNode;
 
-public class DenseNodeExtractor {
-  long latOffset;
-  long lonOffset;
-  long granularity;
-  long firstId;
-  long firstLat;
-  long firstLon;
-  Integer keyIndex;
+public class NodeExtractor {
 
-  Osmformat.DenseNodes nodes;
+  Osmformat.PrimitiveGroup primitiveGroup;
+  Osmformat.PrimitiveBlock primitiveBlock;
 
-  public DenseNodeExtractor(
-      Osmformat.DenseNodes nodes, long latOffset, long lonOffset, long granularity) {
-    this.firstId = 0;
-    this.firstLat = 0;
-    this.firstLon = 0;
-    this.latOffset = latOffset;
-    this.lonOffset = lonOffset;
-    this.granularity = granularity;
-    this.nodes = nodes;
-    this.keyIndex = 0;
+  public NodeExtractor(
+      Osmformat.PrimitiveGroup primitiveGroup, Osmformat.PrimitiveBlock primitiveBlock) {
+    this.primitiveGroup = primitiveGroup;
+    this.primitiveBlock = primitiveBlock;
   }
 
   public OsmNode extract(int idx, Osmformat.StringTable stringTable) {
@@ -50,37 +39,25 @@ public class DenseNodeExtractor {
   }
 
   private OsmNode parse(int idx, Osmformat.StringTable stringTable) {
-    long id = nodes.getId(idx) + firstId;
-    long latitude = nodes.getLat(idx) + firstLat;
-    long longitude = nodes.getLon(idx) + firstLon;
+    Osmformat.Node node = primitiveGroup.getNodes(idx);
 
+    long id = node.getId();
+    long latitude = node.getLat();
+    long longitude = node.getLon();
+
+    long latOffset = primitiveBlock.getLatOffset();
+    long lonOffset = primitiveBlock.getLonOffset();
+    long granularity = primitiveBlock.getGranularity();
+
+    // https://wiki.openstreetmap.org/wiki/PBF_Format
+    // latitude = .000000001 * (lat_offset + (granularity * lat))
+    // longitude = .000000001 * (lon_offset + (granularity * lon))
     float lat = (float) (.000000001 * (latOffset + (latitude * granularity)));
     float lon = (float) (.000000001 * (lonOffset + (longitude * granularity)));
 
-    firstId = id;
-    firstLat = latitude;
-    firstLon = longitude;
-
-    HashMap<String, String> tags = parseTags(stringTable);
+    HashMap<String, String> tags =
+        TagsResolver.resolveTags(node.getKeysCount(), node::getKeys, node::getVals, stringTable);
 
     return new OsmNode(id, lat, lon, tags);
   }
-
-  HashMap<String, String> parseTags(Osmformat.StringTable stringTable) {
-    HashMap<String, String> tags = new HashMap<>();
-
-    while (nodes.getKeysVals(keyIndex) != 0) {
-      int key = nodes.getKeysVals(keyIndex);
-      int value = nodes.getKeysVals(keyIndex + 1);
-
-      String keyString = stringTable.getS(key).toStringUtf8();
-      String valueString = stringTable.getS(value).toStringUtf8();
-
-      tags.put(keyString, valueString);
-
-      keyIndex = keyIndex + 2;
-    }
-
-    return tags;
-  }
 }
diff --git a/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/iterators/BlobIterator.java b/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/iterators/BlobIterator.java
index 89a9005771..ff7a2f88d2 100644
--- a/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/iterators/BlobIterator.java
+++ b/spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/iterators/BlobIterator.java
@@ -27,6 +27,7 @@ import org.apache.sedona.sql.datasources.osmpbf.DenseNodeIterator;
 import org.apache.sedona.sql.datasources.osmpbf.build.Fileformat.Blob;
 import org.apache.sedona.sql.datasources.osmpbf.build.Osmformat;
 import org.apache.sedona.sql.datasources.osmpbf.extractors.DenseNodeExtractor;
+import org.apache.sedona.sql.datasources.osmpbf.extractors.NodeExtractor;
 import org.apache.sedona.sql.datasources.osmpbf.extractors.RelationExtractor;
 import org.apache.sedona.sql.datasources.osmpbf.extractors.WaysExtractor;
 import org.apache.sedona.sql.datasources.osmpbf.model.OSMEntity;
@@ -68,7 +69,7 @@ public class BlobIterator implements Iterator<OSMEntity> {
     }
 
     if (!currentPrimitiveGroup.getNodesList().isEmpty()) {
-      return null;
+      return extractNodePrimitiveGroup();
     }
 
     if (!currentPrimitiveGroup.getWaysList().isEmpty()) {
@@ -86,6 +87,18 @@ public class BlobIterator implements Iterator<OSMEntity> {
     return null;
   }
 
+  private OSMEntity extractNodePrimitiveGroup() {
+    osmEntityIdx += 1;
+    if (currentPrimitiveGroup.getNodesList().size() == osmEntityIdx) {
+      nextEntity();
+    }
+
+    Osmformat.StringTable stringTable = primitiveBlock.getStringtable();
+
+    return new NodeExtractor(currentPrimitiveGroup, primitiveBlock)
+        .extract(osmEntityIdx, stringTable);
+  }
+
   public OSMEntity extractDenseNodePrimitiveGroup() {
     if (denseNodesIterator == null) {
       denseNodesIterator =
diff --git a/spark/common/src/test/resources/osmpbf/dense.pbf b/spark/common/src/test/resources/osmpbf/dense.pbf
new file mode 100644
index 0000000000..24082953a9
Binary files /dev/null and b/spark/common/src/test/resources/osmpbf/dense.pbf differ
diff --git a/spark/common/src/test/resources/osmpbf/nodes.pbf b/spark/common/src/test/resources/osmpbf/nodes.pbf
new file mode 100644
index 0000000000..c3b3a40e4c
Binary files /dev/null and b/spark/common/src/test/resources/osmpbf/nodes.pbf differ
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/OsmReaderTest.scala b/spark/common/src/test/scala/org/apache/sedona/sql/OsmReaderTest.scala
index 1764e0dfaf..d3f9a52c1c 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/OsmReaderTest.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/OsmReaderTest.scala
@@ -25,8 +25,12 @@ import org.testcontainers.containers.MinIOContainer
 
 import java.io.FileInputStream
 
+case class Node(id: Long, latitude: Double, longitude: Double, tags: Map[String, String])
+
 class OsmReaderTest extends TestBaseScala with Matchers {
   val monacoPath: String = resourceFolder + "osmpbf/monaco-latest.osm.pbf"
+  val densePath: String = resourceFolder + "osmpbf/dense.pbf"
+  val nodesPath: String = resourceFolder + "osmpbf/nodes.pbf"
 
   import sparkSession.implicits._
 
@@ -44,6 +48,40 @@ class OsmReaderTest extends TestBaseScala with Matchers {
       assert(cnt > 0)
     }
 
+    it("should parse normal nodes") {
+      sparkSession.read
+        .format("osmpbf")
+        .load(nodesPath)
+        .select("id", "location.*", "tags")
+        .selectExpr(
+          "id",
+          "ROUND(latitude, 2) AS latitude",
+          "ROUND(longitude, 2) AS longitude",
+          "tags")
+        .as[Node]
+        .collect() should contain theSameElementsAs Array(
+        Node(1002, 48.86, 2.35, Map("amenity" -> "cafe", "name" -> "Cafe de Paris")),
+        Node(1003, 30.12, 22.23, Map("amenity" -> "bakery", "name" -> "Delicious Pastries")),
+        Node(1001, 52.52, 13.40, Map("amenity" -> "restaurant", "name" -> "Curry 36")))
+    }
+
+    it("should parse dense nodes") {
+      sparkSession.read
+        .format("osmpbf")
+        .load(densePath)
+        .select("id", "location.*", "tags")
+        .selectExpr(
+          "id",
+          "ROUND(latitude, 2) AS latitude",
+          "ROUND(longitude, 2) AS longitude",
+          "tags")
+        .as[Node]
+        .collect() should contain theSameElementsAs Array(
+        Node(1002, 48.86, 2.35, Map("amenity" -> "cafe", "name" -> "Cafe de Paris")),
+        Node(1003, 30.12, 22.23, Map("amenity" -> "bakery", "name" -> "Delicious Pastries")),
+        Node(1001, 52.52, 13.40, Map("amenity" -> "restaurant", "name" -> "Curry 36")))
+    }
+
     it("should be able to read from osm file on s3") {
       val container = new MinIOContainer("minio/minio:latest")
 


(sedona) 13/35: [DOCS] Format Python Markdown codeblocks with black (#1881)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 5cae18cd35d58ec50d58e0adb4eff4c6c82cfb18
Author: John Bampton <jb...@users.noreply.github.com>
AuthorDate: Fri Mar 28 14:58:42 2025 +1000

    [DOCS] Format Python Markdown codeblocks with black (#1881)
---
 docs/tutorial/concepts/spatial-joins.md        |  6 ++++--
 docs/tutorial/files/geojson-sedona-spark.md    | 14 ++++++++------
 docs/tutorial/files/geopackage-sedona-spark.md | 12 ++++++------
 docs/tutorial/files/geoparquet-sedona-spark.md | 21 +++++++++++++--------
 docs/tutorial/files/shapefiles-sedona-spark.md | 20 ++++++++++++--------
 5 files changed, 43 insertions(+), 30 deletions(-)

diff --git a/docs/tutorial/concepts/spatial-joins.md b/docs/tutorial/concepts/spatial-joins.md
index b63efdf5d2..e58f30ee3e 100644
--- a/docs/tutorial/concepts/spatial-joins.md
+++ b/docs/tutorial/concepts/spatial-joins.md
@@ -164,13 +164,15 @@ Here is the content of the lines table:
 Here’s a join that matches any touching values:
 
 ```python
-sedona.sql("""
+sedona.sql(
+    """
 SELECT
     lines.id as line_id,
     polygons.id as polygon_id
 FROM lines
 LEFT JOIN polygons ON ST_Touches(lines.geometry, polygons.geometry);  
-""").show()
+"""
+).show()
 ```
 
 Here’s the result of the join:
diff --git a/docs/tutorial/files/geojson-sedona-spark.md b/docs/tutorial/files/geojson-sedona-spark.md
index 56d4816efc..9b60859a1f 100644
--- a/docs/tutorial/files/geojson-sedona-spark.md
+++ b/docs/tutorial/files/geojson-sedona-spark.md
@@ -40,10 +40,14 @@ Here’s how to read a multiline GeoJSON file with Sedona:
 
 ```python
 df = (
-    sedona.read.format("geojson").option("multiLine", "true").load("data/multiline_geojson.json")
+    sedona.read.format("geojson")
+    .option("multiLine", "true")
+    .load("data/multiline_geojson.json")
     .selectExpr("explode(features) as features")
     .select("features.*")
-    .withColumn("prop0", expr("properties['prop0']")).drop("properties").drop("type")
+    .withColumn("prop0", expr("properties['prop0']"))
+    .drop("properties")
+    .drop("type")
 )
 df.show(truncate=False)
 ```
@@ -112,9 +116,7 @@ Here's how you can read many GeoJSON files:
 
 ```python
 df = (
-    sedona.read.format("geojson")
-    .option("multiLine", "true")
-    .load("data/many_geojsons")
+    sedona.read.format("geojson").option("multiLine", "true").load("data/many_geojsons")
 )
 ```
 
@@ -132,7 +134,7 @@ df = (
     .load("data/singleline_geojson.json")
     .withColumn("prop0", expr("properties['prop0']"))
     .drop("properties")
-    .drop("type")  
+    .drop("type")
 )
 df.show(truncate=False)
 ```
diff --git a/docs/tutorial/files/geopackage-sedona-spark.md b/docs/tutorial/files/geopackage-sedona-spark.md
index aeeb94c5c0..107f8ffa5b 100644
--- a/docs/tutorial/files/geopackage-sedona-spark.md
+++ b/docs/tutorial/files/geopackage-sedona-spark.md
@@ -113,11 +113,7 @@ gpkgs/
 Here’s how you can read all the files:
 
 ```python
-df = (
-    sedona.read.format("geopackage")
-    .option("tableName", "my_layer")
-    .load("/tmp/gpkgs")
-)
+df = sedona.read.format("geopackage").option("tableName", "my_layer").load("/tmp/gpkgs")
 df.show()
 ```
 
@@ -143,7 +139,11 @@ Sedona is an excellent option for analyzing many GeoPackage files because it can
 You can also load data from raster tables in the GeoPackage file. To load raster data, you can use the following code.
 
 ```python
-df = sedona.read.format("geopackage").option("tableName", "raster_table").load("/path/to/geopackage")
+df = (
+    sedona.read.format("geopackage")
+    .option("tableName", "raster_table")
+    .load("/path/to/geopackage")
+)
 ```
 
 Here are the contents of the DataFrame:
diff --git a/docs/tutorial/files/geoparquet-sedona-spark.md b/docs/tutorial/files/geoparquet-sedona-spark.md
index 11d95d9c6d..d5412cdff8 100644
--- a/docs/tutorial/files/geoparquet-sedona-spark.md
+++ b/docs/tutorial/files/geoparquet-sedona-spark.md
@@ -37,11 +37,14 @@ Let's see how to write a Sedona DataFrame to GeoParquet files.
 Start by creating a Sedona DataFrame:
 
 ```python
-df = sedona.createDataFrame([
-    ("a", 'LINESTRING(2.0 5.0,6.0 1.0)'),
-    ("b", 'LINESTRING(7.0 4.0,9.0 2.0)'),
-    ("c", 'LINESTRING(1.0 3.0,3.0 1.0)'),
-], ["id", "geometry"])
+df = sedona.createDataFrame(
+    [
+        ("a", "LINESTRING(2.0 5.0,6.0 1.0)"),
+        ("b", "LINESTRING(7.0 4.0,9.0 2.0)"),
+        ("c", "LINESTRING(1.0 3.0,3.0 1.0)"),
+    ],
+    ["id", "geometry"],
+)
 df = df.withColumn("geometry", ST_GeomFromText(col("geometry")))
 ```
 
@@ -258,13 +261,15 @@ Now, let’s apply a spatial filter to read points within a particular area:
 Here is the query:
 
 ```python
-my_shape = 'POLYGON((4.0 3.5, 4.0 6.0, 8.0 6.0, 8.0 4.5, 4.0 3.5))'
+my_shape = "POLYGON((4.0 3.5, 4.0 6.0, 8.0 6.0, 8.0 4.5, 4.0 3.5))"
 
-res = sedona.sql(f'''
+res = sedona.sql(
+    f"""
 select *
 from points
 where st_intersects(geometry, ST_GeomFromWKT('{my_shape}'))
-''')
+"""
+)
 res.show(truncate=False)
 ```
 
diff --git a/docs/tutorial/files/shapefiles-sedona-spark.md b/docs/tutorial/files/shapefiles-sedona-spark.md
index a7df23c521..dff3209162 100644
--- a/docs/tutorial/files/shapefiles-sedona-spark.md
+++ b/docs/tutorial/files/shapefiles-sedona-spark.md
@@ -38,13 +38,9 @@ from shapely.geometry import Point
 point1 = Point(0, 0)
 point2 = Point(1, 1)
 
-data = {
-    'name': ['Point A', 'Point B'],
-    'value': [10, 20],
-    'geometry': [point1, point2]
-}
+data = {"name": ["Point A", "Point B"], "value": [10, 20], "geometry": [point1, point2]}
 
-gdf = gpd.GeoDataFrame(data, geometry='geometry')
+gdf = gpd.GeoDataFrame(data, geometry="geometry")
 gdf.to_file("/tmp/my_geodata.shp")
 ```
 
@@ -98,7 +94,11 @@ df = (
 The name of the geometry column is geometry by default. You can change the name of the geometry column using the `geometry.name` option. Suppose one of the non-spatial attributes is named "geometry", `geometry.name` must be configured to avoid conflict.
 
 ```python
-df = sedona.read.format("shapefile").option("geometry.name", "geom").load("/path/to/shapefile")
+df = (
+    sedona.read.format("shapefile")
+    .option("geometry.name", "geom")
+    .load("/path/to/shapefile")
+)
 ```
 
 The character encoding of string attributes are inferred from the `.cpg` file. If you see garbled values in string fields, you can manually specify the correct charset using the `charset` option. For example:
@@ -118,7 +118,11 @@ The character encoding of string attributes are inferred from the `.cpg` file. I
 === "Python"
 
     ```python
-    df = sedona.read.format("shapefile").option("charset", "UTF-8").load("/path/to/shapefile")
+    df = (
+        sedona.read.format("shapefile")
+        .option("charset", "UTF-8")
+        .load("/path/to/shapefile")
+    )
     ```
 
 Let’s see how to load many Shapefiles into a Sedona DataFrame.


(sedona) 35/35: [GH-1945] Shade Jiffle and its dependencies (#1964)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 6ba5b8bcf014827ee3cfb8b5d1fa86b58926905e
Author: Kristin Cowalcijk <ko...@apache.org>
AuthorDate: Fri May 30 23:54:22 2025 +0800

    [GH-1945] Shade Jiffle and its dependencies (#1964)
    
    * Shade jiffle and its dependencies
    
    * Fix lint warning
---
 common/pom.xml       | 85 ++++++++++++++++++++++++++++++++++++++++++++--------
 pom.xml              | 12 ++++++--
 spark-shaded/pom.xml |  5 ++++
 spark/common/pom.xml |  4 ---
 4 files changed, 87 insertions(+), 19 deletions(-)

diff --git a/common/pom.xml b/common/pom.xml
index f6a14d5241..56afb11045 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -105,19 +105,6 @@
             <groupId>it.geosolutions.jaiext.jiffle</groupId>
             <artifactId>jt-jiffle-language</artifactId>
         </dependency>
-        <!-- These test dependencies are for running map algebra tests -->
-        <dependency>
-            <groupId>org.antlr</groupId>
-            <artifactId>antlr4-runtime</artifactId>
-            <version>${antlr-runtime.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.codehaus.janino</groupId>
-            <artifactId>janino</artifactId>
-            <version>${janino-version}</version>
-            <scope>test</scope>
-        </dependency>
         <dependency>
             <groupId>edu.ucar</groupId>
             <artifactId>cdm-core</artifactId>
@@ -134,6 +121,78 @@
                     <target>8</target>
                 </configuration>
             </plugin>
+            <plugin>
+                <!-- Skip running resolved-pom-maven-plugin since shade will
+                     generate dependency reduced pom which substitutes property
+                     values. resolved-pom-maven-plugin will break pom
+                     installation when working with maven-shade-plugin.  -->
+                <groupId>io.paradoxical</groupId>
+                <artifactId>resolved-pom-maven-plugin</artifactId>
+                <version>1.0</version>
+                <executions>
+                    <execution>
+                        <id>resolve-my-pom</id>
+                        <phase>none</phase>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <!--
+                  We need to shade jiffle and its antlr and janino dependencies for the following reasons:
+
+                  1. Databricks runtime uses an older version of janino (3.0.16) that does not work
+                     with jiffle in Spark repl. See https://github.com/apache/sedona/discussions/1945
+
+                  2. Spark 4 uses an incompatible version of antlr at runtime.
+                -->
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <artifactSet>
+                                <includes>
+                                    <include>it.geosolutions.jaiext.jiffle:*</include>
+                                    <include>org.antlr:*</include>
+                                    <include>org.codehaus.janino:*</include>
+                                </includes>
+                            </artifactSet>
+                            <relocations>
+                                <relocation>
+                                    <pattern>it.geosolutions.jaiext.jiffle</pattern>
+                                    <shadedPattern>org.apache.sedona.shaded.jiffle</shadedPattern>
+                                    <excludes>
+                                        <exclude>it.geosolutions.jaiext.jiffle.runtime.*</exclude>
+                                    </excludes>
+                                </relocation>
+                                <relocation>
+                                    <pattern>org.antlr.v4.runtime</pattern>
+                                    <shadedPattern>org.apache.sedona.shaded.antlr</shadedPattern>
+                                </relocation>
+                                <relocation>
+                                    <pattern>org.codehaus</pattern>
+                                    <shadedPattern>org.apache.sedona.shaded.codehaus</shadedPattern>
+                                </relocation>
+                            </relocations>
+                            <filters>
+                                <!--  filter to address "Invalid signature file" issue - see http://stackoverflow.com/a/6743609/589215 -->
+                                <filter>
+                                    <artifact>*:*</artifact>
+                                    <excludes>
+                                        <exclude>META-INF/*.SF</exclude>
+                                        <exclude>META-INF/*.DSA</exclude>
+                                        <exclude>META-INF/*.RSA</exclude>
+                                    </excludes>
+                                </filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 </project>
diff --git a/pom.xml b/pom.xml
index 6bd14879f4..6d1761063f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -272,12 +272,15 @@
                 <groupId>it.geosolutions.jaiext.jiffle</groupId>
                 <artifactId>jt-jiffle-language</artifactId>
                 <version>${jt-jiffle.version}</version>
-                <scope>${geotools.scope}</scope>
                 <exclusions>
                     <exclusion>
-                        <groupId>*</groupId>
+                        <groupId>javax.media</groupId>
                         <artifactId>*</artifactId>
                     </exclusion>
+                    <exclusion>
+                        <groupId>it.geosolutions.jaiext.utilities</groupId>
+                        <artifactId>jt-utilities</artifactId>
+                    </exclusion>
                 </exclusions>
             </dependency>
            <dependency>
@@ -341,6 +344,11 @@
                 <version>4.0.2</version>
                 <scope>provided</scope>
             </dependency>
+            <dependency>
+                <groupId>org.codehaus.janino</groupId>
+                <artifactId>janino</artifactId>
+                <version>${janino-version}</version>
+            </dependency>
             <dependency>
                 <groupId>junit</groupId>
                 <artifactId>junit</artifactId>
diff --git a/spark-shaded/pom.xml b/spark-shaded/pom.xml
index cdd31ef0ae..c98a500811 100644
--- a/spark-shaded/pom.xml
+++ b/spark-shaded/pom.xml
@@ -47,6 +47,11 @@
                     <groupId>com.fasterxml.jackson.core</groupId>
                     <artifactId>*</artifactId>
                 </exclusion>
+                <!-- Even those these are shaded, the shaded POM isn't used when building projects in a single Maven run -->
+                <exclusion>
+                    <groupId>it.geosolutions.jaiext.jiffle</groupId>
+                    <artifactId>jt-jiffle-language</artifactId>
+                </exclusion>
             </exclusions>
         </dependency>
         <dependency>
diff --git a/spark/common/pom.xml b/spark/common/pom.xml
index eb3cb27d9f..d3e473e6dc 100644
--- a/spark/common/pom.xml
+++ b/spark/common/pom.xml
@@ -162,10 +162,6 @@
             <groupId>org.geotools</groupId>
             <artifactId>gt-arcgrid</artifactId>
         </dependency>
-        <dependency>
-            <groupId>it.geosolutions.jaiext.jiffle</groupId>
-            <artifactId>jt-jiffle-language</artifactId>
-        </dependency>
         <dependency>
             <groupId>org.locationtech.jts</groupId>
             <artifactId>jts-core</artifactId>


(sedona) 08/35: [DOCS] Update the Databricks doc to reflect issues on DBR 16 (#1877)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 657325d65013808cea72086a9164a1d0d7162688
Author: Jia Yu <ji...@apache.org>
AuthorDate: Tue Mar 25 19:06:57 2025 -0700

    [DOCS] Update the Databricks doc to reflect issues on DBR 16 (#1877)
---
 docs/setup/databricks.md | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/docs/setup/databricks.md b/docs/setup/databricks.md
index d0a8d332d9..ff51a9eb70 100644
--- a/docs/setup/databricks.md
+++ b/docs/setup/databricks.md
@@ -19,8 +19,10 @@
 
 In Databricks advanced editions, you need to install Sedona via [cluster init-scripts](https://docs.databricks.com/clusters/init-scripts.html) as described below. Sedona is not guaranteed to be 100% compatible with `Databricks photon acceleration`. Sedona requires Spark internal APIs to inject many optimization strategies, which sometimes is not accessible in `Photon`.
 
-!!!note
-    The following steps use DBR including Apache Spark 3.4.x as an example. Please change the Spark version according to your DBR version. Please pay attention to the Spark version postfix and Scala version postfix on our [Maven Coordinate page](maven-coordinates.md). Databricks Spark and Apache Spark's compatibility can be found [here](https://docs.databricks.com/en/release-notes/runtime/index.html).
+The following steps use DBR including Apache Spark 3.4.x as an example. Please change the Spark version according to your DBR version. Please pay attention to the Spark version postfix and Scala version postfix on our [Maven Coordinate page](maven-coordinates.md). Databricks Spark and Apache Spark's compatibility can be found [here](https://docs.databricks.com/en/release-notes/runtime/index.html).
+
+!!! bug
+    Databricks Runtime 16.2 (non-LTS) introduces a change in the json4s dependency, which may lead to compatibility issues with Apache Sedona. We recommend using a currently supported LTS version, such as Databricks Runtime 15.4 LTS or 14.3 LTS, to ensure stability. A patch will be provided once an official Databricks Runtime 16 LTS version is released.
 
 ### Download Sedona jars
 
@@ -75,6 +77,7 @@ From your cluster configuration (`Cluster` -> `Edit` -> `Configuration` -> `Adva
 spark.sql.extensions org.apache.sedona.viz.sql.SedonaVizExtensions,org.apache.sedona.sql.SedonaSqlExtensions
 spark.serializer org.apache.spark.serializer.KryoSerializer
 spark.kryo.registrator org.apache.sedona.core.serde.SedonaKryoRegistrator
+spark.sedona.enableParserExtension false
 ```
 
 From your cluster configuration (`Cluster` -> `Edit` -> `Configuration` -> `Advanced options` -> `Init Scripts`) add the newly created `Workspace` init script


(sedona) 18/35: [CI] Clean up the ignored words list `codespell.txt` (#1890)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 8935ca7a9ee1e57749623b9993bc575f3ba9f39f
Author: John Bampton <jb...@users.noreply.github.com>
AuthorDate: Mon Mar 31 14:37:26 2025 +1000

    [CI] Clean up the ignored words list `codespell.txt` (#1890)
---
 .github/linters/codespell.txt | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/.github/linters/codespell.txt b/.github/linters/codespell.txt
index 1ac5f08975..d209260c9c 100644
--- a/.github/linters/codespell.txt
+++ b/.github/linters/codespell.txt
@@ -1,20 +1,12 @@
-LOD
 actualy
 afterall
 atmost
-bu
 celle
 checkin
 eiter
-errorprone
-fpt
-limite
-nd
-ois
 parm
 pavin
 pixelx
-refere
 ser
 shouldbe
 wel


(sedona) 01/35: [SEDONA-722] Fix precision loss problems caused by casting world coordinates from double to float (#1860)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 6b5b65e4a2278e16cc3729b07a24fc94d32f82f4
Author: Kristin Cowalcijk <ko...@apache.org>
AuthorDate: Tue Mar 18 00:35:24 2025 +0800

    [SEDONA-722] Fix precision loss problems caused by casting world coordinates from double to float (#1860)
---
 .../org/apache/sedona/common/utils/RasterUtils.java  |  6 +++---
 .../apache/sedona/common/raster/FunctionsTest.java   | 20 ++++++++++----------
 .../org/apache/sedona/sql/rasteralgebraTest.scala    | 14 ++++++++------
 3 files changed, 21 insertions(+), 19 deletions(-)

diff --git a/common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java b/common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java
index f18cf8b95e..f1425ca8e9 100644
--- a/common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java
+++ b/common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java
@@ -45,7 +45,6 @@ import org.geotools.coverage.Category;
 import org.geotools.coverage.CoverageFactoryFinder;
 import org.geotools.coverage.GridSampleDimension;
 import org.geotools.coverage.TypeMap;
-import org.geotools.coverage.grid.GridCoordinates2D;
 import org.geotools.coverage.grid.GridCoverage2D;
 import org.geotools.coverage.grid.GridCoverageFactory;
 import org.geotools.coverage.grid.GridEnvelope2D;
@@ -467,10 +466,11 @@ public class RasterUtils {
 
   public static Point2D getWorldCornerCoordinates(GridCoverage2D raster, int colX, int rowY)
       throws TransformException {
+    Point2D.Double gridCoord = new DirectPosition2D(colX - 1, rowY - 1);
     return raster
         .getGridGeometry()
         .getGridToCRS2D(PixelOrientation.UPPER_LEFT)
-        .transform(new GridCoordinates2D(colX - 1, rowY - 1), null);
+        .transform(gridCoord, null);
   }
 
   /**
@@ -488,7 +488,7 @@ public class RasterUtils {
   public static Point2D getWorldCornerCoordinatesWithRangeCheck(
       GridCoverage2D raster, int colX, int rowY)
       throws IndexOutOfBoundsException, TransformException {
-    GridCoordinates2D gridCoordinates2D = new GridCoordinates2D(colX - 1, rowY - 1);
+    Point2D.Double gridCoordinates2D = new DirectPosition2D(colX - 1, rowY - 1);
     if (!(raster.getGridGeometry().getGridRange2D().contains(gridCoordinates2D)))
       throw new IndexOutOfBoundsException(
           String.format(
diff --git a/common/src/test/java/org/apache/sedona/common/raster/FunctionsTest.java b/common/src/test/java/org/apache/sedona/common/raster/FunctionsTest.java
index 60aae75e7d..cd8f54621e 100644
--- a/common/src/test/java/org/apache/sedona/common/raster/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/raster/FunctionsTest.java
@@ -149,7 +149,7 @@ public class FunctionsTest extends RasterTestBase {
     List<PixelRecord> points = PixelFunctions.getPixelAsPolygons(emptyRaster, 1);
 
     PixelRecord point = points.get(11);
-    Geometry geom = (Geometry) point.geom;
+    Geometry geom = point.geom;
     String expected = "POLYGON ((131 -246, 139 -246, 139 -254, 131 -254, 131 -246))";
     assertEquals(expected, geom.toString());
 
@@ -157,7 +157,7 @@ public class FunctionsTest extends RasterTestBase {
     emptyRaster = RasterConstructors.makeEmptyRaster(1, 5, 10, 234, -43, 3, 4, 2, 3, 0);
     points = PixelFunctions.getPixelAsPolygons(emptyRaster, 1);
     point = points.get(11);
-    geom = (Geometry) point.geom;
+    geom = point.geom;
     expected = "POLYGON ((241 -32, 244 -29, 246 -25, 243 -28, 241 -32))";
     assertEquals(expected, geom.toString());
   }
@@ -284,13 +284,13 @@ public class FunctionsTest extends RasterTestBase {
     List<PixelRecord> points = PixelFunctions.getPixelAsPoints(emptyRaster, 1);
 
     PixelRecord point1 = points.get(0);
-    Geometry geom1 = (Geometry) point1.geom;
+    Geometry geom1 = point1.geom;
     assertEquals(123, geom1.getCoordinate().x, FP_TOLERANCE);
     assertEquals(-230, geom1.getCoordinate().y, FP_TOLERANCE);
     assertEquals(0.0, point1.value, FP_TOLERANCE);
 
     PixelRecord point2 = points.get(22);
-    Geometry geom2 = (Geometry) point2.geom;
+    Geometry geom2 = point2.geom;
     assertEquals(139, geom2.getCoordinate().x, FP_TOLERANCE);
     assertEquals(-262, geom2.getCoordinate().y, FP_TOLERANCE);
     assertEquals(0.0, point2.value, FP_TOLERANCE);
@@ -303,7 +303,7 @@ public class FunctionsTest extends RasterTestBase {
         RasterConstructors.makeEmptyRaster(1, 5, 5, -123, 54, 5, 5, 0, 0, srid);
     List<PixelRecord> points = PixelFunctions.getPixelAsPoints(emptyRaster, 1);
     PixelRecord point1 = points.get(0);
-    Geometry geom1 = (Geometry) point1.geom;
+    Geometry geom1 = point1.geom;
     assertEquals(-123, geom1.getCoordinate().x, FP_TOLERANCE);
     assertEquals(54, geom1.getCoordinate().y, FP_TOLERANCE);
     assertEquals(srid, geom1.getSRID());
@@ -317,7 +317,7 @@ public class FunctionsTest extends RasterTestBase {
     List<PixelRecord> points = PixelFunctions.getPixelAsPoints(emptyRaster, 1);
 
     PixelRecord point1 = points.get(11);
-    Geometry geom1 = (Geometry) point1.geom;
+    Geometry geom1 = point1.geom;
     assertEquals(-118, geom1.getCoordinate().x, FP_TOLERANCE);
     assertEquals(34, geom1.getCoordinate().y, FP_TOLERANCE);
     assertEquals(srid, geom1.getSRID());
@@ -329,10 +329,10 @@ public class FunctionsTest extends RasterTestBase {
     GridCoverage2D raster = rasterFromGeoTiff(resourceFolder + "raster/test1.tiff");
     List<PixelRecord> points = PixelFunctions.getPixelAsPoints(raster, 1);
     PixelRecord firstPoint = points.get(0);
-    Geometry firstGeom = (Geometry) firstPoint.geom;
+    Geometry firstGeom = firstPoint.geom;
 
-    double expectedX = -1.3095818E7;
-    double expectedY = 4021262.75;
+    double expectedX = -1.3095817809482181E7;
+    double expectedY = 4021262.7487925636;
     double val = 0.0;
 
     assertEquals(expectedX, firstGeom.getCoordinate().x, FP_TOLERANCE);
@@ -348,7 +348,7 @@ public class FunctionsTest extends RasterTestBase {
     List<PixelRecord> points = PixelFunctions.getPixelAsPoints(emptyRaster, 1);
 
     PixelRecord point1 = points.get(26);
-    Geometry geom1 = (Geometry) point1.geom;
+    Geometry geom1 = point1.geom;
     String expected = "POINT (250 -186)";
     assertEquals(expected, geom1.toString());
   }
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala b/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
index 1f7142a73d..3e77f8f509 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
@@ -1831,7 +1831,7 @@ class rasteralgebraTest extends TestBaseScala with BeforeAndAfter with GivenWhen
           s"SELECT RS_PixelAsPoints(RS_MakeEmptyRaster($numBands, $widthInPixel, $heightInPixel, $upperLeftX, $upperLeftY, $cellSize), 1)")
         .first()
         .getList(0)
-      val expected = "[POINT (127.19000244140625 -12),0.0,2,1]"
+      val expected = "[POINT (127.19 -12),0.0,2,1]"
       assertEquals(expected, result.get(1).toString)
     }
 
@@ -1863,7 +1863,7 @@ class rasteralgebraTest extends TestBaseScala with BeforeAndAfter with GivenWhen
         .first()
         .getString(0)
       val expected =
-        "POLYGON ((127.19000244140625 -20, 131.19000244140625 -20, 131.19000244140625 -24, 127.19000244140625 -24, 127.19000244140625 -20))"
+        "POLYGON ((127.19 -20, 131.19 -20, 131.19 -24, 127.19 -24, 127.19 -20))"
       assertEquals(expected, result)
     }
 
@@ -1880,7 +1880,7 @@ class rasteralgebraTest extends TestBaseScala with BeforeAndAfter with GivenWhen
         .first()
         .getList(0)
       val expected =
-        "[POLYGON ((127.19000244140625 -20, 131.19000244140625 -20, 131.19000244140625 -24, 127.19000244140625 -24, 127.19000244140625 -20)),0.0,2,3]"
+        "[POLYGON ((127.19 -20, 131.19 -20, 131.19 -24, 127.19 -24, 127.19 -20)),0.0,2,3]"
       assertEquals(expected, result.get(11).toString)
     }
 
@@ -1963,9 +1963,11 @@ class rasteralgebraTest extends TestBaseScala with BeforeAndAfter with GivenWhen
     it("Passed RS_RasterToWorldCoord with raster") {
       var df = sparkSession.read.format("binaryFile").load(resourceFolder + "raster/test1.tiff")
       df = df.selectExpr("RS_FromGeoTiff(content) as raster")
-      val result = df.selectExpr("RS_RasterToWorldCoord(raster, 1, 1)").first().get(0).toString
-      val expected = "POINT (-13095818 4021262.75)"
-      assertEquals(expected, result)
+      val result =
+        df.selectExpr("RS_RasterToWorldCoord(raster, 1, 1)").first().get(0).asInstanceOf[Geometry]
+      val actualCoordinate = result.getCoordinate
+      assertEquals(-13095817.809482181, actualCoordinate.x, 0.5d)
+      assertEquals(4021262.7487925636, actualCoordinate.y, 0.5d)
     }
 
     it("Passed RS_WorldToRasterCoord with raster") {


(sedona) 23/35: [DOC] Fix Referer of matomo tracking pixel (#1907)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit bf240dd0c58b9807fd99832551c3e8f8fd942566
Author: Kristin Cowalcijk <ko...@apache.org>
AuthorDate: Fri Apr 4 01:08:35 2025 +0800

    [DOC] Fix Referer of matomo tracking pixel (#1907)
---
 mkdocs.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mkdocs.yml b/mkdocs.yml
index 421f0fe47a..ea58221d10 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -201,7 +201,7 @@ extra:
     current_rc: 1.7.1-rc1
     current_snapshot: 1.8.0-SNAPSHOT
     next_version: 1.8.0
-copyright: Copyright © 2025 The Apache Software Foundation. Apache Sedona, Sedona, Apache, the Apache feather logo, and the Apache Sedona project logo are either registered trademarks or trademarks of The Apache Software Foundation in the United States and other countries. All other marks mentioned may be trademarks or registered trademarks of their respective owners. Please visit <a href="https://www.apache.org/">Apache Software Foundation</a> for more details.<img referrerpolicy="no-re [...]
+copyright: Copyright © 2025 The Apache Software Foundation. Apache Sedona, Sedona, Apache, the Apache feather logo, and the Apache Sedona project logo are either registered trademarks or trademarks of The Apache Software Foundation in the United States and other countries. All other marks mentioned may be trademarks or registered trademarks of their respective owners. Please visit <a href="https://www.apache.org/">Apache Software Foundation</a> for more details.<img referrerpolicy="no-re [...]
 
 markdown_extensions:
   - admonition


(sedona) 06/35: [GH-1868] Fix spark sql extension load failure when parser failed to load (#1872)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 7a8a41f7d829a3c3eb84dab89634feba010ae750
Author: Feng Zhang <fe...@wherobots.com>
AuthorDate: Thu Mar 20 16:19:25 2025 -0700

    [GH-1868] Fix spark sql extension load failure when parser failed to load (#1872)
    
    * [SEDONA-636] Fix spark sql extension load failure when parser failed to load
    
    * fix exception capture issue
---
 .../org/apache/sedona/sql/SedonaSqlExtensions.scala   | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/spark/common/src/main/scala/org/apache/sedona/sql/SedonaSqlExtensions.scala b/spark/common/src/main/scala/org/apache/sedona/sql/SedonaSqlExtensions.scala
index fbc3567192..2856a735bf 100644
--- a/spark/common/src/main/scala/org/apache/sedona/sql/SedonaSqlExtensions.scala
+++ b/spark/common/src/main/scala/org/apache/sedona/sql/SedonaSqlExtensions.scala
@@ -22,8 +22,11 @@ import org.apache.sedona.spark.SedonaContext
 import org.apache.spark.SparkContext
 import org.apache.spark.sql.SparkSessionExtensions
 import org.apache.spark.sql.parser.ParserFactory
+import org.slf4j.{Logger, LoggerFactory}
 
 class SedonaSqlExtensions extends (SparkSessionExtensions => Unit) {
+  val logger: Logger = LoggerFactory.getLogger(getClass)
+
   private lazy val enableParser =
     SparkContext.getOrCreate().getConf.get("spark.sedona.enableParserExtension", "true").toBoolean
 
@@ -33,9 +36,23 @@ class SedonaSqlExtensions extends (SparkSessionExtensions => Unit) {
       _ => ()
     })
 
+    // Inject Sedona SQL parser
     if (enableParser) {
+      // Try to inject the Sedona SQL parser but gracefully handle initialization failures.
+      // This prevents extension loading errors from causing the SparkSession initialization to fail,
+      // allowing the application to continue running without the Sedona parser extension.
+      // Common failures include version incompatibilities between Spark and Sedona.
       e.injectParser { case (_, parser) =>
-        ParserFactory.getParser("org.apache.sedona.sql.parser.SedonaSqlParser", parser)
+        try {
+          ParserFactory.getParser("org.apache.sedona.sql.parser.SedonaSqlParser", parser)
+        } catch {
+          case parserInjectionException: Throwable =>
+            logger.warn(
+              s"Failed to inject Sedona SQL parser: ${parserInjectionException.getMessage}",
+              parserInjectionException)
+            // Return the original parser instead of failing
+            parser
+        }
       }
     }
   }


(sedona) 27/35: [GH-1922] ST_X/Y/Z ON null geometries (#1923)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit e55714fe8fb001afcb754e438780ab7a8fe233d9
Author: Javier Goday <jg...@gmail.com>
AuthorDate: Wed Apr 23 20:29:41 2025 +0200

    [GH-1922] ST_X/Y/Z ON null geometries (#1923)
---
 .../main/java/org/apache/sedona/common/Functions.java  | 12 ++++++------
 .../java/org/apache/sedona/common/FunctionsTest.java   | 18 ++++++++++++++++++
 2 files changed, 24 insertions(+), 6 deletions(-)

diff --git a/common/src/main/java/org/apache/sedona/common/Functions.java b/common/src/main/java/org/apache/sedona/common/Functions.java
index 97e08b7b0f..4e14055d8c 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -603,28 +603,28 @@ public class Functions {
   }
 
   public static Double x(Geometry geometry) {
-    if (geometry instanceof Point) {
+    if (geometry instanceof Point && geometry.getCoordinate() != null) {
       return geometry.getCoordinate().x;
     }
     return null;
   }
 
   public static Double y(Geometry geometry) {
-    if (geometry instanceof Point) {
+    if (geometry instanceof Point && geometry.getCoordinate() != null) {
       return geometry.getCoordinate().y;
     }
     return null;
   }
 
   public static Double z(Geometry geometry) {
-    if (geometry instanceof Point) {
+    if (geometry instanceof Point && geometry.getCoordinate() != null) {
       return geometry.getCoordinate().z;
     }
     return null;
   }
 
   public static Double m(Geometry geom) {
-    if (geom instanceof Point) {
+    if (geom instanceof Point && geom.getCoordinate() != null) {
       return geom.getCoordinate().getM();
     }
     return null;
@@ -708,12 +708,12 @@ public class Functions {
 
   public static boolean hasM(Geometry geom) {
     Coordinate coord = geom.getCoordinate();
-    return !Double.isNaN(coord.getM());
+    return coord != null && !Double.isNaN(coord.getM());
   }
 
   public static boolean hasZ(Geometry geom) {
     Coordinate coord = geom.getCoordinate();
-    return !Double.isNaN(coord.getZ());
+    return coord != null && !Double.isNaN(coord.getZ());
   }
 
   public static Geometry flipCoordinates(Geometry geometry) {
diff --git a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
index 4819384e70..748c0b6c12 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -4402,4 +4402,22 @@ public class FunctionsTest extends TestBase {
     expected = 2.75;
     assertEquals(expected, actual, 1e-6);
   }
+
+  @Test
+  public void emptyPoint() throws ParseException {
+    Geometry point = Constructors.geomFromEWKT("POINT(1 1)");
+    Geometry emptyPoint = Constructors.geomFromEWKT("POINT EMPTY");
+
+    double actualX = Functions.x(point);
+    double expectedX = 1.0;
+    assertEquals(expectedX, actualX, 1e-6);
+
+    assertNull(Functions.x(emptyPoint));
+    assertNull(Functions.y(emptyPoint));
+    assertNull(Functions.z(emptyPoint));
+    assertNull(Functions.m(emptyPoint));
+
+    assertFalse(Functions.hasM(emptyPoint));
+    assertFalse(Functions.hasZ(emptyPoint));
+  }
 }


(sedona) 20/35: [CI] pre-commit: add `requirements-docs.txt` to `requirements-txt-fixer` (#1889)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit f6ef7732da33b78791f0af389f27cf3f432456d9
Author: John Bampton <jb...@users.noreply.github.com>
AuthorDate: Mon Mar 31 14:40:33 2025 +1000

    [CI] pre-commit: add `requirements-docs.txt` to `requirements-txt-fixer` (#1889)
    
    This hook auto sorts Python requirements files.
    
    Rename root `requirements.txt` to `requirements-dev.txt`
    
    And update the references in the Makefile.
---
 .pre-commit-config.yaml                  | 2 +-
 Makefile                                 | 6 +++---
 requirements.txt => requirements-dev.txt | 0
 requirements-docs.txt                    | 6 +++---
 4 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a1defa0e6c..d2530db4ff 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -194,7 +194,7 @@ repos:
               python/tests/tools\.py
           )$
       - id: requirements-txt-fixer
-        files: ^docker/requirements\.txt$
+        files: ^docker/requirements\.txt$|^requirements-docs\.txt$
       - id: trailing-whitespace
         args: [--markdown-linebreak-ext=md]
         exclude: ^docs-overrides/main\.html$|\.Rd$
diff --git a/Makefile b/Makefile
index 4034de4d7f..b861ed228c 100644
--- a/Makefile
+++ b/Makefile
@@ -43,10 +43,10 @@ checkupdate: checkinstall
 
 install:
 	@echo "Installing dependencies..."
-	@if [ -f requirements.txt ]; then \
-		$(PIP) install -r requirements.txt; \
+	@if [ -f requirements-dev.txt ]; then \
+		$(PIP) install -r requirements-dev.txt; \
 	else \
-		echo "Error: requirements.txt not found."; \
+		echo "Error: requirements-dev.txt not found."; \
 		exit 1; \
 	fi
 
diff --git a/requirements.txt b/requirements-dev.txt
similarity index 100%
rename from requirements.txt
rename to requirements-dev.txt
diff --git a/requirements-docs.txt b/requirements-docs.txt
index f3015888b4..8f76842070 100644
--- a/requirements-docs.txt
+++ b/requirements-docs.txt
@@ -1,6 +1,6 @@
+mike
 mkdocs
+mkdocs-git-revision-date-localized-plugin
 mkdocs-jupyter
-mkdocs-material
 mkdocs-macros-plugin
-mkdocs-git-revision-date-localized-plugin
-mike
+mkdocs-material


(sedona) 15/35: [DOCS] Fix spelling in Java code

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 2b34fa86839002adf035ff4be93458f6c47159f0
Author: John Bampton <jb...@users.noreply.github.com>
AuthorDate: Sat Mar 29 12:12:58 2025 +1000

    [DOCS] Fix spelling in Java code
---
 .../java/org/apache/sedona/common/raster/RasterBandAccessors.java     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java b/common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java
index 7f816d8b52..b843ce924c 100644
--- a/common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java
+++ b/common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java
@@ -362,11 +362,11 @@ public class RasterBandAccessors {
     // Adding an arbitrary value '150' for the pixels that are under the geometry.
     GridCoverage2D rasterizedGeom =
         RasterConstructors.asRasterWithRasterExtent(roi, raster, datatype, allTouched, 150, null);
-    Raster rasterziedData = RasterUtils.getRaster(rasterizedGeom.getRenderedImage());
+    Raster rasterizedData = RasterUtils.getRaster(rasterizedGeom.getRenderedImage());
     int width = RasterAccessors.getWidth(rasterizedGeom),
         height = RasterAccessors.getHeight(rasterizedGeom);
     double[] rasterizedPixelData =
-        rasterziedData.getSamples(0, 0, width, height, 0, (double[]) null);
+        rasterizedData.getSamples(0, 0, width, height, 0, (double[]) null);
     double[] rasterPixelData =
         rasterData.getSamples(0, 0, width, height, band - 1, (double[]) null);
 


(sedona) 03/35: [DOCS] Bump Sedona versions in doc (#1862)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit fb11f4f49a037032ec8b04944333d091e20d9197
Author: Jia Yu <ji...@apache.org>
AuthorDate: Mon Mar 17 16:46:40 2025 -0700

    [DOCS] Bump Sedona versions in doc (#1862)
---
 mkdocs.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/mkdocs.yml b/mkdocs.yml
index bb9fa611df..421f0fe47a 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -193,8 +193,8 @@ extra:
     - icon: fontawesome/brands/discord
       link: 'https://share.hsforms.com/1Ndql_ZigTdmLlVQc_d1o4gqga4q'
   sedona:
-    current_version: 1.7.0
-    current_geotools: 1.7.0-28.5
+    current_version: 1.7.1
+    current_geotools: 1.7.1-28.5
   sedona_create_release:
     current_version: 1.7.1
     current_git_tag: sedona-1.7.1-rc1


(sedona) 04/35: [SEDONA-715] Unify Zeppelin and Jupyter setting in Docker (#1850)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 8383a9a28afedf5ce417076b06e85ac84484e757
Author: sshiv012 <12...@users.noreply.github.com>
AuthorDate: Wed Mar 19 01:09:09 2025 -0700

    [SEDONA-715] Unify Zeppelin and Jupyter setting in Docker (#1850)
    
    * [SEDONA-715] Update sedona-jupyterlab.dockerfile
    
    Did you read the Contributor Guide?
    Yes, I have read Contributor Rules and Contributor Development Guide
    
    Is this PR related to a JIRA ticket?
    Yes, the URL of the associated JIRA ticket is https://issues.apache.org/jira/browse/SEDONA-715. The PR name follows the format [SEDONA-XXX] my subject.
    
    What changes were proposed in this PR?
    Bug fix, zeppelin config is dynamically inferred now during build.
    
    How was this patch tested?
    Tested locally by building the image and running it. Visualized with the sample notebook that has been bundled with the commit.
    
    Did this PR include necessary documentation updates?
    Yes, added additional information that may help the users.
    
    * Update sedona-jupyterlab.dockerfile
    
    * Fix the docker build
    
    * Add some update
    
    * Get the image build working with docker cache
    
    * Bring back the full spark distribution
    
    * Fix again
    
    * Use the classic ui
    
    * All code are working now
    
    * Update pre-commit
    
    * Drop the binder folder completely
    
    * Wrap up the Zeppelin example
    
    * Fix a number of issues in Jupyter
    
    * Test all jupyter notebooks
    
    ---------
    
    Co-authored-by: Jia Yu <ji...@apache.org>
---
 .github/dependabot.yml                             |      2 +-
 .github/labeler.yml                                |      6 -
 .github/workflows/docker-build.yml                 |      2 +-
 .pre-commit-config.yaml                            |     26 +-
 README.md                                          |      2 -
 binder/.dockerignore                               |      9 -
 binder/Pipfile                                     |     33 -
 binder/Pipfile.lock                                |   3198 -
 binder/apt.txt                                     |      1 -
 binder/postBuild                                   |     12 -
 binder/start                                       |     23 -
 docker/{sedona-spark-jupyterlab => }/build.sh      |      6 +-
 docker/{sedona.sh => install-sedona.sh}            |      6 +-
 docker/{spark.sh => install-spark.sh}              |     18 +-
 docker/{zeppelin => }/install-zeppelin.sh          |     10 +-
 .../{sedona-spark-jupyterlab => }/requirements.txt |     10 +-
 ...yterlab.dockerfile => sedona-docker.dockerfile} |     61 +-
 ...gnore => sedona-docker.dockerfile.dockerignore} |      2 +-
 docker/sedona-spark-jupyterlab/.gitignore          |      6 -
 docker/{sedona-spark-jupyterlab => }/start.sh      |     20 +-
 docker/zeppelin/conf/interpreter.json              |     42 +-
 docker/zeppelin/conf/zeppelin-site.xml             |      4 +-
 .../examples/airport-per-country_2KR22DCHD.zpln    |    620 +
 docker/zeppelin/examples/arealm.csv                | 121960 ----
 .../examples/raster-management_2KPK484NG.zpln      |    732 +
 .../examples/read-write-geoparquet_2KPERG982.zpln  |    330 +
 .../examples/sedona-zeppelin-demo_2KPTK5RSG.zpln   |    459 -
 docker/zeppelin/update-zeppelin-interpreter.sh     |     27 +
 docs/setup/compile.md                              |      2 +-
 docs/setup/docker.md                               |     14 +-
 docs/setup/install-python.md                       |      2 -
 docs/tutorial/jupyter-notebook.md                  |      2 -
 docs/tutorial/raster.md                            |      2 +-
 docs/tutorial/sql.md                               |      6 +-
 docs/usecases/ApacheSedonaCore.ipynb               |    140 +-
 docs/usecases/ApacheSedonaRaster.ipynb             |    250 +-
 docs/usecases/ApacheSedonaSQL.ipynb                |      5 +-
 ...eSedonaSQL_SpatialJoin_AirportsPerCountry.ipynb | 669341 +-----------------
 docs/usecases/Sedona_OvertureMaps_GeoParquet.ipynb |    371 +-
 39 files changed, 2139 insertions(+), 795623 deletions(-)

diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 8dc21c22a6..84b51dacbb 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -24,7 +24,7 @@ updates:
       interval: monthly
 
   - package-ecosystem: pip
-    directory: /docker/sedona-spark-jupyterlab
+    directory: /docker
     open-pull-requests-limit: 2
     schedule:
       interval: monthly
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 292bd296f6..c581b37b2f 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -22,12 +22,6 @@ root:
           - any-glob-to-any-file:
               - '*'
 
-binder:
-  - any:
-      - changed-files:
-          - any-glob-to-any-file:
-              - binder/**/*
-
 docs:
   - any:
       - changed-files:
diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml
index 4a8a9cee7d..106061c164 100644
--- a/.github/workflows/docker-build.yml
+++ b/.github/workflows/docker-build.yml
@@ -83,4 +83,4 @@ jobs:
           SPARK_VERSION: ${{ matrix.spark }}
           SEDONA_VERSION: ${{ matrix.sedona }}
           GEOTOOLS_VERSION: ${{ matrix.geotools }}
-        run: ./docker/sedona-spark-jupyterlab/build.sh ${SPARK_VERSION} ${SEDONA_VERSION} local ${GEOTOOLS_VERSION}
+        run: ./docker/build.sh ${SPARK_VERSION} ${SEDONA_VERSION} local ${GEOTOOLS_VERSION}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d4bee1fdbb..f78518204c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -103,7 +103,7 @@ repos:
         name: run black-jupyter
         description: format Python files and Jupyter Notebooks with black
   - repo: https://github.com/pre-commit/mirrors-clang-format
-    rev: v19.1.7
+    rev: v20.1.0
     hooks:
       - id: clang-format
         name: run clang-format
@@ -111,7 +111,7 @@ repos:
         args: [--style=Google]
         types_or: [c]
   - repo: https://github.com/PyCQA/bandit
-    rev: 1.8.2
+    rev: 1.8.3
     hooks:
       - id: bandit
         name: run bandit
@@ -125,20 +125,13 @@ repos:
         name: run codespell
         description: check spelling with codespell
         args: [--ignore-words=.github/linters/codespell.txt]
-        exclude: ^docs/image|^spark/common/src/test/resources|^docs/usecases|^tools/maven/scalafmt|osmpbf/build
+        exclude: ^docs/image|^spark/common/src/test/resources|^docs/usecases|^tools/maven/scalafmt|osmpbf/build|^docker/zeppelin
   - repo: https://github.com/gitleaks/gitleaks
-    rev: v8.23.3
+    rev: v8.24.0
     hooks:
       - id: gitleaks
         name: run gitleaks
         description: check for secrets with gitleaks
-  - repo: https://github.com/shssoichiro/oxipng
-    rev: v9.1.3
-    hooks:
-      - id: oxipng
-        name: run oxipng
-        description: check PNG files with oxipng
-        args: ["-o", "4", "--strip", "safe", "--alpha"]
   - repo: https://github.com/pre-commit/pre-commit-hooks
     rev: v5.0.0
     hooks:
@@ -183,7 +176,7 @@ repos:
               python/tests/tools\.py
           )$
       - id: requirements-txt-fixer
-        files: ^docker/sedona-spark-jupyterlab/requirements\.txt$
+        files: ^docker/requirements\.txt$
       - id: trailing-whitespace
         args: [--markdown-linebreak-ext=md]
         exclude: ^docs-overrides/main\.html$|\.Rd$
@@ -204,7 +197,7 @@ repos:
         name: run shellcheck
         description: check Shell scripts with shellcheck
   - repo: https://github.com/adrienverge/yamllint
-    rev: v1.35.1
+    rev: v1.36.2
     hooks:
       - id: yamllint
         name: run yamllint
@@ -212,3 +205,10 @@ repos:
         args: [--strict, -c=.github/linters/.yaml-lint.yml]
         types: [yaml]
         files: \.ya?ml$
+  - repo: https://github.com/shssoichiro/oxipng
+    rev: v9.1.4
+    hooks:
+      - id: oxipng
+        name: run oxipng
+        description: check PNG files with oxipng
+        args: ["-o", "4", "--strip", "safe", "--alpha"]
diff --git a/README.md b/README.md
index 2e236de94d..ca75ffefec 100644
--- a/README.md
+++ b/README.md
@@ -72,8 +72,6 @@ Some of the key features of Apache Sedona include:
 
 These are some of the key features of Apache Sedona, but it may offer additional capabilities depending on the specific version and configuration.
 
-Click [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/apache/sedona/HEAD?filepath=docs/usecases) and play the interactive Sedona Python [Jupyter](https://jupyter.org/) Notebook immediately!
-
 ## When to use Sedona?
 
 ### Use Cases:
diff --git a/binder/.dockerignore b/binder/.dockerignore
deleted file mode 100644
index d5c370693c..0000000000
--- a/binder/.dockerignore
+++ /dev/null
@@ -1,9 +0,0 @@
-# Ignore everything
-*
-
-# Allow files and folders with a pattern starting with !
-!binder
-!docs/usecases
-!README.md
-!NOTICE
-!LICENSE
diff --git a/binder/Pipfile b/binder/Pipfile
deleted file mode 100644
index 6bc499d6b1..0000000000
--- a/binder/Pipfile
+++ /dev/null
@@ -1,33 +0,0 @@
-# REMIND: Please run `pipenv lock` to update Pipfile.lock after editing this file
-
-[[source]]
-name = "pypi"
-url = "https://pypi.org/simple"
-verify_ssl = true
-
-[dev-packages]
-pytest="*"
-notebook="==6.0.0"
-jupyter = "*"
-mkdocs="*"
-pytest-cov = "*"
-
-[packages]
-pandas="==1.5.3"
-shapely="==2.0.4"
-geopandas="==0.14.4"
-pyspark="==3.4.2"
-attrs="*"
-apache-sedona="==1.6.0"
-matplotlib = "*"
-descartes = "*"
-keplergl = "==0.3.2"
-pydeck = "==0.8.0"
-ipywidgets = "*"
-jupyterlab-widgets = "==1.1.7"
-ipykernel = "*"
-jupyterlab = "==3.6.8"
-pyspark-extension = "==2.11.0.3.4"
-
-[requires]
-python_version = "3.9"
diff --git a/binder/Pipfile.lock b/binder/Pipfile.lock
deleted file mode 100644
index 54ae7bc722..0000000000
--- a/binder/Pipfile.lock
+++ /dev/null
@@ -1,3198 +0,0 @@
-{
-    "_meta": {
-        "hash": {
-            "sha256": "0231b3d4658cdda67b4fce7e882c17ee7fa88bc9c6596822a71b247cca4c979f"
-        },
-        "pipfile-spec": 6,
-        "requires": {
-            "python_version": "3.9"
-        },
-        "sources": [
-            {
-                "name": "pypi",
-                "url": "https://pypi.org/simple",
-                "verify_ssl": true
-            }
-        ]
-    },
-    "default": {
-        "affine": {
-            "hashes": [
-                "sha256:8a3df80e2b2378aef598a83c1392efd47967afec4242021a0b06b4c7cbc61a92",
-                "sha256:a24d818d6a836c131976d22f8c27b8d3ca32d0af64c1d8d29deb7bafa4da1eea"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.4.0"
-        },
-        "aiofiles": {
-            "hashes": [
-                "sha256:1142fa8e80dbae46bb6339573ad4c8c0841358f79c6eb50a493dceca14621bad",
-                "sha256:9107f1ca0b2a5553987a94a3c9959fe5b491fdf731389aa5b7b1bd0733e32de6"
-            ],
-            "markers": "python_version >= '3.7' and python_version < '4.0'",
-            "version": "==22.1.0"
-        },
-        "aiosqlite": {
-            "hashes": [
-                "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6",
-                "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.20.0"
-        },
-        "anyio": {
-            "hashes": [
-                "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94",
-                "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==4.4.0"
-        },
-        "apache-sedona": {
-            "hashes": [
-                "sha256:03096e88d385fc0f1b71f94be1936f5156d4e668e6347ca2c9733490a895c3ee",
-                "sha256:05058b876ea2457f140b4962f2e8187f03e40bf10dc0ab0f15e9a8bc4fbd2f79",
-                "sha256:05ad29648042ff5c67f61751c51e50feb213aaa6b1fb88cd3bd9a2ddbed4cad7",
-                "sha256:063ab3a263581a6d8beaa2a686cd88ae16ffd904fe4a3992e1df687c7d3c185b",
-                "sha256:109a1a59b5a466aff5cd25b9f526fece4036e560d9b00c535de32e35add35daa",
-                "sha256:24d72ee588f70fcad955d21245292825e463feccca61d5c84d4344fdbd42b3e4",
-                "sha256:2c6d16d17549885f9bb6a78691dbafdee5eb1d1dec50db1926a283a6a8642614",
-                "sha256:2d7cdbde3f1671a39e59f905e4a7577a428ae47686f26f035b3ff87b45beee0b",
-                "sha256:2e6c931023ae1a663c1d074d6640697ef0e57385c97968f8cf606132531c2f1d",
-                "sha256:2e8b21f00e5d1eb2824cb6fbd28be1d6803bb5e45db8a90eae2acf84f035f160",
-                "sha256:32246064fdaf4fd1e644349a357e9323d3329b4757027c9f8700411293f62ef5",
-                "sha256:43465354a01bdadf857c65943ee60ff6599934634b29875503b84f75069a2e7d",
-                "sha256:49bedbe6d0d074fcfb50cc6eda5ea4ece534451970e78c706fed2541b941495c",
-                "sha256:501528a24674eb15b2dce794e1fffa1318ee90daacdf683e4ef9199db7404e4a",
-                "sha256:52b6d419c21605af977b9946d5897885c7392e2e994596c828d2d9d0dd21d86c",
-                "sha256:540d13af7c782a06248b3a162c81275d7b07c8eb76948aec73a0e954e8e7d445",
-                "sha256:568c461eb2d04627eac6e2bf5f44310a0d266e5ca253371ff1ef478e38800ed7",
-                "sha256:62a78d6971d8499573f8d799c3b3602822f7229501fc286c7b874653d0af920c",
-                "sha256:6538184adb3385150f026ea480aff57198dc094aa140ea179cb0893b66e9af2d",
-                "sha256:6790031f0ef834d6dd40d68bf28a8da7460d73f7661a1af0bf72b03f7b79cb1d",
-                "sha256:776fab66dcad0379cea9a0d562a9faa0287cc5870bce19061d8256039777a737",
-                "sha256:7cf0e8ad2a1874e4bde7660dd5ac4b747b8177e8aa599ca9ee8a74251b8f19ac",
-                "sha256:8f9c1be8b7f7bf26e85a21ac92ad93dbd851881080b8b358b0a108f7dc2a1c9a",
-                "sha256:91eb820ab9d06813ba2a33f7cefe19b7ac37f0df3ad781f4222028f6c729c54a",
-                "sha256:9ca3898c9727b3754fc3eb598edf51d6aefa1cf31d1618ac357db8123ae5c468",
-                "sha256:a0dcc9940cb20d17c6d009e4ca1434da1ebb44d74a96ecf6e90061bae2e4a36c",
-                "sha256:ace58907afde089fd05c72b8047b33695fb29cf395aa1d5361a71ee63182f55a",
-                "sha256:bb03f61b27d9cb0791a7efcf8e80c7cab581b7989c13d38875247747e4ee5d9f",
-                "sha256:bb9d92471391e3a76dc6f9a62cdc846a6862e72649bb2759ef38ee8cd7bda00b",
-                "sha256:c9234f55c8ed4fcd4ccbeb7a14588f8bb775f314ecf7e3099d3a6ef9983618ee",
-                "sha256:ccf18173bd9bf0646c8b8e2a6d441c8815ac452cea561517e3425e0b21552822",
-                "sha256:dbff30d1cf11980af89e506eb31ec5b8765803f3ebae21a93e68eb37b19a336e",
-                "sha256:deb6caad1f36f407f25cd11f136d865e05349b3731231d2c033cba83b7ed6714",
-                "sha256:e14b1e9077219f7de67a24ca40b1fc60fcab6fd2e61e5e8bf1d13d0b4b12abb5",
-                "sha256:e2361f4bbc0e38cdc6320baf3e477d58329b07a6f478e27a16d10a8dd2613334",
-                "sha256:e81b95b79e2a7e45f22b058071ccd13324b85f53d7f93abfe380c4a91e0ae578",
-                "sha256:e88ecd377329163f722c18d0d9121a741a612b7877056e97610489593a16cfca",
-                "sha256:ef268501e489751294ca6a682f69ec6abb56c9cb04c4a466c3dea1b7a38bb8a8"
-            ],
-            "index": "pypi",
-            "version": "==1.6.0"
-        },
-        "argon2-cffi": {
-            "hashes": [
-                "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08",
-                "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==23.1.0"
-        },
-        "argon2-cffi-bindings": {
-            "hashes": [
-                "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670",
-                "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f",
-                "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583",
-                "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194",
-                "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c",
-                "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a",
-                "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082",
-                "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5",
-                "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f",
-                "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7",
-                "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d",
-                "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f",
-                "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae",
-                "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3",
-                "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86",
-                "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367",
-                "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d",
-                "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93",
-                "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb",
-                "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e",
-                "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==21.2.0"
-        },
-        "arrow": {
-            "hashes": [
-                "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80",
-                "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==1.3.0"
-        },
-        "asttokens": {
-            "hashes": [
-                "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24",
-                "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"
-            ],
-            "version": "==2.4.1"
-        },
-        "attrs": {
-            "hashes": [
-                "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346",
-                "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"
-            ],
-            "index": "pypi",
-            "markers": "python_version >= '3.7'",
-            "version": "==24.2.0"
-        },
-        "babel": {
-            "hashes": [
-                "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b",
-                "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.16.0"
-        },
-        "beautifulsoup4": {
-            "hashes": [
-                "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051",
-                "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"
-            ],
-            "markers": "python_full_version >= '3.6.0'",
-            "version": "==4.12.3"
-        },
-        "bleach": {
-            "hashes": [
-                "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe",
-                "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==6.1.0"
-        },
-        "certifi": {
-            "hashes": [
-                "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8",
-                "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==2024.8.30"
-        },
-        "cffi": {
-            "hashes": [
-                "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f",
-                "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab",
-                "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499",
-                "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058",
-                "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693",
-                "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb",
-                "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377",
-                "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885",
-                "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2",
-                "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401",
-                "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4",
-                "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b",
-                "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59",
-                "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f",
-                "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c",
-                "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555",
-                "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa",
-                "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424",
-                "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb",
-                "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2",
-                "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8",
-                "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e",
-                "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9",
-                "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82",
-                "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828",
-                "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759",
-                "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc",
-                "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118",
-                "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf",
-                "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932",
-                "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a",
-                "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29",
-                "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206",
-                "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2",
-                "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c",
-                "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c",
-                "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0",
-                "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a",
-                "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195",
-                "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6",
-                "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9",
-                "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc",
-                "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb",
-                "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0",
-                "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7",
-                "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb",
-                "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a",
-                "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492",
-                "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720",
-                "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42",
-                "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7",
-                "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d",
-                "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d",
-                "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb",
-                "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4",
-                "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2",
-                "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b",
-                "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8",
-                "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e",
-                "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204",
-                "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3",
-                "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150",
-                "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4",
-                "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76",
-                "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e",
-                "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb",
-                "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==1.17.0"
-        },
-        "charset-normalizer": {
-            "hashes": [
-                "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027",
-                "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087",
-                "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786",
-                "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8",
-                "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09",
-                "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185",
-                "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574",
-                "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e",
-                "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519",
-                "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898",
-                "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269",
-                "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3",
-                "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f",
-                "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6",
-                "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8",
-                "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a",
-                "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73",
-                "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc",
-                "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714",
-                "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2",
-                "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc",
-                "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce",
-                "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d",
-                "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e",
-                "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6",
-                "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269",
-                "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96",
-                "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d",
-                "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a",
-                "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4",
-                "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77",
-                "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d",
-                "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0",
-                "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed",
-                "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068",
-                "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac",
-                "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25",
-                "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8",
-                "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab",
-                "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26",
-                "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2",
-                "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db",
-                "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f",
-                "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5",
-                "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99",
-                "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c",
-                "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d",
-                "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811",
-                "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa",
-                "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a",
-                "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03",
-                "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b",
-                "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04",
-                "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c",
-                "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001",
-                "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458",
-                "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389",
-                "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99",
-                "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985",
-                "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537",
-                "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238",
-                "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f",
-                "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d",
-                "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796",
-                "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a",
-                "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143",
-                "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8",
-                "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c",
-                "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5",
-                "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5",
-                "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711",
-                "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4",
-                "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6",
-                "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c",
-                "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7",
-                "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4",
-                "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b",
-                "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae",
-                "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12",
-                "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c",
-                "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae",
-                "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8",
-                "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887",
-                "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b",
-                "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4",
-                "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f",
-                "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5",
-                "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33",
-                "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519",
-                "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"
-            ],
-            "markers": "python_full_version >= '3.7.0'",
-            "version": "==3.3.2"
-        },
-        "click": {
-            "hashes": [
-                "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
-                "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==8.1.7"
-        },
-        "click-plugins": {
-            "hashes": [
-                "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b",
-                "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"
-            ],
-            "version": "==1.1.1"
-        },
-        "cligj": {
-            "hashes": [
-                "sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27",
-                "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'",
-            "version": "==0.7.2"
-        },
-        "comm": {
-            "hashes": [
-                "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e",
-                "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.2.2"
-        },
-        "contourpy": {
-            "hashes": [
-                "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2",
-                "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9",
-                "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9",
-                "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4",
-                "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce",
-                "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7",
-                "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f",
-                "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922",
-                "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4",
-                "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e",
-                "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b",
-                "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619",
-                "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205",
-                "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480",
-                "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965",
-                "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c",
-                "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd",
-                "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5",
-                "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f",
-                "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc",
-                "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec",
-                "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd",
-                "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b",
-                "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9",
-                "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe",
-                "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce",
-                "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609",
-                "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8",
-                "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0",
-                "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f",
-                "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8",
-                "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b",
-                "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364",
-                "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040",
-                "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f",
-                "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083",
-                "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df",
-                "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba",
-                "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445",
-                "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da",
-                "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3",
-                "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72",
-                "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02",
-                "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985"
-            ],
-            "markers": "python_version >= '3.9'",
-            "version": "==1.2.1"
-        },
-        "cycler": {
-            "hashes": [
-                "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30",
-                "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.12.1"
-        },
-        "debugpy": {
-            "hashes": [
-                "sha256:0a1029a2869d01cb777216af8c53cda0476875ef02a2b6ff8b2f2c9a4b04176c",
-                "sha256:1cd04a73eb2769eb0bfe43f5bfde1215c5923d6924b9b90f94d15f207a402226",
-                "sha256:28ced650c974aaf179231668a293ecd5c63c0a671ae6d56b8795ecc5d2f48d3c",
-                "sha256:345d6a0206e81eb68b1493ce2fbffd57c3088e2ce4b46592077a943d2b968ca3",
-                "sha256:3df6692351172a42af7558daa5019651f898fc67450bf091335aa8a18fbf6f3a",
-                "sha256:4413b7a3ede757dc33a273a17d685ea2b0c09dbd312cc03f5534a0fd4d40750a",
-                "sha256:4fbb3b39ae1aa3e5ad578f37a48a7a303dad9a3d018d369bc9ec629c1cfa7408",
-                "sha256:55919dce65b471eff25901acf82d328bbd5b833526b6c1364bd5133754777a44",
-                "sha256:5b5c770977c8ec6c40c60d6f58cacc7f7fe5a45960363d6974ddb9b62dbee156",
-                "sha256:606bccba19f7188b6ea9579c8a4f5a5364ecd0bf5a0659c8a5d0e10dcee3032a",
-                "sha256:7b0fe36ed9d26cb6836b0a51453653f8f2e347ba7348f2bbfe76bfeb670bfb1c",
-                "sha256:7e4d594367d6407a120b76bdaa03886e9eb652c05ba7f87e37418426ad2079f7",
-                "sha256:8f913ee8e9fcf9d38a751f56e6de12a297ae7832749d35de26d960f14280750a",
-                "sha256:a697beca97dad3780b89a7fb525d5e79f33821a8bc0c06faf1f1289e549743cf",
-                "sha256:ad84b7cde7fd96cf6eea34ff6c4a1b7887e0fe2ea46e099e53234856f9d99a34",
-                "sha256:b2112cfeb34b4507399d298fe7023a16656fc553ed5246536060ca7bd0e668d0",
-                "sha256:b78c1250441ce893cb5035dd6f5fc12db968cc07f91cc06996b2087f7cefdd8e",
-                "sha256:c0a65b00b7cdd2ee0c2cf4c7335fef31e15f1b7056c7fdbce9e90193e1a8c8cb",
-                "sha256:c9f7c15ea1da18d2fcc2709e9f3d6de98b69a5b0fff1807fb80bc55f906691f7",
-                "sha256:db9fb642938a7a609a6c865c32ecd0d795d56c1aaa7a7a5722d77855d5e77f2b",
-                "sha256:dd3811bd63632bb25eda6bd73bea8e0521794cda02be41fa3160eb26fc29e7ed",
-                "sha256:e84c276489e141ed0b93b0af648eef891546143d6a48f610945416453a8ad406"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==1.8.5"
-        },
-        "decorator": {
-            "hashes": [
-                "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330",
-                "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==5.1.1"
-        },
-        "defusedxml": {
-            "hashes": [
-                "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
-                "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==0.7.1"
-        },
-        "descartes": {
-            "hashes": [
-                "sha256:135a502146af5ed6ff359975e2ebc5fa4b71b5432c355c2cafdc6dea1337035b",
-                "sha256:4c62dc41109689d03e4b35de0a2bcbdeeb81047badc607c4415d5c753bd683af",
-                "sha256:b7e412e7e6e294412f1d0f661f187babc970088c2456089e6801eebb043c2e1b"
-            ],
-            "index": "pypi",
-            "version": "==1.1.0"
-        },
-        "entrypoints": {
-            "hashes": [
-                "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4",
-                "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==0.4"
-        },
-        "exceptiongroup": {
-            "hashes": [
-                "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b",
-                "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"
-            ],
-            "markers": "python_version < '3.11'",
-            "version": "==1.2.2"
-        },
-        "executing": {
-            "hashes": [
-                "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147",
-                "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==2.0.1"
-        },
-        "fastjsonschema": {
-            "hashes": [
-                "sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23",
-                "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a"
-            ],
-            "version": "==2.20.0"
-        },
-        "fiona": {
-            "hashes": [
-                "sha256:065328fcc61caeca7c5d4b0b1b741213e422997ea66eaa04c2e207e1609eb0e7",
-                "sha256:16072f9313f60220310951561c5df29122d078d26316455dc58516c330881d35",
-                "sha256:1a7f28c5667d5cb05891fd00ed8ca2d3067bb58c90ca928a2741c0da74daf10d",
-                "sha256:2854fca6478ca3c1dc3f06531025550046f405488909a5be11ea97e4c55894db",
-                "sha256:2f8c6e7d2e5e2dc3b72b520cd160ca6d3689748b20d35b518eec9073f5a3c5bf",
-                "sha256:34575acb68b41009b28237121c728015aebb2ab3969cf46e57d9b9e5510ece61",
-                "sha256:3529fd46d269ff3f70aeb9316a93ae95cf2f87d7e148a8ff0d68532bf81ff7ae",
-                "sha256:382cffc905d44a933951857ae07f79cf59567db08f4201ed2a28f9f9bcc86932",
-                "sha256:5f1db6fc3f9212fdc817628981731e0adcdce88b0afd30531986c732a75f6a98",
-                "sha256:80929d220a92b50592b5ce96233d631c61a3c9b9bebd26ea136e7e48ffb50616",
-                "sha256:82514e8cc113245f2c7deda1c66f0aba67b183e20404365bbb7347e12b35c80f",
-                "sha256:8ad1ea80555bc56047d4b4e60c9424cf66875f161b3e56ebd60fa0574dc6693f",
-                "sha256:8c1a555b2546d3c69d0826a526a5c3e50a1cd330c95e2e0ebf6e82fa81fc2d96",
-                "sha256:a273cc957e86289609b47155f75d60c2cc37974a92618e016f75bc5ab9cc2dec",
-                "sha256:b2a42fb0a7168003015ab9648712f7d7eb971237af3c9be6000df69cd4b97ba7",
-                "sha256:bf11de868ab601b8d917dc38719b39bf8bdeaadc4fda9964d96b4b354e528fff",
-                "sha256:bfc9b76345a58ddcf9aae8697eb444728ff7a2cde7d5925f3031ff25e9eaa339",
-                "sha256:c0e72c4ed6ce7e2b2531a62844f3216528c149b55b89ff2881bcfe34d3b37300",
-                "sha256:cc68efb479a04be9a8e9c381940493c22a47f6aec5bbec22d0bb57a9cb240296",
-                "sha256:d099b1b5ab99db1d144ac47f657e14471178537d878ebd016c37afdf37303399",
-                "sha256:d95a93d6d37e04232b141d9f08190b30e7dc368126a34342d764b9d0a857d770",
-                "sha256:d99d2faa8cb3590dc0018a2832af39e1930b39cfff53017f3681d505bf813fee",
-                "sha256:ef27d75046e57b3e55c4b5d4e1bf7e43ef490e73ada5e86f9b148aa23a2e7fd4",
-                "sha256:ef44b3b0704244503a5f12ae92ca88b2cf4fedc9fd881b00c7c58b6899ca5133",
-                "sha256:f335beb927c5e9d9997a9cf665d6eee2e24191a02582f01786afa76119b71110"
-            ],
-            "index": "pypi",
-            "markers": "python_version >= '3.8'",
-            "version": "==1.10.0"
-        },
-        "fonttools": {
-            "hashes": [
-                "sha256:0118ef998a0699a96c7b28457f15546815015a2710a1b23a7bf6c1be60c01636",
-                "sha256:0d145976194a5242fdd22df18a1b451481a88071feadf251221af110ca8f00ce",
-                "sha256:0e19bd9e9964a09cd2433a4b100ca7f34e34731e0758e13ba9a1ed6e5468cc0f",
-                "sha256:0f08c901d3866a8905363619e3741c33f0a83a680d92a9f0e575985c2634fcc1",
-                "sha256:1250e818b5f8a679ad79660855528120a8f0288f8f30ec88b83db51515411fcc",
-                "sha256:15c94eeef6b095831067f72c825eb0e2d48bb4cea0647c1b05c981ecba2bf39f",
-                "sha256:1621ee57da887c17312acc4b0e7ac30d3a4fb0fec6174b2e3754a74c26bbed1e",
-                "sha256:180194c7fe60c989bb627d7ed5011f2bef1c4d36ecf3ec64daec8302f1ae0716",
-                "sha256:278e50f6b003c6aed19bae2242b364e575bcb16304b53f2b64f6551b9c000e15",
-                "sha256:32b17504696f605e9e960647c5f64b35704782a502cc26a37b800b4d69ff3c77",
-                "sha256:3bee3f3bd9fa1d5ee616ccfd13b27ca605c2b4270e45715bd2883e9504735034",
-                "sha256:4060acc2bfa2d8e98117828a238889f13b6f69d59f4f2d5857eece5277b829ba",
-                "sha256:54dcf21a2f2d06ded676e3c3f9f74b2bafded3a8ff12f0983160b13e9f2fb4a7",
-                "sha256:56fc244f2585d6c00b9bcc59e6593e646cf095a96fe68d62cd4da53dd1287b55",
-                "sha256:599bdb75e220241cedc6faebfafedd7670335d2e29620d207dd0378a4e9ccc5a",
-                "sha256:5f6bc991d1610f5c3bbe997b0233cbc234b8e82fa99fc0b2932dc1ca5e5afec0",
-                "sha256:60a3409c9112aec02d5fb546f557bca6efa773dcb32ac147c6baf5f742e6258b",
-                "sha256:68b3fb7775a923be73e739f92f7e8a72725fd333eab24834041365d2278c3671",
-                "sha256:76f1777d8b3386479ffb4a282e74318e730014d86ce60f016908d9801af9ca2a",
-                "sha256:806e7912c32a657fa39d2d6eb1d3012d35f841387c8fc6cf349ed70b7c340039",
-                "sha256:84d7751f4468dd8cdd03ddada18b8b0857a5beec80bce9f435742abc9a851a74",
-                "sha256:865a58b6e60b0938874af0968cd0553bcd88e0b2cb6e588727117bd099eef836",
-                "sha256:8ac27f436e8af7779f0bb4d5425aa3535270494d3bc5459ed27de3f03151e4c2",
-                "sha256:8b4850fa2ef2cfbc1d1f689bc159ef0f45d8d83298c1425838095bf53ef46308",
-                "sha256:8b5ad456813d93b9c4b7ee55302208db2b45324315129d85275c01f5cb7e61a2",
-                "sha256:8e2f1a4499e3b5ee82c19b5ee57f0294673125c65b0a1ff3764ea1f9db2f9ef5",
-                "sha256:9696fe9f3f0c32e9a321d5268208a7cc9205a52f99b89479d1b035ed54c923f1",
-                "sha256:96a48e137c36be55e68845fc4284533bda2980f8d6f835e26bca79d7e2006438",
-                "sha256:a8feca65bab31479d795b0d16c9a9852902e3a3c0630678efb0b2b7941ea9c74",
-                "sha256:aefa011207ed36cd280babfaa8510b8176f1a77261833e895a9d96e57e44802f",
-                "sha256:b2b92381f37b39ba2fc98c3a45a9d6383bfc9916a87d66ccb6553f7bdd129097",
-                "sha256:b3c61423f22165541b9403ee39874dcae84cd57a9078b82e1dce8cb06b07fa2e",
-                "sha256:b5b48a1121117047d82695d276c2af2ee3a24ffe0f502ed581acc2673ecf1037",
-                "sha256:c18b49adc721a7d0b8dfe7c3130c89b8704baf599fb396396d07d4aa69b824a1",
-                "sha256:c5b8cab0c137ca229433570151b5c1fc6af212680b58b15abd797dcdd9dd5051",
-                "sha256:c7e91abdfae1b5c9e3a543f48ce96013f9a08c6c9668f1e6be0beabf0a569c1b",
-                "sha256:cadf4e12a608ef1d13e039864f484c8a968840afa0258b0b843a0556497ea9ed",
-                "sha256:dc0673361331566d7a663d7ce0f6fdcbfbdc1f59c6e3ed1165ad7202ca183c68",
-                "sha256:de7c29bdbdd35811f14493ffd2534b88f0ce1b9065316433b22d63ca1cd21f14",
-                "sha256:e9d9298be7a05bb4801f558522adbe2feea1b0b103d5294ebf24a92dd49b78e5",
-                "sha256:ee1af4be1c5afe4c96ca23badd368d8dc75f611887fb0c0dac9f71ee5d6f110e",
-                "sha256:f7e89853d8bea103c8e3514b9f9dc86b5b4120afb4583b57eb10dfa5afbe0936"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==4.51.0"
-        },
-        "fqdn": {
-            "hashes": [
-                "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f",
-                "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"
-            ],
-            "version": "==1.5.1"
-        },
-        "geopandas": {
-            "hashes": [
-                "sha256:3bb6473cb59d51e1a7fe2dbc24a1a063fb0ebdeddf3ce08ddbf8c7ddc99689aa",
-                "sha256:56765be9d58e2c743078085db3bd07dc6be7719f0dbe1dfdc1d705cb80be7c25"
-            ],
-            "index": "pypi",
-            "version": "==0.14.4"
-        },
-        "idna": {
-            "hashes": [
-                "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac",
-                "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==3.8"
-        },
-        "importlib-metadata": {
-            "hashes": [
-                "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1",
-                "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"
-            ],
-            "markers": "python_version < '3.10'",
-            "version": "==8.4.0"
-        },
-        "importlib-resources": {
-            "hashes": [
-                "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c",
-                "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"
-            ],
-            "markers": "python_version < '3.10'",
-            "version": "==6.4.0"
-        },
-        "ipykernel": {
-            "hashes": [
-                "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5",
-                "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"
-            ],
-            "index": "pypi",
-            "markers": "python_version >= '3.8'",
-            "version": "==6.29.5"
-        },
-        "ipython": {
-            "hashes": [
-                "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27",
-                "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"
-            ],
-            "markers": "python_version >= '3.9'",
-            "version": "==8.18.1"
-        },
-        "ipython-genutils": {
-            "hashes": [
-                "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8",
-                "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"
-            ],
-            "version": "==0.2.0"
-        },
-        "ipywidgets": {
-            "hashes": [
-                "sha256:050b87bb9ac11641859af4c36cdb639ca072fb5e121f0f1a401f8a80f9fa008d",
-                "sha256:29f7056d368bf0a7b35d51cf0c56b58582da57c78bb9f765965fef7c332e807c"
-            ],
-            "index": "pypi",
-            "version": "==7.8.1"
-        },
-        "isoduration": {
-            "hashes": [
-                "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9",
-                "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"
-            ],
-            "version": "==20.11.0"
-        },
-        "jedi": {
-            "hashes": [
-                "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd",
-                "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==0.19.1"
-        },
-        "jinja2": {
-            "hashes": [
-                "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d",
-                "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"
-            ],
-            "index": "pypi",
-            "markers": "python_version >= '3.7'",
-            "version": "==3.1.6"
-        },
-        "json5": {
-            "hashes": [
-                "sha256:34ed7d834b1341a86987ed52f3f76cd8ee184394906b6e22a1e0deb9ab294e8f",
-                "sha256:548e41b9be043f9426776f05df8635a00fe06104ea51ed24b67f908856e151ae"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.9.25"
-        },
-        "jsonpointer": {
-            "hashes": [
-                "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942",
-                "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"
-            ],
-            "version": "==3.0.0"
-        },
-        "jsonschema": {
-            "extras": [
-                "format-nongpl"
-            ],
-            "hashes": [
-                "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4",
-                "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==4.23.0"
-        },
-        "jsonschema-specifications": {
-            "hashes": [
-                "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc",
-                "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2023.12.1"
-        },
-        "jupyter-client": {
-            "hashes": [
-                "sha256:214668aaea208195f4c13d28eb272ba79f945fc0cf3f11c7092c20b2ca1980e7",
-                "sha256:52be28e04171f07aed8f20e1616a5a552ab9fee9cbbe6c1896ae170c3880d392"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==7.4.9"
-        },
-        "jupyter-core": {
-            "hashes": [
-                "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409",
-                "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==5.7.2"
-        },
-        "jupyter-events": {
-            "hashes": [
-                "sha256:4b72130875e59d57716d327ea70d3ebc3af1944d3717e5a498b8a06c6c159960",
-                "sha256:670b8229d3cc882ec782144ed22e0d29e1c2d639263f92ca8383e66682845e22"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.10.0"
-        },
-        "jupyter-server": {
-            "hashes": [
-                "sha256:47ff506127c2f7851a17bf4713434208fc490955d0e8632e95014a9a9afbeefd",
-                "sha256:66095021aa9638ced276c248b1d81862e4c50f292d575920bbe960de1c56b12b"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.14.2"
-        },
-        "jupyter-server-fileid": {
-            "hashes": [
-                "sha256:76a2fbcea6950968485dcd509c2d6ac417ca11e61ab1ad447a475f0878ca808f",
-                "sha256:ffb11460ca5f8567644f6120b25613fca8e3f3048b38d14c6e3fe1902f314a9b"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.9.2"
-        },
-        "jupyter-server-terminals": {
-            "hashes": [
-                "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa",
-                "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.5.3"
-        },
-        "jupyter-server-ydoc": {
-            "hashes": [
-                "sha256:969a3a1a77ed4e99487d60a74048dc9fa7d3b0dcd32e60885d835bbf7ba7be11",
-                "sha256:a6fe125091792d16c962cc3720c950c2b87fcc8c3ecf0c54c84e9a20b814526c"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.8.0"
-        },
-        "jupyter-ydoc": {
-            "hashes": [
-                "sha256:5759170f112c70320a84217dd98d287699076ae65a7f88d458d57940a9f2b882",
-                "sha256:5a02ca7449f0d875f73e8cb8efdf695dddef15a8e71378b1f4eda6b7c90f5382"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.2.5"
-        },
-        "jupyterlab": {
-            "hashes": [
-                "sha256:891284e75158998e23eb7a23ecc4caaf27b365e41adca374109b1305b9f769db",
-                "sha256:a2477383e23f20009188bd9dac7e6e38dbc54307bc36d716bea6ced450647c97"
-            ],
-            "index": "pypi",
-            "markers": "python_version >= '3.7'",
-            "version": "==3.6.8"
-        },
-        "jupyterlab-pygments": {
-            "hashes": [
-                "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d",
-                "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.3.0"
-        },
-        "jupyterlab-server": {
-            "hashes": [
-                "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4",
-                "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.27.3"
-        },
-        "jupyterlab-widgets": {
-            "hashes": [
-                "sha256:0c4548cf42032e490447e4180f2c7d49ba5c30b42164992b38fb8c9d56c4e1b2",
-                "sha256:318dab34267915d658e7b0dc57433ff0ce0d52b3e283986b73b66f7ab9017ae8"
-            ],
-            "index": "pypi",
-            "version": "==1.1.7"
-        },
-        "keplergl": {
-            "hashes": [
-                "sha256:1762c4212f177843884ad51f74e06ee3efe58ea3711d0327b6aa99ccbed52815"
-            ],
-            "index": "pypi",
-            "version": "==0.3.2"
-        },
-        "kiwisolver": {
-            "hashes": [
-                "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf",
-                "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e",
-                "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af",
-                "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f",
-                "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046",
-                "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3",
-                "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5",
-                "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71",
-                "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee",
-                "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3",
-                "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9",
-                "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b",
-                "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985",
-                "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea",
-                "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16",
-                "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89",
-                "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c",
-                "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9",
-                "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712",
-                "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342",
-                "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a",
-                "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958",
-                "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d",
-                "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a",
-                "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130",
-                "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff",
-                "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898",
-                "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b",
-                "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f",
-                "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265",
-                "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93",
-                "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929",
-                "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635",
-                "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709",
-                "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b",
-                "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb",
-                "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a",
-                "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920",
-                "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e",
-                "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544",
-                "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45",
-                "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390",
-                "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77",
-                "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355",
-                "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff",
-                "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4",
-                "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7",
-                "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20",
-                "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c",
-                "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162",
-                "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228",
-                "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437",
-                "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc",
-                "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a",
-                "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901",
-                "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4",
-                "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770",
-                "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525",
-                "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad",
-                "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a",
-                "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29",
-                "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90",
-                "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250",
-                "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d",
-                "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3",
-                "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54",
-                "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f",
-                "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1",
-                "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da",
-                "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238",
-                "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa",
-                "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523",
-                "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0",
-                "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205",
-                "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3",
-                "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4",
-                "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac",
-                "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9",
-                "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb",
-                "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced",
-                "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd",
-                "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0",
-                "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da",
-                "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18",
-                "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9",
-                "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276",
-                "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333",
-                "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b",
-                "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db",
-                "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126",
-                "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9",
-                "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09",
-                "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0",
-                "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec",
-                "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7",
-                "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff",
-                "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9",
-                "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192",
-                "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8",
-                "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d",
-                "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6",
-                "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797",
-                "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892",
-                "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==1.4.5"
-        },
-        "markupsafe": {
-            "hashes": [
-                "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4",
-                "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30",
-                "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0",
-                "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9",
-                "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396",
-                "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13",
-                "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028",
-                "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca",
-                "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557",
-                "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832",
-                "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0",
-                "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b",
-                "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579",
-                "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a",
-                "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c",
-                "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff",
-                "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c",
-                "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22",
-                "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094",
-                "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb",
-                "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e",
-                "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5",
-                "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a",
-                "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d",
-                "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a",
-                "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b",
-                "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8",
-                "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225",
-                "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c",
-                "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144",
-                "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f",
-                "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87",
-                "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d",
-                "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93",
-                "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf",
-                "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158",
-                "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84",
-                "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb",
-                "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48",
-                "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171",
-                "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c",
-                "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6",
-                "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd",
-                "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d",
-                "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1",
-                "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d",
-                "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca",
-                "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a",
-                "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29",
-                "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe",
-                "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798",
-                "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c",
-                "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8",
-                "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f",
-                "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f",
-                "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a",
-                "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178",
-                "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0",
-                "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79",
-                "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430",
-                "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"
-            ],
-            "markers": "python_version >= '3.9'",
-            "version": "==3.0.2"
-        },
-        "matplotlib": {
-            "hashes": [
-                "sha256:063af8587fceeac13b0936c42a2b6c732c2ab1c98d38abc3337e430e1ff75e38",
-                "sha256:06a478f0d67636554fa78558cfbcd7b9dba85b51f5c3b5a0c9be49010cf5f321",
-                "sha256:0a490715b3b9984fa609116481b22178348c1a220a4499cda79132000a79b4db",
-                "sha256:0fc51eaa5262553868461c083d9adadb11a6017315f3a757fc45ec6ec5f02888",
-                "sha256:13beb4840317d45ffd4183a778685e215939be7b08616f431c7795276e067463",
-                "sha256:290d304e59be2b33ef5c2d768d0237f5bd132986bdcc66f80bc9bcc300066a03",
-                "sha256:2bcee1dffaf60fe7656183ac2190bd630842ff87b3153afb3e384d966b57fe56",
-                "sha256:2e7f03e5cbbfacdd48c8ea394d365d91ee8f3cae7e6ec611409927b5ed997ee4",
-                "sha256:3f988bafb0fa39d1074ddd5bacd958c853e11def40800c5824556eb630f94d3b",
-                "sha256:52146fc3bd7813cc784562cb93a15788be0b2875c4655e2cc6ea646bfa30344b",
-                "sha256:550cdda3adbd596078cca7d13ed50b77879104e2e46392dcd7c75259d8f00e85",
-                "sha256:616fabf4981a3b3c5a15cd95eba359c8489c4e20e03717aea42866d8d0465956",
-                "sha256:76cce0f31b351e3551d1f3779420cf8f6ec0d4a8cf9c0237a3b549fd28eb4abb",
-                "sha256:7ff2e239c26be4f24bfa45860c20ffccd118d270c5b5d081fa4ea409b5469fcd",
-                "sha256:8146ce83cbc5dc71c223a74a1996d446cd35cfb6a04b683e1446b7e6c73603b7",
-                "sha256:81c40af649d19c85f8073e25e5806926986806fa6d54be506fbf02aef47d5a89",
-                "sha256:9a2fa6d899e17ddca6d6526cf6e7ba677738bf2a6a9590d702c277204a7c6152",
-                "sha256:a5be985db2596d761cdf0c2eaf52396f26e6a64ab46bd8cd810c48972349d1be",
-                "sha256:af4001b7cae70f7eaacfb063db605280058246de590fa7874f00f62259f2df7e",
-                "sha256:bd4f2831168afac55b881db82a7730992aa41c4f007f1913465fb182d6fb20c0",
-                "sha256:bdd1ecbe268eb3e7653e04f451635f0fb0f77f07fd070242b44c076c9106da84",
-                "sha256:c53aeb514ccbbcbab55a27f912d79ea30ab21ee0531ee2c09f13800efb272674",
-                "sha256:c79f3a585f1368da6049318bdf1f85568d8d04b2e89fc24b7e02cc9b62017382",
-                "sha256:cd53c79fd02f1c1808d2cfc87dd3cf4dbc63c5244a58ee7944497107469c8d8a",
-                "sha256:d38e85a1a6d732f645f1403ce5e6727fd9418cd4574521d5803d3d94911038e5",
-                "sha256:d91a4ffc587bacf5c4ce4ecfe4bcd23a4b675e76315f2866e588686cc97fccdf",
-                "sha256:e6d29ea6c19e34b30fb7d88b7081f869a03014f66fe06d62cc77d5a6ea88ed7a",
-                "sha256:eaf3978060a106fab40c328778b148f590e27f6fa3cd15a19d6892575bce387d",
-                "sha256:fe428e191ea016bb278758c8ee82a8129c51d81d8c4bc0846c09e7e8e9057241"
-            ],
-            "index": "pypi",
-            "version": "==3.9.0"
-        },
-        "matplotlib-inline": {
-            "hashes": [
-                "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90",
-                "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.1.7"
-        },
-        "mistune": {
-            "hashes": [
-                "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205",
-                "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.0.2"
-        },
-        "nbclassic": {
-            "hashes": [
-                "sha256:77b77ba85f9e988f9bad85df345b514e9e64c7f0e822992ab1df4a78ac64fc1e",
-                "sha256:8c0fd6e36e320a18657ff44ed96c3a400f17a903a3744fc322303a515778f2ba"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==1.1.0"
-        },
-        "nbclient": {
-            "hashes": [
-                "sha256:4b3f1b7dba531e498449c4db4f53da339c91d449dc11e9af3a43b4eb5c5abb09",
-                "sha256:f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f"
-            ],
-            "markers": "python_full_version >= '3.8.0'",
-            "version": "==0.10.0"
-        },
-        "nbconvert": {
-            "hashes": [
-                "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3",
-                "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==7.16.4"
-        },
-        "nbformat": {
-            "hashes": [
-                "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a",
-                "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==5.10.4"
-        },
-        "nest-asyncio": {
-            "hashes": [
-                "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe",
-                "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==1.6.0"
-        },
-        "notebook": {
-            "hashes": [
-                "sha256:04eb9011dfac634fbd4442adaf0a8c27cd26beef831fe1d19faf930c327768e4",
-                "sha256:a6afa9a4ff4d149a0771ff8b8c881a7a73b3835f9add0606696d6e9d98ac1cd0"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==6.5.7"
-        },
-        "notebook-shim": {
-            "hashes": [
-                "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef",
-                "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.2.4"
-        },
-        "numpy": {
-            "hashes": [
-                "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b",
-                "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818",
-                "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20",
-                "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0",
-                "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010",
-                "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a",
-                "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea",
-                "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c",
-                "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71",
-                "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110",
-                "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be",
-                "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a",
-                "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a",
-                "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5",
-                "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed",
-                "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd",
-                "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c",
-                "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e",
-                "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0",
-                "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c",
-                "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a",
-                "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b",
-                "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0",
-                "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6",
-                "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2",
-                "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a",
-                "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30",
-                "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218",
-                "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5",
-                "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07",
-                "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2",
-                "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4",
-                "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764",
-                "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef",
-                "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3",
-                "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"
-            ],
-            "markers": "python_version >= '3.9'",
-            "version": "==1.26.4"
-        },
-        "overrides": {
-            "hashes": [
-                "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a",
-                "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==7.7.0"
-        },
-        "packaging": {
-            "hashes": [
-                "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002",
-                "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==24.1"
-        },
-        "pandas": {
-            "hashes": [
-                "sha256:14e45300521902689a81f3f41386dc86f19b8ba8dd5ac5a3c7010ef8d2932813",
-                "sha256:26d9c71772c7afb9d5046e6e9cf42d83dd147b5cf5bcb9d97252077118543792",
-                "sha256:3749077d86e3a2f0ed51367f30bf5b82e131cc0f14260c4d3e499186fccc4406",
-                "sha256:41179ce559943d83a9b4bbacb736b04c928b095b5f25dd2b7389eda08f46f373",
-                "sha256:478ff646ca42b20376e4ed3fa2e8d7341e8a63105586efe54fa2508ee087f328",
-                "sha256:50869a35cbb0f2e0cd5ec04b191e7b12ed688874bd05dd777c19b28cbea90996",
-                "sha256:565fa34a5434d38e9d250af3c12ff931abaf88050551d9fbcdfafca50d62babf",
-                "sha256:5f2b952406a1588ad4cad5b3f55f520e82e902388a6d5a4a91baa8d38d23c7f6",
-                "sha256:5fbcb19d6fceb9e946b3e23258757c7b225ba450990d9ed63ccceeb8cae609f7",
-                "sha256:6973549c01ca91ec96199e940495219c887ea815b2083722821f1d7abfa2b4dc",
-                "sha256:74a3fd7e5a7ec052f183273dc7b0acd3a863edf7520f5d3a1765c04ffdb3b0b1",
-                "sha256:7a0a56cef15fd1586726dace5616db75ebcfec9179a3a55e78f72c5639fa2a23",
-                "sha256:7cec0bee9f294e5de5bbfc14d0573f65526071029d036b753ee6507d2a21480a",
-                "sha256:87bd9c03da1ac870a6d2c8902a0e1fd4267ca00f13bc494c9e5a9020920e1d51",
-                "sha256:972d8a45395f2a2d26733eb8d0f629b2f90bebe8e8eddbb8829b180c09639572",
-                "sha256:9842b6f4b8479e41968eced654487258ed81df7d1c9b7b870ceea24ed9459b31",
-                "sha256:9f69c4029613de47816b1bb30ff5ac778686688751a5e9c99ad8c7031f6508e5",
-                "sha256:a50d9a4336a9621cab7b8eb3fb11adb82de58f9b91d84c2cd526576b881a0c5a",
-                "sha256:bc4c368f42b551bf72fac35c5128963a171b40dce866fb066540eeaf46faa003",
-                "sha256:c39a8da13cede5adcd3be1182883aea1c925476f4e84b2807a46e2775306305d",
-                "sha256:c3ac844a0fe00bfaeb2c9b51ab1424e5c8744f89860b138434a363b1f620f354",
-                "sha256:c4c00e0b0597c8e4f59e8d461f797e5d70b4d025880516a8261b2817c47759ee",
-                "sha256:c74a62747864ed568f5a82a49a23a8d7fe171d0c69038b38cedf0976831296fa",
-                "sha256:dd05f7783b3274aa206a1af06f0ceed3f9b412cf665b7247eacd83be41cf7bf0",
-                "sha256:dfd681c5dc216037e0b0a2c821f5ed99ba9f03ebcf119c7dac0e9a7b960b9ec9",
-                "sha256:e474390e60ed609cec869b0da796ad94f420bb057d86784191eefc62b65819ae",
-                "sha256:f76d097d12c82a535fda9dfe5e8dd4127952b45fea9b0276cb30cca5ea313fbc"
-            ],
-            "index": "pypi",
-            "version": "==1.5.3"
-        },
-        "pandocfilters": {
-            "hashes": [
-                "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e",
-                "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.5.1"
-        },
-        "parso": {
-            "hashes": [
-                "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18",
-                "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==0.8.4"
-        },
-        "pexpect": {
-            "hashes": [
-                "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523",
-                "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"
-            ],
-            "markers": "sys_platform != 'win32'",
-            "version": "==4.9.0"
-        },
-        "pillow": {
-            "hashes": [
-                "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c",
-                "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2",
-                "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb",
-                "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d",
-                "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa",
-                "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3",
-                "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1",
-                "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a",
-                "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd",
-                "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8",
-                "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999",
-                "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599",
-                "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936",
-                "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375",
-                "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d",
-                "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b",
-                "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60",
-                "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572",
-                "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3",
-                "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced",
-                "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f",
-                "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b",
-                "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19",
-                "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f",
-                "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d",
-                "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383",
-                "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795",
-                "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355",
-                "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57",
-                "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09",
-                "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b",
-                "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462",
-                "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf",
-                "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f",
-                "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a",
-                "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad",
-                "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9",
-                "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d",
-                "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45",
-                "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994",
-                "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d",
-                "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338",
-                "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463",
-                "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451",
-                "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591",
-                "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c",
-                "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd",
-                "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32",
-                "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9",
-                "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf",
-                "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5",
-                "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828",
-                "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3",
-                "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5",
-                "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2",
-                "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b",
-                "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2",
-                "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475",
-                "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3",
-                "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb",
-                "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef",
-                "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015",
-                "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002",
-                "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170",
-                "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84",
-                "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57",
-                "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f",
-                "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27",
-                "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==10.3.0"
-        },
-        "platformdirs": {
-            "hashes": [
-                "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee",
-                "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==4.2.2"
-        },
-        "prometheus-client": {
-            "hashes": [
-                "sha256:287629d00b147a32dcb2be0b9df905da599b2d82f80377083ec8463309a4bb89",
-                "sha256:cde524a85bce83ca359cc837f28b8c0db5cac7aa653a588fd7e84ba061c329e7"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.20.0"
-        },
-        "prompt-toolkit": {
-            "hashes": [
-                "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10",
-                "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"
-            ],
-            "markers": "python_full_version >= '3.7.0'",
-            "version": "==3.0.47"
-        },
-        "psutil": {
-            "hashes": [
-                "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35",
-                "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0",
-                "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c",
-                "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1",
-                "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3",
-                "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c",
-                "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd",
-                "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3",
-                "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0",
-                "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2",
-                "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6",
-                "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d",
-                "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c",
-                "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0",
-                "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132",
-                "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14",
-                "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
-            "version": "==6.0.0"
-        },
-        "ptyprocess": {
-            "hashes": [
-                "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35",
-                "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"
-            ],
-            "version": "==0.7.0"
-        },
-        "pure-eval": {
-            "hashes": [
-                "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0",
-                "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"
-            ],
-            "version": "==0.2.3"
-        },
-        "py4j": {
-            "hashes": [
-                "sha256:0b6e5315bb3ada5cf62ac651d107bb2ebc02def3dee9d9548e3baac644ea8dbb",
-                "sha256:85defdfd2b2376eb3abf5ca6474b51ab7e0de341c75a02f46dc9b5976f5a5c1b"
-            ],
-            "version": "==0.10.9.7"
-        },
-        "pycparser": {
-            "hashes": [
-                "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6",
-                "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.22"
-        },
-        "pydeck": {
-            "hashes": [
-                "sha256:07edde833f7cfcef6749124351195aa7dcd24663d4909fd7898dbd0b6fbc01ec",
-                "sha256:a8fa7757c6f24bba033af39db3147cb020eef44012ba7e60d954de187f9ed4d5"
-            ],
-            "index": "pypi",
-            "version": "==0.8.0"
-        },
-        "pygments": {
-            "hashes": [
-                "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199",
-                "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.18.0"
-        },
-        "pyparsing": {
-            "hashes": [
-                "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad",
-                "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"
-            ],
-            "markers": "python_full_version >= '3.6.8'",
-            "version": "==3.1.2"
-        },
-        "pyproj": {
-            "hashes": [
-                "sha256:18faa54a3ca475bfe6255156f2f2874e9a1c8917b0004eee9f664b86ccc513d3",
-                "sha256:1e9fbaf920f0f9b4ee62aab832be3ae3968f33f24e2e3f7fbb8c6728ef1d9746",
-                "sha256:2d6ff73cc6dbbce3766b6c0bce70ce070193105d8de17aa2470009463682a8eb",
-                "sha256:36b64c2cb6ea1cc091f329c5bd34f9c01bb5da8c8e4492c709bda6a09f96808f",
-                "sha256:38a3361941eb72b82bd9a18f60c78b0df8408416f9340521df442cebfc4306e2",
-                "sha256:447db19c7efad70ff161e5e46a54ab9cc2399acebb656b6ccf63e4bc4a04b97a",
-                "sha256:44aa7c704c2b7d8fb3d483bbf75af6cb2350d30a63b144279a09b75fead501bf",
-                "sha256:4ba1f9b03d04d8cab24d6375609070580a26ce76eaed54631f03bab00a9c737b",
-                "sha256:4bc0472302919e59114aa140fd7213c2370d848a7249d09704f10f5b062031fe",
-                "sha256:50100b2726a3ca946906cbaa789dd0749f213abf0cbb877e6de72ca7aa50e1ae",
-                "sha256:5279586013b8d6582e22b6f9e30c49796966770389a9d5b85e25a4223286cd3f",
-                "sha256:6420ea8e7d2a88cb148b124429fba8cd2e0fae700a2d96eab7083c0928a85110",
-                "sha256:65ad699e0c830e2b8565afe42bd58cc972b47d829b2e0e48ad9638386d994915",
-                "sha256:6d227a865356f225591b6732430b1d1781e946893789a609bb34f59d09b8b0f8",
-                "sha256:7a27151ddad8e1439ba70c9b4b2b617b290c39395fa9ddb7411ebb0eb86d6fb0",
-                "sha256:80fafd1f3eb421694857f254a9bdbacd1eb22fc6c24ca74b136679f376f97d35",
-                "sha256:83039e5ae04e5afc974f7d25ee0870a80a6bd6b7957c3aca5613ccbe0d3e72bf",
-                "sha256:8b8acc31fb8702c54625f4d5a2a6543557bec3c28a0ef638778b7ab1d1772132",
-                "sha256:9274880263256f6292ff644ca92c46d96aa7e57a75c6df3f11d636ce845a1877",
-                "sha256:ab7aa4d9ff3c3acf60d4b285ccec134167a948df02347585fdd934ebad8811b4",
-                "sha256:c41e80ddee130450dcb8829af7118f1ab69eaf8169c4bf0ee8d52b72f098dc2f",
-                "sha256:db3aedd458e7f7f21d8176f0a1d924f1ae06d725228302b872885a1c34f3119e",
-                "sha256:e7e13c40183884ec7f94eb8e0f622f08f1d5716150b8d7a134de48c6110fee85",
-                "sha256:ebfbdbd0936e178091309f6cd4fcb4decd9eab12aa513cdd9add89efa3ec2882",
-                "sha256:fd43bd9a9b9239805f406fd82ba6b106bf4838d9ef37c167d3ed70383943ade1",
-                "sha256:fd93c1a0c6c4aedc77c0fe275a9f2aba4d59b8acf88cebfc19fe3c430cfabf4f",
-                "sha256:fffb059ba3bced6f6725961ba758649261d85ed6ce670d3e3b0a26e81cf1aa8d"
-            ],
-            "markers": "python_version >= '3.9'",
-            "version": "==3.6.1"
-        },
-        "pyspark": {
-            "hashes": [
-                "sha256:088db1b8ff33a748b802f1710ff6f6dcef0e0f2cca7d69bbbe55b187a0d55c3f"
-            ],
-            "index": "pypi",
-            "version": "==3.4.2"
-        },
-        "pyspark-extension": {
-            "hashes": [
-                "sha256:20026ab66a1ef5faa5f68d0ca708255309db688d0de356908186dda7ae4f4ae0",
-                "sha256:7d66f19c5718840dd9e9c5f94f8b5b27837da1e244a0690e3a3a224045c21f63"
-            ],
-            "index": "pypi",
-            "version": "==2.11.0.3.4"
-        },
-        "python-dateutil": {
-            "hashes": [
-                "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
-                "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==2.9.0.post0"
-        },
-        "python-json-logger": {
-            "hashes": [
-                "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c",
-                "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==2.0.7"
-        },
-        "pytz": {
-            "hashes": [
-                "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812",
-                "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"
-            ],
-            "version": "==2024.1"
-        },
-        "pyyaml": {
-            "hashes": [
-                "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff",
-                "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48",
-                "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086",
-                "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e",
-                "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133",
-                "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5",
-                "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484",
-                "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee",
-                "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5",
-                "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68",
-                "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a",
-                "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf",
-                "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99",
-                "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8",
-                "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85",
-                "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19",
-                "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc",
-                "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a",
-                "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1",
-                "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317",
-                "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c",
-                "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631",
-                "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d",
-                "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652",
-                "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5",
-                "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e",
-                "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b",
-                "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8",
-                "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476",
-                "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706",
-                "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563",
-                "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237",
-                "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b",
-                "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083",
-                "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180",
-                "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425",
-                "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e",
-                "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f",
-                "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725",
-                "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183",
-                "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab",
-                "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774",
-                "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725",
-                "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e",
-                "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5",
-                "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d",
-                "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290",
-                "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44",
-                "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed",
-                "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4",
-                "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba",
-                "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12",
-                "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==6.0.2"
-        },
-        "pyzmq": {
-            "hashes": [
-                "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6",
-                "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a",
-                "sha256:05590cdbc6b902101d0e65d6a4780af14dc22914cc6ab995d99b85af45362cc9",
-                "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f",
-                "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37",
-                "sha256:0bed0e799e6120b9c32756203fb9dfe8ca2fb8467fed830c34c877e25638c3fc",
-                "sha256:0d987a3ae5a71c6226b203cfd298720e0086c7fe7c74f35fa8edddfbd6597eed",
-                "sha256:0eaa83fc4c1e271c24eaf8fb083cbccef8fde77ec8cd45f3c35a9a123e6da097",
-                "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d",
-                "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52",
-                "sha256:17c412bad2eb9468e876f556eb4ee910e62d721d2c7a53c7fa31e643d35352e6",
-                "sha256:18c8dc3b7468d8b4bdf60ce9d7141897da103c7a4690157b32b60acb45e333e6",
-                "sha256:1a534f43bc738181aa7cbbaf48e3eca62c76453a40a746ab95d4b27b1111a7d2",
-                "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282",
-                "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3",
-                "sha256:1fcc03fa4997c447dce58264e93b5aa2d57714fbe0f06c07b7785ae131512732",
-                "sha256:226af7dcb51fdb0109f0016449b357e182ea0ceb6b47dfb5999d569e5db161d5",
-                "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18",
-                "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306",
-                "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f",
-                "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3",
-                "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b",
-                "sha256:29c7947c594e105cb9e6c466bace8532dc1ca02d498684128b339799f5248277",
-                "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a",
-                "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797",
-                "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca",
-                "sha256:2ea4ad4e6a12e454de05f2949d4beddb52460f3de7c8b9d5c46fbb7d7222e02c",
-                "sha256:2eb7735ee73ca1b0d71e0e67c3739c689067f055c764f73aac4cc8ecf958ee3f",
-                "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5",
-                "sha256:35cffef589bcdc587d06f9149f8d5e9e8859920a071df5a2671de2213bef592a",
-                "sha256:367b4f689786fca726ef7a6c5ba606958b145b9340a5e4808132cc65759abd44",
-                "sha256:39887ac397ff35b7b775db7201095fc6310a35fdbae85bac4523f7eb3b840e20",
-                "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4",
-                "sha256:3b55a4229ce5da9497dd0452b914556ae58e96a4381bb6f59f1305dfd7e53fc8",
-                "sha256:402b190912935d3db15b03e8f7485812db350d271b284ded2b80d2e5704be780",
-                "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386",
-                "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5",
-                "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2",
-                "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0",
-                "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971",
-                "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b",
-                "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50",
-                "sha256:4da04c48873a6abdd71811c5e163bd656ee1b957971db7f35140a2d573f6949c",
-                "sha256:4f78c88905461a9203eac9faac157a2a0dbba84a0fd09fd29315db27be40af9f",
-                "sha256:4ff9dc6bc1664bb9eec25cd17506ef6672d506115095411e237d571e92a58231",
-                "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c",
-                "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08",
-                "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5",
-                "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6",
-                "sha256:6835dd60355593de10350394242b5757fbbd88b25287314316f266e24c61d073",
-                "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e",
-                "sha256:6a96179a24b14fa6428cbfc08641c779a53f8fcec43644030328f44034c7f1f4",
-                "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317",
-                "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3",
-                "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072",
-                "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad",
-                "sha256:7133d0a1677aec369d67dd78520d3fa96dd7f3dcec99d66c1762870e5ea1a50a",
-                "sha256:7445be39143a8aa4faec43b076e06944b8f9d0701b669df4af200531b21e40bb",
-                "sha256:76589c020680778f06b7e0b193f4b6dd66d470234a16e1df90329f5e14a171cd",
-                "sha256:76589f2cd6b77b5bdea4fca5992dc1c23389d68b18ccc26a53680ba2dc80ff2f",
-                "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef",
-                "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5",
-                "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187",
-                "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711",
-                "sha256:8423c1877d72c041f2c263b1ec6e34360448decfb323fa8b94e85883043ef988",
-                "sha256:8685fa9c25ff00f550c1fec650430c4b71e4e48e8d852f7ddcf2e48308038640",
-                "sha256:878206a45202247781472a2d99df12a176fef806ca175799e1c6ad263510d57c",
-                "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764",
-                "sha256:8ab5cad923cc95c87bffee098a27856c859bd5d0af31bd346035aa816b081fe1",
-                "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1",
-                "sha256:8be4700cd8bb02cc454f630dcdf7cfa99de96788b80c51b60fe2fe1dac480289",
-                "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb",
-                "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a",
-                "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218",
-                "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c",
-                "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf",
-                "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7",
-                "sha256:9cb3a6460cdea8fe8194a76de8895707e61ded10ad0be97188cc8463ffa7e3a8",
-                "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726",
-                "sha256:9ed69074a610fad1c2fda66180e7b2edd4d31c53f2d1872bc2d1211563904cd9",
-                "sha256:9edda2df81daa129b25a39b86cb57dfdfe16f7ec15b42b19bfac503360d27a93",
-                "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88",
-                "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115",
-                "sha256:aedd5dd8692635813368e558a05266b995d3d020b23e49581ddd5bbe197a8ab6",
-                "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672",
-                "sha256:b1d464cb8d72bfc1a3adc53305a63a8e0cac6bc8c5a07e8ca190ab8d3faa43c2",
-                "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea",
-                "sha256:bc6bee759a6bddea5db78d7dcd609397449cb2d2d6587f48f3ca613b19410cfc",
-                "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b",
-                "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa",
-                "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003",
-                "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797",
-                "sha256:c530e1eecd036ecc83c3407f77bb86feb79916d4a33d11394b8234f3bd35b940",
-                "sha256:c811cfcd6a9bf680236c40c6f617187515269ab2912f3d7e8c0174898e2519db",
-                "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc",
-                "sha256:cccba051221b916a4f5e538997c45d7d136a5646442b1231b916d0164067ea27",
-                "sha256:cdeabcff45d1c219636ee2e54d852262e5c2e085d6cb476d938aee8d921356b3",
-                "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e",
-                "sha256:d049df610ac811dcffdc147153b414147428567fbbc8be43bb8885f04db39d98",
-                "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b",
-                "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629",
-                "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9",
-                "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6",
-                "sha256:e6fa2e3e683f34aea77de8112f6483803c96a44fd726d7358b9888ae5bb394ec",
-                "sha256:ea0eb6af8a17fa272f7b98d7bebfab7836a0d62738e16ba380f440fceca2d951",
-                "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae",
-                "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4",
-                "sha256:fc4f7a173a5609631bb0c42c23d12c49df3966f89f496a51d3eb0ec81f4519d6",
-                "sha256:fdb5b3e311d4d4b0eb8b3e8b4d1b0a512713ad7e6a68791d0923d1aec433d919"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==26.2.0"
-        },
-        "rasterio": {
-            "hashes": [
-                "sha256:0bbd62b45a35cab53cb7fe72419e823e47ab31ee2d055af8e21dc7f37fe5ed6c",
-                "sha256:287e8d0d0472c778aa0b6392e9c00894a80f2bace28fa6eddb76c0a895097947",
-                "sha256:2ef27c3eff6f44f8b5d5de228003367c1843593edf648d85c0dc1319c00dc57d",
-                "sha256:30f27e309a14a70c821d10a0ea18b110968dc2e2186b06a900aebd92094f4e00",
-                "sha256:3a9c4fb63e050e11bcd23e53f084ca186b445f976df1f70e7abd851c4072837f",
-                "sha256:450f2bd45335308829da90566fbcbdb8e8aa0251a9d1f6ebb60667855dfb7554",
-                "sha256:505b3e659eb3b137192c25233bf7954bc4997b1a474bae9e129fbd5ac2619404",
-                "sha256:73ea4d0e584f696ef115601bbb97ba8d2b68a67c2bb3b40999414d31b6c7cf89",
-                "sha256:7a22c0e0cf07dbed6576faf9a49bc4afa1afedd5a14441b64a3d3dd6d10dc274",
-                "sha256:7c7ddca79444fd3b933f4cd1a1773e9f7839d0ce5d76e600bdf92ee9a79b95f8",
-                "sha256:7e653968f64840654d277e0f86f8666ed8f3030ba36fa865f420f9bc38d619ee",
-                "sha256:97d867cada29f16cb83f1743217f775f8b982676fcdda77671d25abb26698159",
-                "sha256:a420e5f25108b1c92c5d071cfd6518b3766f20a6eddb1b322d06c3d46a89fab6",
-                "sha256:c711b497e9ef0c4f5e1c01e34ba910708e066e1c4a69c25df18d1bcc04481287",
-                "sha256:cbb2eea127328302f9e3158a000363a7d9eea22537378dee4f824a7fa2d78c05",
-                "sha256:ce182c735b4f9e8735d90600607ecab15ef895eb8aa660bf665751529477e326",
-                "sha256:d1ac85857144cb8075e332e9d908b65426d30ddc1f59f7a04bcf6ed6fd3c0d47",
-                "sha256:d29d30c2271fa265913bd3db93fa213d3a0894362ec704e7273cf30443098a90",
-                "sha256:e6eece6420d7d6ef9b9830633b8fcd15e86b8702cb13419abe251c16ca502cf3",
-                "sha256:ef8a496740df1e68f7a3d3449aa3be9c3210c22f4bb78a4a9e1c290183abd9b1",
-                "sha256:f9cd757e11cfb07ef39b1cc79a32497bf22aff7fec41fe330b868cb3043b4db5"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==1.3.10"
-        },
-        "referencing": {
-            "hashes": [
-                "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c",
-                "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.35.1"
-        },
-        "requests": {
-            "hashes": [
-                "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
-                "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.32.3"
-        },
-        "rfc3339-validator": {
-            "hashes": [
-                "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b",
-                "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==0.1.4"
-        },
-        "rfc3986-validator": {
-            "hashes": [
-                "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9",
-                "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==0.1.1"
-        },
-        "rpds-py": {
-            "hashes": [
-                "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c",
-                "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585",
-                "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5",
-                "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6",
-                "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef",
-                "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2",
-                "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29",
-                "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318",
-                "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b",
-                "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399",
-                "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739",
-                "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee",
-                "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174",
-                "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a",
-                "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344",
-                "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2",
-                "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03",
-                "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5",
-                "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22",
-                "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e",
-                "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96",
-                "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91",
-                "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752",
-                "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075",
-                "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253",
-                "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee",
-                "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad",
-                "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5",
-                "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce",
-                "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7",
-                "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b",
-                "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8",
-                "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57",
-                "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3",
-                "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec",
-                "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209",
-                "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921",
-                "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045",
-                "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074",
-                "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580",
-                "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7",
-                "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5",
-                "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3",
-                "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0",
-                "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24",
-                "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139",
-                "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db",
-                "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc",
-                "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789",
-                "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f",
-                "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2",
-                "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c",
-                "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232",
-                "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6",
-                "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c",
-                "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29",
-                "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489",
-                "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94",
-                "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751",
-                "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2",
-                "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda",
-                "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9",
-                "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51",
-                "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c",
-                "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8",
-                "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989",
-                "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511",
-                "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1",
-                "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2",
-                "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150",
-                "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c",
-                "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965",
-                "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f",
-                "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58",
-                "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b",
-                "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f",
-                "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d",
-                "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821",
-                "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de",
-                "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121",
-                "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855",
-                "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272",
-                "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60",
-                "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02",
-                "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1",
-                "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140",
-                "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879",
-                "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940",
-                "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364",
-                "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4",
-                "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e",
-                "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420",
-                "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5",
-                "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24",
-                "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c",
-                "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf",
-                "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f",
-                "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e",
-                "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab",
-                "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08",
-                "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92",
-                "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a",
-                "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.20.0"
-        },
-        "send2trash": {
-            "hashes": [
-                "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9",
-                "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
-            "version": "==1.8.3"
-        },
-        "setuptools": {
-            "hashes": [
-                "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4",
-                "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==70.0.0"
-        },
-        "shapely": {
-            "hashes": [
-                "sha256:011b77153906030b795791f2fdfa2d68f1a8d7e40bce78b029782ade3afe4f2f",
-                "sha256:03152442d311a5e85ac73b39680dd64a9892fa42bb08fd83b3bab4fe6999bfa0",
-                "sha256:05ffd6491e9e8958b742b0e2e7c346635033d0a5f1a0ea083547fcc854e5d5cf",
-                "sha256:0776c92d584f72f1e584d2e43cfc5542c2f3dd19d53f70df0900fda643f4bae6",
-                "sha256:263bcf0c24d7a57c80991e64ab57cba7a3906e31d2e21b455f493d4aab534aaa",
-                "sha256:2fbdc1140a7d08faa748256438291394967aa54b40009f54e8d9825e75ef6113",
-                "sha256:30982f79f21bb0ff7d7d4a4e531e3fcaa39b778584c2ce81a147f95be1cd58c9",
-                "sha256:31c19a668b5a1eadab82ff070b5a260478ac6ddad3a5b62295095174a8d26398",
-                "sha256:3f9103abd1678cb1b5f7e8e1af565a652e036844166c91ec031eeb25c5ca8af0",
-                "sha256:41388321a73ba1a84edd90d86ecc8bfed55e6a1e51882eafb019f45895ec0f65",
-                "sha256:4310b5494271e18580d61022c0857eb85d30510d88606fa3b8314790df7f367d",
-                "sha256:464157509ce4efa5ff285c646a38b49f8c5ef8d4b340f722685b09bb033c5ccf",
-                "sha256:485246fcdb93336105c29a5cfbff8a226949db37b7473c89caa26c9bae52a242",
-                "sha256:489c19152ec1f0e5c5e525356bcbf7e532f311bff630c9b6bc2db6f04da6a8b9",
-                "sha256:4f2ab0faf8188b9f99e6a273b24b97662194160cc8ca17cf9d1fb6f18d7fb93f",
-                "sha256:55a38dcd1cee2f298d8c2ebc60fc7d39f3b4535684a1e9e2f39a80ae88b0cea7",
-                "sha256:58b0ecc505bbe49a99551eea3f2e8a9b3b24b3edd2a4de1ac0dc17bc75c9ec07",
-                "sha256:5af4cd0d8cf2912bd95f33586600cac9c4b7c5053a036422b97cfe4728d2eb53",
-                "sha256:5bbd974193e2cc274312da16b189b38f5f128410f3377721cadb76b1e8ca5328",
-                "sha256:5c4849916f71dc44e19ed370421518c0d86cf73b26e8656192fcfcda08218fbd",
-                "sha256:5dc736127fac70009b8d309a0eeb74f3e08979e530cf7017f2f507ef62e6cfb8",
-                "sha256:63f3a80daf4f867bd80f5c97fbe03314348ac1b3b70fb1c0ad255a69e3749879",
-                "sha256:674d7baf0015a6037d5758496d550fc1946f34bfc89c1bf247cabdc415d7747e",
-                "sha256:6cd4ccecc5ea5abd06deeaab52fcdba372f649728050c6143cc405ee0c166679",
-                "sha256:790a168a808bd00ee42786b8ba883307c0e3684ebb292e0e20009588c426da47",
-                "sha256:7d56ce3e2a6a556b59a288771cf9d091470116867e578bebced8bfc4147fbfd7",
-                "sha256:841f93a0e31e4c64d62ea570d81c35de0f6cea224568b2430d832967536308e6",
-                "sha256:8de4578e838a9409b5b134a18ee820730e507b2d21700c14b71a2b0757396acc",
-                "sha256:92a41d936f7d6743f343be265ace93b7c57f5b231e21b9605716f5a47c2879e7",
-                "sha256:9831816a5d34d5170aa9ed32a64982c3d6f4332e7ecfe62dc97767e163cb0b17",
-                "sha256:994c244e004bc3cfbea96257b883c90a86e8cbd76e069718eb4c6b222a56f78b",
-                "sha256:9dab4c98acfb5fb85f5a20548b5c0abe9b163ad3525ee28822ffecb5c40e724c",
-                "sha256:b79bbd648664aa6f44ef018474ff958b6b296fed5c2d42db60078de3cffbc8aa",
-                "sha256:c3e700abf4a37b7b8b90532fa6ed5c38a9bfc777098bc9fbae5ec8e618ac8f30",
-                "sha256:c52ed79f683f721b69a10fb9e3d940a468203f5054927215586c5d49a072de8d",
-                "sha256:c75c98380b1ede1cae9a252c6dc247e6279403fae38c77060a5e6186c95073ac",
-                "sha256:d2b4431f522b277c79c34b65da128029a9955e4481462cbf7ebec23aab61fc58",
-                "sha256:ddf4a9bfaac643e62702ed662afc36f6abed2a88a21270e891038f9a19bc08fc",
-                "sha256:de0205cb21ad5ddaef607cda9a3191eadd1e7a62a756ea3a356369675230ac35",
-                "sha256:ec555c9d0db12d7fd777ba3f8b75044c73e576c720a851667432fabb7057da6c",
-                "sha256:fb5cdcbbe3080181498931b52a91a21a781a35dcb859da741c0345c6402bf00c"
-            ],
-            "index": "pypi",
-            "version": "==2.0.4"
-        },
-        "six": {
-            "hashes": [
-                "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
-                "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.16.0"
-        },
-        "sniffio": {
-            "hashes": [
-                "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2",
-                "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==1.3.1"
-        },
-        "snuggs": {
-            "hashes": [
-                "sha256:501cf113fe3892e14e2fee76da5cd0606b7e149c411c271898e6259ebde2617b",
-                "sha256:988dde5d4db88e9d71c99457404773dabcc7a1c45971bfbe81900999942d9f07"
-            ],
-            "version": "==1.4.7"
-        },
-        "soupsieve": {
-            "hashes": [
-                "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb",
-                "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.6"
-        },
-        "stack-data": {
-            "hashes": [
-                "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9",
-                "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"
-            ],
-            "version": "==0.6.3"
-        },
-        "terminado": {
-            "hashes": [
-                "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0",
-                "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.18.1"
-        },
-        "tinycss2": {
-            "hashes": [
-                "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d",
-                "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==1.3.0"
-        },
-        "tomli": {
-            "hashes": [
-                "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
-                "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
-            ],
-            "markers": "python_version < '3.11'",
-            "version": "==2.0.1"
-        },
-        "tornado": {
-            "hashes": [
-                "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803",
-                "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec",
-                "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482",
-                "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634",
-                "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38",
-                "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b",
-                "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c",
-                "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf",
-                "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946",
-                "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73",
-                "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1"
-            ],
-            "index": "pypi",
-            "markers": "python_version >= '3.8'",
-            "version": "==6.4.2"
-        },
-        "traitlets": {
-            "hashes": [
-                "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7",
-                "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==5.14.3"
-        },
-        "traittypes": {
-            "hashes": [
-                "sha256:1340af133810b6eee1a2eb2e988f862b0d12b6c2d16f282aaf3207b782134c2e",
-                "sha256:be6fa26294733e7489822ded4ae25da5b4824a8a7a0e0c2dccfde596e3489bd6"
-            ],
-            "version": "==0.2.1"
-        },
-        "types-python-dateutil": {
-            "hashes": [
-                "sha256:9649d1dcb6fef1046fb18bebe9ea2aa0028b160918518c34589a46045f6ebd98",
-                "sha256:f5889fcb4e63ed4aaa379b44f93c32593d50b9a94c9a60a0c854d8cc3511cd57"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.9.0.20240821"
-        },
-        "typing-extensions": {
-            "hashes": [
-                "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d",
-                "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"
-            ],
-            "markers": "python_version < '3.10'",
-            "version": "==4.12.2"
-        },
-        "uri-template": {
-            "hashes": [
-                "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7",
-                "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363"
-            ],
-            "version": "==1.3.0"
-        },
-        "urllib3": {
-            "hashes": [
-                "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472",
-                "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.2.2"
-        },
-        "wcwidth": {
-            "hashes": [
-                "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859",
-                "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"
-            ],
-            "version": "==0.2.13"
-        },
-        "webcolors": {
-            "hashes": [
-                "sha256:08b07af286a01bcd30d583a7acadf629583d1f79bfef27dd2c2c5c263817277d",
-                "sha256:fc4c3b59358ada164552084a8ebee637c221e4059267d0f8325b3b560f6c7f0a"
-            ],
-            "version": "==24.8.0"
-        },
-        "webencodings": {
-            "hashes": [
-                "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
-                "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
-            ],
-            "version": "==0.5.1"
-        },
-        "websocket-client": {
-            "hashes": [
-                "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526",
-                "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==1.8.0"
-        },
-        "widgetsnbextension": {
-            "hashes": [
-                "sha256:46f4e3cb2d451bbd6141a13696d6ba17c9b5f50645dca9cfd26fe9644d5a00e1",
-                "sha256:e7fb9999845affc9024ecfbe0a824dd8e633403d027b28ceadab398b633ad51c"
-            ],
-            "version": "==3.6.6"
-        },
-        "y-py": {
-            "hashes": [
-                "sha256:015f7f6c1ce8a83d57955d1dc7ddd57cb633ae00576741a4fc9a0f72ed70007d",
-                "sha256:032365dfe932bfab8e80937ad6093b4c22e67d63ad880096b5fa8768f8d829ba",
-                "sha256:0649a41cd3c98e290c16592c082dbe42c7ffec747b596172eebcafb7fd8767b0",
-                "sha256:0787e85645bb4986c27e271715bc5ce21bba428a17964e5ec527368ed64669bc",
-                "sha256:0cd6213c3cf2b9eee6f2c9867f198c39124c557f4b3b77d04a73f30fd1277a59",
-                "sha256:0f2d881f0f8bf5674f8fe4774a438c545501e40fa27320c73be4f22463af4b05",
-                "sha256:17bce637a89f6e75f0013be68becac3e38dc082e7aefaf38935e89215f0aa64a",
-                "sha256:17edd21eef863d230ea00004ebc6d582cc91d325e7132deb93f0a90eb368c855",
-                "sha256:1d5b544e79ace93fdbd0b36ed329c86e346898153ac7ba2ec62bc9b4c6b745c9",
-                "sha256:1f798165158b76365a463a4f8aa2e3c2a12eb89b1fc092e7020e93713f2ad4dc",
-                "sha256:266ec46ab9f9cb40fbb5e649f55c329fc4620fa0b1a8117bdeefe91595e182dc",
-                "sha256:26cb1307c3ca9e21a3e307ab2c2099677e071ae9c26ec10ddffb3faceddd76b3",
-                "sha256:2a497ebe617bec6a420fc47378856caae40ab0652e756f3ed40c5f1fe2a12220",
-                "sha256:2b4fac4ea2ce27b86d173ae45765ced7f159120687d4410bb6d0846cbdb170a3",
-                "sha256:2cf817a72ffec4295def5c5be615dd8f1e954cdf449d72ebac579ff427951328",
-                "sha256:2d2b054a1a5f4004967532a4b82c6d1a45421ef2a5b41d35b6a8d41c7142aabe",
-                "sha256:316e5e1c40259d482883d1926fd33fa558dc87b2bd2ca53ce237a6fe8a34e473",
-                "sha256:35fcb9def6ce137540fdc0e91b08729677548b9c393c0151a6359fd199da3bd7",
-                "sha256:376c5cc0c177f03267340f36aec23e5eaf19520d41428d87605ca2ca3235d845",
-                "sha256:3ba99d0bdbd9cabd65f914cd07b4fb2e939ce199b54ae5ace1639ce1edf8e0a2",
-                "sha256:3c011303eb2b360695d2bd4bd7ca85f42373ae89fcea48e7fa5b8dc6fc254a98",
-                "sha256:4757a82a50406a0b3a333aa0122019a331bd6f16e49fed67dca423f928b3fd4d",
-                "sha256:47fcc19158150dc4a6ae9a970c5bc12f40b0298a2b7d0c573a510a7b6bead3f3",
-                "sha256:4c28d977f516d4928f6bc0cd44561f6d0fdd661d76bac7cdc4b73e3c209441d9",
-                "sha256:5415083f7f10eac25e1c434c87f07cb9bfa58909a6cad6649166fdad21119fc5",
-                "sha256:613f83713714972886e81d71685403098a83ffdacf616f12344b52bc73705107",
-                "sha256:69cfbcbe0a05f43e780e6a198080ba28034bf2bb4804d7d28f71a0379bfd1b19",
-                "sha256:6c2f2831c5733b404d2f2da4bfd02bb4612ae18d0822e14ae79b0b92436b816d",
-                "sha256:7227f232f2daf130ba786f6834548f2cfcfa45b7ec4f0d449e72560ac298186c",
-                "sha256:72875641a907523d37f4619eb4b303611d17e0a76f2ffc423b62dd1ca67eef41",
-                "sha256:7c7302619fc962e53093ba4a94559281491c045c925e5c4defec5dac358e0568",
-                "sha256:7cbefd4f1060f05768227ddf83be126397b1d430b026c64e0eb25d3cf50c5734",
-                "sha256:80a827e173372682959a57e6b8cc4f6468b1a4495b4bc7a775ef6ca05ae3e8e8",
-                "sha256:82f2e5b31678065e7a7fa089ed974af5a4f076673cf4f414219bdadfc3246a21",
-                "sha256:82f5ca62bedbf35aaf5a75d1f53b4457a1d9b6ff033497ca346e2a0cedf13d14",
-                "sha256:8448da4092265142662bbd3fc46cb8b0796b1e259189c020bc8f738899abd0b5",
-                "sha256:863e175ce5585f9ff3eba2aa16626928387e2a576157f02c8eb247a218ecdeae",
-                "sha256:86422c6090f34906c062fd3e4fdfdccf3934f2922021e979573ae315050b4288",
-                "sha256:898fede446ca1926b8406bdd711617c2aebba8227ee8ec1f0c2f8568047116f7",
-                "sha256:8f5c14d25611b263b876e9ada1701415a13c3e9f02ea397224fbe4ca9703992b",
-                "sha256:8f6071328aad06fdcc0a4acc2dc4839396d645f5916de07584af807eb7c08407",
-                "sha256:932abb560fe739416b50716a72ba6c6c20b219edded4389d1fc93266f3505d4b",
-                "sha256:9b7cafbe946b4cafc1e5709957e6dd5c6259d241d48ed75713ded42a5e8a4663",
-                "sha256:9b8822a5c0fd9a8cffcabfcc0cd7326bad537ee614fc3654e413a03137b6da1a",
-                "sha256:a21148b8ea09a631b752d975f9410ee2a31c0e16796fdc113422a6d244be10e5",
-                "sha256:a3932f53418b408fa03bd002e6dc573a74075c2c092926dde80657c39aa2e054",
-                "sha256:a70aee572da3994238c974694767365f237fc5949a550bee78a650fe16f83184",
-                "sha256:ae80d505aee7b3172cdcc2620ca6e2f85586337371138bb2b71aa377d2c31e9a",
-                "sha256:b2686d7d8ca31531458a48e08b0344a8eec6c402405446ce7d838e2a7e43355a",
-                "sha256:bae1b1ad8d2b8cf938a60313f8f7461de609621c5dcae491b6e54975f76f83c5",
-                "sha256:bd302c6d46a3be57664571a5f0d4224646804be9890a01d73a0b294f2d3bbff1",
-                "sha256:beea5ad9bd9e56aa77a6583b6f4e347d66f1fe7b1a2cb196fff53b7634f9dc84",
-                "sha256:bf6020560584671e76375b7a0539e0d5388fc70fa183c99dc769895f7ef90233",
-                "sha256:c011997f62d0c3b40a617e61b7faaaf6078e4eeff2e95ce4c45838db537816eb",
-                "sha256:c08311db17647a47d4898fc6f8d9c1f0e58b927752c894877ff0c38b3db0d6e1",
-                "sha256:c26bada6cd109095139237a46f50fc4308f861f0d304bc9e70acbc6c4503d158",
-                "sha256:c31240e30d5636ded02a54b7280aa129344fe8e964fd63885e85d9a8a83db206",
-                "sha256:ce0ae49879d10610cf3c40f4f376bb3cc425b18d939966ac63a2a9c73eb6f32a",
-                "sha256:ce15a842c2a0bf46180ae136743b561fa276300dd7fa61fe76daf00ec7dc0c2d",
-                "sha256:ce7c20b9395696d3b5425dccf2706d374e61ccf8f3656bff9423093a6df488f5",
-                "sha256:cfc8381df1f0f873da8969729974f90111cfb61a725ef0a2e0e6215408fe1217",
-                "sha256:d1dca48687f41efd862355e58b0aa31150586219324901dbea2989a506e291d4",
-                "sha256:d3bbe2f925cc587545c8d01587b4523177408edd252a32ce6d61b97113fe234d",
-                "sha256:d917f5bc27b85611ceee4eb85f0e4088b0a03b4eed22c472409933a94ee953cf",
-                "sha256:dab84c52f64e10adc79011a08673eb80286c159b14e8fb455524bf2994f0cb38",
-                "sha256:de9cfafe97c75cd3ea052a24cd4aabf9fb0cfc3c0f9f810f00121cdf123db9e4",
-                "sha256:df35ea436592eb7e30e59c5403ec08ec3a5e7759e270cf226df73c47b3e739f5",
-                "sha256:e13cba03c7af8c8a846c4495875a09d64362cc4caeed495ada5390644411bbe7",
-                "sha256:e1935d12e503780b859d343161a80df65205d23cad7b4f6c3df6e50321e188a3",
-                "sha256:e42258f66ad9f16d9b62e9c9642742982acb1f30b90f5061522048c1cb99814f",
-                "sha256:e794e44fa260300b8850246c6371d94014753c73528f97f6ccb42f5e7ce698ae",
-                "sha256:e8638355ae2f996356f7f281e03a3e3ce31f1259510f9d551465356532e0302c",
-                "sha256:e92878cc05e844c8da937204bc34c2e6caf66709ce5936802fbfb35f04132892",
-                "sha256:ff32548e45e45bf3280ac1d28b3148337a5c6714c28db23aeb0693e33eba257e"
-            ],
-            "version": "==0.6.2"
-        },
-        "ypy-websocket": {
-            "hashes": [
-                "sha256:43a001473f5c8abcf182f603049cf305cbc855ad8deaa9dfa0f3b5a7cea9d0ff",
-                "sha256:b1ba0dfcc9762f0ca168d2378062d3ca1299d39076b0f145d961359121042be5"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.8.4"
-        },
-        "zipp": {
-            "hashes": [
-                "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064",
-                "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"
-            ],
-            "markers": "python_version < '3.10'",
-            "version": "==3.20.1"
-        }
-    },
-    "develop": {
-        "asttokens": {
-            "hashes": [
-                "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24",
-                "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"
-            ],
-            "version": "==2.4.1"
-        },
-        "attrs": {
-            "hashes": [
-                "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30",
-                "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"
-            ],
-            "index": "pypi",
-            "version": "==23.2.0"
-        },
-        "beautifulsoup4": {
-            "hashes": [
-                "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051",
-                "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==4.12.3"
-        },
-        "bleach": {
-            "hashes": [
-                "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe",
-                "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==6.1.0"
-        },
-        "click": {
-            "hashes": [
-                "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
-                "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==8.1.7"
-        },
-        "comm": {
-            "hashes": [
-                "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e",
-                "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.2.2"
-        },
-        "coverage": {
-            "extras": [
-                "toml"
-            ],
-            "hashes": [
-                "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de",
-                "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661",
-                "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26",
-                "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41",
-                "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d",
-                "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981",
-                "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2",
-                "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34",
-                "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f",
-                "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a",
-                "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35",
-                "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223",
-                "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1",
-                "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746",
-                "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90",
-                "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c",
-                "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca",
-                "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8",
-                "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596",
-                "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e",
-                "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd",
-                "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e",
-                "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3",
-                "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e",
-                "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312",
-                "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7",
-                "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572",
-                "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428",
-                "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f",
-                "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07",
-                "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e",
-                "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4",
-                "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136",
-                "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5",
-                "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8",
-                "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d",
-                "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228",
-                "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206",
-                "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa",
-                "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e",
-                "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be",
-                "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5",
-                "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668",
-                "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601",
-                "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057",
-                "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146",
-                "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f",
-                "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8",
-                "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7",
-                "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987",
-                "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19",
-                "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==7.5.1"
-        },
-        "debugpy": {
-            "hashes": [
-                "sha256:016a9fcfc2c6b57f939673c874310d8581d51a0fe0858e7fac4e240c5eb743cb",
-                "sha256:0de56aba8249c28a300bdb0672a9b94785074eb82eb672db66c8144fff673146",
-                "sha256:1a9fe0829c2b854757b4fd0a338d93bc17249a3bf69ecf765c61d4c522bb92a8",
-                "sha256:28acbe2241222b87e255260c76741e1fbf04fdc3b6d094fcf57b6c6f75ce1242",
-                "sha256:3a79c6f62adef994b2dbe9fc2cc9cc3864a23575b6e387339ab739873bea53d0",
-                "sha256:3bda0f1e943d386cc7a0e71bfa59f4137909e2ed947fb3946c506e113000f741",
-                "sha256:3ebb70ba1a6524d19fa7bb122f44b74170c447d5746a503e36adc244a20ac539",
-                "sha256:58911e8521ca0c785ac7a0539f1e77e0ce2df753f786188f382229278b4cdf23",
-                "sha256:6df9aa9599eb05ca179fb0b810282255202a66835c6efb1d112d21ecb830ddd3",
-                "sha256:7a3afa222f6fd3d9dfecd52729bc2e12c93e22a7491405a0ecbf9e1d32d45b39",
-                "sha256:7eb7bd2b56ea3bedb009616d9e2f64aab8fc7000d481faec3cd26c98a964bcdd",
-                "sha256:92116039b5500633cc8d44ecc187abe2dfa9b90f7a82bbf81d079fcdd506bae9",
-                "sha256:a2e658a9630f27534e63922ebf655a6ab60c370f4d2fc5c02a5b19baf4410ace",
-                "sha256:bfb20cb57486c8e4793d41996652e5a6a885b4d9175dd369045dad59eaacea42",
-                "sha256:caad2846e21188797a1f17fc09c31b84c7c3c23baf2516fed5b40b378515bbf0",
-                "sha256:d915a18f0597ef685e88bb35e5d7ab968964b7befefe1aaea1eb5b2640b586c7",
-                "sha256:dda73bf69ea479c8577a0448f8c707691152e6c4de7f0c4dec5a4bc11dee516e",
-                "sha256:e38beb7992b5afd9d5244e96ad5fa9135e94993b0c551ceebf3fe1a5d9beb234",
-                "sha256:edcc9f58ec0fd121a25bc950d4578df47428d72e1a0d66c07403b04eb93bcf98",
-                "sha256:efd3fdd3f67a7e576dd869c184c5dd71d9aaa36ded271939da352880c012e703",
-                "sha256:f696d6be15be87aef621917585f9bb94b1dc9e8aced570db1b8a6fc14e8f9b42",
-                "sha256:fd97ed11a4c7f6d042d320ce03d83b20c3fb40da892f994bc041bbc415d7a099"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==1.8.1"
-        },
-        "decorator": {
-            "hashes": [
-                "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330",
-                "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==5.1.1"
-        },
-        "defusedxml": {
-            "hashes": [
-                "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
-                "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==0.7.1"
-        },
-        "exceptiongroup": {
-            "hashes": [
-                "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad",
-                "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"
-            ],
-            "markers": "python_version < '3.11'",
-            "version": "==1.2.1"
-        },
-        "executing": {
-            "hashes": [
-                "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147",
-                "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==2.0.1"
-        },
-        "fastjsonschema": {
-            "hashes": [
-                "sha256:3672b47bc94178c9f23dbb654bf47440155d4db9df5f7bc47643315f9c405cd0",
-                "sha256:e3126a94bdc4623d3de4485f8d468a12f02a67921315ddc87836d6e456dc789d"
-            ],
-            "version": "==2.19.1"
-        },
-        "ghp-import": {
-            "hashes": [
-                "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619",
-                "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"
-            ],
-            "version": "==2.1.0"
-        },
-        "importlib-metadata": {
-            "hashes": [
-                "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570",
-                "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"
-            ],
-            "markers": "python_version < '3.10'",
-            "version": "==7.1.0"
-        },
-        "iniconfig": {
-            "hashes": [
-                "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
-                "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.0.0"
-        },
-        "ipykernel": {
-            "hashes": [
-                "sha256:1181e653d95c6808039c509ef8e67c4126b3b3af7781496c7cbfb5ed938a27da",
-                "sha256:3d44070060f9475ac2092b760123fadf105d2e2493c24848b6691a7c4f42af5c"
-            ],
-            "index": "pypi",
-            "version": "==6.29.4"
-        },
-        "ipython": {
-            "hashes": [
-                "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27",
-                "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"
-            ],
-            "markers": "python_version >= '3.9'",
-            "version": "==8.18.1"
-        },
-        "ipython-genutils": {
-            "hashes": [
-                "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8",
-                "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"
-            ],
-            "version": "==0.2.0"
-        },
-        "ipywidgets": {
-            "hashes": [
-                "sha256:050b87bb9ac11641859af4c36cdb639ca072fb5e121f0f1a401f8a80f9fa008d",
-                "sha256:29f7056d368bf0a7b35d51cf0c56b58582da57c78bb9f765965fef7c332e807c"
-            ],
-            "index": "pypi",
-            "version": "==7.8.1"
-        },
-        "jedi": {
-            "hashes": [
-                "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd",
-                "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==0.19.1"
-        },
-        "jinja2": {
-            "hashes": [
-                "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369",
-                "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.1.4"
-        },
-        "jsonschema": {
-            "extras": [
-                "format-nongpl"
-            ],
-            "hashes": [
-                "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7",
-                "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==4.22.0"
-        },
-        "jsonschema-specifications": {
-            "hashes": [
-                "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc",
-                "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2023.12.1"
-        },
-        "jupyter": {
-            "hashes": [
-                "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7",
-                "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78",
-                "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"
-            ],
-            "index": "pypi",
-            "version": "==1.0.0"
-        },
-        "jupyter-client": {
-            "hashes": [
-                "sha256:214668aaea208195f4c13d28eb272ba79f945fc0cf3f11c7092c20b2ca1980e7",
-                "sha256:52be28e04171f07aed8f20e1616a5a552ab9fee9cbbe6c1896ae170c3880d392"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==7.4.9"
-        },
-        "jupyter-console": {
-            "hashes": [
-                "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485",
-                "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==6.6.3"
-        },
-        "jupyter-core": {
-            "hashes": [
-                "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409",
-                "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==5.7.2"
-        },
-        "jupyterlab-pygments": {
-            "hashes": [
-                "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d",
-                "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.3.0"
-        },
-        "jupyterlab-widgets": {
-            "hashes": [
-                "sha256:0c4548cf42032e490447e4180f2c7d49ba5c30b42164992b38fb8c9d56c4e1b2",
-                "sha256:318dab34267915d658e7b0dc57433ff0ce0d52b3e283986b73b66f7ab9017ae8"
-            ],
-            "index": "pypi",
-            "version": "==1.1.7"
-        },
-        "markdown": {
-            "hashes": [
-                "sha256:48f276f4d8cfb8ce6527c8f79e2ee29708508bf4d40aa410fbc3b4ee832c850f",
-                "sha256:ed4f41f6daecbeeb96e576ce414c41d2d876daa9a16cb35fa8ed8c2ddfad0224"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==3.6"
-        },
-        "markupsafe": {
-            "hashes": [
-                "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf",
-                "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff",
-                "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f",
-                "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3",
-                "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532",
-                "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f",
-                "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617",
-                "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df",
-                "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4",
-                "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906",
-                "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f",
-                "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4",
-                "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8",
-                "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371",
-                "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2",
-                "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465",
-                "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52",
-                "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6",
-                "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169",
-                "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad",
-                "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2",
-                "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0",
-                "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029",
-                "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f",
-                "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a",
-                "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced",
-                "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5",
-                "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c",
-                "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf",
-                "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9",
-                "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb",
-                "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad",
-                "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3",
-                "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1",
-                "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46",
-                "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc",
-                "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a",
-                "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee",
-                "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900",
-                "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5",
-                "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea",
-                "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f",
-                "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5",
-                "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e",
-                "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a",
-                "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f",
-                "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50",
-                "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a",
-                "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b",
-                "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4",
-                "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff",
-                "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2",
-                "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46",
-                "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b",
-                "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf",
-                "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5",
-                "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5",
-                "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab",
-                "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd",
-                "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.1.5"
-        },
-        "matplotlib-inline": {
-            "hashes": [
-                "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90",
-                "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.1.7"
-        },
-        "mergedeep": {
-            "hashes": [
-                "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8",
-                "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==1.3.4"
-        },
-        "mistune": {
-            "hashes": [
-                "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205",
-                "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.0.2"
-        },
-        "mkdocs": {
-            "hashes": [
-                "sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7",
-                "sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512"
-            ],
-            "index": "pypi",
-            "version": "==1.6.0"
-        },
-        "mkdocs-get-deps": {
-            "hashes": [
-                "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c",
-                "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.2.0"
-        },
-        "nbclient": {
-            "hashes": [
-                "sha256:4b3f1b7dba531e498449c4db4f53da339c91d449dc11e9af3a43b4eb5c5abb09",
-                "sha256:f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.10.0"
-        },
-        "nbconvert": {
-            "hashes": [
-                "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3",
-                "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==7.16.4"
-        },
-        "nbformat": {
-            "hashes": [
-                "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a",
-                "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==5.10.4"
-        },
-        "nest-asyncio": {
-            "hashes": [
-                "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe",
-                "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==1.6.0"
-        },
-        "notebook": {
-            "hashes": [
-                "sha256:04eb9011dfac634fbd4442adaf0a8c27cd26beef831fe1d19faf930c327768e4",
-                "sha256:a6afa9a4ff4d149a0771ff8b8c881a7a73b3835f9add0606696d6e9d98ac1cd0"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==6.5.7"
-        },
-        "packaging": {
-            "hashes": [
-                "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5",
-                "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==24.0"
-        },
-        "pandocfilters": {
-            "hashes": [
-                "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e",
-                "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.5.1"
-        },
-        "parso": {
-            "hashes": [
-                "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18",
-                "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==0.8.4"
-        },
-        "pathspec": {
-            "hashes": [
-                "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08",
-                "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.12.1"
-        },
-        "pexpect": {
-            "hashes": [
-                "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523",
-                "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"
-            ],
-            "markers": "sys_platform != 'win32'",
-            "version": "==4.9.0"
-        },
-        "platformdirs": {
-            "hashes": [
-                "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee",
-                "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==4.2.2"
-        },
-        "pluggy": {
-            "hashes": [
-                "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1",
-                "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==1.5.0"
-        },
-        "prometheus-client": {
-            "hashes": [
-                "sha256:287629d00b147a32dcb2be0b9df905da599b2d82f80377083ec8463309a4bb89",
-                "sha256:cde524a85bce83ca359cc837f28b8c0db5cac7aa653a588fd7e84ba061c329e7"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.20.0"
-        },
-        "prompt-toolkit": {
-            "hashes": [
-                "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d",
-                "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.0.43"
-        },
-        "psutil": {
-            "hashes": [
-                "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d",
-                "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73",
-                "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8",
-                "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2",
-                "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e",
-                "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36",
-                "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7",
-                "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c",
-                "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee",
-                "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421",
-                "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf",
-                "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81",
-                "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0",
-                "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631",
-                "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4",
-                "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
-            "version": "==5.9.8"
-        },
-        "ptyprocess": {
-            "hashes": [
-                "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35",
-                "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"
-            ],
-            "version": "==0.7.0"
-        },
-        "pure-eval": {
-            "hashes": [
-                "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350",
-                "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"
-            ],
-            "version": "==0.2.2"
-        },
-        "pygments": {
-            "hashes": [
-                "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199",
-                "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.18.0"
-        },
-        "pytest": {
-            "hashes": [
-                "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd",
-                "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"
-            ],
-            "index": "pypi",
-            "version": "==8.2.1"
-        },
-        "pytest-cov": {
-            "hashes": [
-                "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652",
-                "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"
-            ],
-            "index": "pypi",
-            "version": "==5.0.0"
-        },
-        "python-dateutil": {
-            "hashes": [
-                "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
-                "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
-            "version": "==2.9.0.post0"
-        },
-        "pyyaml": {
-            "hashes": [
-                "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5",
-                "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc",
-                "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df",
-                "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741",
-                "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206",
-                "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27",
-                "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595",
-                "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62",
-                "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98",
-                "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696",
-                "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290",
-                "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9",
-                "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d",
-                "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6",
-                "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867",
-                "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47",
-                "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486",
-                "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6",
-                "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3",
-                "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007",
-                "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938",
-                "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0",
-                "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c",
-                "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735",
-                "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d",
-                "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28",
-                "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4",
-                "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba",
-                "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8",
-                "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef",
-                "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5",
-                "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd",
-                "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3",
-                "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0",
-                "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515",
-                "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c",
-                "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c",
-                "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924",
-                "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34",
-                "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43",
-                "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859",
-                "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673",
-                "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54",
-                "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a",
-                "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b",
-                "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab",
-                "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa",
-                "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c",
-                "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585",
-                "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d",
-                "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==6.0.1"
-        },
-        "pyyaml-env-tag": {
-            "hashes": [
-                "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb",
-                "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==0.1"
-        },
-        "pyzmq": {
-            "hashes": [
-                "sha256:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa",
-                "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4",
-                "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09",
-                "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753",
-                "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c",
-                "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537",
-                "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5",
-                "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620",
-                "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920",
-                "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77",
-                "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450",
-                "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a",
-                "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc",
-                "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0",
-                "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de",
-                "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18",
-                "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606",
-                "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500",
-                "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972",
-                "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6",
-                "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad",
-                "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee",
-                "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a",
-                "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59",
-                "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7",
-                "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709",
-                "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625",
-                "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d",
-                "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527",
-                "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5",
-                "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987",
-                "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d",
-                "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b",
-                "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de",
-                "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12",
-                "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798",
-                "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be",
-                "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2",
-                "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20",
-                "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67",
-                "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4",
-                "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd",
-                "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a",
-                "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1",
-                "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4",
-                "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381",
-                "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd",
-                "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02",
-                "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35",
-                "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7",
-                "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce",
-                "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5",
-                "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf",
-                "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf",
-                "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84",
-                "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c",
-                "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5",
-                "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32",
-                "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a",
-                "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90",
-                "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97",
-                "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8",
-                "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5",
-                "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94",
-                "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf",
-                "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f",
-                "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2",
-                "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17",
-                "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879",
-                "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81",
-                "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223",
-                "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a",
-                "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b",
-                "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab",
-                "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7",
-                "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6",
-                "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2",
-                "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480",
-                "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8",
-                "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67",
-                "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad",
-                "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b",
-                "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3",
-                "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9",
-                "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47",
-                "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83",
-                "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad",
-                "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==26.0.3"
-        },
-        "qtconsole": {
-            "hashes": [
-                "sha256:42d745f3d05d36240244a04e1e1ec2a86d5d9b6edb16dbdef582ccb629e87e0b",
-                "sha256:6b5fb11274b297463706af84dcbbd5c92273b1f619e6d25d08874b0a88516989"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==5.5.2"
-        },
-        "qtpy": {
-            "hashes": [
-                "sha256:1c1d8c4fa2c884ae742b069151b0abe15b3f70491f3972698c683b8e38de839b",
-                "sha256:a5a15ffd519550a1361bdc56ffc07fda56a6af7292f17c7b395d4083af632987"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.4.1"
-        },
-        "referencing": {
-            "hashes": [
-                "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c",
-                "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.35.1"
-        },
-        "rpds-py": {
-            "hashes": [
-                "sha256:05f3d615099bd9b13ecf2fc9cf2d839ad3f20239c678f461c753e93755d629ee",
-                "sha256:06d218939e1bf2ca50e6b0ec700ffe755e5216a8230ab3e87c059ebb4ea06afc",
-                "sha256:07f2139741e5deb2c5154a7b9629bc5aa48c766b643c1a6750d16f865a82c5fc",
-                "sha256:08d74b184f9ab6289b87b19fe6a6d1a97fbfea84b8a3e745e87a5de3029bf944",
-                "sha256:0abeee75434e2ee2d142d650d1e54ac1f8b01e6e6abdde8ffd6eeac6e9c38e20",
-                "sha256:154bf5c93d79558b44e5b50cc354aa0459e518e83677791e6adb0b039b7aa6a7",
-                "sha256:17c6d2155e2423f7e79e3bb18151c686d40db42d8645e7977442170c360194d4",
-                "sha256:1805d5901779662d599d0e2e4159d8a82c0b05faa86ef9222bf974572286b2b6",
-                "sha256:19ba472b9606c36716062c023afa2484d1e4220548751bda14f725a7de17b4f6",
-                "sha256:19e515b78c3fc1039dd7da0a33c28c3154458f947f4dc198d3c72db2b6b5dc93",
-                "sha256:1d54f74f40b1f7aaa595a02ff42ef38ca654b1469bef7d52867da474243cc633",
-                "sha256:207c82978115baa1fd8d706d720b4a4d2b0913df1c78c85ba73fe6c5804505f0",
-                "sha256:2625f03b105328729f9450c8badda34d5243231eef6535f80064d57035738360",
-                "sha256:27bba383e8c5231cd559affe169ca0b96ec78d39909ffd817f28b166d7ddd4d8",
-                "sha256:2c3caec4ec5cd1d18e5dd6ae5194d24ed12785212a90b37f5f7f06b8bedd7139",
-                "sha256:2cc7c1a47f3a63282ab0f422d90ddac4aa3034e39fc66a559ab93041e6505da7",
-                "sha256:2fc24a329a717f9e2448f8cd1f960f9dac4e45b6224d60734edeb67499bab03a",
-                "sha256:312fe69b4fe1ffbe76520a7676b1e5ac06ddf7826d764cc10265c3b53f96dbe9",
-                "sha256:32b7daaa3e9389db3695964ce8e566e3413b0c43e3394c05e4b243a4cd7bef26",
-                "sha256:338dee44b0cef8b70fd2ef54b4e09bb1b97fc6c3a58fea5db6cc083fd9fc2724",
-                "sha256:352a88dc7892f1da66b6027af06a2e7e5d53fe05924cc2cfc56495b586a10b72",
-                "sha256:35b2b771b13eee8729a5049c976197ff58a27a3829c018a04341bcf1ae409b2b",
-                "sha256:38e14fb4e370885c4ecd734f093a2225ee52dc384b86fa55fe3f74638b2cfb09",
-                "sha256:3c20f05e8e3d4fc76875fc9cb8cf24b90a63f5a1b4c5b9273f0e8225e169b100",
-                "sha256:3dd3cd86e1db5aadd334e011eba4e29d37a104b403e8ca24dcd6703c68ca55b3",
-                "sha256:489bdfe1abd0406eba6b3bb4fdc87c7fa40f1031de073d0cfb744634cc8fa261",
-                "sha256:48c2faaa8adfacefcbfdb5f2e2e7bdad081e5ace8d182e5f4ade971f128e6bb3",
-                "sha256:4a98a1f0552b5f227a3d6422dbd61bc6f30db170939bd87ed14f3c339aa6c7c9",
-                "sha256:4adec039b8e2928983f885c53b7cc4cda8965b62b6596501a0308d2703f8af1b",
-                "sha256:4e0ee01ad8260184db21468a6e1c37afa0529acc12c3a697ee498d3c2c4dcaf3",
-                "sha256:51584acc5916212e1bf45edd17f3a6b05fe0cbb40482d25e619f824dccb679de",
-                "sha256:531796fb842b53f2695e94dc338929e9f9dbf473b64710c28af5a160b2a8927d",
-                "sha256:5463c47c08630007dc0fe99fb480ea4f34a89712410592380425a9b4e1611d8e",
-                "sha256:5c45a639e93a0c5d4b788b2613bd637468edd62f8f95ebc6fcc303d58ab3f0a8",
-                "sha256:6031b25fb1b06327b43d841f33842b383beba399884f8228a6bb3df3088485ff",
-                "sha256:607345bd5912aacc0c5a63d45a1f73fef29e697884f7e861094e443187c02be5",
-                "sha256:618916f5535784960f3ecf8111581f4ad31d347c3de66d02e728de460a46303c",
-                "sha256:636a15acc588f70fda1661234761f9ed9ad79ebed3f2125d44be0862708b666e",
-                "sha256:673fdbbf668dd958eff750e500495ef3f611e2ecc209464f661bc82e9838991e",
-                "sha256:6afd80f6c79893cfc0574956f78a0add8c76e3696f2d6a15bca2c66c415cf2d4",
-                "sha256:6b5ff7e1d63a8281654b5e2896d7f08799378e594f09cf3674e832ecaf396ce8",
-                "sha256:6c4c4c3f878df21faf5fac86eda32671c27889e13570645a9eea0a1abdd50922",
-                "sha256:6cd8098517c64a85e790657e7b1e509b9fe07487fd358e19431cb120f7d96338",
-                "sha256:6d1e42d2735d437e7e80bab4d78eb2e459af48c0a46e686ea35f690b93db792d",
-                "sha256:6e30ac5e329098903262dc5bdd7e2086e0256aa762cc8b744f9e7bf2a427d3f8",
-                "sha256:70a838f7754483bcdc830444952fd89645569e7452e3226de4a613a4c1793fb2",
-                "sha256:720edcb916df872d80f80a1cc5ea9058300b97721efda8651efcd938a9c70a72",
-                "sha256:732672fbc449bab754e0b15356c077cc31566df874964d4801ab14f71951ea80",
-                "sha256:740884bc62a5e2bbb31e584f5d23b32320fd75d79f916f15a788d527a5e83644",
-                "sha256:7700936ef9d006b7ef605dc53aa364da2de5a3aa65516a1f3ce73bf82ecfc7ae",
-                "sha256:7732770412bab81c5a9f6d20aeb60ae943a9b36dcd990d876a773526468e7163",
-                "sha256:7750569d9526199c5b97e5a9f8d96a13300950d910cf04a861d96f4273d5b104",
-                "sha256:7f1944ce16401aad1e3f7d312247b3d5de7981f634dc9dfe90da72b87d37887d",
-                "sha256:81c5196a790032e0fc2464c0b4ab95f8610f96f1f2fa3d4deacce6a79852da60",
-                "sha256:8352f48d511de5f973e4f2f9412736d7dea76c69faa6d36bcf885b50c758ab9a",
-                "sha256:8927638a4d4137a289e41d0fd631551e89fa346d6dbcfc31ad627557d03ceb6d",
-                "sha256:8c7672e9fba7425f79019db9945b16e308ed8bc89348c23d955c8c0540da0a07",
-                "sha256:8d2e182c9ee01135e11e9676e9a62dfad791a7a467738f06726872374a83db49",
-                "sha256:910e71711d1055b2768181efa0a17537b2622afeb0424116619817007f8a2b10",
-                "sha256:942695a206a58d2575033ff1e42b12b2aece98d6003c6bc739fbf33d1773b12f",
-                "sha256:9437ca26784120a279f3137ee080b0e717012c42921eb07861b412340f85bae2",
-                "sha256:967342e045564cef76dfcf1edb700b1e20838d83b1aa02ab313e6a497cf923b8",
-                "sha256:998125738de0158f088aef3cb264a34251908dd2e5d9966774fdab7402edfab7",
-                "sha256:9e6934d70dc50f9f8ea47081ceafdec09245fd9f6032669c3b45705dea096b88",
-                "sha256:a3d456ff2a6a4d2adcdf3c1c960a36f4fd2fec6e3b4902a42a384d17cf4e7a65",
-                "sha256:a7b28c5b066bca9a4eb4e2f2663012debe680f097979d880657f00e1c30875a0",
-                "sha256:a888e8bdb45916234b99da2d859566f1e8a1d2275a801bb8e4a9644e3c7e7909",
-                "sha256:aa3679e751408d75a0b4d8d26d6647b6d9326f5e35c00a7ccd82b78ef64f65f8",
-                "sha256:aaa71ee43a703c321906813bb252f69524f02aa05bf4eec85f0c41d5d62d0f4c",
-                "sha256:b646bf655b135ccf4522ed43d6902af37d3f5dbcf0da66c769a2b3938b9d8184",
-                "sha256:b906b5f58892813e5ba5c6056d6a5ad08f358ba49f046d910ad992196ea61397",
-                "sha256:b9bb1f182a97880f6078283b3505a707057c42bf55d8fca604f70dedfdc0772a",
-                "sha256:bd1105b50ede37461c1d51b9698c4f4be6e13e69a908ab7751e3807985fc0346",
-                "sha256:bf18932d0003c8c4d51a39f244231986ab23ee057d235a12b2684ea26a353590",
-                "sha256:c273e795e7a0f1fddd46e1e3cb8be15634c29ae8ff31c196debb620e1edb9333",
-                "sha256:c69882964516dc143083d3795cb508e806b09fc3800fd0d4cddc1df6c36e76bb",
-                "sha256:c827576e2fa017a081346dce87d532a5310241648eb3700af9a571a6e9fc7e74",
-                "sha256:cbfbea39ba64f5e53ae2915de36f130588bba71245b418060ec3330ebf85678e",
-                "sha256:ce0bb20e3a11bd04461324a6a798af34d503f8d6f1aa3d2aa8901ceaf039176d",
-                "sha256:d0cee71bc618cd93716f3c1bf56653740d2d13ddbd47673efa8bf41435a60daa",
-                "sha256:d21be4770ff4e08698e1e8e0bce06edb6ea0626e7c8f560bc08222880aca6a6f",
-                "sha256:d31dea506d718693b6b2cffc0648a8929bdc51c70a311b2770f09611caa10d53",
-                "sha256:d44607f98caa2961bab4fa3c4309724b185b464cdc3ba6f3d7340bac3ec97cc1",
-                "sha256:d58ad6317d188c43750cb76e9deacf6051d0f884d87dc6518e0280438648a9ac",
-                "sha256:d70129cef4a8d979caa37e7fe957202e7eee8ea02c5e16455bc9808a59c6b2f0",
-                "sha256:d85164315bd68c0806768dc6bb0429c6f95c354f87485ee3593c4f6b14def2bd",
-                "sha256:d960de62227635d2e61068f42a6cb6aae91a7fe00fca0e3aeed17667c8a34611",
-                "sha256:dc48b479d540770c811fbd1eb9ba2bb66951863e448efec2e2c102625328e92f",
-                "sha256:e1735502458621921cee039c47318cb90b51d532c2766593be6207eec53e5c4c",
-                "sha256:e2be6e9dd4111d5b31ba3b74d17da54a8319d8168890fbaea4b9e5c3de630ae5",
-                "sha256:e4c39ad2f512b4041343ea3c7894339e4ca7839ac38ca83d68a832fc8b3748ab",
-                "sha256:ed402d6153c5d519a0faf1bb69898e97fb31613b49da27a84a13935ea9164dfc",
-                "sha256:ee17cd26b97d537af8f33635ef38be873073d516fd425e80559f4585a7b90c43",
-                "sha256:f3027be483868c99b4985fda802a57a67fdf30c5d9a50338d9db646d590198da",
-                "sha256:f5bab211605d91db0e2995a17b5c6ee5edec1270e46223e513eaa20da20076ac",
-                "sha256:f6f8e3fecca256fefc91bb6765a693d96692459d7d4c644660a9fff32e517843",
-                "sha256:f7afbfee1157e0f9376c00bb232e80a60e59ed716e3211a80cb8506550671e6e",
-                "sha256:fa242ac1ff583e4ec7771141606aafc92b361cd90a05c30d93e343a0c2d82a89",
-                "sha256:fab6ce90574645a0d6c58890e9bcaac8d94dff54fb51c69e5522a7358b80ab64"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.18.1"
-        },
-        "send2trash": {
-            "hashes": [
-                "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9",
-                "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
-            "version": "==1.8.3"
-        },
-        "six": {
-            "hashes": [
-                "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
-                "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
-            "version": "==1.16.0"
-        },
-        "soupsieve": {
-            "hashes": [
-                "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690",
-                "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.5"
-        },
-        "stack-data": {
-            "hashes": [
-                "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9",
-                "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"
-            ],
-            "version": "==0.6.3"
-        },
-        "terminado": {
-            "hashes": [
-                "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0",
-                "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.18.1"
-        },
-        "tinycss2": {
-            "hashes": [
-                "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d",
-                "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==1.3.0"
-        },
-        "tomli": {
-            "hashes": [
-                "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
-                "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
-            ],
-            "markers": "python_version < '3.11'",
-            "version": "==2.0.1"
-        },
-        "tornado": {
-            "hashes": [
-                "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0",
-                "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63",
-                "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263",
-                "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052",
-                "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f",
-                "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee",
-                "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78",
-                "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579",
-                "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212",
-                "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e",
-                "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==6.4"
-        },
-        "traitlets": {
-            "hashes": [
-                "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7",
-                "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==5.14.3"
-        },
-        "typing-extensions": {
-            "hashes": [
-                "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0",
-                "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"
-            ],
-            "markers": "python_version < '3.10'",
-            "version": "==4.11.0"
-        },
-        "watchdog": {
-            "hashes": [
-                "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257",
-                "sha256:2895bf0518361a9728773083908801a376743bcc37dfa252b801af8fd281b1ca",
-                "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b",
-                "sha256:45cc09cc4c3b43fb10b59ef4d07318d9a3ecdbff03abd2e36e77b6dd9f9a5c85",
-                "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b",
-                "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19",
-                "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50",
-                "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92",
-                "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269",
-                "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f",
-                "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c",
-                "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b",
-                "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87",
-                "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b",
-                "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b",
-                "sha256:87e9df830022488e235dd601478c15ad73a0389628588ba0b028cb74eb72fed8",
-                "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c",
-                "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3",
-                "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7",
-                "sha256:ba30a896166f0fee83183cec913298151b73164160d965af2e93a20bbd2ab605",
-                "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935",
-                "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b",
-                "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927",
-                "sha256:d18d7f18a47de6863cd480734613502904611730f8def45fc52a5d97503e5101",
-                "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07",
-                "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec",
-                "sha256:eed82cdf79cd7f0232e2fdc1ad05b06a5e102a43e331f7d041e5f0e0a34a51c4",
-                "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245",
-                "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==4.0.0"
-        },
-        "wcwidth": {
-            "hashes": [
-                "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859",
-                "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"
-            ],
-            "version": "==0.2.13"
-        },
-        "webencodings": {
-            "hashes": [
-                "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
-                "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
-            ],
-            "version": "==0.5.1"
-        },
-        "widgetsnbextension": {
-            "hashes": [
-                "sha256:46f4e3cb2d451bbd6141a13696d6ba17c9b5f50645dca9cfd26fe9644d5a00e1",
-                "sha256:e7fb9999845affc9024ecfbe0a824dd8e633403d027b28ceadab398b633ad51c"
-            ],
-            "version": "==3.6.6"
-        },
-        "zipp": {
-            "hashes": [
-                "sha256:6278d9ddbcfb1f1089a88fde84481528b07b0e10474e09dcfe53dad4069fa059",
-                "sha256:dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e"
-            ],
-            "markers": "python_version < '3.10'",
-            "version": "==3.18.2"
-        }
-    }
-}
diff --git a/binder/apt.txt b/binder/apt.txt
deleted file mode 100644
index 2b626690b3..0000000000
--- a/binder/apt.txt
+++ /dev/null
@@ -1 +0,0 @@
-openjdk-8-jre-headless
diff --git a/binder/postBuild b/binder/postBuild
deleted file mode 100644
index e456a01676..0000000000
--- a/binder/postBuild
+++ /dev/null
@@ -1,12 +0,0 @@
-#Download Apache Spark
-wget https://archive.apache.org/dist/spark/spark-3.4.2/spark-3.4.2-bin-hadoop3.tgz
-tar -xzf spark-3.4.2-bin-hadoop3.tgz
-# Get AWS jars
-hadoop_s3_version=3.3.4
-aws_sdk_version=1.12.402
-
-curl https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aws/${hadoop_s3_version}/hadoop-aws-${hadoop_s3_version}.jar -o $HOME/spark-3.4.2-bin-hadoop3/jars/hadoop-aws-${hadoop_s3_version}.jar
-curl https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-bundle/${aws_sdk_version}/aws-java-sdk-bundle-${aws_sdk_version}.jar -o $HOME/spark-3.4.2-bin-hadoop3/jars/aws-java-sdk-bundle-${aws_sdk_version}.jar
-
-#Tidy up
-rm spark-3.4.2-bin-hadoop3.tgz
diff --git a/binder/start b/binder/start
deleted file mode 100755
index a3e1910712..0000000000
--- a/binder/start
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-
-# 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.
-
-export SPARK_HOME=$HOME/spark-3.4.2-bin-hadoop3
-export PATH=$SPARK_HOME/bin:$PATH
-export PYTHONPATH=$SPARK_HOME/python:$PYTHONPATH
-export PYSPARK_SUBMIT_ARGS="--master local[*] pyspark-shell"
-
-exec "$@"
diff --git a/docker/sedona-spark-jupyterlab/build.sh b/docker/build.sh
similarity index 95%
rename from docker/sedona-spark-jupyterlab/build.sh
rename to docker/build.sh
index 960f97cf31..0f5aac6719 100755
--- a/docker/sedona-spark-jupyterlab/build.sh
+++ b/docker/build.sh
@@ -87,11 +87,11 @@ fi
 
 if [ -z "$BUILD_MODE" ] || [ "$BUILD_MODE" = "local" ]; then
     # If local, build the image for the local environment
-    docker buildx build \
+    docker buildx build --load \
     --build-arg spark_version="${SPARK_VERSION}" \
     --build-arg sedona_version="${SEDONA_VERSION}" \
     --build-arg geotools_wrapper_version="${GEOTOOLS_WRAPPER_VERSION}" \
-    -f docker/sedona-spark-jupyterlab/sedona-jupyterlab.dockerfile \
+    -f docker/sedona-docker.dockerfile \
     -t apache/sedona:"${SEDONA_VERSION}" .
 else
     # If release, build the image for cross-platform
@@ -102,6 +102,6 @@ else
     --build-arg spark_version="${SPARK_VERSION}" \
     --build-arg sedona_version="${SEDONA_VERSION}" \
     --build-arg geotools_wrapper_version="${GEOTOOLS_WRAPPER_VERSION}" \
-    -f docker/sedona-spark-jupyterlab/sedona-jupyterlab.dockerfile \
+    -f docker/sedona-docker.dockerfile \
     -t apache/sedona:"${SEDONA_VERSION}" .
 fi
diff --git a/docker/sedona.sh b/docker/install-sedona.sh
similarity index 63%
rename from docker/sedona.sh
rename to docker/install-sedona.sh
index af8446c584..591ce50a01 100755
--- a/docker/sedona.sh
+++ b/docker/install-sedona.sh
@@ -37,17 +37,17 @@ if [ "$sedona_version" = "latest" ]; then
 else
     # Code to execute when SEDONA_VERSION is not "latest"
     # Download Sedona
-	curl https://repo1.maven.org/maven2/org/apache/sedona/sedona-spark-shaded-"${sedona_spark_version}"_2.12/"${sedona_version}"/sedona-spark-shaded-"${sedona_spark_version}"_2.12-"${sedona_version}".jar -o "$SPARK_HOME"/jars/sedona-spark-shaded-"${sedona_spark_version}"_2.12-"${sedona_version}".jar
+	curl --retry 5 --retry-delay 10 --retry-connrefused https://repo1.maven.org/maven2/org/apache/sedona/sedona-spark-shaded-"${sedona_spark_version}"_2.12/"${sedona_version}"/sedona-spark-shaded-"${sedona_spark_version}"_2.12-"${sedona_version}".jar -o "$SPARK_HOME"/jars/sedona-spark-shaded-"${sedona_spark_version}"_2.12-"${sedona_version}".jar
 
 	# Install Sedona Python
 	pip3 install apache-sedona=="${sedona_version}"
 fi
 
 # Download gresearch spark extension
-curl https://repo1.maven.org/maven2/uk/co/gresearch/spark/spark-extension_2.12/"${spark_extension_version}"-"${spark_compat_version}"/spark-extension_2.12-"${spark_extension_version}"-"${spark_compat_version}".jar -o "$SPARK_HOME"/jars/spark-extension_2.12-"${spark_extension_version}"-"${spark_compat_version}".jar
+curl --retry 5 --retry-delay 10 --retry-connrefused https://repo1.maven.org/maven2/uk/co/gresearch/spark/spark-extension_2.12/"${spark_extension_version}"-"${spark_compat_version}"/spark-extension_2.12-"${spark_extension_version}"-"${spark_compat_version}".jar -o "$SPARK_HOME"/jars/spark-extension_2.12-"${spark_extension_version}"-"${spark_compat_version}".jar
 
 # Install Spark extension Python
 pip3 install pyspark-extension=="${spark_extension_version}"."${spark_compat_version}"
 
 # Download GeoTools jar
-curl https://repo1.maven.org/maven2/org/datasyslab/geotools-wrapper/"${geotools_wrapper_version}"/geotools-wrapper-"${geotools_wrapper_version}".jar -o "$SPARK_HOME"/jars/geotools-wrapper-"${geotools_wrapper_version}".jar
+curl --retry 5 --retry-delay 10 --retry-connrefused https://repo1.maven.org/maven2/org/datasyslab/geotools-wrapper/"${geotools_wrapper_version}"/geotools-wrapper-"${geotools_wrapper_version}".jar -o "$SPARK_HOME"/jars/geotools-wrapper-"${geotools_wrapper_version}".jar
diff --git a/docker/spark.sh b/docker/install-spark.sh
similarity index 53%
rename from docker/spark.sh
rename to docker/install-spark.sh
index bd935e8a45..ed4a077a2e 100755
--- a/docker/spark.sh
+++ b/docker/install-spark.sh
@@ -23,20 +23,20 @@ hadoop_s3_version=$2
 aws_sdk_version=$3
 spark_xml_version=$4
 
-# Set up OS libraries
-apt-get update
-apt-get install -y openjdk-19-jdk-headless curl python3-pip maven
-pip3 install --upgrade pip && pip3 install pipenv
-
 # Download Spark jar and set up PySpark
-pip3 install pyspark=="${spark_version}"
+curl --retry 5 --retry-delay 10 --retry-connrefused https://archive.apache.org/dist/spark/spark-"${spark_version}"/spark-"${spark_version}"-bin-hadoop3.tgz -o spark.tgz
+tar -xf spark.tgz && mv spark-"${spark_version}"-bin-hadoop3/* "${SPARK_HOME}"/
+rm spark.tgz && rm -rf spark-"${spark_version}"-bin-hadoop3
 
 # Add S3 jars
-curl https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aws/"${hadoop_s3_version}"/hadoop-aws-"${hadoop_s3_version}".jar -o "${SPARK_HOME}"/jars/hadoop-aws-"${hadoop_s3_version}".jar
-curl https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-bundle/"${aws_sdk_version}"/aws-java-sdk-bundle-"${aws_sdk_version}".jar -o "${SPARK_HOME}"/jars/aws-java-sdk-bundle-"${aws_sdk_version}".jar
+curl --retry 5 --retry-delay 10 --retry-connrefused https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aws/"${hadoop_s3_version}"/hadoop-aws-"${hadoop_s3_version}".jar -o "${SPARK_HOME}"/jars/hadoop-aws-"${hadoop_s3_version}".jar
+curl --retry 5 --retry-delay 10 --retry-connrefused https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-bundle/"${aws_sdk_version}"/aws-java-sdk-bundle-"${aws_sdk_version}".jar -o "${SPARK_HOME}"/jars/aws-java-sdk-bundle-"${aws_sdk_version}".jar
 
 # Add spark-xml jar
-curl https://repo1.maven.org/maven2/com/databricks/spark-xml_2.12/"${spark_xml_version}"/spark-xml_2.12-"${spark_xml_version}".jar -o "${SPARK_HOME}"/jars/spark-xml_2.12-"${spark_xml_version}".jar
+curl --retry 5 --retry-delay 10 --retry-connrefused https://repo1.maven.org/maven2/com/databricks/spark-xml_2.12/"${spark_xml_version}"/spark-xml_2.12-"${spark_xml_version}".jar -o "${SPARK_HOME}"/jars/spark-xml_2.12-"${spark_xml_version}".jar
+
+# Set up master IP address and executor memory
+cp "${SPARK_HOME}"/conf/spark-defaults.conf.template "${SPARK_HOME}"/conf/spark-defaults.conf
 
 # Install required libraries for GeoPandas on Apple chip mac
 apt-get install -y gdal-bin libgdal-dev
diff --git a/docker/zeppelin/install-zeppelin.sh b/docker/install-zeppelin.sh
similarity index 68%
rename from docker/zeppelin/install-zeppelin.sh
rename to docker/install-zeppelin.sh
index cfef65726c..20dfe4078d 100755
--- a/docker/zeppelin/install-zeppelin.sh
+++ b/docker/install-zeppelin.sh
@@ -21,8 +21,8 @@ ZEPPELIN_VERSION=${1:-0.9.0}
 TARGET_DIR=${2:-/opt}
 
 # Download and extract Zeppelin using curl
-curl -L https://archive.apache.org/dist/zeppelin/zeppelin-"${ZEPPELIN_VERSION}"/zeppelin-"${ZEPPELIN_VERSION}"-bin-all.tgz \
-    -o zeppelin-"${ZEPPELIN_VERSION}"-bin-all.tgz
-tar -xzf zeppelin-"${ZEPPELIN_VERSION}"-bin-all.tgz -C "${TARGET_DIR}"
-mv "${TARGET_DIR}"/zeppelin-"${ZEPPELIN_VERSION}"-bin-all "${ZEPPELIN_HOME}"
-rm zeppelin-"${ZEPPELIN_VERSION}"-bin-all.tgz
+curl -L --retry 5 --retry-delay 10 --retry-connrefused https://archive.apache.org/dist/zeppelin/zeppelin-"${ZEPPELIN_VERSION}"/zeppelin-"${ZEPPELIN_VERSION}"-bin-netinst.tgz \
+    -o zeppelin-"${ZEPPELIN_VERSION}"-bin-netinst.tgz
+tar -xzf zeppelin-"${ZEPPELIN_VERSION}"-bin-netinst.tgz -C "${TARGET_DIR}"
+mv "${TARGET_DIR}"/zeppelin-"${ZEPPELIN_VERSION}"-bin-netinst "${ZEPPELIN_HOME}"
+rm zeppelin-"${ZEPPELIN_VERSION}"-bin-netinst.tgz
diff --git a/docker/sedona-spark-jupyterlab/requirements.txt b/docker/requirements.txt
similarity index 55%
rename from docker/sedona-spark-jupyterlab/requirements.txt
rename to docker/requirements.txt
index c4d3bc3c65..ff9f2602e1 100644
--- a/docker/sedona-spark-jupyterlab/requirements.txt
+++ b/docker/requirements.txt
@@ -1,15 +1,15 @@
 attrs
 descartes
 fiona==1.10.1
-geopandas==0.14.4
+geopandas==1.0.1
 ipykernel
 ipywidgets
-jupyterlab==3.6.4
-jupyterlab-widgets==1.1.7
-keplergl==0.3.2
+jupyterlab==4.3.6
+jupyterlab-widgets==3.0.13
+keplergl==0.3.7
 matplotlib
 numpy<2
 pandas==1.5.3
-pydeck==0.8.0
+pydeck==0.9.1
 rasterio==1.3.11
 shapely==2.0.4
diff --git a/docker/sedona-spark-jupyterlab/sedona-jupyterlab.dockerfile b/docker/sedona-docker.dockerfile
similarity index 55%
rename from docker/sedona-spark-jupyterlab/sedona-jupyterlab.dockerfile
rename to docker/sedona-docker.dockerfile
index 9b0c707a82..3153382c72 100644
--- a/docker/sedona-spark-jupyterlab/sedona-jupyterlab.dockerfile
+++ b/docker/sedona-docker.dockerfile
@@ -18,20 +18,21 @@
 FROM ubuntu:22.04
 
 ARG shared_workspace=/opt/workspace
-ARG spark_version=3.4.1
+ARG spark_version=3.5.5
 ARG hadoop_s3_version=3.3.4
 ARG aws_sdk_version=1.12.402
 ARG spark_xml_version=0.16.0
-ARG sedona_version=1.5.1
-ARG geotools_wrapper_version=1.5.1-28.2
+ARG sedona_version=1.7.1
+ARG geotools_wrapper_version=1.7.1-28.5
 ARG spark_extension_version=2.11.0
 ARG zeppelin_version=0.12.0
 
 # Set up envs
 ENV SHARED_WORKSPACE=${shared_workspace}
-ENV SPARK_HOME=/usr/local/lib/python3.10/dist-packages/pyspark
+ENV SPARK_HOME=/opt/spark
 ENV SEDONA_HOME=/opt/sedona
 ENV ZEPPELIN_HOME=/opt/zeppelin
+RUN mkdir ${SPARK_HOME}
 RUN mkdir ${SEDONA_HOME}
 
 ENV SPARK_MASTER_HOST=localhost
@@ -39,44 +40,56 @@ ENV SPARK_MASTER_PORT=7077
 ENV PYTHONPATH=$SPARK_HOME/python
 ENV PYSPARK_PYTHON=python3
 ENV PYSPARK_DRIVER_PYTHON=jupyter
-COPY ./ ${SEDONA_HOME}/
 
-RUN chmod +x ${SEDONA_HOME}/docker/spark.sh
-RUN chmod +x ${SEDONA_HOME}/docker/sedona.sh
-RUN chmod +x ${SEDONA_HOME}/docker/zeppelin/install-zeppelin.sh
-RUN ${SEDONA_HOME}/docker/spark.sh ${spark_version} ${hadoop_s3_version} ${aws_sdk_version} ${spark_xml_version}
+# Set up OS libraries and PySpark
+RUN apt-get update
+RUN apt-get install -y openjdk-19-jdk-headless curl python3-pip maven
+RUN pip3 install --upgrade pip && pip3 install pipenv
+COPY ./docker/install-spark.sh ${SEDONA_HOME}/docker/
+RUN chmod +x ${SEDONA_HOME}/docker/install-spark.sh
+RUN ${SEDONA_HOME}/docker/install-spark.sh ${spark_version} ${hadoop_s3_version} ${aws_sdk_version} ${spark_xml_version}
 
 # Install Python dependencies
-COPY docker/sedona-spark-jupyterlab/requirements.txt /opt/requirements.txt
-RUN pip3 install --default-timeout=100 -r /opt/requirements.txt
+COPY docker/requirements.txt /opt/requirements.txt
+RUN pip3 install -r /opt/requirements.txt
+
+
+# Copy local compiled jars and python code to the docker environment
+COPY ./spark-shaded/ ${SEDONA_HOME}/spark-shaded/
+COPY ./python/ ${SEDONA_HOME}/python/
+# Install Sedona
+COPY ./docker/install-sedona.sh ${SEDONA_HOME}/docker/
+RUN chmod +x ${SEDONA_HOME}/docker/install-sedona.sh
+RUN ${SEDONA_HOME}/docker/install-sedona.sh ${sedona_version} ${geotools_wrapper_version} ${spark_version} ${spark_extension_version}
+
+
+# Install Zeppelin
+COPY ./docker/install-zeppelin.sh ${SEDONA_HOME}/docker/
+RUN chmod +x ${SEDONA_HOME}/docker/install-zeppelin.sh
+RUN ${SEDONA_HOME}/docker/install-zeppelin.sh ${zeppelin_version} /opt
+COPY ./docker/zeppelin/ ${SEDONA_HOME}/docker/zeppelin/
 
-RUN ${SEDONA_HOME}/docker/sedona.sh ${sedona_version} ${geotools_wrapper_version} ${spark_version} ${spark_extension_version}
-RUN ${SEDONA_HOME}/docker/zeppelin/install-zeppelin.sh ${zeppelin_version} /opt
 # Set up Zeppelin configuration
 COPY docker/zeppelin/conf/zeppelin-site.xml ${ZEPPELIN_HOME}/conf/
 COPY docker/zeppelin/conf/helium.json ${ZEPPELIN_HOME}/conf/
 COPY docker/zeppelin/conf/interpreter.json ${ZEPPELIN_HOME}/conf/
+
+# Extract version from the actual JAR file and update interpreter.json
+COPY ./docker/zeppelin/update-zeppelin-interpreter.sh ${SEDONA_HOME}/docker/zeppelin/
+RUN chmod +x ${SEDONA_HOME}/docker/zeppelin/update-zeppelin-interpreter.sh
+RUN ${SEDONA_HOME}/docker/zeppelin/update-zeppelin-interpreter.sh
+
 RUN mkdir ${ZEPPELIN_HOME}/helium
 RUN mkdir ${ZEPPELIN_HOME}/leaflet
 RUN mkdir ${ZEPPELIN_HOME}/notebook/sedona-tutorial
 COPY zeppelin/ ${ZEPPELIN_HOME}/leaflet
 COPY docker/zeppelin/conf/sedona-zeppelin.json ${ZEPPELIN_HOME}/helium/
 COPY docker/zeppelin/examples/*.zpln ${ZEPPELIN_HOME}/notebook/sedona-tutorial/
-COPY docker/zeppelin/examples/arealm.csv /opt/workspace/examples/data/
 
 COPY docs/usecases/*.ipynb /opt/workspace/examples/
 COPY docs/usecases/*.py /opt/workspace/examples/
 COPY docs/usecases/data /opt/workspace/examples/data
 
-# Add the master IP address to all notebooks
-RUN find /opt/workspace/examples/ -type f -name "*.ipynb" -exec sed -i 's/config = SedonaContext.builder()/config = SedonaContext.builder().master(\\"spark:\/\/localhost:7077\\")/' {} +
-# Delete packages configured by the notebooks
-RUN find /opt/workspace/examples/ -type f -name "*.ipynb" -exec sed -i '/spark\.jars\.packages/d' {} +
-RUN find /opt/workspace/examples/ -type f -name "*.ipynb" -exec sed -i '/org\.apache\.sedona:sedona-spark-shaded-/d' {} +
-RUN find /opt/workspace/examples/ -type f -name "*.ipynb" -exec sed -i '/org\.apache\.sedona:sedona-spark-/d' {} +
-RUN find /opt/workspace/examples/ -type f -name "*.ipynb" -exec sed -i '/org\.datasyslab:geotools-wrapper:/d' {} +
-RUN find /opt/workspace/examples/ -type f -name "*.ipynb" -exec sed -i '/uk\.co\.gresearch\.spark:spark-extension_2\.12:/d' {} +
-
 RUN rm -rf ${SEDONA_HOME}
 
 EXPOSE 8888
@@ -87,5 +100,5 @@ EXPOSE 8085
 
 WORKDIR ${SHARED_WORKSPACE}
 
-COPY docker/sedona-spark-jupyterlab/start.sh /opt/
+COPY ./docker/start.sh /opt/
 CMD ["/bin/bash", "/opt/start.sh"]
diff --git a/docker/sedona-spark-jupyterlab/sedona-jupyterlab.dockerfile.dockerignore b/docker/sedona-docker.dockerfile.dockerignore
similarity index 84%
rename from docker/sedona-spark-jupyterlab/sedona-jupyterlab.dockerfile.dockerignore
rename to docker/sedona-docker.dockerfile.dockerignore
index 6953ae1332..3699b015a2 100644
--- a/docker/sedona-spark-jupyterlab/sedona-jupyterlab.dockerfile.dockerignore
+++ b/docker/sedona-docker.dockerfile.dockerignore
@@ -6,4 +6,4 @@
 !zeppelin/**
 !docs/usecases/**
 !python/**
-!spark-shaded/target/**
+!spark-shaded/**
diff --git a/docker/sedona-spark-jupyterlab/.gitignore b/docker/sedona-spark-jupyterlab/.gitignore
deleted file mode 100644
index 5ef0e2c826..0000000000
--- a/docker/sedona-spark-jupyterlab/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-commands.txt
-docker-compose-orig.yml
-Dockerfile.bak
-log.txt
-examples
-.ipynb_checkpoints
diff --git a/docker/sedona-spark-jupyterlab/start.sh b/docker/start.sh
similarity index 79%
rename from docker/sedona-spark-jupyterlab/start.sh
rename to docker/start.sh
index 71338500be..c9beeaafe5 100755
--- a/docker/sedona-spark-jupyterlab/start.sh
+++ b/docker/start.sh
@@ -67,11 +67,21 @@ if [ $TOTAL_REQUIRED_MEM_MB -gt "$TOTAL_PHYSICAL_MEM_MB" ]; then
     exit 1
 fi
 
-# Configure spark
-cp "${SPARK_HOME}"/conf/spark-env.sh.template "${SPARK_HOME}"/conf/spark-env.sh
-echo "SPARK_WORKER_MEMORY=${EXECUTOR_MEM}" >> "${SPARK_HOME}"/conf/spark-env.sh
-echo "spark.driver.memory $DRIVER_MEM" >> "${SPARK_HOME}"/conf/spark-defaults.conf
-echo "spark.executor.memory $EXECUTOR_MEM" >> "${SPARK_HOME}"/conf/spark-defaults.conf
+# Configure Spark
+cp "${SPARK_HOME}/conf/spark-env.sh.template" "${SPARK_HOME}/conf/spark-env.sh"
+
+# Append Spark environment variables
+{
+  echo "SPARK_WORKER_MEMORY=${EXECUTOR_MEM}"
+} >> "${SPARK_HOME}/conf/spark-env.sh"
+
+# Append Spark default configurations
+{
+  echo "spark.driver.memory $DRIVER_MEM"
+  echo "spark.executor.memory $EXECUTOR_MEM"
+  echo "spark.sql.extensions org.apache.sedona.viz.sql.SedonaVizExtensions,org.apache.sedona.sql.SedonaSqlExtensions"
+  echo "spark.hadoop.fs.s3a.aws.credentials.provider org.apache.hadoop.fs.s3a.AnonymousAWSCredentialsProvider"
+} >> "${SPARK_HOME}/conf/spark-defaults.conf"
 
 # Start spark standalone cluster
 service ssh start
diff --git a/docker/zeppelin/conf/interpreter.json b/docker/zeppelin/conf/interpreter.json
index e9e83e8844..41ff251b16 100644
--- a/docker/zeppelin/conf/interpreter.json
+++ b/docker/zeppelin/conf/interpreter.json
@@ -977,7 +977,7 @@
       "properties": {
         "SPARK_HOME": {
           "name": "SPARK_HOME",
-          "value": "/usr/local/lib/python3.10/dist-packages/pyspark",
+          "value": "/opt/spark",
           "type": "string",
           "description": "Location of spark distribution"
         }
@@ -1383,13 +1383,13 @@
       "properties": {
         "SPARK_HOME": {
           "name": "SPARK_HOME",
-          "value": "/usr/local/lib/python3.10/dist-packages/pyspark",
+          "value": "/opt/spark",
           "type": "string",
           "description": "Location of spark distribution"
         },
         "spark.master": {
           "name": "spark.master",
-          "value": "local[*]",
+          "value": "spark://localhost:7077",
           "type": "string",
           "description": "Spark master uri. local | yarn-client | yarn-cluster | spark master address of standalone mode, ex) spark://master_host:7077"
         },
@@ -1405,36 +1405,6 @@
           "type": "string",
           "description": "The name of spark application."
         },
-        "spark.driver.cores": {
-          "name": "spark.driver.cores",
-          "value": "1",
-          "type": "number",
-          "description": "Number of cores to use for the driver process, only in cluster mode."
-        },
-        "spark.driver.memory": {
-          "name": "spark.driver.memory",
-          "value": "1g",
-          "type": "string",
-          "description": "Amount of memory to use for the driver process, i.e. where SparkContext is initialized, in the same format as JVM memory strings with a size unit suffix (\"k\", \"m\", \"g\" or \"t\") (e.g. 512m, 2g)."
-        },
-        "spark.executor.cores": {
-          "name": "spark.executor.cores",
-          "value": "1",
-          "type": "number",
-          "description": "The number of cores to use on each executor"
-        },
-        "spark.executor.memory": {
-          "name": "spark.executor.memory",
-          "value": "1g",
-          "type": "string",
-          "description": "Executor memory per worker instance. ex) 512m, 32g"
-        },
-        "spark.executor.instances": {
-          "name": "spark.executor.instances",
-          "value": "2",
-          "type": "number",
-          "description": "The number of executors for static allocation."
-        },
         "spark.files": {
           "name": "spark.files",
           "value": "",
@@ -1668,7 +1638,11 @@
       ],
       "dependencies": [
         {
-          "groupArtifactVersion": "/usr/local/lib/python3.10/dist-packages/pyspark/jars/sedona-spark-shaded-3.4_2.12-1.7.0.jar",
+          "groupArtifactVersion": "/opt/spark/jars/sedona-spark-shaded-3.5_2.12-1.7.1.jar",
+          "local": false
+        },
+        {
+          "groupArtifactVersion": "/opt/spark/jars/geotools-wrapper-1.7.1-28.5.jar",
           "local": false
         }
       ],
diff --git a/docker/zeppelin/conf/zeppelin-site.xml b/docker/zeppelin/conf/zeppelin-site.xml
index dff7e9af2e..08ba6696e6 100644
--- a/docker/zeppelin/conf/zeppelin-site.xml
+++ b/docker/zeppelin/conf/zeppelin-site.xml
@@ -57,7 +57,7 @@
 
 <property>
   <name>zeppelin.default.ui</name>
-  <value>new</value>
+  <value>classic</value>
   <description>Default UI for Zeppelin. Options: classic or new</description>
 </property>
 
@@ -435,7 +435,7 @@
 
 <property>
   <name>zeppelin.interpreter.output.limit</name>
-  <value>102400</value>
+  <value>10240000</value>
   <description>Output message from interpreter exceeding the limit will be truncated</description>
 </property>
 
diff --git a/docker/zeppelin/examples/airport-per-country_2KR22DCHD.zpln b/docker/zeppelin/examples/airport-per-country_2KR22DCHD.zpln
new file mode 100644
index 0000000000..b355e24bb0
--- /dev/null
+++ b/docker/zeppelin/examples/airport-per-country_2KR22DCHD.zpln
@@ -0,0 +1,620 @@
+{
+  "paragraphs": [
+    {
+      "text": "%md\n# Find airports in each country\n\n## Read all airports from shapefiles",
+      "user": "anonymous",
+      "dateUpdated": "2025-03-19T06:04:51+0000",
+      "progress": 0,
+      "config": {
+        "tableHide": false,
+        "editorSetting": {
+          "language": "markdown",
+          "editOnDblClick": true,
+          "completionKey": "TAB",
+          "completionSupport": false
+        },
+        "colWidth": 12,
+        "editorMode": "ace/mode/markdown",
+        "fontSize": 9,
+        "editorHide": true,
+        "results": {},
+        "enabled": true
+      },
+      "settings": {
+        "params": {},
+        "forms": {}
+      },
+      "apps": [],
+      "runtimeInfos": {},
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1742364239990_440806182",
+      "id": "paragraph_1742364239990_440806182",
+      "dateCreated": "2025-03-19T06:03:59+0000",
+      "status": "FINISHED",
+      "focus": true,
+      "$$hashKey": "object:1759",
+      "dateFinished": "2025-03-19T06:04:53+0000",
+      "dateStarted": "2025-03-19T06:04:51+0000",
+      "results": {
+        "code": "SUCCESS",
+        "msg": [
+          {
+            "type": "HTML",
+            "data": "<div class=\"markdown-body\">\n<h1>Find airports in each country</h1>\n<h2>Read all airports from shapefiles</h2>\n\n</div>"
+          }
+        ]
+      }
+    },
+    {
+      "text": "%sql\nCREATE OR REPLACE TEMPORARY VIEW airports\nUSING shapefile\nOPTIONS (\n  path \"file:///opt/workspace/examples/data/ne_50m_airports\"\n);\n\nSELECT * FROM airports",
+      "user": "anonymous",
+      "dateUpdated": "2025-03-19T06:13:23+0000",
+      "progress": 0,
+      "config": {
+        "colWidth": 12,
+        "editorMode": "ace/mode/sql",
+        "fontSize": 9,
+        "results": {
+          "0": {
+            "graph": {
+              "mode": "table",
+              "height": 300,
+              "optionOpen": false,
+              "setting": {
+                "table": {
+                  "tableGridState": {},
+                  "tableColumnTypeState": {
+                    "names": {
+                      "geometry": "string",
+                      "scalerank": "string",
+                      "featurecla": "string",
+                      "type": "string",
+                      "name": "string",
+                      "abbrev": "string",
+                      "location": "string",
+                      "gps_code": "string",
+                      "iata_code": "string",
+                      "wikipedia": "string",
+                      "natlscale": "string"
+                    },
+                    "updated": false
+                  },
+                  "tableOptionSpecHash": "[{\"name\":\"useFilter\",\"valueType\":\"boolean\",\"defaultValue\":false,\"widget\":\"checkbox\",\"description\":\"Enable filter for columns\"},{\"name\":\"showPagination\",\"valueType\":\"boolean\",\"defaultValue\":false,\"widget\":\"checkbox\",\"description\":\"Enable pagination for better navigation\"},{\"name\":\"showAggregationFooter\",\"valueType\":\"boolean\",\"defaultValue\":false,\"widget\":\"checkbox\",\"description\":\"Enable a footer [...]
+                  "tableOptionValue": {
+                    "useFilter": false,
+                    "showPagination": false,
+                    "showAggregationFooter": false
+                  },
+                  "updated": false,
+                  "initialized": false
+                },
+                "apache-sedona": {
+                  "geometry": {
+                    "name": "geometry",
+                    "index": 0,
+                    "aggr": "sum"
+                  },
+                  "info": {
+                    "name": "name",
+                    "index": 4,
+                    "aggr": "sum"
+                  }
+                }
+              }
+            },
+            "helium": {}
+          }
+        },
+        "enabled": true,
+        "editorSetting": {
+          "language": "sql",
+          "editOnDblClick": false,
+          "completionKey": "TAB",
+          "completionSupport": true
+        }
+      },
+      "settings": {
+        "params": {},
+        "forms": {}
+      },
+      "results": {
+        "code": "SUCCESS",
+        "msg": [
+          {
+            "type": "TABLE",
+            "data": "geometry\tscalerank\tfeaturecla\ttype\tname\tabbrev\tlocation\tgps_code\tiata_code\twikipedia\tnatlscale\nPOINT (113.93501638737635 22.315332828086753)\t2\tAirport\tmajor\tHong Kong Int'l\tHKG\tterminal\tVHHH\tHKG\thttp://en.wikipedia.org/wiki/Hong_Kong_International_Airport\t150.000\nPOINT (121.23137045332327 25.076741104334637)\t2\tAirport\tmajor\tTaoyuan\tTPE\tterminal\tRCTP\tTPE\thttp://en.wikipedia.org/wiki/Taiwan_Taoyuan_International_Airport\t150.000\nPOINT (4 [...]
+          }
+        ]
+      },
+      "apps": [],
+      "runtimeInfos": {
+        "jobUrl": {
+          "propertyName": "jobUrl",
+          "label": "SPARK JOB",
+          "tooltip": "View in Spark web UI",
+          "group": "spark",
+          "values": [
+            {
+              "jobUrl": "http://09b9743a48e8:4040/jobs/job?id=4",
+              "$$hashKey": "object:4745"
+            },
+            {
+              "jobUrl": "http://09b9743a48e8:4040/jobs/job?id=5",
+              "$$hashKey": "object:4746"
+            }
+          ],
+          "interpreterSettingId": "spark"
+        }
+      },
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1742352330525_888925942",
+      "id": "paragraph_1742352330525_888925942",
+      "dateCreated": "2025-03-19T02:45:30+0000",
+      "dateStarted": "2025-03-19T02:52:29+0000",
+      "dateFinished": "2025-03-19T02:52:29+0000",
+      "status": "FINISHED",
+      "focus": true,
+      "$$hashKey": "object:602"
+    },
+    {
+      "text": "%md\n## Read all countries from shapefiles",
+      "user": "anonymous",
+      "dateUpdated": "2025-03-19T06:05:14+0000",
+      "progress": 0,
+      "config": {
+        "tableHide": false,
+        "editorSetting": {
+          "language": "markdown",
+          "editOnDblClick": true,
+          "completionKey": "TAB",
+          "completionSupport": false
+        },
+        "colWidth": 12,
+        "editorMode": "ace/mode/markdown",
+        "fontSize": 9,
+        "editorHide": true,
+        "results": {},
+        "enabled": true
+      },
+      "settings": {
+        "params": {},
+        "forms": {}
+      },
+      "apps": [],
+      "runtimeInfos": {},
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1742364298709_115589496",
+      "id": "paragraph_1742364298709_115589496",
+      "dateCreated": "2025-03-19T06:04:58+0000",
+      "status": "FINISHED",
+      "focus": true,
+      "$$hashKey": "object:1877",
+      "dateFinished": "2025-03-19T06:05:14+0000",
+      "dateStarted": "2025-03-19T06:05:14+0000",
+      "results": {
+        "code": "SUCCESS",
+        "msg": [
+          {
+            "type": "HTML",
+            "data": "<div class=\"markdown-body\">\n<h2>Read all countries from shapefiles</h2>\n\n</div>"
+          }
+        ]
+      }
+    },
+    {
+      "text": "%sql\nCREATE OR REPLACE TEMPORARY VIEW countries\nUSING shapefile\nOPTIONS (\n  path \"file:///opt/workspace/examples/data/ne_50m_admin_0_countries_lakes\"\n);\n\nSELECT * FROM countries",
+      "user": "anonymous",
+      "dateUpdated": "2025-03-19T02:54:00+0000",
+      "progress": 0,
+      "config": {
+        "colWidth": 12,
+        "editorMode": "ace/mode/sql",
+        "fontSize": 9,
+        "results": {
+          "0": {
+            "graph": {
+              "mode": "apache-sedona",
+              "height": 300,
+              "optionOpen": false,
+              "setting": {
+                "table": {
+                  "tableGridState": {},
+                  "tableColumnTypeState": {
+                    "names": {
+                      "geometry": "string",
+                      "featurecla": "string",
+                      "scalerank": "string",
+                      "LABELRANK": "string",
+                      "SOVEREIGNT": "string",
+                      "SOV_A3": "string",
+                      "ADM0_DIF": "string",
+                      "LEVEL": "string",
+                      "TYPE": "string",
+                      "ADMIN": "string",
+                      "ADM0_A3": "string",
+                      "GEOU_DIF": "string",
+                      "GEOUNIT": "string",
+                      "GU_A3": "string",
+                      "SU_DIF": "string",
+                      "SUBUNIT": "string",
+                      "SU_A3": "string",
+                      "BRK_DIFF": "string",
+                      "NAME": "string",
+                      "NAME_LONG": "string",
+                      "BRK_A3": "string",
+                      "BRK_NAME": "string",
+                      "BRK_GROUP": "string",
+                      "ABBREV": "string",
+                      "POSTAL": "string",
+                      "FORMAL_EN": "string",
+                      "FORMAL_FR": "string",
+                      "NAME_CIAWF": "string",
+                      "NOTE_ADM0": "string",
+                      "NOTE_BRK": "string",
+                      "NAME_SORT": "string",
+                      "NAME_ALT": "string",
+                      "MAPCOLOR7": "string",
+                      "MAPCOLOR8": "string",
+                      "MAPCOLOR9": "string",
+                      "MAPCOLOR13": "string",
+                      "POP_EST": "string",
+                      "POP_RANK": "string",
+                      "GDP_MD_EST": "string",
+                      "POP_YEAR": "string",
+                      "LASTCENSUS": "string",
+                      "GDP_YEAR": "string",
+                      "ECONOMY": "string",
+                      "INCOME_GRP": "string",
+                      "WIKIPEDIA": "string",
+                      "FIPS_10_": "string",
+                      "ISO_A2": "string",
+                      "ISO_A3": "string",
+                      "ISO_A3_EH": "string",
+                      "ISO_N3": "string",
+                      "UN_A3": "string",
+                      "WB_A2": "string",
+                      "WB_A3": "string",
+                      "WOE_ID": "string",
+                      "WOE_ID_EH": "string",
+                      "WOE_NOTE": "string",
+                      "ADM0_A3_IS": "string",
+                      "ADM0_A3_US": "string",
+                      "ADM0_A3_UN": "string",
+                      "ADM0_A3_WB": "string",
+                      "CONTINENT": "string",
+                      "REGION_UN": "string",
+                      "SUBREGION": "string",
+                      "REGION_WB": "string",
+                      "NAME_LEN": "string",
+                      "LONG_LEN": "string",
+                      "ABBREV_LEN": "string",
+                      "TINY": "string",
+                      "HOMEPART": "string",
+                      "MIN_ZOOM": "string",
+                      "MIN_LABEL": "string",
+                      "MAX_LABEL": "string",
+                      "NE_ID": "string",
+                      "WIKIDATAID": "string",
+                      "NAME_AR": "string",
+                      "NAME_BN": "string",
+                      "NAME_DE": "string",
+                      "NAME_EN": "string",
+                      "NAME_ES": "string",
+                      "NAME_FR": "string",
+                      "NAME_EL": "string",
+                      "NAME_HI": "string",
+                      "NAME_HU": "string",
+                      "NAME_ID": "string",
+                      "NAME_IT": "string",
+                      "NAME_JA": "string",
+                      "NAME_KO": "string",
+                      "NAME_NL": "string",
+                      "NAME_PL": "string",
+                      "NAME_PT": "string",
+                      "NAME_RU": "string",
+                      "NAME_SV": "string",
+                      "NAME_TR": "string",
+                      "NAME_VI": "string",
+                      "NAME_ZH": "string"
+                    },
+                    "updated": false
+                  },
+                  "tableOptionSpecHash": "[{\"name\":\"useFilter\",\"valueType\":\"boolean\",\"defaultValue\":false,\"widget\":\"checkbox\",\"description\":\"Enable filter for columns\"},{\"name\":\"showPagination\",\"valueType\":\"boolean\",\"defaultValue\":false,\"widget\":\"checkbox\",\"description\":\"Enable pagination for better navigation\"},{\"name\":\"showAggregationFooter\",\"valueType\":\"boolean\",\"defaultValue\":false,\"widget\":\"checkbox\",\"description\":\"Enable a footer [...]
+                  "tableOptionValue": {
+                    "useFilter": false,
+                    "showPagination": false,
+                    "showAggregationFooter": false
+                  },
+                  "updated": false,
+                  "initialized": false
+                },
+                "apache-sedona": {
+                  "geometry": {
+                    "name": "geometry",
+                    "index": 0,
+                    "aggr": "sum"
+                  }
+                }
+              }
+            },
+            "helium": {}
+          }
+        },
+        "enabled": true,
+        "editorSetting": {
+          "language": "sql",
+          "editOnDblClick": false,
+          "completionKey": "TAB",
+          "completionSupport": true
+        }
+      },
+      "settings": {
+        "params": {},
+        "forms": {}
+      },
+      "results": {
+        "code": "SUCCESS",
+        "msg": [
+          {
+            "type": "TABLE",
+            "data": "geometry\tfeaturecla\tscalerank\tLABELRANK\tSOVEREIGNT\tSOV_A3\tADM0_DIF\tLEVEL\tTYPE\tADMIN\tADM0_A3\tGEOU_DIF\tGEOUNIT\tGU_A3\tSU_DIF\tSUBUNIT\tSU_A3\tBRK_DIFF\tNAME\tNAME_LONG\tBRK_A3\tBRK_NAME\tBRK_GROUP\tABBREV\tPOSTAL\tFORMAL_EN\tFORMAL_FR\tNAME_CIAWF\tNOTE_ADM0\tNOTE_BRK\tNAME_SORT\tNAME_ALT\tMAPCOLOR7\tMAPCOLOR8\tMAPCOLOR9\tMAPCOLOR13\tPOP_EST\tPOP_RANK\tGDP_MD_EST\tPOP_YEAR\tLASTCENSUS\tGDP_YEAR\tECONOMY\tINCOME_GRP\tWIKIPEDIA\tFIPS_10_\tISO_A2\tISO_A3\tISO_ [...]
+          },
+          {
+            "type": "HTML",
+            "data": "<div class=\"result-alert alert-warning\" role=\"alert\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"Close\"><span aria-hidden=\"true\">&times;</span></button><strong>Output is truncated</strong> to 102400 bytes. Learn more about <strong>ZEPPELIN_INTERPRETER_OUTPUT_LIMIT</strong></div>"
+          }
+        ]
+      },
+      "apps": [],
+      "runtimeInfos": {
+        "jobUrl": {
+          "propertyName": "jobUrl",
+          "label": "SPARK JOB",
+          "tooltip": "View in Spark web UI",
+          "group": "spark",
+          "values": [
+            {
+              "jobUrl": "http://09b9743a48e8:4040/jobs/job?id=6",
+              "$$hashKey": "object:779"
+            },
+            {
+              "jobUrl": "http://09b9743a48e8:4040/jobs/job?id=7",
+              "$$hashKey": "object:780"
+            }
+          ],
+          "interpreterSettingId": "spark"
+        }
+      },
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1742352621186_1076375910",
+      "id": "paragraph_1742352621186_1076375910",
+      "dateCreated": "2025-03-19T02:50:21+0000",
+      "dateStarted": "2025-03-19T02:53:02+0000",
+      "dateFinished": "2025-03-19T02:53:03+0000",
+      "status": "FINISHED",
+      "$$hashKey": "object:603"
+    },
+    {
+      "text": "%md\n## Perform a spatial join using ST_Contains",
+      "user": "anonymous",
+      "dateUpdated": "2025-03-19T06:05:37+0000",
+      "progress": 0,
+      "config": {
+        "tableHide": false,
+        "editorSetting": {
+          "language": "markdown",
+          "editOnDblClick": true,
+          "completionKey": "TAB",
+          "completionSupport": false
+        },
+        "colWidth": 12,
+        "editorMode": "ace/mode/markdown",
+        "fontSize": 9,
+        "editorHide": true,
+        "results": {},
+        "enabled": true
+      },
+      "settings": {
+        "params": {},
+        "forms": {}
+      },
+      "apps": [],
+      "runtimeInfos": {},
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1742364319364_228060959",
+      "id": "paragraph_1742364319364_228060959",
+      "dateCreated": "2025-03-19T06:05:19+0000",
+      "status": "FINISHED",
+      "focus": true,
+      "$$hashKey": "object:1970",
+      "dateFinished": "2025-03-19T06:05:37+0000",
+      "dateStarted": "2025-03-19T06:05:37+0000",
+      "results": {
+        "code": "SUCCESS",
+        "msg": [
+          {
+            "type": "HTML",
+            "data": "<div class=\"markdown-body\">\n<h2>Perform a spatial join using ST_Contains</h2>\n\n</div>"
+          }
+        ]
+      }
+    },
+    {
+      "text": "%sql\nSELECT country_name, country_geom, count(*)\nFROM \n(\n    SELECT c.geometry as country_geom, c.NAME_EN as country_name, a.geometry as airport_geom, a.name as airport_name\n    FROM countries c, airports a\n    WHERE ST_Contains(c.geometry, a.geometry)\n)\nGROUP BY country_name, country_geom\nORDER BY COUNT(*) DESC",
+      "user": "anonymous",
+      "dateUpdated": "2025-03-19T06:12:51+0000",
+      "progress": 0,
+      "config": {
+        "colWidth": 12,
+        "editorMode": "ace/mode/sql",
+        "fontSize": 9,
+        "results": {
+          "0": {
+            "graph": {
+              "mode": "apache-sedona",
+              "height": 300,
+              "optionOpen": true,
+              "setting": {
+                "table": {
+                  "tableGridState": {},
+                  "tableColumnTypeState": {
+                    "names": {
+                      "country_name": "string",
+                      "country_geom": "string",
+                      "count(1)": "string"
+                    },
+                    "updated": false
+                  },
+                  "tableOptionSpecHash": "[{\"name\":\"useFilter\",\"valueType\":\"boolean\",\"defaultValue\":false,\"widget\":\"checkbox\",\"description\":\"Enable filter for columns\"},{\"name\":\"showPagination\",\"valueType\":\"boolean\",\"defaultValue\":false,\"widget\":\"checkbox\",\"description\":\"Enable pagination for better navigation\"},{\"name\":\"showAggregationFooter\",\"valueType\":\"boolean\",\"defaultValue\":false,\"widget\":\"checkbox\",\"description\":\"Enable a footer [...]
+                  "tableOptionValue": {
+                    "useFilter": false,
+                    "showPagination": false,
+                    "showAggregationFooter": false
+                  },
+                  "updated": false,
+                  "initialized": false
+                },
+                "lineChart": {
+                  "rotate": {
+                    "degree": "-45"
+                  },
+                  "xLabelStatus": "default"
+                },
+                "stackedAreaChart": {
+                  "rotate": {
+                    "degree": "-45"
+                  },
+                  "xLabelStatus": "default"
+                },
+                "multiBarChart": {
+                  "rotate": {
+                    "degree": "-45"
+                  },
+                  "xLabelStatus": "default"
+                },
+                "apache-sedona": {
+                  "geometry": {
+                    "name": "country_geom",
+                    "index": 1,
+                    "aggr": "sum"
+                  },
+                  "info": {
+                    "name": "count(1)",
+                    "index": 2,
+                    "aggr": "sum"
+                  }
+                }
+              },
+              "keys": [],
+              "groups": [],
+              "values": [],
+              "commonSetting": {}
+            },
+            "helium": {}
+          }
+        },
+        "enabled": true,
+        "editorSetting": {
+          "language": "sql",
+          "editOnDblClick": false,
+          "completionKey": "TAB",
+          "completionSupport": true
+        }
+      },
+      "settings": {
+        "params": {},
+        "forms": {}
+      },
+      "results": {
+        "code": "SUCCESS",
+        "msg": [
+          {
+            "type": "TABLE",
+            "data": "country_name\tcountry_geom\tcount(1)\nUnited States of America\tMULTIPOLYGON (((-132.746875 56.525683593749996, -132.7576171875 56.511035156249996, -132.884716796875 56.512451171875, -132.930810546875 56.524462890624996, -132.948046875 56.567236328125, -132.93623046875 56.6068359375, -132.90654296875 56.63740234375, -132.870654296875 56.69638671875, -132.842529296875 56.794775390625, -132.655859375 56.684716796875, -132.598681640625 56.6357421875, -132.56796875 56.57 [...]
+          },
+          {
+            "type": "HTML",
+            "data": "<div class=\"result-alert alert-warning\" role=\"alert\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"Close\"><span aria-hidden=\"true\">&times;</span></button><strong>Output is truncated</strong> to 102400 bytes. Learn more about <strong>ZEPPELIN_INTERPRETER_OUTPUT_LIMIT</strong></div>"
+          }
+        ]
+      },
+      "apps": [],
+      "runtimeInfos": {
+        "jobUrl": {
+          "propertyName": "jobUrl",
+          "label": "SPARK JOB",
+          "tooltip": "View in Spark web UI",
+          "group": "spark",
+          "values": [
+            {
+              "jobUrl": "http://09b9743a48e8:4040/jobs/job?id=21",
+              "$$hashKey": "object:2812"
+            },
+            {
+              "jobUrl": "http://09b9743a48e8:4040/jobs/job?id=22",
+              "$$hashKey": "object:2813"
+            },
+            {
+              "jobUrl": "http://09b9743a48e8:4040/jobs/job?id=23",
+              "$$hashKey": "object:2814"
+            }
+          ],
+          "interpreterSettingId": "spark"
+        }
+      },
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1742352782995_2012813389",
+      "id": "paragraph_1742352782995_2012813389",
+      "dateCreated": "2025-03-19T02:53:02+0000",
+      "dateStarted": "2025-03-19T05:46:32+0000",
+      "dateFinished": "2025-03-19T05:46:33+0000",
+      "status": "FINISHED",
+      "$$hashKey": "object:604"
+    },
+    {
+      "text": "%sql\n",
+      "user": "anonymous",
+      "dateUpdated": "2025-03-19T02:54:56+0000",
+      "progress": 0,
+      "config": {
+        "colWidth": 12,
+        "fontSize": 9,
+        "enabled": true,
+        "results": {},
+        "editorSetting": {
+          "language": "sql",
+          "editOnDblClick": false,
+          "completionKey": "TAB",
+          "completionSupport": true
+        },
+        "editorMode": "ace/mode/sql"
+      },
+      "settings": {
+        "params": {},
+        "forms": {}
+      },
+      "apps": [],
+      "runtimeInfos": {},
+      "progressUpdateIntervalMs": 500,
+      "jobName": "paragraph_1742352896001_1410361735",
+      "id": "paragraph_1742352896001_1410361735",
+      "dateCreated": "2025-03-19T02:54:56+0000",
+      "status": "READY",
+      "$$hashKey": "object:605"
+    }
+  ],
+  "name": "airport-per-country",
+  "id": "2KR22DCHD",
+  "defaultInterpreterGroup": "spark",
+  "version": "0.12.0",
+  "noteParams": {},
+  "noteForms": {},
+  "angularObjects": {},
+  "config": {
+    "isZeppelinNotebookCronEnable": false,
+    "looknfeel": "default",
+    "personalizedMode": "false"
+  },
+  "info": {},
+  "path": "/airport-per-country"
+}
diff --git a/docker/zeppelin/examples/arealm.csv b/docker/zeppelin/examples/arealm.csv
deleted file mode 100644
index 44aa6a1661..0000000000
--- a/docker/zeppelin/examples/arealm.csv
+++ /dev/null
@@ -1,121960 +0,0 @@
--88.331492,32.324142
--88.175933,32.360763
--88.388954,32.357073
--88.221102,32.35078
--88.323995,32.950671
--88.231077,32.700812
--88.349276,32.548266
--88.304259,32.488903
--88.182481,32.59966
--86.955186,32.617088
--87.059169,32.378484
--87.251219,32.052598
--87.534883,31.934442
--87.49702,31.894541
--88.280149,33.325473
--88.153618,33.261297
--86.988768,31.866884
--87.586341,31.959751
--87.40708,31.887472
--87.43091,31.901283
--87.296975,31.98196
--87.989825,33.138512
--88.279714,33.056158
--87.903345,33.325066
--85.770442,34.635835
--87.441076,34.732319
--87.849593,32.514133
--87.727727,32.072313
--87.297214,32.136159
--88.22334,33.401035
--88.27892,33.424303
--88.231087,33.427042
--85.313559,32.475982
--85.86993,34.252899
--85.624964,34.5007
--85.731468,34.570682
--85.692302,34.624062
--85.531317,34.515826
--85.176298,32.712359
--85.675547,34.702872
--85.622648,34.726025
--85.868304,34.280348
--87.997666,32.067377
--87.754018,31.933427
--87.900857,31.505556
--87.75206,31.835213
--87.896822,31.825628
--87.832314,33.079741
--88.009489,33.048378
--88.052668,33.025677
--88.040059,32.890493
--88.08272,32.858272
--87.904517,32.638514
--85.852638,33.485912
--85.60879,33.499487
--85.43995,31.831141
--85.508885,31.662225
--85.592603,31.919352
--85.576266,31.37517
--85.471051,31.477447
--86.907484,32.898887
--85.595598,34.295102
--85.590928,31.391637
--85.76892,34.244298
--87.410957,31.428077
--87.67258,31.350691
--86.995653,31.8049
--87.49562,31.768868
--86.192919,34.396765
--85.472376,31.306926
--85.534558,34.198603
--85.459413,34.083355
--85.79148,34.243672
--85.716908,34.234799
--85.747839,34.189321
--85.480576,34.24759
--85.685087,34.138127
--85.710173,34.117499
--85.773445,31.555344
--85.631668,31.606412
--85.648618,31.450672
--85.640518,31.453655
--85.647882,31.517597
--88.229438,33.640421
--88.084205,33.908644
--88.022795,33.530668
--86.943759,31.032665
--88.153808,32.169641
--88.38539,32.044417
--87.358799,31.322289
--87.4252,31.536122
--87.463713,31.157989
--88.223098,32.08822
--86.199954,34.42215
--87.178928,31.031561
--86.211962,34.481298
--87.147388,31.241187
--86.787323,31.136704
--87.597205,31.004429
--86.174011,34.198769
--86.439078,34.478315
--86.2102,34.266362
--86.141862,31.50221
--86.183997,31.47244
--85.9281,31.386069
--85.249583,31.255858
--85.12123,31.295949
--85.9537,32.949935
--85.675825,32.910345
--85.976017,32.920054
--87.320307,31.523614
--85.91654,32.810679
--86.689135,31.692897
--86.484397,31.882322
--86.467182,31.950225
--86.834423,31.775774
--86.519626,31.810448
--86.568509,31.802747
--86.539381,31.780034
--86.469484,32.942814
--88.031196,34.320087
--87.886084,34.435597
--87.76535,34.519997
--86.956018,34.792149
--87.140665,34.87079
--87.134174,34.77669
--87.208278,34.803619
--87.946867,33.679719
--87.805737,33.632587
--87.688688,33.728094
--87.589427,33.846982
--87.622974,33.822286
--87.591126,33.808408
--86.839554,34.868023
--85.279364,31.42974
--85.387035,31.558294
--87.591384,33.677834
--86.193177,31.125174
--86.069076,31.122589
--86.946109,34.810445
--85.08419,31.602258
--86.995607,34.803945
--85.387264,31.482895
--87.182419,33.814973
--87.092926,33.740829
--87.289164,33.854504
--87.358187,33.900677
--87.214226,33.956774
--86.257877,33.831763
--87.23697,33.670688
--87.331908,33.984754
--85.417535,32.246656
--85.085513,32.178963
--85.079281,32.126125
--86.262055,33.693827
--86.688062,33.961911
--86.589048,33.960245
--86.264658,34.626
--86.292029,34.737364
--86.139904,34.685524
--85.835169,34.874204
--86.04574,34.669234
--85.732584,34.863339
--86.141461,34.74407
--85.705225,34.750445
--85.712377,34.751851
--85.99517,34.66097
--85.998752,34.886373
--85.915426,34.888336
--85.915808,34.714939
--85.945006,34.718875
--86.461129,34.155822
--86.558472,34.002858
--86.481722,34.12128
--87.529107,33.889491
--85.82299,34.618513
--85.990183,34.766026
--85.897266,34.765103
--86.078793,34.893002
--85.807645,34.841702
--86.036259,34.662036
--88.274033,30.654661
--88.248711,30.428442
--87.392559,34.19413
--87.547093,34.047505
--87.574166,34.253648
--87.630336,34.233353
--87.633806,34.29214
--86.270448,32.227196
--86.115793,32.192337
--88.328687,31.092389
--88.231224,31.064117
--88.35633,30.819372
--88.35457,30.634836
--88.312483,31.043354
--86.26295,32.352382
--88.12883,30.251176
--88.258617,30.679254
--88.095613,30.697563
--86.303285,32.484327
--85.999763,32.522367
--88.238112,31.090917
--88.152078,31.11831
--86.334758,32.575647
--86.195705,32.562671
--87.665227,34.854558
--87.299915,34.827757
--87.78928,34.908502
--87.34158,34.907976
--87.301546,34.811941
--87.529125,34.877648
--85.858592,33.27232
--87.671252,34.79503
--85.684548,33.488647
--87.516048,32.454481
--87.383782,32.496119
--87.405794,32.641742
--87.327001,32.620843
--87.318449,32.631468
--86.252236,33.171583
--86.972997,34.523103
--87.105455,34.64664
--86.110675,33.611043
--86.324848,33.334643
--87.059583,34.484611
--87.103763,34.492224
--85.404462,32.899
--85.49,33.0623
--86.863941,34.259495
--86.779523,34.298889
--86.709657,34.294134
--87.498863,34.592301
--87.167192,34.680133
--87.237538,34.637676
--87.294124,34.44363
--87.520222,34.367609
--87.445219,34.536725
--87.385319,34.539729
--87.318847,34.481296
--87.325244,34.759587
--86.633521,32.818894
--86.524659,32.755352
--86.571503,32.796475
--86.63847,32.860605
--86.768239,33.054101
--86.692411,32.808126
--86.794306,32.918877
--86.856849,32.946244
--86.849555,32.927106
--86.799716,32.919851
--86.542447,32.937733
--86.586318,32.940945
--86.602837,32.901716
--86.928204,31.633677
--87.03305,31.296621
--85.772524,33.824565
--87.722351,30.260513
--85.622386,33.919953
--85.773801,33.821567
--86.308526,31.62918
--86.407279,31.920133
--86.358954,31.551705
--86.353709,31.595469
--87.541825,33.261329
--85.841853,32.14996
--85.966597,32.131295
--85.658597,32.161924
--85.877393,32.214371
--85.60367,32.216655
--85.573166,32.170074
--85.970015,31.807818
--87.177647,33.295354
--87.754418,33.14246
--87.412662,33.573082
--85.728834,31.860101
--85.706317,31.823355
--86.126184,31.747603
--88.100508,34.094523
--87.734685,34.120051
--86.589085,34.680443
--87.815135,33.935088
--87.999352,34.111859
--87.813889,34.155057
--88.272022,31.4381
--86.537713,34.719291
--86.379107,34.750098
--86.598029,34.866564
--86.442157,34.603768
--86.489228,34.505811
--86.369816,34.5681
--86.75027,34.844376
--86.724759,34.647421
--86.718717,34.782714
--86.685308,34.774874
--86.652811,34.77161
--86.599272,34.736348
--86.379009,34.749755
--86.438937,33.334532
--86.719272,33.206293
--86.778207,34.745706
--86.393392,31.170624
--86.785772,33.296791
--86.795242,33.34646
--86.6679,34.698494
--86.430756,31.245033
--86.427225,31.31718
--86.428343,31.095276
--86.472434,31.285208
--86.253861,31.284346
--86.628894,31.513763
--86.281895,31.508007
--86.676382,32.181479
--86.632802,32.160198
--86.633423,32.156464
--86.739685,33.554167
--86.45477,32.175195
--86.856913,33.477542
--86.709508,32.469068
--86.719281,33.577392
--86.758741,33.565205
--86.830538,33.555539
--85.786077,32.298444
--85.864352,32.459293
--85.760367,32.465395
--85.661947,32.352511
--85.617273,32.277048
--87.736584,30.545518
--87.806677,30.383542
--86.876465,33.501841
--87.8158,30.807194
--87.785978,30.757256
--86.826296,33.525889
--87.768528,31.278179
--87.684565,30.73504
--87.522569,30.414045
--87.848711,31.042592
--87.756806,30.253419
--87.688457,30.24873
--87.902885,30.605001
--87.160103,34.280239
--87.920995,33.309852
--86.91365,33.479007
--87.70359,30.485254
--86.780063,33.565142
--86.887479,33.48474
--86.876689,33.493178
--86.816614,33.245043
--86.628386,33.181625
--86.809032,33.814569
--87.581279,30.2898
--86.756263,33.501213
--88.383822,32.349204
--85.628821,32.032609
--88.048249,34.758061
--86.73144,32.18025
--85.727765,31.733181
--85.663957,34.184797
--85.827504,34.92251
--85.915561,34.61215
--88.241926,32.709591
--87.97473,33.480883
--87.279108,33.627665
--87.291893,34.431676
--85.639183,32.415549
--86.834474,31.153512
--86.186711,34.389375
--86.341801,31.473137
--85.652507,33.280223
--86.257071,34.802574
--86.048716,32.44059
--87.784291,32.302
--86.687292,34.770874
--86.953922,33.400892
--86.733092,33.547855
--86.717359,33.583832
--86.124099,34.437269
--87.725025,34.388238
--85.041156,31.000585
--87.082701,34.169986
--87.219391,32.680325
--87.693801,30.414064
--86.620382,31.407858
--87.931755,32.67465
--88.205027,33.103257
--86.3708,34.762286
--87.981987,33.582388
--86.325459,34.556721
--85.591277,34.061312
--88.38771,31.291488
--87.504942,32.447505
--86.308316,33.870472
--86.598472,33.134679
--86.587709,32.951804
--87.004191,34.13678
--88.025575,31.056138
--86.677745,33.389966
--85.451636,32.948546
--86.728498,33.567304
--86.481563,32.138831
--88.154989,32.795201
--85.754616,31.304855
--86.818935,34.433497
--87.221347,33.304955
--86.450267,31.742019
--85.953909,32.845653
--86.393742,34.167866
--87.218309,33.113756
--87.588166,34.314393
--85.777431,33.818477
--86.945634,32.441588
--88.104757,34.405693
--87.907967,32.25723
--85.251936,31.572208
--85.964178,34.359783
--85.650389,32.83594
--86.615802,34.861497
--85.862334,33.584871
--85.281778,31.652543
--85.706824,33.325629
--85.838235,34.867976
--87.768081,30.862751
--86.983396,34.768058
--86.599959,34.545151
--86.797994,31.231891
--86.747794,33.076058
--86.879076,33.507319
--86.915689,31.701685
--88.384664,32.34299
--87.215083,34.057359
--85.665839,32.342602
--85.475087,32.479419
--85.812437,34.579143
--87.625247,30.3822
--86.489998,33.929028
--86.247291,34.694335
--88.075762,34.221227
--85.469698,31.511232
--88.029262,31.079208
--87.517995,32.420758
--86.293286,34.357907
--85.891041,34.900988
--85.171258,32.013258
--86.210346,31.679173
--87.65326,33.53571
--87.918905,31.51441
--87.493438,34.9251
--86.776809,32.522333
--86.761997,34.6751
--85.263894,31.574663
--86.954912,31.111595
--86.609578,34.0938
--85.594688,33.92309
--85.715538,34.157346
--86.448778,32.03044
--85.627308,34.764328
--85.652338,34.750083
--87.961805,33.36692
--85.533082,34.294976
--87.666907,31.020253
--87.996276,34.11457
--87.278659,31.304097
--85.361343,33.147729
--87.430378,33.117015
--86.085225,31.412311
--85.777608,34.876162
--86.577605,32.177981
--86.675037,32.131858
--87.237329,34.169095
--85.249783,31.567258
--87.71071,33.713029
--88.023887,33.569306
--87.203432,34.683797
--87.191427,34.92707
--86.688301,34.323123
--87.116624,34.471642
--86.296687,32.159787
--86.944905,32.253974
--88.006658,32.976363
--85.963664,32.872109
--88.217247,32.656121
--86.712374,31.04646
--86.185367,32.495435
--86.551141,31.831533
--88.239041,31.305381
--85.977257,31.775216
--85.805262,34.877265
--86.984928,34.605586
--87.182068,31.03064
--85.336109,31.35158
--87.997639,33.306554
--87.908921,31.67044
--86.776779,33.536222
--87.18995,31.736327
--86.726404,32.151463
--85.425687,31.933472
--86.465403,33.9506
--87.682881,30.412977
--88.083414,30.244859
--87.070564,31.992672
--86.959284,31.41676
--87.306059,32.32135
--88.051643,32.876198
--85.148553,32.192729
--88.016063,33.466383
--86.916217,31.517558
--86.442845,34.601243
--86.801896,33.905075
--87.367379,34.073219
--85.033719,32.509451
--85.610817,32.026868
--86.624797,33.179727
--87.345752,34.915186
--86.875777,34.102354
--86.880908,34.049256
--86.81656,34.172451
--86.48557,32.089851
--87.33606,34.360797
--85.647631,34.829221
--86.578361,34.850553
--88.299543,32.842042
--86.259114,31.577035
--86.888117,33.54088
--85.678991,31.637388
--85.751747,31.34646
--85.872517,31.94539
--85.695276,32.437745
--87.013685,34.678126
--85.804199,32.258495
--87.621828,32.717306
--86.782718,32.458228
--88.046041,32.815373
--87.095533,34.414939
--85.248655,32.355918
--87.188932,33.511262
--85.432366,31.782543
--87.669786,33.441284
--86.730425,31.629108
--86.77016,33.501206
--85.583742,32.972887
--86.620831,34.743173
--85.64521,31.487689
--86.21095,34.26737
--88.343429,32.92559
--85.649943,32.007458
--87.756588,32.206277
--86.608513,32.983714
--85.892986,32.535704
--86.592761,32.957371
--86.790265,33.47907
--87.823276,34.452555
--85.943286,34.979206
--86.499984,31.601078
--86.544468,31.898205
--86.278036,32.387864
--85.477625,32.216059
--85.861945,34.82898
--87.28468,34.509193
--88.061832,32.916877
--86.633615,32.02261
--87.917839,34.367813
--87.40833,34.644286
--87.259129,34.284952
--88.193709,31.438093
--85.115473,31.532494
--88.09568,33.860857
--86.391119,31.553239
--87.019974,32.424417
--88.199433,33.127659
--85.50774,32.584043
--86.16586,34.162295
--85.875579,34.55046
--85.895275,34.776976
--87.27657,34.041832
--85.785491,34.623936
--88.076364,33.295085
--87.642073,34.095263
--88.206633,33.72003
--85.653906,31.511704
--88.112518,34.728463
--87.487106,34.47734
--86.427684,34.664184
--85.968109,34.711481
--85.775765,34.936669
--87.906318,34.211538
--85.866297,31.038765
--87.497839,34.867151
--85.756262,34.945873
--86.895253,32.997732
--86.932213,34.451346
--86.331167,34.671664
--87.805654,30.472903
--86.401172,31.948492
--86.091041,33.432561
--85.090884,31.562775
--85.369419,33.140171
--85.86031,34.959136
--85.96618,33.490187
--86.34079,30.994331
--86.952022,31.659498
--88.034266,34.347443
--87.667585,30.392203
--87.913147,33.285961
--86.708441,31.508779
--86.724201,32.927055
--88.051433,32.75774
--86.758937,34.32903
--87.845263,33.133826
--87.110288,32.226443
--86.6897,31.219826
--86.192288,31.075947
--85.620155,31.776683
--87.892311,32.009901
--86.188752,32.361325
--85.890783,32.259669
--86.952835,33.439197
--86.707893,33.373596
--88.283598,32.174773
--87.551498,34.842292
--88.290852,30.918718
--88.433239,31.424064
--86.99104,31.11539
--85.385302,31.202856
--85.331478,31.719644
--86.696758,32.337125
--87.082541,31.920676
--87.459194,34.469472
--87.548336,33.315541
--86.32417,34.013789
--86.90931,33.482642
--85.724018,34.439862
--88.082014,31.187496
--85.695639,34.653984
--85.82291,34.967365
--86.234226,33.584863
--85.947435,32.922415
--87.786468,33.687922
--87.166113,33.970356
--88.21562,33.638781
--87.860728,31.014264
--86.577356,34.721189
--87.418734,34.87048
--87.981607,34.761804
--86.907779,33.4665
--85.850062,32.039673
--88.33384,32.818562
--85.83404,32.826079
--86.528525,32.319122
--87.506151,31.308529
--86.751054,33.376606
--88.39203,32.507796
--87.238671,34.749723
--87.62147,34.225833
--88.089185,32.674746
--85.311773,33.152894
--85.643606,34.634533
--87.193177,31.544829
--86.720296,32.797156
--87.430689,34.820238
--86.352684,33.266303
--86.567204,32.88196
--87.86424,32.841997
--87.662692,33.474603
--87.734928,33.546086
--87.686982,34.803559
--87.368582,31.03007
--86.954592,31.435689
--87.683927,30.62592
--87.185367,33.750947
--88.092666,32.859598
--86.329458,34.069801
--86.450904,31.197666
--86.862045,32.933379
--87.606025,33.17792
--87.682814,30.561043
--88.257387,31.464939
--85.675929,34.149189
--87.883052,34.735771
--88.238912,30.681108
--87.408418,32.502057
--86.33276,34.670648
--86.935839,34.261647
--87.47178,34.629541
--87.541709,34.027098
--87.810187,30.805809
--88.393581,30.719663
--87.191388,34.844669
--86.560033,32.89841
--86.745402,33.489928
--88.035294,30.667355
--85.696658,34.889271
--88.078256,34.143629
--87.608881,33.452863
--85.368421,33.150657
--88.004043,33.401148
--88.031019,34.482755
--87.582709,31.58088
--85.863783,33.222747
--86.569115,33.137921
--87.627813,33.513745
--86.824393,33.466513
--87.525919,34.57419
--88.009548,31.087675
--86.200816,34.180035
--86.9504,34.103457
--87.6294,33.905259
--87.807955,32.483947
--86.792479,32.677062
--87.634507,33.000698
--85.621002,31.812962
--88.341128,31.219645
--87.799625,30.38068
--86.932306,32.815554
--85.418598,31.535549
--85.965249,31.227181
--85.481303,32.293501
--86.193061,31.161104
--88.145555,34.207423
--86.789072,34.833016
--87.758948,31.930778
--87.051124,32.486061
--85.808139,34.907425
--85.676043,31.595825
--88.084794,33.098864
--88.103996,31.123967
--86.216193,33.20003
--86.63448,32.981122
--87.8196,31.210995
--86.94342,33.365682
--86.728572,32.32341
--87.297251,32.141227
--87.745222,30.996559
--85.994388,33.98933
--86.830669,34.149724
--86.77898,33.551926
--86.7457,34.698158
--87.901576,34.151585
--86.681624,32.98147
--86.304342,33.606379
--86.234139,31.262822
--87.821686,33.72374
--87.638468,32.341924
--85.539069,31.301392
--87.279914,31.7874
--87.527908,33.72387
--85.993678,33.987033
--88.252489,32.916134
--87.191388,34.945502
--86.150893,31.815061
--85.249941,32.446286
--86.861541,33.12963
--87.517464,33.16857
--85.838051,33.159457
--86.419762,32.291142
--86.08815,34.022278
--87.771957,30.614546
--86.048282,34.373231
--86.632433,33.671847
--87.079752,31.056924
--87.388952,34.621195
--87.800737,33.747223
--85.697958,31.86864
--85.599376,31.576691
--87.608946,31.986559
--87.636282,34.991464
--87.741249,31.719453
--86.934992,34.444539
--85.837262,34.486223
--86.627024,32.956611
--86.304076,34.049651
--87.347015,31.732285
--87.7603,33.631626
--85.943409,32.619646
--85.699788,34.501948
--87.878693,31.731882
--86.816079,33.124083
--86.856649,33.128143
--87.419506,34.592943
--86.847787,33.523772
--86.261298,34.358022
--85.944914,34.600183
--85.520327,32.426701
--87.289256,31.986936
--87.547826,31.163691
--87.094083,32.938017
--86.996333,32.948292
--86.437984,33.138573
--87.745234,34.484401
--87.145193,34.682716
--87.482304,33.192318
--85.213575,32.705243
--85.655431,34.141887
--86.49038,34.056175
--87.124441,34.419973
--87.181288,31.216203
--86.024002,33.583785
--87.64626,33.708432
--85.449737,31.992118
--85.472209,31.863598
--87.715681,33.413097
--87.009549,34.674517
--85.352719,32.463603
--86.96375,34.800526
--86.59768,32.973882
--86.499559,32.228764
--87.917567,33.338968
--85.946607,34.712429
--86.59797,34.742166
--85.399823,31.839168
--87.73709,32.114396
--88.111529,34.640422
--85.627249,31.779813
--86.248101,32.332281
--88.075296,32.820901
--86.298374,32.381285
--87.036125,31.317859
--85.999457,34.35249
--86.503743,31.311205
--88.105733,33.870143
--87.832415,33.684657
--85.920136,34.863151
--85.618456,32.108327
--86.031503,32.448149
--88.145264,33.141073
--88.137359,34.434415
--86.417101,34.537389
--86.322259,32.10308
--85.678627,34.152457
--87.667861,33.620807
--87.286651,34.764193
--86.338839,32.573693
--86.492253,31.307966
--87.428411,34.470353
--88.074946,30.764509
--86.21139,32.920731
--85.545091,34.050575
--87.798988,34.583758
--88.133603,34.437643
--88.42534,31.817802
--86.967508,34.80268
--85.962891,31.79538
--87.701982,33.593304
--85.078263,32.112499
--85.976247,31.512647
--88.401397,32.341222
--87.901815,33.276668
--85.40507,32.622729
--85.580139,34.393922
--85.931604,32.94943
--86.642788,32.946114
--85.524893,34.152014
--88.11999,33.400497
--87.528963,32.025903
--85.693509,34.264547
--85.979904,34.823145
--88.054891,32.996114
--87.9933,34.38015
--87.44321,34.569106
--86.282655,32.346736
--88.090335,34.756901
--86.343536,31.964485
--87.404335,31.540814
--86.840162,34.833112
--85.847952,34.616188
--86.788009,34.617995
--86.717655,34.294428
--86.559696,31.808031
--88.414987,32.338364
--85.962453,32.619085
--86.892326,31.150379
--85.761704,32.08779
--88.150379,32.089391
--86.162086,34.859719
--88.04535,33.906609
--85.752475,34.955938
--87.245895,33.026988
--86.515637,34.523387
--85.465548,31.77916
--85.637795,31.450452
--86.981307,31.873659
--87.568417,32.493361
--88.01794,33.374294
--88.237578,33.608082
--86.750436,33.550309
--87.323472,33.228547
--85.853206,31.060451
--85.591098,31.437951
--88.015003,34.251982
--86.180494,34.764915
--85.375687,33.150037
--87.797976,32.307066
--88.35803,32.121423
--86.253359,31.287438
--86.809751,33.522224
--85.848578,34.49338
--85.592506,33.456325
--88.015849,33.925081
--87.641946,34.373392
--85.820558,31.736544
--87.631992,33.650967
--85.102118,31.153121
--86.865782,34.34837
--87.510713,31.612856
--87.504382,34.93479
--87.902732,30.602125
--87.571448,33.73317
--87.639971,33.427728
--87.155918,31.06689
--85.606095,34.931308
--85.675132,34.269873
--87.110994,34.417081
--88.149756,34.341584
--87.295526,34.787902
--85.622285,31.853022
--85.320297,31.573628
--87.70528,32.027847
--86.52925,32.088292
--86.449963,31.587829
--86.225571,32.970796
--87.538706,31.970311
--86.926159,33.341197
--88.435243,31.844656
--85.491267,34.203894
--86.818014,33.523828
--86.801726,31.165166
--88.277689,32.310778
--85.22659,32.030994
--85.843458,34.142417
--87.42307,34.469948
--87.860296,30.834375
--86.353352,33.276977
--85.672257,33.250165
--88.027577,32.357778
--87.382905,34.834233
--87.207959,32.000332
--85.754534,32.021065
--86.967064,34.567908
--87.519807,33.217557
--86.288806,34.37544
--88.084277,33.425272
--87.062144,34.937228
--87.58939,31.898999
--86.556694,34.548827
--85.886923,34.800433
--85.796952,34.452452
--87.572434,34.963567
--87.018109,34.058734
--85.572118,32.440964
--86.282366,32.334158
--88.275702,32.499449
--86.097371,32.445843
--87.88182,33.117767
--88.158469,32.372466
--86.447239,34.596983
--86.691631,32.998942
--87.734969,34.12138
--85.482724,31.87103
--87.668239,32.13785
--87.900683,33.47403
--86.57736,32.503372
--87.362662,32.382771
--86.281261,34.020646
--86.846518,33.509341
--86.871853,33.099344
--87.337535,31.427193
--85.622726,31.448553
--85.666056,32.429772
--88.178521,33.592193
--87.343318,31.994784
--85.22469,31.30362
--86.384279,33.450248
--87.92646,34.352185
--87.238875,31.41025
--86.758526,34.749498
--86.687276,34.668651
--87.672362,34.505195
--86.606552,32.889718
--85.558297,31.779088
--87.573531,33.908083
--85.84963,34.864768
--86.807676,34.809585
--87.102759,33.231389
--86.359405,32.143365
--87.668032,33.673123
--86.454688,32.582576
--88.00911,34.175462
--86.841701,32.815612
--86.927619,33.442408
--85.972404,34.925075
--86.508051,32.817134
--88.071767,30.805039
--87.29805,34.833907
--88.386016,31.971007
--87.475527,34.852916
--86.252108,33.310845
--86.784964,33.446644
--85.476399,32.203319
--86.559553,32.050452
--88.335387,31.802093
--86.831183,31.575458
--85.986739,34.579118
--86.524375,34.802602
--87.442765,32.449052
--86.715384,31.563071
--87.791701,30.476698
--86.518028,31.824979
--88.023323,31.144001
--87.215491,34.347384
--88.032516,34.165461
--85.530851,31.888413
--86.208836,31.863428
--87.649515,34.929168
--85.890076,34.294791
--86.272457,34.73349
--88.05115,34.178593
--86.836351,32.960906
--88.215236,31.158398
--87.172506,34.294434
--88.286957,32.650542
--88.382818,32.319915
--86.162747,31.200307
--85.798162,32.860491
--87.148095,32.07539
--86.577131,32.763786
--86.713981,34.755539
--87.959232,34.143021
--87.204785,31.736924
--88.366119,32.402014
--86.752003,32.893185
--85.881631,31.274226
--87.073344,31.106794
--86.60257,34.843481
--88.030711,34.226744
--85.624964,34.502482
--85.966289,32.985352
--88.178106,32.025086
--87.42117,31.027249
--85.983292,34.884762
--87.026539,32.476219
--85.761003,32.492334
--87.006521,34.615945
--88.062471,33.863911
--86.480615,31.291367
--85.54088,32.301992
--87.688249,34.945121
--88.136235,33.361839
--87.291561,34.403161
--85.634713,34.492848
--87.210001,34.320572
--86.421871,32.195431
--86.352681,34.715766
--86.375448,32.032364
--86.767741,34.05822
--86.11428,34.851831
--85.777438,33.821463
--85.718703,32.441811
--87.705207,34.002808
--87.932807,33.017901
--87.329222,33.103718
--86.358438,32.975837
--86.392347,31.559875
--85.249481,31.569909
--85.961009,34.671606
--85.542118,32.298387
--86.807623,33.502595
--86.065972,32.111896
--87.121161,31.01478
--86.218946,32.052137
--87.532712,33.89635
--85.957862,34.739671
--88.142863,30.685555
--85.100741,32.071193
--85.88311,34.874494
--88.436889,31.263191
--86.872582,32.863734
--85.657256,31.99122
--86.584197,34.720602
--86.995645,34.801695
--86.602919,34.479023
--87.694941,32.626825
--88.03068,31.345465
--87.256299,32.238391
--87.499387,32.626536
--87.259112,33.738832
--87.075409,34.436399
--86.109642,33.654841
--87.610095,31.986317
--85.582327,32.098985
--86.069655,34.465132
--86.787737,34.625079
--88.22008,32.088957
--86.374021,33.535126
--88.296633,33.349508
--87.992537,34.123641
--86.196906,34.441663
--88.253681,32.690725
--87.391203,34.416816
--85.686709,32.567712
--87.743317,34.038796
--85.993339,34.676097
--86.817671,32.899627
--87.599228,33.386093
--86.582817,34.719833
--88.000789,31.371085
--86.766435,34.794937
--85.349253,33.277369
--85.774846,34.416536
--85.64132,34.491918
--87.714323,34.810413
--86.782969,33.325612
--87.263726,34.801533
--88.182011,33.20243
--87.375021,34.789139
--88.27126,33.511256
--88.2544,30.690093
--86.336151,33.363442
--87.411315,34.854124
--87.561296,31.710897
--85.617961,34.981495
--88.193725,30.689424
--85.696983,30.99646
--84.965961,32.243
--87.760902,34.028711
--87.305978,34.707027
--85.67149,34.155623
--86.726929,31.542322
--87.910202,30.523102
--87.315568,32.631549
--85.788547,34.915711
--87.910357,34.30599
--86.8344,33.305431
--86.27481,32.129331
--87.602704,33.417718
--87.387564,34.8149
--86.476445,31.296225
--86.547745,34.125398
--88.301967,33.050493
--85.594826,32.088155
--86.693388,32.059271
--85.599148,34.906285
--85.245586,32.3962
--86.136945,32.940434
--87.432231,34.84599
--87.801044,34.170272
--88.196424,32.594804
--88.107958,32.840539
--87.049511,33.751792
--85.999953,31.94694
--85.551374,34.181376
--87.438854,31.058559
--85.62933,34.750401
--86.705004,34.625147
--85.402625,31.854224
--87.860687,32.020052
--85.16911,31.241759
--85.659308,32.406151
--86.669051,34.785482
--86.138367,33.426747
--86.65999,32.234364
--86.387586,31.248512
--88.168136,34.161808
--87.262634,31.639107
--85.633164,34.602908
--87.707121,30.546452
--85.497837,31.610879
--85.626266,34.410182
--85.950662,31.385697
--86.313198,31.546018
--86.854182,33.488135
--87.698489,34.985086
--86.155647,34.427331
--86.293393,32.205706
--87.278485,34.482197
--85.56995,33.657747
--87.61458,32.273328
--87.473273,34.051456
--85.38616,31.926073
--86.17839,34.192652
--87.149744,34.838186
--87.470983,34.626503
--87.733652,32.843547
--87.667071,34.579334
--85.567961,33.417883
--86.045775,34.816149
--87.532906,33.89418
--87.154,33.108841
--85.437767,31.029833
--87.540428,33.214275
--87.280556,34.475249
--86.222665,34.411398
--86.574785,32.186225
--85.999545,32.079074
--86.9785,33.39208
--88.182029,34.023182
--87.876653,33.397567
--87.752845,31.001171
--86.987947,34.605056
--86.54898,32.933289
--88.30022,32.69146
--86.052892,34.730582
--87.78262,31.706248
--85.654626,31.227908
--85.634716,34.56951
--86.579542,31.98821
--86.811572,33.49738
--85.303102,32.786634
--86.769712,33.100049
--88.226156,33.157232
--86.714167,31.555145
--86.648469,34.71624
--87.466712,34.128207
--85.96749,31.605972
--86.355935,34.698591
--85.760808,33.812882
--85.974671,31.806108
--86.794481,33.472752
--85.923632,34.843196
--86.339695,31.452501
--87.32722,34.833304
--87.673649,34.290734
--85.795696,34.902267
--86.758628,33.888396
--85.872274,34.254858
--87.735687,32.090497
--87.724974,34.478406
--85.231817,32.875214
--87.613188,32.421711
--85.684078,32.073743
--87.089943,34.489831
--86.292958,34.359931
--87.270187,34.481932
--85.378699,32.640462
--85.641282,34.502635
--87.321634,34.446371
--85.555811,32.128811
--87.346306,33.153146
--87.604547,30.370335
--88.308384,31.464821
--86.769082,33.47579
--86.800854,32.834735
--85.759213,32.828327
--88.226833,33.182178
--87.093246,34.93512
--85.2436,32.349829
--87.756237,32.204362
--85.65536,31.834455
--88.052141,33.059924
--86.804193,33.521872
--85.930576,32.345982
--87.681214,30.275538
--85.678861,31.555624
--86.937169,34.451848
--87.795713,30.471683
--88.289412,30.48934
--85.578059,32.106957
--87.776647,32.065162
--86.246406,32.375152
--86.985009,34.804056
--86.502444,34.636575
--86.82885,31.659122
--86.334706,34.606941
--86.328389,34.467801
--85.66477,34.599027
--88.084233,34.849091
--85.287176,33.12568
--88.121932,32.636975
--87.892714,30.589372
--88.149703,32.670654
--85.408862,31.386571
--87.956403,34.485663
--85.376926,31.7642
--85.810137,34.652579
--86.818117,32.579346
--87.74425,31.605253
--86.657771,34.856808
--85.548846,31.957879
--87.073842,33.246858
--87.031939,33.93901
--86.464957,34.451798
--87.232313,31.315236
--86.736302,34.583359
--88.351636,32.742787
--86.689256,31.99172
--87.161949,33.975127
--86.301602,34.353152
--88.228696,31.090098
--85.806527,34.875657
--86.775601,32.966813
--88.257431,32.106744
--86.015274,32.897924
--85.448918,32.463025
--87.609853,32.436202
--85.800144,34.346756
--85.830914,34.854978
--88.001236,33.187723
--87.704275,34.198189
--87.19318,33.049732
--86.193476,31.177667
--87.154835,33.164008
--86.761226,31.893461
--86.743578,31.622557
--85.614032,32.214897
--86.753308,31.540288
--86.735506,34.579801
--85.596212,34.378986
--85.73255,34.43345
--86.654551,31.601849
--87.765297,33.696978
--85.99283,34.286952
--87.671764,34.795916
--86.497916,34.327571
--86.826204,33.097183
--86.84481,33.287633
--85.748294,31.781641
--85.925257,34.729455
--87.336208,32.630912
--86.863482,33.463174
--87.52767,33.902884
--86.673917,32.429412
--86.412876,34.638639
--88.315179,31.232685
--86.421308,31.777879
--87.762338,33.2012
--86.77565,33.583859
--87.715548,30.878444
--85.668258,32.560606
--88.018436,31.241007
--85.574726,31.422178
--86.040849,31.70367
--87.816142,34.130029
--86.326694,34.733961
--87.604057,34.887601
--88.114928,32.083542
--85.812951,34.867066
--87.089707,32.941786
--87.462866,34.670451
--87.918661,30.62971
--87.803473,30.843508
--86.397441,32.066059
--86.631346,32.813505
--85.584649,33.645735
--88.135808,30.451429
--87.95299,33.259445
--85.887977,34.798677
--85.62237,34.575069
--86.720205,34.773386
--86.911502,31.752762
--87.09773,31.111664
--85.527275,31.970338
--85.144135,31.938506
--87.787067,30.88063
--86.494855,34.593946
--87.36402,34.886851
--88.016331,33.925825
--88.051039,33.628102
--86.36575,31.497569
--87.10534,34.615442
--85.913306,34.812654
--88.025707,32.782705
--87.751613,34.019537
--85.946223,34.71582
--88.029343,30.658505
--85.91313,32.622086
--87.343065,34.506981
--85.405181,31.805935
--85.369496,31.734513
--87.794673,34.499319
--87.959884,33.351278
--87.954443,34.393526
--87.545796,32.055481
--87.022413,31.286906
--86.71839,32.154387
--88.02919,32.782916
--87.07363,32.15276
--88.177288,32.100935
--86.799416,33.608859
--86.696462,32.99813
--87.794993,32.350738
--87.125428,33.356488
--85.913948,34.976956
--86.817811,34.349604
--86.016839,31.80554
--87.861403,31.030946
--87.467822,33.199018
--85.639451,34.506837
--87.392522,32.500134
--86.91116,33.053061
--85.844489,32.144596
--88.115977,30.255345
--87.278022,32.77376
--87.820957,33.980448
--86.140617,32.807888
--86.965592,34.560508
--85.711603,31.309279
--85.644529,34.636425
--85.626858,34.275825
--88.125101,34.259525
--87.28659,34.823046
--87.419422,31.025134
--87.658997,31.741789
--87.165394,32.918249
--86.250557,31.716267
--85.865691,31.030543
--86.500612,31.662171
--87.551107,32.117086
--87.823962,32.514907
--85.431155,31.9385
--88.166853,33.556659
--88.182429,33.08381
--87.055156,34.217996
--86.983053,34.800494
--88.141564,30.76057
--86.437103,34.236576
--85.50352,33.496377
--87.268766,34.409023
--87.02581,33.217159
--86.173208,33.74172
--87.806917,34.468162
--87.408738,32.398856
--88.143389,30.782682
--86.733086,34.855272
--88.182668,33.154862
--85.546977,31.960606
--88.15943,32.006724
--87.636961,32.358089
--86.272678,32.309115
--87.559958,33.937448
--88.139598,34.248918
--85.992117,34.67937
--85.530665,32.10745
--87.775105,33.872223
--87.704589,34.732376
--86.94719,34.290138
--87.283383,32.195794
--86.975051,32.989558
--87.904504,33.731808
--86.967194,33.427968
--87.286619,34.493597
--86.4916,34.669047
--88.004209,31.08964
--87.564675,33.977427
--87.790338,32.281951
--87.028426,34.683031
--86.846704,32.923856
--86.881081,31.610434
--87.815891,30.252557
--86.650631,32.919735
--88.114422,32.923129
--87.714812,34.018978
--88.043412,34.213848
--87.443557,33.506458
--87.79181,34.43613
--85.974489,34.814202
--86.325866,32.395536
--86.419762,32.291142
--85.636213,33.922398
--86.407979,32.978644
--85.510688,32.020715
--86.889912,33.518942
--87.729872,34.473669
--87.457443,31.85831
--85.701105,34.768843
--85.936023,34.8236
--88.21296,32.004365
--87.130955,33.129346
--86.77445,33.518152
--85.246035,31.316284
--86.594536,34.604603
--86.193286,31.090363
--85.690954,32.15589
--87.300111,34.685
--85.935568,34.623212
--88.17926,34.995625
--86.647445,31.825696
--88.348484,30.462432
--85.629608,32.033192
--86.647827,32.428759
--86.145464,32.371575
--88.322626,32.10235
--88.074889,34.174303
--88.313506,32.621434
--86.37648,32.531883
--88.329313,32.618924
--85.516284,32.104084
--88.323865,31.877834
--86.286621,31.727973
--86.770451,32.897917
--85.939036,32.252688
--87.686686,34.482423
--85.466376,31.479554
--86.544525,31.896216
--85.710048,34.492851
--87.507231,31.23917
--86.274388,34.647348
--86.886749,33.473079
--86.434318,31.877845
--87.279841,33.958765
--85.834203,33.614103
--86.626457,34.658523
--86.688388,31.889335
--86.649243,32.961909
--86.981199,31.545354
--88.163855,33.386494
--88.101156,34.092811
--86.379999,32.033443
--85.942803,34.830656
--86.132424,34.564744
--86.362727,32.387026
--86.68645,34.558196
--86.814541,33.515438
--87.212133,31.005749
--86.191872,31.063864
--88.146221,33.100288
--86.787352,33.501863
--87.00461,31.082074
--85.461686,31.821867
--86.639401,31.826314
--85.034187,32.502358
--87.797897,33.449315
--86.205344,34.376511
--85.222814,33.039855
--87.525405,33.188018
--87.048361,31.058248
--86.660011,34.77689
--87.199227,33.109029
--86.428937,33.928615
--86.146791,31.650708
--88.261118,31.771388
--86.794378,34.431712
--88.10467,34.232855
--85.711774,34.152963
--86.183707,34.389467
--87.614836,33.390358
--88.243232,30.390259
--87.319679,34.818277
--87.253005,32.298656
--88.282253,30.502499
--86.800378,33.475037
--87.620508,33.210318
--86.377284,31.517323
--86.881671,34.558431
--86.890536,31.83789
--86.102326,32.386954
--85.527153,32.021169
--87.003062,34.095178
--87.566433,31.110794
--86.488357,34.485213
--86.299918,34.327448
--87.345219,31.595538
--87.459298,34.750774
--87.483991,34.070007
--87.160372,34.648321
--86.049923,32.206587
--87.868703,30.861299
--86.065113,33.088185
--85.562708,31.467074
--87.676164,33.676938
--86.797244,33.488753
--86.321842,32.101425
--85.436833,31.857312
--87.013487,34.677543
--87.112712,33.11187
--86.203142,31.776836
--87.519321,32.437565
--86.633867,34.845881
--86.255709,31.283151
--87.500866,33.429637
--86.254124,33.83584
--86.834697,33.503181
--87.371198,34.967374
--85.44093,32.303396
--86.197071,32.56188
--86.148407,31.832527
--88.068631,33.476574
--85.909508,34.737575
--86.947024,33.026347
--86.881823,32.835079
--88.023337,31.144263
--87.561085,33.427975
--88.098183,34.141711
--86.917322,33.566531
--87.920071,30.484761
--85.970415,34.685104
--85.791271,31.985006
--86.301687,31.618316
--87.681779,33.867236
--86.534214,34.544891
--86.787968,34.619247
--87.785093,33.606538
--85.556931,31.94087
--87.139899,34.374722
--88.166677,32.225372
--88.389383,30.827061
--86.983317,34.797945
--85.074652,31.500555
--85.42945,32.189345
--88.247848,30.403851
--87.319457,31.529229
--88.172635,33.730143
--87.477965,31.159536
--86.300881,31.617315
--86.346105,32.353019
--85.740404,34.844703
--85.503958,34.172166
--86.667299,33.123311
--87.590884,30.415548
--86.815682,32.954807
--85.997838,33.983862
--88.091051,33.52318
--85.232891,33.000123
--86.240798,34.664801
--85.588157,31.687892
--86.707105,31.658543
--85.57066,31.44019
--87.712297,33.911826
--88.156646,32.773975
--86.607385,34.611003
--86.364485,31.005426
--87.397808,31.143134
--85.617617,33.932556
--86.535877,32.518104
--88.316184,31.871201
--86.63571,31.61157
--85.176247,32.097462
--87.693911,33.284045
--85.806142,34.858782
--88.349168,31.779559
--85.668906,34.536896
--87.408402,34.834459
--85.175082,32.122985
--85.574504,32.733452
--86.789025,33.467194
--87.170701,34.352364
--88.139683,31.185816
--86.672353,32.192624
--87.529929,33.761652
--86.221667,32.889232
--87.878519,33.39786
--86.950359,33.429236
--85.388882,31.03061
--86.95504,33.600631
--85.655682,31.459779
--85.434952,31.545406
--87.696163,34.553073
--86.778942,33.5579
--88.411525,32.261086
--86.612287,32.853771
--86.905826,34.560875
--86.65189,32.432691
--87.491755,34.571356
--88.034696,32.009862
--87.347568,34.869577
--86.817168,33.978687
--87.482087,31.252712
--88.381385,31.373546
--86.557312,34.85131
--87.738746,30.83359
--85.673991,32.147506
--86.850312,33.525442
--88.059214,32.996226
--85.874442,34.633921
--86.879448,34.548445
--87.149421,33.889216
--88.201686,32.553618
--86.628763,34.421259
--86.126376,34.175451
--87.319394,32.636521
--86.85329,33.480548
--86.903522,31.66048
--86.729591,31.607093
--86.475368,32.462961
--86.068689,32.980932
--85.833889,34.206658
--86.68158,34.874392
--86.384607,34.112688
--87.105936,31.181899
--85.782526,32.434759
--86.480846,32.139688
--86.272341,30.996088
--86.220949,32.381758
--88.071544,33.437223
--86.983397,33.814911
--86.958333,34.560629
--86.763711,34.878423
--85.614678,32.464321
--88.120847,32.988647
--85.438477,31.031281
--87.31796,32.633918
--86.676174,32.898528
--85.985924,34.280581
--85.309958,31.685077
--86.188387,34.404044
--85.953885,32.944116
--85.761147,34.279319
--88.282985,32.938583
--86.825489,33.394477
--85.944429,32.951363
--86.861756,33.511826
--88.112208,33.470205
--86.378411,31.903687
--88.06862,34.767313
--85.107599,31.561639
--87.451755,30.391596
--87.848739,32.512358
--85.648025,34.527956
--85.632376,34.242614
--87.307746,34.671001
--85.81658,32.032552
--87.618427,34.22531
--86.880548,34.062644
--88.113989,32.087829
--86.092136,32.369143
--87.482976,34.870749
--86.917801,33.638304
--85.45016,31.52279
--85.966703,34.75582
--85.460382,34.189987
--88.299574,30.997603
--86.970603,34.817625
--85.824099,33.654433
--87.581716,33.996707
--87.43869,32.753921
--85.89229,34.850343
--85.490576,32.289479
--85.849502,34.678599
--87.862506,33.611384
--88.039509,32.817557
--86.319722,34.586862
--87.37969,34.835592
--88.220915,31.082831
--87.632721,30.969367
--85.935078,32.114075
--88.261715,32.697733
--87.2635,33.737548
--86.741183,32.898829
--85.356077,31.302942
--87.490306,32.863064
--86.807034,33.532395
--87.244896,34.548603
--86.822586,33.494282
--86.833954,31.86411
--85.624968,34.418653
--85.499032,32.154218
--86.06746,31.10089
--86.532486,34.554459
--85.835541,33.651568
--87.514861,31.250172
--85.709509,31.309879
--86.936743,34.56369
--87.300613,31.049628
--86.529854,34.325318
--88.111254,33.745811
--88.133069,34.175717
--88.208054,32.544096
--87.614437,33.390813
--87.756338,31.988896
--86.047724,34.664632
--86.64868,31.868685
--86.30002,32.359425
--88.042714,30.678918
--88.153897,34.459691
--86.834233,33.359969
--85.979113,34.792979
--85.898768,32.547058
--87.75281,31.910549
--87.971522,34.765239
--87.763467,30.889294
--88.347036,32.454904
--85.869145,31.225769
--85.340637,32.486621
--87.72579,34.55292
--85.879335,32.213994
--85.974658,32.538051
--86.991647,31.53612
--87.897467,30.599491
--87.294462,34.423443
--85.630421,32.040908
--85.427312,31.743302
--86.969154,33.488779
--87.833652,34.463832
--85.17749,32.34684
--86.672049,31.668247
--87.789174,31.525644
--86.908514,31.918873
--86.261113,34.397054
--86.288872,33.584486
--88.02979,31.41991
--85.657555,31.801034
--85.627531,34.435762
--87.522528,33.151823
--87.903462,34.152107
--88.052682,30.647277
--85.721449,31.411736
--86.696429,34.667515
--85.887098,34.985657
--87.320911,34.064518
--86.49897,33.906245
--88.104773,30.251308
--86.111992,32.367621
--88.053804,33.078333
--88.044887,32.842039
--87.334384,31.993857
--87.263578,32.68827
--86.542486,34.569419
--85.309608,31.623694
--88.198962,31.030944
--87.692386,34.78311
--86.290543,32.346792
--85.439724,33.568947
--87.616664,31.941391
--86.331759,34.655846
--85.786762,34.439105
--86.850132,33.526701
--87.210201,33.222163
--86.109218,33.428272
--86.484714,34.153623
--85.370287,32.355806
--86.630918,31.809708
--86.674267,34.742538
--86.928384,33.414737
--87.194193,34.686305
--88.044112,34.30918
--86.54749,32.227151
--88.211531,30.450788
--87.690496,30.271012
--86.607629,33.172452
--87.374661,34.792792
--86.907541,34.236248
--87.053307,33.746377
--86.722939,34.628918
--87.276835,33.830108
--85.655665,34.853119
--85.167182,32.118228
--87.293846,31.99471
--88.240832,33.462881
--85.47535,31.815721
--87.328419,31.59527
--88.178117,33.040591
--87.787333,34.200304
--87.724096,33.499271
--86.753306,34.641802
--86.851266,31.15254
--85.750418,31.012339
--86.775496,33.500939
--88.1509,32.813919
--87.228447,34.730172
--86.590756,34.684901
--85.689312,32.146344
--87.814131,34.185477
--85.782039,32.484676
--86.309681,31.742162
--85.707824,31.103204
--87.552238,33.226674
--86.666433,31.293972
--87.830066,31.0983
--87.387485,33.475009
--87.135503,31.067424
--85.196136,32.460366
--87.950479,34.279852
--86.574672,34.729324
--87.065786,33.246856
--86.568515,34.87092
--88.133374,32.367435
--85.920219,34.560865
--86.20273,34.290659
--85.531407,31.815053
--86.490412,31.88357
--86.848009,34.162436
--86.393312,32.580293
--87.648291,33.698129
--86.497729,31.309238
--87.294648,34.041565
--85.401199,31.810372
--85.930961,31.420652
--87.754949,30.549657
--88.006444,33.955343
--86.375588,34.428522
--86.865249,34.608295
--87.284995,33.308414
--85.993778,34.58199
--86.133321,31.280953
--85.214548,33.017057
--88.17683,31.119976
--88.099957,33.266092
--87.781743,34.011029
--86.964123,32.329064
--85.727905,32.526864
--86.997548,32.946908
--85.549645,34.67979
--87.163161,34.82596
--86.949666,34.11256
--87.570766,33.474251
--85.401907,32.311594
--85.832233,33.659724
--88.280384,30.943127
--87.412038,34.151809
--88.265642,32.359191
--85.442589,31.57
--86.497064,33.219977
--85.833477,33.27667
--87.882723,31.508593
--86.780258,33.507537
--88.062103,34.445542
--88.130603,34.397744
--85.706927,31.104302
--87.302097,34.789119
--86.626983,34.180297
--88.096426,33.25976
--85.861219,34.264577
--86.75356,33.551036
--87.676885,34.806807
--85.430052,32.641462
--85.36998,31.749026
--88.143402,34.052642
--87.167724,34.679185
--85.630247,34.5084
--87.763905,33.082853
--85.691258,32.292578
--88.180913,31.458483
--86.298119,32.877358
--85.765292,32.421076
--87.185088,32.922958
--85.625355,34.936679
--85.577191,33.912254
--86.902841,32.738272
--86.375937,32.98471
--87.669502,34.153452
--86.03868,32.435705
--85.883833,34.531104
--88.010954,33.848756
--88.011065,34.713648
--85.444232,32.299408
--87.86158,30.772392
--87.291374,32.002437
--87.894953,34.293954
--86.013071,34.015944
--85.347865,31.349189
--86.193569,34.261374
--85.983506,34.64731
--86.77898,33.550309
--87.270624,33.823475
--88.10978,33.466388
--86.3007,32.207344
--85.394869,32.895519
--88.188661,32.249567
--87.675822,34.071446
--88.437301,31.290257
--86.41585,34.732573
--86.809545,33.974077
--87.70688,31.647559
--85.562703,31.613137
--85.792675,34.586075
--85.963451,32.911901
--87.833648,31.113257
--85.492831,32.151331
--88.19469,33.023562
--86.503769,31.841217
--85.82716,33.443976
--87.40607,34.760315
--86.836872,32.894005
--88.189248,32.23978
--86.675405,34.504776
--88.14751,33.14414
--87.472191,34.728174
--87.947644,33.678628
--86.530272,31.752133
--88.38322,31.151415
--87.409828,32.13706
--88.314176,32.117023
--87.190134,34.345929
--87.338095,34.549764
--86.737643,31.632434
--88.190652,32.585248
--87.106975,33.738833
--87.785343,33.999053
--86.075165,34.466991
--86.538526,34.009212
--87.506449,33.795947
--85.870657,31.891905
--87.289913,33.706298
--86.337303,32.845266
--86.787468,33.169499
--85.658968,34.514279
--87.818583,33.914262
--86.900144,31.837652
--86.655654,31.894689
--86.468316,33.947432
--85.271073,32.847759
--86.750489,33.557104
--85.708059,34.939476
--87.360777,32.045456
--85.354603,31.401686
--87.984465,34.343163
--86.450031,31.919843
--87.618823,34.191399
--86.932128,33.762868
--85.544231,34.234601
--85.349526,32.408687
--85.481807,31.935778
--87.889687,34.975908
--85.293077,32.440058
--88.075803,33.298703
--86.271417,33.556555
--87.532259,32.250151
--88.050526,33.776364
--86.354865,31.527429
--86.878028,33.90091
--85.831772,34.651603
--85.70243,31.610767
--85.589142,32.138393
--87.588734,33.194785
--85.242028,31.569084
--87.024806,31.042596
--86.394285,33.735718
--87.089862,31.100352
--86.484035,31.309281
--86.780075,31.824484
--86.888584,32.590677
--87.835281,33.695135
--85.297662,31.527873
--87.424485,34.729931
--88.099961,32.895263
--87.204033,34.646443
--88.015596,32.95769
--88.074437,34.393287
--85.021134,32.437247
--85.423052,32.239235
--86.495611,33.222563
--86.603182,33.620322
--86.682539,33.129334
--86.312854,31.965204
--85.166813,31.839881
--88.361118,31.490917
--88.051832,30.856187
--86.413761,33.211224
--86.260859,34.431529
--85.742096,32.581453
--87.992722,32.643887
--87.223115,34.059387
--87.725186,30.421562
--85.490024,32.204959
--87.836374,31.784265
--87.626705,34.557926
--87.808714,33.748123
--87.544646,30.413665
--85.917024,31.162784
--86.747306,34.007239
--86.588974,32.966676
--86.59116,34.529189
--87.789588,33.924412
--86.593318,32.044157
--88.384241,32.428933
--85.988819,32.280453
--85.733701,34.701133
--87.596012,33.663182
--87.880558,33.852652
--86.634247,34.846012
--88.298057,33.131188
--86.368529,34.356234
--85.488537,31.92045
--86.298764,34.065691
--86.661971,34.744143
--86.56869,31.831827
--85.263518,33.266565
--87.08084,34.414684
--87.204299,34.543232
--86.808749,33.530715
--87.467742,31.155029
--88.197055,32.776437
--88.312981,31.217106
--88.18926,33.439346
--87.510999,32.394432
--85.990762,32.552677
--87.846683,30.418745
--86.760524,32.271335
--85.622023,32.455106
--85.843939,34.708273
--85.916553,32.290975
--85.314747,32.418196
--87.698493,30.35571
--87.540456,32.299059
--88.065373,33.307161
--86.333102,34.661394
--85.705704,34.534087
--86.305473,32.373883
--85.853658,34.134905
--87.303489,34.435439
--86.255446,31.291531
--86.430287,34.701154
--87.35695,34.108283
--86.829211,34.172528
--87.539446,34.857139
--87.066926,33.736625
--87.834207,33.949908
--86.693873,34.467665
--87.698587,34.763944
--86.41631,31.520149
--86.155936,34.249642
--86.705276,34.751516
--86.498218,34.681795
--85.646901,32.015579
--88.051791,32.364536
--86.788604,33.906749
--86.74275,33.882391
--88.403815,31.406524
--86.252131,31.623191
--87.536462,34.8605
--86.934283,34.489712
--85.604791,34.626682
--86.758444,31.676829
--86.915734,33.484088
--85.680421,34.340484
--86.813136,33.650905
--85.43261,31.455973
--87.158417,32.922049
--87.0355,34.141643
--87.318485,31.517185
--85.799915,31.323498
--85.471355,31.762065
--87.819781,34.168894
--87.593227,33.397655
--86.625181,32.140591
--88.273707,30.50181
--87.455485,30.50986
--85.746325,31.535477
--85.998818,34.635904
--85.340531,32.079304
--87.976017,33.479141
--88.412472,32.264207
--87.500092,34.877802
--87.73162,31.668013
--86.497372,32.596905
--86.955019,33.396549
--87.738528,30.305548
--86.39273,31.552255
--87.545107,33.196765
--86.743019,32.983896
--88.273771,30.915169
--86.527539,33.054688
--85.61132,32.138114
--88.228951,33.237409
--87.683408,30.472684
--88.039704,31.081374
--88.142274,30.970001
--86.365292,32.148713
--87.050729,32.487544
--88.234518,31.461783
--88.198716,33.998449
--85.783431,32.12981
--86.822766,33.48428
--87.019992,31.085361
--87.798217,34.088355
--85.451705,32.477913
--86.787529,33.092311
--85.490172,32.543463
--88.36171,32.189977
--85.612602,33.922851
--87.447899,33.598372
--87.151631,33.68446
--87.427612,32.569533
--85.62372,32.277214
--87.361972,32.382393
--86.80687,33.603407
--86.365175,31.430992
--85.053699,32.165396
--85.256211,31.563918
--87.609986,34.08054
--87.707128,34.108528
--86.874562,33.531736
--86.627706,33.183735
--87.391668,31.506912
--85.62919,34.778532
--87.617913,34.738701
--86.949688,31.433932
--87.31949,34.253691
--85.451292,31.123061
--86.560875,32.966945
--87.35836,34.599465
--88.1167,32.894514
--85.159774,31.879324
--85.7234,32.147514
--85.772773,32.808031
--87.090347,34.465767
--86.881874,33.50134
--86.278564,32.102784
--87.285897,32.009206
--87.19351,34.606175
--85.885018,34.931872
--87.23268,31.808908
--86.857904,33.465638
--88.171414,33.248616
--86.397574,31.892978
--85.884786,34.647959
--87.476978,32.849344
--87.091149,32.939596
--85.599197,32.083694
--87.145639,34.747718
--86.165333,34.195505
--85.375511,31.916891
--86.071102,34.781833
--85.827866,34.883561
--86.789198,33.507947
--85.285791,32.291232
--86.461455,31.13923
--87.403039,33.186614
--88.051115,33.585529
--88.108762,30.256334
--86.811198,33.519551
--87.083774,32.413762
--85.994145,34.955075
--87.900267,31.610178
--86.119273,34.786482
--86.772095,34.749578
--85.832466,34.545158
--85.271476,32.40298
--86.919791,32.944544
--87.464709,33.951807
--88.131958,33.421255
--86.871336,33.500903
--86.24528,34.846269
--85.751488,33.304805
--85.894304,32.495998
--87.740727,34.162522
--86.945848,33.431365
--88.007625,33.434834
--85.359482,32.351326
--86.783712,34.873863
--86.097204,32.455937
--87.552798,33.739463
--87.097441,34.377546
--85.483814,33.309401
--85.85324,32.697816
--87.511563,31.501083
--86.315798,31.900553
--86.951787,34.5609
--85.570274,34.697753
--86.546548,31.898893
--87.903892,30.52566
--88.267627,32.70608
--88.146222,31.120636
--85.749087,31.691639
--87.382988,34.453933
--87.773231,33.954432
--87.812687,33.730847
--85.787231,33.480376
--86.961613,33.422722
--87.987973,34.297551
--87.617623,31.109832
--87.539518,33.25627
--85.418977,32.243554
--86.496983,34.203006
--86.709605,32.180808
--85.495616,31.598776
--85.671567,32.396547
--87.320049,34.11145
--85.755067,34.624488
--85.618865,34.705803
--85.717624,32.144526
--87.508441,34.915797
--85.931085,34.814281
--86.180022,34.689402
--85.241529,31.718873
--88.161342,34.147708
--86.739326,34.826185
--86.680488,31.725646
--86.901174,33.49355
--86.753412,32.912369
--87.91286,30.523176
--85.37477,31.416971
--86.637089,33.902858
--86.267215,32.366912
--88.128006,33.896936
--87.162806,34.678285
--86.801207,33.459679
--88.240487,33.378478
--86.626398,32.785476
--87.54778,32.470732
--87.406725,31.595279
--85.276847,31.265403
--86.582112,34.7789
--88.109635,30.737394
--86.686667,31.521315
--85.769466,34.700487
--85.415364,30.999232
--86.799851,33.498444
--85.909699,31.172029
--87.725906,34.106388
--86.110352,34.393238
--86.003808,32.461747
--87.765091,33.190272
--88.011972,33.04545
--85.326625,31.561301
--87.612041,33.922837
--88.206616,31.727695
--87.998386,34.125101
--88.10636,34.21154
--86.291881,34.736612
--85.140682,31.684104
--85.763085,34.511477
--87.18222,34.652033
--86.994853,33.442707
--88.103271,33.870276
--87.480402,30.444073
--85.281459,33.168636
--87.28285,34.474091
--87.202694,33.034675
--87.211772,34.013074
--87.733795,33.477331
--86.809985,34.589604
--86.275531,34.421558
--87.867142,32.815652
--87.329207,31.48469
--88.224297,32.597595
--87.24689,31.571855
--87.122213,34.847899
--86.38201,33.355474
--85.74473,34.719181
--85.707121,31.507157
--86.988934,34.25428
--86.713247,32.885793
--85.705312,31.60437
--86.662604,34.87372
--85.437804,31.909414
--87.302108,33.451833
--87.454286,30.394053
--85.940829,34.526692
--85.614796,32.100049
--85.332827,31.693656
--86.229896,32.543666
--85.796903,34.682564
--88.264246,32.443922
--86.889504,33.530144
--87.412264,34.741071
--85.20243,32.455205
--85.605121,31.334805
--85.177452,31.651088
--86.07498,34.465043
--86.891259,32.392716
--87.000012,33.006801
--86.82174,32.92909
--87.32687,33.452528
--87.444186,34.865479
--87.763398,31.934073
--86.873852,32.771619
--88.12588,33.741462
--87.454112,34.969014
--85.997966,34.024309
--86.626363,32.835577
--85.721282,31.862282
--86.455497,34.74975
--86.065628,34.468794
--88.067995,34.192851
--86.91861,33.453947
--85.334524,32.351242
--86.915384,34.000101
--86.293978,32.908247
--88.173485,30.618294
--85.874973,34.550284
--85.615741,34.352754
--86.231068,34.341826
--85.617613,34.711104
--86.624891,32.840812
--86.396287,31.963099
--88.395374,30.845192
--87.356521,32.001506
--87.478702,34.662879
--86.780783,34.793929
--87.568493,33.197522
--86.241143,32.461644
--88.08544,32.872242
--85.977159,32.203853
--85.582472,34.26868
--88.000494,33.679208
--87.34844,33.257323
--85.940239,34.720786
--87.426242,31.346145
--85.285822,33.16406
--86.992051,33.754098
--87.272373,34.829728
--86.69494,34.670667
--87.123794,34.423752
--86.24789,34.341827
--87.859141,30.987162
--87.913829,30.59794
--85.833389,32.042766
--88.019116,33.043264
--87.123129,34.838793
--86.117624,32.432969
--87.654799,33.440852
--86.757907,33.527513
--86.642022,33.596536
--85.072861,32.385857
--85.726425,31.587395
--85.391565,31.225212
--85.724337,32.13383
--86.322418,31.451453
--86.309495,32.172091
--86.224598,32.401024
--87.306282,32.083975
--87.742495,30.406595
--87.366979,32.090514
--85.964942,34.63666
--85.428726,31.608885
--87.46402,34.748009
--85.611988,32.019146
--85.085204,32.156982
--87.724881,34.522703
--88.134429,33.014192
--85.494192,33.309975
--88.019624,33.391015
--86.694709,34.485171
--88.047443,32.224497
--87.911156,33.964633
--87.559825,34.952425
--86.893151,32.907257
--86.842383,32.884999
--87.364114,31.594289
--86.794169,34.066751
--87.922828,33.635998
--85.370296,31.212344
--88.338655,31.036403
--86.972667,34.578036
--88.202665,32.027133
--86.536813,31.551626
--86.60434,32.911323
--87.660228,32.497988
--86.345218,34.413694
--88.266338,33.001779
--86.594765,34.53607
--87.716854,30.549536
--86.706665,34.411115
--88.114135,32.890369
--85.910485,32.907316
--86.643114,34.207224
--87.935107,33.406593
--86.94823,34.644923
--85.681575,32.338783
--87.4728,34.617318
--85.501739,32.606099
--87.317195,32.628898
--86.453547,32.903108
--88.158846,32.774503
--87.764361,31.739863
--87.539598,34.084111
--87.97199,31.561124
--86.736206,34.795129
--87.519009,34.341462
--85.856678,34.579384
--87.217513,33.196764
--86.610812,32.975754
--85.808291,31.906192
--87.931833,33.132958
--85.155498,32.188482
--88.312432,32.499214
--87.033203,33.96895
--86.714509,34.844504
--85.602311,33.924722
--85.796028,32.107298
--86.307485,32.998435
--86.146377,34.472478
--85.975499,34.938171
--86.350228,32.211748
--86.74518,34.699538
--85.717551,34.827957
--87.4722,30.440064
--86.71135,33.985283
--85.620985,34.708651
--86.783125,33.484098
--88.05732,34.37479
--87.61434,30.410758
--87.596107,30.409688
--88.424073,31.403316
--86.32925,33.792038
--86.819505,32.949624
--86.737206,32.212535
--87.65596,32.898535
--87.861634,33.947572
--86.387494,33.7427
--88.013425,32.906866
--87.511636,34.95412
--85.816164,34.267434
--87.70966,32.166078
--87.584106,31.670702
--88.260656,32.523218
--85.576148,33.126436
--86.909974,33.473891
--86.59993,32.808956
--85.598946,31.566925
--85.830448,34.926374
--85.633827,34.848849
--86.699967,33.123147
--85.861221,34.889971
--87.994814,31.094559
--85.254364,31.480762
--86.966709,34.802329
--87.994666,31.716631
--86.955085,31.733679
--87.879766,33.424287
--85.545459,31.511078
--87.899113,33.395213
--88.245717,32.091728
--88.023014,33.568596
--86.966709,34.802329
--88.119632,33.107667
--86.082178,34.440108
--87.209234,33.160397
--85.091572,31.607484
--85.291226,31.596796
--88.155956,33.260283
--86.322499,32.572931
--87.161159,33.173954
--85.816497,34.896106
--86.732614,32.168354
--88.166132,33.146287
--87.261295,33.40103
--88.097133,30.706736
--88.1921,33.484094
--87.296365,34.489265
--87.026367,33.217019
--88.118707,33.775126
--88.086961,33.55225
--85.233417,33.009155
--87.275928,33.834788
--87.946865,33.678762
--88.027149,34.410162
--88.038085,32.987876
--85.63588,34.779737
--87.716671,30.421606
--88.263786,33.234288
--87.920086,30.626768
--87.856193,31.039368
--86.496891,32.176924
--85.000498,32.222077
--86.812014,33.772168
--86.551135,34.85127
--88.261092,30.640373
--87.493119,34.086413
--87.852497,33.685298
--86.954231,34.752831
--87.453048,31.131556
--88.174633,33.779485
--86.993681,33.965729
--86.621981,31.795171
--86.383737,33.735755
--86.445462,33.10877
--85.917063,34.753597
--86.732158,34.837912
--86.959784,34.803695
--87.267822,34.849771
--87.553364,33.2012
--85.911152,32.970607
--87.511306,33.747641
--87.534842,32.109336
--86.732934,34.837981
--86.054979,32.065852
--88.392763,31.475888
--86.654786,32.865671
--87.866144,31.567547
--87.874924,32.21436
--85.641053,32.161808
--87.888491,31.634309
--86.593997,31.79537
--87.505856,31.250172
--88.419249,31.356205
--85.39353,31.224269
--86.246978,34.744839
--87.705043,33.37534
--87.807093,34.452365
--87.114583,34.938699
--85.739053,34.794107
--88.058282,33.55639
--88.140758,33.111403
--86.967568,34.804332
--86.275062,31.721478
--85.729661,34.468813
--86.849107,33.47048
--85.618619,34.570647
--87.33988,33.88285
--87.94699,33.679959
--86.973144,33.48894
--86.617505,31.827817
--87.415503,31.022435
--88.245359,33.072476
--87.667235,34.00448
--86.899999,33.500379
--87.199195,34.702786
--86.2683,34.284437
--86.078683,31.431803
--87.580415,31.995016
--86.415086,34.680755
--85.653144,31.449307
--86.823944,31.722073
--87.398824,34.781694
--85.96308,34.901
--85.871951,34.26009
--87.43805,34.376254
--85.18069,31.612273
--85.276895,31.702515
--85.931037,31.384767
--88.145404,32.690717
--88.012692,34.087547
--85.798621,33.281564
--86.749019,32.943992
--87.158988,34.956344
--87.671557,32.910153
--85.714346,32.423451
--85.070743,32.023412
--85.615804,34.216386
--86.925651,33.538283
--88.215011,33.061616
--85.457904,32.0345
--87.595975,33.203131
--88.018155,32.628382
--88.250684,32.935654
--86.820736,32.808186
--87.898557,33.296022
--87.63342,34.520968
--87.120157,34.467565
--85.875321,31.05418
--87.289513,34.024078
--85.667514,34.129747
--85.512341,32.020961
--87.253777,31.794442
--86.770111,34.751418
--86.32714,34.171948
--85.748887,32.959252
--87.551197,33.990389
--87.559381,30.279972
--86.531234,34.074616
--88.125763,31.82393
--87.42738,34.658649
--88.058666,33.30953
--87.784031,30.848013
--86.369496,34.389354
--88.121743,33.264021
--86.428428,34.063243
--87.819106,31.199016
--86.757097,33.543754
--87.926876,34.327758
--87.773118,34.432277
--87.832333,34.243018
--88.215692,33.411212
--86.975733,33.322474
--85.411761,31.406519
--87.436542,32.027222
--85.935625,32.305262
--86.758812,32.989603
--85.97342,34.657336
--87.618715,31.071895
--85.710938,34.491617
--87.840139,34.123188
--87.487916,34.189664
--86.053856,33.064916
--87.11368,34.942542
--86.869959,33.496018
--87.906508,32.89649
--85.7852,33.742906
--87.402484,34.146332
--85.976322,32.680847
--85.892779,34.808835
--85.339707,31.503057
--85.980249,32.737513
--88.08308,32.286685
--85.900378,31.568626
--87.004175,32.587018
--86.098468,32.866087
--86.646674,31.826507
--85.811533,32.883643
--86.728344,32.982778
--85.821995,33.705002
--85.745666,34.869168
--87.174141,31.743014
--87.498481,31.29968
--85.38295,32.072074
--87.513191,31.545607
--86.766444,34.87113
--87.904396,34.315359
--85.873104,30.997468
--87.697682,34.339832
--87.738972,32.880598
--86.758319,34.860712
--87.728971,34.507429
--86.257905,34.104786
--87.376281,34.590378
--88.067841,30.685135
--86.994604,34.429335
--85.695642,34.635667
--85.855837,32.377693
--85.701094,31.098232
--87.681318,34.733354
--85.499844,31.224624
--85.4003,31.385334
--86.368568,32.239371
--85.907511,31.295065
--87.161068,33.112834
--87.97121,31.809685
--87.140809,34.488183
--88.056445,33.564033
--88.112116,34.693586
--86.143053,33.418852
--86.656133,34.039325
--88.042489,32.92381
--86.707075,33.537947
--87.916761,33.35053
--87.787287,32.047044
--86.536166,34.806962
--87.375025,34.914436
--85.208881,31.309052
--87.875871,33.812497
--87.630083,34.875479
--88.16841,33.236127
--87.095308,34.134573
--87.836044,33.797618
--87.723386,31.046096
--85.590291,31.755472
--86.115989,33.429601
--87.903359,33.427736
--85.964649,32.960431
--87.627589,34.171131
--85.602295,34.652809
--87.514005,33.177118
--87.079134,33.738387
--86.029285,32.971881
--85.614067,34.633539
--86.249354,31.715819
--85.547591,31.558648
--87.901772,32.849728
--86.813881,33.494937
--86.592621,32.175619
--87.760474,30.8437
--88.379184,31.201997
--87.876752,30.614738
--87.169985,33.521645
--86.774935,33.488971
--86.832982,33.972828
--88.128998,33.218477
--85.695694,32.433374
--87.831351,34.396278
--86.39618,34.808485
--86.239399,31.295322
--88.203072,31.160573
--85.909505,31.596036
--86.069497,34.737614
--86.445035,33.500261
--88.117644,32.861134
--85.940424,31.531111
--87.259846,34.10621
--87.186331,34.312517
--85.963707,34.87981
--86.990886,31.764603
--86.267575,33.981931
--87.61759,31.110324
--86.185888,34.42218
--85.90158,34.591553
--87.341619,32.084156
--87.685184,34.792638
--88.088534,32.754924
--87.334917,34.969692
--87.991301,30.937949
--86.802272,34.834264
--86.784553,33.50415
--86.990714,31.880454
--86.437306,34.474056
--86.440654,31.876199
--85.494999,31.886402
--86.34617,33.267923
--88.189257,34.058572
--86.52012,32.297629
--85.094235,31.581075
--85.381085,31.222986
--88.040712,30.680405
--85.239407,31.59628
--85.613709,31.904305
--88.025029,33.355528
--86.184285,31.66971
--85.260074,33.156767
--87.571126,31.737688
--86.996075,31.771855
--86.542024,34.737393
--87.314673,34.471504
--88.096985,33.412754
--86.123266,34.736949
--86.732955,32.163946
--86.357785,34.611774
--87.510364,31.795532
--87.092926,33.74097
--88.077563,34.14227
--86.251415,31.57955
--86.104587,33.432344
--87.775285,30.884394
--87.327637,33.363341
--85.99053,32.618028
--88.266303,32.135454
--88.162374,33.724117
--85.966975,32.357607
--87.524422,32.574944
--85.982053,32.965562
--85.739933,31.147947
--87.508355,34.262654
--87.338643,33.721578
--87.72039,33.687437
--85.871968,31.046949
--87.790036,33.426254
--85.03769,32.371009
--85.694894,34.289464
--85.661423,34.511426
--85.924945,34.833842
--86.274271,32.942189
--86.759813,33.974708
--85.231624,32.648031
--87.128238,31.006906
--88.287195,33.397021
--85.314549,31.672038
--86.950018,31.838712
--88.149143,33.944221
--85.782147,34.598363
--85.797405,31.176316
--85.637836,34.50044
--87.279452,34.042333
--87.087605,33.747735
--87.708578,34.912957
--85.986753,34.577871
--87.639159,30.85162
--85.485941,31.884457
--87.916721,34.59612
--86.770539,33.507469
--87.544211,32.055926
--85.176202,32.805529
--85.756713,34.650837
--85.772844,32.022534
--87.769084,31.721413
--148.660235,70.190098
--159.998693,70.636758
--139.722434,59.577651
--138.098147,59.442952
--136.44849,57.778325
--143.603317,70.126574
--156.31949,71.355134
--149.920788,61.52137
--149.217207,70.49049
--149.716718,70.567648
--151.749027,68.133665
--139.749016,59.561058
--136.575604,57.845573
--139.727265,59.564716
--139.661325,59.591018
--139.511396,59.699204
--139.541059,59.964257
--139.541059,59.964257
--150.061363,61.745328
--150.063731,62.316787
--161.385924,64.419958
--171.738229,63.761606
--161.150717,64.322658
--149.931358,61.527071
--160.274118,66.921675
--135.719572,57.638861
--164.565505,67.730911
--165.281217,60.573341
--164.282366,60.148427
--161.579366,59.117604
--155.445841,61.355912
--173.120769,60.314863
--161.509145,60.801473
--162.525725,60.874539
--157.401384,57.511976
--135.878028,56.988791
--161.848863,60.77376
--161.235536,60.904134
--135.353914,58.91863
--135.278875,59.06928
--161.921623,59.745464
--146.219783,61.135292
--146.230867,60.863
--146.009775,60.670884
--146.411987,60.691126
--142.893332,61.433857
--145.807813,60.612469
--146.266305,61.124792
--144.903825,60.391688
--142.316719,60.99109
--143.711369,61.745711
--148.316306,60.011191
--145.461306,60.259227
--145.732963,60.620304
--146.310328,61.124117
--144.967065,60.464528
--148.00063,60.079089
--145.440293,60.336886
--158.621956,59.280496
--145.835166,64.130734
--141.21111,64.789265
--132.377869,56.469051
--145.179072,60.408245
--144.932024,60.453357
--145.141925,60.295279
--144.932041,60.462567
--145.167513,60.344033
--144.978418,60.489584
--144.861693,60.469681
--144.88267,60.467618
--146.308801,61.122906
--145.732853,60.543497
--147.629695,60.523264
--148.656253,60.681756
--148.656376,60.496969
--147.461537,60.863599
--148.019787,60.655386
--147.814898,60.756514
--141.829951,61.908296
--143.346724,63.365035
--149.106835,61.585027
--147.751597,60.813403
--147.904638,60.329899
--147.904638,60.329899
--147.425918,59.86395
--147.923161,60.276652
--147.663126,60.481915
--147.668299,60.485744
--146.845925,60.843862
--159.781913,62.183732
--147.95146,64.790817
--147.063802,64.55248
--146.866122,64.330762
--147.0655,64.542528
--147.865253,64.848013
--161.210755,55.999777
--163.182389,55.39495
--154.853351,59.937275
--166.143063,61.52604
--160.117041,55.304757
--156.461529,59.069765
--159.53254,55.851473
--160.938886,55.895788
--162.033078,55.090381
--162.278442,55.115007
--149.823798,61.186935
--135.031038,58.470484
--157.29001,58.195132
--149.98798,61.19496
--149.796916,60.922526
--149.753528,61.233777
--149.968653,61.183685
--158.859365,55.925674
--154.521121,59.536165
--154.829676,59.752646
--134.66976,58.33017
--147.437094,66.361876
--150.148081,65.522371
--158.933051,63.957436
--133.816358,57.840956
--133.813283,57.837624
--145.582444,68.111362
--152.065321,65.142264
--158.456867,64.449273
--144.069199,65.825073
--158.721421,64.132465
--145.268965,66.565197
--150.031063,65.764757
--149.070192,65.982641
--134.469079,58.32904
--152.47462,65.114453
--149.431788,60.131778
--151.527383,59.104737
--153.291227,65.101268
--151.531872,59.503315
--153.658613,59.851414
--155.284045,64.813063
--151.4775,59.538651
--130.635743,54.80157
--131.744008,55.402335
--134.422144,56.001151
--160.204577,62.523075
--152.094063,65.151193
--150.908493,59.853135
--164.345193,62.716958
--164.642179,62.674113
--162.075746,61.562955
--133.074906,56.51059
--165.585551,61.535531
--150.435334,59.342298
--149.668398,59.641263
--156.758387,56.191045
--132.956945,56.820983
--153.369565,57.717288
--153.443492,59.630863
--163.289018,62.054442
--134.666709,56.169603
--161.332247,61.750852
--164.863193,62.699743
--153.443634,59.629033
--151.193031,59.74067
--131.700529,55.564737
--131.342241,54.971899
--152.37655,57.799995
--152.400133,57.789133
--152.31563,57.363682
--153.952745,56.72193
--152.853069,57.884689
--161.97844,61.577189
--152.521737,57.884035
--152.736842,58.35382
--131.682836,55.577978
--156.282505,57.373502
--156.807369,56.146929
--151.895833,58.164545
--154.364686,56.492922
--152.701721,58.453894
--170.284439,57.125262
--170.234919,57.158283
--154.132643,56.683189
--176.146521,51.789018
--176.211855,51.967748
--178.304251,51.473619
--176.16802,51.94532
--175.49608,51.97428
--131.690389,55.880197
--131.57789,55.308699
--132.266329,55.744626
--132.267164,55.736223
--170.012487,52.774042
--168.404791,52.99305
-173.14598,52.822511
--178.860011,51.541806
--166.886438,53.428655
--131.831113,55.480285
--131.691068,55.570894
--130.689517,55.96044
--156.645002,56.044584
--131.478815,55.498964
--131.326937,55.241389
--130.983752,55.315896
--136.028195,58.506235
--132.973959,56.494416
--131.842036,55.845785
--131.24469,55.044215
--131.342241,54.971899
--133.892643,56.927796
--133.919766,56.709484
--132.875814,56.128107
--132.971177,55.355591
--133.763267,56.786797
--133.7393,56.721923
--132.931195,57.031716
--132.371944,56.355565
--134.088396,56.926167
--134.072225,56.896652
--132.961727,56.798955
--133.047042,55.247814
--132.446547,54.78445
--132.950037,55.211034
--132.98404,56.190545
--132.93115,56.157194
--151.823619,64.67858
--160.067706,62.891356
--132.568672,56.662452
--132.192982,55.919295
--132.384713,55.908749
--132.633121,56.033856
--132.723362,55.912557
--133.361661,55.77253
--149.948261,61.518236
--132.346859,55.888312
--132.036287,55.035655
--132.336037,55.850749
--135.704004,56.983364
--132.336037,55.850749
--135.417501,57.082355
--135.407033,57.075546
--131.93469,56.213612
--133.676419,56.160589
--133.415093,55.137589
--132.410745,54.68963
--133.62731,56.351517
--133.816726,55.893339
--132.937918,55.042694
--133.438107,55.894567
--133.068228,56.194837
--133.496365,55.903439
--133.505713,55.930649
--133.385622,55.799273
--133.401419,55.877931
--133.082192,56.046825
--132.025243,55.030565
--133.401419,55.877931
--133.418644,55.708587
--132.97169,56.01253
--133.164257,55.497633
--135.413983,57.067762
--133.437844,55.633643
--133.624143,55.642745
--133.094041,55.376155
--162.593209,66.895913
--133.663861,55.418057
--133.692369,55.236266
--133.458573,55.449799
--164.029536,59.927315
--133.191544,55.264921
--133.097922,55.57895
--133.116769,55.427559
--132.994658,55.158504
--132.869791,55.191914
--132.85274,55.232113
--132.732031,55.105578
--163.980701,59.670071
--132.914628,55.072806
--132.147939,55.436754
--133.67867,56.210068
--162.274242,60.86188
--132.213288,55.397993
--144.996194,60.488758
--132.001238,55.05209
--131.990014,54.88052
--133.621579,56.327329
--132.38195,55.486823
--135.345795,56.994175
--144.955822,60.444841
--132.475161,55.666628
--136.373918,58.238148
--136.373918,58.238148
--135.966559,58.449842
--133.396167,55.97669
--131.434249,55.214779
--133.949594,55.844469
--135.732839,58.326397
--135.463395,58.318104
--134.7362,58.263551
--133.676419,54.68963
--134.550878,57.511246
--136.407989,58.192758
--133.677343,57.174925
--134.268851,57.881177
--134.285133,57.904962
--133.927068,57.450812
--133.588098,57.496711
--135.33826,57.024386
--135.324592,57.037258
--134.167418,57.898044
--133.499969,57.298614
--136.215359,57.944573
--134.167418,57.898044
--136.221629,57.951667
--135.312874,57.036216
--135.368327,57.026394
--134.553821,57.504073
--136.36307,58.196011
--136.187973,58.624458
--133.499969,57.298614
--135.500483,58.183249
--136.359161,58.193592
--135.341425,57.044495
--135.278966,56.695408
--143.774734,70.085313
--144.953122,60.522593
--134.15328,57.888335
--134.094696,57.316478
--135.413983,57.067762
--136.37384,57.454605
--135.395211,56.839033
--156.74251,58.682612
--157.029725,58.731319
--145.160218,66.497038
--148.770917,70.431313
--159.991268,70.637328
--139.651782,59.663397
--139.711768,59.607322
--135.419181,57.076058
--135.314421,57.010798
--135.455485,58.317874
--166.152656,66.217206
--161.033427,66.828395
--149.950667,61.516193
--149.112289,61.601629
--135.692452,57.227958
--135.709181,56.98547
--156.611473,64.639335
--135.482364,57.098181
--139.736629,59.582578
--135.404891,56.77712
--156.59321,61.786443
--150.661993,60.488418
--150.81244,60.872958
--135.310139,56.730463
--159.56101,61.573646
--163.183522,59.872155
--166.282979,60.373751
--135.069975,56.733164
--135.331155,57.039032
--135.692452,57.227958
--135.740363,56.988364
--135.306061,57.004577
--135.333164,57.038546
--135.323142,57.03718
--135.306061,57.004577
--135.365926,56.996241
--135.557977,57.139966
--135.296796,57.012916
--135.312874,57.036216
--135.358967,57.023365
--157.010693,66.637227
--162.273905,60.852209
--172.7743,60.194583
--153.498123,65.110034
--149.044764,70.474325
--147.430285,70.364785
--139.736629,59.582578
--145.538474,62.109691
--139.748187,59.588355
--140.01959,59.772277
--149.443907,61.590978
--149.85315,61.395073
--167.24112,65.906933
--161.755793,63.603257
--152.866552,62.722004
--166.866771,65.247487
--157.868045,67.086591
--139.804221,59.558982
--161.383398,60.79441
--135.334517,57.052375
--156.531457,57.874322
--164.401552,65.436206
--158.13712,61.865895
--162.375636,60.110833
--144.994022,60.449819
--162.353746,60.171878
--160.356397,61.512734
--162.072207,58.642474
--162.040352,60.209955
--146.245366,61.125577
--152.076718,65.143451
--132.941072,56.099669
--132.209887,55.926721
--145.947419,60.684273
--145.268009,60.221499
--145.200459,60.304766
--159.503107,55.841721
--159.369181,55.747081
--144.895189,61.713663
--144.948961,62.02345
--144.068427,61.07026
--146.731865,60.233988
--148.065858,60.808938
--146.255657,61.130333
--145.497171,60.488993
--148.746285,60.802072
--158.374287,56.307957
--144.871056,60.463825
--144.848591,60.493379
--144.853553,60.459522
--144.836628,60.487551
--157.28283,59.730564
--148.656253,60.681756
--160.064676,55.500342
--162.437454,54.827188
--144.397136,59.987545
--157.285036,59.457371
--142.98697,62.388664
--148.161642,60.616422
--147.807861,60.764336
--147.431665,60.283589
--158.56962,59.260183
--148.24748,60.627618
--146.248086,61.120374
--142.263584,62.599663
--141.828913,61.90103
--147.874287,64.812203
--139.727265,59.564716
--161.442698,55.159286
--158.003813,64.745673
--146.465216,62.06523
--159.829493,55.060714
--159.752367,55.089087
--161.190651,56.004796
--149.885905,61.243569
--155.47241,64.724502
--146.564024,62.295358
--145.422006,62.060617
--158.606687,56.271314
--154.68201,59.742624
--156.893912,66.908088
--160.345704,61.534475
--135.279215,59.06928
--149.717579,59.657599
--157.745997,56.272158
--157.026709,56.559333
--156.865419,59.115831
--158.632682,56.953596
--150.146447,67.397362
--156.285938,64.693138
--135.278106,59.026336
--145.343592,60.242909
--149.413088,59.835066
--135.404613,57.079785
--145.732862,60.543227
--145.07247,60.440656
--158.513774,59.035807
--145.091151,60.319514
--145.029187,60.493931
--144.998095,60.50948
--159.658361,55.036739
--149.859907,61.200217
--149.438263,60.127528
--139.661262,59.595252
--146.817324,60.883063
--135.45599,59.199733
--160.275731,59.071672
--148.161642,60.616422
--147.502803,60.617492
--158.556654,58.839132
--146.498235,61.130951
--145.752245,63.968365
--159.352302,56.694723
--159.360709,56.690728
--160.863334,56.01593
--162.482703,54.367312
--159.668455,55.168793
--161.915866,54.977993
--149.854202,61.202327
--156.900967,56.845323
--158.405147,56.295848
--154.315001,60.206495
--155.912902,59.328417
--149.889403,61.213625
--150.377484,65.48642
--151.539319,66.904944
--148.038327,65.210815
--160.190015,62.646369
--150.654346,64.98892
--151.484336,59.185448
--150.448124,60.426173
--149.440516,60.099203
--165.57938,61.84312
--164.872733,62.765372
-175.870273,52.334831
-177.200423,51.825971
--139.688007,59.494816
--156.684111,56.083473
--153.309449,58.853273
--154.093964,59.17472
--152.701721,58.453894
--152.519658,57.739904
--152.707835,58.440696
--151.006754,60.488181
--152.310669,57.748516
--152.416269,57.778867
--171.313083,52.447452
--158.70004,59.277934
--178.992111,51.31093
--176.801877,51.829913
--131.045675,55.285365
--131.844347,55.520533
--131.840168,55.485198
--176.236246,51.770743
--174.052296,52.023032
--131.810787,55.482788
--131.880704,55.129242
--151.684445,64.994505
--145.963044,60.445402
--132.950037,55.211034
--131.659928,55.303921
--131.459187,55.237098
--132.95153,55.168533
--131.764712,55.534718
--131.842036,55.845785
--133.185486,56.654431
--175.87775,51.914153
--169.680946,56.580711
--133.207723,57.098985
--161.175595,55.997149
--165.688885,54.107921
--149.274898,65.949065
--160.127477,62.464093
--145.226664,66.56227
--154.429481,64.921596
-178.310672,51.962646
--160.043232,62.881835
--149.668398,59.641263
--164.601146,62.688261
--175.962258,51.963862
--131.784197,55.191529
--131.682836,55.577978
--132.20147,55.49747
--131.696758,55.853199
--130.923867,55.401377
--149.645356,59.600486
--164.51515,62.77566
--164.849602,62.516654
--150.4354,59.383893
--133.827122,56.391524
--152.641648,60.094256
--152.360409,58.904372
--151.233884,60.565165
--135.331138,57.029421
--131.521553,54.958536
--134.31197,56.624444
--135.325096,59.455322
--134.666717,56.169603
--133.946835,56.947525
--132.490413,56.497106
--133.763267,56.786797
--133.7393,56.721923
--134.246614,56.451634
--135.325932,57.035144
--134.113259,55.978229
--134.642864,56.272963
--132.522691,56.716187
--132.270211,55.737672
--132.271498,55.739807
--133.006016,56.247818
--132.723362,55.912557
--132.556466,56.650773
--160.958583,61.119023
--132.566998,56.524175
--133.102984,55.431017
--132.633121,56.033856
--132.946184,55.268096
--132.172401,56.359075
--132.179254,55.91925
--132.049546,55.98241
--133.634269,56.349249
--133.622967,56.353652
--133.342391,55.910789
--133.728517,55.581006
--133.610888,55.649376
--149.737918,61.284373
--133.616435,55.588593
--133.472256,55.200825
--133.144169,55.487181
--133.157668,55.26871
--133.157668,55.26871
--156.085747,62.99302
--133.116769,55.427559
--132.851488,55.20028
--132.851488,55.20028
--162.197024,60.593708
--133.161579,55.388401
--132.679223,55.458468
--132.95153,55.168533
--162.036909,60.219501
--132.90273,55.086221
--132.817973,54.750975
--132.17428,55.395559
--133.526867,56.15196
--133.32366,56.317705
--136.366116,58.229769
--135.995893,58.649069
--136.44849,57.775253
--134.336948,58.126573
--136.157478,58.257256
--146.009775,60.670884
--132.213288,55.397993
--146.332272,60.450335
--144.40875,61.403508
--144.667992,62.286101
--143.865971,62.302143
--144.695228,62.553968
--136.066684,58.205127
--134.110589,58.098033
--134.024077,57.280392
--136.492006,57.867793
--136.285598,57.689416
--133.546578,57.211212
--136.412555,58.155459
--135.391329,57.038361
--146.731865,60.233988
--146.255657,61.130333
--135.347435,57.022793
--135.312214,57.035202
--135.27929,57.032747
--135.493894,56.824298
--139.722253,59.598015
--139.710244,59.603514
--142.939214,61.423801
--135.485655,56.819589
--139.710337,59.590401
--139.710337,59.590401
--135.324587,57.034984
--135.461864,56.809935
--135.416055,56.803858
--135.373747,56.766675
--146.528414,62.287589
--149.027643,61.685616
--149.956916,61.520797
--135.29619,56.720812
--135.288774,57.035098
--157.169825,66.885768
--139.675012,59.617811
--150.091954,62.323054
--163.41275,64.684337
--166.474855,65.328056
--166.346565,65.240277
--143.18357,60.5004
--150.647881,61.277732
--149.868118,61.549254
--163.666912,64.893693
--160.802762,63.882261
--146.56451,62.287942
--139.514147,59.417891
--161.433109,60.886639
--161.672258,58.556359
--135.455485,58.317874
--135.757086,58.209086
--135.194779,58.597473
--135.261111,58.170295
--165.028344,60.815217
--135.52782,59.241936
--161.886473,59.755663
--142.839029,61.485174
--145.055966,60.435238
--145.17295,60.343185
--135.319949,57.034362
--135.009049,57.293307
--144.927411,60.216226
--144.824231,60.48188
--135.282172,56.736029
--135.293509,57.039894
--144.430726,60.088666
--133.078557,56.532781
--132.490413,56.497106
--132.570075,56.620091
--135.278966,56.695408
--136.249549,57.639758
--136.249549,57.639758
--133.063358,56.58961
--132.341664,56.612145
--132.33595,56.611259
--156.666228,56.068594
--133.910307,56.90473
--132.955198,56.164006
--132.557432,56.053536
--132.179254,55.91925
--132.063057,55.975385
--132.11004,55.057329
--133.51236,55.879515
--132.334653,55.477717
--153.223407,57.988937
--133.418644,55.708587
--133.299479,55.989428
--133.082488,56.054624
--132.242531,55.233278
--136.320171,57.72753
--133.406069,55.614375
--133.247649,55.570262
--133.126902,55.408035
--132.018072,55.059671
--132.946184,55.268096
--156.203997,71.336621
--132.007291,54.86746
--131.586556,55.032824
--134.07706,57.310881
--163.541727,63.024636
--135.500483,58.183249
--134.258211,58.015941
--134.024077,57.280392
--133.837263,57.289823
--134.612757,57.463954
--154.983234,71.122325
--133.489422,57.300433
--136.425207,57.831636
--135.333979,57.026047
--135.325159,57.007945
--144.804035,60.449653
--144.819328,60.45114
--159.556678,61.573646
--156.670165,58.66458
--155.585248,71.202866
--156.156373,71.331412
--135.462255,58.100254
--135.196473,58.582392
--139.722434,59.577651
--168.098725,65.615321
--161.140901,65.979045
--135.07467,58.231392
--144.794413,60.454541
--132.957704,56.810566
--148.15825,59.984777
--144.840717,60.475149
--148.107109,60.273885
--158.496082,58.954082
--158.597547,59.279281
--160.235157,58.819283
--157.715992,58.904
--157.211323,59.724355
--159.054691,58.986859
--167.460215,59.749143
--147.807861,60.764336
--147.724138,60.816243
--147.738391,60.785422
--147.676107,60.803478
--147.485916,60.717164
--148.251088,59.931262
--147.767368,60.44117
--146.717767,60.848083
--150.741497,65.421795
--134.487534,58.360771
--147.420023,60.682862
--147.434827,60.51351
--146.738038,60.708766
--146.996639,64.482653
--160.528408,55.242629
--148.83009,61.070628
--160.257233,54.86378
--154.338544,59.707103
--158.080671,64.725413
--164.707669,60.465484
--141.769107,61.680709
--150.106923,61.411955
--162.984079,55.25226
--162.266123,54.740319
--157.43049,56.633582
--154.691682,59.500971
--157.366068,58.196314
--159.858956,55.010277
--159.825511,54.792268
--160.621526,55.339743
--165.482469,54.039165
--149.533239,61.33971
--159.188633,55.824686
--134.765912,58.027243
--134.689207,58.229579
--149.36097,64.897818
--145.246073,66.560307
--142.145512,67.190691
--134.107489,58.099034
--133.727444,58.12988
--155.607933,62.95136
--155.600334,62.956767
--158.712429,64.093482
--153.524659,66.363785
--147.518318,66.330377
--153.41094,65.11299
--156.77491,64.631916
--145.240839,66.564582
--154.210878,64.953122
--152.15217,65.153702
--160.04788,62.048937
--149.728735,59.669262
--149.845058,59.607669
--151.361155,59.546318
--163.560934,63.025161
--161.329113,61.786238
--155.911412,59.324126
--149.364303,59.896718
--165.577754,61.530446
--174.497406,52.011878
--161.324013,61.702205
--156.682686,55.996268
--151.344745,60.632638
--152.079108,58.909962
--153.889972,57.428851
--152.541024,57.703897
--152.402709,57.769948
--154.136355,56.496412
-173.946952,52.719857
--167.852519,53.238765
--158.116953,64.695275
--166.566384,53.866518
--153.535098,57.797053
-173.081237,52.787505
--168.046554,53.925836
--166.06115,53.856258
-178.20629,51.763907
--178.685843,51.586537
--176.114457,51.929979
--131.729524,55.535646
--131.784197,55.191529
--131.506562,55.455285
--131.510796,55.479828
--132.070295,56.016674
--131.328694,55.406534
--131.459187,55.237098
--131.847863,55.513798
--131.840168,55.485198
--131.761668,55.515928
--131.281126,55.013203
--131.298605,54.860174
--130.907956,55.51102
--132.070295,56.016674
--133.173244,56.665379
--134.221405,56.450668
--134.202729,56.456136
--133.827122,56.391524
--132.56643,56.733526
--132.454713,56.56968
--135.367811,59.50894
--134.6666,56.168782
--134.6666,56.168782
--132.269576,55.737124
--133.006593,56.203259
--130.076828,55.911198
--133.314889,56.315069
--158.403783,56.295183
--132.906046,56.129371
--132.209887,55.926721
--132.307893,56.632359
--132.400368,55.849918
--159.47797,55.900337
--133.364424,55.943732
--133.320324,55.9484
--133.275526,55.92599
--154.844337,59.97519
--133.274962,55.761278
--132.851692,56.021568
--133.655615,55.604934
--134.52091,57.514057
--133.183733,55.476492
--133.5985,55.618675
--133.090671,55.395731
--133.100816,55.381868
--132.541373,55.579039
--133.637947,55.583825
--133.821706,55.438186
--133.692369,55.236266
--133.445939,55.379861
--133.110818,55.541951
--132.881033,55.184554
--132.914628,55.072806
--132.671238,55.240762
--131.634707,55.131856
--149.573093,59.696851
--176.795685,51.794212
--136.405989,58.253748
--136.165707,58.750221
--135.537891,59.239315
--136.066684,58.205127
--134.110589,58.098033
--156.252855,57.383363
--133.475463,57.697232
--134.090154,57.295669
--136.36307,58.196011
--136.215359,57.944573
--133.813481,57.834896
--149.343731,60.091624
--136.37384,57.367888
--136.318941,57.784398
--176.055372,51.928539
--130.61741,54.792568
--135.571551,57.588556
--135.02402,57.409582
--135.704004,56.983364
--172.965084,52.072583
--135.482364,57.098181
--135.564581,56.833876
--135.309377,57.010452
--135.33826,57.024386
--135.515212,57.183133
--135.515212,57.183133
--135.557977,57.139966
--135.358967,57.023365
--135.368327,57.026394
--147.507854,60.225312
--159.088677,58.951529
--146.006964,60.363922
--142.972467,63.326117
--160.348098,55.376532
--148.691262,66.081379
--151.262262,60.558574
--150.441252,59.358026
--149.845058,59.607669
--134.50498,57.534581
--134.114056,56.928097
--133.964261,56.283884
--134.51723,57.512
--135.256375,58.165498
--132.98404,56.190545
--132.31215,56.665179
--156.666792,58.671854
--156.669923,58.692886
--156.924498,71.230292
--132.557072,56.510999
--132.393004,56.469775
--133.496365,55.903439
--133.31032,55.575899
--133.121444,55.602039
--133.187922,55.495228
--133.161579,55.388401
--132.87439,55.061667
--133.63082,55.910858
--133.695834,55.851367
--136.246972,58.632083
--134.52066,57.533674
--134.290147,57.157856
--134.586263,57.455602
--135.313089,57.006052
--135.909551,59.334773
--160.974314,61.093785
--135.447918,56.878914
--161.444482,60.784241
--161.828794,59.74399
--162.017515,60.215475
--146.760623,59.986875
--135.461864,56.809935
--158.471926,59.039986
--150.002462,61.332041
--135.333164,57.038546
--141.771013,61.695351
--144.730532,60.845959
--149.641093,70.545756
--149.388296,70.532104
--148.474362,70.191074
--147.265625,70.207646
--138.602322,59.069822
--139.660171,59.634631
--139.722253,59.598015
--139.710244,59.603514
--139.651782,59.663397
--139.701716,59.605719
--144.068775,61.502955
--139.675012,59.617811
--150.876674,62.536427
--151.202553,61.966754
--161.319684,64.112449
--135.133199,58.198172
--166.812169,66.048457
--166.471677,65.331154
--161.422927,64.426021
--168.7554,65.622837
--166.665151,66.092632
--163.044757,64.540355
--161.309515,64.395961
--146.410771,61.075378
--146.692143,60.866389
--145.19029,60.367773
--145.165394,60.325734
--145.05651,60.464583
--144.886694,60.474219
--144.894923,60.467351
--147.471836,60.85692
--157.313071,59.445285
--157.313091,59.447159
--157.290096,59.733125
--157.852405,64.838733
--143.796342,61.392616
--144.840031,60.482454
--144.848444,60.432299
--144.825322,60.43704
--142.506451,62.598671
--144.78171,65.573478
--142.819268,61.357279
--147.507854,60.225312
--144.601141,59.796875
--147.767368,60.44117
--148.509016,60.779965
--141.83022,62.087282
--148.908997,63.72651
--147.643429,64.776404
--164.946351,54.389417
--162.904233,54.893528
--147.774477,60.660268
--148.05808,59.933452
--147.326288,60.852121
--162.729458,54.799142
--161.881853,55.075873
--161.697114,55.191825
--147.071056,60.956605
--146.356087,61.129861
--147.931686,59.762618
--144.129281,64.458848
--160.407224,55.53467
--159.607191,54.751923
--160.866554,55.118642
--165.220046,54.06434
--149.568587,61.288538
--149.789004,61.137508
--154.380534,63.013305
--150.286283,61.123183
--149.968272,61.178822
--159.131074,55.881747
--156.04302,58.882286
--134.597303,58.351655
--134.708027,58.027243
--149.092314,64.543047
--150.143458,65.505679
--151.166551,59.680561
--154.758011,59.724721
--158.893521,63.980946
--164.655643,62.641877
--154.554849,59.537535
--159.16767,55.908726
--149.831005,61.184444
--134.511622,58.092507
--152.671902,58.552596
--152.448917,58.480692
--176.991322,51.590982
--147.664978,66.258913
--148.25578,66.184951
--147.862017,66.200563
--151.885635,59.134869
--151.002241,59.422981
--175.341624,52.000797
--175.953225,52.041358
--159.940087,62.999679
--159.358365,63.689794
--160.165778,61.948012
--149.764003,60.938681
--149.78078,65.864223
-177.360102,52.120932
--164.97196,62.541858
--164.199384,62.698835
--164.040337,62.909141
--151.262756,60.350554
--150.4354,59.383893
--149.440112,60.102831
--155.74721,55.739381
--154.799002,56.742271
--154.516363,58.047421
--156.735531,56.07178
-178.089474,51.987017
--156.30854,57.353933
--152.929264,57.98097
--153.536504,57.773659
--152.863007,57.920842
--154.03974,57.127548
--152.416989,57.771668
--170.185684,52.716172
--169.76274,52.947117
--169.173915,52.904252
-179.475569,51.871603
--179.059502,51.569698
-172.444761,52.746042
--179.147236,51.215427
--175.747836,51.93281
--174.446189,52.170405
--166.310741,53.707289
--166.542002,53.871108
--131.650815,55.273341
--131.24469,55.044215
--132.85274,55.232113
--132.897171,54.988329
--131.715877,55.852935
--131.847863,55.513798
--131.521553,54.958536
--131.298605,54.860174
--130.739365,54.718451
--130.714988,54.724003
--131.539337,54.963198
--134.216137,56.616176
--134.136639,56.918895
--134.050555,56.435137
--133.185486,56.654431
--134.367854,55.823261
--133.621579,56.327329
--133.573458,56.126755
--134.811397,57.013247
--134.12716,55.959133
--132.56643,56.733526
--134.12716,55.959133
--135.02402,57.409582
--134.138921,55.932713
--133.842113,56.454613
--133.842113,56.454613
--135.709181,56.98547
--134.50498,57.534581
--132.944167,56.88768
--135.363283,59.340791
--135.367726,59.485616
--134.422144,56.001151
--133.310425,56.455431
--133.881565,57.453713
--133.126322,56.454519
--152.372944,57.798582
--132.267529,55.745561
--133.006016,56.247818
--132.968797,56.173752
--136.578723,58.877207
--139.661262,59.595252
--132.906046,56.129371
--132.283006,55.746766
--132.384713,55.908749
--132.592483,56.65912
--132.394172,56.115876
--132.570075,56.614289
--132.11286,56.028794
--155.224651,71.162937
--132.613217,56.015251
--139.72017,59.595096
--132.11286,56.028794
--133.354757,55.982167
--133.366983,55.935359
--133.479274,55.79493
--133.406069,55.614375
--133.068228,56.194837
--133.01205,56.103043
--133.082192,56.046825
--133.437844,55.633643
--133.655615,55.604934
--133.326582,55.401141
--133.663861,55.418057
--139.72017,59.595096
--133.191544,55.264921
--133.076174,55.597405
--133.071004,55.580869
--132.679223,55.458468
--133.047042,55.247814
--133.115134,55.592629
--133.072774,55.607552
--132.90273,55.086221
--132.793295,55.169661
--132.867241,55.080439
--132.625665,55.276116
--131.968059,55.144849
--147.52972,70.381929
--132.147939,55.436754
--131.989001,55.002728
--132.073066,55.054269
--136.375842,58.22867
--134.735659,58.244681
--134.535909,57.523556
--134.285133,57.904962
--135.921237,58.378562
--134.218383,57.77127
--135.435487,57.128495
--135.732839,58.326397
--136.492006,57.867793
--134.535629,57.513069
--135.454315,58.091777
--134.336948,58.126573
--135.470171,57.086439
--133.648516,57.753727
--133.881565,57.453713
--134.092056,57.305128
--134.07706,57.310881
--134.090154,57.295669
--135.906966,57.403032
--135.719572,57.638861
--135.333257,56.998435
--135.391329,57.038361
--135.253818,57.009709
--135.290065,57.036708
--135.304256,57.000885
--135.493894,56.824298
--155.111718,71.132537
--135.485655,56.819589
--135.373747,56.766675
--135.305563,56.723013
--151.669315,65.064375
--135.318513,56.73765
--134.922039,57.294833
--145.267492,66.563204
--147.946545,70.234154
--139.707038,59.589402
--139.511396,59.699204
--135.256375,58.165498
--150.271541,61.733477
--150.603031,61.416519
--162.500147,67.011702
--162.669277,60.332476
--142.148297,66.212249
--165.614524,66.370972
--170.493077,63.686601
--161.204159,64.358561
--165.416049,64.535698
--165.409265,64.49722
--157.153217,66.870868
--157.191272,66.851435
--162.57226,66.901881
--149.928159,61.520444
--149.934474,61.519114
--162.615796,66.875854
--160.451604,66.973426
--161.041328,66.815457
--139.804221,59.558982
--145.009609,60.477539
--144.952137,60.44448
--144.931019,60.458941
--161.91819,60.748295
--165.420994,60.292317
--157.715119,61.901527
--165.112541,60.534271
--162.882682,59.958079
--162.012513,60.212837
--157.15584,61.679786
--135.210303,58.605883
--144.931761,60.426288
--134.923358,58.554166
--135.133199,58.198172
--135.472359,59.283816
--135.894632,59.3377
--160.15322,61.939252
--161.782856,60.69982
--158.74334,61.53501
--148.680063,59.91551
--144.396819,61.166844
--148.24748,60.627618
--145.760828,60.543878
--146.356261,61.133182
--149.930394,61.51888
--146.024427,60.458674
--146.024427,60.458674
--148.15825,59.984777
--145.6953,61.17318
--144.495457,60.828711
--145.793536,60.62037
--145.774183,60.547743
--145.69296,60.54347
--145.015508,60.516245
--157.700699,64.878426
--144.922058,60.42555
--144.833791,60.504921
--144.771998,60.452558
--144.608561,60.163255
--144.430726,60.088666
--147.471836,60.85692
--158.463165,59.075992
--148.15172,60.781843
--147.753848,60.842576
--154.562432,65.830897
--147.774477,60.660268
--147.724138,60.816243
--147.782356,64.783415
--147.775732,64.819807
--166.11636,54.033536
--133.684719,56.196081
--147.577938,60.863492
--147.676107,60.803478
--148.05808,59.933452
--147.326288,60.852121
--162.436536,54.944119
--161.099561,55.157939
--147.420023,60.682862
--148.167148,59.982759
--157.33327,59.445312
--144.431822,61.575188
--164.847629,54.178301
--159.504427,55.274802
--159.426708,56.67032
--163.410872,54.844583
--162.441197,60.902827
--161.612994,55.057822
--149.791243,61.153405
--149.733519,61.224141
--150.037713,61.159048
--149.830132,61.137438
--149.838239,61.186126
--159.402122,55.713391
--155.37148,59.388156
--149.583814,61.300852
--134.136639,56.918895
--157.74169,56.481205
--134.706255,58.347213
--134.718502,58.048269
--165.091343,60.536524
--157.905667,56.309314
--150.009535,60.1132
--147.133041,60.899298
--156.490878,56.999853
--157.578687,57.564856
--147.765941,64.830656
--147.761818,64.83065
--134.609563,58.345383
--152.315002,63.87521
--145.272177,66.563778
--145.283983,66.590682
--156.904782,64.726741
--156.365572,65.862537
--146.418022,67.01408
--154.733033,58.938063
--155.41932,64.780944
--142.320022,62.959586
--145.319498,66.564965
--161.106953,55.26441
--166.200055,53.952235
--158.580385,64.403749
--158.704151,64.33933
--154.917898,65.784504
--144.120089,64.647618
--151.515757,59.512115
--160.193736,62.6005
--160.197395,62.575398
--153.861819,56.735971
--159.514435,63.522962
--159.688836,63.280948
--164.660956,62.655575
--161.773924,61.621152
--150.435334,59.342298
--152.409374,57.764029
--148.667494,60.67614
--149.524224,59.764586
--149.573093,59.696851
--161.320815,61.776405
--164.763183,62.742732
--133.173244,56.665379
--161.534006,61.641968
--161.313619,61.848683
--160.7269,61.930455
--163.173512,62.05025
--139.701716,59.605719
--152.895498,62.280737
--152.641648,60.094256
--154.799002,56.742271
--164.338419,59.929993
--152.050976,58.883903
--154.795794,56.401497
--152.556582,58.637527
-173.103841,52.795855
--153.065248,57.944559
--151.232297,59.723961
--170.416439,57.046266
--170.841936,52.54024
--166.54763,53.894182
--168.447558,53.041137
-177.621513,51.941491
--178.609479,51.58341
--149.815853,61.180828
-173.356103,52.35637
-174.057252,52.706059
--161.789113,60.785812
--176.115489,51.846253
--175.463273,51.977823
--174.439514,52.028022
--131.644593,55.265687
--131.729524,55.535646
--131.718189,55.529686
--131.810787,55.482788
--131.510796,55.479828
--131.696758,55.853199
--149.773133,61.202454
--131.715877,55.852935
--131.690389,55.880197
--149.457948,61.278565
--145.468089,62.147593
--148.725612,60.775348
--131.599837,54.931908
--131.22019,55.141551
--131.281126,55.013203
--132.91634,56.885773
--131.204953,54.866803
--130.851395,55.875478
--130.907956,55.51102
--130.741659,54.797253
--133.731684,56.668911
--134.208577,56.458583
--134.202729,56.456136
--134.367854,55.823261
--132.859086,56.973712
--132.944167,56.88768
--131.691068,55.570894
--132.91634,56.885773
--133.076116,56.506152
--134.376851,56.873562
--152.374872,57.803463
--132.311883,55.900188
--133.011017,56.153243
--132.311883,55.900188
--133.704859,55.692007
--133.011017,56.153243
--132.875814,56.128107
--132.468172,56.614289
--133.62731,56.351517
--133.622967,56.353652
--132.941912,55.676525
--145.786253,60.585833
--133.151755,56.288483
--133.438107,55.894567
--133.505713,55.930649
--133.366983,55.935359
--133.364424,55.943732
--133.51236,55.879515
--133.247573,54.664478
--133.247573,54.664478
--145.732963,60.620304
--133.479274,55.79493
--133.348755,55.738264
--132.852287,55.209311
--132.993464,54.81349
--132.478209,55.405077
--165.573988,54.025623
--133.288538,55.916851
--133.624143,55.642745
--135.343584,57.028035
--133.183733,55.476492
--133.120232,55.563987
--133.120232,55.563987
--133.821706,55.438186
--133.624203,55.42847
--133.449255,55.411248
--133.121058,55.439431
--132.671238,55.240762
--132.083671,55.29251
--131.989001,55.002728
--131.990014,54.88052
--131.64705,54.996673
--130.388737,55.778748
--133.361661,55.77253
--133.67867,56.210068
--133.681248,56.098423
--145.280489,66.566935
--151.214807,59.430956
--133.707135,55.887707
--133.32366,56.317705
--136.366116,58.229769
--136.078369,58.783573
--136.525745,58.279943
--134.538818,57.505067
--134.218383,57.77127
--133.860439,57.433943
--133.290015,57.303394
--133.475463,57.697232
--133.448577,57.410423
--133.448577,57.410423
--135.301275,57.011031
--134.107638,57.241839
--134.016171,57.293087
--135.498025,58.148895
--136.221629,57.951667
--134.094696,57.316478
--134.535629,57.513069
--136.210511,57.623842
--135.694415,56.161536
--135.740363,56.988364
--135.470171,57.086439
--135.450677,57.084186
--135.417501,57.082355
--136.085023,57.550377
--135.319949,57.034362
--135.878028,56.988791
--135.333979,57.026047
--135.416055,56.803858
--134.907597,57.268721
--136.320171,57.72753
--164.92447,54.140055
--162.683436,55.19596
--154.038971,57.656465
--142.342404,59.693783
--134.59469,58.179403
--152.418672,57.890312
--135.29619,56.720812
--135.343584,57.028035
--152.887871,58.274414
--135.262137,59.007209
--161.777693,60.791395
--147.434827,60.51351
--135.043867,58.523005
--152.937231,58.281785
--152.937231,58.281785
--147.409032,60.517297
--147.408514,60.292032
--141.950886,62.951887
--146.385083,64.008803
--148.801909,63.632018
--152.223815,58.895737
--158.15973,56.133337
--154.480774,59.706273
--134.994181,58.454048
--155.733224,62.774373
--160.041818,62.368274
--135.210303,58.605883
--149.728735,59.669262
--162.116439,61.805126
--134.088396,56.926167
--153.579084,59.368264
--132.049546,55.98241
--132.454713,56.56968
--132.381976,56.464451
--144.006119,70.089229
--139.863222,59.125359
--153.927072,57.699934
--175.528604,52.159494
--131.26217,55.358849
--132.869791,55.191914
--139.749016,59.561058
--134.113259,55.978229
--132.33595,56.611259
--133.684719,56.196081
--138.417876,58.209949
--133.424793,55.881441
--133.120321,55.393517
--135.915592,58.540722
--133.020884,55.361703
--133.010813,55.155513
--132.994658,55.158504
--132.872504,55.20046
--132.698539,55.086144
--132.732031,55.105578
--134.290147,57.157856
--135.296796,57.012916
--139.267565,59.308684
--145.143368,60.300821
--147.461537,60.863599
--146.352626,61.130823
--162.01404,63.4904
--162.277012,64.609879
--159.327885,54.863141
--154.203675,56.915589
--146.564024,62.295358
--149.623347,59.634209
--149.717579,59.657599
--162.771066,66.068768
--164.196034,66.992307
--149.922432,61.519537
--149.922982,61.516318
--165.971024,61.959691
--132.557072,56.510999
--135.196473,58.582392
--132.477606,56.511366
--135.085491,58.257984
--161.977941,60.688385
--131.968059,55.144849
--150.044834,65.718304
--134.41556,58.301314
--143.603869,70.133545
--148.679861,70.399794
--161.826818,59.0081
--155.712871,70.918048
--147.987649,70.473137
--139.748187,59.588355
--149.325479,61.616537
--166.230915,64.473952
--165.27068,65.237012
--139.597584,59.704421
--165.489797,64.505481
--165.400262,64.505798
--162.28347,63.516096
--145.807813,60.612469
--145.212367,62.203454
--150.074682,61.13671
--148.7462,60.72581
--145.153246,60.30169
--145.049413,60.46597
--144.943862,60.453568
--149.888384,61.197387
--148.412436,64.650065
--158.278517,64.607633
--144.877877,60.503617
--144.836853,60.481112
--144.842399,60.437727
--144.836761,60.465531
--144.601141,59.796875
--148.1563,60.275478
--157.302118,59.452105
--157.308334,59.446178
--157.400892,59.758737
--157.276993,59.733013
--157.286289,59.723904
--160.395614,59.050668
--147.855221,60.785885
--147.738391,60.785422
--147.954797,60.222207
--148.046328,60.147053
--146.817324,60.883063
--146.717767,60.848083
--147.931686,59.762618
--141.16238,64.778551
--145.823679,66.261594
--162.863324,55.351511
--162.861447,54.385431
--162.835851,54.36926
--159.372968,55.819569
--152.662039,66.558443
--133.695759,57.796379
--161.967204,55.037386
--159.49202,55.075716
--159.482619,54.941114
--160.524899,55.309041
--150.037713,61.16008
--149.966355,61.140231
--154.52556,59.527979
--154.618299,59.535013
--154.21336,59.694183
--154.814484,59.431234
--155.327497,59.203919
--149.860804,61.217556
--134.92537,58.370262
--149.885316,61.213624
--149.835865,61.185413
--159.74501,62.137047
--156.516,57.025078
--158.785117,56.255144
--158.534474,56.309807
--154.331686,60.201311
--159.556249,63.365118
--142.151744,66.201541
--148.996911,65.993595
--143.754325,66.6411
--156.332832,65.781164
--155.445815,62.909898
--147.796891,66.233988
--147.070012,66.40705
--146.56563,66.518345
--151.718216,59.421989
--164.904902,62.602991
--151.468255,59.505442
--145.278637,66.565173
--151.705419,59.439996
--151.529967,59.648909
--154.928261,64.865142
--153.015483,65.18006
--154.355968,64.945683
--131.326937,55.241389
--152.783313,65.170097
--152.70216,65.147658
--152.447788,65.109272
--149.623347,59.634209
--153.478659,60.91462
--149.841491,65.746448
--161.326269,61.787254
--164.617917,63.034137
--164.383141,62.681238
--166.011958,62.012033
--166.148648,61.736585
--160.864757,61.927326
--156.752047,56.006073
--152.338789,58.42513
--171.175448,52.55724
--152.331978,58.420043
--154.153499,56.944626
--154.156882,56.681279
--134.15328,57.888335
--153.369565,57.717288
--152.517282,57.739904
--152.437801,57.753999
--178.351185,51.482424
--153.618417,56.886747
--151.511447,59.664255
--152.360733,57.760301
--135.294171,57.037951
--153.902571,56.720355
--154.006959,57.639441
--169.115498,52.821014
--177.468879,51.89019
--135.009049,57.293307
--134.907597,57.268721
--169.789659,56.536939
--166.825312,53.665883
--175.923375,51.839402
--169.950569,57.181589
--170.12349,52.857433
-178.316974,51.945445
--130.938259,55.571722
--131.685448,55.353731
--131.204953,54.866803
--130.714988,54.724003
--133.519322,57.01784
--134.114056,56.928097
--157.018061,58.732163
--131.599837,54.931908
--133.908914,56.903082
--155.626131,70.989215
--176.135461,51.947851
--134.072225,56.896652
--133.948831,56.866326
--133.923265,56.912814
--133.731684,56.668911
--134.225397,56.462406
--134.221405,56.450668
--133.063358,56.58961
--133.126322,56.454519
--132.341664,56.612145
--134.143952,56.938055
--132.961727,56.798955
--132.266071,55.743505
--133.299479,55.989428
--132.93115,56.157194
--132.941072,56.099669
--132.367841,55.636609
--133.070059,56.232107
--133.070059,56.232107
--132.192982,55.919295
--132.852287,55.209311
--132.477606,56.511366
--132.324125,56.627542
--132.881033,55.184554
--132.817973,54.750975
--132.654495,56.44856
--132.563695,56.352257
--132.557432,56.053536
--132.400368,55.849918
--131.905559,55.959039
--132.653137,55.480732
--133.638106,56.343289
--133.621132,56.35607
--133.621132,56.35607
--132.641509,55.689082
--133.314889,56.315069
--133.424793,55.881441
--132.521788,55.580957
--132.37401,55.42861
--133.356403,55.884
--132.698539,55.086144
--133.356403,55.884
--132.134995,55.212477
--133.288538,55.916851
--133.274962,55.761278
--133.082488,56.054624
--133.326582,55.401141
--133.163637,55.519459
--133.445939,55.379861
--133.449255,55.411248
--133.472256,55.200825
--133.093476,55.575097
--132.937918,55.042694
--132.137185,55.204304
--132.137185,55.204304
--132.001238,55.05209
--131.510174,55.264172
--132.007291,54.86746
--132.414473,54.777027
--132.50169,55.449373
--133.396167,55.97669
--133.949594,55.844469
--136.350987,58.19508
--134.102328,57.314127
--133.681248,56.098423
--133.695834,55.851367
--136.230216,57.953139
--136.15327,58.560469
--136.425207,57.831178
--135.463395,58.318104
--134.7362,58.263551
--136.157478,58.257256
--136.575604,57.845573
--133.860439,57.433943
--133.572827,57.212617
--135.571551,57.588556
--135.694415,56.161536
--135.450621,57.087727
--135.314421,57.010798
--135.309377,57.010452
--135.313089,57.006052
--135.447918,56.878914
--176.018204,51.944756
--135.318513,56.73765
--155.978583,71.281696
-173.861653,52.746649
--135.305563,56.723013
--135.323142,57.03718
--135.331155,57.039032
--135.325932,57.035144
--135.356867,57.007471
--135.377578,57.058366
--135.377578,57.058366
--135.314832,57.033924
--135.273646,57.03195
--135.273646,57.03195
--153.535098,57.797053
--155.75375,71.224941
--147.09824,70.30312
--156.681976,71.323494
--139.707038,59.589402
--139.661325,59.591018
--139.69404,59.449383
--150.5892,61.358419
--165.468596,64.507213
--161.156292,64.934818
--146.533007,62.295543
--149.546058,61.570429
--162.880117,68.037492
--149.897441,61.535155
--149.943313,61.519147
--159.997311,66.599592
--162.978081,67.564717
--175.140919,52.214939
--162.609735,66.887954
--159.985733,66.600192
--139.597584,59.704421
--159.24851,61.58459
--173.116318,60.618033
--162.04114,60.64941
--157.366248,61.78613
--135.448585,59.230361
--135.262137,59.007209
--135.218355,58.614711
--135.181379,58.61181
--135.115126,58.186013
--161.39617,60.91231
--145.749399,60.593236
--145.670617,60.390587
--145.259737,60.298365
--145.174806,60.369966
--145.135147,60.385572
--145.162194,60.323268
--142.017011,61.90103
--144.886394,60.482491
--169.246614,52.768219
-178.022812,51.873623
--144.949742,60.477825
--144.935647,60.433541
--144.859825,60.514528
--144.824759,60.534988
--144.835025,60.537468
--144.847053,60.449956
--144.811507,60.474802
-178.265063,51.968784
--142.882656,62.509697
--147.629695,60.523264
--157.30697,59.733018
--154.563299,60.099969
-178.616868,51.348718
--158.653111,59.280765
--160.442491,58.676849
--157.18652,59.705037
--157.300678,59.455316
--157.362111,59.739124
--158.655949,56.952142
--160.318138,58.635997
--147.485916,60.717164
--148.316306,60.011191
--177.746014,51.802015
--147.855221,60.785885
--147.814898,60.756514
--147.577938,60.863492
--148.046328,60.147053
--147.91738,60.152939
--147.91738,60.152939
--148.251088,59.931262
--147.663126,60.481915
--147.668299,60.485744
--158.646888,59.261284
--146.845925,60.843862
--146.382695,59.402666
--144.716184,63.997116
--142.350519,62.410216
--142.244447,62.749674
--149.131893,64.290249
--149.843917,61.533523
--147.749906,64.781356
--142.528121,63.131173
--147.37718,64.728477
--161.023589,56.004977
--162.518534,54.362082
--161.099814,55.155783
--159.333181,55.050186
--154.535176,59.537258
--154.130331,59.792108
--159.426181,55.060967
--161.903931,55.052898
--149.815819,61.184317
--149.419408,61.426234
--148.244624,64.689617
--158.900922,55.806913
--158.301574,56.608318
--156.459441,57.910675
--154.375531,59.715516
--134.749634,58.329332
--151.423786,64.857296
--148.617465,66.144263
--144.618119,65.487693
--145.279887,66.528078
--149.448476,60.110721
--134.897444,58.387394
--134.605827,58.056964
--153.286918,63.376162
--151.515757,59.512115
--159.125046,63.887985
--152.578983,65.129153
--151.872324,65.121607
--163.699615,62.094493
--150.441252,59.358026
--152.39735,58.328529
--149.645356,59.600486
--149.383517,59.879236
--176.806374,51.807671
--149.524224,59.764586
--162.069237,61.865495
--161.985241,61.561056
--161.340338,61.685084
--162.906295,61.933644
--164.709316,62.664121
--152.085334,60.340729
--164.64048,63.072465
--161.328207,61.84904
--152.024251,58.907893
--154.036628,56.985059
--153.889972,57.428851
--131.700529,55.564737
--131.880704,55.129242
--131.850392,55.286427
--131.726457,55.347047
--131.718189,55.529686
--131.635981,55.343959
--131.644593,55.265687
--131.478815,55.498964
--131.26217,55.358849
--130.938259,55.571722
--130.983752,55.315896
--131.844347,55.520533
--131.761668,55.515928
--132.20233,54.763865
--131.614532,55.914826
--135.45617,58.107318
--130.885906,55.825688
--130.739365,54.718451
--131.539337,54.963198
--134.050555,56.435137
--133.836408,56.793443
--134.225397,56.462406
--134.513657,57.50815
--133.717775,56.390491
--133.076116,56.506152
--133.207723,57.098985
--132.949652,56.701086
--132.859086,56.973712
--133.247649,55.570262
--133.078557,56.532781
--133.074906,56.51059
--133.310425,56.455431
--133.097957,56.456943
--133.927342,56.90473
--134.376851,56.873562
--134.345579,56.843708
--152.397076,57.780182
--132.271755,55.743206
--133.006593,56.203259
--132.968797,56.173752
--132.566998,56.524175
--132.31215,56.665179
--133.190918,55.550237
--133.126902,55.408035
--132.654495,56.44856
--132.617232,56.486879
--132.563695,56.352257
--132.346859,55.888312
--133.087687,55.384902
--132.185989,56.045045
--132.063057,55.975385
--132.382644,56.480292
--133.638106,56.343289
--133.634269,56.349249
--133.816726,55.893339
--131.64705,54.996673
--133.347682,55.928156
--133.342391,55.910789
--133.275526,55.92599
--133.610888,55.649376
--133.624203,55.42847
--133.163637,55.519459
--133.573458,56.126755
--132.954572,55.353894
--132.018072,55.059671
--133.084201,55.254226
--132.872504,55.20046
--132.897171,54.988329
--132.912469,55.052469
--133.54717,54.762964
--132.530931,55.581647
--132.334653,55.477717
--132.625665,55.276116
--132.083671,55.29251
--132.11004,55.057329
--132.414473,54.777027
--131.572853,55.155331
--132.38195,55.486823
--131.333991,55.183869
--133.556085,56.156799
--136.011917,58.72239
--134.811397,57.013247
--137.595723,58.631897
--136.86342,58.904821
--136.059602,58.719115
--134.268851,57.881177
--157.720271,64.871171
--136.375842,58.22867
--133.927068,57.450812
--136.350987,58.19508
--135.733236,58.392196
--135.721371,58.418858
--135.290065,57.036708
--136.407989,58.192758
--133.694101,57.786677
--133.588098,57.496711
--134.102328,57.314127
--134.016171,57.293087
--133.864762,57.273119
--133.837263,57.289823
--135.419679,58.096679
--133.443298,57.417648
--131.575426,55.124916
--133.152834,55.475631
--136.285598,57.689416
--147.336418,70.335736
--139.660171,59.634631
--135.574561,57.158839
--135.407033,57.075546
--135.404613,57.079785
--135.564581,56.833876
--135.323144,57.030558
--135.324543,57.010751
--149.10084,61.587861
--135.404891,56.77712
--136.412555,58.155459
--135.304256,57.000885
--135.310139,56.730463
--135.301275,57.011031
--135.38353,57.058158
--136.032819,57.367888
--164.454541,64.558402
--162.719247,63.53353
--162.575323,63.537971
--136.424624,57.819544
--135.282172,56.736029
--157.007333,58.697689
--162.921438,67.048007
--156.783707,71.292328
--156.11336,71.310578
--135.278106,59.026336
--149.932766,70.551941
--165.386654,64.49617
--162.622004,66.836618
--149.663187,61.641042
--146.572475,62.294316
--131.244649,54.984484
--132.592483,56.65912
--146.572475,62.294316
--131.93469,56.213612
--132.172401,56.359075
--136.137808,58.522035
--145.084455,60.432997
--133.489422,57.300433
--133.572827,57.212617
--133.290015,57.303394
--145.170137,60.306553
--144.214573,60.117273
--133.546578,57.211212
--161.557492,61.610484
--131.651148,55.347561
--139.711768,59.607322
--139.626194,59.446714
--167.535284,65.245558
--168.954508,65.738045
--165.483028,66.400614
--171.849984,62.937527
--149.878043,61.547257
--148.1563,60.273885
--161.395845,64.39991
--162.054195,63.451776
--166.02306,66.224384
--162.031896,63.477578
--146.519273,62.294265
--168.106692,64.957975
--161.846914,66.210059
--149.916209,61.530226
--150.018061,61.354985
--159.994078,66.595922
--157.169343,61.698808
--164.260755,59.827671
--154.987616,59.849331
--154.323158,59.834444
--164.644757,60.934973
--161.397273,60.819272
--135.353914,58.91863
--135.289141,59.045543
--135.289141,59.045543
--135.372605,58.963431
--135.218355,58.614711
--135.261111,58.170295
--135.115126,58.18612
--157.480469,59.356002
--141.194,64.789233
--164.67032,60.835069
--164.51792,60.538702
--161.44822,60.807763
--161.923016,59.743514
--161.788978,60.786447
--146.332272,60.450335
--141.817908,61.585808
--145.435917,61.534213
--148.00063,60.079089
--145.30481,60.30173
--145.727103,64.049612
--149.936725,61.534824
--147.127305,64.5716
--144.998342,60.495807
--145.004179,60.452836
--144.923081,60.470411
--144.878273,60.511667
--144.887576,60.500766
--144.888848,60.461826
--144.848144,60.455135
--144.812588,60.462129
--134.643144,58.38145
--154.269059,66.034316
--144.397136,59.987545
--161.177402,58.545809
--159.996033,58.591491
--158.56421,58.807914
--157.298532,59.449243
--158.721859,56.92079
--158.519771,59.034996
--148.15172,60.781843
--147.133041,60.899298
--148.019787,60.655386
--147.502803,60.617492
--148.003876,60.045903
--147.61556,60.518386
--154.71657,59.717536
--147.071056,60.956605
--147.409032,60.517297
--146.374753,61.134156
--146.818207,60.805519
--146.818207,60.805519
--146.738038,60.708766
--147.89292,64.796452
--147.939674,64.781625
--154.073499,60.320828
--149.879633,61.220518
--155.889559,62.508359
--149.378414,65.143586
--162.73757,55.18591
--162.737531,55.195365
--164.883208,54.190505
--134.582826,58.052321
--159.570514,62.691224
--156.387614,65.697748
--162.637748,55.410217
--162.789526,54.372418
--149.162424,60.733788
--156.878161,56.782546
--156.730648,56.980095
--154.92754,59.746518
--157.557634,57.555905
--157.686792,64.862913
--158.747457,64.304279
--144.096142,65.82075
--157.728007,64.879143
--154.726918,63.103044
--152.02713,65.153644
--134.64891,58.389441
--152.541985,65.137571
--152.123447,65.174071
--151.252915,59.593678
--151.269008,60.279475
--151.103948,59.741567
--156.381609,65.43235
--155.694985,64.75833
--151.726518,59.095943
--153.752646,65.061097
--153.109224,65.142912
--151.304427,60.582646
--146.292028,66.972448
--152.638976,66.552553
--149.083135,66.014174
--163.045745,61.884984
--164.587359,63.084986
--153.55275,59.320115
--151.055893,60.474025
--149.643659,59.644598
--164.720438,62.610504
--164.940723,62.639709
--164.015124,62.899432
--150.777195,59.2889
--152.162352,58.966148
--153.331712,57.710739
--176.657754,51.860541
--153.091073,57.948317
--149.424194,60.126549
--153.401902,56.991597
--152.387514,57.779103
--176.652875,51.864412
--166.573473,53.894408
--168.855798,52.942155
--152.707835,58.440696
--169.796152,53.046275
--168.482384,52.975637
--166.848764,53.690562
--170.423548,57.110593
-178.435786,51.899644
--178.873024,51.746071
--178.227822,51.596671
--177.707802,51.653604
--176.836675,51.798126
--172.634174,52.247279
--152.667576,58.469109
--153.420871,57.976738
--176.424338,51.728675
--174.656598,52.167162
--133.128038,55.381935
--131.831113,55.480285
--131.650815,55.273341
--131.506562,55.455285
--131.328694,55.406534
--133.094041,55.376155
--131.045675,55.285365
--131.764712,55.534718
--131.614532,55.914826
--134.107638,57.241839
--131.614467,54.940652
--131.499743,54.85623
--131.499743,54.85623
--131.244649,54.984484
--130.923867,55.401377
--130.635743,54.80157
--133.864762,57.273119
--130.747711,54.768473
--130.747711,54.768473
--130.61741,54.792568
--133.964261,56.283884
--133.948831,56.866326
--135.498025,58.148895
--133.919766,56.709484
--134.246614,56.451634
--134.208577,56.458583
--134.138921,55.932713
--136.153647,58.530199
--134.549632,57.501734
--133.717775,56.390491
--132.982289,56.494416
--133.097957,56.456943
--133.91788,56.960549
--134.143952,56.938055
--135.324933,59.455322
--134.323225,56.832187
--132.556466,56.713601
--132.270327,55.746068
--132.955198,56.164006
--132.568672,56.662452
--132.324125,56.627542
--132.307893,56.632359
--133.704859,55.692007
--132.617232,56.486879
--132.365988,56.115876
--133.354757,55.982167
--133.505713,55.930649
--132.613217,56.015251
--133.347682,55.928156
--133.443298,57.417648
--132.185989,56.045045
--132.1941,56.179198
--133.523852,55.881074
--133.352382,55.729459
--133.320324,55.9484
--133.057652,56.134248
--133.728517,55.581006
--133.190918,55.550237
--133.449832,55.656797
--133.449832,55.656797
--133.5985,55.618675
--133.164257,55.497633
--133.637947,55.583825
--133.458573,55.449799
--133.31032,55.575899
--133.092109,55.425299
--132.912469,55.052469
--133.54717,54.762964
--132.993464,54.81349
--132.17428,55.395559
--132.134995,55.212477
--132.073066,55.054269
--132.446547,54.78445
--131.386676,55.163438
--132.480525,55.628895
--133.63082,55.910858
--133.526867,56.15196
--134.735659,58.244681
--133.556085,56.156799
--136.405989,58.253748
--135.900791,58.523856
--134.971272,58.096485
--136.386476,58.194978
--136.386476,58.194978
--136.000344,58.46105
--134.546959,57.515497
--133.648516,57.753727
--133.454695,57.297261
--134.092056,57.305128
--135.574561,57.158839
--135.435487,57.128495
--135.906966,57.403032
--135.333257,56.998435
--135.419181,57.076058
--135.324543,57.010751
--135.347435,57.022793
--136.085023,57.550377
--135.38353,57.058158
--135.314832,57.033924
--135.294171,57.037951
--135.310802,57.013606
--135.294185,57.000181
--135.294185,57.000181
--135.310842,57.004686
--135.310842,57.004686
--135.27929,57.032747
--135.472574,56.829586
--135.472574,56.829586
--135.373102,57.04061
--134.922039,57.294833
--135.324587,57.034984
--135.322924,57.036039
--135.356867,57.007471
--135.365926,56.996241
--148.410797,66.198831
--109.389866,31.97827
--109.541569,31.851955
--109.625185,32.175579
--110.281956,31.949022
--109.129666,31.678387
--109.32492,31.969734
--109.511463,31.346144
--113.624616,33.7688
--110.80467,33.93201
--110.187114,33.860895
--111.028885,32.349785
--112.043549,33.076538
--110.966769,31.341672
--109.624448,34.472222
--110.95947,32.226473
--110.963507,31.850385
--110.750506,31.764571
--110.926655,32.206917
--110.918831,32.206988
--111.015899,32.177864
--111.860841,31.921574
--111.132406,32.184804
--113.085228,31.815907
--111.331968,31.57641
--112.077116,32.053239
--111.783831,32.055982
--111.561196,32.011151
--111.494602,32.086897
--112.54585,31.945551
--112.617073,32.239367
--111.069261,32.110314
--114.624412,32.661042
--114.422637,32.66765
--114.40531,32.668979
--114.449515,32.671155
--112.036928,33.074263
--111.379118,33.038438
--112.074732,33.030116
--111.881622,33.135334
--111.740997,33.123841
--110.862166,32.587308
--111.562699,32.758417
--111.561829,32.75685
--111.387399,33.002181
--111.580422,33.420105
--111.369887,33.023466
--111.549865,32.968587
--111.532635,32.973349
--111.513541,32.978357
--110.914953,33.05063
--114.648523,32.480233
--111.76882,34.645456
--112.478184,34.544112
--111.782011,34.635612
--111.789638,34.627628
--111.71916,34.64302
--112.459004,34.542804
--112.460627,34.541832
--112.467362,34.540886
--112.885078,35.31956
--112.45405,34.603534
--112.425778,34.590085
--112.479877,35.231076
--112.409089,34.376687
--112.823967,34.015039
--112.149737,34.086345
--112.076634,34.780706
--112.482242,34.560828
--111.853417,34.5642
--112.051955,34.777681
--112.480237,34.558768
--109.69353,32.787945
--109.747866,32.813115
--109.816185,32.63286
--109.949141,32.619762
--109.973352,32.579984
--111.664005,35.20203
--111.064148,34.684986
--113.845903,35.955067
--113.364927,35.762559
--113.364657,35.744551
--112.172255,33.797991
--111.74914,33.419406
--111.969626,33.421953
--113.869812,35.402072
--113.958356,35.251379
--114.207273,35.411103
--114.036025,35.199445
--113.418719,35.539325
--112.738295,36.860421
--112.144201,33.370609
--111.959109,33.320551
--111.791414,31.890762
--111.986765,33.563928
--111.584996,33.396929
--112.102118,33.433048
--112.017745,33.62858
--112.129943,33.515262
--112.07512,33.447145
--112.063455,33.481661
--112.060222,33.403867
--111.973561,33.604326
--112.033997,33.647754
--111.952264,33.424177
--112.151794,33.465438
--112.20294,33.556399
--111.738792,33.690632
--112.134218,33.502175
--111.633435,33.243544
--112.719874,32.929107
--112.547617,33.406668
--112.220551,33.486035
--112.182101,33.484153
--112.202103,33.693237
--112.147167,33.534827
--109.864047,35.52166
--110.000665,34.265383
--110.16012,34.867843
--109.406077,32.001678
--109.85615,32.261175
--109.909333,32.092453
--109.836037,31.730354
--109.25528,31.995788
--109.511228,31.335117
--114.280132,34.139798
--114.311563,34.129663
--114.523615,33.605466
--114.23632,34.01651
--111.452151,34.385255
--110.45913,33.353097
--110.457102,33.32857
--111.347492,34.254744
--110.714535,32.081075
--111.746273,33.119735
--110.875957,32.0596
--110.790911,32.033883
--110.971708,31.937241
--111.893613,31.926274
--111.305545,32.189513
--111.919384,32.073636
--111.97005,32.034634
--111.898623,32.083686
--111.711432,32.011073
--111.562649,32.436281
--111.549781,32.414006
--111.233577,32.402527
--111.02552,32.155445
--110.972939,32.265562
--111.474299,33.35333
--110.933421,32.552678
--111.856232,32.879393
--111.787283,32.758744
--111.666833,32.741526
--111.523997,32.979728
--111.515282,32.980151
--111.775848,32.945704
--111.998677,32.581939
--111.507232,32.73255
--112.043276,33.08126
--112.043791,33.078969
--111.652914,35.184943
--114.638961,32.627594
--114.623913,32.65885
--113.359291,32.099424
--114.442338,32.654186
--114.707758,32.597926
--111.816289,34.826917
--112.06678,34.747054
--112.027663,34.764891
--112.173219,33.502166
--112.080471,33.433143
--111.847805,34.605476
--112.474075,34.544537
--112.486771,34.561745
--112.431338,34.650633
--112.456706,34.756325
--111.919774,34.609539
--109.662344,32.809632
--109.643041,32.848166
--109.843848,32.868557
--111.532727,35.266249
--110.82155,36.669089
--111.705294,35.106889
--111.616851,35.216584
--111.501652,36.910744
--111.445933,34.906214
--111.641575,35.18874
--109.330442,34.511656
--114.369601,34.551539
--114.050145,36.807701
--113.903703,35.95157
--114.622561,35.041102
--114.597824,34.91047
--109.392705,34.314639
--109.545784,35.70898
--109.54335,35.69798
--109.514243,36.861552
--113.904334,35.964948
--113.85149,35.99516
--109.366072,34.501314
--111.988299,33.351818
--112.682912,32.954667
--111.940032,33.410933
--112.071085,33.388629
--112.06625,33.449378
--112.002735,33.591497
--111.98497,33.46923
--111.998329,33.448576
--111.9885,33.604749
--112.037363,33.645521
--111.653307,33.418107
--111.964937,33.628921
--111.909279,33.451279
--112.134345,33.425253
--112.051809,33.53212
--112.170549,33.500706
--112.075113,33.452387
--112.053809,33.435509
--112.120293,33.452964
--112.20185,33.45156
--112.077234,33.4401
--112.082801,33.647405
--111.752142,33.653228
--111.724923,33.660615
--111.941054,33.630513
--112.741898,32.912062
--112.642116,33.750033
--112.24652,33.471415
--112.740534,33.982057
--112.255077,33.580839
--112.340518,33.634583
--111.989392,33.720504
--112.02094,33.693451
--111.865353,33.484321
--111.831664,33.380429
--111.976679,33.376578
--110.227604,36.726652
... 789283 lines suppressed ...


(sedona) 11/35: [CI] pre-commit(scala): auto add and standardize license headers (#1880)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 8998ea05cdb0aadfe4327eaaef93adc7d935f771
Author: John Bampton <jb...@users.noreply.github.com>
AuthorDate: Fri Mar 28 14:57:29 2025 +1000

    [CI] pre-commit(scala): auto add and standardize license headers (#1880)
---
 .pre-commit-config.yaml                            |  9 ++++++++
 examples/spark-sql/src/main/scala/RddExample.scala | 26 +++++++++++++---------
 examples/spark-sql/src/main/scala/SqlExample.scala | 26 +++++++++++++---------
 examples/spark-sql/src/main/scala/VizExample.scala | 26 +++++++++++++---------
 4 files changed, 54 insertions(+), 33 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f78518204c..00a6c1e621 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -78,6 +78,15 @@ repos:
           - --license-filepath
           - .github/workflows/license-templates/LICENSE.txt
           - --fuzzy-match-generates-todo
+      - id: insert-license
+        name: add license for all Scala files
+        files: \.scala$
+        args:
+          - --comment-style
+          - "/*| *| */"
+          - --license-filepath
+          - .github/workflows/license-templates/LICENSE.txt
+          - --fuzzy-match-generates-todo
       - id: insert-license
         name: add license for all TOML files
         files: \.toml$
diff --git a/examples/spark-sql/src/main/scala/RddExample.scala b/examples/spark-sql/src/main/scala/RddExample.scala
index d51133ff5f..7dc54860cf 100644
--- a/examples/spark-sql/src/main/scala/RddExample.scala
+++ b/examples/spark-sql/src/main/scala/RddExample.scala
@@ -1,16 +1,20 @@
-/**
+/*
+ * 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
  *
- * Licensed 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
  *
- * 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.
+ * 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.
  */
 
 import Main.resourceFolder
diff --git a/examples/spark-sql/src/main/scala/SqlExample.scala b/examples/spark-sql/src/main/scala/SqlExample.scala
index 1da9248cd2..367f06160f 100644
--- a/examples/spark-sql/src/main/scala/SqlExample.scala
+++ b/examples/spark-sql/src/main/scala/SqlExample.scala
@@ -1,16 +1,20 @@
-/**
+/*
+ * 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
  *
- * Licensed 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
  *
- * 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.
+ * 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.
  */
 
 import Main.resourceFolder
diff --git a/examples/spark-sql/src/main/scala/VizExample.scala b/examples/spark-sql/src/main/scala/VizExample.scala
index d0e1e498d6..b7b333a156 100644
--- a/examples/spark-sql/src/main/scala/VizExample.scala
+++ b/examples/spark-sql/src/main/scala/VizExample.scala
@@ -1,16 +1,20 @@
-/**
+/*
+ * 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
  *
- * Licensed 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
  *
- * 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.
+ * 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.
  */
 
 import Main.resourceFolder


(sedona) 29/35: [DOCS] Fix the wrong discord link

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit cc5b6f14984cb6ba4d2e7998355dfb08b7d40ca4
Author: Jia Yu <ji...@apache.org>
AuthorDate: Fri Apr 25 21:54:22 2025 -0700

    [DOCS] Fix the wrong discord link
---
 CONTRIBUTING.md           |  2 +-
 README.md                 | 17 ++++++++++-------
 docs/community/contact.md |  2 +-
 mkdocs.yml                |  6 +++---
 4 files changed, 15 insertions(+), 12 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c87a36b73a..d47e2bd277 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -39,6 +39,6 @@ It's always better to get aligned with the core devs before writing any code.
 
 ## Do you have questions about the source code?
 
-Feel free to create an issue or join the [Discord](https://share.hsforms.com/1Ndql_ZigTdmLlVQc_d1o4gqga4q) with questions!
+Feel free to create an issue or join the [Discord](https://discord.gg/9A3k5dEBsY) with questions!
 
 Thanks for reading and looking forward to collaborating with you!
diff --git a/README.md b/README.md
index ca75ffefec..eb6ffc790c 100644
--- a/README.md
+++ b/README.md
@@ -39,17 +39,20 @@
 
 ## Join the community
 
-Follow Sedona on Twitter for fresh news: [Sedona@Twitter](https://twitter.com/ApacheSedona)
+Everyone is welcome to join our community events. We have a community office hour every 4 weeks. Please register to the event you want to attend: https://bit.ly/3UBmxFY
 
-Join the Sedona Discord community: [![Apache Sedona Community Discord Server](https://dcbadge.vercel.app/api/server/9A3k5dEBsY)](https://share.hsforms.com/1Ndql_ZigTdmLlVQc_d1o4gqga4q)
+Please join our Discord community!
 
-Join the Sedona monthly community office hour: [Google Calendar](https://calendar.google.com/calendar/event?action=TEMPLATE&tmeid=NjI0cWgwcTZndnI1anAzYnFrNHY5Y2wyaTRfMjAyNDA0MDlUMTUwMDAwWiBjX2VmN2Q1NGY1MzA4YTRiN2YyNWFjMzNkMGY3ZWViNTRhM2E3ZjExNWI2ODlmYWY0ZDgyNDI1ZjNjYjVlZGU5MzVAZw&tmsrc=c_ef7d54f5308a4b7f25ac33d0f7eeb54a3a7f115b689faf4d82425f3cb5ede935%40group.calendar.google.com&scp=ALL), Tuesdays from 8 AM to 9 AM Pacific Time, every 4 weeks
+[![Apache Sedona Community Discord Server](https://dcbadge.vercel.app/api/server/9A3k5dEBsY)](https://discord.gg/9A3k5dEBsY)
 
-[Sedona JIRA](https://issues.apache.org/jira/projects/SEDONA): Bugs, Pull Requests, and other similar issues
+* [Apache Sedona@LinkedIn](https://www.linkedin.com/company/apache-sedona)
+* [Apache Sedona@X](https://X.com/ApacheSedona)
+* [Sedona JIRA](https://issues.apache.org/jira/projects/SEDONA): bug reports and feature requests
+* [Sedona GitHub Issues](https://github.com/apache/sedona/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen): bug reports and feature requests
+* [Sedona GitHub Discussion](https://github.com/apache/sedona/discussions): project development and general questions
+* [Sedona Mailing Lists](https://lists.apache.org/list.html?sedona.apache.org): [dev@sedona.apache.org](https://lists.apache.org/list.html?dev@sedona.apache.org): project development and general questions
 
-[Sedona Mailing Lists](https://lists.apache.org/list.html?sedona.apache.org): [dev@sedona.apache.org](https://lists.apache.org/list.html?dev@sedona.apache.org): project development, general questions or tutorials.
-
-* Please first subscribe and then post emails. To subscribe, please send an email (leave the subject and content blank) to dev-subscribe@sedona.apache.org
+For the mailing list, Please first subscribe and then post emails. To subscribe, please send an email (leave the subject and content blank) to [dev-subscribe@sedona.apache.org](mailto:dev-subscribe@sedona.apache.org?subject=Subscribe&body=Subscribe)
 
 ## What is Apache Sedona?
 
diff --git a/docs/community/contact.md b/docs/community/contact.md
index 7d81ae1e86..c93ff62e24 100644
--- a/docs/community/contact.md
+++ b/docs/community/contact.md
@@ -42,7 +42,7 @@ Everyone is welcome to join our community events. We have a community office hou
 
 ## Discord Server
 
-[![Apache Sedona Community Discord Server](https://dcbadge.vercel.app/api/server/9A3k5dEBsY)](https://share.hsforms.com/1Ndql_ZigTdmLlVQc_d1o4gqga4q)
+[![Apache Sedona Community Discord Server](https://dcbadge.vercel.app/api/server/9A3k5dEBsY)](https://discord.gg/9A3k5dEBsY)
 
 ## Get help
 
diff --git a/mkdocs.yml b/mkdocs.yml
index ea58221d10..d315bb5b18 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -97,7 +97,7 @@ nav:
                   - DataFrame Style functions: api/sql/DataFrameAPI.md
                   - Query optimization: api/sql/Optimizer.md
                   - Nearest-Neighbour searching: api/sql/NearestNeighbourSearching.md
-                  - "Spider:Spatial Data Generator": api/sql/Spider.md
+                  - 'Spider:Spatial Data Generator': api/sql/Spider.md
                   - Reading Legacy Parquet Files: api/sql/Reading-legacy-parquet.md
                   - Visualization:
                       - SedonaPyDeck: api/sql/Visualization_SedonaPyDeck.md
@@ -191,7 +191,7 @@ extra:
     - icon: fontawesome/brands/twitter
       link: 'https://twitter.com/ApacheSedona'
     - icon: fontawesome/brands/discord
-      link: 'https://share.hsforms.com/1Ndql_ZigTdmLlVQc_d1o4gqga4q'
+      link: 'https://discord.gg/9A3k5dEBsY'
   sedona:
     current_version: 1.7.1
     current_geotools: 1.7.1-28.5
@@ -235,4 +235,4 @@ plugins:
   - git-revision-date-localized:
       type: datetime
   - mike:
-      canonical_version: "latest"
+      canonical_version: 'latest'


(sedona) 31/35: [GH-1910] Import geopandas only when type checking (#1929)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 2a19d9410b08ad1306b9f11b7cf4f9a9d49642bc
Author: Olly Beagley <50...@users.noreply.github.com>
AuthorDate: Wed Apr 30 05:23:14 2025 +0100

    [GH-1910] Import geopandas only when type checking (#1929)
    
    Co-authored-by: Olly Beagley <50...@users.noreply.github.com>
---
 python/sedona/utils/geoarrow.py | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/python/sedona/utils/geoarrow.py b/python/sedona/utils/geoarrow.py
index b4a539dfa4..25ec012a8f 100644
--- a/python/sedona/utils/geoarrow.py
+++ b/python/sedona/utils/geoarrow.py
@@ -15,7 +15,7 @@
 #  specific language governing permissions and limitations
 #  under the License.
 import itertools
-from typing import List, Callable
+from typing import TYPE_CHECKING, Callable, List
 
 # We may be able to achieve streaming rather than complete materialization by using
 # with the ArrowStreamSerializer (instead of the ArrowCollectSerializer)
@@ -27,12 +27,14 @@ from pyspark.sql import DataFrame
 from pyspark.sql.types import StructType, StructField, DataType, ArrayType, MapType
 
 from sedona.sql.types import GeometryType
-import geopandas as gpd
 from pyspark.sql.pandas.types import (
     from_arrow_type,
 )
 from pyspark.sql.pandas.serializers import ArrowStreamPandasSerializer
 
+if TYPE_CHECKING:
+    import geopandas as gpd
+
 
 def dataframe_to_arrow(df, crs=None):
     """
@@ -272,7 +274,7 @@ def _deduplicate_field_names(dt: DataType) -> DataType:
         return dt
 
 
-def infer_schema(gdf: gpd.GeoDataFrame) -> StructType:
+def infer_schema(gdf: "gpd.GeoDataFrame") -> StructType:
     import pyarrow as pa
 
     fields = gdf.dtypes.reset_index().values.tolist()
@@ -307,7 +309,7 @@ def infer_schema(gdf: gpd.GeoDataFrame) -> StructType:
 
 # Modified backport from Spark 4.0
 # https://github.com/apache/spark/blob/3515b207c41d78194d11933cd04bddc21f8418dd/python/pyspark/sql/pandas/conversion.py#L632
-def create_spatial_dataframe(spark: SparkSession, gdf: gpd.GeoDataFrame) -> DataFrame:
+def create_spatial_dataframe(spark: SparkSession, gdf: "gpd.GeoDataFrame") -> DataFrame:
     from pyspark.sql.pandas.types import (
         to_arrow_type,
     )


(sedona) 05/35: [DOCS] add missing useSpheroid argument to ST_DBSCAN docs (#1870)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 98a99cb5ce3612240fab4f0e1d7559e24924a7aa
Author: James Willis <ja...@users.noreply.github.com>
AuthorDate: Wed Mar 19 13:12:46 2025 -0700

    [DOCS] add missing useSpheroid argument to ST_DBSCAN docs (#1870)
    
    Co-authored-by: jameswillis <ja...@wherobots.com>
---
 docs/api/sql/Function.md | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 2dc28f21ce..d4bda5c44a 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -1140,15 +1140,16 @@ Returns a struct containing the cluster ID and a boolean indicating if the recor
 
 - `epsilon` is the maximum distance between two points for them to be considered as part of the same cluster.
 - `minPoints` is the minimum number of neighbors a single record must have to form a cluster.
+- `useSpheroid` is whether to use ST_DistanceSpheroid or ST_Distance as the distance metric.
 
-Format: `ST_DBSCAN(geom: Geometry, epsilon: Double, minPoints: Integer)`
+Format: `ST_DBSCAN(geom: Geometry, epsilon: Double, minPoints: Integer, useSpheroid: Boolean)`
 
 Since: `v1.7.1`
 
 SQL Example
 
 ```sql
-SELECT ST_DBSCAN(geom, 1.0, 2)
+SELECT ST_DBSCAN(geom, 1.0, 2, False)
 ```
 
 Output:


(sedona) 32/35: [GH-1931] Move packaging module import to geopandas try-except block (#1932)

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch prepare-1.7.2
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 02e57ef54185fc1e711868261e249034655d0354
Author: Olly Beagley <50...@users.noreply.github.com>
AuthorDate: Wed Apr 30 19:09:34 2025 +0100

    [GH-1931] Move packaging module import to geopandas try-except block (#1932)
---
 python/sedona/maps/SedonaMapUtils.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/python/sedona/maps/SedonaMapUtils.py b/python/sedona/maps/SedonaMapUtils.py
index ddca721c45..d1385e8d98 100644
--- a/python/sedona/maps/SedonaMapUtils.py
+++ b/python/sedona/maps/SedonaMapUtils.py
@@ -19,7 +19,6 @@ import json
 
 from sedona.sql.types import GeometryType
 from sedona.utils.geoarrow import dataframe_to_arrow
-from packaging.version import parse
 
 
 class SedonaMapUtils:
@@ -46,6 +45,9 @@ class SedonaMapUtils:
             return data_pyarrow.to_pandas()
         try:
             import geopandas as gpd
+
+            # packaging is a dependency of geopandas
+            from packaging.version import parse
         except ImportError:
             msg = "GeoPandas is missing. You can install it manually or via apache-sedona[kepler-map] or apache-sedona[pydeck-map]."
             raise ImportError(msg) from None