About
Export points from https://pokemongo.com/en/map to import into https://prp.oath.pw/
How to use
- Install tampermonkey (and enable userscripts)
- Add and enable user script from below
- Go to https://pokemongo.com/en/map and login
- As you move the map dots are collected
- Click “Export GeoJSON”
Userscript
// ==UserScript==
// @name Pokémon GO Map GeoJSON Exporter
// @namespace http://tampermonkey.net/
// @version 4.0
// @description Captures PokéStops & Gyms from the map's GraphQL traffic (in any frame) and exports GeoJSON.
// @match https://pokemongo.com/*
// @match https://*.pokemongo.com/*
// @match https://*.nianticlabs.com/*
// @run-at document-start
// @grant unsafeWindow
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addValueChangeListener
// ==/UserScript==
(function () {
'use strict';
const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const IS_TOP = window.top === window.self;
const KEY = 'pogo_pois';
const TAG = `[PoGO Exporter${IS_TOP ? '' : ' @' + location.hostname}]`;
// ---- Shared store (GM storage is shared by every frame running this script) ----
let store = GM_getValue(KEY, {}) || {};
let pending = {};
let flushTimer = null;
GM_addValueChangeListener(KEY, (_name, _old, newVal) => {
store = newVal || {};
updateCounters();
});
function queue(poi) {
if (store[poi.id] || pending[poi.id]) return;
pending[poi.id] = poi;
clearTimeout(flushTimer);
flushTimer = setTimeout(flush, 400);
}
function flush() {
clearTimeout(flushTimer);
const batch = pending;
pending = {};
const n = Object.keys(batch).length;
if (!n) return;
const merged = Object.assign({}, GM_getValue(KEY, {}) || {}, batch);
store = merged;
GM_setValue(KEY, merged);
console.log(`${TAG} +${n} new → ${Object.keys(merged).length} total`);
updateCounters();
}
// ---- Extraction ----
function extract(obj) {
if (!obj || typeof obj !== 'object') return;
if (Array.isArray(obj)) { obj.forEach(extract); return; }
if (typeof obj.id === 'string') {
const stop = obj.pgoPokestop;
const gym = obj.pgoGym;
if (stop && stop.location && typeof stop.location.latitude === 'number') {
queue({
id: obj.id,
type: 'PGO_POKESTOP',
lat: stop.location.latitude,
lng: stop.location.longitude
});
} else if (gym && gym.location && typeof gym.location.latitude === 'number') {
queue({
id: obj.id,
type: 'PGO_GYM',
lat: gym.location.latitude,
lng: gym.location.longitude,
team: gym.team || 'NEUTRAL'
});
}
}
for (const k in obj) {
if (obj[k] && typeof obj[k] === 'object') extract(obj[k]);
}
}
const isMapUrl = (url) => typeof url === 'string' && /graphql|niantic-social-api/i.test(url);
// ---- fetch hook (string, URL, or Request) ----
const originalFetch = win.fetch;
win.fetch = async function (...args) {
const response = await originalFetch.apply(this, args);
try {
const target = args[0];
const url = typeof target === 'string' ? target
: (target && typeof target.url === 'string') ? target.url
: String(target);
if (isMapUrl(url)) {
response.clone().json().then(extract).catch(() => {});
}
} catch (e) { /* ignore */ }
return response;
};
// ---- XHR hook ----
const XHR = win.XMLHttpRequest.prototype;
const originalOpen = XHR.open;
const originalSend = XHR.send;
XHR.open = function (method, url) {
this.__pogoUrl = String(url);
return originalOpen.apply(this, arguments);
};
XHR.send = function () {
if (isMapUrl(this.__pogoUrl)) {
this.addEventListener('load', function () {
try {
const data = this.responseType === 'json'
? this.response
: JSON.parse(this.responseText);
extract(data);
} catch (e) { /* ignore */ }
});
}
return originalSend.apply(this, arguments);
};
// ---- Export (works from any frame's console) ----
function downloadPOIs() {
flush();
const all = Object.values(GM_getValue(KEY, {}) || {});
if (!all.length) {
alert('No points collected yet! Pan and zoom the map around.');
return;
}
const geojson = {
type: 'FeatureCollection',
features: all.map(p => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [p.lng, p.lat] },
properties: {
id: p.id,
type: p.type,
name: p.type === 'PGO_GYM' ? 'Gym' : 'PokéStop',
...(p.team && { team: p.team })
}
}))
};
const blob = new Blob([JSON.stringify(geojson, null, 2)], { type: 'application/geo+json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `pokemongo_map_export_${Date.now()}.geojson`;
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000);
console.log(`${TAG} Exported ${all.length} locations.`);
}
function clearPOIs() {
if (!confirm('Clear all collected points?')) return;
pending = {};
store = {};
GM_setValue(KEY, {});
updateCounters();
console.log(`${TAG} Cleared.`);
}
win.downloadPOIs = downloadPOIs;
win.clearPOIs = clearPOIs;
// ---- UI (top frame only) ----
function updateCounters() {
if (!IS_TOP) return;
let stops = 0, gyms = 0;
for (const id in store) {
if (store[id].type === 'PGO_POKESTOP') stops++;
else if (store[id].type === 'PGO_GYM') gyms++;
}
const set = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };
set('pogo-stop-count', stops);
set('pogo-gym-count', gyms);
set('pogo-total-badge', `${stops + gyms} items`);
}
function createUI() {
if (!document.body || document.getElementById('pogo-exporter-panel')) return;
const panel = document.createElement('div');
panel.id = 'pogo-exporter-panel';
panel.style.cssText = `
position:fixed; bottom:20px; right:20px; z-index:2147483647;
background:rgba(20,20,20,0.95); color:#fff; padding:14px; border-radius:8px;
font-family:sans-serif; font-size:13px; box-shadow:0 4px 12px rgba(0,0,0,0.4);
display:flex; flex-direction:column; gap:8px; border:1px solid rgba(255,255,255,0.2);
`;
panel.innerHTML = `
<div style="font-weight:bold;font-size:14px;border-bottom:1px solid rgba(255,255,255,0.2);padding-bottom:6px;display:flex;justify-content:space-between;align-items:center;gap:10px;">
<span>🗺️ PoGO Exporter</span>
<span id="pogo-total-badge" style="background:#e3350d;padding:2px 6px;border-radius:10px;font-size:11px;">0 items</span>
</div>
<div style="display:flex;justify-content:space-between;gap:10px;">
<span>PokéStops:</span><strong id="pogo-stop-count" style="color:#38ef7d;">0</strong>
</div>
<div style="display:flex;justify-content:space-between;gap:10px;">
<span>Gyms:</span><strong id="pogo-gym-count" style="color:#f1c40f;">0</strong>
</div>
<div style="display:flex;gap:6px;margin-top:4px;">
<button id="pogo-export-btn" style="flex:2;padding:8px;background:#2ecc71;color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:bold;">📥 Export GeoJSON</button>
<button id="pogo-clear-btn" style="flex:1;padding:8px;background:#e74c3c;color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:bold;">🗑️ Clear</button>
</div>
`;
document.body.appendChild(panel);
panel.querySelector('#pogo-export-btn').onclick = downloadPOIs;
panel.querySelector('#pogo-clear-btn').onclick = clearPOIs;
updateCounters();
}
if (IS_TOP) setInterval(createUI, 1500);
})();