// ==UserScript==
// @name Google Street View Panorama Info
// @namespace http://greasyfork.icu/users/1340965
// @version 1.13
// @description Displays the country name, coordinates, and panoId for a given Google Street View panorama
// @author ZecaGeo
// @run-at document-end
// @match https://www.google.com/maps/*
// @match https://www.google.at/maps/*
// @grant GM_log
// @grant GM_setClipboard
// @grant GM_xmlhttpRequest
// @icon https://www.google.com/s2/favicons?sz=64&domain=geohints.com
// @connect nominatim.openstreetmap.org
// @license MIT
// ==/UserScript==
/* jshint esversion: 11 */
(function () {
'use strict';
const DEBUG_MODE = false;
function init() {
log(
`Starting userscript '${GM_info.script.name}' v${GM_info.script.version}`
);
waitForElement('.pB8Nmf > div:last-child', updateTitleCard, '#titlecard');
}
async function updateTitleCard(referenceElement) {
let h2Element = document.createElement('h2');
h2Element.setAttribute('class', 'lsdM5 fontBodySmall');
h2Element.setAttribute('jsan', '7.lsdM5,7.fontBodySmall');
let divElement = document.createElement('div');
divElement.appendChild(h2Element);
let [latitude, longitude, panoId] = [
...parsePanoramaInfoFromUrl(window.location.href),
];
let countryNode = referenceElement.parentNode.insertBefore(
cloneNode(divElement, 'Retrieving country...'),
referenceElement
);
let url = `https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=json&accept-language=en-US`;
countryNode.querySelector('h2').innerText = await getCountry(url);
referenceElement.parentNode.insertBefore(
cloneNode(divElement, latitude, true),
referenceElement
);
referenceElement.parentNode.insertBefore(
cloneNode(divElement, longitude, true),
referenceElement
);
referenceElement.parentNode.insertBefore(
cloneNode(divElement, panoId, true),
referenceElement
);
}
function parseCoordinates(url) {
const regex_coordinates = new RegExp(/@(-?\d+\.\d+),(-?\d+\.\d+)/);
return regex_coordinates.exec(url).slice(1);
}
function parsePanoId(url) {
const regex_panoId = new RegExp(/!1s(.+)!2e/);
return regex_panoId.exec(url).slice(1);
}
function parsePanoramaInfoFromUrl(url) {
parseCoordinates(url).forEach((x) => debug(x));
debug(...parsePanoId(url));
return [...parseCoordinates(url), ...parsePanoId(url)];
}
function cloneNode(originalNode, value, isClickable = false) {
let node = originalNode.cloneNode(true);
node.querySelector('h2').innerText = value;
if (isClickable) {
node.style.cursor = 'pointer';
node.onclick = () => GM_setClipboard(value);
}
return node;
}
async function getCountry(url) {
const response = await promiseRequest('GET', url);
const data = JSON.parse(response.responseText);
return data?.address?.country ?? 'Country not found';
}
function promiseRequest(method, url) {
log(
[
'---PROMISEREQUEST---',
'\tmethod: ' + method,
'\turl: ' + url,
'---PROMISEREQUEST---',
].join('\n')
);
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: method,
url: url,
onload: (result) => {
responseInfo(result);
if (result.status >= 200 && result.status < 300) {
// ok
resolve(result);
} else {
// error
reject(result.responseText);
}
},
ontimeout: () => {
let l = new URL(url);
reject(
' timeout detected: "no answer from ' +
l.host +
' for ' +
l.timeout / 1000 +
's"'
);
},
onerror: (result) => {
// network error
responseInfo(result);
reject(
' error: ' + result.status + ', message: ' + result.statusText
);
},
});
});
}
// GM_xmlhttpRequest response info
function responseInfo(r) {
debug(
[
'',
'finalUrl: \t\t' + (r.finalUrl || '-'),
'status: \t\t' + (r.status || '-'),
'statusText: \t' + (r.statusText || '-'),
'readyState: \t' + (r.readyState || '-'),
'responseHeaders: ' +
(r.responseHeaders.replaceAll('\r\n', ';') || '-'),
'responseText: \t' + (r.responseText || '-'),
].join('\n')
);
}
const waitForElement = (selector, callback, targetNode) => {
new MutationObserver((mutationsList, observer) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
const element = document.querySelector(selector);
if (element) {
observer.disconnect();
callback(element);
return;
}
}
}
}).observe(
targetNode ? document.querySelector(targetNode) : document.body,
{
childList: true,
subtree: true,
}
);
};
// debug output functions
function toLog(typ, msg) {
if (DEBUG_MODE) {
if (console && console[typ] && console.group && console.groupEnd) {
console[typ](msg);
} else {
GM_log(typ + ': ' + msg.toString());
}
}
}
function log(msg) {
toLog('log', msg);
}
function debug(msg) {
toLog('debug', msg);
}
function error(msg) {
toLog('error', msg);
}
init();
})();