nj_state.py takes the NJTA Turnpike and Parkway amounts from the NJ corpus (/home/geo1/maps/toll/corpus/NJ, 2026-10-09) and adds positions (NJTA's own GIS layers for Turnpike interchanges and Parkway plazas), the Atlantic City Expressway, the Port Authority crossings, and the Burlington and Cape May county bridges.
The corpus adapter is /home/geo1/maps/toll/bin/adapters/nj.py (rewritten 2026-10-09 after the original was overwritten by a stub on 2026-10-04): it parses the 8 Turnpike class PDFs (entry × exit matrices; cells placed in their exit column by position) and the 3 Parkway tables, and reproduced every stored price (17,581 rows) exactly. Turnpike interchanges are named from NJTA's Interchanges layer ("#10 Edison Int."). Refresh: cd /home/geo1/maps/toll && python3 bin/tollup.py NJ update && python3 bin/check.py NJ, then TOLL_WEB=/home/geo1/maps/toll/corpus python3 bin/publish.py NJ if anything changed, then rebuild the draft. The schedule URLs carry the year (2026-turnpike-class-…): for 2027 edit TPK_CLASSES / GSP_TABLES in the adapter.
njta_tpk_class1..6, classb2, classb3, njta_gsp_*); their URLs carry the year, so a new year means new URLs. Peak/off-peak windows: njta_toll_discounts.nj_state.py; amounts from sjta_ace_toll_calculator (ace_calc.json).panynj_tolls); amounts typed into nj_state.py (cars 14.79 / 16.79 / 23.30; trucks 43.34 / 45.34 / 51.34), effective 2026-07-12 per the page. Collected only entering New York.bcbc_toll_rates, typed in (3.00 / 4.00).cmcbc_toll_schedule, 9 classes, parsed by nj_state.py); E-ZPass and Toll-by-Plate pay the same (cmcbc_faq). The page prints no effective date: re-fetch each spring. Each bridge is charged in one direction only (cmcbc_bridge_history: Ocean City-Longport southbound, stated; the others by their OSM gantry direction: Corson's Inlet and Grassy Sound northbound, Townsends Inlet southbound, Middle Thorofare westbound).python3 tollnode_ways.py NJ (NJ's OSM files are links to the shared panj_* extract, box -80.6,38.9,-73.88,42.3).NJ. NJTA ArcGIS layers can be re-paged with python3 fetch_layer.py NJ <name> <url>.#<n>) because the corpus location names are wrong ("#8 Cape May Point").assert len(PLZ) == 34 (Parkway plazas) and the hard-coded milepost list in the MileMarkers query break on a schedule layout change. ACE exit 7S and 41→44 are hand-coded.time_bands.csv is empty (the corpus one has the 12 NJTA windows); the page lede still says some facilities are "priced later".Hosts: geo1 (192.168.50.220) holds the sources, builders, drafts, map and API; b100 (192.168.50.14) is the test client. All times are Eastern.
| what | where (geo1 unless said) |
|---|---|
| sources list | /home/geo1/maps/toll/NJ/urls.txt (name <tab> URL) |
| archived sources | /home/geo1/maps/toll/NJ/sources/<name>/<YYYY-MM-DD>/ (file + meta.json: url, fetched, sha256) |
| builder | /home/geo1/system/handbuilt/nj_state.py; shared code toll_common.py |
| OpenStreetMap inputs | /home/geo1/system/handbuilt/osm/nj_* (cut from /home/geo1/maps/osm/us-latest.osm.pbf) |
| output | draft /srv/toll/draft/NJ/ (the builders write here); published /srv/toll/NJ/ (copied from the draft by promote.py publish). The map and the API read the published page, or the draft while it is listed for review |
| toll engine | source /home/geo1/maps/web/tolls.js, served copy /srv/maps/tolls.js (the API runs the served copy) |
| API | /home/geo1/maps/api/server.mjs on 127.0.0.1:8090 behind nginx /api/; pid in logs/test.pid |
| regression trips | b100: ~/apitest/site/cases.json, runner ~/apitest/run.py, browser check ~/apitest/browser.mjs |
python3 /home/geo1/system/handbuilt/fetch_list.py NJ [name ...] (no names = every line of urls.txt). A new source: add a line to urls.txt first, then fetch it by name.node ~/apitest/fetch_b100.mjs <out> <name>=<url> ..., then rsync -a <out>/ geo1@192.168.50.220:/home/geo1/maps/toll/NJ/sources/. Its meta.json records that it came from b100.diff, pdftotext -layout for PDFs). Same content: nothing to do. Changed amounts or a new effective date: rebuild (step 2) and update the expected amounts of the affected regression trips from the operator's own figures.python3 /home/geo1/maps/toll/bin/tollup.py NJ update fetches the adapter's sources (bin/adapters/nj.py), stores a document only when its content changes, writes one rate file per version under /home/geo1/maps/toll/NJ/rates/ and appends every price change to history.csv (every run to runs.log). TOLL_WEB=/home/geo1/maps/toll/corpus python3 /home/geo1/maps/toll/bin/publish.py NJ writes the corpus /home/geo1/maps/toll/corpus/NJ from it (DE: plain publish.py DE writes /srv/toll/DE). Never hand-edit either. Then rebuild the draft (step 2) so it picks up the new rates.
cd /home/geo1/system/handbuilt && python3 nj_state.pybash /home/geo1/system/handbuilt/chain.sh NJ (logs in /tmp/nj1.log ... nj6.log; it prints the points, any point on no road and the untied count). The builders written since October 2026 share state_kit.py (sources, booths, terms, rows, write) and own_state.py (keeps a state's untied list to toll nodes inside the state, by the local geocoder; cache osm/node_state.json).cd /home/geo1/system/handbuilt; H=$(ls osm/nj_highways_*.geojsonseq); J=$(ls osm/nj_junctions_*.geojsonseq)
python3 nj_state.py && python3 osm_streets.py NJ $H $J && python3 nj_state.py && python3 osm_snap.py NJ && python3 toll_ways.py NJ && python3 nj_state.pytoll_ways.py's output: nodes claimed by two points: {} and points on no road: [] must both be empty.OSM way <id>) needs that way in osm/nj_lane_ways.json (the builder writes it). A point on a road priced entry to exit needs gantry in its location name, or toll_ways.py treats it as a position-only exit marker.osm/ny_not_toll_nodes.json (congestion-zone gantries that are not tolls); every state's untied list leaves those out, so rebuild NJ and PA after NY.tolls.js when it starts. Restart it: cd /home/geo1/maps/api && kill $(cat logs/test.pid); (nohup /home/geo1/maps/node/bin/node server.mjs > logs/server.log 2>&1 & echo $! > logs/test.pid) (it is started by hand, not a service).python3 ~/apitest/run.py ~/apitest/site/cases.json. Every trip must be OK. Add a trip for every rate or rule you change, with the amount taken from the operator's own source.tolls.js): edit /home/geo1/maps/web/tolls.js, check with /home/geo1/maps/node/bin/node --check, copy to /srv/maps/tolls.js with sudo install -o www-data -g www-data -m 644, restart the API, run the trips.The builders match toll points to the OSM way ids the router drives, so the extracts here must come from the same us-latest.osm.pbf the router (Valhalla, /home/geo1/maps/valhalla_data) was built from (now 2026-09-27). New OSM data means: rebuild the router tiles, then for each state cut the box, rebuild its OSM inputs and run the full chain, then compare untied_toll_nodes.csv and the estimated positions below. The box for NJ and the cutting commands are in the state's procedure above; the toll-node and way index comes from python3 tollnode_ways.py NJ.
A rebuild writes the draft (/srv/toll/draft/NJ); the published page (/srv/toll/NJ) does not change. python3 /home/geo1/maps/toll/bin/promote.py review NJ lists the draft for review: the map and the API then read it instead of the published page. Run the regression trips from b100. After approval, python3 /home/geo1/maps/toll/bin/promote.py publish NJ copies the draft to /srv/toll/NJ (the previous page is kept under /home/geo1/maps/toll/NJ/published/<time>/) and takes it off the review list. After a change, update the coverage line for NJ in the API page (/api.html, generator /home/geo1/maps/api/doc/api_page.py) and rebuild this page: python3 /home/geo1/system/handbuilt/maintain_pages.py NJ.
Each fetch is kept in its own dated folder under /home/geo1/maps/toll/NJ/sources/<name>/; the builder always reads the newest one, so a new fetch takes effect at the next rebuild and the old one stays for comparison.
| id | facility | operator | points | rate / trip rows | effective dates in the rows | source |
|---|---|---|---|---|---|---|
NJ-NJTP | New Jersey Turnpike | New Jersey Turnpike Authority (NJTA) | 30 | 16576 | 2026-10-03_observed | ezpassnj_facilities |
NJ-GSP | Garden State Parkway | New Jersey Turnpike Authority (NJTA) | 34 | 1005 | 2026-10-03_observed | ezpassnj_facilities |
NJ-ACE | Atlantic City Expressway | South Jersey Transportation Authority (SJTA) | 16 | 1875 | 2026-01-04 | sjta_ace_toll_calculator |
NJ-CMCBC-OCL | Ocean City-Longport Bridge | Cape May County Bridge Commission | 1 | 18 | 2026-10-09_observed | cmcbc_toll_schedule |
NJ-CMCBC-CI | Corson's Inlet Bridge | Cape May County Bridge Commission | 1 | 18 | 2026-10-09_observed | cmcbc_toll_schedule |
NJ-CMCBC-TI | Townsends Inlet Bridge | Cape May County Bridge Commission | 1 | 18 | 2026-10-09_observed | cmcbc_toll_schedule |
NJ-CMCBC-GS | Grassy Sound Bridge | Cape May County Bridge Commission | 1 | 18 | 2026-10-09_observed | cmcbc_toll_schedule |
NJ-CMCBC-MT | Middle Thorofare Bridge | Cape May County Bridge Commission | 1 | 18 | 2026-10-09_observed | cmcbc_toll_schedule |
NJ-BCBC-TP | Tacony-Palmyra Bridge | Burlington County Bridge Commission | 1 | 2 | 2026-10-08_observed | bcbc_toll_rates |
NJ-BCBC-BB | Burlington-Bristol Bridge | Burlington County Bridge Commission | 1 | 2 | 2026-10-08_observed | bcbc_toll_rates |
NJ-PANYNJ-GWB | George Washington Bridge | Port Authority of New York and New Jersey | 1 | 8 | 2026-07-12 | panynj_tolls |
NJ-PANYNJ-LT | Lincoln Tunnel | Port Authority of New York and New Jersey | 1 | 8 | 2026-07-12 | panynj_tolls |
NJ-PANYNJ-HT | Holland Tunnel | Port Authority of New York and New Jersey | 1 | 8 | 2026-07-12 | panynj_tolls |
NJ-PANYNJ-BB | Bayonne Bridge | Port Authority of New York and New Jersey | 1 | 8 | 2026-07-12 | panynj_tolls |
NJ-PANYNJ-GB | Goethals Bridge | Port Authority of New York and New Jersey | 1 | 8 | 2026-07-12 | panynj_tolls |
NJ-PANYNJ-OC | Outerbridge Crossing | Port Authority of New York and New Jersey | 1 | 8 | 2026-07-12 | panynj_tolls |
None: every position comes from an official layer or a mapped toll node.
R1: 63, R2b: 20, R2d: 10, one: 5 (all in decisions.csv).
None.
Toll booths / gantries mapped in OpenStreetMap on or near this state's roads that no toll point claims; a route through one lists it under unpriced. After an OSM refresh, look at new ones here (untied_toll_nodes.csv): a new gantry on a priced road means a point is missing.
Run from b100 (never on geo1): python3 ~/apitest/run.py ~/apitest/site/cases.json. Every trip must come back OK; a changed amount is either a rate change (update the expected amount from the operator's own source) or a fault.
| trip | expected |
|---|---|
| GWB NJ->NY, NJ E-ZPass off-peak | $14.79 |
| GWB NJ->NY, NJ E-ZPass peak 08:00 | $16.79 |
| GWB NJ->NY, DE E-ZPass | $23.30 |
| GWB NY->NJ (no toll) | $0.00 |
| Lincoln Tunnel Weehawken->Manhattan, NJ off-peak 10:30 Wed: tunnel $14.79 + zone peak $9.00 - $3.00 Lincoln credit | $20.79 |
| Holland Tunnel JC->Manhattan, mail 10:30 Wed: tunnel $23.30 + zone peak $13.50 (no credit without E-ZPass) | $36.80 |
| Goethals Elizabeth->Staten Island | $14.79 |
| Outerbridge Perth Amboy->Staten Island | $14.79 |
| Bayonne Bridge Bayonne->Staten Island | $14.79 |
| Tacony-Palmyra NJ->PA | $3.00 |
| Tacony-Palmyra PA->NJ (no toll) | $0.00 |
| Burlington-Bristol NJ->PA | $3.00 |
| Ocean City-Longport | $2.50 |
| NJTP 9->13A NJ E-ZPass off-peak | $3.45 |
| NJTP 9->13A NJ E-ZPass peak 08:00 | $4.60 |
| ACE AC->Turnersville (0->44) | $6.49 |
| ACE AC->Hammonton (0->28) | $6.49 |
| ACE Mays Landing->AC (12->0) | $2.98 |
| ACE Hammonton->Williamstown (28->38) | $1.49 |
| ACE Pleasantville(5)->AC | $1.49 |
| GWB NJ->NY, NY E-ZPass off-peak (NY/NJ E-ZPass rate $14.79) | $14.79 |
| GWB NJ->NY, account_nj (NJ toll account = NJ E-ZPass) $14.79 | $14.79 |
| GWB NJ->NY, account_ga (E-ZPass network, not NY/NJ) = mail rate $23.30 | $23.30 |
| GWB NJ->NY, account_tx (not E-ZPass network: not accepted) = bill by mail $23.30 | $23.30 |
| NJ Ocean City-Longport Bridge, Toll-by-Plate car ($2.50, Cape May County schedule) | $2.50 |
| NJ Ocean City-Longport Bridge southbound, E-ZPass 5-axle truck ($16.50, Class 15) | $16.50 |
| NJ Ocean City-Longport Bridge southbound (Longport -> Ocean City), E-ZPass NJ car: charged | $2.50 |
| NJ Ocean City-Longport Bridge northbound (Ocean City -> Longport), E-ZPass NJ car: free | $0.00 |
| NJ Townsends Inlet Bridge southbound (Sea Isle -> Avalon), E-ZPass NJ car: charged | $2.50 |
| NJ Townsends Inlet Bridge northbound (Avalon -> Sea Isle), E-ZPass NJ car: free | $0.00 |