App component is the primary React component for the NYC Permit Pulse isometric map view. It orchestrates the OpenSeadragon viewer, permit data management, filtering, and all UI interactions including the sidebar, permit list, and detail drawer.
Component Structure
TheApp component is defined in src/App.tsx and serves as the root component for the isometric map experience.
State Management
The component uses React hooks to manage comprehensive application state:Map State
Reference to the DOM element that hosts the OpenSeadragon viewer
Reference to the OpenSeadragon viewer instance
Map of permit marker elements by key (e.g.,
job-{filing_number})Reference to the NeighborhoodLabels overlay manager
Map of helicopter marker elements by hex identifier
Data State
Complete array of permits loaded from the API
Memoized array of permits after applying job type and borough filters
Memoized array of filtered permits sorted by date (most recent first)
True when permit data is being fetched
Error message if permit data fetch fails
UI State
Tooltip state for marker hover, includes permit data and screen coordinates
Currently selected permit shown in the detail drawer
Currently selected permit in the list (for highlighting)
True when the Deep Zoom Image (DZI) tileset has loaded
Whether permit markers are visible on the map
Whether the filters section in the sidebar is expanded
Whether the permits list section in the sidebar is expanded
Whether the info modal is visible
Whether the sidebar is open on mobile devices
Filter State
Current filter configuration object
Key Functions
buildTileSource
- width: 123,904 pixels
- height: 100,864 pixels
- tileSize: 512 pixels
- minLevel/maxLevel: Computed from DZI dimensions (8 zoom levels)
- getTileUrl: Function that maps OSD zoom level and tile coordinates to WebP tile URLs
Tile URLs use the
/dzi/tiles_files proxy path. In development, Vite proxies these requests; in production, Vercel rewrites handle CORS.src/App.tsx:27-42.
placeMarkers
- Nuclear clear: Removes all existing permit overlays by DOM class and ref tracking
- Recency fade: Pre-computes opacity values in O(n) based on permit date (older permits are more transparent)
- Chunked rendering: Adds markers in batches of 400 per frame using
requestAnimationFrameto avoid blocking the main thread - Event handlers: Attaches mouseenter, mouseleave, and click handlers to each marker
- Generation tracking: Uses
markerGenRefto cancel stale RAF callbacks from previous renders
src/App.tsx:468-561.
placeHelicopters
- Smooth interpolation: Animates helicopter movement between position updates over 12 seconds
- Directional rotation: Flips helicopter emoji based on track (heading) angle
- Stale removal: Removes helicopter markers that are no longer in the active set
- RAF animation loop: Continuously updates positions using
requestAnimationFrame
src/App.tsx:383-451.
flyToPermit
- Parses latitude/longitude from the permit
- Converts lat/lng to image pixel coordinates using
latlngToImagePx() - Converts image coordinates to OpenSeadragon viewport coordinates
- Pans to the target location
- Zooms to level 6 (or maintains current zoom if already > 4)
src/App.tsx:565-576.
toggleJobType
filters.jobTypes Set.
See implementation at src/App.tsx:578-582.
toggleBorough
filters.boroughs Set.
See implementation at src/App.tsx:584-588.
computeOpacities
- Extracts timestamps from issued_date or approved_date
- Finds min/max valid timestamps
- Maps each permit to an opacity value from 0.5 to 1.0 (older = more transparent)
- Returns a Map for O(1) lookup during marker rendering
src/App.tsx:264-277.
Sub-Components
PermitChart
- Groups permits by
job_typeand counts occurrences - Sorts by count (descending) and shows top 8 types
- Renders color-coded bars with glow effects
- Shows total permit count at the bottom
src/App.tsx:50-77.
PermitDrawer
The permit object to display
Callback function when the drawer close button is clicked
- Color-coded header with job type emoji and label
- Address and location (neighborhood, borough, zip)
- Job description
- Filing details (type, status, dates)
- Estimated cost
- Owner, contractor, and expediter information
- BIN, BBL, and community board metadata
- External links to DOB BIS, ZoLa, Google Maps, and Street View
src/App.tsx:80-222.
PermitRow
react-window):
Row index in the virtualized list
Position styles provided by react-window
Shared data object containing:
sortedPermits: Full array of sorted permitsselectedPermit: Currently selected permit (for highlighting)setDrawerPermit: Function to open the detail drawersetSelectedPermit: Function to update selection stateflyToPermit: Function to animate map to permit location
- Color-coded dot indicator matching job type
- Job type badge and issue date
- Formatted address
- Highlights when selected
- Click handler opens drawer and flies to location
src/App.tsx:233-261.
Effect Lifecycle
OpenSeadragon Initialization
Initializes the OSD viewer on mount (src/App.tsx:323-359):
- Creates OpenSeadragon viewer with custom tile source
- Configures gesture settings (scroll to zoom, no click to zoom)
- Sets up navigator (mini-map) for desktop only
- Adds ‘open’ handler that:
- Sets
dziLoadedto true - Creates NeighborhoodLabels instance
- Pans to Midtown Manhattan (Empire State Building)
- Zooms to 3.5 (desktop) or 10 (mobile)
- Sets
- Cleanup function destroys viewer and labels on unmount
Permit Data Fetching
Fetches permit data whenfilters.daysBack changes (src/App.tsx:362-380):
- Calls
fetchPermits(filters.daysBack)API - Updates
permitsstate on success - Sets
errorstate on failure - Re-fetches every 5 minutes (300,000ms interval)
- Cleanup function clears the interval
Helicopter Live Layer
Polls helicopter data every 12 seconds after map loads (src/App.tsx:454-465):
- Waits for
dziLoadedto be true - Calls
fetchHelicopters()API - Passes results to
placeHelicopters() - Repeats every 12 seconds
- Cleanup function cancels polling
Marker Rendering
Re-renders markers when data or overlay state changes (src/App.tsx:563):
- Waits for
dziLoadedto be true - Calls
placeMarkers()whenfilteredPermitsoroverlayOnchanges
Virtual List Scrolling
Scrolls the permit list to keep selected permit visible (src/App.tsx:609-616):
- Finds index of
selectedPermitinsortedPermits - Calls
listRef.current.scrollToItem(idx, 'smart')
Marker Highlighting
Highlights the selected permit marker on the map (src/App.tsx:619-630):
- Removes
permit-marker--selectedclass from all markers - Finds the marker element by key (e.g.,
job-{filing_number}) - Adds
permit-marker--selectedclass to the matching marker
Usage Example
The App component requires the following CSS files to be imported:
./App.css- Main component stylesopenseadragon- OSD viewer styles (imported automatically)
Performance Optimizations
- Chunked marker rendering: Adds markers in batches of 400 per frame to avoid blocking the main thread
- Memoized filtering: Uses
useMemoforfilteredPermitsandsortedPermitsto avoid redundant computations - Pre-computed opacities: Calculates marker opacity values in O(n) instead of O(n²)
- Virtual scrolling: Uses
react-windowto render only visible permit list rows - Dynamic list height: Uses ResizeObserver to measure list container height for accurate virtualization
- RAF animation loops: Uses
requestAnimationFramefor smooth helicopter and marker animations - Generation tracking: Cancels stale RAF callbacks when filters change
Dependencies
react- Core React libraryopenseadragon- Deep zoom image viewerreact-window- Virtual scrolling for permit list- Custom utilities:
latlngToImagePx,fetchPermits,fetchHelicopters,getJobColor,getJobEmoji,getJobLabel,formatAddress,formatDate NeighborhoodLabels- Custom overlay manager for neighborhood labels
See Also
- NeighborhoodLabels - Overlay manager for LOD neighborhood labels
- Permit Map - MapLibre alternative map view