Eric / Brooklyn

Random 5

Free Parking NYC

Operator as Methods

Web Accounts

Honeypot Bots

Stripe and Tap

Git Save

Whitespace Problems

Ruby Exits

Appending in Javascript

Sendgrid Ban

Clean URLs

Integer Division

Multi-tab Websockets

Bad Content

JS Data Chutzpah

Responsive tables

Concerns

Cookies

Emoji Bits

Git

Ruby vs. Ruby

Extending Devise

Rails UJS

ENV Variables

See All

Jan 2026 - Looking for a Ruby/JS dev? Currently open for new opportunities - eric@unfoundedlabs.com

Free Parking - New York City

Jan 2026

Alternate Side Parking

Free Parking in NYC can be found if you’re willing to navigate the world of “Alternate Side Parking” (ASP). A couple times per week, parkers need to accommodate street-sweeping trucks by moving their cars to the “alternate side” of the street. Ask any NYC street-parker about this and they’ll bore you with details of their little weekly routine.

Most of this free parking is in residential neighborhoods, but surprisingly, you can find it just about anywhere. If you know where to look, you’ll find spots in Midtown, SoHo, Chinatown, FiDi, Union Square, Flushing etc.

And when I say you’ll find these spots anywhere - technically true, but the spots you find will be occupied, as free parking is at nearly 100% utilization around the clock, especially in busy and/or commercial areas.

However, if you’re riding a Vespa or motorcycle, you don’t have this problem — you can always find space backed-in between parked cars (this scenario is the actual motivation for this post/app).

On two wheels, you can park anywhere in NYC for free at any time. Given this, I continue to be surprised at the low numbers of Vespas/motorcycles in NYC. Though, car drivers are insane and most people value their lives so perhaps that’s a reason.

Anyways, where are these spots? There isn’t a free parking map provided by the city. The NYC DOT provides this map, which has some fidgets and widgets that don’t seem to solve our problem.

So let’s make a map: parkgratis.com

Building the Map

All the code for this map is available here, and is separated into two areas - app and data_processing.

app builds the website and data_processing does … data processing.

I’ll spare you the details — feed the repo into your favorite LLM if you want to dig in — rather I’ll touch on a few points I think are worth mentioning

Data

NYC Open Data provides this dataset.

This file contains all Parking Regulation Locations and Signs in NYC. There are 440,363 rows, corresponding to 440,363 parking signs.

Here is an abridged sample:

borough on_street sign_description sign_x_coord sign_y_coord
Manhattan 3 AVENUE 2 HMP SATURDAY 8AM-7PM <-> 996877 222815
Bronx EAST 217 STREET TRUCK (SYMBOL) TRUCK LOADING ONLY MONDAY-FRIDAY 8AM-6PM –> (SUPERSEDES SP-449CA & SP-70BA) 1022145 260647
Manhattan 3 AVENUE 1 HMP COMMERCIAL VEHICLES ONLY MONDAY-FRIDAY 8AM-7PM <-> 996877 222815
Queens NORTHERN BOULEVARD LOCAL MTA BUS ROUTE PANEL (TEXT TO BE MODIFIED AS REQUESTED) 1057386 219889
Manhattan HUDSON STREET NO PARKING (SANITATION BROOM SYMBOL) 7:30AM-8AM EXCEPT SUNDAY –> (SUPERSEDES SP-139CA & PS-23BA) 982560 207047

This snippet only shows a handful of columns, but the full set has 25 columns:

"order_number","record_type","borough","on_street","from_street","to_street","side_of_street","sign_code","sign_description","distance_from_intersection","sign_x_coord","sign_y_coord"

Alternate Side Parking (ASP)

Unfortunately, none of the columns mention anything about “Free Parking”, because the truth is a bit more complicated.

Free parking sometimes exists on streets that have ASP

Some streets (or sections of streets) can have multiple overlapping signs; so a street with ASP doesn’t guarantee free parking.

Looking at the sample data in the sign_description column, we can equate ASP with the text “Broom Symbol” (ie street sweeping)

When is ASP free?

My heuristic, which seems right 95% of the time, is that ASP zones with:

  • 90 minute ranges are free
  • Shorter ranges (30 minutes) tend to come with additional restrictions (ie parking meters, special permits, etc).
  • Longer ranges (4 hours) are hard to predict. Sometimes free, sometimes not.
  • Overnight restrictions are also hard to predict. Sometimes free, sometimes not.

I’ve found this approach to be pretty reliable in practice (but suggestions welcome).

Data Processing

We have 440,363 rows that we need to parse, looking for “BROOM SYMBOL” with a 90 minute range.

Regex handles most of the heavy lifting here. You can see the details here.

For our output file, we don’t need all 25 columns of data - we just mapping these locations eventually, so we just bring along:

'borough',
'on_street',
'from_street',
'to_street',
'sign_description',
'sign_x_coord_lat',
'sign_y_coord_lon'

Dupes

On any given city block, there can be multiple signs saying the same thing at different locations on that block. For performance and UI reasons, we only want to display one of these signs per block.

To do this, we must check for duplicates:

# outside loop
seen_keys = Set.new

# inside loop (of 441k rows)
key = [
  borough,
  row['on_street'].to_s.strip,
  row['from_street'].to_s.strip,
  row['to_street'].to_s.strip,
  desc.to_s.strip
]

next if seen_keys.include?(key)

seen_keys << key

On any given block, all signs will have the same on_street, from_street, to_street, and borough. And we need to check the sign description desc to make sure it’s actually a duplicate.

To do this in Ruby, a Set datatype is ideal for identifying duplicates, we just fill it like an array, and checking for presence of data is very fast.

Coordinates

Eventually we’ll plot these signs on a map, and if we look back to the sample data, we’ll see that we have sign_x_coord and sign_y_coord.

Only thing is they don’t appear to be latitude and longitude. Rather they are EPSG:2263 coordinates (EPSG is the system, and 2263 corresponds to the NYC region).

EPSG is an alternate system meant for hyper-local location targeting - which in this case - signs on a city block - seems to be a valid use case.

We need to convert these coordinates to lat/long to feed into Google maps - here’s our converter.

App

The processed data is exported to a CSV file that we load into our frontend. It’s all fairly straightforward HTML, Javascript, Google Maps Apis, with most of the logic living here.

Frontend Performance

We started with 440,363 rows in our original dataset, and after filtering for 90 minute ASP and removing dupes, we have 57,587 rows in our CSV.

If we try to add 57,587 markers onto a Google Map, our browser will explode.

Instead, we have a couple of performance-related tricks.

  1. Segment all the markers by borough, and default to showing Manhattan markers. You can still enable all 5 boroughs using the checkboxes, but at least on initial load, we reduce loading by 89% (only 11% of signs are in Manhattan).
  2. Use heat maps when zoomed out. We determined a zoom threshold where showing individual location markers would render quickly (const MARKER_ZOOM_THRESHOLD = 16;), and when zoomed out farther than that, we’ll display a heat map of the markers, rather than the markers themselves. Heat maps are performant and useful for large chunks of location data. It’s still important to have the individual markers at an appropriate zoom level, so that we can see information regarding that sign, and even pop into a street view if need be.

The end

Ride safe and free park.