natural disasters
DISASTER HISTORY
Search historical earthquakes, storms, volcanoes, wildfires & floods by date range, type, and severity. Powered by NASA EONET and USGS — no signup required.
Data sources
● USGS — Full earthquake history since 1900. Queries return up to 20,000 results per request. Magnitude, depth, coordinates, and wave data included.
● NASA EONET — Storms, volcanoes, wildfires, floods, and 10+ other natural event categories. Coverage from 2010 onwards, real-time satellite sourced.
← back to live map
● NASA EONET — Storms, volcanoes, wildfires, floods, and 10+ other natural event categories. Coverage from 2010 onwards, real-time satellite sourced.
← back to live map
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
(function () {
/* ── CONFIG ────────────────────────────────────────── */
const USGS_BASE = 'https://earthquake.usgs.gov/fdsnws/event/1/query';
const EONET_BASE = 'https://eonet.gsfc.nasa.gov/api/v3/events';
const CAT_META = {
earthquakes: { label: 'Earthquake', color: '#ff0000', icon: '≋', bg: 'rgba(255,0,0,0.15)' },
severeStorms: { label: 'Severe Storm', color: '#f5c518', icon: '⌁', bg: 'rgba(245,197,24,0.15)' },
volcanoes: { label: 'Volcano', color: '#9d00ff', icon: '△', bg: 'rgba(157,0,255,0.15)' },
wildfires: { label: 'Wildfire', color: '#ff8000', icon: '⬡', bg: 'rgba(255,128,0,0.15)' },
floods: { label: 'Flood', color: '#4488ff', icon: '≈', bg: 'rgba(68,136,255,0.15)' },
landslides: { label: 'Landslide', color: '#aa8855', icon: '◿', bg: 'rgba(170,136,85,0.15)' },
seaLakeIce: { label: 'Sea/Lake Ice', color: '#aaddff', icon: '❄', bg: 'rgba(170,221,255,0.15)' },
drought: { label: 'Drought', color: '#cc9944', icon: '○', bg: 'rgba(204,153,68,0.15)' },
dustHaze: { label: 'Dust / Haze', color: '#bbaa88', icon: '≀', bg: 'rgba(187,170,136,0.15)' },
tempExtremes: { label: 'Temp Extreme', color: '#ff8855', icon: '◈', bg: 'rgba(255,136,85,0.15)' },
other: { label: 'Other', color: '#888888', icon: '⬤', bg: 'rgba(136,136,136,0.15)' },
};
function getMeta(catId) { return CAT_META[catId] || CAT_META.other; }
/* ── DATE HELPERS ──────────────────────────────────── */
function toISODate(d) { return d.toISOString().split('T')[0]; }
function daysAgo(n) {
const d = new Date();
d.setDate(d.getDate() - n);
return d;
}
function setDates(start, end) {
document.getElementById('startDate').value = toISODate(start);
document.getElementById('endDate').value = toISODate(end);
}
/* ── PRESETS ───────────────────────────────────────── */
const today = new Date();
document.getElementById('endDate').value = toISODate(today);
document.getElementById('startDate').value = toISODate(daysAgo(30));
document.querySelectorAll('.preset-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.preset-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const days = parseInt(btn.dataset.days);
setDates(daysAgo(days), today);
});
});
// Show/hide magnitude field based on event type
const eventTypeEl = document.getElementById('eventType');
const magFieldEl = document.getElementById('magField');
eventTypeEl.addEventListener('change', () => {
const isEq = eventTypeEl.value === 'earthquakes' || eventTypeEl.value === 'all';
magFieldEl.style.opacity = isEq ? '1' : '0.4';
});
/* ── FETCH USGS ────────────────────────────────────── */
async function fetchUSGS(start, end, minMag, limit) {
const params = new URLSearchParams({
format: 'geojson',
starttime: start,
endtime: end,
orderby: 'time',
limit: Math.min(limit, 500),
});
if (minMag) params.set('minmagnitude', minMag);
const res = await fetch(`${USGS_BASE}?${params}`);
if (!res.ok) throw new Error(`USGS error ${res.status}`);
const data = await res.json();
return (data.features || []).map(f => {
const p = f.properties;
return {
id: f.id,
title: p.title || `M${p.mag?.toFixed(1)} Earthquake`,
catId: 'earthquakes',
date: new Date(p.time),
mag: p.mag,
place: p.place,
url: p.url,
source: 'USGS',
depth: f.geometry.coordinates[2],
};
});
}
/* ── FETCH EONET ───────────────────────────────────── */
async function fetchEONET(start, end, catId, limit) {
const params = new URLSearchParams({
status: 'all',
start,
end,
limit: Math.min(limit, 500),
});
if (catId && catId !== 'all' && catId !== 'earthquakes') {
params.set('category', catId);
}
const res = await fetch(`${EONET_BASE}?${params}`);
if (!res.ok) throw new Error(`EONET error ${res.status}`);
const data = await res.json();
return (data.events || []).map(ev => {
const catData = ev.categories?.[0];
const geoms = ev.geometry || ev.geometries || [];
const geom = geoms.find(g => g.type === 'Point') || geoms[0];
const [lng, lat] = geom?.coordinates || [null, null];
return {
id: ev.id,
title: ev.title,
catId: catData?.id || 'other',
date: new Date(geom?.date || Date.now()),
mag: null,
place: null,
url: ev.link,
source: 'NASA EONET',
lat, lng,
};
}).filter(e => e.catId !== 'earthquakes'); // avoid duplicates
}
/* ── SORT ──────────────────────────────────────────── */
let currentResults = [];
let currentSort = 'date-desc';
let currentPage = 0;
const PAGE_SIZE = 50;
function sortEvents(events, sort) {
const arr = [...events];
if (sort === 'date-desc') arr.sort((a, b) => b.date - a.date);
else if (sort === 'date-asc') arr.sort((a, b) => a.date - b.date);
else if (sort === 'mag-desc') arr.sort((a, b) => (b.mag || 0) - (a.mag || 0));
return arr;
}
document.querySelectorAll('.sort-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.sort-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentSort = btn.dataset.sort;
currentPage = 0;
renderResults();
});
});
/* ── RENDER RESULTS ────────────────────────────────── */
function fmtDate(date) {
return date.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })
+ ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + ' UTC';
}
function renderResults() {
const sorted = sortEvents(currentResults, currentSort);
const total = sorted.length;
const start = currentPage * PAGE_SIZE;
const page = sorted.slice(start, start + PAGE_SIZE);
document.getElementById('resultsCount').textContent = `${total} events`;
// Source breakdown
const usgsCount = currentResults.filter(e => e.source === 'USGS').length;
const eonetCount = currentResults.filter(e => e.source === 'NASA EONET').length;
const splitEl = document.getElementById('sourceSplit');
splitEl.innerHTML = [
usgsCount ? `<div class="source-chip"><div class="source-dot" style="background:#ff2200"></div>${usgsCount} from USGS</div>` : '',
eonetCount ? `<div class="source-chip"><div class="source-dot" style="background:#f5c518"></div>${eonetCount} from NASA EONET</div>` : '',
].join('');
if (!total) {
document.getElementById('resultsList').innerHTML = `
<div class="results-empty">
<span class="icon">◎</span>
No events found for the selected criteria.<br>Try expanding the date range or lowering the minimum magnitude.
</div>`;
document.getElementById('pagination').style.display = 'none';
return;
}
const html = page.map(ev => {
const meta = getMeta(ev.catId);
const magClass = ev.mag >= 7 ? 'big' : ev.mag >= 5.5 ? 'med' : '';
const magDisplay = ev.mag !== null
? `<div class="event-mag ${magClass}">M${ev.mag.toFixed(1)}</div>` : '';
const depthLine = ev.depth != null
? `<span class="event-place">depth ${ev.depth.toFixed(0)} km</span>` : '';
const srcParam = ev.source === 'USGS' ? 'usgs' : 'eonet';
const linkEl = `<a class="event-link" href="/disasters/detail.html?id=${encodeURIComponent(ev.id)}&source=${srcParam}">details →</a>`;
return `
<div class="event-item">
<div class="event-icon" style="background:${meta.bg};border:1px solid ${meta.color}33;">
<span style="color:${meta.color}">${meta.icon}</span>
</div>
<div class="event-body">
<div class="event-title">${ev.title}</div>
<div class="event-meta">
<span class="event-type" style="color:${meta.color};border-color:${meta.color}44;background:${meta.bg};">${meta.label}</span>
${ev.place ? `<span class="event-place">${ev.place}</span>` : ''}
${depthLine}
</div>
${linkEl}
</div>
<div class="event-right">
<div class="event-date">${fmtDate(ev.date)}</div>
${magDisplay}
<div class="event-source">${ev.source}</div>
</div>
</div>`;
}).join('');
document.getElementById('resultsList').innerHTML = html;
// Pagination
const totalPages = Math.ceil(total / PAGE_SIZE);
const paginationEl = document.getElementById('pagination');
if (totalPages > 1) {
paginationEl.style.display = 'flex';
document.getElementById('pageInfo').textContent =
`Page ${currentPage + 1} of ${totalPages}`;
document.getElementById('prevPage').disabled = currentPage === 0;
document.getElementById('nextPage').disabled = currentPage >= totalPages - 1;
} else {
paginationEl.style.display = 'none';
}
}
document.getElementById('prevPage').addEventListener('click', () => {
if (currentPage > 0) { currentPage--; renderResults(); window.scrollTo({ top: 300, behavior: 'smooth' }); }
});
document.getElementById('nextPage').addEventListener('click', () => {
const max = Math.ceil(currentResults.length / PAGE_SIZE) - 1;
if (currentPage < max) { currentPage++; renderResults(); window.scrollTo({ top: 300, behavior: 'smooth' }); }
});
/* ── SEARCH ────────────────────────────────────────── */
const searchBtn = document.getElementById('searchBtn');
const clearBtn = document.getElementById('clearBtn');
const statusEl = document.getElementById('searchStatus');
async function doSearch() {
const startVal = document.getElementById('startDate').value;
const endVal = document.getElementById('endDate').value;
const catVal = document.getElementById('eventType').value;
const magVal = document.getElementById('minMag').value;
const limitVal = parseInt(document.getElementById('limitCount').value);
if (!startVal || !endVal) {
statusEl.textContent = 'Please select a date range.';
statusEl.style.color = 'var(--error)';
return;
}
if (new Date(startVal) > new Date(endVal)) {
statusEl.textContent = 'Start date must be before end date.';
statusEl.style.color = 'var(--error)';
return;
}
searchBtn.disabled = true;
searchBtn.textContent = 'Searching…';
statusEl.textContent = '';
document.getElementById('resultsSection').style.display = 'none';
currentResults = [];
currentPage = 0;
try {
const fetches = [];
const needUSGS = catVal === 'all' || catVal === 'earthquakes';
const needEONET = catVal === 'all' || (catVal !== 'earthquakes');
if (needUSGS) {
fetches.push(
fetchUSGS(startVal, endVal, magVal, limitVal).catch(err => {
console.warn('USGS fetch failed:', err);
return [];
})
);
} else {
fetches.push(Promise.resolve([]));
}
if (needEONET) {
fetches.push(
fetchEONET(startVal, endVal, catVal, limitVal).catch(err => {
console.warn('EONET fetch failed:', err);
return [];
})
);
} else {
fetches.push(Promise.resolve([]));
}
const [usgsEvents, eonetEvents] = await Promise.all(fetches);
currentResults = [...usgsEvents, ...eonetEvents];
const titleRange = `${startVal} → ${endVal}`;
const catLabel = catVal === 'all' ? 'all types' : (CAT_META[catVal]?.label || catVal);
document.getElementById('resultsTitle').textContent =
`${catLabel} · ${titleRange}`;
document.getElementById('resultsSection').style.display = 'block';
renderResults();
document.getElementById('resultsSection').scrollIntoView({ behavior: 'smooth', block: 'start' });
} catch (err) {
statusEl.textContent = 'Error fetching data. Try again.';
statusEl.style.color = 'var(--error)';
console.error(err);
} finally {
searchBtn.disabled = false;
searchBtn.textContent = 'Search disasters';
}
}
searchBtn.addEventListener('click', doSearch);
clearBtn.addEventListener('click', () => {
document.getElementById('startDate').value = toISODate(daysAgo(30));
document.getElementById('endDate').value = toISODate(today);
document.getElementById('eventType').value = 'all';
document.getElementById('minMag').value = '4.5';
document.getElementById('limitCount').value = '100';
document.querySelectorAll('.preset-btn').forEach(b => b.classList.remove('active'));
statusEl.textContent = '';
document.getElementById('resultsSection').style.display = 'none';
currentResults = [];
});
// Enter key to search
document.addEventListener('keydown', e => {
if (e.key === 'Enter' && !searchBtn.disabled) doSearch();
});
})();