New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@camptocamp/ogc-client

Package Overview
Dependencies
Maintainers
0
Versions
55
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@camptocamp/ogc-client - npm Package Compare versions

Comparing version 1.1.1-dev.ecb3450 to 1.2.0

dist/assets/worker-QhPIJBhI.js.map

7

dist/index.d.ts
export { default as WfsEndpoint } from './wfs/endpoint.js';
export type { WfsVersion, WfsFeatureWithProps, WfsFeatureTypeSummary, WfsFeatureTypeBrief, FeatureGeometryType, FeaturePropertyType, WfsFeatureTypeFull, WfsFeatureTypePropDetails, WfsFeatureTypePropsDetails, WfsFeatureTypeUniqueValue, } from './wfs/model.js';
export type { WfsVersion, WfsFeatureWithProps, WfsFeatureTypeSummary, WfsFeatureTypeBrief, FeatureGeometryType, FeaturePropertyType, WfsFeatureTypeFull, WfsFeatureTypePropDetails, WfsFeatureTypePropsDetails, WfsFeatureTypeUniqueValue, WfsGetFeatureOptions, } from './wfs/model.js';
export { default as WmsEndpoint } from './wms/endpoint.js';

@@ -7,8 +7,9 @@ export type { WmsLayerFull, WmsVersion, WmsLayerSummary, WmsLayerAttribution, } from './wms/model.js';

export type { WmtsLayerDimensionValue, WmtsLayerResourceLink, WmtsEndpointInfo, WmtsLayer, WmtsMatrixSet, } from './wmts/model.js';
export type { LayerStyle, BoundingBox, FetchOptions, GenericEndpointInfo, } from './shared/models.js';
export type { Address, Contact, Provider, LayerStyle, BoundingBox, MetadataURL, FetchOptions, GenericEndpointInfo, MimeType, CrsCode, } from './shared/models.js';
export { default as OgcApiEndpoint } from './ogc-api/endpoint.js';
export * from './ogc-api/model.js';
export { useCache } from './shared/cache.js';
export { useCache, clearCache } from './shared/cache.js';
export { sharedFetch, setFetchOptions, resetFetchOptions, } from './shared/http-utils.js';
export { check, ServiceExceptionError, EndpointError, } from './shared/errors.js';
export { enableFallbackWithoutWorker } from './worker/index.js';
//# sourceMappingURL=index.d.ts.map

@@ -6,3 +6,3 @@ import { default as default2 } from "./wfs/endpoint.js";

