// ==UserScript==
// @name Google Street View Panorama Info
// @namespace http://greasyfork.icu/users/1340965
// @version 1.9
// @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 */
(() => {
'use strict'
/* globals */
let
VERSION = GM_info.script.version,
DEBUG_MODE = 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);
}
// 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"));
}
function cloneNode(originalNode, value, isClickable) {
let node = originalNode.cloneNode(true)
node.querySelector('h2').innerText = value
if (isClickable) {
node.style.cursor = "pointer"
node.onclick = () => GM_setClipboard(value)
}
return node
}
function parseCoordinates(url) {
const regex_coordinates = new RegExp(/@(-?\d+\.\d+),(-?\d+\.\d+)/)
const regex_panoId = new RegExp(/!1s(.+)!2e/)
const result_coordinates = regex_coordinates.exec(url)
const result_panoId = regex_panoId.exec(url)
return [...result_coordinates.slice(1), result_panoId]
}
async function updateTitleCard(_, observer) {
const svElement = document.querySelector('.pB8Nmf div:last-child')
if (svElement) {
observer.disconnect()
log('Starting DOM manipulation')
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)
debug(divElement)
debug(svElement)
let [latitude, longitude, panoId] = parseCoordinates(window.location.href)
let countryElement = cloneNode(divElement, '', false)
svElement.parentNode.insertBefore(countryElement, svElement.nextSibling)
let latitudeElement = cloneNode(divElement, latitude, true)
svElement.parentNode.insertBefore(latitudeElement, countryElement.nextSibling)
let longitudeElement = cloneNode(divElement, longitude, true)
svElement.parentNode.insertBefore(longitudeElement, latitudeElement.nextSibling)
let panoIdElement = cloneNode(divElement, panoId, true)
svElement.parentNode.insertBefore(panoIdElement, longitudeElement.nextSibling)
let url = `https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=json&accept-language=en-US`
countryElement.querySelector('h2').innerText = await getCountry(url)
}
}
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)
}
});
});
}
const init = () => {
const observer = new MutationObserver(updateTitleCard)
observer.observe(document.body, { childList: true, subtree: true })
}
init();
})();