> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wherobots.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Raster Tables

> Create, write, and query Apache Iceberg tables with raster columns in WherobotsDB.

WherobotsDB stores tables in Apache Iceberg and supports Iceberg format-version 3 (V3). New tables in Wherobots-managed catalogs, such as `org_catalog`, are V3 tables. Tables created before September 15, 2026 were created at format-version 2 (V2), and Wherobots doesn't raise them to V3. To check a table, run `SHOW TBLPROPERTIES <table> ('format-version')`. Geometry and geography columns declared with a CRS, such as `GEOMETRY(4326)`, use the V3 `geometry` and `geography` types. Wherobots adds a `raster` column type and the `CREATE SPATIAL INDEX` statement, which rewrites a table's data files clustered by a spatial column.

On V3 tables, merge-on-read `DELETE`, `UPDATE`, and `MERGE` write deletion vectors, the row lineage columns `_row_id` and `_last_updated_sequence_number` can be queried, and `variant` columns can be written and read. Column default values can't be set, and a table with a `timestamp_ns` column can't be read.

A raster column can hold in-db or out-db rasters, and you can use the raster functions in WherobotsDB to manipulate them. This document describes how to use the raster data type and raster functions in tables.

## Raster as a primitive type

Wherobots adds a `raster` column type to Apache Iceberg tables. For instance, user can create a table with a raster column using SQL:

**Python**

```python theme={"system"}
sedona.sql("""
CREATE TABLE IF NOT EXISTS org_catalog.test_db.test_table (
    id bigint,
    data string,
    rast raster
) USING iceberg""")
```

**Scala**

```scala theme={"system"}
sedona.sql("""
CREATE TABLE IF NOT EXISTS org_catalog.test_db.test_table (
    id bigint,
    data string,
    rast raster
) USING iceberg""")
```

**Java**

```java theme={"system"}
sedona.sql(
    "CREATE TABLE IF NOT EXISTS org_catalog.test_db.test_table (" +
    "id bigint," +
    "data string," +
    "rast raster" +
    ") USING iceberg"
);
```

This will create an empty table with a raster column `rast`. We can inspect the table schema using `DESCRIBE` command:

**Python**

```python theme={"system"}
sedona.sql("DESCRIBE TABLE org_catalog.test_db.test_table").show()
```

**Scala**

```scala theme={"system"}
sedona.sql("DESCRIBE TABLE org_catalog.test_db.test_table").show()
```

**Java**

```java theme={"system"}
sedona.sql("DESCRIBE TABLE org_catalog.test_db.test_table").show();
```

```
+--------+---------+-------+
|col_name|data_type|comment|
+--------+---------+-------+
|      id|   bigint|   null|
|    data|   string|   null|
|    rast|   raster|   null|
+--------+---------+-------+
```

Or using `.printSchema()` function:

**Python**

```python theme={"system"}
sedona.table("org_catalog.test_db.test_table").printSchema()
```

**Scala**

```scala theme={"system"}
sedona.table("org_catalog.test_db.test_table").printSchema()
```

**Java**

```java theme={"system"}
sedona.table("org_catalog.test_db.test_table").printSchema();
```

```
root
 |-- id: long (nullable = true)
 |-- data: string (nullable = true)
 |-- rast: raster (nullable = true)
```

`SHOW CREATE TABLE` prints a raster column as `rast BINARY` on a format-version 3 table. The column still holds rasters, stored the same way as on a format-version 2 table.

## Creating table with raster column

As mentioned above, user can create a table with a raster column using SQL:

**Python**

```python theme={"system"}
sedona.sql("""
CREATE TABLE IF NOT EXISTS org_catalog.test_db.test_table (
    id bigint,
    data string,
    rast raster
) USING iceberg""")
```

**Scala**

```scala theme={"system"}
sedona.sql("""
CREATE TABLE IF NOT EXISTS org_catalog.test_db.test_table (
    id bigint,
    data string,
    rast raster
) USING iceberg""")
```

**Java**

```java theme={"system"}
sedona.sql(
    "CREATE TABLE IF NOT EXISTS org_catalog.test_db.test_table (" +
    "id bigint," +
    "data string," +
    "rast raster" +
    ") USING iceberg"
);
```

