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 2023/03/16 00:00:59 UTC

[sedona] branch master updated: [DOCS] Update the docs for 1.4.0 release (#799)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 4106230f [DOCS] Update the docs for 1.4.0 release (#799)
4106230f is described below

commit 4106230ffff7873d370e0acd6d20f0802487a53e
Author: Jia Yu <ji...@apache.org>
AuthorDate: Wed Mar 15 17:00:53 2023 -0700

    [DOCS] Update the docs for 1.4.0 release (#799)
---
 LICENSE                                            |   4 +
 README.md                                          |   2 -
 .../org/apache/sedona/common/utils/S2Utils.java    |  18 +
 docs/api/r-api.md                                  |   1 -
 docs/api/sql/DataFrameAPI.md                       |  75 ++
 docs/api/sql/Optimizer.md                          | 102 ++-
 docs/api/sql/Raster-loader.md                      | 223 ++---
 docs/api/sql/Raster-operators.md                   | 271 +++---
 .../api/sql/{Raster-loader.md => Raster-writer.md} | 166 +---
 docs/community/contact.md                          |   5 -
 docs/community/publish.md                          |  26 +-
 docs/setup/compile.md                              |  10 +-
 docs/setup/databricks.md                           |   9 +-
 docs/setup/emr.md                                  |  48 +
 docs/setup/flink/platform.md                       |   4 +-
 docs/setup/install-python.md                       |  10 +-
 docs/setup/install-r.md                            | 105 ---
 docs/setup/install-scala.md                        |   8 +-
 docs/setup/maven-coordinates.md                    | 106 +--
 docs/setup/release-notes.md                        | 119 +++
 docs/tutorial/core-python.md                       | 734 ---------------
 docs/tutorial/geopandas-shapely.md                 | 342 +++++++
 docs/tutorial/jupyter-notebook.md                  |   2 +-
 docs/tutorial/python-vector-osm.md                 |   2 +-
 docs/tutorial/raster.md                            |   6 +-
 docs/tutorial/rdd-r.md                             |  88 --
 docs/tutorial/rdd.md                               | 989 ++++++++++++++++-----
 docs/tutorial/sql-pure-sql.md                      |   2 +-
 docs/tutorial/sql-python.md                        | 562 ------------
 docs/tutorial/sql-r.md                             |  59 --
 docs/tutorial/sql.md                               | 413 ++++++---
 docs/tutorial/viz-r.md                             |  68 --
 licenses/LICENSE-pygeos                            |  29 +
 mkdocs.yml                                         |  34 +-
 .../spark/sql/sedona_sql/UDT/RasterUDT.scala       |  18 +
 35 files changed, 2077 insertions(+), 2583 deletions(-)

diff --git a/LICENSE b/LICENSE
index 58c092c0..13e2f5f4 100644
--- a/LICENSE
+++ b/LICENSE
@@ -213,6 +213,10 @@ BSD 2-Clause License
 --------------------------------------
 zeppelin/index.js (modified based on volume-leaflet: https://github.com/volumeint/helium-volume-leaflet)
 
+BSD 3-Clause License
+--------------------------------------
+python/src/pygeos/c_api.h (modified based on https://github.com/pygeos/pygeos/blob/master/src/c_api.h)
+
 
 No-copyright data used in unit tests
 --------------------------------------
diff --git a/README.md b/README.md
index 18ac8992..2eea967d 100644
--- a/README.md
+++ b/README.md
@@ -41,8 +41,6 @@ Please refer to [Sedona website](https://sedona.apache.org/latest-snapshot/setup
 
 ## Contact
 
-Feedback to improve Apache Sedona: [Google Form](https://docs.google.com/forms/d/e/1FAIpQLSeYHlc4cX5Pw0bIx2dQbhHDeWF2G2Wf7BgN_n29IzXsSzwptA/viewform)
-
 Twitter: [Sedona@Twitter](https://twitter.com/ApacheSedona)
 
 [![](https://dcbadge.vercel.app/api/server/9A3k5dEBsY)](https://discord.gg/9A3k5dEBsY)
diff --git a/common/src/main/java/org/apache/sedona/common/utils/S2Utils.java b/common/src/main/java/org/apache/sedona/common/utils/S2Utils.java
index 3a59dc59..51df201d 100644
--- a/common/src/main/java/org/apache/sedona/common/utils/S2Utils.java
+++ b/common/src/main/java/org/apache/sedona/common/utils/S2Utils.java
@@ -1,3 +1,21 @@
+/*
+ * 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 com.google.common.geometry.*;
diff --git a/docs/api/r-api.md b/docs/api/r-api.md
deleted file mode 100644
index 72967411..00000000
--- a/docs/api/r-api.md
+++ /dev/null
@@ -1 +0,0 @@
-Please read [R docs](../rdocs/)
\ No newline at end of file
diff --git a/docs/api/sql/DataFrameAPI.md b/docs/api/sql/DataFrameAPI.md
new file mode 100644
index 00000000..788b5696
--- /dev/null
+++ b/docs/api/sql/DataFrameAPI.md
@@ -0,0 +1,75 @@
+Sedona SQL functions can be used in a DataFrame style API similar to Spark functions.
+
+The following objects contain the exposed functions: `org.apache.spark.sql.sedona_sql.expressions.st_functions`, `org.apache.spark.sql.sedona_sql.expressions.st_constructors`, `org.apache.spark.sql.sedona_sql.expressions.st_predicates`, and `org.apache.spark.sql.sedona_sql.expressions.st_aggregates`.
+
+Every functions can take all `Column` arguments. Additionally, overloaded forms can commonly take a mix of `String` and other Scala types (such as `Double`) as arguments.
+
+In general the following rules apply (although check the documentation of specific functions for any exceptions):
+
+=== "Scala"
+	1. Every function returns a `Column` so that it can be used interchangeably with Spark functions as well as `DataFrame` methods such as `DataFrame.select` or `DataFrame.join`.
+	2. Every function has a form that takes all `Column` arguments.
+	These are the most versatile of the forms.
+	3. Most functions have a form that takes a mix of `String` arguments with other Scala types.
+
+=== "Python"
+
+	1. `Column` type arguments are passed straight through and are always accepted.
+	2. `str` type arguments are always assumed to be names of columns and are wrapped in a `Column` to support that.
+	If an actual string literal needs to be passed then it will need to be wrapped in a `Column` using `pyspark.sql.functions.lit`.
+	3. Any other types of arguments are checked on a per function basis. Generally, arguments that could reasonably support a python native type are accepted and passed through.	4. Shapely `Geometry` objects are not currently accepted in any of the functions.
+
+The exact mixture of argument types allowed is function specific.
+However, in these instances, all `String` arguments are assumed to be the names of columns and will be wrapped in a `Column` automatically.
+Non-`String` arguments are assumed to be literals that are passed to the sedona function. If you need to pass a `String` literal then you should use the all `Column` form of the sedona function and wrap the `String` literal in a `Column` with the `lit` Spark function.
+
+A short example of using this API (uses the `array_min` and `array_max` Spark functions):
+
+=== "Scala"
+
+	```scala
+	val values_df = spark.sql("SELECT array(0.0, 1.0, 2.0) AS values")
+	val min_value = array_min("values")
+	val max_value = array_max("values")
+	val point_df = values_df.select(ST_Point(min_value, max_value).as("point"))
+	```
+
+=== "Python"
+
+	```python3
+	from pyspark.sql import functions as f
+	
+	from sedona.sql import st_constructors as stc
+	
+	df = spark.sql("SELECT array(0.0, 1.0, 2.0) AS values")
+	
+	min_value = f.array_min("values")
+	max_value = f.array_max("values")
+	
+	df = df.select(stc.ST_Point(min_value, max_value).alias("point"))
+	```
+	
+The above code will generate the following dataframe:
+```
++-----------+
+|point      |
++-----------+
+|POINT (0 2)|
++-----------+
+```
+
+Some functions will take native python values and infer them as literals.
+For example:
+
+```python3
+df = df.select(stc.ST_Point(1.0, 3.0).alias("point"))
+```
+
+This will generate a dataframe with a constant point in a column:
+```
++-----------+
+|point      |
++-----------+
+|POINT (1 3)|
++-----------+
+```
\ No newline at end of file
diff --git a/docs/api/sql/Optimizer.md b/docs/api/sql/Optimizer.md
index a6dd2c7a..2019d51a 100644
--- a/docs/api/sql/Optimizer.md
+++ b/docs/api/sql/Optimizer.md
@@ -1,9 +1,11 @@
-# SedonaSQL query optimizer
 Sedona Spatial operators fully supports Apache SparkSQL query optimizer. It has the following query optimization features:
 
 * Automatically optimizes range join query and distance join query.
 * Automatically performs predicate pushdown.
 
+!!! tip
+	Sedona join performance is heavily affected by the number of partitions. If the join performance is not ideal, please increase the number of partitions by doing `df.repartition(XXX)` right after you create the original DataFrame.
+
 ## Range join
 Introduction: Find geometries from A and geometries from B such that each geometry pair satisfies a certain predicate. Most predicates supported by SedonaSQL can trigger a range join.
 
@@ -72,11 +74,15 @@ DistanceJoin pointshape1#12: geometry, pointshape2#33: geometry, 2.0, true
 !!!warning
 	Sedona doesn't control the distance's unit (degree or meter). It is same with the geometry. To change the geometry's unit, please transform the coordinate reference system. See [ST_Transform](Function.md#st_transform).
 
-## Broadcast join
-Introduction: Perform a range join or distance join but broadcast one of the sides of the join.
-This maintains the partitioning of the non-broadcast side and doesn't require a shuffle.
+## Broadcast index join
+
+Introduction: Perform a range join or distance join but broadcast one of the sides of the join. This maintains the partitioning of the non-broadcast side and doesn't require a shuffle.
+
+Sedona will create a spatial index on the broadcasted table.
+
 Sedona uses broadcast join only if the correct side has a broadcast hint.
-The supported join type - broadcast side combinations are
+The supported join type - broadcast side combinations are:
+
 * Inner - either side, preferring to broadcast left if both sides have the hint
 * Left semi - broadcast right
 * Left anti - broadcast right
@@ -117,9 +123,81 @@ BroadcastIndexJoin pointshape#52: geometry, BuildRight, BuildLeft, true, 2.0 ST_
 
 Note: If the distance is an expression, it is only evaluated on the first argument to ST_Distance (`pointDf1` above).
 
-## Predicate pushdown
+## Auotmatic broadcast index join
+
+When one table involved a spatial join query is smaller than a threadhold, Sedona will automatically choose broadcast index join instead of Sedona optimized join. The current threshold is controlled by [sedona.join.autoBroadcastJoinThreshold](../Parameter) and set to the same as `spark.sql.autoBroadcastJoinThreshold`.
+
+## Google S2 based equi-join
+
+If the performance of Sedona optimized join is not ideal, which is possibly caused by  complicated and overlapping geometries, you can resort to Sedona built-in Google S2-based equi-join. This equi-join leverages Spark's internal equi-join algorithm and might be performant in some cases given that the refinement step is optional.
+
+Please use the following steps:
+
+### 1. Generate S2 ids for both tables
+
+Use [ST_S2CellIds](../Function/#st_s2cellids) to generate cell IDs. Each geometry may produce one or more IDs.
+
+```sql
+SELECT id, geom, name, explode(ST_S2CellIDs(geom, 15)) as cellId
+FROM lefts
+```
+
+```sql
+SELECT id, geom, name, explode(ST_S2CellIDs(geom, 15)) as cellId
+FROM rights
+```
+
+### 2. Perform equi-join
+
+Join the two tables by their S2 cellId
+
+```sql
+SELECT lcs.id as lcs_id, lcs.geom as lcs_geom, lcs.name as lcs_name, rcs.id as rcs_id, rcs.geom as rcs_geom, rcs.name as rcs_name
+FROM lcs JOIN rcs ON lcs.cellId = rcs.cellId
+```
+
+
+### 3. Optional: Refine the result
 
-Introduction: Given a join query and a predicate in the same WHERE clause, first executes the Predicate as a filter, then executes the join query*
+Due to the nature of S2 Cellid, the equi-join results might have a few false-positives depending on the S2 level you choose. A smaller level indicates bigger cells, less exploded rows, but more false positives.
+
+To ensure the correctness, you can use [Spatial Predicate](../Predicate/) to filter out them. 
+
+```sql
+SELECT *
+FROM joinresult
+WHERE ST_Contains(lcs.geom, rcs.geom)
+```
+
+!!!tip
+	You can skip this step if you don't need 100% accuracy and want faster query speed.
+
+### 4. Optional: De-duplcate
+
+Due to the explode function used when we generate S2 Cell Ids, the resulting DataFrame may have several duplicate <lcs_geom, rcs_geom> matches. You can remove them by performing a GroupBy query.
+
+```sql
+SELECT lcs_id, rcs_id , first(lcs_geom), first(lcs_name), first(rcs_geom), first(rcs_name)
+FROM joinresult
+GROUP BY (lcs_id, rcs_id)
+```
+
+The `first` function is to take the first value from a number of duplicate values.
+
+If you don't have a unique id for each geometry, you can also group by geometry itself. See below:
+
+```sql
+SELECT lcs_geom, rcs_geom, first(lcs_name), first(rcs_name)
+FROM joinresult
+GROUP BY (lcs_geom, rcs_geom)
+```
+
+!!!note
+	If you are doing point-in-polygon join, this is not a problem and you can safely discard this issue. This issue only happens when you do polygon-polygon, polygon-linestring, linestring-linestring join.
+ 
+
+## Regular spatial predicate pushdown
+Introduction: Given a join query and a predicate in the same WHERE clause, first executes the Predicate as a filter, then executes the join query.
 
 Spark SQL Example:
 
@@ -142,13 +220,21 @@ RangeJoin polygonshape#20: geometry, pointshape#43: geometry, false
    +- *FileScan csv
 ```
 
-### GeoParquet
+## Push spatial predicates to GeoParquet
 
 Sedona supports spatial predicate push-down for GeoParquet files. When spatial filters were applied to dataframes backed by GeoParquet files, Sedona will use the
 [`bbox` properties in the metadata](https://github.com/opengeospatial/geoparquet/blob/v1.0.0-beta.1/format-specs/geoparquet.md#bbox)
 to determine if all data in the file will be discarded by the spatial predicate. This optimization could reduce the number of files scanned
 when the queried GeoParquet dataset was partitioned by spatial proximity.
 
+To maximize the performance of Sedona GeoParquet filter pushdown, we suggest that you sort the data by their geohash values (see [ST_GeoHash](../../api/sql/Function/#st_geohash)) and then save as a GeoParquet file. An example is as follows:
+
+```
+SELECT col1, col2, geom, ST_GeoHash(geom, 5) as geohash
+FROM spatialDf
+ORDER BY geohash
+```
+
 The following figure is the visualization of a GeoParquet dataset. `bbox`es of all GeoParquet files were plotted as blue rectangles and the query window was plotted as a red rectangle. Sedona will only scan 1 of the 6 files to
 answer queries such as `SELECT * FROM geoparquet_dataset WHERE ST_Intersects(geom, <query window>)`, thus only part of the data covered by the light green rectangle needs to be scanned.
 
diff --git a/docs/api/sql/Raster-loader.md b/docs/api/sql/Raster-loader.md
index aa72d80b..2da7cd48 100644
--- a/docs/api/sql/Raster-loader.md
+++ b/docs/api/sql/Raster-loader.md
@@ -1,7 +1,55 @@
-## Geotiff Dataframe Loader
+!!!note
+	Sedona loader are available in Scala, Java and Python and have the same APIs.
+
+Sedona provides two types of raster DataFrame loaders. They both use Sedona built-in data source but load raster images to different internal formats.
+
+## Load any raster to RasterUDT format
+
+The raster loader of Sedona leverages Spark built-in binary data source and works with several RS RasterUDT constrcutors to produce RasterUDT type. Each raster is a row in the resulting DataFrame and stored in a `RasterUDT` format.
+
+### Load raster to a binary DataFrame
+
+You can load any type of raster data using the code below. Then use the RS constructors below to create RasterUDT.
+
+```scala
+spark.read.format("binaryFile").load("/some/path/*.asc")
+```
+
+
+### RS_FromArcInfoAsciiGrid
+
+Introduction: Returns a raster geometry from an Arc Info Ascii Grid file.
+
+Format: `RS_FromArcInfoAsciiGrid(asc: Array[Byte])`
+
+Since: `v1.4.0`
+
+Spark SQL example:
+
+```scala
+var df = spark.read.format("binaryFile").load("/some/path/*.asc")
+df = df.withColumn("raster", f.expr("RS_FromArcInfoAsciiGrid(content)"))
+```
+
+
+### RS_FromGeoTiff
+
+Introduction: Returns a raster geometry from a GeoTiff file.
+
+Format: `RS_FromGeoTiff(asc: Array[Byte])`
+
+Since: `v1.4.0`
+
+Spark SQL example:
+
+```scala
+var df = spark.read.format("binaryFile").load("/some/path/*.tiff")
+df = df.withColumn("raster", f.expr("RS_FromGeoTiff(content)"))
+```
+
+## Load GeoTiff to Array[Double] format
 
-Introduction: The GeoTiff loader of Sedona is a Spark built-in data source. It can read a single geotiff image or 
-a number of geotiff images into a DataFrame.
+The `geotiff` loader of Sedona is a Spark built-in data source. It can read a single geotiff image or a number of geotiff images into a DataFrame. Each geotiff is a row in the resulting DataFrame and stored in an arrya of Double type format.
 
 Since: `v1.1.0`
 
@@ -76,87 +124,7 @@ Output:
 +--------------------+--------------------+------+-----+--------------------+-----+
 ```
 
-## Geotiff Dataframe Writer
-
-Introduction: You can write a GeoTiff dataframe as GeoTiff images using the spark `write` feature with the format `geotiff`.
-
-Since: `v1.2.1`
-
-Spark SQL example:
-
-The schema of the GeoTiff dataframe to be written can be one of the following two schemas:
-
-```html
- |-- image: struct (nullable = true)
- |    |-- origin: string (nullable = true)
- |    |-- Geometry: geometry (nullable = true)
- |    |-- height: integer (nullable = true)
- |    |-- width: integer (nullable = true)
- |    |-- nBands: integer (nullable = true)
- |    |-- data: array (nullable = true)
- |    |    |-- element: double (containsNull = true)
-```
-
-or
-
-```html
- |-- origin: string (nullable = true)
- |-- Geometry: geometry (nullable = true)
- |-- height: integer (nullable = true)
- |-- width: integer (nullable = true)
- |-- nBands: integer (nullable = true)
- |-- data: array (nullable = true)
- |    |-- element: double (containsNull = true)
-```
-
-Field names can be renamed, but schema should exactly match with one of the above two schemas. The output path could be a path to a directory where GeoTiff images will be saved. If the directory already exists, `write` should be called in `overwrite` mode.
-
-```scala
-var dfToWrite = sparkSession.read.format("geotiff").option("dropInvalid", true).option("readToCRS", "EPSG:4326").load("PATH_TO_INPUT_GEOTIFF_IMAGES")
-dfToWrite.write.format("geotiff").save("DESTINATION_PATH")
-```
-
-You can override an existing path with the following approach:
-
-```scala
-dfToWrite.write.mode("overwrite").format("geotiff").save("DESTINATION_PATH")
-```
-
-You can also extract the columns nested within `image` column and write the dataframe as GeoTiff image.
-
-```scala
-dfToWrite = dfToWrite.selectExpr("image.origin as origin","image.geometry as geometry", "image.height as height", "image.width as width", "image.data as data", "image.nBands as nBands")
-dfToWrite.write.mode("overwrite").format("geotiff").save("DESTINATION_PATH")
-```
-
-If you want the saved GeoTiff images not to be distributed into multiple partitions, you can call coalesce to merge all files in a single partition.
-
-```scala
-dfToWrite.coalesce(1).write.mode("overwrite").format("geotiff").save("DESTINATION_PATH")
-```
-
-In case, you rename the columns of GeoTiff dataframe, you can set the corresponding column names with the `option` parameter. All available optional parameters are listed below:
-
-```html
- |-- writeToCRS: (Default value "EPSG:4326") => Coordinate reference system of the geometry coordinates representing the location of the Geotiff.
- |-- fieldImage: (Default value "image") => Indicates the image column of GeoTiff DataFrame.
- |-- fieldOrigin: (Default value "origin") => Indicates the origin column of GeoTiff DataFrame.
- |-- fieldNBands: (Default value "nBands") => Indicates the nBands column of GeoTiff DataFrame.
- |-- fieldWidth: (Default value "width") => Indicates the width column of GeoTiff DataFrame.
- |-- fieldHeight: (Default value "height") => Indicates the height column of GeoTiff DataFrame.
- |-- fieldGeometry: (Default value "geometry") => Indicates the geometry column of GeoTiff DataFrame.
- |-- fieldData: (Default value "data") => Indicates the data column of GeoTiff DataFrame.
-```
-
-An example:
-
-```scala
-dfToWrite = sparkSession.read.format("geotiff").option("dropInvalid", true).option("readToCRS", "EPSG:4326").load("PATH_TO_INPUT_GEOTIFF_IMAGES")
-dfToWrite = dfToWrite.selectExpr("image.origin as source","ST_GeomFromWkt(image.geometry) as geom", "image.height as height", "image.width as width", "image.data as data", "image.nBands as bands")
-dfToWrite.write.mode("overwrite").format("geotiff").option("writeToCRS", "EPSG:4326").option("fieldOrigin", "source").option("fieldGeometry", "geom").option("fieldNBands", "bands").save("DESTINATION_PATH")
-```
-
-## RS_Array
+### RS_Array
 
 Introduction: Create an array that is filled by the given value
 
@@ -170,69 +138,7 @@ Spark SQL example:
 SELECT RS_Array(height * width, 0.0)
 ```
 
-## RS_Base64
-
-Introduction: Return a Base64 String from a geotiff image
-
-Format: `RS_Base64 (height:Int, width:Int, redBand: Array[Double], greenBand: Array[Double], blackBand: Array[Double],
-optional: alphaBand: Array[Double])`
-
-Since: `v1.1.0`
-
-Spark SQL example:
-```scala
-val BandDF = spark.sql("select RS_Base64(h, w, band1, band2, RS_Array(h*w, 0)) as baseString from dataframe")
-BandDF.show()
-```
-
-Output:
-
-```html
-+--------------------+
-|          baseString|
-+--------------------+
-|QJCIAAAAAABAkDwAA...|
-|QJOoAAAAAABAlEgAA...|
-+--------------------+
-```
-
-!!!note
-    Although the 3 RGB bands are mandatory, you can use [RS_Array(h*w, 0.0)](#rs_array) to create an array (zeroed out, size = h * w) as input.
-
-
-## RS_FromArcInfoAsciiGrid
-
-Introduction: Returns a raster geometry from an Arc Info Ascii Grid file.
-
-Format: `RS_FromArcInfoAsciiGrid(asc: Array[Byte])`
-
-Since: `v1.4.0`
-
-Spark SQL example:
-
-```scala
-val df = spark.read.format("binaryFile").load("/some/path/*.asc")
-  .withColumn("raster", f.expr("RS_FromArcInfoAsciiGrid(content)"))
-```
-
-
-## RS_FromGeoTiff
-
-Introduction: Returns a raster geometry from a GeoTiff file.
-
-Format: `RS_FromGeoTiff(asc: Array[Byte])`
-
-Since: `v1.4.0`
-
-Spark SQL example:
-
-```scala
-val df = spark.read.format("binaryFile").load("/some/path/*.tiff")
-  .withColumn("raster", f.expr("RS_FromGeoTiff(content)"))
-```
-
-
-## RS_GetBand
+### RS_GetBand
 
 Introduction: Return a particular band from Geotiff Dataframe
 
@@ -262,26 +168,3 @@ Output:
 |[1258.0, 1298.0, ...|
 +--------------------+
 ```
-
-## RS_HTML
-
-Introduction: Return a html img tag with the base64 string embedded
-
-Format: `RS_HTML(base64:String, optional: width_in_px:String)`
-
-Spark SQL example:
-
-```scala
-df.selectExpr("RS_HTML(encodedstring, '300') as htmlstring" ).show()
-```
-
-Output:
-
-```html
-+--------------------+
-|          htmlstring|
-+--------------------+
-|<img src="data:im...|
-|<img src="data:im...|
-+--------------------+
-```
diff --git a/docs/api/sql/Raster-operators.md b/docs/api/sql/Raster-operators.md
index 06db6b65..1011c303 100644
--- a/docs/api/sql/Raster-operators.md
+++ b/docs/api/sql/Raster-operators.md
@@ -1,4 +1,119 @@
-## RS_Add
+## RasterUDT based operators
+
+### RS_Envelope
+
+Introduction: Returns the envelope of the raster as a Geometry.
+
+Format: `RS_Envelope (raster: Raster)`
+
+Since: `v1.4.0`
+
+Spark SQL example:
+```sql
+SELECT RS_Envelope(raster) FROM raster_table
+```
+Output:
+```
+POLYGON((0 0,20 0,20 60,0 60,0 0))
+```
+
+### RS_NumBands
+
+Introduction: Returns the number of the bands in the raster.
+
+Format: `RS_NumBands (raster: Raster)`
+
+Since: `v1.4.0`
+
+Spark SQL example:
+```sql
+SELECT RS_NumBands(raster) FROM raster_table
+```
+
+Output:
+```
+4
+```
+
+### RS_Value
+
+Introduction: Returns the value at the given point in the raster.
+If no band number is specified it defaults to 1. 
+
+Format: `RS_Value (raster: Raster, point: Geometry)`
+
+Format: `RS_Value (raster: Raster, point: Geometry, band: Int)`
+
+Since: `v1.4.0`
+
+Spark SQL example:
+```sql
+SELECT RS_Value(raster, ST_Point(-13077301.685, 4002565.802)) FROM raster_table
+```
+
+Output:
+```
+5.0
+```
+
+### RS_Values
+
+Introduction: Returns the values at the given points in the raster.
+If no band number is specified it defaults to 1.
+
+RS_Values is similar to RS_Value but operates on an array of points.
+RS_Values can be significantly faster since a raster only has to be loaded once for several points.
+
+Format: `RS_Values (raster: Raster, points: Array[Geometry])`
+
+Format: `RS_Values (raster: Raster, points: Array[Geometry], band: Int)`
+
+Since: `v1.4.0`
+
+Spark SQL example:
+```sql
+SELECT RS_Values(raster, Array(ST_Point(-1307.5, 400.8), ST_Point(-1403.3, 399.1)))
+FROM raster_table
+```
+
+Output:
+```
+Array(5.0, 3.0)
+```
+
+Spark SQL example for joining a point dataset with a raster dataset:
+```scala
+val pointDf = spark.read...
+val rasterDf = spark.read.format("binaryFile").load("/some/path/*.tiff")
+  .withColumn("raster", expr("RS_FromGeoTiff(content)"))
+  .withColumn("envelope", expr("RS_Envelope(raster)"))
+
+// Join the points with the raster extent and aggregate points to arrays.
+// We only use the path and envelope of the raster to keep the shuffle as small as possible.
+val df = pointDf.join(rasterDf.select("path", "envelope"), expr("ST_Within(point_geom, envelope)"))
+  .groupBy("path")
+  .agg(collect_list("point_geom").alias("point"), collect_list("point_id").alias("id"))
+
+df.join(rasterDf, "path")
+  .selectExpr("explode(arrays_zip(id, point, RS_Values(raster, point))) as result")
+  .selectExpr("result.*")
+  .show()
+```
+
+Output:
+```
++----+------------+-------+
+| id | point      | value |
++----+------------+-------+
+|  4 | POINT(1 1) |   3.0 |
+|  5 | POINT(2 2) |   7.0 |
++----+------------+-------+
+```
+
+
+## Array[Double] based operators
+
+### RS_Add
 
 Introduction: Add two spectral bands in a Geotiff image 
 
@@ -13,7 +128,7 @@ val sumDF = spark.sql("select RS_Add(band1, band2) as sumOfBands from dataframe"
 
 ```
 
-## RS_Append
+### RS_Append
 
 Introduction: Appends a new band to the end of Geotiff image data and returns the new data. The new band to be appended can be a normalized difference index between two bands (example: NBR, NDBI). Normalized difference index between two bands can be calculated with RS_NormalizedDifference operator described earlier in this page. Specific bands can be retrieved using RS_GetBand operator described [here](../Raster-loader/).
 
@@ -28,7 +143,7 @@ val dfAppended = spark.sql("select RS_Append(data, normalizedDifference, nBands)
 
 ```
 
-## RS_BitwiseAND
+### RS_BitwiseAND
 
 Introduction: Find Bitwise AND between two bands of Geotiff image
 
@@ -43,7 +158,7 @@ val biwiseandDF = spark.sql("select RS_BitwiseAND(band1, band2) as andvalue from
 
 ```
 
-## RS_BitwiseOR
+### RS_BitwiseOR
 
 Introduction: Find Bitwise OR between two bands of Geotiff image
 
@@ -58,7 +173,7 @@ val biwiseorDF = spark.sql("select RS_BitwiseOR(band1, band2) as or from datafra
 
 ```
 
-## RS_Count
+### RS_Count
 
 Introduction: Returns count of a particular value from a spectral band in a raster image
 
@@ -73,7 +188,7 @@ val countDF = spark.sql("select RS_Count(band1, target) as count from dataframe"
 
 ```
 
-## RS_Divide
+### RS_Divide
 
 Introduction: Divide band1 with band2 from a geotiff image
 
@@ -88,24 +203,7 @@ val multiplyDF = spark.sql("select RS_Divide(band1, band2) as divideBands from d
 
 ```
 
-## RS_Envelope
-
-Introduction: Returns the envelope of the raster as a Geometry.
-
-Format: `RS_Envelope (raster: Raster)`
-
-Since: `v1.4.0`
-
-Spark SQL example:
-```sql
-SELECT RS_Envelope(raster) FROM raster_table
-```
-Output:
-```
-POLYGON((0 0,20 0,20 60,0 60,0 0))
-```
-
-## RS_FetchRegion
+### RS_FetchRegion
 
 Introduction: Fetch a subset of region from given Geotiff image based on minimumX, minimumY, maximumX and maximumY index as well original height and width of image
 
@@ -119,7 +217,7 @@ Spark SQL example:
 val region = spark.sql("select RS_FetchRegion(Band,Array(0, 0, 1, 2),Array(3, 3)) as Region from dataframe")
 ```
 
-## RS_GreaterThan
+### RS_GreaterThan
 
 Introduction: Mask all the values with 1 which are greater than a particular target value
 
@@ -134,7 +232,7 @@ val greaterDF = spark.sql("select RS_GreaterThan(band, target) as maskedvalues f
 
 ```
 
-## RS_GreaterThanEqual
+### RS_GreaterThanEqual
 
 Introduction: Mask all the values with 1 which are greater than equal to a particular target value
 
@@ -149,7 +247,7 @@ val greaterEqualDF = spark.sql("select RS_GreaterThanEqual(band, target) as mask
 
 ```
 
-## RS_LessThan
+### RS_LessThan
 
 Introduction: Mask all the values with 1 which are less than a particular target value
 
@@ -164,7 +262,7 @@ val lessDF = spark.sql("select RS_LessThan(band, target) as maskedvalues from da
 
 ```
 
-## RS_LessThanEqual
+### RS_LessThanEqual
 
 Introduction: Mask all the values with 1 which are less than equal to a particular target value
 
@@ -179,7 +277,7 @@ val lessEqualDF = spark.sql("select RS_LessThanEqual(band, target) as maskedvalu
 
 ```
 
-## RS_LogicalDifference
+### RS_LogicalDifference
 
 Introduction: Return value from band 1 if a value in band1 and band2 are different, else return 0
 
@@ -194,7 +292,7 @@ val logicalDifference = spark.sql("select RS_LogicalDifference(band1, band2) as
 
 ```
 
-## RS_LogicalOver
+### RS_LogicalOver
 
 Introduction: Return value from band1 if it's not equal to 0, else return band2 value
 
@@ -209,7 +307,7 @@ val logicalOver = spark.sql("select RS_LogicalOver(band1, band2) as logover from
 
 ```
 
-## RS_Mean
+### RS_Mean
 
 Introduction: Returns Mean value for a spectral band in a Geotiff image
 
@@ -224,7 +322,7 @@ val meanDF = spark.sql("select RS_Mean(band) as mean from dataframe")
 
 ```
 
-## RS_Mode
+### RS_Mode
 
 Introduction: Returns Mode from a spectral band in a Geotiff image in form of an array
 
@@ -239,7 +337,7 @@ val modeDF = spark.sql("select RS_Mode(band) as mode from dataframe")
 
 ```
 
-## RS_Modulo
+### RS_Modulo
 
 Introduction: Find modulo of pixels with respect to a particular value
 
@@ -254,7 +352,7 @@ val moduloDF = spark.sql("select RS_Modulo(band, target) as modulo from datafram
 
 ```
 
-## RS_Multiply
+### RS_Multiply
 
 Introduction: Multiply two spectral bands in a Geotiff image
 
@@ -269,7 +367,7 @@ val multiplyDF = spark.sql("select RS_Multiply(band1, band2) as multiplyBands fr
 
 ```
 
-## RS_MultiplyFactor
+### RS_MultiplyFactor
 
 Introduction: Multiply a factor to a spectral band in a geotiff image
 
@@ -284,7 +382,7 @@ val multiplyFactorDF = spark.sql("select RS_MultiplyFactor(band1, 2) as multiply
 
 ```
 
-## RS_Normalize
+### RS_Normalize
 
 Introduction: Normalize the value in the array to [0, 255]
 
@@ -295,7 +393,7 @@ Spark SQL example
 SELECT RS_Normalize(band)
 ```
 
-## RS_NormalizedDifference
+### RS_NormalizedDifference
 
 Introduction: Returns Normalized Difference between two bands(band2 and band1) in a Geotiff image(example: NDVI, NDBI)
 
@@ -310,25 +408,7 @@ val normalizedDF = spark.sql("select RS_NormalizedDifference(band1, band2) as no
 
 ```
 
-## RS_NumBands
-
-Introduction: Returns the number of the bands in the raster.
-
-Format: `RS_NumBands (raster: Raster)`
-
-Since: `v1.4.0`
-
-Spark SQL example:
-```sql
-SELECT RS_NumBands(raster) FROM raster_table
-```
-
-Output:
-```
-4
-```
-
-## RS_SquareRoot
+### RS_SquareRoot
 
 Introduction: Find Square root of band values in a geotiff image 
 
@@ -343,7 +423,7 @@ val rootDF = spark.sql("select RS_SquareRoot(band) as squareroot from dataframe"
 
 ```
 
-## RS_Subtract
+### RS_Subtract
 
 Introduction: Subtract two spectral bands in a Geotiff image(band2 - band1)
 
@@ -356,79 +436,4 @@ Spark SQL example:
 
 val subtractDF = spark.sql("select RS_Subtract(band1, band2) as differenceOfOfBands from dataframe")
 
-```
-
-## RS_Value
-
-Introduction: Returns the value at the given point in the raster.
-If no band number is specified it defaults to 1. 
-
-Format: `RS_Value (raster: Raster, point: Geometry)`
-
-Format: `RS_Value (raster: Raster, point: Geometry, band: Int)`
-
-Since: `v1.4.0`
-
-Spark SQL example:
-```sql
-SELECT RS_Value(raster, ST_Point(-13077301.685, 4002565.802)) FROM raster_table
-```
-
-Output:
-```
-5.0
-```
-
-## RS_Values
-
-Introduction: Returns the values at the given points in the raster.
-If no band number is specified it defaults to 1.
-
-RS_Values is similar to RS_Value but operates on an array of points.
-RS_Values can be significantly faster since a raster only has to be loaded once for several points.
-
-Format: `RS_Values (raster: Raster, points: Array[Geometry])`
-
-Format: `RS_Values (raster: Raster, points: Array[Geometry], band: Int)`
-
-Since: `v1.4.0`
-
-Spark SQL example:
-```sql
-SELECT RS_Values(raster, Array(ST_Point(-1307.5, 400.8), ST_Point(-1403.3, 399.1)))
-FROM raster_table
-```
-
-Output:
-```
-Array(5.0, 3.0)
-```
-
-Spark SQL example for joining a point dataset with a raster dataset:
-```scala
-val pointDf = spark.read...
-val rasterDf = spark.read.format("binaryFile").load("/some/path/*.tiff")
-  .withColumn("raster", expr("RS_FromGeoTiff(content)"))
-  .withColumn("envelope", expr("RS_Envelope(raster)"))
-
-// Join the points with the raster extent and aggregate points to arrays.
-// We only use the path and envelope of the raster to keep the shuffle as small as possible.
-val df = pointDf.join(rasterDf.select("path", "envelope"), expr("ST_Within(point_geom, envelope)"))
-  .groupBy("path")
-  .agg(collect_list("point_geom").alias("point"), collect_list("point_id").alias("id"))
-
-df.join(rasterDf, "path")
-  .selectExpr("explode(arrays_zip(id, point, RS_Values(raster, point))) as result")
-  .selectExpr("result.*")
-  .show()
-```
-
-Output:
-```
-+----+------------+-------+
-| id | point      | value |
-+----+------------+-------+
-|  4 | POINT(1 1) |   3.0 |
-|  5 | POINT(2 2) |   7.0 |
-+----+------------+-------+
-```
+```
\ No newline at end of file
diff --git a/docs/api/sql/Raster-loader.md b/docs/api/sql/Raster-writer.md
similarity index 50%
copy from docs/api/sql/Raster-loader.md
copy to docs/api/sql/Raster-writer.md
index aa72d80b..a2663c31 100644
--- a/docs/api/sql/Raster-loader.md
+++ b/docs/api/sql/Raster-writer.md
@@ -1,84 +1,9 @@
-## Geotiff Dataframe Loader
-
-Introduction: The GeoTiff loader of Sedona is a Spark built-in data source. It can read a single geotiff image or 
-a number of geotiff images into a DataFrame.
-
-Since: `v1.1.0`
-
-Spark SQL example:
-
-The input path could be a path to a single GeoTiff image or a directory of GeoTiff images.
- You can optionally append an option to drop invalid images. The geometry bound of each image is automatically loaded
-as a Sedona geometry and is transformed to WGS84 (EPSG:4326) reference system.
-
-```scala
-var geotiffDF = sparkSession.read.format("geotiff").option("dropInvalid", true).load("YOUR_PATH")
-geotiffDF.printSchema()
-```
-
-Output:
-
-```html
- |-- image: struct (nullable = true)
- |    |-- origin: string (nullable = true)
- |    |-- Geometry: string (nullable = true)
- |    |-- height: integer (nullable = true)
- |    |-- width: integer (nullable = true)
- |    |-- nBands: integer (nullable = true)
- |    |-- data: array (nullable = true)
- |    |    |-- element: double (containsNull = true)
-```
-
-There are three more optional parameters for reading GeoTiff:
-
-```html
- |-- readfromCRS: Coordinate reference system of the geometry coordinates representing the location of the Geotiff. An example value of readfromCRS is EPSG:4326.
- |-- readToCRS: If you want to transform the Geotiff location geometry coordinates to a different coordinate reference system, you can define the target coordinate reference system with this option.
- |-- disableErrorInCRS: (Default value false) => Indicates whether to ignore errors in CRS transformation.
-```
-
-An example with all GeoTiff read options:
-
-```scala
-var geotiffDF = sparkSession.read.format("geotiff").option("dropInvalid", true).option("readFromCRS", "EPSG:4499").option("readToCRS", "EPSG:4326").option("disableErrorInCRS", true).load("YOUR_PATH")
-geotiffDF.printSchema()
-```
-
-Output:
-
-```html
- |-- image: struct (nullable = true)
- |    |-- origin: string (nullable = true)
- |    |-- Geometry: string (nullable = true)
- |    |-- height: integer (nullable = true)
- |    |-- width: integer (nullable = true)
- |    |-- nBands: integer (nullable = true)
- |    |-- data: array (nullable = true)
- |    |    |-- element: double (containsNull = true)
-```
-
-You can also select sub-attributes individually to construct a new DataFrame
-
-```scala
-geotiffDF = geotiffDF.selectExpr("image.origin as origin","ST_GeomFromWkt(image.geometry) as Geom", "image.height as height", "image.width as width", "image.data as data", "image.nBands as bands")
-geotiffDF.createOrReplaceTempView("GeotiffDataframe")
-geotiffDF.show()
-```
-
-Output:
-
-```html
-+--------------------+--------------------+------+-----+--------------------+-----+
-|              origin|                Geom|height|width|                data|bands|
-+--------------------+--------------------+------+-----+--------------------+-----+
-|file:///home/hp/D...|POLYGON ((-58.699...|    32|   32|[1058.0, 1039.0, ...|    4|
-|file:///home/hp/D...|POLYGON ((-58.297...|    32|   32|[1258.0, 1298.0, ...|    4|
-+--------------------+--------------------+------+-----+--------------------+-----+
-```
-
-## Geotiff Dataframe Writer
+!!!note
+	Sedona writers are available in Scala, Java and Python and have the same APIs.
+	
+## Write Array[Double] to GeoTiff files
 
-Introduction: You can write a GeoTiff dataframe as GeoTiff images using the spark `write` feature with the format `geotiff`.
+Introduction: You can write a GeoTiff dataframe as GeoTiff images using the spark `write` feature with the format `geotiff`. The geotiff raster column needs to be an array of double type data.
 
 Since: `v1.2.1`
 
@@ -156,21 +81,9 @@ dfToWrite = dfToWrite.selectExpr("image.origin as source","ST_GeomFromWkt(image.
 dfToWrite.write.mode("overwrite").format("geotiff").option("writeToCRS", "EPSG:4326").option("fieldOrigin", "source").option("fieldGeometry", "geom").option("fieldNBands", "bands").save("DESTINATION_PATH")
 ```
 
-## RS_Array
+## Write Array[Double] to other formats
 
-Introduction: Create an array that is filled by the given value
-
-Format: `RS_Array(length:Int, value: Decimal)`
-
-Since: `v1.1.0`
-
-Spark SQL example:
-
-```scala
-SELECT RS_Array(height * width, 0.0)
-```
-
-## RS_Base64
+### RS_Base64
 
 Introduction: Return a Base64 String from a geotiff image
 
@@ -200,70 +113,7 @@ Output:
     Although the 3 RGB bands are mandatory, you can use [RS_Array(h*w, 0.0)](#rs_array) to create an array (zeroed out, size = h * w) as input.
 
 
-## RS_FromArcInfoAsciiGrid
-
-Introduction: Returns a raster geometry from an Arc Info Ascii Grid file.
-
-Format: `RS_FromArcInfoAsciiGrid(asc: Array[Byte])`
-
-Since: `v1.4.0`
-
-Spark SQL example:
-
-```scala
-val df = spark.read.format("binaryFile").load("/some/path/*.asc")
-  .withColumn("raster", f.expr("RS_FromArcInfoAsciiGrid(content)"))
-```
-
-
-## RS_FromGeoTiff
-
-Introduction: Returns a raster geometry from a GeoTiff file.
-
-Format: `RS_FromGeoTiff(asc: Array[Byte])`
-
-Since: `v1.4.0`
-
-Spark SQL example:
-
-```scala
-val df = spark.read.format("binaryFile").load("/some/path/*.tiff")
-  .withColumn("raster", f.expr("RS_FromGeoTiff(content)"))
-```
-
-
-## RS_GetBand
-
-Introduction: Return a particular band from Geotiff Dataframe
-
-The number of total bands can be obtained from the GeoTiff loader
-
-Format: `RS_GetBand (allBandValues: Array[Double], targetBand:Int, totalBands:Int)`
-
-Since: `v1.1.0`
-
-!!!note
-	Index of targetBand starts from 1 (instead of 0). Index of the first band is 1.
-
-Spark SQL example:
-
-```scala
-val BandDF = spark.sql("select RS_GetBand(data, 2, Band) as targetBand from GeotiffDataframe")
-BandDF.show()
-```
-
-Output:
-
-```html
-+--------------------+
-|          targetBand|
-+--------------------+
-|[1058.0, 1039.0, ...|
-|[1258.0, 1298.0, ...|
-+--------------------+
-```
-
-## RS_HTML
+### RS_HTML
 
 Introduction: Return a html img tag with the base64 string embedded
 
diff --git a/docs/community/contact.md b/docs/community/contact.md
index 7cde82b4..dfb0305d 100644
--- a/docs/community/contact.md
+++ b/docs/community/contact.md
@@ -9,11 +9,6 @@ You can participate in the community as follows:
 * Report bugs and submit patches.
 * Contribute code and documentation.
 
-## Feedback
-
-Feedback to improve Apache Sedona: [Google Form](https://docs.google.com/forms/d/e/1FAIpQLSeYHlc4cX5Pw0bIx2dQbhHDeWF2G2Wf7BgN_n29IzXsSzwptA/viewform)
-
-
 ## Twitter
 
 [Apache Sedona@Twitter](https://twitter.com/ApacheSedona)
diff --git a/docs/community/publish.md b/docs/community/publish.md
index c9f3f261..fb3eae8c 100644
--- a/docs/community/publish.md
+++ b/docs/community/publish.md
@@ -22,15 +22,16 @@ chmod 777 create-release.sh
 1. Run the following script:
 ```bash
 #!/bin/bash
-wget -q https://dlcdn.apache.org//creadur/apache-rat-$RAT_VERSION/apache-rat-0.15-bin.tar.gz
+wget -q https://dlcdn.apache.org//creadur/apache-rat-0.15/apache-rat-0.15-bin.tar.gz
 tar -xvf  apache-rat-0.15-bin.tar.gz
 git clone --shared --branch master https://github.com/apache/sedona.git sedona-src
-java -jar apache-rat-0.15.jar -d sedona-src > report.txt
+java -jar apache-rat-0.15/apache-rat-0.15.jar -d sedona-src > report.txt
 ```
 2. Read the generated report.txt file and make sure all source code files have ASF header.
 3. Delete the generated report and cloned files
 ```bash
 #!/bin/bash
+rm -rf apache-rat-0.15
 rm -rf sedona-src
 rm report.txt
 ```
@@ -41,7 +42,8 @@ Make sure the Sedona version in the following files are {{ sedona_create_release
 
 1. https://github.com/apache/sedona/blob/master/python/sedona/version.py
 2. https://github.com/apache/sedona/blob/master/R/DESCRIPTION
-3. https://github.com/apache/sedona/blob/master/zeppelin/package.json
+3. https://github.com/apache/sedona/blob/master/R/R/dependencies.R#L42
+4. https://github.com/apache/sedona/blob/master/zeppelin/package.json
 
 
 ## 3. Update mkdocs.yml
@@ -107,18 +109,21 @@ echo "Compiling the source code..."
 mkdir apache-sedona-{{ sedona_create_release.current_version }}-bin
 
 cd apache-sedona-{{ sedona_create_release.current_version }}-src && mvn -q clean install -DskipTests -Dscala=2.12 && cd ..
+cp apache-sedona-{{ sedona_create_release.current_version }}-src/common/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
 cp apache-sedona-{{ sedona_create_release.current_version }}-src/core/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
 cp apache-sedona-{{ sedona_create_release.current_version }}-src/sql/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
 cp apache-sedona-{{ sedona_create_release.current_version }}-src/viz/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
 cp apache-sedona-{{ sedona_create_release.current_version }}-src/python-adapter/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
+cp apache-sedona-{{ sedona_create_release.current_version }}-src/spark-shaded/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
 cp apache-sedona-{{ sedona_create_release.current_version }}-src/flink/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
+cp apache-sedona-{{ sedona_create_release.current_version }}-src/flink-shaded/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
 
 cd apache-sedona-{{ sedona_create_release.current_version }}-src && mvn -q clean install -DskipTests -Dscala=2.13 && cd ..
 cp apache-sedona-{{ sedona_create_release.current_version }}-src/core/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
 cp apache-sedona-{{ sedona_create_release.current_version }}-src/sql/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
 cp apache-sedona-{{ sedona_create_release.current_version }}-src/viz/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
 cp apache-sedona-{{ sedona_create_release.current_version }}-src/python-adapter/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
-cp apache-sedona-{{ sedona_create_release.current_version }}-src/flink/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
+cp apache-sedona-{{ sedona_create_release.current_version }}-src/spark-shaded/target/sedona-*{{ sedona_create_release.current_version}}.jar apache-sedona-{{ sedona_create_release.current_version }}-bin/
 
 tar czf apache-sedona-{{ sedona_create_release.current_version }}-bin.tar.gz apache-sedona-{{ sedona_create_release.current_version }}-bin
 shasum -a 512 apache-sedona-{{ sedona_create_release.current_version }}-src.tar.gz > apache-sedona-{{ sedona_create_release.current_version }}-src.tar.gz.sha512
@@ -377,7 +382,7 @@ username=admin
 password=admin123
 stagingid=1027
 
-artifacts=(parent core-3.0_2.12 core-3.0_2.13 sql-3.0_2.12 sql-3.0_2.13 viz-3.0_2.12 viz-3.0_2.13 python-adapter-3.0_2.12 python-adapter-3.0_2.13 common flink_2.12)
+artifacts=(parent core-3.0_2.12 core-3.0_2.13 sql-3.0_2.12 sql-3.0_2.13 viz-3.0_2.12 viz-3.0_2.13 python-adapter-3.0_2.12 python-adapter-3.0_2.13 common flink_2.12 spark-shaded-3.0_2.12 spark-shaded-3.0_2.13 flink-shaded_2.12)
 filenames=(.pom .jar -javadoc.jar)
 
 echo "Re-uploading signatures to fix *failureMessage Invalid Signature*"
@@ -451,16 +456,7 @@ Please do not commit these generated docs to Sedona GitHub.
 
 ### Compile R html docs
 
-1. Make sure you install R, tree and curl on your Ubuntu machine. On Mac, just do `brew install tree`
-```
-sudo apt install littler tree libcurl4-openssl-dev
-```
-2. In the Sedona root directory, run the script below. This will create `rdocs` folder in Sedona `/docs/api/rdocs`
-```bash
-#!/bin/bash
-Rscript generate-docs.R
-cd ./docs/api/rdocs && tree -H '.' -L 1 --noreport --charset utf-8 -o index.html && cd ../../../
-```
+From [GitHub Action docs workflow](https://github.com/apache/sedona/actions/workflows/docs.yml), find generated-docs in the tagged commit. Download it and copy this folder `docs/api/rdocs` to the same location of the Sedona to-be-released source repo.
 
 ### Deploy the website
 
diff --git a/docs/setup/compile.md b/docs/setup/compile.md
index 4680091a..0050bb63 100644
--- a/docs/setup/compile.md
+++ b/docs/setup/compile.md
@@ -4,7 +4,7 @@
 
 
 ## Compile Scala / Java source code
-Sedona Scala/Java code is a project with four modules, core, sql, viz and python adapter. Each module is a Scala/Java mixed project which is managed by Apache Maven 3.
+Sedona Scala/Java code is a project with multiple modules. Each module is a Scala/Java mixed project which is managed by Apache Maven 3.
 
 * Make sure your Linux/Mac machine has Java 1.8, Apache Maven 3.3.1+, and Python3. The compilation of Sedona is not tested on Windows machine.
 
@@ -43,7 +43,7 @@ To compile all modules, please make sure you are in the root folder of all modul
 	```
 
 !!!tip
-	To get the Sedona Python-adapter jar with all GeoTools jars included, simply append `-Dgeotools` option. The command is like this:`mvn clean install -DskipTests -Dscala=2.12 -Dspark=3.0 -Dgeotools`
+	To get the Sedona Spark Shaded jar with all GeoTools jars included, simply append `-Dgeotools` option. The command is like this:`mvn clean install -DskipTests -Dscala=2.12 -Dspark=3.0 -Dgeotools`
 
 ### Download staged jars
 
@@ -58,9 +58,9 @@ For example,
 export SPARK_HOME=$PWD/spark-3.0.1-bin-hadoop2.7
 export PYTHONPATH=$SPARK_HOME/python
 ```
-2. Compile the Sedona Scala and Java code with `-Dgeotools` and then copy the ==sedona-python-adapter-{{ sedona.current_version }}.jar== to ==SPARK_HOME/jars/== folder.
+2. Compile the Sedona Scala and Java code with `-Dgeotools` and then copy the ==sedona-spark-shaded-{{ sedona.current_version }}.jar== to ==SPARK_HOME/jars/== folder.
 ```
-cp python-adapter/target/sedona-python-adapter-xxx.jar SPARK_HOME/jars/
+cp spark-shaded/target/sedona-spark-shaded-xxx.jar SPARK_HOME/jars/
 ```
 3. Install the following libraries
 ```
@@ -111,7 +111,7 @@ pip install mike
 After installing MkDocs and MkDocs-Material, run the command in Sedona root folder:
 
 ```
-mike deploy --update-aliases current-snapshot latest
+mike deploy --update-aliases latest-snapshot latest
 mike set-default latest
 mike serve
 ```
diff --git a/docs/setup/databricks.md b/docs/setup/databricks.md
index bf97395e..1e3d5e9b 100644
--- a/docs/setup/databricks.md
+++ b/docs/setup/databricks.md
@@ -5,13 +5,14 @@ You just need to install the Sedona jars and Sedona Python on Databricks using D
 ## Advanced editions
 
 * Sedona 1.0.1 & 1.1.0 is compiled against Spark 3.1 (~ Databricks DBR 9 LTS, DBR 7 is Spark 3.0)
-* Sedona 1.1.1 is compiled against Spark 3.2 (~ DBR 10 & 11)
+* Sedona 1.1.1, 1.2.0 are compiled against Spark 3.2 (~ DBR 10 & 11)
+* Sedona 1.2.1, 1.3.1, 1.4.0 are complied against Spark 3.3
 
 > In Spark 3.2, `org.apache.spark.sql.catalyst.expressions.Generator` class added a field `nodePatterns`. Any SQL functions that rely on Generator class may have issues if compiled for a runtime with a differing spark version. For Sedona, those functions are:
 >    * ST_MakeValid
 >    * ST_SubDivideExplode
 
-__Sedona `1.1.1-incubating` is overall the recommended version to use. It is generally backwards compatible with earlier Spark releases but you should be aware of what Spark version Sedona was compiled against versus which is being executed in case you hit issues.__
+__Sedona `1.1.1-incubating` and above is overall the recommended version to use. It is generally backwards compatible with earlier Spark releases but you should be aware of what Spark version Sedona was compiled against versus which is being executed in case you hit issues.__
 
 #### Databricks 10.x+ (Recommended)
 
@@ -26,7 +27,7 @@ __Sedona `1.1.1-incubating` is overall the recommended version to use. It is gen
 
 1) From the Libraries tab install from Maven Coordinates
     ```
-    org.apache.sedona:sedona-python-adapter-3.0_2.12:{{ sedona.current_version }}
+    org.apache.sedona:sedona-spark-shaded-3.0_2.12:{{ sedona.current_version }}
     org.datasyslab:geotools-wrapper:{{ sedona.current_geotools }}
     ```
 
@@ -75,7 +76,7 @@ mkdir -p /dbfs/FileStore/jars/sedona/{{ sedona.current_version }}
 # Download the dependencies from Maven into DBFS
 curl -o /dbfs/FileStore/jars/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 /dbfs/FileStore/jars/sedona/{{ sedona.current_version }}/sedona-python-adapter-3.0_2.12-{{ sedona.current_version }}.jar "https://repo1.maven.org/maven2/org/apache/sedona/sedona-python-adapter-3.0_2.12/{{ sedona.current_version }}/sedona-python-adapter-3.0_2.12-{{ sedona.current_version }}.jar"
+curl -o /dbfs/FileStore/jars/sedona/{{ sedona.current_version }}/sedona-spark-shaded-3.0_2.12-{{ sedona.current_version }}.jar "https://repo1.maven.org/maven2/org/apache/sedona/sedona-spark-shaded-3.0_2.12/{{ sedona.current_version }}/sedona-spark-shaded-3.0_2.12-{{ sedona.current_version }}.jar"
 
 curl -o /dbfs/FileStore/jars/sedona/{{ sedona.current_version }}/sedona-viz-3.0_2.12-{{ sedona.current_version }}.jar "https://repo1.maven.org/maven2/org/apache/sedona/sedona-viz-3.0_2.12/{{ sedona.current_version }}/sedona-viz-3.0_2.12-{{ sedona.current_version }}.jar"
 ```
diff --git a/docs/setup/emr.md b/docs/setup/emr.md
new file mode 100644
index 00000000..f9e24ae7
--- /dev/null
+++ b/docs/setup/emr.md
@@ -0,0 +1,48 @@
+We recommend Sedona-1.3.1-incuabting and above for EMR. In the tutorial, we use AWS Elastic MapReduce (EMR) 6.9.0. It has the following applications installed: Hadoop 3.3.3, JupyterEnterpriseGateway 2.6.0, Livy 0.7.1, Spark 3.3.0.
+
+This tutorial is tested on EMR on EC2 with EMR Studio (notebooks). EMR on EC2 uses YARN to manage resources.
+
+## Prepare initialization script
+
+In your S3 bucket, add a script that has the following content:
+
+```bash
+#!/bin/bash
+
+# EMR clusters only have ephemeral local storage. It does not really matter where we store the jars.
+sudo mkdir /jars
+
+# Download Sedona jar
+sudo curl -o /jars/sedona-spark-shaded-3.0_2.12-{{ sedona.current_version }}.jar "https://repo1.maven.org/maven2/org/apache/sedona/sedona-spark-shaded-3.0_2.12/{{ sedona.current_version }}/sedona-spark-shaded-3.0_2.12-{{ sedona.current_version }}.jar"
+
+# Download GeoTools jar
+sudo curl -o /jars/geotools-wrapper-{{ sedona.current_geotools }}.jar "https://repo1.maven.org/maven2/org/datasyslab/geotools-wrapper/{{ sedona.current_geotools }}/geotools-wrapper-{{ sedona.current_geotools }}.jar"
+
+# Install necessary python libraries
+sudo python3 -m pip install pandas shapely==1.8.5
+sudo python3 -m pip install pandas geopandas==0.10.2
+sudo python3 -m pip install attrs matplotlib descartes apache-sedona==1.4.0
+```
+
+When you create a EMR cluster, in the `bootstrap action`, specify the location of this script.
+
+## Add software configuration
+
+When you create a EMR cluster, in the software configuration, add the following content:
+
+```bash
+[
+  {
+    "Classification":"spark-defaults", 
+    "Properties":{
+      "spark.yarn.dist.jars": "/jars/sedona-spark-shaded-3.0_2.12-{{ sedona.current_version }}.jar,/jars/geotools-wrapper-{{ sedona.current_geotools }}.jar",
+      "spark.serializer": "org.apache.spark.serializer.KryoSerializer",
+      "spark.kryo.registrator": "org.apache.sedona.core.serde.SedonaKryoRegistrator",
+      "spark.sql.extensions": "org.apache.sedona.viz.sql.SedonaVizExtensions,org.apache.sedona.sql.SedonaSqlExtensions"
+      }
+  }
+]
+```
+
+!!!note
+	If you use Sedona 1.3.1-incubating, please use `sedona-python-adpater-3.0_2.12` jar in the content above, instead of `sedona-spark-shaded-3.0_2.12`.
\ No newline at end of file
diff --git a/docs/setup/flink/platform.md b/docs/setup/flink/platform.md
index 95dca007..0f0ff7f6 100644
--- a/docs/setup/flink/platform.md
+++ b/docs/setup/flink/platform.md
@@ -1,8 +1,8 @@
-Sedona Flink binary releases are compiled by Java 1.8 and Scala 2.12/2.11, and tested in the following environments:
+Sedona Flink binary releases are compiled by Java 1.8 and Scala 2.12, and tested in the following environments:
 
 === "Sedona Scala/Java"
 
 	|             | Flink 1.12 | Flink 1.13 | Flink 1.14 |
 	|:-----------:| :---------:|:---------:|:---------:|
 	| Scala 2.12 | ✅  |  ✅  | ✅ |
-	| Scala 2.11 | ✅  |  ✅  | ✅ |
\ No newline at end of file
+	| Scala 2.11 | not tested  |  not tested  | not tested |
\ No newline at end of file
diff --git a/docs/setup/install-python.md b/docs/setup/install-python.md
index a298ef0e..f7a09f17 100644
--- a/docs/setup/install-python.md
+++ b/docs/setup/install-python.md
@@ -31,13 +31,13 @@ cd python
 python3 setup.py install
 ```
 
-### Prepare python-adapter jar
+### Prepare sedona-spark-shaded jar
 
-Sedona Python needs one additional jar file called `sedona-python-adapter` to work properly. Please make sure you use the correct version for Spark and Scala. For Spark 3.0 + Scala 2.12, it is called `sedona-python-adapter-3.0_2.12-{{ sedona.current_version }}.jar`
+Sedona Python needs one additional jar file called `sedona-spark-shaded` to work properly. Please make sure you use the correct version for Spark and Scala. For Spark 3.0 + Scala 2.12, it is called `sedona-spark-shaded-3.0_2.12-{{ sedona.current_version }}.jar`
 
 You can get it using one of the following methods:
 
-1. Compile from the source within main project directory and copy it (in `python-adapter/target` folder) to SPARK_HOME/jars/ folder ([more details](../compile))
+1. Compile from the source within main project directory and copy it (in `spark-shaded/target` folder) to SPARK_HOME/jars/ folder ([more details](../compile))
 
 2. Download from [GitHub release](https://github.com/apache/sedona/releases) and copy it to SPARK_HOME/jars/ folder
 3. Call the [Maven Central coordinate](../maven-coordinates) in your python program. For example, in PySparkSQL
@@ -48,7 +48,7 @@ spark = SparkSession. \
     config("spark.serializer", KryoSerializer.getName). \
     config("spark.kryo.registrator", SedonaKryoRegistrator.getName). \
     config('spark.jars.packages',
-           'org.apache.sedona:sedona-python-adapter-3.0_2.12:{{ sedona.current_version }},'
+           'org.apache.sedona:sedona-spark-shaded-3.0_2.12:{{ sedona.current_version }},'
            'org.datasyslab:geotools-wrapper:{{ sedona.current_geotools }}'). \
     getOrCreate()
 ```
@@ -58,7 +58,7 @@ spark = SparkSession. \
 
 ### Setup environment variables
 
-If you manually copy the python-adapter jar to `SPARK_HOME/jars/` folder, you need to setup two environment variables
+If you manually copy the sedona-spark-shaded jar to `SPARK_HOME/jars/` folder, you need to setup two environment variables
 
 * SPARK_HOME. For example, run the command in your terminal
 
diff --git a/docs/setup/install-r.md b/docs/setup/install-r.md
deleted file mode 100644
index b2375857..00000000
--- a/docs/setup/install-r.md
+++ /dev/null
@@ -1,105 +0,0 @@
-## Introduction
-
-apache.sedona ([cran.r-project.org/package=apache.sedona](https://cran.r-project.org/package=apache.sedona)) is a
-[sparklyr](https://github.com/sparklyr/sparklyr)-based R interface for
-[Apache Sedona](https://sedona.apache.org). It presents what Apache
-Sedona has to offer through idiomatic frameworks and constructs in R
-(e.g., one can build spatial Spark SQL queries using Sedona UDFs in
-conjunction with a wide range of dplyr expressions), hence making Apache
-Sedona highly friendly for R users.
-
-Generally speaking, when working with Apache Sedona, one choose between
-the following two modes:
-
--   Manipulating Sedona [Spatial Resilient Distributed
-    Datasets](../../tutorial/rdd)
-    with spatial-RDD-related routines
--   Querying geometric columns within [Spatial dataframes](../../tutorial/sql) with Sedona
-    spatial UDFs
-
-While the former option enables more fine-grained control over low-level
-implementation details (e.g., which index to build for spatial queries,
-which data structure to use for spatial partitioning, etc), the latter
-is simpler and leads to a straightforward integration with `dplyr`,
-`sparklyr`, and other `sparklyr` extensions (e.g., one can build ML
-feature extractors with Sedona UDFs and connect them with ML pipelines
-using `ml_*()` family of functions in `sparklyr`, hence creating ML
-workflows capable of understanding spatial data).
-
-Because data from spatial RDDs can be imported into Spark dataframes as
-geometry columns and vice versa, one can switch between the
-abovementioned two modes fairly easily.
-
-At the moment `apache.sedona` consists of the following components:
-
--   R interface for Spatial-RDD-related functionalities
-    -   Reading/writing spatial data in WKT, WKB, and GeoJSON formats
-    -   Shapefile reader
-    -   Spatial partition, index, join, KNN query, and range query
-        operations
-    -   Visualization routines
--   `dplyr`-integration for Sedona spatial UDTs and UDFs
-    -   See [SQL APIs](../../api/sql/Overview/) for the list
-        of available UDFs
--   Functions importing data from spatial RDDs to Spark dataframes and
-    vice versa
-
-## Connect to Spark
-
-To ensure Sedona serialization routines, UDTs, and UDFs are properly
-registered when creating a Spark session, one simply needs to attach
-`apache.sedona` before instantiating a Spark connection. apache.sedona
-will take care of the rest. For example,
-
-```r
-library(sparklyr)
-library(apache.sedona)
-
-spark_home <- "/usr/lib/spark"  # NOTE: replace this with your $SPARK_HOME directory
-sc <- spark_connect(master = "yarn", spark_home = spark_home)
-```
-
-will create a Sedona-capable Spark connection in YARN client mode, and
-
-```r
-library(sparklyr)
-library(apache.sedona)
-
-sc <- spark_connect(master = "local")
-```
-
-will create a Sedona-capable Spark connection to an Apache Spark
-instance running locally.
-
-In `sparklyr`, one can easily inspect the Spark connection object to
-sanity-check it has been properly initialized with all Sedona-related
-dependencies, e.g.,
-
-```r
-print(sc$extensions$packages)
-```
-
-    ## [1] "org.apache.sedona:sedona-core-3.0_2.12:{{ sedona.current_version }}"
-    ## [2] "org.apache.sedona:sedona-sql-3.0_2.12:{{ sedona.current_version }}"
-    ## [3] "org.apache.sedona:sedona-viz-3.0_2.12:{{ sedona.current_version }}"
-    ## [4] "org.datasyslab:geotools-wrapper:{{ sedona.current_geotools }}"
-    ## [5] "org.datasyslab:sernetcdf:0.1.0"
-    ## [6] "org.locationtech.jts:jts-core:1.18.0"
-    ## [7] "org.wololo:jts2geojson:0.14.3"
-
-and
-
-```r
-spark_session(sc) %>%
-  invoke("%>%", list("conf"), list("get", "spark.kryo.registrator")) %>%
-  print()
-```
-
-    ## [1] "org.apache.sedona.viz.core.Serde.SedonaVizKryoRegistrator"
-
-
-For more information about connecting to Spark with `sparklyr`, see
-<https://therinspark.com/connections.html> and
-`?sparklyr::spark_connect`. Also see
-[Initiate Spark Context](../../tutorial/rdd/#initiate-sparkcontext) and [Initiate Spark Session](../../tutorial/sql/#initiate-sparksession) for
-minimum and recommended dependencies for Apache Sedona.
diff --git a/docs/setup/install-scala.md b/docs/setup/install-scala.md
index a8f07c73..6fe6f291 100644
--- a/docs/setup/install-scala.md
+++ b/docs/setup/install-scala.md
@@ -18,12 +18,12 @@ There are two ways to use a Scala or Java library with Apache Spark. You can use
 
 * Local mode: test Sedona without setting up a cluster
 ```
-./bin/spark-shell --packages org.apache.sedona:sedona-python-adapter-3.0_2.12:{{ sedona.current_version }},org.apache.sedona:sedona-viz-3.0_2.12:{{ sedona.current_version }},org.datasyslab:geotools-wrapper:{{ sedona.current_geotools }}
+./bin/spark-shell --packages org.apache.sedona:sedona-spark-shaded-3.0_2.12:{{ sedona.current_version }},org.apache.sedona:sedona-viz-3.0_2.12:{{ sedona.current_version }},org.datasyslab:geotools-wrapper:{{ sedona.current_geotools }}
 ```
   
 * Cluster mode: you need to specify Spark Master IP
 ```
-./bin/spark-shell --master spark://localhost:7077 --packages org.apache.sedona:sedona-python-adapter-3.0_2.12:{{ sedona.current_version }},org.apache.sedona:sedona-viz-3.0_2.12:{{ sedona.current_version }},org.datasyslab:geotools-wrapper:{{ sedona.current_geotools }}
+./bin/spark-shell --master spark://localhost:7077 --packages org.apache.sedona:sedona-spark-shaded-3.0_2.12:{{ sedona.current_version }},org.apache.sedona:sedona-viz-3.0_2.12:{{ sedona.current_version }},org.datasyslab:geotools-wrapper:{{ sedona.current_geotools }}
 ```
   
 ### Download Sedona jar manually
@@ -39,12 +39,12 @@ There are two ways to use a Scala or Java library with Apache Spark. You can use
  
 * Local mode: test Sedona without setting up a cluster
 ```
-./bin/spark-shell --jars org.apache.sedona:sedona-python-adapter-3.0_2.12:{{ sedona.current_version }},org.apache.sedona:sedona-viz-3.0_2.12:{{ sedona.current_version }},org.datasyslab:geotools-wrapper:{{ sedona.current_geotools }}
+./bin/spark-shell --jars org.apache.sedona:sedona-spark-shaded-3.0_2.12:{{ sedona.current_version }},org.apache.sedona:sedona-viz-3.0_2.12:{{ sedona.current_version }},org.datasyslab:geotools-wrapper:{{ sedona.current_geotools }}
 ```
   
 * Cluster mode: you need to specify Spark Master IP  
 ```
-./bin/spark-shell --master spark://localhost:7077 --jars org.apache.sedona:sedona-python-adapter-3.0_2.12:{{ sedona.current_version }},org.apache.sedona:sedona-viz-3.0_2.12:{{ sedona.current_version }},org.datasyslab:geotools-wrapper:{{ sedona.current_geotools }}
+./bin/spark-shell --master spark://localhost:7077 --jars org.apache.sedona:sedona-spark-shaded-3.0_2.12:{{ sedona.current_version }},org.apache.sedona:sedona-viz-3.0_2.12:{{ sedona.current_version }},org.datasyslab:geotools-wrapper:{{ sedona.current_geotools }}
 ```
 
 ## Spark SQL shell
diff --git a/docs/setup/maven-coordinates.md b/docs/setup/maven-coordinates.md
index cbd48ded..90f8faf7 100644
--- a/docs/setup/maven-coordinates.md
+++ b/docs/setup/maven-coordinates.md
@@ -1,16 +1,15 @@
 # Maven Coordinates
 
-Sedona Spark has four modules: `sedona-core, sedona-sql, sedona-viz, sedona-python-adapter`. `sedona-python-adapter` is a fat jar of `sedona-core, sedona-sql` and python adapter code. If you want to use SedonaViz, you will include one more jar: `sedona-viz`.
 
-Sedona Flink has four modules :`sedona-core, sedona-sql, sedona-python-adapter, sedona-flink`. `sedona-python-adapter` is a fat jar of `sedona-core, sedona-sql`.
-
-
-## Use Sedona fat jars
+## Use Sedona shaded (fat) jars
 
 !!!warning
-	For Scala/Java/Python/R users, this is the most common way to use Sedona in your environment. Do not use separate Sedona jars otherwise you will get dependency conflicts. `sedona-python-adapter` already contains all you need.
+	For Scala/Java/Python users, this is the most common way to use Sedona in your environment. Do not use separate Sedona jars unless you are sure that you do not need shaded jars.
+	
+!!!warning
+	For R users, this is the only way to use Sedona in your environment.
 
-The optional GeoTools library is required only if you want to use CRS transformation and ShapefileReader. This wrapper library is a re-distribution of GeoTools official jars. The only purpose of this library is to bring GeoTools jars from OSGEO repository to Maven Central. This library is under GNU Lesser General Public License (LGPL) license so we cannot package it in Sedona official release.
+The optional GeoTools library is required if you want to use CRS transformation, ShapefileReader or GeoTiff reader. This wrapper library is a re-distribution of GeoTools official jars. The only purpose of this library is to bring GeoTools jars from OSGEO repository to Maven Central. This library is under GNU Lesser General Public License (LGPL) license so we cannot package it in Sedona official release.
 
 !!! abstract "Sedona with Apache Spark"
 
@@ -19,7 +18,7 @@ The optional GeoTools library is required only if you want to use CRS transforma
 		```xml
 		<dependency>
 		  <groupId>org.apache.sedona</groupId>
-		  <artifactId>sedona-python-adapter-3.0_2.12</artifactId>
+		  <artifactId>sedona-spark-shaded-3.0_2.12</artifactId>
 		  <version>{{ sedona.current_version }}</version>
 		</dependency>
 		<dependency>
@@ -40,7 +39,7 @@ The optional GeoTools library is required only if you want to use CRS transforma
 		```xml
 		<dependency>
 		  <groupId>org.apache.sedona</groupId>
-		  <artifactId>sedona-python-adapter-3.0_2.13</artifactId>
+		  <artifactId>sedona-spark-shaded-3.0_2.13</artifactId>
 		  <version>{{ sedona.current_version }}</version>
 		</dependency>
 		<dependency>
@@ -64,12 +63,7 @@ The optional GeoTools library is required only if you want to use CRS transforma
 		```xml
 		<dependency>
 		  <groupId>org.apache.sedona</groupId>
-		  <artifactId>sedona-python-adapter-3.0_2.12</artifactId>
-		  <version>{{ sedona.current_version }}</version>
-		</dependency>
-		<dependency>
-		  <groupId>org.apache.sedona</groupId>
-		  <artifactId>sedona-flink_2.12</artifactId>
+		  <artifactId>sedona-flink-shaded_2.12</artifactId>
 		  <version>{{ sedona.current_version }}</version>
 		</dependency>
 		<!-- Optional: https://mvnrepository.com/artifact/org.datasyslab/geotools-wrapper -->
@@ -127,9 +121,12 @@ Under BSD 3-clause (compatible with Apache 2.0 license)
 		```
 
 
-## Use Sedona and third-party jars separately
+## Use Sedona unshaded jars
+
+!!!warning
+	For Scala, Java, Python users, please use the following jars only if you satisfy these conditions: (1) you know how to exclude transient dependencies in a complex application. (2) your environment has internet access (3) you are using some sort of Maven package resolver, or pom.xml, or build.sbt. It usually directly takes an input like this `GroupID:ArtifactID:Version`. If you don't understand what we are talking about, the following jars are not for you.
 
-==For Scala and Java users==, if by any chance you don't want to use an uber jar that includes every dependency, you can use the following jars instead. ==Otherwise, please do not continue reading this section.==
+The optional GeoTools library is required if you want to use CRS transformation, ShapefileReader or GeoTiff reader. This wrapper library is a re-distribution of GeoTools official jars. The only purpose of this library is to bring GeoTools jars from OSGEO repository to Maven Central. This library is under GNU Lesser General Public License (LGPL) license so we cannot package it in Sedona official release.
 
 !!! abstract "Sedona with Apache Spark"
 
@@ -151,6 +148,17 @@ Under BSD 3-clause (compatible with Apache 2.0 license)
 		  <artifactId>sedona-viz-3.0_2.12</artifactId>
 		  <version>{{ sedona.current_version }}</version>
 		</dependency>
+		<!-- Required if you use Sedona Python -->
+		<dependency>
+		  <groupId>org.apache.sedona</groupId>
+		  <artifactId>sedona-python-adapter-3.0_2.12</artifactId>
+		  <version>{{ sedona.current_version }}</version>
+		</dependency>			
+		<dependency>
+		    <groupId>org.datasyslab</groupId>
+		    <artifactId>geotools-wrapper</artifactId>
+		    <version>{{ sedona.current_geotools }}</version>
+		</dependency>	
 		```
 	=== "Spark 3.0+ and Scala 2.13"
 	
@@ -170,6 +178,17 @@ Under BSD 3-clause (compatible with Apache 2.0 license)
 		  <artifactId>sedona-viz-3.0_2.13</artifactId>
 		  <version>{{ sedona.current_version }}</version>
 		</dependency>
+		<!-- Required if you use Sedona Python -->
+		<dependency>
+		  <groupId>org.apache.sedona</groupId>
+		  <artifactId>sedona-python-adapter-3.0_2.12</artifactId>
+		  <version>{{ sedona.current_version }}</version>
+		</dependency>	
+		<dependency>
+		    <groupId>org.datasyslab</groupId>
+		    <artifactId>geotools-wrapper</artifactId>
+		    <version>{{ sedona.current_geotools }}</version>
+		</dependency>		
 		```
 		
 	
@@ -194,56 +213,13 @@ Under BSD 3-clause (compatible with Apache 2.0 license)
 		  <artifactId>sedona-flink-3.0_2.12</artifactId>
 		  <version>{{ sedona.current_version }}</version>
 		</dependency>
+		<dependency>
+		    <groupId>org.datasyslab</groupId>
+		    <artifactId>geotools-wrapper</artifactId>
+		    <version>{{ sedona.current_geotools }}</version>
+		</dependency>		
 		```
 
-### LocationTech JTS-core 1.18.0+
-
-Under Eclipse Public License 2.0 ("EPL") or the Eclipse Distribution License 1.0 (a BSD Style License)
-
-```xml
-<!-- https://mvnrepository.com/artifact/org.locationtech.jts/jts-core -->
-<dependency>
-    <groupId>org.locationtech.jts</groupId>
-    <artifactId>jts-core</artifactId>
-    <version>1.18.0</version>
-</dependency>
-```
-
-### jts2geojson 0.16.1+
-
-Under MIT License. Please make sure you exclude jts and jackson from this library.
-
-```xml
-<!-- https://mvnrepository.com/artifact/org.wololo/jts2geojson -->
-<dependency>
-    <groupId>org.wololo</groupId>
-    <artifactId>jts2geojson</artifactId>
-    <version>0.16.1</version>
-    <exclusions>
-        <exclusion>
-            <groupId>org.locationtech.jts</groupId>
-            <artifactId>jts-core</artifactId>
-        </exclusion>
-        <exclusion>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>*</artifactId>
-        </exclusion>
-    </exclusions>
-</dependency>
-```
-
-### GeoTools 24.0+
-
-GeoTools library is required only if you want to use CRS transformation and ShapefileReader. This wrapper library is a re-distriution of GeoTools official jars. The only purpose of this library is to bring GeoTools jars from OSGEO repository to Maven Central. This library is under GNU Lesser General Public License (LGPL) license so we cannot package it in Sedona official release.
-
-```xml
-<!-- https://mvnrepository.com/artifact/org.datasyslab/geotools-wrapper -->
-<dependency>
-    <groupId>org.datasyslab</groupId>
-    <artifactId>geotools-wrapper</artifactId>
-    <version>{{ sedona.current_geotools }}</version>
-</dependency>
-```
 
 ### netCDF-Java 5.4.2
 
diff --git a/docs/setup/release-notes.md b/docs/setup/release-notes.md
index 588f89fd..99179c5a 100644
--- a/docs/setup/release-notes.md
+++ b/docs/setup/release-notes.md
@@ -1,6 +1,125 @@
 !!!warning
 	Support of Spark 2.X and Scala 2.11 was removed in Sedona 1.3.0+ although some parts of the source code might still be compatible. Sedona 1.3.0+ releases binary for both Scala 2.12 and 2.13.
 
+## Sedona 1.4.0
+
+Sedona 1.4.0 is compiled against, Spark 3.3 / Flink 1.12, Java 8.
+
+### Highlights
+
+* [X] **Sedona Spark** Pushdown spatial predicate on GeoParquet to reduce memory consumption by 10X: see [explanation](../../api/sql/Optimizer/#geoparquet)
+* [X] **Sedona Spark & Flink** Serialize and deserialize geometries 3 - 7X faster
+* [X] **Sedona Spark** Automatically use broadcast index spatial join for small datasets
+* [X] **Sedona Spark** New RasterUDT added to Sedona GeoTiff reader.
+* [X] **Sedona Spark** A number of bug fixes and improvement to the Sedona R module.
+
+### API change
+
+* **Sedona Spark & Flink** Packaging strategy changed. See [Maven Coordinate](../maven-coordinates). Please change your Sedona dependencies if needed. We recommend `sedona-spark-shaded-3.0_2.12-1.4.0` and `sedona-flink-shaded-3.0_2.12-1.4.0`
+* **Sedona Spark & Flink** GeoTools-wrapper version upgraded. Please use `geotools-wrapper-1.4.0-28.2`.
+
+### Behavior change
+
+* **Sedona Flink** Sedona Flink no longer outputs any LinearRing type geometry. All LinearRing are changed to LineString.
+* **Sedona Spark** Join optimization strategy changed. Sedona no longer optimizes spatial join when use a spatial predicate together with a equijoin predicate. By default, it prefers equijoin whenever possible. SedonaConf adds a config option called `sedona.join.optimizationmode`, it can be configured as one of the following values:
+	* `all`: optimize all joins having spatial predicate in join conditions. This was the behavior of Apache Sedona prior to 1.4.0.
+	* `none`: disable spatial join optimization.
+	* `nonequi`: only enable spatial join optimization on non-equi joins. This is the default mode.
+
+When `sedona.join.optimizationmode` is configured as `nonequi`, it won't optimize join queries such as `SELECT * FROM A, B WHERE A.x = B.x AND ST_Contains(A.geom, B.geom)`, since it is an equi-join with equi-condition `A.x = B.x`. Sedona will optimize for `SELECT * FROM A, B WHERE A.x = B.x AND ST_Contains(A.geom, B.geom)`
+
+
+### Bug
+
+<ul>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-218'>SEDONA-218</a>] -         Flaky test caused by improper handling of null struct values in Adapter.toDf
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-221'>SEDONA-221</a>] -         Outer join throws NPE for null geometries
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-222'>SEDONA-222</a>] -         GeoParquet reader does not work in non-local mode
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-224'>SEDONA-224</a>] -         java.lang.NoSuchMethodError when loading GeoParquet files using Spark 3.0.x ~ 3.2.x
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-225'>SEDONA-225</a>] -         Cannot count dataframes loaded from GeoParquet files
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-227'>SEDONA-227</a>] -         Python SerDe Performance Degradation
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-230'>SEDONA-230</a>] -         rdd.saveAsGeoJSON should generate feature properties with field names
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-233'>SEDONA-233</a>] -         Incorrect results for several joins in a single stage
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-236'>SEDONA-236</a>] -         Flakey python tests in tests.serialization.test_[de]serializers
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-242'>SEDONA-242</a>] -         Update jars dependencies in Sedona R to Sedona 1.4.0 version
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-250'>SEDONA-250</a>] -         R Deprecate use of Spark 2.4
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-252'>SEDONA-252</a>] -         Fix disabled RS_Base64 test
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-255'>SEDONA-255</a>] -         R – Translation issue for ST_Point and ST_PolygonFromEnvelope
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-258'>SEDONA-258</a>] -         Cannot directly assign raw spatial RDD to CircleRDD using Python binding
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-259'>SEDONA-259</a>] -         Adapter.toSpatialRdd in Python binding does not have valid implementation for specifying custom field names for user data
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-261'>SEDONA-261</a>] -         Cannot run distance join using broadcast index join when the distance expression references to attributes from the right-side relation
+</li>
+</ul>
+        
+### New Feature
+
+<ul>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-156'>SEDONA-156</a>] -         predicate pushdown support for GeoParquet
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-215'>SEDONA-215</a>] -         Add ST_ConcaveHull
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-216'>SEDONA-216</a>] -         Upgrade jts version to 1.19.0
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-235'>SEDONA-235</a>] -         Create ST_S2CellIds in Sedona
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-246'>SEDONA-246</a>] -         R GeoTiff read/write
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-254'>SEDONA-254</a>] -         R – Add raster type
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-262'>SEDONA-262</a>] -         Don&#39;t optimize equi-join by default, add an option to configure when to optimize spatial joins
+</li>
+</ul>
+        
+<h2>        Improvement
+</h2>
+<ul>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-205'>SEDONA-205</a>] -         Use BinaryType in GeometryUDT in Sedona Spark
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-207'>SEDONA-207</a>] -         Faster serialization/deserialization of geometry objects
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-212'>SEDONA-212</a>] -         Move shading to separate maven modules
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-217'>SEDONA-217</a>] -         Automatically broadcast small datasets
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-220'>SEDONA-220</a>] -         Upgrade Ubuntu build image from 18.04 to 20.04
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-226'>SEDONA-226</a>] -         Support reading and writing GeoParquet file metadata
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-228'>SEDONA-228</a>] -         Standardize logging dependencies
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-231'>SEDONA-231</a>] -         Redundant Serde Removal
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-234'>SEDONA-234</a>] -         ST_Point inconsistencies
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-243'>SEDONA-243</a>] -         Improve Sedona R file readers: GeoParquet and Shapefile
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-244'>SEDONA-244</a>] -         Align R read/write functions with the Sparklyr framework
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-249'>SEDONA-249</a>] -         Add jvm flags for running tests on Java 17 
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-251'>SEDONA-251</a>] -         Add raster type to Sedona
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-253'>SEDONA-253</a>] -         Upgrade geotools to version 28.2
+</li>
+<li>[<a href='https://issues.apache.org/jira/browse/SEDONA-260'>SEDONA-260</a>] -         More intuitive configuration of partition and index-build side of spatial joins in Sedona SQL
+</li>
+</ul>
+
 ## Sedona 1.3.1
 
 This version is a minor release on Sedoma 1.3.0 line. It fixes a few critical bugs in 1.3.0. We suggest all 1.3.0 users to migrate to this version.
diff --git a/docs/tutorial/core-python.md b/docs/tutorial/core-python.md
deleted file mode 100644
index ec2ae086..00000000
--- a/docs/tutorial/core-python.md
+++ /dev/null
@@ -1,734 +0,0 @@
-# Spatial RDD Applications in Python
-
-## Introduction
-<div style="text-align: justify">
-Sedona provides a Python wrapper on Sedona core Java/Scala library.
-Sedona SpatialRDDs (and other classes when it was necessary) have implemented meta classes which allow 
-to use overloaded functions, methods and constructors to be the most similar to Java/Scala API as possible. 
-</div>
-
-Apache Sedona core provides five special SpatialRDDs:
-
-<li> PointRDD </li>
-<li> PolygonRDD </li>
-<li> LineStringRDD </li>
-<li> CircleRDD </li>
-<li> RectangleRDD </li>
-<div style="text-align: justify">
-<p>
-All of them can be imported from <b> sedona.core.SpatialRDD </b> module
-
-<b> sedona </b> has written serializers which convert Sedona SpatialRDD to Python objects.
-Converting will produce GeoData objects which have 2 attributes:
-</p>
-</div>
-<li> geom: shapely.geometry.BaseGeometry </li>
-<li> userData: str </li>
-
-geom attribute holds geometry representation as shapely objects. 
-userData is string representation of other attributes separated by "\t"
-</br>
-
-GeoData has one method to get user data.
-<li> getUserData() -> str </li>
-
-!!!note
-	This tutorial is based on [Sedona Core Jupyter Notebook example](../jupyter-notebook). You can interact with Sedona Python Jupyter notebook immediately on Binder. Click [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/apache/sedona/HEAD?filepath=binder) and wait for a few minutes. Then select a notebook and enjoy!
-
-## Installation
-
-Please read [Quick start](../../setup/install-python) to install Sedona Python.
-
-## Apache Sedona Serializers
-Sedona has a suite of well-written geometry and index serializers. Forgetting to enable these serializers will lead to high memory consumption.
-
-```python
-conf.set("spark.serializer", KryoSerializer.getName)
-conf.set("spark.kryo.registrator", SedonaKryoRegistrator.getName)
-sc = SparkContext(conf=conf)
-```
-    
-## Create a SpatialRDD
-
-### Create a typed SpatialRDD
-Apache Sedona core provides three special SpatialRDDs:
-<li> PointRDD </li>
-<li> PolygonRDD </li> 
-<li> LineStringRDD </li>
-<li> CircleRDD </li>
-<li> RectangleRDD </li>
-<br>
-
-They can be loaded from CSV, TSV, WKT, WKB, Shapefiles, GeoJSON formats.
-To pass the format to SpatialRDD constructor please use <b> FileDataSplitter </b> enumeration. 
-
-sedona SpatialRDDs (and other classes when it was necessary) have implemented meta classes which allow 
-to use overloaded functions how Scala/Java Apache Sedona API allows. ex. 
-
-
-```python
-from pyspark import StorageLevel
-from sedona.core.SpatialRDD import PointRDD
-from sedona.core.enums import FileDataSplitter
-
-input_location = "checkin.csv"
-offset = 0  # The point long/lat starts from Column 0
-splitter = FileDataSplitter.CSV # FileDataSplitter enumeration
-carry_other_attributes = True  # Carry Column 2 (hotel, gas, bar...)
-level = StorageLevel.MEMORY_ONLY # Storage level from pyspark
-s_epsg = "epsg:4326" # Source epsg code
-t_epsg = "epsg:5070" # target epsg code
-
-point_rdd = PointRDD(sc, input_location, offset, splitter, carry_other_attributes)
-
-point_rdd = PointRDD(sc, input_location, splitter, carry_other_attributes, level, s_epsg, t_epsg)
-
-point_rdd = PointRDD(
-    sparkContext=sc,
-    InputLocation=input_location,
-    Offset=offset,
-    splitter=splitter,
-    carryInputData=carry_other_attributes
-)
-```
-
-
-#### From SparkSQL DataFrame
-To create spatialRDD from other formats you can use adapter between Spark DataFrame and SpatialRDD
-
-<li> Load data in SedonaSQL. </li>
-
-```python
-csv_point_input_location= "/tests/resources/county_small.tsv"
-
-df = spark.read.\
-    format("csv").\
-    option("delimiter", "\t").\
-    option("header", "false").\
-    load(csv_point_input_location)
-
-df.createOrReplaceTempView("counties")
-
-```
-
-<li> Create a Geometry type column in SedonaSQL </li>
-
-```python
-spatial_df = spark.sql(
-    """
-        SELECT ST_GeomFromWKT(_c0) as geom, _c6 as county_name
-        FROM counties
-    """
-)
-spatial_df.printSchema()
-```
-
-```
-root
- |-- geom: geometry (nullable = false)
- |-- county_name: string (nullable = true)
-```
-
-<li> Use SedonaSQL DataFrame-RDD Adapter to convert a DataFrame to an SpatialRDD </li>
-Note that, you have to name your column geometry
-
-```python
-from sedona.utils.adapter import Adapter
-
-spatial_rdd = Adapter.toSpatialRdd(spatial_df)
-spatial_rdd.analyze()
-
-spatial_rdd.boundaryEnvelope
-```
-
-```
-<sedona.core.geom_types.Envelope object at 0x7f1e5f29fe10>
-```
-
-or pass Geometry column name as a second argument
-
-```python
-spatial_rdd = Adapter.toSpatialRdd(spatial_df, "geom")
-```
-
-For WKT/WKB/GeoJSON data, please use ==ST_GeomFromWKT / ST_GeomFromWKB / ST_GeomFromGeoJSON== instead.
-    
-## Read other attributes in an SpatialRDD
-
-Each SpatialRDD can carry non-spatial attributes such as price, age and name as long as the user sets ==carryOtherAttributes== as [TRUE](#create-a-spatialrdd).
-
-The other attributes are combined together to a string and stored in ==UserData== field of each geometry.
-
-To retrieve the UserData field, use the following code:
-```python
-rdd_with_other_attributes = object_rdd.rawSpatialRDD.map(lambda x: x.getUserData())
-```
-
-## Write a Spatial Range Query
-
-```python
-from sedona.core.geom.envelope import Envelope
-from sedona.core.spatialOperator import RangeQuery
-
-range_query_window = Envelope(-90.01, -80.01, 30.01, 40.01)
-consider_boundary_intersection = False  ## Only return gemeotries fully covered by the window
-using_index = False
-query_result = RangeQuery.SpatialRangeQuery(spatial_rdd, range_query_window, consider_boundary_intersection, using_index)
-```
-
-!!!note
-    Please use RangeQueryRaw from the same module
-    if you want to avoid jvm python serde while converting to Spatial DataFrame
-    It takes the same parameters as RangeQuery but returns reference to jvm rdd which
-    can be converted to dataframe without python - jvm serde using Adapter.
-    
-    Example:
-    ```python
-    from sedona.core.geom.envelope import Envelope
-    from sedona.core.spatialOperator import RangeQueryRaw
-    from sedona.utils.adapter import Adapter
-    
-    range_query_window = Envelope(-90.01, -80.01, 30.01, 40.01)
-    consider_boundary_intersection = False  ## Only return gemeotries fully covered by the window
-    using_index = False
-    query_result = RangeQueryRaw.SpatialRangeQuery(spatial_rdd, range_query_window, consider_boundary_intersection, using_index)
-    gdf = Adapter.toDf(query_result, spark, ["col1", ..., "coln"])
-
-    ```
-
-### Range query window
-
-Besides the rectangle (Envelope) type range query window, Apache Sedona range query window can be 
-<li> Point </li> 
-<li> Polygon </li>
-<li> LineString </li>
-</br>
-
-To create shapely geometries please follow [Shapely official docs](https://shapely.readthedocs.io/en/stable/manual.html)
-
-
-### Use spatial indexes
-
-Sedona provides two types of spatial indexes,
-<li> Quad-Tree </li>
-<li> R-Tree </li>
-Once you specify an index type, 
-Sedona will build a local tree index on each of the SpatialRDD partition.
-
-To utilize a spatial index in a spatial range query, use the following code:
-
-```python
-from sedona.core.geom.envelope import Envelope
-from sedona.core.enums import IndexType
-from sedona.core.spatialOperator import RangeQuery
-
-range_query_window = Envelope(-90.01, -80.01, 30.01, 40.01)
-consider_boundary_intersection = False ## Only return gemeotries fully covered by the window
-
-build_on_spatial_partitioned_rdd = False ## Set to TRUE only if run join query
-spatial_rdd.buildIndex(IndexType.QUADTREE, build_on_spatial_partitioned_rdd)
-
-using_index = True
-
-query_result = RangeQuery.SpatialRangeQuery(
-    spatial_rdd,
-    range_query_window,
-    consider_boundary_intersection,
-    using_index
-)
-```
-
-### Output format
-
-The output format of the spatial range query is another RDD which consists of GeoData objects.
-
-SpatialRangeQuery result can be used as RDD with map or other spark RDD functions. Also it can be used as 
-Python objects when using collect method.
-Example:
-
-```python
-query_result.map(lambda x: x.geom.length).collect()
-```
-
-```
-[
- 1.5900840000000045,
- 1.5906639999999896,
- 1.1110299999999995,
- 1.1096700000000084,
- 1.1415619999999933,
- 1.1386399999999952,
- 1.1415619999999933,
- 1.1418860000000137,
- 1.1392780000000045,
- ...
-]
-```
-
-Or transformed to GeoPandas GeoDataFrame
-
-```python
-import geopandas as gpd
-gpd.GeoDataFrame(
-    query_result.map(lambda x: [x.geom, x.userData]).collect(),
-    columns=["geom", "user_data"],
-    geometry="geom"
-)
-```
-
-## Write a Spatial KNN Query
-
-A spatial K Nearnest Neighbor query takes as input a K, a query point and an SpatialRDD and finds the K geometries in the RDD which are the closest to he query point.
-
-Assume you now have an SpatialRDD (typed or generic). You can use the following code to issue an Spatial KNN Query on it.
-
-```python
-from sedona.core.spatialOperator import KNNQuery
-from shapely.geometry import Point
-
-point = Point(-84.01, 34.01)
-k = 1000 ## K Nearest Neighbors
-using_index = False
-result = KNNQuery.SpatialKnnQuery(object_rdd, point, k, using_index)
-```
-
-### Query center geometry
-
-Besides the Point type, Apache Sedona KNN query center can be 
-<li> Polygon </li>
-<li> LineString </li>
-
-To create Polygon or Linestring object please follow [Shapely official docs](https://shapely.readthedocs.io/en/stable/manual.html)
-### Use spatial indexes
-
-To utilize a spatial index in a spatial KNN query, use the following code:
-
-```python
-from sedona.core.spatialOperator import KNNQuery
-from sedona.core.enums import IndexType
-from shapely.geometry import Point
-
-point = Point(-84.01, 34.01)
-k = 5 ## K Nearest Neighbors
-
-build_on_spatial_partitioned_rdd = False ## Set to TRUE only if run join query
-spatial_rdd.buildIndex(IndexType.RTREE, build_on_spatial_partitioned_rdd)
-
-using_index = True
-result = KNNQuery.SpatialKnnQuery(spatial_rdd, point, k, using_index)
-```
-
-!!!warning
-    Only R-Tree index supports Spatial KNN query
-
-### Output format
-
-The output format of the spatial KNN query is a list of GeoData objects. 
-The list has K GeoData objects.
-
-Example:
-```python
->> result
-
-[GeoData, GeoData, GeoData, GeoData, GeoData]
-```
-
-
-## Write a Spatial Join Query
-
-A spatial join query takes as input two Spatial RDD A and B. For each geometry in A, finds the geometries (from B) covered/intersected by it. A and B can be any geometry type and are not necessary to have the same geometry type.
-
-Assume you now have two SpatialRDDs (typed or generic). You can use the following code to issue an Spatial Join Query on them.
-
-```python
-from sedona.core.enums import GridType
-from sedona.core.spatialOperator import JoinQuery
-
-consider_boundary_intersection = False ## Only return geometries fully covered by each query window in queryWindowRDD
-using_index = False
-
-object_rdd.analyze()
-
-object_rdd.spatialPartitioning(GridType.KDBTREE)
-query_window_rdd.spatialPartitioning(object_rdd.getPartitioner())
-
-result = JoinQuery.SpatialJoinQuery(object_rdd, query_window_rdd, using_index, consider_boundary_intersection)
-```
-
-Result of SpatialJoinQuery is RDD which consists of GeoData instance and list of GeoData instances which spatially intersects or 
-are covered by GeoData. 
-
-```python
-result.collect())
-```
-
-```
-[
-    [GeoData, [GeoData, GeoData, GeoData, GeoData]],
-    [GeoData, [GeoData, GeoData, GeoData]],
-    [GeoData, [GeoData]],
-    [GeoData, [GeoData, GeoData]],
-    ...
-    [GeoData, [GeoData, GeoData]]
-]
-
-```
-
-### Use spatial partitioning
-
-Apache Sedona spatial partitioning method can significantly speed up the join query. Three spatial partitioning methods are available: KDB-Tree, Quad-Tree and R-Tree. Two SpatialRDD must be partitioned by the same way.
-
-If you first partition SpatialRDD A, then you must use the partitioner of A to partition B.
-
-```python
-object_rdd.spatialPartitioning(GridType.KDBTREE)
-query_window_rdd.spatialPartitioning(object_rdd.getPartitioner())
-```
-
-Or 
-
-```python
-query_window_rdd.spatialPartitioning(GridType.KDBTREE)
-object_rdd.spatialPartitioning(query_window_rdd.getPartitioner())
-```
-
-
-### Use spatial indexes
-
-To utilize a spatial index in a spatial join query, use the following code:
-
-```python
-from sedona.core.enums import GridType
-from sedona.core.enums import IndexType
-from sedona.core.spatialOperator import JoinQuery
-
-object_rdd.spatialPartitioning(GridType.KDBTREE)
-query_window_rdd.spatialPartitioning(object_rdd.getPartitioner())
-
-build_on_spatial_partitioned_rdd = True ## Set to TRUE only if run join query
-using_index = True
-query_window_rdd.buildIndex(IndexType.QUADTREE, build_on_spatial_partitioned_rdd)
-
-result = JoinQuery.SpatialJoinQueryFlat(object_rdd, query_window_rdd, using_index, True)
-```
-
-The index should be built on either one of two SpatialRDDs. In general, you should build it on the larger SpatialRDD.
-
-### Output format
-
-The output format of the spatial join query is a PairRDD. In this PairRDD, each object is a pair of two GeoData objects.
-The left one is the GeoData from object_rdd and the right one is the GeoData from the query_window_rdd.
-
-```
-Point,Polygon
-Point,Polygon
-Point,Polygon
-Polygon,Polygon
-LineString,LineString
-Polygon,LineString
-...
-```
-
-example 
-```python
-result.collect()
-```
-
-```
-[
- [GeoData, GeoData],
- [GeoData, GeoData],
- [GeoData, GeoData],
- [GeoData, GeoData],
- ...
- [GeoData, GeoData],
- [GeoData, GeoData]
-]
-```
-
-Each object on the left is covered/intersected by the object on the right.
-
-## Write a Distance Join Query
-
-!!!warning
-    RDD distance joins are only reliable for points. For other geometry types, please use Spatial SQL.
-
-A distance join query takes two spatial RDD assuming that we have two SpatialRDD's:
-<li> object_rdd </li>
-<li> spatial_rdd </li>
-
-And finds the geometries (from spatial_rdd) are within given distance to it. spatial_rdd and object_rdd
-can be any geometry type (point, line, polygon) and are not necessary to have the same geometry type
- 
-You can use the following code to issue an Distance Join Query on them.
-
-```python
-from sedona.core.SpatialRDD import CircleRDD
-from sedona.core.enums import GridType
-from sedona.core.spatialOperator import JoinQuery
-
-object_rdd.analyze()
-
-circle_rdd = CircleRDD(object_rdd, 0.1) ## Create a CircleRDD using the given distance
-circle_rdd.analyze()
-
-circle_rdd.spatialPartitioning(GridType.KDBTREE)
-spatial_rdd.spatialPartitioning(circle_rdd.getPartitioner())
-
-consider_boundary_intersection = False ## Only return gemeotries fully covered by each query window in queryWindowRDD
-using_index = False
-
-result = JoinQuery.DistanceJoinQueryFlat(spatial_rdd, circle_rdd, using_index, consider_boundary_intersection)
-```
-
-!!!note
-    Please use JoinQueryRaw from the same module for methods 
-    
-    - spatialJoin
-    
-    - DistanceJoinQueryFlat
-
-    - SpatialJoinQueryFlat
-
-    For better performance while converting to dataframe with adapter. 
-    That approach allows to avoid costly serialization between Python 
-    and jvm and in result operating on python object instead of native geometries.
-    
-    Example:
-    ```python
-    from sedona.core.SpatialRDD import CircleRDD
-    from sedona.core.enums import GridType
-    from sedona.core.spatialOperator import JoinQueryRaw
-    
-    object_rdd.analyze()
-    
-    circle_rdd = CircleRDD(object_rdd, 0.1) ## Create a CircleRDD using the given distance
-    circle_rdd.analyze()
-    
-    circle_rdd.spatialPartitioning(GridType.KDBTREE)
-    spatial_rdd.spatialPartitioning(circle_rdd.getPartitioner())
-    
-    consider_boundary_intersection = False ## Only return gemeotries fully covered by each query window in queryWindowRDD
-    using_index = False
-    
-    result = JoinQueryRaw.DistanceJoinQueryFlat(spatial_rdd, circle_rdd, using_index, consider_boundary_intersection)
-    
-    gdf = Adapter.toDf(result, ["left_col1", ..., "lefcoln"], ["rightcol1", ..., "rightcol2"], spark)
-    ```
-
-### Output format
-
-Result for this query is RDD which holds two GeoData objects within list of lists.
-Example:
-```python
-result.collect()
-```
-
-```
-[[GeoData, GeoData], [GeoData, GeoData] ...]
-```
-
-It is possible to do some RDD operation on result data ex. Getting polygon centroid.
-```python
-result.map(lambda x: x[0].geom.centroid).collect()
-```
-
-```
-[
- <shapely.geometry.point.Point at 0x7efee2d28128>,
- <shapely.geometry.point.Point at 0x7efee2d280b8>,
- <shapely.geometry.point.Point at 0x7efee2d28fd0>,
- <shapely.geometry.point.Point at 0x7efee2d28080>,
- ...
-]
-```
-    
-## Save to permanent storage
-
-You can always save an SpatialRDD back to some permanent storage such as HDFS and Amazon S3. You can save distributed SpatialRDD to WKT, GeoJSON and object files.
-
-!!!note
-    Non-spatial attributes such as price, age and name will also be stored to permanent storage.
-
-### Save an SpatialRDD (not indexed)
-
-Typed SpatialRDD and generic SpatialRDD can be saved to permanent storage.
-
-#### Save to distributed WKT text file
-
-Use the following code to save an SpatialRDD as a distributed WKT text file:
-
-```python
-object_rdd.rawSpatialRDD.saveAsTextFile("hdfs://PATH")
-object_rdd.saveAsWKT("hdfs://PATH")
-```
-
-#### Save to distributed WKB text file
-
-Use the following code to save an SpatialRDD as a distributed WKB text file:
-
-```python
-object_rdd.saveAsWKB("hdfs://PATH")
-```
-
-#### Save to distributed GeoJSON text file
-
-Use the following code to save an SpatialRDD as a distributed GeoJSON text file:
-
-```python
-object_rdd.saveAsGeoJSON("hdfs://PATH")
-```
-
-
-#### Save to distributed object file
-
-Use the following code to save an SpatialRDD as a distributed object file:
-
-```python
-object_rdd.rawJvmSpatialRDD.saveAsObjectFile("hdfs://PATH")
-```
-
-!!!note
-    Each object in a distributed object file is a byte array (not human-readable). This byte array is the serialized format of a Geometry or a SpatialIndex.
-
-### Save an SpatialRDD (indexed)
-
-Indexed typed SpatialRDD and generic SpatialRDD can be saved to permanent storage. However, the indexed SpatialRDD has to be stored as a distributed object file.
-
-#### Save to distributed object file
-
-Use the following code to save an SpatialRDD as a distributed object file:
-
-```python
-object_rdd.indexedRawRDD.saveAsObjectFile("hdfs://PATH")
-```
-
-### Save an SpatialRDD (spatialPartitioned W/O indexed)
-
-A spatial partitioned RDD can be saved to permanent storage but Spark is not able to maintain the same RDD partition Id of the original RDD. This will lead to wrong join query results. We are working on some solutions. Stay tuned!
-
-### Reload a saved SpatialRDD
-
-You can easily reload an SpatialRDD that has been saved to ==a distributed object file==.
-
-#### Load to a typed SpatialRDD
-
-Use the following code to reload the PointRDD/PolygonRDD/LineStringRDD:
-
-```python
-from sedona.core.formatMapper.disc_utils import load_spatial_rdd_from_disc, GeoType
-
-polygon_rdd = load_spatial_rdd_from_disc(sc, "hdfs://PATH", GeoType.POLYGON)
-point_rdd = load_spatial_rdd_from_disc(sc, "hdfs://PATH", GeoType.POINT)
-linestring_rdd = load_spatial_rdd_from_disc(sc, "hdfs://PATH", GeoType.LINESTRING)
-```
-
-#### Load to a generic SpatialRDD
-
-Use the following code to reload the SpatialRDD:
-
-```python
-saved_rdd = load_spatial_rdd_from_disc(sc, "hdfs://PATH", GeoType.GEOMETRY)
-```
-
-Use the following code to reload the indexed SpatialRDD:
-```python
-saved_rdd = SpatialRDD()
-saved_rdd.indexedRawRDD = load_spatial_index_rdd_from_disc(sc, "hdfs://PATH")
-```
-
-## Read from other Geometry files
-
-All below methods will return SpatialRDD object which can be used with Spatial functions such as Spatial Join etc.
-
-### Read from WKT file
-```python
-from sedona.core.formatMapper import WktReader
-
-WktReader.readToGeometryRDD(sc, wkt_geometries_location, 0, True, False)
-```
-```
-<sedona.core.SpatialRDD.spatial_rdd.SpatialRDD at 0x7f8fd2fbf250>
-```
-
-### Read from WKB file
-```python
-from sedona.core.formatMapper import WkbReader
-
-WkbReader.readToGeometryRDD(sc, wkb_geometries_location, 0, True, False)
-```
-```
-<sedona.core.SpatialRDD.spatial_rdd.SpatialRDD at 0x7f8fd2eece50>
-```
-### Read from GeoJson file
-
-```python
-from sedona.core.formatMapper import GeoJsonReader
-
-GeoJsonReader.readToGeometryRDD(sc, geo_json_file_location)
-```
-```
-<sedona.core.SpatialRDD.spatial_rdd.SpatialRDD at 0x7f8fd2eecb90>
-```
-### Read from Shapefile
-
-```python
-from sedona.core.formatMapper.shapefileParser import ShapefileReader
-
-ShapefileReader.readToGeometryRDD(sc, shape_file_location)
-```
-```
-<sedona.core.SpatialRDD.spatial_rdd.SpatialRDD at 0x7f8fd2ee0710>
-```
-
-### Tips
-When you use Sedona functions such as
-
-- JoinQuery.spatialJoin
-
-- JoinQuery.DistanceJoinQueryFlat
-
-- JoinQuery.SpatialJoinQueryFlat
-
-- RangeQuery.SpatialRangeQuery
-
-For better performance when converting to dataframe you can use
-JoinQueryRaw and RangeQueryRaw from the same module and adapter to convert 
-to Spatial DataFrame. 
-
-Example, JoinQueryRaw:
-
-```python
-from sedona.core.SpatialRDD import CircleRDD
-from sedona.core.enums import GridType
-from sedona.core.spatialOperator import JoinQueryRaw
-
-object_rdd.analyze()
-
-circle_rdd = CircleRDD(object_rdd, 0.1) ## Create a CircleRDD using the given distance
-circle_rdd.analyze()
-
-circle_rdd.spatialPartitioning(GridType.KDBTREE)
-spatial_rdd.spatialPartitioning(circle_rdd.getPartitioner())
-
-consider_boundary_intersection = False ## Only return gemeotries fully covered by each query window in queryWindowRDD
-using_index = False
-
-result = JoinQueryRaw.DistanceJoinQueryFlat(spatial_rdd, circle_rdd, using_index, consider_boundary_intersection)
-
-gdf = Adapter.toDf(result, ["left_col1", ..., "lefcoln"], ["rightcol1", ..., "rightcol2"], spark)
-```
-
-and RangeQueryRaw
-
-```python
-from sedona.core.geom.envelope import Envelope
-from sedona.core.spatialOperator import RangeQueryRaw
-from sedona.utils.adapter import Adapter
-
-range_query_window = Envelope(-90.01, -80.01, 30.01, 40.01)
-consider_boundary_intersection = False  ## Only return gemeotries fully covered by the window
-using_index = False
-query_result = RangeQueryRaw.SpatialRangeQuery(spatial_rdd, range_query_window, consider_boundary_intersection, using_index)
-gdf = Adapter.toDf(query_result, spark, ["col1", ..., "coln"])
-```
\ No newline at end of file
diff --git a/docs/tutorial/geopandas-shapely.md b/docs/tutorial/geopandas-shapely.md
new file mode 100644
index 00000000..4b1251fa
--- /dev/null
+++ b/docs/tutorial/geopandas-shapely.md
@@ -0,0 +1,342 @@
+# Work with GeoPandas and Shapely
+
+## Interoperate with GeoPandas
+
+Sedona Python has implemented serializers and deserializers which allows to convert Sedona Geometry objects into Shapely BaseGeometry objects. Based on that it is possible to load the data with geopandas from file (look at Fiona possible drivers) and create Spark DataFrame based on GeoDataFrame object.
+
+### From GeoPandas to Sedona DataFrame
+
+Loading the data from shapefile using geopandas read_file method and create Spark DataFrame based on GeoDataFrame:
+
+```python
+
+import geopandas as gpd
+from pyspark.sql import SparkSession
+
+from sedona.register import SedonaRegistrator
+
+spark = SparkSession.builder.\
+      getOrCreate()
+
+SedonaRegistrator.registerAll(spark)
+
+gdf = gpd.read_file("gis_osm_pois_free_1.shp")
+
+spark.createDataFrame(
+  gdf
+).show()
+
+```
+
+This query will show the following outputs:
+
+```
+
++---------+----+-----------+--------------------+--------------------+
+|   osm_id|code|     fclass|                name|            geometry|
++---------+----+-----------+--------------------+--------------------+
+| 26860257|2422|  camp_site|            de Kroon|POINT (15.3393145...|
+| 26860294|2406|     chalet|      Leśne Ustronie|POINT (14.8709625...|
+| 29947493|2402|      motel|                null|POINT (15.0946636...|
+| 29947498|2602|        atm|                null|POINT (15.0732014...|
+| 29947499|2401|      hotel|                null|POINT (15.0696777...|
+| 29947505|2401|      hotel|                null|POINT (15.0155749...|
++---------+----+-----------+--------------------+--------------------+
+
+```
+
+### From Sedona DataFrame to GeoPandas
+
+Reading data with Spark and converting to GeoPandas
+
+```python
+
+import geopandas as gpd
+from pyspark.sql import SparkSession
+
+from sedona.register import SedonaRegistrator
+
+spark = SparkSession.builder.\
+    getOrCreate()
+
+SedonaRegistrator.registerAll(spark)
+
+counties = spark.\
+    read.\
+    option("delimiter", "|").\
+    option("header", "true").\
+    csv("counties.csv")
+
+counties.createOrReplaceTempView("county")
+
+counties_geom = spark.sql(
+    "SELECT *, st_geomFromWKT(geom) as geometry from county"
+)
+
+df = counties_geom.toPandas()
+gdf = gpd.GeoDataFrame(df, geometry="geometry")
+
+gdf.plot(
+    figsize=(10, 8),
+    column="value",
+    legend=True,
+    cmap='YlOrBr',
+    scheme='quantiles',
+    edgecolor='lightgray'
+)
+
+```
+<br>
+<br>
+
+![poland_image](https://user-images.githubusercontent.com/22958216/67603296-c08b4680-f778-11e9-8cde-d2e14ffbba3b.png)
+
+<br>
+<br>
+
+
+## Interoperate with shapely objects
+
+### Supported Shapely objects
+
+| shapely object  | Available          |
+|-----------------|--------------------|
+| Point           | :heavy_check_mark: |
+| MultiPoint      | :heavy_check_mark: |
+| LineString      | :heavy_check_mark: |
+| MultiLinestring | :heavy_check_mark: |
+| Polygon         | :heavy_check_mark: |
+| MultiPolygon    | :heavy_check_mark: |
+
+To create Spark DataFrame based on mentioned Geometry types, please use <b> GeometryType </b> from  <b> sedona.sql.types </b> module. Converting works for list or tuple with shapely objects.
+
+Schema for target table with integer id and geometry type can be defined as follow:
+
+```python
+
+from pyspark.sql.types import IntegerType, StructField, StructType
+
+from sedona.sql.types import GeometryType
+
+schema = StructType(
+    [
+        StructField("id", IntegerType(), False),
+        StructField("geom", GeometryType(), False)
+    ]
+)
+
+```
+
+Also Spark DataFrame with geometry type can be converted to list of shapely objects with <b> collect </b> method.
+
+### Point example
+
+```python
+from shapely.geometry import Point
+
+data = [
+    [1, Point(21.0, 52.0)],
+    [1, Point(23.0, 42.0)],
+    [1, Point(26.0, 32.0)]
+]
+
+
+gdf = spark.createDataFrame(
+    data,
+    schema
+)
+
+gdf.show()
+
+```
+
+```
++---+-------------+
+| id|         geom|
++---+-------------+
+|  1|POINT (21 52)|
+|  1|POINT (23 42)|
+|  1|POINT (26 32)|
++---+-------------+
+```
+
+```python
+gdf.printSchema()
+```
+
+```
+root
+ |-- id: integer (nullable = false)
+ |-- geom: geometry (nullable = false)
+```
+
+### MultiPoint example
+
+```python3
+
+from shapely.geometry import MultiPoint
+
+data = [
+    [1, MultiPoint([[19.511463, 51.765158], [19.446408, 51.779752]])]
+]
+
+gdf = spark.createDataFrame(
+    data,
+    schema
+).show(1, False)
+
+```
+
+```
+
++---+---------------------------------------------------------+
+|id |geom                                                     |
++---+---------------------------------------------------------+
+|1  |MULTIPOINT ((19.511463 51.765158), (19.446408 51.779752))|
++---+---------------------------------------------------------+
+
+
+```
+
+### LineString example
+
+```python3
+
+from shapely.geometry import LineString
+
+line = [(40, 40), (30, 30), (40, 20), (30, 10)]
+
+data = [
+    [1, LineString(line)]
+]
+
+gdf = spark.createDataFrame(
+    data,
+    schema
+)
+
+gdf.show(1, False)
+
+```
+
+```
+
++---+--------------------------------+
+|id |geom                            |
++---+--------------------------------+
+|1  |LINESTRING (10 10, 20 20, 10 40)|
++---+--------------------------------+
+
+```
+
+### MultiLineString example
+
+```python3
+
+from shapely.geometry import MultiLineString
+
+line1 = [(10, 10), (20, 20), (10, 40)]
+line2 = [(40, 40), (30, 30), (40, 20), (30, 10)]
+
+data = [
+    [1, MultiLineString([line1, line2])]
+]
+
+gdf = spark.createDataFrame(
+    data,
+    schema
+)
+
+gdf.show(1, False)
+
+```
+
+```
+
++---+---------------------------------------------------------------------+
+|id |geom                                                                 |
++---+---------------------------------------------------------------------+
+|1  |MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))|
++---+---------------------------------------------------------------------+
+
+```
+
+### Polygon example
+
+```python3
+
+from shapely.geometry import Polygon
+
+polygon = Polygon(
+    [
+         [19.51121, 51.76426],
+         [19.51056, 51.76583],
+         [19.51216, 51.76599],
+         [19.51280, 51.76448],
+         [19.51121, 51.76426]
+    ]
+)
+
+data = [
+    [1, polygon]
+]
+
+gdf = spark.createDataFrame(
+    data,
+    schema
+)
+
+gdf.show(1, False)
+
+```
+
+
+```
+
++---+--------------------------------------------------------------------------------------------------------+
+|id |geom                                                                                                    |
++---+--------------------------------------------------------------------------------------------------------+
+|1  |POLYGON ((19.51121 51.76426, 19.51056 51.76583, 19.51216 51.76599, 19.5128 51.76448, 19.51121 51.76426))|
++---+--------------------------------------------------------------------------------------------------------+
+
+```
+
+### MultiPolygon example
+
+```python3
+
+from shapely.geometry import MultiPolygon
+
+exterior_p1 = [(0, 0), (0, 2), (2, 2), (2, 0), (0, 0)]
+interior_p1 = [(1, 1), (1, 1.5), (1.5, 1.5), (1.5, 1), (1, 1)]
+
+exterior_p2 = [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]
+
+polygons = [
+    Polygon(exterior_p1, [interior_p1]),
+    Polygon(exterior_p2)
+]
+
+data = [
+    [1, MultiPolygon(polygons)]
+]
+
+gdf = spark.createDataFrame(
+    data,
+    schema
+)
+
+gdf.show(1, False)
+
+```
+
+```
+
++---+----------------------------------------------------------------------------------------------------------+
+|id |geom                                                                                                      |
++---+----------------------------------------------------------------------------------------------------------+
+|1  |MULTIPOLYGON (((0 0, 0 2, 2 2, 2 0, 0 0), (1 1, 1.5 1, 1.5 1.5, 1 1.5, 1 1)), ((0 0, 0 1, 1 1, 1 0, 0 0)))|
++---+----------------------------------------------------------------------------------------------------------+
+
+```
+
diff --git a/docs/tutorial/jupyter-notebook.md b/docs/tutorial/jupyter-notebook.md
index 318c073c..bc0a91e2 100644
--- a/docs/tutorial/jupyter-notebook.md
+++ b/docs/tutorial/jupyter-notebook.md
@@ -9,7 +9,7 @@ Please use the following steps to run Jupyter notebook with Pipenv on your machi
 
 1. Clone Sedona GitHub repo or download the source code
 2. Install Sedona Python from PyPI or GitHub source: Read [Install Sedona Python](../../setup/install-python/#install-sedona) to learn.
-3. Prepare python-adapter jar: Read [Install Sedona Python](../../setup/install-python/#prepare-python-adapter-jar) to learn.
+3. Prepare spark-shaded jar: Read [Install Sedona Python](../../setup/install-python/#prepare-spark-shaded-jar) to learn.
 4. Setup pipenv python version. Please use your desired Python version.
 ```bash
 cd binder
diff --git a/docs/tutorial/python-vector-osm.md b/docs/tutorial/python-vector-osm.md
index 23d10d70..0e20414a 100644
--- a/docs/tutorial/python-vector-osm.md
+++ b/docs/tutorial/python-vector-osm.md
@@ -40,7 +40,7 @@ spark = SparkSession.\
     config('spark.kryoserializer.buffer.max', 2047).\
     config("spark.serializer", KryoSerializer.getName).\
     config("spark.kryo.registrator", SedonaKryoRegistrator.getName).\
-    config("spark.jars.packages", "org.apache.sedona:sedona-python-adapter-3.0_2.12:1.1.0-incubating,org.datasyslab:geotools-wrapper:1.1.0-25.2") .\
+    config("spark.jars.packages", "org.apache.sedona:sedona-spark-shaded-3.0_2.12:1.4.0,org.datasyslab:geotools-wrapper:1.4.0-28.2") .\
     enableHiveSupport().\
     getOrCreate()
 
diff --git a/docs/tutorial/raster.md b/docs/tutorial/raster.md
index 6470b9c6..d7c24e0e 100644
--- a/docs/tutorial/raster.md
+++ b/docs/tutorial/raster.md
@@ -8,9 +8,11 @@ Starting from `v1.1.0`, Sedona SQL supports raster data sources and raster opera
 
 ## API docs
 
-[IO of raster data in DataFrame](../../api/sql/Raster-loader/)
+[Read raster data in DataFrame](../../api/sql/Raster-loader/)
 
-[Map algebra in DataFrame](../../api/sql/Raster-operators/)
+[Write raster data in DataFrame](../../api/sql/Raster-writer/)
+
+[Raster operators in DataFrame](../../api/sql/Raster-operators/)
 
 ## Tutorials
 
diff --git a/docs/tutorial/rdd-r.md b/docs/tutorial/rdd-r.md
deleted file mode 100644
index eef1ec08..00000000
--- a/docs/tutorial/rdd-r.md
+++ /dev/null
@@ -1,88 +0,0 @@
-# Spatial RDD applications in R language
-
-## What are `SpatialRDD`s?
-
-[SpatialRDDs](../rdd) are basic building blocks of distributed spatial data in Apache Sedona.
-A `SpatialRDD` can be partitioned and indexed using well-known spatial
-data structures to facilitate range queries, KNN queries, and other
-low-level operations. One can also export records from `SpatailRDD`s
-into regular Spark dataframes, making them accessible through Spark SQL
-and through the `dplyr` interface of `sparklyr`.
-
-## Creating a SpatialRDD
-
-NOTE: this section is largely based on
-[Spatial RDD Scala tutorial](../rdd/#create-a-spatialrdd), except
-for examples have been written in R instead of Scala to reflect usages
-of `apache.sedona`.
-
-Currently `SpatialRDD`s can be created in `apache.sedona` by reading a
-file in a supported geospatial format, or by extracting data from a
-Spark SQL query.
-
-For example, the following code will import data from
-[arealm-small.csv](https://github.com/apache/sedona/blob/master/binder/data/arealm-small.csv)
-into a `SpatialRDD`:
-
-```r
-pt_rdd <- sedona_read_dsv_to_typed_rdd(
-  sc,
-  location = "arealm-small.csv",
-  delimiter = ",",
-  type = "point",
-  first_spatial_col_index = 1,
-  has_non_spatial_attrs = TRUE
-)
-```
-
-.
-
-Records from the example
-[arealm-small.csv](https://github.com/apache/sedona/blob/master/binder/data/arealm-small.csv)
-file look like the following:
-
-    testattribute0,-88.331492,32.324142,testattribute1,testattribute2
-    testattribute0,-88.175933,32.360763,testattribute1,testattribute2
-    testattribute0,-88.388954,32.357073,testattribute1,testattribute2
-
-As one can see from the above, each record is comma-separated and
-consists of a 2-dimensional coordinate starting at the 2nd column and
-ending at the 3rd column. All other columns contain non-spatial
-attributes. Because column indexes are 0-based, we need to specify
-`first_spatial_col_index = 1` in the example above to ensure each record
-is parsed correctly.
-
-In addition to formats such as CSV and TSV, currently `apache.sedona`
-also supports reading files in WKT (Well-Known Text), WKB (Well-Known
-Binary), and GeoJSON formats. See `?apache.sedona::sedona_read_wkt`,
-`?apache.sedona::sedona_read_wkb`, and
-`?apache.sedona::sedona_read_geojson` for details.
-
-One can also run `to_spatial_rdd()` to extract a SpatailRDD from a Spark
-SQL query, e.g.,
-
-```r
-library(sparklyr)
-library(apache.sedona)
-library(dplyr)
-
-sc <- spark_connect(master = "local")
-
-sdf <- tbl(
-  sc,
-  sql("SELECT ST_GeomFromText('POINT(-71.064544 42.28787)') AS `geom`, \"point\" AS `type`")
-)
-
-spatial_rdd <- sdf %>% to_spatial_rdd(spatial_col = "geom")
-print(spatial_rdd)
-```
-
-    ## $.jobj
-    ## <jobj[70]>
-    ##   org.apache.sedona.core.spatialRDD.SpatialRDD
-    ##   org.apache.sedona.core.spatialRDD.SpatialRDD@422afc5a
-    ##
-    ## ...
-
-will extract a spatial column named `"geom"` from the Sedona spatial SQL
-query above and store it in a `SpatialRDD` object.
diff --git a/docs/tutorial/rdd.md b/docs/tutorial/rdd.md
index f97a5a2d..99880f38 100644
--- a/docs/tutorial/rdd.md
+++ b/docs/tutorial/rdd.md
@@ -1,104 +1,83 @@
 
-The page outlines the steps to create Spatial RDDs and run spatial queries using Sedona-core. ==The example code is written in Scala but also works for Java==.
+The page outlines the steps to create Spatial RDDs and run spatial queries using Sedona-core.
 
 ## Set up dependencies
 
-1. Read [Sedona Maven Central coordinates](../setup/maven-coordinates.md)
-2. Select ==the minimum dependencies==: Add Apache Spark (only the Spark core) and Sedona (core).
-3. Add the dependencies in build.sbt or pom.xml.
+=== "Scala/Java"
 
-!!!note
-	To enjoy the full functions of Sedona, we suggest you include ==the full dependencies==: [Apache Spark core](https://mvnrepository.com/artifact/org.apache.spark/spark-core_2.11), [Apache SparkSQL](https://mvnrepository.com/artifact/org.apache.spark/spark-sql), Sedona-core, Sedona-SQL, Sedona-Viz. Please see [RDD example project](../demo/)
+	1. Read [Sedona Maven Central coordinates](../setup/maven-coordinates.md) and add Sedona dependencies in build.sbt or pom.xml.
+	2. Add [Apache Spark core](https://mvnrepository.com/artifact/org.apache.spark/spark-core_2.11), [Apache SparkSQL](https://mvnrepository.com/artifact/org.apache.spark/spark-sql) in build.sbt or pom.xml.
+	3. Please see [RDD example project](../demo/)
 
-## Initiate SparkContext
+=== "Python"
 
-```scala
-val conf = new SparkConf()
-conf.setAppName("SedonaRunnableExample") // Change this to a proper name
-conf.setMaster("local[*]") // Delete this if run in cluster mode
-// Enable Sedona custom Kryo serializer
-conf.set("spark.serializer", classOf[KryoSerializer].getName) // org.apache.spark.serializer.KryoSerializer
-conf.set("spark.kryo.registrator", classOf[SedonaKryoRegistrator].getName) // org.apache.sedona.core.serde.SedonaKryoRegistrator
-val sc = new SparkContext(conf)
-```
+	1. Please read [Quick start](../../setup/install-python) to install Sedona Python.
+	2. This tutorial is based on [Sedona Core Jupyter Notebook example](../jupyter-notebook). You can interact with Sedona Python Jupyter notebook immediately on Binder. Click [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/apache/sedona/HEAD?filepath=binder) to interact with Sedona Python Jupyter notebook immediately on Binder.
 
-!!!warning
-	Sedona has a suite of well-written geometry and index serializers. Forgetting to enable these serializers will lead to high memory consumption.
+## Initiate SparkContext
 
-If you add ==the Sedona full dependencies== as suggested above, please use the following two lines to enable Sedona Kryo serializer instead:
-```scala
-conf.set("spark.serializer", classOf[KryoSerializer].getName) // org.apache.spark.serializer.KryoSerializer
-conf.set("spark.kryo.registrator", classOf[SedonaVizKryoRegistrator].getName) // org.apache.sedona.viz.core.Serde.SedonaVizKryoRegistrator
-```
+=== "Scala"
 
-## Create a SpatialRDD
+	```scala
+	val conf = new SparkConf()
+	conf.setAppName("SedonaRunnableExample") // Change this to a proper name
+	conf.setMaster("local[*]") // Delete this if run in cluster mode
+	// Enable Sedona custom Kryo serializer
+	conf.set("spark.serializer", classOf[KryoSerializer].getName) // org.apache.spark.serializer.KryoSerializer
+	conf.set("spark.kryo.registrator", classOf[SedonaKryoRegistrator].getName) // org.apache.sedona.core.serde.SedonaKryoRegistrator
+	val sc = new SparkContext(conf)
+	```
+	
+	If you add ==the Sedona full dependencies== as suggested above, please use the following two lines to enable Sedona Kryo serializer instead:
+	```scala
+	conf.set("spark.serializer", classOf[KryoSerializer].getName) // org.apache.spark.serializer.KryoSerializer
+	conf.set("spark.kryo.registrator", classOf[SedonaVizKryoRegistrator].getName) // org.apache.sedona.viz.core.Serde.SedonaVizKryoRegistrator
+	```
 
-### Create a typed SpatialRDD
-Sedona-core provides three special SpatialRDDs: ==PointRDD, PolygonRDD, and LineStringRDD==. They can be loaded from CSV, TSV, WKT, WKB, Shapefiles, GeoJSON and NetCDF/HDF format.
+=== "Java"
 
-#### PointRDD from CSV/TSV
-Suppose we have a `checkin.csv` CSV file at Path `/Download/checkin.csv` as follows:
-```
--88.331492,32.324142,hotel
--88.175933,32.360763,gas
--88.388954,32.357073,bar
--88.221102,32.35078,restaurant
-```
-This file has three columns and corresponding ==offsets==(Column IDs) are 0, 1, 2.
-Use the following code to create a PointRDD
+	```java
+	SparkConf conf = new SparkConf()
+	conf.setAppName("SedonaRunnableExample") // Change this to a proper name
+	conf.setMaster("local[*]") // Delete this if run in cluster mode
+	// Enable Sedona custom Kryo serializer
+	conf.set("spark.serializer", KryoSerializer.class.getName) // org.apache.spark.serializer.KryoSerializer
+	conf.set("spark.kryo.registrator", SedonaKryoRegistrator.class.getName) // org.apache.sedona.core.serde.SedonaKryoRegistrator
+	SparkContext sc = new SparkContext(conf)
+	```
+	
+	If you use SedonaViz with SedonaRDD, please use the following two lines to enable Sedona Kryo serializer instead:
+	```scala
+	conf.set("spark.serializer", KryoSerializer.class.getName) // org.apache.spark.serializer.KryoSerializer
+	conf.set("spark.kryo.registrator", SedonaVizKryoRegistrator.class.getName) // org.apache.sedona.viz.core.Serde.SedonaVizKryoRegistrator
+	```
 
-```scala
-val pointRDDInputLocation = "/Download/checkin.csv"
-val pointRDDOffset = 0 // The point long/lat starts from Column 0
-val pointRDDSplitter = FileDataSplitter.CSV
-val carryOtherAttributes = true // Carry Column 2 (hotel, gas, bar...)
-var objectRDD = new PointRDD(sc, pointRDDInputLocation, pointRDDOffset, pointRDDSplitter, carryOtherAttributes)
-```
+=== "Python"
 
-If the data file is in TSV format, just simply use the following line to replace the old FileDataSplitter:
-```scala
-val pointRDDSplitter = FileDataSplitter.TSV
+```python
+conf.set("spark.serializer", KryoSerializer.getName)
+conf.set("spark.kryo.registrator", SedonaKryoRegistrator.getName)
+sc = SparkContext(conf=conf)
 ```
 
-#### PolygonRDD/LineStringRDD from CSV/TSV
-In general, polygon and line string data is stored in WKT, WKB, GeoJSON and Shapefile formats instead of CSV/TSV because the geometries in a file may have different lengths. However, if all polygons / line strings in your CSV/TSV possess the same length, you can create PolygonRDD and LineStringRDD from these files.
+!!!warning
+	Sedona has a suite of well-written geometry and index serializers. Forgetting to enable these serializers will lead to high memory consumption.
 
-Suppose we have a `checkinshape.csv` CSV file at Path `/Download/checkinshape.csv` as follows:
-```
--88.331492,32.324142,-88.331492,32.324142,-88.331492,32.324142,-88.331492,32.324142,-88.331492,32.324142,hotel
--88.175933,32.360763,-88.175933,32.360763,-88.175933,32.360763,-88.175933,32.360763,-88.175933,32.360763,gas
--88.388954,32.357073,-88.388954,32.357073,-88.388954,32.357073,-88.388954,32.357073,-88.388954,32.357073,bar
--88.221102,32.35078,-88.221102,32.35078,-88.221102,32.35078,-88.221102,32.35078,-88.221102,32.35078,restaurant
-```
+## Create a SpatialRDD
 
-This file has 11 columns and corresponding offsets (Column IDs) are 0 - 10. Column 0 - 9 are 5 coordinates (longitude/latitude pairs). In this file, all geometries have the same number of coordinates. The geometries can be polyons or line strings.
+### Create a typed SpatialRDD
+Sedona-core provides three special SpatialRDDs: PointRDD, PolygonRDD, and LineStringRDD.
 
 !!!warning
-	For polygon data, the last coordinate must be the same as the first coordinate because a polygon is a closed linear ring.
-	
-Use the following code to create a PolygonRDD.
-```scala
-val polygonRDDInputLocation = "/Download/checkinshape.csv"
-val polygonRDDStartOffset = 0 // The coordinates start from Column 0
-val polygonRDDEndOffset = 9 // The coordinates end at Column 9
-val polygonRDDSplitter = FileDataSplitter.CSV
-val carryOtherAttributes = true // Carry Column 10 (hotel, gas, bar...)
-var objectRDD = new PolygonRDD(sc, polygonRDDInputLocation, polygonRDDStartOffset, polygonRDDEndOffset, polygonRDDSplitter, carryOtherAttributes)
-```
-
-If the data file is in TSV format, just simply use the following line to replace the old FileDataSplitter:
-```scala
-val polygonRDDSplitter = FileDataSplitter.TSV
-```
-
-The way to create a LineStringRDD is the same as PolygonRDD.
+	Typed SpatialRDD has been deprecated for a long time. We do NOT recommend it anymore.
 
 ### Create a generic SpatialRDD
 
 A generic SpatialRDD is not typed to a certain geometry type and open to more scenarios. It allows an input data file contains mixed types of geometries. For instance, a WKT file contains three types gemetries ==LineString==, ==Polygon== and ==MultiPolygon==.
 
 #### From WKT/WKB
-Geometries in a WKT and WKB file always occupy a single column no matter how many coordinates they have. Therefore, creating a typed SpatialRDD is easy.
+
+Geometries in a WKT and WKB file always occupy a single column no matter how many coordinates they have. Sedona provides `WktReader ` and `WkbReader` to create generic SpatialRDD.
 
 Suppose we have a `checkin.tsv` WKT TSV file at Path `/Download/checkin.tsv` as follows:
 ```
@@ -111,13 +90,37 @@ This file has two columns and corresponding ==offsets==(Column IDs) are 0, 1. Co
 
 Use the following code to create a SpatialRDD
 
-```scala
-val inputLocation = "/Download/checkin.tsv"
-val wktColumn = 0 // The WKT string starts from Column 0
-val allowTopologyInvalidGeometries = true // Optional
-val skipSyntaxInvalidGeometries = false // Optional
-val spatialRDD = WktReader.readToGeometryRDD(sparkSession.sparkContext, inputLocation, wktColumn, allowTopologyInvalidGeometries, skipSyntaxInvalidGeometries)
-```
+=== "Scala"
+
+	```scala
+	val inputLocation = "/Download/checkin.tsv"
+	val wktColumn = 0 // The WKT string starts from Column 0
+	val allowTopologyInvalidGeometries = true // Optional
+	val skipSyntaxInvalidGeometries = false // Optional
+	val spatialRDD = WktReader.readToGeometryRDD(sparkSession.sparkContext, inputLocation, wktColumn, allowTopologyInvalidGeometries, skipSyntaxInvalidGeometries)
+	```
+
+=== "Java"
+
+	```java
+	String inputLocation = "/Download/checkin.tsv"
+	int wktColumn = 0 // The WKT string starts from Column 0
+	boolean allowTopologyInvalidGeometries = true // Optional
+	boolean skipSyntaxInvalidGeometries = false // Optional
+	SpatialRDD spatialRDD = WktReader.readToGeometryRDD(sparkSession.sparkContext, inputLocation, wktColumn, allowTopologyInvalidGeometries, skipSyntaxInvalidGeometries)
+	```
+
+=== "Python"
+
+	```python
+	from sedona.core.formatMapper import WktReader
+	from sedona.core.formatMapper import WkbReader
+	
+	WktReader.readToGeometryRDD(sc, wkt_geometries_location, 0, True, False)
+	
+	WkbReader.readToGeometryRDD(sc, wkb_geometries_location, 0, True, False)
+	```
+
 
 #### From GeoJSON
 
@@ -134,25 +137,65 @@ Suppose we have a `polygon.json` GeoJSON file at Path `/Download/polygon.json` a
 ```
 
 Use the following code to create a generic SpatialRDD:
-```scala
-val inputLocation = "/Download/polygon.json"
-val allowTopologyInvalidGeometries = true // Optional
-val skipSyntaxInvalidGeometries = false // Optional
-val spatialRDD = GeoJsonReader.readToGeometryRDD(sparkSession.sparkContext, inputLocation, allowTopologyInvalidGeometries, skipSyntaxInvalidGeometries)
-```
+
+=== "Scala"
+
+	```scala
+	val inputLocation = "/Download/polygon.json"
+	val allowTopologyInvalidGeometries = true // Optional
+	val skipSyntaxInvalidGeometries = false // Optional
+	val spatialRDD = GeoJsonReader.readToGeometryRDD(sparkSession.sparkContext, inputLocation, allowTopologyInvalidGeometries, skipSyntaxInvalidGeometries)
+	```
+
+=== "Java"
+
+	```java
+	String inputLocation = "/Download/polygon.json"
+	boolean allowTopologyInvalidGeometries = true // Optional
+	boolean skipSyntaxInvalidGeometries = false // Optional
+	SpatialRDD spatialRDD = GeoJsonReader.readToGeometryRDD(sparkSession.sparkContext, inputLocation, allowTopologyInvalidGeometries, skipSyntaxInvalidGeometries)
+	```
+	
+=== "Python"
+
+	```python
+	from sedona.core.formatMapper import GeoJsonReader
+	
+	GeoJsonReader.readToGeometryRDD(sc, geo_json_file_location)
+	```
 
 !!!warning
 	The way that Sedona reads JSON file is different from SparkSQL
 	
 #### From Shapefile
 
-```scala
-val shapefileInputLocation="/Download/myshapefile"
-val spatialRDD = ShapefileReader.readToGeometryRDD(sparkSession.sparkContext, shapefileInputLocation)
-```
+=== "Scala"
+
+	```scala
+	val shapefileInputLocation="/Download/myshapefile"
+	val spatialRDD = ShapefileReader.readToGeometryRDD(sparkSession.sparkContext, shapefileInputLocation)
+	```
+
+=== "Java"
+
+	```java
+	String shapefileInputLocation="/Download/myshapefile"
+	SpatialRDD spatialRDD = ShapefileReader.readToGeometryRDD(sparkSession.sparkContext, shapefileInputLocation)
+	```
+
+=== "Python"
+
+	```python
+	from sedona.core.formatMapper.shapefileParser import ShapefileReader
+	
+	ShapefileReader.readToGeometryRDD(sc, shape_file_location)
+	```
+
+
 
 !!!note
 	The file extensions of .shp, .shx, .dbf must be in lowercase. Assume you have a shape file called ==myShapefile==, the file structure should be like this:
+	
 	```
 	- shapefile1
 	- shapefile2
@@ -165,19 +208,24 @@ val spatialRDD = ShapefileReader.readToGeometryRDD(sparkSession.sparkContext, sh
 	```
 
 If the file you are reading contains non-ASCII characters you'll need to explicitly set the encoding
-via `sedona.global.charset` system property before the call to `ShapefileReader.readToGeometryRDD`.
+via `sedona.global.charset` system property before creating your Spark context.
 
 Example:
 
 ```scala
 System.setProperty("sedona.global.charset", "utf8")
+
+val sc = new SparkContext(...)
 ```
 
-#### From SparkSQL DataFrame
+#### From SedonaSQL DataFrame
 
-To create a generic SpatialRDD from CSV, TSV, WKT, WKB and GeoJSON input formats, you can use SedonaSQL. Make sure you include ==the full dependencies== of Sedona. Read [SedonaSQL API](../../api/sql/Overview).
+!!!note
+	More details about SedonaSQL, please read the SedonaSQL tutorial.
 
-We use [checkin.csv CSV file](#pointrdd-from-csvtsv) as the example. You can create a generic SpatialRDD using the following steps:
+To create a generic SpatialRDD from CSV, TSV, WKT, WKB and GeoJSON input formats, you can use SedonaSQL.
+
+We use checkin.csv CSV file as the example. You can create a generic SpatialRDD using the following steps:
 
 1. Load data in SedonaSQL.
 ```scala
@@ -207,48 +255,92 @@ Sedona doesn't control the coordinate unit (degree-based or meter-based) of all
 
 To convert Coordinate Reference System of an SpatialRDD, use the following code:
 
-```scala
-val sourceCrsCode = "epsg:4326" // WGS84, the most common degree-based CRS
-val targetCrsCode = "epsg:3857" // The most common meter-based CRS
-objectRDD.CRSTransform(sourceCrsCode, targetCrsCode, false)
-```
+=== "Scala"
+
+	```scala
+	val sourceCrsCode = "epsg:4326" // WGS84, the most common degree-based CRS
+	val targetCrsCode = "epsg:3857" // The most common meter-based CRS
+	objectRDD.CRSTransform(sourceCrsCode, targetCrsCode, false)
+	```
+
+=== "Java"
+
+	```java
+	String sourceCrsCode = "epsg:4326" // WGS84, the most common degree-based CRS
+	String targetCrsCode = "epsg:3857" // The most common meter-based CRS
+	objectRDD.CRSTransform(sourceCrsCode, targetCrsCode, false)
+	```
+
+=== "Python"
+
+	```python
+	sourceCrsCode = "epsg:4326" // WGS84, the most common degree-based CRS
+	targetCrsCode = "epsg:3857" // The most common meter-based CRS
+	objectRDD.CRSTransform(sourceCrsCode, targetCrsCode, False)
+	```
 
 `false` in CRSTransform(sourceCrsCode, targetCrsCode, false) means that it will not tolerate Datum shift. If you want it to be lenient, use `true` instead.
 
 !!!warning
 	CRS transformation should be done right after creating each SpatialRDD, otherwise it will lead to wrong query results. For instance, use something like this:
+
+
+=== "Scala"
+
 	```scala
-	var objectRDD = new PointRDD(sc, pointRDDInputLocation, pointRDDOffset, pointRDDSplitter, carryOtherAttributes)
+	val objectRDD = WktReader.readToGeometryRDD(sparkSession.sparkContext, inputLocation, wktColumn, allowTopologyInvalidGeometries, skipSyntaxInvalidGeometries)
+	objectRDD.CRSTransform("epsg:4326", "epsg:3857", false)
+	```
+
+=== "Java"
+
+	```java
+	SpatialRDD objectRDD = WktReader.readToGeometryRDD(sparkSession.sparkContext, inputLocation, wktColumn, allowTopologyInvalidGeometries, skipSyntaxInvalidGeometries)
 	objectRDD.CRSTransform("epsg:4326", "epsg:3857", false)
 	```
 
+=== "Python"
+
+	```python
+	objectRDD = WktReader.readToGeometryRDD(sparkSession.sparkContext, inputLocation, wktColumn, allowTopologyInvalidGeometries, skipSyntaxInvalidGeometries)
+	objectRDD.CRSTransform("epsg:4326", "epsg:3857", False)
+	```
+
 The details CRS information can be found on [EPSG.io](https://epsg.io/)
 
 ## Read other attributes in an SpatialRDD
 
-Each SpatialRDD can carry non-spatial attributes such as price, age and name as long as the user sets ==carryOtherAttributes== as [TRUE](#create-a-spatialrdd).
+Each SpatialRDD can carry non-spatial attributes such as price, age and name.
 
 The other attributes are combined together to a string and stored in ==UserData== field of each geometry.
 
 To retrieve the UserData field, use the following code:
-```scala
-val rddWithOtherAttributes = objectRDD.rawSpatialRDD.rdd.map[String](f=>f.getUserData.asInstanceOf[String])
-```
+
+=== "Scala"
+
+	```scala
+	val rddWithOtherAttributes = objectRDD.rawSpatialRDD.rdd.map[String](f=>f.getUserData.asInstanceOf[String])
+	```
+
+=== "Java"
+
+	```java
+	SpatialRDD<Geometry> spatialRDD = Adapter.toSpatialRdd(spatialDf, "arealandmark");
+	spatialRDD.rawSpatialRDD.map(obj -> {return obj.getUserData();});
+	```
+
+=== "Python"
+
+	```python
+	rdd_with_other_attributes = object_rdd.rawSpatialRDD.map(lambda x: x.getUserData())
+	```
 
 ## Write a Spatial Range Query
 
 A spatial range query takes as input a range query window and an SpatialRDD and returns all geometries that have specified relationship with the query window.
 
-
 Assume you now have an SpatialRDD (typed or generic). You can use the following code to issue an Spatial Range Query on it.
 
-```scala
-val rangeQueryWindow = new Envelope(-90.01, -80.01, 30.01, 40.01)
-val spatialPredicate = SpatialPredicate.COVERED_BY // Only return gemeotries fully covered by the window
-val usingIndex = false
-var queryResult = RangeQuery.SpatialRangeQuery(spatialRDD, rangeQueryWindow, spatialPredicate, usingIndex)
-```
-
 ==spatialPredicate== can be set to `SpatialPredicate.INTERSECTS` to return all geometries intersect with query window. Supported spatial predicates are:
 
 * `CONTAINS`: geometry is completely inside the query window
@@ -269,41 +361,110 @@ var queryResult = RangeQuery.SpatialRangeQuery(spatialRDD, rangeQueryWindow, spa
 	WHERE ST_Intersects(checkin.location, queryWindow)
 	```
 
+=== "Scala"
+
+	```scala
+	val rangeQueryWindow = new Envelope(-90.01, -80.01, 30.01, 40.01)
+	val spatialPredicate = SpatialPredicate.COVERED_BY // Only return gemeotries fully covered by the window
+	val usingIndex = false
+	var queryResult = RangeQuery.SpatialRangeQuery(spatialRDD, rangeQueryWindow, spatialPredicate, usingIndex)
+	```
+
+=== "Java"
+
+	```java
+	Envelope rangeQueryWindow = new Envelope(-90.01, -80.01, 30.01, 40.01)
+	SpatialPredicate spatialPredicate = SpatialPredicate.COVERED_BY // Only return gemeotries fully covered by the window
+	boolean usingIndex = false
+	JavaRDD queryResult = RangeQuery.SpatialRangeQuery(spatialRDD, rangeQueryWindow, spatialPredicate, usingIndex)
+	```
+
+=== "Python"
+
+	```python
+	from sedona.core.geom.envelope import Envelope
+	from sedona.core.spatialOperator import RangeQuery
+	
+	range_query_window = Envelope(-90.01, -80.01, 30.01, 40.01)
+	consider_boundary_intersection = False  ## Only return gemeotries fully covered by the window
+	using_index = False
+	query_result = RangeQuery.SpatialRangeQuery(spatial_rdd, range_query_window, consider_boundary_intersection, using_index)
+	```
+
+!!!note
+    Sedona Python users: Please use RangeQueryRaw from the same module if you want to avoid jvm python serde while converting to Spatial DataFrame. It takes the same parameters as RangeQuery but returns reference to jvm rdd which can be converted to dataframe without python - jvm serde using Adapter.
+    
+    Example:
+    ```python
+    from sedona.core.geom.envelope import Envelope
+    from sedona.core.spatialOperator import RangeQueryRaw
+    from sedona.utils.adapter import Adapter
+    
+    range_query_window = Envelope(-90.01, -80.01, 30.01, 40.01)
+    consider_boundary_intersection = False  ## Only return gemeotries fully covered by the window
+    using_index = False
+    query_result = RangeQueryRaw.SpatialRangeQuery(spatial_rdd, range_query_window, consider_boundary_intersection, using_index)
+    gdf = Adapter.toDf(query_result, spark, ["col1", ..., "coln"])
+    ```
+
+
 ### Range query window
 
 Besides the rectangle (Envelope) type range query window, Sedona range query window can be Point/Polygon/LineString.
 
-The code to create a point is as follows:
+The code to create a point, linestring (4 vertexes) and polygon (4 vertexes) is as follows:
 
-```scala
-val geometryFactory = new GeometryFactory()
-val pointObject = geometryFactory.createPoint(new Coordinate(-84.01, 34.01))
-```
+=== "Scala"
 
-The code to create a polygon (with 4 vertexes) is as follows:
+	```scala
+	val geometryFactory = new GeometryFactory()
+	val pointObject = geometryFactory.createPoint(new Coordinate(-84.01, 34.01))
+	
+	val geometryFactory = new GeometryFactory()
+	val coordinates = new Array[Coordinate](5)
+	coordinates(0) = new Coordinate(0,0)
+	coordinates(1) = new Coordinate(0,4)
+	coordinates(2) = new Coordinate(4,4)
+	coordinates(3) = new Coordinate(4,0)
+	coordinates(4) = coordinates(0) // The last coordinate is the same as the first coordinate in order to compose a closed ring
+	val polygonObject = geometryFactory.createPolygon(coordinates)
+	
+	val geometryFactory = new GeometryFactory()
+	val coordinates = new Array[Coordinate](4)
+	coordinates(0) = new Coordinate(0,0)
+	coordinates(1) = new Coordinate(0,4)
+	coordinates(2) = new Coordinate(4,4)
+	coordinates(3) = new Coordinate(4,0)
+	val linestringObject = geometryFactory.createLineString(coordinates)
+	```
 
-```scala
-val geometryFactory = new GeometryFactory()
-val coordinates = new Array[Coordinate](5)
-coordinates(0) = new Coordinate(0,0)
-coordinates(1) = new Coordinate(0,4)
-coordinates(2) = new Coordinate(4,4)
-coordinates(3) = new Coordinate(4,0)
-coordinates(4) = coordinates(0) // The last coordinate is the same as the first coordinate in order to compose a closed ring
-val polygonObject = geometryFactory.createPolygon(coordinates)
-```
+=== "Java"
 
-The code to create a line string (with 4 vertexes) is as follows:
+	```java
+	GeometryFactory geometryFactory = new GeometryFactory()
+	Point pointObject = geometryFactory.createPoint(new Coordinate(-84.01, 34.01))
+	
+	GeometryFactory geometryFactory = new GeometryFactory()
+	Coordinate[] coordinates = new Array[Coordinate](5)
+	coordinates(0) = new Coordinate(0,0)
+	coordinates(1) = new Coordinate(0,4)
+	coordinates(2) = new Coordinate(4,4)
+	coordinates(3) = new Coordinate(4,0)
+	coordinates(4) = coordinates(0) // The last coordinate is the same as the first coordinate in order to compose a closed ring
+	Polygon polygonObject = geometryFactory.createPolygon(coordinates)
+	
+	GeometryFactory geometryFactory = new GeometryFactory()
+	val coordinates = new Array[Coordinate](4)
+	coordinates(0) = new Coordinate(0,0)
+	coordinates(1) = new Coordinate(0,4)
+	coordinates(2) = new Coordinate(4,4)
+	coordinates(3) = new Coordinate(4,0)
+	LineString linestringObject = geometryFactory.createLineString(coordinates)
+	```
 
-```scala
-val geometryFactory = new GeometryFactory()
-val coordinates = new Array[Coordinate](4)
-coordinates(0) = new Coordinate(0,0)
-coordinates(1) = new Coordinate(0,4)
-coordinates(2) = new Coordinate(4,4)
-coordinates(3) = new Coordinate(4,0)
-val linestringObject = geometryFactory.createLineString(coordinates)
-```
+=== "Python"
+
+	A Shapely geometry can be used as a query window. To create shapely geometries, please follow [Shapely official docs](https://shapely.readthedocs.io/en/stable/manual.html)
 
 ### Use spatial indexes
 
@@ -311,23 +472,101 @@ Sedona provides two types of spatial indexes, Quad-Tree and R-Tree. Once you spe
 
 To utilize a spatial index in a spatial range query, use the following code:
 
-```scala
-val rangeQueryWindow = new Envelope(-90.01, -80.01, 30.01, 40.01)
-val spatialPredicate = SpatialPredicate.COVERED_BY // Only return gemeotries fully covered by the window
+=== "Scala"
 
-val buildOnSpatialPartitionedRDD = false // Set to TRUE only if run join query
-spatialRDD.buildIndex(IndexType.QUADTREE, buildOnSpatialPartitionedRDD)
+	```scala
+	val rangeQueryWindow = new Envelope(-90.01, -80.01, 30.01, 40.01)
+	val spatialPredicate = SpatialPredicate.COVERED_BY // Only return gemeotries fully covered by the window
+	
+	val buildOnSpatialPartitionedRDD = false // Set to TRUE only if run join query
+	spatialRDD.buildIndex(IndexType.QUADTREE, buildOnSpatialPartitionedRDD)
+	
+	val usingIndex = true
+	var queryResult = RangeQuery.SpatialRangeQuery(spatialRDD, rangeQueryWindow, spatialPredicate, usingIndex)
+	```
 
-val usingIndex = true
-var queryResult = RangeQuery.SpatialRangeQuery(spatialRDD, rangeQueryWindow, spatialPredicate, usingIndex)
-```
+=== "Java"
+
+	```java
+	Envelope rangeQueryWindow = new Envelope(-90.01, -80.01, 30.01, 40.01)
+	SpatialPredicate spatialPredicate = SpatialPredicate.COVERED_BY // Only return gemeotries fully covered by the window
+	
+	boolean buildOnSpatialPartitionedRDD = false // Set to TRUE only if run join query
+	spatialRDD.buildIndex(IndexType.QUADTREE, buildOnSpatialPartitionedRDD)
+	
+	boolean usingIndex = true
+	JavaRDD queryResult = RangeQuery.SpatialRangeQuery(spatialRDD, rangeQueryWindow, spatialPredicate, usingIndex)
+	```
+
+=== "Python"
+
+	```python
+	from sedona.core.geom.envelope import Envelope
+	from sedona.core.enums import IndexType
+	from sedona.core.spatialOperator import RangeQuery
+	
+	range_query_window = Envelope(-90.01, -80.01, 30.01, 40.01)
+	consider_boundary_intersection = False ## Only return gemeotries fully covered by the window
+	
+	build_on_spatial_partitioned_rdd = False ## Set to TRUE only if run join query
+	spatial_rdd.buildIndex(IndexType.QUADTREE, build_on_spatial_partitioned_rdd)
+	
+	using_index = True
+	
+	query_result = RangeQuery.SpatialRangeQuery(
+	    spatial_rdd,
+	    range_query_window,
+	    consider_boundary_intersection,
+	    using_index
+	)
+	```
 
 !!!tip
 	Using an index might not be the best choice all the time because building index also takes time. A spatial index is very useful when your data is complex polygons and line strings.
 
 ### Output format
 
-The output format of the spatial range query is another SpatialRDD.
+=== "Scala/Java"
+
+	The output format of the spatial range query is another SpatialRDD.
+
+=== "Python"
+
+	The output format of the spatial range query is another RDD which consists of GeoData objects.
+	
+	SpatialRangeQuery result can be used as RDD with map or other spark RDD functions. Also it can be used as 
+	Python objects when using collect method.
+	Example:
+	
+	```python
+	query_result.map(lambda x: x.geom.length).collect()
+	```
+	
+	```
+	[
+	 1.5900840000000045,
+	 1.5906639999999896,
+	 1.1110299999999995,
+	 1.1096700000000084,
+	 1.1415619999999933,
+	 1.1386399999999952,
+	 1.1415619999999933,
+	 1.1418860000000137,
+	 1.1392780000000045,
+	 ...
+	]
+	```
+	
+	Or transformed to GeoPandas GeoDataFrame
+	
+	```python
+	import geopandas as gpd
+	gpd.GeoDataFrame(
+	    query_result.map(lambda x: [x.geom, x.userData]).collect(),
+	    columns=["geom", "user_data"],
+	    geometry="geom"
+	)
+	```
 
 ## Write a Spatial KNN Query
 
@@ -335,13 +574,37 @@ A spatial K Nearnest Neighbor query takes as input a K, a query point and an Spa
 
 Assume you now have an SpatialRDD (typed or generic). You can use the following code to issue an Spatial KNN Query on it.
 
-```scala
-val geometryFactory = new GeometryFactory()
-val pointObject = geometryFactory.createPoint(new Coordinate(-84.01, 34.01))
-val K = 1000 // K Nearest Neighbors
-val usingIndex = false
-val result = KNNQuery.SpatialKnnQuery(objectRDD, pointObject, K, usingIndex)
-```
+=== "Scala"
+
+	```scala
+	val geometryFactory = new GeometryFactory()
+	val pointObject = geometryFactory.createPoint(new Coordinate(-84.01, 34.01))
+	val K = 1000 // K Nearest Neighbors
+	val usingIndex = false
+	val result = KNNQuery.SpatialKnnQuery(objectRDD, pointObject, K, usingIndex)
+	```
+
+=== "Java"
+
+	```java
+	GeometryFactory geometryFactory = new GeometryFactory()
+	Point pointObject = geometryFactory.createPoint(new Coordinate(-84.01, 34.01))
+	int K = 1000 // K Nearest Neighbors
+	boolean usingIndex = false
+	JavaRDD result = KNNQuery.SpatialKnnQuery(objectRDD, pointObject, K, usingIndex)
+	```
+
+=== "Python"
+
+	```python
+	from sedona.core.spatialOperator import KNNQuery
+	from shapely.geometry import Point
+	
+	point = Point(-84.01, 34.01)
+	k = 1000 ## K Nearest Neighbors
+	using_index = False
+	result = KNNQuery.SpatialKnnQuery(object_rdd, point, k, using_index)
+	```
 
 !!!note
 	Spatial KNN query that returns 5 Nearest Neighbors is equal to the following statement in Spatial SQL
@@ -356,7 +619,13 @@ val result = KNNQuery.SpatialKnnQuery(objectRDD, pointObject, K, usingIndex)
 
 Besides the Point type, Sedona KNN query center can be Polygon and LineString.
 
-To learn how to create Polygon and LineString object, see [Range query window](#range-query-window).
+=== "Scala/Java"
+
+	To learn how to create Polygon and LineString object, see [Range query window](#range-query-window).
+
+=== "Python"
+
+	To create Polygon or Linestring object please follow [Shapely official docs](https://shapely.readthedocs.io/en/stable/manual.html)
 
 
 
@@ -365,43 +634,126 @@ To learn how to create Polygon and LineString object, see [Range query window](#
 
 To utilize a spatial index in a spatial KNN query, use the following code:
 
-```scala
-val geometryFactory = new GeometryFactory()
-val pointObject = geometryFactory.createPoint(new Coordinate(-84.01, 34.01))
-val K = 1000 // K Nearest Neighbors
+=== "Scala"
 
+	```scala
+	val geometryFactory = new GeometryFactory()
+	val pointObject = geometryFactory.createPoint(new Coordinate(-84.01, 34.01))
+	val K = 1000 // K Nearest Neighbors
+	
+	
+	val buildOnSpatialPartitionedRDD = false // Set to TRUE only if run join query
+	objectRDD.buildIndex(IndexType.RTREE, buildOnSpatialPartitionedRDD)
+	
+	val usingIndex = true
+	val result = KNNQuery.SpatialKnnQuery(objectRDD, pointObject, K, usingIndex)
+	```
 
-val buildOnSpatialPartitionedRDD = false // Set to TRUE only if run join query
-objectRDD.buildIndex(IndexType.RTREE, buildOnSpatialPartitionedRDD)
+=== "Java"
+
+	```java
+	GeometryFactory geometryFactory = new GeometryFactory()
+	Point pointObject = geometryFactory.createPoint(new Coordinate(-84.01, 34.01))
+	val K = 1000 // K Nearest Neighbors
+	
+	
+	boolean buildOnSpatialPartitionedRDD = false // Set to TRUE only if run join query
+	objectRDD.buildIndex(IndexType.RTREE, buildOnSpatialPartitionedRDD)
+	
+	boolean usingIndex = true
+	JavaRDD result = KNNQuery.SpatialKnnQuery(objectRDD, pointObject, K, usingIndex)
+	```
+
+=== "Python"
+
+	```python
+	from sedona.core.spatialOperator import KNNQuery
+	from sedona.core.enums import IndexType
+	from shapely.geometry import Point
+	
+	point = Point(-84.01, 34.01)
+	k = 5 ## K Nearest Neighbors
+	
+	build_on_spatial_partitioned_rdd = False ## Set to TRUE only if run join query
+	spatial_rdd.buildIndex(IndexType.RTREE, build_on_spatial_partitioned_rdd)
+	
+	using_index = True
+	result = KNNQuery.SpatialKnnQuery(spatial_rdd, point, k, using_index)
+	```
 
-val usingIndex = true
-val result = KNNQuery.SpatialKnnQuery(objectRDD, pointObject, K, usingIndex)
-```
 
 !!!warning
 	Only R-Tree index supports Spatial KNN query
 
 ### Output format
 
-The output format of the spatial KNN query is a list of geometries. The list has K geometry objects.
+=== "Scala/Java"
+
+	The output format of the spatial KNN query is a list of geometries. The list has K geometry objects.
+
+=== "Python"
+
+	The output format of the spatial KNN query is a list of GeoData objects. 
+	The list has K GeoData objects.
+	
+	Example:
+	```python
+	>> result
+	
+	[GeoData, GeoData, GeoData, GeoData, GeoData]
+	```
 
 ## Write a Spatial Join Query
 
+
 A spatial join query takes as input two Spatial RDD A and B. For each geometry in A, finds the geometries (from B) covered/intersected by it. A and B can be any geometry type and are not necessary to have the same geometry type.
 
 Assume you now have two SpatialRDDs (typed or generic). You can use the following code to issue an Spatial Join Query on them.
 
-```scala
-val spatialPredicate = SpatialPredicate.COVERED_BY // Only return gemeotries fully covered by each query window in queryWindowRDD
-val usingIndex = false
+=== "Scala"
 
-objectRDD.analyze()
+	```scala
+	val spatialPredicate = SpatialPredicate.COVERED_BY // Only return gemeotries fully covered by each query window in queryWindowRDD
+	val usingIndex = false
+	
+	objectRDD.analyze()
+	
+	objectRDD.spatialPartitioning(GridType.KDBTREE)
+	queryWindowRDD.spatialPartitioning(objectRDD.getPartitioner)
+	
+	val result = JoinQuery.SpatialJoinQuery(objectRDD, queryWindowRDD, usingIndex, spatialPredicate)
+	```
 
-objectRDD.spatialPartitioning(GridType.KDBTREE)
-queryWindowRDD.spatialPartitioning(objectRDD.getPartitioner)
+=== "Java"
 
-val result = JoinQuery.SpatialJoinQuery(objectRDD, queryWindowRDD, usingIndex, spatialPredicate)
-```
+	```java
+	SpatialPredicate spatialPredicate = SpatialPredicate.COVERED_BY // Only return gemeotries fully covered by each query window in queryWindowRDD
+	val usingIndex = false
+	
+	objectRDD.analyze()
+	
+	objectRDD.spatialPartitioning(GridType.KDBTREE)
+	queryWindowRDD.spatialPartitioning(objectRDD.getPartitioner)
+	
+	JavaPairRDD result = JoinQuery.SpatialJoinQuery(objectRDD, queryWindowRDD, usingIndex, spatialPredicate)
+	```
+
+=== "Python"
+
+	```python
+	from sedona.core.enums import GridType
+	from sedona.core.spatialOperator import JoinQuery
+	
+	consider_boundary_intersection = False ## Only return geometries fully covered by each query window in queryWindowRDD
+	using_index = False
+	
+	object_rdd.analyze()
+	
+	object_rdd.spatialPartitioning(GridType.KDBTREE)
+	query_window_rdd.spatialPartitioning(object_rdd.getPartitioner())
+	
+	result = JoinQuery.SpatialJoinQuery(object_rdd, query_window_rdd, using_index, consider_boundary_intersection)
+	```
 
 !!!note
 	Spatial join query is equal to the following query in Spatial SQL:
@@ -418,51 +770,156 @@ Sedona spatial partitioning method can significantly speed up the join query. Th
 
 If you first partition SpatialRDD A, then you must use the partitioner of A to partition B.
 
-```scala
-objectRDD.spatialPartitioning(GridType.KDBTREE)
-queryWindowRDD.spatialPartitioning(objectRDD.getPartitioner)
-```
+=== "Scala/Java"
+
+	```scala
+	objectRDD.spatialPartitioning(GridType.KDBTREE)
+	queryWindowRDD.spatialPartitioning(objectRDD.getPartitioner)
+	```
+
+=== "Python"
+
+	```python
+	object_rdd.spatialPartitioning(GridType.KDBTREE)
+	query_window_rdd.spatialPartitioning(object_rdd.getPartitioner())
+	```
 
 Or 
 
-```scala
-queryWindowRDD.spatialPartitioning(GridType.KDBTREE)
-objectRDD.spatialPartitioning(queryWindowRDD.getPartitioner)
-```
+=== "Scala/Java"
+
+	```scala
+	queryWindowRDD.spatialPartitioning(GridType.KDBTREE)
+	objectRDD.spatialPartitioning(queryWindowRDD.getPartitioner)
+	```
+
+=== "Python"
+
+	```python
+	query_window_rdd.spatialPartitioning(GridType.KDBTREE)
+	object_rdd.spatialPartitioning(query_window_rdd.getPartitioner())
+	```
 
 
 ### Use spatial indexes
 
 To utilize a spatial index in a spatial join query, use the following code:
 
-```scala
-objectRDD.spatialPartitioning(joinQueryPartitioningType)
-queryWindowRDD.spatialPartitioning(objectRDD.getPartitioner)
+=== "Scala"
 
-val buildOnSpatialPartitionedRDD = true // Set to TRUE only if run join query
-val usingIndex = true
-queryWindowRDD.buildIndex(IndexType.QUADTREE, buildOnSpatialPartitionedRDD)
+	```scala
+	objectRDD.spatialPartitioning(joinQueryPartitioningType)
+	queryWindowRDD.spatialPartitioning(objectRDD.getPartitioner)
+	
+	val buildOnSpatialPartitionedRDD = true // Set to TRUE only if run join query
+	val usingIndex = true
+	queryWindowRDD.buildIndex(IndexType.QUADTREE, buildOnSpatialPartitionedRDD)
+	
+	val result = JoinQuery.SpatialJoinQueryFlat(objectRDD, queryWindowRDD, usingIndex, spatialPredicate)
+	```
 
-val result = JoinQuery.SpatialJoinQueryFlat(objectRDD, queryWindowRDD, usingIndex, spatialPredicate)
-```
+=== "Java"
+
+	```java
+	objectRDD.spatialPartitioning(joinQueryPartitioningType)
+	queryWindowRDD.spatialPartitioning(objectRDD.getPartitioner)
+	
+	boolean buildOnSpatialPartitionedRDD = true // Set to TRUE only if run join query
+	boolean usingIndex = true
+	queryWindowRDD.buildIndex(IndexType.QUADTREE, buildOnSpatialPartitionedRDD)
+	
+	JavaPairRDD result = JoinQuery.SpatialJoinQueryFlat(objectRDD, queryWindowRDD, usingIndex, spatialPredicate)
+	```
+
+=== "Python"
+
+	```python
+	from sedona.core.enums import GridType
+	from sedona.core.enums import IndexType
+	from sedona.core.spatialOperator import JoinQuery
+	
+	object_rdd.spatialPartitioning(GridType.KDBTREE)
+	query_window_rdd.spatialPartitioning(object_rdd.getPartitioner())
+	
+	build_on_spatial_partitioned_rdd = True ## Set to TRUE only if run join query
+	using_index = True
+	query_window_rdd.buildIndex(IndexType.QUADTREE, build_on_spatial_partitioned_rdd)
+	
+	result = JoinQuery.SpatialJoinQueryFlat(object_rdd, query_window_rdd, using_index, True)
+	```
 
 The index should be built on either one of two SpatialRDDs. In general, you should build it on the larger SpatialRDD.
 
 ### Output format
 
-The output format of the spatial join query is a PairRDD. In this PairRDD, each object is a pair of two geometries. The left one is the geometry from objectRDD and the right one is the geometry from the queryWindowRDD.
+=== "Scala/Java"
 
-```
-Point,Polygon
-Point,Polygon
-Point,Polygon
-Polygon,Polygon
-LineString,LineString
-Polygon,LineString
-...
-```
+	The output format of the spatial join query is a PairRDD. In this PairRDD, each object is a pair of two geometries. The left one is the geometry from objectRDD and the right one is the geometry from the queryWindowRDD.
+	
+	```
+	Point,Polygon
+	Point,Polygon
+	Point,Polygon
+	Polygon,Polygon
+	LineString,LineString
+	Polygon,LineString
+	...
+	```
+	
+	Each object on the left is covered/intersected by the object on the right.
+
+=== "Python"
+
+	Result for this query is RDD which holds two GeoData objects within list of lists.
+	Example:
+	```python
+	result.collect()
+	```
+	
+	```
+	[[GeoData, GeoData], [GeoData, GeoData] ...]
+	```
+	
+	It is possible to do some RDD operation on result data ex. Getting polygon centroid.
+	```python
+	result.map(lambda x: x[0].geom.centroid).collect()
+	```
+
+!!!note
+    Sedona Python users: Please use JoinQueryRaw from the same module for methods 
+    
+    - spatialJoin
+    
+    - DistanceJoinQueryFlat
+
+    - SpatialJoinQueryFlat
+
+    For better performance while converting to dataframe with adapter. 
+    That approach allows to avoid costly serialization between Python 
+    and jvm and in result operating on python object instead of native geometries.
+    
+    Example:
+    ```python
+    from sedona.core.SpatialRDD import CircleRDD
+    from sedona.core.enums import GridType
+    from sedona.core.spatialOperator import JoinQueryRaw
+    
+    object_rdd.analyze()
+    
+    circle_rdd = CircleRDD(object_rdd, 0.1) ## Create a CircleRDD using the given distance
+    circle_rdd.analyze()
+    
+    circle_rdd.spatialPartitioning(GridType.KDBTREE)
+    spatial_rdd.spatialPartitioning(circle_rdd.getPartitioner())
+    
+    consider_boundary_intersection = False ## Only return gemeotries fully covered by each query window in queryWindowRDD
+    using_index = False
+    
+    result = JoinQueryRaw.DistanceJoinQueryFlat(spatial_rdd, circle_rdd, using_index, consider_boundary_intersection)
+    
+    gdf = Adapter.toDf(result, ["left_col1", ..., "lefcoln"], ["rightcol1", ..., "rightcol2"], spark)
+    ```
 
-Each object on the left is covered/intersected by the object on the right.
 
 ## Write a Distance Join Query
 
@@ -473,19 +930,58 @@ A distance join query takes as input two Spatial RDD A and B and a distance. For
 
 Assume you now have two SpatialRDDs (typed or generic). You can use the following code to issue an Distance Join Query on them.
 
-```scala
-objectRddA.analyze()
+=== "Scala"
 
-val circleRDD = new CircleRDD(objectRddA, 0.1) // Create a CircleRDD using the given distance
+	```scala
+	objectRddA.analyze()
+	
+	val circleRDD = new CircleRDD(objectRddA, 0.1) // Create a CircleRDD using the given distance
+	
+	circleRDD.spatialPartitioning(GridType.KDBTREE)
+	objectRddB.spatialPartitioning(circleRDD.getPartitioner)
+	
+	val spatialPredicate = SpatialPredicate.COVERED_BY // Only return gemeotries fully covered by each query window in queryWindowRDD
+	val usingIndex = false
+	
+	val result = JoinQuery.DistanceJoinQueryFlat(objectRddB, circleRDD, usingIndex, spatialPredicate)
+	```
 
-circleRDD.spatialPartitioning(GridType.KDBTREE)
-objectRddB.spatialPartitioning(circleRDD.getPartitioner)
+=== "Java"
+
+	```java
+	objectRddA.analyze()
+	
+	CircleRDD circleRDD = new CircleRDD(objectRddA, 0.1) // Create a CircleRDD using the given distance
+	
+	circleRDD.spatialPartitioning(GridType.KDBTREE)
+	objectRddB.spatialPartitioning(circleRDD.getPartitioner)
+	
+	SpatialPredicate spatialPredicate = SpatialPredicate.COVERED_BY // Only return gemeotries fully covered by each query window in queryWindowRDD
+	boolean usingIndex = false
+	
+	JavaPairRDD result = JoinQuery.DistanceJoinQueryFlat(objectRddB, circleRDD, usingIndex, spatialPredicate)
+	```
 
-val spatialPredicate = SpatialPredicate.COVERED_BY // Only return gemeotries fully covered by each query window in queryWindowRDD
-val usingIndex = false
+=== "Python"
 
-val result = JoinQuery.DistanceJoinQueryFlat(objectRddB, circleRDD, usingIndex, spatialPredicate)
-```
+	```python
+	from sedona.core.SpatialRDD import CircleRDD
+	from sedona.core.enums import GridType
+	from sedona.core.spatialOperator import JoinQuery
+	
+	object_rdd.analyze()
+	
+	circle_rdd = CircleRDD(object_rdd, 0.1) ## Create a CircleRDD using the given distance
+	circle_rdd.analyze()
+	
+	circle_rdd.spatialPartitioning(GridType.KDBTREE)
+	spatial_rdd.spatialPartitioning(circle_rdd.getPartitioner())
+	
+	consider_boundary_intersection = False ## Only return gemeotries fully covered by each query window in queryWindowRDD
+	using_index = False
+	
+	result = JoinQuery.DistanceJoinQueryFlat(spatial_rdd, circle_rdd, using_index, consider_boundary_intersection)
+	```
 
 Distance join can only accept `COVERED_BY` and `INTERSECTS` as spatial predicates. The rest part of the join query is same as the spatial join query.
 
@@ -545,9 +1041,17 @@ objectRDD.saveAsGeoJSON("hdfs://PATH")
 
 Use the following code to save an SpatialRDD as a distributed object file:
 
-```scala
-objectRDD.rawSpatialRDD.saveAsObjectFile("hdfs://PATH")
-```
+=== "Scala/Java"
+
+	```scala
+	objectRDD.rawSpatialRDD.saveAsObjectFile("hdfs://PATH")
+	```
+
+=== "Python"
+
+	```python
+	object_rdd.rawJvmSpatialRDD.saveAsObjectFile("hdfs://PATH")
+	```
 
 !!!note
 	Each object in a distributed object file is a byte array (not human-readable). This byte array is the serialized format of a Geometry or a SpatialIndex.
@@ -574,27 +1078,52 @@ You can easily reload an SpatialRDD that has been saved to ==a distributed objec
 
 #### Load to a typed SpatialRDD
 
-Use the following code to reload the PointRDD/PolygonRDD/LineStringRDD:
+!!!warning
+	Typed SpatialRDD has been deprecated for a long time. We do NOT recommend it anymore.
 
-```scala
-var savedRDD = new PointRDD(sc.objectFile[Point]("hdfs://PATH"))
+#### Load to a generic SpatialRDD
 
-var savedRDD = new PointRDD(sc.objectFile[Polygon]("hdfs://PATH"))
+Use the following code to reload the SpatialRDD:
 
-var savedRDD = new PointRDD(sc.objectFile[LineString]("hdfs://PATH"))
-```
+=== "Scala"
 
-#### Load to a generic SpatialRDD
+	```scala
+	var savedRDD = new SpatialRDD[Geometry]
+	savedRDD.rawSpatialRDD = sc.objectFile[Geometry]("hdfs://PATH")
+	```
 
-Use the following code to reload the SpatialRDD:
+=== "Java"
 
-```scala
-var savedRDD = new SpatialRDD[Geometry]
-savedRDD.rawSpatialRDD = sc.objectFile[Geometry]("hdfs://PATH")
-```
+	```java
+	SpatialRDD savedRDD = new SpatialRDD<Geometry>
+	savedRDD.rawSpatialRDD = sc.objectFile<Geometry>("hdfs://PATH")
+	```
+
+=== "Python"
+
+	```python
+	saved_rdd = load_spatial_rdd_from_disc(sc, "hdfs://PATH", GeoType.GEOMETRY)
+	```
 
 Use the following code to reload the indexed SpatialRDD:
-```scala
-var savedRDD = new SpatialRDD[Geometry]
-savedRDD.indexedRawRDD = sc.objectFile[SpatialIndex]("hdfs://PATH")
-```
+
+=== "Scala"
+
+	```scala
+	var savedRDD = new SpatialRDD[Geometry]
+	savedRDD.indexedRawRDD = sc.objectFile[SpatialIndex]("hdfs://PATH")
+	```
+
+=== "Java"
+
+	```java
+	SpatialRDD savedRDD = new SpatialRDD<Geometry>
+	savedRDD.indexedRawRDD = sc.objectFile<SpatialIndex>("hdfs://PATH")
+	```
+
+=== "Python"
+
+	```python
+	saved_rdd = SpatialRDD()
+	saved_rdd.indexedRawRDD = load_spatial_index_rdd_from_disc(sc, "hdfs://PATH")
+	```
\ No newline at end of file
diff --git a/docs/tutorial/sql-pure-sql.md b/docs/tutorial/sql-pure-sql.md
index cdc6097c..cbc1a8a8 100644
--- a/docs/tutorial/sql-pure-sql.md
+++ b/docs/tutorial/sql-pure-sql.md
@@ -8,7 +8,7 @@ SedonaSQL supports SQL/MM Part3 Spatial SQL Standard. Detailed SedonaSQL APIs ar
 Start `spark-sql` as following (replace `<VERSION>` with actual version, like, `1.0.1-incubating`):
 
 ```sh
-spark-sql --packages org.apache.sedona:sedona-python-adapter-3.0_2.12:<VERSION>,org.apache.sedona:sedona-viz-3.0_2.12:<VERSION>,org.datasyslab:geotools-wrapper:geotools-24.0 \
+spark-sql --packages org.apache.sedona:sedona-spark-shaded-3.0_2.12:<VERSION>,org.apache.sedona:sedona-viz-3.0_2.12:<VERSION>,org.datasyslab:geotools-wrapper:geotools-24.0 \
   --conf spark.serializer=org.apache.spark.serializer.KryoSerializer \
   --conf spark.kryo.registrator=org.apache.sedona.viz.core.Serde.SedonaVizKryoRegistrator \
   --conf spark.sql.extensions=org.apache.sedona.viz.sql.SedonaVizExtensions,org.apache.sedona.sql.SedonaSqlExtensions
diff --git a/docs/tutorial/sql-python.md b/docs/tutorial/sql-python.md
deleted file mode 100644
index af472571..00000000
--- a/docs/tutorial/sql-python.md
+++ /dev/null
@@ -1,562 +0,0 @@
-# Spatial SQL Application in Python
-
-## Introduction
-
-This package is an extension to Apache Spark SQL package. It allow to use 
-spatial functions on dataframes.
-
-SedonaSQL supports SQL/MM Part3 Spatial SQL Standard. 
-It includes four kinds of SQL operators as follows.
-All these operators can be directly called through:
-
-```python
-spark.sql("YOUR_SQL")
-```
-
-!!!note
-	This tutorial is based on [Sedona SQL Jupyter Notebook example](../jupyter-notebook). You can interact with Sedona Python Jupyter notebook immediately on Binder. Click [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/apache/sedona/HEAD?filepath=binder) and wait for a few minutes. Then select a notebook and enjoy!
-	
-## Installation
-
-Please read [Quick start](../../setup/install-python) to install Sedona Python.
-
-## Register package
-Before writing any code with Sedona please use the following code.
-
-```python
-from sedona.register import SedonaRegistrator
-
-SedonaRegistrator.registerAll(spark)
-```
-
-You can also register functions by passing `--conf spark.sql.extensions=org.apache.sedona.sql.SedonaSqlExtensions` to `spark-submit` or `spark-shell`.
-
-## Writing Application
-
-Use KryoSerializer.getName and SedonaKryoRegistrator.getName class properties to reduce memory impact.
-
-```python
-spark = SparkSession.\
-    builder.\
-    master("local[*]").\
-    appName("Sedona App").\
-    config("spark.serializer", KryoSerializer.getName).\
-    config("spark.kryo.registrator", SedonaKryoRegistrator.getName) .\
-    getOrCreate()
-```
-
-To turn on SedonaSQL function inside pyspark code use SedonaRegistrator.registerAll method on existing pyspark.sql.SparkSession instance ex.
-
-`SedonaRegistrator.registerAll(spark)`
-
-After that all the functions from SedonaSQL are available,
-moreover using collect or toPandas methods on Spark DataFrame 
-returns Shapely BaseGeometry objects. 
-
-Based on GeoPandas DataFrame,
-Pandas DataFrame with shapely objects or Sequence with 
-shapely objects, Spark DataFrame can be created using 
-spark.createDataFrame method. To specify Schema with 
-geometry inside please use `GeometryType()` instance 
-(look at examples section to see that in practice).
-
-
-### Examples
-
-### SedonaSQL
-
-
-All SedonaSQL functions (list depends on SedonaSQL version) are available in Python API.
-For details please refer to API/SedonaSQL page.
-
-For example use SedonaSQL for Spatial Join.
-
-```python3
-
-counties = spark.\
-    read.\
-    option("delimiter", "|").\
-    option("header", "true").\
-    csv("counties.csv")
-
-counties.createOrReplaceTempView("county")
-
-counties_geom = spark.sql(
-      "SELECT county_code, st_geomFromWKT(geom) as geometry from county"
-)
-
-counties_geom.show(5)
-
-```
-```
-+-----------+--------------------+
-|county_code|            geometry|
-+-----------+--------------------+
-|       1815|POLYGON ((21.6942...|
-|       1410|POLYGON ((22.7238...|
-|       1418|POLYGON ((21.1100...|
-|       1425|POLYGON ((20.9891...|
-|       1427|POLYGON ((19.5087...|
-+-----------+--------------------+
-```
-```python3
-import geopandas as gpd
-
-points = gpd.read_file("gis_osm_pois_free_1.shp")
-
-points_geom = spark.createDataFrame(
-    points[["fclass", "geometry"]]
-)
-
-points_geom.show(5, False)
-```
-```
-+---------+-----------------------------+
-|fclass   |geometry                     |
-+---------+-----------------------------+
-|camp_site|POINT (15.3393145 52.3504247)|
-|chalet   |POINT (14.8709625 52.691693) |
-|motel    |POINT (15.0946636 52.3130396)|
-|atm      |POINT (15.0732014 52.3141083)|
-|hotel    |POINT (15.0696777 52.3143013)|
-+---------+-----------------------------+
-```
-
-```python3
-
-points_geom.createOrReplaceTempView("pois")
-counties_geom.createOrReplaceTempView("counties")
-
-spatial_join_result = spark.sql(
-    """
-        SELECT c.county_code, p.fclass
-        FROM pois AS p, counties AS c
-        WHERE ST_Intersects(p.geometry, c.geometry)
-    """
-)
-
-spatial_join_result.explain()
-
-```
-```
-== Physical Plan ==
-*(2) Project [county_code#230, fclass#239]
-+- RangeJoin geometry#240: geometry, geometry#236: geometry, true
-   :- Scan ExistingRDD[fclass#239,geometry#240]
-   +- Project [county_code#230, st_geomfromwkt(geom#232) AS geometry#236]
-      +- *(1) FileScan csv [county_code#230,geom#232] Batched: false, Format: CSV, Location: InMemoryFileIndex[file:/projects/sedona/counties.csv], PartitionFilters: [], PushedFilters: [], ReadSchema: struct<county_code:string,geom:string>
-```
-Calculating Number of Pois within counties per fclass.
-
-```python3
-pois_per_county = spatial_join_result.groupBy("county_code", "fclass"). \
-    count()
-
-pois_per_county.show(5, False)
-
-```
-```
-+-----------+---------+-----+
-|county_code|fclass   |count|
-+-----------+---------+-----+
-|0805       |atm      |6    |
-|0805       |bench    |75   |
-|0803       |museum   |9    |
-|0802       |fast_food|5    |
-|0862       |atm      |20   |
-+-----------+---------+-----+
-```
-
-## Integration with GeoPandas and Shapely
-
-
-sedona has implemented serializers and deserializers which allows to convert Sedona Geometry objects into Shapely BaseGeometry objects. Based on that it is possible to load the data with geopandas from file (look at Fiona possible drivers) and create Spark DataFrame based on GeoDataFrame object.
-
-Example, loading the data from shapefile using geopandas read_file method and create Spark DataFrame based on GeoDataFrame:
-
-```python
-
-import geopandas as gpd
-from pyspark.sql import SparkSession
-
-from sedona.register import SedonaRegistrator
-
-spark = SparkSession.builder.\
-      getOrCreate()
-
-SedonaRegistrator.registerAll(spark)
-
-gdf = gpd.read_file("gis_osm_pois_free_1.shp")
-
-spark.createDataFrame(
-  gdf
-).show()
-
-```
-
-```
-
-+---------+----+-----------+--------------------+--------------------+
-|   osm_id|code|     fclass|                name|            geometry|
-+---------+----+-----------+--------------------+--------------------+
-| 26860257|2422|  camp_site|            de Kroon|POINT (15.3393145...|
-| 26860294|2406|     chalet|      Leśne Ustronie|POINT (14.8709625...|
-| 29947493|2402|      motel|                null|POINT (15.0946636...|
-| 29947498|2602|        atm|                null|POINT (15.0732014...|
-| 29947499|2401|      hotel|                null|POINT (15.0696777...|
-| 29947505|2401|      hotel|                null|POINT (15.0155749...|
-+---------+----+-----------+--------------------+--------------------+
-
-```
-
-Reading data with Spark and converting to GeoPandas
-
-```python
-
-import geopandas as gpd
-from pyspark.sql import SparkSession
-
-from sedona.register import SedonaRegistrator
-
-spark = SparkSession.builder.\
-    getOrCreate()
-
-SedonaRegistrator.registerAll(spark)
-
-counties = spark.\
-    read.\
-    option("delimiter", "|").\
-    option("header", "true").\
-    csv("counties.csv")
-
-counties.createOrReplaceTempView("county")
-
-counties_geom = spark.sql(
-    "SELECT *, st_geomFromWKT(geom) as geometry from county"
-)
-
-df = counties_geom.toPandas()
-gdf = gpd.GeoDataFrame(df, geometry="geometry")
-
-gdf.plot(
-    figsize=(10, 8),
-    column="value",
-    legend=True,
-    cmap='YlOrBr',
-    scheme='quantiles',
-    edgecolor='lightgray'
-)
-
-```
-<br>
-<br>
-
-![poland_image](https://user-images.githubusercontent.com/22958216/67603296-c08b4680-f778-11e9-8cde-d2e14ffbba3b.png)
-
-<br>
-<br>
-
-### DataFrame Style API
-
-Sedona functions can be called used a DataFrame style API similar to PySpark's own functions.
-The functions are spread across four different modules: `sedona.sql.st_constructors`, `sedona.sql.st_functions`, `sedona.sql.st_predicates`, and `sedona.sql.st_aggregates`.
-All of the functions can take columns or strings as arguments and will return a column representing the sedona function call.
-This makes them integratable with `DataFrame.select`, `DataFrame.join`, and all of the PySpark functions found in the `pyspark.sql.functions` module.
-
-As an example of the flexibility:
-
-```python3
-from pyspark.sql import functions as f
-
-from sedona.sql import st_constructors as stc
-
-df = spark.sql("SELECT array(0.0, 1.0, 2.0) AS values")
-
-min_value = f.array_min("values")
-max_value = f.array_max("values")
-
-df = df.select(stc.ST_Point(min_value, max_value).alias("point"))
-```
-
-The above code will generate the following dataframe:
-```
-+-----------+
-|point      |
-+-----------+
-|POINT (0 2)|
-+-----------+
-```
-
-Some functions will take native python values and infer them as literals.
-For example:
-
-```python3
-df = df.select(stc.ST_Point(1.0, 3.0).alias("point"))
-```
-
-This will generate a dataframe with a constant point in a column:
-```
-+-----------+
-|point      |
-+-----------+
-|POINT (1 3)|
-+-----------+
-```
-
-For a description of what values a function may take please refer to their specific docstrings.
-
-The following rules are followed when passing values to the sedona functions:
-1. `Column` type arguments are passed straight through and are always accepted.
-1. `str` type arguments are always assumed to be names of columns and are wrapped in a `Column` to support that.
-If an actual string literal needs to be passed then it will need to be wrapped in a `Column` using `pyspark.sql.functions.lit`.
-1. Any other types of arguments are checked on a per function basis.
-Generally, arguments that could reasonably support a python native type are accepted and passed through.
-Check the specific docstring of the function to be sure.
-1. Shapely `Geometry` objects are not currently accepted in any of the functions.
-
-## Creating Spark DataFrame based on shapely objects
-
-### Supported Shapely objects
-
-| shapely object  | Available          |
-|-----------------|--------------------|
-| Point           | :heavy_check_mark: |
-| MultiPoint      | :heavy_check_mark: |
-| LineString      | :heavy_check_mark: |
-| MultiLinestring | :heavy_check_mark: |
-| Polygon         | :heavy_check_mark: |
-| MultiPolygon    | :heavy_check_mark: |
-
-To create Spark DataFrame based on mentioned Geometry types, please use <b> GeometryType </b> from  <b> sedona.sql.types </b> module. Converting works for list or tuple with shapely objects.
-
-Schema for target table with integer id and geometry type can be defined as follow:
-
-```python
-
-from pyspark.sql.types import IntegerType, StructField, StructType
-
-from sedona.sql.types import GeometryType
-
-schema = StructType(
-    [
-        StructField("id", IntegerType(), False),
-        StructField("geom", GeometryType(), False)
-    ]
-)
-
-```
-
-Also Spark DataFrame with geometry type can be converted to list of shapely objects with <b> collect </b> method.
-
-## Example usage for Shapely objects
-
-### Point
-
-```python
-from shapely.geometry import Point
-
-data = [
-    [1, Point(21.0, 52.0)],
-    [1, Point(23.0, 42.0)],
-    [1, Point(26.0, 32.0)]
-]
-
-
-gdf = spark.createDataFrame(
-    data,
-    schema
-)
-
-gdf.show()
-
-```
-
-```
-+---+-------------+
-| id|         geom|
-+---+-------------+
-|  1|POINT (21 52)|
-|  1|POINT (23 42)|
-|  1|POINT (26 32)|
-+---+-------------+
-```
-
-```python
-gdf.printSchema()
-```
-
-```
-root
- |-- id: integer (nullable = false)
- |-- geom: geometry (nullable = false)
-```
-
-### MultiPoint
-
-```python3
-
-from shapely.geometry import MultiPoint
-
-data = [
-    [1, MultiPoint([[19.511463, 51.765158], [19.446408, 51.779752]])]
-]
-
-gdf = spark.createDataFrame(
-    data,
-    schema
-).show(1, False)
-
-```
-
-```
-
-+---+---------------------------------------------------------+
-|id |geom                                                     |
-+---+---------------------------------------------------------+
-|1  |MULTIPOINT ((19.511463 51.765158), (19.446408 51.779752))|
-+---+---------------------------------------------------------+
-
-
-```
-
-### LineString
-
-```python3
-
-from shapely.geometry import LineString
-
-line = [(40, 40), (30, 30), (40, 20), (30, 10)]
-
-data = [
-    [1, LineString(line)]
-]
-
-gdf = spark.createDataFrame(
-    data,
-    schema
-)
-
-gdf.show(1, False)
-
-```
-
-```
-
-+---+--------------------------------+
-|id |geom                            |
-+---+--------------------------------+
-|1  |LINESTRING (10 10, 20 20, 10 40)|
-+---+--------------------------------+
-
-```
-
-### MultiLineString
-
-```python3
-
-from shapely.geometry import MultiLineString
-
-line1 = [(10, 10), (20, 20), (10, 40)]
-line2 = [(40, 40), (30, 30), (40, 20), (30, 10)]
-
-data = [
-    [1, MultiLineString([line1, line2])]
-]
-
-gdf = spark.createDataFrame(
-    data,
-    schema
-)
-
-gdf.show(1, False)
-
-```
-
-```
-
-+---+---------------------------------------------------------------------+
-|id |geom                                                                 |
-+---+---------------------------------------------------------------------+
-|1  |MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))|
-+---+---------------------------------------------------------------------+
-
-```
-
-### Polygon
-
-```python3
-
-from shapely.geometry import Polygon
-
-polygon = Polygon(
-    [
-         [19.51121, 51.76426],
-         [19.51056, 51.76583],
-         [19.51216, 51.76599],
-         [19.51280, 51.76448],
-         [19.51121, 51.76426]
-    ]
-)
-
-data = [
-    [1, polygon]
-]
-
-gdf = spark.createDataFrame(
-    data,
-    schema
-)
-
-gdf.show(1, False)
-
-```
-
-
-```
-
-+---+--------------------------------------------------------------------------------------------------------+
-|id |geom                                                                                                    |
-+---+--------------------------------------------------------------------------------------------------------+
-|1  |POLYGON ((19.51121 51.76426, 19.51056 51.76583, 19.51216 51.76599, 19.5128 51.76448, 19.51121 51.76426))|
-+---+--------------------------------------------------------------------------------------------------------+
-
-```
-
-### MultiPolygon
-
-```python3
-
-from shapely.geometry import MultiPolygon
-
-exterior_p1 = [(0, 0), (0, 2), (2, 2), (2, 0), (0, 0)]
-interior_p1 = [(1, 1), (1, 1.5), (1.5, 1.5), (1.5, 1), (1, 1)]
-
-exterior_p2 = [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)]
-
-polygons = [
-    Polygon(exterior_p1, [interior_p1]),
-    Polygon(exterior_p2)
-]
-
-data = [
-    [1, MultiPolygon(polygons)]
-]
-
-gdf = spark.createDataFrame(
-    data,
-    schema
-)
-
-gdf.show(1, False)
-
-```
-
-```
-
-+---+----------------------------------------------------------------------------------------------------------+
-|id |geom                                                                                                      |
-+---+----------------------------------------------------------------------------------------------------------+
-|1  |MULTIPOLYGON (((0 0, 0 2, 2 2, 2 0, 0 0), (1 1, 1.5 1, 1.5 1.5, 1 1.5, 1 1)), ((0 0, 0 1, 1 1, 1 0, 0 0)))|
-+---+----------------------------------------------------------------------------------------------------------+
-
-```
diff --git a/docs/tutorial/sql-r.md b/docs/tutorial/sql-r.md
deleted file mode 100644
index 4ac72201..00000000
--- a/docs/tutorial/sql-r.md
+++ /dev/null
@@ -1,59 +0,0 @@
-# Spatial SQL applications in R language
-
-
-In `apache.sedona` , `sdf_register()`, a S3 generic from `sparklyr`
-converting a lower-level object to a Spark dataframe, can be applied to
-a `SpatialRDD` objects:
-
-```r
-library(sparklyr)
-library(apache.sedona)
-
-sc <- spark_connect(master = "local")
-polygon_rdd <- sedona_read_geojson(sc, location = "/tmp/polygon.json")
-polygon_sdf <- polygon_rdd %>% sdf_register()
-
-polygon_sdf %>% print(n = 3)
-```
-
-    ## # Source: spark<?> [?? x 1]
-    ##   geometry
-    ##   <list>
-    ## 1 <POLYGON ((-87.621765 34.873444, -87.617535 34.873369, -87.6123 34.873337, -8…
-    ## 2 <POLYGON ((-85.719017 31.297901, -85.715626 31.305203, -85.714271 31.307096, …
-    ## 3 <POLYGON ((-86.000685 34.00537, -85.998837 34.009768, -85.998012 34.010398, -…
-    ## # … with more rows
-
-The resulting Spark dataframe object can then be modified using `dplyr`
-verbs familiar to many R users. In addition, spatial UDFs supported by
-Sedona can inter-operate seamlessly with other functions supported in
-`sparklyr`’s dbplyr SQL translation env. For example, the code below
-finds the average area of all polygons in `polygon_sdf`:
-
-```r
-mean_area_sdf <- polygon_sdf %>%
-  dplyr::summarize(mean_area = mean(ST_Area(geometry)))
-print(mean_area_sdf)
-```
-
-    ## # Source: spark<?> [?? x 1]
-    ##   mean_area
-    ##       <dbl>
-    ## 1   0.00217
-
-Once spatial objects are imported into Spark dataframes, they can also
-be easily integrated with other non-spatial attributes, e.g.,
-
-```r
-modified_polygon_sdf <- polygon_sdf %>%
-  dplyr::mutate(type = "polygon")
-```
-
-
-Notice that all of the above can open up many interesting possibilities. For
-example, one can extract ML features from geospatial data in Spark
-dataframes, build a ML pipeline using `ml_*` family of functions in
-`sparklyr` to work with such features, and if the output of a ML model
-happens to be a geospatial object as well, one can even apply
-visualization routines in `apache.sedona` to visualize the difference
-between any predicted geometry and the corresponding ground truth.
diff --git a/docs/tutorial/sql.md b/docs/tutorial/sql.md
index 0219c3e8..81b43c9d 100644
--- a/docs/tutorial/sql.md
+++ b/docs/tutorial/sql.md
@@ -1,50 +1,123 @@
-The page outlines the steps to manage spatial data using SedonaSQL. ==The example code is written in Scala but also works for Java==.
+The page outlines the steps to manage spatial data using SedonaSQL.
+
 
 SedonaSQL supports SQL/MM Part3 Spatial SQL Standard. It includes four kinds of SQL operators as follows. All these operators can be directly called through:
-```scala
-var myDataFrame = sparkSession.sql("YOUR_SQL")
-```
 
-Detailed SedonaSQL APIs are available here: [SedonaSQL API](../api/sql/Overview.md)
+=== "Scala"
+
+	```scala
+	var myDataFrame = sparkSession.sql("YOUR_SQL")
+	myDataFrame.createOrReplaceTempView("spatialDf")
+	```
+
+=== "Java"
+
+	```java
+	Dataset<Row> myDataFrame = sparkSession.sql("YOUR_SQL")
+	myDataFrame.createOrReplaceTempView("spatialDf")
+	```
+	
+=== "Python"
+
+	```python
+	myDataFrame = sparkSession.sql("YOUR_SQL")
+	myDataFrame.createOrReplaceTempView("spatialDf")
+	```
+
+Detailed SedonaSQL APIs are available here: [SedonaSQL API](../api/sql/Overview.md). You can find example county data (i.e., `county_small.tsv`) in [Sedona GitHub repo](https://github.com/apache/sedona/tree/master/core/src/test/resources).
 
 ## Set up dependencies
 
-1. Read [Sedona Maven Central coordinates](../setup/maven-coordinates.md)
-2. Select ==the minimum dependencies==: Add [Apache Spark core](https://mvnrepository.com/artifact/org.apache.spark/spark-core_2.11), [Apache SparkSQL](https://mvnrepository.com/artifact/org.apache.spark/spark-sql), Sedona-core and Sedona-SQL
-3. Add the dependencies in build.sbt or pom.xml.
+=== "Scala/Java"
 
-!!!note
-	To enjoy the full functions of Sedona, we suggest you include ==the full dependencies==: [Apache Spark core](https://mvnrepository.com/artifact/org.apache.spark/spark-core_2.11), [Apache SparkSQL](https://mvnrepository.com/artifact/org.apache.spark/spark-sql), Sedona-core, Sedona-SQL, Sedona-Viz. Please see [SQL example project](../demo/)
+	1. Read [Sedona Maven Central coordinates](../setup/maven-coordinates.md) and add Sedona dependencies in build.sbt or pom.xml.
+	2. Add [Apache Spark core](https://mvnrepository.com/artifact/org.apache.spark/spark-core_2.11), [Apache SparkSQL](https://mvnrepository.com/artifact/org.apache.spark/spark-sql) in build.sbt or pom.xml.
+	3. Please see [SQL example project](../demo/)
+
+=== "Python"
 
+	1. Please read [Quick start](../../setup/install-python) to install Sedona Python.
+	2. This tutorial is based on [Sedona SQL Jupyter Notebook example](../jupyter-notebook). You can interact with Sedona Python Jupyter notebook immediately on Binder. Click [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/apache/sedona/HEAD?filepath=binder) to interact with Sedona Python Jupyter notebook immediately on Binder.
 
 ## Initiate SparkSession
 Use the following code to initiate your SparkSession at the beginning:
-```scala
-var sparkSession = SparkSession.builder()
-.master("local[*]") // Delete this if run in cluster mode
-.appName("readTestScala") // Change this to a proper name
-// Enable Sedona custom Kryo serializer
-.config("spark.serializer", classOf[KryoSerializer].getName) // org.apache.spark.serializer.KryoSerializer
-.config("spark.kryo.registrator", classOf[SedonaKryoRegistrator].getName)
-.getOrCreate() // org.apache.sedona.core.serde.SedonaKryoRegistrator
-```
+
+=== "Scala"
+
+	```scala
+	var sparkSession = SparkSession.builder()
+	.master("local[*]") // Delete this if run in cluster mode
+	.appName("readTestScala") // Change this to a proper name
+	// Enable Sedona custom Kryo serializer
+	.config("spark.serializer", classOf[KryoSerializer].getName) // org.apache.spark.serializer.KryoSerializer
+	.config("spark.kryo.registrator", classOf[SedonaKryoRegistrator].getName)
+	.getOrCreate() // org.apache.sedona.core.serde.SedonaKryoRegistrator
+	```
+	If you use SedonaViz together with SedonaSQL, please use the following two lines to enable Sedona Kryo serializer instead:
+	```scala
+	.config("spark.serializer", classOf[KryoSerializer].getName) // org.apache.spark.serializer.KryoSerializer
+	.config("spark.kryo.registrator", classOf[SedonaVizKryoRegistrator].getName) // org.apache.sedona.viz.core.Serde.SedonaVizKryoRegistrator
+	```
+
+=== "Java"
+
+	```java
+	SparkSession sparkSession = SparkSession.builder()
+	.master("local[*]") // Delete this if run in cluster mode
+	.appName("readTestScala") // Change this to a proper name
+	// Enable Sedona custom Kryo serializer
+	.config("spark.serializer", KryoSerializer.class.getName) // org.apache.spark.serializer.KryoSerializer
+	.config("spark.kryo.registrator", SedonaKryoRegistrator.class.getName)
+	.getOrCreate() // org.apache.sedona.core.serde.SedonaKryoRegistrator
+	```
+	If you use SedonaViz together with SedonaSQL, please use the following two lines to enable Sedona Kryo serializer instead:
+	```scala
+	.config("spark.serializer", KryoSerializer.class.getName) // org.apache.spark.serializer.KryoSerializer
+	.config("spark.kryo.registrator", SedonaVizKryoRegistrator.class.getName) // org.apache.sedona.viz.core.Serde.SedonaVizKryoRegistrator
+	```
+	
+=== "Python"
+
+	```python
+	sparkSession = SparkSession. \
+	    builder. \
+	    appName('appName'). \
+	    config("spark.serializer", KryoSerializer.getName). \
+	    config("spark.kryo.registrator", SedonaKryoRegistrator.getName). \
+	    config('spark.jars.packages',
+	           'org.apache.sedona:sedona-spark-shaded-3.0_2.12:{{ sedona.current_version }},'
+	           'org.datasyslab:geotools-wrapper:{{ sedona.current_geotools }}'). \
+	    getOrCreate()
+	```
 
 !!!warning
-	Sedona has a suite of well-written geometry and index serializers. Forgetting to enable these serializers will lead to high memory consumption.
+	Sedona has a suite of well-written geometry and index serializers. Forgetting to enable these serializers will lead to high memory consumption and slow performance.
+
 
-If you add ==the Sedona full dependencies== as suggested above, please use the following two lines to enable Sedona Kryo serializer instead:
-```scala
-.config("spark.serializer", classOf[KryoSerializer].getName) // org.apache.spark.serializer.KryoSerializer
-.config("spark.kryo.registrator", classOf[SedonaVizKryoRegistrator].getName) // org.apache.sedona.viz.core.Serde.SedonaVizKryoRegistrator
-```
 
 ## Register SedonaSQL
 
 Add the following line after your SparkSession declaration
 
-```scala
-SedonaSQLRegistrator.registerAll(sparkSession)
-```
+=== "Scala"
+
+	```scala
+	SedonaSQLRegistrator.registerAll(sparkSession)
+	```
+
+=== "Java"
+
+	```java
+	SedonaSQLRegistrator.registerAll(sparkSession)
+	```
+	
+=== "Python"
+
+	```python
+	from sedona.register import SedonaRegistrator
+	
+	SedonaRegistrator.registerAll(spark)
+	```
 
 This function will register Sedona User Defined Type, User Defined Function and optimized join query strategy.
 
@@ -64,11 +137,26 @@ The file may have many other columns.
 
 Use the following code to load the data and create a raw DataFrame:
 
-```scala
-var rawDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load("/Download/usa-county.tsv")
-rawDf.createOrReplaceTempView("rawdf")
-rawDf.show()
-```
+=== "Scala/Java"
+	```scala
+	var rawDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load("/Download/usa-county.tsv")
+	rawDf.createOrReplaceTempView("rawdf")
+	rawDf.show()
+	```
+
+=== "Java"
+	```java
+	Dataset<Row> rawDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load("/Download/usa-county.tsv")
+	rawDf.createOrReplaceTempView("rawdf")
+	rawDf.show()
+	```
+
+=== "Python"
+	```python
+	rawDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load("/Download/usa-county.tsv")
+	rawDf.createOrReplaceTempView("rawdf")
+	rawDf.show()
+	```
 
 The output will be like this:
 
@@ -85,15 +173,8 @@ The output will be like this:
 
 All geometrical operations in SedonaSQL are on Geometry type objects. Therefore, before any kind of queries, you need to create a Geometry type column on a DataFrame.
 
-
-```scala
-var spatialDf = sparkSession.sql(
-  """
-    |SELECT ST_GeomFromWKT(_c0) AS countyshape, _c1, _c2
-    |FROM rawdf
-  """.stripMargin)
-spatialDf.createOrReplaceTempView("spatialdf")
-spatialDf.show()
+```sql
+SELECT ST_GeomFromWKT(_c0) AS countyshape, _c1, _c2
 ```
 
 You can select many other attributes to compose this `spatialdDf`. The output will be something like this:
@@ -140,10 +221,27 @@ Shapefile and GeoJSON must be loaded by SpatialRDD and converted to DataFrame us
 
 Since v`1.3.0`, Sedona natively supports loading GeoParquet file. Sedona will infer geometry fields using the "geo" metadata in GeoParquet files.
 
-```scala
-val df = sparkSession.read.format("geoparquet").load(geoparquetdatalocation1)
-df.printSchema()
-```
+=== "Scala/Java"
+
+	```scala
+	val df = sparkSession.read.format("geoparquet").load(geoparquetdatalocation1)
+	df.printSchema()
+	```
+
+=== "Java"
+
+	```java
+	Dataset<Row> df = sparkSession.read.format("geoparquet").load(geoparquetdatalocation1)
+	df.printSchema()
+	```
+
+=== "Python"
+
+	```python
+	df = sparkSession.read.format("geoparquet").load(geoparquetdatalocation1)
+	df.printSchema()
+	```
+
 The output will be as follows:
 
 ```
@@ -164,14 +262,9 @@ Sedona doesn't control the coordinate unit (degree-based or meter-based) of all
 
 To convert Coordinate Reference System of the Geometry column created before, use the following code:
 
-```scala
-spatialDf = sparkSession.sql(
-  """
-    |SELECT ST_Transform(countyshape, "epsg:4326", "epsg:3857") AS newcountyshape, _c1, _c2, _c3, _c4, _c5, _c6, _c7
-    |FROM spatialdf
-  """.stripMargin)
-spatialDf.createOrReplaceTempView("spatialdf")
-spatialDf.show()
+```sql
+SELECT ST_Transform(countyshape, "epsg:4326", "epsg:3857") AS newcountyshape, _c1, _c2, _c3, _c4, _c5, _c6, _c7
+FROM spatialdf
 ```
 
 The first EPSG code EPSG:4326 in `ST_Transform` is the source CRS of the geometries. It is WGS84, the most common degree-based CRS.
@@ -204,35 +297,27 @@ Use ==ST_Contains==, ==ST_Intersects==, ==ST_Within== to run a range query over
 
 The following example finds all counties that are within the given polygon:
 
-```scala
-spatialDf = sparkSession.sql(
-  """
-    |SELECT *
-    |FROM spatialdf
-    |WHERE ST_Contains (ST_PolygonFromEnvelope(1.0,100.0,1000.0,1100.0), newcountyshape)
-  """.stripMargin)
-spatialDf.createOrReplaceTempView("spatialdf")
-spatialDf.show()
+```sql
+SELECT *
+FROM spatialdf
+WHERE ST_Contains (ST_PolygonFromEnvelope(1.0,100.0,1000.0,1100.0), newcountyshape)
 ```
 
+
 !!!note
 	Read [SedonaSQL constructor API](../api/sql/Constructor.md) to learn how to create a Geometry type query window
+
 ### KNN query
 
 Use ==ST_Distance== to calculate the distance and rank the distance.
 
 The following code returns the 5 nearest neighbor of the given polygon.
 
-```scala
-spatialDf = sparkSession.sql(
-  """
-    |SELECT countyname, ST_Distance(ST_PolygonFromEnvelope(1.0,100.0,1000.0,1100.0), newcountyshape) AS distance
-    |FROM spatialdf
-    |ORDER BY distance DESC
-    |LIMIT 5
-  """.stripMargin)
-spatialDf.createOrReplaceTempView("spatialdf")
-spatialDf.show()
+```sql
+SELECT countyname, ST_Distance(ST_PolygonFromEnvelope(1.0,100.0,1000.0,1100.0), newcountyshape) AS distance
+FROM spatialdf
+ORDER BY distance DESC
+LIMIT 5
 ```
 
 ### Join query
@@ -249,12 +334,10 @@ To save a Spatial DataFrame to some permanent storage such as Hive tables and HD
 
 
 Use the following code to convert the Geometry column in a DataFrame back to a WKT string column:
-```scala
-var stringDf = sparkSession.sql(
-  """
-    |SELECT ST_AsText(countyshape)
-    |FROM polygondf
-  """.stripMargin)
+
+```sql
+SELECT ST_AsText(countyshape)
+FROM polygondf
 ```
 
 !!!note
@@ -269,15 +352,42 @@ Since v`1.3.0`, Sedona natively supports writing GeoParquet file. GeoParquet can
 df.write.format("geoparquet").save(geoparquetoutputlocation + "/GeoParquet_File_Name.parquet")
 ```
 
+## Sort then Save GeoParquet
+
+To maximize the performance of Sedona GeoParquet filter pushdown, we suggest that you sort the data by their geohash values (see [ST_GeoHash](../../api/sql/Function/#st_geohash)) and then save as a GeoParquet file. An example is as follows:
+
+```
+SELECT col1, col2, geom, ST_GeoHash(geom, 5) as geohash
+FROM spatialDf
+ORDER BY geohash
+```
+
+
 ## Convert between DataFrame and SpatialRDD
 
 ### DataFrame to SpatialRDD
 
 Use SedonaSQL DataFrame-RDD Adapter to convert a DataFrame to an SpatialRDD. Please read [Adapter Scaladoc](../../api/javadoc/sql/org/apache/sedona/sql/utils/index.html)
 
-```scala
-var spatialRDD = Adapter.toSpatialRdd(spatialDf, "usacounty")
-```
+=== "Scala"
+
+	```scala
+	var spatialRDD = Adapter.toSpatialRdd(spatialDf, "usacounty")
+	```
+	
+=== "Java"
+
+	```java
+	SpatialRDD spatialRDD = Adapter.toSpatialRdd(spatialDf, "usacounty")
+	```
+
+=== "Python"
+
+	```python
+	from sedona.utils.adapter import Adapter
+
+	spatialRDD = Adapter.toSpatialRdd(spatialDf, "usacounty")
+	```
 
 "usacounty" is the name of the geometry column
 
@@ -288,9 +398,25 @@ var spatialRDD = Adapter.toSpatialRdd(spatialDf, "usacounty")
 
 Use SedonaSQL DataFrame-RDD Adapter to convert a DataFrame to an SpatialRDD. Please read [Adapter Scaladoc](../../api/javadoc/sql/org/apache/sedona/sql/utils/index.html)
 
-```scala
-var spatialDf = Adapter.toDf(spatialRDD, sparkSession)
-```
+=== "Scala"
+
+	```scala
+	var spatialDf = Adapter.toDf(spatialRDD, sparkSession)
+	```
+
+=== "Java"
+
+	```java
+	Dataset<Row> spatialDf = Adapter.toDf(spatialRDD, sparkSession)
+	```
+	
+=== "Python"
+
+	```python
+	from sedona.utils.adapter import Adapter
+	
+	spatialDf = Adapter.toDf(spatialRDD, sparkSession)
+	```
 
 All other attributes such as price and age will be also brought to the DataFrame as long as you specify ==carryOtherAttributes== (see [Read other attributes in an SpatialRDD](../rdd#read-other-attributes-in-an-spatialrdd)).
 
@@ -299,30 +425,67 @@ types. Note that string schemas and not all data types are supported&mdash;pleas
 [Adapter Scaladoc](../../api/javadoc/sql/org/apache/sedona/sql/utils/index.html) to confirm what is supported for your use
 case. At least one column for the user data must be provided.
 
-```scala
-val schema = StructType(Array(
-  StructField("county", GeometryUDT, nullable = true),
-  StructField("name", StringType, nullable = true),
-  StructField("price", DoubleType, nullable = true),
-  StructField("age", IntegerType, nullable = true)
-))
-val spatialDf = Adapter.toDf(spatialRDD, schema, sparkSession)
-```
+=== "Scala"
+
+	```scala
+	val schema = StructType(Array(
+	  StructField("county", GeometryUDT, nullable = true),
+	  StructField("name", StringType, nullable = true),
+	  StructField("price", DoubleType, nullable = true),
+	  StructField("age", IntegerType, nullable = true)
+	))
+	val spatialDf = Adapter.toDf(spatialRDD, schema, sparkSession)
+	```
 
 ### SpatialPairRDD to DataFrame
 
 PairRDD is the result of a spatial join query or distance join query. SedonaSQL DataFrame-RDD Adapter can convert the result to a DataFrame. But you need to provide the name of other attributes.
 
-```scala
-var joinResultDf = Adapter.toDf(joinResultPairRDD, Seq("left_attribute1", "left_attribute2"), Seq("right_attribute1", "right_attribute2"), sparkSession)
-```
+=== "Scala"
+
+	```scala
+	var joinResultDf = Adapter.toDf(joinResultPairRDD, Seq("left_attribute1", "left_attribute2"), Seq("right_attribute1", "right_attribute2"), sparkSession)
+	```
+
+=== "Java"
+
+	```java
+	import scala.collection.JavaConverters;	
+	
+	List leftFields = new ArrayList<>(Arrays.asList("c1", "c2", "c3"));
+	List rightFields = new ArrayList<>(Arrays.asList("c4", "c5", "c6"));
+	Dataset joinResultDf = Adapter.toDf(joinResultPairRDD, JavaConverters.asScalaBuffer(leftFields).toSeq(), JavaConverters.asScalaBuffer(rightFields).toSeq(), sparkSession);
+	```
 
+=== "Python"
+
+	```python
+	from sedona.utils.adapter import Adapter
+
+	joinResultDf = Adapter.toDf(jvm_sedona_rdd, ["poi_from_id", "poi_from_name"], ["poi_to_id", "poi_to_name"], spark))
+	```
 or you can use the attribute names directly from the input RDD
 
-```scala
-import scala.collection.JavaConversions._
-var joinResultDf = Adapter.toDf(joinResultPairRDD, leftRdd.fieldNames, rightRdd.fieldNames, sparkSession)
-```
+=== "Scala"
+
+	```scala
+	import scala.collection.JavaConversions._
+	var joinResultDf = Adapter.toDf(joinResultPairRDD, leftRdd.fieldNames, rightRdd.fieldNames, sparkSession)
+	```
+
+=== "Java"
+
+	```java
+	import scala.collection.JavaConverters;	
+	Dataset joinResultDf = Adapter.toDf(joinResultPairRDD, JavaConverters.asScalaBuffer(leftRdd.fieldNames).toSeq(), JavaConverters.asScalaBuffer(rightRdd.fieldNames).toSeq(), sparkSession);
+	```
+=== "Python"
+
+	```python
+	from sedona.utils.adapter import Adapter
+
+	joinResultDf = Adapter.toDf(result_pair_rdd, leftRdd.fieldNames, rightRdd.fieldNames, spark)
+	```
 
 All other attributes such as price and age will be also brought to the DataFrame as long as you specify ==carryOtherAttributes== (see [Read other attributes in an SpatialRDD](../rdd#read-other-attributes-in-an-spatialrdd)).
 
@@ -331,38 +494,16 @@ types. Note that string schemas and not all data types are supported&mdash;pleas
 [Adapter Scaladoc](../../api/javadoc/sql/org/apache/sedona/sql/utils/index.html) to confirm what is supported for your use
 case. Columns for the left and right user data must be provided.
 
-```scala
-val schema = StructType(Array(
-  StructField("leftGeometry", GeometryUDT, nullable = true),
-  StructField("name", StringType, nullable = true),
-  StructField("price", DoubleType, nullable = true),
-  StructField("age", IntegerType, nullable = true),
-  StructField("rightGeometry", GeometryUDT, nullable = true),
-  StructField("category", StringType, nullable = true)
-))
-val joinResultDf = Adapter.toDf(joinResultPairRDD, schema, sparkSession)
-```
-
-## DataFrame Style API
-
-Sedona functions can be used in a DataFrame style API similar to Spark functions.
-The following objects contain the exposed functions: `org.apache.spark.sql.sedona_sql.expressions.st_functions`, `org.apache.spark.sql.sedona_sql.expressions.st_constructors`, `org.apache.spark.sql.sedona_sql.expressions.st_predicates`, and `org.apache.spark.sql.sedona_sql.expressions.st_aggregates`.
-Every functions can take all `Column` arguments.
-Additionally, overloaded forms can commonly take a mix of `String` and other Scala types (such as `Double`) as arguments.
-In general the following rules apply (although check the documentation of specific functions for any exceptions):
-1. Every function returns a `Column` so that it can be used interchangeably with Spark functions as well as `DataFrame` methods such as `DataFrame.select` or `DataFrame.join`.
-1. Every function has a form that takes all `Column` arguments.
-These are the most versatile of the forms.
-1. Most functions have a form that takes a mix of `String` arguments with other Scala types.
-The exact mixture of argument types allowed is function specific.
-However, in these instances, all `String` arguments are assumed to be the names of columns and will be wrapped in a `Column` automatically.
-Non-`String` arguments are assumed to be literals that are passed to the sedona function. If you need to pass a `String` literal then you should use the all `Column` form of the sedona function and wrap the `String` literal in a `Column` with the `lit` Spark function.
-
-A short example of using this API (uses the `array_min` and `array_max` Spark functions):
-
-```scala
-val values_df = spark.sql("SELECT array(0.0, 1.0, 2.0) AS values")
-val min_value = array_min("values")
-val max_value = array_max("values")
-val point_df = values_df.select(ST_Point(min_value, max_value).as("point"))
-```
+=== "Scala"
+
+	```scala
+	val schema = StructType(Array(
+	  StructField("leftGeometry", GeometryUDT, nullable = true),
+	  StructField("name", StringType, nullable = true),
+	  StructField("price", DoubleType, nullable = true),
+	  StructField("age", IntegerType, nullable = true),
+	  StructField("rightGeometry", GeometryUDT, nullable = true),
+	  StructField("category", StringType, nullable = true)
+	))
+	val joinResultDf = Adapter.toDf(joinResultPairRDD, schema, sparkSession)
+	```
\ No newline at end of file
diff --git a/docs/tutorial/viz-r.md b/docs/tutorial/viz-r.md
deleted file mode 100644
index 4bce9e5c..00000000
--- a/docs/tutorial/viz-r.md
+++ /dev/null
@@ -1,68 +0,0 @@
-# Map Visualization applications in R language
-
-
-An important part of `apache.sedona` is its collection of R interfaces
-to Sedona visualization routines. For example, the following is
-essentially the R equivalent of [this example in
-Scala](https://github.com/apache/sedona/blob/f6b1c5e24bdb67d2c8d701a9b2af1fb5658fdc4d/viz/src/main/scala/org/apache/sedona/viz/showcase/ScalaExample.scala#L142-L160).
-
-```r
-library(sparklyr)
-library(apache.sedona)
-
-sc <- spark_connect(master = "local")
-
-resolution_x <- 1000
-resolution_y <- 600
-boundary <- c(-126.790180, -64.630926, 24.863836, 50.000)
-
-pt_rdd <- sedona_read_dsv_to_typed_rdd(
-  sc,
-  location = "arealm.csv",
-  type = "point"
-)
-polygon_rdd <- sedona_read_dsv_to_typed_rdd(
-  sc,
-  location = "primaryroads-polygon.csv",
-  type = "polygon"
-)
-pair_rdd <- sedona_spatial_join_count_by_key(
-  pt_rdd,
-  polygon_rdd,
-  join_type = "intersect"
-)
-
-overlay <- sedona_render_scatter_plot(
-  polygon_rdd,
-  resolution_x,
-  resolution_y,
-  output_location = tempfile("scatter-plot-"),
-  boundary = boundary,
-  base_color = c(255, 0, 0),
-  browse = FALSE
-)
-
-sedona_render_choropleth_map(
-  pair_rdd,
-  resolution_x,
-  resolution_y,
-  output_location = "/tmp/choropleth-map",
-  boundary = boundary,
-  overlay = overlay,
-  # vary the green color channel according to relative magnitudes of data points so
-  # that the resulting map will show light blue, light purple, and light gray pixels
-  color_of_variation = "green",
-  base_color = c(225, 225, 255)
-)
-```
-
-It will create a scatter plot, and then overlay it on top of a
-choropleth map, as shown below:
-
-<img src="../../image/choropleth-map.png" width=800 />
-
-See `?apache.sedona::sedona_render_scatter_plot`,
-`?apache.sedona::sedona_render_heatmap`, and
-`?apache.sedona::sedona_render_choropleth_map` for more details on
-visualization-related R interfaces currently implemented by
-`apache.sedona`.
diff --git a/licenses/LICENSE-pygeos b/licenses/LICENSE-pygeos
new file mode 100644
index 00000000..6c21cb47
--- /dev/null
+++ b/licenses/LICENSE-pygeos
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2019, Casper van der Wel
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
index 2834d5dc..b5981fcb 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -15,9 +15,10 @@ nav:
       - Install with Apache Spark:
         - Install Sedona Scala/Java: setup/install-scala.md
         - Install Sedona Python: setup/install-python.md
-        - Install Sedona R: setup/install-r.md
+        - Install Sedona R: api/rdocs
         - Install Sedona-Zeppelin: setup/zeppelin.md
         - Install on Databricks: setup/databricks.md
+        - Install on AWS EMR: setup/emr.md
         - Set up Spark cluster: setup/cluster.md
       - Install with Apache Flink:
         - Install Sedona Scala/Java: setup/flink/install-scala.md
@@ -26,27 +27,21 @@ nav:
     - Download: download.md
     - Programming Guides:
       - Sedona with Apache Spark:
-        - Spatial SQL app:
-          - Scala/Java: tutorial/sql.md
-          - Pure SQL: tutorial/sql-pure-sql.md
-          - Python: tutorial/sql-python.md
-          - R: tutorial/sql-r.md
-          - Raster data - Map Algebra: tutorial/raster.md      
-        - Spatial RDD app:
-          - Scala/Java: tutorial/rdd.md
-          - Python: tutorial/core-python.md
-          - R: tutorial/rdd-r.md
+        - Spatial SQL app: tutorial/sql.md
+        - Raster SQL app: tutorial/raster.md
+        - Pure SQL environment: tutorial/sql-pure-sql.md
+        - Spatial RDD app: tutorial/rdd.md
+        - Sedona R: api/rdocs
+        - Work with GeoPandas and Shapely: tutorial/geopandas-shapely.md
         - Map visualization SQL app:
           - Scala/Java: tutorial/viz.md
           - Use Apache Zeppelin: tutorial/zeppelin.md
-          - R: tutorial/viz-r.md
           - Gallery: tutorial/viz-gallery.md
         - Performance tuning:
-          - Benchmark: tutorial/benchmark.md            
+          - Benchmark: tutorial/benchmark.md
           - Tune RDD application: tutorial/Advanced-Tutorial-Tune-your-Application.md
       - Sedona with Apache Flink:
-        - Spatial SQL app:
-          - Scala/Java: tutorial/flink/sql.md
+        - Spatial SQL app: tutorial/flink/sql.md
       - Examples:
           - Scala/Java: tutorial/demo.md
           - Python: tutorial/jupyter-notebook.md
@@ -59,18 +54,19 @@ nav:
               - Function: api/sql/Function.md
               - Predicate: api/sql/Predicate.md
               - Aggregate function: api/sql/AggregateFunction.md
-              - SedonaSQL query optimizer: api/sql/Optimizer.md
+              - DataFrame Style functions: api/sql/DataFrameAPI.md
+              - Query optimization: api/sql/Optimizer.md
           - Raster data:
-              - Raster input and output: api/sql/Raster-loader.md
+              - Raster loader: api/sql/Raster-loader.md
+              - Raster writer: api/sql/Raster-writer.md
               - Raster operators: api/sql/Raster-operators.md
           - Parameter: api/sql/Parameter.md
         - RDD (core):
           - Scala/Java doc: api/java-api.md
-          - Python doc: api/python-api.md
-          - R doc: api/r-api.md
         - Viz:
           - DataFrame/SQL: api/viz/sql.md
           - RDD: api/viz/java-api.md
+        - Sedona R: api/rdocs
       - Sedona with Apache Flink:
         - SQL:
           - Overview: api/flink/Overview.md
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/UDT/RasterUDT.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/UDT/RasterUDT.scala
index 3411a49c..f046d6b8 100644
--- a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/UDT/RasterUDT.scala
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/UDT/RasterUDT.scala
@@ -1,3 +1,21 @@
+/*
+ * 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.UDT