History of us_rail_narn
The changes to the source code of this entry in the repository, newest first, each with the differences it made to the entry's files. They are also listed on GitLab.
[entries] Add the date when each entry was added, and remove icon_hash beff7c9
The dates are those of the commits that first added the entries' files (2020-08-11 for the entries of the initial commit); renamed entries keep the date of their first name. Changed: none
diff --git a/entries/us_rail_narn/__init__.py b/entries/us_rail_narn/__init__.py
index 86aa423..cd56192 100644
--- a/entries/us_rail_narn/__init__.py
+++ b/entries/us_rail_narn/__init__.py
@@ -55,7 +55,7 @@ url = ['https://www.arcgis.com/home/item.html?id=e143f436d4774402aa8cca1e663b1d2
citation = [('Federal Railroad Administration, Bureau of Transportation Statistics, "North American Rail Network Lines." National Transportation Atlas Database (NTAD), dataset (2026)', 'https://doi.org/10.21949/1528950'),
('Federal Railroad Administration, Bureau of Transportation Statistics, "North American Rail Network Nodes." National Transportation Atlas Database (NTAD), dataset (2026)', 'https://doi.org/10.21949/1529070')]
upstream_license = ("Public domain (US government work)", "https://www.usa.gov/government-works")
-icon_hash = None
+date_added = '2026-10-31'
# The layers of the National Transportation Atlas Database on ArcGIS Online,
# downloaded with paged queries (2,000 features each) and stored as one
[entries] Prefix the properties in the descriptions with (Node), (Edge) or (Graph) bc3dbbd
The lists of properties at the end of the descriptions marked edge properties with "Edge" but left node properties unmarked, or grouped them in "Nodes: `a`, ...; `b`, ..." items, so the kind of a property was often unclear. Every item now starts with (Node), (Edge) or (Graph), followed by the property names, a colon and the explanation, one item per property or group of properties; qualifications (kinds of nodes, networks) follow the names in parentheses. Properties that were only named got a short explanation, and a few items that named the wrong kind of property were corrected.
diff --git a/entries/us_rail_narn/__init__.py b/entries/us_rail_narn/__init__.py
index 5a7bbba..86aa423 100644
--- a/entries/us_rail_narn/__init__.py
+++ b/entries/us_rail_narn/__init__.py
@@ -28,8 +28,26 @@ Nodes are the FRA's topological nodes (junctions, ends of lines, and points wher
The network has the following properties:
-- Nodes: `node_id`, the FRA node id; `latitude` and `longitude`; `country` (`US`, `CA`, `MX`); `state`, the state or province; `county_fips`, the FIPS code of the US state and county; `fra_district`, the FRA district (1 to 8 in the US, 99 outside); `passenger`, the passenger service at the station (`A` Amtrak, `B` Amtrak and commuter, `C` commuter); `station`, the name of the commuter station; `boundary`, whether the node is on a border (0 none, 1 state, 2 international, 3 county).
-- Edges: `link_id`, the FRA link id; `owners`, the reporting marks of the (up to three) railroads in charge of dispatching, maintenance and/or ownership of the line; `trackage_rights`, the reporting marks of the (up to nine) railroads with trackage rights; `net`, the kind of link (`M` main network, `O` other track, `Y` yard, `I` major industrial lead, `S` passing siding over 4,000 feet, `X` out of service, `A` abandoned, `T` trail on a former rail right-of-way, `R` abandoned and removed, `F` rail ferry); `tracks`, the number of main line tracks; `passenger`, the passenger service on the line (`A` Amtrak, `B` Amtrak and commuter, `C` commuter, `D` Alaska Railroad, `E` intercity high-speed rail and commuter, `I` intercity high-speed rail, `R` rapid transit, `T` tourist, museum or scenic, `O` Ontario Northland, `V` Via Rail Canada); `stracnet`, whether the line is on the Strategic Rail Corridor Network (`S`) or a connector to it (`C`); `division`, `subdivision`, `branch` and `yard`, the names of the railroad's division, subdivision, branch or line, and yard; `country`, `state`, `county_fips` and `fra_district`, as for the nodes; `timezone` (`E`, `C`, `M`, `P`, `Z` Arizona, `K` Alaska, `H` Hawaii); `length`, the length of the link (km). Missing values are empty strings or -1."""
+- (Node) `node_id`: the FRA node id.
+- (Node) `latitude` and `longitude`.
+- (Node) `country` (`US`, `CA`, `MX`).
+- (Node) `state`: the state or province.
+- (Node) `county_fips`: the FIPS code of the US state and county.
+- (Node) `fra_district`: the FRA district (1 to 8 in the US, 99 outside).
+- (Node) `passenger`: the passenger service at the station (`A` Amtrak, `B` Amtrak and commuter, `C` commuter).
+- (Node) `station`: the name of the commuter station.
+- (Node) `boundary`: whether the node is on a border (0 none, 1 state, 2 international, 3 county).
+- (Edge) `link_id`: the FRA link id.
+- (Edge) `owners`: the reporting marks of the (up to three) railroads in charge of dispatching, maintenance and/or ownership of the line.
+- (Edge) `trackage_rights`: the reporting marks of the (up to nine) railroads with trackage rights.
+- (Edge) `net`: the kind of link (`M` main network, `O` other track, `Y` yard, `I` major industrial lead, `S` passing siding over 4,000 feet, `X` out of service, `A` abandoned, `T` trail on a former rail right-of-way, `R` abandoned and removed, `F` rail ferry).
+- (Edge) `tracks`: the number of main line tracks.
+- (Edge) `passenger`: the passenger service on the line (`A` Amtrak, `B` Amtrak and commuter, `C` commuter, `D` Alaska Railroad, `E` intercity high-speed rail and commuter, `I` intercity high-speed rail, `R` rapid transit, `T` tourist, museum or scenic, `O` Ontario Northland, `V` Via Rail Canada).
+- (Edge) `stracnet`: whether the line is on the Strategic Rail Corridor Network (`S`) or a connector to it (`C`).
+- (Edge) `division`, `subdivision`, `branch` and `yard`: the names of the railroad's division, subdivision, branch or line, and yard.
+- (Edge) `country`, `state`, `county_fips` and `fra_district`: as for the nodes.
+- (Edge) `timezone` (`E`, `C`, `M`, `P`, `Z` Arizona, `K` Alaska, `H` Hawaii).
+- (Edge) `length`: the length of the link (km). Missing values are empty strings or -1."""
tags = ['Transportation', 'Rail', 'Spatial', 'Weighted', 'Multigraph', 'Metadata']
url = ['https://www.arcgis.com/home/item.html?id=e143f436d4774402aa8cca1e663b1d24',
'https://www.arcgis.com/home/item.html?id=54f40ddee0844fb285dafb13a6b2f623',
[entries] New entry: us_rail_narn 3512a63
diff --git a/entries/us_rail_narn/__init__.py b/entries/us_rail_narn/__init__.py
new file mode 100644
index 0000000..5a7bbba
--- /dev/null
+++ b/entries/us_rail_narn/__init__.py
@@ -0,0 +1,161 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2026 Tiago de Paula Peixoto <tiago@skewed.de>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from .. import *
+import json
+import urllib.parse
+import process_entry
+
+title = "North American Rail Network (2026)"
+description = """The North American Rail Network (NARN) of the US Federal Railroad Administration (FRA), part of the National Transportation Atlas Database (NTAD) of the Bureau of Transportation Statistics: a topological database of the railway lines of the United States (all 50 states and the District of Columbia; at a scale of 1:24,000 or better), Canada and Mexico, with their owners, the railroads with trackage rights, the passenger services and the strategic rail corridors. The dataset was created in 2016 and is updated in place; this is the version updated on October 19, 2026.
+
+Nodes are the FRA's topological nodes (junctions, ends of lines, and points where the attributes of a line change, e.g. at state or county borders), and edges are the rail links between them, from the "from" node to the "to" node of each link (the network is undirected). All the links are included, whatever their kind (property `net`): main lines (95,932 links), other tracks such as minor industrial leads (88,559), yard tracks (79,028), major industrial leads (16,130), passing sidings (10,193), out-of-service lines (6,554), abandoned lines (4,267), trails on former rights-of-way (1,473), abandoned lines that were physically removed (623), and rail ferry connections (15). The network has parallel edges (13,673, e.g. tracks running side by side between the same nodes) and 71 self-loops (e.g. loops of yard tracks), and 253 components (the largest with 249,070 of the 250,204 nodes). The geometries of the links are not included.
+
+The network has the following properties:
+
+- Nodes: `node_id`, the FRA node id; `latitude` and `longitude`; `country` (`US`, `CA`, `MX`); `state`, the state or province; `county_fips`, the FIPS code of the US state and county; `fra_district`, the FRA district (1 to 8 in the US, 99 outside); `passenger`, the passenger service at the station (`A` Amtrak, `B` Amtrak and commuter, `C` commuter); `station`, the name of the commuter station; `boundary`, whether the node is on a border (0 none, 1 state, 2 international, 3 county).
+- Edges: `link_id`, the FRA link id; `owners`, the reporting marks of the (up to three) railroads in charge of dispatching, maintenance and/or ownership of the line; `trackage_rights`, the reporting marks of the (up to nine) railroads with trackage rights; `net`, the kind of link (`M` main network, `O` other track, `Y` yard, `I` major industrial lead, `S` passing siding over 4,000 feet, `X` out of service, `A` abandoned, `T` trail on a former rail right-of-way, `R` abandoned and removed, `F` rail ferry); `tracks`, the number of main line tracks; `passenger`, the passenger service on the line (`A` Amtrak, `B` Amtrak and commuter, `C` commuter, `D` Alaska Railroad, `E` intercity high-speed rail and commuter, `I` intercity high-speed rail, `R` rapid transit, `T` tourist, museum or scenic, `O` Ontario Northland, `V` Via Rail Canada); `stracnet`, whether the line is on the Strategic Rail Corridor Network (`S`) or a connector to it (`C`); `division`, `subdivision`, `branch` and `yard`, the names of the railroad's division, subdivision, branch or line, and yard; `country`, `state`, `county_fips` and `fra_district`, as for the nodes; `timezone` (`E`, `C`, `M`, `P`, `Z` Arizona, `K` Alaska, `H` Hawaii); `length`, the length of the link (km). Missing values are empty strings or -1."""
+tags = ['Transportation', 'Rail', 'Spatial', 'Weighted', 'Multigraph', 'Metadata']
+url = ['https://www.arcgis.com/home/item.html?id=e143f436d4774402aa8cca1e663b1d24',
+ 'https://www.arcgis.com/home/item.html?id=54f40ddee0844fb285dafb13a6b2f623',
+ 'https://services.arcgis.com/xOi1kZaI0eWDREZv/ArcGIS/rest/services']
+citation = [('Federal Railroad Administration, Bureau of Transportation Statistics, "North American Rail Network Lines." National Transportation Atlas Database (NTAD), dataset (2026)', 'https://doi.org/10.21949/1528950'),
+ ('Federal Railroad Administration, Bureau of Transportation Statistics, "North American Rail Network Nodes." National Transportation Atlas Database (NTAD), dataset (2026)', 'https://doi.org/10.21949/1529070')]
+upstream_license = ("Public domain (US government work)", "https://www.usa.gov/government-works")
+icon_hash = None
+
+# The layers of the National Transportation Atlas Database on ArcGIS Online,
+# downloaded with paged queries (2,000 features each) and stored as one
+# JSON record per line. They are replaced in place by new versions (the one
+# fetched here was updated on October 19, 2026); the server downloads the
+# current one.
+upstream_prefix = 'https://services.arcgis.com/xOi1kZaI0eWDREZv/ArcGIS/rest/services'
+layers = {"narn_nodes.jsonl": ("NTAD_North_American_Rail_Network_Nodes", True),
+ "narn_lines.jsonl": ("NTAD_North_American_Rail_Network_Lines", False)}
+files = [(("narn_nodes.jsonl", "narn_lines.jsonl"), None, None)]
+PAGE = 2000
+
+def _query(layer, **params):
+ params = dict(dict(where="1=1", f="json"), **params)
+ return (f"{upstream_prefix}/{layer}/FeatureServer/0/query?"
+ f"{urllib.parse.urlencode(params)}")
+
+def _page_query(layer, geometry, offset, count=PAGE):
+ return _query(layer, outFields="*", returnGeometry=str(geometry).lower(),
+ outSR=4326, orderByFields="OBJECTID", resultOffset=offset,
+ resultRecordCount=count)
+
+def fetch_arcgis(name, fname, layer, geometry):
+ "Download all the records of an ArcGIS feature layer, page by page."
+ with open_url(_query(layer, returnCountOnly="true")) as r:
+ count = json.load(r)["count"]
+ seen = set()
+ # (written atomically: an interrupted download leaves nothing behind)
+ with open_upstream_file(name, fname, "wb") as out:
+ for offset in range(0, count, PAGE):
+ data = retry_download(lambda: open_url(_page_query(layer, geometry,
+ offset)).read())
+ for feat in json.loads(data)["features"]:
+ rec = feat["attributes"]
+ if rec["OBJECTID"] in seen:
+ raise RuntimeError(f"record {rec['OBJECTID']} returned twice")
+ seen.add(rec["OBJECTID"])
+ if geometry:
+ rec["x"] = feat["geometry"]["x"]
+ rec["y"] = feat["geometry"]["y"]
+ out.write(json.dumps(rec, separators=(",", ":")).encode() + b"\n")
+ if len(seen) != count:
+ raise RuntimeError(f"{len(seen)} records downloaded, {count} expected")
+
+def fetch_upstream(force=False):
+ name = __name__.split(".")[-1]
+ for fname, (layer, geometry) in layers.items():
+ if process_entry._check_health:
+ with open_url(_page_query(layer, geometry, 0, 1)) as r:
+ json.load(r)["features"][0]
+ continue
+ if not force and upstream_file_exists(name, fname):
+ continue
+ fetch_arcgis(name, fname, layer, geometry)
+
+def _str(x):
+ # "#N\\A" is the code for "none" of the division, subdivision, branch
+ # and yard names
+ x = "" if x is None else str(x).strip()
+ return "" if x == "#N\\A" else x
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+ name = __name__.split(".")[-1]
+ nodes, lines = files[0][0]
+ vp = defaultdict(list)
+ idx = {}
+ with open_upstream_file(name, nodes, "rb") as f:
+ for l in f:
+ r = json.loads(l)
+ idx[r["FRANODEID"]] = len(idx)
+ vp["node_id"].append(r["FRANODEID"])
+ vp["longitude"].append(r["x"])
+ vp["latitude"].append(r["y"])
+ for k, c in [("country", "COUNTRY"), ("state", "STATE"),
+ ("county_fips", "STCYFIPS"), ("passenger", "PASSNGR"),
+ ("station", "PASSNGRSTN")]:
+ vp[k].append(_str(r[c]))
+ vp["fra_district"].append(-1 if r["FRADISTRCT"] is None else r["FRADISTRCT"])
+ vp["boundary"].append(-1 if r["BNDRY"] is None else r["BNDRY"])
+ edges = []
+ ep = defaultdict(list)
+ with open_upstream_file(name, lines, "rb") as f:
+ for l in f:
+ r = json.loads(l)
+ edges.append((idx[r["FRFRANODE"]], idx[r["TOFRANODE"]]))
+ ep["link_id"].append(r["FRAARCID"])
+ ep["owners"].append([r[f"RROWNER{i}"] for i in range(1, 4)
+ if r[f"RROWNER{i}"]])
+ ep["trackage_rights"].append([r[f"TRKRGHTS{i}"] for i in range(1, 10)
+ if r[f"TRKRGHTS{i}"]])
+ for k, c in [("state", "STATEAB"), ("country", "COUNTRY"),
+ ("county_fips", "STCNTYFIPS"), ("division", "DIVISION"),
+ ("subdivision", "SUBDIV"), ("branch", "BRANCH"),
+ ("yard", "YARDNAME"), ("passenger", "PASSNGR"),
+ ("stracnet", "STRACNET"), ("net", "NET"),
+ ("timezone", "TIMEZONE")]:
+ ep[k].append(_str(r[c]))
+ ep["fra_district"].append(-1 if r["FRADISTRCT"] is None else r["FRADISTRCT"])
+ ep["tracks"].append(-1 if r["TRACKS"] is None else r["TRACKS"])
+ ep["length"].append(numpy.nan if r["KM"] is None else r["KM"])
+ g = Graph(directed=False)
+ g.add_vertex(len(idx))
+ g.add_edge_list(edges)
+ for k, vals in vp.items():
+ t = ("int64_t" if k in ("node_id", "fra_district", "boundary") else
+ "double" if k in ("longitude", "latitude") else "string")
+ g.vp[k] = g.new_vp(t, vals=vals)
+ for k, vals in ep.items():
+ t = ("int64_t" if k in ("link_id", "fra_district", "tracks") else
+ "double" if k == "length" else
+ "vector<string>" if k in ("owners", "trackage_rights") else "string")
+ g.ep[k] = g.new_ep(t, vals=vals)
+ yield None, g
+
+def get_pos(g, sbm_fit):
+ # Spatial network: laid out with the default parameters of cfd_layout(),
+ # without the partition of the SBM fit
+ return cfd_layout(g)