// TIF (Tax Increment Financing) district point-in-polygon lookup. // Given a property's lat/long, returns which Chicago TIF district (if // any) contains it. TIF districts are public-subsidy zones — a property // inside one is receiving city tax-increment funding for its build. // Strong "this project has financial backing" signal for the Project Index. // // Data: data/_entity_cache/tif_districts.geojson (Chicago Open Data // dataset eejr-xtfb, 100 active districts, 3.2MB). Refresh by re-running // `curl ... eejr-xtfb.geojson > tif_districts.geojson` — districts // change rarely (only when city council approves new ones or repeals). // // Algorithm: classic ray-casting. For each MultiPolygon's outer ring, // count edge crossings of an east-going horizontal ray from the point. // Odd crossings = inside. Holes (inner rings) flip the parity. Library- // free; correct for arbitrary polygons including the irregular Chicago // shapes which often have many small detours. import { readFile } from "node:fs/promises"; import { existsSync } from "node:fs"; import { join } from "node:path"; const TIF_GEOJSON = join("/home/profit/lakehouse/data/_entity_cache", "tif_districts.geojson"); type LngLat = [number, number]; // GeoJSON convention: [longitude, latitude] type Ring = LngLat[]; type Polygon = Ring[]; // outer ring + optional inner rings (holes) type MultiPolygon = Polygon[]; type TifFeature = { name: string; trim_name?: string; ref?: string; approval_date?: string; expiration?: string; type?: string; // T-1xx etc. comm_area?: string; wards?: string; // Bounding box for quick reject bbox: { minLon: number; minLat: number; maxLon: number; maxLat: number }; geometry: MultiPolygon; }; let tifIdx: TifFeature[] | null = null; function bboxOfMultiPolygon(mp: MultiPolygon): TifFeature["bbox"] { let minLon = Infinity, minLat = Infinity, maxLon = -Infinity, maxLat = -Infinity; for (const poly of mp) { for (const ring of poly) { for (const [lon, lat] of ring) { if (lon < minLon) minLon = lon; if (lat < minLat) minLat = lat; if (lon > maxLon) maxLon = lon; if (lat > maxLat) maxLat = lat; } } } return { minLon, minLat, maxLon, maxLat }; } async function ensureLoaded(): Promise { if (tifIdx) return tifIdx; if (!existsSync(TIF_GEOJSON)) { tifIdx = []; return tifIdx; } try { const raw = JSON.parse(await readFile(TIF_GEOJSON, "utf-8")); const out: TifFeature[] = []; for (const f of raw.features || []) { const geom = f.geometry; if (!geom) continue; // Normalize Polygon → MultiPolygon for uniform iteration let mp: MultiPolygon; if (geom.type === "MultiPolygon") { mp = geom.coordinates; } else if (geom.type === "Polygon") { mp = [geom.coordinates]; } else { continue; } const props = f.properties || {}; out.push({ name: props.name || "Unknown TIF", trim_name: props.name_trim, ref: props.ref, approval_date: props.approval_d, expiration: props.expiration, type: props.type, comm_area: props.comm_area, wards: props.wards, bbox: bboxOfMultiPolygon(mp), geometry: mp, }); } tifIdx = out; return tifIdx; } catch (e) { console.warn("[tif] load failed:", (e as Error).message); tifIdx = []; return tifIdx; } } // Ray-casting point-in-polygon (single ring). Returns true if (lon, lat) // is strictly inside the ring. Edge cases (point exactly on a vertex) // resolve by half-open interval convention; for our use case (Chicago // boundary precision is ~1m, sites are point queries) this is fine. function pointInRing(lon: number, lat: number, ring: Ring): boolean { let inside = false; const n = ring.length; for (let i = 0, j = n - 1; i < n; j = i++) { const [xi, yi] = ring[i]; const [xj, yj] = ring[j]; const intersect = yi > lat !== yj > lat && lon < ((xj - xi) * (lat - yi)) / (yj - yi + 0) + xi; if (intersect) inside = !inside; } return inside; } // Polygon = outer ring + holes. Inside outer AND not inside any hole. function pointInPolygon(lon: number, lat: number, polygon: Polygon): boolean { if (polygon.length === 0) return false; if (!pointInRing(lon, lat, polygon[0])) return false; for (let i = 1; i < polygon.length; i++) { if (pointInRing(lon, lat, polygon[i])) return false; } return true; } export type TifMatch = { name: string; ref?: string; approval_date?: string; expiration?: string; comm_area?: string; wards?: string; }; export async function findTifDistrict( longitude: number | string | undefined, latitude: number | string | undefined, ): Promise { const lon = typeof longitude === "string" ? parseFloat(longitude) : longitude; const lat = typeof latitude === "string" ? parseFloat(latitude) : latitude; if (!lon || !lat || isNaN(lon) || isNaN(lat)) return null; const idx = await ensureLoaded(); if (idx.length === 0) return null; for (const f of idx) { // Bbox reject — cheap O(1) skip for the 99% of districts that // can't possibly contain the point. const b = f.bbox; if (lon < b.minLon || lon > b.maxLon || lat < b.minLat || lat > b.maxLat) continue; // Full point-in-polygon for any polygon in this MultiPolygon for (const poly of f.geometry) { if (pointInPolygon(lon, lat, poly)) { return { name: f.name, ref: f.ref, approval_date: f.approval_date, expiration: f.expiration, comm_area: f.comm_area, wards: f.wards, }; } } } return null; } export async function getTifIndexStats(): Promise<{ total: number; loaded: boolean; }> { const idx = await ensureLoaded(); return { total: idx.length, loaded: idx.length > 0 }; }