Nepali WMS layers

Displays layers from Nepali WMS services in WME

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey to install this script.

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name          Nepali WMS layers
// @version       2026.04.22.002
// @author        kid4rm90s
// @description   Displays layers from Nepali WMS services in WME
// @match         https://www.waze.com/*/editor*
// @match         https://www.waze.com/editor*
// @match         https://beta.waze.com/*
// @exclude       https://www.waze.com/*user/*editor/*
// @run-at        document-end
// @namespace     https://greatest.deepsurf.us/en/users/1087400-kid4rm90s
// @license       MIT
// @grant         GM_xmlhttpRequest
// @grant         unsafeWindow
// @require       https://greatest.deepsurf.us/scripts/560385/code/WazeToastr.js
// @require       https://update.greatest.deepsurf.us/scripts/516445/1480246/Make%20GM%20xhr%20more%20parallel%20again.js
// @require https://update.greatest.deepsurf.us/scripts/565546/1750869/Preeti%20to%20Unicode%20Converter.js
// @connect       geoserver.softwel.com.np
// @connect       admin.nationalgeoportal.gov.np
// @connect       localhost:8080
// @connect       greatest.deepsurf.us
// @connect       geonep.com.np
// ==/UserScript==

/*  Scripts modified from Czech WMS layers (https://greatest.deepsurf.us/cs/scripts/35069-czech-wms-layers; https://greatest.deepsurf.us/en/scripts/34720-private-czech-wms-layers, https://greatest.deepsurf.us/en/scripts/28160) 
orgianl authors: petrjanik, d2-mac, MajkiiTelini, and Croatian WMS layers (https://greatest.deepsurf.us/en/scripts/519676-croatian-wms-layers) author: JS55CT */

/* global W */
/* global WazeToastr */
/* global $ */
/* global OpenLayers */
/* global require */

