@itwin/core-common
Version:
iTwin.js components common to frontend and backend
495 lines • 22.1 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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