Pyspark と Apache Sedona を導入した Docker コンテナ

## この記事の内容
この記事は Apache Spark、特にPythonにおける Spark のライブラリである PySpark と Apache Sedona を用いた開発環境のための Docker コンテナ作成について紹介します。Apache Spark、Sedona そのものについての解説などはしません。そもそも Apache Spark、Sedona とはなんぞや！？という方は他の記事や公式 docs などをご覧ください。

### 参考になる記事や公式 docs
- [Apache Spark で分散処理入門](https://qiita.com/Hiroki11x/items/4f5129094da4c91955bc)
- [PySparkことはじめ](https://qiita.com/taka_yayoi/items/a7ee6287031374efa88a)
- [Pysparkデータ操作](https://qiita.com/gsy0911/items/a4cb8b2d54d6341558e0)
- [Python環境でGeoSpark(Apache Sedona)をインストールしてみる](https://qiita.com/junkor-1011/items/8327699dd4b1fff6dd8c)
- [Apache Spark 公式 docs](https://spark.apache.org)
- [Apache Sedona 公式 docs](https://sedona.apache.org/1.5.3/)

## 背景
PostgreSQL に保存された大量のジオメトリデータを操作する必要がありローカルで Apache Spark や Sedona を使っていたんですがチーム開発やデプロイなどの際に Docker を使ったほうが便利だろうということに気づきました（遅い）。元々そこまで Docker を触ったことがあったわけでもなかったのでこの Dockerfile にたどり着くだけでもかなり時間がかかってしまいました。。。

## 本題
早速、以下に PySpark と Apache Sedona を導入した Docker コンテナを作成するための Dockerfile を示します。

```Dockerfile:Dockerfile
FROM python:3.10-slim

# Install Java17.
RUN apt-get update \
  && apt-get install -y --no-install-recommends \
         openjdk-17-jdk-headless \
         curl \
  && apt-get autoremove -yqq --purge \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*
ENV JAVA_HOME="/usr/lib/jvm/java-17-openjdk-arm64"

# Set working directory and copy requirements.txt (and other materials if any)
WORKDIR /app
COPY . /app

RUN pip install --upgrade pip \
  && pip install -r requirements.txt

# Install JDBC driver for Postgres.
RUN cd /opt && \
    mkdir -p java/jdbc && \
    cd java/jdbc && \
    curl -LO https://jdbc.postgresql.org/download/postgresql-42.7.3.jar && \
    cd /app

# Set environment variables
ENV CLASSPATH="/opt/java/jdbc/postgresql-42.7.3.jar"
ENV SPARK_HOME="/usr/local/lib/python3.10/site-packages/pyspark"
ENV PATH="${PATH}:${SPARK_HOME}/bin:${JAVA_HOME}/bin"
ENV PYTHONUNBUFFERED True
ENV PYTHONPATH="/app:${SPARK_HOME}/python"
```
```text:requirements.txt
apache-sedona==1.5.1
attrs==23.2.0
ipython==8.24.0
numpy==1.26.4
py4j==0.10.9.7
pyspark==3.5.1
shapely==2.0.4
```

この Dockerfile を作成するにあたり、[こちらのページ](https://medium.com/@jjcfrank/build-a-docker-image-pyspark-with-apache-sedona-34a8b0b4618d)を参考にしました。
各ライブラリや JDBC ドライバーのバージョンは更新されている可能性がありますから使用時には適切なバージョンに書き換えてください。

### 解説
この Dockerfile は Docker コンテナに Apache Spark (PySpark) と Apache Sedona を導入しています。また、元々 PostgreSQL 上にあるジオメトリ情報を操作することを念頭に書いたので PostgreSQL の JDBC ドライバーもついでに入れてあります。多分この記事をご覧になっている方はこれをそのまま使えるケースがほとんどなんじゃないかと思います。`docker-comose.yml` なんかとうまく組み合わせて使ってください。

参考にしたページでは一度ソースから Apache Spark をダウンロードして、さらに `pip` でもインストールしていますが冗長だと思われます。`pip` で PySpark としてインストールするだけで少なくとも Python 上では使用可能なはずです。少なくとも私の手元では動きました。

`Dockerfile` の末尾にある環境変数はあまりちゃんと検証していません。特に最後の `PYTHONPATH` なんかは `SPARK_HOME` を入れなくても動く気もしますが、今後検証する機会があれば検証して更新します。

## 実際の使用例
サンプルコードの[レポジトリ](https://github.com/yuma-long/spark-sedona-docker.git)を作成しました。アメリカの州（state）と群（county）のジオメトリデータをダウンロードしてカリフォルニア州内の群を `Intersects` の条件で抜き出します。 DBは利用していませんが、あくまでサンプルということで悪しからず。。。 

クローンしてルートで

```bash:bash
docker-compose build
docker-compose up
```

を実行していただければ動きます。

```terminal:terminal
main  | +----------+--------------------+---------------+--------------------+
main  | |      name|      state_geometry|           name|     county_geometry|
main  | +----------+--------------------+---------------+--------------------+
main  | |California|MULTIPOLYGON (((-...|           Kern|POLYGON ((-119.91...|
main  | |California|MULTIPOLYGON (((-...|          Kings|POLYGON ((-119.95...|
main  | |California|MULTIPOLYGON (((-...|     San Benito|POLYGON ((-121.48...|
main  | |California|MULTIPOLYGON (((-...|         Fresno|POLYGON ((-120.42...|
main  | |California|MULTIPOLYGON (((-...|         Plumas|POLYGON ((-121.36...|
main  | |California|MULTIPOLYGON (((-...|        Mineral|POLYGON ((-118.90...|
main  | |California|MULTIPOLYGON (((-...|  San Francisco|MULTIPOLYGON (((-...|
main  | |California|MULTIPOLYGON (((-...|       Tuolumne|POLYGON ((-120.50...|
main  | |California|MULTIPOLYGON (((-...|      El Dorado|POLYGON ((-121.11...|
main  | |California|MULTIPOLYGON (((-...|          Modoc|POLYGON ((-121.44...|
main  | |California|MULTIPOLYGON (((-...|         Mohave|POLYGON ((-114.57...|
main  | |California|MULTIPOLYGON (((-...|         Tulare|POLYGON ((-118.80...|
main  | |California|MULTIPOLYGON (((-...|       Siskiyou|POLYGON ((-122.87...|
main  | |California|MULTIPOLYGON (((-...|        Klamath|POLYGON ((-121.31...|
main  | |California|MULTIPOLYGON (((-...|     Sacramento|POLYGON ((-121.18...|
main  | |California|MULTIPOLYGON (((-...|      Calaveras|POLYGON ((-120.63...|
main  | |California|MULTIPOLYGON (((-...|         Colusa|POLYGON ((-122.08...|
main  | |California|MULTIPOLYGON (((-...|         Amador|POLYGON ((-121.02...|
main  | |California|MULTIPOLYGON (((-...|San Luis Obispo|POLYGON ((-121.18...|
main  | |California|MULTIPOLYGON (((-...|     Stanislaus|POLYGON ((-120.92...|
main  | +----------+--------------------+---------------+--------------------+
main  | only showing top 20 rows
main  | 
main  | Count: 74
                                                                                
main exited with code 0
```

こんな感じ。ちなみにカリフォルニアには全部で58個の群があるそうです。群の抽出条件を `Contains` に変えると抜き出される個数は30になります。あれ。。。

## おわりに
最後までお読みいただきありがとうございました！この記事が Qiita での初投稿になります。ご質問、間違いのご指摘などございましたらぜひコメントをお寄せください。