export * from "./ogc-api/model.js";
import { useCache } from "./shared/cache.js";
import { useCache, clearCache } from "./shared/cache.js";
import {

@@ -13,9 +13,18 @@ sharedFetch,

} from "./shared/http-utils.js";
import {
check,
ServiceExceptionError,
EndpointError
} from "./shared/errors.js";
import { enableFallbackWithoutWorker } from "./worker/index.js";
import "./worker-fallback/index.js";
export {
EndpointError,
default5 as OgcApiEndpoint,
ServiceExceptionError,
default2 as WfsEndpoint,
default3 as WmsEndpoint,
default4 as WmtsEndpoint,
check,
clearCache,
enableFallbackWithoutWorker,

@@ -22,0 +31,0 @@ resetFetchOptions,

@@ -7,3 +7,3 @@ import {

parseBaseCollectionInfo,
parseFullStyleInfo,
parseBasicStyleInfo,
parseCollectionParameters,

@@ -13,3 +13,3 @@ parseCollections,

parseEndpointInfo,
parseBasicStyleInfo,
parseFullStyleInfo,
parseTileMatrixSets

@@ -16,0 +16,0 @@ } from "./info.js";

@@ -31,3 +31,7 @@ /**

*/
export declare function purgeEntries(): Promise<void>;
export declare function purgeOutdatedEntries(): Promise<void>;
/**
* Remove all cache entries; will not prevent the creation of new ones
*/
export declare function clearCache(): Promise<void>;
//# sourceMappingURL=cache.d.ts.map

@@ -67,3 +67,3 @@ let cacheExpiryDuration = 1e3 * 60 * 60;

async function useCache(factory, ...keys) {
await purgeEntries();
await purgeOutdatedEntries();
if (await hasValidCacheEntry(...keys)) {

@@ -85,3 +85,3 @@ return readCacheEntry(...keys);

}
async function purgeEntries() {
async function purgeOutdatedEntries() {
const cache = await getCache();

@@ -97,8 +97,18 @@ if (!cache)

}
async function clearCache() {
const cache = await getCache();
if (!cache)
return;
const keys = await cache.keys();
for (const key of keys) {
await cache.delete(key);
}
}
export {
_resetCache,
clearCache,
getCache,
getCacheExpiryDuration,
hasValidCacheEntry,
purgeEntries,
purgeOutdatedEntries,
readCacheEntry,

@@ -105,0 +115,0 @@ setCacheExpiryDuration,

@@ -0,7 +1,49 @@

import type { XmlDocument, XmlElement } from '@rgrove/parse-xml';
export declare class EndpointError extends Error {
message: string;
httpStatus?: number;
isCrossOriginRelated?: boolean;
readonly httpStatus?: number;
readonly isCrossOriginRelated?: boolean;
constructor(message: string, httpStatus?: number, isCrossOriginRelated?: boolean);
}
/**
* Representation of an Exception reported by an OWS service
*
* This is usually contained in a ServiceExceptionReport or ExceptionReport
* document and represented as a ServiceException or Exception element
*/
export declare class ServiceExceptionError extends Error {
readonly requestUrl?: string;
readonly code?: string;
readonly locator?: string;
readonly response?: XmlDocument;
/**
* Constructor
* @param message Error message
* @param requestUrl URL which resulted in the ServiceException
* @param code Optional ServiceException code
* @param locator Optional ServiceException locator
* @param response Optional response content received
*/
constructor(message: string, requestUrl?: string, code?: string, locator?: string, response?: XmlDocument);
}
/**
* Parse a ServiceException element to a ServiceExceptionError
* @param serviceException ServiceException element
* @param url URL from which the ServiceException was generated
*/
export declare function parse(serviceException: XmlElement, url?: string): ServiceExceptionError;
/**
* Check the response for a ServiceExceptionReport and if present throw one
* @param response Response to check
* @param url URL from which response was generated
*/
export declare function check(response: XmlDocument, url?: string): XmlDocument;
/**
* This transforms an error object into a JSON-serializable object to be
* transferred from a worker
*/
export declare function encodeError(error: Error): Record<string, unknown>;
/**
* Recreates an error object
*/
export declare function decodeError(error: Record<string, unknown>): Error;
//# sourceMappingURL=errors.d.ts.map

@@ -0,12 +1,122 @@

import {
findChildElement,
getElementAttribute,
getElementName,
getElementText,
getRootElement,
stripNamespace
} from "../shared/xml-utils.js";
class EndpointError extends Error {
constructor(message, httpStatus, isCrossOriginRelated) {
super(message);
this.message = message;
this.httpStatus = httpStatus;
this.isCrossOriginRelated = isCrossOriginRelated;
this.name = "EndpointError";
}
}
class ServiceExceptionError extends Error {
/**
* Constructor
* @param message Error message
* @param requestUrl URL which resulted in the ServiceException
* @param code Optional ServiceException code
* @param locator Optional ServiceException locator
* @param response Optional response content received
*/
constructor(message, requestUrl, code, locator, response) {
super(message);
this.requestUrl = requestUrl;
this.code = code;
this.locator = locator;
this.response = response;
this.name = "ServiceExceptionError";
}
}
function parse(serviceException, url) {
const errorCode = getElementAttribute(serviceException, "code") || getElementAttribute(serviceException, "exceptionCode");
const errorLocator = getElementAttribute(serviceException, "locator");
const textElement = findChildElement(serviceException, "ExceptionText") || serviceException;
const errorMessage = getElementText(textElement).trim();
return new ServiceExceptionError(
errorMessage,
url,
errorCode,
errorLocator,
serviceException.document
);
}
function check(response, url) {
const rootEl = getRootElement(response);
const rootElName = stripNamespace(getElementName(rootEl));
if (rootElName === "ServiceExceptionReport") {
const error = findChildElement(rootEl, "ServiceException");
if (error) {
throw parse(error, url);
}
}
if (rootElName === "ExceptionReport") {
const error = findChildElement(rootEl, "Exception");
if (error) {
throw parse(error, url);
}
}
return response;
}
function encodeError(error) {
const base = {
message: error.message,
stack: error.stack,
name: error.name
};
if (error instanceof ServiceExceptionError) {
return {
...base,
code: error.code,
locator: error.locator,
response: error.response,
requestUrl: error.requestUrl
};
}
if (error instanceof EndpointError) {
return {
...base,
httpStatus: error.httpStatus,
isCrossOriginRelated: error.isCrossOriginRelated
};
}
return base;
}
function decodeError(error) {
if (error.name === "ServiceExceptionError") {
const e2 = new ServiceExceptionError(
error.message,
error.requestUrl,
error.code,
error.locator,
error.response
);
e2.stack = error.stack;
return e2;
}
if (error.name === "EndpointError") {
const e2 = new EndpointError(
error.message,
error.httpStatus,
error.isCrossOriginRelated
);
e2.stack = error.stack;
return e2;
}
const e = new Error(error.message);
e.stack = error.stack;
return e;
}
export {
EndpointError
EndpointError,
ServiceExceptionError,
check,
decodeError,
encodeError,
parse
};
//# sourceMappingURL=errors.js.map

@@ -6,2 +6,23 @@ /**

export type CrsCode = string;
export interface Address {
deliveryPoint?: string;
city?: string;
administrativeArea?: string;
postalCode?: string;
country?: string;
}
export interface Contact {
name?: string;
organization?: string;
position?: string;
phone?: string;
fax?: string;
address?: Address;
email?: string;
}
export interface Provider {
name?: string;
site?: string;
contact?: Contact;
}
export type GenericEndpointInfo = {

@@ -14,2 +35,3 @@ name: string;

keywords: string[];
provider?: Provider;
/**

@@ -40,2 +62,5 @@ * Can contain the list of outputFormats from a WFS GetCapabilities,

}
export type OperationName = string;
export type HttpMethod = 'Get' | 'Post';
export type OperationUrl = Partial<Record<HttpMethod, string>>;
export interface LayerStyle {

@@ -50,2 +75,7 @@ name: string;

}
export type MetadataURL = {
format?: string;
type?: string;
url: string;
};
//# sourceMappingURL=models.d.ts.map
import { XmlDocument } from '@rgrove/parse-xml';
import { GenericEndpointInfo, MimeType } from '../shared/models.js';
import { GenericEndpointInfo, MimeType, type OperationName, type OperationUrl } from '../shared/models.js';
import { WfsFeatureTypeInternal, WfsVersion } from './model.js';
/**
* Will read the operation URLS from the capabilities doc
* @param capabilitiesDoc Capabilities document
*/
export declare function readOperationUrlsFromCapabilities(capabilitiesDoc: XmlDocument): Record<OperationName, OperationUrl>;
/**
* Will read a WFS version from the capabilities doc

@@ -6,0 +11,0 @@ * @param capabilitiesDoc Capabilities document

@@ -0,1 +1,2 @@

import { readProviderFromCapabilities } from "../shared/ows.js";
import {

@@ -8,5 +9,30 @@ findChildElement,

getElementText,
getRootElement
getRootElement,
stripNamespace
} from "../shared/xml-utils.js";
import { simplifyEpsgUrn } from "../shared/crs-utils.js";
function readOperationUrlsFromCapabilities(capabilitiesDoc) {
const urls = {};
const capabilities = getRootElement(capabilitiesDoc);
const operationsMetadata = findChildElement(
capabilities,
"OperationsMetadata"
);
if (operationsMetadata) {
findChildrenElement(operationsMetadata, "Operation").forEach(
(operation) => {
const name = getElementAttribute(operation, "name");
urls[name] = parseOperation110(operation);
}
);
} else {
const capability = findChildElement(capabilities, "Capability");
const request = findChildElement(capability, "Request");
getChildrenElement(request).forEach((operation) => {
const name = stripNamespace(getElementName(operation));
urls[name] = parseOperation100(operation);
});
}
return urls;
}
function readVersionFromCapabilities(capabilitiesDoc) {

@@ -60,2 +86,6 @@ return getRootElement(capabilitiesDoc).attributes["version"];

}
let provider;
if (version !== "1.0.0") {
provider = readProviderFromCapabilities(capabilitiesDoc);
}
return {

@@ -68,2 +98,3 @@ title: getElementText(findChildElement(service, "Title")),

keywords,
provider,
outputFormats: readOutputFormatsFromCapabilities(capabilitiesDoc)

@@ -83,2 +114,24 @@ };

}
function parseOperation100(operation) {
const urls = {};
const dcpType = findChildrenElement(operation, "DCPType");
const http = dcpType.flatMap((d) => findChildrenElement(d, "HTTP"));
const methods = http.flatMap((h) => getChildrenElement(h));
methods.forEach((method) => {
const methodName = stripNamespace(getElementName(method));
urls[methodName] = getElementAttribute(method, "onlineResource");
});
return urls;
}
function parseOperation110(operation) {
const urls = {};
const dcpType = findChildrenElement(operation, "DCP");
const http = dcpType.flatMap((d) => findChildElement(d, "HTTP"));
const methods = http.flatMap((h) => getChildrenElement(h));
methods.forEach((method) => {
const methodName = stripNamespace(getElementName(method));
urls[methodName] = getElementAttribute(method, "xlink:href");
});
return urls;
}
function parseFeatureType(featureTypeEl, serviceVersion, defaultOutputFormats) {

@@ -104,2 +157,13 @@ const srsTag = serviceVersion.startsWith("2.") ? "CRS" : "SRS";

).map(getElementText).filter((v, i, arr) => arr.indexOf(v) === i);
const metadata = serviceVersion === "2.0.0" ? findChildrenElement(featureTypeEl, "MetadataURL").map(
(metadataUrlEl) => ({
url: getElementAttribute(metadataUrlEl, "xlink:href")
})
) : findChildrenElement(featureTypeEl, "MetadataURL").map(
(metadataUrlEl) => ({
format: getElementAttribute(metadataUrlEl, "format"),
type: getElementAttribute(metadataUrlEl, "type"),
url: getElementText(metadataUrlEl).trim()
})
);
return {

@@ -115,3 +179,4 @@ name: getElementText(findChildElement(featureTypeEl, "Name")),

latLonBoundingBox: serviceVersion.startsWith("1.0") ? parseBBox100() : parseBBox(),
keywords
keywords,
...metadata.length && { metadata }
};

@@ -122,2 +187,3 @@ }

readInfoFromCapabilities,
readOperationUrlsFromCapabilities,
readOutputFormatsFromCapabilities,

@@ -124,0 +190,0 @@ readVersionFromCapabilities

@@ -1,3 +0,3 @@

import { BoundingBox, CrsCode, GenericEndpointInfo, MimeType } from '../shared/models.js';
import { WfsFeatureTypeBrief, WfsFeatureTypeSummary, WfsVersion } from './model.js';
import { GenericEndpointInfo, type HttpMethod, type OperationName } from '../shared/models.js';
import { WfsFeatureTypeBrief, WfsFeatureTypeSummary, WfsGetFeatureOptions, WfsVersion } from './model.js';
/**

@@ -11,2 +11,3 @@ * Represents a WFS endpoint advertising several feature types

private _featureTypes;
private _url;
private _version;

@@ -76,22 +77,20 @@ /**

* @param featureType
* @param {Object} [options]
* @property [options.maxFeatures] no limit if undefined
* @property [options.asJson] if true, will ask for GeoJSON; will throw if the service does not support it
* @property [options.outputFormat] a supported output format (overridden by `asJson`)
* @property [options.outputCrs] if unspecified, this will be the data native projection
* @property [options.extent] an extent to restrict returned objects
* @property [options.extentCrs] if unspecified, `extent` should be in the data native projection
* @property [options.startIndex] if the service supports it, this will be the index of the first feature to return
* @param options
* @returns Returns null if endpoint is not ready
*/
getFeatureUrl(featureType: string, options: {
maxFeatures?: number;
asJson?: boolean;
outputFormat?: MimeType;
outputCrs?: CrsCode;
extent?: BoundingBox;
extentCrs?: CrsCode;
startIndex?: number;
}): string;
getFeatureUrl(featureType: string, options?: WfsGetFeatureOptions): string;
/**
* Returns the Capabilities URL of the WMS
*
* This is the URL reported by the service if available, otherwise the URL
* passed to the constructor
*/
getCapabilitiesUrl(): string;
/**
* Returns the URL reported by the WFS for the given operation
* @param operationName e.g. GetFeature, GetCapabilities, etc.
* @param method HTTP method
*/
getOperationUrl(operationName: OperationName, method?: HttpMethod): string;
}
//# sourceMappingURL=endpoint.d.ts.map

@@ -19,2 +19,3 @@ import {

_featureTypes;
_url;
_version;

@@ -36,5 +37,6 @@ /**

this._capabilitiesUrl
).then(({ info, featureTypes, version }) => {
).then(({ info, featureTypes, url: url2, version }) => {
this._info = info;
this._featureTypes = featureTypes;
this._url = url2;
this._version = version;

@@ -99,3 +101,4 @@ });

outputFormats: featureType.outputFormats,
keywords: featureType.keywords
keywords: featureType.keywords,
..."metadata" in featureType && { metadata: featureType.metadata }
};

@@ -116,3 +119,3 @@ }

const describeUrl = generateDescribeFeatureTypeUrl(
this._capabilitiesUrl,
this.getOperationUrl("DescribeFeatureType"),
this._version,

@@ -122,3 +125,3 @@ name

const getFeatureUrl = generateGetFeatureUrl(
this._capabilitiesUrl,
this.getOperationUrl("GetFeature"),
this._version,

@@ -220,10 +223,3 @@ name,

* @param featureType
* @param {Object} [options]
* @property [options.maxFeatures] no limit if undefined
* @property [options.asJson] if true, will ask for GeoJSON; will throw if the service does not support it
* @property [options.outputFormat] a supported output format (overridden by `asJson`)
* @property [options.outputCrs] if unspecified, this will be the data native projection
* @property [options.extent] an extent to restrict returned objects
* @property [options.extentCrs] if unspecified, `extent` should be in the data native projection
* @property [options.startIndex] if the service supports it, this will be the index of the first feature to return
* @param options
* @returns Returns null if endpoint is not ready

@@ -242,3 +238,5 @@ */

extentCrs,
startIndex
startIndex,
attributes,
hitsOnly
} = options || {};

@@ -265,3 +263,3 @@ const internalFeatureType = this._getFeatureTypeByName(featureType);

return generateGetFeatureUrl(
this._capabilitiesUrl,
this.getOperationUrl("GetFeature"),
this._version,

@@ -271,4 +269,4 @@ internalFeatureType.name,

maxFeatures,
void 0,
void 0,
attributes,
hitsOnly,
outputCrs,

@@ -280,2 +278,29 @@ extent,

}
/**
* Returns the Capabilities URL of the WMS
*
* This is the URL reported by the service if available, otherwise the URL
* passed to the constructor
*/
getCapabilitiesUrl() {
const baseUrl = this.getOperationUrl("GetCapabilities");
if (!baseUrl) {
return this._capabilitiesUrl;
}
return setQueryParams(baseUrl, {
SERVICE: "WMS",
REQUEST: "GetCapabilities"
});
}
/**
* Returns the URL reported by the WFS for the given operation
* @param operationName e.g. GetFeature, GetCapabilities, etc.
* @param method HTTP method
*/
getOperationUrl(operationName, method = "Get") {
if (!this._url) {
return null;
}
return this._url[operationName]?.[method];
}
}

@@ -282,0 +307,0 @@ export {

@@ -15,3 +15,4 @@ import {

latLonBoundingBox: boundingBox,
keywords
keywords,
metadata
} = featureType;

@@ -28,3 +29,3 @@ const hitsAttr = serviceVersion.startsWith("2.0") ? "numberMatched" : "numberOfFeatures";

const typeElementsEls = findChildrenElement(complexTypeEl, "element", true);
const properties = typeElementsEls.filter((el) => getElementAttribute(el, "type").startsWith("xsd:")).reduce(
const properties = typeElementsEls.filter((el) => /^xsd:|^xs:/.test(getElementAttribute(el, "type"))).reduce(
(prev, curr) => ({

@@ -55,3 +56,4 @@ ...prev,

...!Number.isNaN(objectCount) && { objectCount },
...keywords && { keywords }
...keywords && { keywords },
...metadata && { metadata }
};

@@ -58,0 +60,0 @@ }

@@ -1,2 +0,2 @@

import { BoundingBox, CrsCode, MimeType } from '../shared/models.js';
import { BoundingBox, CrsCode, MetadataURL, MimeType } from '../shared/models.js';
export type WfsVersion = '1.0.0' | '1.1.0' | '2.0.0';

@@ -12,2 +12,3 @@ export type WfsFeatureTypeInternal = {

keywords?: string[];
metadata?: MetadataURL[];
};

@@ -37,2 +38,3 @@ export type FeaturePropertyType = string | number | boolean;

keywords?: string[];
metadata?: MetadataURL[];
};

@@ -86,2 +88,42 @@ export type WfsFeatureTypeFull = {

export type WfsFeatureTypePropsDetails = Record<string, WfsFeatureTypePropDetails>;
export type WfsGetFeatureOptions = {
/**
* No limit if undefined
*/
maxFeatures?: number;
/**
* if true, will ask for GeoJSON; will throw if the service does not support it
*/
asJson?: boolean;
/**
* a supported output format (overridden by `asJson`)
*/
outputFormat?: MimeType;
/**
* if unspecified, this will be the data native projection
*/
outputCrs?: CrsCode;
/**
* an extent to restrict returned objects
*/
extent?: BoundingBox;
/**
* if unspecified, `extent` should be in the data native projection
*/
extentCrs?: CrsCode;
/**
* if the service supports it, this will be the index of the first feature to return
*/
startIndex?: number;
/**
* if not defined, all attributes will be included; note that the fact
* that these attributes exist or not will not be checked!
*/
attributes?: string[];
/**
* if true, will not return feature data, only hit count
* note: this might not work for WFS version < 2
*/
hitsOnly?: boolean;
};
//# sourceMappingURL=model.d.ts.map

@@ -5,2 +5,8 @@ import { XmlDocument } from '@rgrove/parse-xml';

/**
* Will read all operation URLs from the capabilities doc
* @param capabilitiesDoc Capabilities document
* @return The parsed operations URLs
*/
export declare function readOperationUrlsFromCapabilities(capabilitiesDoc: XmlDocument): Record<string, Partial<Record<import("../shared/models.js").HttpMethod, string>>>;
/**
* Will read a WMS version from the capabilities doc

@@ -7,0 +13,0 @@ * @param capabilitiesDoc Capabilities document

@@ -0,9 +1,25 @@

import { hasInvertedCoordinates } from "../shared/crs-utils.js";
import {
findChildElement,
findChildrenElement,
getChildrenElement,
getElementAttribute,
getElementName,
getElementText,
getRootElement
getRootElement,
stripNamespace
} from "../shared/xml-utils.js";
import { hasInvertedCoordinates } from "../shared/crs-utils.js";
function readOperationUrlsFromCapabilities(capabilitiesDoc) {
const urls = {};
const capability = findChildElement(
getRootElement(capabilitiesDoc),
"Capability"
);
const request = findChildElement(capability, "Request");
getChildrenElement(request).forEach((operation) => {
const operationName = stripNamespace(getElementName(operation));
urls[operationName] = parseOperation(operation);
});
return urls;
}
function readVersionFromCapabilities(capabilitiesDoc) {

@@ -70,2 +86,3 @@ return getRootElement(capabilitiesDoc).attributes["version"];

).map(getElementText).filter((v, i, arr) => arr.indexOf(v) === i);
const provider = readProviderFromCapabilities(capabilitiesDoc);
return {

@@ -80,6 +97,19 @@ title: getElementText(findChildElement(service, "Title")),

constraints: getElementText(findChildElement(service, "AccessConstraints")),
provider,
keywords
};
}
function parseLayer(layerEl, version, inheritedSrs = [], inheritedStyles = [], inheritedAttribution = null, inheritedBoundingBoxes = null) {
function parseOperation(operation) {
const urls = {};
const dcpType = findChildrenElement(operation, "DCPType");
const http = dcpType.flatMap((d) => findChildElement(d, "HTTP"));
const methods = http.flatMap((h) => getChildrenElement(h));
methods.forEach((method) => {
const onlineResource = findChildElement(method, "OnlineResource");
const methodName = stripNamespace(getElementName(method));
urls[methodName] = getElementAttribute(onlineResource, "xlink:href");
});
return urls;
}
function parseLayer(layerEl, version, inheritedSrs = [], inheritedStyles = [], inheritedAttribution = null, inheritedBoundingBoxes = null, inheritedMaxScaleDenom = null, inheritedMinScaleDenom = null) {
const srsTag = version === "1.3.0" ? "CRS" : "SRS";

@@ -110,2 +140,24 @@ const srsList = findChildrenElement(layerEl, srsTag).map(getElementText);

}
function parseScaleHintValue(textValue, defaultValue) {
if (textValue === "") {
return defaultValue;
}
return Math.sqrt(0.5 * parseFloat(textValue) ** 2) / 28e-5;
}
function parseScaleHint() {
const scaleHint = findChildElement(layerEl, "ScaleHint");
if (!scaleHint) {
return [inheritedMinScaleDenom, inheritedMaxScaleDenom];
}
const min = getElementAttribute(scaleHint, "min");
const max = getElementAttribute(scaleHint, "max");
return [
parseScaleHintValue(min, inheritedMinScaleDenom),
parseScaleHintValue(max, inheritedMaxScaleDenom)
];
}
function parseScaleDenominator(name, inheritedValue) {
const textValue = getElementText(findChildElement(layerEl, name));
return textValue === "" ? inheritedValue : parseFloat(textValue);
}
const attributionEl = findChildElement(layerEl, "Attribution");

@@ -132,4 +184,36 @@ const attribution = attributionEl !== null ? parseLayerAttribution(attributionEl) : inheritedAttribution;

).map(getElementText).filter((v, i, arr) => arr.indexOf(v) === i);
let minScaleDenominator, maxScaleDenominator;
if (version === "1.3.0") {
minScaleDenominator = parseScaleDenominator(
"MinScaleDenominator",
inheritedMinScaleDenom
);
maxScaleDenominator = parseScaleDenominator(
"MaxScaleDenominator",
inheritedMaxScaleDenom
);
} else {
[minScaleDenominator, maxScaleDenominator] = parseScaleHint();
}
const metadata = findChildrenElement(layerEl, "MetadataURL").map(
(metadataUrlEl) => ({
type: getElementAttribute(metadataUrlEl, "type"),
format: getElementText(findChildElement(metadataUrlEl, "Format")),
url: getElementAttribute(
findChildElement(metadataUrlEl, "OnlineResource"),
"xlink:href"
)
})
);
const children = findChildrenElement(layerEl, "Layer").map(
(layer) => parseLayer(layer, version, availableCrs, styles, attribution, boundingBoxes)
(layer) => parseLayer(
layer,
version,
availableCrs,
styles,
attribution,
boundingBoxes,
maxScaleDenominator,
minScaleDenominator
)
);

@@ -147,2 +231,5 @@ return {

opaque,
...minScaleDenominator !== null ? { minScaleDenominator } : {},
...maxScaleDenominator !== null ? { maxScaleDenominator } : {},
...metadata.length && { metadata },
...children.length && { children }

@@ -183,2 +270,42 @@ };

}
function readProviderFromCapabilities(capabilitiesDoc) {
const service = findChildElement(getRootElement(capabilitiesDoc), "Service");
const contactInformation = findChildElement(service, "ContactInformation");
const contactPersonPrimary = findChildElement(
contactInformation,
"ContactPersonPrimary"
);
const address = findChildElement(contactInformation, "ContactAddress");
return {
contact: {
name: getElementText(
findChildElement(contactPersonPrimary, "ContactPerson")
),
organization: getElementText(
findChildElement(contactPersonPrimary, "ContactOrganization")
),
position: getElementText(
findChildElement(contactInformation, "ContactPosition")
),
phone: getElementText(
findChildElement(contactInformation, "ContactVoiceTelephone")
),
fax: getElementText(
findChildElement(contactInformation, "ContactFacsimileTelephone")
),
address: {
deliveryPoint: getElementText(findChildElement(address, "Address")),
city: getElementText(findChildElement(address, "City")),
administrativeArea: getElementText(
findChildElement(address, "StateOrProvince")
),
postalCode: getElementText(findChildElement(address, "PostCode")),
country: getElementText(findChildElement(address, "Country"))
},
email: getElementText(
findChildElement(contactInformation, "ContactElectronicMailAddress")
)
}
};
}
export {

@@ -189,2 +316,3 @@ readExceptionFormatsFromCapabilities,

readLayersFromCapabilities,
readOperationUrlsFromCapabilities,
readOutputFormatsFromCapabilities,

@@ -191,0 +319,0 @@ readVersionFromCapabilities

@@ -1,2 +0,2 @@

import { BoundingBox, CrsCode, GenericEndpointInfo, MimeType } from '../shared/models.js';
import { BoundingBox, CrsCode, GenericEndpointInfo, type HttpMethod, MimeType, type OperationName } from '../shared/models.js';
import { WmsLayerFull, WmsLayerSummary, WmsVersion } from './model.js';

@@ -11,2 +11,3 @@ /**

private _layers;
private _url;
private _version;

@@ -69,3 +70,16 @@ /**

}): string;
/**
* Returns the Capabilities URL of the WMS
*
* This is the URL reported by the service if available, otherwise the URL
* passed to the constructor
*/
getCapabilitiesUrl(): string;
/**
* Returns the URL reported by the WMS for the given operation
* @param operationName e.g. GetMap, GetCapabilities, etc.
* @param method HTTP method
*/
getOperationUrl(operationName: OperationName, method?: HttpMethod): string;
}
//# sourceMappingURL=endpoint.d.ts.map

@@ -10,2 +10,3 @@ import { parseWmsCapabilities } from "../worker/index.js";

_layers;
_url;
_version;

@@ -26,5 +27,6 @@ /**

this._capabilitiesUrl
).then(({ info, layers, version }) => {
).then(({ info, layers, url: url2, version }) => {
this._info = info;
this._layers = layers;
this._url = url2;
this._version = version;

@@ -131,3 +133,3 @@ });

return generateGetMapUrl(
this._capabilitiesUrl,
this.getOperationUrl("GetMap") || this._capabilitiesUrl,
this._version,

@@ -143,2 +145,29 @@ layers.join(","),

}
/**
* Returns the Capabilities URL of the WMS
*
* This is the URL reported by the service if available, otherwise the URL
* passed to the constructor
*/
getCapabilitiesUrl() {
const baseUrl = this.getOperationUrl("GetCapabilities");
if (!baseUrl) {
return this._capabilitiesUrl;
}
return setQueryParams(baseUrl, {
SERVICE: "WMS",
REQUEST: "GetCapabilities"
});
}
/**
* Returns the URL reported by the WMS for the given operation
* @param operationName e.g. GetMap, GetCapabilities, etc.
* @param method HTTP method
*/
getOperationUrl(operationName, method = "Get") {
if (!this._url) {
return null;
}
return this._url[operationName]?.[method];
}
}

@@ -145,0 +174,0 @@ export {

@@ -1,2 +0,2 @@

import { BoundingBox, CrsCode, LayerStyle } from '../shared/models.js';
import { BoundingBox, CrsCode, LayerStyle, type MetadataURL } from '../shared/models.js';
export type WmsLayerAttribution = {

@@ -34,4 +34,7 @@ title?: string;

opaque: boolean;
maxScaleDenominator?: number;
minScaleDenominator?: number;
attribution?: WmsLayerAttribution;
keywords?: string[];
metadata?: MetadataURL[];
/**

@@ -38,0 +41,0 @@ * Not defined if the layer is a leaf in the tree

@@ -0,1 +1,2 @@

import { readProviderFromCapabilities } from "../shared/ows.js";
import {

@@ -44,2 +45,3 @@ findChildElement,

keywords,
provider: readProviderFromCapabilities(capabilitiesDoc),
getTileUrls

@@ -95,9 +97,19 @@ };

const contentsEl = findChildElement(rootEl, "Contents");
function getMatrixSetCrs(contentsEl2, identifier) {
const matrixSet = findChildrenElement(contentsEl2, "TileMatrixSet").find(
(matrixSetEl) => {
const identifierEl = findChildElement(matrixSetEl, "Identifier");
return getElementText(identifierEl) === identifier;
}
);
return getElementText(findChildElement(matrixSet, "SupportedCRS"));
}
function parseMatrixSetLink(element) {
const fullMatrixSet = findChildrenElement(contentsEl, "TileMatrixSet").find(
(el) => getElementText(findChildElement(el, "Identifier"))
const identifier = getElementText(
findChildElement(element, "TileMatrixSet")
);
const crs = getMatrixSetCrs(contentsEl, identifier);
return {
identifier: getElementText(findChildElement(element, "TileMatrixSet")),
crs: getElementText(findChildElement(fullMatrixSet, "SupportedCRS")),
identifier,
crs,
limits: findChildrenElement(element, "TileMatrixLimits", true).map(

@@ -104,0 +116,0 @@ (element2) => ({

import { MimeType } from '../shared/models.js';
import { WmtsLayerDimensionValue, WmtsLayerResourceLink, WmtsEndpointInfo, WmtsLayer, WmtsMatrixSet } from './model.js';
import { WmtsEndpointInfo, WmtsLayer, WmtsLayerDimensionValue, WmtsLayerResourceLink, WmtsMatrixSet } from './model.js';
import type WMTSTileGrid from 'ol/tilegrid/WMTS';

@@ -4,0 +4,0 @@ /**

@@ -172,5 +172,10 @@ import { setQueryParams } from "../shared/http-utils.js";

const matrixSet = this.getMatrixSetByIdentifier(matrixSetLink.identifier);
return this.tileGridModule.then(
({ buildOpenLayersTileGrid }) => buildOpenLayersTileGrid(matrixSet, matrixSetLink.limits)
);
return this.tileGridModule.then((olTileGridModule) => {
if (!olTileGridModule)
return null;
return olTileGridModule.buildOpenLayersTileGrid(
matrixSet,
matrixSetLink.limits
);
});
}

@@ -177,0 +182,0 @@ }

@@ -1,2 +0,2 @@

import { GenericEndpointInfo } from '../shared/models.js';
import { GenericEndpointInfo, OperationName, OperationUrl } from '../shared/models.js';
import { WmtsEndpointInfo, WmtsLayer, WmtsMatrixSet } from '../wmts/model.js';

@@ -15,2 +15,3 @@ import { WfsFeatureTypeFull, WfsFeatureTypeInternal, WfsFeatureTypePropsDetails, WfsVersion } from '../wfs/model.js';

version: WmsVersion;
url: Record<OperationName, OperationUrl>;
info: GenericEndpointInfo;

@@ -25,2 +26,3 @@ layers: WmsLayerFull[];

version: WfsVersion;
url: Record<OperationName, OperationUrl>;
info: GenericEndpointInfo;

@@ -27,0 +29,0 @@ featureTypes: WfsFeatureTypeInternal[];

@@ -10,3 +10,3 @@ type TaskParams = Record<string, unknown>;

requestId: number;
error?: unknown;
error?: Record<string, unknown>;
response?: TaskResponse;

@@ -13,0 +13,0 @@ };

import { getUniqueId } from "../shared/id.js";
import { decodeError, encodeError } from "../shared/errors.js";
function sendTaskRequest(taskName, workerInstance, params) {

@@ -27,3 +28,3 @@ return new Promise((resolve, reject) => {

if ("error" in response) {
reject(response.error);
reject(decodeError(response.error));
} else {

@@ -51,3 +52,3 @@ resolve(response.response);

} catch (e) {
error = e;
error = encodeError(e);
}

@@ -54,0 +55,0 @@ const message = (

import { addTaskHandler } from "./utils.js";
import { queryXmlDocument, setFetchOptions } from "../shared/http-utils.js";
import { check } from "../shared/errors.js";
import * as wmsCapabilities from "../wms/capabilities.js";

@@ -14,5 +15,6 @@ import * as wfsCapabilities from "../wfs/capabilities.js";

globalThis,
({ url }) => queryXmlDocument(url).then((xmlDoc) => ({
({ url }) => queryXmlDocument(url).then((xmlDoc) => check(xmlDoc, url)).then((xmlDoc) => ({
info: wmsCapabilities.readInfoFromCapabilities(xmlDoc),
layers: wmsCapabilities.readLayersFromCapabilities(xmlDoc),
url: wmsCapabilities.readOperationUrlsFromCapabilities(xmlDoc),
version: wmsCapabilities.readVersionFromCapabilities(xmlDoc)

@@ -24,5 +26,6 @@ }))

globalThis,
({ url }) => queryXmlDocument(url).then((xmlDoc) => ({
({ url }) => queryXmlDocument(url).then((xmlDoc) => check(xmlDoc, url)).then((xmlDoc) => ({
info: wfsCapabilities.readInfoFromCapabilities(xmlDoc),
featureTypes: wfsCapabilities.readFeatureTypesFromCapabilities(xmlDoc),
url: wfsCapabilities.readOperationUrlsFromCapabilities(xmlDoc),
version: wfsCapabilities.readVersionFromCapabilities(xmlDoc)

@@ -65,3 +68,3 @@ }))

globalThis,
({ url }) => queryXmlDocument(url).then((xmlDoc) => ({
({ url }) => queryXmlDocument(url).then((xmlDoc) => check(xmlDoc, url)).then((xmlDoc) => ({
info: wmtsCapabilities.readInfoFromCapabilities(xmlDoc),

@@ -68,0 +71,0 @@ layers: wmtsCapabilities.readLayersFromCapabilities(xmlDoc),

{
"name": "@camptocamp/ogc-client",
"version": "1.1.1-dev.ecb3450",
"description": "A pure JS library for interacting with geospatial services.",
"version": "1.2.0",
"description": "A Typescript library for interacting with geospatial services.",
"main": "./dist/dist-node.js",

@@ -6,0 +6,0 @@ "browser": "./dist/index.js",

@@ -13,2 +13,3 @@ export { default as WfsEndpoint } from './wfs/endpoint.js';

WfsFeatureTypeUniqueValue,
WfsGetFeatureOptions,
} from './wfs/model.js';

@@ -31,10 +32,16 @@ export { default as WmsEndpoint } from './wms/endpoint.js';

export type {
Address,
Contact,
Provider,
LayerStyle,
BoundingBox,
MetadataURL,
FetchOptions,
GenericEndpointInfo,
MimeType,
CrsCode,
} from './shared/models.js';
export { default as OgcApiEndpoint } from './ogc-api/endpoint.js';
export * from './ogc-api/model.js';
export { useCache } from './shared/cache.js';
export { useCache, clearCache } from './shared/cache.js';
export {

@@ -45,4 +52,9 @@ sharedFetch,

} from './shared/http-utils.js';
export {
check,
ServiceExceptionError,
EndpointError,
} from './shared/errors.js';
export { enableFallbackWithoutWorker } from './worker/index.js';
import './worker-fallback/index.js';

@@ -7,3 +7,3 @@ import {

parseBaseCollectionInfo,
parseFullStyleInfo,
parseBasicStyleInfo,
parseCollectionParameters,

@@ -13,3 +13,3 @@ parseCollections,

parseEndpointInfo,
parseBasicStyleInfo,
parseFullStyleInfo,
parseTileMatrixSets,

@@ -16,0 +16,0 @@ } from './info.js';

import {
_resetCache,
clearCache,
getCache,
purgeEntries,
purgeOutdatedEntries,
readCacheEntry,

@@ -25,3 +26,3 @@ setCacheExpiryDuration,

NOW = 50000;
await purgeEntries();
await purgeOutdatedEntries();
// set start time

@@ -112,2 +113,15 @@ NOW = 10000;

});
describe('clearCache', () => {
let result;
beforeEach(async () => {
await storeCacheEntry({ old: true }, 'test', 'entry', '03');
NOW = 10800;
await clearCache();
result = await useCache(factory, 'test', 'entry', '03');
});
it('do not use the cached function object', () => {
expect(factory).toHaveBeenCalled();
expect(result).toEqual({ fresh: true });
});
});
});

@@ -114,0 +128,0 @@

@@ -100,3 +100,3 @@ let cacheExpiryDuration = 1000 * 60 * 60; // 1 day

): Promise<T> {
await purgeEntries();
await purgeOutdatedEntries();
if (await hasValidCacheEntry(...keys)) {

@@ -122,3 +122,3 @@ return readCacheEntry(...keys);

*/
export async function purgeEntries() {
export async function purgeOutdatedEntries() {
const cache = await getCache();

@@ -133,1 +133,13 @@ if (!cache) return;

}
/**
* Remove all cache entries; will not prevent the creation of new ones
*/
export async function clearCache() {
const cache = await getCache();
if (!cache) return;
const keys = await cache.keys();
for (const key of keys) {
await cache.delete(key);
}
}

@@ -0,9 +1,156 @@

import type { XmlDocument, XmlElement } from '@rgrove/parse-xml';
import {
findChildElement,
getElementAttribute,
getElementName,
getElementText,
getRootElement,
stripNamespace,
} from '../shared/xml-utils.js';
export class EndpointError extends Error {
constructor(
public message: string,
public httpStatus?: number,
public isCrossOriginRelated?: boolean
message: string,
public readonly httpStatus?: number,
public readonly isCrossOriginRelated?: boolean
) {
super(message);
this.name = 'EndpointError';
}
}
/**
* Representation of an Exception reported by an OWS service
*
* This is usually contained in a ServiceExceptionReport or ExceptionReport
* document and represented as a ServiceException or Exception element
*/
export class ServiceExceptionError extends Error {
/**
* Constructor
* @param message Error message
* @param requestUrl URL which resulted in the ServiceException
* @param code Optional ServiceException code
* @param locator Optional ServiceException locator
* @param response Optional response content received
*/
public constructor(
message: string,
public readonly requestUrl?: string,
public readonly code?: string,
public readonly locator?: string,
public readonly response?: XmlDocument
) {
super(message);
this.name = 'ServiceExceptionError';
}
}
/**
* Parse a ServiceException element to a ServiceExceptionError
* @param serviceException ServiceException element
* @param url URL from which the ServiceException was generated
*/
export function parse(
serviceException: XmlElement,
url?: string
): ServiceExceptionError {
const errorCode =
getElementAttribute(serviceException, 'code') ||
getElementAttribute(serviceException, 'exceptionCode');
const errorLocator = getElementAttribute(serviceException, 'locator');
const textElement =
findChildElement(serviceException, 'ExceptionText') || serviceException;
const errorMessage = getElementText(textElement).trim();
return new ServiceExceptionError(
errorMessage,
url,
errorCode,
errorLocator,
serviceException.document
);
}
/**
* Check the response for a ServiceExceptionReport and if present throw one
* @param response Response to check
* @param url URL from which response was generated
*/
export function check(response: XmlDocument, url?: string): XmlDocument {
const rootEl = getRootElement(response);
const rootElName = stripNamespace(getElementName(rootEl));
if (rootElName === 'ServiceExceptionReport') {
// document contains a ServiceExceptionReport, so generate an Error from
// the first ServiceException contained in it
const error = findChildElement(rootEl, 'ServiceException');
if (error) {
throw parse(error, url);
}
}
if (rootElName === 'ExceptionReport') {
const error = findChildElement(rootEl, 'Exception');
if (error) {
throw parse(error, url);
}
}
// there was nothing to convert to an Error so just pass the document on
return response;
}
/**
* This transforms an error object into a JSON-serializable object to be
* transferred from a worker
*/
export function encodeError(error: Error): Record<string, unknown> {
const base = {
message: error.message,
stack: error.stack,
name: error.name,
};
if (error instanceof ServiceExceptionError) {
return {
...base,
code: error.code,
locator: error.locator,
response: error.response,
requestUrl: error.requestUrl,
};
}
if (error instanceof EndpointError) {
return {
...base,
httpStatus: error.httpStatus,
isCrossOriginRelated: error.isCrossOriginRelated,
};
}
return base;
}
/**
* Recreates an error object
*/
export function decodeError(error: Record<string, unknown>): Error {
if (error.name === 'ServiceExceptionError') {
const e = new ServiceExceptionError(
error.message as string,
error.requestUrl as string,
error.code as string,
error.locator as string,
error.response as XmlDocument
);
e.stack = error.stack as string;
return e;
}
if (error.name === 'EndpointError') {
const e = new EndpointError(
error.message as string,
error.httpStatus as number,
error.isCrossOriginRelated as boolean
);
e.stack = error.stack as string;
return e;
}
const e = new Error(error.message as string);
e.stack = error.stack as string;
return e;
}

@@ -104,3 +104,3 @@ import { parseXmlString } from './xml-utils.js';

)
.then(async (resp) => {
.then(async (resp: Response) => {
if (!resp.ok) {

@@ -107,0 +107,0 @@ const text = await resp.text();

@@ -8,2 +8,26 @@ /**

export interface Address {
deliveryPoint?: string;
city?: string;
administrativeArea?: string;
postalCode?: string;
country?: string;
}
export interface Contact {
name?: string;
organization?: string;
position?: string;
phone?: string;
fax?: string;
address?: Address;
email?: string;
}
export interface Provider {
name?: string;
site?: string;
contact?: Contact;
}
export type GenericEndpointInfo = {

@@ -16,2 +40,3 @@ name: string;

keywords: string[];
provider?: Provider;
/**

@@ -45,2 +70,6 @@ * Can contain the list of outputFormats from a WFS GetCapabilities,

export type OperationName = string;
export type HttpMethod = 'Get' | 'Post';
export type OperationUrl = Partial<Record<HttpMethod, string>>;
export interface LayerStyle {

@@ -55,1 +84,7 @@ name: string;

}
export type MetadataURL = {
format?: string;
type?: string;
url: string;
};

@@ -13,2 +13,3 @@ import { parseXmlString } from '../shared/xml-utils.js';

readInfoFromCapabilities,
readOperationUrlsFromCapabilities,
readVersionFromCapabilities,

@@ -44,2 +45,9 @@ } from './capabilities.js';

],
metadata: [
{
format: 'text/plain',
type: 'TC211',
url: 'https://www.pigma.org/geonetwork/?uuid=cbcae9a4-7fc0-4fc8-bd78-089af3af4e8a',
},
],
name: 'asp:asp_rpg2010',

@@ -64,2 +72,19 @@ otherCrs: ['EPSG:32615', 'EPSG:32616', 'EPSG:32617', 'EPSG:32618'],

],
metadata: [
{
format: 'text/html',
type: 'TC211',
url: 'https://www.pigma.org/geonetwork?uuid=4d840710-3f09-4f48-aa31-d2c4c0ee6fda',
},
{
format: 'text/html',
type: '19115',
url: 'https://www.pigma.org/geonetwork?uuid=4d840710-3f09-4f48-aa31-d2c4c0ee6fda',
},
{
format: 'text/xml',
type: '19115',
url: 'https://www.pigma.org/geonetwork/srv/fre/xml_iso19139?uuid=4d840710-3f09-4f48-aa31-d2c4c0ee6fda',
},
],
name: 'cd16:comptages_routiers_l',

@@ -86,2 +111,19 @@ otherCrs: ['EPSG:32615', 'EPSG:32616', 'EPSG:32617', 'EPSG:32618'],

name: 'cd16:hierarchisation_l',
metadata: [
{
format: 'text/html',
type: 'TC211',
url: 'https://www.pigma.org/geonetwork?uuid=cd27adaa-0ec5-4934-9374-143df09fb9f6',
},
{
format: 'text/html',
type: '19115',
url: 'https://www.pigma.org/geonetwork?uuid=cd27adaa-0ec5-4934-9374-143df09fb9f6',
},
{
format: 'text/xml',
type: '19115',
url: 'https://www.pigma.org/geonetwork/srv/fre/xml_iso19139?uuid=cd27adaa-0ec5-4934-9374-143df09fb9f6',
},
],
otherCrs: ['EPSG:32615', 'EPSG:32616', 'EPSG:32617', 'EPSG:32618'],

@@ -99,3 +141,9 @@ outputFormats: [

const doc = parseXmlString(capabilities200);
expect(readFeatureTypesFromCapabilities(doc)).toEqual(expectedTypes);
const typesWithoutMetadataUrlAttributes = expectedTypes.map((type) => ({
...type,
metadata: type.metadata.map((metadata) => ({ url: metadata.url })),
}));
expect(readFeatureTypesFromCapabilities(doc)).toEqual(
typesWithoutMetadataUrlAttributes
);
});

@@ -147,2 +195,16 @@ it('reads the feature types (1.1.0)', () => {

],
metadata: [
{
url: 'https://www.geo2france.fr/geonetwork/srv/fre/catalog.search#/metadata/facf3747-bc19-44c7-9fd8-1f765d99c059',
},
{
url: 'https://www.geo2france.fr/geonetwork/srv/fre/catalog.search#/metadata/facf3747-bc19-44c7-9fd8-1f765d99c059',
},
{
url: 'https://www.geo2france.fr/geonetwork/srv/api/records/facf3747-bc19-44c7-9fd8-1f765d99c059/formatters/xml',
},
{
url: 'https://www.geo2france.fr/geonetwork/srv/api/records/facf3747-bc19-44c7-9fd8-1f765d99c059/formatters/xml',
},
],
name: 'cr_hdf:domaine_public_hdf_com',

@@ -186,2 +248,20 @@ otherCrs: [],

};
const provider = {
name: 'GIP ATGeRi',
site: '',
contact: {
name: 'PIGMA',
position: '',
phone: '05.57.85.40.42',
fax: '',
address: {
deliveryPoint: '',
city: 'Bordeaux',
administrativeArea: '',
postalCode: '33075',
country: '',
},
email: 'admin.pigma@gipatgeri.fr',
},
};
it('reads the service info (2.0.0)', () => {

@@ -191,2 +271,3 @@ const doc = parseXmlString(capabilities200);

...expectedInfo,
provider,
outputFormats: [

@@ -219,2 +300,3 @@ 'application/gml+xml; version=3.2',

...expectedInfo,
provider,
outputFormats: [

@@ -262,2 +344,118 @@ 'text/xml; subtype=gml/3.1.1',

});
describe('readOperationUrlsFromCapabilities', () => {
it('reads the operation URLs (2.0.0)', () => {
const doc = parseXmlString(capabilities200);
const expectedUrls = {
GetCapabilities: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
DescribeFeatureType: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
GetFeature: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
GetPropertyValue: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
ListStoredQueries: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
DescribeStoredQueries: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
CreateStoredQuery: {
Post: 'https://www.pigma.org/geoserver/wfs',
},
DropStoredQuery: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
LockFeature: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
GetFeatureWithLock: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
Transaction: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
};
expect(readOperationUrlsFromCapabilities(doc)).toEqual(expectedUrls);
});
it('reads the operation URLs (1.1.0)', () => {
const doc = parseXmlString(capabilities110);
const expectedUrls = {
GetCapabilities: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
DescribeFeatureType: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
GetFeature: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
GetGmlObject: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
LockFeature: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
GetFeatureWithLock: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
Transaction: {
Get: 'https://www.pigma.org/geoserver/wfs',
Post: 'https://www.pigma.org/geoserver/wfs',
},
};
expect(readOperationUrlsFromCapabilities(doc)).toEqual(expectedUrls);
});
it('reads the operation URLs (1.0.0)', () => {
const doc = parseXmlString(capabilities100);
const expectedUrls = {
GetCapabilities: {
Get: 'https://www.pigma.org/geoserver/wfs?request=GetCapabilities',
Post: 'https://www.pigma.org/geoserver/wfs',
},
DescribeFeatureType: {
Get: 'https://www.pigma.org/geoserver/wfs?request=DescribeFeatureType',
Post: 'https://www.pigma.org/geoserver/wfs',
},
GetFeature: {
Get: 'https://www.pigma.org/geoserver/wfs?request=GetFeature',
Post: 'https://www.pigma.org/geoserver/wfs',
},
Transaction: {
Get: 'https://www.pigma.org/geoserver/wfs?request=Transaction',
Post: 'https://www.pigma.org/geoserver/wfs',
},
LockFeature: {
Get: 'https://www.pigma.org/geoserver/wfs?request=LockFeature',
Post: 'https://www.pigma.org/geoserver/wfs',
},
GetFeatureWithLock: {
Get: 'https://www.pigma.org/geoserver/wfs?request=GetFeatureWithLock',
Post: 'https://www.pigma.org/geoserver/wfs',
},
};
expect(readOperationUrlsFromCapabilities(doc)).toEqual(expectedUrls);
});
});
});

@@ -0,1 +1,2 @@

import { readProviderFromCapabilities } from '../shared/ows.js';
import {

@@ -9,2 +10,3 @@ findChildElement,

getRootElement,
stripNamespace,
} from '../shared/xml-utils.js';

@@ -17,2 +19,4 @@ import { simplifyEpsgUrn } from '../shared/crs-utils.js';

MimeType,
type OperationName,
type OperationUrl,
} from '../shared/models.js';

@@ -22,2 +26,35 @@ import { WfsFeatureTypeInternal, WfsVersion } from './model.js';

/**
* Will read the operation URLS from the capabilities doc
* @param capabilitiesDoc Capabilities document
*/
export function readOperationUrlsFromCapabilities(
capabilitiesDoc: XmlDocument
): Record<OperationName, OperationUrl> {
const urls: Record<OperationName, OperationUrl> = {};
const capabilities = getRootElement(capabilitiesDoc);
const operationsMetadata = findChildElement(
capabilities,
'OperationsMetadata'
);
if (operationsMetadata) {
// WFS 1.1.0 or 2.0.0
findChildrenElement(operationsMetadata, 'Operation').forEach(
(operation) => {
const name = getElementAttribute(operation, 'name');
urls[name] = parseOperation110(operation);
}
);
} else {
// WFS 1.0.0
const capability = findChildElement(capabilities, 'Capability');
const request = findChildElement(capability, 'Request');
getChildrenElement(request).forEach((operation) => {
const name = stripNamespace(getElementName(operation));
urls[name] = parseOperation100(operation);
});
}
return urls;
}
/**
* Will read a WFS version from the capabilities doc

@@ -97,2 +134,7 @@ * @param capabilitiesDoc Capabilities document

}
let provider;
// no provider information defined in capabilities for WFS 1.0.0
if (version !== '1.0.0') {
provider = readProviderFromCapabilities(capabilitiesDoc);
}

@@ -106,2 +148,3 @@ return {

keywords,
provider,
outputFormats: readOutputFormatsFromCapabilities(capabilitiesDoc),

@@ -129,2 +172,34 @@ };

/**
* Parse an operation definition from a WFS 1.0.0 capabilities (e.g. GetFeature)
* @param operation Operation element
*/
function parseOperation100(operation: XmlElement): OperationUrl {
const urls: OperationUrl = {};
const dcpType = findChildrenElement(operation, 'DCPType');
const http = dcpType.flatMap((d) => findChildrenElement(d, 'HTTP'));
const methods = http.flatMap((h) => getChildrenElement(h));
methods.forEach((method) => {
const methodName = stripNamespace(getElementName(method));
urls[methodName] = getElementAttribute(method, 'onlineResource');
});
return urls;
}
/**
* Parse an operation definition from a WFS 1.1+ capabilities (e.g. GetFeature)
* @param operation Operation element
*/
function parseOperation110(operation: XmlElement): OperationUrl {
const urls: OperationUrl = {};
const dcpType = findChildrenElement(operation, 'DCP');
const http = dcpType.flatMap((d) => findChildElement(d, 'HTTP'));
const methods = http.flatMap((h) => getChildrenElement(h));
methods.forEach((method) => {
const methodName = stripNamespace(getElementName(method));
urls[methodName] = getElementAttribute(method, 'xlink:href');
});
return urls;
}
/**
* Parse a feature type in a capabilities doc

@@ -177,2 +252,18 @@ */

.filter((v, i, arr) => arr.indexOf(v) === i);
const metadata =
serviceVersion === '2.0.0'
? findChildrenElement(featureTypeEl, 'MetadataURL').map(
(metadataUrlEl) => ({
url: getElementAttribute(metadataUrlEl, 'xlink:href'),
})
)
: findChildrenElement(featureTypeEl, 'MetadataURL').map(
(metadataUrlEl) => ({
format: getElementAttribute(metadataUrlEl, 'format'),
type: getElementAttribute(metadataUrlEl, 'type'),
url: getElementText(metadataUrlEl).trim(),
})
);
return {

@@ -192,3 +283,4 @@ name: getElementText(findChildElement(featureTypeEl, 'Name')),

keywords: keywords,
...(metadata.length && { metadata }),
};
}

@@ -11,4 +11,7 @@ // @ts-expect-error ts-migrate(7016)

import capabilitiesStates from '../../fixtures/wfs/capabilities-states-2-0-0.xml';
// @ts-expect-error ts-migrate(7016)
import exceptionReportWms from '../../fixtures/wfs/exception-report-wms.xml';
import WfsEndpoint from './endpoint.js';
import { useCache } from '../shared/cache.js';
import { EndpointError, ServiceExceptionError } from '../shared/errors.js';

@@ -30,2 +33,3 @@ jest.mock('../shared/cache', () => ({

beforeEach(() => {
global.fetchPreHandler = () => {};
global.fetchResponseFactory = (url) => {

@@ -76,2 +80,87 @@ if (url.indexOf('GetCapabilities') > -1) return capabilities200;

});
describe('CORS error handling', () => {
beforeEach(() => {
global.fetchPreHandler = (url, options) => {
if (options?.method === 'HEAD') return 'ok!';
throw new Error('CORS problem');
};
endpoint = new WfsEndpoint('https://my.test.service/ogc/wfs');
});
it('rejects with a relevant error', async () => {
const error = (await endpoint
.isReady()
.catch((e) => e)) as EndpointError;
expect(error).toBeInstanceOf(EndpointError);
expect(error.message).toBe(
'The document could not be fetched due to CORS limitations'
);
expect(error.httpStatus).toBe(0);
expect(error.isCrossOriginRelated).toBe(true);
});
});
describe('endpoint error handling', () => {
beforeEach(() => {
global.fetchPreHandler = () => {
throw new TypeError('other kind of problem');
};
endpoint = new WfsEndpoint('https://my.test.service/ogc/wfs');
});
it('rejects with a relevant error', async () => {
const error = (await endpoint
.isReady()
.catch((e) => e)) as EndpointError;
expect(error).toBeInstanceOf(EndpointError);
expect(error.message).toBe(
'Fetching the document failed either due to network errors or unreachable host, error is: other kind of problem'
);
expect(error.httpStatus).toBe(0);
expect(error.isCrossOriginRelated).toBe(false);
});
});
describe('http error handling', () => {
beforeEach(() => {
global.fetchPreHandler = () => ({
ok: false,
text: () => Promise.resolve('something broke in the server'),
status: 500,
statusText: 'Internal Server Error',
});
endpoint = new WfsEndpoint('https://my.test.service/ogc/wfs');
});
it('rejects with a relevant error', async () => {
const error = (await endpoint
.isReady()
.catch((e) => e)) as EndpointError;
expect(error).toBeInstanceOf(EndpointError);
expect(error.message).toBe(
'Received an error with code 500: something broke in the server'
);
expect(error.httpStatus).toBe(500);
expect(error.isCrossOriginRelated).toBe(false);
});
});
describe('service exception handling', () => {
beforeEach(() => {
global.fetchResponseFactory = () => exceptionReportWms;
endpoint = new WfsEndpoint('https://my.test.service/ogc/wfs');
});
it('rejects when the endpoint returns an exception report', async () => {
const error = (await endpoint
.isReady()
.catch((e) => e)) as ServiceExceptionError;
expect(error).toBeInstanceOf(ServiceExceptionError);
expect(error.message).toBe(
'msWFSDispatch(): WFS server error. WFS request not enabled. Check wfs/ows_enable_request settings.'
);
expect(error.requestUrl).toBe(
'https://my.test.service/ogc/wfs?SERVICE=WFS&REQUEST=GetCapabilities'
);
expect(error.code).toBe('InvalidParameterValue');
expect(error.locator).toBe('request');
});
});
});

@@ -138,2 +227,13 @@

keywords: ['features', 'hierarchisation_l'],
metadata: [
{
url: 'https://www.pigma.org/geonetwork?uuid=cd27adaa-0ec5-4934-9374-143df09fb9f6',
},
{
url: 'https://www.pigma.org/geonetwork?uuid=cd27adaa-0ec5-4934-9374-143df09fb9f6',
},
{
url: 'https://www.pigma.org/geonetwork/srv/fre/xml_iso19139?uuid=cd27adaa-0ec5-4934-9374-143df09fb9f6',
},
],
otherCrs: ['EPSG:32615', 'EPSG:32616', 'EPSG:32617', 'EPSG:32618'],

@@ -179,2 +279,13 @@ outputFormats: [

keywords: ['features', 'hierarchisation_l'],
metadata: [
{
url: 'https://www.pigma.org/geonetwork?uuid=cd27adaa-0ec5-4934-9374-143df09fb9f6',
},
{
url: 'https://www.pigma.org/geonetwork?uuid=cd27adaa-0ec5-4934-9374-143df09fb9f6',
},
{
url: 'https://www.pigma.org/geonetwork/srv/fre/xml_iso19139?uuid=cd27adaa-0ec5-4934-9374-143df09fb9f6',
},
],
otherCrs: ['EPSG:32615', 'EPSG:32616', 'EPSG:32617', 'EPSG:32618'],

@@ -234,2 +345,20 @@ outputFormats: [

keywords: ['WFS', 'WMS', 'GEOSERVER'],
provider: {
name: 'GIP ATGeRi',
site: '',
contact: {
name: 'PIGMA',
position: '',
phone: '05.57.85.40.42',
fax: '',
address: {
deliveryPoint: '',
city: 'Bordeaux',
administrativeArea: '',
postalCode: '33075',
country: '',
},
email: 'admin.pigma@gipatgeri.fr',
},
},
outputFormats: [

@@ -454,3 +583,3 @@ 'application/gml+xml; version=3.2',

).toEqual(
'https://my.test.service/ogc/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=cd16%3Ahierarchisation_l&OUTPUTFORMAT=application%2Fgml%2Bxml%3B+version%3D3.2&COUNT=200'
'https://www.pigma.org/geoserver/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=cd16%3Ahierarchisation_l&OUTPUTFORMAT=application%2Fgml%2Bxml%3B+version%3D3.2&COUNT=200'
);

@@ -464,3 +593,3 @@ });

).toEqual(
'https://my.test.service/ogc/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=cd16%3Acomptages_routiers_l&OUTPUTFORMAT=application%2Fjson'
'https://www.pigma.org/geoserver/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=cd16%3Acomptages_routiers_l&OUTPUTFORMAT=application%2Fjson'
);

@@ -475,3 +604,3 @@ });

).toEqual(
'https://my.test.service/ogc/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=cd16%3Ahierarchisation_l&SRSNAME=EPSG%3A2154&BBOX=1%2C2%2C3%2C4'
'https://www.pigma.org/geoserver/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=cd16%3Ahierarchisation_l&SRSNAME=EPSG%3A2154&BBOX=1%2C2%2C3%2C4'
);

@@ -500,2 +629,20 @@ });

});
it('returns a GetFeature url with the desired attributes', () => {
expect(
endpoint.getFeatureUrl('hierarchisation_l', {
attributes: ['field1', 'field2'],
})
).toEqual(
'https://www.pigma.org/geoserver/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=cd16%3Ahierarchisation_l&PROPERTYNAME=field1%2Cfield2'
);
});
it('returns a GetFeature url with only the hit count', () => {
expect(
endpoint.getFeatureUrl('hierarchisation_l', {
hitsOnly: true,
})
).toEqual(
'https://www.pigma.org/geoserver/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=cd16%3Ahierarchisation_l&RESULTTYPE=hits&COUNT=1'
);
});
});

@@ -526,2 +673,37 @@

});
describe('#getCapabilitiesUrl', () => {
it.skip('returns the URL used for the request before the capabilities are retrieved', async () => {
expect(endpoint.getCapabilitiesUrl()).toBe(
'https://my.test.service/ogc/wms?aa=bb&SERVICE=WMS&REQUEST=GetCapabilities'
);
await endpoint.isReady();
});
it('returns the self-reported URL after the capabilities are retrieved', async () => {
await endpoint.isReady();
expect(endpoint.getCapabilitiesUrl()).toBe(
'https://www.pigma.org/geoserver/wfs?SERVICE=WMS&REQUEST=GetCapabilities'
);
});
});
describe('#getOperationUrl', () => {
it.skip('returns NULL before the document is loaded', async () => {
expect(endpoint.getOperationUrl('GetMap')).toBeNull();
await endpoint.isReady();
});
it('returns undefined for a non-existant operation', async () => {
await endpoint.isReady();
expect(endpoint.getOperationUrl('foo')).toBeUndefined();
});
it('returns the correct URL for an existant operation', async () => {
await endpoint.isReady();
expect(endpoint.getOperationUrl('GetFeature')).toBe(
'https://www.pigma.org/geoserver/wfs'
);
});
});
});

