diff --git a/api-idee-js/src/facade/assets/css/controls/rotate.css b/api-idee-js/src/facade/assets/css/controls/rotate.css index 1a531bf3e..c52d24b75 100644 --- a/api-idee-js/src/facade/assets/css/controls/rotate.css +++ b/api-idee-js/src/facade/assets/css/controls/rotate.css @@ -10,16 +10,10 @@ background-color: var(--idee-color-links) !important; } -.m-areas > div.m-area > div.m-panel.opened.m-rotate { - background-color: transparent !important; - box-shadow: none !important; - border: none !important; -} - .m-rotate-compass { position: relative; - width: 150px; - height: 150px; + width: 90px; + height: 90px; display: flex; justify-content: center; align-items: center; @@ -51,15 +45,6 @@ div.m-control.m-rotate-container { opacity: 1; } -.m-rotate-compass { - position: relative; - width: 150px; - height: 150px; - display: flex; - justify-content: center; - align-items: center; -} - .m-rotate-background-exterior { fill: var(--idee-color-primary); } @@ -67,43 +52,52 @@ div.m-control.m-rotate-container { .m-rotate-background-exterior, .m-rotate-svgPath-svg { position: absolute; - width: 99px; - height: 99px; + /* width: 99px; + height: 99px; */ + width: 80px; + height: 84px; } .m-rotate-svgPath-svg { - width: 100px; + width: 85px; } .m-rotate-exterior-svg { position: absolute; - width: 120px; - height: 120px; + width: 85px; overflow: hidden; } +.m-rotate-exterior-svg.g-cartografia-exterior:before { + margin-bottom: 4px; +} + +.m-rotate-exterior-svg.g-cartografia-exterior:before, .m-rotate-exterior-svg.g-cartografia-exterior-1:before { color: #fff; - font-size: 100px; + width: inherit; } .m-rotate-giroscopio-svg { position: absolute; - width: 40px; - height: 40px; + width: 26px; + height: 26px; box-sizing: content-box; + background-color: var(--idee-color-white); + border-radius: 26px; } .m-rotate-giroscopio-svg.g-cartografia-giroscopio:before { - font-size: 40px; - background-color: white; - border-radius: 40px; + margin: 0; + width: 16px; + margin: calc((26px - 16px) / 2); + border-radius: inherit; color: var(--idee-color-primary); } .m-rotate-help-button { position: absolute; - top: 50%; + top: 12px; right: 0; transform: translateY(-50%); background-color: var(--idee-color-primary); @@ -111,13 +105,13 @@ div.m-control.m-rotate-container { font-weight: bold; border: 2px solid white; cursor: pointer; - width: 30px; - height: 30px; - border-top-right-radius: 8px; - border-bottom-right-radius: 8px; + height: 19px; + width: 19px; + border-radius: 50%; text-align: center; font-family: 'Muli', 'sans-serif'; - font-size: 18px; + font-size: 10px; + z-index: 1; } #m-rotate-slider-container { @@ -148,15 +142,16 @@ div.m-control.m-rotate-container { } .m-rotate-help-button:hover { - background-color: var(--idee-color-primary); + background-color: var(--idee-color-links); } .m-rotate-rotation-maker { position: absolute; - width: 99px; - height: 99px; - fill: var(--idee-color-primary); + width: 88px; + height: 80px; + fill: var(--idee-color-links); display: none; + overflow: hidden; } .m-rotate-help-container { @@ -209,11 +204,6 @@ button.m-rotate-help-close.g-cartografia-cancelar:before { color: #000; } -.m-rotate-exterior-svg.g-cartografia-exterior:before { - color: #fff; - font-size: 120px; -} - .m-rotate-help-option { display: flex; gap: 8px; diff --git a/api-idee-js/src/facade/assets/css/fonts.css b/api-idee-js/src/facade/assets/css/fonts.css index 19e7d4149..bf974424a 100644 --- a/api-idee-js/src/facade/assets/css/fonts.css +++ b/api-idee-js/src/facade/assets/css/fonts.css @@ -337,5 +337,14 @@ -webkit-mask-image: url('https://componentes.idee.es/estaticos/Simbologia/svg/icons_cota/pinterest.svg'); mask-image: url('https://componentes.idee.es/estaticos/Simbologia/svg/icons_cota/pinterest.svg'); } - + + .g-cartografia-exterior::before { + -webkit-mask-image: url('https://componentes.idee.es/estaticos/Simbologia/svg/icons_cota/rotate-exterior.svg'); + mask-image: url('https://componentes.idee.es/estaticos/Simbologia/svg/icons_cota/rotate-exterior.svg'); + } + + .g-cartografia-giroscopio::before { + -webkit-mask-image: url('https://componentes.idee.es/estaticos/Simbologia/svg/icons_cota/rotate-giroscopio.svg'); + mask-image: url('https://componentes.idee.es/estaticos/Simbologia/svg/icons_cota/rotate-giroscopio.svg'); + } } \ No newline at end of file diff --git a/api-idee-js/src/facade/js/Map.js b/api-idee-js/src/facade/js/Map.js index 41b5e2369..d50eede91 100644 --- a/api-idee-js/src/facade/js/Map.js +++ b/api-idee-js/src/facade/js/Map.js @@ -5421,6 +5421,40 @@ class Map extends Base { return this; } + /** + * Este método añade una interacción de OpenLayers al mapa. + * + * @function + * @param {ol.interaction.Interaction} interaction Interacción a añadir. + * @returns {IDEE.Map} Mapa. + * @public + * @api + */ + addInteraction(interaction) { + if (isUndefined(MapImpl.prototype.addInteraction)) { + Exception(getValue('exception').addinteraction_method); + } + this.getImpl().addInteraction(interaction); + return this; + } + + /** + * Este método elimina una interacción de OpenLayers del mapa. + * + * @function + * @param {ol.interaction.Interaction} interaction Interacción a eliminar. + * @returns {IDEE.Map} Mapa. + * @public + * @api + */ + removeInteraction(interaction) { + if (isUndefined(MapImpl.prototype.removeInteraction)) { + Exception(getValue('exception').removeinteraction_method); + } + this.getImpl().removeInteraction(interaction); + return this; + } + /** * Este método permite activar o desactivar la interacción de panneo. * El valor por defecto es true. diff --git a/api-idee-js/src/facade/js/control/Location.js b/api-idee-js/src/facade/js/control/Location.js index 8d20c893b..575fb27b4 100644 --- a/api-idee-js/src/facade/js/control/Location.js +++ b/api-idee-js/src/facade/js/control/Location.js @@ -5,6 +5,7 @@ import LocationImpl from 'impl/control/Location'; import locationTemplate from 'templates/location'; import myhelp from 'templates/locationhelp'; import 'assets/css/controls/location'; +import * as EventType from 'IDEE/event/eventtype'; import { getValue } from '../i18n/language'; import ControlBase from './Control'; import { @@ -68,7 +69,11 @@ class Location extends ControlBase { } // implementation of this control - const impl = new LocationImpl(tracking, highAccuracy, 60000, vendorOptions); + const impl = new LocationImpl({ + tracking, + highAccuracy, + vendorOptions, + }); // calls the super constructor super(Location.NAME, impl, options); @@ -101,9 +106,10 @@ class Location extends ControlBase { * @api */ createView(map) { + const lang = this.translation || {}; const element = compileTemplate(locationTemplate, { vars: { - title: this.tooltip ?? getValue('location').title, + title: this.tooltip ?? lang.title, }, }); @@ -131,6 +137,60 @@ class Location extends ControlBase { return element.querySelector('button#m-location-button'); } + /** + * Genera un objeto clave-valor traducido con los datos actualizados. + * Perfecto para el InfoBox nativo de Cesium. + * @param {number} lon + * @param {number} lat + * @returns {Object} Diccionario mapeado con traducciones vigentes + */ + getPopupProperties(lon, lat) { + const lang = this.translation || {}; + const labels = lang.popup; + + return { + [labels.title]: labels.titleValue || 'Mi ubicación', + [labels.longitude]: typeof lon === 'number' ? lon.toFixed(6) : lon, + [labels.latitude]: typeof lat === 'number' ? lat.toFixed(6) : lat, + }; + } + + /** + * Crea un contenedor DOM con una tabla estructurada y traducida. + * Perfecto para inyectar en Overlays de OpenLayers. + * @param {number} lon + * @param {number} lat + * @returns {HTMLElement} Elemento contenedor de la tabla + */ + createPopupContent(lon, lat) { + const lang = this.translation || {}; + const labels = lang.popup; + + const container = document.createElement('div'); + container.className = 'm-location-popup-container'; + + container.innerHTML = ` + + + + + + + + + + + + + + + + +
${labels.titleValue || 'Mi ubicación'}
+ `; + return container; + } + /** * Obtiene la ayuda del control * @@ -139,7 +199,8 @@ class Location extends ControlBase { * @api */ getHelp() { - const textHelp = getValue('location').textHelp; + const lang = this.translation || {}; + const textHelp = lang.textHelp; return { title: Location.NAME, content: new Promise((success) => { @@ -147,9 +208,9 @@ class Location extends ControlBase { vars: { urlImages: `${IDEE.config.STATIC_RESOURCES_URL}/imagenes/controles`, translations: { - help1: textHelp.text1, - help2: textHelp.text2, - help3: textHelp.text2, + help1: textHelp.text1 ?? '', + help2: textHelp.text2 ?? '', + help3: textHelp.text2 ?? '', }, }, }); @@ -183,6 +244,15 @@ class Location extends ControlBase { setTracking(tracking) { this.getImpl().tracking = tracking; } + + /** + * @override + * Destroys facade implementacion of this control + */ + destroy() { + super.destroy(); + this.un(EventType.CHANGE); + } } /** diff --git a/api-idee-js/src/facade/js/i18n/en.json b/api-idee-js/src/facade/js/i18n/en.json index a124e63e1..bd137cf0b 100644 --- a/api-idee-js/src/facade/js/i18n/en.json +++ b/api-idee-js/src/facade/js/i18n/en.json @@ -20,6 +20,12 @@ }, "location": { "title": "Get current location", + "popup": { + "title": "Location", + "titleValue": "Current Position", + "longitude": "Longitude", + "latitude": "Latitude" + }, "textHelp": { "text1": "The location tool allows you to obtain the position of a device and position it on the map. Its operation is restricted to WEB pages that are published securely over the HTTPS protocol and in local environments.", "text2": "If used on a mobile device equipped with a GNSS system, the position will be obtained according to the device's location permissions.", @@ -262,6 +268,8 @@ "invalidBase64Encoding": "Invalid base64 encoding", "invalidJsonFormat": "Invalid JSON format", "removecontrol_method": "The used implementation does not have the removeControls method.", + "addinteraction_method": "La implementación usada no posee el método addInteraction.", + "removeinteraction_method": "La implementación usada no posee el método removeInteraction.", "setmaxextent_method": "The used implementation does not have the setMaxExtent method.", "flyTo_method": "The used implementation does not have the flyTo method.", "getbbox_method": "The used implementation does not have the getBbox method.", diff --git a/api-idee-js/src/facade/js/i18n/es.json b/api-idee-js/src/facade/js/i18n/es.json index e40106273..c1cc5d8c1 100644 --- a/api-idee-js/src/facade/js/i18n/es.json +++ b/api-idee-js/src/facade/js/i18n/es.json @@ -21,6 +21,12 @@ "location": { "error": "El control ubicación no acepta http. Debe usar https.", "title": "Obtener ubicación actual", + "popup": { + "title": "Ubicación", + "titleValue": "Posición actual", + "longitude": "Longitud", + "latitude": "Latitud" + }, "textHelp": { "text1": "La herramienta de localización permite obtener la posición de un dispositivo y posicionarla en el mapa. Su funcionamiento queda restringido a páginas WEB que se publiquen de forma segura sobre el protocolo HTTPS y en entornos locales.", "text2": "En el caso de su utilización en un dispositivo móvil provisto de un sistema GNSS, se obtendrá la posición según los permisos de localización del dispositivo.", @@ -263,6 +269,8 @@ "invalidBase64Encoding": "Codificación base64 inválida", "invalidJsonFormat": "Formato JSON inválido", "removecontrol_method": "La implementación usada no posee el método removeControls.", + "addinteraction_method": "The used implementation does not have the addInteraction method.", + "removeinteraction_method": "The used implementation does not have the removeInteraction method.", "setmaxextent_method": "La implementación usada no posee el método setMaxExtent.", "flyTo_method": "La implementación usada no posee el método flyTo.", "getbbox_method": "La implementación usada no posee el método getBbox.", diff --git a/api-idee-js/src/facade/js/ui/panels/Panel.js b/api-idee-js/src/facade/js/ui/panels/Panel.js index 1b859a6ca..aec93595d 100644 --- a/api-idee-js/src/facade/js/ui/panels/Panel.js +++ b/api-idee-js/src/facade/js/ui/panels/Panel.js @@ -334,14 +334,13 @@ class Panel extends MObject { /** * Este método elimina los controles del panel. * - ⚠️ Advertencia: Este método no debe ser llamado por el usuario. - * @public + * @private * @function * @param {array} controls Control. * @api */ _removeControl(controlsParam) { - const controls = this.map.controls(controlsParam); - controls.forEach((control) => { + this.map.controls(controlsParam).forEach((control) => { const index = this.controls.indexOf(control); if (index !== -1) { this.controls.splice(index, 1); diff --git a/api-idee-js/src/facade/js/util/LoadFiles.js b/api-idee-js/src/facade/js/util/LoadFiles.js index 248147631..f8ad5b35d 100644 --- a/api-idee-js/src/facade/js/util/LoadFiles.js +++ b/api-idee-js/src/facade/js/util/LoadFiles.js @@ -28,6 +28,32 @@ export const loadFeaturesLoadFilesImpl = (map, layerName, features) => { const layer = new Vector({ name: layerName, legend: layerName, extract: true }); layer.addFeatures(features); map.addLayers(layer); + features.forEach((feature) => { + let labelText = feature.getAttribute('lbl_txt'); + let labelFont = feature.getAttribute('lbl_font'); + let labelColor = feature.getAttribute('lbl_clr'); + if (!labelText) { + const desc = feature.getAttribute('desc'); + if (desc && desc.includes('lbl_txt=')) { + desc.split('\n').forEach((pair) => { + const sep = pair.indexOf('='); + if (sep > 0) { + const key = pair.substring(0, sep); + const val = pair.substring(sep + 1); + if (key === 'lbl_txt') labelText = val; + else if (key === 'lbl_font') labelFont = val; + else if (key === 'lbl_clr') labelColor = val; + } + }); + } + } + if (labelText) { + feature.setStyle(new IDEE.style.Point({ + radius: 0, + label: { text: labelText, font: labelFont, color: labelColor }, + })); + } + }); LoadFilesImpl.centerFeatures(features, map); } }; diff --git a/api-idee-js/src/impl/cesium/js/Map.js b/api-idee-js/src/impl/cesium/js/Map.js index 42a77fc49..fb34120c1 100755 --- a/api-idee-js/src/impl/cesium/js/Map.js +++ b/api-idee-js/src/impl/cesium/js/Map.js @@ -140,6 +140,13 @@ class Map extends MObject { */ this.controls_ = []; + /** + * Interacciones añadidas al mapa. + * @private + * @type {Array} + */ + this.interactions_ = []; + /** * Indica si el zoom inicial fue calculado. Por defecto verdadero. * @private @@ -2460,6 +2467,18 @@ class Map extends MObject { }; } + /** + * Obtiene la proyección nativa de Cesium activa en el mapa. + * + * @function + * @returns {GeographicProjection} Instancia nativa de Cesium MapProjection. + * @public + * @api + */ + getNativeProjection() { + return this.map_.scene.mapProjection; + } + /** * Este método obtiene la implementación del mapa. * @@ -2739,6 +2758,45 @@ class Map extends MObject { }); } + /** + * Este método añade una interacción al mapa. + * + * @function + * @param {Object} interaction Interacción a añadir. + * @returns {Map} Mapa. + * @public + * @api + */ + addInteraction(interaction) { + if (!isNullOrEmpty(interaction) && !this.interactions_.includes(interaction)) { + this.interactions_.push(interaction); + if (typeof interaction.activate === 'function') { + interaction.activate(this.map_); + } + } + return this; + } + + /** + * Este método elimina una interacción del mapa. + * + * @function + * @param {Object} interaction Interacción a eliminar. + * @returns {Map} Mapa. + * @public + * @api + */ + removeInteraction(interaction) { + const idx = this.interactions_.indexOf(interaction); + if (idx !== -1) { + if (typeof interaction.deactivate === 'function') { + interaction.deactivate(); + } + this.interactions_.splice(idx, 1); + } + return this; + } + /** * Función que obtiene el nombre de la implementación del mapa. * diff --git a/api-idee-js/src/impl/cesium/js/control/Location.js b/api-idee-js/src/impl/cesium/js/control/Location.js index 736923f64..6692e011e 100644 --- a/api-idee-js/src/impl/cesium/js/control/Location.js +++ b/api-idee-js/src/impl/cesium/js/control/Location.js @@ -8,6 +8,17 @@ import * as Cesium from 'cesium'; import Control from './Control'; import Feature from '../feature/Feature'; +/** + * @typedef {Object} Options Opciones de configuración del control de implementación + * @param {Boolean} [tracking] Seguimiento de la localización, por defecto verdadero. + * @param {Boolean} [highAccuracy] Alta precisión del seguimiento, por defecto falso. + * @param {Number} [maximumAge] Indica la antigüedad máxima en milisegundos de una posible + * posición almacenada en caché. + * Valor por defecto 60000. + * @param {Object} [vendorOptions] Opciones de proveedor para la biblioteca base, + * por defecto objeto vacío. Estos valores no son configurables. +*/ + /** * @classdesc * Hereda de {@link module:IDEE/impl/control/Control|Control}. @@ -25,24 +36,22 @@ import Feature from '../feature/Feature'; class Location extends Control { /** * @constructor - * @param {Boolean} tracking Seguimiento de la localización. - * @param {Boolean} highAccuracy Alta precisión del seguimiento. - * @param {Number} maximumAge Antigüedad máxima en caché. - * @param {Object} vendorOptions Opciones de proveedor para Cesium/Geolocation. + * @extends {IDEE.impl.Control} + * @param {Options} options * @example * const control = new IDEE.impl.ol.control.Location(true, false, 60000, { * enableHighAccuracy: true, * }); */ - constructor(tracking, highAccuracy, maximumAge, vendorOptions) { - super(vendorOptions); + constructor(options = {}) { + super(options.vendorOptions); /** * Opciones para la biblioteca base. * @private * @type {Object} */ - this.vendorOptions_ = vendorOptions; + this.vendorOptions_ = options.vendorOptions; this.watchId_ = null; /** @@ -50,21 +59,21 @@ class Location extends Control { * @private * @type {Boolean} */ - this.tracking_ = tracking; + this.tracking_ = options.tracking ?? true; /** * Alta precisión del seguimiento, por defecto falso. * @private * @type {Boolean} */ - this.highAccuracy_ = highAccuracy; + this.highAccuracy_ = options.highAccuracy ?? false; /** * Valor por defecto 60000. * @private * @type {Number} */ - this.maximumAge_ = maximumAge; + this.maximumAge_ = options.maximumAge ?? 6000; /** * Activa el control. @@ -101,7 +110,7 @@ class Location extends Control { outlineWidth: 1, }, }); - // Inyección de compatibilidad (Duck Typing) para la fachada Feature.js + cesiumAccuracyEntity.get = (key) => { return this[key]; }; @@ -121,8 +130,20 @@ class Location extends Control { disableDepthTestDistance: Number.POSITIVE_INFINITY, }, }); - // Inyección de compatibilidad (Duck Typing) para la fachada Feature.js + + cesiumPositionEntity.getAttributes = () => { + return this._rawProperties; + }; + + cesiumPositionEntity.getAttribute = (key) => { + return this._rawProperties[key]; + }; + cesiumPositionEntity.get = (key) => { + // eslint-disable-next-line no-prototype-builtins + if (this._rawProperties && this._rawProperties.hasOwnProperty(key)) { + return this._rawProperties[key]; + } return this[key]; }; cesiumPositionEntity.isUtilityFeature = true; @@ -150,10 +171,9 @@ class Location extends Control { const accuracy = position.coords.accuracy; const newCoord = [lon, lat]; - // Cesium trabaja con coordenadas Cartesianas 3D basándose en WGS84 (grados) const centerCartesian = Cesium.Cartesian3.fromDegrees(lon, lat); - // 1. Actualizar posición y radio de la geometría de precisión + // Actualizar anillo de precisión const accEntity = this.accuracyFeature_.getImpl().getFeature(); accEntity.position = centerCartesian; if (accEntity.ellipse) { @@ -161,11 +181,18 @@ class Location extends Control { accEntity.ellipse.semiMinorAxis = accuracy; } - // 2. Actualizar posición del punto indicador const posEntity = this.positionFeature_.getImpl().getFeature(); posEntity.position = centerCartesian; - // 3. Reposicionar el mapa global (Fachada) + if (!isNullOrEmpty(this.facadeObj_)) { + const translatedProps = this.facadeObj_.getPopupProperties(lon, lat); + + posEntity.properties = new Cesium.PropertyBag(translatedProps); + + // eslint-disable-next-line no-underscore-dangle + posEntity._rawProperties = translatedProps; + } + this.facadeMap_.setCenter(newCoord); if (this.element.classList.contains('m-locating')) { this.facadeMap_.setZoom(Location.ZOOM); @@ -174,12 +201,10 @@ class Location extends Control { this.element.classList.remove('m-locating'); this.element.classList.add('m-located'); - // Si no requiere trackear continuamente, apagamos el watcher tras la primera lectura fija if (!this.tracking_) { this.clearWatch_(); } - // 4. Notificar cambios a la fachada mediante eventos abstractos if (!isNullOrEmpty(this.facadeObj_)) { if (!setEquals(newCoord, this.lastCoord_)) { this.facadeObj_.fire(EventType.CHANGE, [newCoord]); diff --git a/api-idee-js/src/impl/ol/js/Map.js b/api-idee-js/src/impl/ol/js/Map.js index c2ddc9896..9e6659412 100644 --- a/api-idee-js/src/impl/ol/js/Map.js +++ b/api-idee-js/src/impl/ol/js/Map.js @@ -3889,6 +3889,34 @@ class Map extends MObject { this.getMapImpl().getView().setResolution(resolution); } + /** + * Este método añade una interacción de OpenLayers al mapa. + * + * @function + * @param {ol.interaction.Interaction} interaction Interacción a añadir. + * @returns {Map} Mapa. + * @public + * @api + */ + addInteraction(interaction) { + this.map_.addInteraction(interaction); + return this; + } + + /** + * Este método elimina una interacción de OpenLayers del mapa. + * + * @function + * @param {ol.interaction.Interaction} interaction Interacción a eliminar. + * @returns {Map} Mapa. + * @public + * @api + */ + removeInteraction(interaction) { + this.map_.removeInteraction(interaction); + return this; + } + /** * Función que obtiene el nombre de la implementación del mapa. * diff --git a/api-idee-js/src/impl/ol/js/control/Attributions.js b/api-idee-js/src/impl/ol/js/control/Attributions.js index 3617f68ca..40633809f 100644 --- a/api-idee-js/src/impl/ol/js/control/Attributions.js +++ b/api-idee-js/src/impl/ol/js/control/Attributions.js @@ -27,7 +27,7 @@ class Attributions extends Control { * @api stable */ constructor(options = {}) { - super(); + super(options); /** * Map of the plugin * @private diff --git a/api-idee-js/src/impl/ol/js/control/Location.js b/api-idee-js/src/impl/ol/js/control/Location.js index 14260db24..162e8cb68 100644 --- a/api-idee-js/src/impl/ol/js/control/Location.js +++ b/api-idee-js/src/impl/ol/js/control/Location.js @@ -1,3 +1,4 @@ +/* eslint-disable no-underscore-dangle */ /** * @module IDEE/impl/control/Location */ @@ -17,6 +18,17 @@ import OLStyleStroke from 'ol/style/Stroke'; import Control from './Control'; import Feature from '../feature/Feature'; +/** + * @typedef {Object} Options Opciones de configuración del control de implementación + * @param {Boolean} [tracking] Seguimiento de la localización, por defecto verdadero. + * @param {Boolean} [highAccuracy] Alta precisión del seguimiento, por defecto falso. + * @param {Number} [maximumAge] Indica la antigüedad máxima en milisegundos de una posible + * posición almacenada en caché. + * Valor por defecto 60000. + * @param {Object} [vendorOptions] Opciones de proveedor para la biblioteca base, + * por defecto objeto vacío. Estos valores no son configurables. +*/ + /** * @classdesc * Hereda de {@link module:IDEE/impl/control/Control|Control}. @@ -41,30 +53,23 @@ class Location extends Control { * posición en el mapa. * * @constructor - * @param {Boolean} tracking Seguimiento de la localización, por defecto verdadero. - * @param {Boolean} highAccuracy Alta precisión del seguimiento, por defecto falso. - * @param {Number} maximumAge Indica la antigüedad máxima en milisegundos de una posible - * posición almacenada en caché. - * Valor por defecto 60000. - * @param {Object} vendorOptions Opciones de proveedor para la biblioteca base, - * por defecto objeto vacío. Estos valores no son configurables. + * @extends {IDEE.impl.Control} + * @param {Options} options * @example * const control = new IDEE.impl.ol.control.Location(true, false, 60000, { * enableHighAccuracy: true, * }); - * @extends {IDEE.impl.Control} * @api stable */ - - constructor(tracking, highAccuracy, maximumAge, vendorOptions) { - super(vendorOptions); + constructor(options = {}) { + super(options.vendorOptions); /** * Opciones para la biblioteca base. * @private * @type {Object} */ - this.vendorOptions_ = vendorOptions; + this.vendorOptions_ = options.vendorOptions; /** * Proporcionar Geolocalización HTML5. @@ -79,7 +84,7 @@ class Location extends Control { * @type {OLFeature} */ const olAccuracyFeature = new OLFeature(); - olAccuracyFeature.set('isUtilityFeature', true); // No interactivo + olAccuracyFeature.set('isUtilityFeature', true); this.accuracyFeature_ = Feature.feature2Facade(olAccuracyFeature); /** @@ -87,21 +92,21 @@ class Location extends Control { * @private * @type {Boolean} */ - this.tracking_ = tracking; + this.tracking_ = options.tracking ?? true; /** * Alta precisión del seguimiento, por defecto falso. * @private * @type {Boolean} */ - this.highAccuracy_ = highAccuracy; + this.highAccuracy_ = options.highAccuracy ?? false; /** * Valor por defecto 60000. * @private * @type {Number} */ - this.maximumAge_ = maximumAge; + this.maximumAge_ = options.maximumAge ?? 6000; /** * Activa el control. @@ -168,18 +173,28 @@ class Location extends Control { }, }, this.vendorOptions_, true)); this.geolocation_.on('change:accuracyGeometry', (evt) => { - const accuracyGeom = evt.target.get(evt.key); + // const accuracyGeom = evt.target.get(evt.key); + const accuracyGeom = evt.target.getAccuracyGeometry(); + if (accuracyGeom) { + const nativeMap = this.facadeMap_.getImpl().map_; + const mapProjection = nativeMap.getView().getProjection(); + accuracyGeom.transform('EPSG:4326', mapProjection); + const nativeAccuracyFeature = this.accuracyFeature_.getImpl().getFeature(); + nativeAccuracyFeature.setGeometry(accuracyGeom); + nativeAccuracyFeature.setStyle(Location.ACCURACY_STYLE); + } this.accuracyFeature_.getImpl().getFeature().setGeometry(accuracyGeom); }); this.geolocation_.on('change:position', (evt) => { - const newCoord = evt.target.get(evt.key); + const [lon, lat] = evt.target.get(evt.key); + const newCoord = [lon, lat]; const newPosition = isNullOrEmpty(newCoord) ? null : new OLGeomPoint(newCoord); this.positionFeature_.getImpl().getFeature().setGeometry(newPosition); this.facadeMap_.setCenter(newCoord); if (this.element.classList.contains('m-locating')) { - this.facadeMap_.setZoom(Location.ZOOM); // solo 1a vez + this.facadeMap_.setZoom(Location.ZOOM); } this.element.classList.remove('m-locating'); this.element.classList.add('m-located'); @@ -283,6 +298,24 @@ Location.POSITION_STYLE = new OLStyle({ }), }); +/** + * Estilo del area de cercanía para la localización. + * @const + * @type {ol.style.Style} + * @public + * @api stable + * @memberof module:IDEE/impl/control/Location~ + */ +Location.ACCURACY_STYLE = new OLStyle({ + fill: new OLStyleFill({ + color: 'rgba(0, 204, 255, 0.12)', + }), + stroke: new OLStyleStroke({ + color: 'rgba(0, 68, 85, 0.42)', + width: 1.5, + }), +}); + /** * Zoom de la localización. * @const diff --git a/api-idee-js/src/impl/ol/js/projections.js b/api-idee-js/src/impl/ol/js/projections.js index 6c16ff9b4..e3c3f9175 100644 --- a/api-idee-js/src/impl/ol/js/projections.js +++ b/api-idee-js/src/impl/ol/js/projections.js @@ -5,7 +5,7 @@ import proj4 from 'proj4'; import OLProjection from 'ol/proj/Projection'; import { register } from 'ol/proj/proj4'; -import { addEquivalentProjections } from 'ol/proj'; +import { addEquivalentProjections, get as getProj } from 'ol/proj'; import { parseCRSWKTtoJSON } from '../../../facade/js/util/Utils'; /** @@ -709,6 +709,25 @@ const setNewProjection = async (projection) => { addProjections([newProjection]); }; +/** + * Asegura que una proyección está registrada. Si no lo está, la obtiene de epsg.io y la registra. + * @param {String} projCode Código EPSG de la proyección + * @returns {Promise} true si la proyección está disponible, false si no existe + * @public + * @function + * @api + */ +const ensureProjection = async (projCode) => { + const normalizedCode = projCode.startsWith('EPSG:') ? projCode : `EPSG:${projCode}`; + if (getProj(normalizedCode)) return true; + try { + await setNewProjection(normalizedCode); + return true; + } catch { + return false; + } +}; + // register proj4 addProjections(projections, false); @@ -724,4 +743,5 @@ export default { addProjections, getSupportedProjs, setNewProjection, + ensureProjection, }; diff --git a/api-idee-js/src/plugins/infocoordinates/src/facade/js/infocoordinatescontrol.js b/api-idee-js/src/plugins/infocoordinates/src/facade/js/infocoordinatescontrol.js index f9e1e41e0..9bef06eb7 100644 --- a/api-idee-js/src/plugins/infocoordinates/src/facade/js/infocoordinatescontrol.js +++ b/api-idee-js/src/plugins/infocoordinates/src/facade/js/infocoordinatescontrol.js @@ -156,6 +156,7 @@ export default class InfocoordinatesControl extends IDEE.Control { const openDropdown = () => { if (!isEditable) { + this.refreshProjectionsSelector(selector); selector.classList.remove('noDisplay'); arrow.classList.add('arrow-up'); } @@ -194,6 +195,9 @@ export default class InfocoordinatesControl extends IDEE.Control { }); arrow.addEventListener('click', () => { + if (selector.classList.contains('noDisplay')) { + this.refreshProjectionsSelector(selector); + } selector.classList.toggle('noDisplay'); arrow.classList.toggle('arrow-up'); }); @@ -491,6 +495,32 @@ export default class InfocoordinatesControl extends IDEE.Control { idBotonTab.classList.add('active'); } + /** + * Refresca el listado de proyecciones del selector de EPSG con las + * proyecciones actualmente soportadas, incluyendo las registradas + * al vuelo por otros plugins. + * @param {HTMLElement} selector Elemento