Automating GIS Processes 2024 写経　Lesson 3（Point-in-polygon queries）後半


https://autogis-site.readthedocs.io/en/latest/lessons/lesson-3/point-in-polygon-queries.html

## Point-in-polygon queries on geopandas.GeoDataFrames

> In the following practical example we find which of the addresses we obtained in the geocoding section are located within a certain city district of Helsinki.

> The data set we are using is from Helsinki Region Infoshare, and licensed under a Creative-Commons-Attribution-4.0 license.

[geocoding](https://autogis-site.readthedocs.io/en/latest/lessons/lesson-3/geocoding-in-geopandas)セクションで取得した住所情報のうち、ヘルシンキのとある地域の住所のみを取り出してみましょう。

ヘルシンキのデータセットは、以下から取得したものです。
https://hri.fi/data/en_GB/dataset/helsingin-piirijako

ライセンスはこちらに属します。
https://creativecommons.org/licenses/by/4.0/

ファイルは[ここ](https://github.com/mopinfish/auto-gis-notebooks/tree/main/lessons/lesson-3/data/helsinki_city_districts)にあります。

ファイルのパスを指定します。
```
import pathlib
NOTEBOOK_PATH = pathlib.Path().resolve()
DATA_DIRECTORY = NOTEBOOK_PATH / "data"
```
ファイルを読み込みます。
```
import geopandas

city_districts = geopandas.read_file(
    DATA_DIRECTORY / "helsinki_city_districts" / "helsinki_city_districts_2021.gpkg"
)
city_districts.head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076503/be4fedf9-8c29-47dc-9407-1af8aacc7c3a.png)

読み込んだデータを表示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076503/f8d23482-354b-4648-9249-cd6b2a7380ca.png)


> Specifically, we want to find out which points are within the ‘Eteläinen’ (‘southern’) city district. Let’s start by obtaining a separate data set for this district, loading the addresses data, and plotting a multi-layer map that shows all districts, the ‘Eteläinen’ district, and all the points in one map:

どの住所の点がHelsinkiのEteläinen（フィンランド語で「南部」らしいです）地区に含まれるかを調べます。

まずはこの地域だけを切り出します。
```
southern_district = city_districts[city_districts.name == "Eteläinen"]
southern_district
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076503/e95779e4-7dd1-42af-bc84-0e7879930fb8.png)

次に住所情報をファイルから読み込みます。

ファイルは、[こちら](https://qiita.com/t_t_japan/items/998a90ea3f27a069c6b3#join-data-frames)で作成したものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076503/d48f5daa-f555-438d-b17f-249ae6592b4b.png)


```
addresses = geopandas.read_file(DATA_DIRECTORY / "addresses.gpkg")
addresses.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076503/b5a05240-a98a-4a99-b26b-6031810be3db.png)


そして、複数レイヤー地図をプロットをします。一つのレイヤーは全地区、もう一つのレイヤーはEteläinen地区のみです。

### Hint: Plotting multiple map layers

> To plot several map layers in one figure, use the ax parameter to specify in which axes data should be plotted. This was used in lesson 7 of Geo-Python to add text to a plot or modify axes properties.

> The easiest way to obtain an axes is to save the return value of the first plot() (see below). Another option is to create subplots(), possibly with only one row and one column.

いくつかの地図レイヤーを一つのfigure（これは適切な日本語が思い浮かびませんでした）にプロットする場合、
`ax`パラメータを使います。これはどのaxes（軸）でデータをプロットするかを指定するものです。

もっとも手早い方法は、以下のように、最初の`plot()`関数の戻り値を使うことです。
他には、`matplotlib.pyplot.subplots`も使えます。

https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.subplots.html


```
axes = city_districts.plot(facecolor="grey")
southern_district.plot(ax=axes, facecolor="red")
addresses.plot(ax=axes, color="blue", markersize=5)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076503/ddb749f3-cbf6-40c5-8122-fe5ad739366b.png)


> Some points are within the ‘Eteläinen’ district, but others are not. To find out which are the ones inside the district, we can use a **point-in-polygon** query, this time on the entire geopandas.GeoDataFrame. Its method within() returns Boolean (True/False) values that indicate whether or not a row’s geometry is contained in the supplied other geometry:

いくつかの点がEteläinen地区の内側にあります。が、そうでない点もあります。内側の点だけを検出するために、point-in-polygonクエリを使います。今回は`geopandas.GeoDataFrame`上、つまり複数行の地図データセット上でpoint-in-polygonクエリをします。`within() `メソッドの戻り値はbool型です。

仮に、`geopandas.GeoDataFrame`１行目のgeometry（今回は住所の点）が対象となるgeometry（今回はEteläinen地区のポリゴン）に含まれているとすると、その行の結果は`True`となります。

https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoSeries.within.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076503/9994a935-e507-43f5-b679-2ca34559bf47.png)

