フリーダムの日記

GIS(地理情報システム)を中心に技術的なことを書いています。

MapLibre GL JS から OpenAI API を使ってみる!

はじめに

本エントリーは MapLibre Advent Calendar 2023 の 17日目の記事です。

qiita.com

OpenAI API、MapLibre GL JS 、および ArcGIS ロケーションサービスを使用して、目的の場所の美味しいランチ情報を表示するアプリを作成しましたので、紹介していきたいと思います。表示しているピンをドラッグしてランチ情報を表示します。

ArcGIS - OpenAI API

全体の構成

地図表示には、オープンソースの地図ライブラリ MapLibre GL JS を使用して、目的場所の住所情報の取得や背景地図には、ArcGIS のロケーションサービスを使用しました。目的場所の住所情報の取得には、緯度経度情報から住所を表示するリバースジオコーディングのサービスを使用しています。 そして、Open AI API を使用して、取得した住所からランチ情報を取得しています。

ArcGIS - OpenAI API

MapLibre GL JS の利用

MapLibre GL JS は、WebGL を使用してベクトルタイルとスタイルからインタラクティブな地図をレンダリングするオープンソースJavaScript ライブラリです。このライブラリを使用して ArcGIS のロケーション サービスにアクセスし、マップを表示したり、ビジュアライゼーション、ジオコーディング、ルーティング、人口統計解析、空間解析などの機能を実行したりできます。

developers.arcgis.com

maplibre.org

はじめに背景地図の表示部分に説明します。背景地図のスタイルは、道路地図やナビゲーションなど様々なスタイルの地図を利用することができます。今回は streets の道路地図を指定しました。language パラメータとして ja を指定することで、日本語表記のラベルを表示します。
背景地図のスタイルや言語などの詳細は Basemap styles service (v2) を参照してください。また、実装方法については、チュートリアルDisplay a map をご確認ください。

// APIKey を指定
const api_Key = import.meta.env.VITE_ARCGIS_KEY;

const map = new Map({
  container: "map",
  style: `https://basemapstyles-api.arcgis.com/arcgis/rest/services/styles/v2/styles/arcgis/streets?token=${api_Key}&language=ja`,
  center: [139.767125, 35.681236],
  zoom: 10,
  attributionControl: false
});

次にジオコーディングサービスの利用ですが、ArcGIS REST JS のメソッドを利用しています。実装方法については、チュートリアルリバースジオコーディングをご確認ください。

// ArcGIS REST JS をインポート
import { reverseGeocode, geocode } from "@esri/arcgis-rest-geocoding";

// APIKey を指定
const api_Key = import.meta.env.VITE_ARCGIS_KEY;
const authentication = ApiKeyManager.fromKey(api_Key);

// リバースジオコーディング
reverseGeocode([139.767125, 35.681236], {
  authentication: authentication,
}).then((res) => {
  getInfo(res);
});

Open AI API の利用

OpenAI APIとは、Open AI が提供する人工知能(AI)プラットフォームへのアクセスを提供する APIApplication Programming Interface)です。この API を使用することで、開発者は自然言語処理タスクや他の AI 関連のタスクを実行するために、OpenAI のモデルを利用することができます。

OpenAI API の利用には料金がかかります。料金は API の使用量に基づいており、API のエンドポイントの呼び出し回数や生成されたテキストの量に応じて請求されます。詳細な料金体系については、OpenAI の公式ウェブサイトや料金ページを参照ください。

openai.com

OpenAI API は、言語生成(text generation)、文章の要約(text summarization)、質問応答(question answering)、文章翻訳(text translation)など、さまざまなタスクに対応しています。これにより、開発者はさまざまな自然言語処理のニーズに応じて API を利用することができます。

API で利用できるモデルや詳細な設定方法は API リファレンスを参照ください。

OpenAI API の利用には、APIKey の作成などが必要になります。また、Pay as you go となっており、クレジットカードの登録が必要となります。今回試しに5 ドル分の購入をしました。これをしないと API を利用することができませんでした。

https://platform.openai.com/docs/quickstart?context=node