@@ -14,6 +14,6 @@ import {

import {
BoundingBox,
CrsCode,
GenericEndpointInfo,
MimeType,
type HttpMethod,
type OperationName,
type OperationUrl,
} from '../shared/models.js';

@@ -24,2 +24,3 @@ import {

WfsFeatureTypeSummary,
WfsGetFeatureOptions,
WfsVersion,

@@ -37,2 +38,3 @@ } from './model.js';

private _featureTypes: WfsFeatureTypeInternal[] | null;
private _url: Record<OperationName, OperationUrl>;
private _version: WfsVersion | null;

@@ -59,5 +61,6 @@

this._capabilitiesUrl
).then(({ info, featureTypes, version }) => {
).then(({ info, featureTypes, url, version }) => {
this._info = info;
this._featureTypes = featureTypes;
this._url = url;
this._version = version;

@@ -132,2 +135,3 @@ });

keywords: featureType.keywords,
...('metadata' in featureType && { metadata: featureType.metadata }),
} as WfsFeatureTypeSummary;

@@ -149,3 +153,3 @@ }

const describeUrl = generateDescribeFeatureTypeUrl(
this._capabilitiesUrl,
this.getOperationUrl('DescribeFeatureType'),
this._version,

@@ -155,3 +159,3 @@ name

const getFeatureUrl = generateGetFeatureUrl(
this._capabilitiesUrl,
this.getOperationUrl('GetFeature'),
this._version,

@@ -257,24 +261,6 @@ name,

* @param featureType
* @param {Object} [options]
* @property [options.maxFeatures] no limit if undefined
* @property [options.asJson] if true, will ask for GeoJSON; will throw if the service does not support it
* @property [options.outputFormat] a supported output format (overridden by `asJson`)
* @property [options.outputCrs] if unspecified, this will be the data native projection
* @property [options.extent] an extent to restrict returned objects
* @property [options.extentCrs] if unspecified, `extent` should be in the data native projection
* @property [options.startIndex] if the service supports it, this will be the index of the first feature to return
* @param options
* @returns Returns null if endpoint is not ready
*/
getFeatureUrl(
featureType: string,
options: {
maxFeatures?: number;
asJson?: boolean;
outputFormat?: MimeType;
outputCrs?: CrsCode;
extent?: BoundingBox;
extentCrs?: CrsCode;
startIndex?: number;
}
) {
getFeatureUrl(featureType: string, options?: WfsGetFeatureOptions) {
if (!this._featureTypes) {

@@ -291,2 +277,4 @@ return null;

startIndex,
attributes,
hitsOnly,
} = options || {};

@@ -317,3 +305,3 @@ const internalFeatureType = this._getFeatureTypeByName(featureType);

return generateGetFeatureUrl(
this._capabilitiesUrl,
this.getOperationUrl('GetFeature'),
this._version,

@@ -323,4 +311,4 @@ internalFeatureType.name,

maxFeatures,
undefined,
undefined,
attributes,
hitsOnly,
outputCrs,

@@ -332,2 +320,31 @@ extent,

}
/**
* Returns the Capabilities URL of the WMS
*
* This is the URL reported by the service if available, otherwise the URL
* passed to the constructor
*/
getCapabilitiesUrl() {
const baseUrl = this.getOperationUrl('GetCapabilities');
if (!baseUrl) {
return this._capabilitiesUrl;
}
return setQueryParams(baseUrl, {
SERVICE: 'WMS',
REQUEST: 'GetCapabilities',
});
}
/**
* Returns the URL reported by the WFS for the given operation
* @param operationName e.g. GetFeature, GetCapabilities, etc.
* @param method HTTP method
*/
getOperationUrl(operationName: OperationName, method: HttpMethod = 'Get') {
if (!this._url) {
return null;
}
return this._url[operationName]?.[method];
}
}

@@ -34,2 +34,3 @@ import {

keywords,
metadata,
} = featureType;

@@ -51,3 +52,3 @@

const properties = typeElementsEls
.filter((el) => getElementAttribute(el, 'type').startsWith('xsd:'))
.filter((el) => /^xsd:|^xs:/.test(getElementAttribute(el, 'type')))
.reduce(

@@ -84,2 +85,3 @@ (prev, curr) => ({

...(keywords && { keywords }),
...(metadata && { metadata }),
};

@@ -86,0 +88,0 @@ }

@@ -1,2 +0,7 @@

import { BoundingBox, CrsCode, MimeType } from '../shared/models.js';
import {
BoundingBox,
CrsCode,
MetadataURL,
MimeType,
} from '../shared/models.js';

@@ -14,2 +19,3 @@ export type WfsVersion = '1.0.0' | '1.1.0' | '2.0.0';

keywords?: string[];
metadata?: MetadataURL[];
};

@@ -50,2 +56,3 @@

keywords?: string[];
metadata?: MetadataURL[];
};

@@ -107,1 +114,42 @@

>;
export type WfsGetFeatureOptions = {
/**
* No limit if undefined
*/
maxFeatures?: number;
/**
* if true, will ask for GeoJSON; will throw if the service does not support it
*/
asJson?: boolean;
/**
* a supported output format (overridden by `asJson`)
*/
outputFormat?: MimeType;
/**
* if unspecified, this will be the data native projection
*/
outputCrs?: CrsCode;
/**
* an extent to restrict returned objects
*/
extent?: BoundingBox;
/**
* if unspecified, `extent` should be in the data native projection
*/
extentCrs?: CrsCode;
/**
* if the service supports it, this will be the index of the first feature to return
*/
startIndex?: number;
/**
* if not defined, all attributes will be included; note that the fact
* that these attributes exist or not will not be checked!
*/
attributes?: string[];
/**
* if true, will not return feature data, only hit count
* note: this might not work for WFS version < 2
*/
hitsOnly?: boolean;
};
import {
readInfoFromCapabilities,
readLayersFromCapabilities,
readOperationUrlsFromCapabilities,
readVersionFromCapabilities,

@@ -11,2 +12,3 @@ } from './capabilities.js';

import { parseXmlString } from '../shared/xml-utils.js';
import type { WmsLayerFull } from './model.js';

@@ -136,2 +138,11 @@ describe('WMS capabilities', () => {

keywords: ['Geologie', 'INSPIRE:Geology', 'Geology'],
maxScaleDenominator: 1e7,
minScaleDenominator: 200000,
metadata: [
{
format: 'text/xml',
type: 'TC211',
url: 'http://www.geocatalogue.fr/api-public/servicesRest?Service=CSW&Request=GetRecordById&Version=2.0.2&id=BR_CAR_ADA&outputSchema=http://www.isotc211.org/2005/gmd&elementSetName=full',
},
],
name: 'SCAN_F_GEOL1M',

@@ -190,3 +201,12 @@ queryable: false,

keywords: ['Geologie', 'INSPIRE:Geology', 'Geology'],
maxScaleDenominator: 500000,
minScaleDenominator: 80000,
name: 'SCAN_F_GEOL250',
metadata: [
{
format: 'text/xml',
type: 'TC211',
url: 'http://www.geocatalogue.fr/api-public/servicesRest?Service=CSW&Request=GetRecordById&Version=2.0.2&id=BR_CAR_ACA&outputSchema=http://www.isotc211.org/2005/gmd&elementSetName=full',
},
],
queryable: true,

@@ -231,3 +251,12 @@ opaque: true,

keywords: ['Geologie', 'INSPIRE:Geology', 'Geology'],
maxScaleDenominator: 251000,
minScaleDenominator: 9000,
name: 'SCAN_D_GEOL50',
metadata: [
{
format: 'text/xml',
type: 'TC211',
url: 'http://www.geocatalogue.fr/api-public/servicesRest?Service=CSW&Request=GetRecordById&Version=2.0.2&id=72cc8d40-1bb6-41a3-8376-9734f23336ff&outputSchema=http://www.isotc211.org/2005/gmd&elementSetName=full',
},
],
queryable: true,

@@ -237,2 +266,45 @@ opaque: true,

title: 'Carte géologique image de la France au 1/50 000e',
children: [
{
abstract: '',
attribution,
availableCrs: [
'EPSG:4326',
'EPSG:3857',
'CRS:84',
'EPSG:32620',
'EPSG:32621',
],
boundingBoxes: {
'CRS:84': ['-12.2064', '40.681', '11.894', '52.1672'],
'EPSG:32620': [
'3.88148e+06',
'6.13796e+06',
'6.31307e+06',
'8.70752e+06',
],
'EPSG:32621': [
'3.52434e+06',
'5.74736e+06',
'5.97375e+06',
'8.23867e+06',
],
'EPSG:3857': [
'-1.35881e+06',
'4.96541e+06',
'1.32403e+06',
'6.83041e+06',
],
'EPSG:4326': ['-12.2064', '40.681', '11.894', '52.1672'],
},
keywords: [],
maxScaleDenominator: 251000,
minScaleDenominator: 9000,
name: 'INHERIT_SCALE',
queryable: false,
opaque: false,
styles,
title: 'Inherited scale denominators',
},
],
},

@@ -283,3 +355,5 @@ {

const doc = parseXmlString(capabilities111);
expect(readLayersFromCapabilities(doc)).toEqual(expectedLayers);
expect(
readLayersFromCapabilities(doc).map(fixupScaleDenominators)
).toEqual(expectedLayers);
});

@@ -319,2 +393,19 @@ });

],
provider: {
contact: {
name: 'Support BRGM',
organization: 'BRGM',
position: 'pointOfContact',
phone: '+33(0)2 38 64 34 34',
fax: '+33(0)2 38 64 35 18',
address: {
deliveryPoint: '3, Avenue Claude Guillemin, BP36009',
city: 'Orléans',
administrativeArea: 'Centre',
postalCode: '45060',
country: 'France',
},
email: 'contact-brgm@brgm.fr',
},
},
};

