Silencing the Span
Rendered copy. The authoritative version of this document is data-collection/README.md in the repository. This page is regenerated from it by build_pages.py — roughly 10,779 words. Not peer-reviewed. No option in this document is recommended for procurement.

Establishing train frequency over the Manhattan Bridge, and who is under it

Six scripts that answer four questions this programme had been asserting answers to without ever deriving them: how many trains actually cross the Manhattan Bridge and when, how many people are underneath to hear them, how long each of them stays, and what space they are standing in.

Script Answers Cost
bridge_schedule.py What is scheduled, by hour and day type One 5 MB download, about a minute
bridge_realtime.py What actually ran Polls; a week takes a week
build_dashboard_data.py Per-event train data for the interactive dashboard Seconds, after the download
build_pedestrian_data.py How many people are underneath, and when Four API pulls, about a minute
build_cohort_model.py How long they stay, and how badly that is identified About six minutes of arithmetic
fetch_geodata.py What shape of space they are standing in Two API pulls, about a minute

None needs an API key, an account, or a payment method. All use only the Python standard library except bridge_realtime.py, which needs pip install gtfs-realtime-bindings protobuf.

Interactive views: visual-review/frequency-dashboard.html, visual-review/agent-model.html and visual-review/noise-canyon.html


Why not Google Maps

The originating thesis was that a week of Google Maps travel data would establish running frequency. It will not, for three reasons.

It is the wrong direction of travel. Google Maps does not originate NYC transit schedules. It ingests the MTA's published GTFS feed. Querying Google means paying for, and accepting restrictions on, a redistribution of a file the MTA gives away with no restrictions at all. The file is linked in bridge_schedule.py.

The Terms of Service forbid the use. Google Maps Platform ToS prohibit scraping and bulk export, and restrict how long results may be cached. A week-long frequency census is precisely bulk extraction and long-term storage. (Rated 3/5 SNIPPET - read from secondary summaries of the ToS, not from a legal reading of the current text. If this route were ever taken seriously the actual clause would need reading.)

The API returns the wrong object. Directions and Routes return itineraries - "leave at 14:12, arrive 14:31". Frequency has to be inferred by firing thousands of queries at staggered departure times and differencing the results. That is expensive, lossy, and prohibited, to reconstruct a number that is stated outright in a free file.

Conclusion: go to the MTA feed directly. It is the upstream source for Google's own answer.


The premise correction

A week of normal travel data would establish day-time running frequency from York St and East Broadway.

Both of those stations are on the F line, through the Rutgers Street Tunnel. Neither is on the Manhattan Bridge. Verified against MTA's own stops.txt and trips.txt:

Station stop_id Routes serving it On the bridge?
York St F18 F, FX only No - Rutgers Street Tunnel
East Broadway F16 F, FX only No - Rutgers Street Tunnel
Grand St D22 B, D Yes - north tracks
Canal St Q01 N, Q Yes - south tracks

A week spent at York St would have measured the F train, which does not cross the bridge and contributes nothing to the noise in DUMBO. The error is easy to make - York St is the closest station to the affected blocks, and it is the station a resident of DUMBO uses. It is just not the station the noise comes from.


Four traps, all of which silently produce a wrong number

Three more, specific to the corridor geometry, are in the geodata section below.

These are not hypothetical. Each one was hit while writing these scripts.

Trap 1 - stop_name is not unique. "Grand St" is a B/D station in Manhattan (D22) and an L station in Williamsburg (L12). "DeKalb Av" is a Brooklyn bridge-side station (R30) and an L station in Bushwick (L16). "Canal St" resolves to six different stop_id values. Any join on station name silently merges platforms that are miles apart. Always key on stop_id.

Trap 2 - DeKalb Av undercounts by about half. It looks like the natural chokepoint: everything from the bridge passes through it. It does not stop there. The D and the N run express and skip DeKalb, running Grand St or Canal St straight to Atlantic Av. Counting at DeKalb yields roughly 1,269 of 2,524 weekly crossings and gives no error message. The correct chokepoints are Grand St D22 for B/D and Canal St Q01 for N/Q - every bridge train stops at one of them.

Trap 3 - route ID is not enough. The R and the W never touch the bridge; they use the Montague St tunnel. Worse, the N is not consistently a bridge train: of 717 weekly N crossings of the East River, 584 are via the bridge and 133 via Montague. Filtering on route_id == "N" overcounts. The stop pair has to be checked.

Trap 4 - static and realtime disagree about stop_id. The static feed identifies the Canal St N/Q platform as Q01. The realtime feed was observed emitting R23 for the same trains. Filtering realtime on the static ID returned 16 traversals where the true figure was 48 - a 3x undercount, with no error and no warning. bridge_realtime.py accepts both.


The result

bridge_schedule.py output, feed 20260731-149-st-hostos, valid 2026-05-26 to 2026-09-07. All four tracks, both directions.

Hour Weekday headway Saturday headway Sunday headway
00 16 225s 17 212s 16 225s
01-04 12 300s 12 300s 12 300s
05 15 240s 14 257s 13 277s
06 42 86s 26 138s 24 150s
07 58 62s 31 116s 29 124s
08 67 54s 32 112s 31 116s
09 65 55s 32 112s 32 112s
10 59 61s 32 112s 32 112s
11 60 60s 35 103s 34 106s
12 60 60s 34 106s 35 103s
13 60 60s 36 100s 36 100s
14 62 58s 34 106s 34 106s
15 58 62s 36 100s 36 100s
16 62 58s 34 106s 34 106s
17 66 55s 36 100s 36 100s
18 62 58s 34 106s 34 106s
19 57 63s 36 100s 35 103s
20 53 68s 34 106s 32 112s
21 48 75s 33 109s 31 116s
22 34 106s 32 112s 30 120s
23 21 171s 21 171s 19 190s

Daily totals: Weekday 1,073 - Saturday 667 - Sunday 651.

Per-route daily traversals:

Day B D N Q Total
Weekday 220 296 242 315 1,073
Saturday 0 216 171 280 667
Sunday 0 216 171 264 651

The B does not run weekends at all. Weekend service is not "the same trains less often" - the route mix changes, and with it the car classes, so the weekend acoustic signature is not the weekday one attenuated.

By noise period

