Documentation menu

ProRail stationsopnamen

Deliveries under ProRail's Vraagspecificatie Stationsopnamen 2025: the publicly accessible spaces of one station, captured as 360° panoramas and a LiDAR point cloud, for publication in SpoorInBeeld.

One dataset is one station delivery — the contract has suppliers deliver stationsgewijs (O-31), and the delivery's own YYYYMM_XXXX code becomes the set name, so it is what people see in the viewer.

The straatopnamen template is the same format from the other tender. The differences are small and are called out below.

What we check, and what we do not. Most of what the specification asks for is measurement quality: 5 cm absolute accuracy, 1.5 cm relative precision, freedom from outliers and moving objects, no visible compression artefacts at 200 %, full coverage of the leveringsgebied. An independent party assesses those and advises ProRail on acceptance (O-32). Nothing here duplicates that judgement. What this page describes is the structural half: the things that are objectively true or false about the files you upload, and that would otherwise surface as a failed run days later.

Folder layout#

<dataset bucket>/
├── panorama/
│   ├── 202603_UT.csv                          the pose CSV — exactly one
│   ├── 202603_UT_panoramaopnamesysteem1.txt   one per capture system
│   └── jpg/
│       ├── 202603_UT_0001.jpg
│       └── 202603_UT_0002.jpg
├── pointcloud/
│   ├── LL136500_455800.laz
│   └── LL136500_455900.laz
└── Reports/delivery/                          the PDF and GeoPackage deliverables
    ├── 20260312_Opnamerapport_UT.pdf
    ├── 202603_UT_pano_verwerking.pdf
    └── 202603_UT.gpkg

The panorama base folder is looked for under these names, in this order — the first base folder containing any object wins, and candidates below it are then dead:

  1. panorama/

Once a base folder is chosen, an empty images folder inside it fails the run — it does not fall through to the next candidate.

The pointcloud folder is looked for under these names, in this order — the first containing at least one matching file wins:

  1. pointcloud/

Both halves are required. A stationsopname is panoramas and LiDAR by definition, so a delivery missing one of them is incomplete rather than a smaller complete thing.

Panoramas#

  • Every delivered photo must start with a JPEG marker and end with an end-of-image marker. This catches a file that is not a JPEG whatever its extension says, and the truncated upload — which a size check cannot see, because a truncated file just looks smaller and nothing knows how big it should have been.
  • Each photo's own frame header must describe the panorama the spec asks for: exactly twice as wide as it is high (PF-12), in colour at 8 bits per channel (PF-4). Both are read from the file itself rather than taken on trust, because a delivery of the wrong projection indexes and displays perfectly while pointing every viewer at the wrong part of the world.
  • Photo filenames should be YYYYMM_XXXX_pppp.jpg (PF-14) — capture month, station code, unique number — and the station code should be the one in the pose CSV's own name. Nothing breaks if they are not, which is exactly why it is worth saying: the delivery is then labelled with a station or a month it did not come from, and no later step can tell. (a warning, not an error)
  • The panoramaopnamesysteem files (PF-16) must describe the delivery they came with: their pixel dimensions agree with the photos, their pixel size in degrees is the 360° the width divides into, and between them they account for every photo in the pose CSV exactly once — each under the system name that photo's CSV row gives. These files are the delivery's only record of which camera took which photo, so a stale or mismatched one is a silent loss of provenance.
  • Conventions in the pose CSV that we read either way but the spec fixes: capture time written as YYYY:MM:DD-HH:MM:SS (PF-15), coordinates to at most three decimals, and photo names without their file extension. These are warnings rather than errors on purpose — the delivery is complete and correct, and re-exporting several hundred gigabytes to change a separator would help nobody. The independent checker works from the spec, though, so it is better to hear it here. (a warning, not an error)
  • Reported when the pose CSV carries the optional pan, tilt and roll columns. Nothing is wrong with the delivery — the angles are read and applied — but the specification asks for panoramas that are already north-aligned and level, so a delivery supplying attitude instead is working to an agreement the specification does not describe. Whoever reads this report should know that from the report rather than discover it from a viewer. (a warning, not an error)
  • Panorama positions should be at most 20 m apart where the two points can see each other (PF-8). Visibility is not something a file can tell us — a wall, a corner or a level change legitimately breaks the line — so this reports the gaps rather than failing them. A cluster of them usually means a run was cut short or a stretch was never walked. (a warning, not an error)

Panoramas must arrive already oriented, which is the single most important thing to get right. The specification requires the centre column of pixels to face grid north in RD, the first and last columns to face south, the top row to be the zenith, and the horizon to sit on the half-height row (PF-12, PF-13). Your capture system's three orientation angles have to be baked into the image by a transformation before delivery.

