MapsWidget.vue

<template>
  <div class="mw-container" ref="mw-container">
    <Viewer v-if="isReady" />
    <Tour :prop_steps="tourSteps" @finish_tour="finishTour"/>
    <Overlay />
  </div>
</template>

<style>
.mw-container {
  position: absolute;
  overflow: hidden;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
.test-responsive {
  width: 100px;
  background-color: #ffffff;
  margin: 0 auto;
  position: fixed;
  top: 10px;
  left: 10px;
}
</style>

<script>
// Constants
import { WidgetConstants } from './widgets/WidgetConstants.js';
import { FunctionsConstants } from '@/components/FunctionsConstants.js';
import { ElLoading } from 'element-plus';
// Utils
import { Utils } from '@/assets/js/utils/Utils.js';
import { HttpClient } from '@/assets/js/utils/HttpClient.js';
// Stores
import { envStore } from '@/stores/envStore.js';
import { authStore } from '@/stores/authStore';
import { userSettingsStore } from '@/stores/userSettingsStore';
import { contextStore } from '@/stores/contextStore';
import { termsofuseStore } from '@/stores/termsofuseStore';
import { paramsStore } from '@/stores/paramsStore';
import { componentStore } from "@/stores/componentStore"; 
import { mapStore } from '@/stores/mapStore';
import { layerStore } from '@/stores/layerStore';
import { toolStore } from '@/stores/toolStore';
import { styleStore } from '@/stores/styleStore';
import { panelStore } from '@/stores/panelStore';
import { searchStore } from '@/stores/searchStore';
import { projectStore } from '@/stores/projectStore';

import { MapsWidgetService } from './mapswidget.service.js';
import { ToolsConstants } from '@/components/widgets/tools/ToolsConstants.js'
// Components
import Viewer from "./map/Viewer.vue";
import Tour from '@/components/tour/Tour.vue';
import Overlay from "@/components/overlay/Overlay.vue";
// Tour 
import { TourSteps } from '@/components/tour/TourSteps.js'

// Openlayers drawing condition
import {drawingCondition, finishDrawCondition} from "@/components/widgets/tools/editing/interactions/conditions"

// ---------------
// MODULES
// ---------------
let editingBcStore;
(async () => {
  // Modules to load
  const modules = await import('@/modules/modules.js');
  // Module BC component/objects to use
  editingBcStore = modules?.ModuleEditingBC?.editingBcStore;
})();

import projectService from '@/services/project.service';
import { transformExtent } from 'ol/proj';
import { ElNotification, ElMessage } from 'element-plus';
// import LocalStorage from '@/assets/js/utils/LocalStorage';
// import { DrawMeasureToolConfig } from '@/components/widgets/tools/draw-measure/DrawMeasureToolConfig.js';

let scopeApi = null;

/**
* @component
* @description Componente di mappa
*
* @vue-event mounted Raised when components has been mounted
* @vue-event map_loaded Raised when map has finished to load all its components (controls and layers)
* @vue-event drawn_geometry Raised when geometry has been drawn from "activateDrawgeometry" control (see related API)
* @vue-event drawn_geometry_clear Raised when drawn geometries drawn by "activateDrawgeometry" have been removed
* @vue-event finish_tour Raised when the presentation tour was over
* @vue-event identify_result Raised when identifying features of selected themes are passed to the component
* @vue-event new_notification Raised when a new notification is detected/added
* @vue-event change_lang Raised when the application language changes
* @vue-event search_result Raised when a result is selected from the list using the search tool
*/
export default {
  /**
   * Props passed to widget
   * - **current_user_iam**  
   *    User logged in IAM
   *    
   *   **current_user**
   *    User logged in application
   * 
   * - **context_path**  
   *    The path to be pre-appended to the URL paths used in the requests.  
   * 
   *    Ex.
   *    ~~~
   *    basiccore
   *    ~~~
   * 
   * - **base_url**  
   *    The API URL of "maps" to be used when the widget is loaded within an application with a domain different from that of maps.
   *    
   *    Ex.
   *    ~~~
   *    https:/deve-mapsview.civis.bz.it
   *    ~~~
   * 
   * - **context_identifier**  
   *    Identifier of the context to load.
   *    It is possible to provide the identifier instead of the entire context configuration.  
   *    The widget will make the appropriate request to the server to obtain the correct configuration.  
   *    It is not necessary to pass the "context_configs" parameter to the widget.
   * 
   * - **context_configs**  
   *    Context configurations to run widget. 
   *    For details of this configurations see response of maps API:
   *    ~~~
   *    /maps/api/v1/contexts/<context_identifier>
   *    ~~~
   * 
   * - **language**  
   *    Language to localized widget  
   * 
   *    Ex.
   *    ~~~
   *    it
   *    ~~~
   * 
   * - **initial_extent**  
   *    Initial extent of the map
   *    ~~~
   *    [<minX>, <minY, <maxX>, <maxY>]
   *    ~~~
   * 
   * - **initial_extent_epsg**  
   *    Coordinate system of initial extent  
   *  
   *    Ex.
   *    ~~~
   *    EPSG:25832
   *    ~~~
   *  
   * - **filtered_layers**  
   *    Layers of the context to load that can not be visualized   
   * 
   *    Ex.
   *    ~~~
   *    {
   *      contexts: ['BC-BASIC-CORE'],
   *      identifiers: ['PROV-ADMINISTRATIVE-UNITS-MUNICIPALITIES'],
   *    }
   *    ~~~
   * 
   * - **cmp_configs**  
   *    Tools and widgets configurations  
   *    This JSON configuration defines the **component settings (`cmpConfigs`)** for a mapping or GIS web application. It specifies which features, tools, and behaviors should be enabled or customized in different parts of the application, such as overview maps, catalogs, tools, and layer controls (TOC – Table of Contents). Here's a breakdown of its structure and functionality:
   * ---
   * 
   * ### **Top-Level Keys in `cmpConfigs`:**
   * 
   * ---
   * 
   * #### 1. **`overview`**
   * 
   * * **Purpose**: Sets the base layer for the overview map (typically a mini-map or context map).
   * * **Key**:
   * 
   *   * `layerIdentifier`: Uses layer as the background layer.
   * 
   * ---
   * 
   * #### 2. **`catalogs`**
   * 
   * * **Purpose**: Manages the visibility and availability of layer and data catalogs displayed on the left side of the widget interface.
   * 
   * * **Keys**:
   * 
   *   * `enabled`: Activates catalogs in the desktop version of the application
   *   * `mobile`: Specifies which catalogs are available when the application is used on mobile devices.
   * 
   * ---
   * 
   * #### 3. **`tools`**
   * 
   * * **Purpose**: Defines which map tools are available to the user.
   * * **Keys**:
   * 
   *   * `enabled`: List of desktop tools like:
   * 
   *     * `drawMeasure`: Drawing and measuring on the map
   *     * `selection`: Spatial or attribute-based selection
   *     * `identifyMap`: Identify features on the map
   *     * `printMap`: Print the current map view
   *     * `downloadMap`: Download the map as an image or file
   *     * `downloadLayer`: Download geodata
   *     * `editingMap`: Edit spatial features
   *     * `geoprocess`: Run geospatial processing functions
   *     * `timeseriesTool`: Time slider for temporal data
   *     * `externalThemes`: Add external WMS Themes
   *   * `default`: Index in the enabled list marks the default active
   *   * `mobile`: specifies which map tool are available when the application is used on mobile devices.
   * 
   * ---
   * 
   * #### 4. **`functions`**
   * 
   * * **Purpose**: Activates additional application-wide features accessible through various submenus within the application.
   * 
   * * **Keys**:
   *     * `enabled`: List of desktop tools like:
   * 
   *         * `fnBaseMaps`: Switch between base maps
   *         * `fnViewTools`: Toggle map view tools
   *         * `fnMasterDetails`: Use master-detail UI layout
   *         * `fnDocumentsRead`, `fnDocumentsWrite`: Document management (read/write)
   *         * `fnExportData`: Exporting data 
   *         * `fnImportData`: Importing data
   *         * `fnScalebar`: Show scale bar
   *         * `fnSearch`: Search capabilities
   *         * `fnLocation`: Geolocation tools
   *         * `fnGoogleStreetView`: Integration with Google Street View
   *         * `fn3dView`: 3D map viewing
   *         * `fnViewTools`: 2D map viewing tools
   * 
   * ---
   * 
   * #### 5. **`toc` (Table of Contents)**
   * 
   * * **Purpose**: Manages available tools for each layer in the layer list panel.
   * 
   * * **Keys**:
   *   * `tools.enabled`: For desktop, includes:
   *
   *     * `infoLayer`: View metadata or description
   *     * `layerLegend`: Show legend
   *     * `filterLayer`: Filter data within layer
   *     * `layerTransparency`: Adjust layer opacity
   *     * `removeLayer`: Remove the layer
   *     * `shortInfo`: Show short information tooltip
   *     * `downloadLayer`: Download layer data
   *     * `zoomToExtent`: Download layer data
   *   * `tools.mobile`: specifies which map tool are available when the application is used on mobile devices.
   *
   *    ---
   *    Ex.
   *    ~~~
   *    cmpConfigs: {
   *      overview: {
   *        layerIdentifier: "PROV-BASEMAP-STANDARD",
   *      },
   *      catalogs: {
   *        enabled: ['contextCatalog','geoCatalog'],
   *        mobile: ['contextCatalog']
   *      },
   *      tools: {
   *        enabled: ['drawMeasure','selection','identifyMap','printMap','downloadMap','downloadLayer','editingMap','geoprocess','timeseriesTool'],
   *        default: 2,
   *        mobile: ['identifyMap']
   *      },
   *      functions: {
   *        enabled: ['fnBaseMaps','fnViewTools','fnMasterDetails','fnDocumentsRead','fnDocumentsWrite','fnExportData','fnImportData','fnScalebar','fnSearch','fnLocation','fnGoogleStreetView','fn3dView']
   *      },
   *      toc: {
   *        tools: {
   *          enabled: ['infoLayer','layerLegend','filterLayer','layerTransparency','removeLayer','shortInfo', 'downloadLayer'],
   *          mobile: ['layerLegend','layerTransparency','removeLayer', 'shortInfo']
   *        }
   *      }
   *    }
   *    ~~~
   * 
   * - **territory**  
   *    Territory parameter: This parameter consists of two values separated by a colon (:).  
   *    - Territory name
   *    - Territory id (code)
   * 
   *    Ex.
   *    ~~~
   *    MUNICIPALITY:21001
   *    ~~~
   * 
   * - **maps_api_env**  
   *    Env code of maps api envinment to use.
   *    ~~~
   *    DEV - TEST - DEMO - PROD
   *    ~~~
   * 
   * - **panel_tools_catalogs_visible**  
   *    Boolean parameter to show or hide the buttons of tools and themes panel.  
   * 
   *    Ex.
   *    ~~~
   *    true
   *    ~~~
   * 
   * - **panel_active_themes_visible**  
   *    Boolean parameter to show or not the right panel that shows active themes.  
   * 
   *    Ex.
   *    ~~~
   *    true
   *    ~~~
   * 
   * - **ctrl_zoom**  
   *    Boolean parameter to enable the Zoom In/Out function on the map with ctrl + scroll.  
   * 
   *    Ex.
   *    ~~~
   *    true
   *    ~~~
   * 
   * - **disable_identify_display**  
   *    Boolean parameter to disable the display of identifying information in the template elements  
   *    of some components (e.g., `Map.vue`, `IdentifyTool.vue`) for the selected layers.
   * 
   *    Ex.
   *    ~~~
   *    true
   *    ~~~
   * 
   * - **enable_search_event**  
   *    Boolean parameter to enable the event generated by selecting an item from the search results.
   *    If not defined, event is disabled by default.
   * 
   *    Ex.
   *    ~~~
   *    true
   *    ~~~
   */
  props: {
    current_user_iam: {
      type: Object,
      default: null
    },
    current_user: {
      type: Object,
      default: null
    },
    context_path: {
      type: String,
      default: ""
    },
    context_identifier: {
      type: String,
      default: ""
    },
    context_configs: {
      type: Object,
      default: null
    },
    base_url: {
      type: String,
      default: ""
    },
    language: {
      type: String,
      default: "it"
    },
    initial_extent: {
      type: Array,
      default: () => []
    },
    initial_extent_epsg: {
      type: String,
      default: ""
    },
    filtered_layers: {
      type: Object,
      default: () => ({})
    },
    cmp_configs: {
      type: Object,
      default: () => ({})
    },
    territory: {
      type: String,
      default: ""
    },
    maps_api_env: {
      type: String,
      default: ""
    },
    bc_data: {
      type: Object,
      default: null
    },
    panel_tools_catalogs_visible: {
      type: Boolean,
      default: true
    },
    panel_active_themes_visible: {
      type: Boolean,
      default: true
    },
    ctrl_zoom: {
      type: Boolean,
      default: false
    },
    disable_identify_display: {
      type: Boolean,
      default: false
    },
    enable_search_event: {
      type: Boolean,
      default: false
    },
  },

  expose: [
    "activateTour",
    "loadFeatures",
    "activateLayer",
    "activateDrawGeometry", 
    "toggleIdentify",
    "filterLayerWms", 
    "zoomToExtent",
    "search",
  ],

  emits: [
    "mounted",
    "map_loaded",
    "center_changed",
    "drawn_geometry",
    "drawn_geometry_clear",
    "finish_tour",
    "identify_result",
    "new_notification",
    "change_lang",
    "search_result",
    // Only for BC
    "bc_editing_complete",
  ],

  events: {
		'map_loaded': 'onMapLoaded',
    "map_ready": 'onMapReady',
    'center_changed': 'onCenterChanged',
    'handle_identify_result': 'handleIdentifyResult',
    'new_notification': 'onNewNotification',
    'handle_search_result': 'handleSearchResult',
    // Only for BC
    'bc_editing_complete': 'onCompleteBcEditing',
    'apply_project': 'applyProject',
    'changeLang': 'changeLang'
	},

  components: {
    Viewer,
    Tour,
    Overlay,
    ElNotification,
    ElMessage
  },

  computed: {
    isReady() {
      return (
        this.componentStore.initialized &&
        this.contextStore.initialized &&
        this.authStore.currentUserIam &&
        this.userSettingsStore.userSettingsInitialized &&
        this.territoryReady &&
        this.initialExtentReady &&
        this.initialExtentEpsgReady
      );
    },
    tourSteps() {
      return TourSteps ? TourSteps : [];
    },
    projectsEnabled() {
			return this.componentStore.getEnabledFunctions().includes(FunctionsConstants.projects);
		}
  },

  created() {
    this.$registerEvents(this);
  },

  mounted() {
    scopeApi = this;
    this.panelStore.elementRoot = this.$refs['mw-container'];
    this.$emit("mounted");

    // Memorizza il parametro disable_identify_display nello store
    this.componentStore.setDisableIdentifyDisplay(this.disable_identify_display);

    window.addEventListener("dragover",function(e){
      e.preventDefault();
    },false);
    window.addEventListener("drop",function(e){
      e.preventDefault();
    },false);

  },

  data() {
    return {
      // Stores
      envStore: envStore(),
      authStore: authStore(),
      userSettingsStore: userSettingsStore(),
      contextStore: contextStore(),
      termsofuseStore: termsofuseStore(),
      paramsStore: paramsStore(),
      panelStore: panelStore(),
      componentStore: componentStore(),
      mapStore: mapStore(),
      layerStore: layerStore(),
      toolStore: toolStore(),
      styleStore: styleStore(),
      editingBcStore: editingBcStore ? editingBcStore() : null,
      searchStore: searchStore(),
      projectStore: projectStore(),

      // Params
      territoryReady: false,
      initialExtentReady: false,
      initialExtentEpsgReady: false,

      loadingProject: null
    };
  },

  methods: {
    handleSearchResult(feature) {
      this.$emit('search_result', feature);
    },
    handleIdentifyResult(features) {
        this.$emit('identify_result', features);
    },
    finishTour(value){
      this.$emit("finish_tour", value);
    }, 

    getDefinedUserStyles(contextId) {
      HttpClient.get('./maps/api/v1/contexts/' + contextId + "/symbols", {
        headers: {
            "maps-user-context": this.contextStore.getContext()
        },
        onSuccess: (response) => {
          this.styleStore.init(response.data);
        }
      });
    },

    getUserSettings() {
      this.createMapSession();

      const contextId = this.contextStore.getContextId();
      HttpClient.get('./maps/api/v1/contexts/' + contextId + "/settings" 
        + '?uid=' + localStorage.getItem("session-uid")
				+ '&id=' + localStorage.getItem("session-id"), {
        onSuccess: (response) => {
          this.userSettingsStore.setSettings(response.data);
        }
      });
    },

    getSpatialOptions() {
      HttpClient.get('./maps/api/v1/features/spatialops', {
        headers: {
          "maps-user-context": this.contextStore.getContext()
        },
        onSuccess: (response) => {
          this.toolStore.spatialOptions = response.data;
        }
      });
    },

    createMapSession() {
      const uuid = localStorage.getItem("session-uid") ? localStorage.getItem("session-uid") : Utils.generaGUID();
      const id = localStorage.getItem("session-id") ? localStorage.getItem("session-id") : Math.floor(Math.random() * 100000);      
      localStorage.setItem("session-uid", uuid);
      localStorage.setItem("session-id", id);
    },

    resetStores() {
      this.contextStore.destroy();
      this.mapStore.destroy();
      //this.toolStore.destroy();
      this.layerStore.destroy();
      this.userSettingsStore.destroy();
    },

    // #############################################
    // Init context with received configs (init all)
    // #############################################
    handleTermsOfUse(contextConfigs) {
      if(!contextConfigs) return;

      // Retrieve terms of use before set context
      // Layers have to be load depending on terms of use

      // If logged in
      if(this.authStore.isAuthenticated()) {
        HttpClient.get('./maps/api/v1/termsofuse/accepted', {
          loading: true,
          headers: {
            "maps-user-context": contextConfigs.identifier
          },
          onSuccess: (response) => {
            this.termsofuseStore.accepted = response.data;
            setTimeout(() => {
              // Set context and load map
              this.setContextConfigs(contextConfigs);
            }, 300);
          }
        });
      }
      // No logged in
      else {
        const accepted = localStorage.getItem("tou-accepted-ids");
        this.termsofuseStore.accepted = accepted ? accepted : [];
        setTimeout(() => {
          // Set context and load map
          this.setContextConfigs(contextConfigs);
        }, 300);
      }
    },

    setContextConfigs(contextConfigs) {
      // Retrieve defined user styles
      this.getDefinedUserStyles(contextConfigs.id);     
  
      this.contextStore.backupConfigs = Utils.cloneDeep(contextConfigs); //copia clone per caricamento progetto
      // Change baselayers ids
      MapsWidgetService.updateBaselayersIds(contextConfigs.baseMaps);   
      

      this.contextStore.init(contextConfigs);
      this.mapStore.init(contextConfigs);
      this.toolStore.init(contextConfigs);

      // Retrieve defined user styles
      this.getDefinedUserStyles(contextConfigs.id);
      // Get user settings: last saved configurations
      this.getUserSettings();
      // Get spatial options
      this.getSpatialOptions();
    },

    initLayers() {
      // Map layer: layerTree and layerGroups from json config
      const layerTree = this.contextStore.getLayerTree();
      const layerGroups = this.contextStore.getLayerGroups();
      let wmsExternalTheme = this.userSettingsStore.getWmsThemes();
      let mapLayers = layerTree.concat(layerGroups);

      if (wmsExternalTheme && wmsExternalTheme.items && Array.isArray(wmsExternalTheme.items) && wmsExternalTheme.items.length > 0) {
        mapLayers = mapLayers.concat(wmsExternalTheme);
        console.log('concateno anche i wmsExternal trovati',mapLayers);
      }
      
      // Add imported data 
      MapsWidgetService.addImportedVectorData(mapLayers);
      
      // Set user tree state configurations (if present)
      if(this.userSettingsStore.getLayerTreeState()) {
        MapsWidgetService.setLayerTreeUserState(mapLayers, {
          childrenTag: "items"
        });
      }


      // Get protected layers from configs
      const protectedLayers = MapsWidgetService.getProtectedLayers(mapLayers);

      if(protectedLayers.length == 0 || !this.authStore.isAuthenticated()) {
        this.initLayerStore(mapLayers);
        return;
      }

      let protectedServers = new Set();
      protectedLayers.forEach(layer => {
        protectedServers.add(layer.serverIdentifier);
      });

      const todoCalls = protectedServers.size;
		  let completedCalls = 0;
      protectedServers.forEach(server => {
        HttpClient.post('./maps/api/v1/tokens/' + server, {
          onSuccess: (response) => {
            completedCalls++;
            this.authStore.setAuthServerToken(server, response.data.token);
            if(todoCalls == completedCalls) {
              this.initLayerStore(mapLayers);
            }
          },
          onFail: (error) => {
            completedCalls++;
            if(todoCalls == completedCalls) {
              this.initLayerStore(mapLayers);
            }
          }
        })
      });
    },

    initLayerStore(mapLayers) {
      // Set prefix in group id
      MapsWidgetService.setPrefixToFolderId(mapLayers);

      // set parent group for layers
      MapsWidgetService.addGroupToLeafs(mapLayers);

      // If children layer transparency is not specified, the parent's one is used
      MapsWidgetService.applyGroupTransparencyToChildren(mapLayers);
      
      // Update external layers group name, useful on language switch
      MapsWidgetService.updateExternalLayersGroupName(mapLayers);

      // Catalog layers (if catalog is present)
      let catalogLayers = [];
      if (this.componentStore.hasCatalog()) {
        catalogLayers = mapLayers.filter(layer => 
          !layer.isImportGroup && !layer.importedFile);
      }

      // Set active layers filtering maplayers by "layer_filtered" parameter
      function isLayerActive(layer) {
        let visible = true;
        let matchContext = false;
        let matchIdentifier = false;

        if (scopeApi?.filtered_layers?.contexts?.length > 0) {
          visible = false;
          matchContext = scopeApi.filtered_layers.contexts.includes(layer.serverIdentifier);
        }
        if (scopeApi?.filtered_layers?.identifiers?.length > 0) {
          visible = false;
          matchIdentifier = scopeApi.filtered_layers.identifiers.includes(layer.identifier);
        }

        // Filter if layer has terms of use not already accepted
        const touAccepted = MapsWidgetService.isTermsOfUseLayerAccepted(layer);
        return (matchContext || matchIdentifier || visible) && touAccepted;
      }
      // console.log("Active layers: " + activeLayers.length);

      function filterGroupVisibleLayers(group){
        
        group.items = group.items.filter((item) => {
          if (item["@type"] === "layer") {
            item.visible = Boolean(item.visible); //set false if null
            item.isActive = 'isActive' in item ? item.isActive : item.visible; // init isActive if not retrieved from settings
            if (!componentStore().hasCatalog() || (item.visible && isLayerActive(item))) {
              return true;
            } else {
              return false;
            }
          } else {
            const filteredGroup = filterGroupVisibleLayers(item);
            if (filteredGroup.items.length > 0) {
              filteredGroup.visible = filteredGroup.items.some((i) => i.visible);
              filteredGroup.isActive = 'isActive' in filteredGroup ? filteredGroup.isActive : filteredGroup.visible; // init isActive if not retrieved from settings
              return true;
            }
          }
        });
        return group;
      }

      let activeLayers = Utils.cloneDeep(mapLayers).filter((item) => {
        if (item["@type"] === "layer") {
          item.visible = Boolean(item.visible); //set false if null
          item.isActive = 'isActive' in item ? item.isActive : item.visible; // init isActive if not retrieved from settings
          if (item.visible || !componentStore().hasCatalog()) {
            return true;
          } else {
            return false;
          }
        } else {
          const filteredGroup = filterGroupVisibleLayers(item);
          if (filteredGroup.items.length > 0) {
            filteredGroup.visible = filteredGroup.items.some((i) => i.visible);
            filteredGroup.isActive = 'isActive' in filteredGroup ? filteredGroup.isActive : filteredGroup.visible; // init isActive if not retrieved from settings
            return true;
          }
        }
      })
      this.layerStore.initCatalogLayers(catalogLayers);
      //inizializzo gli active layers ordinando l'albero in base alla proprietà index
      this.layerStore.initActiveLayers(Utils.orderTree(activeLayers, 'index', 'items'));
      this.layerStore.initExpandedLayers(this.layerStore.activeLayers);
    },

    onMapLoaded() {
      this.initLayers();
      if (this.projectsEnabled) {
        this.initProjects();
      }
    },

    onMapReady() {
      this.$emit("map_loaded");
      this.$trigger("updateMapSize");
    },

    onNewNotification(notification) {
      this.$emit('new_notification', notification);
    },
  
    onCenterChanged(center) {
      this.$emit("center_changed", {
        lon: center[0],
        lat: center[1]
      });
    },

    onDrawnGeometry(epsg, wkt) {
      epsg = epsg.split(":")[1];
      scopeApi.$emit("drawn_geometry", {
        wkt: wkt,
        epsg: epsg
      });
    },

    onDrawnGeometryClear() {
      scopeApi.$emit("drawn_geometry_clear");
    },

    /**
     * BC: emit event of completed editing by the user
     * (From EditingBcTool)
     */
    onCompleteBcEditing(sessionId) {
      this.$emit("bc_editing_complete", sessionId);
    },

    // ###########
    // API
    // ###########
    /**
     * API -
     * Load a list of vector features into map
     *
     * @param {JSON} jsonFeatures Json object with list of features in geojson format and their EPSG
     *
     * @example
     * {
     *    features: [
     *      <geojsonFeature>,
     *      <geojsonfeature>,
     *      ...
     *      ...
     *    ]
     *    epsg: 28532
     * }
     */
    loadFeatures(jsonFeatures) {
      console.log("MW - API: Loading features into map");

      // Handle layer to load in map if it exists as feature property
      if(jsonFeatures?.features) {
        let layerIdentifiers = jsonFeatures.features.map(geojsonFeature => {
          return geojsonFeature.properties?.layerIdentifier;
        });
        scopeApi.$trigger('activateLayers', [ layerIdentifiers ]);
      }

      let epsg = "EPSG:" + jsonFeatures.epsg;
      scopeApi.$trigger("loadFeatures", [jsonFeatures.features, epsg, true]);
    },

    /**
     * API -
     * Activates tour widget.
     * The widget displays a wizard at startup to describe the base functionalities of maps-widget
     *
     * @param {JSON} parentSteps List of parent application elements to insert into tour widget.
     *
     * @example
     * parentSteps = [
     *    ...
     *    {
     *      // Identifier class of parent element
     *      target: '.tour-target-1',
     *      // Relative position of popup
     *      position: 'bottom-right',
     *      // Content of popup: title and text
     *      content: {
     *        title: 'TOUR.steps.parent.1.title', //
     *        text: 'TOUR.steps.parent.1.text'
     *      },
     *    },
     *    ...
     * ]
     *
     * WARNING: "text" and "title" parameters are the localization tag of text to display.
     * They have to be added in localization file of maps-widget
     */
    activateTour(parentSteps) {
      console.log("MW - API: activating tour..");
      let steps = null;
      if(parentSteps) {
        // Merge steps parent with widget steps
        if(scopeApi.tourSteps && scopeApi.tourSteps[0]?.once) {
          steps = Utils.mergeAtIndex(scopeApi.tourSteps, parentSteps, (scopeApi.tourSteps.length));
        }
        else {
          steps = parentSteps.concat(scopeApi.tourSteps);
          //steps = scopeApi.tourSteps.concat(parentSteps);
        }
      }
      scopeApi.$trigger('activateTour', [steps]);
    },

    /**
     * API -
     * Turn on / off a layer in map.
     *
     * @param {String} layerIdentifier Identificativo del layer
     * @param {Boolean} active Stato del layer: acceso / spento
     * @param {Boolean} visible (opzionale) se il layer è acceso, indica se deve essere visibile o no
     */
    activateLayer(layerIdentifier, active, visible=true) {
      console.log("MW - API: activate layer in map " + layerIdentifier);
      if(layerIdentifier) {
        if(active == true || active == null || active == undefined) {
          const layerIdentifiers = [layerIdentifier]
          this.$nextTick(() => {
            scopeApi.$trigger('activateLayers', [layerIdentifiers]);
            this.$nextTick(() => {
              Utils.setPropertyTreeById(scopeApi.layerStore.activeLayers, layerIdentifier, 'isActive', visible, true);
            });
          });
        }
        else if(active == false){
          scopeApi.layerStore.removeActiveLayerByIdentifier(layerIdentifier);
        }
      }
    },

    /**
     * API -
     * Enable control to draw geometry in map
     *
     * @param {String} Identifier of draw geometry control that will be initialized. User can interact later with same control passing the same identifier
     * @param {('Point'|'LineString'|'Polygon')} type Geometry type to draw
     * @param {String} epsgOutput Coordinate system of drawn geometry returned by map control (Only identifier code)
     * @param {JSON} options Extra params to handle draw control
     *
     * @example
     * epsgOutput = 25832
     *
     * options = {
     *    // Geometry format of drawn geometry: geojson, wkt..
     *    outputFormat: "WKT",
     *    // If clean previus drawn geometries when user starts to draw new one
     *    cleanOnStart: true,
     *    // If drawn geometries have to be removed after deactivating draw control.
     *    cleanOnDeactivate: true
     * }
     */
    activateDrawGeometry(identifierControl, type, epsgOutput, options) {
      console.log("MW - API: activate draw geometry ");

      const epsgOutputCode = "EPSG:" + epsgOutput;
      //scopeApi.$trigger('activateDrawGeometry', [type, epsgOutputCode, scopeApi.onDrawnGeometry, scopeApi.onDrawnGeometryClear]);

      // Init control draw 
      scopeApi.$trigger('initDrawGeometry', [{
        identifier: identifierControl,
        onAdd: scopeApi.onDrawnGeometry,
        onClear: scopeApi.onDrawnGeometryClear,
        epsgOutput: epsgOutputCode,
        tooltip: false,
        drawingConditionFn: drawingCondition,
				finishConditionFn: finishDrawCondition,
        outputFormat: options?.outputFormat,
        cleanOnStart: options?.cleanOnStart, 
        cleanOnDeactivate: options?.cleanOnDeactivate
      }]);

      scopeApi.$trigger('activateDrawGeometry', [{
				identifier: identifierControl,
				geometryType: type
			}]);
    },

    /**
     * API -
     * Turn on / off the identify tool. 
     * 
     * @param {Boolean} active Identify tool state: on / off
     */
    toggleIdentify(active) {
      if (typeof active === 'boolean') {
          if (active) {
            this.toolStore.activeTool = ToolsConstants.tools.identify;
          } else {
            if (this.toolStore.activeTool == ToolsConstants.tools.identify) {
              this.toolStore.activeTool = '';
            }
          }
        }
    },

    /**
     * API -
     * Filter WMS layer 
     * 
     * @param {JSON} filter list of cql filters to apply for specific layers
     *
     * @example
     * filter = [
     *    {
     *      layerIdentifier: BC-SESSIONS,
     *      cql_filter: <cql_filter_string>,
     *      columns: [<column1>, <column2>, ...]>
     *    }
     *  ]
     */
    filterLayerWms(filters) {
      console.log("MW - API: filter WMS layer " + JSON.stringify(filters));
      scopeApi.$trigger('filterWms', [filters]);
    },

    /**
     * API -
     * Zoom to extent
     * 
     * @param {JSON} extent List of coordinates of extent
     * @param {String} epsg Coordinate system of extent
     *
     * @example
     * extent = [ <minX>, <minY>, <maxX>, <maxY> ]
     *
     * epsg = 25832
     */
    zoomToExtent(extent, epsg) {
      console.log("MW - API: Zoom to extent " + extent);
      const epsgOutputCode = "EPSG:" + epsg;
      scopeApi.$trigger('zoomToExtent', [extent, epsgOutputCode]);
    },

    /**
     * API -
     * Allows to make a search on different levels and displays results on map.
     * The search has been performed through the maps search API: 'maps/api/v1/search/\<searchTypeId\>/features'.
     *
     * searchTypeId -> one of params of 'searchParams' object parameter.
     *
     * *For more details of how "searchParms" is defined see documentation of maps search API service.*
     *
     * @param {Boolean} active If tool has to activate or to be closed.
     * @param {JSON} searchParams Search parameters.
     *
     */
    search(active, searchParams) {
      console.log("MW - API: Search in map..");
      if (!active) {
        scopeApi.$trigger('toggleTableResults', [false, null]);
      } else {
        const params = {
          q: searchParams.searchText,
          center: searchParams.center.lat + "," + searchParams.center.lon
        }
        const headers = {
          'maps-user-context': searchParams.context
        }

        HttpClient.get('./maps/api/v1/search/' + searchParams.searchType.id + '/features', {
          params: params,
          headers: headers,
          onSuccess: (response) => {
            let results = [];

            if (response?.data && response?.data.length) {
              results = response.data.map(featureCollection => {
                return {
                  ...featureCollection,
                  ...{
                    label: searchParams.searchType.name,
                    epsg: 'EPSG:' + featureCollection.crs?.properties.name.split('EPSG::')[1]
                  }
                }
              });
            }
            scopeApi.$trigger('toggleTableResults', [true, results, null, false, false, true, 'search']);
          }
        });
      }
    },

    initProjects() {
      if (localStorage.getItem('projectRecovery')) {
        this.loadingProject = ElLoading.service({lock: true, text: this.$t('PROJECTS.projectLoading')});
      }
      //ottengo i progetti associati 
      projectService.getAllProjects().then(res => {
        if (res?.data?.length) {
          this.projectStore.setProjects(res.data);
          if (localStorage.getItem('projectRecovery')) {
            let recProject = JSON.parse(localStorage.getItem('projectRecovery'));
            this.userSettingsStore.userSettings = Utils.cloneDeep(recProject.settings);
            this.layerStore.destroy();
            this.applyProject(recProject);
          }
        }
        this.$nextTick(() => {
          if (localStorage.getItem('projectRecovery')) {
            this.loadingProject.close();
            localStorage.removeItem('projectRecovery');
          }
        })
        this.projectStore.setInitProjects();
      }).catch((error) => {
        ElMessage({
          appendTo: this.panelStore.elementRoot,
          message: `${this.$t('MESSAGES.error')} ${error?.message}`,
          type: 'error',
          grouping: true
        });

        this.projectStore.setInitProjects();
        if (localStorage.getItem('projectRecovery')) {
          this.loadingProject.close();
          localStorage.removeItem('projectRecovery');
        }
      });
    },

    /* Applico il progetto selezionato */
    applyProject(project) {

      this.$trigger('deactivateAllControls');
      if (this.mapStore.use3D) {
        this.$trigger('deactivateAllControls3D');
      }

      this.$nextTick(() => {
        //ricarico context settings (senza dover richiamare il servizio)
        this.contextStore.config = Utils.cloneDeep(this.contextStore.backupConfigs); 

        //ricarico basemap
        if (this.userSettingsStore.getBasemap()) { 
          let baseMap = this.mapStore.getBaseLayers().find(el => el.identifier == this.userSettingsStore.getBasemap());
          if (baseMap) {
            this.$trigger('set_active_baseLayer', [baseMap]);
          }
          else {
            this.restoreEmptyBaseMap();
          }
          this.layerStore.setBaseLayersInitialized(true);
        }

        //ricarico extent
        if (this.userSettingsStore.getExtent()) { 
          let extent = Utils.cloneDeep(this.userSettingsStore.getExtent());
          let transfExtent = transformExtent(this.userSettingsStore.getExtent(), 'EPSG:3857', project.crs);
          this.$trigger('zoomToExtent', [transfExtent, project.crs]);

          if (this.mapStore.use3D) {
            this.$trigger('saveExtent2D', [extent]);
            this.$trigger('setExtent3D', [transfExtent, project.crs]);
          }
        }

        //ricarico projection
        if (this.mapStore.getCurrentViewedProjection() != project.crs) {
          this.mapStore.setCurrentViewedProjection(project.crs);
        }

        //ricarico i layers
        this.initLayers(); 

        //aggiorno
        this.$forceUpdate();

        this.$nextTick(() => {
          ElNotification({
            type: 'success',
            position: 'top-right',
            title: this.$t('MESSAGES.success'),
            message: this.$t('PROJECTS.successApplication', {projectName: project.title}),
            duration: 5000
          });

          this.projectStore.selectedRecoveryProjectId = project.id;
          
          this.toolStore.setActiveTool(this.toolStore.defaultTool);
          this.$trigger('initDrawnFeaturesStore', [true]);
          if (this.mapStore.use3D) {
            this.$trigger('loadSavedFeatures3D', ['drawMeasures3D']);
          }
        });
      });
    },

    restoreEmptyBaseMap() {
      //ripristino basemap in caso fosse salvata quella vuota
      const blankBaselayer = {
        id: "blank",
        identifier: "EMPTY_BASEMAP_IDENTIFIER",
        name: this.$t('WIDGETS.baselayers.blank'),
        blank: true
      };
      this.$trigger('set_active_baseLayer', [blankBaselayer]);
    },

    changeLang(lang) {
      this.$emit('change_lang', lang);
    }
  },

  watch: {
    isReady: {
      immediate: true,
      handler(isReady) {
        if (isReady) {
          // Force to open left panel with activated editing tool
          if(this.toolStore.defaultTool == ToolsConstants.tools.editingBc) {
            this.$nextTick(() => {
              this.panelStore.openLeftPanel(WidgetConstants.tools);
            });
          }
        }
      }
    },
    maps_api_env: {
      immediate: true,
      handler(env) {
        if (env) {
          this.envStore.setEnv(env);
        }
      }
    },
    language: {
      immediate: true,
      handler(language) {
        if (language) {
          this.$i18n.locale = language;
        }
      }
    },

    current_user_iam: {
      immediate: true,
      handler(currentUserIam) {
        if (currentUserIam) {
          this.authStore.setCurrentUserIam(currentUserIam);
        }
      }
    },

    current_user: {
      immediate: true,
      handler(currentUser) {
        if (currentUser) {
          this.authStore.setCurrentUser(currentUser);
        }
      }
    },

    context_path: {
      immediate: true,
      handler(contextPath) {
        if (contextPath) {
          this.paramsStore.setContextPath(contextPath);
        }
      }
    },

    base_url: {
      immediate: true,
      handler(baseUrl) {
        if (baseUrl) {
          this.paramsStore.setBaseUrl(baseUrl);
        }
      }
    },

    context_identifier: {
      immediate: true,
      handler(contextIdentifier) {
        //this.resetStores();
        if(contextIdentifier) {
          console.log("MW - Getting context configuration by Identifier: " + contextIdentifier);
          HttpClient.get('./maps/api/v1/contexts/' + contextIdentifier, {
            onSuccess: (response) => {
              // Retrieve terms of use
              this.handleTermsOfUse(response.data);
            }
          });
        }
      }
    },
    context_configs: {
      immediate: true,
      handler(contextConfigs) {
        //this.resetStores();
        if (contextConfigs) {
          // Retrieve terms of use
          this.handleTermsOfUse(contextConfigs);
        }
      }
    },

    /**
     * Listener for component configs:
     * See "cmpConfigs" parameter in App.vue or documentation
     */
    cmp_configs: {
      immediate: true,
      handler(config) {
        console.log("MW - Comp. props:" + JSON.stringify(config));
        if (config != null) {
          // config.tools.enabled -> remove editing tools
          MapsWidgetService.setAllowedMobileTools(config.tools);
          MapsWidgetService.setAllowedMobileTools(config.toc.tools);
          MapsWidgetService.setAllowedMobileCatalogs(config.catalogs);

          // Put component config into componentStore
          this.componentStore.init(config);

          // Set default tool
          if (config.tools.enabled?.length) {
            this.toolStore.defaultTool = config.tools.enabled[config.tools.default];
          }
          // Set overview default layer to use if specified
          this.mapStore.setOverviewLayerIdentifier(config.overview?.layerIdentifier);
        }
      },
      
    },
 
    /**
     * Listener for initial extent
     */
    initial_extent: {
      immediate: true,
      handler(extent) {
        if(extent && extent.length) {
          console.log("MW - Extent iniziale: " + extent);
          this.mapStore.initialExtent.extent = extent.map(coord => parseFloat(coord));
        }
        this.initialExtentReady = true;
      }
    },
    initial_extent_epsg: {
      immediate: true,
      handler(epsg) {
        if(epsg) {
          console.log("MW - EPSG extent iniziale: " + epsg);
          this.mapStore.initialExtent.epsg = epsg;
        }
        this.initialExtentEpsgReady = true;
      }
    },

    /**
     *  Territory props
     */
    territory: {
      immediate: true,
      handler(territory) {
        if(territory) {
          this.paramsStore.setTerritory(territory);
        }
        this.territoryReady = true;
      }
    },
    panel_tools_catalogs_visible: {
      immediate: true,
      handler(value) {
        if (typeof value  === 'boolean') {
          this.componentStore.setPanelToolsCatalogsVisibile(value);
          if (!value) {
            this.panelStore.closeLeftPanel();
          }
        }
      }
    },

    panel_active_themes_visible: {
      immediate: true,
      handler(value) {
        if (typeof value  === 'boolean') {
          this.componentStore.setPanelActiveThemesVisible(value);
        }
      }
    },
    
    ctrl_zoom: {
      immediate: true,
      handler(ctrl_zoom) {
        if(typeof ctrl_zoom === 'boolean') {
          this.componentStore.setCtrlZoom(ctrl_zoom);
        }
      }
    },
    
    /**
     *  Props object passed only from basiccore
     *  bc_data is an object with:
     *  - user: basiccore user
     *  - session: basiccore session to edit
     */
    bc_data: {
      immediate: true,
      handler(data) {
        if(data) {
          if(data.session && !this.editingBcStore) {
            throw new Error("Can not load BC store. Missing BC module. Check if BC module has been loaded")
          }
          this.editingBcStore?.initMainData(data);
        }
      }
    },

    enable_search_event: {
      immediate: true,
      handler(value) {
        if (typeof value === 'boolean') {
          this.componentStore.setEnableSearchEvent(value);
        }
      }
    },
  }
};
</script>