OpenStreetMapの変化量を調べる

## はじめに

データの変化は、時間の経過とともにさまざまな要因によって引き起こされます。特に、地図データのような情報は、現実世界の変化に合わせて、ユーザーによる編集や更新が頻繁に行われるため、常に変化しています。

本記事では、OpenStreetMapを活用して、エリアと期間、およびTagを指定して、そのデータがどれだけ変化するのかの指標を調査する方法について考えてみたいと思います。

>本記事のコードについてはサンプルになります。ご利用の場合は各自環境に合わせてカスタマイズをお願いいたします。

## 事例
例として、指定された期間内に制限そデータがどれだけ変化したかを明らかにすることを目指してみます。

1. 一定の地域における該当データ（制限速度）の追加、削除、変更の件数を把握する。
2. データの変化割合を計算し、どの程度の変化があったのかを定量的に示す。

## 手法

本調査では、OpenStreetMap（OSM）を活用します。特に、OverpassTurboAPIを利用して、指定されたバウンディングボックス内のタグを持つウェイを取得しデータを分析する方法にトライします。

### ステップ1: データ取得

OverpassTurboAPIではOSMデータについて、バウンディングボックスの緯度・経度、比較する日付、変更期間、およびタグに基づいて、APIから必要なデータを動的に取得することが可能です。

>国や地域別に取得する方法も試したのですが、クエリが適切に機能しない（あるいはデータが不足している）などで適切に取得できなかったため、今回はバウンディングボックスによる領域によってデータを取得しています。

例えば、下記のようなEndpointが活用できます。

```https
https://overpass-api.de/api/interpreter?data=[out:json];way['{user_tag}']({lat1},{lon1},{lat2},{lon2});out body;"
```
user_tagに調査対象のタグを、latとlonでバウンディングボックスを指定します。 （例えば今回の事例では、user_tag=oneway等）

注意：取得するデータが大きすぎる場合api側の制限がありますので取得できなくなります。今回は地域レベルのエリアを想定していますので、より大規模なエリアの解析については、別途大規模データのapiをご検討ください。

### ステップ2: データの抽出と比較

取得したデータから、1年前と現在の制限速度データを抽出し、追加されたウェイ、削除されたウェイ、そして変更されたウェイの数を計算します。この過程では、データの整合性を保ちながら、変化を取得します。
Pythonを使った具体例を下記に紹介します。

```python
# Fetch the data
data_now = fetch_data(url_now)
data_old = fetch_data(url_old)

# Check if data fetching was successful
if data_now is None or data_old is None:
    print("Data fetching failed. Please check your inputs and try again.")
else:
    # Extract tagged data
    tagged_data_now = extract_tagged_data(data_now, user_tag)
    tagged_data_old = extract_tagged_data(data_old, user_tag)

    # Calculate the differences
    added = set(tagged_data_now.keys()) - set(tagged_data_old.keys())
    removed = set(tagged_data_old.keys()) - set(tagged_data_now.keys())
    changed = {way for way in tagged_data_now if way in tagged_data_old and tagged_data_now[way] != tagged_data_old[way]}

    # Count totals
    total_now = len(tagged_data_now)
    total_old = len(tagged_data_old)

    # Count the number of changes
    added_count = len(added)
    removed_count = len(removed)
    changed_count = len(changed)

    # Count total changes
    changes_count = added_count + removed_count + changed_count

    # Calculate the change percentage
    if total_old > 0:
        change_percentage = (changes_count / total_old) * 100
    else:
        change_percentage = 0

```

### ステップ3: 変更割合の計算

最終的には、追加や削除、変更された件数をもとに、全体に対する変更割合を計算し、出力します。（下記は例）

>Number of ways old: 157
Number of ways now: 130
Number of added ways: 2
Number of removed ways: 29
Number of changed ways: 2
Total changes: 33 (2+29+2)
Change percentage: 21.02% (33/157)

最終的にChange parcentageの数値でどの程度該当データが変化したのかを把握することができます。

## サンプル
例えば、特定の領域をバウンディングボックスで、期間とタグを入力することで、様々なデータや期間の解析をする場合下記のような処理にて実施が可能かと思います。
デフォルトを1年間としていますが、入力で変更できるインプットも用意した例です。

