diff --git a/src/shared/base-endpoint.ts b/src/shared/base-endpoint.ts new file mode 100644 index 00000000..f50282af --- /dev/null +++ b/src/shared/base-endpoint.ts @@ -0,0 +1,6 @@ +import type { BaseLayerCore, BaseLayerExtended } from './base-layer.js'; + +export interface BaseEndpoint { + getLayers(): BaseLayerCore[]; + getLayerById(id: string): BaseLayerCore | null; +} diff --git a/src/shared/base-layer.ts b/src/shared/base-layer.ts new file mode 100644 index 00000000..ee3d5fc6 --- /dev/null +++ b/src/shared/base-layer.ts @@ -0,0 +1,61 @@ +import type { BoundingBox, MetadataURL } from './models.js'; + +/** + * Core properties common to all layer types. + * This is the minimal contract that all layers must satisfy. + */ +export interface BaseLayerCore { + /** + * Unique identifier within the service. + * Maps to: + * - `name` for WMS, WFS, WMTS + * - `id` for OGC API, STAC + * - `title` for TMS (which lacks a separate ID) + */ + id: string; + + /** + * Human-readable title for the layer. + */ + title?: string; + + /** + * Detailed description of the layer. + * Maps to: + * - `abstract` for WMS, WFS, TMS + * - `description` for OGC API, STAC + */ + description?: string; +} + +/** + * Extended layer properties that most layers support. + * Includes common metadata fields found across different services. + */ +export interface BaseLayerExtended extends BaseLayerCore { + /** + * Keywords/tags associated with the layer. + * Used for search and categorization. + */ + keywords?: string[]; + + /** + * Bounding box in WGS84 (EPSG:4326) coordinates. + * Format: [minx, miny, maxx, maxy] + * + * This is normalized across services: + * - WMS: Uses boundingBoxes['EPSG:4326'] or boundingBoxes['CRS:84'] + * - WFS: Uses boundingBox or latLonBoundingBox + * - WMTS: Uses latLonBoundingBox + * - OGC API: Uses extent.spatial.bbox[0] + * - STAC: Uses extent.spatial.bbox[0] + * - TMS: Uses boundingBox (may not be WGS84) + */ + boundingBox?: BoundingBox; + + /** + * Metadata URLs providing additional information about the layer. + * Links to ISO metadata, FGDC records, etc. + */ + metadata?: MetadataURL[]; +} diff --git a/src/stac/model.ts b/src/stac/model.ts index 50b80ee3..a3ba0cca 100644 --- a/src/stac/model.ts +++ b/src/stac/model.ts @@ -1,4 +1,5 @@ import { Feature, Geometry } from 'geojson'; +import { BoundingBox } from '../shared/models'; /** * STAC specification version 1.0.0 @@ -10,7 +11,7 @@ export type StacVersion = '1.0.0'; * or [minx, miny, minz, maxx, maxy, maxz] for 3D */ export type StacBBox = - | [number, number, number, number] + | BoundingBox | [number, number, number, number, number, number]; /** diff --git a/src/wms/capabilities.ts b/src/wms/capabilities.ts index 58aaa4cf..893cbca9 100644 --- a/src/wms/capabilities.ts +++ b/src/wms/capabilities.ts @@ -318,9 +318,9 @@ function parseLayer( ) ); return { - name: getElementText(findChildElement(layerEl, 'Name')), + id: getElementText(findChildElement(layerEl, 'Name')), title: getElementText(findChildElement(layerEl, 'Title')), - abstract: getElementText(findChildElement(layerEl, 'Abstract')), + description: getElementText(findChildElement(layerEl, 'Abstract')), availableCrs, styles, attribution, diff --git a/src/wms/endpoint.ts b/src/wms/endpoint.ts index 203eda0a..b41d5204 100644 --- a/src/wms/endpoint.ts +++ b/src/wms/endpoint.ts @@ -10,13 +10,15 @@ import { type OperationName, type OperationUrl, } from '../shared/models.js'; +import { BaseEndpoint } from '../shared/base-endpoint.js'; +import type { BaseLayerExtended } from '../shared/base-layer.js'; import { WmsLayerFull, WmsLayerSummary, WmsVersion } from './model.js'; import { generateGetMapUrl } from './url.js'; /** * Represents a WMS endpoint advertising several layers arranged in a tree structure. */ -export default class WmsEndpoint { +export default class WmsEndpoint implements BaseEndpoint { private _capabilitiesUrl: string; private _capabilitiesPromise: Promise; private _info: GenericEndpointInfo | null; @@ -66,15 +68,16 @@ export default class WmsEndpoint { } /** - * Returns an array of layers in summary format; layers are organized in a tree - * structure with each having an optional `children` property + * Returns an array of layers in tree structure with summary information. + * This preserves the WMS hierarchical layer organization. + * For a flat list, use getLayers() instead. */ - getLayers() { + getLayersTree() { function layerSummaryMapper(layerFull) { return { title: layerFull.title, - name: layerFull.name, - abstract: layerFull.abstract, + id: layerFull.id, + description: layerFull.description, ...('children' in layerFull && { children: layerFull.children.map(layerSummaryMapper), }), @@ -84,10 +87,30 @@ export default class WmsEndpoint { } /** - * Returns a array of layers, same as WmsEndpoint.getLayers(), but flattened + * Returns a flat array of all layers in normalized format. + * Implements the BaseEndpoint generic interface. + * @returns Array of layers with extended information + */ + getLayers(): WmsLayerSummary[] { + return this.getFlattenedLayers(); + } + + /** + * Get a specific layer by its identifier in normalized format. + * Implements the BaseEndpoint generic interface. + * @param id The layer name (WMS uses 'name' as identifier) + * @returns Layer with extended information, or null if not found + */ + getLayerById(id: string): BaseLayerExtended | null { + return this.getLayerByName(id); + } + + /** + * Returns a array of layers in flattened format (WMS-specific summary format). + * For normalized BaseLayer format, use getLayers() instead. */ getFlattenedLayers() { - return this.getLayers().flatMap(wmsLayerFlatten); + return this.getLayersTree().flatMap(wmsLayerFlatten); } /** @@ -119,7 +142,7 @@ export default class WmsEndpoint { if (!this._layers) return null; const layers: WmsLayerFull[] = []; function layerLookup(layer: WmsLayerFull) { - if (layer.name) { + if (layer.id) { layers.push(layer); } if ('children' in layer) { @@ -127,7 +150,7 @@ export default class WmsEndpoint { } } this._layers.map(layerLookup); - if (layers.length === 1) return layers[0].name; + if (layers.length === 1) return layers[0].id; return null; } @@ -217,7 +240,7 @@ function wmsLayerFlatten(layerFull) { const layer = { title: layerFull.title, name: layerFull.name, - abstract: layerFull.abstract, + description: layerFull.description, }; return 'children' in layerFull && Array.isArray(layerFull.children) diff --git a/src/wms/model.ts b/src/wms/model.ts index 0f0f06cf..cda20f10 100644 --- a/src/wms/model.ts +++ b/src/wms/model.ts @@ -4,6 +4,7 @@ import { LayerStyle, type MetadataURL, } from '../shared/models.js'; +import { BaseLayerCore } from '../shared/base-layer'; export type WmsLayerAttribution = { title?: string; @@ -11,26 +12,17 @@ export type WmsLayerAttribution = { logoUrl?: string; }; -export type WmsLayerSummary = { - /** - * The layer is renderable if defined - */ - name?: string; - title: string; - abstract?: string; +export interface WmsLayerSummary extends BaseLayerCore { /** * Not defined if the layer is a leaf in the tree */ children?: WmsLayerSummary[]; -}; +} -export type WmsLayerFull = { +export interface WmsLayerFull extends Exclude { /** * The layer is renderable if defined */ - name?: string; - title: string; - abstract?: string; availableCrs: CrsCode[]; styles: LayerStyle[]; /** @@ -48,6 +40,6 @@ export type WmsLayerFull = { * Not defined if the layer is a leaf in the tree */ children?: WmsLayerFull[]; -}; +} export type WmsVersion = '1.1.0' | '1.1.1' | '1.3.0';