@dlr-eoc/services-ogc
Version:
This module bundles our clients for OGC standards. E.g. parse OWS Context JSON, WMS, WMTS or WPS.
1 lines • 149 kB
Source Map (JSON)
{"version":3,"file":"dlr-eoc-services-ogc.mjs","sources":["../../../projects/services-ogc/src/lib/owc/types/owc-json.ts","../../../projects/services-ogc/src/lib/owc/types/eoc-owc-json.ts","../../../projects/services-ogc/src/lib/owc/types/owc-json.utils.ts","../../../projects/services-ogc/src/lib/wmts/wmtsclient.service.ts","../../../projects/services-ogc/src/lib/owc/owc-json.service.ts","../../../projects/services-ogc/src/lib/wps/wpsclient.ts","../../../projects/services-ogc/src/lib/wms/wmsclient.service.ts","../../../projects/services-ogc/src/public-api.ts","../../../projects/services-ogc/src/dlr-eoc-services-ogc.ts"],"sourcesContent":["/**\n * Type definitions for OGC OWS Context Geo Encoding Standard Version: 1.0\n * http://docs.opengeospatial.org/is/14-055r2/14-055r2.html\n * Definitions by: Mathias Boeck\n * TypeScript Version: 2.5.3\n *\n * depends on @types/geojson@^7946.0.2\n */\nimport * as GeoJSON from 'geojson';\n\n/**\n * The OWS Context describes Metadata, API, Time Range\n * http://www.owscontext.org/owc_user_guide/C0_userGuide.html#truethe-ows-context-document-structure\n * If no bounding box is specified, do not change the current view when the context document is loaded.\n */\nexport interface IOwsContext extends GeoJSON.FeatureCollection<GeoJSON.GeometryObject | null, GeoJSON.GeoJsonProperties> {\n /**\n * The id element defines a mandatory reference to the identification of the Context document.\n * The content for the id element SHALL be an IRI, as defined by IETF [RFC3987]\n */\n id: string | number;\n properties: {\n links: {\n profiles: IOwsLinks[],\n via?: IOwsLinks[]\n };\n /** Language of Context document content */\n lang: LangString;\n /** Title for the Context document */\n title: string;\n /** Date of a creation or update of the Context document */\n updated: DateString;\n /** Description of the Context document purpose or content */\n subtitle?: string;\n /** This element is optional and indicates the authors array of the Context document */\n authors?: IOwsAuthor[];\n /** Identifier for the publisher of the Context document */\n publisher?: string;\n /** Tool/application used to create the Context document and its properties */\n generator?: IOwsGenerator;\n /**\n * Properties of the display in use when the context document was created (for display based applications only).\n * This class is optional and intended for creator applications that use a graphical user interface with a geographical display within a fixed pixel size and not scalable to different computational devices\n */\n display?: IOwsCreatorDisplay[];\n /** Information about rights held in and over the Context document */\n rights?: string;\n /**\n * This element is optional and expressed a date or range of dates relevant to the Context document.\n * It can contain the element start, stop and instant. The values of these elements SHALL conform to the \"date-time\" production of ISO-8601[5]. An uppercase \"T\" character SHALL be used to separate date and time, and an uppercase \"Z\" character SHALL be present in the absence of a numeric time zone offset. To specify a range of dates the \"/\" character SHALL be used.\n */\n date?: DateString;\n /** This array is an optional and expresses categories related to this Context document */\n categories?: IOwsCategory[];\n /** Extension Any other element */\n [k: string]: any;\n };\n /** Ordered List of Resources available on the Context document\n * The order of the member of the features MAY be used to identify the drawing order of the resources.\n * In that case, the first item of the array represents the top most layer\n */\n features: IOwsResource[];\n /** Extension Any other element */\n [k: string]: any;\n}\n\n/**\n * Each layer (a.k.a. feature) in a context document is known as a ‘Resource’\n * A Resource reference a set of geospatial information to be treated as a logical element.\n * The resources are ordered such that the first item in the document is to be displayed at the front.\n * This defines the order in which layers are drawn.\n * A resource (which in GIS terms is a layer) can have a number of offerings, and each offering\n * is focussed on a particular representation of information.\n * These can be one of a number of OGC Web Services, specifically WMS, WMTS, WFS, WCS, WPS and CSW,\n * or one of a number of inline or referenced formats, specifically GML, KML, GeoTIFF, GMLJP2, GMLCOV,\n * or a custom offering type defined in a profile or by an organisation.\n * http://www.owscontext.org/owc_user_guide/C0_userGuide.html#truethe-ows-context-document-structure\n */\nexport interface IOwsResource extends GeoJSON.Feature {\n /**\n * Unambiguous reference to the identification of the Context resource (IRI)\n * String type that SHALL contain a URI value\n */\n id: string | number;\n properties: IOwsResourceProperties;\n [k: string]: any;\n}\nexport interface IOwsResourceProperties {\n /** Title given to the Context resource */\n title: string;\n /** Date of the last update of the Context resource */\n updated: DateString;\n /** The purpose is to provide a generic description of the content in a format understandable by generic readers */\n abstract?: string;\n /** This element is optional and indicates the authors array of the Context resource */\n authors?: IOwsAuthor[];\n /** Entity responsible for making the Context resource available */\n publisher?: string;\n /** Information about rights held in and over the Context resource */\n rights?: string;\n /** Date or range of dates relevant to the Context resource. The values of these elements SHALL conform to the \"date-time\" production of ISO-8601[5]*/\n date?: DateString;\n /** This element is optional and can contain a number of offerings defined by the class OWC:Offering */\n offerings?: IOwsOffering[];\n /** Flag value indicating to the client if the Context resource should be displayed by default.\n * E.g. Layer is visible\n */\n active?: boolean;\n /** This array is optional and expresses a category related to the Context resource */\n categories?: IOwsCategory[];\n /** Minimum scale for the display of the Context resource Double */\n minscaledenominator?: number;\n /** Maximum scale for the display of the Context resource Double */\n maxscaledenominator?: number;\n /** Definition of the folder in which the resource is placed\n * The folder attribute is intended to support the concept present in many clients or organising layers into folders.\n */\n folder?: string;\n links?: {\n previews?: IOwsLinks[],\n alternates?: IOwsLinks[],\n data?: IOwsLinks[],\n via?: IOwsLinks[]\n };\n [k: string]: any;\n}\n\n/**\n * In reality a resource can be realized in a number of different ways, and so an OWC document allows various options to be specified.\n * These are known as offerings.\n * The intention is that these are, as far as is possible by the format used,\n * equivalent and no priority is assigned to their order in the standard.\n * They are intended to be alternatives that the client can use to allow it to visualize or use the resource.\n *\n * So for example four offerings, a WMS, a WFS with portrayal as SLD, and an inline GML Offering again with portrayal as SLD.\n * Different clients could use these offerings as appropriate:\n * - a simple browser based client could use the WMS offering provided, using the standard portrayal\n * - a more sophisticated client, could use the WFS offering and the associated SLD Document.\n *\n * There are two types of offering, service offerings and data offerings.\n * A service offering has a service request (in the form of a capabilities request and a data request)\n * and optional content and styling elements.\n * A data offering has a content element and optional styling elements.\n *\n *\n * http://www.owscontext.org/owc_user_guide/C0_userGuide.html#truemultiple-offerings-and-priority\n */\nexport interface IOwsOffering {\n /** Code identifying the type of offering - Extension Offerings with type - string */\n code: WMS_Offering | WFS_Offering | WCS_Offering | WPS_Offering | CSW_Offering | WMTS_Offering |\n GML_Offering | KML_Offering | GeoTIFF_Offering | GMLJP2_Offering | GMLCOV_Offering | string;\n /** Web Service Offerings provide their operations - Array of operations used to invoke the service */\n operations?: IOwsOperation[];\n /** Content Offerings allow content to be embedded in an OWS Context document (inline or byRef) */\n contents?: IOwsContent[];\n /** Array of style sets - A style representation for a resource (inline or service derived) content */\n styles?: IOwsStyleSet[];\n [k: string]: any;\n}\n\nexport interface IOwsGenerator {\n title?: string;\n uri?: string;\n version?: string;\n}\n\nexport interface IOwsAuthor {\n /** Entity primarily responsible for making the Context document\n * Properties that all types of authors have. It mimics the Atom author\n */\n name: string;\n email?: string;\n uri?: string;\n [k: string]: any;\n}\n\nexport interface IOwsCategory {\n /** Category related to this context document. It MAY have a related code-list that is identified by the scheme attribute */\n term: string;\n scheme?: string;\n label?: string;\n}\n\n/** Properties that all types of links have. It mimics the Atom link */\nexport interface IOwsLinks {\n href: string;\n type?: string;\n title?: string;\n lang?: LangString;\n length?: number;\n [k: string]: any;\n}\n\nexport interface IOwsCreatorApplication {\n title?: string;\n uri?: string;\n version?: string;\n}\n\nexport interface IOwsCreatorDisplay {\n /** Width measured in pixels of the display showing the Area of Interest */\n pixelWidth?: number;\n /** Width measured in pixels of the display showing by the Area of Interest */\n pixelHeight?: number;\n /** The size of a pixel of the display in millimeters\n * (combined with the previous ones allows for the real display size to be calculated)\n */\n mmPerPixel?: number;\n [k: string]: any;\n}\n\n/**\n * Most service offerings have two operations, a ‘GetCapabilities’ operation and a data operation such as ‘GetMap’ for WMS\n */\nexport interface IOwsOperation {\n /**\n * The code identifies the type of operation.\n * Valid types are defined within each specific extension within the OWS Context conceptual model [OGC 12-080].\n */\n code: string;\n /** method defines the access method, for example GET or POST. */\n method: string;\n /** Service Request URL - The URI containing the definition of the request */\n href: string;\n /** MIME type of the expected results */\n type?: string;\n /** Optional request body content */\n request?: IOwsContent;\n /** Optional Result Payload of the operation */\n result?: IOwsContent;\n /** Extension of Operation */\n [k: string]: any;\n}\n\nexport interface IOwsContent {\n /** MIME type of the Content */\n type: string;\n /** URL of the Content */\n href?: string;\n /** Title of the Content */\n title?: string;\n /** In-line content for the Content element- String type, not empty that can contain any text encoded media type */\n content?: string;\n [k: string]: any;\n}\n\nexport interface IOwsStyleSet {\n /** Unique name of the styleSet within a given offering */\n name: string;\n /** Human Readable title of the styleSet within a given offering */\n title: string;\n /** Description of the styleSet */\n abstract?: string;\n /** Whether this styleSet is the one to be defined by default */\n default?: boolean;\n /** URL of a legend image for the styleSet */\n legendURL?: string;\n /** The inline or a external reference to the styleSet definition */\n content?: IOwsContent;\n [k: string]: any;\n}\n\n/** ISO-8601 format e.g. YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ssZ/YYYY-MM-DDThh:mm:ssZ */\nexport type DateString = string;\n\n\n/** RFC-3066 code e.g. en,de */\nexport type LangString = string;\n\nexport const wmsOffering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/wms' as const;\nexport type WMS_Offering = typeof wmsOffering;\n\nexport const wfsOffering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/wfs' as const;\nexport type WFS_Offering = typeof wfsOffering;\n\nexport const wcsOffering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/wcs' as const;\nexport type WCS_Offering = typeof wcsOffering;\n\nexport const wpsOffering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/wps' as const;\nexport type WPS_Offering = typeof wpsOffering;\n\nexport const cswOffering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/csw' as const;\nexport type CSW_Offering = typeof cswOffering;\n\nexport const wmtsOffering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/wmts' as const;\nexport type WMTS_Offering = typeof wmtsOffering;\n\nexport const gmlOffering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/gml' as const;\nexport type GML_Offering = typeof gmlOffering;\n\nexport const kmlOffering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/kml' as const;\nexport type KML_Offering = typeof kmlOffering;\n\nexport const GeoTIFFOffering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/geotiff' as const;\nexport type GeoTIFF_Offering = typeof GeoTIFFOffering;\n\nexport const GMLJP2Offering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/gmljp2' as const;\nexport type GMLJP2_Offering = typeof GMLJP2Offering;\n\nexport const GMLCOVOffering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/gmlcov' as const;\nexport type GMLCOV_Offering = typeof GMLCOVOffering;\n","import {\n IOwsContext, IOwsResource, IOwsOffering, WFS_Offering, WCS_Offering, WPS_Offering,\n CSW_Offering, WMTS_Offering, GML_Offering, KML_Offering, GeoTIFF_Offering, GMLJP2_Offering,\n GMLCOV_Offering, IOwsResourceProperties, WMS_Offering\n} from './owc-json';\n\n\nimport * as GeoJSON from 'geojson';\nexport interface IEocOwsContext extends IOwsContext {\n features: IEocOwsResource[];\n /** @deprecated we do not use this currently */\n projections?: IEocOwsProjection[];\n}\n\nexport interface IEocOwsResource extends IOwsResource {\n properties: IEocOwsResourceProperties;\n}\n\nexport interface IEocOwsResourceProperties extends IOwsResourceProperties {\n /** The opacity of the displayed Layer */\n opacity?: number;\n attribution?: string; /** maybe this should be in IOwsResourceProperties.rights */\n /** Subdomains for urls in layers - e.g. 'a-d' is placed in https://{s}.tiles.geoservice.dlr.de/... as {a-d} or multiple urls are generated\n * e.g. https://a.tiles..., https://b.tiles...\n */\n shards?: string;\n /** Layer Dimension like Time and Elevation - To define e.g. the available Time data points/ranges in the Layer and a hint how to display them */\n dimensions?: IEocOwsResourceDimension[];\n /** Alternative to IOwsResourceProperties.minscaledenominator; easier to calculate in browser-apps */\n minZoom?: number;\n /** Alternative to IOwsResourceProperties.maxscaledenominator; easier to calculate in browser-apps */\n maxZoom?: number;\n /**\n * Folder is already defined on IOwsResourceProperties, this should only show how ukis is using it.\n * - string - Single Folder inside the Layers Slot `Layers`\n * - `${TFiltertypes}/string` - Single Folder inside one of the Layers Slots `TFiltertypes`\n */\n folder?: string;\n}\n\n\n\ntype isoInterval = `${string}/${string}`;\ntype intervalPeriod = `${isoInterval}/P${string}`;\n\nexport interface IEocOwsTimeDimension {\n name: 'time';\n /**\n * For time:\n * - '1984-01-01T00:00:00.000Z,1990-01-01T00:00:00.000Z,1995-01-01T00:00:00.000Z,...'\n * - '2000-09-01T00:00:00.000Z/2017-08-31T00:00:00.000Z/P1D'\n * - '2000-09-01T00:00:00.000Z/2010-08-31T00:00:00.000Z/P1D,2010-09-01T00:00:00.000Z/2020-08-31T00:00:00.000Z/P1D,...'\n * - '1984-01-01T00:00:00.000Z/P1Y,1985-01-01T00:00:00.000Z/P1Y,1986-01-01T00:00:00.000Z,1987-01-01T00:00:00.000Z,...'\n * also see https://moment.github.io/luxon/api-docs/index.html#intervalfromiso\n */\n values: `${string | isoInterval | intervalPeriod},${string | isoInterval | intervalPeriod}` | isoInterval | intervalPeriod;\n /**\n * For time: 'ISO8601'\n * ISO8601 has been chosen because this is how\n * geoserver's GetCapabilities response exposes\n * time information.\n */\n units: 'ISO8601' | string;\n display?: {\n /** format how to display the values e.g. YYYY-MM-DD */\n format?: string;\n /** in case the app should display data at a different period than what is available on the server */\n period?: string;\n /** The value which should be shown/used as default */\n default?: string;\n };\n}\n\n/** 12-111r1_Best_Practices_for_WMS_with_Time_or_Elevation_dependent_data.pdf - https://portal.ogc.org/files/?artifact_id=56394 */\nexport interface IEocOwsElevationDimension {\n name: 'elevation';\n /**\n *\n */\n value: string;\n /**\n * string or range\n * 100,200,300...\n * 100/1000\n */\n units: string;\n display?: {\n unitSymbol?: string;\n format?: string;\n /** in case the app should display data at a different elevation step */\n step?: string;\n /** The value which should be shown/used as default */\n default?: string;\n };\n}\n\nexport type IEocOwsResourceDimension = IEocOwsTimeDimension | IEocOwsElevationDimension;\n\nexport interface IEocOwsOffering extends IOwsOffering {\n code: WMS_Offering | WFS_Offering | WCS_Offering | WPS_Offering | CSW_Offering |\n WMTS_Offering | GML_Offering | KML_Offering | GeoTIFF_Offering | GMLJP2_Offering |\n GMLCOV_Offering | GeoJson_Offering | TMS_Offering | string;\n /** @deprecated we do not use this currently */\n iconUrl?: string;\n /** @deprecated we do not use this currently */\n title?: string;\n /** only for WMTS_Offering */\n matrixSets?: IEocOwsWmtsMatrixSet[];\n}\n\nexport interface IEocOwsWmtsMatrixSet {\n /** EPSG-Code */\n srs: string;\n matrixSet: string;\n matrixIds: string[];\n origin: {\n x: number,\n y: number\n };\n resolutions: number[];\n tilesize: {\n height: number,\n width: number\n };\n}\n\n/**\n * @deprecated we do not use this currently\n */\nexport interface IEocOwsProjection {\n bbox?: GeoJSON.BBox;\n code: string;\n default?: boolean;\n unit?: string | number;\n}\n/**\n * http://www.owscontext.org/owc_user_guide/C0_userGuide.html#trueextension-offerings\n */\nexport const GeoJsonOffering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/geojson' as const;\nexport type GeoJson_Offering = typeof GeoJsonOffering;\n\nexport const xyzOffering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/xyz' as const;\nexport type Xyz_Offering = typeof xyzOffering;\n\nexport const tmsOffering = 'http://www.opengis.net/spec/owc-geojson/1.0/req/tms' as const;\nexport type TMS_Offering = typeof tmsOffering;\n\n\n/** list of context files */\nexport interface IEocOwsContextListItem {\n id: IEocOwsContext['id'];\n /** relative or absolute link/path to context file */\n url: string;\n /** default is true */\n enabled?: boolean;\n}\n\nexport type EocOwsContextList = IEocOwsContextListItem[];\n","/** This file contains functions (Type Guards) to test for types in owc-json.ts */\n\nimport { Xyz_Offering, GeoJson_Offering, TMS_Offering, GeoJsonOffering, tmsOffering, xyzOffering } from './eoc-owc-json';\nimport { cswOffering, CSW_Offering, GeoTIFFOffering, GeoTIFF_Offering, GMLCOVOffering, GMLCOV_Offering, GMLJP2Offering, GMLJP2_Offering, gmlOffering, GML_Offering, IOwsAuthor, IOwsCategory, IOwsContent, IOwsContext, IOwsGenerator, IOwsCreatorDisplay, IOwsLinks, IOwsOffering, IOwsOperation, IOwsResource, IOwsResourceProperties, IOwsStyleSet, kmlOffering, KML_Offering, wcsOffering, WCS_Offering, wfsOffering, WFS_Offering, wmsOffering, WMS_Offering, wmtsOffering, WMTS_Offering } from './owc-json';\n\n\n/**\n * export types to create layers from Offerings\n */\nexport const GetMapOperationCode = 'GetMap' as const;\nexport type WMS_Code = typeof GetMapOperationCode;\n\nexport const GetFeatureOperationCode = 'GetFeature' as const;\nexport type WFS_Code = typeof GetFeatureOperationCode;\n\nexport const GetTileOperationCode = 'GetTile' as const;\nexport type WMTS_Code = typeof GetTileOperationCode;\n\nexport const RESTOperationCode = 'REST' as const;\nexport type TMS_Code = typeof RESTOperationCode;\nexport type XYZ_Code = typeof RESTOperationCode;\n\nexport const GetCapabilitiesOperationCode = 'GetCapabilities' as const;\nexport const DescribeFeatureTypeOperationCode = 'DescribeFeatureType' as const;\nexport const GetFeatureInfoOperationCode = 'GetFeatureInfo' as const;\n\n\nfunction trueForAll(list: any[], predicate: (o: any) => boolean): boolean {\n for (const entry of list) {\n if (!predicate(entry)) {\n return false;\n }\n }\n return true;\n}\n\nexport function isIOwsContext(object: IOwsContext): object is IOwsContext {\n let ISCONTEXT_1_0;\n if (object?.properties?.links) {\n ISCONTEXT_1_0 = object.properties.links.profiles.find(item => item.href === 'http://www.opengis.net/spec/owc-geojson/1.0/req/core');\n }\n\n if (!ISCONTEXT_1_0) {\n console.error('this is not a valid OWS Context v1.0!');\n return false;\n } else {\n return true;\n }\n}\n\nexport function isIOwsResource(object: any): object is IOwsResource {\n return 'id' in object && 'type' in object\n && 'properties' in object && isIOwsResourceProperties(object.properties);\n}\n\nexport function isIOwsResourceProperties(object: any): object is IOwsResourceProperties {\n return 'title' in object\n && 'updated' in object\n && (object.authors ? trueForAll(object.authors, isIOwsAuthor) : true)\n && (object.offerings ? trueForAll(object.offerings, isIOwsOffering) : true)\n && (object.categories ? trueForAll(object.categories, isIOwsCategory) : true);\n}\n\nexport function isIOwsOffering(object: any): object is IOwsOffering {\n return 'code' in object\n && (object.operations ? trueForAll(object.operations, isIOwsOperation) : true)\n && (object.contents ? trueForAll(object.contents, isIOwsContent) : true)\n && (object.styles ? trueForAll(object.styles, isIOwsStyleSet) : true)\n}\n\nexport function isIOwsGenerator(object: any): object is IOwsGenerator {\n return 'title' in object\n || 'uri' in object\n || 'version' in object;\n}\n\nexport function isIOwsAuthor(object: any): object is IOwsAuthor {\n return 'name' in object\n || 'email' in object\n || 'uri' in object;\n}\n\nexport function isIOwsCategory(object: any): object is IOwsCategory {\n return 'scheme' in object\n || 'term' in object\n || 'label' in object;\n}\n\nexport function isIOwsLinks(object: any): object is IOwsLinks {\n return 'rel' in object;\n}\n\nexport function isIOwsCreatorDisplay(object: any): object is IOwsCreatorDisplay {\n return 'pixelWidth' in object\n || 'pixelHeight' in object\n || 'mmPerPixel' in object;\n}\n\nexport function isIOwsOperation(object: any): object is IOwsOperation {\n return 'code' in object\n && 'method' in object\n && (object.request ? isIOwsContent(object.request) : true)\n && (object.result ? isIOwsContent(object.result) : true);\n}\n\nexport function isIOwsRasterOperation(object: any): object is IOwsOperation {\n if (isIOwsOperation(object)) {\n return [GetMapOperationCode, GetTileOperationCode, RESTOperationCode].includes(object.code as any);\n } else {\n return false;\n }\n}\n\nexport function isIOwsVectorOperation(object: any): object is IOwsOperation {\n if (isIOwsOperation(object)) {\n return [GetFeatureOperationCode].includes(object.code as any);\n } else {\n return false;\n }\n}\n\nexport function isIOwsContent(object: any): object is IOwsContent {\n return 'type' in object;\n}\n\nexport function isIOwsStyleSet(object: any): object is IOwsStyleSet {\n return 'name' in object\n && 'title' in object;\n}\n\nexport function isWmsOffering(str: string): str is WMS_Offering {\n return str === wmsOffering;\n}\nexport function isWfsOffering(str: string): str is WFS_Offering {\n return str === wfsOffering;\n}\nexport function isWpsOffering(str: string): str is WCS_Offering {\n return str === wcsOffering;\n}\nexport function isCswOffering(str: string): str is CSW_Offering {\n return str === cswOffering;\n}\nexport function isWmtsOffering(str: string): str is WMTS_Offering {\n return str === wmtsOffering;\n}\nexport function isGmlOffering(str: string): str is GML_Offering {\n return str === gmlOffering;\n}\nexport function isKmlOffering(str: string): str is KML_Offering {\n return str === kmlOffering;\n}\nexport function isGeoTIFFOffering(str: string): str is GeoTIFF_Offering {\n return str === GeoTIFFOffering;\n}\nexport function isGMLJP2Offering(str: string): str is GMLJP2_Offering {\n return str === GMLJP2Offering;\n}\nexport function isGMLCOVOffering(str: string): str is GMLCOV_Offering {\n return str === GMLCOVOffering;\n}\nexport function isXyzOffering(str: string): str is Xyz_Offering {\n return str === xyzOffering;\n}\nexport function isGeoJsonOffering(str: string): str is GeoJson_Offering {\n return str === GeoJsonOffering;\n}\nexport function isTMSOffering(str: string): str is TMS_Offering {\n return str === tmsOffering;\n}\n","import { Injectable } from '@angular/core';\nimport { HttpClient, HttpHeaders } from '@angular/common/http';\nimport { Observable } from 'rxjs';\nimport { Jsonix } from 'jsonix';\nimport { map } from 'rxjs/operators';\nimport * as XLink_1_0_Factory from 'w3c-schemas/lib/XLink_1_0';\nconst XLink_1_0 = XLink_1_0_Factory.XLink_1_0;\nimport * as OWS_1_1_0_Factory from 'ogc-schemas/lib/OWS_1_1_0';\nconst OWS_1_1_0 = OWS_1_1_0_Factory.OWS_1_1_0;\nimport * as SMIL_2_0_Factory from 'ogc-schemas/lib/SMIL_2_0';\nconst SMIL_2_0 = SMIL_2_0_Factory.SMIL_2_0;\nimport * as SMIL_2_0_Language_Factory from 'ogc-schemas/lib/SMIL_2_0_Language';\nconst SMIL_2_0_Language = SMIL_2_0_Language_Factory.SMIL_2_0_Language;\nimport * as GML_3_1_1_Factory from 'ogc-schemas/lib/GML_3_1_1';\nconst GML_3_1_1 = GML_3_1_1_Factory.GML_3_1_1;\nimport * as WMTS_1_0_Factory from 'ogc-schemas/lib/WMTS_1_0';\nconst WMTS_1_0 = WMTS_1_0_Factory.WMTS_1_0;\n\n\n\n@Injectable({\n providedIn: 'root'\n})\nexport class WmtsClientService {\n\n private xmlmarshaller;\n private xmlunmarshaller;\n\n constructor(private http: HttpClient) {\n const context = new Jsonix.Context([SMIL_2_0, SMIL_2_0_Language, GML_3_1_1, XLink_1_0, OWS_1_1_0, WMTS_1_0]);\n this.xmlunmarshaller = context.createUnmarshaller();\n this.xmlmarshaller = context.createMarshaller();\n }\n\n public getCapabilities(url: string, version = '1.1.0'): Observable<object> {\n // example: https://tiles.geoservice.dlr.de/service/wmts?SERVICE=WMTS&REQUEST=GetCapabilities&VERSION=1.1.0\n const getCapabilitiesUrl = `${url}?SERVICE=WMTS&REQUEST=GetCapabilities&VERSION=${version}`;\n const headers = new HttpHeaders({\n 'Content-Type': 'text/xml',\n Accept: 'text/xml, application/xml'\n });\n return this.http.get(getCapabilitiesUrl, { headers, responseType: 'text' }).pipe(\n map(response => {\n return this.xmlunmarshaller.unmarshalString(response);\n })\n );\n }\n\n}\n","\nimport { Injectable } from '@angular/core';\nimport {\n IOwsContext, IOwsResource, IOwsOffering, IOwsOperation, IOwsContent, kmlOffering, wfsOffering, wmsOffering, wmtsOffering\n} from './types/owc-json';\nimport { DescribeFeatureTypeOperationCode, GetCapabilitiesOperationCode, GetFeatureInfoOperationCode, GetFeatureOperationCode, GetMapOperationCode, GetTileOperationCode, isGeoJsonOffering, isIOwsContext, isIOwsRasterOperation, isKmlOffering, isTMSOffering, isWfsOffering, isWmsOffering, isWmtsOffering, isXyzOffering, RESTOperationCode } from './types/owc-json.utils';\nimport {\n IEocOwsContext, IEocOwsResource, IEocOwsOffering, IEocOwsWmtsMatrixSet,\n IEocOwsResourceDimension,\n IEocOwsTimeDimension,\n IEocOwsElevationDimension,\n GeoJsonOffering,\n xyzOffering,\n tmsOffering\n} from './types/eoc-owc-json';\nimport {\n ILayerOptions, IRasterLayerOptions, VectorLayer, RasterLayer, IVectorLayerOptions,\n Layer, TLayertype, WmsLayertype, WmtsLayertype, WfsLayertype, GeojsonLayertype, XyzLayertype,\n TRasterLayertype, ILayerDimensions,\n ILayerIntervalAndPeriod,\n WmtsLayer,\n IWmtsOptions,\n WmsLayer,\n IWmsParams,\n IWmsOptions,\n IListMatrixSet,\n TFiltertypes,\n LayerGroup,\n ILayerTimeDimension,\n ILayerElevationDimension,\n Filtertypes,\n TmsLayertype,\n KmlLayertype,\n IWmtsParams,\n TVectorLayertype,\n StackedLayer,\n IStackedLayerOptions\n} from '@dlr-eoc/services-layers';\nimport { TGeoExtent } from '@dlr-eoc/services-map-state';\nimport { WmtsClientService } from '../wmts/wmtsclient.service';\nimport { of, Observable, forkJoin, concat } from 'rxjs';\nimport { filter, map } from 'rxjs/operators';\n\nimport { HttpClient } from '@angular/common/http';\nimport { DateTime, Interval } from 'luxon';\nimport { get as getProjection } from 'ol/proj';\n\n\nexport function shardsExpand(v: string) {\n if (!v) { return; }\n const o: string[] = [];\n const shardsSplit = v.split(',');\n for (const i in shardsSplit) {\n if (shardsSplit[i]) {\n const j = shardsSplit[i].split('-');\n if (j.length === 1) {\n o.push(shardsSplit[i]);\n } else if (j.length === 2) {\n const start = j[0].charCodeAt(0);\n const end = j[1].charCodeAt(0);\n if (start <= end) {\n for (let k = start; k <= end; k++) {\n o.push(String.fromCharCode(k).toLowerCase());\n }\n } else {\n for (let k = start; k >= end; k--) {\n o.push(String.fromCharCode(k).toLowerCase());\n }\n }\n }\n }\n }\n return o;\n}\n\n/**\n * OWS Context Service\n * OGC OWS Context Geo Encoding Standard Version: 1.0\n * http://docs.opengeospatial.org/is/14-055r2/14-055r2.html\n * http://www.owscontext.org/owc_user_guide/C0_userGuide.html\n *\n * This service allows you to read and write OWC-data.\n * We have added some custom fields to the OWC standard.\n * - accepts the OWC-standard-data-types as function inputs (so as to be as general as possible)\n * - returns our extended OWC-data-types as function outputs (so as to be as information-rich as possible)\n *\n * As a policy, this services does *not* make any HTTP requests to GetCapabilities (or similar) to gather\n * additional information (with very few exceptions) - we want to save on network traffic.\n * However there are scripts that auto-generate OWC files from Capabilities, those, of course,\n * *do* scrape as much information online as possible; But they are not intended to be used in\n * a live-application. Run them batch-wise and server-side instead.\n */\n\n@Injectable({\n providedIn: 'root'\n})\nexport class OwcJsonService {\n\n constructor(\n private wmtsClient: WmtsClientService,\n private http: HttpClient) {\n // http://www.owscontext.org/owc_user_guide/C0_userGuide.html#truegeojson-encoding-2\n }\n\n\n checkContext(context: IOwsContext) {\n return isIOwsContext(context);\n }\n\n getContextTitle(context: IOwsContext) {\n return context.properties.title;\n }\n\n getContextPublisher(context: IOwsContext) {\n return (context.properties.publisher) ? context.properties.publisher : null;\n }\n\n getContextExtent(context: IOwsContext) {\n return (context.bbox) ? context.bbox : null; // or [-180, -90, 180, 90];\n }\n\n getResources(context: IOwsContext): IOwsResource[] {\n return context.features;\n }\n\n /**\n * Get Resources whith Folder property but not including Layer-Filtertypes\n */\n getGroupResources(context: IOwsContext): IOwsResource[] {\n const resources = context.features;\n return resources.filter(r => {\n const groupName = this.getLayerGroupFromFolder(r);\n return groupName && !Object.keys(Filtertypes).includes(groupName);\n });\n }\n\n /**\n * Get Resources without Folder property or Folder is only Layer-Filtertypes\n */\n getSingleResources(context: IOwsContext): IOwsResource[] {\n const resources = context.features;\n return resources.filter(r => {\n const groupName = this.getLayerGroupFromFolder(r);\n return !groupName || Object.keys(Filtertypes).includes(groupName);\n });\n }\n\n /** Resource --------------------------------------------------- */\n getResourceTitle(resource: IOwsResource): string {\n return resource.properties.title;\n }\n\n /**\n * The Folder property of IOwsResource\n * @returns string | `${TFiltertypes}/string`\n */\n getResourceFolder(resource: IOwsResource): string {\n return resource.properties.folder;\n }\n\n /**\n * returns name from Resource Folder if it is not only a Filtertype `TFiltertypes`\n */\n private getLayerGroupFromFolder(resource: IOwsResource) {\n const folderName = this.getResourceFolder(resource);\n if (folderName) {\n const folderParts = folderName.split('/');\n if (folderParts.length === 1) {\n if (!Filtertypes[folderName]) {\n return folderName\n }\n } else if (folderParts.length === 2) {\n const filtertype = folderParts[0];\n if (!Filtertypes[filtertype]) {\n console.warn(`Folder (${folderName}) should be named like: ${Object.keys(Filtertypes).map(k => `${k}/<FolderName>`).join(' | ')}`);\n }\n return folderParts[1];\n } else {\n console.log(`only one Folder hierarchy is implemented`, folderParts);\n }\n }\n }\n\n /**\n * FilterType in IOwsResource Folder property\n */\n getFilterType(resource: IOwsResource): TFiltertypes {\n if (resource.properties.folder) {\n const pathParts = resource.properties.folder.split('/');\n const first = pathParts[0];\n if (Filtertypes[first]) {\n return first as TFiltertypes;\n }\n }\n }\n\n getResourceUpdated(resource: IOwsResource) {\n return resource.properties.updated;\n }\n\n getResourceDate(resource: IOwsResource) {\n return (resource.properties.date) ? resource.properties.date : null;\n }\n\n getResourceOfferings(resource: IOwsResource): IOwsOffering[] {\n return (resource.properties.offerings) ? resource.properties.offerings : null;\n }\n\n /**\n * retrieve layer status active / inactive based on IOwsResource\n * @param resource: IOwsResource\n */\n isActive(resource: IOwsResource) {\n let active = true;\n if (resource.properties.active === false || resource.properties?.active) {\n active = resource.properties.active;\n }\n return active;\n }\n\n getResourceDescription(resource: IOwsResource): string {\n let description = '';\n if (resource.properties.abstract) {\n description = resource.properties.abstract;\n }\n return description;\n }\n\n /** OWS Extenson IEocOwsResource */\n getResourceOpacity(resource: IEocOwsResource): number {\n let opacity = 1;\n if (resource.properties?.opacity) {\n opacity = resource.properties.opacity;\n }\n return opacity;\n }\n\n /** OWS Extenson IEocOwsResource */\n getResourceAttribution(resource: IEocOwsResource): string {\n let attribution = '';\n if (resource.properties?.attribution) {\n attribution = resource.properties.attribution;\n } else if (resource.properties.rights) {\n attribution = resource.properties.rights;\n }\n return attribution;\n }\n\n /** OWS Extenson IEocOwsResource */\n getResourceShards(resource: IEocOwsResource): string {\n if (resource.properties?.shards) {\n return resource.properties.shards;\n }\n }\n\n /** OWS Extenson IEocOwsResource */\n getResourceMinMaxZoom(resource: IEocOwsResource, targetProjection: string = 'EPSG:4326'): { minZoom: number; maxZoom: number; } {\n const zooms = { minZoom: null, maxZoom: null };\n if (resource.properties.minZoom) {\n zooms.minZoom = resource.properties.minZoom;\n } else if (resource.properties.maxscaledenominator) { // *Max*ScaleDenom ~ *Min*Zoom\n zooms.minZoom = this.scaleDenominatorToZoom(resource.properties.maxscaledenominator, targetProjection) || null;\n }\n if (resource.properties.maxZoom) {\n zooms.maxZoom = resource.properties.maxZoom;\n } else if (resource.properties.minscaledenominator) { // *Min*ScaleDenom ~ *Max*Zoom\n zooms.maxZoom = this.scaleDenominatorToZoom(resource.properties.minscaledenominator, targetProjection) || null;\n }\n return zooms;\n }\n\n\n /**\n * e.g.\n * (array) value: '1984-01-01T00:00:00.000Z/1989-12-31T23:59:59.000Z/PT1S,1990-01-01T00:00:00.000Z/1994-12-31T23:59:59.000Z/PT1S,...'\n * (array) value: '1984-01-01T00:00:00.000Z/P1D,P1D/2000-01-01T00:00:00.000Z,...'\n * (array) value: '2000-01-01T00:00:00.000Z,2001-01-01T00:00:00.000Z,2002-01-01T00:00:00.000Z,...'\n * (single) value: '2016-01-01T00:00:00.000Z/2018-01-01T00:00:00.000Z/P1Y'\n */\n getTimeValueFromDimensions(values: IEocOwsTimeDimension['values'], period?: IEocOwsTimeDimension['display']['period']): ILayerIntervalAndPeriod | Array<string | ILayerIntervalAndPeriod> {\n if (values === null) {\n return;\n } else {\n const isList = /,/g.test(values);\n if (isList) {\n // values: `${string},${string}`\n const splitValues = values.split(',');\n if (splitValues.length > 0) {\n const parsed: Array<string | ILayerIntervalAndPeriod> = []; //\n for (const value of splitValues) {\n const parsedSingle = this.parseSingleTimeOrPeriod(value);\n if (typeof parsedSingle === 'object' && parsedSingle.interval) {\n if (!parsedSingle.periodicity && period) {\n parsedSingle.periodicity = period;\n } else if (!parsedSingle.periodicity && !period) {\n console.warn(`Interval without a period`, values, period);\n }\n }\n parsed.push(parsedSingle);\n }\n return parsed;\n }\n } else {\n // `${string}/${string}` | `${string}/${string}/P${string}`\n const parsedSingle = this.parseSingleTimeOrPeriod(values);\n if (typeof parsedSingle === 'object' && parsedSingle.interval) {\n if (!parsedSingle.periodicity && period) {\n parsedSingle.periodicity = period;\n } else if (!parsedSingle.periodicity && !period) {\n console.warn(`Interval without a period`, values, period);\n }\n return parsedSingle;\n } else if (typeof parsedSingle === 'string') {\n return [parsedSingle];\n }\n }\n }\n }\n\n /**\n * time could be:\n *\n * - date\n * - start/end/duration //Geoserver specific\n * - start/end\n * - start/duration, and duration/end\n */\n private parseSingleTimeOrPeriod(time: string): string | ILayerIntervalAndPeriod | null {\n const dateTime = DateTime.fromISO(time);\n if (dateTime.isValid) {\n return dateTime.toUTC().toISO();\n } else {\n // is Interval ----------------------------\n const interval = Interval.fromISO(time);\n if (interval.isValid) {\n const period = this.parseISO8601Period(time);\n const intervalObject: ILayerIntervalAndPeriod = {\n periodicity: period,\n interval: `${interval.start.toUTC().toISO()}/${interval.end.toUTC().toISO()}`\n };\n return intervalObject;\n } else {\n console.warn(`no Interval or not valid`, time);\n return null;\n }\n }\n }\n\n private parseISO8601Period(value: string): string {\n const periodMatches = value.match(/P\\d*[YMWD](T\\d\\d[HMS])*/);\n if (periodMatches) {\n return periodMatches[0];\n }\n }\n\n getResourceDimensions(resource: IEocOwsResource) {\n if (!resource.properties.dimensions) {\n return undefined;\n }\n\n const dims: ILayerDimensions = {};\n for (const d of resource.properties.dimensions) {\n const name = d.name;\n if (name === 'time') {\n dims.time = this.getTimeDimensions(resource.properties.dimensions);\n /** if dimensions are defined but the values are null */\n if (dims.time.values === null) {\n console.log('check to get time dimensions value from OGC Service later!!', resource);\n }\n } else if (name === 'elevation') {\n dims.elevation = this.getElevationDimension(resource.properties.dimensions);\n /** if dimensions are defined but the values are null */\n if (dims.elevation.values === null) {\n console.log('check to get elevation dimensions value from OGC Service later!!', resource);\n }\n } else {\n dims[name] = d;\n }\n }\n\n return dims;\n }\n\n getTimeDimensions(dimensions: IEocOwsResourceDimension[]): ILayerTimeDimension {\n let dim: ILayerTimeDimension = { values: null, units: null };\n const value = dimensions.find(d => d.name === 'time') as IEocOwsTimeDimension;\n if (!value) {\n return;\n }\n\n const parsedValues = this.getTimeValueFromDimensions(value.values, value?.display?.period);\n dim = {\n values: null,\n units: value.units,\n display: {}\n };\n\n /** check if is array or single value */\n if (Array.isArray(parsedValues)) {\n dim.values = parsedValues as (string[] | ILayerIntervalAndPeriod[]);\n /** don't set dim.display.period if it is an array because there could be different periods */\n // dim.display.period = ...\n } else if (parsedValues && typeof parsedValues !== 'string' && parsedValues.interval && parsedValues.periodicity) {\n dim.values = parsedValues;\n /** set dim.display.period from the parsed values */\n if (parsedValues.periodicity) {\n dim.display.period = parsedValues.periodicity;\n }\n }\n\n if (value?.display?.format) {\n dim.display.format = value.display.format;\n }\n\n return dim;\n }\n\n getElevationDimension(dimensions: IEocOwsResourceDimension[]): ILayerElevationDimension {\n const dim: ILayerElevationDimension = { values: null, units: null };\n const value = dimensions.find(d => d.name === 'elevation') as IEocOwsElevationDimension;\n if (!value) {\n return;\n } else {\n dim.values = value.value;\n dim.units = value.units;\n\n if (value.display) {\n dim.display = value.display;\n }\n\n return dim;\n }\n }\n\n\n /** Offering --------------------------------------------------- */\n getLayertypeFromOfferingCode(offering: IOwsOffering): TLayertype {\n if (isWmsOffering(offering.code)) {\n return WmsLayertype;\n } else if (isWmtsOffering(offering.code)) {\n return WmtsLayertype;\n } else if (isWfsOffering(offering.code)) {\n return WfsLayertype;\n } else if (isKmlOffering(offering.code)) {\n return KmlLayertype;\n } else if (isGeoJsonOffering(offering.code)) {\n return GeojsonLayertype;\n } else if (isXyzOffering(offering.code)) {\n return XyzLayertype;\n } else if (isTMSOffering(offering.code)) {\n return TmsLayertype;\n } else {\n return offering.code; // an offering can also be any other string.\n }\n }\n\n checkIfServiceOffering(offering: IOwsOffering): boolean {\n return (!offering.contents && offering.operations) ? true : false;\n }\n\n checkIfDataOffering(offering: IOwsOffering): boolean {\n return (offering.contents && !offering.operations) ? true : false;\n }\n\n /**\n * Helper function to extract legendURL from project specific ows Context\n * @param offering layer offering\n */\n getLegendUrl(offering: IOwsOffering) {\n let legendUrl = '';\n\n if (offering.styles) {\n const defaultStyle = offering.styles.find(style => style.default);\n if (defaultStyle) {\n return defaultStyle.legendURL;\n }\n } else if (offering.legendUrl) {\n legendUrl = offering.legendUrl;\n }\n return legendUrl;\n }\n\n\n /**\n * Get all Layers from the IOwsContext.\n *\n * The order of the layers is reversed to get the context drawing order!\n */\n getLayers(owc: IOwsContext, targetProjection: string): Observable<(Layer | LayerGroup)[]> {\n const layers$: Observable<Layer | LayerGroup>[] = [];\n /** For the order of Layers see IOwsContext['features'] */\n\n /**\n * LayerGroups\n *\n * e.g. if groupName: Layers/test -> a group \"test\" in the slot Layers will be created with the layer in it\n * e.g. if groupName: Overlays/test -> a group \"test\" in the slot Overlays will be created with the layer in it\n * if groupName is only: Layers | Overlays | Baselayers use layerResources\n */\n\n const resources = this.getResources(owc);\n const groups = [];\n resources.forEach(r => {\n const lg = this.createLayerOrGroupFromResource(r, owc, targetProjection, groups);\n layers$.push(lg);\n });\n\n return forkJoin(layers$)\n // making sure no undefined/null layers are returned\n .pipe(map(layers => layers.filter(layer => layer)))\n // reverse so layer order is like in the context\n .pipe(map(layers => layers.reverse()));\n }\n\n /**\n * Creates Layers or LayerGroups from IOwsResource and IOwsContext\n * Add uniqueGroups array to track already created groups\n */\n private createLayerOrGroupFromResource(resource: IOwsResource, context: IOwsContext, targetProjection: string, uniqueGroups: string[]) {\n const layergroupResources = this.getGroupResources(context);\n const groupName = this.getLayerGroupFromFolder(resource);\n\n /** Layers with folder property */\n if (groupName) {\n /** unique layergroupResources */\n if (!uniqueGroups.includes(groupName)) {\n uniqueGroups.push(groupName);\n /** reverse so layer order is like in the context */\n const includedResources = layergroupResources.filter(r => this.getLayerGroupFromFolder(r) === groupName).reverse();\n const layerGroup$ = this.createLayerGroup(groupName, includedResources, context, targetProjection);\n return layerGroup$;\n } else {\n return of(null);\n }\n } else {\n /** Single Layers */\n const layer$ = this.createLayerFromDefaultOffering(resource, context, targetProjection);\n return layer$;\n }\n }\n\n\n\n /**\n *\n * @param groupName string | `${TFiltertypes}/string`\n */\n createLayerGroup(groupName: string, includedResources: IOwsResource[], owc: IOwsContext, targetProjection: string): Observable<LayerGroup | StackedLayer> {\n const layers$: Observable<Layer>[] = [];\n let filterType = null;\n for (const resource of includedResources) {\n filterType = this.getFilterType(resource);\n layers$.push(this.createLayerFromDefaultOffering(resource, owc, targetProjection));\n }\n\n const layerGroup$ = forkJoin(layers$)\n // making sure no undefined layers are returned\n .pipe(map((layers: Layer[]) => layers.filter(layer => layer)))\n // putting layers in a LayerGroup\n .pipe(map((layers: Layer[]) => {\n if (layers.length) {\n /** if filterType is Baselayers -> create a merged Layer */\n if (filterType === Filtertypes.Baselayers) {\n const descriptionLayers = layers.filter(l => l.description); // filter empty elements\n const mergedDescription = descriptionLayers.map(i => i.description);\n const legendImages = layers.map(i => i.legendImg).filter(d => d); // filter empty elements\n const layerOptions: IStackedLayerOptions = {\n id: `${groupName}_${layers.map(i => i.id).join(' ')}`.replace(/\\s/g, '_'),\n name: groupName,\n layers: layers,\n filtertype: Filtertypes.Baselayers\n };\n if (mergedDescription.length) {\n layerOptions.description = mergedDescription.map((d, index) => this.generateAbstractFromLayerDescription(d, descriptionLayers[index].id)).join(';\\r\\n');\n }\n if (legendImages) {\n layerOptions.legendImg = legendImages[0];\n }\n\n const stackedLayer = new StackedLayer(layerOptions);\n return stackedLayer;\n } else {\n const layerGroup = new LayerGroup({\n id: `${groupName}_${layers.map(i => i.id).join(' ')}`.replace(/\\s/g, '_'),\n name: groupName,\n layers,\n filtertype: layers[0].filtertype // @TODO: can some layers have a different filter-type? -> All layers in a Group must be from the same filter type\n });\n return layerGroup;\n }\n }\n }))\n // making sure no undefined layers are returned\n .pipe(filter(lg => lg instanceof LayerGroup || lg instanceof Layer));\n\n return layerGroup$;\n }\n\n createLayerFromDefaultOffering(resource: IOwsResource, owc: IOwsContext, targetProjection: string): Observable<Layer> {\n const offerings = resource.properties?.offerings;\n if (offerings) {\n // TODO: allow Multiple offerings ???\n const offering = offerings.find(o => isWmsOffering(o.code))\n || offerings.find(o => isWmtsOffering(o.code))\n || offerings.find(o => isWfsOffering(o.code))\n || offerings.find(o => isTMSOffering(o.code))\n || offerings[0];\n return this.createLayerFromOffering(offering, resource, owc, targetProjection);\n } else {\n return of(null);\n }\n }\n\n createLayerFromOffering(offering: IOwsOffering, resource: IOwsResource, context: IOwsContext, targetProjection: string): Observable<Layer> {\n const layerType = this.getLayertypeFromOfferingCode(offering);\n if (this.isRasterLayerType(layerType) && this