見出し画像

[無料]AIといっしょにWebアプリ開発をしてみる

最近AIといっしょにWebアプリを作ってます。一緒にっていうか、「こんな感じで書いて」って頼むとだいたいその通りに書いてくれるので、それを動かして気に入らない部分があれば再度依頼して、上手く直ればそのまま使うし、ダメなら自分で手をいれるって感じ。

複雑なことになるとAIに指示を出すより自分でコードを書いてしまった方が速いこともあり、自分で書いた部分も多いですが、雛形の作成や、文法的にどう書けばいいのか分からない部分などはAIが圧倒的に優秀です。

僕はこれまであまりWebアプリって作ってこなかったんですが、AIに頼めばとてもキレイなコードを書いてくれて楽なので、ここ1ヶ月半くらいいろいろ作ってました。(専門はスマホのネイティブアプリ(AppStoreからインストールするアプリ)です)

公開してるのはこんな感じ。

下記の熊地図は、AIに注文して出てたコードを何回か修正してもらって、どうしてもAIが直せない部分だけ僕が直して、1時間程度でリリースしました。AI無しだとけっこう面倒くさいので、ダラダラやって1日掛かったかも。

コードの95%くらいはAIが書きました。

って事で、今回は誰でもWebアプリを作り始められる手順について。なお、スマホやタブレットでは無理なので、試す場合はPCでやってください。機種は問いません。Webブラウザとテキストエディタ(メモ帳とか、VS Codeとか)が動けばOKなのでパソコンのスペックも低くて大丈夫。開発環境は全部無料です。分からなかったらAIに聞いてください。

題材は、地図アプリにしましょう。地図に情報を載せるアプリは見た目が派手な割に簡単に作れてしまいます。いわゆるGISってやつですが、みんなが思っている以上に手軽です。

やっていきましょう。

まずはAIと契約

最近はいろいろありますが、基本はChatGPTかなぁと。claudeも賢いですがChatGPTを使ったことがないなら、まずはここから。

https://chatgpt.com/

上のURLを開いてサインインしてください。AIモデルは無料だとGPT3.5ですかね。もうすぐGPT4o-miniという軽量モデルがリリースされるらしいです。課金すれば4でも4oでも好きなのを使えます。月に$20です。激安ですね。

で、今回はWebブラウザで動く地図を作ります。質問はこうです。

ブラウザで動く地図アプリを作りたい。何から始めたらいい?

ChatGPT 4oの回答

ブラウザで動く地図アプリを作成するには、以下のステップを踏むと良いでしょう:

  1. 基本的な技術の習得

    • HTML: ウェブページの構造を作るためのマークアップ言語。

    • CSS: ページのデザインとレイアウトを整えるためのスタイルシート言語。

    • JavaScript: ウェブページに動的な機能を追加するためのプログラミング言語。

  2. 地図ライブラリの選定

    • Leaflet: 軽量でオープンソースのJavaScriptライブラリ。簡単に使える上に拡張性も高いです。

    • Google Maps API: Googleが提供する高機能な地図サービス。多くの機能が利用できる反面、利用にはAPIキーが必要で、一定の使用量を超えると料金が発生します。

  3. APIキーの取得(必要な場合)

    • Google Maps APIを利用する場合、Google Cloud Platformでプロジェクトを作成し、APIキーを取得する必要があります。

  4. 基本的な地図の表示
    コードは下記

  5. 追加機能の実装

    • マーカーの追加、ポリラインの描画、ジオコーディング、逆ジオコーディング、検索機能などを実装していきます。

  6. 応用的な機能の実装

    • データの読み込みと表示(GeoJSON、CSVなど)

    • カスタムマーカーやポップアップのデザイン

    • ユーザーの位置情報を取得し表示する機能

  7. デプロイと運用

    • アプリケーションをホスティングするために、Netlify、Vercel、GitHub Pagesなどのサービスを利用できます。

コード

<!DOCTYPE html>
<html>
<head>
  <title>Full Screen Map with GSI Pale Tiles</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
  <style>
    html, body {
      height: 100%;
      margin: 0;
    }
    #map  {
      width: 100%;
      height: 100%;
    }
  </style>
</head>
<body>
  <div id="map"></div>
  <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
  <script>
    var map = L.map('map').setView([35.681236, 139.767125], 13);
    L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png', {
      attribution: '<a href="https://maps.gsi.go.jp/development/ichiran.html">© 国土地理院</a>'
    }).addTo(map);
  </script>