@@ -338,2 +429,56 @@

});
describe('readOperationUrlsFromCapabilities', () => {
const expectedUrls = {
GetCapabilities: {
Get: 'http://geoservices.brgm.fr/geologie?language=fre&',
Post: 'http://geoservices.brgm.fr/geologie?language=fre&',
},
GetMap: {
Get: 'http://geoservices.brgm.fr/geologie?language=fre&',
Post: 'http://geoservices.brgm.fr/geologie?language=fre&',
},
GetFeatureInfo: {
Get: 'http://geoservices.brgm.fr/geologie?language=fre&',
Post: 'http://geoservices.brgm.fr/geologie?language=fre&',
},
DescribeLayer: {
Get: 'http://geoservices.brgm.fr/geologie?language=fre&',
Post: 'http://geoservices.brgm.fr/geologie?language=fre&',
},
GetLegendGraphic: {
Get: 'http://geoservices.brgm.fr/geologie?language=fre&',
Post: 'http://geoservices.brgm.fr/geologie?language=fre&',
},
GetStyles: {
Get: 'http://geoservices.brgm.fr/geologie?language=fre&',
Post: 'http://geoservices.brgm.fr/geologie?language=fre&',
},
};
it('reads the operations URLs (1.3.0)', () => {
const doc = parseXmlString(capabilities130);
expect(readOperationUrlsFromCapabilities(doc)).toEqual(expectedUrls);
});
it('reads the operations URLs (1.1.1)', () => {
const doc = parseXmlString(capabilities111);
expect(readOperationUrlsFromCapabilities(doc)).toEqual(expectedUrls);
});
});
});
/**
* Round scale denominators to avoid problems with floating point precision
* @param layer
*/
function fixupScaleDenominators(layer: WmsLayerFull): WmsLayerFull {
if (layer.minScaleDenominator !== undefined) {
layer.minScaleDenominator = Math.round(layer.minScaleDenominator);
}
if (layer.maxScaleDenominator !== undefined) {
layer.maxScaleDenominator = Math.round(layer.maxScaleDenominator);
}
layer.children?.forEach(fixupScaleDenominators);
return layer;
}

