HERE Trafficを使って、事故情報を地図上にマッピング（React+FastAPI）してみた。

とある件で地図関連の内容を扱う必要があったので、ちょっとしたアプリを作成してみました。内容はHERE TrafficAPIを利用してニューヨークの事故情報を地図上にマッピングさせるというものになっています。地図は情報も豊富なReact Leafletを使っています。

https://react-leaflet.js.org/


# 前提
OS：Linux amznlinux2023 6.1.94-99.176.amzn2023.x86_64
FastAPI：0.111.1
node：v18.20.4
npm:10.7.0
react-leaflet:4.2.1
axios:1.7.3


# 事前準備

まずはHereプラットフォームサイトに登録し、APIキーをゲットしましょう。

https://platform.here.com/portal/sign-up?step=verify-identity&aid=www.here.com-top-nav-start-building

また、地図上には2地点間の線を引くわけですが、そのままだと地図上の道なりに表示がされず直線表示されてしまうため、Mapbox Directions APIに登録してこちらのAPIキーも利用て道なりに線が表示されるようにします。以下のサイトから登録できます。

https://account.mapbox.com/auth/signup/

# 1. エンドポイント作成
まずはFastAPIでReactからリクエストを受け付けるエンドポイントを作成します。
```python:main.py
import json
import requests
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/incident")
def search():
    base_url = "https://data.traffic.hereapi.com/v7/incidents"
    api_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # Here trafficのAPIキー
    bbox_v7 = "-74.2591,40.4774,-73.7002,40.9176"  # ニューヨークの西経、南緯、東経、北緯を入れます。

    params = {
        "locationReferencing": "shape",
        "in": f"bbox:{bbox_v7}",
        "apiKey": api_key
    }

    response = requests.get(base_url, params=params)
    if response.status_code != 200:
        raise HTTPException(status_code=response.status_code, detail="Failed to fetch incidents")

    data = response.json()
    incidents = data.get('results', [])
    resultdata = []
    for incident in incidents:
        location = incident.get('location', {}).get('shape', {}).get('links', [])[0].get('points', [])
        description = incident.get('incidentDetails', {}).get('description', {}).get('value', '')
        if location:
            start_point = location[0]
            end_point = location[-1]
            resultdata.append({
                "Start Point": {"Lat": start_point['lat'], "Lng": start_point['lng']},
                "End Point": {"Lat": end_point['lat'], "Lng": end_point['lng']},
                "Description": description
            })

    return {"data": resultdata}
```
Here TrafficのAPI関する情報は以下ページより確認できます。

https://www.here.com/docs/bundle/traffic-api-v7-api-reference/page/index.html

作成できたら、以下コマンドで起動しておきます。
```bash
uvicorn main:app --host 0.0.0.0 --reload
```

# 2. Reactコード実装

```js:src/App.js
import React, { useEffect, useState } from 'react';
import { MapContainer, TileLayer, Marker, Popup, Polyline } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import L from 'leaflet';
import axios from 'axios';

delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
  iconRetinaUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon-2x.png',
  iconUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png',
  shadowUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-shadow.png',
});

const App = () => {
  const [routes, setRoutes] = useState([]);
  const mapboxToken = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; // Mapbox APIキー

  useEffect(() => {
    const fetchData = async () => {
      try {
        const apiResponse = await axios.get('http://[FastAPI_IP:8000]/incident');
        const data = apiResponse.data.data;

        const routePromises = data.map(async (route) => {
          const startPoint = [route['Start Point'].Lat, route['Start Point'].Lng];
          const endPoint = [route['End Point'].Lat, route['End Point'].Lng];
          const url = `https://api.mapbox.com/directions/v5/mapbox/driving/${startPoint[1]},${startPoint[0]};${endPoint[1]},${endPoint[0]}?geometries=geojson&access_token=${mapboxToken}`;
          const response = await axios.get(url);
          const routeData = response.data.routes[0].geometry.coordinates;
          return {
            start: startPoint,
            end: endPoint,
            description: route.Description,
            path: routeData.map(coord => [coord[1], coord[0]])
          };
        });

        const allRoutes = await Promise.all(routePromises);
        setRoutes(allRoutes);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    };

    fetchData();
  }, [mapboxToken]);

  return (
    <MapContainer center={[40.81092, -73.93109]} zoom={15} style={{ height: '100vh', width: '100%' }}>
      <TileLayer
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
      />
      {routes.map((route, index) => (
        <React.Fragment key={index}>
          <Marker position={route.start}>
            <Popup>
              Start Point: {route.description}
            </Popup>
          </Marker>
          <Marker position={route.end}>
            <Popup>
              End Point: {route.description}
            </Popup>
          </Marker>
          <Polyline positions={route.path} color="blue" />
        </React.Fragment>
      ))}
    </MapContainer>
  );
}

export default App;
```

# 3. ブラウザからアクセス確認
それでは、ブラウザからアクセス確認してみます。

http://[ip]:3000/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/c9e0253b-816c-64b7-2516-4b32f61e458c.png)

2地点間の線が直線ではなく、道なりに表示されていることがわかります。クローズアップしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/6ca77750-e734-7e7f-9733-e2d66bbc9581.png)

また、Pinマークをクリックすると詳細がわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/451c068b-bae0-b5bf-ff64-f85e665342cf.png)

# その他
残念ながらHere Trafficの事故情報ですが、日本は非対応でした。（ただ別サービスは＋＆を加えることで色々なことに利用できそうだなと思いました。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/f66fc96a-81be-b979-5cff-403278603be5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/d762f655-3874-722a-749f-dc9069fb4bbb.png)


https://www.here.com/docs/bundle/traffic-api-developer-guide-v7/page/topics/coverage-information/traffic.html