/**
* @module modules/LocateByLayer.js
* @name LocateByLayer
* @copyright 2024 3Liz
* @author BOISTEAULT Nicolas
* @license MPL-2.0
*/
import DOMPurify from 'dompurify';
import GeoJSON from 'ol/format/GeoJSON.js';
/**
* @class
* @name LocateByLayer
*/
export default class LocateByLayer {
/**
* Build the lizmap LocateByLayer instance
* @param {LocateByLayerConfig} locateByLayer - The lizmap locateByLayer config
* @param {WfsFeatureType[]} vectorLayerFeatureTypeList - The list of WFS feature type
* @param {Map} map - OpenLayers map
* @param {object} lizmap3 - The old lizmap object
*/
constructor(locateByLayer, vectorLayerFeatureTypeList, map, lizmap3) {
this._map = map;
this._vectorLayerFeatureTypeList = vectorLayerFeatureTypeList;
this._lizmap3 = lizmap3;
this._lizmap3LocateByLayerConfig = lizmap3.config.locateByLayer;
const locateBtn = document.getElementById('button-locate');
if (locateByLayer) {
this.addLocateByLayer();
document.querySelector('#mapmenu .locate').classList.add('active')
document.getElementById('locate').classList.remove('hide')
} else {
locateBtn?.parentNode.classList.add('hide');
}
}
addLocateByLayer() {
var locateByLayerList = [];
for (var lname in this._lizmap3LocateByLayerConfig) {
if ('order' in this._lizmap3LocateByLayerConfig[lname]){
locateByLayerList[this._lizmap3LocateByLayerConfig[lname].order] = lname;
} else {
locateByLayerList.push(lname);
}
}
var locateContent = [];
for (var l in locateByLayerList) {
var lname = locateByLayerList[l];
var lConfig = this._lizmap3.config.layers[lname];
var html = '<div class="locate-layer">';
html += '<select id="locate-layer-' + this._lizmap3.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 = this._vectorLayerFeatureTypeList;
if (featureTypes.length == 0) {
this._lizmap3LocateByLayerConfig = {};
$('#button-locate').parent().remove();
$('#locate-menu').remove();
} else {
for (const featureType of featureTypes) {
var typeName = featureType.Name;
var lname = this._lizmap3.getNameByTypeName(typeName);
if (!lname) {
if (typeName in this._lizmap3LocateByLayerConfig)
lname = typeName
else if ((typeName in shortNameMap) && (shortNameMap[typeName] in this._lizmap3LocateByLayerConfig))
lname = shortNameMap[typeName];
else {
for (var lbl in this._lizmap3LocateByLayerConfig) {
if (lbl.split(' ').join('_') == typeName) {
lname = lbl;
break;
}
}
}
}
if (!(lname in this._lizmap3LocateByLayerConfig))
continue;
var locate = this._lizmap3LocateByLayerConfig[lname];
locate['crs'] = featureType.SRS;
locate['bbox'] = featureType.LatLongBoundingBox;
}
// get joins
for (var lName in this._lizmap3LocateByLayerConfig) {
var locate = this._lizmap3LocateByLayerConfig[lName];
if ('vectorjoins' in locate && locate['vectorjoins'].length != 0) {
var vectorjoin = locate['vectorjoins'][0];
locate['joinFieldName'] = vectorjoin['targetFieldName'];
for (var jName in this._lizmap3LocateByLayerConfig) {
var jLocate = this._lizmap3LocateByLayerConfig[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 this._lizmap3LocateByLayerConfig) {
this.getLocateFeature(lname);
}
document.getElementById('locate-clear').addEventListener('click', () => {
this._lizmap3.mainLizmap.map.clearHighlightFeatures();
$('#locate select').val('-1');
$('div.locate-layer span > input').val('');
if (this._lizmap3.lizmapLayerFilterActive) {
this._lizmap3.events.triggerEvent('lizmaplocatefeaturecanceled',
{ 'featureType': this._lizmap3.lizmapLayerFilterActive }
);
}
return false;
});
document.getElementById('locate-close').addEventListener('click', () => {
$('.btn-locate-clear').click(); // deactivate locate and filter
document.getElementById('button-locate')?.click();
return false;
});
}
}
/**
* Get features for locate by layer tool
* @param aName
*/
getLocateFeature(aName) {
var locate = this._lizmap3LocateByLayerConfig[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 = this._lizmap3.getVectorLayerWfsUrl( aName, null, null, 'extent' );
getFeatureUrlData['options']['PROPERTYNAME'] = fields.join(',');
var layerName = this._lizmap3.cleanName(aName);
// Get data
$.post( getFeatureUrlData['url'], getFeatureUrlData['options'], data => {
var lConfig = this._lizmap3.config.layers[aName];
locate['features'] = {};
if ( !data.features )
data = JSON.parse(data);
var features = data.features;
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
document.getElementById('locate-layer-'+layerName+'-'+locate.filterFieldName).addEventListener("change", () => {
var filterValue = $(this).children(':selected').val();
this.updateLocateFeatureList( aName );
if (filterValue == '-1')
$('#locate-layer-'+layerName+'-'+locate.filterFieldName+' ~ span > input').val('');
$('#locate-layer-'+layerName+' ~ span > input').val('');
$('#locate-layer-'+layerName).val('-1');
this.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 ) {
const self = this;
var uiItem = $(ui.item);
window.setTimeout(function(){
self.value = uiItem.val();
self.dispatchEvent(new Event('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>';
}
document.getElementById('locate-layer-'+layerName).innerHTML = options;
// listen to select changes
document.getElementById('locate-layer-'+layerName).addEventListener("change", (event) => {
var val = event.target.value;
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 this._lizmap3LocateByLayerConfig ) {
// update joined select options
var oldVal = $('#locate-layer-'+cleanName(jName)).val();
this.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 this._lizmap3LocateByLayerConfig ) {
this.zoomToLocateFeature( jName );
return;
}
}
// clear the map
this.zoomToLocateFeature( aName );
} else {
// zoom to val
this.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 this._lizmap3LocateByLayerConfig ) {
// update joined select options
this.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 ) {
const self = this;
var uiItem = $(ui.item);
window.setTimeout(function(){
self.value = uiItem.val();
self.dispatchEvent(new Event('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 (this._lizmap3.checkMobile()) {
// autocompletion items for locatebylayer feature
$('div.locate-layer select').show();
$('span.custom-combobox').hide();
}
},'json');
}
/**
* Zoom to locate feature
* @param aName
*/
zoomToLocateFeature(aName) {
// clear highlight layer
this._map.clearHighlightFeatures();
// get locate by layer val
var locate = this._lizmap3LocateByLayerConfig[aName];
var layerName = this._lizmap3.cleanName(aName);
var val = $('#locate-layer-'+layerName).val();
if (val == '-1') {
// Trigger event
this._lizmap3.events.triggerEvent('lizmaplocatefeaturecanceled', {'featureType': aName });
} else {
// zoom to val
const featGeoJSON = locate.features[val];
if( featGeoJSON.geometry){
const geom = (new GeoJSON()).readGeometry(featGeoJSON.geometry, {
dataProjection: 'EPSG:4326',
featureProjection: this._lizmap3.mainLizmap.projection
});
// Show geometry if asked
if (locate.displayGeom == 'True') {
var getFeatureUrlData = this._lizmap3.getVectorLayerWfsUrl( aName, null, null, null );
getFeatureUrlData['options']['PROPERTYNAME'] = ['geometry',locate.fieldName].join(',');
getFeatureUrlData['options']['FEATUREID'] = val;
// Get data
$.post( getFeatureUrlData['url'], getFeatureUrlData['options'], data => {
if ( !data.features ){
data = JSON.parse(data);
}
this._map.setHighlightFeatures(data.features[0], "geojson");
}).fail(() => {
this._.map.setHighlightFeatures(feat, "geojson");
});
}
// zoom to extent
this._map.zoomToGeometryOrExtent(geom);
}
var fid = val.split('.')[1];
// Trigger event
this._lizmap3.events.triggerEvent('lizmaplocatefeaturechanged',
{
'featureType': aName,
'featureId': fid
}
);
}
}
/**
* Get features for locate by layer tool
* @param aName
*/
updateLocateFeatureList(aName) {
var locate = this._lizmap3LocateByLayerConfig[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-' + this._lizmap3.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 this._lizmap3LocateByLayerConfig ) {
var jLocate = this._lizmap3LocateByLayerConfig[jName];
var jVal = $('#locate-layer-' + this._lizmap3.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 = this._lizmap3.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-'+ this._lizmap3.cleanName(aName)).html(options);
}
};