@@ -0,19 +1,46 @@

import { XmlDocument, XmlElement } from '@rgrove/parse-xml';
import { hasInvertedCoordinates } from '../shared/crs-utils.js';
import {
BoundingBox,
CrsCode,
GenericEndpointInfo,
LayerStyle,
type Provider,
type OperationName,
type OperationUrl,
} from '../shared/models.js';
import {
findChildElement,
findChildrenElement,
getChildrenElement,
getElementAttribute,
getElementName,
getElementText,
getRootElement,
stripNamespace,
} from '../shared/xml-utils.js';
import { hasInvertedCoordinates } from '../shared/crs-utils.js';
import { XmlDocument, XmlElement } from '@rgrove/parse-xml';
import {
BoundingBox,
CrsCode,
GenericEndpointInfo,
LayerStyle,
} from '../shared/models.js';
import { WmsLayerAttribution, WmsLayerFull, WmsVersion } from './model.js';
/**
* Will read all operation URLs from the capabilities doc
* @param capabilitiesDoc Capabilities document
* @return The parsed operations URLs
*/
export function readOperationUrlsFromCapabilities(
capabilitiesDoc: XmlDocument
) {
const urls: Record<OperationName, OperationUrl> = {};
const capability = findChildElement(
getRootElement(capabilitiesDoc),
'Capability'
);
const request = findChildElement(capability, 'Request');
getChildrenElement(request).forEach((operation) => {
const operationName = stripNamespace(getElementName(operation));
urls[operationName] = parseOperation(operation);
});
return urls;
}
/**
* Will read a WMS version from the capabilities doc

@@ -113,2 +140,3 @@ * @param capabilitiesDoc Capabilities document

.filter((v, i, arr) => arr.indexOf(v) === i);
const provider = readProviderFromCapabilities(capabilitiesDoc);

@@ -124,2 +152,3 @@ return {

constraints: getElementText(findChildElement(service, 'AccessConstraints')),
provider,
keywords,

@@ -130,2 +159,19 @@ };

/**
* Parse an operation definition from a WMS capabilities (e.g. GetMap)
* @param operation Operation element
*/
function parseOperation(operation: XmlElement): OperationUrl {
const urls: OperationUrl = {};
const dcpType = findChildrenElement(operation, 'DCPType');
const http = dcpType.flatMap((d) => findChildElement(d, 'HTTP'));
const methods = http.flatMap((h) => getChildrenElement(h));
methods.forEach((method) => {
const onlineResource = findChildElement(method, 'OnlineResource');
const methodName = stripNamespace(getElementName(method));
urls[methodName] = getElementAttribute(onlineResource, 'xlink:href');
});
return urls;
}
/**
* Parse a layer in a capabilities doc

@@ -139,3 +185,5 @@ */

