ispu
Node.js ESM wrapper for official Indonesian ISPU air-quality data (KemenLH). No API key — auto token, CLI + module.
6 Oktober 2026653 file
ispu.jsjavascript · 815 baris
#!/usr/bin/env node/** * ISPU Indonesia — Durable air-quality wrapper (Node.js ESM) * =========================================================== * Sumber resmi: Kementerian Lingkungan Hidup / BPLH * Portal : https://ispu.kemenlh.go.id * API : https://ispu.kemenlh.go.id/apiweb * * Token JWT diambil OTOMATIS dari bundle frontend website * (tidak perlu copy manual). Cache token di memory. * * ── Module ────────────────────────────────────────────────── * import ISPU, { ispuCategory } from './ispu.js'; * const aq = new ISPU(); * const all = await aq.all(); * const jkt = await aq.search('jakarta'); * const rank = await aq.ranking({ limit: 15, provinsi: 'Kalimantan' }); * const one = await aq.station('PALANGKARAYA'); * * ── CLI ───────────────────────────────────────────────────── * node ispu.js summary * node ispu.js ranking --limit=20 --provinsi="Jawa Barat" * node ispu.js search --q=bandung * node ispu.js station --id=PALANGKARAYA * node ispu.js provinsi --name="DKI Jakarta" * node ispu.js stations * node ispu.js list-provinsi */const API_BASE = 'https://ispu.kemenlh.go.id/apiweb';const APP_URL = 'https://ispu.kemenlh.go.id/app/';/** Fallback jika auto-fetch gagal (token publik yang tertanam di frontend). */const FALLBACK_TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJ0eXBlIjoiaXNwdS5rZW1lbmxoLmdvLmlkIiwia2V5IjoiRlB4ZnFSQzlKTzY1ZmpxRiIsInNlY3JldCI6Ijk1M2Q0NzZhLTllMDktNDQzNy05OGQ1LTg4ODQwYmIxOThmMCJ9.bkluNcKYjhgBor57qU04_LTvnKSSULUIJ8B2IOV20Yvx996vfWJReM7S-NKYIyycJ3WiORm8kqorzl0aTLrWCQ';const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';// ─── Helpers ────────────────────────────────────────────────export class ISPUError extends Error { constructor(message, { status, response } = {}) { super(message); this.name = 'ISPUError'; this.status = status ?? null; this.response = response ?? null; }}/** * Kategori ISPU — Permen LHK No. 14 Tahun 2020 */export function ispuCategory(val) { const n = Number(val); if (val == null || Number.isNaN(n) || n < 0) return null; if (n <= 50) return { code: 1, name: 'Baik', nameEn: 'Good', color: '#00e400', range: '0-50', advice: 'Tidak ada dampak kesehatan.', }; if (n <= 100) return { code: 2, name: 'Sedang', nameEn: 'Moderate', color: '#0066ff', range: '51-100', advice: 'Masih dapat diterima; kelompok sensitif waspada.', }; if (n <= 200) return { code: 3, name: 'Tidak Sehat', nameEn: 'Unhealthy', color: '#ffcc00', range: '101-200', advice: 'Kurangi aktivitas di luar, terutama kelompok sensitif.', }; if (n <= 300) return { code: 4, name: 'Sangat Tidak Sehat', nameEn: 'Very Unhealthy', color: '#ff0000', range: '201-300', advice: 'Hindari aktivitas luar ruangan.', }; return { code: 5, name: 'Berbahaya', nameEn: 'Hazardous', color: '#000000', range: '≥301', advice: 'Tetap di dalam ruangan; dampak kesehatan serius.', };}function num(v) { if (v === null || v === undefined || v === '') return null; const n = Number(v); return Number.isNaN(n) ? null : n;}function stripHtml(s) { if (!s) return null; return ( String(s) .replace(/<\/?sub>/gi, '') .replace(/<[^>]+>/g, '') .replace(/ /g, ' ') .trim() || null );}function normalizeCategoryName(raw) { if (!raw) return null; const u = String(raw).toUpperCase(); if (u.includes('BAIK') && !u.includes('TIDAK')) return 'Baik'; if (u.includes('SEDANG')) return 'Sedang'; if (u.includes('SANGAT')) return 'Sangat Tidak Sehat'; if (u.includes('TIDAK SEHAT')) return 'Tidak Sehat'; if (u.includes('BERBAHAYA')) return 'Berbahaya'; return stripHtml(raw);}function tipeLabel(t) { if (t === '0' || t === 0) return 'KLH/BPLH'; if (t === '1' || t === 1) return 'Integrasi'; return t != null ? String(t) : null;}function mapHist(arr) { if (!Array.isArray(arr)) return null; return arr.map((x) => ({ nilai: num(x?.nilai), warna: x?.warna ?? null, }));}function normalizeStation(row, { includeRaw = false } = {}) { if (!row || typeof row !== 'object') return null; const ispu = num(row.val); const catName = normalizeCategoryName(row.cat) || normalizeCategoryName(row.kategori?.nilai) || ispuCategory(ispu)?.name || null; const out = { id: row.id_stasiun ?? null, nama: row.nama ?? null, kota: row.kota ?? null, provinsi: row.provinsi ?? null, kelurahan: row.kelurahan || null, kecamatan: row.kecamatan || null, alamat: row.alamat || null, kawasan: row.p3e || null, lat: num(row.lat), lon: num(row.lon), ispu, kategori: catName, kategoriDetail: ispuCategory(ispu), parameterKritis: stripHtml(row.param), tipe: row.tipe_text || tipeLabel(row.tipe), timezone: row.time_z || 'WIB', waktu: row.waktu || null, waktuText: row.waktu_text || null, maintenance: row.is_maintenance === '1' || row.is_maintenance === 1, autoValidation: row.auto_validation === '1' || row.auto_validation === 1, konsentrasi: { pm10: num(row.a_pm10), pm25: num(row.a_pm25), so2: num(row.a_so2), co: num(row.a_co), o3: num(row.a_o3), no2: num(row.a_no2), hc: num(row.a_hc), }, ispuPerParameter: { pm10: num(row.t_pm10) ?? num(row.pm10), pm25: num(row.t_pm25) ?? num(row.pm25), so2: num(row.t_so2) ?? num(row.so2), co: num(row.t_co) ?? num(row.co), o3: num(row.t_o3) ?? num(row.o3), no2: num(row.t_no2) ?? num(row.no2), hc: num(row.t_hc) ?? num(row.hc), }, }; if (row.waktu_24 || row.pm25_24_rev || row.pm10_24_rev) { out.histori24jam = { waktu: Array.isArray(row.waktu_24) ? row.waktu_24 : null, pm25: mapHist(row.pm25_24_rev), pm10: mapHist(row.pm10_24_rev), }; } if (includeRaw) out.raw = row; return out;}function stripRaw(list) { return list.map(({ raw, ...rest }) => rest);}/** * Ambil JWT dari bundle Angular frontend ISPU. * Cari main.*.js di halaman app, lalu ekstrak string eyJ... */async function fetchTokenFromFrontend(timeout = 15000) { const ac = new AbortController(); const timer = setTimeout(() => ac.abort(), timeout); try { const htmlRes = await fetch(APP_URL, { headers: { Accept: 'text/html', 'User-Agent': UA, }, signal: ac.signal, redirect: 'follow', }); const html = await htmlRes.text(); // main.<hash>.js const mainMatch = html.match(/src="(main\.[a-f0-9]+\.js)"/i) || html.match(/(main\.[a-f0-9]+\.js)/i); if (!mainMatch) { throw new Error('main.js tidak ditemukan di halaman app'); } let mainUrl = mainMatch[1]; if (!mainUrl.startsWith('http')) { mainUrl = new URL(mainUrl, APP_URL).href; } const jsRes = await fetch(mainUrl, { headers: { Accept: '*/*', 'User-Agent': UA }, signal: ac.signal, }); const js = await jsRes.text(); // JWT HS512 yang dipakai Authorization const jwtRe = /eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g; const found = js.match(jwtRe); if (!found || !found.length) { throw new Error('JWT tidak ditemukan di main.js'); } // pilih yang payload-nya mengandung ispu / kemenlh for (const tok of found) { try { const payload = JSON.parse( Buffer.from(tok.split('.')[1], 'base64url').toString('utf8') ); if ( String(payload.type || '').includes('ispu') || String(payload.type || '').includes('kemenlh') ) { return tok; } } catch { /* skip */ } } // fallback: JWT terpanjang (biasanya yang benar) return found.sort((a, b) => b.length - a.length)[0]; } finally { clearTimeout(timer); }}// ─── Client ─────────────────────────────────────────────────export class ISPU { /** * @param {object} [opts] * @param {string} [opts.token] Paksa token (skip auto-fetch) * @param {boolean} [opts.autoToken] default true — ambil dari frontend * @param {number} [opts.timeout] ms * @param {number} [opts.cacheTtl] ms cache data stasiun * @param {number} [opts.tokenTtl] ms cache token (default 6 jam) */ constructor(opts = {}) { this._manualToken = opts.token || process.env.ISPU_TOKEN || null; this.autoToken = opts.autoToken !== false; this.timeout = opts.timeout ?? 20_000; this.cacheTtl = opts.cacheTtl ?? 5 * 60 * 1000; this.tokenTtl = opts.tokenTtl ?? 6 * 60 * 60 * 1000; this._token = this._manualToken; this._tokenAt = this._manualToken ? Date.now() : 0; this._tokenPromise = null; this._cache = null; this._cacheAt = 0; } /** Paksa set token manual */ setToken(token) { this._token = token; this._tokenAt = Date.now(); this._manualToken = token; this._cache = null; } /** * Pastikan ada token valid (auto-fetch jika perlu) */ async ensureToken({ force = false } = {}) { if (this._manualToken && !force) { this._token = this._manualToken; return this._token; } const fresh = this._token && Date.now() - this._tokenAt < this.tokenTtl && !force; if (fresh) return this._token; // dedupe concurrent fetch if (this._tokenPromise) return this._tokenPromise; this._tokenPromise = (async () => { try { if (this.autoToken) { const tok = await fetchTokenFromFrontend(this.timeout); this._token = tok; this._tokenAt = Date.now(); return tok; } } catch (e) { // fallback ke token bawaan if (!this._token) { this._token = FALLBACK_TOKEN; this._tokenAt = Date.now(); } return this._token; } finally { this._tokenPromise = null; } if (!this._token) { this._token = FALLBACK_TOKEN; this._tokenAt = Date.now(); } return this._token; })(); return this._tokenPromise; } /** @private */ async _get(path) { await this.ensureToken(); const url = `${API_BASE}${path.startsWith('/') ? path : `/${path}`}`; const ac = new AbortController(); const t = setTimeout(() => ac.abort(), this.timeout); try { let res = await fetch(url, { method: 'GET', headers: { Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json', Authorization: `Bearer ${this._token}`, Referer: APP_URL, Origin: 'https://ispu.kemenlh.go.id', 'User-Agent': UA, }, signal: ac.signal, }); let body = await res.json().catch(() => ({})); let code = body?.status?.code; // 401 → refresh token sekali lalu retry if ( (res.status === 401 || code === 401) && this.autoToken && !this._manualToken ) { await this.ensureToken({ force: true }); res = await fetch(url, { method: 'GET', headers: { Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json', Authorization: `Bearer ${this._token}`, Referer: APP_URL, Origin: 'https://ispu.kemenlh.go.id', 'User-Agent': UA, }, signal: ac.signal, }); body = await res.json().catch(() => ({})); code = body?.status?.code; } if (res.status === 401 || code === 401) { throw new ISPUError( 'Unauthorized (401) meski sudah refresh token. Coba set ISPU_TOKEN manual.', { status: 401, response: body } ); } if (!res.ok || (code != null && code !== 200)) { throw new ISPUError(body?.status?.desc || `HTTP ${res.status}`, { status: res.status, response: body, }); } return body; } catch (e) { if (e.name === 'AbortError') { throw new ISPUError(`Request timeout (${this.timeout}ms)`); } if (e instanceof ISPUError) throw e; throw new ISPUError(e.message || 'Network error'); } finally { clearTimeout(t); } } // ── Public API ──────────────────────────────────────────── /** * Semua stasiun realtime * @param {{ force?: boolean, includeRaw?: boolean }} [opts] */ async all({ force = false, includeRaw = false } = {}) { const now = Date.now(); if (!force && this._cache && now - this._cacheAt < this.cacheTtl) { const stations = includeRaw ? this._cache.stations : stripRaw(this._cache.stations); return { ...this._cache, stations }; } const data = await this._get('/stasiun'); const stations = (data.rows || []).map((r) => normalizeStation(r, { includeRaw: true }) ); this._cache = { total: data.total ?? stations.length, updated: stations.find((s) => s?.waktu)?.waktu ?? null, stations, source: 'https://ispu.kemenlh.go.id', sourceApi: `${API_BASE}/stasiun`, scrapedAt: new Date().toISOString(), }; this._cacheAt = now; return { ...this._cache, stations: includeRaw ? stations : stripRaw(stations), }; } stations(opts) { return this.all(opts); } /** * Detail stasiun (+ histori 24 jam) * @param {string} id */ async station(id, { includeRaw = false } = {}) { if (!id) throw new ISPUError('id stasiun wajib'); const data = await this._get( `/stasiun/detail/id/${encodeURIComponent(id)}` ); const row = data.rows?.[0] ?? data.row ?? data; const s = normalizeStation(row, { includeRaw }); if (!s) throw new ISPUError(`Stasiun tidak ditemukan: ${id}`); return s; } /** * Cari nama / kota / provinsi / id * @param {string} query */ async search(query, { includeRaw = false } = {}) { const q = String(query ?? '') .toLowerCase() .trim(); if (!q) throw new ISPUError('query wajib'); const all = await this.all({ includeRaw: true }); const matched = all.stations.filter((s) => { const hay = [s.id, s.nama, s.kota, s.provinsi, s.alamat, s.kawasan] .filter(Boolean) .join(' ') .toLowerCase(); return hay.includes(q); }); return { query: q, total: matched.length, updated: all.updated, stations: includeRaw ? matched : stripRaw(matched), scrapedAt: all.scrapedAt, }; } /** * Ranking ISPU tertinggi * @param {{ limit?: number, provinsi?: string, kota?: string }} [opts] */ async ranking({ limit = 20, provinsi, kota } = {}) { let list = (await this.all({ includeRaw: true })).stations.filter( (s) => s.ispu != null && !s.maintenance ); if (provinsi) { const p = provinsi.toLowerCase(); list = list.filter((s) => (s.provinsi || '').toLowerCase().includes(p)); } if (kota) { const k = kota.toLowerCase(); list = list.filter( (s) => (s.kota || '').toLowerCase().includes(k) || (s.nama || '').toLowerCase().includes(k) ); } list.sort((a, b) => (b.ispu || 0) - (a.ispu || 0)); const sliced = list.slice(0, limit); return { total: list.length, limit, filter: { provinsi: provinsi || null, kota: kota || null }, updated: sliced[0]?.waktu || null, stations: stripRaw(sliced.map((s, i) => ({ rank: i + 1, ...s }))), scrapedAt: new Date().toISOString(), }; } async byProvinsi(name) { if (!name) throw new ISPUError('nama provinsi wajib'); const p = name.toLowerCase(); const all = await this.all({ includeRaw: true }); const stations = all.stations.filter((s) => (s.provinsi || '').toLowerCase().includes(p) ); return { provinsi: name, total: stations.length, updated: all.updated, stations: stripRaw(stations), scrapedAt: all.scrapedAt, }; } async byKota(name) { if (!name) throw new ISPUError('nama kota wajib'); const k = name.toLowerCase(); const all = await this.all({ includeRaw: true }); const stations = all.stations.filter( (s) => (s.kota || '').toLowerCase().includes(k) || (s.nama || '').toLowerCase().includes(k) ); return { kota: name, total: stations.length, updated: all.updated, stations: stripRaw(stations), scrapedAt: all.scrapedAt, }; } async listProvinsi() { const all = await this.all({ includeRaw: true }); const map = new Map(); for (const s of all.stations) { const key = s.provinsi || 'Tidak diketahui'; if (!map.has(key)) { map.set(key, { provinsi: key, count: 0, ispuSum: 0, ispuN: 0, stations: [], }); } const g = map.get(key); g.count++; if (s.ispu != null) { g.ispuSum += s.ispu; g.ispuN++; } g.stations.push({ id: s.id, nama: s.nama, kota: s.kota, ispu: s.ispu, kategori: s.kategori, }); } const list = [...map.values()] .map((g) => ({ provinsi: g.provinsi, jumlahStasiun: g.count, ispuRataRata: g.ispuN ? Math.round(g.ispuSum / g.ispuN) : null, stations: g.stations.sort((a, b) => (b.ispu || 0) - (a.ispu || 0)), })) .sort((a, b) => (b.ispuRataRata || 0) - (a.ispuRataRata || 0)); return { total: list.length, updated: all.updated, provinsi: list, scrapedAt: all.scrapedAt, }; } async summary() { const all = await this.all({ includeRaw: true }); const active = all.stations.filter((s) => s.ispu != null && !s.maintenance); const sorted = [...active].sort((a, b) => (b.ispu || 0) - (a.ispu || 0)); const counts = { Baik: 0, Sedang: 0, 'Tidak Sehat': 0, 'Sangat Tidak Sehat': 0, Berbahaya: 0, }; for (const s of active) { const k = s.kategori; if (k && counts[k] !== undefined) counts[k]++; } const pick = (s, i) => ({ rank: i + 1, id: s.id, nama: s.nama, kota: s.kota, provinsi: s.provinsi, ispu: s.ispu, kategori: s.kategori, parameterKritis: s.parameterKritis, lat: s.lat, lon: s.lon, }); return { total: active.length, updated: all.updated, kategoriCount: counts, topPolusi: sorted.slice(0, 10).map(pick), topBersih: [...sorted].reverse().slice(0, 10).map(pick), source: all.source, scrapedAt: all.scrapedAt, }; }}export default ISPU;// ═══════════════════════════════════════════════════════════// CLI// ═══════════════════════════════════════════════════════════function parseArgs(argv) { const args = { _: [] }; for (let i = 0; i < argv.length; i++) { const a = argv[i]; if (a.startsWith('--')) { const eq = a.indexOf('='); if (eq > -1) { args[a.slice(2, eq)] = a.slice(eq + 1); } else { const key = a.slice(2); const next = argv[i + 1]; if (next && !next.startsWith('-')) { args[key] = next; i++; } else { args[key] = true; } } } else { args._.push(a); } } return args;}function help() { console.log(`ISPU Indonesia — kualitas udara resmi (KemenLH/BPLH)====================================================Token diambil otomatis dari website (tidak perlu manual).Commands: summary Ringkasan nasional stations [--force] Semua stasiun station --id=PALANGKARAYA Detail + histori 24 jam search --q=jakarta Cari nama/kota/provinsi ranking [--limit=20] [--provinsi=...] [--kota=...] provinsi --name="DKI Jakarta" Filter provinsi kota --name=Bandung Filter kota list-provinsi Daftar provinsi + rata-rata ISPUOptions: --token=JWT Paksa token (skip auto) --force Abaikan cache data --help, -hEnv: ISPU_TOKEN (opsional)Examples: node ispu.js summary node ispu.js ranking --limit=10 --provinsi="Kalimantan Tengah" node ispu.js search --q=surabaya node ispu.js station --id=PALANGKARAYA node ispu.js list-provinsiSumber: https://ispu.kemenlh.go.id`);}async function main() { const args = parseArgs(process.argv.slice(2)); const cmd = args._[0]; if (!cmd || args.help || args.h) { help(); process.exit(cmd ? 0 : 1); } const client = new ISPU({ token: args.token || undefined }); let result; try { switch (cmd) { case 'summary': case 'ringkas': result = await client.summary(); break; case 'stations': case 'stasiun': case 'all': result = await client.all({ force: !!args.force }); break; case 'station': case 'detail': if (!args.id) throw new ISPUError('--id wajib'); result = await client.station(args.id); break; case 'search': case 'cari': if (!args.q && !args.query) throw new ISPUError('--q wajib'); result = await client.search(args.q || args.query); break; case 'ranking': result = await client.ranking({ limit: args.limit ? Number(args.limit) : 20, provinsi: args.provinsi, kota: args.kota, }); break; case 'provinsi': if (!args.name && !args.nama) throw new ISPUError('--name wajib'); result = await client.byProvinsi(args.name || args.nama); break; case 'kota': if (!args.name && !args.nama) throw new ISPUError('--name wajib'); result = await client.byKota(args.name || args.nama); break; case 'list-provinsi': case 'list_provinsi': result = await client.listProvinsi(); break; default: console.error('Unknown command: ' + cmd); help(); process.exit(1); } console.log(JSON.stringify(result, null, 2)); } catch (err) { console.error( JSON.stringify( { error: true, message: err.message, status: err.status ?? null }, null, 2 ) ); process.exit(1); }}const isMain = typeof process !== 'undefined' && process.argv[1] && (process.argv[1].endsWith('ispu.js') || process.argv[1].endsWith('/ispu'));if (isMain) main();README.mdmarkdown · 156 baris
example-output.jsonjson · 2041 baris
$ node ispu.js summary{ "total": 117, "updated": "2026-10-06 20:00:00", "kategoriCount": { "Baik": 33, "Sedang": 46, "Tidak Sehat": 16, "Sangat Tidak Sehat": 6, "Berbahaya": 16 }, "topPolusi": [ { "rank": 1, "id": "KABUPATEN_GUNUNG_MAS", "nama": "Kabupaten Gunung Mas", "kota": "Kabupaten Gunung Mas", "provinsi": "Kalimantan Tengah", "ispu": 1531, "kategori": "Berbahaya", "parameterKritis": "PM10", "lat": -1.1311999559402466, "lon": 113.85700225830078 }, { "rank": 2, "id": "KABUPATEN_KOTAWARINGINTIMUR", "nama": "Kabupaten Kotawaringin Timur", "kota": "Kabupaten Kotawaringin Timur", "provinsi": "Kalimantan Tengah", "ispu": 1308, "kategori": "Berbahaya", "parameterKritis": "PM10", "lat": -2.535559892654419, "lon": 112.87799835205078 }, { "rank": 3, "id": "PALANGKARAYA", "nama": "Palangkaraya Jekan Raya", "kota": "Palangkaraya", "provinsi": "Kalimantan Tengah", "ispu": 1299, "kategori": "Berbahaya", "parameterKritis": "PM2.5", "lat": -2.2215399742126465, "lon": 113.86000061035156 }, { "rank": 4, "id": "KABUPATEN_BARITOSELATAN", "nama": "Kabupaten Barito Selatan Sanggu", "kota": "Kabupaten Barito Selatan", "provinsi": "Kalimantan Tengah", "ispu": 1000, "kategori": "Berbahaya", "parameterKritis": "PM10", "lat": -1.6793400049209595, "lon": 114.90799713134766 }, { "rank": 5, "id": "KABUPATEN_MURUNG_RAYA", "nama": "Kabupaten Murung Raya", "kota": "Kabupaten Murung Raya", "provinsi": "Kalimantan Tengah", "ispu": 790, "kategori": "Berbahaya", "parameterKritis": "PM2.5", "lat": -0.6390770077705383, "lon": 114.53700256347656 }, { "rank": 6, "id": "PALEMBANG", "nama": "Palembang Bukit Kecil", "kota": "Palembang", "provinsi": "Sumatera Selatan", "ispu": 717, "kategori": "Berbahaya", "parameterKritis": "PM10", "lat": -2.9803900718688965, "lon": 104.74700164794922 }, { "rank": 7, "id": "PONTIANAK", "nama": "Pontianak Tenggara", "kota": "Pontianak", "provinsi": "Kalimantan Barat", "ispu": 575, "kategori": "Berbahaya", "parameterKritis": "PM10", "lat": -0.03109000064432621, "lon": 109.3219985961914 }, { "rank": 8, "id": "KABUPATEN_BARITO_KUALA", "nama": "Kabupaten Barito Kuala", "kota": "Kabupaten Barito Kuala", "provinsi": "Kalimantan Selatan", "ispu": 549, "kategori": "Berbahaya", "parameterKritis": "PM10", "lat": -2.9836199283599854, "lon": 114.73300170898438 }, { "rank": 9, "id": "KABUPATEN_KAMPAR", "nama": "Kabupaten Kampar Bangkinang", "kota": "Kabupaten Kampar", "provinsi": "Riau", "ispu": 429, "kategori": "Berbahaya", "parameterKritis": "PM2.5", "lat": 0.3249180018901825, "lon": 101.0250015258789 }, { "rank": 10, "id": "MOBILE_AQMS_BPBD_KALTENG", "nama": "Mobile AQMS BPBD Kalteng...", "kota": "Palangkaraya", "provinsi": "Kalimantan Tengah", "ispu": 416, "kategori": "Berbahaya", "parameterKritis": "PM2.5", "lat": -2.1697499752044678, "lon": 113.86100006103516 } ], "topBersih": [ { "rank": 1, "id": "KENDARI", "nama": "Kendari Mandonga", "kota": "Kendari", "provinsi": "Sulawesi Tenggara", "ispu": 2, "kategori": "Baik", "parameterKritis": "PM2.5", "lat": -3.9755001068115234, "lon": 122.5199966430664 }, { "rank": 2, "id": "KABUPATEN_BANYUWANGI", "nama": "Kabupaten Banyuwangi Mojopanggung", "kota": "Kabupaten Banyuwangi", "provinsi": "Jawa Timur", "ispu": 11, "kategori": "Baik", "parameterKritis": "O3", "lat": -8.211389541625977, "lon": 114.35099792480469 }, { "rank": 3, "id": "KABUPATEN_ACEH_TENGAH", "nama": "Kabupaten Aceh Tengah", "kota": "Kabupaten Aceh Tengah", "provinsi": "Aceh", "ispu": 11, "kategori": "Baik", "parameterKritis": "PM2.5", "lat": 4.624090194702148, "lon": 96.84210205078125 }, { "rank": 4, "id": "KABUPATEN_TANGERANGCG", "nama": "Kabupaten Tangerang Curug", "kota": "Tangerang", "provinsi": "Banten", "ispu": 21, "kategori": "Baik", "parameterKritis": "SO2", "lat": -6.2477898597717285, "lon": 106.56300354003906 }, { "rank": 5, "id": "KABUPATEN_BOGOR", "nama": "Kabupaten Bogor Tegar Beriman", "kota": "Kabupaten Bogor", "provinsi": "Jawa Barat", "ispu": 22, "kategori": "Baik", "parameterKritis": "O3", "lat": -6.481369972229004, "lon": 106.8290023803711 }, { "rank": 6, "id": "DLH_MEDAN_02", "nama": "DLH MEDAN 02", "kota": "Medan", "provinsi": "Sumatera Utara", "ispu": 22, "kategori": "Baik", "parameterKritis": "SO2,CO", "lat": 3.652869939804077, "lon": 98.66580200195312 }, { "rank": 7, "id": "TERNATE", "nama": "Ternate Marikurubu", "kota": "Ternate", "provinsi": "Maluku Utara", "ispu": 24, "kategori": "Baik", "parameterKritis": "PM2.5", "lat": 0.7872629761695862, "lon": 127.3740005493164 }, { "rank": 8, "id": "KABUPATEN_LUMAJANG", "nama": "Kabupaten Lumajang Ditrotunan", "kota": "Kabupaten Lumajang", "provinsi": "Jawa Timur", "ispu": 26, "kategori": "Baik", "parameterKritis": "O3", "lat": -8.135390281677246, "lon": 113.2239990234375 }, { "rank": 9, "id": "MALANG", "nama": "Malang Taman Riau", "kota": "Malang", "provinsi": "Jawa Timur", "ispu": 27, "kategori": "Baik", "parameterKritis": "PM2.5", "lat": -7.991559982299805, "lon": 112.62899780273438 }, { "rank": 10, "id": "KOTA_MOJOKERTO", "nama": "Kota Mojokerto Magersari", "kota": "Mojokerto", "provinsi": "Jawa Timur", "ispu": 28, "kategori": "Baik", "parameterKritis": "PM2.5", "lat": -7.4712300300598145, "lon": 112.44100189208984 } ], "source": "https://ispu.kemenlh.go.id", "scrapedAt": "2026-10-06T13:27:00.440Z"}$ node ispu.js search --q=jakarta{ "query": "jakarta", "total": 7, "updated": "2026-10-06 20:00:00", "stations": [ { "id": "DKI1", "nama": "DKI 1 Bundaran HI", "kota": "Jakarta", "provinsi": "DKI Jakarta", "kelurahan": null, "kecamatan": null, "alamat": "Jl. M.H. Thamrin No.1, RT.1/RW.5, Menteng, Kec. Menteng, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10310", "kawasan": "Jawa", "lat": -6.194789886474609, "lon": 106.822998046875, "ispu": 112, "kategori": "Tidak Sehat", "kategoriDetail": { "code": 3, "name": "Tidak Sehat", "nameEn": "Unhealthy", "color": "#ffcc00", "range": "101-200", "advice": "Kurangi aktivitas di luar, terutama kelompok sensitif." }, "parameterKritis": "PM2.5", "tipe": "INTEGRASI", "timezone": "WIB", "waktu": "2026-10-06 18:00:00", "waktuText": "06 Oktober 2026 18:00:00", "maintenance": false, "autoValidation": false, "konsentrasi": { "pm10": 0, "pm25": 0, "so2": 0, "co": 0, "o3": 0, "no2": 0, "hc": 0 }, "ispuPerParameter": { "pm10": 70, "pm25": 112, "so2": 44, "co": 13, "o3": 16, "no2": 75, "hc": 0 } }, { "id": "DKI4", "nama": "DKI 4 Lubang Buaya", "kota": "Jakarta", "provinsi": "DKI Jakarta", "kelurahan": null, "kecamatan": null, "alamat": "4, Jl. Monumen Pancasila Sakti No.1, RT.4/RW.9, Lubang Buaya, Kec. Cipayung, Kota Jakarta Timur, Daerah Khusus Ibukota Jakarta 13810", "kawasan": "Jawa", "lat": -6.290359973907471, "lon": 106.90499877929688, "ispu": 107, "kategori": "Tidak Sehat", "kategoriDetail": { "code": 3, "name": "Tidak Sehat", "nameEn": "Unhealthy", "color": "#ffcc00", "range": "101-200", "advice": "Kurangi aktivitas di luar, terutama kelompok sensitif." }, "parameterKritis": "PM2.5", "tipe": "INTEGRASI", "timezone": "WIB", "waktu": "2026-10-06 18:00:00", "waktuText": "06 Oktober 2026 18:00:00", "maintenance": false, "autoValidation": false, "konsentrasi": { "pm10": 0, "pm25": 0, "so2": 0, "co": 0, "o3": 0, "no2": 0, "hc": 0 }, "ispuPerParameter": { "pm10": 69, "pm25": 107, "so2": 26, "co": 10, "o3": 23, "no2": 21, "hc": 0 } }, { "id": "DKI5", "nama": "DKI 5 Kebon Jeruk", "kota": "Jakarta", "provinsi": "DKI Jakarta", "kelurahan": null, "kecamatan": null, "alamat": "No.5-, Jl. Jeruk Kuning Blok J-7 No.6, RT.2/RW.12, Srengseng, Kec. Kembangan, Kota Jakarta Barat, Daerah Khusus Ibukota Jakarta 11630", "kawasan": "Jawa", "lat": -6.207409858703613, "lon": 106.75, "ispu": 105, "kategori": "Tidak Sehat", "kategoriDetail": { "code": 3, "name": "Tidak Sehat", "nameEn": "Unhealthy", "color": "#ffcc00", "range": "101-200", "advice": "Kurangi aktivitas di luar, terutama kelompok sensitif." }, "parameterKritis": "PM2.5", "tipe": "INTEGRASI", "timezone": "WIB", "waktu": "2026-10-06 18:00:00", "waktuText": "06 Oktober 2026 18:00:00", "maintenance": false, "autoValidation": false, "konsentrasi": { "pm10": 0, "pm25": 0, "so2": 0, "co": 0, "o3": 0, "no2": 0, "hc": 0 }, "ispuPerParameter": { "pm10": 57, "pm25": 105, "so2": 35, "co": 10, "o3": 57, "no2": 8, "hc": 0 } }, { "id": "DKI2", "nama": "DKI 2 Kelapa Gading", "kota": "Jakarta", "provinsi": "DKI Jakarta", "kelurahan": null, "kecamatan": null, "alamat": "Jl. Boulevard Raya, RT.11/RW.18, Klp. Gading Tim., Kec. Klp. Gading, Kota Jkt Utara, Daerah Khusus Ibukota Jakarta 14240", "kawasan": "Jawa", "lat": -6.15880012512207, "lon": 106.90399932861328, "ispu": 88, "kategori": "Sedang", "kategoriDetail": { "code": 2, "name": "Sedang", "nameEn": "Moderate", "color": "#0066ff", "range": "51-100", "advice": "Masih dapat diterima; kelompok sensitif waspada." }, "parameterKritis": "PM2.5", "tipe": "INTEGRASI", "timezone": "WIB", "waktu": "2026-10-06 18:00:00", "waktuText": "06 Oktober 2026 18:00:00", "maintenance": false, "autoValidation": false, "konsentrasi": { "pm10": 0, "pm25": 0, "so2": 0, "co": 0, "o3": 0, "no2": 0, "hc": 0 }, "ispuPerParameter": { "pm10": 0, "pm25": 88, "so2": 15, "co": 7, "o3": 28, "no2": 23, "hc": 0 } }, { "id": "DKI3", "nama": "DKI 3 Jagakarsa", "kota": "Jakarta", "provinsi": "DKI Jakarta", "kelurahan": null, "kecamatan": null, "alamat": "Jl. Purwa II No.9, RT.6/RW.6, Cipedak, Kec. Jagakarsa, Kota Jakarta Selatan, Daerah Khusus Ibukota Jakarta 12630", "kawasan": "Jawa", "lat": -6.3577799797058105, "lon": 106.8030014038086, "ispu": 86, "kategori": "Sedang", "kategoriDetail": { "code": 2, "name": "Sedang", "nameEn": "Moderate", "color": "#0066ff", "range": "51-100", "advice": "Masih dapat diterima; kelompok sensitif waspada." }, "parameterKritis": "PM2.5", "tipe": "INTEGRASI", "timezone": "WIB", "waktu": "2026-10-06 18:00:00", "waktuText": "06 Oktober 2026 18:00:00", "maintenance": false, "autoValidation": false, "konsentrasi": { "pm10": 0, "pm25": 0, "so2": 0, "co": 0, "o3": 0, "no2": 0, "hc": 0 }, "ispuPerParameter": { "pm10": 46, "pm25": 86, "so2": 44, "co": 11, "o3": 25, "no2": 25, "hc": 0 } }, { "id": "JAKARTA", "nama": "Jakarta GBK", "kota": "Jakarta", "provinsi": "DKI Jakarta", "kelurahan": null, "kecamatan": "Tanah Abang", "alamat": "Lapangan ABC, Pintu 10 Gelora Bung Karno, Kota Jakarta Pusat", "kawasan": "Jawa", "lat": -6.2154998779296875, "lon": 106.8030014038086, "ispu": 55, "kategori": "Sedang", "kategoriDetail": { "code": 2, "name": "Sedang", "nameEn": "Moderate", "color": "#0066ff", "range": "51-100", "advice": "Masih dapat diterima; kelompok sensitif waspada." }, "parameterKritis": "PM2.5", "tipe": "KLH/BPLH", "timezone": "WIB", "waktu": "2026-10-06 20:00:00", "waktuText": "06 Oktober 2026 20:00:00", "maintenance": false, "autoValidation": true, "konsentrasi": { "pm10": 28.79, "pm25": 19.23, "so2": 1.04, "co": 47.1, "o3": 116.48, "no2": 43.27, "hc": 0.69 }, "ispuPerParameter": { "pm10": 57, "pm25": 55, "so2": 1, "co": 1, "o3": 77, "no2": 72, "hc": 0 } }, { "id": "KBN_MARUNDA", "nama": "KBN Marunda Jakarta Utara", "kota": "Jakarta Utara", "provinsi": "DKI Jakarta", "kelurahan": "Marunda", "kecamatan": "Cilincing", "alamat": "SBU Kawasan Marunda PT Kawasan Berikat Nusantara, Jl. Lampung No.1, RT.6/RW.2, Marunda, Kec. Cilincing, Jakarta Utara", "kawasan": "Jawa", "lat": -6.108980178833008, "lon": 106.94400024414062, "ispu": 47, "kategori": "Baik", "kategoriDetail": { "code": 1, "name": "Baik", "nameEn": "Good", "color": "#00e400", "range": "0-50", "advice": "Tidak ada dampak kesehatan." }, "parameterKritis": "SO2", "tipe": "INTEGRASI", "timezone": "WIB", "waktu": "2026-10-06 20:00:00", "waktuText": "06 Oktober 2026 20:00:00", "maintenance": false, "autoValidation": true, "konsentrasi": { "pm10": 19.55, "pm25": 11.42, "so2": 48.46, "co": 866.06, "o3": 100.17, "no2": 0, "hc": 0 }, "ispuPerParameter": { "pm10": 47, "pm25": 38, "so2": 53, "co": 22, "o3": 66, "no2": 0, "hc": 0 } } ], "scrapedAt": "2026-10-06T13:27:02.673Z"}$ node ispu.js ranking --provinsi="Jawa Barat"{ "total": 9, "limit": 20, "filter": { "provinsi": "Jawa Barat", "kota": null }, "updated": "2026-10-06 20:00:00", "stations": [ { "rank": 1, "id": "KABUPATEN_SUKABUMI", "nama": "Kabupaten Sukabumi Pelabuhanratu", "kota": "Kabupaten Sukabumi", "provinsi": "Jawa Barat", "kelurahan": "Pelabuhanratu", "kecamatan": "Pelabuhanratu", "alamat": "Taman Kota Cangehgar Palabuhanratu, Jl. Jend. Ahmad Yani, Kelurahan Palabuhanratu, Kecamatan Palabuh, Kabupaten Sukabumi, Jawa Barat", "kawasan": "Jawa", "lat": -6.992879867553711, "lon": 106.55899810791016, "ispu": 86, "kategori": "Sedang", "kategoriDetail": { "code": 2, "name": "Sedang", "nameEn": "Moderate", "color": "#0066ff", "range": "51-100", "advice": "Masih dapat diterima; kelompok sensitif waspada." }, "parameterKritis": "PM2.5", "tipe": "KLH/BPLH", "timezone": "WIB", "waktu": "2026-10-06 20:00:00", "waktuText": "06 Oktober 2026 20:00:00", "maintenance": false, "autoValidation": true, "konsentrasi": { "pm10": 104.81, "pm25": 44.42, "so2": 0.01, "co": 0.01, "o3": 0.01, "no2": 0.01, "hc": 0.01 }, "ispuPerParameter": { "pm10": 130, "pm25": 87, "so2": 0, "co": 0, "o3": 0, "no2": 0, "hc": 0 } }, { "rank": 2, "id": "KABUPATEN_BOGORLL", "nama": "Kabupaten Bogor Leuwiliang", "kota": "Bogor", "provinsi": "Jawa Barat", "kelurahan": "Leuwiliang", "kecamatan": "Leuwiliang", "alamat": "Kantor Kecamatan Leuwiliang,\r\nKec. Leuwiliang, Kabupaten\r\nBogor, Jawa Barat 16640\r\n", "kawasan": "Jawa", "lat": -6.576610088348389, "lon": 106.63700103759766, "ispu": 83, "kategori": "Sedang", "kategoriDetail": { "code": 2, "name": "Sedang", "nameEn": "Moderate", "color": "#0066ff", "range": "51-100", "advice": "Masih dapat diterima; kelompok sensitif waspada." }, "parameterKritis": "O3", "tipe": "KLH/BPLH", "timezone": "WIB", "waktu": "2026-10-06 20:00:00", "waktuText": "06 Oktober 2026 20:00:00", "maintenance": false, "autoValidation": true, "konsentrasi": { "pm10": 43.45, "pm25": 24.15, "so2": 19.75, "co": 1666.26, "o3": 195.64, "no2": 8.96, "hc": 0.01 }, "ispuPerParameter": { "pm10": 71, "pm25": 61, "so2": 23, "co": 42, "o3": 143, "no2": 17, "hc": 0 } }, { "rank": 3, "id": "BEKASI", "nama": "Bekasi Kayuringin", "kota": "Bekasi", "provinsi": "Jawa Barat", "kelurahan": "Kayuringin Jaya", "kecamatan": "Bekasi Selatan", "alamat": "Stadium Patriot Candrabaga, Kayuringin Jaya, Bekasi Selatan, Kota Bekasi, Jawa Barat 17144, Kota Bekasi, Propinsi Jawa Barat", "kawasan": "Jawa", "lat": -6.237450122833252, "lon": 106.99299621582031, "ispu": 79, "kategori": "Sedang", "kategoriDetail": { "code": 2, "name": "Sedang", "nameEn": "Moderate", "color": "#0066ff", "range": "51-100", "advice": "Masih dapat diterima; kelompok sensitif waspada." }, "parameterKritis": "PM2.5", "tipe": "KLH/BPLH", "timezone": "WIB", "waktu": "2026-10-06 20:00:00", "waktuText": "06 Oktober 2026 20:00:00", "maintenance": false, "autoValidation": true, "konsentrasi": { "pm10": 58.38, "pm25": 38.27, "so2": 25.62, "co": 425.98, "o3": 15.48, "no2": 106.39, "hc": 7.25 }, "ispuPerParameter": { "pm10": 85, "pm25": 79, "so2": 30, "co": 11, "o3": 10, "no2": 114, "hc": 5 } }, { "rank": 4, "id": "BOGOR", "nama": "Kota Bogor Tanah Sereal", "kota": "Bogor", "provinsi": "Jawa Barat", "kelurahan": null, "kecamatan": null, "alamat": "Jl. Pemuda No.25, RT.01/RW.06, Tanah Sareal, Kec. Tanah Sereal, Kota Bogor, Jawa Barat 16161", "kawasan": "Jawa", "lat": -6.572700023651123, "lon": 106.79199981689453, "ispu": 68, "kategori": "Sedang", "kategoriDetail": { "code": 2, "name": "Sedang", "nameEn": "Moderate", "color": "#0066ff", "range": "51-100", "advice": "Masih dapat diterima; kelompok sensitif waspada." }, "parameterKritis": "SO2", "tipe": "KLH/BPLH", "timezone": "WIB", "waktu": "2026-10-06 20:00:00", "waktuText": "06 Oktober 2026 20:00:00", "maintenance": false, "autoValidation": true, "konsentrasi": { "pm10": 0, "pm25": 0, "so2": 97.23, "co": 208.29, "o3": 56.79, "no2": 14.5, "hc": null }, "ispuPerParameter": { "pm10": 0, "pm25": 0, "so2": 75, "co": 5, "o3": 37, "no2": 28, "hc": null } }, { "rank": 5, "id": "KABUPATEN_INDRAMAYU", "nama": "Kabupaten Indramayu Margadadi", "kota": "Indramayu", "provinsi": "Jawa Barat", "kelurahan": "Margadadi", "kecamatan": "Indramayu", "alamat": "Lingkungan Masjid Agung\r\nIndramayu\r\nDekat Jl. Letjen Sutoyo,\r\nMargadadi, Kec. Indramayu,\r\nKabupaten Indramayu, Jawa\r\nBarat 45212\r\n", "kawasan": "Jawa", "lat": -6.327040195465088, "lon": 108.3219985961914, "ispu": 58, "kategori": "Sedang", "kategoriDetail": { "code": 2, "name": "Sedang", "nameEn": "Moderate", "color": "#0066ff", "range": "51-100", "advice": "Masih dapat diterima; kelompok sensitif waspada." }, "parameterKritis": "PM2.5", "tipe": "KLH/BPLH", "timezone": "WIB", "waktu": "2026-10-06 20:00:00", "waktuText": "06 Oktober 2026 20:00:00", "maintenance": false, "autoValidation": true, "konsentrasi": { "pm10": 28.2, "pm25": 22.07, "so2": 13.75, "co": 3296.76, "o3": 43.34, "no2": 14.15, "hc": 1.04 }, "ispuPerParameter": { "pm10": 57, "pm25": 59, "so2": 16, "co": 82, "o3": 29, "no2": 27, "hc": 1 } }, { "rank": 6, "id": "KABUPATEN_BANDUNGBB", "nama": "Kabupaten Bandung Barat Saguling", "kota": "Bandung Barat", "provinsi": "Jawa Barat", "kelurahan": "Bojonghaleuang", "kecamatan": "Saguling", "alamat": "Masjid As Sidiq, Kelurahan\r\nBojonghaleuang, Kec. Saguling,\r\nKabupaten Bandung Barat 40561", "kawasan": "Jawa", "lat": -6.839240074157715, "lon": 107.51100158691406, "ispu": 57, "kategori": "Sedang", "kategoriDetail": { "code": 2, "name": "Sedang", "nameEn": "Moderate", "color": "#0066ff", "range": "51-100", "advice": "Masih dapat diterima; kelompok sensitif waspada." }, "parameterKritis": "PM2.5", "tipe": "KLH/BPLH", "timezone": "WIB", "waktu": "2026-10-06 20:00:00", "waktuText": "06 Oktober 2026 20:00:00", "maintenance": false, "autoValidation": true, "konsentrasi": { "pm10": 39.44, "pm25": 21.28, "so2": 8.1, "co": 2306.64, "o3": 43.12, "no2": 55.34, "hc": 4.29 }, "ispuPerParameter": { "pm10": 67, "pm25": 58, "so2": 9, "co": 58, "o3": 28, "no2": 88, "hc": 3 } }, { "rank": 7, "id": "KABUPATEN_PURWAKARTA", "nama": "Kabupaten Purwakarta Purnawarman", "kota": "Kabupaten Purwakarta", "provinsi": "Jawa Barat", "kelurahan": "Purwamekar", "kecamatan": "Purwakarta", "alamat": "Jl. Purnawarman Timur, Sindangkasih, Kec. Purwakarta", "kawasan": "Jawa", "lat": -6.564449787139893, "lon": 107.44599914550781, "ispu": 48, "kategori": "Baik", "kategoriDetail": { "code": 1, "name": "Baik", "nameEn": "Good", "color": "#00e400", "range": "0-50", "advice": "Tidak ada dampak kesehatan." }, "parameterKritis": "PM2.5", "tipe": "KLH/BPLH", "timezone": "WIB", "waktu": "2026-10-06 20:00:00", "waktuText": "06 Oktober 2026 20:00:00", "maintenance": false, "autoValidation": true, "konsentrasi": { "pm10": 18.71, "pm25": 14.94, "so2": 9.26, "co": 243.93, "o3": 30.65, "no2": 9.44, "hc": 5.23 }, "ispuPerParameter": { "pm10": 45, "pm25": 50, "so2": 11, "co": 6, "o3": 20, "no2": 18, "hc": 4 } }, { "rank": 8, "id": "KABUPATEN_BEKASI", "nama": "Kabupaten Bekasi Sukamahi", "kota": "Kabupaten Bekasi", "provinsi": "Jawa Barat", "kelurahan": null, "kecamatan": null, "alamat": " Sukamahi, Kec. Cikarang Pusat, Kabupaten Bekasi, Jawa Barat 17530", "kawasan": "Jawa", "lat": -6.363800048828125, "lon": 107.1719970703125, "ispu": 40, "kategori": "Baik", "kategoriDetail": { "code": 1, "name": "Baik", "nameEn": "Good", "color": "#00e400", "range": "0-50", "advice": "Tidak ada dampak kesehatan." }, "parameterKritis": "PM10", "tipe": "KLH/BPLH", "timezone": "WIB", "waktu": "2026-10-06 20:00:00", "waktuText": "06 Oktober 2026 20:00:00", "maintenance": false, "autoValidation": true, "konsentrasi": { "pm10": 39.83, "pm25": 7.77, "so2": 4, "co": 1787.73, "o3": 87.29, "no2": 6.12, "hc": null }, "ispuPerParameter": { "pm10": 67, "pm25": 26, "so2": 5, "co": 45, "o3": 58, "no2": 12, "hc": null } }, { "rank": 9, "id": "KABUPATEN_BOGOR", "nama": "Kabupaten Bogor Tegar Beriman", "kota": "Kabupaten Bogor", "provinsi": "Jawa Barat", "kelurahan": null, "kecamatan": null, "alamat": " Jl. Tegar Beriman, Tengah, Kec. Cibinong, Kabupaten Bogor, Jawa Barat 16914", "kawasan": "Jawa", "lat": -6.481369972229004, "lon": 106.8290023803711, "ispu": 22, "kategori": "Baik", "kategoriDetail": { "code": 1, "name": "Baik", "nameEn": "Good", "color": "#00e400", "range": "0-50", "advice": "Tidak ada dampak kesehatan." }, "parameterKritis": "O3", "tipe": "KLH/BPLH", "timezone": "WIB", "waktu": "2026-10-06 20:00:00", "waktuText": "06 Oktober 2026 20:00:00", "maintenance": false, "autoValidation": true, "konsentrasi": { "pm10": 0, "pm25": 0, "so2": 3, "co": 1298, "o3": 54, "no2": null, "hc": 1 }, "ispuPerParameter": { "pm10": 0, "pm25": 0, "so2": 3, "co": 32, "o3": 36, "no2": null, "hc": 1 } } ], "scrapedAt": "2026-10-06T13:27:07.736Z"}$ node ispu.js station --id=DKI1{ "id": "DKI1", "nama": "DKI 1 Bundaran HI", "kota": "Jakarta", "provinsi": "DKI Jakarta", "kelurahan": null, "kecamatan": null, "alamat": "Jl. M.H. Thamrin No.1, RT.1/RW.5, Menteng, Kec. Menteng, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10310", "kawasan": "Jawa", "lat": -6.194789886474609, "lon": 106.822998046875, "ispu": 112, "kategori": "Tidak Sehat", "kategoriDetail": { "code": 3, "name": "Tidak Sehat", "nameEn": "Unhealthy", "color": "#ffcc00", "range": "101-200", "advice": "Kurangi aktivitas di luar, terutama kelompok sensitif." }, "parameterKritis": "PM2.5", "tipe": "INTEGRASI", "timezone": "WIB", "waktu": "06 Oktober 2026 18:00:00", "waktuText": null, "maintenance": false, "autoValidation": false, "konsentrasi": { "pm10": 0, "pm25": 0, "so2": 0, "co": 0, "o3": 0, "no2": 0, "hc": 0 }, "ispuPerParameter": { "pm10": 70, "pm25": 112, "so2": 44, "co": 13, "o3": 16, "no2": 75, "hc": 0 }, "histori24jam": { "waktu": [ "2026-10-05 17:00:00", "2026-10-05 18:00:00", "2026-10-05 19:00:00", "2026-10-05 20:00:00", "2026-10-05 21:00:00", "2026-10-05 22:00:00", "2026-10-05 23:00:00", "2026-10-06 00:00:00", "2026-10-06 01:00:00", "2026-10-06 02:00:00", "2026-10-06 03:00:00", "2026-10-06 04:00:00", "2026-10-06 05:00:00", "2026-10-06 06:00:00", "2026-10-06 08:00:00", "2026-10-06 09:00:00", "2026-10-06 10:00:00", "2026-10-06 12:00:00", "2026-10-06 13:00:00", "2026-10-06 14:00:00", "2026-10-06 15:00:00", "2026-10-06 16:00:00", "2026-10-06 17:00:00", "2026-10-06 18:00:00" ], "pm25": [ { "nilai": 106, "warna": "#cccc00" }, { "nilai": 106, "warna": "#cccc00" }, { "nilai": 106, "warna": "#cccc00" }, { "nilai": 106, "warna": "#cccc00" }, { "nilai": 106, "warna": "#cccc00" }, { "nilai": 105, "warna": "#cccc00" }, { "nilai": 105, "warna": "#cccc00" }, { "nilai": 106, "warna": "#cccc00" }, { "nilai": 109, "warna": "#cccc00" }, { "nilai": 110, "warna": "#cccc00" }, { "nilai": 111, "warna": "#cccc00" }, { "nilai": 111, "warna": "#cccc00" }, { "nilai": 111, "warna": "#cccc00" }, { "nilai": 110, "warna": "#cccc00" }, { "nilai": 110, "warna": "#cccc00" }, { "nilai": 111, "warna": "#cccc00" }, { "nilai": 111, "warna": "#cccc00" }, { "nilai": 112, "warna": "#cccc00" }, { "nilai": 112, "warna": "#cccc00" }, { "nilai": 112, "warna": "#cccc00" }, { "nilai": 112, "warna": "#cccc00" }, { "nilai": 111, "warna": "#cccc00" }, { "nilai": 111, "warna": "#cccc00" }, { "nilai": 112, "warna": "#cccc00" } ], "pm10": [ { "nilai": 65, "warna": "#0000cc" }, { "nilai": 65, "warna": "#0000cc" }, { "nilai": 65, "warna": "#0000cc" }, { "nilai": 64, "warna": "#0000cc" }, { "nilai": 64, "warna": "#0000cc" }, { "nilai": 64, "warna": "#0000cc" }, { "nilai": 64, "warna": "#0000cc" }, { "nilai": 64, "warna": "#0000cc" }, { "nilai": 64, "warna": "#0000cc" }, { "nilai": 65, "warna": "#0000cc" }, { "nilai": 65, "warna": "#0000cc" }, { "nilai": 66, "warna": "#0000cc" }, { "nilai": 66, "warna": "#0000cc" }, { "nilai": 67, "warna": "#0000cc" }, { "nilai": 68, "warna": "#0000cc" }, { "nilai": 68, "warna": "#0000cc" }, { "nilai": 68, "warna": "#0000cc" }, { "nilai": 68, "warna": "#0000cc" }, { "nilai": 68, "warna": "#0000cc" }, { "nilai": 68, "warna": "#0000cc" }, { "nilai": 68, "warna": "#0000cc" }, { "nilai": 69, "warna": "#0000cc" }, { "nilai": 69, "warna": "#0000cc" }, { "nilai": 70, "warna": "#0000cc" } ] }}$ node ispu.js list-provinsi{ "total": 27, "updated": "2026-10-06 20:00:00", "provinsi": [ { "provinsi": "Kalimantan Tengah", "jumlahStasiun": 7, "ispuRataRata": 935, "stations": [ { "id": "KABUPATEN_GUNUNG_MAS", "nama": "Kabupaten Gunung Mas", "kota": "Kabupaten Gunung Mas", "ispu": 1531, "kategori": "Berbahaya" }, { "id": "KABUPATEN_KOTAWARINGINTIMUR", "nama": "Kabupaten Kotawaringin Timur", "kota": "Kabupaten Kotawaringin Timur", "ispu": 1308, "kategori": "Berbahaya" }, { "id": "PALANGKARAYA", "nama": "Palangkaraya Jekan Raya", "kota": "Palangkaraya", "ispu": 1299, "kategori": "Berbahaya" }, { "id": "KABUPATEN_BARITOSELATAN", "nama": "Kabupaten Barito Selatan Sanggu", "kota": "Kabupaten Barito Selatan", "ispu": 1000, "kategori": "Berbahaya" }, { "id": "KABUPATEN_MURUNG_RAYA", "nama": "Kabupaten Murung Raya", "kota": "Kabupaten Murung Raya", "ispu": 790, "kategori": "Berbahaya" }, { "id": "MOBILE_AQMS_BPBD_KALTENG", "nama": "Mobile AQMS BPBD Kalteng...", "kota": "Palangkaraya", "ispu": 416, "kategori": "Berbahaya" }, { "id": "KABUPATEN_KAPUAS", "nama": "Kabupaten Kapuas", "kota": "Kabupaten Kapuas", "ispu": 201, "kategori": "Sangat Tidak Sehat" } ] }, { "provinsi": "Jambi", "jumlahStasiun": 4, "ispuRataRata": 324, "stations": [ { "id": "KABUPATEN_MUARO_JAMBI", "nama": "Kabupaten Muaro Jambi", "kota": "Kabupaten Muaro Jambi", "ispu": 411, "kategori": "Berbahaya" }, { "id": "KABUPATEN_TEBO", "nama": "Kabupaten Tebo", "kota": "Kabupaten Tebo", "ispu": 343, "kategori": "Berbahaya" }, { "id": "JAMBI", "nama": "Jambi Paal Lima", "kota": "Jambi", "ispu": 342, "kategori": "Berbahaya" }, { "id": "KABUPATEN_TANJUNGJABUNGTIMUR", "nama": "Kabupaten Tanjabtim Muara Sabak", "kota": "Kabupaten Tanjung Jabung Timur", "ispu": 198, "kategori": "Tidak Sehat" } ] }, { "provinsi": "Kalimantan Selatan", "jumlahStasiun": 5, "ispuRataRata": 272, "stations": [ { "id": "KABUPATEN_BARITO_KUALA", "nama": "Kabupaten Barito Kuala", "kota": "Kabupaten Barito Kuala", "ispu": 549, "kategori": "Berbahaya" }, { "id": "KABUPATEN_TABALONG", "nama": "Kabupaten Tabalong", "kota": "Kabupaten Tabalong", "ispu": 366, "kategori": "Berbahaya" }, { "id": "BANJARMASIN", "nama": "Banjarmasin Kayu Tangi", "kota": "Banjarmasin", "ispu": 175, "kategori": "Tidak Sehat" }, { "id": "KABUPATEN_HSS", "nama": "Kabupaten Hulu Sungai Selatan", "kota": "Kabupaten Hulu Sungai Selatan", "ispu": 174, "kategori": "Tidak Sehat" }, { "id": "KABUPATEN_BANJAR", "nama": "Kabupaten Banjar", "kota": "Kabupaten Banjar", "ispu": 97, "kategori": "Sedang" } ] }, { "provinsi": "Riau", "jumlahStasiun": 9, "ispuRataRata": 217, "stations": [ { "id": "KABUPATEN_KAMPAR", "nama": "Kabupaten Kampar Bangkinang", "kota": "Kabupaten Kampar", "ispu": 429, "kategori": "Berbahaya" }, { "id": "KABUPATEN_SIAK_BUNGARAYA", "nama": "Kabupaten Siak Bunga Raya", "kota": "Kabupaten Siak", "ispu": 326, "kategori": "Berbahaya" }, { "id": "PEKANBARU", "nama": "Pekanbaru Tenayan Raya", "kota": "Pekanbaru", "ispu": 247, "kategori": "Sangat Tidak Sehat" }, { "id": "KABUPATEN_INDRAGIRIHILIR", "nama": "Kabupaten Indragiri Hilir", "kota": "Kabupaten Indragiri Hilir", "ispu": 242, "kategori": "Sangat Tidak Sehat" }, { "id": "KABUPATEN_BENGKALIS", "nama": "Kabupaten Bengkalis Sanggoro", "kota": "Kabupaten Bengkalis", "ispu": 239, "kategori": "Sangat Tidak Sehat" }, { "id": "KABUPATEN_SIAK", "nama": "Siak", "kota": "Siak", "ispu": 182, "kategori": "Tidak Sehat" }, { "id": "KABUPATEN_ROKANHILIR", "nama": "Kabupaten Rokan Hilir Bangko", "kota": "Rokan Hilir", "ispu": 158, "kategori": "Tidak Sehat" }, { "id": "KABUPATEN_PELALAWAN", "nama": "Kabupaten Pelalawan ", "kota": "Kabupaten Pelalawan", "ispu": 74, "kategori": "Sedang" }, { "id": "DUMAI", "nama": "Dumai Bukit Kapur", "kota": "Dumai", "ispu": 59, "kategori": "Sedang" } ] }, { "provinsi": "Sumatera Selatan", "jumlahStasiun": 9, "ispuRataRata": 212, "stations": [ { "id": "PALEMBANG", "nama": "Palembang Bukit Kecil", "kota": "Palembang", "ispu": 717, "kategori": "Berbahaya" }, { "id": "KABUPATEN_PALI", "nama": "Kabupaten Penukal Abab Lematang...", "kota": "Kabupaten Abab Lematang Ilir", "ispu": 290, "kategori": "Sangat Tidak Sehat" }, { "id": "KOTA_PRABUMULIH", "nama": "Kota Prabumulih", "kota": "Prabumulih", "ispu": 204, "kategori": "Sangat Tidak Sehat" }, { "id": "KABUPATEN_MURA", "nama": "Kabupaten Musi Rawas", "kota": "Kabupaten Musi Rawas", "ispu": 181, "kategori": "Tidak Sehat" }, { "id": "KABUPATEN_MUSI_BANYUASIN", "nama": "Kabupaten Musi Banyuasin Serasan...", "kota": "Kabupaten Musi Banyuasin", "ispu": 157, "kategori": "Tidak Sehat" }, { "id": "KABUPATEN_OGAN_ILIR", "nama": "Kabupaten Ogan Ilir", "kota": "Kabupaten Ogan Ilir", "ispu": 140, "kategori": "Tidak Sehat" }, { "id": "KABUPATEN_LAHAT", "nama": "Kabupaten Lahat", "kota": "Kabupaten Lahat", "ispu": 92, "kategori": "Sedang" }, { "id": "KABUPATEN_OKU_SELATAN", "nama": "Kabupaten OKU Selatan Pelangki", "kota": "Kabupaten Oku Selatan", "ispu": 69, "kategori": "Sedang" }, { "id": "KABUPATEN_MUARA_ENIM", "nama": "Kabupaten Muara Enim", "kota": "Kabupaten Muara Enim", "ispu": 60, "kategori": "Sedang" } ] }, { "provinsi": "Papua Tengah", "jumlahStasiun": 1, "ispuRataRata": 176, "stations": [ { "id": "KABUPATEN_NABIRE", "nama": "Kabupaten Nabire", "kota": "Kabupaten Nabire", "ispu": 176, "kategori": "Tidak Sehat" } ] }, { "provinsi": "Kalimantan Barat", "jumlahStasiun": 6, "ispuRataRata": 166, "stations": [ { "id": "PONTIANAK", "nama": "Pontianak Tenggara", "kota": "Pontianak", "ispu": 575, "kategori": "Berbahaya" }, { "id": "KABUPATEN_SINTANG", "nama": "Kabupaten Sintang Baning Kota", "kota": "Kabupaten Sintang", "ispu": 117, "kategori": "Tidak Sehat" }, { "id": "KABUPATEN_KUBU_RAYA", "nama": "Kabupaten Kubu Raya Kapuas", "kota": "Kabupaten Kubu Raya", "ispu": 93, "kategori": "Sedang" }, { "id": "KABUPATEN_SEKADAU", "nama": "Kabupaten Sekadau Sekadau Hilir", "kota": "Kabupaten Sekadau", "ispu": 88, "kategori": "Sedang" }, { "id": "KABUPATEN_BENGKAYANG", "nama": "Kabupaten Bengkayang Sebalo", "kota": "Kabupaten Bengkayang", "ispu": 70, "kategori": "Sedang" }, { "id": "KABUPATEN_SAMBAS", "nama": "Kabupaten Sambas Dalam Kaum", "kota": "Kabupaten Sambas", "ispu": 50, "kategori": "Baik" } ] }, { "provinsi": "Kalimantan Timur", "jumlahStasiun": 11, "ispuRataRata": 114, "stations": [ { "id": "KABUPATEN_KUTAIBARAT", "nama": "Kabupaten Kutai Barat Sendawar", "kota": "Kabupaten Kutai Barat", "ispu": 414, "kategori": "Berbahaya" }, { "id": "WT_KPIKN_IKN", "nama": "Wonotirto KPIKN IKN", "kota": "Kabupaten Kutai Kartanegara", "ispu": 136, "kategori": "Tidak Sehat" }, { "id": "MJT_KPIKN_IKN", "nama": "Muara Jawa Tengah KPIKN...", "kota": "Kabupaten Kutai Kartanegara", "ispu": 110, "kategori": "Tidak Sehat" }, { "id": "SNP_KPIKN_IKN", "nama": "Sanipah KPIKN IKN", "kota": "Kabupaten Kutai Kartanegara", "ispu": 106, "kategori": "Tidak Sehat" }, { "id": "PB_KIPP_IKN", "nama": "Plaza Bhinneka KIPP IKN", "kota": "Kabupaten Penajam Paser Utara", "ispu": 80, "kategori": "Sedang" }, { "id": "KOTA_BONTANG", "nama": "Kota Bontang Tanjung Laut", "kota": "Kota Bontang", "ispu": 75, "kategori": "Sedang" }, { "id": "KABUPATEN_PENAJAM_PASER_UTARA", "nama": "Kabutapen Penajam Paser Utara...", "kota": "Kabupaten Penajam Paser Utara", "ispu": 74, "kategori": "Sedang" }, { "id": "SAMARINDA", "nama": "Samarinda Taman Segiri", "kota": "Samarinda", "ispu": 74, "kategori": "Sedang" }, { "id": "BALIKPAPAN_BB", "nama": "BALIKPAPAN BARU", "kota": "BALIKPAPAN", "ispu": 69, "kategori": "Sedang" }, { "id": "BALIKPAPAN", "nama": "Balikpapan Sepinggan", "kota": "Balikpapan", "ispu": 66, "kategori": "Sedang" }, { "id": "PS_KIPP_IKN", "nama": "Plaza Seremoni KIPP IKN", "kota": "Kabupaten Penajam Paser Utara", "ispu": 53, "kategori": "Sedang" } ] }, { "provinsi": "Sumatera Barat", "jumlahStasiun": 1, "ispuRataRata": 100, "stations": [ { "id": "KABUPATEN_PADANG_PARIAMAN", "nama": "Kabupaten Padang Pariaman", "kota": "Kabupaten Padang Pariaman", "ispu": 100, "kategori": "Sedang" } ] }, { "provinsi": "Bengkulu", "jumlahStasiun": 1, "ispuRataRata": 98, "stations": [ { "id": "BENGKULU", "nama": "Bengkulu Singaran Pati", "kota": "Bengkulu", "ispu": 98, "kategori": "Sedang" } ] }, { "provinsi": "DKI Jakarta", "jumlahStasiun": 7, "ispuRataRata": 86, "stations": [ { "id": "DKI1", "nama": "DKI 1 Bundaran HI", "kota": "Jakarta", "ispu": 112, "kategori": "Tidak Sehat" }, { "id": "DKI4", "nama": "DKI 4 Lubang Buaya", "kota": "Jakarta", "ispu": 107, "kategori": "Tidak Sehat" }, { "id": "DKI5", "nama": "DKI 5 Kebon Jeruk", "kota": "Jakarta", "ispu": 105, "kategori": "Tidak Sehat" }, { "id": "DKI2", "nama": "DKI 2 Kelapa Gading", "kota": "Jakarta", "ispu": 88, "kategori": "Sedang" }, { "id": "DKI3", "nama": "DKI 3 Jagakarsa", "kota": "Jakarta", "ispu": 86, "kategori": "Sedang" }, { "id": "JAKARTA", "nama": "Jakarta GBK", "kota": "Jakarta", "ispu": 55, "kategori": "Sedang" }, { "id": "KBN_MARUNDA", "nama": "KBN Marunda Jakarta Utara", "kota": "Jakarta Utara", "ispu": 47, "kategori": "Baik" } ] }, { "provinsi": "Kalimantan Utara", "jumlahStasiun": 1, "ispuRataRata": 85, "stations": [ { "id": "KOTA_TARAKAN", "nama": "Kota Tarakan Pamusian", "kota": "Kota Tarakan", "ispu": 85, "kategori": "Sedang" } ] }, { "provinsi": "Sulawesi Tengah", "jumlahStasiun": 1, "ispuRataRata": 75, "stations": [ { "id": "KABUPATEN_MOROWALI", "nama": "Kabupaten Morowali", "kota": "Kabupaten Morowali", "ispu": 75, "kategori": "Sedang" } ] }, { "provinsi": "Banten", "jumlahStasiun": 9, "ispuRataRata": 65, "stations": [ { "id": "KABUPATEN_TANGERANG", "nama": "Kabupaten Tangerang Tigaraksa", "kota": "Tangerang", "ispu": 95, "kategori": "Sedang" }, { "id": "KABUPATEN_SERANG", "nama": "Kabupaten Serang Binuang", "kota": "Serang", "ispu": 93, "kategori": "Sedang" }, { "id": "TANGERANG", "nama": "Tangerang Pasir Jaya", "kota": "Tangerang", "ispu": 81, "kategori": "Sedang" }, { "id": "TANGERANG_KARANG_TENGAH", "nama": "Tangerang Karang Tengah", "kota": "Tangerang", "ispu": 75, "kategori": "Sedang" }, { "id": "KOTA_CILEGON", "nama": "Kota Cilegon Pulomerak", "kota": "Cilegon", "ispu": 73, "kategori": "Sedang" }, { "id": "TANGERANG_BENTENG_BETAWI", "nama": "Tangerang Benteng Betawi", "kota": "Tangerang", "ispu": 63, "kategori": "Sedang" }, { "id": "TANGERANG_KALI_PASIR", "nama": "Tangerang Kali Pasir", "kota": "Tangerang", "ispu": 50, "kategori": "Baik" }, { "id": "KIK2_PT_KSP", "nama": "PT Krakatau Sarana Properti", "kota": "Cilegon", "ispu": 35, "kategori": "Baik" }, { "id": "KABUPATEN_TANGERANGCG", "nama": "Kabupaten Tangerang Curug", "kota": "Tangerang", "ispu": 21, "kategori": "Baik" } ] }, { "provinsi": "Jawa TImur", "jumlahStasiun": 1, "ispuRataRata": 64, "stations": [ { "id": "KABUPATEN_TUBAN", "nama": "Kabupaten Tuban", "kota": "Kabupaten Tuban", "ispu": 64, "kategori": "Sedang" } ] }, { "provinsi": "Jawa Barat", "jumlahStasiun": 9, "ispuRataRata": 60, "stations": [ { "id": "KABUPATEN_SUKABUMI", "nama": "Kabupaten Sukabumi Pelabuhanratu", "kota": "Kabupaten Sukabumi", "ispu": 86, "kategori": "Sedang" }, { "id": "KABUPATEN_BOGORLL", "nama": "Kabupaten Bogor Leuwiliang", "kota": "Bogor", "ispu": 83, "kategori": "Sedang" }, { "id": "BEKASI", "nama": "Bekasi Kayuringin", "kota": "Bekasi", "ispu": 79, "kategori": "Sedang" }, { "id": "BOGOR", "nama": "Kota Bogor Tanah Sereal", "kota": "Bogor", "ispu": 68, "kategori": "Sedang" }, { "id": "KABUPATEN_INDRAMAYU", "nama": "Kabupaten Indramayu Margadadi", "kota": "Indramayu", "ispu": 58, "kategori": "Sedang" }, { "id": "KABUPATEN_BANDUNGBB", "nama": "Kabupaten Bandung Barat Saguling", "kota": "Bandung Barat", "ispu": 57, "kategori": "Sedang" }, { "id": "KABUPATEN_PURWAKARTA", "nama": "Kabupaten Purwakarta Purnawarman", "kota": "Kabupaten Purwakarta", "ispu": 48, "kategori": "Baik" }, { "id": "KABUPATEN_BEKASI", "nama": "Kabupaten Bekasi Sukamahi", "kota": "Kabupaten Bekasi", "ispu": 40, "kategori": "Baik" }, { "id": "KABUPATEN_BOGOR", "nama": "Kabupaten Bogor Tegar Beriman", "kota": "Kabupaten Bogor", "ispu": 22, "kategori": "Baik" } ] }, { "provinsi": "Papua", "jumlahStasiun": 1, "ispuRataRata": 50, "stations": [ { "id": "PAPUA", "nama": "Jayapura Abepura", "kota": "Jayapura", "ispu": 50, "kategori": "Baik" } ] }, { "provinsi": "Jawa Timur", "jumlahStasiun": 13, "ispuRataRata": 48, "stations": [ { "id": "KOTA_PASURUAN", "nama": "Kota Pasuruan Petamanan", "kota": "Kota Pasuruan", "ispu": 94, "kategori": "Sedang" }, { "id": "KOTA_MADIUN", "nama": "Kota Madiun Taman", "kota": "Madiun", "ispu": 73, "kategori": "Sedang" }, { "id": "KOTA_PROBOLINGGO", "nama": "Kota Probolinggo Kedopok", Komentar
Masuk untuk ikut berkomentar.
Belum ada komentar.