Periods follow the EU Environmental Noise Directive 2002/49/EC Annex I default split, quoted verbatim: "the day is 12 hours, the evening four hours and the night eight hours... the default values are 07.00 to 19.00, 19.00 to 23.00 and 23.00 to 07.00 local time." (5/5 VERIFIED, primary text.)

Day type Period Trains Per hour Hourly range Mean headway North / south
Weekday Daytime 739 61.6 58-67 58 s 372 / 367
Weekday Evening 192 48.0 34-57 75 s 93 / 99
Weekday Night 142 17.8 12-42 203 s 71 / 71
Saturday Daytime 406 33.8 31-36 106 s 204 / 202
Saturday Evening 135 33.8 32-36 107 s 67 / 68
Saturday Night 126 15.8 12-26 229 s 62 / 64
Sunday Daytime 403 33.6 29-36 107 s 203 / 200
Sunday Evening 128 32.0 30-35 113 s 63 / 65
Sunday Night 120 15.0 12-24 240 s 59 / 61

Direction is very nearly balanced in every period - within a few percent everywhere. Whatever asymmetry exists in the noise is not an asymmetry of traffic.

Note the weekday night range: 12 to 42. The night period runs to 07:00, so the 06:00 hour sits inside it, and on a weekday that hour carries 42 crossings. Under the Lden convention the night period carries a +10 dB penalty and the evening +5 dB, on the reasoning that the same sound does more harm at those hours. That makes 06:00-07:00 the single most heavily weighted busy hour of the day, and it is invisible in any framing that starts the day at 06:00. (The +5/+10 weightings are 3/5 SNIPPET - the Directive's formula is an image in the source PDF and was read from secondary summaries. No penalty is applied to any number in this repository; these are counts of events, not levels.)

The dashboard

visual-review/frequency-dashboard.html presents all of the above interactively - by hour, by route, by direction, by day type - and lets you vary the coincidence window and the overlap model. It is self-contained: no build step, no server, no network, no dependencies.

Rebuild its embedded data with python build_dashboard_data.py.


Three findings that were not being looked for

1. Peak park use and peak train frequency are out of phase - narrowed

An earlier version of this file published the following:

Peak park use and peak train frequency are out of phase. Brooklyn Bridge Park is most heavily used on weekend afternoons. That is when bridge traffic is at its lowest daytime value: 34-36 per hour on a Saturday against 57-66 per hour on a weekday afternoon - a factor of about 1.8.

Person-exposure is attendance multiplied by event rate, and the two move in opposite directions across the week.

The arithmetic is right. The generalisation is withdrawn. It silently assumed one receptor population - park visitors - and then spoke as if that were the affected population.

The affected corridor is much larger than the park. It runs from the York St station down to the water entrance to Brooklyn Bridge Park, and it contains at least three populations with different daily and weekly rhythms:

Population Peak presence Relationship to train rate Phase
Residents Every hour Exposure tracks traversals directly; weekday is worst, and the daytime period alone carries 69% of the weekday total In phase
Commuters and workers Weekday morning and evening peaks Peak corridor pedestrian flow coincides with the 24-hour maximum train rate In phase
Park visitors and tourists Weekend afternoons Heaviest attendance against the lowest daytime train rate Out of phase

There is no single phase relationship, because there is no single receptor population. Two of the three are in phase; the withdrawn claim generalised from the one that is not.

The sharper consequence: the worst case is not the park. It is the corridor on a weekday morning, when the train rate is at its 24-hour maximum - 67 crossings between 08:00 and 09:00, a 54-second headway - and the corridor is carrying commuters. The original framing could not see that case, because it was only looking at the park.

That replacement is also withdrawn

The paragraph immediately above was written before any pedestrian data existed. It is now measurable, and it is wrong.

The worst case is not the park. It is the corridor on a weekday morning, when the train rate is at its 24-hour maximum.

That is withdrawn. The 08:00 hour does carry the most trains - 67. It carries 46% of the corridor's daily maximum number of people. Multiply the two and it scores 50 out of 100 on the exposure index. The actual maximum is 14:00, with about 3,780 people present against 62 crossings.

Day type Trains peak People peak Exposure peak Index at 08:00 Index at 13:00
Weekday 08:00 13:00 14:00 50 97
Saturday 13:00 13:00 13:00 23 100
Sunday 13:00 14:00 15:00 19 100

This programme has now made the same error twice, in opposite directions. The first framing optimised on attendance alone and concluded the concern was the park on a weekend afternoon. The correction optimised on train rate alone and concluded it was the corridor on a weekday morning. Exposure is the product, and the product peaks in between - in the early afternoon, on every day of the week.

All three day types peak within two hours of each other despite carrying very different train rates. The reason is that train rate is nearly flat from 07:00 to 19:00 while presence changes by a factor of four inside the same window. Where people are matters more than where trains are, and only one of those two had ever been measured.

The index is deliberately relative - each day type scaled to its own maximum. No absolute person-event figure is published, because presence here is a lower bound covering only subway-delivered transients, and multiplying a lower bound by a train count produces a number that looks authoritative and is not.

What is verified and what is not. The train rates are 5/5 VERIFIED. Presence is 2/5, up from the 1/5 this section carried until the denominator work below was done. No pedestrian has been counted directly anywhere in this corridor. What changed is that arrival rate is now derivable from fare data, so the exposure index is a statement about shape rather than a guess. An absolute exposure figure still cannot be published, because dwell time is unmeasured and residents are not in the accumulation at all.

There is also a resolution here of the premise correction above. York St is an F train station and no train crossing this bridge stops there, so it is useless as a place to measure the source. It is nonetheless the pedestrian gateway to the affected corridor. It matters as a receptor origin, not as a source - which is why it felt relevant, and why measuring trains there would still have measured the wrong thing.

2. The MTA's own train counts do not match the MTA's own schedule

From the section 1204-a noise sessions already recorded in IDEA-CONCEPT.md section 1.2:

Site Trains counted Session length Implied rate Implied headway
BBP dog run 26 37:45 41.3/hr 87s
DUMBO Archway 18 25:35 42.2/hr 85s
Adams St Library 9 18:56 28.5/hr 126s

The weekday daytime schedule is 53-67/hr. Weekend midday is 32-36/hr. Every measured session falls below the weekday schedule, and two of the three fall above the weekend schedule.

Three candidate explanations, none verified:

  1. The sessions were conducted at weekends or off-peak. This would fit 28.5 and partly fit 41-42.
  2. Service was rerouted during the measurement window.
  3. The observer counted audible events, not trains. On a four-track crossing, two trains passing together are heard as one.

3. Explanation 3 would undermine this programme's own derived result

IDEA-CONCEPT.md section 1.7 derives event duration as

Te = (T*10^(Leq/10) - T*10^(Lbase/10)) / (N*(10^(Lmax/10) - 10^(Lbase/10)))

N is the train count, in the denominator. If N is an event count rather than a train count, and events merge, then N is an undercount and Te is overestimated. The published 5.70 / 6.28 / 7.25 s figures would then be durations per audible event - which would also help explain why three independent sites agree as closely as they do, since merging is a smoothing operation.

How large is the effect? An earlier version of this file answered that with the following table:

Day Merged pairs Traversals Distinct events Loss
Weekday 67 1,073 1,006 6.2%
Saturday 21 667 646 3.1%
Sunday 22 651 629 3.4%

That is withdrawn. It is not a weak lower bound, as was claimed at the time. It is not a bound at all - it is an artefact of the feed's time resolution, and it should never have been published as a quantity.

Every scheduled departure in this feed falls on an exact :00 or :30 second. Across 1,073 weekday traversals there are exactly two distinct sub-minute values. The schedule is quantised to 30 seconds, so the gap between two consecutive crossings is never recorded as 1 s, or 4 s, or 12 s - the entire range in which two trains would actually merge acoustically is empty by construction.

The consequence is directly demonstrable, and visual-review/frequency-dashboard.html demonstrates it live: set the overlap model to Schedule and drag the coincidence window from 1 s to 29 s. The answer does not move - it sits at 50.3 events/hr the whole way - and then jumps to 33.0 the instant you cross 30 s. Counting coincidences in the schedule measures the scheduler's rounding, not the railway.

What can honestly be said instead

Since the schedule cannot locate the answer, bracket it with two models that do not depend on it. For the weekday daytime period (61.6 trains/hr) at a 6-second coincidence window:

Model Assumption Events/hr Merging
Regular Perfectly even spacing - least merging physically possible 61.6 0%
Poisson Maximum disorder - most merging plausible 55.6 9.8%

The Poisson figure is N * exp(-lambda*w) with lambda = N/3600. It is defensible as a ceiling on merging because the superposition of several semi-independent streams tends toward Poisson (Palm-Khintchine), and four tracks is enough for that to be directionally right. Real operation is more regular than Poisson and less regular than a metronome, so the true value lies inside the bracket - and the bracket narrows sharply at night, where low rates make coincidence rare regardless of model.

At a 6-second window the merging ceiling is 9.8% by day, 7.7% in the evening, 2.9% at night. So the discrepancy in the table above - the MTA counting 28-42/hr against a scheduled 53-67/hr - is too large to be explained by acoustic merging alone. Merging can account for at most about a tenth of it. That does not rule explanation 3 out, but it does mean explanation 1 or 2 must be doing most of the work.

A single live snapshot showed a minimum observed gap of 3 s and two D trains predicted at the identical second. The real coincidence rate is an empirical question that only bridge_realtime.py, run for a week, can answer - and it bears directly on whether section 1.7 is measuring what it claims to measure.


What these scripts still do not give you

Schedule is not service. bridge_schedule.py reports what is planned. Delays, reroutes, planned weekend work and incidents all move the real number.

Neither feed contains non-revenue moves. Work trains, put-ins, lay-ups and equipment moves cross the bridge and make noise - in some cases more noise, since work equipment is not designed for ride quality. They appear in no public feed. Every figure here is a systematic undercount of acoustic events by an unknown amount.

Realtime is prediction, not observation. stop_time_update carries the MTA's estimate of when a train will reach a platform. It is revised as the train approaches, and bridge_realtime.py keeps the last revision, but it is never a measurement.

A platform is not the bridge. Grand St and Canal St are near the bridge, not on it. Converting a platform time to a time over Brooklyn Bridge Park needs a travel-time offset that has not been measured. For rate and headway this cancels. For aligning a specific train to a specific acoustic event - capture C4 in FIELD-CAPTURE-PROTOCOL.md - it does not, and it is the reason C4 is the least certain capture in that document.

Confidence. The GTFS-derived numbers are 5/5 VERIFIED: they come from MTA's own published feed, read directly, and both scripts reproduce them from scratch. The Google Maps ToS position is 3/5 SNIPPET. The three findings above are 2/5 UNVERIFIED interpretations of verified numbers, and the third is written as a challenge rather than a claim.


Counting the denominator

build_pedestrian_data.py answers a question the train work could not: how many people are actually under the bridge, and when.

It does not answer it completely. It closes one half of the problem and names the other half precisely.

The direction trap

The obvious move is to pull turnstile data at York St and High St. That measures the wrong direction, and nothing in the data says so.

MTA's own column definition for the hourly ridership feed, quoted verbatim:

Total number of riders that entered a subway complex via OMNY or MetroCard at the specific hour and for that specific fare type.

Entries are people leaving the corridor on foot-to-subway. The question was about people arriving. Over a whole day the two roughly balance, so a daily total would have looked fine. By hour they are close to opposite - at a residential station, entries peak when residents leave and exits peak when they return. Using entries as a proxy for arrivals produces a curve with the peak in the wrong place.

The fix is the Origin-Destination Ridership Estimate, a separate MTA feed that carries an inferred destination for each trip. Summed over all origins with destination York St or High St, it gives arrivals by hour and day of week.

Four sources, none of which counts a pedestrian

Source Host Dataset Gives Rating
MTA Subway Hourly Ridership: Beginning 2025 data.ny.gov 5wq4-mkjj Entries, hourly, observed 5/5 VERIFIED
MTA Subway Origin-Destination Ridership Estimate: Beginning 2026 data.ny.gov 28vm-gjqr Arrivals, hourly by day of week, inferred 4/5 VERIFIED
Brooklyn Bridge Automated Pedestrian Counts data.cityofnewyork.us 6fi9-q3ta Walkway flow, hourly, directional 4/5 VERIFIED
PLUTO tax lots data.cityofnewyork.us 64uk-42ks Residential units 5/5 VERIFIED

None needs an API key. All four were pulled with urllib and no credentials. The Census API, by contrast, now refuses key-less requests with Missing Key, which is why resident counts come from tax lots rather than ACS.

What it found

Typical day, corridor total for both stations, EU 2002/49/EC periods:

Day type Period Arrive by subway Enter subway Walkway to Brooklyn Walkway to Manhattan
Weekday Daytime 18,074 16,401 7,147 6,605
Weekday Evening 2,877 4,692 462 492
Weekday Night 1,379 848 63 74
Saturday Daytime 16,105 13,956 10,895 10,176
Saturday Evening 2,615 4,320 875 751
Saturday Night 1,106 1,046 68 108
Sunday Daytime 14,551 12,106 8,886 8,008
Sunday Evening 2,066 4,068 639 567
Sunday Night 816 778 55 83

Subway figures are May 2026. Walkway figures are 2019, because the counter died in 2019 and no live pedestrian counter exists anywhere near DUMBO. Of the ten pedestrian-capable sensors NYC DOT currently operates, the nearest is at Willis Avenue in the Bronx. The Brooklyn Bridge and Manhattan Bridge counters that are still live are bicycle-only - including one named "Manhattan Bridge Ped Path", which counts bikes.

The corridor fills in the morning and empties in the evening

Net flow, arrivals minus entries, weekday:

Hour 06 07 08 09 10 12 14 16 18 20 22
Net +374 +511 +546 +800 +590 +183 -14 -332 -454 -590 -303

That is the signature of a destination district, not a dormitory. A purely residential neighbourhood would show the opposite. The people under the bridge during the busiest train hours are disproportionately not the people who live there. They are workers and visitors, and they are the population least likely to appear at a community board meeting.

A cross-check that was not designed in

Over a full day, arrivals and entries at the same two stations should balance. They do:

Day type Arrivals (inferred) Entries (observed) Closure error
Weekday 22,330 21,942 +1.77%
Saturday 19,827 19,322 +2.61%
Sunday 17,433 16,952 +2.84%

Two datasets built by entirely different methods - one counting fare transactions, one inferring destinations from return swipes - agree to within 3% at the level of two station complexes.

They are not fully independent. The origin-destination estimate is scaled to match total system ridership. But that scaling is system-wide, not per-station, so agreement at this level is still informative. The residual is carried through as the uncertainty band on the accumulation curve in the dashboard, rather than being discarded.

The walkway figures were checked the same way: the typical-day profile implies 6,013,344 pedestrians in 2019 against a published annual sum of 6,011,174, a 0.04% difference attributable to rounding.

Residents

Study area Lots Homes Residents
DUMBO and Vinegar Hill 231 6,314 9,875-13,360
Affected corridor 376 10,128 15,840-21,431
Wide catchment 822 20,279 31,716-42,910

Homes are a tax fact. Residents are an assumption - units multiplied by 92% occupancy and a household size of 1.7 to 2.3, reported as a range for that reason and rated 2/5.

The boxes are stated as explicit coordinates in the script rather than borrowed from a census geography, because no census geography matches this corridor. DUMBO is not its own Neighborhood Tabulation Area; it sits inside BK0202 Downtown Brooklyn-DUMBO-Boerum Hill, an area of about 2.2 square kilometres. Using that NTA would have overstated the affected population several times over while appearing more official.

The "DUMBO and Vinegar Hill" box includes Farragut Houses, which sits directly under the bridge approach. That is deliberate. It is public housing, it is the closest large residential population to the structure, and any framing that treats this as a question about loft conversions and a park has already excluded the people most exposed.

Little's Law, half solved

The number of people present in an area is L = lambda * W - arrival rate multiplied by mean dwell time.

This work produces lambda. It does not produce W. A commuter walking from York St to an office on Water St is exposed for four minutes. A family on the Brooklyn Bridge Park lawn is exposed for three hours. Both count once in the arrival figures.

Until dwell time is measured, the accumulation curve is a lower bound on transient presence, not a headcount, and it omits, in rough order of size:

  • Residents, who are present whether or not they touch a turnstile. Almost certainly the largest omission, and the population whose exposure is continuous rather than episodic.
  • Walkway arrivals - about 7,700 toward Brooklyn on a typical weekday in 2019. Unknown today.
  • Every other mode - the ferry at Fulton Ferry Landing, the B25 and B67, Citi Bike, private car, the Brooklyn Heights stair.
  • Mode-swapping. Anyone who arrives by subway and leaves on foot over the bridge is counted arriving and never leaving. The small closure error suggests these flows roughly cancel. It does not prove it.

Where this section is likely to be wrong

The origin-destination hour is probably the origin hour, not the arrival hour. MTA's column description says only "the hour of the day in which the subway trips occurred". If it is the entry hour, every arrival figure is timestamped 10-25 minutes early, which shifts the accumulation curve left by part of an hour. This has not been confirmed with MTA.

Not everyone who arrives at High St is going to DUMBO. High St also serves Brooklyn Heights, and no allocation has been attempted. The same is true of York St and Vinegar Hill. Arrivals are attributed to the corridor in full, which overstates it.

The walkway counter was on the Manhattan approach, not the Brooklyn end. Pedestrians who turned back mid-span are counted as heading to Brooklyn.

2019 predates the 2021 promenade reconfiguration that moved cyclists off the walkway onto the roadway. Pedestrian volumes and behaviour will have changed in ways the 2019 figures cannot show.

One month is not a year. May 2026 was chosen because it is the most recent month present in both MTA feeds. Seasonality in a corridor with this much tourist traffic is likely to be large and is not characterised.


Who is actually here, hour by hour

The pedestrian work above produces a flow. Exposure needs a stock. Those are different quantities and the gap between them is dwell time, which nobody has measured in DUMBO.

build_cohort_model.py closes that gap by inference rather than measurement, and the honest headline is that it half succeeds. It recovers a defensible figure for how many non-residents are present at a given hour. It cannot recover who they are, and the reason it cannot is a permanent property of the data, not a limitation of the search.

The model

Four cohorts, each with its own arrival profile and its own dwell distribution. Presence at hour h is the arrivals in every earlier hour multiplied by the probability that a person who arrived then has not yet left.

Cohort Arrival shape Dwell Source of the dwell figure
Workers Gaussian, fitted 8.5 h, sd 1.6 assumed working day
Visitors Gaussian, fitted 2.31 h mean Louisville waterfront survey, binned
Transients follows the arrival curve 0.35 h assumed pass-through
Residents present unless away schedule, fitted PLUTO units, corridor boxes

The visitor dwell distribution is not a guess. It is a published survey with seven duration bins, stratified by whether the respondent lived locally, and it is applied at a 45% out-of-town share. Its mean is 2.31 h. What it is not is a survey of Brooklyn.

Residents come from the PLUTO tax-lot count established in the section above: 15,840 to 21,431 in the affected corridor, midpoint 18,636. The corridor box deliberately includes Farragut Houses.

What it is fitted to

The only observable is the MTA hourly entries series - people leaving the corridor through a turnstile. The model computes implied departures from its own cohort presence and compares. The fit is a coarse grid over ten parameters followed by two refinement passes.

Day RMS fit error Same, over the resident window Cost of the visitor prior
Weekday 0.186 0.092 +0.053
Saturday 0.127 0.014 +0.064
Sunday 0.150 0.013 +0.031

Ten free parameters against twenty-four data points is a generous ratio. A good fit here is close to guaranteed and is therefore not evidence that the cohort structure is right. It is evidence only that the structure is capable of producing the observed curve, which is a much weaker statement.

The visitor prior, and what it costs

An unconstrained fit pushed the visitor arrival peak to a narrow spike at 19:00, which zeroed visitors during the day and let the long-dwell worker cohort absorb every daytime arrival. Saturday came out with fifteen visitors, which is absurd - a Saturday has more visitors than a Tuesday, not four hundred times fewer.

Two constraints were declared:

  • visitor arrivals peak no later than 16:00
  • the visitor arrival distribution has standard deviation of at least 2.0 h

Justification is the FEIS, which places peak park attendance in the early to mid Sunday afternoon. Saturday visitors moved from 15 to 2,153, Sunday from 9 to 2,944.

A declared prior that improves a result is exactly the kind of move this programme is supposed to distrust, so the prior is priced. The script also sweeps a strictly larger unconstrained grid and reports how much better the unconstrained best fit is. That difference is the third column above: between +0.031 and +0.064 RMS. The prior costs almost nothing in fit and changes the answer by two orders of magnitude, which is the signature of a flat likelihood surface rather than of a prior overriding evidence.

The finding: cohort labels are not identifiable

The script does not report a point estimate. It sweeps the parameter grid and collects every parameter set whose fit is within 10% of the best, then reports the range of outcomes across that admissible family.

Day Admissible sets Non-residents at 14:00 of which workers of which visitors
Weekday 9,248 6,029 to 7,338 3,629 to 5,490 1,460 to 2,313
Saturday 2,791 5,775 to 6,516 3,462 to 4,374 1,881 to 2,330
Sunday 11,590 5,932 to 6,820 2,619 to 3,911 2,541 to 3,137

Read the columns against each other. The total is pinned to about plus or minus 10%. The split inside it swings by more than a factor of one and a half on a weekday, across parameter sets that fit the observed data equally well.

The reason is not subtle once stated. A departure curve carries no job titles. Someone who is in the corridor for eight hours looks identical whether they came to work or came for the day. The only thing that distinguishes a worker from a visitor in this model is the shape of their arrival, and a smooth aggregate curve can be decomposed into two smooth components in a great many ways.

A degeneracy test makes this concrete. It measures the share of fitted worker arrivals landing between 06:00 and 10:00 - if the "worker" cohort does not arrive in the morning, the label is not doing any work.

Day Worker arrivals in the morning window Verdict
Weekday 96% label supported
Saturday 33% DEGENERATE - do not read this as workers
Sunday 63% weak

Saturday's best fit puts the "worker" arrival standard deviation at 5.6 h, which is approaching uniform across the whole day. That parameter sits on the edge of its grid and wants to go further. It has not been chased, because chasing it would produce a smoother number that means even less: a cohort arriving uniformly across a day is not a cohort, it is a residual.

Consequence for anything downstream. Total non-resident presence may be quoted. The worker/visitor split may not be quoted at all on a Saturday, and should be quoted only as a range on the other two days.

The other thing the curve is not

Total presence, including residents, peaks in the evening on every day type - weekday 13:00, Saturday 09:00, Sunday 13:00 for the totals, but the shape is dominated by residents being at home. That is not an exposure curve. Residents at home are indoors, behind a facade, and the one indoor measurement in the record comes from a building whose occupants had already paid privately to isolate it.

The non-resident curve peaks at 12:00 weekday, 14:00 Saturday, 15:00 Sunday, which is consistent with the exposure-index result in the section above and was arrived at by a completely different route.

Where this section is likely to be wrong

The dwell distribution is from Louisville, Kentucky. It is real, published and stratified. It is also about a different park, in a different city, on a different river, with a different mix of tourists and no elevated railway over it. This is the largest single transfer in the model and it is load-bearing.

Ten parameters against twenty-four points. The fit quality reported above is close to meaningless as validation. The identifiability sweep is the only part of this section that constitutes evidence about anything.

Entries are still entries. The model is fitted to the same series whose direction trap is documented above. If the origin-destination hour is the entry hour rather than the arrival hour, every arrival is timestamped early and the fitted arrival Gaussians absorb the error silently.

Residents are a stock with no measured schedule. The away-fraction, leave hour and return hour are all fitted, which means the resident cohort is the most flexible object in the model and will absorb misfit from everywhere else.

Nothing here is a headcount. Presence is modelled. The word "modelled" should survive every act of quotation, and if it does not, this section has done net harm.

What would actually move this

The three weaknesses above are not equally tractable, and two of them cannot be improved by working harder on the same data.

Q52. Can any cohort be pinned from outside the departure curve? The identifiability sweep is not a statement about this fit being poor. It is a statement that a departure curve carries no labels: someone present for eight hours is the same observation whether they came to work or came for the day. Nine thousand parameter sets fit within ten per cent of the best, and across them the fitted worker count at 14:00 ranges 3,629 to 5,490 while the visitor count ranges 1,460 to 2,313. The fit cannot narrow that, because the information is not in the series. One exogenous number does. LEHD LODES Workplace Area Characteristics reports jobs by census block and is free; it pins the worker cohort directly and demotes the visitor cohort from a guess to a residual. Method 34.

Q53. How much of a resident's day is spent outdoors, and by which type? The model tracks whether a resident is inside the district. Being inside the district includes being asleep indoors, so the resident curve is an occupancy curve and the exposure analysis is right to exclude it — but excluding it entirely asserts that residents are never outdoors, which is false and is not a smaller error than including them at full weight. The scale is not marginal. Against 18,636 residents, published leisure-walking figures imply an outdoor population in the same range as the whole visitor cohort. Type matters and is measurable: dog owners walk about 300 minutes a week against 168 for non-owners, a difference of roughly nineteen minutes a day. The national instrument is the American Time Use Survey, which codes location, including "outdoors away from home" — and which carries its own trap, recorded below. Method 35.

Q54. Which assumptions move the total rather than only the split? Arrivals exceed entries by 1.77 per cent on a weekday, 2.61 on a Saturday and 2.84 on a Sunday. That residual is real, it is reproducible, and it is currently attributed to nothing. It is the only available bound on how wrong the arrival and departure logistics are. Sweeping every declared assumption and sorting the results into those that change the corridor total and those that only redistribute it between cohorts would separate the parameters that matter from the parameters that merely look uncertain. Method 36.

The trap in the obvious instrument

LOCUS. George, B.J. and McCurdy, T., "Investigating the American Time Use Survey from an exposure modeling perspective", Journal of Exposure Science and Environmental Epidemiology, 21(1), 2011, pp. 92–105, abstract:

Another limitation is that ATUS does not distinguish between indoor and outdoor activities at home, two of the traditional locational demarcations used in human exposure modeling.

and, on the scale of the gap:

The ATUS does not report the location for a number of activities regarded as "personal." For 2006, personal activities with missing location information totaled 572 min/day, on average, for survey participants: about 40% of their day.

ATUS is the instrument any exposure modeller reaches for, it has been evaluated for exactly this purpose by the EPA's own exposure modelling branch, and it was found wanting for the exact distinction this question needs. Two consequences follow, and only one of them is fatal. The home-versus-yard conflation matters little in a district of apartment buildings. The 40 per cent of the diary day with no location code matters a great deal, although it is concentrated in personal care, which is overwhelmingly indoors and at home, so the bias it introduces here is signed and can be bounded rather than merely noted. Rated 5/5 for what the paper says, and 3/5 as a transfer, because the evaluation is of the 2003–2007 waves.


Where the people go, not merely how many there are

../visual-review/agent-model.html is the next term again. The cohort model supplies a duration; it has no position. Exposure is an integral of level along a path, so a duration without a path is still not exposure.

The agent model is a demonstration of that mechanism and not a measurement of DUMBO. It carries its own weaknesses list on the page. Two results from building it are worth recording here because they bear on the data work:

A propagation model over the four MTA measurement points cannot be fitted. The three near-bridge sites agree with ideal line-source spreading to within 0.15 dB, which looks like a result and is not one: a Monte-Carlo test that jitters the digitised positions by plus or minus 10 m puts the fitted decay exponent anywhere between 0.7 and 22.3. The agreement is coincidence.

Distance does not order the measurements, and that is robust. The DUMBO Archway sits directly under the structure and is the quietest of the four. The Brooklyn Bridge Park site several hundred metres away is the loudest, and exceeds a line-source prediction fitted to the near sites by about 17 dB. This survives any plausible correction to the positions. It matters because under-deck treatment - the intervention most often proposed, including in the November 2025 residents' petition - would be applied where the measured problem is smallest.

Who is standing in it, and the number that is refused

The agent model now carries a fifth capability rung that models the susceptibility composition of the people on those paths: general decreased sound tolerance, autistic listeners with current hyperacusis, people aged 65 or over, infants who are carried, people living with a history of cancer, and dogs.

No class is given a decibel penalty anywhere in the model, and that restraint is the design. A per-class adjustment to the dose would be a fabricated exposure-response function wearing the costume of a measurement. The dose is a property of the air; it does not change because of who is standing in it. What the layer changes is who is standing there, and whether they chose to be. That invariance is asserted on the page and it is tested: ../visual-review/verify_agent_model.js runs the model with the layer off and on at the same seed and fails the build if any group whose itinerary did not change receives a different dose.

Two questions come out of building it, and both are cheap.

Q61. Who is actually in this corridor, by attribute? Every share in the layer is a national prevalence applied flat, and every one is rated 1/5 on whether it applies here, because nobody has ever counted the people in this corridor by any attribute at all. A flat national rate is knowably wrong at a place whose purpose selects for a class, and this corridor is full of them: a dog run, a carousel, a lawn, and the residential blocks at Farragut Houses. The correction is a clicker at the same cordon Method 28 already requires, tallying prams, dogs, mobility aids and apparent age band. The honest half of the method is what it cannot do: an age band, a pram and a lead are observable; hyperacusis, autism and a cancer history are not, so three of the six shares stay national and the method must say which. Method 42.

Q62. What licenses comparing a discomfort threshold to a field level? Loudness discomfort centres near 100 dB HL for normal-hearing listeners and hyperacusis is commonly marked at 90 dB HL or below; the MTA measured 98.90 dB(A) peaks at the Brooklyn Bridge dog run. Those look comparable and they are not the same units - one is per-frequency, pure-tone and headphone-presented against audiometric zero, the other broadband, free-field and weighted by one fixed curve. Bridging them requires the frequency-specific reference thresholds and the third-octave spectrum of the actual sound, and this repository has no spectrum for either bridge - which its own field protocol names as the largest single fabrication in the corpus. So the model refuses the comparison, and the refusal is worth more than the estimate would have been: it converts an open-ended plea for more evidence into one named missing measurement, capture C1. Method 43.

When people arrive, and the objection it retires

The model's arrival process was, until this was checked, a deterministic ramp - a fixed number of groups admitted per tick, with no random draw anywhere - sitting underneath a code comment that described it as "a Poisson-ish trickle". That is withdrawn. It was not Poisson-ish; a constant rate is the one thing arrivals never are, and a subway station is the least plausible place on earth to find one. A door opens and a slug of people climbs the stairs together.

So three arrival processes were implemented and compared: the original ramp, a Poisson process, and a burst process in which groups entering at the York Street or High Street gateways are admitted in slugs on their feeder line's headway, spread by a stair-climb delay. Groups who walked over the bridge or stepped off the ferry keep the Poisson process, because there is no starting gun for someone who walked here.

The population is built before the schedule is assigned, from a separate random stream, so group i is the same group under all three processes - same persona, same party size, same itinerary, same gateways. A diff across processes is therefore a diff of timing and of nothing else, and the verification harness asserts exactly that rather than trusting it.

The result is a clean negative, and it is worth more than a positive would have been. Across twelve seeds run pairwise, at the model's default settings:

Process Peak groups/min Mean dose Contention events
Uniform ramp 14.0 86.083 dB 57.1
Poisson 31.5 86.082 dB 58.0
Train doors 40.0 86.088 dB 68.3

Bunching rises 2.9x. The paired burst-minus-Poisson difference in mean dose is +0.006 dB, and the largest single-seed difference across twelve seeds is 0.047 dB.

One correction, and it is about how a number was framed rather than how it was computed. The first version of this section reported the same run as

"peak arrivals per minute go from 14.0 to 31.5 to 58.9 - a 4.2x increase in bunching - and the paired difference in mean dose is +0.001 dB"

Those figures are correct and they are not the model's default settings. That run was executed with the stair-climb spread tightened to 0-10 s, the adversarial end of the sensitivity sweep, rather than the default 25-150 s. Reporting them plainly invited anyone who re-ran the comparison on the page to get different numbers and to have no way of knowing why. That is withdrawn. The table above is the default run. The tightened run is retained below as what it always was: a check, not a headline.

The adversarial check strengthens the result rather than qualifying it. Re-running the same twelve seeds with the stair spread tightened to 0-10 s - a whole train reaching the street almost at once, which is physically impossible - pushes bunching to 58.9 per minute, 4.2x the ramp, and the paired dose difference falls to +0.001 dB with a worst seed of 0.037 dB. Making the burst as sharp as it can possibly be does not make it matter. The two runs are reported separately rather than averaged, because averaging a default against an adversarial case would describe neither.

That null has a mechanism, which is why it is reportable rather than merely observed. Arrival time sets a group's phase against the train cycle, and phase can only matter if the visit is comparable to the headway. Weekday daytime headway on the bridge is 58 s, and even the shortest-dwell tenth of the modelled population is in the corridor for a median of about 430 s - roughly seven crossings. Shifting phase moves that by at most one crossing in seven, and across a population it cancels.

Crowding is the exception, and it is real. Contention events rise from about 58 to 68, a paired difference of +10.3 (sd 6.5), positive in eleven of twelve seeds. People who arrive together compete for the same bench at the same moment. So the arrival process is irrelevant to the quantity this model exists to compute, and relevant to the one it computes as a side effect.

This bounds an objection rather than answering a question, which is the useful thing about it. "Your arrivals are invented, therefore your exposure numbers are invented" was true and remains true - but for the dose it is now invented within 0.05 dB, far below every other uncertainty in the model.

Q63. What is the shortest time anyone actually spends in this corridor? The whole null above rests on one comparison: dwell against headway. It holds because the shortest-dwell decile in the model sits at about 430 s against a 58 s headway. That 430 s is itself modelled, not measured, and it inherits every weakness of the cohort model - which cannot identify its own cohort labels. If a real cohort crosses in under a minute, arrival phase reaches them and the null does not apply to the people it would matter most for, because the shortest visits are the ones a single badly-timed crossing can dominate. This is Method 28 asked from a new direction: not "what is the mean dwell?" but "what is the bottom of the distribution?" Method 28 as specified would answer it at no extra cost, provided the tally records individual crossings rather than only a total.

Q64. Does crowding push people into louder places, and by how much? The one quantity that does respond to arrival structure is contention, and every input to it is invented: the place capacities, the feeder headways (1/5, plausible timetable intervals), and the stair-climb dispersal (1/5, invented). The feeder headways are one constant away from being computed - bridge_schedule.py line 41 reads BRIDGE_ROUTES = {"B","D","N","Q"}, and changing it to {"F"} or {"A","C"} produces them by hour and day type in about a minute. The capacities are not: they would need counting benches, and then watching whether a full bench actually sends someone toward the bridge rather than away from it. Method 44.

Note what this pair does to the priority order. The cheap half of Method 44 - the feeder headways - now has a known small payoff, because the quantity it feeds has been shown not to reach the dose. Running it anyway is defensible only as part of the crowding question, and saying so is the honest reading of a negative result: it retires work as well as motivating it.

What shape of space they are standing in

fetch_geodata.py answers the last question the other five leave open. The frequency work established how often trains cross; the pedestrian and cohort work established roughly how many people are underneath and for how long. Neither says anything about the space itself, and the space is what turns a source level into a received level.

Two public sources, no key, no account:

Source Supplies Rows
NYC OpenData 5zhs-2jue BUILDING Footprint polygon, height_roof, ground_elevation, construction_year 2,342 footprints, of which 1,034 are in the walkable extent
OpenStreetMap via Overpass, ODbL 1.0 Streets, footways, parks, water, coastline, piers, the subway alignment, station nodes 3,142 ways and 9 nodes

build_carousel.py in the repository root draws ../visual-review/noise-canyon.html from them. Slides are declared in ../visual-review/carousel.json; the build refuses to run if any slide lacks a source or a caveat. build_walkable_map.py, also in the root, draws ../visual-review/walkable-map.html from the same two files.

Five traps, in the same style as the four above.

Trap 5. Overpass returns 406 for a reason that has nothing to do with the query. Adding Accept: application/json is necessary and not sufficient. The request also failed with a User-Agent containing parentheses and a semicolon - the shape every browser uses - which trips the endpoint's request filtering. The same query with a plain token User-Agent succeeds. The failure looks like a malformed query and is not one.

Trap 6. The building footprint dataset most search results point at is dead. qb5r-6dgf and nqwf-w8eh both return nothing. 5zhs-2jue is the live one and is the only one of the three that carries the_geom. Query it with within_box(the_geom, north, west, south, east) - note the argument order.

Trap 7. Chainage measured from a fitted axis can start in the river. The alignment is fitted by principal components to the OpenStreetMap track geometry, which returns a centroid on the river span. Selecting a window around s = 0 then silently returns four buildings instead of seventy-six, with no error. The origin has to be slid along the fitted axis to a landmark on land - here the DUMBO Archway, which is also one of the four MTA measurement points.

What came out of it that was not the point of it.

The alignment this repository had digitised by eye for the agent model and the alignment fitted to the open track geometry differ in bearing by 2.3 degrees. That is an independent check on a number that had never had one.

Of the 77 objects in the massing frame, 76 are surveyed to the roof and one is not, and the one that is not is the elevated structure. No public source gives the deck elevation over DUMBO. The drawing therefore renders 76 buildings as solid geometry rated 5/5 and the bridge as a hatched assumed band rated 1/5 - which is the honest rendering, and is also the argument.

Along the 1,482 m walk from the York Street F platform to Pier 1, 347 m lies within a measured band - 23.4 per cent - and those bands are drawn as wide as the position uncertainty in the MTA memos rather than as points.

Two extents, and why the boundary is now named rather than assumed

The fetch originally used one bounding box for everything. That box was wrong in two directions at once and neither error announced itself.

It cut Pier 1 in half. The western edge sat at -73.9975, and the walk this repository keeps describing ends at Pier 1, which starts further west than that. 74 buildings were missing, 9.0 m to 92.4 m tall, median 56.0 m.

It contained no Manhattan at all, so the bridge could only ever be drawn leaving the frame. A drawing of a crossing that shows one bank is not a drawing of a crossing.

The fetch now names two extents and says which is for what:

Extent Bounds What comes from it
STUDY 40.6955 – 40.7085 N, 74.0030 – 73.9775 W Everything walkable, routable and measured. All footway, street and park geometry.
CONTEXT 40.6955 – 40.7175 N, same east and west Buildings only, plus the bridge-carried track geometry. The far bank and the full river span.

Every building record now carries near: 1 if its centroid lies in STUDY and near: 0 otherwise, and every measurement in this repository is taken over near only. The count went from 960 to 2,342, of which 1,034 are near. The carousel was rebuilt after the extension and every published statistic is byte-identical — 76 buildings in the section, tallest 99.36 m, 91.22 m between facades, height-to-width 0.29, 347.25 m of measured coverage. That is the check that the extension is additive rather than a silent restatement.

Trap 8. Overpass out geom returns a way's entire geometry when the way merely touches the box. Extending north to reach Manhattan moved the corner of the request far enough to clip the Williamsburg Bridge. Overpass returned the whole Williamsburg Bridge — about 1.5 km of track, most of it more than a kilometre outside the request, tagged identically to the geometry that was wanted, with no error and no warning.

The fitted bridge bearing moved from 337.26° to 43.41°. That error was large enough to be obvious. A four-degree error would not have been, and this repository has already published a 2.3° discrepancy as a finding. The fetch now runs _outside(), a vertex test that rejects any way with a vertex beyond the requested box, and build_carousel.py asserts the fitted bearing lands between 325° and 350° and names fetch_geodata._outside in the assertion message.

Trap 9. Chaining ways by endpoint proximity is wrong for parallel tracks. The Manhattan Bridge carries four tracks about 10 m apart. Joining ways whose endpoints come within 40 m — correct for a route split into consecutive pieces — runs up one track and back down the next, producing a centreline that doubles back on itself. It draws as a fold in the deck and raises nothing. The span is now built by projecting every vertex onto the fitted axis, binning by chainage at 60 m and averaging, so the deck centreline is the mean of the tracks it carries and no two ways are ever joined.

The walkable model, and the audit it made possible

../visual-review/walkable-map.html is built by build_walkable_map.py from the same two files. It carries a routing graph over the footway network — 8,712 connected nodes, 10,200 edges — and routes the walk from the York Street F platform to Pier 1 by shortest path rather than drawing it between waypoints.

That walk is 1,528.3 m and the carousel's is 1,482.44 m. These are two different walks, not two estimates of one walk. The carousel route has five waypoints and stops at the water. This one adds a sixth, Fulton Ferry Landing, because a route that stops at the water's edge omits the half of the walk that turns back under the bridge — which is the half the canyon argument is about. Neither figure corrects the other and they must not be quoted against each other.

The graph made a check possible that nothing here could previously perform: every inherited coordinate can now be tested against surveyed ground.

What was tested Result
18 places in the population model 2 marked outdoors fall inside a surveyed building footprint — "Washington St view" inside an 81 m tower, "York St (F)" inside a 15 m building
The same 18 2 more fall inside a footprint and are marked indoors, which is correct
Pier 1 lawn and promenade 47.9 m from the nearest walkable path
4 MTA measurement points 2 resolve inside a footprint, one of them "Front and Pine Street"

A position inside a building, for a place the model itself marks as outdoors, is not an approximation. It is a coordinate that cannot be right.

Nothing has been moved. Every offset is published beside the original. Editing a coordinate quietly would replace one guess with another and delete the audit trail, and this repository has no written rule yet for how a place named in words becomes a point. Writing that rule is Method 48.

Q68. Is "Front and Pine Street" inside a building because we digitised it wrongly, or because the MTA measured indoors there? The memo describes some sessions indoors and some outdoors, and section 1.7 of the concept document found independently that the indoor rows behave differently from the outdoor ones. But Front and Pine Street is the name of an intersection, and an intersection cannot be inside a building. Either the position is ours and wrong, or the label is the MTA's and describes the nearest cross-streets to an indoor session. The two readings imply different corrections and the difference is not cosmetic: a level attributed to a street corner and the same level attributed to a room are different pieces of evidence about the same railway. Settling it needs the memo re-read for its exact wording, not more arithmetic.


# What is scheduled. No dependencies beyond the standard library.
python bridge_schedule.py

# What is running right now.
pip install gtfs-realtime-bindings protobuf
python bridge_realtime.py --once

# What actually ran, over a week. Resumable - re-running with the same
# --out file reloads and continues.
python bridge_realtime.py --poll 30 --out bridge_week.csv

# Who is underneath. Four public datasets, no API key, no dependencies
# beyond the standard library. Writes pedestrian-data.json and injects
# it into the dashboard.
python build_pedestrian_data.py

# Per-event train data for the dashboard.
python build_dashboard_data.py

# Who is here at each hour, and for how long. Fits four cohorts to the
# observed departure curve, sweeps for identifiability, writes
# cohort-data.json and injects it into the dashboard. Takes about six
# minutes; --no-inject writes the JSON only.
python build_cohort_model.py

# What shape of space they are standing in. Building footprints with
# surveyed roof heights, and the street, park, water and footway network.
# Writes geo/buildings.json and geo/osm.json.
python fetch_geodata.py

# Then, from the repository root, draw the corridor from that geodata.
cd ..
python build_carousel.py

A 30 s poll against an 85-300 s headway samples every train several times before it arrives. Shorter intervals gain nothing and are less polite to a free public endpoint.

← All research · Overview · Source on GitHub · Built from 6f6b90d on 2026-08-11.