Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/shared/base-endpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { BaseLayerCore, BaseLayerExtended } from './base-layer.js';

Check failure on line 1 in src/shared/base-endpoint.ts

View workflow job for this annotation

GitHub Actions / Formatting, types and tests

'BaseLayerExtended' is defined but never used

export interface BaseEndpoint {
getLayers(): BaseLayerCore[];
getLayerById(id: string): BaseLayerCore | null;
}
61 changes: 61 additions & 0 deletions src/shared/base-layer.ts
Original file line number Diff line number Diff line change
@@ -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[];
}
3 changes: 2 additions & 1 deletion src/stac/model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Feature, Geometry } from 'geojson';
import { BoundingBox } from '../shared/models';

Check failure on line 2 in src/stac/model.ts

View workflow job for this annotation

GitHub Actions / Formatting, types and tests

Missing file extension for "../shared/models"

/**
* STAC specification version 1.0.0
Expand All @@ -10,7 +11,7 @@
* or [minx, miny, minz, maxx, maxy, maxz] for 3D
*/
export type StacBBox =
| [number, number, number, number]
| BoundingBox
| [number, number, number, number, number, number];

/**
Expand Down
4 changes: 2 additions & 2 deletions src/wms/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
45 changes: 34 additions & 11 deletions src/wms/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>;
private _info: GenericEndpointInfo | null;
Expand Down Expand Up @@ -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),
}),
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -119,15 +142,15 @@ 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) {
layer.children.map(layerLookup);
}
}
this._layers.map(layerLookup);
if (layers.length === 1) return layers[0].name;
if (layers.length === 1) return layers[0].id;
return null;
}

Expand Down Expand Up @@ -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)
Expand Down
18 changes: 5 additions & 13 deletions src/wms/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,25 @@
LayerStyle,
type MetadataURL,
} from '../shared/models.js';
import { BaseLayerCore } from '../shared/base-layer';

Check failure on line 7 in src/wms/model.ts

View workflow job for this annotation

GitHub Actions / Formatting, types and tests

Missing file extension for "../shared/base-layer"

export type WmsLayerAttribution = {
title?: string;
url?: string;
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<WmsLayerSummary, 'children'> {
/**
* The layer is renderable if defined
*/
name?: string;
title: string;
abstract?: string;
availableCrs: CrsCode[];
styles: LayerStyle[];
/**
Expand All @@ -48,6 +40,6 @@
* 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';
Loading