If user has a DataFrame containing raster data, they can also create a table from the DataFrame:

**Python**

```python theme={"system"}
# Create a DataFrame containing raster data
df_binary = sedona.read.format("binaryFile")\
    .option("pathGlobFilter", "*.tif")\
    .option("recursiveFileLookup", "true")\
    .load('s3://wherobots-examples/data/eurosat_small')
df_geotiff = df_binary.withColumn("rast", expr("RS_FromGeoTiff(content)"))\
    .selectExpr("rast", "path AS data")

# Create a table from the DataFrame
df_geotiff.writeTo("org_catalog.test_db.test_table").create()
```

**Scala**

```scala theme={"system"}
// Create a DataFrame containing raster data
val df_binary = sedona.read.format("binaryFile")
    .option("pathGlobFilter", "*.tif")
    .option("recursiveFileLookup", "true")
    .load("s3://wherobots-examples/data/eurosat_small")

val df_geotiff = df_binary.withColumn("rast", expr("RS_FromGeoTiff(content)"))
    .selectExpr("rast", "path AS data")

// Create a table from the DataFrame
df_geotiff.writeTo("org_catalog.test_db.test_table").create()
```

**Java**

```java theme={"system"}
// Create a DataFrame containing raster data
Dataset<Row> df_binary = sedona.read().format("binaryFile")
    .option("pathGlobFilter", "*.tif")
    .option("recursiveFileLookup", "true")
    .load("s3://wherobots-examples/data/eurosat_small");

Dataset<Row> df_geotiff = df_binary.withColumn("rast", expr("RS_FromGeoTiff(content)"))
    .selectExpr("rast", "path AS data");

// Create a table from the DataFrame
df_geotiff.writeTo("org_catalog.test_db.test_table").create();
```

## Writing data

Storing raster data in a table is similar to PostGIS. There are two options for storing raster data:

* in-db raster: raster data completely stored in a table.
* out-db raster: raster data is stored in an external file (such as GeoTIFF file on S3) and the table stores the path to the file and geo-referencing metadata of that raster.

### In-DB Raster

