はじめに
「やりたいこと」を実現するため、いろんな人の情報を参考にし、作成しました。
不備・間違いの指摘やもっと良い方法等があれば、ご教示頂けますと幸いです。
やりたいこと
緯度・経度のついたデータをフォームを用いて収集し、地図表示したい
上記を実現するための具体的な仕組み
Googleフォームに回答した結果(緯度・経度情報あり)をGoogleスプレッドシートに反映し、geojsonに変換。その後、地図ライブラリ(今回はMapLibre)を用い表示した。
収集したデータ例
緯度・経度と日時、数値データが含まれたものを想定。
Googleフォームでの回答をGoogleスプレッドシートに連携させる。
data.csv
タイムスタンプ,name,place,緯度,経度,date,time,data,memo
2024/05/15 03:03:22,田中,大阪城,34.6872571,135.5258546,2024/05/09,20:50:00,35.43,
2024/04/19 20:13:36,佐藤,姫路城,34.8394341,134.6913859,2024/09/11,21:15:00,19.17,
Googleスプレッドシートのコード
スプレッドシートのApps scriptに下記を記入
WEBアプリとしてデプロイすると、そのURLがAPIの働きをする。その際、公開範囲を「全員」にしておくとWEBからもアクセスが可能(一方セキュリティ観点からはリスクがある)。
spreadsheet_to_geojson.gs
function getData(sheetName) {
//sheet名注意
var sheet = SpreadsheetApp.getActive().getSheetByName("sheet1");
//.slice(1)をつけることで、タイトル行を無視
var rows = sheet.getDataRange().getValues().slice(1);
//下記でjsonに含めたい項目を設定
return rows.map(function(row) {
var obj = {};
obj.type = "Feature"
obj.properties = {};
obj.properties.timestamp = row[0];
obj.properties.name = row[1];
obj.properties.place = row[2];
obj.properties.経度 = row[3];
obj.properties.緯度 = row[4];
//DateTime型は扱いにくい場合があるので、指定の形に整形
obj.properties.date = Utilities.formatDate(row[5], 'JST', "yyyy/MM/dd");
//””の時にエラーが起きるので、場合わけ処理
if (row[6] == "") {
//何も実行しない
}else{
obj.properties.time = Utilities.formatDate(row[6], 'JST', "HH:mm");
}
obj.properties.data = row[7];
obj.properties.times = row[8];
obj.properties.memo = row[9];
obj.geometry = {};
obj.geometry.type = "Point";
obj.geometry.coordinates = [];
obj.geometry.coordinates.push(row[4],row[3]);
return obj;
});
}
function doGet(e) {
var data = getData('Sheet1');
var geojson = {};
geojson.type = "FeatureCollection";
geojson.features = data;
var callback = e.parameter.callback;
if (callback) {
return ContentService
.createTextOutput(callback+'('+JSON.stringify(geojson, null, 2)+')')
.setMimeType(ContentService.MimeType.JAVASCRIPT);
} else {
return ContentService
.createTextOutput(JSON.stringify(geojson, null, 2))
.setMimeType(ContentService.MimeType.JSON);
}
}
地図に表示
MapLibreを使って、geojsonを地図表示した。
その際、各地点の数値に従い色分けと数値ラベルつけた。
map.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@3.6.2/dist/maplibre-gl.css" rel="stylesheet" />
<title>マップ</title>
</head>
<body style="margin: 0;">
<div id="map" style="height: 100vh;"></div>
<script type="module">
const map = new maplibregl.Map({
container: 'map', // div要素のid
zoom: 8, // 初期表示のズーム
center: [134.860814, 35.127726], // 初期表示の中心
minZoom: 5, // 最小ズーム
maxZoom: 18, // 最大ズーム
pitch: 0,
maxPitch: 85,
bearing: 0,
hash: true,
localIdeographFontFamily: ['sans-serif'], // 日本語を表示するための設定
style: {
version: 8,
glyphs: "https://glyphs.geolonia.com/{fontstack}/{range}.pbf",// 日本語を表示するための設定
sources: {
// 背景地図ソース(Open Street Map)
osm: {
type: 'raster',
tiles: [
'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png',
'https://b.tile.openstreetmap.org/{z}/{x}/{y}.png',
],
tileSize: 256,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
},
},
layers: [
// 背景地図レイヤー
{
id: 'osm-layer',
source: 'osm',
type: 'raster',
minZoom: 0,
maxZoom: 18,
}
]
}
});
map.on('load', function () {
// geojsonを表示する
map.addSource('result', {
type: 'geojson',
data: 'Apps scriptのURLを入力',
});
map.addLayer({
id: 'result',
type: 'circle',
source: 'result',
layout: {
},
paint: {
//dataの数値によってポイントの色を変える
'circle-color': [
"interpolate",
["linear"],
["get", "data"],
0, //dataの値が0の時
"#ffffff", //この色になる
10,
"#ff52ff",
20,
"#ff0004",
30,
"#ff9d00",
40,
"#fffb00",
50,
"#00ff40",
60,
"#00810b",
70,
"#007bff",
80,
"#0800ff",
90,
"#16285d",
100,
"#000000",
],
'circle-radius': 7 //ポイントの大きさ
},
});
map.addLayer({
id: 'result_label',
type: 'symbol',
source: 'result',
minzoom: 8,
//dataをポイントの下にラベル表示
layout: {
"text-field": ["get", "data"],
'text-font': [
'Noto Sans Regular',
],
'text-offset': [0, 1.25],
'text-anchor': 'top',
"text-size": [
"interpolate",
["linear"],
["zoom"],
10, //zoomレベル10の時
10, //フォントサイズ8
14, //zoomレベル14の時
14 //フォントサイズ14
],
},
paint: {
"text-halo-width": 1,
"text-halo-color": "#fff"
},
});
// NavigationControl
let nc = new maplibregl.NavigationControl();
map.addControl(nc, 'top-left');
// スケール表示
map.addControl(new maplibregl.ScaleControl({
maxWidth: 200,
unit: 'metric'
}));
// 地物クリック時にポップアップを表示する
map.on('click', "result", function (e) {
var coordinates = e.features[0].geometry.coordinates.slice();
var place = e.features[0].properties.place;
var date = e.features[0].properties.date;
var time = e.features[0].properties.time;
var data = e.features[0].properties.data;
var memo = e.features[0].properties.memo;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
// ポップアップを表示する
new maplibregl.Popup({
offset: 10, // ポップアップの位置
closeButton: false, // 閉じるボタンの表示
})
.setLngLat(coordinates)
//ポップアップに表示する内容
.setHTML("場所 : " + place + "<br>DATA : " + data + "<br>日時 : " + date + " (" + time + ")<br>備考 : " + memo)
.addTo(map);
});
})
</script>
</body>
</html>
参考
下記を大いにパク参考にさせていただきました。本当にありがとうございます。
- GAS関係
- MapLibre GL 関係 #いつもお世話になってます。
- 聖書、全人類買うべき本