Pro edition
Geo — Deep Reference
At a glance
Section titled “At a glance”This page is the contract-level reference for the NextPDF Pro Geo module. The surface is four immutable value objects — GeoCoordinate, GeoControlPoint, ProjectionType, and GeoRegistration — plus GeoPdfLayer, which associates registrations with page indices and emits viewport output. The module produces PDF dictionary text: a /Measure dictionary with /Subtype /GEO, a /Viewport dictionary, and the page-level /VP array value. Generation is deterministic string assembly: no network call, no filesystem access, no randomness. This page states the public API, the observable behavior contract, and the failure modes.
Availability & licensing
Section titled “Availability & licensing”This capability ships in NextPDF Pro (nextpdf/pro) and activates with a Pro-tier license envelope. A deployment without that entitlement does not load the capability’s classes. Compare editions and get a license.
No per-feature license flag gates this module. The Geo classes are available whenever nextpdf/pro is installed.
Public API surface
Section titled “Public API surface”| Symbol | Parameters | Default behavior | Returns | Throws or fails with | Notes |
|---|---|---|---|---|---|
GeoCoordinate::__construct | float $latitude, float $longitude, float $altitude = 0.0 | Validates latitude within [-90, 90] and longitude within [-180, 180] | — | InvalidArgumentException when either value is out of range | final readonly; altitude is metres above sea level and is not range-checked |
GeoCoordinate::toDms() | none | Formats as degrees-minutes-seconds with N/S and E/W suffixes | string | — | Near-zero seconds render as 00; otherwise two decimals with trailing zeros trimmed |
GeoCoordinate::toDecimal() | none | Formats latitude and longitude to six decimals, comma-separated | string | — | Altitude is not included |
GeoCoordinate::fromDms() | string $dms | Parses a DMS string; seconds are optional; typographic degree and quote glyphs are normalized | self | InvalidArgumentException when the string does not parse, or when parsed values fail constructor range checks | Static factory; hemisphere letters are case-insensitive; altitude defaults to 0.0 |
GeoControlPoint::__construct | float $pdfX, float $pdfY, GeoCoordinate $geo | Pairs a PDF user-space point (points) with a geographic coordinate | — | — | final readonly; PDF coordinates are not validated |
ProjectionType | string-backed enum, 4 cases | Cases: Geographic, UTM, TransverseMercator, LambertConformal | backing values GEO, UTM, TM, LCC | — | See the projection mapping table below |
ProjectionType::epsgCode() | none | Maps the case to one fixed EPSG code | int | — | 4326, 32601, 2154, or 3347 |
ProjectionType::label() | none | Human-readable projection name | string | — | For example WGS 84 Geographic |
GeoRegistration::__construct | array $controlPoints, ProjectionType $projection, string $datum = 'WGS84' | Holds control points, projection, and geodetic datum | — | — | final readonly; the control-point count is not validated at construction |
GeoRegistration::isValid() | none | Requires at least two control points | bool | — | Two points is the minimum for an affine mapping |
GeoRegistration::toPdfMeasureDictionary() | none | Emits a /Measure dictionary with /Subtype /GEO, /GCS, /GPTS, /LPTS, and /Bounds | string | — | Does not check isValid(); guard the call or route through GeoPdfLayer |
GeoPdfLayer::addRegistration() | int $pageIndex, GeoRegistration $registration | Appends a registration for a zero-based page index | self | InvalidArgumentException when $pageIndex is negative | Fluent; the first registration added for a page wins at generation time |
GeoPdfLayer::getRegistrations() | none | Returns all registrations in insertion order | list<array{pageIndex: int, registration: GeoRegistration}> | — | Includes duplicates and invalid registrations as added |
GeoPdfLayer::generateViewportDictionary() | int $pageIndex | Emits a /Viewport dictionary with /BBox, /Name, and an inline /Measure | string | — | Empty string when the page has no registration or the registration is invalid |
GeoPdfLayer::generateViewportArray() | int $pageIndex | Wraps the viewport dictionary in brackets as the /VP array literal | string | — | Empty string when absent; callers then omit /VP for that page |
GeoPdfLayer::writeToPdfWriter() | BinaryBuffer $buffer, int $pageIndex | Writes /VP plus the array literal and a newline onto the buffer | bool | — | true when an entry was written; no-op and false otherwise |
Entry-point signatures
Section titled “Entry-point signatures”public function __construct( public float $latitude, public float $longitude, public float $altitude = 0.0,)
public function toDms(): string
public function toDecimal(): string
public static function fromDms(string $dms): selfpublic function __construct( public float $pdfX, public float $pdfY, public GeoCoordinate $geo,)public function __construct( public array $controlPoints, public ProjectionType $projection, public string $datum = 'WGS84',)
public function isValid(): bool
public function toPdfMeasureDictionary(): stringpublic function addRegistration(int $pageIndex, GeoRegistration $registration): self
public function getRegistrations(): array
public function generateViewportDictionary(int $pageIndex): string
public function generateViewportArray(int $pageIndex): string
public function writeToPdfWriter(BinaryBuffer $buffer, int $pageIndex): boolBehavior contract
Section titled “Behavior contract”Coordinate validation and formatting
Section titled “Coordinate validation and formatting”GeoCoordinate validates at construction and never mutates. Latitude outside [-90, 90] or longitude outside [-180, 180] throws InvalidArgumentException naming the offending value. toDms() renders both axes as degrees, zero-padded minutes, seconds, and a hemisphere suffix. toDecimal() renders latitude, longitude at six decimals. fromDms() accepts seconds-optional DMS input, normalizes prime, double-prime, degree-sign, and smart-quote glyphs, converts to signed decimal degrees, and constructs a new instance. Southern latitudes and western longitudes become negative values.
Projection mapping
Section titled “Projection mapping”Each ProjectionType case carries one fixed EPSG code and label. The mapping is a closed table, not a coordinate-reference-system registry.
| Case | Backing value | epsgCode() | label() |
|---|---|---|---|
Geographic | GEO | 4326 | WGS 84 Geographic |
UTM | UTM | 32601 | Universal Transverse Mercator |
TransverseMercator | TM | 2154 | Transverse Mercator |
LambertConformal | LCC | 3347 | Lambert Conformal Conic |
The UTM case emits the zone 1 code. Projects that need a different UTM zone, or any EPSG code outside this table, should carry the authoritative CRS description in the datum string as Well Known Text.
Measure dictionary emission
Section titled “Measure dictionary emission”GeoRegistration::toPdfMeasureDictionary() emits a multi-line dictionary: /Type /Measure, /Subtype /GEO, a /GCS coordinate-system dictionary, /GPTS, /LPTS, and /Bounds, per ISO 32000-2:2020 §12.10 (Table 269). Concrete behavior:
/GCSis emitted as<< /Type /PROJCS /EPSG <code> /WKT (<datum>) >>. The EPSG code comes from the projection case. The/WKTvalue is thedatumstring exactly as supplied; the default isWGS84./GPTSlists latitude-longitude pairs at six decimals, in control-point order./LPTSlistspdfX/pdfYpairs at six decimals, exactly as supplied. ISO 32000-2:2020 Table 269 definesLPTSpoints in a 2D unit square; supplying unit-square-normalized values is the caller’s responsibility./Boundsis fixed at[0 0 0 1 1 1 1 0], the full unit square.- The
datumstring is escaped before interpolation into the literal string: backslash, parentheses, and common control characters become their backslash escapes per ISO 32000-2:2020 §7.3.4.2. A caller-influenced datum cannot terminate the literal string or inject raw PDF tokens.
Viewport and page emission
Section titled “Viewport and page emission”GeoPdfLayer keeps registrations in insertion order, keyed by zero-based page index. generateViewportDictionary() resolves the first registration for the requested page and returns an empty string when none exists or when isValid() is false. A produced dictionary carries /Type /Viewport, a /BBox computed from the minimum and maximum control-point PDF coordinates, a /Name of the form GeoViewport_Page<n>, and the inline /Measure dictionary. The viewport /Measure entry follows ISO 32000-2:2020 §12.9. generateViewportArray() wraps the dictionary in brackets, producing the page /VP value: an array of viewport dictionaries per ISO 32000-2:2020 §7.7.3.3 (Table 31). writeToPdfWriter() writes /VP plus the array literal onto a Core BinaryBuffer and reports whether anything was written, so page serialization can omit the key cleanly.
Edge cases & failure modes
Section titled “Edge cases & failure modes”- Out-of-range latitude or longitude throws
InvalidArgumentExceptionat construction; no partially valid coordinate exists. fromDms()throws on unparseable input. Parsed values pass through the constructor, so a syntactically valid string with out-of-range values also throws.- DMS notation carries no altitude;
fromDms()always yields altitude0.0. - A
GeoRegistrationwith fewer than two control points reportsisValid()false, yettoPdfMeasureDictionary()still emits a dictionary with short point arrays. Guard direct calls withisValid(), or route emission throughGeoPdfLayer, which suppresses invalid registrations. - Duplicate registrations for one page index are all retained by
getRegistrations(); viewport generation uses the first one added. - A negative page index throws
InvalidArgumentException; page indices are zero-based. - Control points that share an X or Y value produce a degenerate zero-width or zero-height
/BBox. Supply points that span both axes. - All output is generated text. Nothing is written to disk or network, and identical input produces identical output.
- No cryptographic operation occurs in this module, so there is no FIPS-mode-specific behavior.
Conformance
Section titled “Conformance”| Claim | Standard | Clause |
|---|---|---|
The measure dictionary is emitted with subtype GEO, with GPTS latitude-longitude pairs and paired LPTS values. | ISO 32000-2:2020 | §12.10 |
The viewport dictionary carries BBox, Name, and Measure entries. | ISO 32000-2:2020 | §12.9 |
The page VP value is emitted as an array of viewport dictionaries. | ISO 32000-2:2020 | §7.7.3.3 |
| Datum interpolation escapes literal-string metacharacters. | ISO 32000-2:2020 | §7.3.4.2 |
All clauses are paraphrased; NextPDF does not reproduce normative text. EPSG codes are fixed representative values per projection case, and the /WKT entry carries the supplied datum string rather than a generated Well Known Text description; both statements are product-grounded. Validate emitted GeoPDF output in the target interactive PDF processors before relying on viewer-side measurement.
Development notes
Section titled “Development notes”- Available since
nextpdf/pro1.9.0; current innextpdf/pro3.1.0. - Check
isValid()before callingtoPdfMeasureDictionary()directly;GeoPdfLayerperforms this check for you. - When downstream consumers parse
/WKT, pass a full Well Known Text description asdatum; the defaultWGS84is a datum label only. - Normalize
LPTSinputs to the unit square before constructing control points when the viewport bounds differ from your PDF-space values. - Emission cost is linear in the control-point count; lookup in
GeoPdfLayeris linear in the registration count. writeToPdfWriter()integrates with page serialization throughNextPDF\Support\BinaryBufferfrom Core.
Publication boundary
Section titled “Publication boundary”This page documents externally observable behavior and the supported public API surface only. Internal namespace paths, helper classes, mechanism tables, runbook filenames, and ticket prefixes are out of scope.
See also
Section titled “See also”- Geo (capability) — install, conceptual overview, and quick-start samples.
- Document — Deep Reference — the document and page composition surface.