</body>
</html>

これらのステップを踏むことで、ブラウザで動く地図アプリを作成できます。具体的なコード例やチュートリアルは、LeafletやGoogle Maps APIの公式ドキュメントや関連するオンラインリソースを参照すると良いでしょう。

回答ココまで

黒背景のとこに書かれていたコードを適当に「map_demo.html」みたいな名前のファイルを作って、それをメモ帳などテキストエディタで開いてコピペします。

map_demo.htmlをWebブラウザにドラッグアンドドロップすると実行されます。

で、出来てきたのがこれ。

画面の左上に地図が表示された。

いろいろイマイチなので修正してもらいましょう。

修正してもらう

地図を画面全体に表示させて。地図のタイルは国土地理院の淡色タイルを使って。

回答

地図を画面全体に表示し、国土地理院の淡色タイルを使用するには、以下のコードを参考にしてください。

<!DOCTYPE html>
<html>

<head>
    <title>Full Screen Map with GSI Pale Tiles</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
    <style>
        html,
        body {
            height: 100%;
            margin: 0;
        }

        #map  {
            width: 100%;
            height: 100%;
        }
    </style>
</head>

<body>
    <div id="map"></div>
    <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
    <script>
        var map = L.map('map').setView([35.681236, 139.767125], 13);
        L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png', {
            attribution: '<a href="https://maps.gsi.go.jp/development/ichiran.html">© 国土地理院</a>'
        }).addTo(map);
    </script>
</body>

</html>

実行するとこうなりました。

頼んだとおりに直ってきました。優秀。

次の注文

画面の中央座標を記憶して、次回同じ場所とズームレベルで開けるようにして。中央座標とズームレベルをURLに入れて、そのURLを開いたら同じ場所を開けるようにもして。

回答

画面の中央座標とズームレベルを記憶し、次回同じ場所とズームレベルで開けるようにするには、localStorageを使って情報を保存する方法と、URLにパラメータを追加してその情報を取得する方法の2つを組み合わせます。
以下のコードでは、画面の中央座標とズームレベルをURLに含め、それを読み取って地図を初期化するようにしています。また、地図の状態が変更されるたびにlocalStorageにも保存されます。

<!DOCTYPE html>
<html>

<head>
    <title>Full Screen Map with GSI Pale Tiles</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
    <style>
        html,
        body {
            height: 100%;
            margin: 0;
        }

        #map  {
            width: 100%;
            height: 100%;
        }
    </style>
</head>

<body>
    <div id="map"></div>
    <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
    <script>
        function getParameterByName(name, url = window.location.href) {
            name = name.replace(/[\[\]]/g, '\\$&');
            let regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
                results = regex.exec(url);
            if (!results) return null;
            if (!results[2]) return '';
            return decodeURIComponent(results[2].replace(/\+/g, ' '));
        }

        // URLから座標とズームレベルを取得
        const lat = parseFloat(getParameterByName('lat')) || 35.681236;
        const lng = parseFloat(getParameterByName('lng')) || 139.767125;
        const zoom = parseInt(getParameterByName('zoom')) || 13;

        var map = L.map('map').setView([lat, lng], zoom);
        L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png', {
            attribution: '<a href="https://maps.gsi.go.jp/development/ichiran.html">© 国土地理院</a>'
        }).addTo(map);

        // 中央座標とズームレベルをlocalStorageに保存
        function saveMapState() {
            const center = map.getCenter();
            const zoom = map.getZoom();
            localStorage.setItem('mapLat', center.lat);
            localStorage.setItem('mapLng', center.lng);
            localStorage.setItem('mapZoom', zoom);
        }

        map.on('moveend', saveMapState);
        map.on('zoomend', saveMapState);

        // ページを再読み込みしたときにlocalStorageから座標とズームレベルを取得
        window.addEventListener('load', function () {
            const savedLat = parseFloat(localStorage.getItem('mapLat'));
            const savedLng = parseFloat(localStorage.getItem('mapLng'));
            const savedZoom = parseInt(localStorage.getItem('mapZoom'));

            if (!isNaN(savedLat) && !isNaN(savedLng) && !isNaN(savedZoom)) {
                map.setView([savedLat, savedLng], savedZoom);
            }
        });

        // URLを更新する関数
        function updateURL() {
            const center = map.getCenter();
            const zoom = map.getZoom();
            const newUrl = `${window.location.pathname}?lat=${center.lat}&lng=${center.lng}&zoom=${zoom}`;
            history.replaceState(null, null, newUrl);
        }

        map.on('moveend', updateURL);
        map.on('zoomend', updateURL);
    </script>