（行数が多いので、最初の５行に絞っています）
```
addresses.within(southern_district.at[0, "geometry"]).head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076503/d1924add-bf0e-4a35-95aa-e841829aed8a.png)


::: note
### Geometry vs. geometry column

> In the example below, we use southern.at[0, "geometry"] to obtain a single value, a shapely.geometry.Polygon, instead of an entire column (a GeoSeries). This ensures that each row’s geometry in the addresses data frame is matched against the same polygon. In contrast, if we ran within() against a column, the operation would be carried out row-wise: the first address point would be checked against the first polygon, the second address point against the second polygon, and so forth.

> Check the documentation for ``within()` <https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoSeries.within.html>`__ to learn more!


以下の例では、`southern.at[0, "geometry"]`と、列そのもの（`GeoSeries`型）ではなく、一つの`shapely.geometry.Polygon`型の値に対して、`within()`しています。

```
addresses.within(southern_district.at[0, "geometry"])
```

これにより、`addresses`の各々の行の`geometry`に単一のポリゴンを突き合わせることができます。
（余談ですが、atは、局所的な場所を指すときに使う前置詞です。）

一方、列そのもの（`GeoSeries`型）に対して、`within()`すると、行ごとに実行されます。`addresses`の１行目のgeometryと`southern`の１行目のgeometry、`addresses`の２行目のgeometryと`southern`の２行目のgeometry、ということになります。

```
addresses.within(southern.geometry)
```
詳細は[こちら](https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoSeries.within.html)

:::


> This list of Boolean values, also called a mask array can be used to filter the input data frame:

このリストは、bool型の値のリストです。マスク配列とも呼ばれ、データフレームの行フィルタとして使うことができます。

```
addresses_in_the_southern_district = addresses[
    addresses.within(southern_district.at[0, "geometry"])
]
addresses_in_the_southern_district
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076503/f21a20a5-ff15-4b74-871b-4f31e74c96f7.png)

マスク配列`addresses.within(southern_district.at[0, "geometry"]) `の１・２・３行目はTrueでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076503/6e3846bb-e5e5-41b9-ac0c-2fb5b24536fd.png)


なので、１・２・３行目（インデックスは０・１・２）はフィルター後も出力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076503/a7c477c9-6610-4280-8e0b-507396efde1a.png)




> Finally, let’s plot this list of addresses one more time to visually verify that all of them, indeed, are located within the ‘Eteläinen’ city district:

最後に、住所情報のリストをもう一度プロットします。今後は、‘Eteläinen’地区の中にあるものだけに限定されます。

```
axes = city_districts.plot(facecolor="grey")
southern_district.plot(ax=axes, facecolor="red")

addresses_in_the_southern_district.plot(
    ax=axes,
    color="gold",
    markersize=5
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076503/55c642f3-d6ea-46d1-b0db-705ba6c84df2.png)


> Perfect! Now we are left with only the (golden) points which, indeed, are inside the red polygon. That’s exactly what we wanted!

黄金色の点だけが残りました。これは、赤色のポリゴンの（’Eteläinen’地区）の中にあるものだけに限定されます。狙い通りです。