Spaces:
Running
Running
CyberMax commited on
Initial free demo (CyberMax)
Browse files- README.md +24 -5
- index.html +99 -17
README.md
CHANGED
|
@@ -1,10 +1,29 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: "Ziplore: US ZIP Code Lookup & Radius Search"
|
| 3 |
+
emoji: 📍
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: gray
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
license: cc-by-4.0
|
| 9 |
+
short_description: Free ZIP lookup with county, time zone & income
|
| 10 |
+
tags:
|
| 11 |
+
- zip-codes
|
| 12 |
+
- geocoding
|
| 13 |
+
- census
|
| 14 |
+
- demographics
|
| 15 |
+
- radius-search
|
| 16 |
+
- real-estate
|
| 17 |
+
- agents
|
| 18 |
+
- apify
|
| 19 |
+
datasets:
|
| 20 |
+
- shaw276/us-zip-codes-demographics
|
| 21 |
---
|
| 22 |
|
| 23 |
+
# Ziplore: free US ZIP code lookup & radius search
|
| 24 |
+
|
| 25 |
+
Type a ZIP code (or "lat,lon") and a radius: get city, county, time zone, local time, population, median household income, home value, rent and age for that ZIP and every ZIP around it. All 41,682 US ZIPs load in your browser; nothing is sent anywhere.
|
| 26 |
+
|
| 27 |
+
**Whole lists, workflows and AI agents:** [Ziplore on the Apify Store](https://apify.com/cybermax/zip-data-api) takes thousands of ZIPs, addresses, "City, ST" names or coordinates and returns JSON with county FIPS, UTC offsets and nearby ZIPs; $0.0005 per ZIP, and it's an MCP tool for Claude and Cursor. Bulk CSV: [shaw276/us-zip-codes-demographics](https://huggingface.co/datasets/shaw276/us-zip-codes-demographics).
|
| 28 |
+
|
| 29 |
+
Data: GeoNames (CC BY 4.0), US Census Bureau (2024 Gazetteer, ACS 2020–2024 5-year), timezone-boundary-builder (ODbL, © OpenStreetMap contributors). Made by CyberMax.
|
index.html
CHANGED
|
@@ -1,19 +1,101 @@
|
|
| 1 |
<!doctype html>
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
+
<title>Ziplore: free US ZIP code lookup & radius search</title>
|
| 7 |
+
<meta name="description" content="Free US ZIP code lookup: city, county, time zone, population, income and home value for any ZIP, plus every ZIP within N miles.">
|
| 8 |
+
<style>
|
| 9 |
+
:root { --bg: #fbfaf7; --fg: #1d2330; --muted: #5d6675; --line: #e3e0d8; --card: #fff; --accent: #1f6f5c; --accent-fg: #fff; }
|
| 10 |
+
@media (prefers-color-scheme: dark) { :root { --bg: #14171c; --fg: #e8e6e1; --muted: #9aa3ad; --line: #2b3038; --card: #1b1f26; --accent: #4fb89b; --accent-fg: #0d1a16; } }
|
| 11 |
+
* { box-sizing: border-box; }
|
| 12 |
+
body { margin: 0; background: var(--bg); color: var(--fg); font: 16px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
|
| 13 |
+
main { max-width: 980px; margin: 0 auto; padding: 24px 16px 48px; }
|
| 14 |
+
h1 { font-size: 1.7rem; margin: 0 0 4px; }
|
| 15 |
+
.sub { color: var(--muted); margin: 0 0 20px; }
|
| 16 |
+
form { display: flex; flex-wrap: wrap; gap: 8px; align-items: end; background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 14px; }
|
| 17 |
+
label { display: flex; flex-direction: column; font-size: .85rem; color: var(--muted); gap: 4px; }
|
| 18 |
+
input { font: inherit; padding: 8px 10px; border: 1px solid var(--line); border-radius: 6px; background: var(--bg); color: var(--fg); width: 150px; }
|
| 19 |
+
button { font: inherit; padding: 9px 16px; border: 0; border-radius: 6px; background: var(--accent); color: var(--accent-fg); cursor: pointer; }
|
| 20 |
+
#status { color: var(--muted); margin: 12px 0; min-height: 1.5em; }
|
| 21 |
+
.wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: 10px; background: var(--card); }
|
| 22 |
+
table { border-collapse: collapse; width: 100%; font-size: .9rem; }
|
| 23 |
+
th, td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
| 24 |
+
th { font-weight: 600; color: var(--muted); }
|
| 25 |
+
td.n { text-align: right; font-variant-numeric: tabular-nums; }
|
| 26 |
+
.cta { margin-top: 24px; padding: 16px; border-radius: 10px; border: 1px solid var(--accent); background: var(--card); }
|
| 27 |
+
.cta a { color: var(--accent); font-weight: 600; }
|
| 28 |
+
footer { margin-top: 28px; color: var(--muted); font-size: .8rem; }
|
| 29 |
+
footer a { color: inherit; }
|
| 30 |
+
</style>
|
| 31 |
+
</head>
|
| 32 |
+
<body>
|
| 33 |
+
<main>
|
| 34 |
+
<h1>Ziplore: free US ZIP code lookup & radius search</h1>
|
| 35 |
+
<p class="sub">City, county, time zone and Census demographics for any US ZIP code, and every ZIP within a radius. 41,682 ZIPs, all in your browser.</p>
|
| 36 |
+
<form id="f">
|
| 37 |
+
<label>ZIP code or "lat,lon" <input id="q" value="10001" autocomplete="postal-code"></label>
|
| 38 |
+
<label>Radius (miles, 0 = just this ZIP) <input id="mi" type="number" min="0" max="100" step="1" value="2"></label>
|
| 39 |
+
<label>Min. population <input id="minpop" type="number" min="0" step="100" value="0"></label>
|
| 40 |
+
<button type="submit">Look up</button>
|
| 41 |
+
</form>
|
| 42 |
+
<div id="status">Loading ZIP table…</div>
|
| 43 |
+
<div class="wrap"><table id="t"><thead><tr><th>ZIP</th><th>City</th><th>State</th><th>County</th><th>Miles</th><th>Time zone</th><th>Local time</th><th>Population</th><th>Median household income</th><th>Median home value</th><th>Median rent</th><th>Median age</th></tr></thead><tbody></tbody></table></div>
|
| 44 |
+
<div class="cta">
|
| 45 |
+
<strong>Need this for a whole list, inside a workflow, or for an AI agent?</strong>
|
| 46 |
+
<a href="https://apify.com/cybermax/zip-data-api" target="_blank" rel="noopener">Run Ziplore on Apify</a>: send thousands of ZIPs, addresses, "City, ST" names or coordinates and get clean JSON with county FIPS, every county a ZIP touches, UTC offsets, per-capita income, owner-occupied share and nearby ZIPs. $0.0005 per ZIP, unknown ZIPs free, and it works as an MCP tool for Claude and Cursor.
|
| 47 |
+
Bulk data: <a href="https://huggingface.co/datasets/shaw276/us-zip-codes-demographics" target="_blank" rel="noopener">free dataset (CSV)</a>.
|
| 48 |
+
</div>
|
| 49 |
+
<footer>
|
| 50 |
+
Data: <a href="https://www.geonames.org/">GeoNames</a> (CC BY 4.0), US Census Bureau (2024 Gazetteer, ACS 2020–2024 5-year estimates), time zones from timezone-boundary-builder (ODbL, © OpenStreetMap contributors). Distances are between ZIP center points. Home values of $2,000,000 and incomes of $250,000 are Census caps ("or more"). Made by CyberMax. Not affiliated with USPS or the Census Bureau.
|
| 51 |
+
</footer>
|
| 52 |
+
</main>
|
| 53 |
+
<script>
|
| 54 |
+
let rows = [], byZip = new Map();
|
| 55 |
+
const $ = (id) => document.getElementById(id);
|
| 56 |
+
const fmt = (v, pre = '') => v == null ? '–' : pre + Number(v).toLocaleString('en-US');
|
| 57 |
+
const esc = (s) => String(s ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
|
| 58 |
+
function miles(a, b, c, d) {
|
| 59 |
+
const r = Math.PI / 180, x = Math.sin((c - a) * r / 2) ** 2 + Math.cos(a * r) * Math.cos(c * r) * Math.sin((d - b) * r / 2) ** 2;
|
| 60 |
+
return 2 * 3958.7613 * Math.asin(Math.min(1, Math.sqrt(x)));
|
| 61 |
+
}
|
| 62 |
+
function localTime(tz) {
|
| 63 |
+
if (!tz) return '–';
|
| 64 |
+
try { return new Intl.DateTimeFormat('en-US', { timeZone: tz, hour: 'numeric', minute: '2-digit' }).format(new Date()); } catch { return '–'; }
|
| 65 |
+
}
|
| 66 |
+
function render(list) {
|
| 67 |
+
$('t').tBodies[0].innerHTML = list.map(([d, r]) => `<tr><td>${esc(r.zip)}</td><td>${esc(r.city)}</td><td>${esc(r.state)}</td><td>${esc(r.county)}</td>
|
| 68 |
+
<td class="n">${d == null ? '–' : d.toFixed(1)}</td><td>${esc(r.timezone || '–')}</td><td>${localTime(r.timezone)}</td><td class="n">${fmt(r.population)}</td>
|
| 69 |
+
<td class="n">${fmt(r.medianHouseholdIncome, '$')}</td><td class="n">${fmt(r.medianHomeValue, '$')}</td><td class="n">${fmt(r.medianGrossRent, '$')}</td><td class="n">${r.medianAge ?? '–'}</td></tr>`).join('');
|
| 70 |
+
}
|
| 71 |
+
function run() {
|
| 72 |
+
const q = $('q').value.trim(), mi = Math.min(Math.max(Number($('mi').value) || 0, 0), 100), minPop = Number($('minpop').value) || 0;
|
| 73 |
+
let lat, lon, center;
|
| 74 |
+
const pt = q.match(/^(-?\d+(?:\.\d+)?)\s*[,\s]\s*(-?\d+(?:\.\d+)?)$/);
|
| 75 |
+
if (pt) { lat = +pt[1]; lon = +pt[2]; }
|
| 76 |
+
else {
|
| 77 |
+
const z = (q.match(/\d{5}/) || [q.replace(/\D/g, '').padStart(5, '0')])[0];
|
| 78 |
+
center = byZip.get(z);
|
| 79 |
+
if (!center) { $('status').textContent = `${z} isn't a US ZIP code in this table.`; render([]); return; }
|
| 80 |
+
lat = center.latitude; lon = center.longitude;
|
| 81 |
+
}
|
| 82 |
+
let list;
|
| 83 |
+
if (mi > 0 || !center) {
|
| 84 |
+
list = rows.filter((r) => !['AA', 'AE', 'AP'].includes(r.state)).map((r) => [miles(lat, lon, r.latitude, r.longitude), r]);
|
| 85 |
+
list = mi > 0 ? list.filter(([d]) => d <= mi) : [list.filter(([, r]) => r.hasZcta).sort((a, b) => a[0] - b[0])[0]].filter(Boolean);
|
| 86 |
+
list = list.filter(([d, r]) => (r.population ?? 0) >= minPop || (center && r === center)).sort((a, b) => a[0] - b[0]);
|
| 87 |
+
} else list = [[null, center]];
|
| 88 |
+
const shown = list.slice(0, 300);
|
| 89 |
+
$('status').textContent = mi > 0 ? `${list.length} ZIP code${list.length === 1 ? '' : 's'} within ${mi} miles${list.length > 300 ? ' (first 300 shown)' : ''}.` : (center ? '' : 'Nearest ZIP code to that point:');
|
| 90 |
+
render(shown);
|
| 91 |
+
}
|
| 92 |
+
$('f').addEventListener('submit', (e) => { e.preventDefault(); run(); });
|
| 93 |
+
fetch('zips.json').then((r) => r.json()).then(({ cols, rows: raw }) => {
|
| 94 |
+
rows = raw.map((a) => Object.fromEntries(cols.map((c, i) => [c, a[i]])));
|
| 95 |
+
byZip = new Map(rows.map((r) => [r.zip, r]));
|
| 96 |
+
$('status').textContent = '';
|
| 97 |
+
run();
|
| 98 |
+
}).catch(() => { $('status').textContent = 'Could not load the ZIP table. Reload the page.'; });
|
| 99 |
+
</script>
|
| 100 |
+
</body>
|
| 101 |
</html>
|