(function main() {
  ('use strict');
  const updateMessage =
'<strong>Fixed:</strong><br> - Removed unused LMC layers';
  const scriptName = GM_info.script.name;
  const scriptVersion = GM_info.script.version;
  const downloadUrl = 'https://greatest.deepsurf.us/scripts/521924-nepali-wms-layers/code/nepali-wms-layers.user.js';
  let wmeSDK;

  var WMSLayersTechSource = {};
  var W;
  var OL;
  var I18n;
  var ZIndexes = {};
  var WMSLayerTogglers = {};
  var loadedGeoJSONLayers = [];
  var geoJsonLayerOffsets = {};

  // Helper: update GeoJSON layer selector dropdown
  function updateGeoJsonLayerSelector() {
    const select = document.getElementById('geoJsonLayerSelect');
    if (!select) return;

    // Clear existing options except first (default)
    while (select.options.length > 1) {
      select.remove(1);
    }

    // Add options for each loaded layer
    loadedGeoJSONLayers.forEach(layerInfo => {
      const option = document.createElement('option');
      option.value = layerInfo.name;
      option.textContent = layerInfo.name;
      select.appendChild(option);
    });

    // Reset to default if no layers
    if (loadedGeoJSONLayers.length === 0) {
      select.selectedIndex = 0;
    }
  }

  // Helper: shift GeoJSON layer
  function shiftGeoJsonLayer(direction) {
    const selectElem = document.getElementById('geoJsonLayerSelect');
    const distInput = document.getElementById('geoJsonShiftDistance');
    
    if (!selectElem || !distInput) return;
    
    const layerName = selectElem.value;
    const dist = parseFloat(distInput.value) || 0;
    
    if (!layerName || dist === 0) {
      WazeToastr.Alerts.warning('Selection Required', 'Please select a layer and enter a shift distance.', false, false, 2000);
      return;
    }

    // Find the layer
    const layerInfo = loadedGeoJSONLayers.find(l => l.name === layerName);
    if (!layerInfo) return;

    // Find paired layer (boundary/building)
    const layersToShift = [layerInfo];
    const wardNo = layerInfo.wardNo;
    if (wardNo) {
      const pairedLayerName = layerInfo.layerType === 'buildings' 
        ? `LMC_Ward_${wardNo}_Boundary`
        : `LMC_Ward_${wardNo}_Buildings`;
      const pairedLayer = loadedGeoJSONLayers.find(l => l.name === pairedLayerName);
      if (pairedLayer) {
        layersToShift.push(pairedLayer);
      }
    }

    const map = W.map;
    const proj = map.getProjectionObject();

    let dx = 0, dy = 0;
    const diag = dist * 0.7071; // sqrt(2)/2 for diagonal

    if (proj && proj.projCode === 'EPSG:4326') {
      const centerLat = map.getCenter().lat;
      const metersPerDegreeLat = 111320;
      const metersPerDegreeLon = (40075000 * Math.cos((centerLat * Math.PI) / 180)) / 360;

      switch (direction) {
        case 'up': dy = dist / metersPerDegreeLat; break;
        case 'down': dy = -dist / metersPerDegreeLat; break;
        case 'left': dx = -dist / metersPerDegreeLon; break;
        case 'right': dx = dist / metersPerDegreeLon; break;
        case 'upleft': dx = -diag / metersPerDegreeLon; dy = diag / metersPerDegreeLat; break;
        case 'upright': dx = diag / metersPerDegreeLon; dy = diag / metersPerDegreeLat; break;
        case 'downleft': dx = -diag / metersPerDegreeLon; dy = -diag / metersPerDegreeLat; break;
        case 'downright': dx = diag / metersPerDegreeLon; dy = -diag / metersPerDegreeLat; break;
      }
    } else {
      switch (direction) {
        case 'up': dy = dist; break;
        case 'down': dy = -dist; break;
        case 'left': dx = -dist; break;
        case 'right': dx = dist; break;
        case 'upleft': dx = -diag; dy = diag; break;
        case 'upright': dx = diag; dy = diag; break;
        case 'downleft': dx = -diag; dy = -diag; break;
        case 'downright': dx = diag; dy = -diag; break;
      }
    }

    // Shift all paired layers together
    layersToShift.forEach(info => {
      const layerToShift = info.layer;
      const shiftLayerName = info.name;
      
      // Initialize offset if not exists
      if (!geoJsonLayerOffsets[shiftLayerName]) {
        geoJsonLayerOffsets[shiftLayerName] = { x: 0, y: 0 };
      }

      // Update offset
      geoJsonLayerOffsets[shiftLayerName].x += dx;
      geoJsonLayerOffsets[shiftLayerName].y += dy;

      // Apply offset to all features
      layerToShift.features.forEach(feature => {
        if (feature.geometry && feature.geometry.move) {
          feature.geometry.move(dx, dy);
        }
      });

      // Redraw layer
      layerToShift.redraw();
    });

    const shiftMsg = layersToShift.length > 1 
      ? `Ward ${wardNo} layers shifted ${dist} meters ${direction}`
      : `Layer shifted ${dist} meters ${direction}`;
    WazeToastr.Alerts.info('Layer Shifted', shiftMsg, false, false, 2000);
  }

  // Helper: reset GeoJSON layer shift
  function resetGeoJsonShift() {
    const selectElem = document.getElementById('geoJsonLayerSelect');
    if (!selectElem) return;

    const layerName = selectElem.value;
    if (!layerName) {
      WazeToastr.Alerts.warning('Selection Required', 'Please select a layer to reset.', false, false, 2000);
      return;
    }

    // Find the layer
    const layerInfo = loadedGeoJSONLayers.find(l => l.name === layerName);
    if (!layerInfo) return;

    // Find paired layer (boundary/building)
    const layersToReset = [layerInfo];
    const wardNo = layerInfo.wardNo;
    if (wardNo) {
      const pairedLayerName = layerInfo.layerType === 'buildings' 
        ? `LMC_Ward_${wardNo}_Boundary`
        : `LMC_Ward_${wardNo}_Buildings`;
      const pairedLayer = loadedGeoJSONLayers.find(l => l.name === pairedLayerName);
      if (pairedLayer) {
        layersToReset.push(pairedLayer);
      }
    }

    let hasOffset = false;
    layersToReset.forEach(info => {
      const resetLayer = info.layer;
      const resetLayerName = info.name;
      const offset = geoJsonLayerOffsets[resetLayerName];

      if (offset && (offset.x !== 0 || offset.y !== 0)) {
        hasOffset = true;
        // Move back to original position
        resetLayer.features.forEach(feature => {
          if (feature.geometry && feature.geometry.move) {
            feature.geometry.move(-offset.x, -offset.y);
          }
        });

        // Reset offset
        geoJsonLayerOffsets[resetLayerName] = { x: 0, y: 0 };

        // Redraw layer
        resetLayer.redraw();
      }
    });

    if (hasOffset) {
      const resetMsg = layersToReset.length > 1
        ? `Ward ${wardNo} layers position reset to original`
        : 'Layer position reset to original';
      WazeToastr.Alerts.success('Shift Reset', resetMsg, false, false, 2000);
    } else {
      WazeToastr.Alerts.info('No Shift', 'Layer has no offset to reset.', false, false, 2000);
    }
  }

  async function init() {
    console.log(`${scriptName} initializing.`);
    W = unsafeWindow.W;
    OL = unsafeWindow.OpenLayers;
    I18n = unsafeWindow.I18n;

    WMSLayersTechSource.tileSizeG = new OL.Size(512, 512);
    WMSLayersTechSource.resolutions = [
      156543.03390625, 78271.516953125, 39135.7584765625, 19567.87923828125, 9783.939619140625, 4891.9698095703125, 2445.9849047851562, 1222.9924523925781, 611.4962261962891, 305.74811309814453, 152.87405654907226, 76.43702827453613,
      38.218514137268066, 19.109257068634033, 9.554628534317017, 4.777314267158508, 2.388657133579254, 1.194328566789627, 0.5971642833948135, 0.298582141697406, 0.149291070848703, 0.0746455354243515, 0.0373227677121757,
    ];
    ZIndexes.base = W.map.getOLMap().Z_INDEX_BASE.Overlay + 20;
    ZIndexes.overlay = W.map.getOLMap().Z_INDEX_BASE.Overlay + 100;
    ZIndexes.popup = W.map.getOLMap().Z_INDEX_BASE.Overlay + 500;

    // adresy WMS služeb * WMS service addresses
    var service_wms_PL2023 = {
      type: 'WMS',
      url: 'https://geoserver.softwel.com.np/geoserver/ows/wms?CQL_FILTER=dyear%3D%272023%27',
      attribution: '© DoR / Softwel.com.np',
      comment: 'ssrn_PavementLayer2023',
    };

    var service_wms_softwel = {
      type: 'WMS',
      url: 'https://geoserver.softwel.com.np/geoserver/ows/wms?',
      attribution: '© DoR Nepal/Softwel.com.np',
      comment: 'geoserver softwel.com.np',
    };

    var service_wms_geoportal = {
      type: 'WMS',
      url: 'https://admin.nationalgeoportal.gov.np/geoserver/wms?',
      attribution: '© National Geoportal Nepal',
      comment: 'Municipalities names and boundaries',
    };
    // var service_wms_geo_lalitpur = {
    //   type: 'WMS_4326',
    //   url: 'http://localhost:8080/geoserver/geo-lalitpur/wms?',
    //   attribution: '© Geonp.com.np / LMC',
    //   comment: 'Lalitpur House numbers and boundaries',
    // };

    //skupiny vrstev v menu * MapTile service addresses
    var service_xyz_livemap = {
      type: 'XYZ',
      url: ['https://worldtiles1.waze.com/tiles/${z}/${x}/${y}.png?highres=true', 'https://worldtiles2.waze.com/tiles/${z}/${x}/${y}.png?highres=true', 'https://worldtiles3.waze.com/tiles/${z}/${x}/${y}.png?highres=true'],
      attribution: "© 2006-2023 Waze Mobile. Všechna práva vyhrazena. <a href='https://www.waze.com/legal/notices' target='_blank'>Poznámky</a>",
      comment: 'Waze Livemapa',
    };
    var service_xyz_google = {
      type: 'XYZ',
      url: [
        'https://mts0.googleapis.com/vt/lyrs=m&x=${x}&y=${y}&z=${z}',
        'https://mts1.googleapis.com/vt/lyrs=m&x=${x}&y=${y}&z=${z}',
        'https://mts2.googleapis.com/vt/lyrs=m&x=${x}&y=${y}&z=${z}',
        'https://mts3.googleapis.com/vt/lyrs=m&x=${x}&y=${y}&z=${z}',
      ],
      attribution: "Mapová data ©2023 GeoBasis-DE/BKG (©2009),Google <a href='https://www.google.com/intl/cs_cz/help/terms_maps.html' target='_blank'>Terms and conditions</a>",
      comment: 'Google Mapy',
    };
    var service_xyz_google_terrain = {
      type: 'XYZ',
      url: [
        'https://mts0.googleapis.com/vt/lyrs=p&x=${x}&y=${y}&z=${z}',
        'https://mts1.googleapis.com/vt/lyrs=p&x=${x}&y=${y}&z=${z}',
        'https://mts2.googleapis.com/vt/lyrs=p&x=${x}&y=${y}&z=${z}',
        'https://mts3.googleapis.com/vt/lyrs=p&x=${x}&y=${y}&z=${z}',
      ],
      attribution: "Mapová data ©2023 GeoBasis-DE/BKG (©2009),Google <a href='https://www.google.com/intl/cs_cz/help/terms_maps.html' target='_blank'>Terms and conditions</a>",
      comment: 'Google Terénní Mapy',
    };
    var service_xyz_google_hybrid = {
      type: 'XYZ',
      url: [
        'https://mts0.googleapis.com/vt/lyrs=y&x=${x}&y=${y}&z=${z}',
        'https://mts1.googleapis.com/vt/lyrs=y&x=${x}&y=${y}&z=${z}',
        'https://mts2.googleapis.com/vt/lyrs=y&x=${x}&y=${y}&z=${z}',
        'https://mts3.googleapis.com/vt/lyrs=y&x=${x}&y=${y}&z=${z}',
      ],
      attribution: "Snímky ©2023 Landsat / Copernicus, Google, GEODIS Brno, Mapová data ©2023 GeoBasis-DE/BKG (©2009),Google <a href='https://www.google.com/intl/cs_cz/help/terms_maps.html' target='_blank'>Terms and conditions</a>",
      comment: 'Google Hybridní Mapy',
    };
    var service_xyz_google_streetview = {
      type: 'XYZ',
      url: [
        'https://mts0.google.com/mapslt?lyrs=svv&&x=${x}&y=${y}&z=${z}&style=40',
        'https://mts1.google.com/mapslt?lyrs=svv&&x=${x}&y=${y}&z=${z}&style=40',
        'https://mts2.google.com/mapslt?lyrs=svv&&x=${x}&y=${y}&z=${z}&style=40',
        'https://mts3.google.com/mapslt?lyrs=svv&&x=${x}&y=${y}&z=${z}&style=40',
      ],
      attribution: "Google <a href='https://www.google.com/intl/cs_cz/help/terms_maps.html' target='_blank'>Terms and conditions</a>",
      comment: 'Google Streetview',
    };
    var service_xyz_osm = {
      type: 'XYZ',
      maxZoom: 20,
      url: ['https://tile.openstreetmap.org/${z}/${x}/${y}.png'],
      attribution: "© Contributors <a href='https://www.openstreetmap.org/copyright' target='_blank'>OpenStreetMap</a>",
      comment: 'OpenStreetMaps',
    };
    var service_xyz_april = {
      type: 'XYZ',
      maxZoom: 19,
      url: [
        'https://worldtiles1.waze.com/tiles/${z}/${x}/${y}.png?highres=true',
        'https://mts0.googleapis.com/vt/lyrs=m&z=${z}&x=${x}&y=${y}',
        'https://mts0.googleapis.com/vt/lyrs=p&z=${z}&x=${x}&y=${y}',
        'https://tile.openstreetmap.org/${z}/${x}/${y}.png',
      ],
      attribution: 'mišmaš',
      comment: 'mišmaš',
    };

    //skupiny vrstev v menu * layer groups in the menu
    var groupTogglerPlaces = addGroupToggler(true, 'layer-switcher-group_places');
    var groupTogglerRoad = addGroupToggler(true, 'layer-switcher-group_road');
    // var groupTogglerDisplay = addGroupToggler(true, "layer-switcher-group_display");
    var groupTogglerNames = addGroupToggler(false, 'layer-switcher-group_names', 'NP names and addresses');
    var groupTogglerBorders = addGroupToggler(false, 'layer-switcher-group_borders', 'NP Borders');
    var groupTogglerExternal = addGroupToggler(false, 'layer-switcher-group_external', 'External Maps!!!');
    //var groupTogglerLalitpur = addGroupToggler(false, 'layer-switcher-group_lalitpur', 'Lalitpur MC HN!');
    //vrstvy v menu * layers in the menu
    /************************How To add LayerTogglers***************************
	WMSLayerTogglers.*(1)* = addLayerToggler(groupTogglerPlaces, "*(2)*", false, [addNewLayer("*(1)*", *(3)*, "*(4)*")]);
	INDEX:
	*(1)* : LAYER NAME
	*(2)* : LAYER DISPLAY NAME AT LIST
	*(3)* : SERVICE URL NAME TO PULL DATA FROM
	*(4)* : SERVICE URL LAYER NAME TO PULL DATA FROM
	****************************************************************************/

    //MÍSTA * PLACES
    WMSLayerTogglers.wms_rivers = addLayerToggler(groupTogglerPlaces, 'Rivers', false, [addNewLayer('wms_rivers', service_wms_softwel, 'ssrn:ssrn_major_river,npgp:river_nepal')]);
    WMSLayerTogglers.wms_airport = addLayerToggler(groupTogglerPlaces, 'Geoportal Airports', false, [addNewLayer('wms_airport', service_wms_geoportal, 'geonode:Transportation', ZIndexes.popup)]);
    // Separate education facility layers to avoid duplicate labels
    WMSLayerTogglers.wms_prtmp_education = addLayerToggler(groupTogglerPlaces, 'Education Facilities (PRTMP)', false, [addNewLayer('wms_prtmp_education', service_wms_softwel, 'prtmp_01:prtmp_education', ZIndexes.popup)]);
    WMSLayerTogglers.wms_prtmp_health = addLayerToggler(groupTogglerPlaces, 'Health Facilities (PRTMP)', false, [addNewLayer('wms_prtmp_health', service_wms_softwel, 'prtmp_01:health_facilities', ZIndexes.popup)]);
	WMSLayerTogglers.wms_geoportal_health = addLayerToggler(groupTogglerPlaces, 'Health Facilities (Geoportal)', false, [addNewLayer('wms_geoportal_health', service_wms_geoportal, 'geonode:health_facilities', ZIndexes.popup)]);
    WMSLayerTogglers.wms_geoportal_police = addLayerToggler(groupTogglerPlaces, 'Police Units (Geoportal)', false, [addNewLayer('wms_geoportal_police', service_wms_geoportal, 'geonode:All_Nepal_Final_short', ZIndexes.popup)]);
    WMSLayerTogglers.wms_prtmp_palika = addLayerToggler(groupTogglerPlaces, 'Palika Centre (PRTMP)', false, [addNewLayer('wms_prtmp_palika', service_wms_softwel, 'prtmp_01:palika_center,prtmp_01:palika_center_name', ZIndexes.popup)]);
    WMSLayerTogglers.wms_prtmp_ward = addLayerToggler(groupTogglerPlaces, 'Ward Centre (PRTMP)', false, [addNewLayer('wms_prtmp_ward', service_wms_softwel, 'prtmp_01:prtmp_ward_center', ZIndexes.popup)]);
    WMSLayerTogglers.wms_prtmp_tourist = addLayerToggler(groupTogglerPlaces, 'Tourist Attraction', false, [addNewLayer('wms_prtmp_tourist', service_wms_softwel, 'prtmp_01:tourist_attraction', ZIndexes.popup)]);
    WMSLayerTogglers.wms_prtmp_customs = addLayerToggler(groupTogglerPlaces, 'Customs Office', false, [addNewLayer('wms_prtmp_customs', service_wms_softwel, 'prtmp_01:trade_transit', ZIndexes.popup)]);

    //SILNICE * ROAD
    WMSLayerTogglers.wms_PL2023 = addLayerToggler(groupTogglerRoad, 'SSRN Highway 2023', false, [addNewLayer('wms_PL2023', service_wms_PL2023, 'ssrn:ssrn_pavementstatus')]);
    WMSLayerTogglers.wms_PRTMP_NH = addLayerToggler(groupTogglerRoad, 'NH 2023 (BSM/PRTMP)', false, [addNewLayer('wms_PRTMP_NH', service_wms_softwel, 'prtmp_01:road_network,prtmp_01:road_network_name', "road_class='NH';road_class='NH'")]);
    WMSLayerTogglers.wms_PRTMP_PH = addLayerToggler(groupTogglerRoad, 'PH 2023 (BSM/PRTMP)', false, [addNewLayer('wms_PRTMP_PH', service_wms_softwel, 'prtmp_01:road_network,prtmp_01:road_network_name', "road_class='PH';road_class='PH'")]);
    WMSLayerTogglers.wms_PRTMP_PR = addLayerToggler(groupTogglerRoad, 'PR 2023 (BSM/PRTMP)', false, [addNewLayer('wms_PRTMP_PR', service_wms_softwel, 'prtmp_01:road_network,prtmp_01:road_network_name', "road_class='PR';road_class='PR'")]);
    WMSLayerTogglers.wms_BSM_Bridge = addLayerToggler(groupTogglerRoad, 'Bridges (BSM)', false, [addNewLayer('wms_BSM_Bridge', service_wms_softwel, 'bsm:bsm_nc_primary_detail,bsm:nc_primary_detail_code,bsm:bsm_bi_primary_detail', ZIndexes.popup)]);
    WMSLayerTogglers.wms_prtmp_bridge = addLayerToggler(groupTogglerRoad, 'Bridges (PRTMP)', false, [addNewLayer('wms_prtmp_bridge', service_wms_softwel, 'prtmp_01:bridge_inventory_local,prtmp_01:local_bridge,prtmp_01:major_bridge', ZIndexes.popup)]);

    //ZOBRAZENÍ * DISPLAY
    // WMSLayerTogglers.wms_orto = addLayerToggler(groupTogglerDisplay, "Ortofoto ČUZK", true, [addNewLayer("wms_orto", service_wms_orto, "GR_ORTFOTORGB", ZIndexes.base)]);

    //ČÚZK NÁZVY A ADRESY * ČÚZK NAMES AND ADDRESSES
    WMSLayerTogglers.wms_mun_name = addLayerToggler(groupTogglerNames, 'BSM Municipality Names', false, [addNewLayer('wms_mun_name', service_wms_softwel, 'bsm:bsm_localbodies_label')]);
    WMSLayerTogglers.wms_junction_name = addLayerToggler(groupTogglerNames, 'SSRN Junction Names', false, [addNewLayer('wms_junction_name', service_wms_softwel, 'ssrn:ssrn_junction_name')]);
    // WMSLayerTogglers.wms_lalitpur_metric_house = addLayerToggler(groupTogglerNames, 'Lalitpur Metric House', false, [
    //   addNewLayer(
    //     'wms_lalitpur_metric_house',
    //     service_wms_geo_lalitpur,
    //     'geo-lalitpur:lmc_w-01_metric_house,geo-lalitpur:lmc_w-02_metric_house,geo-lalitpur:lmc_w-03_metric_house,geo-lalitpur:lmc_w-04_metric_house,geo-lalitpur:lmc_w-05_metric_house,geo-lalitpur:lmc_w-06_metric_house,geo-lalitpur:lmc_w-07_metric_house,geo-lalitpur:lmc_w-08_metric_house,geo-lalitpur:lmc_w-09_metric_house,geo-lalitpur:lmc_w-10_metric_house,geo-lalitpur:lmc_w-11_metric_house,geo-lalitpur:lmc_w-12_metric_house,geo-lalitpur:lmc_w-13_metric_house,geo-lalitpur:lmc_w-14_metric_house,geo-lalitpur:lmc_w-15_metric_house,geo-lalitpur:lmc_w-16_metric_house,geo-lalitpur:lmc_w-17_metric_house,geo-lalitpur:lmc_w-18_metric_house,geo-lalitpur:lmc_w-19_metric_house,geo-lalitpur:lmc_w-20_metric_house,geo-lalitpur:lmc_w-22_metric_house,geo-lalitpur:lmc_w-23_metric_house,geo-lalitpur:lmc_w-24_metric_house,geo-lalitpur:lmc_w-25_metric_house,geo-lalitpur:lmc_w-26_metric_house,geo-lalitpur:lmc_w-27_metric_house,geo-lalitpur:lmc_w-28_metric_house,geo-lalitpur:lmc_w-29_metric_house'
    //   ),
    // ]);

    //ČÚZK HRANICE * BORDER BOARD
    WMSLayerTogglers.wms_geonational = addLayerToggler(groupTogglerBorders, 'Geoportal National Border', false, [addNewLayer('wms_geonational', service_wms_geoportal, 'geonode:nepal')]);
    WMSLayerTogglers.wms_national = addLayerToggler(groupTogglerBorders, 'SSRN National Border', false, [addNewLayer('wms_national', service_wms_softwel, 'ssrn:ssrn_national_boundary_line')]);
    WMSLayerTogglers.wms_geoprovince = addLayerToggler(groupTogglerBorders, 'Geoportal Province Border', false, [addNewLayer('wms_geoprovince', service_wms_geoportal, 'geonode:province')]);
    WMSLayerTogglers.wms_province = addLayerToggler(groupTogglerBorders, 'SSRN Province Border', false, [addNewLayer('wms_province', service_wms_softwel, 'ssrn:ssrn_province_line')]);
    WMSLayerTogglers.wms_geodistrict = addLayerToggler(groupTogglerBorders, 'Geoportal District Border', false, [addNewLayer('wms_geodistrict', service_wms_geoportal, 'geonode:districts')]);
    WMSLayerTogglers.wms_district = addLayerToggler(groupTogglerBorders, 'SSRN District Border', false, [addNewLayer('wms_district', service_wms_softwel, 'ssrn:ssrn_district_boundary_line')]);
    WMSLayerTogglers.wms_geomunicipality = addLayerToggler(groupTogglerBorders, 'Geoportal Municipality Border', false, [addNewLayer('wms_geomunicipality', service_wms_geoportal, 'geonode:NepalLocalUnits0')]);
    WMSLayerTogglers.wms_municipality = addLayerToggler(groupTogglerBorders, 'BSM Municipality Border', false, [addNewLayer('wms_municipality', service_wms_softwel, 'bsm:bsm_localbodies_line')]);
    // WMSLayerTogglers.wms_lalitpur_boundary = addLayerToggler(groupTogglerBorders, 'Lalitpur Ward Boundary', false, [
    //   addNewLayer(
    //     'wms_lalitpur_boundary',
    //     service_wms_geo_lalitpur,
    //     'geo-lalitpur:lmc_w-01_boundary,geo-lalitpur:lmc_w-02_boundary,geo-lalitpur:lmc_w-03_boundary,geo-lalitpur:lmc_w-04_boundary,geo-lalitpur:lmc_w-05_boundary,geo-lalitpur:lmc_w-06_boundary,geo-lalitpur:lmc_w-07_boundary,geo-lalitpur:lmc_w-08_boundary,geo-lalitpur:lmc_w-09_boundary,geo-lalitpur:lmc_w-10_boundary,geo-lalitpur:lmc_w-11_boundary,geo-lalitpur:lmc_w-12_boundary,geo-lalitpur:lmc_w-13_boundary,geo-lalitpur:lmc_w-14_boundary,geo-lalitpur:lmc_w-15_boundary,geo-lalitpur:lmc_w-16_boundary,geo-lalitpur:lmc_w-17_boundary,geo-lalitpur:lmc_w-18_boundary,geo-lalitpur:lmc_w-19_boundary,geo-lalitpur:lmc_w-20_boundary,geo-lalitpur:lmc_w-22_boundary,geo-lalitpur:lmc_w-23_boundary,geo-lalitpur:lmc_w-24_boundary,geo-lalitpur:lmc_w-25_boundary,geo-lalitpur:lmc_w-26_boundary,geo-lalitpur:lmc_w-27_boundary,geo-lalitpur:lmc_w-28_boundary,geo-lalitpur:lmc_w-29_boundary'
    //   ),
    // ]);

    //EXTERNÍ MAPY * EXTERNAL MAPS
    WMSLayerTogglers.xyz_livemap = addLayerToggler(groupTogglerExternal, 'Waze LiveMap', false, [addNewLayer('xyz_livemap', service_xyz_livemap)]);
    WMSLayerTogglers.xyz_google = addLayerToggler(groupTogglerExternal, 'Google Maps', false, [addNewLayer('xyz_google', service_xyz_google)]);
    WMSLayerTogglers.xyz_google_terrain = addLayerToggler(groupTogglerExternal, 'Google Terrain Maps', false, [addNewLayer('xyz_google_terrain', service_xyz_google_terrain)]);
    WMSLayerTogglers.xyz_google_hybrid = addLayerToggler(groupTogglerExternal, 'Google Hybrid Maps', false, [addNewLayer('xyz_google_hybrid', service_xyz_google_hybrid)]);
    WMSLayerTogglers.xyz_google_streetview = addLayerToggler(groupTogglerExternal, 'Google StreetView', false, [addNewLayer('xyz_google_streetview', service_xyz_google_streetview, null, ZIndexes.popup)]);
    WMSLayerTogglers.xyz_osm = addLayerToggler(groupTogglerExternal, 'OpenStreetMaps', false, [addNewLayer('xyz_osm', service_xyz_osm)]);
    WMSLayerTogglers.xyz_april = addLayerToggler(groupTogglerExternal, 'Apríl !!!', false, [addNewLayer('xyz_april', service_xyz_april)]);

    //LALITPUR METRO CITY METRIC HOUSE NUMBERING
    /*WMSLayerTogglers.wms_lmc_ward1 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 1', false, [addNewLayer('wms_lmc_ward1', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-01_metric_house,geo-lalitpur:lmc_w-01_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward2 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 2', false, [addNewLayer('wms_lmc_ward2', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-02_metric_house,geo-lalitpur:lmc_w-02_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward3 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 3', false, [addNewLayer('wms_lmc_ward3', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-03_metric_house,geo-lalitpur:lmc_w-03_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward4 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 4', false, [addNewLayer('wms_lmc_ward4', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-04_metric_house,geo-lalitpur:lmc_w-04_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward5 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 5', false, [addNewLayer('wms_lmc_ward5', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-05_metric_house,geo-lalitpur:lmc_w-05_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward6 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 6', false, [addNewLayer('wms_lmc_ward6', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-06_metric_house,geo-lalitpur:lmc_w-06_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward7 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 7', false, [addNewLayer('wms_lmc_ward7', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-07_metric_house,geo-lalitpur:lmc_w-07_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward8 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 8', false, [addNewLayer('wms_lmc_ward8', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-08_metric_house,geo-lalitpur:lmc_w-08_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward9 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 9', false, [addNewLayer('wms_lmc_ward9', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-09_metric_house,geo-lalitpur:lmc_w-09_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward10 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 10', false, [addNewLayer('wms_lmc_ward10', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-10_metric_house,geo-lalitpur:lmc_w-10_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward11 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 11', false, [addNewLayer('wms_lmc_ward11', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-11_metric_house,geo-lalitpur:lmc_w-11_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward12 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 12', false, [addNewLayer('wms_lmc_ward12', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-12_metric_house,geo-lalitpur:lmc_w-12_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward13 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 13', false, [addNewLayer('wms_lmc_ward13', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-13_metric_house,geo-lalitpur:lmc_w-13_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward14 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 14', false, [addNewLayer('wms_lmc_ward14', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-14_metric_house,geo-lalitpur:lmc_w-14_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward15 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 15', false, [addNewLayer('wms_lmc_ward15', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-15_metric_house,geo-lalitpur:lmc_w-15_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward16 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 16', false, [addNewLayer('wms_lmc_ward16', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-16_metric_house,geo-lalitpur:lmc_w-16_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward17 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 17', false, [addNewLayer('wms_lmc_ward17', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-17_metric_house,geo-lalitpur:lmc_w-17_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward18 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 18', false, [addNewLayer('wms_lmc_ward18', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-18_metric_house,geo-lalitpur:lmc_w-18_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward19 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 19', false, [addNewLayer('wms_lmc_ward19', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-19_metric_house,geo-lalitpur:lmc_w-19_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward20 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 20', false, [addNewLayer('wms_lmc_ward20', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-20_metric_house,geo-lalitpur:lmc_w-20_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward21 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 21', false, [addNewLayer('wms_lmc_ward21', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-21_metric_house,geo-lalitpur:lmc_w-21_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward22 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 22', false, [addNewLayer('wms_lmc_ward22', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-22_metric_house,geo-lalitpur:lmc_w-22_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward23 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 23', false, [addNewLayer('wms_lmc_ward23', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-23_metric_house,geo-lalitpur:lmc_w-23_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward24 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 24', false, [addNewLayer('wms_lmc_ward24', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-24_metric_house,geo-lalitpur:lmc_w-24_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward25 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 25', false, [addNewLayer('wms_lmc_ward25', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-25_metric_house,geo-lalitpur:lmc_w-25_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward26 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 26', false, [addNewLayer('wms_lmc_ward26', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-26_metric_house,geo-lalitpur:lmc_w-26_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward27 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 27', false, [addNewLayer('wms_lmc_ward27', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-27_metric_house,geo-lalitpur:lmc_w-27_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward28 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 28', false, [addNewLayer('wms_lmc_ward28', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-28_metric_house,geo-lalitpur:lmc_w-28_boundary',ZIndexes.base)]);
    WMSLayerTogglers.wms_lmc_ward29 = addLayerToggler(groupTogglerLalitpur, 'LMC Ward 29', false, [addNewLayer('wms_lmc_ward29', service_wms_geo_lalitpur, 'geo-lalitpur:lmc_w-29_metric_house,geo-lalitpur:lmc_w-29_boundary',ZIndexes.base)]);*/

    // --- Layer toggler state persistence ---
    function saveLayerTogglerStates() {
      if (!localStorage) return;
      const state = {};
      for (const key in WMSLayerTogglers) {
        const togglerId = WMSLayerTogglers[key].htmlItem;
        const toggler = document.getElementById(togglerId);
        if (toggler) state[key] = toggler.checked;
      }
      localStorage.WMSLayers = JSON.stringify(state);
    }

    function restoreLayerTogglerStates() {
      if (!localStorage.WMSLayers) return;
      const state = JSON.parse(localStorage.WMSLayers);
      for (const key in state) {
        if (WMSLayerTogglers[key]) {
          const togglerId = WMSLayerTogglers[key].htmlItem;
          const toggler = document.getElementById(togglerId);
          if (toggler && toggler.checked !== state[key]) {
            toggler.checked = state[key];
            toggler.dispatchEvent(new Event('change', { bubbles: true }));
          }
        }
      }
    }

    // Attach change listeners to save state on toggle
    for (const key in WMSLayerTogglers) {
      const togglerId = WMSLayerTogglers[key].htmlItem;
      const toggler = document.getElementById(togglerId);
      if (toggler) {
        toggler.addEventListener('change', saveLayerTogglerStates);
      }
    }
    // Restore state after togglers are created

    restoreLayerTogglerStates();
    /*********************  start of popup code ***************************/
    // --- WMS GetFeatureInfo popup for SSRN Pavement Layer ---
    const map = W.map.getOLMap();

    // Helper: get all visible supported WMS layers for popup
    function getAllVisibleWMSLayerInfo() {
      const supported = [
        { key: 'wms_rivers', service: service_wms_softwel, queryLayer: 'ssrn:ssrn_major_river,npgp:river_nepal', displayName: 'Rivers', formatFn: (feature) => formatFeatureInfo('RIVER', feature) },
        { key: 'wms_prtmp_education', service: service_wms_softwel, queryLayer: 'prtmp_01:prtmp_education', displayName: 'Education Facilities (PRTMP)', formatFn: (feature) => formatFeatureInfo('EDUCATION', feature) },
        { key: 'wms_prtmp_health', service: service_wms_softwel, queryLayer: 'prtmp_01:health_facilities', displayName: 'Health Facilities (PRTMP)', formatFn: (feature) => formatFeatureInfo('HEALTH', feature) },
        { key: 'wms_geoportal_health', service: service_wms_geoportal, queryLayer: 'geonode:health_facilities', displayName: 'Health Facilities (Geoportal)', formatFn: (feature) => formatFeatureInfo('GEO_HEALTH', feature) },
        { key: 'wms_geoportal_police', service: service_wms_geoportal, queryLayer: 'geonode:All_Nepal_Final_short', displayName: 'Police Units (Geoportal)', formatFn: (feature) => formatFeatureInfo('GEO_POLICE', feature) },
        { key: 'wms_prtmp_palika', service: service_wms_softwel, queryLayer: 'prtmp_01:palika_center', displayName: 'Palika Centre (PRTMP)', formatFn: (feature) => formatFeatureInfo('PALIKA', feature) },
        { key: 'wms_prtmp_ward', service: service_wms_softwel, queryLayer: 'prtmp_01:prtmp_ward_center', displayName: 'Ward Centre (PRTMP)', formatFn: (feature) => formatFeatureInfo('WARD', feature) },
        { key: 'wms_prtmp_tourist', service: service_wms_softwel, queryLayer: 'prtmp_01:tourist_attraction', displayName: 'Tourist Attraction', formatFn: (feature) => formatFeatureInfo('TOURIST', feature) },
        { key: 'wms_prtmp_customs', service: service_wms_softwel, queryLayer: 'prtmp_01:trade_transit', displayName: 'Customs Office', formatFn: (feature) => formatFeatureInfo('CUSTOMS', feature) },
        { key: 'wms_PL2023', service: service_wms_PL2023, queryLayer: 'ssrn:ssrn_pavementstatus', displayName: 'SSRN Highway 2023', formatFn: (feature) => formatFeatureInfo('SSRN', feature) },
        { key: 'wms_PRTMP_NH', service: service_wms_softwel, queryLayer: 'prtmp_01:road_network', displayName: 'NH 2023 (BSM/PRTMP)', formatFn: (feature) => formatFeatureInfo('BSM', feature), cqlFilter: "road_class='NH'" },
        { key: 'wms_PRTMP_PH', service: service_wms_softwel, queryLayer: 'prtmp_01:road_network', displayName: 'PH 2023 (BSM/PRTMP)', formatFn: (feature) => formatFeatureInfo('BSM', feature), cqlFilter: "road_class='PH'" },
        { key: 'wms_PRTMP_PR', service: service_wms_softwel, queryLayer: 'prtmp_01:road_network', displayName: 'PR 2023 (BSM/PRTMP)', formatFn: (feature) => formatFeatureInfo('BSM', feature), cqlFilter: "road_class='PR'" },
        {
          key: 'wms_BSM_Bridge',
          service: service_wms_softwel,
          queryLayer: 'bsm:bsm_nc_primary_detail,bsm:nc_primary_detail_code,bsm:bsm_bi_primary_detail',
          displayName: 'Bridges (BSM)',
          formatFn: (feature) => formatFeatureInfo('BRIDGE', feature),
        },
        {
          key: 'wms_prtmp_bridge',
          service: service_wms_softwel,
          queryLayer: 'prtmp_01:bridge_inventory_local,prtmp_01:local_bridge,prtmp_01:major_bridge',
          displayName: 'Bridges (PRTMP)',
          formatFn: (feature) => formatFeatureInfo('BRIDGE', feature),
        },
      ];
      const visible = [];
      for (const s of supported) {
        const toggler = WMSLayerTogglers[s.key];
        if (!toggler) {
          continue;
        }
        const layer = toggler.layerArray && toggler.layerArray[0] && toggler.layerArray[0].layer;
        if (layer && layer.getVisibility()) {
          visible.push({ layer, service: s.service, queryLayer: s.queryLayer, formatFn: s.formatFn, key: s.key, displayName: s.displayName, cqlFilter: s.cqlFilter });
        }
      }
      return visible;
    }

    // Helper: build GetFeatureInfo URL for any supported WMS layer
    function buildGetFeatureInfoUrl(service, queryLayer, evt, cqlFilter = null) {
      const wmsUrl = service.url;
      const bbox = map.getExtent();
      const width = map.size.w;
      const height = map.size.h;
      const x = Math.round(evt.xy.x);
      const y = Math.round(evt.xy.y);

      // Handle CQL filters - priority: parameter > service URL
      let cql = '';
      if (cqlFilter) {
        cql = 'CQL_FILTER=' + encodeURIComponent(cqlFilter);
        console.log('[WMS DEBUG] Using layer-specific CQL filter for GetFeatureInfo:', cqlFilter);
      } else if (wmsUrl.includes('CQL_FILTER=')) {
        const match = wmsUrl.match(/CQL_FILTER=([^&]*)/);
        if (match) {
          cql = 'CQL_FILTER=' + match[1];
          console.log('[WMS DEBUG] Using service URL CQL filter for GetFeatureInfo:', decodeURIComponent(match[1]));
        }
      }

      // Determine CRS: use map.projection or fallback to EPSG:3857
      let crs = 'EPSG:3857';
      if (map.projection && (map.projection === 'EPSG:4326' || map.projection === 'EPSG:3857')) {
        crs = map.projection;
      }
      // Optionally add FEATURE_COUNT if present in service config
      let featureCount = '';
      if (service.featureCount) {
        featureCount = 'FEATURE_COUNT=' + service.featureCount;
      }
      const params = [
        'SERVICE=WMS',
        'VERSION=1.3.0',
        'REQUEST=GetFeatureInfo',
        'FORMAT=image/png',
        'TRANSPARENT=true',
        'QUERY_LAYERS=' + encodeURIComponent(queryLayer),
        'LAYERS=' + encodeURIComponent(queryLayer),
        'INFO_FORMAT=application/json',
        cql,
        'STYLES=',
        'TILED=true',
        'buffer=10',
        'CRS=' + crs,
        'WIDTH=' + width,
        'HEIGHT=' + height,
        'BBOX=' + bbox.left + ',' + bbox.bottom + ',' + bbox.right + ',' + bbox.top,
        'I=' + x,
        'J=' + y,
        featureCount,
      ].filter(Boolean);

      const finalUrl = wmsUrl.split('?')[0] + '?' + params.join('&');
      if (cqlFilter) {
        console.log('[WMS DEBUG] GetFeatureInfo URL with CQL filter:', finalUrl);
      }
      return finalUrl;
    }

    // Helper: show popup at pixel position with content (custom HTML popup)
    function showWMSPopupAtPixel(pixel, html) {
      let popup = document.getElementById('wms-info-popup');
      if (!popup) {
        popup = document.createElement('div');
        popup.id = 'wms-info-popup';
        popup.style.position = 'absolute';
        popup.style.zIndex = 9999;
        popup.style.background = 'white';
        popup.style.border = '2px solid #333';
        popup.style.borderRadius = '8px';
        popup.style.boxShadow = '0 2px 8px rgba(0,0,0,0.3)';
        popup.style.padding = '10px 14px 10px 10px';
        popup.style.minWidth = '220px';
        popup.style.maxWidth = '350px';
        popup.style.pointerEvents = 'auto';
        popup.style.fontSize = '11px';
        popup.style.fontFamily = 'inherit';
        popup.style.display = 'block';
        popup.innerHTML = '';
        document.body.appendChild(popup);
      }
      // Add close button and table styling
      popup.innerHTML = `
        <a href="#" id="wms-info-popup-close" style="position:absolute;top:4px;right:8px;font-size:16px;text-decoration:none;color:#888;">&times;</a>
        <style>
          #wms-info-popup table { border-collapse: collapse; width: 100%; margin-top: 8px; font-size: 11px; }
          #wms-info-popup th, #wms-info-popup td { border: 1px solid #ccc; padding: 2px 6px; text-align: left; font-size: 11px; }
          #wms-info-popup th { background: #f0f0f0; font-weight: bold; font-size: 11px; }
          #wms-info-popup tr.alert-success th { background: #d4edda; color: #155724; text-align: center; font-size: 11px; }
        </style>
        ${html}
      `;
      // Position popup (pixel is {x, y} relative to map viewport)
      const mapDiv = map.div;
      const rect = mapDiv.getBoundingClientRect();
      popup.style.left = rect.left + pixel.x + 10 + 'px';
      popup.style.top = rect.top + pixel.y - 10 + 'px';
      popup.style.display = 'block';
      // Close handler
      document.getElementById('wms-info-popup-close').onclick = function (e) {
        e.preventDefault();
        popup.style.display = 'none';
      };
    }

    // Helper: show popup at pixel position with content (custom HTML popup), unique per layer
    function showWMSPopupAtPixelForLayer(pixel, html, layerKey) {
      let popupId = 'wms-info-popup-' + layerKey;
      let popup = document.getElementById(popupId);
      if (!popup) {
        popup = document.createElement('div');
        popup.id = popupId;
        popup.style.position = 'absolute';
        popup.style.zIndex = 9999;
        popup.style.background = 'white';
        popup.style.border = '2px solid #333';
        popup.style.borderRadius = '8px';
        popup.style.boxShadow = '0 2px 8px rgba(0,0,0,0.3)';
        popup.style.padding = '10px 14px 10px 10px';
        popup.style.minWidth = '220px';
        popup.style.maxWidth = '350px';
        popup.style.pointerEvents = 'auto';
        popup.style.fontSize = '11px';
        popup.style.fontFamily = 'inherit';
        popup.style.display = 'block';
        popup.innerHTML = '';
        document.body.appendChild(popup);
      }
      // Add close button and table styling
      popup.innerHTML = `
        <a href="#" id="${popupId}-close" style="position:absolute;top:4px;right:8px;font-size:16px;text-decoration:none;color:#888;">&times;</a>
        <style>
          #${popupId} table { border-collapse: collapse; width: 100%; margin-top: 8px; font-size: 11px; }
          #${popupId} th, #${popupId} td { border: 1px solid #ccc; padding: 2px 6px; text-align: left; font-size: 11px; }
          #${popupId} th { background: #f0f0f0; font-weight: bold; font-size: 11px; }
          #${popupId} tr.alert-success th { background: #d4edda; color: #155724; text-align: center; font-size: 11px; }
        </style>
        ${html}
      `;
      // Position popup (pixel is {x, y} relative to map viewport)
      const mapDiv = map.div;
      const rect = mapDiv.getBoundingClientRect();
      // Offset each popup horizontally so they don't overlap
      let offsetX = 10 + 260 * ['wms_PL2023', 'wms_PRTMP_PH', 'wms_PRTMP_PR'].indexOf(layerKey);
      popup.style.left = rect.left + pixel.x + offsetX + 'px';
      popup.style.top = rect.top + pixel.y - 10 + 'px';
      popup.style.display = 'block';
      // Close handler
      document.getElementById(`${popupId}-close`).onclick = function (e) {
        e.preventDefault();
        popup.style.display = 'none';
      };
    }
    // Helper: format feature info for popup based on type
    function formatFeatureInfo(type, feature) {
      // Define field sets and titles for each type
      const configs = {
        SSRN: {
          title: (feature) => feature.layerName || 'Strategic Road Network',
          fields: [
            ['road_code', 'Road Code'],
            ['link_name', 'Link Name'],
            ['road_name', 'Road Name'],
            ['from_ch', 'From chainage'],
            ['to_ch', 'To chainage'],
            ['pave_type', 'Pavement type'],
            ['last_resurface', 'Last Resurface Year'],
            ['pave_width', 'Pave Width'],
            ['dyear', 'Year'],
            ['add_date', 'Added'],
          ],
        },
        BSM: {
          title: (feature) => feature.layerName || 'BSM Province Road Info',
          fields: [
            ['road_code', 'Road Code'],
            ['road_class', 'Road Class'],
            ['road_name', 'Road Name'],
            ['pcode', 'Province'],
            ['start_ch', 'From chainage'],
            ['end_ch', 'To chainage'],
            ['dyear', 'Year'],
            ['add_date', 'Added'],
          ],
        },
        EDUCATION: {
          title: (feature) => feature.layerName || 'Education Facilities',
          fields: [
            ['name', 'School Name'],
            ['loc_bodies', 'Mun Name'],
            ['district', 'District'],
          ],
        },
        HEALTH: {
          title: (feature) => feature.layerName || 'Health Facilities',
          fields: [
            ['hf_name', 'Name'],
            ['category', 'Category'],
            ['loc_bodies', 'Mun Name'],
            ['ward', 'Ward'],
            ['district', 'District'],
            ['province', 'Province'],
          ],
        },
        GEO_HEALTH: {
          title: (feature) => feature.layerName || 'Health Facilities',
          fields: [
            ['health_fac', 'Name'],
            ['Categorise', 'Category'],
            ['status_lev', 'Status Level'],
            ['local_gove', 'Mun Name'],
            ['District', 'District'],
            ['Province', 'Province'],
          ],
        },
        GEO_POLICE: {
          title: (feature) => feature.layerName || 'Police Units',
          fields: [
            ['EngName', 'Name'],
            ['Nepali_Nam', 'Nep Name'],
            ['dis', 'District'],
            ['Provinces', 'Province'],
          ],
        },
        RIVER: {
          title: (feature) => feature.layerName || 'River Features',
          fields: [['riv_name', 'Name']],
        },
        PALIKA: {
          title: (feature) => feature.layerName || 'Palika Centre',
          fields: [
            ['loc_bod', 'Name Eng'],
            ['dist_name', 'District Eng'],
            ['province', 'Province'],
            ['palika_nep', 'Palika NP'],
            ['dist_nep', 'District NP'],
          ],
        },
        WARD: {
          title: (feature) => feature.layerName || 'Ward Centre',
          fields: [
            ['pcode', 'Province'],
            ['loc_name', 'Name'],
            ['type_gn', 'Type'],
            ['ward_no', 'Ward No'],
          ],
        },
        TOURIST: {
          title: (feature) => feature.layerName || 'Tourist Attraction',
          fields: [
            ['pcode', 'Province'],
            ['name', 'Name'],
            ['district', 'District'],
          ],
        },
        CUSTOMS: {
          title: (feature) => feature.layerName || 'Customs Office',
          fields: [
            ['pcode', 'Province'],
            ['name', 'Name'],
            ['district', 'District'],
          ],
        },
        BRIDGE: {
          title: (feature) => feature.layerName || 'Bridge',
          fields: [
            ['pcode', 'Province'],
            [['name', 'bridge_name'], 'Bridge Name'], // Array of fallback field names
            [['bridge_id', 'bridge_no', 'new_bridge_no'], 'Bridge ID'], // Array of fallback field names
            ['bridge_length', 'Bridge Length'],
            [['river', 'river_name'], 'River'], // Array of fallback field names
            ['road', 'Road Name'], // Array of fallback field names
            ['district', 'District'],
            ['updated_date', 'Updated Date'],
          ],
        },
      };

      const config = configs[type];
      if (!config) return '<div>No info available</div>';

      // Always use user-friendly display name if present
      let layerTitle = feature.layerName; // || (typeof config.title === 'function' ? config.title(feature) : config.title);

      let html = '<table class="link-table"><tbody>';
      html += `<tr class="alert-success text-center"><th colspan="2">${layerTitle}</th></tr>`;
      for (const [key, label] of config.fields) {
        let value = '';
        if (Array.isArray(key)) {
          // Handle fallback field names - try each key until we find a value
          for (const fallbackKey of key) {
            if (feature.properties[fallbackKey]) {
              value = feature.properties[fallbackKey];
              break;
            }
          }
        } else {
          // Single field name
          value = feature.properties[key] || '';
        }
        html += `<tr><td>${label}: </td><td>${value}</td></tr>`;
      }
      html += '</tbody></table>';
      return '<div id="popup-content">' + html + '</div>';
    }

    // Map click handler
    map.events.register('click', map, function (evt) {
      console.log('[WMS] Map clicked at', evt.xy, evt.lonlat);
      const visibleLayers = getAllVisibleWMSLayerInfo();
      if (!visibleLayers.length) {
        console.log('[WMS] No supported WMS layer visible for popup.');
        return;
      }
      let responses = 0;
      let foundFeatures = [];
      let total = visibleLayers.length;
      for (const info of visibleLayers) {
        const url = buildGetFeatureInfoUrl(info.service, info.queryLayer, evt, info.cqlFilter);
        console.log(`[WMS] GetFeatureInfo URL for ${info.key}:`, url);
        GM_xmlhttpRequest({
          method: 'GET',
          url: url,
          headers: { Accept: 'application/json' },
          onload: function (response) {
            responses++;
            try {
              const data = JSON.parse(response.responseText);
              console.log(`[WMS] GetFeatureInfo response for ${info.key}:`, data);
              if (data.features && data.features.length > 0) {
                // For combined layers, deduplicate features by name to avoid showing identical entries
                const uniqueFeatures = [];
                const seenNames = new Set();

                for (let feature of data.features) {
                  // Use the facility name as the deduplication key
                  const facilityName = feature.properties?.name || feature.properties?.hf_name || feature.properties?.riv_name || 'unnamed';

                  if (!seenNames.has(facilityName)) {
                    seenNames.add(facilityName);
                    feature.layerName = info.displayName;
                    uniqueFeatures.push({ info, feature });
                  }
                }

                // Add all unique features to the foundFeatures array
                foundFeatures.push(...uniqueFeatures);
              }
            } catch (e) {
              console.error(`[WMS] Error parsing GetFeatureInfo response for ${info.key}:`, e);
            }
            if (responses === total) {
              if (foundFeatures.length > 0) {
                // Show all found features in one popup at the click location
                let html = foundFeatures.map((f) => f.info.formatFn(f.feature)).join('<hr style="margin:6px 0;">');
                showWMSPopupAtPixel(evt.xy, html);
                console.log('[WMS] Popup shown for features:', foundFeatures);
              }
            }
          },
          onerror: function (err) {
            responses++;
            console.error(`[WMS] GetFeatureInfo request failed for ${info.key}:`, err);
            if (responses === total) {
              if (foundFeatures.length > 0) {
                let html = foundFeatures.map((f) => f.info.formatFn(f.feature)).join('<hr style="margin:6px 0;">');
                showWMSPopupAtPixel(evt.xy, html);
              }
            }
          },
        });
      }
    });
    /*end of pop up code*/

    var GSVlayer = WMSLayerTogglers.xyz_google_streetview.layerArray[0].layer;
    var enteringStreetView = false;
    var ignoreStreetViewExit = false;
    var previousDisplayState = true;
    var controlObserver = new MutationObserver(function (mutationRecords) {
      if (!document.getElementById('layer-switcher-item_Google_StreetView').checked) {
        if (mutationRecords[0].target.classList.contains('overlay-button-active') == previousDisplayState) {
          if (previousDisplayState == true && !ignoreStreetViewExit) {
            previousDisplayState = !mutationRecords[0].target.classList.contains('overlay-button-active');
            W.map.addLayer(GSVlayer);
            enteringStreetView = true;
            GSVlayer.setVisibility(true);
            enteringStreetView = false;
          } else if (previousDisplayState == false) {
            previousDisplayState = !mutationRecords[0].target.classList.contains('overlay-button-active');
            GSVlayer.setVisibility(false);
            W.map.removeLayer(GSVlayer);
          }
        }
      }
    });
    controlObserver.observe(document.querySelector('.street-view-control'), { attributes: true, attributeFilter: ['class'] });
    GSVlayer.events.register('visibilitychanged', null, function () {
      if (!enteringStreetView && GSVlayer.getVisibility()) {
        ignoreStreetViewExit = true;
      }
      if (!GSVlayer.getVisibility()) {
        ignoreStreetViewExit = false;
      }
    });

    const { tabLabel, tabPane } = await wmeSDK.Sidebar.registerScriptTab();
    tabLabel.innerText = 'WMS-NP';
    tabLabel.title = 'Nepali WMS Layers';
    tabLabel.id = 'sidepanel-wms';
    tabPane.innerHTML = "<b><u><a href='https://greatest.deepsurf.us/en/scripts/521924' target='_blank'>" + GM_info.script.name + '</a></u></b> &nbsp; v' + GM_info.script.version;
    var section = document.createElement('section');
    section.style.fontSize = '13px';
    section.id = 'WMS';
    section.style.marginBottom = '15px';
    section.appendChild(document.createElement('br'));
    section.appendChild(document.createTextNode('WMS layer: '));
    var WMSSelect = document.createElement('select');
    WMSSelect.id = 'WMSLayersSelect';
    section.appendChild(WMSSelect);
    var opacityRange = document.createElement('input');
    var opacityLabel = document.createElement('label');
    opacityRange.type = 'range';
    opacityRange.min = 0;
    opacityRange.max = 100;
    opacityRange.value = 100;
    opacityRange.id = 'WMSOpacity';
    opacityLabel.textContent = 'Layer transparency: ' + opacityRange.value + ' %';
    opacityLabel.id = 'WMSOpacityLabel';
    opacityLabel.htmlFor = opacityRange.id;
    section.appendChild(opacityLabel);
    section.appendChild(opacityRange);

    // Add shift controls
    var shiftContainer = document.createElement('div');
    shiftContainer.style.marginTop = '10px';
    shiftContainer.style.display = 'flex';
    shiftContainer.style.flexDirection = 'column';
    shiftContainer.style.alignItems = 'flex-start';

    var distanceLabel = document.createElement('label');
    distanceLabel.textContent = 'Shift distance (meters): ';
    distanceLabel.style.marginRight = '5px';
    var distanceInput = document.createElement('input');
    distanceInput.type = 'number';
    distanceInput.value = 1;
    distanceInput.min = 1;
    distanceInput.style.width = '60px';
    distanceInput.id = 'WMSShiftDistance';
    distanceLabel.appendChild(distanceInput);
    shiftContainer.appendChild(distanceLabel);

    var btnRow = document.createElement('div');
    btnRow.style.display = 'flex';
    btnRow.style.gap = '5px';
    btnRow.style.marginTop = '5px';

    var btnUp = document.createElement('button');
    btnUp.textContent = '↑';
    btnUp.title = 'Shift Up';
    var btnDown = document.createElement('button');
    btnDown.textContent = '↓';
    btnDown.title = 'Shift Down';
    var btnLeft = document.createElement('button');
    btnLeft.textContent = '←';
    btnLeft.title = 'Shift Left';
    var btnRight = document.createElement('button');
    btnRight.textContent = '→';
    btnRight.title = 'Shift Right';

    // Diagonal buttons
    var btnUpLeft = document.createElement('button');
    btnUpLeft.textContent = '↖';
    btnUpLeft.title = 'Shift Up-Left';
    var btnUpRight = document.createElement('button');
    btnUpRight.textContent = '↗';
    btnUpRight.title = 'Shift Up-Right';
    var btnDownLeft = document.createElement('button');
    btnDownLeft.textContent = '↙';
    btnDownLeft.title = 'Shift Down-Left';
    var btnDownRight = document.createElement('button');
    btnDownRight.textContent = '↘';
    btnDownRight.title = 'Shift Down-Right';

    // Add reset button
    var btnReset = document.createElement('button');
    btnReset.textContent = 'Reset';
    btnReset.title = 'Reset Shift';
    btnRow.appendChild(btnReset);

    // Arrange buttons in a grid
    var btnGrid = document.createElement('div');
    btnGrid.style.display = 'grid';
    btnGrid.style.gridTemplateColumns = 'repeat(3, 1fr)';
    btnGrid.style.gap = '2px';
    btnGrid.appendChild(btnUpLeft);
    btnGrid.appendChild(btnUp);
    btnGrid.appendChild(btnUpRight);
    btnGrid.appendChild(btnLeft);
    var emptyCell = document.createElement('div');
    btnGrid.appendChild(emptyCell); // center cell empty
    btnGrid.appendChild(btnRight);
    btnGrid.appendChild(btnDownLeft);
    btnGrid.appendChild(btnDown);
    btnGrid.appendChild(btnDownRight);
    shiftContainer.appendChild(btnGrid);
    shiftContainer.appendChild(btnRow);
    section.appendChild(shiftContainer);

    // Helper: store per-layer offset
    var wmsLayerOffsets = {};

    // Helper: store original offset for each layer
    var wmsLayerOriginalOffsets = {};

    // Helper: patch getURL to apply offset
    function patchWMSLayerGetURL(layer) {
      if (!layer || layer._wmsShiftPatched) return;
      const origGetURL = layer.getURL;
      layer._wmsShiftPatched = true;
      layer.getURL = function (bounds) {
        const offset = wmsLayerOffsets?.[layer.name] ?? { x: 0, y: 0 };
        const newBounds = bounds.clone();
        newBounds.right += offset.x;
        newBounds.left += offset.x;
        newBounds.top += offset.y;
        newBounds.bottom += offset.y;
        return origGetURL.call(this, newBounds);
      };
    }

    // Helper to shift layer (now accepts dx, dy)
    function shiftLayer(direction, customDx, customDy) {
      var value = document.getElementById('WMSLayersSelect').value;
      var dist = parseFloat(document.getElementById('WMSShiftDistance').value) || 0;
      if (!value || value === 'undefined' || dist === 0) return;
      var layer = W.map.getLayerByName(value);
      if (!layer) return;
      patchWMSLayerGetURL(layer);
      var map = W.map;
      var proj = map.getProjectionObject();
      var dx = 0,
        dy = 0;
      var diag = dist * 0.7071; // sqrt(2)/2 for diagonal
      if (typeof customDx === 'number' && typeof customDy === 'number') {
        dx = customDx;
        dy = customDy;
      } else if (proj && proj.projCode === 'EPSG:4326') {
        var centerLat = map.getCenter().lat;
        var metersPerDegreeLat = 111320;
        var metersPerDegreeLon = (40075000 * Math.cos((centerLat * Math.PI) / 180)) / 360;
        switch (direction) {
          case 'up':
            dy = -dist / metersPerDegreeLat;
            break;
          case 'down':
            dy = dist / metersPerDegreeLat;
            break;
          case 'left':
            dx = dist / metersPerDegreeLon;
            break;
          case 'right':
            dx = -dist / metersPerDegreeLon;
            break;
          case 'upleft':
            dx = diag / metersPerDegreeLon;
            dy = -diag / metersPerDegreeLat;
            break;
          case 'upright':
            dx = -diag / metersPerDegreeLon;
            dy = -diag / metersPerDegreeLat;
            break;
          case 'downleft':
            dx = diag / metersPerDegreeLon;
            dy = diag / metersPerDegreeLat;
            break;
          case 'downright':
            dx = -diag / metersPerDegreeLon;
            dy = diag / metersPerDegreeLat;
            break;
        }
      } else {
        switch (direction) {
          case 'up':
            dy = -dist;
            break;
          case 'down':
            dy = dist;
            break;
          case 'left':
            dx = dist;
            break;
          case 'right':
            dx = -dist;
            break;
          case 'upleft':
            dx = diag;
            dy = -diag;
            break;
          case 'upright':
            dx = -diag;
            dy = -diag;
            break;
          case 'downleft':
            dx = diag;
            dy = diag;
            break;
          case 'downright':
            dx = -diag;
            dy = diag;
            break;
        }
      }
      if (!wmsLayerOffsets[layer.name]) wmsLayerOffsets[layer.name] = { x: 0, y: 0 };
      wmsLayerOffsets[layer.name].x += dx;
      wmsLayerOffsets[layer.name].y += dy;
      // Store original offset if not already stored
      if (!wmsLayerOriginalOffsets[layer.name]) {
        wmsLayerOriginalOffsets[layer.name] = { x: 0, y: 0 };
      }
      // Show WazeToastr alert
      WazeToastr.Alerts.info('Layer Shifted', `Layer shifted to ${dist} metres ${direction}. Please wait for fully load.`, false, false, 2000);
      layer.redraw();
    }
    btnUp.addEventListener('click', function () {
      shiftLayer('up');
    });
    btnDown.addEventListener('click', function () {
      shiftLayer('down');
    });
    btnLeft.addEventListener('click', function () {
      shiftLayer('left');
    });
    btnRight.addEventListener('click', function () {
      shiftLayer('right');
    });
    btnUpLeft.addEventListener('click', function () {
      shiftLayer('upleft');
    });
    btnUpRight.addEventListener('click', function () {
      shiftLayer('upright');
    });
    btnDownLeft.addEventListener('click', function () {
      shiftLayer('downleft');
    });
    btnDownRight.addEventListener('click', function () {
      shiftLayer('downright');
    });

    // Reset shift for selected layer
    btnReset.addEventListener('click', function () {
      var value = document.getElementById('WMSLayersSelect').value;
      if (!value || value === 'undefined') return;
      var layer = W.map.getLayerByName(value);
      if (!layer) return;
      patchWMSLayerGetURL(layer);
      wmsLayerOffsets[layer.name] = { x: 0, y: 0 };
      layer.redraw();
      // Show WazeToastr alert on reset
      WazeToastr.Alerts.info('Layer Reset', 'Layer shift has been reset to default.', false, false, 2000);
    });

    tabPane.appendChild(section);

    // --- GeoJSON URL Loading Section ---
    var geoJsonSection = document.createElement('section');
    geoJsonSection.style.fontSize = '13px';
    geoJsonSection.id = 'GeoJSONURLSection';
    geoJsonSection.style.marginBottom = '15px';
    geoJsonSection.style.marginTop = '15px';
    geoJsonSection.style.borderTop = '2px solid #ccc';
    geoJsonSection.style.paddingTop = '10px';

    var geoJsonTitle = document.createElement('h3');
    geoJsonTitle.textContent = 'Load GeoJSON from URL';
    geoJsonTitle.style.fontSize = '14px';
    geoJsonTitle.style.fontWeight = 'bold';
    geoJsonTitle.style.marginBottom = '10px';
    geoJsonSection.appendChild(geoJsonTitle);

    // Font styling controls
    var fontStyleContainer = document.createElement('div');
    fontStyleContainer.style.display = 'flex';
    fontStyleContainer.style.gap = '10px';
    fontStyleContainer.style.marginBottom = '10px';

    // Font color picker
    var fontColorContainer = document.createElement('div');
    fontColorContainer.style.flex = '1';
    var fontColorLabel = document.createElement('label');
    fontColorLabel.textContent = 'Label Color: ';
    fontColorLabel.style.display = 'block';
    fontColorLabel.style.marginBottom = '3px';
    fontColorLabel.style.fontSize = '13px';
    var fontColorInput = document.createElement('input');
    fontColorInput.type = 'color';
    fontColorInput.id = 'geoJsonFontColor';
    fontColorInput.value = '#ffffff';
    fontColorInput.style.width = '70%';
    fontColorInput.style.height = '30px';
    fontColorInput.style.cursor = 'pointer';
    fontColorContainer.appendChild(fontColorLabel);
    fontColorContainer.appendChild(fontColorInput);

    // Font size input
    var fontSizeContainer = document.createElement('div');
    fontSizeContainer.style.flex = '1';
    var fontSizeLabel = document.createElement('label');
    fontSizeLabel.textContent = 'Label Size (px): ';
    fontSizeLabel.style.display = 'block';
    fontSizeLabel.style.marginBottom = '3px';
    fontSizeLabel.style.fontSize = '13px';
    var fontSizeInput = document.createElement('input');
    fontSizeInput.type = 'number';
    fontSizeInput.id = 'geoJsonFontSize';
    fontSizeInput.value = '13';
    fontSizeInput.min = '8';
    fontSizeInput.max = '24';
    fontSizeInput.style.width = '70%';
    fontSizeInput.style.padding = '5px';
    fontSizeContainer.appendChild(fontSizeLabel);
    fontSizeContainer.appendChild(fontSizeInput);

    fontStyleContainer.appendChild(fontColorContainer);
    fontStyleContainer.appendChild(fontSizeContainer);
    geoJsonSection.appendChild(fontStyleContainer);

    // Ward selector
    var wardLabel = document.createElement('label');
    wardLabel.textContent = 'Select Ward Number: ';
    wardLabel.style.display = 'block';
    wardLabel.style.marginBottom = '5px';
    geoJsonSection.appendChild(wardLabel);

    var wardSelect = document.createElement('select');
    wardSelect.id = 'geoJsonWardSelect';
    wardSelect.style.width = '100%';
    wardSelect.style.marginBottom = '10px';
    wardSelect.style.padding = '5px';

    // Add ward options 1-29
    for (let i = 1; i <= 29; i++) {
      let option = document.createElement('option');
      option.value = i;
      option.textContent = `Ward ${i}`;
      wardSelect.appendChild(option);
    }
    geoJsonSection.appendChild(wardSelect);

    // Load button
    var loadGeoJsonBtn = document.createElement('button');
    loadGeoJsonBtn.textContent = 'Load Buildings from URL';
    loadGeoJsonBtn.style.width = '100%';
    loadGeoJsonBtn.style.padding = '8px';
    loadGeoJsonBtn.style.marginBottom = '5px';
    loadGeoJsonBtn.style.cursor = 'pointer';
    loadGeoJsonBtn.title = 'Load building data from geonep.com.np';
    loadGeoJsonBtn.addEventListener('click', loadGeoJSONFromURL);
    geoJsonSection.appendChild(loadGeoJsonBtn);

    // Clear button
    var clearGeoJsonBtn = document.createElement('button');
    clearGeoJsonBtn.textContent = 'Clear Loaded Buildings';
    clearGeoJsonBtn.style.width = '100%';
    clearGeoJsonBtn.style.padding = '8px';
    clearGeoJsonBtn.style.cursor = 'pointer';
    clearGeoJsonBtn.title = 'Remove all loaded building layers';
    clearGeoJsonBtn.addEventListener('click', clearLoadedGeoJSON);
    geoJsonSection.appendChild(clearGeoJsonBtn);

    // Status display
    var geoJsonStatus = document.createElement('div');
    geoJsonStatus.id = 'geoJsonStatus';
    geoJsonStatus.style.marginTop = '10px';
    geoJsonStatus.style.fontSize = '12px';
    geoJsonStatus.style.fontStyle = 'italic';
    geoJsonSection.appendChild(geoJsonStatus);

    // --- GeoJSON Layer Shift Controls ---
    var geoJsonShiftContainer = document.createElement('div');
    geoJsonShiftContainer.style.marginTop = '15px';
    geoJsonShiftContainer.style.paddingTop = '10px';
    geoJsonShiftContainer.style.borderTop = '1px solid #ccc';

    var geoJsonShiftTitle = document.createElement('h4');
    geoJsonShiftTitle.textContent = 'GeoJSON Layer Shift Controls';
    geoJsonShiftTitle.style.fontSize = '13px';
    geoJsonShiftTitle.style.marginBottom = '10px';
    geoJsonShiftContainer.appendChild(geoJsonShiftTitle);

    // Layer selector for shift
    var geoJsonLayerSelectLabel = document.createElement('label');
    geoJsonLayerSelectLabel.textContent = 'Select Layer: ';
    geoJsonLayerSelectLabel.style.display = 'block';
    geoJsonLayerSelectLabel.style.marginBottom = '5px';
    geoJsonShiftContainer.appendChild(geoJsonLayerSelectLabel);

    var geoJsonLayerSelect = document.createElement('select');
    geoJsonLayerSelect.id = 'geoJsonLayerSelect';
    geoJsonLayerSelect.style.width = '100%';
    geoJsonLayerSelect.style.marginBottom = '10px';
    geoJsonLayerSelect.style.padding = '5px';
    var defaultOption = document.createElement('option');
    defaultOption.value = '';
    defaultOption.textContent = '-- Select a loaded layer --';
    geoJsonLayerSelect.appendChild(defaultOption);
    geoJsonShiftContainer.appendChild(geoJsonLayerSelect);

    // Shift distance input
    var geoJsonShiftDistLabel = document.createElement('label');
    geoJsonShiftDistLabel.textContent = 'Shift Distance (meters): ';
    geoJsonShiftDistLabel.style.display = 'block';
    geoJsonShiftDistLabel.style.marginBottom = '5px';
    geoJsonShiftContainer.appendChild(geoJsonShiftDistLabel);

    var geoJsonShiftDistInput = document.createElement('input');
    geoJsonShiftDistInput.type = 'number';
    geoJsonShiftDistInput.id = 'geoJsonShiftDistance';
    geoJsonShiftDistInput.value = '1'; // default 1 meter shift in direction of arrow clicked
    geoJsonShiftDistInput.min = '0';
    geoJsonShiftDistInput.step = '1';
    geoJsonShiftDistInput.style.width = '100%';
    geoJsonShiftDistInput.style.marginBottom = '10px';
    geoJsonShiftDistInput.style.padding = '5px';
    geoJsonShiftContainer.appendChild(geoJsonShiftDistInput);

    // Direction buttons for GeoJSON
    var geoJsonBtnUp = document.createElement('button');
    geoJsonBtnUp.textContent = '↑';
    geoJsonBtnUp.title = 'Shift Up';
    var geoJsonBtnDown = document.createElement('button');
    geoJsonBtnDown.textContent = '↓';
    geoJsonBtnDown.title = 'Shift Down';
    var geoJsonBtnLeft = document.createElement('button');
    geoJsonBtnLeft.textContent = '←';
    geoJsonBtnLeft.title = 'Shift Left';
    var geoJsonBtnRight = document.createElement('button');
    geoJsonBtnRight.textContent = '→';
    geoJsonBtnRight.title = 'Shift Right';

    // Diagonal buttons for GeoJSON
    var geoJsonBtnUpLeft = document.createElement('button');
    geoJsonBtnUpLeft.textContent = '↖';
    geoJsonBtnUpLeft.title = 'Shift Up-Left';
    var geoJsonBtnUpRight = document.createElement('button');
    geoJsonBtnUpRight.textContent = '↗';
    geoJsonBtnUpRight.title = 'Shift Up-Right';
    var geoJsonBtnDownLeft = document.createElement('button');
    geoJsonBtnDownLeft.textContent = '↙';
    geoJsonBtnDownLeft.title = 'Shift Down-Left';
    var geoJsonBtnDownRight = document.createElement('button');
    geoJsonBtnDownRight.textContent = '↘';
    geoJsonBtnDownRight.title = 'Shift Down-Right';

    // Reset button for GeoJSON
    var geoJsonBtnReset = document.createElement('button');
    geoJsonBtnReset.textContent = 'Reset';
    geoJsonBtnReset.title = 'Reset Shift';

    // Arrange buttons in a grid
    var geoJsonBtnGrid = document.createElement('div');
    geoJsonBtnGrid.style.display = 'grid';
    geoJsonBtnGrid.style.gridTemplateColumns = 'repeat(3, 1fr)';
    geoJsonBtnGrid.style.gap = '2px';
    geoJsonBtnGrid.style.marginBottom = '10px';
    geoJsonBtnGrid.appendChild(geoJsonBtnUpLeft);
    geoJsonBtnGrid.appendChild(geoJsonBtnUp);
    geoJsonBtnGrid.appendChild(geoJsonBtnUpRight);
    geoJsonBtnGrid.appendChild(geoJsonBtnLeft);
    var geoJsonEmptyCell = document.createElement('div');
    geoJsonBtnGrid.appendChild(geoJsonEmptyCell); // center cell empty
    geoJsonBtnGrid.appendChild(geoJsonBtnRight);
    geoJsonBtnGrid.appendChild(geoJsonBtnDownLeft);
    geoJsonBtnGrid.appendChild(geoJsonBtnDown);
    geoJsonBtnGrid.appendChild(geoJsonBtnDownRight);
    geoJsonShiftContainer.appendChild(geoJsonBtnGrid);

    // Reset button row
    var geoJsonResetRow = document.createElement('div');
    geoJsonResetRow.style.marginTop = '5px';
    geoJsonResetRow.appendChild(geoJsonBtnReset);
    geoJsonShiftContainer.appendChild(geoJsonResetRow);

    // Add event listeners for GeoJSON shift buttons
    geoJsonBtnUp.addEventListener('click', function () {
      shiftGeoJsonLayer('up');
    });
    geoJsonBtnDown.addEventListener('click', function () {
      shiftGeoJsonLayer('down');
    });
    geoJsonBtnLeft.addEventListener('click', function () {
      shiftGeoJsonLayer('left');
    });
    geoJsonBtnRight.addEventListener('click', function () {
      shiftGeoJsonLayer('right');
    });
    geoJsonBtnUpLeft.addEventListener('click', function () {
      shiftGeoJsonLayer('upleft');
    });
    geoJsonBtnUpRight.addEventListener('click', function () {
      shiftGeoJsonLayer('upright');
    });
    geoJsonBtnDownLeft.addEventListener('click', function () {
      shiftGeoJsonLayer('downleft');
    });
    geoJsonBtnDownRight.addEventListener('click', function () {
      shiftGeoJsonLayer('downright');
    });
    geoJsonBtnReset.addEventListener('click', function () {
      resetGeoJsonShift();
    });

    geoJsonSection.appendChild(geoJsonShiftContainer);

    tabPane.appendChild(geoJsonSection);

    fillWMSLayersSelectList();
    opacityRange.addEventListener('input', function () {
      var value = document.getElementById('WMSLayersSelect').value;
      if (value !== '' && value !== 'undefined') {
        var layer = W.map.getLayerByName(value);
        layer.setOpacity(opacityRange.value / 100);
        document.getElementById('WMSOpacityLabel').textContent = 'Layer transparency: ' + document.getElementById('WMSOpacity').value + ' %';
      }
    });
    WMSSelect.addEventListener('change', function () {
      var selectedLayer = W.map.getLayers().filter((layer) => layer.name == WMSSelect.value)[0];
      if (selectedLayer) {
        opacityRange.value = selectedLayer.opacity * 100;
        document.getElementById('WMSOpacityLabel').textContent = 'Layer transparency: ' + document.getElementById('WMSOpacity').value + ' %';
      }
    });
    setZOrdering(WMSLayerTogglers);
    wmeSDK.Events.on({ eventName: 'wme-map-layer-added', eventHandler: fillWMSLayersSelectList });
    wmeSDK.Events.on({ eventName: 'wme-map-layer-removed', eventHandler: fillWMSLayersSelectList });
    wmeSDK.Events.on({ eventName: 'wme-map-layer-added', eventHandler: () => setZOrdering(WMSLayerTogglers) });
    wmeSDK.Events.on({ eventName: 'wme-map-layer-removed', eventHandler: () => setZOrdering(WMSLayerTogglers) });
    wmeSDK.Events.on({ eventName: 'wme-map-move-end', eventHandler: () => setZOrdering(WMSLayerTogglers) });
  }

  function fillWMSLayersSelectList() {
    const select = document.getElementById('WMSLayersSelect');
    const value = select.value;
    let htmlCode = '';
    W.map.getLayers().filter((layer) => layer.params?.SERVICE === 'WMS').forEach((layer) => (htmlCode += `<option value='${layer.name}'>${layer.name}</option><br>`));
    select.innerHTML = htmlCode;
    select.value = value;
  }

  function addNewLayer(id, service, serviceLayers, zIndex = 0, opacity = 1) {
    var newLayer = {};
    newLayer.serviceType = service.type;
    if ((service.type == 'XYZ') & (zIndex == 0)) {
      newLayer.zIndex = ZIndexes.base;
    } else {
      newLayer.zIndex = zIndex == 0 ? ZIndexes.popup : zIndex;
    }
    switch (service.type) {
      case 'WMS':
        // Debug log for WMS request URL and filter
        if (typeof zIndex === 'string' && zIndex.includes("road_class='")) {
          console.log('[WMS DEBUG] Creating WMS Layer:', id);
          console.log('[WMS DEBUG] Service URL:', service.url);
          console.log('[WMS DEBUG] Layers:', serviceLayers);
          console.log('[WMS DEBUG] Filter:', zIndex);
        }
        newLayer.layer = new OL.Layer.WMS(
          id,
          service.url,
          {
            layers: serviceLayers,
            transparent: 'true',
            format: 'image/png',
            version: service.version || '1.3.0', // Use service.version if provided, else default to 1.3.0 use WMS 1.3.0 + EPSG:3857
            CQL_FILTER: typeof zIndex === 'string' ? zIndex : undefined,
          },
          {
            opacity: opacity,
            tileSize: WMSLayersTechSource.tileSizeG || new OL.Size(256, 256), // Use service-defined tile size if available
            isBaseLayer: false,
            visibility: false,
            transitionEffect: 'resize',
            attribution: service.attribution,
            projection: new OL.Projection('EPSG:3857'), //alternativa defaultní EPSG:900913
          }
        );
        break;
      case 'WMS_4326':
        newLayer.layer = new OL.Layer.WMS(
          id,
          service.url,
          {
            layers: serviceLayers,
            transparent: 'true',
            format: 'image/png',
            version: service.version || '1.1.1', //use WMS 1.1.1 + EPSG:4326
            CQL_FILTER: typeof zIndex === 'string' ? zIndex : undefined,
          },
          {
            opacity: opacity,
            tileSize: WMSLayersTechSource.tileSizeG || new OL.Size(256, 256), // Use service-defined tile size if available
            isBaseLayer: false,
            visibility: false,
            transitionEffect: 'resize',
            attribution: service.attribution,
            epsg4326: new OL.Projection('EPSG:4326'),
            getURL: getUrl4326,
            getFullRequestString: getFullRequestString4326,
          }
        );
        break;
      case 'XYZ':
        newLayer.layer = new OL.Layer.XYZ(id, service.url, {
          sphericalMercator: true,
          isBaseLayer: false,
          visibility: false,
          RESOLUTION_PROPERTIES: {},
          resolutions: WMSLayersTechSource.resolutions,
          serverResolutions: WMSLayersTechSource.resolutions.slice(0, 'maxZoom' in service && service.maxZoom > 0 ? service.maxZoom : 23),
          transitionEffect: 'resize',
          attribution: service.attribution,
        });
        break;
      default:
        newLayer.layer = null;
    }
    return newLayer;
  }
  /*For GeoServer WMS:

WMS 1.1.1 prefers coordinates in EPSG:4326 (longitude, latitude order).
WMS 1.3.0 uses EPSG:4326 (latitude, longitude order) and supports EPSG:3857 (Web Mercator) natively.
Recommendations:

If your client expects (longitude, latitude) order, use WMS 1.1.1 with EPSG:4326.
If your client expects (latitude, longitude) order or uses web maps (Google, OSM), use WMS 1.3.0 with EPSG:3857.
Summary:

For web mapping (slippy maps), use WMS 1.3.0 + EPSG:3857.
For GIS tools or legacy clients, use WMS 1.1.1 + EPSG:4326.*/

  function addGroupToggler(isDefault, layerSwitcherGroupItemName, layerGroupVisibleName) {
    var group;
    if (isDefault === true) {
      group = document.getElementById(layerSwitcherGroupItemName).parentElement.parentElement;
    } else {
      var layerGroupsList = document.getElementsByClassName('list-unstyled togglers')[0];
      group = document.createElement('li');
      group.className = 'group';
      var togglerContainer = document.createElement('div');
      togglerContainer.className = 'layer-switcher-toggler-tree-category';
      var groupButton = document.createElement('wz-button');
      groupButton.color = 'clear-icon';
      groupButton.size = 'xs';
      var iCaretDown = document.createElement('i');
      iCaretDown.className = 'toggle-category w-icon w-icon-caret-down';
      iCaretDown.dataset.groupId = layerSwitcherGroupItemName.replace('layer-switcher-', '').toUpperCase();
      var togglerSwitch = document.createElement('wz-toggle-switch');
      togglerSwitch.className = layerSwitcherGroupItemName + ' hydrated';
      togglerSwitch.id = layerSwitcherGroupItemName;
      togglerSwitch.checked = true;
      var label = document.createElement('label');
      label.className = 'label-text';
      label.htmlFor = togglerSwitch.id;
      var togglerChildrenList = document.createElement('ul');
      togglerChildrenList.className = 'collapsible-' + layerSwitcherGroupItemName.replace('layer-switcher-', '').toUpperCase();
      label.appendChild(document.createTextNode(layerGroupVisibleName));
      groupButton.addEventListener('click', layerTogglerGroupMinimizerEventHandler(iCaretDown));
      togglerSwitch.addEventListener('click', layerTogglerGroupMinimizerEventHandler(iCaretDown));
      groupButton.appendChild(iCaretDown);
      togglerContainer.appendChild(groupButton);
      togglerContainer.appendChild(togglerSwitch);
      togglerContainer.appendChild(label);
      group.appendChild(togglerContainer);
      group.appendChild(togglerChildrenList);
      layerGroupsList.appendChild(group);
    }
    return group;
  }

  function addLayerToggler(groupToggler, layerName, isPublic, layerArray) {
    var layerToggler = {};
    layerToggler.layerName = layerName;
    layerToggler.serviceType =
      layerArray.filter(function (e) {
        return e.serviceType == 'XYZ';
      }).length > 0
        ? 'XYZ'
        : 'WMS';
    var layerShortcut = layerName.replace(/ /g, '_').replace('.', '');
    layerToggler.htmlItem = 'layer-switcher-item_' + layerShortcut;
    layerToggler.layerArray = layerArray;
    var layer_container = groupToggler.getElementsByTagName('UL')[0];
    var layerGroupCheckbox = groupToggler.getElementsByClassName('layer-switcher-toggler-tree-category')[0].getElementsByTagName('wz-toggle-switch')[0];
    var toggler = document.createElement('li');
    var togglerCheckbox = document.createElement('wz-checkbox');
    togglerCheckbox.id = layerToggler.htmlItem;
    togglerCheckbox.className = 'hydrated';
    var labelSymbol = document.createElement('span');
    labelSymbol.className = isPublic ? 'fa fa-location-arrow' : 'fa fa-lock';
    togglerCheckbox.appendChild(labelSymbol);
    togglerCheckbox.appendChild(document.createTextNode(layerName));
    toggler.appendChild(togglerCheckbox);
    layer_container.appendChild(toggler);
    for (var i = 0; i < layerArray.length; i++) {
      togglerCheckbox.addEventListener('change', layerTogglerEventHandler(layerArray[i]));
      layerGroupCheckbox.addEventListener('change', layerTogglerGroupEventHandler(togglerCheckbox, layerArray[i]));
      layerArray[i].layer.name = layerName + (layerArray.length > 1 ? ' ' + i : '');
    }
    registerKeyShortcut('WMS: ' + layerName, layerKeyShortcutEventHandler(layerGroupCheckbox, togglerCheckbox), layerShortcut);
    return layerToggler;
  }

  function registerKeyShortcut(actionName, callback, keyName) {
    I18n.translations[I18n.locale].keyboard_shortcuts.groups.default.members[keyName] = actionName;
    W.accelerators.addAction(keyName, { group: 'default' });
    W.accelerators.events.register(keyName, null, callback);
    W.accelerators._registerShortcuts({ ['name']: keyName });
  }

  function layerTogglerEventHandler(layerType) {
    return function () {
      if (this.checked) {
        W.map.addLayer(layerType.layer);
        layerType.layer.setVisibility(this.checked);
      } else {
        layerType.layer.setVisibility(this.checked);
        W.map.removeLayer(layerType.layer);
      }
    };
  }

  function layerKeyShortcutEventHandler(groupCheckbox, checkbox) {
    return function () {
      if (!groupCheckbox.disabled) {
        checkbox.click();
      }
    };
  }

  function layerTogglerGroupEventHandler(checkbox, layerType) {
    return function () {
      if (this.checked) {
        if (checkbox.checked) {
          W.map.addLayer(layerType.layer);
          layerType.layer.setVisibility(this.checked && checkbox.checked);
        }
      } else {
        if (checkbox.checked) {
          layerType.layer.setVisibility(this.checked && checkbox.checked);
          W.map.removeLayer(layerType.layer);
        }
      }
      checkbox.disabled = !this.checked;
    };
  }

  function layerTogglerGroupMinimizerEventHandler(iCaretDown) {
    return function () {
      var ulCollapsible = iCaretDown.parentElement.parentElement.parentElement.getElementsByTagName('UL')[0];
      if (!iCaretDown.classList.contains('upside-down')) {
        iCaretDown.classList.add('upside-down');
        ulCollapsible.classList.add('collapse-layer-switcher-group');
      } else {
        iCaretDown.classList.remove('upside-down');
        ulCollapsible.classList.remove('collapse-layer-switcher-group');
      }
    };
  }

  function setZOrdering(layerTogglers) {
    return function () {
      for (var key in layerTogglers) {
        for (var j = 0; j < layerTogglers[key].layerArray.length; j++) {
          if (layerTogglers[key].layerArray[j].zIndex > 0) {
            var l = W.map.getLayerByName(layerTogglers[key].layerName);
            if (l !== undefined) {
              l.setZIndex(layerTogglers[key].layerArray[j].zIndex);
            }
          }
        }
      }
    };
  }

  function getUrl4326(bounds) {
    var newParams = {};
    bounds.transform(this.projection, this.epsg4326);
    newParams.BBOX = bounds.toArray(this.reverseAxisOrder());
    var imageSize = this.getImageSize(bounds);
    newParams.WIDTH = imageSize.w;
    newParams.HEIGHT = imageSize.h;
    // newParams.WIDTH = 742;
    // newParams.HEIGHT = 485;
    //from geoserver
    // newParams.WIDTH = 648;
    // newParams.HEIGHT = 768;
    var requestString = this.getFullRequestString(newParams);
    return requestString;
  }

  function getFullRequestString4326(newParams) {
    this.params.SRS = 'EPSG:4326';
    return OL.Layer.Grid.prototype.getFullRequestString.apply(this, arguments);
  }

  // Function to load GeoJSON from URL
  function loadGeoJSONFromURL() {
    const wardNo = document.getElementById('geoJsonWardSelect').value;
    const fontColor = document.getElementById('geoJsonFontColor').value;
    const fontSize = document.getElementById('geoJsonFontSize').value;
    const buildingUrl = `https://geonep.com.np/LMC/ajax/x_building.php?ward_no=${wardNo}`;
    const boundaryUrl = `https://geonep.com.np/LMC/ajax/x_ward_bnd.php?ward_no=${wardNo}`;
    const buildingLayerName = `LMC_Ward_${wardNo}_Buildings`;
    const boundaryLayerName = `LMC_Ward_${wardNo}_Boundary`;
    
    // Check if layers already exist
    const existingBuildingLayer = W.map.getLayersByName(buildingLayerName);
    const existingBoundaryLayer = W.map.getLayersByName(boundaryLayerName);
    if ((existingBuildingLayer && existingBuildingLayer.length > 0) || 
        (existingBoundaryLayer && existingBoundaryLayer.length > 0)) {
      WazeToastr.Alerts.warning(
        scriptName,
        `Ward ${wardNo} layers already loaded`,
        false,
        false,
        3000
      );
      return;
    }
    
    updateGeoJsonStatus('Loading buildings and boundary...');
    console.log(`${scriptName}: Fetching buildings from ${buildingUrl}`);
    console.log(`${scriptName}: Fetching boundary from ${boundaryUrl}`);
    
    // Load buildings first
    GM_xmlhttpRequest({
      method: 'GET',
      url: buildingUrl,
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      timeout: 30000,
      onload: function(response) {
        if (response.status >= 200 && response.status < 300) {
          try {
            const geojsonData = JSON.parse(response.responseText);
            
            // Validate GeoJSON structure
            if (!geojsonData || !geojsonData.type || !geojsonData.features) {
              throw new Error('Invalid GeoJSON format');
            }
            
            if (geojsonData.features.length === 0) {
              throw new Error('No features found in GeoJSON');
            }
            
            console.log(`${scriptName}: Loaded ${geojsonData.features.length} building features`);
            
            // Create vector layer for buildings
            createGeoJSONVectorLayer(geojsonData, buildingLayerName, wardNo, fontColor, fontSize, 'buildings');
            
            // Now load the boundary
            loadWardBoundary(wardNo, boundaryUrl, boundaryLayerName, geojsonData.features.length);
            
          } catch (error) {
            console.error(`${scriptName}: Error parsing buildings GeoJSON:`, error);
            updateGeoJsonStatus(`Error: ${error.message}`);
            WazeToastr.Alerts.error(
              scriptName,
              `Failed to parse buildings GeoJSON: ${error.message}`,
              false,
              false,
              5000
            );
          }
        } else {
          const errorMsg = `HTTP ${response.status}: ${response.statusText}`;
          console.error(`${scriptName}: ${errorMsg}`);
          updateGeoJsonStatus(`Error: ${errorMsg}`);
          WazeToastr.Alerts.error(
            scriptName,
            `Failed to load buildings data: ${errorMsg}`,
            false,
            false,
            5000
          );
        }
      },
      onerror: function(error) {
        console.error(`${scriptName}: Network error:`, error);
        updateGeoJsonStatus('Network error occurred');
        WazeToastr.Alerts.error(
          scriptName,
          'Network error: Unable to connect to geonep.com.np',
          false,
          false,
          5000
        );
      },
      ontimeout: function() {
        console.error(`${scriptName}: Request timeout`);
        updateGeoJsonStatus('Request timeout');
        WazeToastr.Alerts.error(
          scriptName,
          'Request timeout: Server took too long to respond',
          false,
          false,
          5000
        );
      }
    });
  }

  // Function to load ward boundary
  function loadWardBoundary(wardNo, boundaryUrl, boundaryLayerName, buildingCount) {
    GM_xmlhttpRequest({
      method: 'GET',
      url: boundaryUrl,
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      timeout: 30000,
      onload: function(response) {
        if (response.status >= 200 && response.status < 300) {
          try {
            const boundaryData = JSON.parse(response.responseText);
            
            // Validate GeoJSON structure
            if (!boundaryData || !boundaryData.type || !boundaryData.features) {
              throw new Error('Invalid boundary GeoJSON format');
            }
            
            console.log(`${scriptName}: Loaded ${boundaryData.features.length} boundary features`);
            
            // Create vector layer for boundary
            createGeoJSONVectorLayer(boundaryData, boundaryLayerName, wardNo, null, null, 'boundary');
            
            updateGeoJsonStatus(`Loaded ${buildingCount} buildings and boundary for Ward ${wardNo}`);
            WazeToastr.Alerts.success(
              scriptName,
              `Successfully loaded ${buildingCount} buildings and boundary for Ward ${wardNo}`,
              false,
              false,
              3000
            );
            
          } catch (error) {
            console.error(`${scriptName}: Error parsing boundary GeoJSON:`, error);
            updateGeoJsonStatus(`Loaded buildings but boundary failed: ${error.message}`);
            WazeToastr.Alerts.warning(
              scriptName,
              `Loaded buildings but boundary failed: ${error.message}`,
              false,
              false,
              5000
            );
          }
        } else {
          const errorMsg = `HTTP ${response.status}: ${response.statusText}`;
          console.error(`${scriptName}: Boundary ${errorMsg}`);
          updateGeoJsonStatus(`Loaded buildings but boundary failed`);
          WazeToastr.Alerts.warning(
            scriptName,
            `Loaded buildings but boundary failed: ${errorMsg}`,
            false,
            false,
            5000
          );
        }
      },
      onerror: function(error) {
        console.error(`${scriptName}: Boundary network error:`, error);
        updateGeoJsonStatus('Buildings loaded, boundary network error');
        WazeToastr.Alerts.warning(
          scriptName,
          'Buildings loaded, but boundary failed to load',
          false,
          false,
          5000
        );
      },
      ontimeout: function() {
        console.error(`${scriptName}: Boundary request timeout`);
        updateGeoJsonStatus('Buildings loaded, boundary timeout');
        WazeToastr.Alerts.warning(
          scriptName,
          'Buildings loaded, but boundary request timeout',
          false,
          false,
          5000
        );
      }
    });
  }

  // Helper function to remove Z coordinates from GeoJSON
  function removeZCoordinates(coords) {
    if (!coords) return coords;
    
    // Check if this is a coordinate pair [lon, lat] or [lon, lat, elevation]
    if (typeof coords[0] === 'number') {
      // It's a coordinate pair/triple - return only [lon, lat]
      return coords.slice(0, 2);
    }
    
    // It's an array of coordinates - recurse
    return coords.map(removeZCoordinates);
  }

  // Function to create vector layer from GeoJSON
  function createGeoJSONVectorLayer(geojsonData, layerName, wardNo, fontColor, fontSize, layerType) {
    try {
      // Default values for building layers
      fontColor = fontColor || '#ffffff';
      fontSize = fontSize || '13';
      layerType = layerType || 'buildings';
      
      console.log(`${scriptName}: Creating ${layerType} vector layer for Ward ${wardNo}`);
      
      if (layerType === 'buildings') {
        console.log(`${scriptName}: Font settings - Color: ${fontColor}, Size: ${fontSize}px`);
      }
      
      console.log(`${scriptName}: GeoJSON data:`, geojsonData);
      
      // Ensure we have valid GeoJSON
      if (!geojsonData || !geojsonData.features || geojsonData.features.length === 0) {
        throw new Error('No features in GeoJSON data');
      }
      
      // Parse GeoJSON - convert string to object if needed
      const geojson = typeof geojsonData === 'string' ? JSON.parse(geojsonData) : geojsonData;
      
      // Remove Z coordinates (elevation) from all features as OpenLayers 2 doesn't handle 3D coordinates well
      geojson.features.forEach(feature => {
        if (feature.geometry && feature.geometry.coordinates) {
          feature.geometry.coordinates = removeZCoordinates(feature.geometry.coordinates);
        }
      });
      
      console.log(`${scriptName}: Processing ${geojson.features.length} features from GeoJSON (Z-coordinates removed)`);
      
      // Create OpenLayers GeoJSON format reader
      const format = new OL.Format.GeoJSON({
        internalProjection: W.map.getProjectionObject(),
        externalProjection: new OL.Projection('EPSG:4326')
      });
      
      // Read features from GeoJSON as a complete FeatureCollection
      let features;
      try {
        // Pass the entire GeoJSON object as a string (OpenLayers 2 way)
        const geojsonString = typeof geojson === 'string' ? geojson : JSON.stringify(geojson);
        features = format.read(geojsonString);
        console.log(`${scriptName}: Successfully parsed features using format.read()`);
      } catch (parseError) {
        console.error(`${scriptName}: Primary GeoJSON parse error:`, parseError);
        console.log(`${scriptName}: Attempting alternative parsing method...`);
        
        // Alternative: parse each feature individually
        features = [];
        geojson.features.forEach(function(feature, index) {
          try {
            // Create a temporary FeatureCollection for each feature
            const singleFeatureCollection = {
              type: 'FeatureCollection',
              features: [feature]
            };
            const featureString = JSON.stringify(singleFeatureCollection);
            const parsedFeatures = format.read(featureString);
            
            if (parsedFeatures && parsedFeatures.length > 0) {
              features = features.concat(parsedFeatures);
            }
          } catch (e) {
            console.warn(`${scriptName}: Skipping feature ${index}:`, e);
          }
        });
      }
      
      console.log(`${scriptName}: Total features parsed: ${features ? features.length : 0}`);
      
      if (!features || features.length === 0) {
        throw new Error('No valid features could be parsed from GeoJSON');
      }
      
      // Define styles based on layer type
      let defaultStyle, selectStyle;
      
      if (layerType === 'boundary') {
        // Boundary layer style - prominent boundary lines
        defaultStyle = new OL.Style({
          strokeColor: '#FF0000',
          strokeWidth: 3,
          strokeOpacity: 0.9,
          fillColor: '#FF0000',
          fillOpacity: 0.05,
          pointRadius: 4,
          label: '',
        });
        selectStyle = new OL.Style({
          strokeColor: '#00FF00',
          strokeWidth: 4,
          fillColor: '#00FF00',
          fillOpacity: 0.05
        });
      } else {
        // Building layer style - with labels
        defaultStyle = new OL.Style({
          strokeColor: '#FF5722',
          strokeWidth: 2,
          strokeOpacity: 0.8,
          fillColor: '#FF5722',
          fillOpacity: 0.01,
          pointRadius: 4,
          label: '${custom_label}',
          labelAlign: 'cm',
          labelOutlineColor: '#000000',
          labelOutlineWidth: 3,
          fontSize: fontSize + 'px',
          fontWeight: 'bold',
          fontFamily: 'inherit',
          fontColor: fontColor,
        });
        selectStyle = new OL.Style({
          strokeColor: '#00BCD4',
          strokeWidth: 3,
          fillColor: '#00BCD4',
          fillOpacity: 0.01
        });
      }
      
      // Create vector layer with parsed features
      const vectorLayer = new OL.Layer.Vector(layerName, {
        displayInLayerSwitcher: false,
        uniqueName: layerName,
        projection: W.map.getProjectionObject(),
        styleMap: new OL.StyleMap({
          default: defaultStyle,
          select: selectStyle
        })
      });
      
      // Process features to handle null values in labels (only for building layers)
      if (layerType === 'buildings') {
        features.forEach(feature => {
          if (feature.attributes) {
            // Create a custom label by filtering out null/undefined values
            const labelParts = [];
              if (feature.attributes.metric_num !== null && feature.attributes.metric_num !== undefined) {
              labelParts.push(feature.attributes.metric_num);
              } else {
              // If metric_num is not available, skip the rest
              feature.attributes.custom_label = '';
              return;
              }
            if (feature.attributes.rd_naeng !== null && feature.attributes.rd_naeng !== undefined) {
              labelParts.push(feature.attributes.rd_naeng);
            }
            if (feature.attributes.rd_nanep !== null && feature.attributes.rd_nanep !== undefined) {
              // Convert Preeti font to Unicode
              const unicodeText = typeof preeti === 'function' ? preeti(feature.attributes.rd_nanep) : feature.attributes.rd_nanep;
              labelParts.push(unicodeText);
            }
            if (feature.attributes.tole_ne_en !== null && feature.attributes.tole_ne_en !== undefined) {
              labelParts.push(feature.attributes.tole_ne_en);
            }
            // Set the custom label attribute
            feature.attributes.custom_label = labelParts.join('\n');
          }
        });
      }
      
      // Add features to layer
      vectorLayer.addFeatures(features);
      console.log(`${scriptName}: Added ${features.length} features to vector layer`);
      
      // Add layer to map
      W.map.addLayer(vectorLayer);
      vectorLayer.setVisibility(true);
      
      // Set z-index based on layer type - boundaries below buildings
      if (layerType === 'boundary') {
        vectorLayer.setZIndex(ZIndexes.popup + 5);
      } else {
        vectorLayer.setZIndex(ZIndexes.popup + 10);
      }
      
      // Store reference for cleanup
      loadedGeoJSONLayers.push({
        layer: vectorLayer,
        name: layerName,
        wardNo: wardNo,
        layerType: layerType
      });
      
      // Update layer selector dropdown
      updateGeoJsonLayerSelector();
      
    } catch (error) {
      console.error(`${scriptName}: Error creating GeoJSON vector layer:`, error);
      WazeToastr.Alerts.error(
        scriptName,
        `Failed to create vector layer: ${error.message}`,
        false,
        false,
        5000
      );
      throw error;
    }
  }

  // Function to clear all loaded GeoJSON layers
  function clearLoadedGeoJSON() {
    if (loadedGeoJSONLayers.length === 0) {
      WazeToastr.Alerts.info(
        scriptName,
        'No building layers to clear',
        false,
        false,
        2000
      );
      return;
    }
    
    let removedCount = 0;
    loadedGeoJSONLayers.forEach(item => {
      if (item.layer) {
        W.map.removeLayer(item.layer);
        item.layer.destroy();
        removedCount++;
      }
    });
    
    loadedGeoJSONLayers = [];
    updateGeoJsonStatus('All building layers cleared');
    
    WazeToastr.Alerts.success(
      scriptName,
      `Removed ${removedCount} building layer(s)`,
      false,
      false,
      2000
    );
  }

  // Helper function to update status display
  function updateGeoJsonStatus(message) {
    const statusDiv = document.getElementById('geoJsonStatus');
    if (statusDiv) {
      statusDiv.textContent = message;
      statusDiv.style.color = message.includes('Error') ? '#f44336' : '#4CAF50';
    }
  }

  // Helper: update GeoJSON layer selector dropdown
  function updateGeoJsonLayerSelector() {
    const select = document.getElementById('geoJsonLayerSelect');
    if (!select) return;

    // Clear existing options except first (default)
    while (select.options.length > 1) {
      select.remove(1);
    }

    // Add options for each loaded layer
    loadedGeoJSONLayers.forEach(layerInfo => {
      const option = document.createElement('option');
      option.value = layerInfo.name;
      option.textContent = layerInfo.name;
      select.appendChild(option);
    });

    // Reset to default if no layers
    if (loadedGeoJSONLayers.length === 0) {
      select.selectedIndex = 0;
    }
  }

  // After all WMSLayerTogglers are created:

  // --- Layer toggler state persistence ---
  // Utility to save all toggler states
  function saveLayerTogglerStates() {
    if (!localStorage) return;
    const state = {};
    for (const key in WMSLayerTogglers) {
      const togglerId = WMSLayerTogglers[key].htmlItem;
      const toggler = document.getElementById(togglerId);
      if (toggler) state[key] = toggler.checked;
    }
    localStorage.WMSLayers = JSON.stringify(state);
  }

  // Utility to restore all toggler states
  function restoreLayerTogglerStates() {
    if (!localStorage.WMSLayers) return;
    const state = JSON.parse(localStorage.WMSLayers);
    for (const key in state) {
      if (WMSLayerTogglers[key]) {
        const togglerId = WMSLayerTogglers[key].htmlItem;
        const toggler = document.getElementById(togglerId);
        if (toggler && toggler.checked !== state[key]) {
          toggler.checked = state[key];
          toggler.dispatchEvent(new Event('change', { bubbles: true }));
        }
      }
    }
  }

  // Attach change listeners to save state on toggle
  for (const key in WMSLayerTogglers) {
    const togglerId = WMSLayerTogglers[key].htmlItem;
    const toggler = document.getElementById(togglerId);
    if (toggler) {
      toggler.addEventListener('change', saveLayerTogglerStates);
    }
  }
  // Restore state after togglers are created
  restoreLayerTogglerStates();

function scriptupdatemonitor() {
  if (WazeToastr?.Ready) {
    // Create and start the ScriptUpdateMonitor
    const updateMonitor = new WazeToastr.Alerts.ScriptUpdateMonitor(scriptName, scriptVersion, downloadUrl, GM_xmlhttpRequest);

    // Check immediately on page load, then every 2 hours
    updateMonitor.start(2, true); // checkImmediately = true

    // Show the update dialog for the current version
    WazeToastr.Interface.ShowScriptUpdate(scriptName, scriptVersion, updateMessage, downloadUrl);
  } else {
    setTimeout(scriptupdatemonitor, 250);
  }
}
  function bootstrap() {
    wmeSDK = unsafeWindow.getWmeSdk({ scriptId: 'nepali-wms-layers-beta', scriptName });
    console.log(`${scriptName} initialized.`);
scriptupdatemonitor();
    document.addEventListener('wme-map-data-loaded', init, { once: true });
  }

  unsafeWindow.SDK_INITIALIZED.then(bootstrap);
  /*
changeLog
2026-04-16.1
<strong>Fixed:</strong><br> - Compability with latest WME version.<br><br> - swapped W.map.olMap with W.map.getOLMap() to fix layers not showing up issue. <br><br> Thanks to davidsl4 to pointing out.
2026.02.06.06
- Added Preeti font to Unicode conversion for rd_nanep field
- Building labels now display Nepali text in proper Unicode format
2026.02.06.01
- Added feature: Load GeoJSON from URL (LMC Ward Buildings from geonep.com.np)
- New UI section to select ward number (1-29) and load building data
- Buildings display with house numbers as labels
- Clear button to remove all loaded GeoJSON layers
2025.11.29.01
- Added layers: Health Facilities from National Geoportal, Police Units from National Geoportal.
2025.08.30.01
- ZIndex update for : Education Facilities (PRTMP),<br> Health Facilities (PRTMP),<br> Palika Centre (PRTMP),<br> Ward Centre (PRTMP),<br> Tourist Attraction,<br> Customs Office <br> Bridges (BSM),<br> Bridges (PRTMP),<br> and Lalitpur Metropolitan City (LMC) layers.
version: 2025.07.27.1 - Added Layers:
  - Rivers
  - Education Facilities (PRTMP)
  - Health Facilities (PRTMP)
  - Palika Centre (PRTMP)
  - Ward Centre (PRTMP)
  - Tourist Attraction
  - Customs Office
  - National Highways 2023
  - Province Highways 2023
  - Province Roads 2023
  - Bridges (BSM)
  - Bridges (PRTMP)
  - and popup support for above layers and more.
version: 2025.07.24.01 - It now supports to display popup for highway with various information.
version: 2025.06.23.01 - Added diagonal (↖, ↗, ↙, ↘) shift buttons for WMS layers.
                       - Shows alert when the shift is reset to default.
version: 2025.06.08.01 - Now the WMS layer can be shifted by a specified distance in meters.
Version: 2025.06.06.02 - Added Bridge Management System bridge locations!
                       - Loaded layers will be reloaded even after the page refresh.
Version: 2025.06.06.01 - Added Bridge Management System bridge locations!
version: 2025.05.11.01 - Fixed Z-ordering
version: 2025.04.13.01 - Fixed Combatible with the latest wme beta v2.287-5! Now it monitors the script update!
version: 2025.03.06.01 - Now LMC HN can be filtered by ward
version: 2025.02.03.01 - Line modification
version: 2025.02.01.02 - Added support for WazeToastr update dialogue box
version: 2025.02.01.01 - Modified how WMS 4326 image is displayed
version: "1.0", message: "Initial Version"

*/
})();