/** * @copyright Sister Software * @license AGPL-3.0 * @author Teffen Ellis, et al. * * Read an INSPIRE Addresses (AD) theme from a member state's WFS download service. * * The INSPIRE Directive makes Addresses a mandatory Annex I theme and accepts two kinds of download * service: a predefined-dataset ATOM feed and a Web Feature Service. Most member states publish the * second, so a file download reaches only a minority of them. * * An `ad:Address` feature carries its point, its lifecycle dates and its locator. It does not carry * its street name, its postcode or its administrative units: those are separate feature types, and * the address references them through `component`. A complete address is therefore assembled from * several feature types rather than read from one. * * Measured on Slovakia's service, one address holds four component references that resolve to three * `ad:AdminUnitName` features and one `ad:PostalDescriptor`. Resolving each reference with its own * request would cost roughly four requests per address, which is 6.8 million for that service's * 1,704,196 addresses. This module therefore pages each feature type in bulk and joins locally. * * A service's feature count is read with `readCheckedWFSFeatureCount` from `@mailwoman/core/api`, * which owns every WFS count this repository takes. */ import { type APIClient } from "@mailwoman/core/api"; /** * The feature types an INSPIRE Addresses service publishes. * * `Address` is the subject. * The other four are the components an address references, and each has to be * paged separately to build the join. */ export declare const AD_FEATURE_TYPES: readonly ["Address", "ThoroughfareName", "PostalDescriptor", "AdminUnitName", "AddressAreaName"]; /** * One of the {@link AD_FEATURE_TYPES}. */ export type ADFeatureType = (typeof AD_FEATURE_TYPES)[number]; /** * The AD feature type a service's qualified name denotes, or `null` where it denotes none. * * Services name the same type four ways, measured across the four this module was written from. * Slovakia and Flanders publish `ad:Address`. * * Estonia publishes `AD_Address:AD.Address` and `AD_Address:AD.Address_ThoroughfareName`. * Poland publishes `ms:AD.Address`. * * A reader that compares the part after the colon against `Address` finds a type * on two services and none on the other two, which reads as a service publishing * no addresses rather than as a naming difference. */ export declare function adFeatureTypeOf(qualifiedName: string): ADFeatureType | null; /** * What a service's capabilities document states about itself. */ export interface WFSCapabilities { /** * Every `outputFormat` value the service advertises for `GetFeature`. */ outputFormats: readonly string[]; /** * The advertised format this module would ask for, or `null` when the service offers no JSON. * * A service without one is readable through its GML, which this module leaves * to its caller rather than reporting as unreadable. * * `application/json` is preferred over `application/geo+json` because a service * may advertise the second and reject it. * Estonia advertises both and answers a `GetFeature` for `application/geo+json` * with `InvalidParameterValue: Failed to find response for output format`, * while the same request under `application/json` returns the features. */ jsonFormat: string | null; /** * Every advertised JSON format, in the order this module would try them. * * A caller whose first request is rejected tries the next rather than concluding that the * service serves no JSON, because an advertised format is a claim rather than a guarantee. */ jsonFormats: readonly string[]; /** * Whether the service advertises `ImplementsResultPaging`. * * A service that does not cannot be paged, so a caller has to take the whole * type in one request or decline it. * Reading past the first page of such a service silently repeats page one. */ supportsPaging: boolean; /** * The `CountDefault` the service advertises, or `null` where it advertises none. * * The largest page the service will serve, which is also the number its own * `numberMatched` reports when that number is a cap rather than a count. * Estonia advertises 1000000 and Poland 1000, measured 2026-10-02. */ countDefault: number | null; /** * The qualified type name for each AD feature type the service publishes, keyed by the local name. * * The prefix differs per service: Slovakia and Flanders publish `ad:Address`, * and Estonia publishes under its own prefix. * A caller that assumes `ad:` receives HTTP 400 from the rest. */ typeNames: Readonly>>; } /** * A value a GeoServer JSON response uses for a voidable property that holds no value. * * INSPIRE marks many attributes voidable, which requires the property to be present * carrying either a value or a void with a reason. * GeoServer encodes that void as an object rather than as `null`, so `String(value)` * on one yields `[object Object]` and stores it as though it were data. */ export interface VoidedValue { "@nil": string | boolean; "@nilReason"?: string; } /** * Is this property value a void rather than a value? */ export declare function isVoided(value: unknown): value is VoidedValue; /** * The value of a property, or `null` where the service marked it void or omitted it. * * `null` for a void keeps the void from reaching a caller as the text `[object Object]`. * * INSPIRE separates a void from an absence: an absent property states that no value exists, * and a void states that whether one exists is unknown. * This collapses both to `null`, so a caller that needs the two apart reads the * property's presence on `properties` itself. */ export declare function readVoidable(value: T | VoidedValue | undefined): T | null; /** * Reads a service's capabilities, so a caller asks only for what the service offers. */ export declare function readWFSCapabilities(client: Pick, options: { wfsURL: string; context: string; }): Promise; /** * One page of features, as the service returned them. */ export interface FeaturePage { features: readonly Feature[]; /** * The count the service reported for the whole query, or `null` where it declined to state one. * * A service may answer `unknown`, which is a refusal to count rather than a count of zero. */ numberMatched: number | null; numberReturned: number; /** * The `timeStamp` the service dated the page with, or `null` where it stated none. * * The service's own clock rather than the caller's, which is what a manifest * records to date a page against the service that served it. */ timeStamp: string | null; } /** * A GeoJSON feature, as far as this module reads one. */ export interface GeoJSONFeature { type: "Feature"; id?: string; geometry: { type: string; coordinates: unknown; } | null; properties: Record; } /** * Reads one page of a feature type. * * `startIndex` past the first page needs {@linkcode WFSCapabilities.supportsPaging}. * A service that declines paging answers page two with page one, so this refuses * rather than returning the repeat. */ export declare function readFeaturePage(client: Pick, options: { wfsURL: string; typeName: string; outputFormat: string; count: number; startIndex: number; supportsPaging: boolean; context: string; /** * The property the service orders the results by, where it honors one. * * Paging without an ordering rests on the service returning the same features in * the same order for every request, which no WFS guarantees. * Estonia honors `sortBy=gml_id` and Flanders answers HTTP 504 for its identifier, * so the parameter is the caller's to supply or leave out. */ sortBy?: string | null; }): Promise>; /** * Every `component` reference an address carries, as the service wrote them. * * Two encodings appear across the services measured. * Slovakia and Flanders write a `component` array of objects carrying `@href`. * * Estonia writes one flat property per slot, `component1_xlink_href` through `component6_xlink_href`, * and fills an unused slot with the string `unpopulated` rather than with a void object. * Reading only the array reported 0 references for every Estonian address, where each carries four. * * A reference may leave the service: Estonia's first three slots address its Administrative * Units theme at `AU_haldusyksused` rather than its Addresses theme. * That is a reference this module reads and {@linkcode resolveComponents} reports as unjoined * against Addresses features, which is the honest answer rather than a dropped reference. */ export declare function componentReferences(feature: GeoJSONFeature): readonly string[]; /** * The key a component feature publishes for an address to reference it by. * * `identifier.value` is the INSPIRE external object identifier, which is what * Flanders writes on both sides of the join. * `gml_id` is the fallback for a service that publishes no identifier, * and a stored-query reference addresses that id. */ export declare function componentIdentifier(feature: GeoJSONFeature): string | null; /** * What a pass over one service established about its references. * * `joined` and `unjoined` are counted against the component features the caller supplied, * so a caller that paged part of a component type reads `unjoined` as "not among * the features read" rather than as "absent from the service". * `unjoinedExample` is there to be looked at for that reason: a reference into another register * and a reference past the end of a page look identical in the counts and different in the URL. */ export interface ComponentResolution { /** * References whose key equals an identifier among the component features supplied. */ joined: number; /** * References whose key matched no supplied component feature, with one example. */ unjoined: number; unjoinedExample: string | null; /** * References this module could not read a key from at all. */ unreadable: number; } /** * Joins an address page's component references against the component features supplied. */ export declare function resolveComponents(addresses: readonly GeoJSONFeature[], components: readonly GeoJSONFeature[]): ComponentResolution; //# sourceMappingURL=inspire-addresses.d.ts.map