@igo2/context
Version:
1,326 lines (1,318 loc) • 281 kB
JavaScript
import * as i0 from '@angular/core';
import { inject, Injectable, input, Component, NgModule, output, computed, Directive, ChangeDetectionStrategy, viewChild, model, Input, DestroyRef, signal, ChangeDetectorRef } from '@angular/core';
import { DOCUMENT, AsyncPipe, NgClass, KeyValuePipe } from '@angular/common';
import * as i1$1 from '@angular/forms';
import { UntypedFormBuilder, Validators, FormsModule, ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
import * as i4 from '@angular/material/button';
import { MatButtonModule } from '@angular/material/button';
import * as i1 from '@angular/material/button-toggle';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatOptionModule } from '@angular/material/core';
import { MatDividerModule } from '@angular/material/divider';
import * as i1$2 from '@angular/material/form-field';
import { MatFormFieldModule } from '@angular/material/form-field';
import * as i2 from '@angular/material/input';
import { MatInputModule } from '@angular/material/input';
import * as i5 from '@angular/material/select';
import { MatSelectModule } from '@angular/material/select';
import { SpinnerComponent } from '@igo2/common/spinner';
import { ConfigService } from '@igo2/core/config';
import { LanguageService, IgoLanguageModule } from '@igo2/core/language';
import { MessageService } from '@igo2/core/message';
import { BehaviorSubject, Subject, of, Observable, ReplaySubject, timer, catchError as catchError$1, tap as tap$1, switchMap as switchMap$1, map as map$1, EMPTY, combineLatest } from 'rxjs';
import { skip, first, catchError, tap, map, mergeMap, debounceTime, take, switchMap, filter, debounce } from 'rxjs/operators';
import { HttpParams, HttpClient } from '@angular/common/http';
import { AuthService } from '@igo2/auth';
import { RouteService } from '@igo2/core/route';
import { StorageService } from '@igo2/core/storage';
import { QueryFormat, isLayerGroupOptions, ID_GROUP_PREFIX, isLayerGroup, isLayerItem, getLayerOptionIdentifier, findParentId, generateIdFromSourceOptions, ExportService, FeatureDataSource, featureRandomStyleFunction, featureRandomStyle, VectorLayer, ClusterDataSource, MapBrowserComponent, LayerService, StyleListService, StyleService, sortLayersByZindex, mergeLayersOptions, MapService, isLayerItemOptions, moveToOlFeatures, FeatureMotion, FeatureDetailsComponent } from '@igo2/geo';
import { ObjectUtils, uuid, downloadContent } from '@igo2/utils';
import GeoJSON from 'ol/format/GeoJSON';
import olPoint from 'ol/geom/Point';
import Cluster from 'ol/source/Cluster';
import olVectorSource from 'ol/source/Vector';
import * as i2$1 from '@angular/material/autocomplete';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import * as i3 from '@angular/material/list';
import { MatListModule } from '@angular/material/list';
import * as i5$1 from '@ngx-translate/core';
import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Clipboard } from '@angular/cdk/clipboard';
import * as i3$1 from '@angular/material/icon';
import { MatIconModule } from '@angular/material/icon';
import * as i6 from '@angular/material/tooltip';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MediaService } from '@igo2/core/media';
import * as i5$2 from '@angular/material/menu';
import { MatMenuTrigger, MatMenuModule } from '@angular/material/menu';
import { StopPropagationDirective } from '@igo2/common/stop-propagation';
import * as i8 from '@angular/material/checkbox';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialog } from '@angular/material/dialog';
import { ActionStore, ActionbarMode, ActionbarComponent } from '@igo2/common/action';
import { CollapsibleComponent } from '@igo2/common/collapsible';
import { ConfirmDialogService } from '@igo2/common/confirm-dialog';
import { SORT_ALPHA_ON_ICON, SORT_ALPHA_OFF_ICON, IgoIconComponent } from '@igo2/common/icon';
import { ListComponent, ListItemDirective } from '@igo2/common/list';
import * as i6$1 from '@angular/material/radio';
import { MatRadioModule } from '@angular/material/radio';
import * as oleasing from 'ol/easing';
import * as olproj from 'ol/proj';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { CustomHtmlComponent } from '@igo2/common/custom-html';
import { MatTabsModule } from '@angular/material/tabs';
import * as i1$3 from '@angular/material/sidenav';
import { MatSidenavModule } from '@angular/material/sidenav';
import { Title } from '@angular/platform-browser';
import { getEntityTitle } from '@igo2/common/entity';
import { FlexibleComponent } from '@igo2/common/flexible';
import { PanelComponent } from '@igo2/common/panel';
const ServiceType = [
'wms',
'wmts',
'arcgisrest',
'imagearcgisrest',
'tilearcgisrest'
];
var ServiceTypeEnum;
(function (ServiceTypeEnum) {
ServiceTypeEnum[ServiceTypeEnum["wms"] = 0] = "wms";
ServiceTypeEnum[ServiceTypeEnum["wmts"] = 1] = "wmts";
ServiceTypeEnum[ServiceTypeEnum["arcgisrest"] = 2] = "arcgisrest";
ServiceTypeEnum[ServiceTypeEnum["imagearcgisrest"] = 3] = "imagearcgisrest";
ServiceTypeEnum[ServiceTypeEnum["tilearcgisrest"] = 4] = "tilearcgisrest";
})(ServiceTypeEnum || (ServiceTypeEnum = {}));
const BaseLayerParamsKeys = [
'visible',
'opacity',
'zIndex',
'parentId'
];
const LayerParamsKeys = [
'id',
'index',
'names',
'type',
'version',
'queryString',
...BaseLayerParamsKeys
];
const GroupParamsKeys = [
'title',
'id',
'expanded',
...BaseLayerParamsKeys
];
const PositionParamsKeys = [
'center',
'zoom',
'rotation',
'projection'
];
const SHARE_MAP_KEYS_DEFAULT_OPTIONS = {
context: 'ctx',
urls: 'urls',
position: 'pos',
layers: 'layers',
groups: 'groups',
center: 'ctr',
zoom: 'z',
projection: 'p',
rotation: 'r',
opacity: 'o'
};
function shareMapKeyDefs(options) {
return {
contextKey: options.context,
urlsKey: options.urls,
languageKey: options.languageKey,
pos: {
key: options.position,
params: {
zoom: {
key: options.zoom,
parse: (params) => parseIntergerParam(params, options.zoom)
},
center: {
key: '@',
parse: parseCenter,
stringify: stringifyCenter
},
rotation: {
key: options.rotation,
parse: (params) => parseRotation(params, options.rotation),
stringify: formatNumber
},
projection: {
key: options.projection,
parse: (params) => extractParam(params, options.projection)
}
}
},
layers: {
key: options.layers,
params: {
index: undefined,
id: {
key: 'id',
parse: (params) => extractParam(params, 'id'),
stringify: (value) => `${value}`
},
names: {
key: 'n',
parse: (params) => {
const param = extractParam(params, 'n');
if (!param)
return undefined;
const unbracketed = param.slice(1, -1);
return decodeURIComponent(unbracketed);
},
stringify: (value) => `[${encodeURIComponent(value)}]`
},
opacity: {
key: 'o',
parse: (params) => parseFloatParam(params, 'o')
},
visible: {
key: 'v',
parse: (params) => parseBooleanParam(params, 'v'),
stringify: stringifyBoolean
},
type: {
key: 't',
parse: (params) => parseLayerType(params, 't'),
stringify: stringifyType
},
zIndex: {
key: 'z',
parse: (params) => parseIntergerParam(params, 'z')
},
parentId: {
key: 'pid',
parse: (params) => extractParam(params, 'pid')
},
version: {
key: 'vrn',
parse: (params) => extractParam(params, 'vrn')
},
queryString: {
key: 'q',
parse: (params) => extractParam(params, 'q')
}
}
},
groups: {
key: options.groups,
params: {
id: {
key: 'id',
parse: (params) => extractIdParam(params, 'id')
},
parentId: {
key: 'pid',
parse: (params) => extractIdParam(params, 'pid')
},
title: {
key: 't',
parse: (params) => {
const param = extractParam(params, 't');
return param ? decodeURIComponent(param) : undefined;
},
stringify: (value) => encodeURIComponent(value)
},
visible: {
key: 'v',
parse: (params) => parseBooleanParam(params, 'v'),
stringify: stringifyBoolean
},
opacity: {
key: 'o',
parse: (params) => parseFloatParam(params, 'o')
},
zIndex: {
key: 'z',
parse: (params) => parseIntergerParam(params, 'z')
},
expanded: {
key: 'e',
parse: (params) => parseBooleanParam(params, 'e'),
stringify: stringifyBoolean
}
}
}
};
}
function stringifyBoolean(value) {
return value ? '1' : '0';
}
function parseBoolean(params) {
return !!parseInt(params);
}
function stringifyType(value) {
return String(ServiceTypeEnum[value]);
}
function parseLayerType(params, key) {
const param = extractParam(params, key);
if (!param) {
return;
}
const type = Number(param);
return ServiceTypeEnum[type];
}
function stringifyCenter(values) {
return values.map(formatNumber).join(',');
}
function parseCenter(params) {
if (!params?.startsWith('@')) {
return;
}
const paramsSplitted = params.split(',');
const index = paramsSplitted.findIndex((param) => param.startsWith('@'));
return [
Number(paramsSplitted[index].slice(1)),
Number(paramsSplitted[index + 1])
];
}
function parseRotation(params, key) {
const param = extractParam(params, key);
if (!param) {
return;
}
const degree = parseInteger(param);
return (degree * Math.PI) / 180;
}
function parseIntergerParam(params, key) {
const param = extractParam(params, key);
if (!param) {
return;
}
return parseInteger(param);
}
function parseBooleanParam(params, key) {
const param = extractParam(params, key);
if (!param) {
return;
}
return parseBoolean(param);
}
function parseFloatParam(params, key) {
const param = extractParam(params, key);
if (!param) {
return;
}
return parseFloat(param);
}
function extractParam(params, suffix) {
return params
?.split(',')
.find((param) => param.endsWith(suffix))
?.slice(0, -suffix.length);
}
function extractIdParam(params, suffix) {
const paramsSplited = params.split(',');
// Find the parameter that ends with exact id/pid and isn't part of another word
const param = paramsSplited.find((param) => {
const key = param.slice(-suffix.length);
if (key !== suffix) {
return false;
}
if (suffix === 'pid') {
return true;
}
const prefixChar = param.slice(-suffix.length - 1, -suffix.length);
return prefixChar !== 'p';
});
return param?.slice(0, -suffix.length);
}
function parseInteger(value) {
return parseInt(value, 10);
}
function formatNumber(value) {
return value.toFixed(5).replace(/\.([^0]+)0+$/, '.$1');
}
function buildDataSourceOptions(type, url, layers, version) {
const isLayerType = [
'wmts',
'arcgisrest',
'imagearcgisrest',
'tilearcgisrest'
].includes(type);
const arcgisClause = type === 'arcgisrest' ||
type === 'imagearcgisrest' ||
type === 'tilearcgisrest';
const params = type === 'wms' ? { LAYERS: layers.join(','), VERSION: version } : undefined;
const layer = isLayerType ? layers.join(',') : undefined;
const baseParams = {
type: type,
url,
params,
layer,
version: type === 'wmts' ? '1.0.0' : undefined
};
if (arcgisClause) {
return {
...baseParams,
queryable: true,
queryFormat: QueryFormat.ESRIJSON
};
}
return baseParams;
}
function getFlattenOptions(options) {
return options.reduce((accumulator, option) => {
if (isLayerGroupOptions(option)) {
const children = option.children
? getFlattenOptions(option.children)
: [];
accumulator.push(option, ...children);
}
else {
accumulator.push(option);
}
return accumulator;
}, []);
}
/**
* Checks if the provided query parameters contain legacy parameter pairs
* (e.g., layers and URLs) that indicate older configuration formats.
*/
function hasLegacyParams(params, optionsLegacy) {
const { layersKey, wmsUrlKey, wmsLayersKey, wmtsUrlKey, wmtsLayersKey, arcgisUrlKey, arcgisLayersKey, iarcgisUrlKey, iarcgisLayersKey, tarcgisUrlKey, tarcgisLayersKey } = optionsLegacy;
// Define valid legacy parameter pairs
const legacyPairs = [
[layersKey, wmsUrlKey],
[wmsLayersKey, wmsUrlKey],
[wmtsLayersKey, wmtsUrlKey],
[arcgisLayersKey, arcgisUrlKey],
[iarcgisLayersKey, iarcgisUrlKey],
[tarcgisLayersKey, tarcgisUrlKey]
].filter(([layer, url]) => layer && url);
// Check if any legacy pair exists in the query parameters
return legacyPairs.some(([layer, url]) => getParamValue(params, layer) && getParamValue(params, url));
}
function hasModernShareParams(params, keysDefinitions) {
const { contextKey, groups } = keysDefinitions;
const hasGroups = !!getParamValue(params, groups.key);
const hasContext = !!getParamValue(params, contextKey);
return hasContext || hasGroups;
}
function getParamValue(params, key) {
const value = params[key];
return value !== '' ? value : undefined;
}
class ShareMapEncoder {
SHARE_MAP_DEFS;
document;
context;
language;
constructor(SHARE_MAP_DEFS, document) {
this.SHARE_MAP_DEFS = SHARE_MAP_DEFS;
this.document = document;
}
generateUrl(map, context) {
this.context = context;
const layers = [
map.layerController.baseLayer,
...map.layerController.layersFlattened
].filter(Boolean);
const urlParams = this.getBaseUrlConfig(map.viewController);
this.buildQueryUrl(layers, urlParams);
const [baseUrl] = this.document.location.href.split('?');
const queryString = urlParams.toString();
return queryString !== '' ? `${baseUrl}?${queryString}` : baseUrl;
}
/**
* Replaces local group IDs with unique IDs to avoid conflicts and have short URL.
* This is necessary for sharing the map context.
*/
replaceGroupLocalIds(layers) {
const idMap = new Map();
const existingIds = new Set(layers.map((layer) => layer.id).filter(Boolean));
// eslint-disable-next-line prefer-const
let counter = 1;
layers.forEach((layer) => {
if (layer.id && layer.id.toString().includes(ID_GROUP_PREFIX)) {
const newId = this.getUniqueId(existingIds, counter);
idMap.set(layer.id, newId);
layer.options.id = newId;
}
});
}
getUniqueId(existingIds, counter) {
while (existingIds.has(String(counter))) {
counter++;
}
existingIds.add(String(counter));
return counter;
}
getCurrentContext() {
return ObjectUtils.removeUndefined({
layers: this.context?.layers,
center: this.context?.map.view.center,
projection: this.context?.map.view.projection,
zoom: this.context?.map.view.zoom,
rotation: this.context?.map.view.rotation
});
}
/**
* Filters layers to include only LayerGroups or LayerItems with a valid ServiceType.
*/
isLayerSharable(layers) {
return layers.filter((layer) => isLayerGroup(layer) ||
(isLayerItem(layer) &&
ServiceType.includes(layer.dataSource?.options?.type)));
}
/**
* Extracts only LayerGroup items from the filtered layers.
*/
getLayerGroups(layers) {
return layers.filter(isLayerGroup);
}
/**
* Extracts only LayerItem items from the filtered layers.
*/
getLayerItems(layers) {
return layers.filter(isLayerItem);
}
buildQueryUrl(layers, urlParams) {
const layersSharable = this.isLayerSharable(layers);
this.replaceGroupLocalIds(layersSharable);
const layersChanged = this.getFilteredMapLayers(layersSharable);
const groups = this.getLayerGroups(layersChanged);
const groupsQueryValue = this.buildGroupsQueryValue(groups);
const layersByService = this.generateLayersOptionsByService(this.getLayerItems(layersChanged));
const [urls, layerParams] = this.buildLayersQueryValues(layersByService);
if (urls.length > 0) {
urlParams.set(this.SHARE_MAP_DEFS.urlsKey, urls.join(','));
}
if (layerParams.length > 0) {
urlParams.set(this.SHARE_MAP_DEFS.layers.key, layerParams.join(';'));
}
if (groupsQueryValue) {
urlParams.set(this.SHARE_MAP_DEFS.groups.key, groupsQueryValue);
}
}
/**
* Retrieves the current context layers and maps them by identifier.
*/
getContextLayersMap() {
const ctxLayers = this.getCurrentContext()?.layers || [];
const ctxFlattened = getFlattenOptions(ctxLayers);
return new Map(ctxFlattened.map((lctx) => {
const identifier = getLayerOptionIdentifier(lctx);
return [identifier, lctx];
}));
}
/**
* Filters layers and context layers based on visibility, opacity, and expanded state.
*/
getFilteredMapLayers(filteredLayers) {
const ctxLayersMap = this.getContextLayersMap();
return filteredLayers.filter((layer) => {
const mapLayerIdentifier = getLayerOptionIdentifier(layer.options);
if (!mapLayerIdentifier)
return false;
const ctxLayer = ctxLayersMap.get(mapLayerIdentifier);
if (!ctxLayer)
return true;
const visibilityChange = layer.visible !== (ctxLayer.visible ?? true);
const opacityChange = layer.opacity !== (ctxLayer.opacity ?? 1);
const layerParentId = this.getIdsNestedParent(layer)?.join('.');
const ctxParentId = ctxLayer.parentId ?? findParentId(this.context.layers, ctxLayer);
const parentIdChange = ctxParentId !== layerParentId;
let expandedChange = false;
let titleChange = false;
if (isLayerGroup(layer) && isLayerGroupOptions(ctxLayer)) {
// Check if the layer group was assign an local id
// If so, we don't support any change for this case
if (String(ctxLayer.id).includes(ID_GROUP_PREFIX)) {
return false;
}
expandedChange = (ctxLayer.expanded ?? false) !== layer.expanded;
titleChange = (ctxLayer.title ?? false) !== layer.title;
}
return (visibilityChange ||
opacityChange ||
expandedChange ||
titleChange ||
parentIdChange);
});
}
getIdsNestedParent(node, ids) {
if (!node.parent)
return ids;
if (node.parent) {
if (!ids) {
ids = [node.parent.id];
}
else {
ids.unshift(node.parent.id);
}
return this.getIdsNestedParent(node.parent, ids);
}
return ids;
}
generateLayersOptionsByService(layers) {
const layersByUrl = new Map();
layers.forEach((layer) => {
const [url, params] = this.generateLayerOption(layer);
if (!layersByUrl.has(url)) {
layersByUrl.set(url, [params]);
}
else {
layersByUrl.get(url).push(params);
}
});
let customIndex = 0;
return Array.from(layersByUrl.entries()).map(([url, layerParams]) => {
const hasNoId = layerParams.some((p) => !p.id);
if (hasNoId) {
layerParams.forEach((params) => {
if (!params.id) {
params.index = customIndex;
}
});
customIndex++;
}
return [url, layerParams];
});
}
generateLayerOption(layer) {
const dataSourceOptions = layer.dataSource.options;
return [
this.concatUrlWithVersion(dataSourceOptions),
this.getLayerParams(layer)
];
}
getLayerNames(dataSourceOptions) {
const type = dataSourceOptions.type.toLowerCase();
if (type === 'wms') {
const params = dataSourceOptions
.params;
return params.LAYERS;
}
return 'layer' in dataSourceOptions ? dataSourceOptions.layer : '';
}
getWmsVersion(dataSourceOptions) {
const params = dataSourceOptions?.params;
return params?.VERSION && params.VERSION !== '1.3.0'
? params.VERSION
: undefined;
}
concatUrlWithVersion(dataSourceOptions) {
const url = dataSourceOptions.url;
if (dataSourceOptions.type.toLowerCase() === 'wms') {
const version = this.getWmsVersion(dataSourceOptions);
if (version) {
const operator = url.includes('?') ? '&' : '?';
const { version: versionDef } = this.SHARE_MAP_DEFS.layers.params;
return `${url}${operator}${versionDef.key}=${version}`;
}
}
return dataSourceOptions.url;
}
getLayerParams(layer) {
const dataSourceOptions = layer.dataSource.options;
const isExisting = this.context?.layers
? this.hasLayerId(this.context.layers, layer.id)
: false;
return {
index: undefined,
...(isExisting
? { id: layer.id }
: {
names: this.getLayerNames(dataSourceOptions),
type: dataSourceOptions?.type
}),
opacity: this.getOpacity(layer.opacity),
parentId: layer.parent?.id,
visible: this.getVisibility(layer.visible),
zIndex: layer.zIndex
};
}
/** Recursive */
hasLayerId(layersOptions, targetId) {
if (targetId == null)
return false;
return layersOptions.some((l) => {
if (l.id == null)
return false;
if (!isLayerGroupOptions(l)) {
return String(l.id) === String(targetId);
}
if (isLayerGroupOptions(l) && Array.isArray(l.children)) {
return this.hasLayerId(l.children, targetId);
}
return false;
});
}
getOpacity(opacity) {
return opacity === 1 ? undefined : opacity;
}
getVisibility(visibility) {
return !!visibility;
}
buildLayersQueryValues(layersByService) {
const urls = [];
const layerParams = [];
for (const [url, layer] of layersByService) {
let needUrl = false;
for (const param of layer) {
layerParams.push(this.stringifyLayerParams(param));
if (param.id == null) {
needUrl = true;
}
}
// If some layer have no id push the url service.
if (needUrl) {
urls.push(url);
}
}
return [urls, layerParams];
}
stringifyLayerParams(params) {
const { index, ...restParams } = params;
const stringifiedParams = this.stringifyDefinitions(restParams, this.SHARE_MAP_DEFS.layers.params);
return restParams.id != null
? `${stringifiedParams}`
: `${index},${stringifiedParams}`;
}
getBaseUrlConfig(viewController) {
const { pos, contextKey, languageKey } = this.SHARE_MAP_DEFS;
const href = this.document.location.href;
const urlParams = this.getSanitizedParams(href);
if (pos) {
const positionStringified = this.stringifyPosition(this.getPosition(viewController));
urlParams.set(pos.key, positionStringified);
}
const contextUri = this.context?.uri;
if (contextUri)
urlParams.set(contextKey, contextUri);
if (this.language && !urlParams.has(languageKey))
urlParams.set(languageKey, this.language);
return urlParams;
}
getSanitizedParams(baseUrl) {
const [, queryString] = baseUrl.split('?');
const params = new URLSearchParams(queryString);
const keys = this.extractKeys(this.SHARE_MAP_DEFS);
keys.forEach((key) => {
params.delete(key);
});
return params;
}
extractKeys(defs) {
const keys = [];
for (const key in defs) {
if (Object.prototype.hasOwnProperty.call(defs, key)) {
const value = defs[key];
if (typeof value === 'string') {
keys.push(value);
}
else if (typeof value === 'object' && value !== null) {
keys.push(value.key);
}
}
}
return keys;
}
getPosition(viewController) {
return ObjectUtils.removeUndefined({
center: viewController.getCenter('EPSG:4326'),
zoom: this.getZoom(viewController),
rotation: this.getRotation(viewController),
projection: this.getProjection(viewController)
});
}
getProjection(viewController) {
const ctxProjection = this.getCurrentContext().projection;
const mapProjection = viewController.getOlProjection().getCode();
return ctxProjection === mapProjection ? undefined : mapProjection;
}
getZoom(viewController) {
const mapZoom = viewController.getZoom();
const ctxZoom = this.getCurrentContext().zoom;
return ctxZoom === mapZoom ? undefined : mapZoom;
}
getRotation(viewController) {
const rotationRadians = viewController.getRotation();
const rotationDegree = (rotationRadians * 180) / Math.PI;
const ctxRotation = this.getCurrentContext().rotation;
return rotationDegree === ctxRotation || rotationDegree === 0
? undefined
: rotationDegree;
}
stringifyPosition(position) {
const definitions = this.SHARE_MAP_DEFS.pos.params;
const { center, ...restPosition } = position;
const stringifiedParams = this.stringifyDefinitions(restPosition, definitions);
const result = [
`${definitions.center.key}${definitions.center.stringify(center)}`,
stringifiedParams
].filter(Boolean);
return result.join(',');
}
stringifyDefinitions(values, definitions) {
const result = Object.keys(ObjectUtils.removeUndefined(values))
.map((key) => {
const { key: defKey, stringify } = definitions[key];
const value = stringify ? stringify(values[key]) : values[key];
return `${value}${defKey}`;
})
.join(',');
return result === '' ? undefined : result;
}
buildGroupsQueryValue(layers) {
if (layers.length === 0)
return undefined;
return layers
.map((layer) => {
const params = this.getLayerGroupParams(layer);
return this.stringifyGroupParams(params);
})
.join(';');
}
getLayerGroupParams(layer) {
return {
id: layer.id,
title: layer.title,
zIndex: layer.zIndex,
parentId: layer.parent?.id,
visible: this.getVisibility(layer.visible),
opacity: this.getOpacity(layer.opacity),
expanded: layer.expanded
};
}
stringifyGroupParams(params) {
return this.stringifyDefinitions(params, this.SHARE_MAP_DEFS.groups.params);
}
}
class ShareMapLegacyParser {
options;
constructor(options) {
this.options = options;
}
parseUrl(params) {
const layerOptions = ServiceType.flatMap((type) => this.readLayersQueryParamsByType(params, type)).filter(Boolean);
return layerOptions;
}
parsePosition(params) {
const center = params[this.options.centerKey];
const projection = params[this.options.projectionKey];
const zoom = params[this.options.zoomKey];
const rotation = params[this.options.rotationKey];
return ObjectUtils.removeUndefined({
center: center?.split(',').map(Number),
projection,
zoom: zoom ? Number(zoom) : undefined,
rotation: rotation ? Number(rotation) : undefined
});
}
readLayersQueryParamsByType(params, type) {
const [nameParamLayersKey, urlsKey] = this.getQueryKeyByType(params, type);
if (!nameParamLayersKey || !urlsKey) {
return undefined;
}
const layersByService = params[nameParamLayersKey].split('),(');
const urls = params[urlsKey].split(',');
return urls
.map((urlSrc, index) => {
// Sanitize URL and extract version
const [url, version] = this.sanitizeUrl(urlSrc);
const layersConfig = this.extractLayersByService(this.removeParenthesis(layersByService[index]));
// Generate layer options for the current service
return this.extractLayersOptions(layersConfig, url, type, version, params);
})
.flat();
}
getQueryKeyByType(params, type) {
let nameParamLayersKey;
let urlsKey;
const { layersKey, wmsUrlKey, wmsLayersKey, wmtsUrlKey, wmtsLayersKey, arcgisUrlKey, arcgisLayersKey, iarcgisUrlKey, iarcgisLayersKey, tarcgisUrlKey, tarcgisLayersKey } = this.options;
switch (type) {
case 'wms':
if ((params[layersKey] || params[wmsLayersKey]) && params[wmsUrlKey]) {
urlsKey = wmsUrlKey;
nameParamLayersKey = params[wmsLayersKey] ? wmsLayersKey : layersKey;
}
break;
case 'wmts':
if (params[wmtsLayersKey] && params[wmtsUrlKey]) {
urlsKey = wmtsUrlKey;
nameParamLayersKey = wmtsLayersKey;
}
break;
case 'arcgisrest':
if (params[arcgisLayersKey] && params[arcgisUrlKey]) {
urlsKey = arcgisUrlKey;
nameParamLayersKey = arcgisLayersKey;
}
break;
case 'imagearcgisrest':
if (params[iarcgisLayersKey] && params[iarcgisUrlKey]) {
urlsKey = iarcgisUrlKey;
nameParamLayersKey = iarcgisLayersKey;
}
break;
case 'tilearcgisrest':
if (params[tarcgisLayersKey] && params[tarcgisUrlKey]) {
urlsKey = tarcgisUrlKey;
nameParamLayersKey = tarcgisLayersKey;
}
break;
}
if (!nameParamLayersKey || !urlsKey) {
return [undefined, undefined];
}
return [nameParamLayersKey, urlsKey];
}
sanitizeUrl(url) {
const version = this.getQueryParam('version', url.toLocaleLowerCase()) || undefined;
if (version) {
const versionRegex = new RegExp(`[?&]version=${version}`, 'i');
url = url.replace(versionRegex, '').replace(/[?&]$/, '');
}
if (url.endsWith('?')) {
url = url.substring(0, url.length - 1);
}
return [url, version];
}
getQueryParam(name, url) {
let paramValue;
if (url.includes('?')) {
const httpParams = new HttpParams({ fromString: url.split('?')[1] });
paramValue = httpParams.get(name);
}
return paramValue;
}
extractLayersByService(layersByService) {
if (!layersByService.includes(':igoz')) {
return [
{
layers: layersByService.split(','),
zIndex: null
}
];
}
const layers = layersByService.match(/([^(),:]+(?:,[^(),:]+)*(:[^(),:]+(?:[:][^(),:]+)*)?)/g);
return layers.map((layer) => {
const [names, zIndex] = layer.split(':igoz');
return {
layers: names.split(','),
zIndex: parseInt(zIndex)
};
});
}
removeParenthesis(value) {
if (value.startsWith('(')) {
value = value.substr(1);
}
if (value.endsWith(')')) {
value = value.slice(0, -1);
}
return value;
}
extractLayersOptions(layersConfig, url, type, version, params) {
return layersConfig.map((layerConfig) => {
const sourceOptions = buildDataSourceOptions(type, url, layerConfig.layers, version);
const id = generateIdFromSourceOptions(sourceOptions);
const visible = this.computeLayerVisibilityFromUrl(params, id);
return ObjectUtils.removeUndefined({
id,
visible: visible,
zIndex: layerConfig.zIndex,
sourceOptions
});
});
}
computeLayerVisibilityFromUrl(params, currentLayerid) {
const queryParams = params;
let visible = true;
if (!queryParams || !currentLayerid) {
return visible;
}
let visibleOnLayersParams = '';
let visibleOffLayersParams = '';
let visiblelayers = [];
let invisiblelayers = [];
if (queryParams['visiblelayers']) {
visibleOnLayersParams = queryParams['visiblelayers'];
}
if (queryParams['invisiblelayers']) {
visibleOffLayersParams = queryParams['invisiblelayers'];
}
/* This order is important because to control whichever
the order of * param. First whe open and close everything.*/
if (visibleOnLayersParams === '*') {
visible = true;
}
if (visibleOffLayersParams === '*') {
visible = false;
}
// After, managing named layer by id (context.json OR id from datasource)
visiblelayers = visibleOnLayersParams.split(',');
invisiblelayers = visibleOffLayersParams.split(',');
if (visiblelayers.indexOf(currentLayerid) > -1 ||
visiblelayers.indexOf(currentLayerid.toString()) > -1) {
visible = true;
}
if (invisiblelayers.indexOf(currentLayerid) > -1 ||
invisiblelayers.indexOf(currentLayerid.toString()) > -1) {
visible = false;
}
return visible;
}
}
class ShareMapParser {
keysDefinitions;
legacyOptions;
legacy;
constructor(keysDefinitions, legacyOptions) {
this.keysDefinitions = keysDefinitions;
this.legacyOptions = legacyOptions;
this.legacy = new ShareMapLegacyParser(legacyOptions);
}
parseLayers(params) {
if (!hasModernShareParams(params, this.keysDefinitions) &&
hasLegacyParams(params, this.legacyOptions)) {
return this.legacy.parseUrl(params);
}
const { urlsKey, layers: layersDef, groups: groupsDef } = this.keysDefinitions;
const layersArray = this.splitParam(getParamValue(params, layersDef.key), ';');
const urlsArray = this.splitParam(getParamValue(params, urlsKey), ',');
const groupsArray = this.splitParam(getParamValue(params, groupsDef.key), ';');
const groupsOptions = groupsArray.map((layer) => this.parseGroup(layer));
const layersOptions = layersArray
.map((layer) => this.parseLayer(layer, urlsArray))
.filter(Boolean);
return [...groupsOptions, ...layersOptions];
}
splitParam(value, delimiter) {
return value ? value.split(delimiter) : [];
}
parsePosition(params) {
const position = decodeURIComponent(params[this.keysDefinitions.pos.key]);
if (!position) {
return this.legacy.parsePosition(params);
}
const { center, zoom, rotation, projection } = this.keysDefinitions.pos.params;
return ObjectUtils.removeUndefined({
center: center.parse(position),
zoom: zoom.parse(position),
rotation: rotation.parse(position),
projection: projection.parse(position)
});
}
parseLayer(layer, urls) {
const { zIndex, visibility, type, opacity, parentId } = this.extractLayerProperties(layer);
const base = {
visible: visibility,
zIndex,
opacity,
parentId
};
const layerNames = this.extractLayerNames(layer);
if (layerNames) {
const urlIndex = this.extractUrlIndex(layer);
if (urlIndex === undefined)
return undefined;
const url = urls[urlIndex];
const version = this.extractVersionFromUrl(url);
const sourceOptions = buildDataSourceOptions(type, url, layerNames, version);
return ObjectUtils.removeUndefined({
id: undefined,
sourceOptions,
...base
});
}
const id = this.extractLayerId(layer);
if (!id)
return undefined;
return ObjectUtils.removeUndefined({
id,
sourceOptions: undefined,
...base
});
}
parseGroup(properties) {
const { params } = this.keysDefinitions.groups;
return ObjectUtils.removeUndefined({
id: params.id.parse(properties),
title: params.title.parse(properties),
zIndex: params.zIndex.parse(properties),
visible: params.visible.parse(properties),
opacity: params.opacity.parse(properties),
parentId: params.parentId.parse(properties),
expanded: params.expanded.parse(properties),
type: 'group'
});
}
extractVersionFromUrl(url) {
const versionDef = this.keysDefinitions.layers.params.version;
return versionDef.parse(url);
}
extractLayerId(layer) {
const { id } = this.keysDefinitions.layers.params;
const regex = new RegExp(`([a-zA-Z0-9_]+)${id.key}\\b`);
const match = layer.match(regex);
return match ? match[1] : undefined;
}
extractUrlIndex(layer) {
const { index } = this.keysDefinitions.layers.params;
const regex = index ? new RegExp(`([\\d.]+)${index}`) : /([\d.]+)/;
const match = layer.match(regex);
return match ? parseInt(match[1], 10) : undefined;
}
extractLayerNames(layer) {
const { names } = this.keysDefinitions.layers.params;
const pattern = new RegExp(`\\[.*?\\]${names.key}`, 'g');
const matches = layer.match(pattern);
if (!matches)
return undefined;
return matches
.map((match) => decodeURIComponent(match.slice(1, -`]${names.key}`.length)))
.join('\n')
.split(',');
}
extractLayerProperties(properties) {
const { params } = this.keysDefinitions.layers;
return {
zIndex: params.zIndex.parse(properties),
visibility: params.visible.parse(properties),
type: params.type.parse(properties),
opacity: params.opacity.parse(properties),
parentId: params.parentId.parse(properties)
};
}
}
class ShareMapService {
routeService = inject(RouteService);
document = inject(DOCUMENT);
get language() {
return this._language;
}
set language(value) {
this._language = value;
if (this.encoder) {
this.encoder.language = value;
}
}
_language = '';
options;
optionsLegacy;
keysDefinitions;
encoder;
parser;
constructor() {
this.options = SHARE_MAP_KEYS_DEFAULT_OPTIONS;
this.optionsLegacy = this.routeService.legacyOptions;
this.keysDefinitions = shareMapKeyDefs({
...SHARE_MAP_KEYS_DEFAULT_OPTIONS,
languageKey: this.routeService.options.languageKey
});
this.encoder = new ShareMapEncoder(this.keysDefinitions, this.document);
this.parser = new ShareMapParser(this.keysDefinitions, this.routeService.legacyOptions);
this.routeService.queryParams.subscribe((params) => {
const language = params[this.keysDefinitions.languageKey];
if (language) {
this.language = language;
}
});
}
generateUrl(map, context) {
return this.encoder.generateUrl(map, context);
}
parsePosition(params) {
return this.parser.parsePosition(params);
}
parseLayers(params) {
return this.parser.parseLayers(params);
}
sanitizeBaseUrl(baseUrl) {
const params = this.encoder.getSanitizedParams(baseUrl);
const [base] = baseUrl.split('?');
const queryString = params.toString();
return queryString !== '' ? `${base}?${queryString}&` : `${base}?`;
}
getContext(params) {
return (params[this.options.context] ?? params[this.optionsLegacy.contextKey]);
}
getZoom(params) {
return this.parsePosition(params).zoom;
}
getUrlWithApi(formValues) {
const loc = this.document.location;
const origin = loc.origin;
const pathname = loc.pathname;
const search = loc.search;
const params = new URLSearchParams(search);
params.set('context', formValues.uri);
if (this.language) {
params.set('lang', this.language);
}
return `${origin}${pathname}?${params.toString()}`;
}
hasPositionParams(params) {
const { projectionKey, rotationKey, zoomKey, centerKey } = this.optionsLegacy;
const { pos } = this.keysDefinitions;
return Boolean(params[pos.key] ||
params[projectionKey] ||
params[rotationKey] ||
params[zoomKey] ||
params[centerKey]);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ShareMapService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ShareMapService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ShareMapService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [] });
class ContextService {
http = inject(HttpClient);
authService = inject(AuthService);
languageService = inject(LanguageService);
config = inject(ConfigService);
messageService = inject(MessageService);
storageService = inject(StorageService);
exportService = inject(ExportService);
shareMapService = inject(ShareMapService);
route = inject(RouteService, { optional: true });
context$ = new BehaviorSubject(undefined);
contexts$ = new BehaviorSubject({ ours: [] });
defaultContextId$ = new BehaviorSubject(undefined);
editedContext$ = new BehaviorSubject(undefined);
importedContext = [];
toolsChanged$ = new Subject();
mapViewFromRoute = {};
options;
baseUrl;
// Until the ContextService is completely refactored, this is needed
// to track the current tools
tools;
toolbar;
get defaultContextUri() {
return (this.storageService.get('favorite.context.uri') ||
this._defaultContextUri ||
this.options.defaultContextUri);
}
set defaultContextUri(uri) {
this._defaultContextUri = uri;
}
_defaultContextUri;
constructor() {
this.options = Object.assign({
basePath: 'contexts',
contextListFile: '_contexts.json',
defaultContextUri: '_default'
}, this.config.getConfig('context'));
this.baseUrl = this.options.url ?? '';
if (this.authService.hasAuthService) {
this.authService.logged$.subscribe((logged) => {
if (logged) {
this.contexts$.pipe(skip(1), first()).subscribe(() => {
this.handleContextsChange();
});
this.loadContexts();
}
});
}
else {
this.loadContexts();
this.handleContextsChange(false);
}
}
get(permissions, hidden) {
let url = this.baseUrl + '/contexts';
if (permissions && this.authService.authenticated) {
url += '?permission=' + permissions.join();
if (hidden) {
url += '&hidden=true';
}
}
return this.http.get(url);
}
getById(id) {
const url = this.baseUrl + '/contexts/' + id;
return this.http.get(url);
}
getDetails(id) {
const url = `${this.baseUrl}/contexts/${id}/details`;
return this.http.get(url).pipe(catchError((res) => {
this.handleError(res, id);
throw res;
}));
}
getDetailsByUri(uri) {
const url = `${this.baseUrl}/contexts/uri/${uri}/details`;
return this.http.get(url).pipe(catchError((res) => {
this.handleError(res, uri);
throw res;
}));
}
getDefault() {
if (this.authService.authenticated) {
const url = this.baseUrl + '/contexts/default';
return this.http.get(url).pipe(tap((context) => {
this.defaultContextId$.next(context.id);
}));
}
else {
const uri = this.storageService.get('favorite.context.uri');
this.defaultContextId$.next(uri);
return this.getContextByUri(uri);
}
}
getProfilByUser() {
if (this.baseUrl) {
const url = this.baseUrl + '/profils?';
return this.http.get(url);
}
return of([]);
}
setDefault(id) {
if (this.authService.authenticated) {
const url = this.baseUrl + '/contexts/default';
return this.http.post(url, { defaultContextId: id });
}
else {
return this.setDefaultLocalStorage(id);
}
}
setDefaultLocalStorage(id) {
const selectedId = this.storageService.get('favorite.context.uri');
if (selectedId === id) {
this.storageService.remove('favorite.context.uri');
}
else {
this.storageService.set('favorite.context.uri', id);
}
return of(selectedId === id ? undefined : id);
}
hideContext(id) {
const url = this.baseUrl + '/contexts/' + id + '/hide';
return this.http.post(url, {});
}
showContext(id) {
const url = this.baseUrl + '/contexts/' + id + '/show';
return this.http.post(url, {});
}
delete(id, imported = false) {
const contexts = { ours: [] };
Object.keys(this.contexts$.value).forEach((key) => (contexts[key] = this.contexts$.value[key].filter((c) => c.id !== id)));
if (imported) {
this.importedContext = this.importedContext.filter((c) => c.id !== id);
return of(this.contexts$.next(contexts));
}
const url = this.baseUrl + '/contexts/' + id;
return this.http.delete(url).pipe(tap(() => {
this.contexts$.next(contexts);
this.loadContext(this.defaultContextUri);
}));
}
create(context) {
const url = this.baseUrl + '/contexts';
return this.http.post(url, context).pipe(map((contextCreated) => {
if (