inheritedAttribution: WmsLayerAttribution = null,
inheritedBoundingBoxes: Record<CrsCode, BoundingBox> = null
inheritedBoundingBoxes: Record<CrsCode, BoundingBox> = null,
inheritedMaxScaleDenom: number = null,
inheritedMinScaleDenom: number = null
): WmsLayerFull {

@@ -170,2 +218,26 @@ const srsTag = version === '1.3.0' ? 'CRS' : 'SRS';

}
function parseScaleHintValue(textValue, defaultValue) {
if (textValue === '') {
return defaultValue;
}
// convert resolution to scale denominator using the common pixel size of
// 0.28×0.28 mm as defined in WMS 1.3.0 specification section 7.2.4.6.9
return Math.sqrt(0.5 * parseFloat(textValue) ** 2) / 0.00028;
}
function parseScaleHint() {
const scaleHint = findChildElement(layerEl, 'ScaleHint');
if (!scaleHint) {
return [inheritedMinScaleDenom, inheritedMaxScaleDenom];
}
const min = getElementAttribute(scaleHint, 'min');
const max = getElementAttribute(scaleHint, 'max');
return [
parseScaleHintValue(min, inheritedMinScaleDenom),
parseScaleHintValue(max, inheritedMaxScaleDenom),
];
}
function parseScaleDenominator(name, inheritedValue) {
const textValue = getElementText(findChildElement(layerEl, name));
return textValue === '' ? inheritedValue : parseFloat(textValue);
}
const attributionEl = findChildElement(layerEl, 'Attribution');

