編集

YAMAPやヤマレコみたいなGPXファイルを使った地図、標高グラフを自分で作りたい

2024/07/01

GPXファイルを使って地図に軌跡表示、標高グラフ作成

これをYAMAP、ヤマレコに頼らず出来ないものか。。。

現時点で出来た物

最初の一歩

テストページ | Trekking from Kochi

テストページ | Trekking from Kochi

高知で登山を趣味にしているオッサンです。 登山と言ってもハイキングって感じのゆる〜い山遊びです。

GPXファイルを持ってる方は ↑ のページで出来ます


GPXマップ
ファイルを選択をクリックでGPXファイルを指定してください


2021剣山軌跡
地図上に軌跡が表示され標高グラフが出てきます


現時点ではここまで、、、

ここから先が大変そう、、、

標高グラフをもっと滑らかな線にするとか、グラフ自体の高さをもっと低くするとか、標高グラフにマウスを持って行くと地図上にマーカーが出るとかやりたいんですが素人にはなかなか難しい問題で、、、

ここから更に写真を地図上に出すとか、山頂等のポイントをマーカー表示するとか、時間経過とポイントへの到着時間を表示するとか、、、

YAMAPみたいなのが理想だよね

引き続きやってはみますが完成するかどうかは、、、

当方、プログラムとか全くの素人なので出来る予感がしない(笑)

それでも地図と標高グラフだけでも完成できれば、、、

プログラマーさんで登山が趣味でブログやってる方で同じ事考えてる人いませんか?


アイコン表示

標高グラフにマウスポインターをもっていくと地図上にアイコンが出るようにしました

スマホ、タグレット等は標高グラフの線をタッチすると地図にアイコンでます
※ポンイター、タッチした所から一番近い標高グラフの線に反応して地図に表示されます

それとスタート、ゴールのアイコン設置

標高グラフのサイズ変更

アイコン追加GPXビュー画面

それと、、、

出来た地図と標高グラフをHTMLコードを貼り付けるだけで表示させたいんだけど
ChatGPTがちゃんと教えてくれないから、、
現在出てくるHTMLコードは使ってもグチャグチャの地図が表示されるだけです


現時点でスクリーンショットで良ければブログ等に貼って参考程度にはなると思います

他にも時間経過が判るようにしたいとか、、、アレコレ考えはあってもChatGPT頼りなのでChatGPTが正解を教えてくれないと無理

質問の仕方で答え変わるし、同じ事聞いても答えが違ったりするから、、、

ちゃんとしたHTMLコードを出してくれないとブログで使えないんだよね

スクリーンショットで使うのは面白くない。。。


素人の限界

地図軌跡のみ
生成されたHTMLを貼り付けると、、、
軌跡は表示されるけど地図が、、、
標高グラフは、、、

ここまで作れて、あともう少しで良い感じになるんじゃないか!?と思ったんですがここから先が、、、

ChatGPTに何度も相談したんですが、、、

無理、、、

自力で解決するのは素人には無理、、、

なんか凄く惜しい所まできている気がするんですが、、、

ChatGPTに聞いても答えがでてこない

ここが解決したらグラフを改良して、時間経過も解るようにすれば個人のブログで使うにはどこにも頼らず便利な物ができるんですけどね

もう素人にはお手上げです(T_T)


コード

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" meta="">
    <meta content="width=device-width, initial-scale=1.0" name="viewport">
    <title>GPX Viewer</title>
    <!-- LeafletのCSS -->
    <link crossorigin="" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" rel="stylesheet"></link>
    <!-- LeafletのJavaScript -->
    <script crossorigin="" integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
    <!-- D3.jsのJavaScript -->
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <style>
        #map {
            height: 400px; /* 地図の高さ */
            width: 80%; /* 地図の幅 */
            margin: 0 auto; /* 中央に配置 */
        }

        #elevationChart {
            width: 80%; /* 標高グラフの幅 */
            height: 0px; /* 標高グラフの高さ */
            margin: 0 auto; /* 中央に配置 */
        }

        #fileInputContainer {
            text-align: center; /* 中央に配置 */
            margin: 10px 0;
        }

        #gpxFileInput {
            display: inline-block; /* インラインブロック要素に設定 */
            padding: 10px 15px; /* ボタンの内側のパディング */
            font-size: 16px; /* フォントサイズ */
            cursor: pointer; /* カーソルをポインターに変更 */
            background-color: #4CAF50; /* ボタンの背景色 */
            color: white; /* テキストの色 */
            border: none; /* ボーダーをなしに設定 */
            border-radius: 5px; /* ボーダーの角を丸める */
        }

        #gpxFileInput:hover {
            background-color: #45a049; /* ホバー時の背景色 */
        }

        #htmlOutputContainer {
            width: 80%; /* 出力の幅 */
            margin: 20px auto; /* 中央に配置 */
            text-align: center; /* 中央に配置 */
        }

        #htmlOutput {
            width: 100%;
            height: 200px;
        }

        #copyButton {
            display: inline-block; /* インラインブロック要素に設定 */
            padding: 10px 15px; /* ボタンの内側のパディング */
            font-size: 16px; /* フォントサイズ */
            cursor: pointer; /* カーソルをポインターに変更 */
            background-color: #4CAF50; /* ボタンの背景色 */
            color: white; /* テキストの色 */
            border: none; /* ボーダーをなしに設定 */
            border-radius: 5px; /* ボーダーの角を丸める */
            margin-top: 10px;
        }

        #copyButton:hover {
            background-color: #45a049; /* ホバー時の背景色 */
        }
    </style>