</body>

</html>

コードの意味は分からなくても大丈夫です。今回はすべてAIに書いてもらいます。

動かすと、確かに位置が記憶されてURLにも座標がパラメーターとして入ってきました。

次の注文

現在地を測位できるようにして。関東の都道府県の県庁所在地にピンを立てて、ピンをクリックしたらその都道府県の名前と人口が表示されるようにして。

回答

以下のコードは、ユーザーの現在地を測位し、関東の都道府県の県庁所在地にピンを立て、それらのピンをクリックした際に都道府県の名前と人口を表示するようにしています。

<!DOCTYPE html>
<html>

<head>
    <title>Map with GSI Pale Tiles and Pins</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
    <style>
        html,
        body {
            height: 100%;
            margin: 0;
        }

        #map  {
            width: 100%;
            height: 100%;
        }
    </style>
</head>

<body>
    <div id="map"></div>
    <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
    <script>
        function getParameterByName(name, url = window.location.href) {
            name = name.replace(/[\[\]]/g, '\\$&');
            let regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
                results = regex.exec(url);
            if (!results) return null;
            if (!results[2]) return '';
            return decodeURIComponent(results[2].replace(/\+/g, ' '));
        }

        // URLから座標とズームレベルを取得
        const lat = parseFloat(getParameterByName('lat')) || 35.681236;
        const lng = parseFloat(getParameterByName('lng')) || 139.767125;
        const zoom = parseInt(getParameterByName('zoom')) || 13;

        var map = L.map('map').setView([lat, lng], zoom);
        L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png', {
            attribution: '<a href="https://maps.gsi.go.jp/development/ichiran.html">© 国土地理院</a>'
        }).addTo(map);

        // 中央座標とズームレベルをlocalStorageに保存
        function saveMapState() {
            const center = map.getCenter();
            const zoom = map.getZoom();
            localStorage.setItem('mapLat', center.lat);
            localStorage.setItem('mapLng', center.lng);
            localStorage.setItem('mapZoom', zoom);
        }

        map.on('moveend', saveMapState);
        map.on('zoomend', saveMapState);

        // ページを再読み込みしたときにlocalStorageから座標とズームレベルを取得
        window.addEventListener('load', function () {
            const savedLat = parseFloat(localStorage.getItem('mapLat'));
            const savedLng = parseFloat(localStorage.getItem('mapLng'));
            const savedZoom = parseInt(localStorage.getItem('mapZoom'));

            if (!isNaN(savedLat) && !isNaN(savedLng) && !isNaN(savedZoom)) {
                map.setView([savedLat, savedLng], savedZoom);
            }
        });

        // URLを更新する関数
        function updateURL() {
            const center = map.getCenter();
            const zoom = map.getZoom();
            const newUrl = `${window.location.pathname}?lat=${center.lat}&lng=${center.lng}&zoom=${zoom}`;
            history.replaceState(null, null, newUrl);
        }

        map.on('moveend', updateURL);
        map.on('zoomend', updateURL);

        // 現在地の測位
        map.locate({ setView: true, maxZoom: 16 });
        function onLocationFound(e) {
            L.marker(e.latlng).addTo(map).bindPopup("You are here").openPopup();
        }
        map.on('locationfound', onLocationFound);
        map.on('locationerror', function (e) {
            alert(e.message);
        });

        // 関東の都道府県の県庁所在地と人口データ
        const prefectures = [
            { name: "東京都", lat: 35.6895, lng: 139.6917, population: 13929286 },
            { name: "神奈川県", lat: 35.4478, lng: 139.6425, population: 9200166 },
            { name: "千葉県", lat: 35.6073, lng: 140.1063, population: 6279026 },
            { name: "埼玉県", lat: 35.8569, lng: 139.6489, population: 7330228 },
            { name: "茨城県", lat: 36.3414, lng: 140.4467, population: 2878005 },
            { name: "栃木県", lat: 36.5657, lng: 139.8836, population: 1931655 },
            { name: "群馬県", lat: 36.3912, lng: 139.0608, population: 1927161 }
        ];

        // ピンを立てる
        prefectures.forEach(function (pref) {
            L.marker([pref.lat, pref.lng]).addTo(map)
                .bindPopup(`<b>${pref.name}</b><br>人口: ${pref.population.toLocaleString()}人`);
        });
    </script>
