// ==UserScript==
// @name Geoguessr Map-Making Auto-Tag
// @namespace http://tampermonkey.net/
// @version 2.6
// @description Tag your street views by date and address
// @author KaKa
// @match https://map-making.app/maps/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_setClipboard
// @license MIT
// ==/UserScript==
(function() {
'use strict';
async function runScript(tags) {
let api_key = GM_getValue("api_key");
if (!api_key) {
api_key = prompt("Please enter your Google API key");
GM_setValue("api_key", api_key);
}
const option = confirm('Do you want to input data from the clipboard? If you click "Cancel", you will need to upload a JSON file.');
let data;
if (option) {
const text = await navigator.clipboard.readText();
try {
data = JSON.parse(text);
} catch (error) {
alert('The input JSON data is invalid or incorrectly formatted.');
return;
}
} else {
const input = document.createElement('input');
input.type = 'file';
document.body.appendChild(input);
data = await new Promise((resolve) => {
input.addEventListener('change', async () => {
const file = input.files[0];
const reader = new FileReader();
reader.onload = (event) => {
try {
const result = JSON.parse(event.target.result);
resolve(result);
document.body.removeChild(input);
} catch (error) {
alert('The input JSON data is invalid or incorrectly formatted.');
}
};
reader.readAsText(file);
});
input.click();
});
}
const newData = [];
async function getMetaData(url) {
try {
const response = await fetch(url);
const data = await response.json();
if (data.status == "OK") {
let year = 'nodate';
const match = data.date.match(/\d{4}/);
if (match) {
year = match[0];
}
let panoType = 'unofficial';
if (data.copyright.includes('Google')) {
panoType = 'official';
}
return [year, panoType];
} else {
return ["nodata","nodata"];
}
} catch (error) {
console.error(`Error fetching metadata: ${error}`);
}
}
function get_Meta(id) {
const url = `https://maps.googleapis.com/maps/api/streetview/metadata?pano=${id}&key=${api_key}`;
return getMetaData(url);
}
function search_Meta(lat, lng) {
const url = `https://maps.googleapis.com/maps/api/streetview/metadata?location=${lat},${lng}&key=${api_key}`;
return getMetaData(url);
}
let last_token = null;
let last_token_expiry = 0;
async function get_Token(api_key) {
let current_time = Date.now() / 1000;
if (last_token && last_token_expiry > current_time) {
return last_token;
}
let url = `https://tile.googleapis.com/v1/createSession?key=${api_key}`;
let headers = {'Content-Type': 'application/json'};
let data = { "mapType": "streetview",
"language": "en-US",
"region": "US"};
let response = await fetch(url, {method: 'POST', headers: headers, body: JSON.stringify(data)});
if (response.status == 200) {
let token = (await response.json()).session;
last_token_expiry = current_time + 5 * 60;
last_token = token;
return token;
} else {
alert(`Error: ${response.status}, ${await response.text()}`);
}
}
async function getAddress(url) {
let country = 'nodata';
let subdivision = 'nodata';
let locality = 'nodata';
try {
let response = await fetch(url);
if (response.status == 200) {
let data = await response.json();
for (let add of data.addressComponents) {
if (add.types.includes('country')) {
country = add.longName;
}
if (add.types.includes('administrative_area_level_1')) {
subdivision = add.longName;
}
if (add.types.includes('locality')) {
locality = add.longName;
}
}
}
} catch (error) {
console.log(error);
}
return [country, subdivision, locality];
}
async function get_add(id) {
let tk = await get_Token(api_key);
let url = `https://tile.googleapis.com/v1/streetview/metadata?session=${tk}&key=${api_key}&panoId=${id}`;
return getAddress(url);
}
async function search_add(lat,lng) {
let tk = await get_Token(api_key);
let url = `https://tile.googleapis.com/v1/streetview/metadata?session=${tk}&key=${api_key}&lat=${lat}&lng=${lng}&radius=50`;
return getAddress(url);
}
function getGeneration(svData) {
if (svData && svData.tiles) {
if (svData.tiles.worldSize.height === 1664) { // Gen 1
return 'Gen1';
} else if (svData.tiles.worldSize.height === 6656) { // Gen 2 or 3
return 'Gen2or3';
} else { // Gen 4
return 'Gen4';
}
}
return 'Unknown';
}
var CHUNK_SIZE = 1000;
var promises = [];
async function processCoord(coord, tags, svData) {
if (!coord.extra) {
coord.extra = {};
}
if (!coord.extra.tags) {
coord.extra.tags = [];
}
var meta;
var address;
if (coord.panoId && (tags.includes('year') || tags.includes('type'))) {
meta = await get_Meta(coord.panoId);
if (meta[0] == 'nodata'&& meta[1] == 'nodata') {
meta = await search_Meta(coord.lat, coord.lng);
}
} else if (tags.includes('year') || tags.includes('type')) {
meta = await search_Meta(coord.lat, coord.lng);
}
if (coord.panoId && (tags.includes('country') || tags.includes('subdivision') || tags.includes('locality'))) {
address = await get_add(coord.panoId);
if (address[0] == 'nodata'&& address[1] == 'nodata'&& address[2] == 'nodata') {
address = await search_add(coord.lat, coord.lng);
}
} else if (tags.includes('country') || tags.includes('subdivision') || tags.includes('locality')) {
address = await search_add(coord.lat, coord.lng);
}
if (meta && meta.length >= 2) {
var year_tag = meta[0];
var type_tag = meta[1];
if (tags.includes('year')) coord.extra.tags.push(year_tag);
if (tags.includes('type')) coord.extra.tags.push(type_tag);
}
if (address && address.length >= 3) {
var country_tag=address[0]
var subdivision_tag=address[1]
var locality_tag=address[2]
if (tags.includes('country')) coord.extra.tags.push(country_tag);
if (tags.includes('subdivision')) coord.extra.tags.push(subdivision_tag);
if (tags.includes('locality')) coord.extra.tags.push(locality_tag);
}
if (svData) {
let trekkerTag = (svData.hasOwnProperty('dn') && svData.dn) ? 'trekker' : null;
let genTag = getGeneration(svData)
if (tags.includes('type') && trekkerTag) {
coord.extra.tags.push(trekkerTag);
}
if (tags.includes('generation') && genTag) {
coord.extra.tags.push(genTag);
}
}
newData.push(coord);
}
async function processChunk(chunk, tags) {
var service = new google.maps.StreetViewService();
var promises = chunk.map(async coord => {
let panoId = coord.panoId;
let latLng = {lat: coord.lat, lng: coord.lng};
let svData;
if ((panoId || latLng) && tags.includes('type')|| tags.includes('generation')) {
svData = await getSVData(service, panoId ? {pano: panoId} : {location: latLng, radius: 50});
}
await processCoord(coord, tags, svData)
});
await Promise.all(promises);
}
function getSVData(service, options) {
return new Promise(resolve => service.getPanorama(options, (data, status) => {
resolve(data);
}));
}
async function processData(tags) {
try {
for (let i = 0; i < data.customCoordinates.length; i += CHUNK_SIZE) {
let chunk = data.customCoordinates.slice(i, i + CHUNK_SIZE);
await processChunk(chunk, tags);
}
GM_setClipboard(JSON.stringify(newData));
alert("New JSON data has been copied to the clipboard!");
} catch (error) {
alert("Invalid JSON data");
console.error('Error processing JSON data:', error);
}
}
processData(tags);
}
var mainButtonContainer = document.createElement('div');
mainButtonContainer.style.position = 'fixed';
mainButtonContainer.style.right = '20px';
mainButtonContainer.style.bottom = '20px';
document.body.appendChild(mainButtonContainer);
var buttonContainer = document.createElement('div');
buttonContainer.style.position = 'fixed';
buttonContainer.style.right = '20px';
buttonContainer.style.bottom = '60px';
document.body.appendChild(buttonContainer);
function createButton(text, tags) {
var button = document.createElement('button');
button.textContent = text;
button.style.display = 'none';
button.addEventListener('click', async function() { await runScript(tags); });
buttonContainer.appendChild(button);
return button;
}
var mainButton = document.createElement('button');
mainButton.textContent = 'Auto-Tag';
mainButton.addEventListener('click', function() {
for (var i = 0; i < buttonContainer.children.length; i++) {
var button = buttonContainer.children[i];
if (button.style.display === 'none') {
button.style.display = 'block';
} else {
button.style.display = 'none';
}
}
});
mainButtonContainer.appendChild(mainButton);
createButton('Tag by Year', ['year']);
createButton('Tag by Type',[ 'type']);
createButton('Tag by Country', ['country']);
createButton('Tag by Subdivision', ['subdivision']);
createButton('Tag by Locality', ['locality']);
createButton('Tag by Generations',['generation'])
})();