@@ -216,4 +288,38 @@ const attribution =

let minScaleDenominator, maxScaleDenominator;
if (version === '1.3.0') {
minScaleDenominator = parseScaleDenominator(
'MinScaleDenominator',
inheritedMinScaleDenom
);
maxScaleDenominator = parseScaleDenominator(
'MaxScaleDenominator',
inheritedMaxScaleDenom
);
} else {
[minScaleDenominator, maxScaleDenominator] = parseScaleHint();
}
const metadata = findChildrenElement(layerEl, 'MetadataURL').map(
(metadataUrlEl) => ({
type: getElementAttribute(metadataUrlEl, 'type'),
format: getElementText(findChildElement(metadataUrlEl, 'Format')),
url: getElementAttribute(
findChildElement(metadataUrlEl, 'OnlineResource'),
'xlink:href'
),
})
);
const children = findChildrenElement(layerEl, 'Layer').map((layer) =>
parseLayer(layer, version, availableCrs, styles, attribution, boundingBoxes)
parseLayer(
layer,
version,
availableCrs,
styles,
attribution,
boundingBoxes,
maxScaleDenominator,
minScaleDenominator
)
);

@@ -231,2 +337,5 @@ return {

opaque,
...(minScaleDenominator !== null ? { minScaleDenominator } : {}),
...(maxScaleDenominator !== null ? { maxScaleDenominator } : {}),
...(metadata.length && { metadata }),
...(children.length && { children }),

@@ -269,1 +378,46 @@ };

}
/**
* Read provider information from capabilities
* @param capabilitiesDoc
*/
function readProviderFromCapabilities(capabilitiesDoc: XmlDocument): Provider {
const service = findChildElement(getRootElement(capabilitiesDoc), 'Service');
const contactInformation = findChildElement(service, 'ContactInformation');
const contactPersonPrimary = findChildElement(
contactInformation,
'ContactPersonPrimary'
);
const address = findChildElement(contactInformation, 'ContactAddress');
return {
contact: {
name: getElementText(
findChildElement(contactPersonPrimary, 'ContactPerson')
),
organization: getElementText(
findChildElement(contactPersonPrimary, 'ContactOrganization')
),
position: getElementText(
findChildElement(contactInformation, 'ContactPosition')
),
phone: getElementText(
findChildElement(contactInformation, 'ContactVoiceTelephone')
),
fax: getElementText(
findChildElement(contactInformation, 'ContactFacsimileTelephone')
),
address: {
deliveryPoint: getElementText(findChildElement(address, 'Address')),
city: getElementText(findChildElement(address, 'City')),
administrativeArea: getElementText(
findChildElement(address, 'StateOrProvince')
),
postalCode: getElementText(findChildElement(address, 'PostCode')),
country: getElementText(findChildElement(address, 'Country')),
},
email: getElementText(
findChildElement(contactInformation, 'ContactElectronicMailAddress')
),
},
};
}

@@ -5,4 +5,7 @@ // @ts-expect-error ts-migrate(7016)

import capabilitiesStates from '../../fixtures/wms/capabilities-states-1-3-0.xml';
// @ts-expect-error ts-migrate(7016)
import exceptionReportWfs from '../../fixtures/wms/service-exception-report-wfs.xml';
import WmsEndpoint from './endpoint.js';
import { useCache } from '../shared/cache.js';
import { EndpointError, ServiceExceptionError } from '../shared/errors.js';

@@ -20,2 +23,3 @@ jest.mock('../shared/cache', () => ({

jest.clearAllMocks();
global.fetchPreHandler = () => {};
global.fetchResponseFactory = () => capabilities130;

@@ -57,2 +61,87 @@ endpoint = new WmsEndpoint(

});
describe('CORS error handling', () => {
beforeEach(() => {
global.fetchPreHandler = (url, options) => {
if (options?.method === 'HEAD') return 'ok!';
throw new Error('CORS problem');
};
endpoint = new WmsEndpoint('https://my.test.service/ogc/wms');
});
it('rejects with a relevant error', async () => {
const error = (await endpoint
.isReady()
.catch((e) => e)) as EndpointError;
expect(error).toBeInstanceOf(EndpointError);
expect(error.message).toBe(
'The document could not be fetched due to CORS limitations'
);
expect(error.httpStatus).toBe(0);
expect(error.isCrossOriginRelated).toBe(true);
});
});
describe('endpoint error handling', () => {
beforeEach(() => {
global.fetchPreHandler = () => {
throw new TypeError('other kind of problem');
};
endpoint = new WmsEndpoint('https://my.test.service/ogc/wms');
});
it('rejects with a relevant error', async () => {
const error = (await endpoint
.isReady()
.catch((e) => e)) as EndpointError;
expect(error).toBeInstanceOf(EndpointError);
expect(error.message).toBe(
'Fetching the document failed either due to network errors or unreachable host, error is: other kind of problem'
);
expect(error.httpStatus).toBe(0);
expect(error.isCrossOriginRelated).toBe(false);
});
});
describe('http error handling', () => {
beforeEach(() => {
global.fetchPreHandler = () => ({
ok: false,
text: () => Promise.resolve('something broke in the server'),
status: 500,
statusText: 'Internal Server Error',
});
endpoint = new WmsEndpoint('https://my.test.service/ogc/wms');
});
it('rejects with a relevant error', async () => {
const error = (await endpoint
.isReady()
.catch((e) => e)) as EndpointError;
expect(error).toBeInstanceOf(EndpointError);
expect(error.message).toBe(
'Received an error with code 500: something broke in the server'
);
expect(error.httpStatus).toBe(500);
expect(error.isCrossOriginRelated).toBe(false);
});
});
describe('service exception handling', () => {
beforeEach(() => {
global.fetchResponseFactory = () => exceptionReportWfs;
endpoint = new WmsEndpoint('https://my.test.service/ogc/wms');
});
it('rejects when the endpoint returns an exception report', async () => {
const error = (await endpoint
.isReady()
.catch((e) => e)) as ServiceExceptionError;
expect(error).toBeInstanceOf(ServiceExceptionError);
expect(error.message).toBe(
'msWMSGetCapabilities(): WMS server error. WMS request not enabled. Check wms/ows_enable_request settings.'
);
expect(error.requestUrl).toBe(
'https://my.test.service/ogc/wms?SERVICE=WMS&REQUEST=GetCapabilities'
);
expect(error.code).toBe('');
expect(error.locator).toBe('');
});
});
});

@@ -95,2 +184,9 @@

title: 'Carte géologique image de la France au 1/50 000e',
children: [
{
abstract: '',
name: 'INHERIT_SCALE',
title: 'Inherited scale denominators',
},
],
},

@@ -205,2 +301,19 @@ {

],
provider: {
contact: {
name: 'Support BRGM',
organization: 'BRGM',
position: 'pointOfContact',
phone: '+33(0)2 38 64 34 34',
fax: '+33(0)2 38 64 35 18',
address: {
deliveryPoint: '3, Avenue Claude Guillemin, BP36009',
city: 'Orléans',
administrativeArea: 'Centre',
postalCode: '45060',
country: 'France',
},
email: 'contact-brgm@brgm.fr',
},
},
});

@@ -222,6 +335,41 @@ });

).toBe(
'https://my.test.service/ogc/wms?aa=bb&SERVICE=WMS&REQUEST=GetMap&VERSION=1.3.0&LAYERS=layer1%2Clayer2&STYLES=&WIDTH=100&HEIGHT=200&FORMAT=image%2Fpng&CRS=EPSG%3A4326&BBOX=10%2C20%2C100%2C200'
'http://geoservices.brgm.fr/geologie?language=fre&SERVICE=WMS&REQUEST=GetMap&VERSION=1.3.0&LAYERS=layer1%2Clayer2&STYLES=&WIDTH=100&HEIGHT=200&FORMAT=image%2Fpng&CRS=EPSG%3A4326&BBOX=10%2C20%2C100%2C200'
);
});
});
describe('#getCapabilitiesUrl', () => {
it.skip('returns the URL used for the request before the capabilities are retrieved', async () => {
expect(endpoint.getCapabilitiesUrl()).toBe(
'https://my.test.service/ogc/wms?aa=bb&SERVICE=WMS&REQUEST=GetCapabilities'
);
await endpoint.isReady();
});
it('returns the self-reported URL after the capabilities are retrieved', async () => {
await endpoint.isReady();
expect(endpoint.getCapabilitiesUrl()).toBe(
'http://geoservices.brgm.fr/geologie?language=fre&SERVICE=WMS&REQUEST=GetCapabilities'
);
});
});
describe('#getOperationUrl', () => {
it.skip('returns NULL before the document is loaded', async () => {
expect(endpoint.getOperationUrl('GetMap')).toBeNull();
await endpoint.isReady();
});
it('returns undefined for a non-existant operation', async () => {
await endpoint.isReady();
expect(endpoint.getOperationUrl('foo')).toBeUndefined();
});
it('returns the correct URL for an existant operation', async () => {
await endpoint.isReady();
expect(endpoint.getOperationUrl('GetMap')).toBe(
'http://geoservices.brgm.fr/geologie?language=fre&'
);
});
});
});