That is why the pose CSV has no orientation columns: a delivered panorama is north-aligned and level by contract, and its pose is a position only. Deliver unrotated images and nothing fails — every photo simply faces the wrong way in the viewer, which is a much more expensive mistake to find later.

No-data pixels below the vertical range go at the bottom of the image and must be black.

Camera system files#

One plain-text file per capture system, next to the pose CSV, named YYYYMM_XXXX_panoramaopnamesysteem#.txt (PF-16). Records are lines:

Ladybug6-01          record 1: the system name, as used in the CSV
8192                 record 2: horizontal pixels
4096                 record 3: vertical pixels
0.043945             record 4: degrees per pixel
202603_UT_0001       records 5..n: the photos these parameters apply to,
202603_UT_0002                     without their extension

These files are the delivery's only record of which camera took which photo, so they are cross-checked against everything else: their dimensions against the photos, their pixel size against their own width (360° divided across it), and their photo lists against the pose CSV — every photo named exactly once, under the system its CSV row gives.

Extensions.txt, matched case-insensitively
RecursionNo — only files directly inside the folder

At least one such file is required. A capture with two rigs delivers two.

The pose CSV#

SeparatorComma
PreambleA header row is optional — see below
ColumnsPositional. A header row is optional: if the first row's numeric columns are not numbers, it is read as a header and discarded
Extra columnsIgnored
Decimals. only
Photo namesMatched exactly as written, then with .jpg appended — first hit wins; case-sensitive
Which fileThe alphabetically first .csv directly inside the folder itself
ColumnTypeMeaning
0textPhoto name without the .jpg extension
1numberRD easting of the panorama's centre, metres
2numberRD northing
3numberNAP height
4timestampCapture time in UTC
5textMust match record 1 of one of the panoramaopnamesysteem files
6number, optionalHeading, degrees — only if attitude was agreed instead of a pre-rotated image
7number, optionalPitch, degrees
8number, optionalRoll, degrees

A header row is optional. The specification lists the fields of a record and never says whether to write one, so both readings are accepted: if the first row's coordinate columns are not numbers, it is a header and is discarded. A damaged first data row is still reported as a damaged row.

Timestamps#

timestamp accepts any of: YYYY:MM:DD-HH:MM:SS[.fff] (read as UTC); RFC 3339 (2026-05-07T09:18:00Z); YYYY-MM-DD HH:MM:SS[.fff] (read as UTC); YYYY-MM-DDTHH:MM:SS[.fff] (read as UTC).

The specification writes the shape out in words rather than naming a standard — het opnametijdstip in UTC (jaar:maand:dag-uur:minuut:seconde) — which is 2026:03:12-09:18:00. Note the colons in the date and the dash before the time.

Example#

202603_UT_0001,136543.210,455812.345,4.120,2026:03:12-09:18:00,Ladybug6-01
202603_UT_0002,136545.900,455814.010,4.135,2026:03:12-09:18:07,Ladybug6-01

Note: optional attitude columns#

Three further columns — pan, tilt and roll in degrees, at positions 6, 7 and 8 — are accepted after the six the specification lists.

They are not part of the specification, which asks for panoramas that arrive already north-aligned and level. They exist because levelling and northing the image is the part suppliers find hardest, and ProRail may agree with a supplier to receive the angles instead. If you have that agreement, put them here and the images may be delivered unrotated, in the same convention Mobile mapping generic uses.

Supplying them raises a warning on your validation report. Nothing is wrong — it is there so that whoever reads the report knows this delivery follows a separate agreement, rather than discovering it from a viewer.

Leave them out and no warning is raised. A row may also give some of the three and not others: the absent ones are read as zero, which is what the specification assumes anyway, and the report says how many rows did that.

Point cloud tiles#

Extensions.laz, matched case-insensitively
RecursionYes — subfolders under the chosen prefix are scanned
Minimum size10 bytes — smaller files fail the run
  • The LAS public header of each tile must parse (it is read from the first 64 KiB). A corrupt or truncated header fails the delivery, naming the file.
  • Each tile's header must say what LP-10 and LP-12 require: LAS 1.4, a point format carrying both colour and GPS time (7 or 8), and GPS time recorded as Adjusted Standard GPS time rather than raw week seconds. The last one is a single flag in the header, and getting it wrong shifts every timestamp in the tile by roughly a billion seconds — about thirty years — which is why it is worth a check of its own.
  • Tiles must be named LLxxxxxx_yyyyyy.laz after their own lower-left RD corner on the hundred-metre grid (LP-11, LP-13), and the points inside must actually lie in the 100 × 100 m square that name claims. The name is a statement about where the data is; if it disagrees with the header, the tile is mislabelled and everything built from it inherits the error.