</head>
<body>
<h1>GPX Viewer</h1>
<!-- 地図を表示するためのコンテナ -->
<div id="map"></div>
<!-- 標高グラフを表示するためのコンテナ -->
<div id="elevation-chart">
   <canvas height="100px" id="elevationChart" width="400px"></canvas>
</div>
<!-- ファイルを選択するボタン -->
<div id="fileInputContainer">
    <input id="gpxFileInput" type="file" />
</div>
<!-- HTMLコード出力用のコンテナ -->
<div id="htmlOutputContainer">
    <textarea id="htmlOutput" readonly=""></textarea>
    <button id="copyButton">Copy</button>
</div>

<!-- LeafletのJavaScript -->
<script>
    var map = L.map('map');
    L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', {
        attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>"
    }).addTo(map);
    map.setView([38.8, 138], 5);
</script>
<!-- Chart.jsのJavaScript -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.6.0/dist/chart.min.js"></script>
<!-- jQuery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<!-- togeojson -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/togeojson/0.16.0/togeojson.min.js"></script>
<script>
    document.getElementById('gpxFileInput').addEventListener('change', handleFileSelect, false);

    var polyline, elevationChart, chartData = [], elevationData = [], marker;

    var personIcon = L.icon({
        iconUrl: 'url', // アイコン画像のURLを指定
        iconSize: [32, 32], // アイコンのサイズを指定
        iconAnchor: [16, 32], // アイコンのアンカーを指定
        popupAnchor: [0, -32] // ポップアップのアンカーを指定
    });
      var startIcon = L.icon({
        iconUrl: 'url', // スタートアイコン画像のURLを指定
        iconSize: [20, 20], // アイコンのサイズを指定
        iconAnchor: [16, 32], // アイコンのアンカーを指定
        popupAnchor: [0, -32] // ポップアップのアンカーを指定
    });
      var endIcon = L.icon({
        iconUrl: 'url', // ゴールアイコン画像のURLを指定
        iconSize: [20, 20], // アイコンのサイズを指定
        iconAnchor: [16, 32], // アイコンのアンカーを指定
        popupAnchor: [0, -32] // ポップアップのアンカーを指定
    });


    function handleFileSelect(evt) {
        var file = evt.target.files[0];
        var reader = new FileReader();
        reader.onload = function (event) {
            var gpx = new DOMParser().parseFromString(event.target.result, 'text/xml');
            var geojson = toGeoJSON.gpx(gpx);
            var coordinates = geojson.features[0].geometry.coordinates;

            var latlngs = coordinates.map(function (coord) {
                return [coord[1], coord[0]];
            });

            if (polyline) {
                map.removeLayer(polyline);
            }
            polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
            map.fitBounds(polyline.getBounds());
                      // Add markers for start and end points

            var startPoint = L.marker([coordinates[0][1], coordinates[0][0]], {icon: startIcon}).addTo(map);

            var endPoint = L.marker([coordinates[coordinates.length - 1][1], coordinates[coordinates.length - 1][0]], {icon: endIcon}).addTo(map);



            elevationData = [];
            var totalDistance = 0;
            chartData = [];

            for (var i = 1; i < coordinates.length; i++) {
                var prev = coordinates[i - 1];
                var curr = coordinates[i];

                var distance = getDistanceFromLatLonInKm(prev[1], prev[0], curr[1], curr[0]) * 1000;
                totalDistance += distance;

                elevationData.push({
                    lat: curr[1],
                    lng: curr[0],
                    elevation: curr[2],
                    distance: totalDistance
                });

                chartData.push({x: totalDistance, y: curr[2]});
            }

            updateElevationChart();
            generateHtmlOutput();
        };
        reader.readAsText(file);
    }

    function getDistanceFromLatLonInKm(lat1, lon1, lat2, lon2) {
        var R = 6371; // Radius of the Earth in km
        var dLat = deg2rad(lat2 - lat1);
        var dLon = deg2rad(lon2 - lon1);
        var a =
            Math.sin(dLat / 2) * Math.sin(dLat / 2) +
            Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
            Math.sin(dLon / 2) * Math.sin(dLon / 2);
        var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        var d = R * c; // Distance in km
        return d;
    }

    function deg2rad(deg) {
        return deg * (Math.PI / 180);
    }

    function updateElevationChart() {
        var ctx = document.getElementById('elevationChart').getContext('2d');
        if (elevationChart) {
            elevationChart.destroy();
        }
        elevationChart = new Chart(ctx, {
            type: 'line',
            data: {
                datasets: [{
                    label: 'Elevation Profile',
                    data: chartData,
                    fill: false,
                    borderColor: 'red',
                    tension: 0.1
                }]
            },
options: {
    scales: {
        x: {
            type: 'linear',
            position: 'bottom',
            title: {
                display: true,
                text: 'Distance (km)' // X軸のタイトルを変更
            },
            ticks: {
                callback: function(value, index, values) {
                    return value / 1000 + ' km'; // X軸のラベルをメートルからキロメートルに変換
                }
            }
        },
        y: {
            title: {
                display: true,
                text: 'Elevation (m)'
            }
        }
    },
    plugins: {
        tooltip: {
            enabled: false
        }
    }
}

        });

        document.getElementById('elevationChart').addEventListener('mousemove', function (event) {
            var points = elevationChart.getElementsAtEventForMode(event, 'nearest', {intersect: false}, false);
            if (points.length) {
                var pointIndex = points[0].index;
                var lat = elevationData[pointIndex].lat;
                var lng = elevationData[pointIndex].lng;

                if (marker) {
                    marker.setLatLng([lat, lng]);
                } else {
                    marker = L.marker([lat, lng], {icon: personIcon}).addTo(map);
                }
            }
        });
    }

    function generateHtmlOutput() {
        var mapContainerHtml = document.getElementById('map').outerHTML;
        var chartContainerHtml = document.getElementById('elevation-chart').outerHTML;
        var htmlOutput = mapContainerHtml + '\n' + chartContainerHtml;
        document.getElementById('htmlOutput').value = htmlOutput;
    }

    document.getElementById('copyButton').addEventListener('click', function () {
        var htmlOutput = document.getElementById('htmlOutput');
        htmlOutput.select();
        document.execCommand('copy');
    });
