Skip to content
getnextpdf.com

Pro edition

Geo — Deep Reference

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.

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.

SymbolParametersDefault behaviorReturnsThrows or fails withNotes
GeoCoordinate::__constructfloat $latitude, float $longitude, float $altitude = 0.0Validates latitude within [-90, 90] and longitude within [-180, 180]InvalidArgumentException when either value is out of rangefinal readonly; altitude is metres above sea level and is not range-checked
GeoCoordinate::toDms()noneFormats as degrees-minutes-seconds with N/S and E/W suffixesstringNear-zero seconds render as 00; otherwise two decimals with trailing zeros trimmed
GeoCoordinate::toDecimal()noneFormats latitude and longitude to six decimals, comma-separatedstringAltitude is not included
GeoCoordinate::fromDms()string $dmsParses a DMS string; seconds are optional; typographic degree and quote glyphs are normalizedselfInvalidArgumentException when the string does not parse, or when parsed values fail constructor range checksStatic factory; hemisphere letters are case-insensitive; altitude defaults to 0.0
GeoControlPoint::__constructfloat $pdfX, float $pdfY, GeoCoordinate $geoPairs a PDF user-space point (points) with a geographic coordinatefinal readonly; PDF coordinates are not validated
ProjectionTypestring-backed enum, 4 casesCases: Geographic, UTM, TransverseMercator, LambertConformalbacking values GEO, UTM, TM, LCCSee the projection mapping table below
ProjectionType::epsgCode()noneMaps the case to one fixed EPSG codeint4326, 32601, 2154, or 3347
ProjectionType::label()noneHuman-readable projection namestringFor example WGS 84 Geographic
GeoRegistration::__constructarray $controlPoints, ProjectionType $projection, string $datum = 'WGS84'Holds control points, projection, and geodetic datumfinal readonly; the control-point count is not validated at construction
GeoRegistration::isValid()noneRequires at least two control pointsboolTwo points is the minimum for an affine mapping
GeoRegistration::toPdfMeasureDictionary()noneEmits a /Measure dictionary with /Subtype /GEO, /GCS, /GPTS, /LPTS, and /BoundsstringDoes not check isValid(); guard the call or route through GeoPdfLayer
GeoPdfLayer::addRegistration()int $pageIndex, GeoRegistration $registrationAppends a registration for a zero-based page indexselfInvalidArgumentException when $pageIndex is negativeFluent; the first registration added for a page wins at generation time
GeoPdfLayer::getRegistrations()noneReturns all registrations in insertion orderlist<array{pageIndex: int, registration: GeoRegistration}>Includes duplicates and invalid registrations as added
GeoPdfLayer::generateViewportDictionary()int $pageIndexEmits a /Viewport dictionary with /BBox, /Name, and an inline /MeasurestringEmpty string when the page has no registration or the registration is invalid
GeoPdfLayer::generateViewportArray()int $pageIndexWraps the viewport dictionary in brackets as the /VP array literalstringEmpty string when absent; callers then omit /VP for that page
GeoPdfLayer::writeToPdfWriter()BinaryBuffer $buffer, int $pageIndexWrites /VP plus the array literal and a newline onto the bufferbooltrue when an entry was written; no-op and false otherwise
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): self
public 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(): string
public 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): bool

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.

Each ProjectionType case carries one fixed EPSG code and label. The mapping is a closed table, not a coordinate-reference-system registry.

CaseBacking valueepsgCode()label()
GeographicGEO4326WGS 84 Geographic
UTMUTM32601Universal Transverse Mercator
TransverseMercatorTM2154Transverse Mercator
LambertConformalLCC3347Lambert 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.

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:

  • /GCS is emitted as << /Type /PROJCS /EPSG <code> /WKT (<datum>) >>. The EPSG code comes from the projection case. The /WKT value is the datum string exactly as supplied; the default is WGS84.
  • /GPTS lists latitude-longitude pairs at six decimals, in control-point order.
  • /LPTS lists pdfX/pdfY pairs at six decimals, exactly as supplied. ISO 32000-2:2020 Table 269 defines LPTS points in a 2D unit square; supplying unit-square-normalized values is the caller’s responsibility.
  • /Bounds is fixed at [0 0 0 1 1 1 1 0], the full unit square.
  • The datum string 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.

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.

  • Out-of-range latitude or longitude throws InvalidArgumentException at 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 altitude 0.0.
  • A GeoRegistration with fewer than two control points reports isValid() false, yet toPdfMeasureDictionary() still emits a dictionary with short point arrays. Guard direct calls with isValid(), or route emission through GeoPdfLayer, 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.
ClaimStandardClause
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.

  • Available since nextpdf/pro 1.9.0; current in nextpdf/pro 3.1.0.
  • Check isValid() before calling toPdfMeasureDictionary() directly; GeoPdfLayer performs this check for you.
  • When downstream consumers parse /WKT, pass a full Well Known Text description as datum; the default WGS84 is a datum label only.
  • Normalize LPTS inputs 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 GeoPdfLayer is linear in the registration count.
  • writeToPdfWriter() integrates with page serialization through NextPDF\Support\BinaryBuffer from Core.

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.