function addressFact(input) {
  const openai = new OpenAIApi(
    new Configuration({
      apiKey: import.meta.env.VITE_OPEN_AI_KEY,
    })
  );

  openai
    .createChatCompletion({
      model: "gpt-3.5-turbo",
      messages: [
        {
          role: "user",
          content: "この付近で美味しいランチはありますか?:" + input,
        },
      ],
    })
    .then((res) => {
      console.log(res.data.choices[0].message.content);
      fact.innerHTML = res.data.choices[0].message.content;
    });
}

全体のソースは以下になります。

import "./style.css";
import { ApiKeyManager } from "@esri/arcgis-rest-request";
import { reverseGeocode, geocode } from "@esri/arcgis-rest-geocoding";
import { Map, Marker, AttributionControl } from "maplibre-gl";
import { Configuration, OpenAIApi } from "openai";

const api_Key = import.meta.env.VITE_ARCGIS_KEY;
const authentication = ApiKeyManager.fromKey(api_Key);
let fact = document.getElementById("fact");

const map = new Map({
  container: "map",
  style: `https://basemapstyles-api.arcgis.com/arcgis/rest/services/styles/v2/styles/arcgis/streets?token=${api_Key}&language=ja`,
  center: [139.767125, 35.681236],
  zoom: 10,
  attributionControl: false
});

map.addControl(new AttributionControl({
  compact: false
}));

let marker = new Marker({
  draggable: true,
  color: "#fd7708",
})
.setLngLat([139.767125, 35.681236])
.addTo(map);

reverseGeocode([139.767125, 35.681236], {
  authentication: authentication,
}).then((res) => {
  getInfo(res);
});

let lngLat;

function onDragEnd() {
  lngLat = marker.getLngLat();
  reverseGeocode([lngLat.lng, lngLat.lat], {
    authentication: authentication,
  }).then((res) => {
    getInfo(res);
  });
}

function getInfo(res) {
  coordinates.innerHTML = res.address.LongLabel;
  addressFact(res.address.LongLabel);
}

function addressFact(input) {
  const openai = new OpenAIApi(
    new Configuration({
      apiKey: import.meta.env.VITE_OPEN_AI_KEY,
    })
  );

  openai
    .createChatCompletion({
      model: "gpt-3.5-turbo",
      messages: [
        {
          role: "user",
          content: "この付近で美味しいランチはありますか?:" + input,
        },
      ],
    })
    .then((res) => {
      console.log(res.data.choices[0].message.content);
      fact.innerHTML = res.data.choices[0].message.content;
    });
}

marker.on("dragend", onDragEnd);

let theBody = document.getElementById("theBody");
let searchInput = document.createElement("input");
searchInput.classList.add("searchInput");
searchInput.setAttribute("type", "text");
searchInput.setAttribute("placeholder", "Enter address or place");
theBody.appendChild(searchInput);
let searchBtn = document.createElement("button");
searchBtn.classList.add("search-btn");
searchBtn.innerHTML = "検索";
theBody.appendChild(searchBtn);

searchBtn.addEventListener("click", () => {
  let searchValue = searchInput.value;
  resetMap(searchValue);
});

function resetMap(searchValue) {
  geocode({
    singleLine: searchValue,
    outFields: "*",
    authentication,
  }).then((res) => {
    let theAdd = res.candidates[0].attributes.LongLabel;
    let searchLong = res.candidates[0].location.x;
    let searchLat = res.candidates[0].location.y;
    moveMap(searchLong, searchLat);
    getNewInfo(theAdd);
  });
}

function getNewInfo(theAdd) {
  coordinates.innerHTML = theAdd;
  addressFact(theAdd);
}

function moveMap(searchLong, searchLat) {
  map.flyTo({
    center: [searchLong, searchLat],
    zoom: 12,
  });
  marker.setLngLat([searchLong, searchLat]);
}

今回このアプリを作成するきっかけとなったのは、Esri の Developer Advocate でもある Yatteau の発信がきっかけでした。GitHub でも公開しており、こちらを参考にしました。

github.com

最後に

今回はじめて OpenAI API を使用しましたが、各 SDK も提供されていますので、簡単に利用することができました。まだまだ、知識不足なところもあり、深くまで追求できていませんので、引き続き、色々と試してみたいと思います。

以上、参考となれば幸いです。