```python
import requests
from datetime import datetime, timedelta

# Function to fetch JSON data from the given URL
def fetch_data(url):
    response = requests.get(url)
    if response.status_code == 200:
        return response.json()
    else:
        print(f"Error fetching data: {response.status_code} - {response.text}")
        return None

# Function to extract tagged data from the response
def extract_tagged_data(data, tag):
    tagged_data = {}
    for element in data['elements']:
        if element['type'] == 'way' and 'tags' in element and tag in element['tags']:
            tagged_data[element['id']] = element['tags'][tag]
    return tagged_data

# User input for bounding box coordinates
user_input = input("Enter the bounding box coordinates (e.g., 139.727733,35.649818,139.799151,35.709364): ")
lon1, lat1, lon2, lat2 = map(float, user_input.split(','))

# User input for the date (Now or a specific date)
user_date = input("Enter the date for comparison (e.g., Now or in YYYY-MM-DD format): ")

# Set the date
if user_date.lower() == 'now':
    now = datetime.now()
else:
    now = datetime.strptime(user_date, "%Y-%m-%d")

# User input for the change period (default is 12 months)
user_period = input("Enter the period in months for comparison (default: 12): ")
if user_period.strip() == "":
    user_period = 12  # Default value
else:
    user_period = int(user_period)

# Calculate the number of days for the change period
days = user_period * 30  # Convert months to days
old = now - timedelta(days=days)

# User input for the tag to compare
user_tag = input("Enter the tag to compare (e.g., oneway): ")

# Specify the URLs
url_now = f"https://overpass-api.de/api/interpreter?data=[out:json];way['{user_tag}']({lat1},{lon1},{lat2},{lon2});out body;"
url_old = f"https://overpass-api.de/api/interpreter?data=[out:json][date:'{old.strftime('%Y-%m-%dT00:00:00Z')}'];way['{user_tag}']({lat1},{lon1},{lat2},{lon2});out body;"

# Fetch the data
data_now = fetch_data(url_now)
data_old = fetch_data(url_old)

# Check if data fetching was successful
if data_now is None or data_old is None:
    print("Data fetching failed. Please check your inputs and try again.")
else:
    # Extract tagged data
    tagged_data_now = extract_tagged_data(data_now, user_tag)
    tagged_data_old = extract_tagged_data(data_old, user_tag)

    # Calculate the differences
    added = set(tagged_data_now.keys()) - set(tagged_data_old.keys())
    removed = set(tagged_data_old.keys()) - set(tagged_data_now.keys())
    changed = {way for way in tagged_data_now if way in tagged_data_old and tagged_data_now[way] != tagged_data_old[way]}

    # Count totals
    total_now = len(tagged_data_now)
    total_old = len(tagged_data_old)

    # Count the number of changes
    added_count = len(added)
    removed_count = len(removed)
    changed_count = len(changed)

    # Count total changes
    changes_count = added_count + removed_count + changed_count

    # Calculate the change percentage
    if total_old > 0:
        change_percentage = (changes_count / total_old) * 100
    else:
        change_percentage = 0

# Output the results
print(f"Number of ways last year: {total_old}")
print(f"Number of ways now: {total_now}")
print(f"Number of added ways: {added_count}")
print(f"Number of removed ways: {removed_count}")
print(f"Number of changed ways: {changed_count}")
print(f"Total changes: {changes_count}")
print(f"Change percentage: {change_percentage:.2f}%")
```


## 終わりに
この調査を通じて、地図データの動的な性質を理解し、特定の期間におけるデータの変化を定量的に評価することができます。
地図データは常に変化し、どのような手法を取っても確実な変化量を予測することやコントロールすることは事実上不可能と言えます。また、現実世界の変化をキャッチアップする以上変化自体はより良くなる（鮮度が良い情報）ことを前提にするのがベースとなるかと思います。
一方で、無限ではないシステム環境上で活用するにはこのように変化量を指標として活用しながら最も効果的な設計を模索することが必要になるかと思います。

この記事が位置情報データの活用のお役に立てれば幸いです！