About

Export points from https://pokemongo.com/en/map to import into https://prp.oath.pw/

How to use

  1. Install tampermonkey (and enable userscripts)
  2. Add and enable user script from below
  3. Go to https://pokemongo.com/en/map and login
  4. As you move the map dots are collected
  5. 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);
})();