← back to AbramsOS
lib/neighborhood.js
69 lines
// neighborhood.js — Neighborhood Watch link builder + free geocoder.
//
// Given an address (+ optional radius in miles), geocode it via OpenStreetMap Nominatim
// (free, no API key) and build real deep-links into the community-safety apps: Ring
// (cameras + Neighbors), Nextdoor, Citizen, crime maps, and the sex-offender registries.
// No data is fabricated — every link is a real public URL; location-aware ones carry the
// geocoded lat/lon when we have it, otherwise the raw address query.
async function geocode(address) {
if (!address) return null;
const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(address)}&format=json&limit=1&addressdetails=1`;
try {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 8000);
const r = await fetch(url, { headers: { 'User-Agent': 'AbramsOS/1.0 (personal household admin)' }, signal: ctrl.signal });
clearTimeout(t);
if (!r.ok) return null;
const j = await r.json();
if (!Array.isArray(j) || !j.length) return null;
const hit = j[0];
return {
lat: Number(hit.lat), lon: Number(hit.lon),
displayName: hit.display_name,
city: (hit.address && (hit.address.city || hit.address.town || hit.address.village || hit.address.county)) || null,
state: (hit.address && hit.address.state) || null,
};
} catch (_) { return null; }
}
// Build the categorized link set. loc may be null (links degrade to address-query form).
function links({ address, loc, radiusMi = 2 }) {
const lat = loc && loc.lat, lon = loc && loc.lon;
const hasGeo = Number.isFinite(lat) && Number.isFinite(lon);
const q = encodeURIComponent(address || '');
const city = (loc && (loc.city || loc.state)) ? encodeURIComponent(`${loc.city || ''} ${loc.state || ''}`.trim()) : q;
const cat = (title, items) => ({ title, items: items.filter(Boolean) });
const L = (name, url, note) => ({ name, url, note: note || null });
return [
cat('Your Ring', [
L('Ring cameras (live view)', 'https://account.ring.com/account/dashboard', 'Your own Ring doorbell/cameras — sign in to view live & history'),
L('Ring Neighbors feed', 'https://neighbors.ring.com/', 'Local safety posts & shared camera clips near you'),
]),
cat('Community apps', [
L('Nextdoor', 'https://nextdoor.com/news_feed/', 'Your registered neighborhood feed'),
L('Citizen', 'https://citizen.com/', 'Real-time local safety alerts (app-based)'),
L('Amber Alerts', 'https://www.missingkids.org/gethelpnow/amber', 'Active AMBER alerts'),
]),
cat(`Crime near here (~${radiusMi} mi)`, [
hasGeo
? L('SpotCrime map', `https://spotcrime.com/map?lat=${lat}&lon=${lon}&zoom=15`, 'Recent reported crime on a map')
: L('SpotCrime', `https://spotcrime.com/`, 'Recent reported crime'),
L('CrimeMapping', address ? `https://www.crimemapping.com/map/location/${q}` : 'https://www.crimemapping.com/', 'Police-sourced incident map'),
L('FBI Crime Data Explorer', 'https://cde.ucr.cjis.gov/', 'Official agency crime statistics'),
]),
cat('Registries & local', [
L('Sex-offender registry (national)', hasGeo ? `https://www.nsopw.gov/en/Search/Verification` : 'https://www.nsopw.gov/', 'US DOJ NSOPW — search by address'),
L("CA Megan's Law", 'https://www.meganslaw.ca.gov/', 'California registered-offender map'),
L('Local police (non-emergency)', `https://www.google.com/search?q=${city}+police+non-emergency+number`, 'Find your local department'),
]),
cat('Map', [
hasGeo ? L('Open in OpenStreetMap', `https://www.openstreetmap.org/?mlat=${lat}&mlon=${lon}#map=15/${lat}/${lon}`, null) : null,
]),
].filter((c) => c.items.length);
}
module.exports = { geocode, links };