/**
* Class: lizMap
* @package
* @subpackage view
* @author 3liz
* @copyright 2011 3liz
* @link http://3liz.com
* @license MPL-2.0
*/
import { extend } from 'ol/extent.js';
import WFS from '../modules/WFS.js';
import WMS from '../modules/WMS.js';
import Utils from '../modules/Utils.js';
import DOMPurify from 'dompurify';
window.lizMap = function() {
/**
* PRIVATE Property: config
* {object} The map config
*/
var config = null;
/**
* PRIVATE Property: keyValueConfig
* {object} Config to replace keys by values
*/
var keyValueConfig = null;
/**
* PRIVATE Property: capabilities
* {object} The wms capabilities
*/
var capabilities = null;
/**
* PRIVATE Property: wmtsCapabilities
* {object} The wmts capabilities
*/
var wmtsCapabilities = null;
/**
* PRIVATE Property: wfsCapabilities
* {object} The wfs capabilities
*/
var wfsCapabilities = null;
/**
* PRIVATE Property: map
* {<OpenLayers.Map>} The map
*/
var map = null;
/**
* PRIVATE Property: baselayers
* {Array(<OpenLayers.Layer>)} Ordered list of base layers
*/
var baselayers = [];
/**
* PRIVATE Property: layers
* {Array(<OpenLayers.Layer>)} Ordered list of layers
*/
var layers = [];
/**
* PRIVATE Property: controls
* {Object({key:<OpenLayers.Control>})} Dictionary of controls
*/
var controls = {};
/**
* PRIVATE Property: getFeatureInfoVendorParams
* {object} Additional QGIS Server parameter for click tolerance in pixels
*/
var defaultGetFeatureInfoTolerances = {
'FI_POINT_TOLERANCE': 25,
'FI_LINE_TOLERANCE': 10,
'FI_POLYGON_TOLERANCE': 5
};
/**
* PRIVATE Property: externalBaselayersReplacement
*
*/
var externalBaselayersReplacement = {
'osm': 'osm-mapnik',
'osm-toner': 'osm-stamen-toner',
'opentopomap': 'open-topo-map',
'osm-cycle': 'osm-cyclemap',
'gsat': 'google-satellite',
'ghyb': 'google-hybrid',
'gphy': 'google-terrain',
'gmap': 'google-street',
'bmap': 'bing-road',
'baerial': 'bing-aerial',
'bhybrid': 'bing-hybrid',
'ignmap': 'ign-scan',
'ignplan': 'ign-plan',
'ignphoto': 'ign-photo',
'igncadastral': 'ign-cadastral'
};
/**
* PRIVATE Property: cleanNameMap
*
*/
var cleanNameMap = {
};
/**
* PRIVATE Property: layerIdMap
*
*/
var layerIdMap = {
};
/**
* PRIVATE Property: shortNameMap
*
*/
var shortNameMap = {
};
/**
* PRIVATE Property: typeNameMap
*
*/
var typeNameMap = {
};
/**
* PRIVATE Property: layerCleanNames
*
*/
var layerCleanNames = {};
/**
* PRIVATE Property: lizmapLayerFilterActive. Contains layer name if filter is active
*
*/
var lizmapLayerFilterActive = null;
/**
* PRIVATE Property: editionPending. True when an edition form has already been displayed. Used to prevent double-click on launchEdition button
*
*/
var editionPending = false;
var lastLonLatInfo = null;
/**
* Get the metadata written in the configuration file by the desktop Lizmap plugin.
*
* This method should be used EVERY TIME we need to add "if" conditions
* to adapt the code for configuration parameters changes across versions.
* This will ease in the future the review of the code to remove all the "if"
* conditions: we will just need to search for "getLizmapDesktopPluginMetadata"
* and not: "if 'someproperty' in someconfig".
*
* For very old configuration files, which have not the needed metadata, we
* return fake versions for each property.
*
* Dependencies:
* config
*/
function getLizmapDesktopPluginMetadata()
{
// Default fake versions if the properties does not yet exist in configuration file
// lizmap/modules/lizmap/lib/Project/Project.php
var plugin_metadata = {
lizmap_plugin_version_str: "3.1.8",
lizmap_plugin_version: 30108,
lizmap_web_client_target_version: 30200,
qgis_desktop_version: 30000
};
if (!('metadata' in config)) {
return plugin_metadata;
}
if ('lizmap_plugin_version' in config['metadata']) {
plugin_metadata['lizmap_plugin_version'] = config['metadata']['lizmap_plugin_version'];
}
if ('lizmap_web_client_target_version' in config['metadata']) {
plugin_metadata['lizmap_web_client_target_version'] = config['metadata']['lizmap_web_client_target_version'];
}
if ('qgis_desktop_version' in config['metadata']) {
plugin_metadata['qgis_desktop_version'] = config['metadata']['qgis_desktop_version'];
}
return plugin_metadata;
}
/**
*
* @param aName
*/
function performCleanName(aName) {
var accentMap = {
"à": "a", "á": "a", "â": "a", "ã": "a", "ä": "a", "ç": "c", "è": "e", "é": "e", "ê": "e", "ë": "e", "ì": "i", "í": "i", "î": "i", "ï": "i", "ñ": "n", "ò": "o", "ó": "o", "ô": "o", "õ": "o", "ö": "o", "ù": "u", "ú": "u", "û": "u", "ü": "u", "ý": "y", "ÿ": "y",
"À": "A", "Á": "A", "Â": "A", "Ã": "A", "Ä": "A", "Ç": "C", "È": "E", "É": "E", "Ê": "E", "Ë": "E", "Ì": "I", "Í": "I", "Î": "I", "Ï": "I", "Ñ": "N", "Ò": "O", "Ó": "O", "Ô": "O", "Õ": "O", "Ö": "O", "Ù": "U", "Ú": "U", "Û": "U", "Ü": "U", "Ý": "Y",
"-":" ", "'": " ", "(": " ", ")": " "};
var normalize = function( term ) {
var ret = "";
for ( var i = 0; i < term.length; i++ ) {
ret += accentMap[ term.charAt(i) ] || term.charAt(i);
}
return ret;
};
var theCleanName = normalize(aName);
var reg = new RegExp('\\W', 'g');
return theCleanName.replace(reg, '_');
}
/**
* PRIVATE function: cleanName
* cleaning layerName for class and layer
* @param aName
*/
function cleanName(aName){
if ( aName in cleanNameMap )
return aName;
if ( aName == undefined ) {
console.log( "An undefined name has been clean" );
return '';
}
var theCleanName = performCleanName( aName );
if ( (theCleanName in cleanNameMap) && cleanNameMap[theCleanName] != aName ){
var i = 1;
var nCleanName = theCleanName+i;
while( (nCleanName in cleanNameMap) && cleanNameMap[nCleanName] != aName ){
i += 1;
nCleanName = theCleanName+i;
}
theCleanName = nCleanName;
}
cleanNameMap[theCleanName] = aName;
return theCleanName;
}
/**
*
* @param cleanName
*/
function getNameByCleanName( cleanName ){
var name = null;
if( cleanName in cleanNameMap )
name = cleanNameMap[cleanName];
return name;
}
/**
*
* @param shortName
*/
function getNameByShortName( shortName ){
var name = null;
if( shortName in shortNameMap )
name = shortNameMap[shortName];
return name;
}
/**
*
* @param typeName
*/
function getNameByTypeName( typeName ){
var name = null;
if( typeName in typeNameMap )
name = typeNameMap[typeName];
return name;
}
/**
*
* @param cleanName
*/
function getLayerNameByCleanName( cleanName ){
var layerName = null;
if( cleanName in layerCleanNames )
layerName = layerCleanNames[cleanName];
if ( layerName == null && cleanName in cleanNameMap ) {
layerName = cleanNameMap[cleanName];
layerCleanNames[cleanName] = layerName;
}
return layerName;
}
/**
* PRIVATE function: updateMobile
* Determine if we should display the mobile version.
*/
function updateMobile(){
var isMobile = mCheckMobile();
var contentIsMobile = $('#content').hasClass('mobile');
if (isMobile == contentIsMobile)
return;
if (isMobile) {
// Add mobile class to content
$('#content, #headermenu').addClass('mobile');
// Hide switcher
if( $('#button-switcher').parent().hasClass('active') )
$('#button-switcher').click();
if( $('#menu').is(':visible'))
$('#menu').hide();
$('#map-content').append($('#toolbar'));
$('#toggleLegend')
.attr('data-bs-toggle', 'tooltip')
.attr('data-bs-title',$('#toggleLegendOn').attr('value'))
.parent().attr('class','legend');
// autocompletion items for locatebylayer feature
$('div.locate-layer select').show();
$('span.custom-combobox').hide();
}
else
{
// Remove mobile class to content
$('#content, #headermenu').removeClass('mobile');
// Show switcher
if( !( $('#button-switcher').parent().hasClass('active') ) )
$('#button-switcher').click();
if( !$('#menu').is(':visible'))
$('#content span.ui-icon-open-menu').click();
else
$('#map-content').show();
$('#toolbar').insertBefore($('#switcher-menu'));
$('#toggleLegend')
.attr('data-bs-toggle', 'tooltip')
.attr('data-bs-title',$('#toggleMapOnlyOn').attr('value'))
.parent().attr('class','map');
// autocompletion items for locatebylayer feature
$('div.locate-layer select').hide();
$('span.custom-combobox').show();
}
}
/**
* PRIVATE function: updateContentSize
* update the content size
*/
function updateContentSize(){
if (document.querySelector('body').classList.contains('print_popup')) {
return;
}
updateMobile();
// calculate height height
var h = $(window).innerHeight();
h = h - $('#header').height();
$('#map').height(h);
// Update body padding top by summing up header+headermenu
$('body').css('padding-top', $('#header').outerHeight() );
// calculate map width depending on theme configuration
// (fullscreen map or not, mobile or not)
var w = $('body').parent()[0].offsetWidth;
w -= parseInt($('#map-content').css('margin-left'));
w -= parseInt($('#map-content').css('margin-right'));
if ($('#menu').is(':hidden') || $('#map-content').hasClass('fullscreen')) {
$('#map-content').css('margin-left','auto');
} else {
w -= $('#menu').width();
$('#map-content').css('margin-left', $('#menu').width());
}
$('#map').width(w);
if ( $('#right-dock-tabs').is(':visible') ){
$('#right-dock-content').css( 'max-height', $('#right-dock').height() - $('#right-dock-tabs').height() );
}
}
/**
* PRIVATE function: getLayerScale
* get the layer scales based on children layer
*
* Parameters:
* nested - {Object} a capability layer
* minScale - {Float} the nested min scale
* maxScale - {Float} the nested max scale
*
* Dependencies:
* config
*
* Returns:
* {Object} the min and max scales
* @param nested
* @param minScale
* @param maxScale
*/
function getLayerScale(nested,minScale,maxScale) {
for (var i = 0, len = nested.nestedLayers.length; i<len; i++) {
var layer = nested.nestedLayers[i];
var qgisLayerName = layer.name;
if ( 'useLayerIDs' in config.options && config.options.useLayerIDs == 'True' )
qgisLayerName = layerIdMap[layer.name];
else if ( layer.name in shortNameMap )
qgisLayerName = shortNameMap[layer.name];
var layerConfig = config.layers[qgisLayerName];
if (layer.nestedLayers.length != 0)
return getLayerScale(layer,minScale,maxScale);
if (layerConfig) {
if (minScale == null)
minScale=layerConfig.minScale;
else if (layerConfig.minScale<minScale)
minScale=layerConfig.minScale;
if (maxScale == null)
maxScale=layerConfig.maxScale;
else if (layerConfig.maxScale>maxScale)
maxScale=layerConfig.maxScale;
}
}
if ( minScale < 1 )
minScale = 1;
return {minScale:minScale,maxScale:maxScale};
}
/**
* PRIVATE function: getLayerOrder
* get the layer order and calculate it if it's a QGIS group
*
* Parameters:
* nested - {Object} a capability layer
*
* Dependencies:
* config
*
* Returns:
* {int} the layer's order
* @param nested
*/
function getLayerOrder(nested) {
// there is no layersOrder in the project
if (!('layersOrder' in config))
return -1;
// the nested is a layer and not a group
if (nested.nestedLayers.length == 0) {
var qgisLayerName = nested.name;
if ( 'useLayerIDs' in config.options && config.options.useLayerIDs == 'True' )
qgisLayerName = layerIdMap[nested.name];
else if ( nested.name in shortNameMap )
qgisLayerName = shortNameMap[nested.name];
if (qgisLayerName in config.layersOrder)
return config.layersOrder[nested.name];
else
return -1;
}
// the nested is a group
var order = -1;
for (var i = 0, len = nested.nestedLayers.length; i<len; i++) {
var layer = nested.nestedLayers[i];
var qgisLayerName = layer.name;
if ( 'useLayerIDs' in config.options && config.options.useLayerIDs == 'True' )
qgisLayerName = layerIdMap[layer.name];
else if ( layer.name in shortNameMap )
qgisLayerName = shortNameMap[layer.name];
var lOrder = -1;
if (layer.nestedLayers.length != 0)
lOrder = getLayerScale(layer);
else if (qgisLayerName in config.layersOrder)
lOrder = config.layersOrder[qgisLayerName];
else
lOrder = -1;
if (lOrder != -1) {
if (order == -1 || lOrder < order)
order = lOrder;
}
}
return order;
}
/**
*
*/
function buildNativeScales() {
if (('EPSG:900913' in Proj4js.defs)
&& !('EPSG:3857' in Proj4js.defs)) {
Proj4js.defs['EPSG:3857'] = Proj4js.defs['EPSG:900913'];
}
// Check config projection
var proj = config.options.projection;
if (proj.ref) {
if ( !(proj.ref in Proj4js.defs) ) {
Proj4js.defs[proj.ref]=proj.proj4;
}
// Build proj
new OpenLayers.Projection(proj.ref);
} else {
proj.ref = 'EPSG:3857';
proj.proj4 = Proj4js.defs['EPSG:3857'];
}
// Force projection if config contains old external baselayers
// To be removed when baselayers only in the tree
if ('osmMapnik' in config.options
|| 'osmStamenToner' in config.options
|| 'openTopoMap' in config.options
|| 'osmCyclemap' in config.options
|| 'googleStreets' in config.options
|| 'googleSatellite' in config.options
|| 'googleHybrid' in config.options
|| 'googleTerrain' in config.options
|| 'bingStreets' in config.options
|| 'bingSatellite' in config.options
|| 'bingHybrid' in config.options
|| 'ignTerrain' in config.options
|| 'ignStreets' in config.options
|| 'ignSatellite' in config.options
|| 'ignCadastral' in config.options) {
// get projection
var projection = new OpenLayers.Projection(proj.ref);
// get and define the max extent
var bbox = config.options.bbox;
var initialBbox = config.options.initialExtent;
var extent = new OpenLayers.Bounds(Number(bbox[0]),Number(bbox[1]),Number(bbox[2]),Number(bbox[3]));
var initialExtent = new OpenLayers.Bounds(Number(initialBbox[0]),Number(initialBbox[1]),Number(initialBbox[2]),Number(initialBbox[3]));
extent.transform(projection, 'EPSG:3857');
initialExtent.transform(projection, 'EPSG:3857');
config.options.bbox = extent.toArray();
config.options.initialExtent = initialExtent.toArray();
proj.ref = 'EPSG:3857';
proj.proj4 = Proj4js.defs['EPSG:3857'];
}
var scales = [];
if ('mapScales' in config.options){
scales = Array.from(config.options.mapScales);
}
if (scales.length < 2){
scales = [config.options.maxScale,config.options.minScale];
}
scales.sort(function(a, b) {
return Number(b) - Number(a);
});
var useNativeZoomLevels = false;
if (!('use_native_zoom_levels' in config.options)) {
if (proj.ref == 'EPSG:3857') {
useNativeZoomLevels = true;
}
if (scales.length == 2) {
useNativeZoomLevels = true;
}
} else {
useNativeZoomLevels = config.options['use_native_zoom_levels'];
}
// Nothing to change
if (!useNativeZoomLevels) {
return;
}
if (proj.ref == 'EPSG:3857') {
var projOSM = new OpenLayers.Projection('EPSG:3857');
var resolutions = [];
config.options.zoomLevelNumber = 24;
var maxScale = scales[0];
var maxRes = OpenLayers.Util.getResolutionFromScale(maxScale, projOSM.proj.units);
var minScale = scales[scales.length-1];
var minRes = OpenLayers.Util.getResolutionFromScale(minScale, projOSM.proj.units);
var res = 156543.03390625;
var n = 1;
while ( res > minRes && n < config.options.zoomLevelNumber) {
if ( res < maxRes ) {
//Add extra scale
resolutions.push(res);
}
res = res/2;
n++;
}
maxRes = resolutions[0];
minRes = resolutions[resolutions.length-1];
//Add extra scale
var maxScale = OpenLayers.Util.getScaleFromResolution(maxRes, projOSM.proj.units);
var minScale = OpenLayers.Util.getScaleFromResolution(minRes, projOSM.proj.units);
config.options['resolutions'] = resolutions;
if (resolutions.length != 0 ) {
config.options.zoomLevelNumber = resolutions.length;
config.options.maxScale = maxScale;
config.options.minScale = minScale;
}
} else if (scales.length == 2) {
var nativeScales = [];
var maxScale = scales[0];
var minScale = scales[scales.length-1];
let n=1;
while (10*Math.pow(10,n)-1 < maxScale) {
nativeScales = nativeScales.concat([10, 25, 50].map((x) => Math.pow(10,n)*x));
n++;
}
let mapScales = [];
for (const scale of nativeScales) {
if (scale < minScale) {
continue;
}
if (scale > maxScale) {
break;
}
mapScales.push(scale);
}
mapScales.sort(function(a, b) {
return Number(b) - Number(a);
});
config.options.mapScales = mapScales;
config.options.maxScale = scales[0];
config.options.minScale = scales[scales.length-1];
}
}
/**
*
* @param firstLayer
*/
function initProjections(firstLayer) {
// Set Proj4js.libPath
const proj4jsLibPath = document.body.dataset?.proj4jsLibPath;
if (proj4jsLibPath) {
Proj4js.libPath = proj4jsLibPath;
}
// Insert or update projection list
if ( globalThis['lizProj4'] ) {
for( var ref in globalThis['lizProj4'] ) {
if ( !(ref in Proj4js.defs) ) {
Proj4js.defs[ref]=globalThis['lizProj4'][ref];
}
}
}
// get and define projection
var proj = config.options.projection;
if ( !(proj.ref in Proj4js.defs) )
Proj4js.defs[proj.ref]=proj.proj4;
var projection = new OpenLayers.Projection(proj.ref);
if ( !(proj.ref in OpenLayers.Projection.defaults) ) {
OpenLayers.Projection.defaults[proj.ref] = projection;
// test extent for inverted axis
if ( proj.ref in firstLayer.bbox ) {
var wmsBbox = firstLayer.bbox[proj.ref].bbox;
var wmsBounds = OpenLayers.Bounds.fromArray( wmsBbox );
var initBounds = OpenLayers.Bounds.fromArray( config.options.initialExtent );
if ( !initBounds.intersectsBounds( wmsBounds ) )
OpenLayers.Projection.defaults[proj.ref].yx = true;
}
}
}
/**
* PRIVATE function: createMap
* creating the map {<OpenLayers.Map>}
* @param {Array} initialExtent initial extent in EPSG:4326 projection
*/
function createMap(initialExtent) {
// get projection
var proj = config.options.projection;
var projection = new OpenLayers.Projection(proj.ref);
var proj4326 = new OpenLayers.Projection('EPSG:4326');
var initialExtentProj = proj4326;
var zoomToClosest = false;
// get and define the max extent
var bbox = config.options.bbox;
var extent = new OpenLayers.Bounds(Number(bbox[0]),Number(bbox[1]),Number(bbox[2]),Number(bbox[3]));
var restrictedExtent = extent.scale(3);
const urlParameters = (new URL(document.location)).searchParams;
if (urlParameters.has('bbox')) {
let initialExtentParam = urlParameters.get('bbox').split(',');
if (initialExtentParam.length === 4) {
initialExtent = initialExtentParam;
}
if (urlParameters.has('crs')) {
initialExtentProj = new OpenLayers.Projection(urlParameters.get('crs'));
}
zoomToClosest = true;
}
let initialExtentPermalink = window.location.hash.substring(1).split('|')[0].split(',');
if (initialExtentPermalink.length === 4) {
initialExtent = initialExtentPermalink;
initialExtentProj = proj4326;
zoomToClosest = true;
}
if(initialExtent){
initialExtent = new OpenLayers.Bounds(initialExtent);
initialExtent.transform(initialExtentProj, projection);
} else {
initialExtent = extent.clone();
if ( 'initialExtent' in config.options && config.options.initialExtent.length == 4 ) {
var initBbox = config.options.initialExtent;
initialExtent = new OpenLayers.Bounds(Number(initBbox[0]),Number(initBbox[1]),Number(initBbox[2]),Number(initBbox[3]));
}
}
// calculate the map height
var mapHeight = $('body').parent()[0].clientHeight;
if(!mapHeight)
mapHeight = $('window').innerHeight();
mapHeight = mapHeight - $('#header').height();
mapHeight = mapHeight - $('#headermenu').height();
$('#map').height(mapHeight);
// Make sure interface divs size are updated before creating the map
// This avoid the request of each singlettile layer 2 times on startup
updateContentSize();
var scales = [];
var resolutions = [];
if ('resolutions' in config.options){
resolutions = Array.from(config.options.resolutions);
}
else if ('mapScales' in config.options){
scales = Array.from(config.options.mapScales);
}
scales.sort(function(a, b) {
return Number(b) - Number(a);
});
// remove duplicate scales
var nScales = [];
while (scales.length != 0){
var scale = scales.pop(0);
if ($.inArray( scale, nScales ) == -1 )
nScales.push( scale );
}
scales = nScales;
// creating the map
OpenLayers.Util.IMAGE_RELOAD_ATTEMPTS = 3; // Avoid some issues with tiles not displayed
OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3;
OpenLayers.Util.DEFAULT_PRECISION=20; // default is 14 : change needed to avoid rounding problem with cache
map = new OpenLayers.Map('map'
,{
controls:[
new OpenLayers.Control.Navigation({mouseWheelOptions: {interval: 100}})
]
,tileManager: null // prevent bug with OL 2.13 : white tiles on panning back
,maxExtent:extent
,restrictedExtent: restrictedExtent
,initialExtent:initialExtent
,zoomToClosest: zoomToClosest
,maxScale: scales.length == 0 ? config.options.minScale : "auto"
,minScale: scales.length == 0 ? config.options.maxScale : "auto"
,numZoomLevels: scales.length == 0 ? config.options.zoomLevelNumber : scales.length
,scales: scales.length == 0 ? null : scales
,resolutions: resolutions.length == 0 ? null : resolutions
,projection:projection
,units:projection.proj.units !== null ? projection.proj.units : "degrees"
,allOverlays:(baselayers.length == 0)
,autoUpdateSize: false
});
// add handler to update the map size
window.addEventListener('resize', updateContentSize);
}
/**
* Get features for locate by layer tool
* @param aName
*/
function updateLocateFeatureList(aName) {
var locate = config.locateByLayer[aName];
// clone features reference
var features = {};
for ( var fid in locate.features ) {
features[fid] = locate.features[fid];
}
// filter by filter field name
if ('filterFieldName' in locate) {
var filterValue = $('#locate-layer-'+cleanName(aName)+'-'+locate.filterFieldName).val();
if ( filterValue != '-1' ) {
for (var fid in features) {
var feat = features[fid];
if (feat.properties[locate.filterFieldName] != filterValue)
delete features[fid];
}
} else
features = {}
}
// filter by vector joins
if ( 'vectorjoins' in locate && locate.vectorjoins.length != 0 ) {
var vectorjoins = locate.vectorjoins;
for ( var i=0, len =vectorjoins.length; i< len; i++) {
var vectorjoin = vectorjoins[i];
var jName = vectorjoin.joinLayer;
if ( jName in config.locateByLayer ) {
var jLocate = config.locateByLayer[jName];
var jVal = $('#locate-layer-'+cleanName(jName)).val();
if ( jVal == '-1' ) continue;
var jFeat = jLocate.features[jVal];
for (var fid in features) {
var feat = features[fid];
if ( feat.properties[vectorjoin.targetFieldName] != jFeat.properties[vectorjoin.joinFieldName] )
delete features[fid];
}
}
}
}
// create the option list
const placeHolder = config.layers[aName].title;
var options = '<option value="-1" label="'+placeHolder+'"></option>';
for (var fid in features) {
var feat = features[fid];
options += '<option value="' + feat.id + '">' + DOMPurify.sanitize(feat.properties[locate.fieldName]) + '</option>';
}
// add option list
$('#locate-layer-'+cleanName(aName)).html(options);
}
/**
*
* @param layer_name
*/
function clearDrawLayer(layer_name) {
var layer = map.getLayersByName(layer_name);
if (layer.length == 0) {
return;
}
layer[0].destroyFeatures();
}
/**
* Zoom to locate feature
* @param aName
*/
function zoomToLocateFeature(aName) {
// clear highlight layer
lizMap.mainLizmap.map.clearHighlightFeatures();
// get locate by layer val
var locate = config.locateByLayer[aName];
var layerName = cleanName(aName);
var proj = new OpenLayers.Projection(locate.crs);
var val = $('#locate-layer-'+layerName).val();
if (val == '-1') {
// Trigger event
lizMap.events.triggerEvent('lizmaplocatefeaturecanceled',
{
'featureType': aName
}
);
} else {
// zoom to val
var feat = locate.features[val];
var format = new OpenLayers.Format.GeoJSON({
ignoreExtraDims: true
});
feat = format.read(feat)[0];
if( feat.geometry != null){
feat.geometry.transform(proj, map.getProjection());
// Show geometry if asked
if (locate.displayGeom == 'True') {
var getFeatureUrlData = getVectorLayerWfsUrl( aName, null, null, null );
getFeatureUrlData['options']['PROPERTYNAME'] = ['geometry',locate.fieldName].join(',');
getFeatureUrlData['options']['FEATUREID'] = val;
// Get data
$.post( getFeatureUrlData['url'], getFeatureUrlData['options'], function(data) {
if ( !data.features ){
data = JSON.parse(data);
}
lizMap.mainLizmap.map.setHighlightFeatures(data.features[0], "geojson");
}).fail(function(){
lizMap.mainLizmap.map.setHighlightFeatures(feat, "geojson");
});
}
// zoom to extent
map.zoomToExtent(feat.geometry.getBounds());
}
var fid = val.split('.')[1];
// Trigger event
lizMap.events.triggerEvent('lizmaplocatefeaturechanged',
{
'featureType': aName,
'featureId': fid
}
);
}
}
/**
* Get features for locate by layer tool
* @param aName
*/
function getLocateFeature(aName) {
var locate = config.locateByLayer[aName];
// get fields to retrieve
var fields = ['geometry',locate.fieldName];
// if a filter field is defined
if ('filterFieldName' in locate)
fields.push( locate.filterFieldName );
// check for join fields
if ( 'filterjoins' in locate ) {
var filterjoins = locate.filterjoins;
for ( var i=0, len=filterjoins.length; i<len; i++) {
var filterjoin = filterjoins[i];
fields.push( filterjoin.targetFieldName );
}
}
if ( 'vectorjoins' in locate ) {
var vectorjoins = locate.vectorjoins;
for ( var i=0, len=vectorjoins.length; i<len; i++) {
var vectorjoin = vectorjoins[i];
fields.push( vectorjoin.targetFieldName );
}
}
// Get WFS url and options
var getFeatureUrlData = getVectorLayerWfsUrl( aName, null, null, 'extent' );
getFeatureUrlData['options']['PROPERTYNAME'] = fields.join(',');
var layerName = cleanName(aName);
// Get data
$.post( getFeatureUrlData['url'], getFeatureUrlData['options'], function(data) {
var lConfig = config.layers[aName];
locate['features'] = {};
if ( !data.features )
data = JSON.parse(data);
var features = data.features;
if( locate.crs != 'EPSG:4326' && features.length != 0) {
// load projection to be sure to have the definition
loadProjDefinition( locate.crs, function() {
locate.crs = 'EPSG:4326';
});
}
if ('filterFieldName' in locate) {
// create filter combobox for the layer
features.sort(function(a, b) {
var aProperty = a.properties[locate.filterFieldName];
var bProperty = b.properties[locate.filterFieldName];
if (isNaN(aProperty)) {
if (isNaN(bProperty)) { // a and b are strings
return aProperty.localeCompare(bProperty);
} else { // a string and b number
return 1; // a > b
}
} else {
if (isNaN(bProperty)) { // a number and b string
return -1; // a < b
} else { // a and b are numbers
return parseFloat(aProperty) - parseFloat(bProperty);
}
}
});
var filterPlaceHolder = '';
if ( 'filterFieldAlias' in locate && locate.filterFieldAlias!='')
filterPlaceHolder += locate.filterFieldAlias+' ';
else
filterPlaceHolder += locate.filterFieldName;
filterPlaceHolder +=' ('+ lConfig.title + ')';
var fOptions = '<option value="-1"></option>';
var fValue = '-1';
for (var i=0, len=features.length; i<len; i++) {
var feat = features[i];
if ( fValue != feat.properties[locate.filterFieldName] ) {
fValue = feat.properties[locate.filterFieldName];
fOptions += '<option value="'+fValue+'">'+fValue+'</option>';
}
}
// add filter values list
$('#locate-layer-'+layerName).parent().before('<div class="locate-layer"><select id="locate-layer-'+layerName+'-'+locate.filterFieldName+'">'+fOptions+'</select></div><br/>');
// listen to filter select changes
$('#locate-layer-'+layerName+'-'+locate.filterFieldName).change(function(){
var filterValue = $(this).children(':selected').val();
updateLocateFeatureList( aName );
if (filterValue == '-1')
$('#locate-layer-'+layerName+'-'+locate.filterFieldName+' ~ span > input').val('');
$('#locate-layer-'+layerName+' ~ span > input').val('');
$('#locate-layer-'+layerName).val('-1');
zoomToLocateFeature(aName);
});
// add combobox to the filter select
$('#locate-layer-'+layerName+'-'+locate.filterFieldName).combobox({
position: { my : "right top", at: "right bottom" },
"selected": function(evt, ui){
if ( ui.item ) {
var self = $(this);
var uiItem = $(ui.item);
window.setTimeout(function(){
self.val(uiItem.val()).change();
}, 1);
}
}
});
// add place holder to the filter combobox input
$('#locate-layer-'+layerName+'-'+locate.filterFieldName+' ~ span > input').attr('placeholder', filterPlaceHolder).val('');
$('#locate-layer-'+layerName+'-'+locate.filterFieldName+' ~ span > input').autocomplete('close');
}
// create combobox for the layer
features.sort(function(a, b) {
var aProperty = a.properties[locate.fieldName];
var bProperty = b.properties[locate.fieldName];
if (isNaN(aProperty)) {
if (isNaN(bProperty)) { // a and b are strings
return aProperty.localeCompare(bProperty);
} else { // a string and b number
return 1; // a > b
}
} else {
if (isNaN(bProperty)) { // a number and b string
return -1; // a < b
} else { // a and b are numbers
return parseFloat(aProperty) - parseFloat(bProperty);
}
}
});
var placeHolder = '';
if ('filterFieldName' in locate) {
if ( 'fieldAlias' in locate && locate.fieldAlias!='' )
placeHolder += locate.fieldAlias+' ';
else
placeHolder += locate.fieldName+' ';
placeHolder += '('+lConfig.title+')';
} else {
placeHolder = lConfig.title;
}
var options = '<option value="-1"></option>';
for (var i=0, len=features.length; i<len; i++) {
var feat = features[i];
locate.features[feat.id.toString()] = feat;
if ( !('filterFieldName' in locate) )
options += '<option value="' + feat.id + '">' + DOMPurify.sanitize(feat.properties[locate.fieldName]) + '</option>';
}
// listen to select changes
$('#locate-layer-'+layerName).html(options).change(function() {
var val = $(this).children(':selected').val();
if (val == '-1') {
$('#locate-layer-'+layerName+' ~ span > input').val('');
// update to join layer
if ( 'filterjoins' in locate && locate.filterjoins.length != 0 ) {
var filterjoins = locate.filterjoins;
for (var i=0, len=filterjoins.length; i<len; i++) {
var filterjoin = filterjoins[i];
var jName = filterjoin.joinLayer;
if ( jName in config.locateByLayer ) {
// update joined select options
var oldVal = $('#locate-layer-'+cleanName(jName)).val();
updateLocateFeatureList( jName );
$('#locate-layer-'+cleanName(jName)).val( oldVal );
return;
}
}
}
// zoom to parent selection
if ( 'vectorjoins' in locate && locate.vectorjoins.length == 1 ) {
var jName = locate.vectorjoins[0].joinLayer;
if ( jName in config.locateByLayer ) {
zoomToLocateFeature( jName );
return;
}
}
// clear the map
zoomToLocateFeature( aName );
} else {
// zoom to val
zoomToLocateFeature( aName );
// update joined layer
if ( 'filterjoins' in locate && locate.filterjoins.length != 0 ) {
var filterjoins = locate.filterjoins;
for (var i=0, len=filterjoins.length; i<len; i++) {
var filterjoin = filterjoins[i];
var jName = filterjoin.joinLayer;
if ( jName in config.locateByLayer ) {
// update joined select options
updateLocateFeatureList( jName );
$('#locate-layer-'+cleanName(jName)).val('-1');
$('#locate-layer-'+cleanName(jName)+' ~ span > input').val('');
}
}
}
}
$(this).blur();
return;
});
$('#locate-layer-'+layerName).combobox({
"minLength": ('minLength' in locate) ? locate.minLength : 0,
"position": { my : "right top", at: "right bottom" },
"selected": function(evt, ui){
if ( ui.item ) {
var self = $(this);
var uiItem = $(ui.item);
window.setTimeout(function(){
self.val(uiItem.val()).change();
}, 1);
}
}
});
$('#locate-layer-'+layerName+' ~ span > input').attr('placeholder', placeHolder).val('');
$('#locate-layer-'+layerName+' option[value=-1]').attr('label', placeHolder);
$('#locate-layer-'+layerName+' ~ span > input').autocomplete('close');
if ( ('minLength' in locate) && locate.minLength > 0 )
$('#locate-layer-'+layerName).parent().addClass('no-toggle');
if(mCheckMobile()){
// autocompletion items for locatebylayer feature
$('div.locate-layer select').show();
$('span.custom-combobox').hide();
}
},'json');
}
function addLocateByLayer(){
var locateByLayerList = [];
for (var lname in config.locateByLayer) {
if ('order' in config.locateByLayer[lname])
locateByLayerList[config.locateByLayer[lname].order] = lname;
else
locateByLayerList.push(lname);
}
var locateContent = [];
for (var l in locateByLayerList) {
var lname = locateByLayerList[l];
var lConfig = config.layers[lname];
var html = '<div class="locate-layer">';
html += '<select id="locate-layer-' + cleanName(lname) + '" class="label">';
html += '<option>' + lConfig.title + '...</option>';
html += '</select>';
html += '</div>';
//constructing the select
locateContent.push(html);
}
$('#locate .menu-content').html(locateContent.join('<hr/>'));
var featureTypes = lizMap.mainLizmap.initialConfig.vectorLayerFeatureTypeList;
if (featureTypes.length == 0) {
config.locateByLayer = {};
$('#button-locate').parent().remove();
$('#locate-menu').remove();
} else {
for (const featureType of featureTypes) {
var typeName = featureType.Name;
var lname = lizMap.getNameByTypeName(typeName);
if (!lname) {
if (typeName in config.locateByLayer)
lname = typeName
else if ((typeName in shortNameMap) && (shortNameMap[typeName] in config.locateByLayer))
lname = shortNameMap[typeName];
else {
for (var lbl in config.locateByLayer) {
if (lbl.split(' ').join('_') == typeName) {
lname = lbl;
break;
}
}
}
}
if (!(lname in config.locateByLayer))
continue;
var locate = config.locateByLayer[lname];
locate['crs'] = featureType.SRS;
loadProjDefinition(locate.crs, function () {
new OpenLayers.Projection(locate.crs);
});
locate['bbox'] = featureType.LatLongBoundingBox;
}
// get joins
for (var lName in config.locateByLayer) {
var locate = config.locateByLayer[lName];
if ('vectorjoins' in locate && locate['vectorjoins'].length != 0) {
var vectorjoin = locate['vectorjoins'][0];
locate['joinFieldName'] = vectorjoin['targetFieldName'];
for (var jName in config.locateByLayer) {
var jLocate = config.locateByLayer[jName];
if (jLocate.layerId == vectorjoin.joinLayerId) {
vectorjoin['joinLayer'] = jName;
locate['joinLayer'] = jName;
jLocate['joinFieldName'] = vectorjoin['joinFieldName'];
jLocate['joinLayer'] = lName;
jLocate['filterjoins'] = [{
'targetFieldName': vectorjoin['joinFieldName'],
'joinFieldName': vectorjoin['targetFieldName'],
'joinLayerId': locate.layerId,
'joinLayer': lName
}];
}
}
}
}
// get locate by layers features
for (var lname in config.locateByLayer) {
getLocateFeature(lname);
}
$('.btn-locate-clear').click(function () {
lizMap.mainLizmap.map.clearHighlightFeatures();
$('#locate select').val('-1');
$('div.locate-layer span > input').val('');
if (lizMap.lizmapLayerFilterActive) {
lizMap.events.triggerEvent('lizmaplocatefeaturecanceled',
{ 'featureType': lizMap.lizmapLayerFilterActive }
);
}
return false;
});
$('#locate-close').click(function () {
$('.btn-locate-clear').click(); // deactivate locate and filter
document.getElementById('button-locate')?.click();
return false;
});
}
}
/**
* PRIVATE function: createToolbar
* create the tool bar (collapse switcher, etc)
*/
function createToolbar() {
var configOptions = config.options;
addFeatureInfo();
if (('geolocation' in configOptions)
&& configOptions['geolocation'] == 'True'){
$('#geolocation button.btn-geolocation-close').click(function () {
$('#button-geolocation').click();
return false;
});
}
if ( ('measure' in configOptions)
&& configOptions['measure'] == 'True')
addMeasureControls();
else {
$('#measure').parent().remove();
$('#measure-length-menu').remove();
$('#measure-area-menu').remove();
$('#measure-perimeter-menu').remove();
}
}
/**
* PRIVATE function: deactivateToolControls
* Deactivate Openlayers controls
* @param evt
*/
function deactivateToolControls( evt ) {
for (var id in controls) {
var ctrl = controls[id];
if(ctrl){
if (evt && ('object' in evt) && ctrl == evt.object){
continue;
}
if (ctrl.type == OpenLayers.Control.TYPE_TOOL){
ctrl.deactivate();
}
}
}
return true;
}
/**
*
* @param {string} text - text to display
* @param {object} xy - x and y in pixels
* @param {array} coordinate - coordinate in map unit
*/
function displayGetFeatureInfo(text, xy, coordinate){
var eventLonLatInfo = map.getLonLatFromPixel(xy);
var popup = null;
var popupContainerId = null;
if( 'popupLocation' in config.options && config.options.popupLocation != 'map' ){
popupContainerId = 'popupcontent';
// create content
var popupReg = new RegExp('lizmapPopupTable', 'g');
text = text.replace( popupReg, 'table table-condensed table-striped table-bordered lizmapPopupTable');
var pcontent = '<div class="lizmapPopupContent">'+text+'</div>';
var hasPopupContent = (!(!text || text == null || text == ''));
document.querySelector('#popupcontent div.menu-content').innerHTML = pcontent;
if ( !$('#mapmenu .nav-list > li.popupcontent').is(':visible') )
$('#mapmenu .nav-list > li.popupcontent').show();
// Warn user no data has been found
if( !hasPopupContent ){
pcontent = '<div class="lizmapPopupContent noContent"><h4>'+lizDict['popup.msg.no.result']+'</h4></div>';
document.querySelector('#popupcontent div.menu-content').innerHTML = pcontent;
window.setTimeout(function(){
if ( $('#mapmenu .nav-list > li.popupcontent').hasClass('active') &&
$('#popupcontent .lizmapPopupContent').hasClass('noContent') &&
config.options.popupLocation != 'right-dock'){
document.getElementById('button-popupcontent').click();
}
},2000);
}
// Display dock if needed
if(
!$('#mapmenu .nav-list > li.popupcontent').hasClass('active')
&& (!mCheckMobile() || ( mCheckMobile() && hasPopupContent ) )
&& (lastLonLatInfo == null || eventLonLatInfo.lon != lastLonLatInfo.lon || eventLonLatInfo.lat != lastLonLatInfo.lat)
){
document.getElementById('button-popupcontent').click();
}
else if(
$('#mapmenu .nav-list > li.popupcontent').hasClass('active')
&& ( mCheckMobile() && hasPopupContent )
){
document.getElementById('button-popupcontent').click();
}
} else {
// Hide previous popup
lizMap.mainLizmap.popup.mapPopup.setVisible(false);
if (!text || text == null || text == ''){
return false;
}
document.getElementById('liz_layer_popup_contentDiv').innerHTML = text;
if (coordinate) {
lizMap.mainLizmap.popup.mapPopup.setPosition(coordinate);
} else {
lizMap.mainLizmap.popup.mapPopup.setPosition([eventLonLatInfo.lon, eventLonLatInfo.lat]);
}
// Activate Boostrap 2 tabs here as they are not
// automatically activated when created in popup anchored
$('#' + popupContainerId + ' a[data-toggle="tab"]').on( 'click',function (e) {
e.preventDefault();
$(this).tab('show');
});
}
lastLonLatInfo = eventLonLatInfo;
// Display related children objects
addChildrenFeatureInfo( popup, popupContainerId );
// Display geometries
addGeometryFeatureInfo( popup, popupContainerId );
// Display the plots of the children layers features filtered by popup item
addChildrenDatavizFilteredByPopupFeature( popup, popupContainerId );
// Trigger event
lizMap.events.triggerEvent("lizmappopupdisplayed",
{'popup': popup, 'containerId': popupContainerId}
);
}
/**
*
* @param popup
* @param containerId
*/
function addGeometryFeatureInfo( popup, containerId ) {
// Build selector
let selector = 'div.lizmapPopupContent div.lizmapPopupDiv > input.lizmap-popup-layer-feature-geometry';
if ( containerId ){
selector = '#'+ containerId +' '+ selector;
}
// Highlight geometries
const geometriesWKT = [];
document.querySelectorAll(selector).forEach(element => geometriesWKT.push(element.value));
if (geometriesWKT.length) {
const geometryCollectionWKT = `GEOMETRYCOLLECTION(${geometriesWKT.join()})`;
lizMap.mainLizmap.map.setHighlightFeatures(geometryCollectionWKT, "wkt");
}
}
/**
*
* @param popup
* @param containerId
*/
function addChildrenDatavizFilteredByPopupFeature(popup, containerId) {
if ('datavizLayers' in lizMap.config) {
// build selector
var selector = 'div.lizmapPopupContent div.lizmapPopupDiv';
if ( containerId )
selector = '#'+ containerId +' '+ selector;
$(selector).each(function(){
var mydiv = $(this);
// Do not add plots if already present
if( $(this).find('div.lizdataviz').length > 0 )
return true;
if ($(this).find('input.lizmap-popup-layer-feature-id:first').val()) {
var getLayerId = $(this).find('input.lizmap-popup-layer-feature-id:first').val().split('.');
var popupId = getLayerId[0] + '_' + getLayerId[1];
var layerId = getLayerId[0];
var fid = getLayerId[1];
var getLayerConfig = lizMap.getLayerConfigById( layerId );
// verifiying related children objects
if ( !getLayerConfig )
return true;
var featureType = getLayerConfig[0];
// We do not want to deactivate the display of filtered children dataviz
// when children popup are not displayed : comment the 2 following lines
if ( !('relations' in lizMap.config) || !(layerId in lizMap.config.relations) )
return true;
//If dataviz exists, get config
if( !('datavizLayers' in lizMap.config ))
return true;
lizMap.getLayerFeature(featureType, fid, function(feat) {
// Where there is all plots
var plotLayers = lizMap.config.datavizLayers.layers;
var lrelations = lizMap.config.relations[layerId];
var nbPlotByLayer = 1;
for ( var i in plotLayers) {
for(var x in lrelations){
var rel = lrelations[x];
// Id of the layer which is the child of layerId
var getChildrenId = rel.referencingLayer;
// Filter of the plot
var filter = '"' + rel.referencingField + '" IN (\''+feat.properties[rel.referencedField]+'\')';
if(plotLayers[i].layer_id==getChildrenId)
{
var plot_config=plotLayers[i];
if('popup_display_child_plot' in plot_config
&& plot_config.popup_display_child_plot == "True"
){
var plot_id=plotLayers[i].plot_id;
popupId = getLayerId[0] + '_' + getLayerId[1] + '_' + String(nbPlotByLayer);
// Be sure the id is unique ( popup can be displayed in atlas tool too)
popupId+= '_' + new Date().valueOf()+btoa(Math.random()).substring(0,12);
var phtml = lizDataviz.buildPlotContainerHtml(
plot_config.title_popup,
plot_config.abstract,
popupId,
false
);
var html = '<div class="lizmapPopupChildren lizdataviz">';
html+= '<h4>'+ plot_config.title_popup+'</h4>';
html+= phtml
html+= '</div>';
var haspc = $(mydiv).find('div.lizmapPopupChildren:last');
if( haspc.length > 0 )
$(haspc).after(html);
else
$(mydiv).append(html);
lizDataviz.getPlot(plot_id, filter, popupId);
nbPlotByLayer++;
}
}
}
}
});
}
});
}else{
return false;
}
}
/**
*
* @param popup
* @param containerId
*/
function addChildrenFeatureInfo( popup, containerId ) {
var selector = 'div.lizmapPopupContent input.lizmap-popup-layer-feature-id';
if ( containerId )
selector = '#'+ containerId +' '+ selector;
$(selector).each(function(){
var self = $(this);
var val = self.val();
var fid = val.split('.').pop();
var layerId = val.replace( '.' + fid, '' );
var getLayerConfig = getLayerConfigById( layerId );
// verifiying related children objects
if ( !getLayerConfig )
return true;
var layerConfig = getLayerConfig[1];
var featureType = getLayerConfig[0];
if ( !('popupDisplayChildren' in layerConfig) || layerConfig.popupDisplayChildren != 'True')
return true;
if ( !('relations' in config) || !(layerId in config.relations) )
return true;
// Display related children objects
var relations = config.relations[layerId];
var popupMaxFeatures = 10;
if ( 'popupMaxFeatures' in layerConfig && !isNaN(parseInt(layerConfig.popupMaxFeatures)) )
popupMaxFeatures = parseInt(layerConfig.popupMaxFeatures);
popupMaxFeatures == 0 ? 10 : popupMaxFeatures;
getLayerFeature(featureType, fid, function(feat) {
var parentDiv = self.parent();
// Array of Promise w/ fetch to request children popup content
const popupChidrenRequests = [];
// Array of pre-processed objects for WMS popup requests
const preProcessedRequests = [];
// Array of object contains utilities for each relation
const preProcessUtilities = [];
const rConfigLayerAll = [];
// Build POST query for every child based on QGIS relations
for ( const relation of relations ){
const rLayerId = relation.referencingLayer;
let preProcessRequest = null;
// prepare utilities object
let rUtilities = {
rLayerId : rLayerId // pivot id or table id
};
const pivotAttributeLayerConf = lizMap.getLayerConfigById( rLayerId, lizMap.config.attributeLayers, 'layerId' );
// check if child is a pivot table
if (pivotAttributeLayerConf && pivotAttributeLayerConf[1]?.pivot == 'True' && config.relations.pivot && config.relations.pivot[rLayerId]) {
// looking for related children
const pivotConfig = lizMap.getLayerConfigById(
rLayerId,
config.layers,
'id'
);
if (pivotConfig) {
// n to m -> get "m" layer id
var mLayer = Object.keys(config.relations.pivot[rLayerId]).filter((k)=>{ return k !== layerId})
if (mLayer.length == 1) {
// "m" layer config
const mLayerConfig = getLayerConfigById( mLayer[0] );
if (mLayerConfig) {
let clRefname = mLayerConfig[1]?.shortname || mLayerConfig[1]?.cleanname;
if ( clRefname === undefined ) {
clRefname = cleanName(mLayerConfig[1].name);
mLayerConfig[1].cleanname = clRefname;
}
if (mLayerConfig[1].popup == 'True' && self.parent().find('div.lizmapPopupChildren.'+clRefname).length == 0) {
// get results from pivot table
const typeName = pivotConfig[1].typename;
const wfsParams = {
TYPENAME: typeName,
GEOMETRYNAME: 'extent'
};
wfsParams['EXP_FILTER'] = '"' + config.relations.pivot[rLayerId][layerId] + '" = ' + "'" + feat.properties[relation.referencedField] + "'";
// Calculate bbox
if (config.options?.limitDataToBbox == 'True') {
wfsParams['BBOX'] = lizMap.mainLizmap.map.getView().calculateExtent();
wfsParams['SRSNAME'] = lizMap.mainLizmap.map.getView().getProjection().getCode();
}
preProcessRequest = lizMap.mainLizmap.wfs.getFeature(wfsParams);
let ut = {
pivotTableId: rLayerId,
mLayerConfig: mLayerConfig
}
rUtilities = {...rUtilities,...ut};
}
}
}
}
} else {
// one to n relation
const rGetLayerConfig = getLayerConfigById( rLayerId );
if ( rGetLayerConfig ) {
preProcessRequest = {
oneToN:true,
layer:rGetLayerConfig[1]
}
let ut = {
referencingField: relation.referencingField,
referencedField: relation.referencedField
}
rUtilities = {...rUtilities, ...ut}
}
}
preProcessedRequests.push(preProcessRequest);
preProcessUtilities.push(rUtilities)
}
Promise.allSettled(preProcessedRequests).then(preProcessResponses =>{
for (let rr = 0; rr < preProcessResponses.length; rr++) {
const resp = preProcessResponses[rr];
const utilities = preProcessUtilities[rr];
if (resp.value) {
const respValue = resp.value;
var confLayer = null, wmsFilter = null;
if (respValue.oneToN && utilities.referencingField && utilities.referencedField) {
confLayer = respValue.layer;
wmsFilter = '"'+utilities.referencingField+'" = \''+feat.properties[utilities.referencedField]+'\'';
} else {
if (respValue.features) {
const features = respValue.features;
const referencedFieldForFilter = config.relations[utilities.mLayerConfig[1].id].filter((fil)=>{
return fil.referencingLayer == utilities.rLayerId
})[0]?.referencedField;
let filArray = [];
const feats = {};
features.forEach((feat)=>{
var fid = feat.id.split('.')[1];
feats[fid] = feat;
if (feat.properties && feat.properties[config.relations.pivot[utilities.rLayerId][utilities.mLayerConfig[1].id]]) {
filArray.push(feat.properties[config.relations.pivot[utilities.rLayerId][utilities.mLayerConfig[1].id]])
}
})
if (filArray.length) {
let fil = filArray.map(function(val){
return '"'+referencedFieldForFilter+'" = \''+val+'\'';
})
wmsFilter = fil.join(" OR ");
}
const pivotConfig = lizMap.getLayerConfigById(
utilities.pivotTableId,
config.layers,
'id'
);
pivotConfig[1].features = feats;
// get feature of mLayer
confLayer = utilities.mLayerConfig[1];
}
}
if (wmsFilter && confLayer) {
const rConfigLayer = confLayer;
let clname = rConfigLayer?.shortname || rConfigLayer.cleanname;
if ( clname === undefined ) {
clname = cleanName(rConfigLayer.name);
rConfigLayer.cleanname = clname;
}
if ( rConfigLayer.popup == 'True' && self.parent().find('div.lizmapPopupChildren.'+clname).length == 0) {
let wmsName = rConfigLayer?.shortname || rConfigLayer.name;
const wmsOptions = {
'LAYERS': wmsName
,'QUERY_LAYERS': wmsName
,'STYLES': ''
,'SERVICE': 'WMS'
,'VERSION': '1.3.0'
,'CRS': (('crs' in rConfigLayer) && rConfigLayer.crs != '') ? rConfigLayer.crs : 'EPSG:4326'
,'REQUEST': 'GetFeatureInfo'
,'EXCEPTIONS': 'application/vnd.ogc.se_inimage'
,'FEATURE_COUNT': popupMaxFeatures
,'INFO_FORMAT': 'text/html'
};
if ( 'popupMaxFeatures' in rConfigLayer && !isNaN(parseInt(rConfigLayer.popupMaxFeatures)) )
wmsOptions['FEATURE_COUNT'] = parseInt(rConfigLayer.popupMaxFeatures);
if ( wmsOptions['FEATURE_COUNT'] == 0 )
wmsOptions['FEATURE_COUNT'] = popupMaxFeatures;
if ( rConfigLayer.request_params && rConfigLayer.request_params.filter &&
rConfigLayer.request_params.filter !== '' )
wmsOptions['FILTER'] = rConfigLayer.request_params.filter+' AND '+wmsFilter;
else
wmsOptions['FILTER'] = wmsName+':'+wmsFilter;
// Fetch queries
// Keep `rConfigLayer` in array with same order that fetch queries
// for later user when Promise.allSettled resolves
rConfigLayerAll.push(rConfigLayer);
popupChidrenRequests.push(
fetch(globalThis['lizUrls'].service, {
"method": "POST",
"body": new URLSearchParams(wmsOptions)
}).then(function (response) {
return response.text();
}).then( function (textResp) {
// add utilities object to response for further controls
return {
popupChildData:textResp,
utilities:utilities
}
})
);
}
}
}
}
// Fetch GetFeatureInfo query for every children popups
Promise.allSettled(popupChidrenRequests).then(popupChildrenData => {
const childPopupElements = [];
for (let index = 0; index < popupChildrenData.length; index++) {
let popupResponse = popupChildrenData[index].value;
let popupChildData = popupResponse.popupChildData;
const utilities = popupResponse.utilities;
var hasPopupContent = (!(!popupChildData || popupChildData == null || popupChildData == ''))
if (hasPopupContent) {
var popupReg = new RegExp('lizmapPopupTable', 'g');
popupChildData = popupChildData.replace(popupReg, 'table table-condensed table-striped lizmapPopupTable');
const configLayer = rConfigLayerAll[index];
var clname = configLayer.cleanname;
if (clname === undefined) {
clname = cleanName(configLayer.name);
configLayer.cleanname = clname;
}
if(utilities.pivotTableId){
var popupFeatureToolbarReg = new RegExp('<lizmap-feature-toolbar ', 'g');
popupChildData = popupChildData.replace(popupFeatureToolbarReg,"<lizmap-feature-toolbar parent-layer-id='"+layerId+"' pivot-layer='"+utilities.pivotTableId+':'+fid+"'")
}
const resizeTablesButtons =
'<button class="compact-tables btn btn-sm" data-bs-toggle="tooltip" data-bs-title="' + lizDict['popup.table.compact'] + '"><i class="icon-resize-small"></i></button>'+
'<button class="explode-tables btn btn-sm hide" data-bs-toggle="tooltip" data-bs-title="' + lizDict['popup.table.explode'] + '"><i class="icon-resize-full"></i></button>';
var childPopup = $('<div class="lizmapPopupChildren ' + clname + '" data-layername="' + clname + '" data-title="' + configLayer.title + '">' + resizeTablesButtons + popupChildData + '</div>');
// Manage if the user choose to create a table for children
if (['qgis', 'form'].indexOf(configLayer.popupSource) !== -1 && childPopup.find('.lizmap_merged').length != 0) {
// save inputs
childPopup.find(".lizmapPopupDiv").each(function (i, e) {
var popupDiv = $(e);
if (popupDiv.find(".lizmapPopupHeader").prop("tagName") == 'TR') {
popupDiv.find(".lizmapPopupHeader").prepend("<th></th>");
popupDiv.find(".lizmapPopupHeader").next().prepend("<td></td>");
} else {
popupDiv.find(".lizmapPopupHeader").next().prepend("<span></span>");
}
popupDiv.find(".lizmapPopupHeader").next().children().first().append(popupDiv.find("input"));
});
childPopup.find("h4").each(function (i, e) {
if (i != 0)
$(e).remove();
});
childPopup.find(".lizmapPopupHeader").each(function (i, e) {
if (i != 0)
$(e).remove();
});
childPopup.find(".lizmapPopupDiv").contents().unwrap();
childPopup.find(".lizmap_merged").contents().unwrap();
childPopup.find(".lizmapPopupDiv").remove();
childPopup.find(".lizmap_merged").remove();
childPopup.find(".lizmapPopupHidden").hide();
var tChildPopup = $("<table class='lizmap_merged'></table>");
childPopup.append(tChildPopup);
childPopup.find('tr').appendTo(tChildPopup);
childPopup.children('tbody').remove();
}
var oldPopupChild = parentDiv.find('div.lizmapPopupChildren.' + clname);
if (oldPopupChild.length != 0) {
oldPopupChild.remove();
}
parentDiv.append(childPopup);
childPopupElements.push(childPopup);
// Trigger event for single popup children
lizMap.events.triggerEvent(
"lizmappopupchildrendisplayed",
{ 'html': childPopup.html() }
);
}
}
// Handle compact-tables/explode-tables behaviour
parentDiv.find('.lizmapPopupChildren .popupAllFeaturesCompact table').DataTable({
order: [[1, 'asc']],
language: { url:globalThis['lizUrls']["dataTableLanguage"] }
});
parentDiv.find('.lizmapPopupChildren .compact-tables, .lizmapPopupChildren .explode-tables').tooltip();
parentDiv.find('.lizmapPopupChildren .compact-tables').off('click').on('click',function() {
$(this)
.addClass('hide')
.siblings('.explode-tables').removeClass('hide')
.siblings('.popupAllFeaturesCompact, .lizmapPopupSingleFeature').toggle();
});
parentDiv.find('.lizmapPopupChildren .explode-tables').off('click').on('click',function () {
$(this)
.addClass('hide')
.siblings('.compact-tables').removeClass('hide')
.siblings('.popupAllFeaturesCompact, .lizmapPopupSingleFeature').toggle();
});
// place children in the right div, if any
let relations = parentDiv.children('.container.popup_lizmap_dd').find(".popup_lizmap_dd_relation");
relations.each((ind,relation) => {
let referencingLayerId = $(relation).attr('data-referencing-layer-id');
if (referencingLayerId) {
let relLayerId = null;
let referencingLayerConfig = lizMap.getLayerConfigById( referencingLayerId, lizMap.config.attributeLayers, 'layerId' );
if (referencingLayerConfig && referencingLayerConfig[1]?.pivot == 'True' && config.relations.pivot && config.relations.pivot[referencingLayerId]) {
// relation is a pivot, search for "m" child layers
let mLayer = Object.keys(config.relations.pivot[referencingLayerId]).filter((k)=>{ return k !== layerId})
if (mLayer.length == 1) {
relLayerId = mLayer[0];
}
} else {
// relation is 1:n, search for n layer
relLayerId = referencingLayerId;
}
if (relLayerId) {
let childLayer = childPopupElements.filter((child)=>{
let lName = child.attr("data-layername");
let lId = lName ? lizMap.config.layers?.[lName]?.id : '';
return relLayerId == lId;
})
if(childLayer.length == 1) {
$(relation).append(childLayer[0])
}
}
}
});
// Trigger event for all popup children
lizMap.events.triggerEvent(
"lizmappopupallchildrendisplayed",
{
parentPopupElement: self.parents('.lizmapPopupSingleFeature'),
childPopupElements: childPopupElements
}
);
});
})
});
});
}
/**
*
*/
function addFeatureInfo() {
// Verifying layers
var popupsAvailable = false;
for ( var l in config.layers ) {
var configLayer = config.layers[l];
var editionLayer = null;
if ( ('editionLayers' in config) && (l in config.editionLayers) )
editionLayer = config.editionLayers[l];
if( (configLayer && configLayer.popup && configLayer.popup == 'True')
|| (editionLayer && ( editionLayer.capabilities.modifyGeometry == 'True'
|| editionLayer.capabilities.modifyAttribute == 'True'
|| editionLayer.capabilities.deleteFeature == 'True') ) ){
popupsAvailable = true;
break;
}
}
if ( !popupsAvailable ) {
if ($('#mapmenu .nav-list > li.popupcontent > a').length ) {
$('#mapmenu .nav-list > li.popupcontent').remove();
}
return null;
}
// Create the dock if needed
if( 'popupLocation' in config.options &&
config.options.popupLocation != 'map' ) {
if ( !$('#mapmenu .nav-list > li.popupcontent > a').length ) {
// Verifying the message
if ( !('popup.msg.start' in lizDict) )
lizDict['popup.msg.start'] = 'Click to the map to get informations.';
// Initialize dock
var popupContainerId = 'popupcontent';
var pcontent = '<div class="lizmapPopupContent"><h4>'+lizDict['popup.msg.start']+'</h4></div>';
addDock(popupContainerId, 'Popup', config.options.popupLocation, pcontent, 'icon-comment');
$('#button-popupcontent').click(function(){
if($(this).parent().hasClass('active')) {
// clear highlight layer
lizMap.mainLizmap.map.clearHighlightFeatures();
// remove information
$('#popupcontent > div.menu-content').html('<div class="lizmapPopupContent"><h4>'+lizDict['popup.msg.start']+'</h4></div>');
}
});
} else {
$('#mapmenu .nav-list > li.popupcontent > a > span.icon').append('<i class="icon-comment icon-white" style="margin-left: 4px;"></i>');
$('#mapmenu .nav-list > li.popupcontent > a > span.icon').css('background-image', 'none');
}
}
/**
*
* @param evt
*/
function refreshGetFeatureInfo( evt ) {
if ( !evt.updateDrawing )
return;
if ( lastLonLatInfo == null )
return true;
var lastPx = map.getPixelFromLonLat(lastLonLatInfo);
if ( $('#liz_layer_popup div.lizmapPopupContent').length < 1
&& $('#popupcontent > div.menu-content div.lizmapPopupContent').length < 1)
return;
var popupContainerId = "liz_layer_popup";
if ( $('#'+popupContainerId+' div.lizmapPopupContent input.lizmap-popup-layer-feature-id').length == 0 )
popupContainerId = 'popupcontent';
// Refresh if needed
var refreshInfo = false;
$('#'+popupContainerId+' div.lizmapPopupContent input.lizmap-popup-layer-feature-id').each(function(){
var self = $(this);
var val = self.val();
var fid = val.split('.').pop();
var layerId = val.replace( '.' + fid, '' );
var aConfig = lizMap.getLayerConfigById( layerId );
if ( aConfig && aConfig[0] == evt.featureType ) {
refreshInfo = true;
return false;
}
});
if ( refreshInfo ) {
//lastLonLatInfo = null;
$('#'+popupContainerId+' div.lizmapPopupContent input.lizmap-popup-layer-feature-id[value="'+evt.layerId+'.'+evt.featureId+'"]').parent().remove();
}
return;
}
lizMap.events.on({
"layerFilterParamChanged": function( evt ) {
// Continue only if there is a popup displayed
// This would avoid useless GETFILTERTOKEN requests
let nbPopupDisplayed = document.querySelectorAll('input.lizmap-popup-layer-feature-id').length;
if (nbPopupDisplayed == 0) {
return;
}
for ( var lName in config.layers ) {
let lConfig = config.layers[lName];
// Do not request if the layer has no popup
if ( lConfig.popup != 'True' )
continue;
// Do not request if the layer has no request parameters
if ( !('request_params' in lConfig)
|| lConfig['request_params'] == null )
continue;
// Do not get the filter token if the popup is not displayed
nbPopupDisplayed = document.querySelectorAll(
`input.lizmap-popup-layer-feature-id[value^=${lConfig.id}]`
).length;
if (nbPopupDisplayed == 0) {
continue;
}
// Get the filter token only if there is a request_params filter
var requestParams = lConfig['request_params'];
if ( ('filter' in lConfig['request_params'])
&& lConfig['request_params']['filter'] != null
&& lConfig['request_params']['filter'] != "" ) {
// Get filter token
var sdata = {
service: 'WMS',
request: 'GETFILTERTOKEN',
typename: lName,
filter: lConfig['request_params']['filter']
};
$.post(globalThis['lizUrls'].service, sdata, function(result){
// Update layer state
lizMap.mainLizmap.state.layersAndGroupsCollection.getLayerByName(lConfig.name).filterToken = {
expressionFilter: lConfig['request_params']['exp_filter'],
token: result.token
};
// Refresh GetFeatureInfo
refreshGetFeatureInfo(evt);
});
}
}
},
"layerSelectionChanged": function( evt ) {
refreshGetFeatureInfo(evt);
},
"lizmapeditionfeaturedeleted": function( evt ) {
if ( $('div.lizmapPopupContent input.lizmap-popup-layer-feature-id').length > 1 ) {
refreshGetFeatureInfo(evt);
} else {
if (map.popups.length != 0)
map.removePopup(map.popups[0]);
if( 'popupLocation' in config.options && config.options.popupLocation != 'map' ){
var pcontent = '<div class="lizmapPopupContent"><h4>'+lizDict['popup.msg.no.result']+'</h4></div>';
document.querySelector('#popupcontent div.menu-content').innerHTML = pcontent;
if ( $('#mapmenu .nav-list > li.popupcontent').hasClass('active') ){
document.getElementById('button-popupcontent').click();
}
if ( !$('#mapmenu .nav-list > li.popupcontent').hasClass('active') ){
$('#mapmenu .nav-list > li.popupcontent').hide();
}
}
}
}
});
}
/**
*
* @param aLayerId
* @param aConfObjet
* @param aIdAttribute
*/
function getLayerConfigById( aLayerId, aConfObjet, aIdAttribute ) {
// Set function parameters if not given
aConfObjet = typeof aConfObjet !== 'undefined' ? aConfObjet : config.layers;
aIdAttribute = typeof aIdAttribute !== 'undefined' ? aIdAttribute : 'id';
// Loop through layers to get the one by id
for ( var lx in aConfObjet ) {
if ( aConfObjet[lx][aIdAttribute] == aLayerId )
return [lx, aConfObjet[lx] ];
}
return null;
}
/**
*
*/
function addMeasureControls() {
// style the sketch fancy
var sketchSymbolizers = {
"Point": {
pointRadius: 4,
graphicName: "square",
fillColor: "white",
fillOpacity: 1,
strokeWidth: 1,
strokeOpacity: 1,
strokeColor: "#333333"
},
"Line": {
strokeWidth: 3,
strokeOpacity: 1,
strokeColor: "#666666",
strokeDashstyle: "dash"
},
"Polygon": {
strokeWidth: 2,
strokeOpacity: 1,
strokeColor: "#666666",
strokeDashstyle: "dash",
fillColor: "white",
fillOpacity: 0.3
}
};
var style = new OpenLayers.Style();
style.addRules([
new OpenLayers.Rule({symbolizer: sketchSymbolizers})
]);
var styleMap = new OpenLayers.StyleMap({"default": style});
var measureControls = {
length: new OpenLayers.Control.Measure(
OpenLayers.Handler.Path, {
persist: true,
geodesic: true,
immediate: true,
handlerOptions: {
layerOptions: {
styleMap: styleMap
}
},
type:OpenLayers.Control.TYPE_TOOL
}
),
area: new OpenLayers.Control.Measure(
OpenLayers.Handler.Polygon, {
persist: true,
geodesic: true,
immediate: true,
handlerOptions: {
layerOptions: {
styleMap: styleMap
}
},
type:OpenLayers.Control.TYPE_TOOL
}
),
perimeter: new OpenLayers.Control.Measure(
OpenLayers.Handler.Polygon, {
persist: true,
geodesic: true,
immediate: true,
handlerOptions: {
layerOptions: {
styleMap: styleMap
}
},
type:OpenLayers.Control.TYPE_TOOL
}
),
angle: new OpenLayers.Control.Measure(
OpenLayers.Handler.Path, {
id: 'angleMeasure',
persist: true,
geodesic: true,
immediate: true,
handlerOptions: {
maxVertices: 3,
layerOptions: {
styleMap: styleMap
}
},
type: OpenLayers.Control.TYPE_TOOL
}
)
};
measureControls.length.events.on({
activate: function() {
mAddMessage(lizDict['measure.activate.length'],'info',true).attr('id','lizmap-measure-message');
},
deactivate: function() {
$('#lizmap-measure-message').remove();
}
});
measureControls.area.events.on({
activate: function() {
mAddMessage(lizDict['measure.activate.area'],'info',true).attr('id','lizmap-measure-message');
},
deactivate: function() {
$('#lizmap-measure-message').remove();
}
});
measureControls.perimeter.events.on({
activate: function () {
mAddMessage(lizDict['measure.activate.perimeter'], 'info', true).attr('id', 'lizmap-measure-message');
},
deactivate: function () {
$('#lizmap-measure-message').remove();
}
});
measureControls.angle.events.on({
activate: function () {
mAddMessage(lizDict['measure.activate.angle'], 'info', true).attr('id', 'lizmap-measure-message');
},
deactivate: function () {
$('#lizmap-measure-message').remove();
}
});
measureControls.perimeter.measure = function(geometry, eventType) {
var stat, order;
if( OpenLayers.Util.indexOf( geometry.CLASS_NAME, 'LineString' ) > -1) {
stat = this.getBestLength(geometry);
order = 1;
} else {
stat = this.getBestLength(geometry.components[0]);
order = 1;
}
this.events.triggerEvent(eventType, {
measure: stat[0],
units: stat[1],
order: order,
geometry: geometry
});
};
/**
*
* @param evt
*/
function handleMeasurements(evt) {
var units = evt.units;
var order = evt.order;
var measure = evt.measure;
var out = "";
// Angle
if (evt.object.id === "angleMeasure") {
out = lizDict['measure.handle'] + " 0°";
// Three points are needed to measure an angle
if (evt.geometry.components.length === 3){
// Invert first and second points and use a flag to make this change occurs once until next measurement
if(evt.object.invert === undefined){
const firstComponent = evt.geometry.components[0].clone();
const secondComponent = evt.geometry.components[1].clone();
evt.geometry.components[0].move(secondComponent.x - firstComponent.x, secondComponent.y - firstComponent.y);
evt.geometry.components[1].move(firstComponent.x - secondComponent.x, firstComponent.y - secondComponent.y);
evt.object.invert = true;
} else if (evt.type === "measure"){
evt.object.invert = undefined;
}
// Display angle ABC between three points. B is center
const A = evt.geometry.components[0];
const B = evt.geometry.components[1];
const C = evt.geometry.components[2];
const AB = Math.sqrt(Math.pow(B.x - A.x, 2) + Math.pow(B.y - A.y, 2));
const BC = Math.sqrt(Math.pow(B.x - C.x, 2) + Math.pow(B.y - C.y, 2));
const AC = Math.sqrt(Math.pow(C.x - A.x, 2) + Math.pow(C.y - A.y, 2));
let angleInDegrees = (Math.acos((BC * BC + AB * AB - AC * AC) / (2 * BC * AB)) * 180) / Math.PI;
if (isNaN(angleInDegrees)) {
angleInDegrees = 0;
}
out = lizDict['measure.handle'] + " " + angleInDegrees.toFixed(2) + "°";
}
// Other measurement tools
}else{
if (order == 1) {
out += lizDict['measure.handle'] + " " + measure.toFixed(3) + " " + units;
} else {
out += lizDict['measure.handle'] + " " + measure.toFixed(3) + " " + units + "<sup>2</" + "sup>";
}
}
var element = $('#lizmap-measure-message');
if ( element.length == 0 ) {
element = mAddMessage(out);
element.attr('id','lizmap-measure-message');
} else {
element.html('<p>'+out+'</p>');
}
}
for(var key in measureControls) {
var control = measureControls[key];
control.events.on({
"measure": handleMeasurements,
"measurepartial": handleMeasurements
});
map.addControl(control);
controls[key+'Measure'] = control;
}
$('#measure-type').change(function() {
var self = $(this);
self.find('option').each(function() {
var val = $( this ).attr('value');
if ( val in measureControls && measureControls[val].active )
measureControls[val].deactivate();
});
measureControls[self.val()].activate();
});
lizMap.events.on({
minidockopened: function(e) {
if ( e.id == 'measure' ) {
// Put old OL2 map on top and synchronize position with new OL map
lizMap.mainLizmap.newOlMap = false;
$('#measure-type').change();
}
},
minidockclosed: function(e) {
if ( e.id == 'measure' ) {
// Put old OL2 map at bottom
lizMap.mainLizmap.newOlMap = true;
var activeCtrl = '';
$('#measure-type option').each(function() {
var val = $( this ).attr('value');
if ( val in measureControls && measureControls[val].active )
activeCtrl = val;
});
if ( activeCtrl != '' )
measureControls[activeCtrl].deactivate();
}
}
});
$('#measure-stop').click(function(){
$('#button-measure').click();
});
return measureControls;
}
/**
* PRIVATE function: loadProjDefinition
* load CRS definition and activate it
*
* Parameters:
* aCRS - {String}
* aCallbalck - {function ( proj )}
* @param aCRS
* @param aCallback
*/
function loadProjDefinition( aCRS, aCallback ) {
var proj = aCRS.replace(/^\s+|\s+$/g, ''); // trim();
if ( proj in Proj4js.defs ) {
aCallback( proj );
} else {
$.get( globalThis['lizUrls'].service, {
'SERVICE':'WMS',
'REQUEST':'GetProj4'
,'authid': proj
}, function ( aText ) {
Proj4js.defs[proj] = aText;
new OpenLayers.Projection(proj);
aCallback( proj );
}
);
}
}
/**
* PRIVATE function: mCheckMobile
* Check wether in mobile context.
*
*
* Returns:
* {Boolean} True if in mobile context.
*/
function mCheckMobile() {
var minMapSize = 450;
var w = $('body').parent()[0].offsetWidth;
var leftW = w - minMapSize;
if(leftW < minMapSize || w < minMapSize)
return true;
return false;
}
/**
* PRIVATE function: mAddMessage
* Write message to the UI
*
*
* Returns:
* {jQuery Object} The message added.
* @param aMessage
* @param aType
* @param aClose
* @param aTimeout
*/
function mAddMessage( aMessage, aType, aClose, aTimeout ) {
var mType = 'info';
var mTypeList = ['info', 'error', 'danger', 'success'];
var mClose = false;
if ( mTypeList.includes(aType) ){
mType = aType;
}
// `.alert-error` does not exist in Bootstrap > 2
if (mType === 'error') {
mType = 'danger';
}
if ( aClose ){
mClose = true;
}
var html = '<div class="alert alert-'+mType+' alert-dismissible fade show" role="alert" data-alert="alert">';
html += '<p>'+aMessage+'</p>';
if ( mClose ){
html += '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>';
}
html += '</div>';
var elt = $(html);
$('#message').append(elt);
if (aTimeout) {
window.setTimeout(() => {
elt.remove();
}, aTimeout)
}
return elt;
}
/**
* PRIVATE function: exportVectorLayer
* @param aName
* @param eformat
* @param restrictToMapExtent
*/
function exportVectorLayer( aName, eformat, restrictToMapExtent ) {
restrictToMapExtent = typeof restrictToMapExtent !== 'undefined' ? restrictToMapExtent : null;
// right not set
if ( !('exportLayers' in lizMap.config.options) || lizMap.config.options.exportLayers != 'True' ) {
mAddMessage(lizDict['layer.export.right.required'], 'danger', true);
return false;
}
// Set function parameters if not given
eformat = typeof eformat !== 'undefined' ? eformat : 'GeoJSON';
// Get selected features
var cleanName = lizMap.cleanName( aName );
var selectionLayer = getLayerNameByCleanName( cleanName );
if (!selectionLayer) {
selectionLayer = aName;
}
// Get the layer Lizmap configuration
var config_layer = lizMap.config.layers[selectionLayer];
// Check if the layer is spatial
const is_spatial = (
config_layer['geometryType'] && config_layer['geometryType'] != 'none' && config_layer != 'unknown'
) ? true : false;
// Check if there is a selection token
const has_selection_token = (
'request_params' in config_layer && 'selectiontoken' in config_layer['request_params']
&& config_layer['request_params']['selectiontoken'] != null
&& config_layer['request_params']['selectiontoken'] != ''
) ? true : false;
// Check for parenthesis inside the layer name
// There is a bug to be fixed in QGIS Server WFS request for this context
const parenthesis_regex = /[\(\)]/g;
const has_parenthesis = selectionLayer.match(parenthesis_regex);
// If there is a selection, use the selectiontoken,
// not a list of features ids to avoid to have too big urls
// There is some cases when we do not want to use the selection token
// * Layers with no selection token
// * Layers with parenthesis inside the layer name (Bug to be fixed in QGIS Server WFS request)
// * Layers with no geometry, because there is no request_params (as it is only for Openlayers layers)
if (is_spatial && has_selection_token && !has_parenthesis) {
// Get the WFS URL with no filter
var getFeatureUrlData = getVectorLayerWfsUrl( aName, null, null, null, restrictToMapExtent );
// Add the SELECTIONTOKEN parameter
var selection_token = config_layer['request_params']['selectiontoken'];
getFeatureUrlData['options']['SELECTIONTOKEN'] = selection_token;
} else {
// Get the WFS feature ids
var featureid = getVectorLayerSelectionFeatureIdsString( selectionLayer );
// Restrict the WFS URL for these IDS
var getFeatureUrlData = getVectorLayerWfsUrl( aName, null, featureid, null, restrictToMapExtent );
}
// Force download
getFeatureUrlData['options']['dl'] = 1;
// Set export format
getFeatureUrlData['options']['OUTPUTFORMAT'] = eformat;
// Download file
document.querySelectorAll('.exportLayer').forEach(el => el.disabled = true);
mAddMessage(lizDict['layer.export.started'], 'info', true).addClass('export-in-progress');
Utils.downloadFile(getFeatureUrlData['url'], getFeatureUrlData['options'], () => {
document.querySelectorAll('.exportLayer').forEach(el => el.disabled = false);
document.querySelector('#message .export-in-progress button').click();
});
return false;
}
/**
*
* @param aName
*/
function getVectorLayerSelectionFeatureIdsString( aName ) {
var featureidParameter = '';
if( aName in config.layers && config.layers[aName]['selectedFeatures'] ){
var fids = [];
// Get WFS typename
var configLayer = config.layers[aName];
var typeName = aName.split(' ').join('_');
if ( 'shortname' in configLayer && configLayer.shortname != '' )
typeName = configLayer.shortname;
for( var id in configLayer['selectedFeatures'] ) {
fids.push( typeName + '.' + configLayer['selectedFeatures'][id] );
}
if( fids.length )
featureidParameter = fids.join();
}
return featureidParameter;
}
/**
*
* @param aName
* @param aFilter
* @param aFeatureId
* @param geometryName
* @param restrictToMapExtent
* @param startIndex
* @param maxFeatures
*/
function getVectorLayerWfsUrl( aName, aFilter, aFeatureId, geometryName, restrictToMapExtent, startIndex, maxFeatures ) {
var getFeatureUrlData = {};
// Set function parameters if not given
aFilter = typeof aFilter !== 'undefined' ? aFilter : null;
aFeatureId = typeof aFeatureId !== 'undefined' ? aFeatureId : null;
geometryName = typeof geometryName !== 'undefined' ? geometryName : null;
restrictToMapExtent = typeof restrictToMapExtent !== 'undefined' ? restrictToMapExtent : false;
startIndex = typeof startIndex !== 'undefined' ? startIndex : null;
maxFeatures = typeof maxFeatures !== 'undefined' ? maxFeatures : null;
// Build WFS request parameters
if ( !(aName in config.layers) ) {
var qgisName = lizMap.getNameByCleanName(aName);
if ( !qgisName || !(qgisName in config.layers))
qgisName = lizMap.getNameByShortName(aName);
if ( !qgisName || !(qgisName in config.layers))
qgisName = lizMap.getNameByTypeName(aName);
if ( qgisName && (qgisName in config.layers)) {
aName = qgisName;
} else {
console.log('getVectorLayerWfsUrl: "'+aName+'" and "'+qgisName+'" not found in config');
return false;
}
}
var configLayer = config.layers[aName];
var typeName = aName.split(' ').join('_');
if ( 'shortname' in configLayer && configLayer.shortname != '' )
typeName = configLayer.shortname;
else if ( 'typename' in configLayer && configLayer.typename != '' )
typeName = configLayer.typename;
var wfsOptions = {
'SERVICE':'WFS'
,'VERSION':'1.0.0'
,'REQUEST':'GetFeature'
,'TYPENAME':typeName
,'OUTPUTFORMAT':'GeoJSON'
};
if( startIndex )
wfsOptions['STARTINDEX'] = startIndex;
if( maxFeatures )
wfsOptions['MAXFEATURES'] = maxFeatures;
var filterParam = [];
if( aFilter ){
// Remove layerName followed by :
aFilter = aFilter.replace( aName + ':', '');
if ( aFilter != '' )
filterParam.push( aFilter );
}else{
// If not filter passed, check if a filter does not exists for the layer
if( 'request_params' in config.layers[aName] && 'filter' in config.layers[aName]['request_params'] ){
var aFilter = config.layers[aName]['request_params']['filter'];
if( aFilter ){
aFilter = aFilter.replace( aName + ':', '');
filterParam.push( aFilter );
}
}
}
// optionnal parameter filterid or EXP_FILTER
if( aFeatureId )
wfsOptions['FEATUREID'] = aFeatureId.replace(new RegExp(aName, 'g'), typeName);
else if( filterParam.length )
wfsOptions['EXP_FILTER'] = filterParam.join( ' AND ' );
// Calculate bbox from map extent if needed
if( restrictToMapExtent ) {
const mapExtent = lizMap.mainLizmap.map.getView().calculateExtent();
const transformedExtent = lizMap.mainLizmap.transformExtent (
mapExtent,
lizMap.mainLizmap.map.getView().getProjection().getCode(),
config.layers[aName].crs
);
wfsOptions['BBOX'] = transformedExtent.join();
}
// Optionnal parameter geometryname
if( geometryName
&& $.inArray( geometryName.toLowerCase(), ['none', 'extent', 'centroid'] ) != -1
){
wfsOptions['GEOMETRYNAME'] = geometryName;
}
getFeatureUrlData['url'] = globalThis['lizUrls'].service;
getFeatureUrlData['options'] = wfsOptions;
return getFeatureUrlData;
}
/**
* storage for callbacks given to getFeatureData
*
* used to avoid multiple request for the same feature
* @type {{}}
*/
var featureDataPool = {};
/**
*
* @param poolId
* @param features
*/
function callFeatureDataCallBacks(poolId, features) {
var callbacksData = featureDataPool[poolId];
delete featureDataPool[poolId];
callbacksData.callbacks.forEach(function(callback) {
if (callback) {
callback(callbacksData.layerName, callbacksData.filter, features, callbacksData.alias, callbacksData.types);
}
});
}
/**
*
* @param aName
* @param aFilter
* @param aFeatureID
* @param aGeometryName
* @param restrictToMapExtent
* @param startIndex
* @param maxFeatures
* @param aCallBack
*/
function getFeatureData(aName, aFilter, aFeatureID, aGeometryName, restrictToMapExtent, startIndex, maxFeatures, aCallBack) {
// Set function parameters if not given
aFilter = typeof aFilter !== 'undefined' ? aFilter : null;
aFeatureID = typeof aFeatureID !== 'undefined' ? aFeatureID : null;
aGeometryName = typeof aGeometryName !== 'undefined' ? aGeometryName : null;
restrictToMapExtent = typeof restrictToMapExtent !== 'undefined' ? restrictToMapExtent : false;
startIndex = typeof startIndex !== 'undefined' ? startIndex : null;
maxFeatures = typeof maxFeatures !== 'undefined' ? maxFeatures : null;
// get layer configs
if ( !(aName in config.layers) ) {
var qgisName = lizMap.getNameByCleanName(aName);
if ( !qgisName || !(qgisName in config.layers))
qgisName = lizMap.getNameByShortName(aName);
if ( !qgisName || !(qgisName in config.layers))
qgisName = lizMap.getNameByTypeName(aName);
if ( qgisName && (qgisName in config.layers)) {
aName = qgisName;
} else {
console.log('getFeatureData: "'+aName+'" and "'+qgisName+'" not found in config');
return false;
}
}
var aConfig = config.layers[aName];
$('body').css('cursor', 'wait');
var getFeatureUrlData = lizMap.getVectorLayerWfsUrl( aName, aFilter, aFeatureID, aGeometryName, restrictToMapExtent, startIndex, maxFeatures );
// see if a request for the same feature is not already made
var poolId = getFeatureUrlData['url'] + "|" + JSON.stringify(getFeatureUrlData['options']);
if (poolId in featureDataPool) {
// there is already a request, let's store our callback and wait...
if (aCallBack) {
featureDataPool[poolId].callbacks.push(aCallBack);
}
return;
}
// no request yet, let's do it and store the callback and its parameters
featureDataPool[poolId] = {
callbacks: [ aCallBack ],
layerName: aName,
filter: aFilter,
alias: aConfig['alias'],
types: aConfig['types']
};
const wfs = new WFS();
wfs.getFeature(getFeatureUrlData['options']).then(data => {
aConfig['featureCrs'] = 'EPSG:4326';
if (aConfig?.['alias'] && aConfig?.['types']) {
callFeatureDataCallBacks(poolId, data.features);
$('body').css('cursor', 'auto');
} else {
$.post(globalThis['lizUrls'].service, {
'SERVICE':'WFS'
,'VERSION':'1.0.0'
,'REQUEST':'DescribeFeatureType'
,'TYPENAME': ('typename' in aConfig) ? aConfig.typename : aName
,'OUTPUTFORMAT':'JSON'
}, function(describe) {
aConfig['alias'] = describe.aliases;
aConfig['types'] = describe.types;
aConfig['columns'] = describe.columns;
callFeatureDataCallBacks(poolId, data.features);
$('body').css('cursor', 'auto');
},'json');
}
});
// $.post( getFeatureUrlData['url'], getFeatureUrlData['options'], function(incomingData) {
// const data = DOMPurify.sanitize(incomingData);
// aConfig['featureCrs'] = 'EPSG:4326';
// if (aConfig?.['alias'] && aConfig?.['types']) {
// callFeatureDataCallBacks(poolId, data.features);
// $('body').css('cursor', 'auto');
// } else {
// $.post(globalThis['lizUrls'].service, {
// 'SERVICE':'WFS'
// ,'VERSION':'1.0.0'
// ,'REQUEST':'DescribeFeatureType'
// ,'TYPENAME': ('typename' in aConfig) ? aConfig.typename : aName
// ,'OUTPUTFORMAT':'JSON'
// }, function(describe) {
// aConfig['alias'] = describe.aliases;
// aConfig['types'] = describe.types;
// aConfig['columns'] = describe.columns;
// callFeatureDataCallBacks(poolId, data.features);
// $('body').css('cursor', 'auto');
// },'json');
// }
// },'json');
return true;
}
/**
*
* @param feature
* @param proj
* @param zoomAction
*/
function zoomToOlFeature( feature, proj, zoomAction ){
zoomAction = typeof zoomAction !== 'undefined' ? zoomAction : 'zoom';
var format = new OpenLayers.Format.GeoJSON({
ignoreExtraDims: true
});
var feat = format.read(feature)[0];
if( feat && 'geometry' in feat ){
feat.geometry.transform( proj, lizMap.map.getProjection() );
// Zoom or center to selected feature
if( zoomAction == 'zoom' )
map.zoomToExtent(feat.geometry.getBounds());
if( zoomAction == 'center' ){
var lonlat = feat.geometry.getBounds().getCenterLonLat()
map.setCenter(lonlat);
}
}
}
/**
*
* @param featureType
* @param fid
* @param zoomAction
*/
function zoomToFeature( featureType, fid, zoomAction ){
zoomAction = typeof zoomAction !== 'undefined' ? zoomAction : 'zoom';
getLayerFeature(featureType, fid, function(feat) {
var proj = new OpenLayers.Projection(config.layers[featureType].crs);
if( config.layers[featureType].featureCrs )
proj = new OpenLayers.Projection(config.layers[featureType].featureCrs);
zoomToOlFeature( feat, proj, zoomAction );
});
}
/**
*
* @param featureType
* @param fid
* @param aCallback
* @param aCallbackNotfound
* @param forceToLoad
*/
function getLayerFeature( featureType, fid, aCallback, aCallbackNotfound, forceToLoad ){
if ( !aCallback )
return;
if ( !(featureType in config.layers) )
return;
var layerConfig = config.layers[featureType];
var featureId = featureType + '.' + fid;
// Use already retrieved feature
if(!forceToLoad && layerConfig['features'] && fid in layerConfig['features'] ){
aCallback(layerConfig['features'][fid]);
}
// Or get the feature via WFS in needed
else{
getFeatureData(featureType, null, featureId, 'extent', false, null, null,
function( aName, aFilter, cFeatures, cAliases ){
if (cFeatures.length == 1) {
var feat = cFeatures[0];
if( !layerConfig['features'] ) {
layerConfig['features'] = {};
}
layerConfig['features'][fid] = feat;
aCallback(feat);
}
else if(aCallbackNotfound) {
aCallbackNotfound(featureType, fid);
}
});
}
}
/**
*
*/
function getVectorLayerFeatureTypes() {
if ( wfsCapabilities == null ){
return [];
}
return wfsCapabilities.getElementsByTagName('FeatureType');
}
/**
*
*/
function getVectorLayerResultFormat() {
let formats = [];
if ( wfsCapabilities == null ){
return formats;
}else{
for (const format of wfsCapabilities.getElementsByTagName('ResultFormat')[0].children) {
formats.push(format.tagName);
}
return formats;
}
}
/**
*
* @param aName
* @param feat
* @param aCallback
*/
function getFeaturePopupContent( aName, feat, aCallback) {
// Only use this function with callback
if ( !aCallback )
return;
// Only use when feat is set
if( !feat )
return false;
// Remove map popup to avoid confusion
if (lizMap.map.popups.length != 0)
lizMap.map.removePopup( lizMap.map.popups[0] );
// Get popup content by FILTER and not with virtual click on map
var filter = '';
var qgisName = aName;
if( lizMap.getLayerNameByCleanName(aName) ){
qgisName = lizMap.getLayerNameByCleanName(aName);
}
var layerConfig = null;
if (qgisName in lizMap.config.layers) {
layerConfig = lizMap.config.layers[qgisName];
}
if( !layerConfig )
return false;
var pkey = null;
// Get primary key with attributelayer options
if( (qgisName in lizMap.config.attributeLayers) ){
pkey = lizMap.config.attributeLayers[qgisName]['primaryKey'];
}
// Test if primary key is set in the atlas tool
// Atlas config with one layer (legacy)
if( !pkey && 'atlasLayer' in lizMap.config.options && 'atlasPrimaryKey' in lizMap.config.options ){
if( layerConfig.id == lizMap.config.options['atlasLayer'] && lizMap.config.options['atlasPrimaryKey'] != '' ){
pkey = lizMap.config.options['atlasPrimaryKey'];
}
}
// Atlas config with several layers (LWC >= 3.4)
if (!pkey && 'atlas' in lizMap.config && 'layers' in lizMap.config.atlas && Array.isArray(lizMap.config.atlas['layers']) && lizMap.config.atlas['layers'].length > 0) {
for (let index = 0; index < lizMap.config.atlas.layers.length; index++) {
const layer = lizMap.config.atlas.layers[index];
if (layerConfig.id === layer.layer){
pkey = layer.primaryKey;
break;
}
}
}
if( !pkey )
return false;
var pkVal = feat.properties[pkey];
const wmsName = layerConfig?.shortname || layerConfig?.name || qgisName;
filter = wmsName + ':"' + pkey + '" = ' + "'" + pkVal + "'" ;
var crs = 'EPSG:4326';
if(('crs' in lizMap.config.layers[qgisName]) && lizMap.config.layers[qgisName].crs != ''){
crs = lizMap.config.layers[qgisName].crs;
}
var wmsOptions = {
'LAYERS': wmsName
,'QUERY_LAYERS': wmsName
,'STYLES': ''
,'SERVICE': 'WMS'
,'VERSION': '1.3.0'
,'CRS': crs
,'REQUEST': 'GetFeatureInfo'
,'EXCEPTIONS': 'application/vnd.ogc.se_inimage'
,'INFO_FORMAT': 'text/html'
,'FEATURE_COUNT': 1
,'FILTER': filter
};
// Query the server
$.post(globalThis['lizUrls'].service, wmsOptions, function(data) {
aCallback(Utils.sanitizeGFIContent(data));
});
}
// Get the popup content for a layer given a feature
/**
*
* @param aName
* @param feat
* @param aCallback
*/
function getFeaturePopupContentByFeatureIntersection(aName, feat, aCallback) {
// Calculate fake bbox around the feature
var units = lizMap.map.getUnits();
var lConfig = lizMap.config.layers[aName];
var minMapScale = lizMap.config.options.mapScales.at(0);
var scale = Math.max( minMapScale, lConfig.minScale ) * 2;
var maxMapScale = lizMap.config.options.mapScales.at(-1);
if (maxMapScale < lConfig.maxScale && scale > maxMapScale) {
scale =scale/2 + (maxMapScale-scale/2)/2;
} else if (scale > lConfig.maxScale) {
scale =scale/2 + (lConfig.maxScale-scale/2)/2
}
var res = OpenLayers.Util.getResolutionFromScale(scale, units);
var geomType = feat.geometry.CLASS_NAME;
if (
geomType == 'OpenLayers.Geometry.Polygon'
|| geomType == 'OpenLayers.Geometry.MultiPolygon'
|| geomType == 'OpenLayers.Geometry.Point'
) {
var lonlat = feat.geometry.getBounds().getCenterLonLat()
}
else {
var vert = feat.geometry.getVertices();
var middlePoint = vert[Math.floor(vert.length/2)];
var lonlat = new OpenLayers.LonLat(middlePoint.x, middlePoint.y);
}
// Calculate fake bbox
var bbox = new OpenLayers.Bounds(
lonlat.lon - 5 * res,
lonlat.lat - 5 * res,
lonlat.lon + 5 * res,
lonlat.lat + 5 * res
);
var gfiCrs = lizMap.map.getProjectionObject().toString();
if ( gfiCrs == 'EPSG:900913' )
gfiCrs = 'EPSG:3857';
var wmsOptions = {
'LAYERS': aName
,'QUERY_LAYERS': aName
,'STYLES': ''
,'SERVICE': 'WMS'
,'VERSION': '1.3.0'
,'REQUEST': 'GetFeatureInfo'
,'EXCEPTIONS': 'application/vnd.ogc.se_inimage'
,'BBOX': bbox.toBBOX()
,'FEATURE_COUNT': 10
,'HEIGHT': 100
,'WIDTH': 100
,'INFO_FORMAT': 'text/html'
,'CRS': gfiCrs
,'I': 50
,'J': 50
};
// Query the server
$.post(globalThis['lizUrls'].service, wmsOptions, function(data) {
if (aCallback) {
aCallback(globalThis['lizUrls'].service, wmsOptions, Utils.sanitizeGFIContent(data));
}
});
}
// Create new dock or minidock
// Example : lizMap.addDock('mydock', 'My dock title', 'dock', 'Some content', 'icon-pencil');
// see icon list here : http://getbootstrap.com/2.3.2/base-css.html#icons
/**
*
* @param dname
* @param dlabel
* @param dtype
* @param dcontent
* @param dicon
*/
function addDock( dname, dlabel, dtype, dcontent, dicon){
// First check if this dname already exists
if( $('#mapmenu .nav-list > li.'+dname+' > a').length ){
console.log(dname + ' menu item already exists');
return;
}
// Create menu icon for activating dock
var dockli = '';
dockli+='<li class="'+dname+' nav-'+dtype+'">';
dockli+=' <a id="button-'+dname+'" data-bs-toggle="tooltip" data-bs-title="'+dlabel+'" data-placement="right" data-dockid="'+dname+'" href="#'+dname+'" data-container="#content">';
dockli += ' <span class="icon"><i class="' + dicon + ' icon-white"></i></span><span class="menu-title">' + dname +'</span>';
dockli+=' </a>';
dockli+='</li>';
$('#mapmenu div ul li.nav-'+dtype+':last').after(dockli);
if ( $('#mapmenu div ul li.nav-'+dtype+'.'+dname).length == 0 )
$('#mapmenu div ul li:last').after(dockli);
// Remove native lizmap icon
$('#mapmenu .nav-list > li.'+dname+' > a .icon').css('background-image','none');
$('#mapmenu .nav-list > li.'+dname+' > a .icon >i ').css('margin-left', '4px');
// Add tooltip
$('#mapmenu .nav-list > li.'+dname+' > a').tooltip();
// Create dock tab content
var docktab = '';
docktab+='<div class="tab-pane" id="'+dname+'">';
if( dtype == 'minidock'){
docktab+='<div class="mini-dock-close" title="' + lizDict['toolbar.content.stop'] + '" style="padding:7px;float:right;cursor:pointer;"><i class="icon-remove icon-white"></i></div>';
docktab+=' <div class="'+dname+'">';
docktab+=' <h3>';
docktab+=' <span class="title">';
docktab+=' <i class="'+dicon+' icon-white"></i>';
docktab+=' <span class="text"> '+dlabel+' </span>';
docktab+=' </span>';
docktab+=' </h3>';
}
docktab+=' <div class="menu-content">';
docktab+= dcontent;
docktab+=' </div>';
docktab+=' </div>';
docktab+='</div>';
if( dtype == 'minidock'){
$('#mini-dock-content').append(docktab);
$('#'+dname+' div.mini-dock-close').click(function(){
if( $('#mapmenu .nav-list > li.'+dname).hasClass('active') ){
$('#button-'+dname).click();
}
});
}
else if( dtype == 'right-dock' )
$('#right-dock-content').append(docktab);
else if( dtype == 'dock' )
$('#dock-content').append(docktab);
else if( dtype == 'bottomdock' )
$('#bottom-dock-content').append(docktab);
// Create dock tab li
var docktabli = '';
docktabli+= '<li id="nav-tab-'+dname+'"><a href="#'+dname+'" data-toggle="tab">'+dlabel+'</a></li>';
if( dtype == 'minidock')
$('#mini-dock-tabs').append(docktabli);
else if( dtype == 'right-dock' )
$('#right-dock-tabs').append(docktabli);
else if( dtype == 'dock' )
$('#dock-tabs').append(docktabli);
else if( dtype == 'bottomdock' )
$('#bottom-dock-tabs').append(docktabli);
}
/**
* PRIVATE function: getFeatureInfoTolerances
* Get tolerances for point, line and polygon
* as configured with lizmap plugin, or default
* if no configuration found.
* Returns:
* {Object} The tolerances for point, line and polygon
*/
function getFeatureInfoTolerances(){
var tolerances = defaultGetFeatureInfoTolerances;
if( 'pointTolerance' in config.options
&& 'lineTolerance' in config.options
&& 'polygonTolerance' in config.options
){
tolerances = {
'FI_POINT_TOLERANCE': config.options.pointTolerance,
'FI_LINE_TOLERANCE': config.options.lineTolerance,
'FI_POLYGON_TOLERANCE': config.options.polygonTolerance
};
}
return tolerances;
}
/* PRIVATE function: isHighDensity
* Return True when the screen is of high density
* Returns:
* Boolean
*/
/**
*
*/
function isHighDensity(){
return ((window.matchMedia && (window.matchMedia('only screen and (min-resolution: 124dpi), only screen and (min-resolution: 1.3dppx), only screen and (min-resolution: 48.8dpcm)').matches || window.matchMedia('only screen and (-webkit-min-device-pixel-ratio: 1.3), only screen and (-o-min-device-pixel-ratio: 2.6/2), only screen and (min--moz-device-pixel-ratio: 1.3), only screen and (min-device-pixel-ratio: 1.3)').matches)) || (window.devicePixelRatio && window.devicePixelRatio > 1.3));
}
/**
*
* @param layername
*/
function deactivateMaplayerFilter (layername) {
let layer = lizMap.mainLizmap.state.layersAndGroupsCollection.getLayerByName(layername);
layer.expressionFilter = null;
// Remove layer filter
if( !('request_params' in config.layers[layername]) ){
config.layers[layername]['request_params'] = {};
}
config.layers[layername]['request_params']['exp_filter'] = null;
config.layers[layername]['request_params']['filtertoken'] = null;
config.layers[layername]['request_params']['filter'] = null;
}
/**
*
* @param layername
* @param filter
*/
function triggerLayerFilter (layername, filter) {
// Get layer information
const layer = lizMap.mainLizmap.state.layersAndGroupsCollection.getLayerByName(layername);
const layerWmsName = layer.wmsName;
// Add filter to the layer
if( !filter || filter == ''){
filter = null;
var lfilter = null;
}else{
var lfilter = layerWmsName + ':' + filter;
}
if( !('request_params' in config.layers[layername]) ){
config.layers[layername]['request_params'] = {};
}
// Add WFS exp_filter param
config.layers[layername]['request_params']['exp_filter'] = filter;
// Get WMS filter token ( used via GET in GetMap or GetPrint )
fetch(globalThis['lizUrls'].service, {
method: "POST",
body: new URLSearchParams({
service: 'WMS',
request: 'GETFILTERTOKEN',
typename: layername,
filter: lfilter
})
}).then(response => {
return response.json();
}).then(result => {
var filtertoken = result.token;
// Add OpenLayers layer parameter
config.layers[layername]['request_params']['filtertoken'] = filtertoken;
// Update layer state
lizMap.mainLizmap.state.layersAndGroupsCollection.getLayerByName(layername).filterToken = {
expressionFilter: config.layers[layername]['request_params']['exp_filter'],
token: result.token
};
// Tell popup to be aware of the filter
lizMap.events.triggerEvent("layerFilterParamChanged",
{
'featureType': layername,
'filter': lfilter,
'updateDrawing': false
}
);
});
return true;
}
// creating the lizMap object
var obj = {
/**
* Property: map
* {<OpenLayers.Map>} The map
*/
map: null,
/**
* Property: layers
* {Array(<OpenLayers.Layer>)} The layers
*/
layers: null,
/**
* Property: baselayers
* {Array(<OpenLayers.Layer>)} The base layers
*/
baselayers: null,
/**
* Property: events
* {<OpenLayers.Events>} An events object that handles all
* events on the lizmap
*/
events: null,
/**
* Property: config
* {Object} The map config
*/
config: null,
/**
* Property: dictionnary
* {Object} The map dictionnary
*/
dictionary: null,
/**
* Property: tree
* {Object} The map tree
*/
tree: null,
/**
* Property: lizmapLayerFilterActive
* {Object} Contains main filtered layer if filter is active
*/
lizmapLayerFilterActive: null,
/**
* Method: getLizmapDesktopPluginMetadata
*/
getLizmapDesktopPluginMetadata: function() {
return getLizmapDesktopPluginMetadata();
},
/**
* Method: checkMobile
*/
checkMobile: function() {
return mCheckMobile();
},
/**
* Method: cleanName
* @param aName
*/
cleanName: function( aName ) {
return cleanName( aName );
},
/**
* Method: getNameByCleanName
* @param cleanName
*/
getNameByCleanName: function( cleanName ) {
return getNameByCleanName( cleanName );
},
/**
* Method: getNameByShortName
* @param shortName
*/
getNameByShortName: function( shortName ) {
return getNameByShortName( shortName );
},
/**
* Method: getNameByTypeName
* @param typeName
*/
getNameByTypeName: function( typeName ) {
return getNameByTypeName( typeName );
},
/**
* Method: getLayerNameByCleanName
* @param cleanName
*/
getLayerNameByCleanName: function( cleanName ) {
return getLayerNameByCleanName( cleanName );
},
/**
* Method: addMessage
* @param aMessage
* @param aType
* @param aClose
* @param aTimeout
*/
addMessage: function( aMessage, aType, aClose, aTimeout ) {
return mAddMessage( aMessage, aType, aClose, aTimeout );
},
/**
* Method: transformBounds
* @param aCRS
* @param aCallback
*/
loadProjDefinition: function( aCRS, aCallback ) {
return loadProjDefinition( aCRS, aCallback );
},
/**
* Method: updateContentSize
*/
updateContentSize: function() {
return updateContentSize();
},
/**
* Method: clearDrawLayer
* @param layerName
*/
clearDrawLayer: function(layerName) {
return clearDrawLayer(layerName);
},
/**
* Method: getLayerFeature
* @param featureType
* @param fid
* @param aCallback
* @param aCallbackNotfound
* @param forceToLoad
*/
getLayerFeature: function( featureType, fid, aCallback, aCallbackNotfound, forceToLoad ) {
getLayerFeature( featureType, fid, aCallback, aCallbackNotfound, forceToLoad );
},
/**
* Method: getFeatureData
* @param aName
* @param aFilter
* @param aFeatureID
* @param aGeometryName
* @param restrictToMapExtent
* @param startIndex
* @param maxFeatures
* @param aCallBack
*/
getFeatureData: function(aName, aFilter, aFeatureID, aGeometryName, restrictToMapExtent, startIndex, maxFeatures, aCallBack) {
getFeatureData(aName, aFilter, aFeatureID, aGeometryName, restrictToMapExtent, startIndex, maxFeatures, aCallBack);
},
/**
* Method: translateWfsFieldValues
* @param aName
* @param fieldName
* @param fieldValue
* @param translation_dict
*/
translateWfsFieldValues: function(aName, fieldName, fieldValue, translation_dict) {
return translateWfsFieldValues(aName, fieldName, fieldValue, translation_dict);
},
/**
* Method: zoomToFeature
* @param featureType
* @param fid
* @param zoomAction
*/
zoomToFeature: function( featureType, fid, zoomAction ) {
zoomToFeature( featureType, fid, zoomAction );
},
/**
* Method: getExternalBaselayersReplacement
*/
getExternalBaselayersReplacement: function() {
return externalBaselayersReplacement;
},
launchEdition: function() {
return false;
},
deleteEditionFeature: function(){
return false;
},
deactivateToolControls: function( evt ) {
return deactivateToolControls( evt );
},
displayGetFeatureInfo: function( text, xy, coordinate ) {
return displayGetFeatureInfo( text, xy, coordinate );
},
/**
* Method: exportVectorLayer
* @param aName
* @param eformat
* @param restrictToMapExtent
*/
exportVectorLayer: function( aName, eformat, restrictToMapExtent ) {
return exportVectorLayer( aName, eformat, restrictToMapExtent );
},
/**
* Method: getVectorLayerWfsUrl
* @param aName
* @param aFilter
* @param aFeatureId
* @param geometryName
* @param restrictToMapExtent
*/
getVectorLayerWfsUrl: function( aName, aFilter, aFeatureId, geometryName, restrictToMapExtent ) {
return getVectorLayerWfsUrl( aName, aFilter, aFeatureId, geometryName, restrictToMapExtent );
},
/**
* Method: getVectorLayerFeatureType
* @returns {Array} Array of FeatureType Elements
*/
getVectorLayerFeatureTypes: function() {
return getVectorLayerFeatureTypes();
},
/**
* Method: getVectorLayerResultFormat
* @returns {string[]} Array of format for file export
*/
getVectorLayerResultFormat: function() {
return getVectorLayerResultFormat();
},
/**
* Method: getLayerConfigById
* @param aLayerId
* @param aConfObjet
* @param aIdAttribute
*/
getLayerConfigById: function( aLayerId, aConfObjet, aIdAttribute ) {
return getLayerConfigById( aLayerId, aConfObjet, aIdAttribute );
},
/**
* Method: getFeaturePopupContent
* @param aName
* @param feat
* @param aCallback
*/
getFeaturePopupContent: function( aName, feat, aCallback) {
return getFeaturePopupContent(aName, feat, aCallback);
},
/**
* Method: getFeaturePopupContentByFeatureIntersection
* @param aName
* @param feat
* @param aCallback
*/
getFeaturePopupContentByFeatureIntersection: function( aName, feat, aCallback) {
return getFeaturePopupContentByFeatureIntersection(aName, feat, aCallback);
},
/**
* Method: addGeometryFeatureInfo
* @param popup
* @param containerId
*/
addGeometryFeatureInfo: function(popup, containerId){
return addGeometryFeatureInfo(popup, containerId);
},
/**
* Method: addChildrenFeatureInfo
* @param popup
* @param containerId
*/
addChildrenFeatureInfo: function(popup, containerId){
return addChildrenFeatureInfo(popup, containerId);
},
/**
* Method: addChildrenDatavizFilteredByPopupFeature
* @param popup
* @param containerId
*/
addChildrenDatavizFilteredByPopupFeature: function(popup, containerId){
return addChildrenDatavizFilteredByPopupFeature(popup, containerId);
},
/**
* Method: addDock
* @param dname
* @param dlabel
* @param dtype
* @param dcontent
* @param dicon
*/
addDock: function( dname, dlabel, dtype, dcontent, dicon){
return addDock(dname, dlabel, dtype, dcontent, dicon);
},
/**
* Method: getHashParamFromUrl
* Utility function to get searched key in URL's hash
* @param {string} hash_key - searched key in hash
* @returns {string} value for searched key
* @example
* URL: https://liz.map/index.php/view/map/?repository=demo&project=cats#fid:v_cat20180426181713938.16,other_param:foo
* console.log(getHashParamFromUrl('fid'))
* returns 'v_cat20180426181713938.16'
*/
getHashParamFromUrl: function (hash_key) {
var ret_val = null;
var hash = location.hash.replace('#', '');
var hash_items = hash.split(',');
for (var i in hash_items) {
var item = hash_items[i];
var param = item.split(':');
if (param.length == 2) {
var key = param[0];
var val = param[1];
if (key == hash_key) {
return val;
}
}
}
return ret_val;
},
/**
* Apply the global filter on a OpenLayer layer
* Only used by filter.js and timemanager.js
* @param layername
* @param filter
*/
triggerLayerFilter: function (layername, filter) {
return triggerLayerFilter(layername, filter);
},
/**
* Deactivate the global filter on a OpenLayer layer
* Only used by filter.js and timemanager.js
* @param layername
*/
deactivateMaplayerFilter: function (layername) {
// Get layer information
return deactivateMaplayerFilter(layername);
},
/**
* Method: init
*/
init: function() {
// Initialize global variables
const lizmapVariablesJSON = document.getElementById('lizmap-vars')?.innerText;
if (lizmapVariablesJSON) {
try {
const lizmapVariables = JSON.parse(lizmapVariablesJSON);
for (const variable in lizmapVariables) {
globalThis[variable] = lizmapVariables[variable];
}
} catch {
console.warn('JSON for Lizmap global variables is not valid!');
}
}
var self = this;
// Get config
const configRequest = fetch(globalThis['lizUrls'].config + '?' + new URLSearchParams(globalThis['lizUrls'].params)).then(function (response) {
if (!response.ok) {
throw 'Config not loaded: ' + response.status + ' ' + response.statusText
}
return response.json()
});
// Get key/value config
const keyValueConfigRequest = fetch(globalThis['lizUrls'].keyValueConfig + '?' + new URLSearchParams(globalThis['lizUrls'].params)).then(function (response) {
if (!response.ok) {
throw 'Key/value config not loaded: ' + response.status + ' ' + response.statusText
}
return response.json()
});
// Get WMS, WMTS, WFS capabilities
const WMSRequest = fetch(globalThis['lizUrls'].service + '&' + new URLSearchParams({ SERVICE: 'WMS', REQUEST: 'GetCapabilities', VERSION: '1.3.0' })).then(function (response) {
if (!response.ok) {
throw 'WMS GetCapabilities not loaded: ' + response.status + ' ' + response.statusText
}
return response.text()
});
const WMTSRequest = fetch(globalThis['lizUrls'].service + '&' + new URLSearchParams({ SERVICE: 'WMTS', REQUEST: 'GetCapabilities', VERSION: '1.0.0' })).then(function (response) {
if (!response.ok) {
throw 'WMTS GetCapabilities not loaded: ' + response.status + ' ' + response.statusText
}
return response.text()
});
const WFSRequest = fetch(globalThis['lizUrls'].service + '&' + new URLSearchParams({ SERVICE: 'WFS', REQUEST: 'GetCapabilities', VERSION: '1.0.0' })).then(function (response) {
if (!response.ok) {
throw 'WFS GetCapabilities not loaded: ' + response.status + ' ' + response.statusText
}
return response.text()
});
// Get feature extent if defined in URL
let featureExtentRequest;
// Get feature info if defined in URL
let getFeatureInfoRequest;
let getFeatureInfo;
const urlParameters = (new URL(document.location)).searchParams;
const layerName = urlParameters.get('layer');
const filter = urlParameters.get('filter');
if(layerName && filter){
// Feature extent
const wfs = new WFS();
const wfsParams = {
TYPENAME: layerName,
EXP_FILTER: filter
};
featureExtentRequest = wfs.getFeature(wfsParams);
// Feature info
if(urlParameters.get('popup') === 'true'){
const wms = new WMS();
const wmsParams = {
QUERY_LAYERS: layerName,
LAYERS: layerName,
FEATURE_COUNT: 50, // TODO: get this value from config after it has been loaded?
FILTER: `${layerName}:${filter}`,
};
getFeatureInfoRequest = wms.getFeatureInfo(wmsParams);
}
}
// Request config and capabilities in parallel
Promise.allSettled([configRequest, keyValueConfigRequest, WMSRequest, WMTSRequest, WFSRequest, featureExtentRequest, getFeatureInfoRequest]).then(responses => {
// Raise an error when one those required requests fails
// Other requests can fail silently
const requiredRequests = [responses[0], responses[2], responses[3], responses[4]];
for (const request of requiredRequests) {
if (request.status === "rejected") {
throw new Error(request.reason);
}
}
// `config` is defined globally
config = responses[0].value;
keyValueConfig = responses[1].value;
const wmsCapaData = responses[2].value;
const wmtsCapaData = responses[3].value;
const wfsCapaData = responses[4].value;
let featuresExtent = responses[5].value?.features?.[0]?.bbox;
let startupFeatures = responses[5].value?.features;
if(featuresExtent){
for (const feature of startupFeatures) {
featuresExtent = extend(featuresExtent, feature.bbox);
}
}
self.events.triggerEvent("configsloaded", {
initialConfig: config,
wmsCapabilities: wmsCapaData,
wmtsCapabilities: wmtsCapaData,
wfsCapabilities: wfsCapaData,
startupFeatures: responses[5].value,
});
getFeatureInfo = responses[6].value;
const domparser = new DOMParser();
config.options.hasOverview = false;
// store layerIDs
if ('useLayerIDs' in config.options && config.options.useLayerIDs == 'True') {
for (var layerName in config.layers) {
var configLayer = config.layers[layerName];
layerIdMap[configLayer.id] = layerName;
}
}
// store shortnames and shortnames
for (var layerName in config.layers) {
var configLayer = config.layers[layerName];
if ('shortname' in configLayer && configLayer.shortname != '')
shortNameMap[configLayer.shortname] = layerName;
configLayer.cleanname = cleanName(layerName);
}
// Parse WMS capabilities
const wmsFormat = new OpenLayers.Format.WMSCapabilities({version:'1.3.0'});
capabilities = wmsFormat.read(wmsCapaData);
if (!capabilities.capability) {
throw 'WMS Capabilities error';
}
// Parse WMTS capabilities
const wmtsFormat = new OpenLayers.Format.WMTSCapabilities({});
wmtsCapabilities = wmtsFormat.read(wmtsCapaData);
if ('exceptionReport' in wmtsCapabilities) {
var wmtsElem = $('#metadata-wmts-getcapabilities-url');
if (wmtsElem.length != 0) {
wmtsElem.before('<i title="' + wmtsCapabilities.exceptionReport.exceptions[0].texts[0] + '" class="icon-warning-sign"></i> ');
}
wmtsCapabilities = null;
}
self.wmtsCapabilities = wmtsCapaData;
// Parse WFS capabilities
wfsCapabilities = domparser.parseFromString(wfsCapaData, "application/xml");
var featureTypes = lizMap.mainLizmap.initialConfig.vectorLayerFeatureTypeList;
for (const featureType of featureTypes) {
var typeName = featureType.Name;
var layerName = lizMap.getNameByTypeName(typeName);
if (!layerName) {
if (typeName in config.layers)
layerName = typeName
else if ((typeName in shortNameMap) && (shortNameMap[typeName] in config.layers))
layerName = shortNameMap[typeName];
else {
for (var l in config.layers) {
if (l.split(' ').join('_') == typeName) {
layerName = l;
break;
}
}
}
}
if (!(layerName in config.layers))
continue;
var configLayer = config.layers[layerName];
configLayer.typename = typeName;
typeNameMap[typeName] = layerName;
}
//set title and abstract coming from capabilities
$('#abstract').html(capabilities.abstract ? capabilities.abstract : '');
// get and analyse tree
var capability = capabilities.capability;
// Copy QGIS project's projection
config.options.qgisProjectProjection = Object.assign({}, config.options.projection);
// Add the config in self here to be able
// to let the JS external script modify some plugin cfg layers properties
// before Lizmap will create the layer tree
self.config = config;
/**
* Event when the tree is going to be created
* @event beforetreecreated
*/
self.events.triggerEvent("beforetreecreated", self);
buildNativeScales();
var firstLayer = capability.nestedLayers[0];
// Re-save the config in self
self.config = config;
self.keyValueConfig = keyValueConfig;
// create the map
initProjections(firstLayer);
createMap(featuresExtent);
self.map = map;
self.layers = layers;
self.baselayers = baselayers;
self.controls = controls;
/**
* Event when the map has been created
* @event mapcreated
*/
self.events.triggerEvent("mapcreated", self);
// Add empty baselayer as needed by OL2 map
if (baselayers.length === 0) {
// hide elements for baselayers
map.addLayer(new OpenLayers.Layer.Vector('baselayer',{
maxExtent:map.maxExtent
,maxScale: map.maxScale
,minScale: map.minScale
,numZoomLevels: map.numZoomLevels
,scales: map.scales
,projection: map.projection
,units: map.projection.proj.units
}));
}
if ('locateByLayer' in config) {
addLocateByLayer();
}
/**
* Event when layers have been added
* @event layersadded
*/
self.events.triggerEvent("layersadded", self);
// Verifying z-index
var lastLayerZIndex = map.layers[map.layers.length - 1].getZIndex();
if (lastLayerZIndex > map.Z_INDEX_BASE['Feature'] - 100) {
map.Z_INDEX_BASE['Feature'] = lastLayerZIndex + 100;
map.Z_INDEX_BASE['Popup'] = map.Z_INDEX_BASE['Feature'] + 25;
if (map.Z_INDEX_BASE['Popup'] > map.Z_INDEX_BASE['Control'] - 25)
map.Z_INDEX_BASE['Control'] = map.Z_INDEX_BASE['Popup'] + 25;
}
// initialize the map
// Set map extent depending on options
if (!map.getCenter()) {
map.zoomToExtent(map.initialExtent, map.zoomToClosest);
}
updateContentSize();
map.events.triggerEvent("zoomend", { "zoomChanged": true });
// create toolbar
createToolbar();
self.events.triggerEvent("toolbarcreated", self);
// Handle docks visibility
document.querySelector('#mapmenu .nav').addEventListener('click', evt => {
let dockType;
const liClicked = evt.target.closest('li');
if (!liClicked) {
return;
}
for (const className of liClicked.classList) {
if (className.includes('nav-')) {
dockType = className.split('nav-')[1];
}
}
if (!dockType) {
return;
}
evt.preventDefault();
const linkClicked = evt.target.closest('a');
const dockId = linkClicked.dataset.dockid;
const parentElement = linkClicked.parentElement;
const wasActive = parentElement.classList.contains('active');
const dockContentSelector = dockType == 'minidock' ? '#mini-dock-content > div' : '#' + dockType + '-content > div';
document.querySelectorAll('#mapmenu .nav-' + dockType).forEach(element => element.classList.remove('active'));
document.querySelectorAll(dockContentSelector).forEach(element => element.classList.add('hide'));
parentElement.classList.toggle('active', !wasActive);
if (dockId) {
document.getElementById(dockId).classList.toggle('hide', wasActive);
}
const dockEvent = dockType == 'right-dock' ? 'rightdock' : dockType;
const lizmapEvent = wasActive ? dockEvent + 'closed' : dockEvent + 'opened';
lizMap.events.triggerEvent(lizmapEvent, { 'id': dockId });
return false;
});
// Show locate by layer
const locateBtn = document.getElementById('button-locate');
if (!('locateByLayer' in config)){
locateBtn?.parentNode.classList.add('hide');
}
else {
locateBtn?.click();
}
// hide mini-dock if no tool is active
if ($('#mapmenu ul li.nav-minidock.active').length == 0) {
$('#mini-dock-content > .tab-pane.active').removeClass('active');
$('#mini-dock-tabs li.active').removeClass('active');
}
// Toggle menu visibility
$('#menuToggle').click(function(){
$(this).toggleClass('opened');
});
// Hide mapmenu when menu item is clicked in mobile context
$('#menuToggle:visible ~ #mapmenu ul').on('click', 'li > a', function () {
$('#menuToggle').removeClass('opened');
});
// Show layer switcher
updateContentSize();
self.events.triggerEvent("uicreated", self);
})
.catch((error) => {
console.error(error);
// Generic error message
let errorMsg = `
<p class="error-msg">
${lizDict['startup.error']}<br>
`;
if (document.body.dataset.lizmapAdminUser == 1) {
// The user is an administrator, we add more infos and buttons.
if (document.body.dataset.lizmapUserDefinedJsCount > 0) {
errorMsg += `${lizDict['startup.user_defined_js']}<br>
<a href="${globalThis['lizUrls'].repositoryAdmin}"><button class="btn btn-primary" type="button">${lizDict['startup.goToRepositoryAdmin']}</button></a>
<a href="`+ window.location+`&no_user_defined_js=1"><button class="btn btn-primary" type="button">${lizDict['startup.projectWithoutJSLink']}</button></a>
`;
} else {
// No additional JavaScript, but still failing, we propose the developer tools :/
errorMsg += `${lizDict['startup.error.developer.tools']}<br>`;
}
// If the flag no_user_defined_js=1, we could give more info ?
} else {
// The user is not an administrator, we invite the admin, and button to get back home
errorMsg += `${lizDict['startup.error.administrator']}<br>`;
}
errorMsg += `<a href="${globalThis['lizUrls'].basepath}"><button class="btn btn-primary" type="button">${lizDict['startup.goToProject']}</button></a>`;
errorMsg += `</p>`;
document.getElementById('header').insertAdjacentHTML('afterend', errorMsg);
})
.finally(() => {
$('body').css('cursor', 'auto');
$('#loading').dialog('close');
// Display getFeatureInfo if requested
if(getFeatureInfo){
displayGetFeatureInfo(getFeatureInfo,
{
x: map.size.w / 2,
y: map.size.h / 2
});
}
});
}
};
// initializing the lizMap events
obj.events = new OpenLayers.Events(
obj, null,
['treecreated','mapcreated','layersadded','uicreated',
'dockopened','dockclosed'],
true,
{includeXY: true}
);
return obj;
}();
/*
* it's possible to add event listener
* before the document is ready
* but after this file
*/
lizMap.events.on({
uicreated: function(){
// Update legend if mobile
if( lizMap.checkMobile() ){
if( $('#button-switcher').parent().hasClass('active') )
$('#button-switcher').click();
}
// Connect dock close button
document.getElementById('dock-close').addEventListener('click', () => { document.querySelector('#mapmenu .nav-list > li.active.nav-dock a').click();});
document.getElementById('right-dock-close').addEventListener('click', () => { document.querySelector('#mapmenu .nav-list > li.active.nav-right-dock > a').click();});
}
});
$(document).ready(function () {
// start waiting
$('body').css('cursor', 'wait');
$('#loading').dialog({
modal: true
, draggable: false
, resizable: false
, closeOnEscape: false
, dialogClass: 'liz-dialog-wait'
, minHeight: 128
})
.parent().removeClass('ui-corner-all')
.children('.ui-dialog-titlebar').removeClass('ui-corner-all');
// configurate OpenLayers
OpenLayers.DOTS_PER_INCH = 96;
// initialize LizMap
lizMap.init();
// Init bootstrap tooltips
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
[...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl, {
trigger: 'hover'
}));
$( "#loading" ).css('min-height','128px');
});