</body>

</html>

説明

  1. 現在地の測位:

    • map.locateを使用してユーザーの現在地を測位し、その位置にマーカーを追加。

  2. 関東の都道府県の県庁所在地のピン:

    • 関東地方の都道府県(東京都、神奈川県、千葉県、埼玉県、茨城県、栃木県、群馬県)の県庁所在地の緯度、経度、および人口データを保持するprefectures配列を定義。

    • 各県庁所在地にマーカーを立て、マーカーをクリックした際に都道府県名と人口を表示するポップアップを設定。

  3. 地図の状態保存と復元:

    • 地図の中央座標とズームレベルをlocalStorageに保存し、ページロード時にそれらを復元。

    • URLパラメータを更新し、地図の現在の状態を反映。

このコードを保存してブラウザで開くと、現在地を測位し、関東の県庁所在地にピンを立てた地図が表示されます。ピンをクリックすると、都道府県の名前と人口が表示されます。

ピンがたった。自分の位置にも青いピンが立っている。

次の注文

自分の位置のピンは顔文字の😀にして

で、出来たものの、😀のサイズがだいぶ小さかったので、

自分の位置のアイコンが小さすぎるので大きくして

と追加で注文しました。

回答

自分の位置のピンを顔文字の😀にするには、カスタムアイコンを使用する必要があります。以下のコードでは、ユーザーの現在地に顔文字アイコンを使用してマーカーを追加しています。自分の位置を示すアイコンを大きくするために、アイコンサイズを調整しましょう。以下のコードでは、顔文字のサイズを大きくして表示しています。

<!DOCTYPE html>
<html>

<head>
    <title>Map with GSI Pale Tiles and Pins</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
    <style>
        html,
        body {
            height: 100%;
            margin: 0;
        }

        #map  {
            width: 100%;
            height: 100%;
        }

        .emoji-icon {
            font-size: 40px;
            /* アイコンのサイズを大きく */
        }
    </style>
</head>

<body>
    <div id="map"></div>
    <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
    <script>
        function getParameterByName(name, url = window.location.href) {
            name = name.replace(/[\[\]]/g, '\\$&');
            let regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
                results = regex.exec(url);
            if (!results) return null;
            if (!results[2]) return '';
            return decodeURIComponent(results[2].replace(/\+/g, ' '));
        }

        // URLから座標とズームレベルを取得
        const lat = parseFloat(getParameterByName('lat')) || 35.681236;
        const lng = parseFloat(getParameterByName('lng')) || 139.767125;
        const zoom = parseInt(getParameterByName('zoom')) || 13;

        var map = L.map('map').setView([lat, lng], zoom);
        L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png', {
            attribution: '<a href="https://maps.gsi.go.jp/development/ichiran.html">© 国土地理院</a>'
        }).addTo(map);

        // 中央座標とズームレベルをlocalStorageに保存
        function saveMapState() {
            const center = map.getCenter();
            const zoom = map.getZoom();
            localStorage.setItem('mapLat', center.lat);
            localStorage.setItem('mapLng', center.lng);
            localStorage.setItem('mapZoom', zoom);
        }

        map.on('moveend', saveMapState);
        map.on('zoomend', saveMapState);

        // ページを再読み込みしたときにlocalStorageから座標とズームレベルを取得
        window.addEventListener('load', function () {
            const savedLat = parseFloat(localStorage.getItem('mapLat'));
            const savedLng = parseFloat(localStorage.getItem('mapLng'));
            const savedZoom = parseInt(localStorage.getItem('mapZoom'));

            if (!isNaN(savedLat) && !isNaN(savedLng) && !isNaN(savedZoom)) {
                map.setView([savedLat, savedLng], savedZoom);
            }
        });

        // URLを更新する関数
        function updateURL() {
            const center = map.getCenter();
            const zoom = map.getZoom();
            const newUrl = `${window.location.pathname}?lat=${center.lat}&lng=${center.lng}&zoom=${zoom}`;
            history.replaceState(null, null, newUrl);
        }

        map.on('moveend', updateURL);
        map.on('zoomend', updateURL);

        // 現在地の測位とカスタムアイコン
        var emojiIcon = L.divIcon({
            className: 'emoji-icon',
            html: '😀',
            iconSize: [40, 40]
        });

        map.locate({ setView: true, maxZoom: 16 });
        function onLocationFound(e) {
            L.marker(e.latlng, { icon: emojiIcon }).addTo(map).bindPopup("You are here").openPopup();
        }
        map.on('locationfound', onLocationFound);
        map.on('locationerror', function (e) {
            alert(e.message);
        });

        // 関東の都道府県の県庁所在地と人口データ
        const prefectures = [
            { name: "東京都", lat: 35.6895, lng: 139.6917, population: 13929286 },
            { name: "神奈川県", lat: 35.4478, lng: 139.6425, population: 9200166 },
            { name: "千葉県", lat: 35.6073, lng: 140.1063, population: 6279026 },
            { name: "埼玉県", lat: 35.8569, lng: 139.6489, population: 7330228 },
            { name: "茨城県", lat: 36.3414, lng: 140.4467, population: 2878005 },
            { name: "栃木県", lat: 36.5657, lng: 139.8836, population: 1931655 },
            { name: "群馬県", lat: 36.3912, lng: 139.0608, population: 1927161 }
        ];

        // ピンを立てる
        prefectures.forEach(function (pref) {
            L.marker([pref.lat, pref.lng]).addTo(map)
                .bindPopup(`<b>${pref.name}</b><br>人口: ${pref.population.toLocaleString()}人`);
        });
    </script>