To construct in-db raster data and write them to a table, user can use [`RS_FromGeoTiff`](https://docs.wherobots.com/reference/wherobots-db/raster-data/raster-loader/#rs_fromgeotiff) to construct a raster value and write the DataFrame using `.writeTo(...).append()`

**Python**

```python theme={"system"}
# Read GeoTIFF files using RS_FromGeoTiff
df_binary = sedona.read.format("binaryFile")\
    .option("pathGlobFilter", "*.tif")\
    .option("recursiveFileLookup", "true")\
    .load('s3://wherobots-examples/data/eurosat_small')
df_geotiff = df_binary.withColumn("rast", expr("RS_FromGeoTiff(content)"))\
    .selectExpr("rast", "path AS data")

# Writing the data into the table
df_geotiff.writeTo("org_catalog.test_db.test_table").append()
```

**Scala**

```scala theme={"system"}
// Read GeoTIFF files using RS_FromGeoTiff
val df_binary = sedona.read.format("binaryFile")
    .option("pathGlobFilter", "*.tif")
    .option("recursiveFileLookup", "true")
    .load("s3://wherobots-examples/data/eurosat_small")

val df_geotiff = df_binary.withColumn("rast", expr("RS_FromGeoTiff(content)"))
    .selectExpr("rast", "path AS data")

// Writing the data into the table
df_geotiff.writeTo("org_catalog.test_db.test_table").append()
```

**Java**

```java theme={"system"}
// Read GeoTIFF files using RS_FromGeoTiff
Dataset<Row> df_binary = sedona.read().format("binaryFile")
    .option("pathGlobFilter", "*.tif")
    .option("recursiveFileLookup", "true")
    .load("s3://wherobots-examples/data/eurosat_small");

Dataset<Row> df_geotiff = df_binary.withColumn("rast", expr("RS_FromGeoTiff(content)"))
    .selectExpr("rast", "path AS data");

// Writing the data into the table
df_geotiff.writeTo("org_catalog.test_db.test_table").append();
```

Or user can first load the GeoTIFF file as out-db raster using [`RS_FromPath`](https://docs.wherobots.com/reference/wherobots-db/raster-data/raster-loader/#rs_frompath), then use [`RS_AsInDB`](https://docs.wherobots.com/reference/wherobots-db/raster-data/raster-loader/#rs_asindb) to convert the out-db raster to in-db raster:

**Python**

```python theme={"system"}
df_paths = spark.createDataFrame([
    ("s3://wherobots-examples/data/eurosat_small/AnnualCrop/AnnualCrop_10.tif",),
    ("s3://wherobots-examples/data/eurosat_small/AnnualCrop/AnnualCrop_11.tif",)
], ["path"])

# Read GeoTIFF files as out-db rasters using RS_FromPath, then convert to in-db rasters using RS_AsInDB
df_rast = df_paths.withColumn("rast", expr("RS_AsInDB(RS_FromPath(path))"))

# Writing the data into the table
df_rast.selectExpr("rast", "path AS data").writeTo("org_catalog.test_db.test_table").append()
```

**Scala**

```scala theme={"system"}
val df_paths = Seq(
    "s3://wherobots-examples/data/eurosat_small/AnnualCrop/AnnualCrop_10.tif",
    "s3://wherobots-examples/data/eurosat_small/AnnualCrop/AnnualCrop_11.tif"
).toDF("path")

// Read GeoTIFF files as out-db rasters using RS_FromPath, then convert to in-db rasters using RS_AsInDB
val df_rast = df_paths.withColumn("rast", expr("RS_AsInDB(RS_FromPath(path))"))

// Writing the data into the table
df_rast.selectExpr("rast", "path AS data").writeTo("org_catalog.test_db.test_table").append()
```

**Java**

```java theme={"system"}
List<Row> data = Arrays.asList(
    RowFactory.create("s3://wherobots-examples/data/eurosat_small/AnnualCrop/AnnualCrop_10.tif"),
    RowFactory.create("s3://wherobots-examples/data/eurosat_small/AnnualCrop/AnnualCrop_11.tif")
);
List<StructField> fields = Arrays.asList(
    DataTypes.createStructField("path", DataTypes.StringType, true)
);
StructType schema = DataTypes.createStructType(fields);
Dataset<Row> df_paths = spark.createDataFrame(data, schema);

// Read GeoTIFF files as out-db rasters using RS_FromPath, then convert to in-db rasters using RS_AsInDB
Dataset<Row> df_rast = df_paths.withColumn("rast", expr("RS_AsInDB(RS_FromPath(path))"));

// Writing the data into the table
df_rast.selectExpr("rast", "path AS data").writeTo("org_catalog.test_db.test_table").append();
```

### Out-DB Raster

To construct out-db raster data and write them to a table, user can use [`RS_FromPath`](https://docs.wherobots.com/reference/wherobots-db/raster-data/raster-loader/#rs_frompath) to construct a raster value and insert it into the table:

**Python**

```python theme={"system"}
df_paths = spark.createDataFrame([
    ("s3://wherobots-examples/data/eurosat_small/AnnualCrop/AnnualCrop_10.tif",),
    ("s3://wherobots-examples/data/eurosat_small/AnnualCrop/AnnualCrop_11.tif",)
], ["path"])

# Read GeoTIFF files as out-db rasters using RS_FromPath
df_rast = df_paths.withColumn("rast", expr("RS_FromPath(path)"))

# Writing the data into the table
df_rast.selectExpr("rast", "path AS data").writeTo("org_catalog.test_db.test_table").append()
```

**Scala**

```scala theme={"system"}
val df_paths = Seq(
    "s3://wherobots-examples/data/eurosat_small/AnnualCrop/AnnualCrop_10.tif",
    "s3://wherobots-examples/data/eurosat_small/AnnualCrop/AnnualCrop_11.tif"
).toDF("path")

// Read GeoTIFF files as out-db rasters using RS_FromPath
val df_rast = df_paths.withColumn("rast", expr("RS_FromPath(path)"))

// Writing the data into the table
df_rast.selectExpr("rast", "path AS data").writeTo("org_catalog.test_db.test_table").append()
```

**Java**

```java theme={"system"}
List<Row> data = Arrays.asList(
    RowFactory.create("s3://wherobots-examples/data/eurosat_small/AnnualCrop/AnnualCrop_10.tif"),
    RowFactory.create("s3://wherobots-examples/data/eurosat_small/AnnualCrop/AnnualCrop_11.tif")
);
List<StructField> fields = Arrays.asList(
    DataTypes.createStructField("path", DataTypes.StringType, true)
);
StructType schema = DataTypes.createStructType(fields);
Dataset<Row> df_paths = spark.createDataFrame(data, schema);

// Read GeoTIFF files as out-db rasters using RS_FromPath
Dataset<Row> df_rast = df_paths.withColumn("rast", expr("RS_FromPath(path)"));

// Writing the data into the table
df_rast.selectExpr("rast", "path AS data").writeTo("org_catalog.test_db.test_table").append();
```

### Writing a DataFrame to a table

Users can write a DataFrame containing raster data to a table:

**Python**

```python theme={"system"}
df_geotiff.writeTo("org_catalog.test_db.test_table").append()
```

**Scala**

```scala theme={"system"}
df_geotiff.writeTo("org_catalog.test_db.test_table").append()
```

**Java**

```java theme={"system"}
df_geotiff.writeTo("org_catalog.test_db.test_table").append();
```

### ACID Transactions

Raster tables support the table update statements of Apache Iceberg, such as `UPDATE`, `DELETE` and `MERGE INTO`, in both copy-on-write and merge-on-read mode.
The syntax is identical to the open source Apache Iceberg, please refer to [Apache Iceberg - Write with SQL](https://iceberg.apache.org/docs/latest/spark-writes/#writing-with-sql) for more information.

## Querying data

User can load data from a table using `sedona.table(...)`:

**Python**

```python theme={"system"}
df = sedona.table("org_catalog.test_db.test_table")
```

**Scala**

```scala theme={"system"}
val df = sedona.table("org_catalog.test_db.test_table")
```

**Java**

```java theme={"system"}
Dataset<Row> df = sedona.table("org_catalog.test_db.test_table");
```

You can apply some configurations for reading the table, such as the split size if you want to read the table into a DataFrame with more partitions:

**Python**

```python theme={"system"}
df = sedona.read.option('split-size', '1000').table("org_catalog.test_db.test_table")
```

**Scala**

```scala theme={"system"}
val df = sedona.read.option("split-size", "1000").table("org_catalog.test_db.test_table")
```

**Java**

```java theme={"system"}
Dataset<Row> df = sedona.read().option("split-size", "1000").table("org_catalog.test_db.test_table");
```

You can run spatial range query on the table using Spatial SQL:

**Python**

```python theme={"system"}
df = sedona.sql("""
SELECT * FROM org_catalog.test_db.test_table
WHERE RS_Intersects(rast, ST_GeomFromText('POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))'))
""")
```

**Scala**

```scala theme={"system"}
val df = sedona.sql("""
SELECT * FROM org_catalog.test_db.test_table
WHERE RS_Intersects(rast, ST_GeomFromText('POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))'))
""")
```

**Java**

```java theme={"system"}
Dataset<Row> df = sedona.sql(
        "SELECT * FROM org_catalog.test_db.test_table " +
        "WHERE RS_Intersects(rast, ST_GeomFromText('POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))'))"
);
```

Or using the DataFrame API:

**Python**

```python theme={"system"}
df = sedona.table("org_catalog.test_db.test_table")\
    .where("RS_Intersects(rast, ST_GeomFromText('POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))'))")
```

**Scala**

```scala theme={"system"}
val df = sedona.table("org_catalog.test_db.test_table")
    .where("RS_Intersects(rast, ST_GeomFromText('POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))'))")
```

**Java**

```java theme={"system"}
Dataset<Row> df = sedona.table("org_catalog.test_db.test_table")
    .where("RS_Intersects(rast, ST_GeomFromText('POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))'))");
```

Users can also load a table by specifying the name of the data source explicitly using `.format("iceberg")`, this will load an isolated table reference that will not automatically refresh tables used by queries.

**Python**

```python theme={"system"}
df = sedona.read.format("iceberg").load("org_catalog.test_db.test_table")
```

**Scala**

```scala theme={"system"}
val df = sedona.read.format("iceberg").load("org_catalog.test_db.test_table")
```

**Java**

```java theme={"system"}
Dataset<Row> df = sedona.read().format("iceberg").load("org_catalog.test_db.test_table");
```

WherobotsDB optimizes raster spatial range queries using data skipping. This feature allows user to skip reading data files that don't contain data that satisfy the spatial filter.
Please refer to [Spatial Filter Push-down for Rasters](https://docs.wherobots.com/data-engineering/iceberg-tables/raster-filter-push-down.md) for more information.

## Working with raster data

Data in columns with `raster` type will be loaded as `RasterUDT` values in WherobotsDB, user can use any RS\_ functions provided by WherobotsDB to manipulate the raster data. For example, user can use `RS_MapAlgebra` to create a new raster by applying a map algebra expression to the raster column:

**Python**

```python theme={"system"}
sedona.sql("SELECT RS_MapAlgebra(rast, NULL, 'out = rast[0] * 0.5;') FROM org_catalog.test_db.test_table").show()
```

**Scala**

```scala theme={"system"}
sedona.sql("SELECT RS_MapAlgebra(rast, NULL, 'out = rast[0] * 0.5;') FROM org_catalog.test_db.test_table").show()
```

**Java**

```java theme={"system"}
sedona.sql("SELECT RS_MapAlgebra(rast, NULL, 'out = rast[0] * 0.5;') FROM org_catalog.test_db.test_table").show();
```

The resulting DataFrame can also be written back to a table using a CTAS statement:

**Python**

```python theme={"system"}
sedona.sql("""
CREATE TABLE org_catalog.test_db.test_table_mapalg AS
SELECT RS_MapAlgebra(rast, NULL, 'out = rast[0] * 0.5;') FROM org_catalog.test_db.test_table
""")
```

**Scala**

```scala theme={"system"}
sedona.sql("""
CREATE TABLE org_catalog.test_db.test_table_mapalg AS
SELECT RS_MapAlgebra(rast, NULL, 'out = rast[0] * 0.5;') FROM org_catalog.test_db.test_table
""")
```

**Java**

```java theme={"system"}
sedona.sql(
        "CREATE TABLE org_catalog.test_db.test_table_mapalg AS " +
        "SELECT RS_MapAlgebra(rast, NULL, 'out = rast[0] * 0.5;') FROM org_catalog.test_db.test_table"
);
```

Or simply call `writeTo` function on the resulting DataFrame of the query:

**Python**

```python theme={"system"}
sedona.sql("SELECT RS_MapAlgebra(rast, NULL, 'out = rast[0] * 0.5;') FROM org_catalog.test_db.test_table")\
    .writeTo("org_catalog.test_db.test_table_mapalg").create()
```

**Scala**

```scala theme={"system"}
sedona.sql("SELECT RS_MapAlgebra(rast, NULL, 'out = rast[0] * 0.5;') FROM org_catalog.test_db.test_table")
    .writeTo("org_catalog.test_db.test_table_mapalg").create()
```

**Java**

```java theme={"system"}
sedona.sql("SELECT RS_MapAlgebra(rast, NULL, 'out = rast[0] * 0.5;') FROM org_catalog.test_db.test_table")
    .writeTo("org_catalog.test_db.test_table_mapalg").create()
```

## Further reading

WherobotsDB tables are Apache Iceberg tables. Please refer to [Apache Iceberg documentation for Spark](https://iceberg.apache.org/docs/latest/getting-started/) for more information.

Out-db rasters usually requires tuning to achieve good performance. To learn more about out-db rasters, please refer to [Out-DB Rasters](https://docs.wherobots.com/data-engineering/iceberg-tables/out-db-rasters.md).

WherobotsDB supports spatial filter push-down for raster columns. Please refer to [Spatial Filter Push-down for Rasters](https://docs.wherobots.com/data-engineering/iceberg-tables/raster-filter-push-down.md) to learn how to take advantage of this feature.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.