</script>
</body>
</html>

これがChatGPTに助けて貰って作ったコードです

細かい記述のミスがある気がします、、、

しかし素人には直し方がわかりません

普通に動いているし(^^;

もう自分は無理なので誰かイジリまくって完成させてください!!


ChatGPTで作成

素人がここまで出来ただけでも凄いでしょ?

と、自分でも思いますがChatGPTに教えて貰いました(笑)

そうじゃないと絶対に無理です!!

ChatGPTだけでは無理だったのでGoogle先生にも聞いて地理院の地図にしたり

本当のとりあえずですがなんとかGPXファイルから地図、標高グラフを作る足掛かりはできました

まだまだブログに貼り付けるような代物ではありませんが、これをなんとか形にできたらいいなと


現在、軌跡や標高グラフはヤマレコが『ブログに貼付け』でHTMLコードを教えてくれるのでブログに貼りつけてます

このサービスもいつまで続くか判りません

実際YAMAPは出来なくなりました、、、以前は出来たのに、、、

他所に頼ってたらなんともならなくなる可能性があるんですよね

アウトドアブームも落ち着き、YAMAPもヤマレコもどんどん有料化、サービスの制限が出てくるでしょう

実際そうなってるし


現在ヤマレコがHTMLコードを教えてくれているので、現時点で自分が作っている物を使用する人はいないと思うし、利用価値がない

それでもこんな事してますよっていう、誰も興味がなさそうな報告日記です


出来る出来ないは自分でもわかりませんが、、、

進歩があったら追記、リライトでアップします!!