Oklahoma: OTA's 13 turnpikes, cashless. The trip tables (every entry-exit pair, PIKEPASS and PlatePay, every class) come from the PIKEPASS calculator's public API. A route is priced from the first OTA plaza it passes on a turnpike to the last: OTA publishes no plaza positions, so ok_routes.py places each plaza name at the interchange it names on ODOT's 'Turnpikes' GIS layer (TOLLID), in OTA's order (= OSM exit numbers, OTA's mileposts) and by the cross road the name gives; a name at a turnpike's end also marks the mainline beyond its last interchange.
sharedlookuppublic/turnpikes/{t}/class/{c}/tollrates) and archive as ota_tollrates_api; the builder asserts every plaza name in the table is placed.ok_routes.py (hint = its crossing, pattern = its cross road).odot_turnpikes_layer): re-fetch when a turnpike opens or is extended (ACCESS Oklahoma).bash chain.sh OK; OK_ROUTES_REPORT=1 python3 ok_state.py prints every name's ramps and the ramps left without a name.
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/OK/urls.txt (name <tab> URL) |
| archived sources | /home/geo1/maps/toll/OK/sources/<name>/<YYYY-MM-DD>/ (file + meta.json: url, fetched, sha256) |
| builder | /home/geo1/system/handbuilt/ok_state.py; shared code toll_common.py |
| OpenStreetMap inputs | /home/geo1/system/handbuilt/osm/ok_* (cut from /home/geo1/maps/osm/us-latest.osm.pbf) |
| output | draft /srv/toll/draft/OK/ (the builders write here); published /srv/toll/OK/ (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 OK [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/OK/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 OK update fetches the adapter's sources (bin/adapters/ok.py), stores a document only when its content changes, writes one rate file per version under /home/geo1/maps/toll/OK/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 OK writes the corpus /home/geo1/maps/toll/corpus/OK 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 ok_state.pybash /home/geo1/system/handbuilt/chain.sh OK (logs in /tmp/ok1.log ... ok6.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/ok_highways_*.geojsonseq); J=$(ls osm/ok_junctions_*.geojsonseq)
python3 ok_state.py && python3 osm_streets.py OK $H $J && python3 ok_state.py && python3 osm_snap.py OK && python3 toll_ways.py OK && python3 ok_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/ok_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 OK and the cutting commands are in the state's procedure above; the toll-node and way index comes from python3 tollnode_ways.py OK.
A rebuild writes the draft (/srv/toll/draft/OK); the published page (/srv/toll/OK) does not change. python3 /home/geo1/maps/toll/bin/promote.py review OK 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 OK copies the draft to /srv/toll/OK (the previous page is kept under /home/geo1/maps/toll/OK/published/<time>/) and takes it off the review list. After a change, update the coverage line for OK 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 OK.
Each fetch is kept in its own dated folder under /home/geo1/maps/toll/OK/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.
| source | fetched (newest) | fetches kept | how | listed in urls.txt | rows using it |
|---|---|---|---|---|---|
odot_turnpikes_layerhttps://services6.arcgis.com/RBtoEUQ2lmN0K3GY/arcgis/rest/services/Turnpikes/FeatureServer | 2026-10-10 | 1 | fetched on b100 (192.168.50.14): ODOT GIS open data layer 'Turnpikes' (gis-okdot.opendata.arcgis.com), TOLLID = OTA turn | yes | |
ota_outlook_2026https://oklahoma.gov/content/dam/ok/en/ota/documents/annual-outlook/2026_OTA%20Outlook.pdf | 2026-10-10 | 1 | fetch_list.py | yes | |
ota_toll_schedules_2024https://oklahoma.gov/content/dam/ok/en/ota/documents/annual-report/2024%20ACFR_Toll%20Sche | 2026-10-10 | 1 | fetch_list.py | yes | |
ota_tollrates_apihttps://ppentapi.pikepass.com/api/sharedlookuppublic/turnpikes/{t}/class/{c}/tollrates (al | 2026-10-10 | 1 | fetched on b100 (192.168.50.14): the PIKEPASS toll calculator's public API, every turnpike x class | yes | 9197 |
pikepass_homehttps://pikepass.com/pikepass | 2026-10-10 | 1 | fetch_list.py | yes | |
pikepass_toll_calculatorhttps://pikepass.com/plan-your-trip/toll-calculator | 2026-10-10 | 1 | fetched on b100, headless Chrome, rendered page | yes |
| id | facility | operator | points | rate / trip rows | effective dates in the rows | source |
|---|---|---|---|---|---|---|
OK-OTA-TURNER | Turner Turnpike | Oklahoma Turnpike Authority (OTA) | 12 | 1080 | 2026-10-10_observed | ota_tollrates_api |
OK-OTA-WILLROGE | Will Rogers Turnpike | Oklahoma Turnpike Authority (OTA) | 11 | 800 | 2026-10-10_observed | ota_tollrates_api |
OK-OTA-HEBAILEY | H.E. Bailey Turnpike | Oklahoma Turnpike Authority (OTA) | 12 | 1020 | 2026-10-10_observed | ota_tollrates_api |
OK-OTA-MUSKOGEE | Muskogee Turnpike | Oklahoma Turnpike Authority (OTA) | 9 | 410 | 2026-10-10_observed | ota_tollrates_api |
OK-OTA-INDIANNA | Indian Nation Turnpike | Oklahoma Turnpike Authority (OTA) | 9 | 460 | 2026-10-10_observed | ota_tollrates_api |
OK-OTA-CIMARRON | Cimarron Turnpike | Oklahoma Turnpike Authority (OTA) | 8 | 360 | 2026-10-10_observed | ota_tollrates_api |
OK-OTA-KILPATRI | Kilpatrick Turnpike | Oklahoma Turnpike Authority (OTA) | 18 | 1632 | 2026-10-10_observed | ota_tollrates_api |
OK-OTA-CHEROKEE | Cherokee Turnpike | Oklahoma Turnpike Authority (OTA) | 6 | 200 | 2026-10-10_observed | ota_tollrates_api |
OK-OTA-CHICKASA | Chickasaw Turnpike | Oklahoma Turnpike Authority (OTA) | 5 | 80 | 2026-10-10_observed | ota_tollrates_api |
OK-OTA-CREEK | Creek Turnpike | Oklahoma Turnpike Authority (OTA) | 21 | 2592 | 2026-10-10_observed | ota_tollrates_api |
OK-OTA-KICKAPOO | Kickapoo Turnpike | Oklahoma Turnpike Authority (OTA) | 7 | 300 | 2026-10-10_observed | ota_tollrates_api |
OK-OTA-SOUTHWES | Southwest JKT Turnpike | Oklahoma Turnpike Authority (OTA) | 6 | 130 | 2026-10-10_observed | ota_tollrates_api |
OK-OTA-GILCREAS | Gilcrease Turnpike | Oklahoma Turnpike Authority (OTA) | 5 | 120 | 2026-10-10_observed | ota_tollrates_api |
None: every position comes from an official layer or a mapped toll node.
R2: 129, no: 9, not: 2, priced: 1 (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 |
|---|---|
| OK Turner OKC (I-35) -> Tulsa, PIKEPASS car | $5.40 |
| OK Turner OKC (I-35) -> Tulsa, PlatePay car | $10.50 |
| OK Turner Luther (Hogback Rd) -> Stroud (SH 99), PIKEPASS car | $1.68 |
| OK Will Rogers Tulsa -> Joplin MO, PIKEPASS car | $5.40 |
| OK H.E. Bailey Lawton -> Oklahoma City, PIKEPASS car | $4.04 |
| OK Indian Nation Henryetta -> Hugo, PIKEPASS car | $7.31 |
| OK Kickapoo Turner jct -> I-40, PIKEPASS car | $2.82 |
| OK Creek Sapulpa (SH 66) -> Broken Arrow (County Line Rd), PIKEPASS car | $2.31 |
| OK Kilpatrick Eastern Ave -> NW Expressway, PIKEPASS car | $1.50 |
| OK Muskogee Broken Arrow -> I-40 (Webbers Falls), PIKEPASS car | $3.80 |
| OK Cimarron I-35 -> Tulsa (Keystone), PIKEPASS car | $3.65 |
| OK Cherokee Locust Grove -> Arkansas line, PIKEPASS car | $3.08 |
| OK Chickasaw Sulphur -> Ada (SH 1), PIKEPASS car | $0.72 |
| OK Gilcrease Sand Springs Expwy -> I-44, PIKEPASS car | $2.05 |
| OK SW John Kilpatrick I-40 -> SH 152, PIKEPASS car | $0.78 |