@@ -8,3 +8,6 @@ import { parseWmsCapabilities } from '../worker/index.js';

GenericEndpointInfo,
type HttpMethod,
MimeType,
type OperationName,
type OperationUrl,
} from '../shared/models.js';

@@ -22,2 +25,3 @@ import { WmsLayerFull, WmsLayerSummary, WmsVersion } from './model.js';

private _layers: WmsLayerFull[] | null;
private _url: Record<OperationName, OperationUrl>;
private _version: WmsVersion | null;

@@ -43,5 +47,6 @@

this._capabilitiesUrl
).then(({ info, layers, version }) => {
).then(({ info, layers, url, version }) => {
this._info = info;
this._layers = layers;
this._url = url;
this._version = version;

@@ -165,3 +170,3 @@ });

return generateGetMapUrl(
this._capabilitiesUrl,
this.getOperationUrl('GetMap') || this._capabilitiesUrl,
this._version,

@@ -177,2 +182,31 @@ layers.join(','),

}
/**
* Returns the Capabilities URL of the WMS
*
* This is the URL reported by the service if available, otherwise the URL
* passed to the constructor
*/
getCapabilitiesUrl() {
const baseUrl = this.getOperationUrl('GetCapabilities');
if (!baseUrl) {
return this._capabilitiesUrl;
}
return setQueryParams(baseUrl, {
SERVICE: 'WMS',
REQUEST: 'GetCapabilities',
});
}
/**
* Returns the URL reported by the WMS for the given operation
* @param operationName e.g. GetMap, GetCapabilities, etc.
* @param method HTTP method
*/
getOperationUrl(operationName: OperationName, method: HttpMethod = 'Get') {
if (!this._url) {
return null;
}
return this._url[operationName]?.[method];
}
}

@@ -1,2 +0,7 @@

import { BoundingBox, CrsCode, LayerStyle } from '../shared/models.js';
import {
BoundingBox,
CrsCode,
LayerStyle,
type MetadataURL,
} from '../shared/models.js';

@@ -37,4 +42,7 @@ export type WmsLayerAttribution = {

opaque: boolean;
maxScaleDenominator?: number;
minScaleDenominator?: number;
attribution?: WmsLayerAttribution;
keywords?: string[];
metadata?: MetadataURL[];
/**

@@ -41,0 +49,0 @@ * Not defined if the layer is a leaf in the tree

@@ -26,2 +26,20 @@ import {

name: 'OGC WMTS',
provider: {
name: 'MiraMon',
site: 'http://www.creaf.uab.cat/miramon',
contact: {
name: 'Joan Maso Pau',
position: 'Senior Software Engineer',
phone: '+34 93 581 1312',
fax: '+34 93 581 4151',
address: {
deliveryPoint: 'Fac Ciencies UAB',
city: 'Bellaterra',
administrativeArea: 'Barcelona',
postalCode: '08193',
country: 'Spain',
},
email: 'joan.maso@uab.cat',
},
},
title: 'Web Map Tile Service',

@@ -43,2 +61,20 @@ getTileUrls: {

name: 'OGC WMTS',
provider: {
name: '',
site: '',
contact: {
name: '',
position: '',
phone: '',
fax: '',
address: {
deliveryPoint: '',
city: '',
administrativeArea: '',
postalCode: '',
country: '',
},
email: '',
},
},
title: 'Demographics_USA_Population_Density',

@@ -81,2 +117,20 @@ getTileUrls: {

name: 'OGC WMTS',
provider: {
name: 'IGN',
site: '',
contact: {
name: 'Géoportail SAV',
position: 'custodian',
phone: '',
fax: '',
address: {
deliveryPoint: '73 avenue de Paris',
city: 'Saint Mandé',
administrativeArea: '',
postalCode: '94160',
country: 'France',
},
email: 'geop_services@geoportail.fr',
},
},
title: 'Service de visualisation WMTS',

@@ -566,3 +620,3 @@ getTileUrls: {

identifier: 'BigWorldPixel',
crs: 'urn:ogc:def:crs:EPSG:6.18:3:3857',
crs: 'urn:ogc:def:crs:OGC:1.3:CRS84',
limits: [],

@@ -634,3 +688,3 @@ },

identifier: 'GoogleMapsCompatible',
crs: 'urn:ogc:def:crs:EPSG::3857',
crs: 'urn:ogc:def:crs:EPSG:6.18.3:3857',
limits: [],

@@ -637,0 +691,0 @@ },

import type { BoundingBox, LayerStyle } from '../shared/models.js';
import { readProviderFromCapabilities } from '../shared/ows.js';
import {

@@ -62,2 +63,3 @@ findChildElement,

keywords,
provider: readProviderFromCapabilities(capabilitiesDoc),
getTileUrls,

@@ -121,9 +123,33 @@ };

const contentsEl = findChildElement(rootEl, 'Contents');
/**
* Get the TileMatrixSet CRS
* @param contentsEl - Contents
* @param identifier - TileMatrixSet identifier
* @returns The SupportedCRS of the TileMatrixSet
*/
function getMatrixSetCrs(contentsEl: XmlElement, identifier: string): string {
const matrixSet = findChildrenElement(contentsEl, 'TileMatrixSet').find(
(matrixSetEl) => {
const identifierEl = findChildElement(matrixSetEl, 'Identifier');
return getElementText(identifierEl) === identifier;
}
);
return getElementText(findChildElement(matrixSet, 'SupportedCRS'));
}
/**
* Get the parameters of the TileMatrixSetLink
* @param element - TileMatrixSetLink
* @returns
*/
function parseMatrixSetLink(element: XmlElement): MatrixSetLink {
const fullMatrixSet = findChildrenElement(contentsEl, 'TileMatrixSet').find(
(el) => getElementText(findChildElement(el, 'Identifier'))
const identifier = getElementText(
findChildElement(element, 'TileMatrixSet')
);
const crs = getMatrixSetCrs(contentsEl, identifier);
return {
identifier: getElementText(findChildElement(element, 'TileMatrixSet')),
crs: getElementText(findChildElement(fullMatrixSet, 'SupportedCRS')),
identifier,
crs,
limits: findChildrenElement(element, 'TileMatrixLimits', true).map(

@@ -130,0 +156,0 @@ (element) => ({

@@ -243,2 +243,20 @@ import WmtsEndpoint from './endpoint.js';

name: 'OGC WMTS',
provider: {
name: 'MiraMon',
site: 'http://www.creaf.uab.cat/miramon',
contact: {
name: 'Joan Maso Pau',
position: 'Senior Software Engineer',
phone: '+34 93 581 1312',
fax: '+34 93 581 4151',
address: {
deliveryPoint: 'Fac Ciencies UAB',
city: 'Bellaterra',
administrativeArea: 'Barcelona',
postalCode: '08193',
country: 'Spain',
},
email: 'joan.maso@uab.cat',
},
},
title: 'Web Map Tile Service',

@@ -349,2 +367,20 @@ getTileUrls: {

title: 'Demographics_USA_Population_Density',
provider: {
name: '',
site: '',
contact: {
name: '',
position: '',
phone: '',
fax: '',
address: {
deliveryPoint: '',
city: '',
administrativeArea: '',
postalCode: '',
country: '',
},
email: '',
},
},
});

@@ -351,0 +387,0 @@ });

@@ -6,6 +6,6 @@ import { MimeType } from '../shared/models.js';

import {
WmtsEndpointInfo,
WmtsLayer,
WmtsLayerDimensionValue,
WmtsLayerResourceLink,
WmtsEndpointInfo,
WmtsLayer,
WmtsMatrixSet,

@@ -213,6 +213,10 @@ } from './model.js';

const matrixSet = this.getMatrixSetByIdentifier(matrixSetLink.identifier);
return this.tileGridModule.then(({ buildOpenLayersTileGrid }) =>
buildOpenLayersTileGrid(matrixSet, matrixSetLink.limits)
);
return this.tileGridModule.then((olTileGridModule) => {
if (!olTileGridModule) return null;
return olTileGridModule.buildOpenLayersTileGrid(
matrixSet,
matrixSetLink.limits
);
});
}
}
import { sendTaskRequest } from './utils.js';
import { GenericEndpointInfo } from '../shared/models.js';
import {
GenericEndpointInfo,
OperationName,
OperationUrl,
} from '../shared/models.js';
import { setFetchOptionsUpdateCallback } from '../shared/http-utils.js';

@@ -46,2 +50,3 @@ import { WmtsEndpointInfo, WmtsLayer, WmtsMatrixSet } from '../wmts/model.js';

version: WmsVersion;
url: Record<OperationName, OperationUrl>;
info: GenericEndpointInfo;

@@ -61,2 +66,3 @@ layers: WmsLayerFull[];

version: WfsVersion;
url: Record<OperationName, OperationUrl>;
info: GenericEndpointInfo;

@@ -63,0 +69,0 @@ featureTypes: WfsFeatureTypeInternal[];

import { getUniqueId } from '../shared/id.js';
import { decodeError, encodeError } from '../shared/errors.js';

@@ -14,3 +15,3 @@ type TaskParams = Record<string, unknown>;

requestId: number;
error?: unknown;
error?: Record<string, unknown>;
response?: TaskResponse;

@@ -49,3 +50,3 @@ };

if ('error' in response) {
reject(response.error);
reject(decodeError(response.error));
} else {

@@ -85,3 +86,3 @@ resolve(response.response as T);

} catch (e) {
error = e;
error = encodeError(e);
}

@@ -88,0 +89,0 @@ const message = /** @type {WorkerResponse} */ {

import { addTaskHandler } from './utils.js';
import { queryXmlDocument, setFetchOptions } from '../shared/http-utils.js';
import { check } from '../shared/errors.js';
import * as wmsCapabilities from '../wms/capabilities.js';

@@ -15,15 +16,21 @@ import * as wfsCapabilities from '../wfs/capabilities.js';

addTaskHandler('parseWmsCapabilities', globalThis, ({ url }: { url: string }) =>
queryXmlDocument(url).then((xmlDoc) => ({
info: wmsCapabilities.readInfoFromCapabilities(xmlDoc),
layers: wmsCapabilities.readLayersFromCapabilities(xmlDoc),
version: wmsCapabilities.readVersionFromCapabilities(xmlDoc),
}))
queryXmlDocument(url)
.then((xmlDoc) => check(xmlDoc, url))
.then((xmlDoc) => ({
info: wmsCapabilities.readInfoFromCapabilities(xmlDoc),
layers: wmsCapabilities.readLayersFromCapabilities(xmlDoc),
url: wmsCapabilities.readOperationUrlsFromCapabilities(xmlDoc),
version: wmsCapabilities.readVersionFromCapabilities(xmlDoc),
}))
);
addTaskHandler('parseWfsCapabilities', globalThis, ({ url }: { url: string }) =>
queryXmlDocument(url).then((xmlDoc) => ({
info: wfsCapabilities.readInfoFromCapabilities(xmlDoc),
featureTypes: wfsCapabilities.readFeatureTypesFromCapabilities(xmlDoc),
version: wfsCapabilities.readVersionFromCapabilities(xmlDoc),
}))
queryXmlDocument(url)
.then((xmlDoc) => check(xmlDoc, url))
.then((xmlDoc) => ({
info: wfsCapabilities.readInfoFromCapabilities(xmlDoc),
featureTypes: wfsCapabilities.readFeatureTypesFromCapabilities(xmlDoc),
url: wfsCapabilities.readOperationUrlsFromCapabilities(xmlDoc),
version: wfsCapabilities.readVersionFromCapabilities(xmlDoc),
}))
);

@@ -72,7 +79,9 @@

({ url }: { url: string }) =>
queryXmlDocument(url).then((xmlDoc) => ({
info: wmtsCapabilities.readInfoFromCapabilities(xmlDoc),
layers: wmtsCapabilities.readLayersFromCapabilities(xmlDoc),
matrixSets: wmtsCapabilities.readMatrixSetsFromCapabilities(xmlDoc),
}))
queryXmlDocument(url)
.then((xmlDoc) => check(xmlDoc, url))
.then((xmlDoc) => ({
info: wmtsCapabilities.readInfoFromCapabilities(xmlDoc),
layers: wmtsCapabilities.readLayersFromCapabilities(xmlDoc),
matrixSets: wmtsCapabilities.readMatrixSetsFromCapabilities(xmlDoc),
}))
);

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc