Google MapsのAir Quality APIから大気汚染データを取得するためのPythonツール

PythonツールでGoogle MapsのAir Quality APIから大気汚染データを取得しよう

世界中からリッチでリアルタイムな大気の質データを取得する方法を学びましょう

この記事では、Google マップの大気質 API を使用して、Python でリアルタイムの大気汚染データ、時系列データ、地図を取得および探索する方法について詳述しています。フルコードはこちらでご確認いただけます。

1. 背景

2023年8月、Google はマッピング API のリストに大気の質サービスを追加すると発表しました。詳細についてはこちらをご覧ください。この情報は現在、Google マップアプリ内からも利用可能であるようですが、API を通じて取得できるデータの方がよりリッチです。

発表によれば、Google はさまざまなソースからの情報を組み合わせて、地上の汚染センサー、衛星データ、リアルタイムの交通情報、数値モデルの予測を含め、最大500mの解像度で100ヶ国の大気の質をダイナミックに更新されるデータセットとして提供しています。これは非常に興味深く、さまざまなマッピング、ヘルスケア、計画アプリケーションにとって有用なデータセットのようです!

これを初めて読んだとき、私は「データと話す」アプリケーションで試してみる予定でした。このトラベルマッパーツールの構築に関する知識を活用して、お気に入りの都市の大気汚染濃度の時系列プロットや、悪い大気を回避するための地元のハイキング計画を支援するツールなどを作成するかもしれません。

ここで役立つ “現在の条件” サービス、場所ごとの現在の大気質指数値と汚染物質濃度を提供する「過去の条件」サービス、およびイメージとして特定の地域の現在の条件を提供する「ヒートマップ」サービスの3つの API ツールがあります。

以前は、Python で Google マップの API を呼び出すために素晴らしいgooglemapsパッケージを使用していましたが、これらの新しい API はまだサポートされていません。驚くべきことに、公式のドキュメント以外では、これらの新しいツールを使用している人々の例や、それらを呼び出すために設計された事前存在の Python パッケージはほとんど見つかりませんでした。もし他に知っている人がいれば、喜んで訂正いたします!

そこで、自分自身の簡単なツールを作成しました。この記事では、それらがどのように機能し、どのように使用するかについて説明します。これが、Python でこれらの新しい API を試してみたい人や、始める場所を探している人に役立つことを願っています。このプロジェクトのすべてのコードはこちらで見つけることができ、時間の経過とともに機能を追加し、大気の質データを使用した何らかのマッピングアプリケーションを作成する予定です。

2. 特定の場所の現在の大気質を取得する

さあ、始めましょう!このセクションでは、Google マップで特定の場所の大気質データを取得する方法について説明します。まず、Google Cloud アカウントを通じて生成できる API キーが必要です。90日間の無料トライアル期間がありますが、それ以降は使用した API サービスに対して料金が発生しますので、コール数を増やす前に価格ポリシーをご確認ください!

Google Cloud API ライブラリのスクリーンショット。大気質 API をアクティブにすることができます。画像は筆者の生成です。

私は通常、API キーを.envファイルに保存し、このようにdotenvを使用してそれをロードします。

from dotenv import load_dotenvfrom pathlib import Pathdef load_secets():    load_dotenv()    env_path = Path(".") / ".env"    load_dotenv(dotenv_path=env_path)    google_maps_key = os.getenv("GOOGLE_MAPS_API_KEY")    return {        "GOOGLE_MAPS_API_KEY": google_maps_key,    }

現在の状況を取得するには、詳細はこちらのPOSTリクエストが必要です。私たちは< a href=”https://github.com/googlemaps/google-maps-services-python”>googlemapsパッケージからインスピレーションを受けて、一般化できる方法でこれを行います。最初に、 requestsを使用して呼び出しを行うクライアントクラスを作成します。目標は非常に簡単です-以下のようなURLを構築し、ユーザーのクエリに固有のリクエストオプションをすべて含めたいのです。

https://airquality.googleapis.com/v1/currentConditions:lookup?key=YOUR_API_KEY

