This guide covers everything you need to run NYC Permit Pulse locally, including the Vite proxy configuration that handles tile serving and API requests.
Prerequisites
Before you begin, ensure you have:
Node.js : Version 18 or higher
npm , yarn , or pnpm : Package manager
Git : To clone the repository
NYC Permit Pulse uses Vite 7 and React 19 with TypeScript. The project requires a modern Node.js environment.
Installation Steps
Clone the repository
git clone https://github.com/yourusername/isometric-permits
cd isometric-permits
This will create a local copy of the project in the isometric-permits directory.
Install dependencies
This installs all required packages including:
React 19 + React DOM — UI framework
OpenSeadragon — Deep-zoom tile viewer
Tailwind CSS v4 — Styling (with Vite plugin)
TypeScript — Type safety
Vite — Build tool and dev server
Start the development server
Expected output: VITE v7.3.1 ready in 342 ms
➜ Local: http://localhost:5177/
➜ Network: use --host to expose
➜ press h + enter to show help
Open in browser
Navigate to http://localhost:5177 to see the app running. You should see the isometric NYC map with permit markers loading immediately.
Vite Proxy Configuration
NYC Permit Pulse relies on proxy routes to avoid CORS issues when fetching tiles and API data. The proxy is configured in vite.config.ts and only runs during development.
Why Proxying is Required
Tile Server CORS : The isometric NYC tile server (isometric-nyc-tiles.cannoneyed.com) is CORS-restricted to cannoneyed.com only. Direct requests from localhost are blocked.
NYC Open Data : While the Socrata API is public, proxying allows us to use consistent /api/* paths in both dev and production.
Proxy Routes
Here’s the complete proxy configuration from vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig ({
plugins: [
react (),
tailwindcss (),
] ,
server: {
port: 5177 ,
proxy: {
// Proxy NYC Open Data API to avoid CORS in dev
'/api/permits' : {
target: 'https://data.cityofnewyork.us' ,
changeOrigin: true ,
rewrite : ( path ) => path . replace ( /^/ api / permits / , '/resource/rbx6-tga4.json' ),
},
'/api/jobs' : {
target: 'https://data.cityofnewyork.us' ,
changeOrigin: true ,
rewrite : ( path ) => path . replace ( /^/ api / jobs / , '/resource/w9ak-ipjd.json' ),
},
// Proxy isometric NYC tiles (DZI xml + tile images) to avoid CORS
'/dzi' : {
target: 'https://isometric-nyc-tiles.cannoneyed.com' ,
changeOrigin: true ,
},
// Proxy ADS-B Exchange helicopter data
'/api/adsb' : {
target: 'https://api.adsb.lol' ,
changeOrigin: true ,
rewrite : ( path ) => path . replace ( /^/ api / adsb / , '/v2' ),
},
},
} ,
})
Proxy Path Mapping
Local Path Target Dataset /api/permitshttps://data.cityofnewyork.us/resource/rbx6-tga4.jsonDOB NOW: Build – Approved Permits (work permits) /api/jobshttps://data.cityofnewyork.us/resource/w9ak-ipjd.jsonDOB NOW: Build – Job Filings (NB + DM) /dzi/*https://isometric-nyc-tiles.cannoneyed.com/dzi/*Isometric NYC map tiles /api/adsbhttps://api.adsb.lol/v2ADS-B Exchange (helicopter tracking)
In the source code, all fetch calls use the local proxy paths (e.g., /api/permits). This ensures the same code works in both development and production.
How It Works
When you request /api/permits?$limit=1000 in development:
Vite intercepts the request
Rewrites the path to /resource/rbx6-tga4.json?$limit=1000
Forwards it to https://data.cityofnewyork.us with changeOrigin: true
Returns the response to your browser as if it came from localhost:5177
Production Deployment
The Vite proxy only runs in development . For production builds, you need server-side rewrites.
Vercel Configuration
NYC Permit Pulse is deployed on Vercel with the following vercel.json configuration:
{
"rewrites" : [
{
"source" : "/api/permits/:path*" ,
"destination" : "https://data.cityofnewyork.us/resource/rbx6-tga4.json/:path*"
},
{
"source" : "/api/jobs/:path*" ,
"destination" : "https://data.cityofnewyork.us/resource/w9ak-ipjd.json/:path*"
},
{
"source" : "/dzi/:path*" ,
"destination" : "https://isometric-nyc-tiles.cannoneyed.com/dzi/:path*"
},
{
"source" : "/api/adsb/:path*" ,
"destination" : "https://api.adsb.lol/v2/:path*"
},
{
"source" : "/map" ,
"destination" : "/index.html"
},
{
"source" : "/(.*)" ,
"destination" : "/index.html"
}
]
}
These rewrites mirror the Vite proxy routes, ensuring the app works identically in production.
If deploying to a platform other than Vercel (e.g., Netlify, Cloudflare Pages), you’ll need to configure equivalent rewrites for /api/* and /dzi/* paths.
Build for Production
To create a production build:
This will:
Run TypeScript compiler (tsc -b) to check types
Build the app with Vite to dist/
To preview the production build locally:
The preview server will not have the proxy configured. You’ll need to deploy to Vercel (or configure local rewrites) to test API/tile requests.
Project Structure
Once installed, your project structure will look like:
isometric-permits/
├── src/
│ ├── App.tsx # Main app — OSD viewer, sidebar, filters, ticker, drawer
│ ├── App.css # All styles — dark terminal aesthetic
│ ├── coordinates.ts # Lat/lng → isometric pixel projection
│ ├── permits.ts # API fetch, normalization, color/label/emoji maps
│ ├── types.ts # Permit, MapConfig, FilterState interfaces
│ ├── NeighborhoodLabels.ts # LOD neighborhood label system
│ ├── helicopters.ts # ADS-B helicopter tracking
│ ├── nta_centroids.json # 197 NYC NTA centroids (computed from NYC Open Data)
│ └── main.tsx # React entry point
├── public/
│ ├── screenshot.jpg # OG image
│ └── favicon.ico
├── vite.config.ts # Vite + React + Tailwind + proxy config
├── vercel.json # Production rewrites for Vercel
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies and scripts
└── README.md # Project overview
Troubleshooting
Port Already in Use
If port 5177 is already in use, you can change it in vite.config.ts:11:
server : {
port : 5178 , // or any other port
proxy : { ... }
}
Tiles Not Loading
If the map appears blank:
Check the browser console for CORS errors
Verify the Vite dev server is running
Ensure /dzi proxy is configured correctly
Try clearing browser cache and reloading
Permits Not Loading
If permit markers don’t appear:
Open browser DevTools → Network tab
Look for requests to /api/permits and /api/jobs
Check for 429 (rate limit) or 500 (server error) responses
The NYC Open Data API occasionally has outages — try again in a few minutes
The Socrata API returns a maximum of 50,000 rows per request. NYC Permit Pulse limits queries to 1,000-5,000 recent permits depending on the time range to ensure fast loading.
Next Steps
Architecture Learn how the coordinate projection and data pipeline work
Data Sources Understand the NYC Open Data APIs and datasets
API Reference Explore core functions and TypeScript types
Deployment Deploy your own instance