Tiles are 100 × 100 m, cut on the hundred-metre lines of the RD grid, and each is named after its own lower-left corner in whole metres: LL136500_455800.laz is the square from 136500–136600 east and 455800–455900 north. Because the name is a statement about where the data is, it is checked against the header.

Per point, format 7 or 8 carries what the specification asks for (LP-12):

Point source IDWhich scanner recorded the point
GPS timeAdjusted Standard GPS time — the flag in the header, not raw week seconds
Colour16 bits per channel
Intensity16 bits, normalised to the scanner's range

The GPS-time flag is worth double-checking. It is one bit in the header, and getting it wrong shifts every timestamp in the tile by about thirty years.

Not yet checked here: that those per-point fields are actually populated, that the tiles cover the surroundings of every panorama, and that a photo and its LiDAR are within 60 minutes of each other (VO-3). Reading point records is a different cost from reading headers, so those remain the independent checker's for now. Their absence from your report is not a pass.

Tolerance#

  • Every CSV row must resolve to a delivered photo. One unresolvable row fails the run — but every row is checked first, so one pass reports them all.
  • Photos in the delivery that the CSV does not reference are not indexed (noted, not an error).

There is no allowance for missing photos, unlike most templates. The specification grants none and the independent checker will not either, so a delivery with photos missing is one you would have to redo (O-38). Hearing it on upload day is the cheap version of that news.

Ingest parameters#

KeyRequiredTypeMeaning
anonymisationnostring — recognised: auto_anonymise, already_anonymisedDefaults to auto_anonymise, because ProRail has faces and plates blurred here before publication (O-39) rather than asking the supplier for it. Set already_anonymised only if the delivered photos are blurred. Default: auto_anonymise.
height_above_groundyesnumberHeight of the camera above the floor or platform surface, in metres. Used for viewer positioning, not stored per photo.
set_namenostringNames the set the photos are grouped under. Leave it unset: the default is the pose CSV's own name (YYYYMM_XXXX), which is the station and capture month, and is what a viewer should show.

Coordinate reference system#

Nothing is reprojected. X and Y must already be RD (Rijksdriehoeksmeting) and Z must be NAP (O-14) — for a Dutch dataset, EPSG:7415 for positions and EPSG:28992 for footprints. See Coordinates are not reprojected.

Delivery documents#

The reports, plans and registration files the specification asks for go under Reports/delivery/, and are published alongside the imagery on acceptance (O-40). They are kept, not indexed, and their names are not currently checked.

DocumentRequirement
YYYYMM_XXXX.gpkg — the opnameplanVO-5
YYYYMMDD_Opnamerapport_XXXX.pdfUO-3
YYYYMM_XXXX_pano_verwerking.pdf / _pano_controle.pdfPF-17, PF-18
YYYYMMDD_XXXX_LiDAR-verwerking.pdf / _LiDAR-Controle.pdfLP-15, LP-16
PMG registration, report, sketches and MOVE3 projectMG-18 … MG-24, if Optie 1 was ordered
KRDZ coordinate listVC-6, if Optie 2 was ordered

A delivery cannot be submitted without at least one document here.

What you get back#

  • Indexed panoramas, positioned and oriented, in one set named after the delivery.
  • Deep-zoom tiles per photo, so the viewer can stream them.
  • Blurred derivatives — ProRail has faces and plates blurred here before publication (O-39), so leave anonymisation at its default.
  • Indexed point cloud tiles, plus a single merged COPC the web viewer streams.

Your tiles are indexed as delivered. Unlike some templates, nothing re-tiles them: they are already on the grid the specification asks for, and replacing them with our own would discard the deliverable you were contracted to produce.

Common failures#

SymptomCause
"the delivery has no panorama content"The photos are not under panorama/jpg/, or are .jpeg rather than .jpg
Every photo reported missingThe pose CSV names photos that are not in jpg/ — check for a case mismatch
"is not the 2:1 equirectangular shape"The panoramas were exported at another aspect, or are not equirectangular at all
Photos face the wrong way in the viewerThe images were delivered unrotated; the orientation must be baked in, not supplied as angles
"no panoramaopnamesysteem file"The .txt sidecar is missing, or was put in jpg/ rather than beside the CSV
"not listed by any panoramaopnamesysteem file"The sidecar is from an earlier capture and no longer lists every photo
"is not the … square that its name claims"Tiles were renamed, or cut on a different grid than the RD hundred-metre lines
"records GPS time as week seconds"The GPS time type flag in the LAS header was not set
First photo missingOnly if your CSV's first row looks like data but is malformed — a real header is detected and skipped