Clientクラスは、keyとしてAPIキーを受け取り、request_urlをクエリのために構築します。リクエストオプションをparams辞書として受け入れ、それらをJSONリクエストボディに配置し、これはself.session.post()呼び出しで処理されます。

import requestsimport ioclass Client(object):    DEFAULT_BASE_URL = "https://airquality.googleapis.com"    def __init__(self, key):        self.session = requests.Session()        self.key = key    def request_post(self, url, params):        request_url = self.compose_url(url)        request_header = self.compose_header()        request_body = params        response = self.session.post(            request_url,            headers=request_header,            json=request_body,        )        return self.get_body(response)    def compose_url(self, path):        return self.DEFAULT_BASE_URL + path + "?" + "key=" + self.key    @staticmethod    def get_body(response):        body = response.json()        if "error" in body:            return body["error"]        return body    @staticmethod    def compose_header():        return {            "Content-Type": "application/json",        }

ここで、このClientクラスを使用して現在の状況APIの有効なリクエストオプションをユーザーが組み立て、その後このクライアントクラスを使用してリクエストを行う関数を作成できます。これもgooglemapsパッケージのデザインに触発されています。

def current_conditions(    client,    location,    include_local_AQI=True,    include_health_suggestion=False,    include_all_pollutants=True,    include_additional_pollutant_info=False,    include_dominent_pollutant_conc=True,    language=None,):    """    こちらのAPIのドキュメンテーションを参照してください    https://developers.google.com/maps/documentation/air-quality/reference/rest/v1/currentConditions/lookup    """    params = {}    if isinstance(location, dict):        params["location"] = location    else:        raise ValueError(            "Location argument must be a dictionary containing latitude and longitude"        )    extra_computations = []    if include_local_AQI:        extra_computations.append("LOCAL_AQI")    if include_health_suggestion:        extra_computations.append("HEALTH_RECOMMENDATIONS")    if include_additional_pollutant_info:        extra_computations.append("POLLUTANT_ADDITIONAL_INFO")    if include_all_pollutants:        extra_computations.append("POLLUTANT_CONCENTRATION")    if include_dominent_pollutant_conc:        extra_computations.append("DOMINANT_POLLUTANT_CONCENTRATION")    if language:        params["language"] = language    params["extraComputations"] = extra_computations    return client.request_post("/v1/currentConditions:lookup", params)

このAPIのオプションは比較的簡単です。調査したいポイントの経度と緯度を含む辞書が必要であり、返される情報の量を制御するさまざまな他の引数をオプションで取ることができます。すべての引数がTrueに設定された状態での動作を見てみましょう

#クライアントの設定client = Client(key=GOOGLE_MAPS_API_KEY)# Los Angeles, CAの場所location = {"longitude":-118.3,"latitude":34.1}# JSONでのレスポンスcurrent_conditions_data = current_conditions(  client,  location,  include_health_suggestion=True,  include_additional_pollutant_info=True)

興味深い情報がたくさん返されます! Universalおよび米国のAQI指数からのエアクオリティインデックスの値だけでなく、主な汚染物質の濃度、それぞれの説明、現在の空気の品質に対する総合的な健康推奨事項もあります。

