// ==UserScript==
// @name AppleGuessr
// @namespace https://greasyfork.org/en/users/946023-mistystar
// @version 2.1
// @description Adds Apple Look Around to GeoGuessr
// @author Mistystar (Mistystar#2205, https://github.com/kittenz) & stocc (stocc#2919, https://github.com/stocc)
// @match https://www.geoguessr.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
// @grant none
// @license MIT
// @run-at document-start
// @require https://cdn.jsdelivr.net/npm/protobufjs@7.0.0/dist/protobuf.js
// @require https://cdn.jsdelivr.net/npm/long@5/umd/index.js
// @require https://cdn.jsdelivr.net/gh/chebum/heic2any@2c517409ac73e86e92560312b58fcfd565ad7393/dist/heic2any.min.js
// ==/UserScript==
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ 297:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
// Blatantly stolen from https://github.com/sk-zk/lookaround-map/blob/main/static/auth.js
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Authenticator_instances, _Authenticator_generateSessionId, _Authenticator_generateTokenP3;
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.Authenticator = void 0;
const options_1 = __webpack_require__(944);
const proto_1 = __webpack_require__(224);
const TOKEN_P1 = "4cjLaD4jGRwlQ9U";
const MANIFEST_URL = "https://gspe35-ssl.ls.apple.com/geo_manifest/dynamic/config?application=geod" +
"&application_version=1&country_code=US&hardware=MacBookPro11,2&os=osx" +
"&os_build=20B29&os_version=11.0.1";
var GLOBAL_TOKENP2 = undefined;
class Authenticator {
constructor() {
_Authenticator_instances.add(this);
this.sessionId = null;
}
init() {
return __awaiter(this, void 0, void 0, function* () {
yield this.refreshCredentials();
});
}
refreshCredentials() {
return __awaiter(this, void 0, void 0, function* () {
this.sessionId = __classPrivateFieldGet(this, _Authenticator_instances, "m", _Authenticator_generateSessionId).call(this);
});
}
hasSession() {
return this.sessionId != null;
}
getTokenP2() {
return __awaiter(this, void 0, void 0, function* () {
if (GLOBAL_TOKENP2 == undefined) {
GLOBAL_TOKENP2 = (yield this.getResourceManifest()).tokenP2;
}
return GLOBAL_TOKENP2;
});
}
authenticateUrl(url) {
return __awaiter(this, void 0, void 0, function* () {
const urlObj = new URL(url);
let p2 = yield this.getTokenP2();
const tokenP3 = __classPrivateFieldGet(this, _Authenticator_instances, "m", _Authenticator_generateTokenP3).call(this);
const token = TOKEN_P1 + p2 + tokenP3;
const timestamp = Math.floor(Date.now() / 1000) + 4200;
const separator = urlObj.search ? "&" : "?";
let urlPath = urlObj.pathname;
if (urlObj.search) {
urlPath += urlObj.search;
}
const plaintext = `${urlPath}${separator}sid=${this.sessionId}${timestamp}${tokenP3}`;
const plaintextBytes = new TextEncoder().encode(plaintext);
const key = yield sha256(token);
const ciphertext = yield aes(key, plaintextBytes);
const ciphertextB64 = btoa(String.fromCharCode(...new Uint8Array(ciphertext)));
const ciphertextUrl = encodeURIComponent(ciphertextB64);
const accessKey = `${timestamp}_${tokenP3}_${ciphertextUrl}`;
const final = `${url}${separator}sid=${this.sessionId}&accessKey=${accessKey}`;
return final;
});
}
getResourceManifest() {
return __awaiter(this, void 0, void 0, function* () {
const response = yield fetch(options_1.CORS_PROXY + MANIFEST_URL);
let pb = yield response.arrayBuffer();
return yield proto_1.default.parseResourceManifest(pb);
});
}
}
exports.Authenticator = Authenticator;
_Authenticator_instances = new WeakSet(), _Authenticator_generateSessionId = function _Authenticator_generateSessionId() {
let id = "";
for (let i = 0; i < 40; i++) {
const digit = (Math.random() * 10) | 0;
id += digit.toString();
}
return id;
}, _Authenticator_generateTokenP3 = function _Authenticator_generateTokenP3() {
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
let token = "";
for (let i = 0; i < 16; i++) {
const idx = (Math.random() * chars.length) | 0;
token += chars[idx];
}
return token;
};
function sha256(message) {
return __awaiter(this, void 0, void 0, function* () {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = yield window.crypto.subtle.digest("SHA-256", msgBuffer);
return hashBuffer;
});
}
function aes(key, encodedMessage) {
return __awaiter(this, void 0, void 0, function* () {
const iv = new Uint8Array(16); // 16 zeroes
const cryptoKey = yield window.crypto.subtle.importKey("raw", key, { name: "AES-CBC" }, true, ["encrypt"]);
return yield window.crypto.subtle.encrypt({
name: "AES-CBC",
iv,
}, cryptoKey, encodedMessage);
});
}
//# sourceMappingURL=auth.js.map
/***/ }),
/***/ 97:
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
const TILE_SIZE = 256;
class GeoUtils {
static haversineDistance(coords1, coords2) {
function toRad(x) {
return x * Math.PI / 180;
}
var lon1 = coords1[0];
var lat1 = coords1[1];
var lon2 = coords2[0];
var lat2 = coords2[1];
var R = 6371; // km
var x1 = lat2 - lat1;
var dLat = toRad(x1);
var x2 = lon2 - lon1;
var dLon = toRad(x2);
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
return d;
}
static radians_to_degrees(radians) {
var pi = Math.PI;
return radians * (180 / pi);
}
static headingFromUnknowns(unknown10, unknown11) {
let westmin = 1;
let westmax = 2159;
let eastmin = 16383; // looking (north/south) and very slightly east
let eastmax = 14318; // looking slightly (north/south) directly east
let northmin = 8204; // this is likely lower
let northmax = 6054;
let southmin = 8204; // this is likely lower
let southmax = 10173;
var ew = 0;
if (unknown10 < westmax) {
ew = -((unknown10 - westmin) / (westmax - westmin));
}
else if (unknown10 > eastmax) {
ew = ((unknown10 - eastmin) / (eastmax - eastmin));
}
var ns = 0;
if (unknown11 <= northmin) {
ns = ((unknown11 - northmin) / (northmax - northmin));
}
else {
ns = -((unknown11 - southmin) / (southmax - southmin));
}
var r = GeoUtils.radians_to_degrees(Math.atan2(ew, ns));
if (r < 0) {
r += 360;
}
return r;
}
static mercator_to_wgs84(x, y) {
let lat = (2 * Math.atan(Math.exp((y - 128) / -(256 / (2 * Math.PI)))) - Math.PI / 2) / (Math.PI / 180);
let lon = (x - 128) / (256 / 360);
return [lat, lon];
}
static tile_coord_to_wgs84(x, y, z) {
let scale = 1 << z;
let pixel_coord = [x * TILE_SIZE, y * TILE_SIZE];
let world_coord = [pixel_coord[0] / scale, pixel_coord[1] / scale];
let lat_lon = GeoUtils.mercator_to_wgs84(world_coord[0], world_coord[1]);
return [lat_lon[0], lat_lon[1]];
}
static protobuf_tile_offset_to_wsg84(x_offset, y_offset, tile_x, tile_y) {
let pano_x = tile_x + (x_offset / 64.0) / (TILE_SIZE - 1);
let pano_y = tile_y + (255 - (y_offset / 64.0)) / (TILE_SIZE - 1);
let coords = GeoUtils.tile_coord_to_wgs84(pano_x, pano_y, 17);
return coords;
}
static wgs84_to_mercator(lat, lon) {
var siny = Math.sin(lat * Math.PI / 180);
siny = Math.min(Math.max(siny, -0.9999), 0.9999);
return [
TILE_SIZE * (0.5 + lon / 360),
TILE_SIZE * (0.5 - Math.log((1 + siny) / (1 - siny)) / (4 * Math.PI))
];
}
static wgs84_to_tile_coord(lat, lon, zoom) {
var scale = 1 << zoom;
var world_coord = this.wgs84_to_mercator(lat, lon);
var tile_coord = [
Math.floor((world_coord[0] * scale) / TILE_SIZE),
Math.floor((world_coord[1] * scale) / TILE_SIZE)
];
return tile_coord;
}
static heading(coords1, coords2) {
try {
let c1 = new google.maps.LatLng(coords1[0], coords1[1]);
let c2 = new google.maps.LatLng(coords2[0], coords2[1]);
let result = google.maps.geometry.spherical.computeHeading(c1, c2);
if (result < 0) {
result += 360;
}
return result;
}
catch (e) {
console.log(e);
}
}
}
exports["default"] = GeoUtils;
//# sourceMappingURL=geoutils.js.map
/***/ }),
/***/ 590:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
// ==UserScript==
// @name AppleGuessr
// @namespace https://greasyfork.org/en/users/946023-mistystar
// @version 2.1
// @description Adds Apple Look Around to GeoGuessr
// @author Mistystar (Mistystar#2205, https://github.com/kittenz) & stocc (stocc#2919, https://github.com/stocc)
// @match https://www.geoguessr.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
// @grant none
// @license MIT
// @run-at document-start
// @require https://cdn.jsdelivr.net/gh/chebum/heic2any@master/dist/heic2any.min.js
// @require https://cdn.jsdelivr.net/npm/protobufjs@7.0.0/dist/protobuf.js
// @require https://cdn.jsdelivr.net/npm/long@5/umd/index.js
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
// ==/UserScript==
/*
CREDITS
Massive thank you to the following people:
- skzk#8049 - Without https://github.com/sk-zk/lookaround-map this script would not have been possible to make
- Jupaoqq#7742 - I learned a lot from looking at Unity Script's source code
- mattisinthesky#1294 or kowalski - For hosting the lookaround-map in Heroku and helping with issues
- efefury#0519 and Apfeloxid#1368 - For making the Take A Look Around Germany map
*/
// BEGIN CODE SECTION
const Options = __webpack_require__(944);
const Lookaround = __webpack_require__(215);
const lookaround_1 = __webpack_require__(215);
const geoutils_1 = __webpack_require__(97);
protobuf.util.Long = Long;
protobuf.configure();
const MENU_HTML = (/* unused pure expression or super */ null && (`
<div class="start-standard-game_settings__x94PU">
<div class="game-settings_default__DIBgs">
<div class="game-settings_toggleLabel__nipwm">
<div class="label_sizeXSmall__mFnrR">Apple Look Around</div>
<span></span>
</div>
<div>
<input type="checkbox" class="apple-look-around-toggle" checked>
</div>
</div>
</div>
`));
const isGamePage = () => location.pathname.startsWith("/challenge/") || location.pathname.startsWith("/results/") ||
location.pathname.startsWith("/game/") || location.pathname.startsWith("/battle-royale/") ||
location.pathname.startsWith("/duels/") || location.pathname.startsWith("/team-duels/") ||
location.pathname.startsWith("/bullseye/") ||
location.pathname.startsWith("/live-challenge/");
function overrideOnLoad(googleScript, observer, overrider) {
const oldOnload = googleScript.onload;
googleScript.onload = (event) => {
const google = window.google;
if (google) {
observer.disconnect();
overrider(google);
}
if (oldOnload) {
oldOnload.call(googleScript, event);
}
};
}
function grabGoogleScript(mutations) {
for (const mutation of mutations) {
for (const newNode of mutation.addedNodes /* Please shut up, it works in JS so it must work here as well */) {
const asScript = newNode;
if (asScript && asScript.src && asScript.src.startsWith("https://maps.googleapis.com/")) {
return asScript;
}
}
}
return null;
}
function injecter(overrider) {
if (document.documentElement) {
injecterCallback(overrider);
}
else {
alert("Script didn't load, refresh to try loading the script");
}
}
function injecterCallback(overrider) {
new MutationObserver((mutations, observer) => {
const googleScript = grabGoogleScript(mutations);
if (googleScript) {
overrideOnLoad(googleScript, observer, overrider);
}
}).observe(document.documentElement, { childList: true, subtree: true });
}
// End Script injection --------------------------------------------------------------s
function injectMenu() {
const inject = () => {
if (document.querySelector(".apple-look-around-toggle") !== null)
return;
const settingsSection = document.querySelector('.section_sectionMedium__yXgE6');
if (settingsSection === null)
return;
settingsSection.insertAdjacentHTML("beforeend", MENU_HTML);
const checkbox = document.querySelector(".apple-look-around-toggle");
if (checkbox) {
let isChecked = localStorage.getItem("applelookaroundchecked");
if (isChecked === null) {
checkbox.checked = false;
localStorage.setItem("applelookaroundchecked", "false");
}
else if (isChecked === "true") {
checkbox.checked = true;
}
else {
checkbox.checked = false;
}
checkbox.addEventListener("change", (event) => {
if (event.currentTarget === null)
return;
if (event.currentTarget.checked) {
localStorage.setItem("applelookaroundchecked", "true");
}
else {
localStorage.setItem("applelookaroundchecked", "false");
}
});
}
};
// We want the page to be loaded before trying to inject anything
let documentLoadedInterval = setInterval(function () {
if (document.readyState === "complete") {
clearInterval(documentLoadedInterval);
inject();
}
}, 100);
}
// ----------------------------------------------------------------------------
// Sate vars
// TODO: Is there a better way to do this?
var loadingInProgress = false;
var currentPano = new lookaround_1.PanoInfo("", "", "", 0, 0, 0);
var currentlyLoadedPanoTiles = [];
var curNeighbors = [];
// When moving, this is used to keep the current viewport while loading the next pano
var oldHeading = 0;
// ----------------------------------------------------------------------------
// Google Maps API callbacks
// Return a pano image given the panoID.
const getCustomPanoramaTileUrl = (pano, zoom, tileX, tileY) => {
// Currently loading first image in a round, return a blank image
//if (pano.startsWith("r")){
if (currentlyLoadedPanoTiles.length === 0) {
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==";
}
return currentlyLoadedPanoTiles[tileX];
};
const getPano = (pano) => {
let rp = Options.RESOLUTION_PROFILES[Options.RESOLUTION_SETTING];
let fullWidth = 2 * rp.big.width + 2 * rp.small.width - 4 * rp.overlap;
return {
location: {
pano: pano,
description: "Apple Look Around",
latLng: new google.maps.LatLng(currentPano.lat, currentPano.lon),
},
links: [],
// The text for the copyright control.
copyright: "(C) Apple",
// The definition of the tiles for this panorama.
tiles: {
tileSize: new google.maps.Size(Math.round(fullWidth / 4), Math.round(Options.EXTENSION_FACTOR * rp.big.height)),
worldSize: new google.maps.Size(fullWidth, Math.round(rp.big.height * Options.EXTENSION_FACTOR)),
// The heading in degrees at the origin of the panorama
// tile set.
centerHeading: function () {
// While loading: use the old heading so that when moving, you keep the same viewport while loading the next pano
if (loadingInProgress) {
return oldHeading;
}
else {
var newHeading = (currentPano.heading + Options.HEADING_CALIBRATION) % 360;
oldHeading = newHeading;
return newHeading;
}
}(),
getTileUrl: getCustomPanoramaTileUrl,
},
};
};
// ----------------------------------------------------------------------------
// Init
function initLookAround() {
google.maps.StreetViewPanorama = class extends google.maps.StreetViewPanorama {
constructor(...args) {
super(...args);
let isChecked = localStorage.getItem("applelookaroundchecked");
if (isChecked === "true") {
this.registerPanoProvider(getPano);
// Position is being changed by GeoGuessr at the beginning of each round. this.getPosition() contains lat/lng of round.
this.addListener("position_changed", () => {
console.log("Position changed " + this.getPosition());
try {
// Detect if this is a new round. Normally, currentPano is already updated if this is a move in the same round.
if ((this.getPosition().lat() === currentPano.lat && this.getPosition().lng() === currentPano.lon)) {
console.log("Position is currentPano => same round");
return;
}
console.warn("Position actually changed => new round; full reload");
currentlyLoadedPanoTiles = []; // Causes black screen again
this.getFirstPanoId();
}
catch (e) {
console.error(e);
}
});
// Called after setPano(). If the pano is "r<panoId>/<regioId>", then we load the tiles for that pano.
// If it doesn't start with "r", then loading is done.
this.addListener("pano_changed", () => {
console.log("Pano changed " + this.getPano());
if (this.getPano() != null && this.getPano() != currentPano.panoFullId() && this.getPano() != "" && this.getPano().startsWith("r")) {
console.log("New pano requested " + this.getPano());
try {
this.beginLoadingPanos(this, this.getPano().replace("r", ""));
}
catch (_a) { }
}
});
this.addListener("links_changed", () => {
console.log("Links changed " + this.getLinks());
if (!this.getPano().startsWith("r") && curNeighbors != null) {
//this.getLinks().push(curNeighbors[0])
let neighborLinks = curNeighbors.map(neighbor => {
return {
"descripton": "",
"pano": "r" + neighbor.panoFullId(),
"heading": Math.round(geoutils_1.default.heading([neighbor.lat, neighbor.lon], [currentPano.lat, currentPano.lon]) + 180) % 360,
};
});
console.log("Pushing Links " + neighborLinks.length);
for (const neighbor of neighborLinks) {
if (neighbor.pano != "") {
this.getLinks().push(neighbor);
}
}
}
});
}
}
getFirstPanoId() {
return __awaiter(this, void 0, void 0, function* () {
let isChecked = localStorage.getItem("applelookaroundchecked");
if (isChecked !== "true")
return;
try {
let lat = this.position.lat();
let lon = this.position.lng();
let lookAroundPanoId, regionId;
let closestObject = yield Lookaround.getClosestPanoAtCoords(lat, lon);
lookAroundPanoId = closestObject.panoId;
regionId = closestObject.regionId;
// Request pano to load
currentPano = closestObject;
this.setPano("r" + lookAroundPanoId + "/" + regionId);
}
catch (_a) { }
});
}
// param panoFullId is "panoId/regionId"
beginLoadingPanos(_t, panoFullId) {
return __awaiter(this, void 0, void 0, function* () {
if (loadingInProgress)
return;
//console.warn("http://localhost:5000/#c=17/"+currentPano.lat+"/"+currentPano.lon+"&p="+currentPano.lat+"/"+currentPano.lon);
// Moved. Find the selected neigbor from ID.
if (curNeighbors.length > 0) {
let selectedNeighbor = curNeighbors.filter(n => n.panoFullId() == panoFullId)[0];
if (selectedNeighbor != null) {
currentPano = selectedNeighbor;
}
}
console.log("Start loading Panos");
loadingInProgress = true;
let pano0 = Lookaround.loadTileForPano(panoFullId, 0);
let pano1 = Lookaround.loadTileForPano(panoFullId, 1);
let pano2 = Lookaround.loadTileForPano(panoFullId, 2);
let pano3 = Lookaround.loadTileForPano(panoFullId, 3);
curNeighbors = yield (yield Lookaround.getNeighbors(currentPano));
loadingInProgress = false;
currentlyLoadedPanoTiles = [yield pano0, yield pano1, yield pano2, yield pano3];
// Set another panoId to refresh the view
this.setPano(panoFullId);
});
}
};
}
function launchObserver() {
initLookAround();
//let observer3 = new MutationObserver((mutations) => {
// const PATH_NAME = window.location.pathname;
// if (PATH_NAME.startsWith("/maps/") && PATH_NAME.endsWith("/play")) { // Inject the options menu if the path name is /maps/XXXXXXX/play
// //injectMenu();
// }
//});
//observer3.observe(document.body, {childList: true, subtree: true, attributes: false, characterData: false});
}
function onLoad() {
let isChecked = localStorage.getItem("applelookaroundchecked");
if (isChecked === null) {
localStorage.setItem("applelookaroundchecked", "true");
}
//const PATH_NAME = window.location.pathname;
//if (PATH_NAME.startsWith("/maps/") && PATH_NAME.endsWith("/play")) { // Inject the options menu if the path name is /maps/XXXXXXX/play
// //injectMenu();
//}
injecter(() => {
launchObserver();
});
}
(function () {
onLoad();
})();
window.onload = onLoad;
//# sourceMappingURL=index.js.map
/***/ }),
/***/ 215:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getNeighbors = exports.getClosestPanoAtCoords = exports.loadTileForPano = exports.PanoInfo = void 0;
const Options = __webpack_require__(944);
const auth_1 = __webpack_require__(297);
const geoutils_1 = __webpack_require__(97);
const proto_1 = __webpack_require__(224);
const auth = new auth_1.Authenticator();
var tileCache = {};
class PanoInfo {
constructor(date, panoId, regionId, heading, lat, lon) {
this.date = date;
this.panoId = panoId;
this.regionId = regionId;
this.heading = heading;
this.lat = lat;
this.lon = lon;
}
panoFullId() {
return this.panoId + "/" + this.regionId;
}
}
exports.PanoInfo = PanoInfo;
function getCoverageTileRaw(tile_x, tile_y) {
return __awaiter(this, void 0, void 0, function* () {
let headers = new Headers({
"maps-tile-style": "style=57&size=2&scale=0&v=0&preflight=2",
"maps-tile-x": tile_x.toString(),
"maps-tile-y": tile_y.toString(),
"maps-tile-z": "17",
"maps-auth-token": "w31CPGRO/n7BsFPh8X7kZnFG0LDj9pAuR8nTtH3xhH8=",
});
let response = yield (yield fetch(Options.CORS_PROXY + "https://gspe76-ssl.ls.apple.com/api/tile?", { headers: headers })).arrayBuffer();
let tile = yield proto_1.default.parseMapTile(response);
return tile;
});
}
function getCoverageInMapTile(x, y) {
return __awaiter(this, void 0, void 0, function* () {
try {
if (tileCache["" + x + "/" + y]) {
return tileCache[x + "/" + y];
}
let response = yield getCoverageTileRaw(x, y);
var coverage = [];
for (let pano of response.pano) {
let coords = geoutils_1.default.protobuf_tile_offset_to_wsg84(pano.unknown4.longitudeOffset, pano.unknown4.latitudeOffset, x, y);
let p = new PanoInfo(pano.timestamp.toString(), pano.panoid.toString(), response.unknown13[pano.regionIdIdx].regionId.toString(), geoutils_1.default.headingFromUnknowns(pano.unknown4.unknown10, pano.unknown4.unknown11), coords[0], coords[1]);
coverage.push(p);
}
tileCache["" + x + "/" + y] = coverage;
return coverage;
}
catch (error) {
console.log(error);
}
});
}
function getClosestPanoAtCoords(lat, lon) {
return __awaiter(this, void 0, void 0, function* () {
try {
let tile = geoutils_1.default.wgs84_to_tile_coord(lat, lon, 17);
let coverage = yield getCoverageInMapTile(tile[0], tile[1]);
if (coverage.length == 0) {
return null;
}
let smallestDistance = 9999999;
let closest = null;
for (let pano of coverage) {
let distance = geoutils_1.default.haversineDistance([lat, lon], [pano.lat, pano.lon]);
if (distance < smallestDistance) {
smallestDistance = distance;
closest = pano;
}
}
return closest;
}
catch (error) {
console.log(error);
return null;
}
});
}
exports.getClosestPanoAtCoords = getClosestPanoAtCoords;
function getNeighbors(panoInfo) {
return __awaiter(this, void 0, void 0, function* () {
try {
let tile = geoutils_1.default.wgs84_to_tile_coord(panoInfo.lat, panoInfo.lon, 17);
var coverage = yield getCoverageInMapTile(tile[0], tile[1]);
// TODO Only extend when needed (we're close to the edge of the tile)
coverage = coverage.concat(yield getCoverageInMapTile(tile[0] + 1, tile[1]));
coverage = coverage.concat(yield getCoverageInMapTile(tile[0] - 1, tile[1]));
coverage = coverage.concat(yield getCoverageInMapTile(tile[0], tile[1] + 1));
coverage = coverage.concat(yield getCoverageInMapTile(tile[0], tile[1] - 1));
coverage = coverage.concat(yield getCoverageInMapTile(tile[0] - 1, tile[1] - 1));
coverage = coverage.concat(yield getCoverageInMapTile(tile[0] + 1, tile[1] - 1));
coverage = coverage.concat(yield getCoverageInMapTile(tile[0] - 1, tile[1] + 1));
coverage = coverage.concat(yield getCoverageInMapTile(tile[0] + 1, tile[1] + 1));
coverage = coverage.sort((a, b) => Math.abs(geoutils_1.default.haversineDistance([panoInfo.lat, panoInfo.lon], [a.lat, a.lon])) - Math.abs(geoutils_1.default.haversineDistance([panoInfo.lat, panoInfo.lon], [b.lat, b.lon])));
coverage = coverage.filter(pano => pano.panoFullId() != panoInfo.panoFullId());
let minDist = 0.030; // 30 meters
let maxDist = 0.300; // 300 meters
coverage = coverage.filter(n => (minDist < Math.abs(geoutils_1.default.haversineDistance([panoInfo.lat, panoInfo.lon], [n.lat, n.lon])) &&
Math.abs(geoutils_1.default.haversineDistance([panoInfo.lat, panoInfo.lon], [n.lat, n.lon])) < maxDist));
return coverage.slice(0, 8);
}
catch (error) {
console.log(error);
}
});
}
exports.getNeighbors = getNeighbors;
function getUrlForTile(panoFullId, x, resolution) {
return __awaiter(this, void 0, void 0, function* () {
try {
//if (!auth.hasSession()) {
yield auth.init();
//}
let segments = panoFullId.split("/");
let panoId = segments[0];
let regionId = segments[1];
let panoid_padded = panoId.padStart(20, "0");
let region_id_padded = regionId.padStart(10, "0");
let panoid_split = panoid_padded.slice(0, 4) + "/" + panoid_padded.slice(4, 8) + "/" + panoid_padded.slice(8, 12) + "/" + panoid_padded.slice(12, 16) + "/" + panoid_padded.slice(16, 20);
return auth.authenticateUrl(Options.APPLE_MAPS_TILE_ENDPOINT + panoid_split + "/" + region_id_padded + "/t/" + x + "/" + resolution);
}
catch (error) {
console.log(error);
}
});
}
// param panoFullId is "panoId/regionId"
function loadTileForPano(panoFullId, x) {
return __awaiter(this, void 0, void 0, function* () {
try {
var jpegblob;
if (Options.CONVERT_LOCALLY) {
// Step 1: Get the URL of the tile to load
// New endpoint /panourl in the python server returns just the Apple URL for the pano
var appleMapsPanoURL = yield getUrlForTile(panoFullId, x, Options.RESOLUTION_SETTING);
appleMapsPanoURL = Options.CORS_PROXY + appleMapsPanoURL;
// Step 2: Load the tile
//console.log("Requesting tile " + [appleMapsPanoURL])
var blobres = yield fetch(appleMapsPanoURL);
var blob = yield blobres.blob();
// Step 3: Convert from HEIC to JPEG with heic2any
//console.log("Fetched tile, converting and resizing... " + [appleMapsPanoURL])
//let startTime = Math.floor(Date.now() / 1000);
jpegblob = heic2any({ "blob": blob, "type": "image/jpeg" });
}
else {
jpegblob = yield (yield fetch(Options.BASE_URL + "pano/" + panoFullId + "/" + Options.RESOLUTION_SETTING + "/" + x + "/")).blob();
}
// Step 4: Process image
// Cut off the overlap from the right of the tile using canvas
// and add black bars on top and bottom because we don't have sky/ground tiles
let rp = Options.RESOLUTION_PROFILES[Options.RESOLUTION_SETTING];
// Putting the jpeg blob into a canvas to remove 256 px from the right (removes overlap)
var w = rp.big.width;
if (x == 1 || x == 3) {
w = rp.small.width;
}
w = w - rp.overlap;
var canvas = document.createElement('canvas');
canvas.height = Math.round(Options.EXTENSION_FACTOR * rp.big.height);
canvas.width = w;
var ctx = canvas.getContext('2d');
var img = new Image();
var result = "";
img.onload = function () {
ctx.drawImage(img, 0, (canvas.height - rp.big.height) / 2);
// This is a big data:image/jpeg;base64, URL
result = canvas.toDataURL("image/jpeg");
};
img.src = URL.createObjectURL(yield jpegblob);
//let endTime = Math.floor(Date.now() / 1000);
//console.log("Time to convert: " + (endTime - startTime) + " seconds");
// Wait for context to finish loading
// TODO: Is there a better way?
const delay = ms => new Promise(res => setTimeout(res, ms));
yield delay(100);
//let endTime2 = Math.floor(Date.now() / 1000);
//console.log("Full time: " + (endTime - startTime) + " seconds");
URL.revokeObjectURL(img.src);
canvas.remove();
img.remove();
return result;
}
catch (error) {
console.log(error);
}
});
}
exports.loadTileForPano = loadTileForPano;
//# sourceMappingURL=lookaround.js.map
/***/ }),
/***/ 944:
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.CONVERT_LOCALLY = exports.BASE_URL = exports.RESOLUTION_PROFILES = exports.APPLE_MAPS_TILE_ENDPOINT = exports.CORS_PROXY = exports.EXTENSION_FACTOR = exports.HEADING_CALIBRATION = exports.RESOLUTION_SETTING = void 0;
// Determines the resolution of images requested from Apple
// Setting a higher resolution will make rounds load WAY slower, until browsers start to support HEIC
// 0 = highest resolution available, 4 = lowest resolution available.
// Default: 2
const RESOLUTION_SETTING = 2;
exports.RESOLUTION_SETTING = RESOLUTION_SETTING;
// Constant value added to calculated heading to calibrate the GeoGuessr compass
const HEADING_CALIBRATION = 45;
exports.HEADING_CALIBRATION = HEADING_CALIBRATION;
const EXTENSION_FACTOR = 2.12; // TODO Play around with this value for best results with image stretching
exports.EXTENSION_FACTOR = EXTENSION_FACTOR;
const BASE_URL = "https://lookaround.stocc.dev/";
exports.BASE_URL = BASE_URL;
const CONVERT_LOCALLY = true;
exports.CONVERT_LOCALLY = CONVERT_LOCALLY;
const CORS_PROXY = "https://nameless-bastion-28139.herokuapp.com/";
exports.CORS_PROXY = CORS_PROXY;
const APPLE_MAPS_TILE_ENDPOINT = "https://gspe72-ssl.ls.apple.com/mnn_us/";
exports.APPLE_MAPS_TILE_ENDPOINT = APPLE_MAPS_TILE_ENDPOINT;
const RESOLUTION_PROFILES = {
0: {
"overlap": 256,
"big": {
"width": 5632,
"height": 4352,
},
"small": {
"width": 3072,
"height": 4352,
}
},
1: {
"overlap": 188,
"big": {
"width": 4128,
"height": 3088,
},
"small": {
"width": 2256,
"height": 3088,
},
},
2: {
"overlap": 100,
"big": {
"width": 2208,
"height": 1648,
},
"small": {
"width": 1200,
"height": 1648,
}
},
3: {
"overlap": 71,
"big": {
"width": 1568,
"height": 1168,
},
"small": {
"width": 848,
"height": 1168,
}
},
4: {
"overlap": 50,
"big": {
"width": 1104,
"height": 832,
},
"small": {
"width": 608,
"height": 832,
}
}
};
exports.RESOLUTION_PROFILES = RESOLUTION_PROFILES;
//# sourceMappingURL=options.js.map
/***/ }),
/***/ 224:
/***/ (function(__unused_webpack_module, exports) {
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
let mapTile = `
syntax = "proto3";
message MapTile {
repeated Pano pano = 1;
repeated Unknown13 unknown13 = 4;
repeated Unknown22 unknown22 = 5;
TileCoordinate tileCoordinate = 6;
message Pano {
uint64 panoid = 1;
int32 unknown1 = 4;
int64 timestamp = 5; // time the pano was taken
int32 region_id_idx = 7;
repeated int32 unknown3 = 9; // goes from 0 to 5. available sizes maybe?
Unknown4 unknown4 = 10;
Unknown5 unknown5 = 12;
message Unknown4 {
int32 longitude_offset = 1;
int32 latitude_offset = 2;
int32 unknown8 = 3;
int32 unknown9 = 4;
int32 unknown10 = 5;
int32 unknown11 = 6;
}
message Unknown5 {
repeated int32 unknown12 = 1;
}
}
message Unknown13 {
int32 unknown14 = 1;
// this is the param that appears in pano URLs after the pano ID.
// no idea what this does exactly.
int32 region_id = 3;
int32 unknown15 = 4;
int32 unknown16 = 5;
int32 unknown17 = 6;
int32 unknown18 = 9;
int32 unknown19 = 10;
int32 unknown20 = 11;
int32 unknown21 = 12;
}
message Unknown22 {
int32 unknown23 = 1;
Unknown24 unknown24 = 4;
Unknown25 unknown25 = 5;
int32 unknown26 = 6;
message Unknown24 {
int32 unknown27 = 1;
double unknown28 = 2;
double unknown29 = 3;
double unknown30 = 4;
double unknown31 = 5;
double unknown32 = 6;
double unknown33 = 7;
double unknown34 = 8;
double unknown35 = 9;
double unknown36 = 10;
}
message Unknown25 {
double unknown37 = 1;
double unknown38 = 2;
double unknown39 = 3;
double unknown40 = 4;
double unknown41 = 5;
double unknown42 = 6;
}
}
message TileCoordinate {
int32 x = 1;
int32 y = 2;
int32 z = 3;
}
}`;
let resourceManifest = `
syntax = "proto3";
message ResourceManifest {
repeated StyleConfig style_config = 2;
string token_p2 = 30;
string cache_base_url = 31;
repeated CacheFile cache_file = 72;
repeated string cache_file_2 = 9;
message CacheFile {
string file_name = 2;
}
message StyleConfig {
string url_prefix_1 = 1;
string url_prefix_2 = 9;
StyleID style_id = 3;
enum StyleID {
_ = 0;
C3MM_1 = 14;
C3M = 15;
DTM_1 = 16;
DTM_2 = 17;
C3MM_2 = 52;
}
}
}
`;
class Proto {
static parseResourceManifest(payload) {
return __awaiter(this, void 0, void 0, function* () {
const array = new Uint8Array(payload);
let manifest = protobuf.parse(resourceManifest).root.lookup("ResourceManifest");
let message = manifest.decode(array);
return message;
});
}
static parseMapTile(payload) {
return __awaiter(this, void 0, void 0, function* () {
const array = new Uint8Array(payload);
let manifest = protobuf.parse(mapTile).root.lookup("MapTile");
let message = manifest.decode(array);
return message;
});
}
}
exports["default"] = Proto;
//# sourceMappingURL=proto.js.map
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module is referenced by other modules so it can't be inlined
/******/ var __webpack_exports__ = __webpack_require__(590);
/******/
/******/ })()
;