</body>

</html>

で、最終的に出来たのがコチラ。

どこのご家庭も契約している"さくらインターネット"のサーバーにFTPでアップロードしました。(意味が分からなかったらChatGPTにコピペして聞いて下さい)

結局僕は1行もコードを書いていません。全部AIが書いて、それが正しく動いています。僕はただ指示を出していただけ。

レンタルサーバーを使ってないなら

レンタルサーバーなど無しでWebサービスを公開する方法もAIに教えてもらいました。いろいろありましたが、これが良さそうです。

Netlify Drop

NetlifyはGitHubを使用せずに、ファイルをドラッグ&ドロップでアップロードできる「Netlify Drop」という機能を提供しています。

手順:

  1. Netlifyのサイトにアクセス:

    • Netlify Dropにアクセスします。

  2. ファイルをアップロード:

    • 「Drop your site folder here」をクリックして、公開したいHTMLファイルやフォルダをドラッグ&ドロップします。

  3. 公開URLを取得:

    • アップロードが完了すると、Netlifyが自動的にサイトをデプロイし、公開URLを表示します。例:https://random-name.netlify.app/

こんな感じで公開できました。

さっき作ったmap_demo.htmlの名前をindex.htmlに変更して、適当なフォルダに入れてフォルダごとアップロードすれば公開されます。ドメイン買うか既存のドメインを使うことも出来るようです。

詳しくはこのへんとか読んでください。

分からないことはAIに聞いたり適当にググれば答えが出てきます。

AIといっしょにプログラミングしてみては?

1年前はこんなことになるなんて思ってなかったですが、もはやAIのサポート無しでプログラムを書くなんて面倒くさすぎて無理というレベルで使っています。

分からない部分は切り出して質問すれば教えてくれるし、エラーが出たらメッセージを貼り付けるだけで意味から対応まで説明してくれます。

これで、お金を払うにしても月に$20。日本円で今のレートだと3200円くらい?どんな面倒くさいことも「よろこんで!」とばかりにこなしてくれます。

ただ、複雑なものを作ろうと思うとAIだけでやらせるのは効率が悪くなります。人間が修正した方が早いということもありますが、今回作った程度の簡単なものでも、読み込ませるデータによっては需要があったりするんですよね。この程度のものなら全文AIに書かせて、都度コピペしても大して時間は掛かりません。

とにかく、わからないことは全部AIが教えてくれます。嫌がらず、面倒くさがらず、不機嫌にもならず、ひたすら教えてくれます。最高すぎる…。

プログラミングだけじゃないと思いますが、今後はAIといっしょに仕事をするのが当たり前になるでしょう。

自分が不得意なことでもAIと一緒ならなんとかなったりします。やっていきましょう。

この記事が参加している募集

わぁい、サポート、あかりサポートだい好きー。