{'dateTime': '2023-10-12T05:00:00Z', 'regionCode': 'us', 'indexes': [{'code': 'uaqi',   'displayName': 'Universal AQI',   'aqi': 60,   'aqiDisplay': '60',   'color': {'red': 0.75686276, 'green': 0.90588236, 'blue': 0.09803922},   'category': 'Good air quality',   'dominantPollutant': 'pm10'},  {'code': 'usa_epa',   'displayName': 'AQI (US)',   'aqi': 39,   'aqiDisplay': '39',   'color': {'green': 0.89411765},   'category': 'Good air quality',   'dominantPollutant': 'pm10'}], 'pollutants': [{'code': 'co',   'displayName': 'CO',   'fullName': 'Carbon monoxide',   'concentration': {'value': 292.61, 'units': 'PARTS_PER_BILLION'},   'additionalInfo': {'sources': 'Typically originates from incomplete combustion of carbon fuels, such as that which occurs in car engines and power plants.',    'effects': 'When inhaled, carbon monoxide can prevent the blood from carrying oxygen. Exposure may cause dizziness, nausea and headaches. Exposure to extreme concentrations can lead to loss of consciousness.'}},  {'code': 'no2',   'displayName': 'NO2',   'fullName': 'Nitrogen dioxide',   'concentration': {'value': 22.3, 'units': 'PARTS_PER_BILLION'},   'additionalInfo': {'sources': 'Main sources are fuel burning processes, such as those used in industry and transportation.',    'effects': 'Exposure may cause increased bronchial reactivity in patients with asthma, lung function decline in patients with Chronic Obstructive Pulmonary Disease

3. ある場所の大気の品質の時系列を取得する

特定の場所でAQIと汚染物質の値の時系列を取得できると便利ですよね?これによって、汚染物質間の相関関係や、交通や天候による日常の変動などの興味深いパターンが明らかになるかもしれません。

これは、歴史的な条件APIに対する別のPOSTリクエストで実現できます。これにより、時間毎の履歴が得られます。現在の条件と同様に機能しますが、結果が非常に長くなる場合があるため、複数のpagesとして返されます。これに対応するために、少し追加の論理処理が必要です。

まず、Clientrequest_postメソッドを以下のように修正しましょう。

  def request_post(self,url,params):    request_url = self.compose_url(url)    request_header = self.compose_header()    request_body = params    response = self.session.post(      request_url,      headers=request_header,      json=request_body,    )    response_body = self.get_body(response)    # まず最初のページをレスポンスの辞書に入れます    page = 1    final_response = {        "page_{}".format(page) : response_body    }    # 必要に応じてすべてのページを取得します     while "nextPageToken" in response_body:      # 次のページのトークンを使って再度呼び出します      request_body.update({          "pageToken":response_body["nextPageToken"]      })      response = self.session.post(          request_url,          headers=request_header,          json=request_body,      )      response_body = self.get_body(response)      page += 1      final_response["page_{}".format(page)] = response_body    return final_response

この修正では、response_bodynextPageTokenというフィールドが含まれる場合に対応しています。これは、生成された次のデータのページのIDであり、取得可能な状態になっています。この情報が存在する場合は、pageTokenという新しいパラメータを使用してAPIを再度呼び出すだけです。これをページがなくなるまで繰り返すwhileループで行います。したがって、final_responseの辞書には、ページ番号で示される別のレイヤーが追加されます。 current_conditionsへの呼び出しでは、通常1つのページしかないのですが、historical_conditionsへの呼び出しでは複数のページが存在する場合があります。

これで準備が整ったので、current_conditionsと非常に似たスタイルでhistorical_conditions関数を記述できます。

def historical_conditions(    client,    location,    specific_time=None,    lag_time=None,    specific_period=None,    include_local_AQI=True,    include_health_suggestion=False,    include_all_pollutants=True,    include_additional_pollutant_info=False,    include_dominant_pollutant_conc=True,    language=None,):    """    このAPIのドキュメントはこちらを参照してください https://developers.google.com/maps/documentation/air-quality/reference/rest/v1/history/lookup    """    params = {}    if isinstance(location, dict):        params["location"] = location    else:        raise ValueError(            "Location argument must be a dictionary containing latitude and longitude"        )    if isinstance(specific_period, dict) and not specific_time and not lag_time:        assert "startTime" in specific_period        assert "endTime" in specific_period        params["period"] = specific_period    elif specific_time and not lag_time and not isinstance(specific_period, dict):        # 注意:時刻は「ズール」形式である必要があります。        # 例:datetime.datetime.strftime(datetime.datetime.now(),"%Y-%m-%dT%H:%M:%SZ")        params["dateTime"] = specific_time    # 時間のラグ期間(時間単位)    elif lag_time and not specific_time and not isinstance(specific_period, dict):        params["hours"] = lag_time    else:        raise ValueError(            "Must provide specific_time, specific_period or lag_time arguments"        )    extra_computations = []    if include_local_AQI:        extra_computations.append("LOCAL_AQI")    if include_health_suggestion:        extra_computations.append("HEALTH_RECOMMENDATIONS")    if include_additional_pollutant_info:        extra_computations.append("POLLUTANT_ADDITIONAL_INFO")    if include_all_pollutants:        extra_computations.append("POLLUTANT_CONCENTRATION")    if include_dominant_pollutant_conc:        extra_computations.append("DOMINANT_POLLUTANT_CONCENTRATION")    if language:        params["language"] = language    params["extraComputations"] = extra_computations    # ページサイズはここでデフォルト値100に設定されています    params["pageSize"] = 100    # page tokenは必要に応じてrequest_postメソッドで埋められます    params["pageToken"] = ""    return client.request_post("/v1/history:lookup", params)

APIを使用して、歴史的な期間を定義するために、最大720時間(30日)までのlag_timeを受け入れることができます。また、specific_period辞書を受け入れることもできます。これは、上記のコメントで説明されている形式で開始時間と終了時間を定義します。最後に、単一のデータの1時間を取得するために、specific_timeによって提供される単一のタイムスタンプを受け入れることができます。また、pageSizeパラメータの使用にも注意してください。これは、APIへの各呼び出しで返される時間ポイントの数を制御します。デフォルトは100です。

試してみましょう。

# クライアントのセットアップclient = Client(key=GOOGLE_MAPS_API_KEY)# ロサンゼルス、カリフォルニアの場所location = {"longitude":-118.3,"latitude":34.1}# JSONレスポンスhistory_conditions_data = historical_conditions(    client,    location,    lag_time=720)

過去720時間の1時間ごとのAQIインデックス値と特定の汚染物値が含まれる、長い入れ子のJSONレスポンスを取得するはずです。これを視覚化や分析に適した構造に整形するためには、さまざまな方法がありますが、以下の関数では、これを「長い」フォーマットのパンダのデータフレームに変換し、プロットに適しています。

from itertools import chainimport pandas as pddef historical_conditions_to_df(response_dict):    chained_pages = list(chain(*[response_dict[p]["hoursInfo"] for p in [*response_dict]]))  all_indexes = []  all_pollutants = []  for i in range(len(chained_pages)):    # タイムスタンプのいずれかがデータが欠落している場合に必要なこのチェックがあります。これは時々起こります。    if "indexes" in chained_pages[i]:      this_element = chained_pages[i]      # 時間を取得      time = this_element["dateTime"]      # すべてのインデックス値を取得してメタデータを追加      all_indexes += [(time , x["code"],x["displayName"],"index",x["aqi"],None) for x in this_element['indexes']]      # すべての汚染物値を取得してメタデータを追加      all_pollutants += [(time , x["code"],x["fullName"],"pollutant",x["concentration"]["value"],x["concentration"]["units"]) for x in this_element['pollutants']]    all_results = all_indexes + all_pollutants  # "長いフォーマット"のデータフレームを生成  res = pd.DataFrame(all_results,columns=["time","code","name","type","value","unit"])  res["time"]=pd.to_datetime(res["time"])  return res

historical_conditionsの出力に対してこれを実行すると、簡単な分析のためにフォーマットされたデータフレームが生成されます。

df = historical_conditions_to_df(history_conditions_data)
プロットのために準備された歴史的なAQIデータの例のデータフレーム

そして、結果を seaborn や他の可視化ツールでプロットすることができます。

import seaborn as snsg = sns.relplot(    x="time",    y="value",    data=df[df["code"].isin(["uaqi","usa_epa","pm25","pm10"])],    kind="line",    col="name",    col_wrap=4,    hue="type",    height=4,    facet_kws={'sharey': False, 'sharex': False})g.set_xticklabels(rotation=90)
LAのこの場所のユニバーサルAQI、US AQI、pm25、およびpm10の値を30日間表示したもの。著者が生成した画像。

これは既に非常に興味深いです!汚染物の時系列には明らかにいくつかの周期性があり、US AQIは、pm25やpm10の濃度と密接に関連していることがわかります。Googleがここで提供しているユニバーサルAQIについてはあまり詳しくないため、なぜpm25とp10と逆相関しているのかは分かりません。UAQIが小さいほど空気の質が良いのでしょうか?いくつかの検索を行っても、良い答えを見つけることができませんでした。

4. エアクオリティヒートマップタイルを取得する

Google Maps Air Quality APIの最終的なユースケースであるヒートマップタイルの生成に入りましょう。これらのタイルは現在の空気のクオリティを視覚化するための強力なツールであり、特にFoliumマップと組み合わせると効果的です。ただし、これに関するドキュメントは少なく、残念なことです。

GETリクエストを使用してこれらのタイルを取得します。URLの形式は以下のようになります。zoomxyによってタイルの場所が指定されます。

GET https://airquality.googleapis.com/v1/mapTypes/{mapType}/heatmapTiles/{zoom}/{x}/{y}

zoomxyは何を意味するのでしょうか?これについては、Google Mapsが緯度と経度の座標を「タイル座標」に変換する方法について学ぶことで理解できます。詳細はこちらで説明されています。基本的に、Google Mapsは各セルが256x256ピクセルであるグリッドにイメージを格納しており、セルの実際の大きさはズームレベルの関数です。APIに対して呼び出しを行う際、どのグリッドから描画するか(ズームレベルによって決まる)と、グリッド上のどこから描画するか(xyのタイル座標によって決まる)を指定する必要があります。APIから返されるのはPython Imaging Library(PIL)や同様のイメージ処理パッケージで読み取ることができるバイト配列です。

上記の形式でurlを作成した後、対応する画像を取得するためにClientクラスにいくつかのメソッドを追加できます。

  def request_get(self,url):    request_url = self.compose_url(url)    response = self.session.get(request_url)    # ヒートマップタイルサービスからの画像の場合    return self.get_image(response)  @staticmethod  def get_image(response):    if response.status_code == 200:      image_content = response.content      # PILのImageを使用していることに注意      # PILからImageをインポートする必要があります      image = Image.open(io.BytesIO(image_content))      return image    else:      print("画像のGETリクエストがエラーを返しました")      return None

これは良いですが、本当に必要なのは経度と緯度の一連の座標をタイル座標に変換する能力です。ドキュメントはこれを説明しており、まず座標をメルカトル投影に変換し、指定されたズームレベルで「ピクセル座標」に変換します。最後に、それをタイル座標に変換します。これらの変換をすべて処理するには、以下のTileHelperクラスを使用できます。

import mathimport numpy as npclass TileHelper(object):  def __init__(self, tile_size=256):    self.tile_size = tile_size  def location_to_tile_xy(self,location,zoom_level=4):    # ここでの関数に基づいています    # https://developers.google.com/maps/documentation/javascript/examples/map-coordinates#maps_map_coordinates-javascript    lat = location["latitude"]    lon = location["longitude"]    world_coordinate = self._project(lat,lon)    scale = 1 << zoom_level    pixel_coord = (math.floor(world_coordinate[0]*scale), math.floor(world_coordinate[1]*scale))    tile_coord = (math.floor(world_coordinate[0]*scale/self.tile_size),math.floor(world_coordinate[1]*scale/self.tile_size))    return world_coordinate, pixel_coord, tile_coord  def tile_to_bounding_box(self,tx,ty,zoom_level):    # 詳細はこちらを参照    # https://developers.google.com/maps/documentation/javascript/coordinates    box_north = self._tiletolat(ty,zoom_level)    # タイル番号は南に進む    box_south = self._tiletolat(ty+1,zoom_level)    box_west = self._tiletolon(tx,zoom_level)    # タイル番号は東に進む    box_east = self._tiletolon(tx+1,zoom_level)    # (latmin, latmax, lonmin, lonmax)    return (box_south, box_north, box_west, box_east)  @staticmethod  def _tiletolon(x,zoom):    return x / math.pow(2.0,zoom) * 360.0 - 180.0  @staticmethod  def _tiletolat(y,zoom):    n = math.pi - (2.0 * math.pi * y)/math.pow(2.0,zoom)    return math.atan(math.sinh(n))*(180.0/math.pi)  def _project(self,lat,lon):    siny = math.sin(lat*math.pi/180.0)    siny = min(max(siny,-0.9999), 0.9999)    return (self.tile_size*(0.5 + lon/360), self.tile_size*(0.5 - math.log((1 + siny) / (1 - siny)) / (4 * math.pi)))  @staticmethod  def find_nearest_corner(location,bounds):    corner_lat_idx = np.argmin([        np.abs(bounds[0]-location["latitude"]),        np.abs(bounds[1]-location["latitude"])        ])    corner_lon_idx = np.argmin([        np.abs(bounds[2]-location["longitude"]),        np.abs(bounds[3]-location["longitude"])        ])    if (corner_lat_idx == 0) and (corner_lon_idx == 0):      # 最も近いのはlatmin, lonmin      direction = "southwest"    elif (corner_lat_idx == 0) and (corner_lon_idx == 1):      direction = "southeast"    elif (corner_lat_idx == 1) and (corner_lon_idx == 0):      direction = "northwest"    else:      direction = "northeast"    corner_coords = (bounds[corner_lat_idx],bounds[corner_lon_idx+2])    return corner_coords, direction  @staticmethod  def get_ajoining_tiles(tx,ty,direction):    if direction == "southwest":      return [(tx-1,ty),(tx-1,ty+1),(tx,ty+1)]    elif direction == "southeast":      return [(tx+1,ty),(tx+1,ty-1),(tx,ty-1)]    elif direction == "northwest":      return [(tx-1,ty-1),(tx-1,ty),(tx,ty-1)]    else:      return [(tx+1,ty-1),(tx+1,ty),(tx,ty-1)]

location_to_tile_xy関数は、位置の辞書とズームレベルを受け取り、その点が含まれるタイルを返します。もう一つ便利な関数はtile_to_bounding_boxです。これは指定したグリッドセルの境界座標を見つけます。この関数は、セルを地理位置に関連付けて地図上にプロットするために必要です。

では、以下のair_quality_tile関数の内部でこれがどのように機能するか見てみましょう。この関数は、clientlocation、および取得するタイルの種類を示す文字列を受け取ります。また、最初に選択するのが難しいズームレベルを指定する必要があり、いくつかの試行とエラーが必要です。後でget_adjoining_tiles引数について説明します。

def air_quality_tile(    client,    location,    pollutant="UAQI_INDIGO_PERSIAN",    zoom=4,    get_adjoining_tiles = True):  # see https://developers.google.com/maps/documentation/air-quality/reference/rest/v1/mapTypes.heatmapTiles/lookupHeatmapTile  assert pollutant in [      "UAQI_INDIGO_PERSIAN",      "UAQI_RED_GREEN",      "PM25_INDIGO_PERSIAN",      "GBR_DEFRA",      "DEU_UBA",      "CAN_EC",      "FRA_ATMO",      "US_AQI"  ]  # contains useful methods for dealing the tile coordinates  helper = TileHelper()  # get the tile that the location is in  world_coordinate, pixel_coord, tile_coord = helper.location_to_tile_xy(location,zoom_level=zoom)  # get the bounding box of the tile  bounding_box = helper.tile_to_bounding_box(tx=tile_coord[0],ty=tile_coord[1],zoom_level=zoom)  if get_adjoining_tiles:    nearest_corner, nearest_corner_direction = helper.find_nearest_corner(location, bounding_box)    adjoining_tiles = helper.get_ajoining_tiles(tile_coord[0],tile_coord[1],nearest_corner_direction)  else:    adjoining_tiles = []  tiles = []  #get all the adjoining tiles, plus the one in question  for tile in adjoining_tiles + [tile_coord]:    bounding_box = helper.tile_to_bounding_box(tx=tile[0],ty=tile[1],zoom_level=zoom)    image_response = client.request_get(        "/v1/mapTypes/" + pollutant + "/heatmapTiles/" + str(zoom) + '/' + str(tile[0]) + '/' + str(tile[1])    )    # convert the PIL image to numpy    try:      image_response = np.array(image_response)    except:      image_response = None    tiles.append({        "bounds":bounding_box,        "image":image_response    })  return tiles

コードを読むことから、以下のワークフローがわかります。まず、興味のある位置のタイル座標を見つけます。これは取得したいグリッドセルを指定します。そして、このグリッドセルの境界座標を見つけます。周囲のタイルを取得したい場合は、境界ボックスの最も近い隅を見つけて、その隅を使用して隣接する3つのグリッドセルのタイル座標を計算します。それからAPIを呼び出し、各タイルを対応する境界ボックスとともに画像として返します。

このコードは以下のように通常の方法で実行できます:

client = Client(key=GOOGLE_MAPS_API_KEY)location = {"longitude":-118.3,"latitude":34.1}zoom = 7tiles = air_quality_tile(    client,    location,    pollutant="UAQI_INDIGO_PERSIAN",    zoom=zoom,    get_adjoining_tiles=False)

そしてズーム可能なマップをfoliumでプロットします!ここでleafmapを使用していることに注意してください。なぜなら、このパッケージはgradioと互換性のあるFoliumマップを生成できるからです。gradioは、Pythonアプリケーションのシンプルなユーザーインターフェースを生成するための強力なツールです。例については、この記事をご覧ください。

import leafmap.foliumap as leafmapimport foliumlat = location["latitude"]lon = location["longitude"]map = leafmap.Map(location=[lat, lon], tiles="OpenStreetMap", zoom_start=zoom)for tile in tiles:  latmin, latmax, lonmin, lonmax = tile["bounds"]  AQ_image = tile["image"]  folium.raster_layers.ImageOverlay(    image=AQ_image,    bounds=[[latmin, lonmin], [latmax, lonmax]],    opacity=0.7  ).add_to(map)

残念なことに、このズームレベルで私たちの場所を含むタイルはほとんど海ですが、詳細な地図の上に大気汚染がプロットされているのを見ることはまだ素晴らしいです。ズームインすると、都市部の大気品質のシグナルに道路交通情報が使用されていることがわかります。

Foliumマップの上に大気の品質ヒートマップタイルをプロットする。作者によって生成された画像。

get_adjoining_tiles=Trueを設定すると、そのズームレベルで最も近い3つの重複しないタイルを取得するため、より素敵な地図が表示されます。私たちの場合、それは地図をより見栄え良くするために非常に役立ちます。

隣接するタイルも一緒に取得すると、より興味深い結果が得られます。ここでは色は全国AQI指数を示しています。作者によって生成された画像。

個人的には、pollutant=US_AQIの場合に生成される画像が好きですが、いくつかの異なるオプションがあります。残念ながらAPIはカラースケールを返さないため、画像のピクセル値と色の意味を知ることでカラースケールを生成することができます。

上記のタイルと同様にUS AQIに基づいて色分けされた地図。この地図は2023年10月12日に生成され、中央カリフォルニアの明るい赤いスポットは、このツール https://www.frontlinewildfire.com/california-wildfire-map/ によるとCoalinga近くの丘陵地帯で予定された火災のようです。作者によって生成された画像。</figcaption></figure><h2 id=結論

最後までお読みいただきありがとうございます!ここでは、PythonでGoogle Maps Air Quality APIを使用して、さまざまな興味深いアプリケーションで使用できる結果を提供する方法を探求しました。将来的には、air_quality_mapperツールについて別の記事を追加する予定ですが、ここで説明したスクリプトが独自の価値を持つことを願っています。常にさらなる開発のための提案は大歓迎です!

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more