UNPKG

@itwin/core-common

Version:

iTwin.js components common to frontend and backend

495 lines • 22.1 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module MapLayers */ import { assert } from "@itwin/core-bentley"; import { BackgroundMapProvider, BackgroundMapType } from "./BackgroundMapProvider"; ; /** Normalized representation of a [[MapSubLayerProps]] for which values * have been validated and default values have been applied where explicit values not defined. * A map sub layer represents a set of objects within the layer that can be controlled separately. These * are produced only from map servers that produce images on demand and are not supported by tiled (cached) servers. * This class can represent a hierarchy, in this case a sub layer is visible only if all its ancestors are also visible. * @see [[MapLayerSettings]] * @public */ export class MapSubLayerSettings { /** Typically Name is a single word used for machine-to-machine communication while the Title is for the benefit of humans (WMS) */ name; /** Title. */ title; /** If true the sub layer is visible. If part of a hierarchy, a sub layer is visible only if its ancestors are also visible. */ visible; /** A unique string or number that may be used to identify the sub layer (ArcGIS) */ id; /** One or more sublayer children */ children; /** sublayer parent. */ parent; constructor(name, title, visible, id, parent, children) { this.name = name; this.title = title; this.visible = visible !== undefined && visible; this.id = (id === undefined) ? this.name : id; this.parent = parent; this.children = children; } /** Construct from JSON, performing validation and applying default values for undefined fields. */ static fromJSON(json) { return new MapSubLayerSettings(json.name, json.title, json.visible, (json.id === json.name) ? undefined : json.id, json.parent, json.children); } toJSON() { const props = { name: this.name, visible: this.visible }; if (undefined !== this.id && this.id !== this.name) props.id = this.id; if (undefined !== this.title) props.title = this.title; if (this.children) props.children = [...this.children]; if (undefined !== this.parent) props.parent = this.parent; return props; } /** Creating a copy of this MapSubLayer, optionally modifying some if its properties */ clone(changedProps) { if (undefined === changedProps) return this; const props = { name: undefined !== changedProps.name ? changedProps.name : this.name, id: undefined !== changedProps.id ? changedProps.id : this.id, visible: undefined !== changedProps.visible ? changedProps.visible : this.visible, parent: undefined !== changedProps.parent ? changedProps.parent : this.parent, children: undefined !== changedProps.children ? changedProps.children.slice() : this.children?.slice(), title: undefined !== changedProps.title ? changedProps.title : this.title, }; return MapSubLayerSettings.fromJSON(props); } /** @internal */ displayMatches(other) { return this.name === other.name && this.visible === other.visible; } /** return true if this sublayer is named. */ get isNamed() { return this.name.length > 0; } /** return true if this sublayer is a leaf (has no children) */ get isLeaf() { return this.children === undefined || this.children.length === 0; } /** return true if this sublayer is an unnamed group */ get isUnnamedGroup() { return !this.isLeaf && !this.isNamed; } /** return a string representing this sublayer id (converting to string if underlying id is number) */ get idString() { return (typeof this.id === "number") ? this.id.toString(10) : this.id; } } /** The target onto which to drape a model map layer. * @beta */ export var ModelMapLayerDrapeTarget; (function (ModelMapLayerDrapeTarget) { /** Drape only onto the globe. */ ModelMapLayerDrapeTarget[ModelMapLayerDrapeTarget["Globe"] = 1] = "Globe"; /** Drape only onto all attached reality data. */ ModelMapLayerDrapeTarget[ModelMapLayerDrapeTarget["RealityData"] = 2] = "RealityData"; /** Drape only onto all models within the iModel. */ ModelMapLayerDrapeTarget[ModelMapLayerDrapeTarget["IModel"] = 4] = "IModel"; })(ModelMapLayerDrapeTarget || (ModelMapLayerDrapeTarget = {})); /** Abstract base class for normalized representation of a [[MapLayerProps]] for which values have been validated and default values have been applied where explicit values not defined. * This class is extended by [[ImageMapLayerSettings]] and [ModelMapLayerSettings]] to create the settings for image and model based layers. * One or more map layers may be included within [[MapImagerySettings]] object. * @see [[MapImagerySettings]] * @public */ export class MapLayerSettings { visible; name; transparency; transparentBackground; /** @internal */ constructor(name, visible = true, transparency = 0, transparentBackground = true) { this.name = name; this.visible = visible; this.transparentBackground = transparentBackground; this.transparency = transparency; } /** Create a map layer settings from its JSON representation. */ static fromJSON(props) { return undefined !== props.modelId ? ModelMapLayerSettings.fromJSON(props) : ImageMapLayerSettings.fromJSON(props); } /** @internal */ _toJSON() { const props = { name: this.name, visible: this.visible, }; if (0 !== this.transparency) props.transparency = this.transparency; if (this.transparentBackground === false) props.transparentBackground = this.transparentBackground; return props; } /** @internal */ cloneProps(changedProps) { return { name: undefined !== changedProps.name ? changedProps.name : this.name, visible: undefined !== changedProps.visible ? changedProps.visible : this.visible, transparency: undefined !== changedProps.transparency ? changedProps.transparency : this.transparency, transparentBackground: undefined !== changedProps.transparentBackground ? changedProps.transparentBackground : this.transparentBackground, }; } /** @internal */ displayMatches(other) { return this.name === other.name && this.visible === other.visible && this.transparency === other.transparency && this.transparentBackground === other.transparentBackground; } /** @internal */ matchesNameAndSource(name, source) { return this.name === name && this.source === source; } } /** Normalized representation of a [[ImageMapLayerProps]] for which values have been validated and default values have been applied where explicit values not defined. * Image map layers are created from servers that produce images that represent map tiles. Map layers map also be represented by models. * One or more map layers may be included within [[MapImagerySettings]] object. * @see [[MapImagerySettings]] * @see [[ModelMapLayerSettings]] for model based map layer settings. * @public */ export class ImageMapLayerSettings extends MapLayerSettings { formatId; url; userName; password; accessKey; /** List of query parameters to append to the settings URL and persisted as part of the JSON representation. * @note Sensitive information like user credentials should be provided in [[unsavedQueryParams]] to ensure it is never persisted. * @beta */ savedQueryParams; /** List of query parameters that will get appended to the settings URL that should *not* be be persisted part of the JSON representation. * @beta */ unsavedQueryParams; /** Properties specific to the map layer provider. * @beta */ properties; subLayers; get source() { return this.url; } /** @internal */ constructor(props) { const transparentBackground = props.transparentBackground ?? true; super(props.name, props.visible, props.transparency, transparentBackground); this.formatId = props.formatId; this.url = props.url; this.accessKey = props.accessKey; if (props.queryParams) { this.savedQueryParams = { ...props.queryParams }; } if (props.properties) { this.properties = { ...props.properties }; } this.subLayers = []; if (!props.subLayers) return; for (const subLayerProps of props.subLayers) { const subLayer = MapSubLayerSettings.fromJSON(subLayerProps); if (subLayer) this.subLayers.push(subLayer); } } static fromJSON(props) { return new this(props); } /** return JSON representation of this MapLayerSettings object */ toJSON() { const props = super._toJSON(); props.url = this.url; props.formatId = this.formatId; if (this.subLayers.length > 0) props.subLayers = this.subLayers.map((x) => x.toJSON()); if (this.savedQueryParams) props.queryParams = { ...this.savedQueryParams }; if (this.properties) { props.properties = structuredClone(this.properties); } return props; } /** Create a copy of this MapLayerSettings, optionally modifying some of its properties. * @param changedProps JSON representation of the properties to change. * @returns A MapLayerSettings with all of its properties set to match those of `this`, except those explicitly defined in `changedProps`. */ clone(changedProps) { const clone = ImageMapLayerSettings.fromJSON(this.cloneProps(changedProps)); // Clone members not part of MapLayerProps clone.userName = this.userName; clone.password = this.password; if (this.unsavedQueryParams) clone.unsavedQueryParams = { ...this.unsavedQueryParams }; if (this.savedQueryParams) clone.savedQueryParams = { ...this.savedQueryParams }; return clone; } /** @internal */ cloneProps(changedProps) { const props = super.cloneProps(changedProps); props.formatId = changedProps.formatId ?? this.formatId; props.url = changedProps.url ?? this.url; props.accessKey = changedProps.accessKey ?? this.accessKey; props.subLayers = changedProps.subLayers ?? this.subLayers; if (changedProps.queryParams) { props.queryParams = { ...changedProps.queryParams }; } else if (this.savedQueryParams) { props.queryParams = { ...this.savedQueryParams }; } if (changedProps.properties) { props.properties = { ...changedProps.properties }; } else if (this.properties) { props.properties = { ...this.properties }; } return props; } /** @internal */ displayMatches(other) { if (!(other instanceof ImageMapLayerSettings) || !super.displayMatches(other)) return false; if (this.userName !== other.userName || this.password !== other.password || this.subLayers.length !== other.subLayers.length) { return false; } for (let i = 0; i < this.subLayers.length; i++) if (!this.subLayers[i].displayMatches(other.subLayers[i])) return false; return true; } /** Return a sublayer matching id -- or undefined if not found */ subLayerById(id) { return id === undefined ? undefined : this.subLayers.find((subLayer) => subLayer.id === id); } hasInvisibleAncestors(subLayer) { if (!subLayer || !subLayer.parent) return false; const parent = this.subLayerById(subLayer.parent); if (!parent) return false; // Visibility of named group has no impact on the visibility of children (only unnamed group does) // i.e For WMS, its should be possible to request a child layer when its parent is not visible (if the parent is also named) return (!parent.visible && !parent.isNamed) || this.hasInvisibleAncestors(parent); } /** Return true if sublayer is visible -- testing ancestors for visibility if they exist. */ isSubLayerVisible(subLayer) { if (!subLayer.visible) return false; return !this.hasInvisibleAncestors(subLayer); } /** Return true if all sublayers are invisible. */ get allSubLayersInvisible() { if (this.subLayers.length === 0) return false; return this.subLayers.every((subLayer) => (subLayer.isUnnamedGroup || !this.isSubLayerVisible(subLayer))); } /** Return the children for a sublayer */ getSubLayerChildren(subLayer) { if (!subLayer.children) return undefined; const children = new Array(); subLayer.children.forEach((childId) => { const child = this.subLayerById(childId); if (child !== undefined) children.push(child); }); return children; } /** @internal */ static mapTypeName(type) { switch (type) { case BackgroundMapType.Aerial: return "Aerial Imagery"; default: case BackgroundMapType.Hybrid: return "Aerial Imagery with labels"; case BackgroundMapType.Street: return "Streets"; } } setCredentials(userName, password) { this.userName = userName; this.password = password; } /** Collect all query parameters * @beta */ collectQueryParams() { let queryParams = {}; if (this.savedQueryParams) queryParams = { ...this.savedQueryParams }; if (this.unsavedQueryParams) queryParams = { ...queryParams, ...this.unsavedQueryParams }; return queryParams; } } /** Normalized representation of a [[ModelMapLayerProps]] for which values have been validated and default values have been applied where explicit values not defined. * Model map layers are produced from models, typically from two dimensional geometry that may originate in a GIS system. * One or more map layers may be included within [[MapImagerySettings]] object. * @see [[MapImagerySettings]] * @see [[ImageMapLayerSettings]] for image based map layer settings. * @public */ export class ModelMapLayerSettings extends MapLayerSettings { /** Specifies the target onto which to drape this model map layer. Defaults to [ModelMapLayerDrapeTarget.Globe]($common). * @beta */ drapeTarget; modelId; get source() { return this.modelId; } /** @internal */ constructor(modelId, name, visible = true, transparency = 0, transparentBackground = true, drapeTarget = ModelMapLayerDrapeTarget.Globe) { super(name, visible, transparency, transparentBackground); this.modelId = modelId; this.drapeTarget = drapeTarget; } /** Construct from JSON, performing validation and applying default values for undefined fields. */ static fromJSON(json) { const transparentBackground = (json.transparentBackground === undefined) ? true : json.transparentBackground; return new this(json.modelId, json.name, json.visible, json.transparency, transparentBackground, json.drapeTarget); } /** return JSON representation of this MapLayerSettings object */ toJSON() { const props = super._toJSON(); props.modelId = this.modelId; if (this.drapeTarget !== ModelMapLayerDrapeTarget.Globe) props.drapeTarget = this.drapeTarget; return props; } /** Create a copy of this MapLayerSettings, optionally modifying some of its properties. * @param changedProps JSON representation of the properties to change. * @returns A MapLayerSettings with all of its properties set to match those of `this`, except those explicitly defined in `changedProps`. */ clone(changedProps) { return ModelMapLayerSettings.fromJSON(this.cloneProps(changedProps)); } /** @internal */ cloneProps(changedProps) { const props = super.cloneProps(changedProps); props.modelId = changedProps.modelId ?? this.modelId; props.drapeTarget = changedProps.drapeTarget ?? this.drapeTarget; return props; } /** @internal */ displayMatches(other) { if (!(other instanceof ModelMapLayerSettings) || !super.displayMatches(other)) return false; return this.modelId === other.modelId; } /** Return true if all sublayers are invisible (always false as model layers do not include sublayers). */ get allSubLayersInvisible() { return false; } } /** A [[ImageMapLayerSettings]] that can serve as the base layer for a [[MapImagerySettings]]. * The base layer supports all of the same options as any other layer, but also allows for simplified configuration based * on a small set of known supported [[BackgroundMapProvider]]s like [Bing Maps](https://www.microsoft.com/en-us/maps). * If the base layer was configured from such a provider, that information will be preserved and can be queried; this allows * the imagery provider and/or type to be easily modified. * @see [[MapImagerySettings.backgroundBase]]. * @public */ export class BaseMapLayerSettings extends ImageMapLayerSettings { _provider; /** The provider from which this base layer was configured, if any. */ get provider() { return this._provider; } /** Create a base layer from its JSON representation. * TODO: This, MapLayerSettings.fromJSON, and MapSubLayerSettings.fromJSON should never return undefined. * That means they should not accept undefined for props and should define props such that it fully describes the * layer - e.g., url and name must be defined. */ static fromJSON(props) { const settings = super.fromJSON(props); assert(settings instanceof BaseMapLayerSettings); if (props.provider) settings._provider = BackgroundMapProvider.fromJSON(props.provider); return settings; } /** Convert this layer to its JSON representation. */ toJSON() { const props = super.toJSON(); if (this.provider) props.provider = this.provider.toJSON(); return props; } /** @internal */ cloneProps(changedProps) { const props = super.cloneProps(changedProps); if (changedProps.provider) props.provider = changedProps.provider; else if (this.provider) props.provider = this.provider.toJSON(); return props; } /** Create a copy of this layer. */ clone(changedProps) { const prevUrl = this.url; const clone = BaseMapLayerSettings.fromJSON(this.cloneProps(changedProps)); if (this.provider && prevUrl !== this.url) clone._provider = undefined; return clone; } /** Create a base layer from a BackgroundMapProvider. */ static fromProvider(provider, options) { let formatId, url, name; switch (provider.name) { case "BingProvider": default: formatId = "BingMaps"; let imagerySet; switch (provider.type) { case BackgroundMapType.Street: imagerySet = "Road"; break; case BackgroundMapType.Aerial: imagerySet = "Aerial"; break; case BackgroundMapType.Hybrid: default: imagerySet = "AerialWithLabels"; break; } name = `Bing Maps: ${ImageMapLayerSettings.mapTypeName(provider.type)}`; url = `https://dev.virtualearth.net/REST/v1/Imagery/Metadata/${imagerySet}?o=json&incl=ImageryProviders&key={bingKey}`; break; case "MapBoxProvider": formatId = "MapboxImagery"; name = `MapBox: ${ImageMapLayerSettings.mapTypeName(provider.type)}`; switch (provider.type) { case BackgroundMapType.Street: url = "https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/"; break; case BackgroundMapType.Aerial: url = "https://api.mapbox.com/styles/v1/mapbox/satellite-v9/tiles/"; break; case BackgroundMapType.Hybrid: url = "https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v11/tiles/"; break; } break; } const settings = super.fromJSON({ name, formatId, url, transparentBackground: false, visible: !options?.invisible, transparency: options?.transparency, }); assert(undefined !== settings); assert(settings instanceof BaseMapLayerSettings); settings._provider = provider; return settings; } /** @internal */ static fromBackgroundMapProps(props) { return this.fromProvider(BackgroundMapProvider.fromBackgroundMapProps(props)); } /** @alpha */ cloneWithProvider(provider) { return BaseMapLayerSettings.fromProvider(provider, { invisible: !this.visible, transparency: this.transparency }); } } //# sourceMappingURL=MapLayerSettings.js.map