📖
座標点群配列から、各点の近傍(半径100m以内)の点群数を数える。座標点群配列に数えた点群数と住所を追加する。
本記事の分類
- 学習ノート
機能
- 舗装修繕箇所(ポットホールの修繕箇所)の座標データから定量的に舗装修繕回数が多い範囲(部分)を評価する
- 市町名及び町丁目情報を取得する
想定シーン
- 舗装修繕箇所の優先順位付けの参考資料を作成する
仕様
- 各舗装補修箇所の近傍(半径100m以内)の補修箇所数を数える。
- 各点の座標データと補修箇所数を要素とする新しい配列を作成する。
- ラフな精度でも問題ないため地球を球体として計算している。
参考
- 高精度を求める場合は別の計算式が必要。https://qiita.com/Yuzu2yan/items/0f312954feeb3c83c70e
Code
- 以下のとおり
createStaffTable
// ==================================================
// Google Apps Script: GSI逆ジオコーダーのみ版(ZipCloud不要)+負荷制御+一括処理
// ==================================================
// ■ 定数設定 ■
const API_BASE_GSI = 'https://mreversegeocoder.gsi.go.jp/reverse-geocoder/LonLatToAddress';
const BATCH_SIZE = 10; // 同時並列リクエスト件数
const QPS_LIMIT = 5; // 1秒あたり最大リクエスト数
const BATCH_PER_RUN = 50; // 1実行あたり処理ポイント数
const RADIUS_KM = 0.1; // 近傍判定半径(km)
const EARTH_RADIUS_KM = 6371; // 地球半径(km)
// ★ 栃木県の対象市町村マッピング表(muniCd → 市町村名)★
const muniCdMap = {
"09210": "大田原市",
"09213": "那須塩原市",
"09407": "那須町"
};
// ■ 1) シート入力取得&正規化 ■
function fetchRawInputs() {
return SpreadsheetApp.getActiveRange().getValues().map(row => {
const s = String(row[0]).trim();
if (s.includes(',')) {
return s; // 座標
}
return ''; // 郵便番号データは無視する(空文字にする)
}).filter(v => v); // 空白除外
}
// ■ 2) 距離計算ユーティリティ ■
function toRadians(deg) {
return deg * Math.PI / 180;
}
function calculateDistance(lat1, lon1, lat2, lon2) {
const φ1 = toRadians(lat1), φ2 = toRadians(lat2);
const λ1 = toRadians(lon1), λ2 = toRadians(lon2);
const cosθ = Math.cos(φ1) * Math.cos(φ2) * Math.cos(λ1 - λ2) + Math.sin(φ1) * Math.sin(φ2);
return EARTH_RADIUS_KM * Math.acos(cosθ);
}
function summarizeNearby(points) {
return points.map(pt => {
const [lat1, lon1] = pt;
let count = points.filter(q => {
const [lat2, lon2] = q;
if (lat1 === lat2 && lon1 === lon2) return false; // 自分自身はスキップ
return calculateDistance(lat1, lon1, lat2, lon2) <= RADIUS_KM;
}).length;
count++;
return [count, lat1, lon1];
});
}
// ■ 4) GSI逆ジオコーダーAPIバッチ取得(市名マッピングあり)■
function fetchGsiAddressesInBatches(coords) {
const out = [];
for (let i = 0; i < coords.length; i += BATCH_SIZE) {
const batch = coords.slice(i, i + BATCH_SIZE);
const reqs = batch.map(([lat, lon]) => ({
url: `${API_BASE_GSI}?lat=${lat}&lon=${lon}`,
muteHttpExceptions: true
}));
const resps = UrlFetchApp.fetchAll(reqs);
resps.forEach(r => {
let city = '', town = '';
try {
const j = JSON.parse(r.getContentText());
if (j.results) {
const results = j.results;
const muniCd = results.muniCd;
const lv01Nm = results.lv01Nm || '';
city = muniCd ? (muniCdMap[muniCd] || `コード${muniCd}`) : '';
town = lv01Nm;
}
} catch (e) { /* ignore */ }
out.push({ city, town });
});
Utilities.sleep(Math.ceil(batch.length / QPS_LIMIT) * 1000);
}
return out;
}
// ■ 5) プロパティ操作(進捗保存)■
function getLastIndex() {
return Number(PropertiesService.getScriptProperties().getProperty('LAST_INDEX') || 0);
}
function setLastIndex(idx) {
PropertiesService.getScriptProperties().setProperty('LAST_INDEX', String(idx));
}
// ■ 6) ソート関数(降順)■
function sortResultSheet() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sh = ss.getSheetByName('result');
if (!sh) return;
const data = sh.getDataRange().getValues();
if (data.length <= 1) return;
const [header, ...body] = data;
body.sort((a, b) => (b[0] || 0) - (a[0] || 0));
sh.clear();
sh.getRange(1, 1, 1, header.length).setValues([header]);
sh.getRange(2, 1, body.length, header.length).setValues(body);
}
// ■ 7) バッチ処理メイン(続きありならtrue、終わったらfalse)■
function processBatch() {
const raw = fetchRawInputs();
const total = raw.length;
let idx = getLastIndex();
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sh = ss.getSheetByName('result') || ss.insertSheet('result');
if (idx === 0) {
sh.clear();
sh.getRange(1, 1, 1, 5).setValues([['R100m内補修回数', '緯度', '経度', '市町', '町丁目']]);
}
const end = Math.min(idx + BATCH_PER_RUN, total);
const sliceRaw = raw.slice(idx, end);
const coords = sliceRaw.map(v => v.split(',').map(Number));
const summary = summarizeNearby(coords);
const gsiAddrs = fetchGsiAddressesInBatches(coords);
const rows = [];
for (let i = 0; i < coords.length; i++) {
const [count, lat, lon] = summary[i];
const { city, town } = gsiAddrs[i];
rows.push([count, lat, lon, city, town]);
}
sh.getRange(idx + 2, 1, rows.length, 5).setValues(rows);
if (end < total) {
setLastIndex(end);
return true; // 続きあり
} else {
sortResultSheet();
PropertiesService.getScriptProperties().deleteProperty('LAST_INDEX');
return false; // 終了
}
}
// ■ 8) 全部一括実行用メイン(whileで全部やる)■
function main() {
while (true) {
const continueProcessing = processBatch();
if (!continueProcessing) break;
}
}
使い方
- googleSpreadSheetに座標値を入力(1列目は緯度、2列目は経度)
- 座標値を範囲選択
- ”拡張機能”→”Apps Script”を選択
- 上記codeを入力しmain関数を実行
Google map(My map)

- Google map (My map) に反映した例
Discussion