/** * Leaflet Map Application * Refactored for better organization, maintainability, and modern JavaScript practices */ // ============================================================================= // CONFIGURATION & CONSTANTS // ============================================================================= const CONFIG = { map: { preferCanvas: true, defaultCenter: [38, -80], defaultZoom: 10, }, icons: { defaultSize: [38, 38], defaultAnchor: [19, 19], smallSize: [12, 12], smallAnchor: [6, 6], mediumSize: [24, 24], mediumAnchor: [12, 12], }, bounds: { wv: { corner1: [37.1411, -82.8003], corner2: [40.6888, -77.6728], }, }, urls: { settingsFile: (mapId) => `/z/doc?command=view&allfile=true&file={tempdirs}/60daytemp/${mapId}_settings.json`, menu: '/z/mapdraw?command=leaflet&step=menu&skin=ajax', query: '/z/mapdraw?command=leaflet&step=query&skin=ajax', lasso: '/z/mapdraw?command=leaflet&step=lassoed&skin=ajax', }, layers: { esri: { taxMaps: 'https://services.wvgis.wvu.edu/arcgis/rest/services/Planning_Cadastre/WV_Parcels/MapServer', floodPublic: 'https://services.wvgis.wvu.edu/arcgis/rest/services/Hazards/floodTool_publicView/MapServer', floodExpert: 'https://services.wvgis.wvu.edu/arcgis/rest/services/Hazards/floodTool_expertView/MapServer', forestParks: 'https://services.wvgis.wvu.edu/arcgis/rest/services/Boundaries/wv_protected_lands/MapServer', trails: 'https://services.wvgis.wvu.edu/arcgis/rest/services/Applications/trails_trailService/MapServer/', politicalBoundary: 'https://services.wvgis.wvu.edu/arcgis/rest/services/Boundaries/wv_political_boundary/MapServer', wvdot: 'https://gis.transportation.wv.gov/arcgis/rest/services/Base_Maps/WVDOT_Base_Map_WM/MapServer', wvdotOwn: 'https://gis.transportation.wv.gov/arcgis/rest/services/Roads_And_Highways/Publication_LRS/MapServer', cellService: 'https://atlas.wvgs.wvnet.edu/arcgis/rest/services/WestVirginiaBroadbandOnlineV10/WvTechnologyGroupD/MapServer', internet: 'https://atlas.wvgs.wvnet.edu/arcgis/rest/services/WestVirginiaBroadbandOnlineV10/WestVirginiaBroadbandMap/MapServer/', contour1ft: 'https://services.wvgis.wvu.edu/arcgis/rest/services/Elevation/wv_contour_1ft/MapServer', waterSewer: 'https://services.wvgis.wvu.edu/arcgis/rest/services/Utilities_Communication/WV_WaterSewer_WVWDA/MapServer', addresses: 'https://services.wvgis.wvu.edu/arcgis/rest/services/Planning_Cadastre/WV_Parcels/MapServer/5', oilGasWells: 'https://tagis.dep.wv.gov/arcgis/rest/services/app_services/oog2/MapServer/7', oilGasDep: 'https://tagis.dep.wv.gov/arcgis/rest/services/app_services/oog2/MapServer/', delinquent: 'https://services.wvgis.wvu.edu/arcgis/rest/services/Planning_Cadastre/Delinquent_Properties/MapServer', topo: 'https://services.arcgisonline.com/ArcGIS/rest/services/USA_Topo_Maps/MapServer', topoAlt: 'https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer', hillshade: 'https://tagis.dep.wv.gov/arcgis/rest/services/webMercator/WVhillshade_wm/MapServer', leafless: 'https://services.wvgis.wvu.edu/arcgis/rest/services/Imagery_BaseMaps_EarthCover/wv_imagery_WVGISTC_leaf_off_mosaic/MapServer', }, coal: { eagleA: 'https://atlas.wvgs.wvnet.edu/arcgis/rest/services/Coal_Web_Mercator/Eagle_A_WM/MapServer', eagle: 'https://atlas.wvgs.wvnet.edu/arcgis/rest/services/Coal_Web_Mercator/Eagle_WM/MapServer', lowerPowellton: 'https://atlas.wvgs.wvnet.edu/arcgis/rest/services/Coal_Web_Mercator/Lower_Powellton_WM/MapServer', lowerWarEagle: 'https://atlas.wvgs.wvnet.edu/arcgis/rest/services/Coal_Web_Mercator/Lower_War_Eagle_WM/MapServer', powellton: 'https://atlas.wvgs.wvnet.edu/arcgis/rest/services/Coal_Web_Mercator/Powellton_WM/MapServer', eagleLowerSplit: 'https://atlas.wvgs.wvnet.edu/arcgis/rest/services/Coal_Web_Mercator/Eagle_Lower_Split_1_WM/MapServer', littleEagle: 'https://atlas.wvgs.wvnet.edu/arcgis/rest/services/Coal_Web_Mercator/Little_Eagle_WM/MapServer', middleWarEagle: 'https://atlas.wvgs.wvnet.edu/arcgis/rest/services/Coal_Web_Mercator/Middle_War_Eagle_WM/MapServer', glenalumTunnel: 'https://atlas.wvgs.wvnet.edu/arcgis/rest/services/Coal_Web_Mercator/Glenalum_Tunnel_WM/MapServer', peerless: 'https://atlas.wvgs.wvnet.edu/arcgis/rest/services/Coal_Web_Mercator/Peerless_WM/MapServer', }, }, }; // ============================================================================= // APPLICATION STATE // ============================================================================= const AppState = { control: '', layers: { base: {}, overlays: {}, settings: { overlays: [] }, }, settings: {}, icons: {}, lasso: {}, activeLayers: [], currentBaseLayer: 'Default', viewSet: false, masterGroupAdded: false, masterGroup: null, }; // Global map variable for backward compatibility with external scripts var map = null; // ============================================================================= // MAP INITIALIZATION // ============================================================================= const MapApp = { /** * Initialize the map application */ init() { this.createMap(); this.setupEventListeners(); this.initializeComponents(); }, /** * Create the Leaflet map instance */ createMap() { map = L.map('map', CONFIG.map) .setView(CONFIG.map.defaultCenter, CONFIG.map.defaultZoom); }, /** * Setup map event listeners */ setupEventListeners() { map.on('baselayerchange', (e) => { AppState.currentBaseLayer = e.name; }); map.on('overlayremove', (e) => { const index = AppState.activeLayers.indexOf(e.name); if (index > -1) { AppState.activeLayers.splice(index, 1); } }); map.on('overlayadd', (e) => { AppState.activeLayers.push(e.name); }); }, /** * Initialize all map components */ initializeComponents() { if (typeof maprealm !== 'undefined' && maprealm === 'agent') { GeocodeSearch.init(); } MeasureControl.init(); Icons.init(); ExternalOverlays.init(); Settings.read(); Events.initMove(); Events.initClick(); Menu.init(); }, }; // ============================================================================= // MEASURE CONTROL // ============================================================================= const MeasureControl = { init() { // Fix for measure control errors L.Control.Measure.include({ _setCaptureMarkerIcon() { this._captureMarker.options.autoPanOnFocus = false; this._captureMarker.setIcon( L.divIcon({ iconSize: this._map.getSize().multiplyBy(2), }) ); }, }); L.control.measure({}).addTo(map); }, }; // ============================================================================= // ICONS // ============================================================================= const Icons = { init() { this.createIcon('redoaksign', '/objects/maps/googlemapsign.png'); this.createIcon('wvumailboxIcon', '/objects/mailbox_icon.png'); this.createOilGasIcons(); }, createIcon(name, url, size = CONFIG.icons.defaultSize, anchor = CONFIG.icons.defaultAnchor) { AppState.icons[name] = L.icon({ iconUrl: url, iconSize: size, iconAnchor: anchor, }); }, createOilGasIcons() { const oilGasIcons = [ { name: 'activewell', file: 'activewell.png', size: CONFIG.icons.smallSize, anchor: CONFIG.icons.smallAnchor }, { name: 'allother', file: 'allother.png', size: CONFIG.icons.mediumSize, anchor: CONFIG.icons.mediumAnchor }, { name: 'neverdrilled', file: 'neverdrilled.png', size: CONFIG.icons.mediumSize, anchor: CONFIG.icons.mediumAnchor }, { name: 'neverissued', file: 'neverissued.png', size: CONFIG.icons.mediumSize, anchor: CONFIG.icons.mediumAnchor }, { name: 'permitapplication', file: 'permitapplication.png', size: CONFIG.icons.mediumSize, anchor: CONFIG.icons.mediumAnchor }, { name: 'permitissued', file: 'permitissued.png', size: CONFIG.icons.mediumSize, anchor: CONFIG.icons.mediumAnchor }, { name: 'plugged', file: 'plugged.png', size: CONFIG.icons.mediumSize, anchor: CONFIG.icons.mediumAnchor }, ]; oilGasIcons.forEach(({ name, file, size, anchor }) => { this.createIcon(name, `/objects/oilandgas/${file}`, size, anchor); }); }, getIcon(name) { return AppState.icons[name] || new L.Icon.Default(); }, }; // ============================================================================= // GEOCODE SEARCH // ============================================================================= const GeocodeSearch = { init() { const bounds = L.latLngBounds(CONFIG.bounds.wv.corner1, CONFIG.bounds.wv.corner2); this.initArcGISSearch(bounds); if (typeof maprealm !== 'undefined' && maprealm === 'agent') { this.initRedOakSearch(bounds); } }, initArcGISSearch(bounds) { const arcgisOnline = L.esri.Geocoding.arcgisOnlineProvider(); const searchControl = L.esri.Geocoding.geosearch({ position: 'topright', providers: [arcgisOnline], searchBounds: bounds, }).addTo(map); const results = L.layerGroup().addTo(map); searchControl.on('results', (data) => { results.clearLayers(); data.results.forEach((result) => { results.addLayer(L.marker(result.latlng)); }); }); }, initRedOakSearch(bounds) { const redOakGeo = L.esri.Geocoding.arcgisOnlineProvider({ url: 'https://www.property4u.com/z/mapdraw?command=geocode&step=redoakgeo&type=p&qin=', searchFields: ['CountyName'], label: 'RED OAK RESULTS', maxResults: '15', }); const searchControl2 = L.esri.Geocoding.geosearch({ position: 'topright', placeholder: 'Search Red Oak', providers: [redOakGeo], searchBounds: bounds, }).addTo(map); const results2 = L.layerGroup().addTo(map); searchControl2.on('results', (data) => { results2.clearLayers(); data.results.forEach((result) => { results2.addLayer(L.marker(result.latlng)); }); }); }, }; // ============================================================================= // LASSO FUNCTIONALITY // ============================================================================= const Lasso = { init() { const lassoControl = L.control.lasso().addTo(map); AppState.lasso.control = lassoControl; $(document).ready(() => { this.setupEvents(); }); }, setupEvents() { const lasso = AppState.lasso; lasso.toggleButton = document.querySelector('#toggleLasso'); lasso.containCheckbox = document.querySelector('#contain'); lasso.intersectCheckbox = document.querySelector('#intersect'); lasso.enabledDisplay = document.querySelector('#lassoEnabled'); lasso.resultDisplay = document.querySelector('#lassoResult'); map.on('mousedown', () => this.resetSelectedState()); map.on('lasso.finished', (event) => this.setSelectedLayers(event.layers)); map.on('lasso.enabled', () => { map.off('click'); if (lasso.enabledDisplay) lasso.enabledDisplay.innerHTML = 'Enabled'; this.resetSelectedState(); }); map.on('lasso.disabled', () => { lasso.finished = Date.now(); map.on('click', Events.handleClick); if (lasso.enabledDisplay) lasso.enabledDisplay.innerHTML = 'Disabled'; }); if (lasso.toggleButton) { lasso.toggleButton.addEventListener('click', () => { if (lasso.control.enabled()) { lasso.control.disable(); } else { lasso.control.enable(); } }); } if (lasso.containCheckbox) { lasso.containCheckbox.addEventListener('change', () => { lasso.control.setOptions({ intersect: lasso.intersectCheckbox?.checked }); }); } if (lasso.intersectCheckbox) { lasso.intersectCheckbox.addEventListener('change', () => { lasso.control.setOptions({ intersect: lasso.intersectCheckbox.checked }); }); } }, resetSelectedState() { map.eachLayer((layer) => { if (layer instanceof L.Marker && !(layer instanceof L.MarkerCluster)) { layer.setIcon(new L.Icon.Default()); } else if (layer instanceof L.Path) { layer.setStyle({ color: '#3388ff' }); } }); if (AppState.lasso.resultDisplay) { AppState.lasso.resultDisplay.innerHTML = ''; } }, setSelectedLayers(layers) { this.resetSelectedState(); const lassoed = { Lassoed: {} }; layers.forEach((layer) => { lassoed.Lassoed[layer.userid] = layer.userid; if (layer instanceof L.Marker && !(layer instanceof L.MarkerCluster)) { layer.setIcon(new L.Icon.Default({ className: 'selected' })); } else if (layer instanceof L.Path) { layer.setStyle({ color: '#ff4620' }); } }); if (AppState.lasso.resultDisplay) { AppState.lasso.resultDisplay.innerHTML = layers.length ? `Selected ${layers.length} layers` : ''; } $.post(CONFIG.urls.lasso, lassoed).done((data) => { if (AppState.lasso.resultDisplay) { AppState.lasso.resultDisplay.innerHTML = data; } if (typeof DropDownUrlinit === 'function') { DropDownUrlinit(); } }); }, }; // ============================================================================= // EVENT HANDLERS // ============================================================================= const Events = { initMove() { if (typeof maprealm !== 'undefined' && maprealm === 'agent') { map.on('move', () => { if (typeof gmapready !== 'undefined' && gmapready && typeof gmapsetbounds === 'function') { const { lat, lng } = map.getCenter(); const zoom = map.getZoom(); gmapsetbounds(lat, lng, zoom); } }); } }, initClick() { if (typeof maprealm !== 'undefined' && maprealm === 'agent') { map.on('click', this.handleClick); } }, handleClick(e) { const now = Date.now(); // Prevent click right after lasso finishes if (AppState.lasso.finished && (now - AppState.lasso.finished) < 3000) { return false; } const theoverlays = {}; AppState.activeLayers.forEach((layer, i) => { theoverlays[i] = layer; }); const { latlng } = e; const popup = L.popup() .setLatLng(latlng) .setContent('loading') .openOn(map); let url = `${CONFIG.urls.query}&latlan=${latlng.toString()}`; if (AppState.settings.querykey) { url += `&key=${AppState.settings.querykey}`; } const bounds = map.getBounds(); url += `&overlays=${JSON.stringify(theoverlays)}`; url += `&maprealm=${maprealm}`; url += `&bounds=${bounds.getWest()},${bounds.getSouth()},${bounds.getEast()},${bounds.getNorth()}`; $.get(url).done((data) => { popup.setContent(data); popup.update(); }); }, }; // ============================================================================= // MENU // ============================================================================= const Menu = { init() { let menuUrl = CONFIG.urls.menu; if (typeof mapid !== 'undefined' && mapid) { menuUrl += `&mapid=${mapid}`; } if (typeof maprealm !== 'undefined' && maprealm) { menuUrl += `&maprealm=${maprealm}`; } $.get(menuUrl).done((data) => { L.control.slideMenu(data, { position: 'bottomright', menuposition: 'bottomright' }).addTo(map); }); // Locate control L.control.locate({ setView: 'untilPanOrZoom', keepCurrentZoomLevel: true, }).addTo(map); // Fullscreen control map.addControl(new L.Control.Fullscreen()); }, }; // ============================================================================= // SETTINGS // ============================================================================= const Settings = { read() { if (typeof readsettings === 'undefined' || readsettings !== 'true') { LayerControl.add(); return; } const settingsUrl = CONFIG.urls.settingsFile(mapid); $.getJSON(settingsUrl, (settingsData) => { AppState.settings = settingsData; AppState.layers.settings.overlays = []; this.initPlugins(); this.initMarkers(); this.initGeoJsonLayers(); LayerControl.add(); Watermark.init(); }).fail((err) => { console.error('Failed to load settings:', err); LayerControl.add(); }); }, initPlugins() { const { settings } = AppState; if (settings.leafletplugin?.lasso) { Lasso.init(); } }, initMarkers() { const { settings } = AppState; if (!settings.markers) return; for (const layerName in settings.markers) { const markerGroup = settings.nocluster ? new L.featureGroup() : L.markerClusterGroup(); for (const id in settings.markers[layerName]) { const markerData = settings.markers[layerName][id]; const markerOptions = this.buildMarkerOptions(markerData); const marker = L.marker( [markerData.lat, markerData.lon], markerOptions ); if (markerData.draggable) { marker.on('dragend', () => { const latlonInput = document.getElementById('latlonlocation'); if (latlonInput) { latlonInput.value = `${marker.getLatLng().lat},${marker.getLatLng().lng}`; } }); } if (markerData.bindpopup) { marker.bindPopup(markerData.bindpopup); } marker.userid = id; marker.username = layerName; marker.addTo(markerGroup); } AppState.layers.settings.overlays[layerName] = markerGroup; if (!settings.layers?.geojson) { map.fitBounds(markerGroup.getBounds()); } } }, buildMarkerOptions(markerData) { const options = {}; if (markerData.icon) { options.icon = L.icon({ iconUrl: markerData.icon, iconSize: CONFIG.icons.defaultSize, iconAnchor: CONFIG.icons.defaultAnchor, }); } if (markerData.numberlabel) { options.icon = new L.AwesomeNumberMarkers({ number: markerData.numberlabel, markerColor: 'blue', }); } if (markerData.draggable) { options.draggable = markerData.draggable; } return options; }, initGeoJsonLayers() { const { settings } = AppState; if (!settings.layers?.geojson) return; settings.layers.geojson.forEach((layerConfig, index) => { const geojsonLayer = new L.GeoJSON.AJAX(layerConfig.url, { onEachFeature: GeoJsonUtils.onEachFeature, }); if (index === 0) { // Primary layer - fit bounds and bring to front geojsonLayer.on('data:loaded', function() { map.fitBounds(this.getBounds()); if (settings.zoom) { map.setZoom(settings.zoom); } geojsonLayer.addTo(map).bringToFront(); }); } else { // Secondary layers - bring to back geojsonLayer.on('data:loaded', function() { geojsonLayer.addTo(map).bringToBack(); }); } AppState.layers.settings.overlays[layerConfig.id] = geojsonLayer; }); }, }; // ============================================================================= // GEOJSON UTILITIES // ============================================================================= const GeoJsonUtils = { onEachFeature(feature, layer) { if (feature.properties?.popup) { layer.bindPopup(feature.properties.popup); } if (feature.style) { layer.setStyle(feature.style); } }, }; // ============================================================================= // LAYER CONTROL // ============================================================================= const LayerControl = { add() { const { settings, layers } = AppState; // Set base layer if (settings.basemap && layers.base[settings.basemap]) { map.addLayer(layers.base[settings.basemap]); } else if (layers.base['Default']) { layers.base['Default'].addTo(map); } // Build overlays object const overlays = { ...layers.overlays }; for (const name in layers.settings.overlays) { overlays[name] = layers.settings.overlays[name]; } const baseLayers = { ...layers.base }; // Load initial overlays if (settings.initoverlays) { for (const name in settings.initoverlays) { if (layers.overlays[name]) { layers.overlays[name].addTo(map); AppState.activeLayers.push(name); } } } // Load specified overlays or all settings layers if (settings.theoverlays) { for (const index in settings.theoverlays) { const layerName = settings.theoverlays[index]; if (overlays[layerName]) { overlays[layerName].addTo(map); } } } else { // Display all settings layers AppState.masterGroup = new L.featureGroup(); for (const name in layers.settings.overlays) { layers.settings.overlays[name].addTo(map); layers.settings.overlays[name].addTo(AppState.masterGroup); AppState.masterGroupAdded = true; overlays[name] = layers.settings.overlays[name]; } } // Set view if no geojson layers if (!settings.layers?.geojson) { this.setView(); } // Add layer control L.control.layers(baseLayers, overlays, { collapsed: true }).addTo(map); // Add scale L.control.scale().addTo(map); }, setView() { const { settings, masterGroup, masterGroupAdded } = AppState; console.log('Setting map view'); if (settings.view) { console.log('Using view setting'); map.setView( [settings.view.latitude, settings.view.longitude], settings.view.zoom ); } else if (settings.masterlayer && AppState.layers.settings.overlays[settings.masterlayer]) { console.log('Using master layer'); map.fitBounds(AppState.layers.settings.overlays[settings.masterlayer].getBounds()); } else if (masterGroupAdded && masterGroup) { console.log('Using master group'); map.fitBounds(masterGroup.getBounds()); masterGroup.bringToFront(); } else { console.log('No bounds method found'); } if (settings.zoom) { console.log('Applying zoom setting'); map.setZoom(settings.zoom); } }, }; // ============================================================================= // WATERMARK // ============================================================================= const Watermark = { init() { L.Control.Watermark = L.Control.extend({ onAdd() { const img = L.DomUtil.create('img'); img.src = AppState.settings.watermark || '/objects/maps/mapwatermarkredoak.png'; img.style.width = '50px'; return img; }, onRemove() {}, }); L.control.watermark = (opts) => new L.Control.Watermark(opts); L.control.watermark({ position: 'bottomleft' }).addTo(map); }, }; // ============================================================================= // EXTERNAL OVERLAYS // ============================================================================= const ExternalOverlays = { init() { this.initBaseLayers(); this.initOverlays(); }, initBaseLayers() { const base = AppState.layers.base; // Google layers const googleTypes = ['roadmap', 'satellite', 'hybrid']; const googleNames = ['RoadMap', 'Satelite', 'Hybrid']; googleTypes.forEach((type, i) => { base[googleNames[i]] = L.gridLayer.googleMutant({ maxZoom: 24, type, }); }); // Default is hybrid base['Default'] = L.gridLayer.googleMutant({ maxZoom: 24, type: 'hybrid', }); // ESRI Topo layers base['Topo'] = L.esri.tiledMapLayer({ maxZoom: 24, maxNativeZoom: 16, url: CONFIG.layers.esri.topo, }); base['TopoAlt'] = L.esri.tiledMapLayer({ maxZoom: 24, maxNativeZoom: 16, url: CONFIG.layers.esri.topoAlt, }); base['ShadedHillside'] = L.esri.tiledMapLayer({ maxZoom: 24, url: CONFIG.layers.esri.hillshade, }); // Agent-only layers if (typeof maprealm !== 'undefined' && maprealm === 'agent') { base['Leafless (slow)'] = L.esri.dynamicMapLayer({ url: CONFIG.layers.esri.leafless, }); } }, initOverlays() { const overlays = AppState.layers.overlays; // Flood layers overlays['FloodPUB'] = this.createDynamicLayer(CONFIG.layers.esri.floodPublic, [1], 0.4); overlays['FloodEX'] = this.createDynamicLayer(CONFIG.layers.esri.floodExpert, [4], 0.7); // Property layers overlays['TaxMaps'] = this.createDynamicLayer(CONFIG.layers.esri.taxMaps, [0, 1], 1, 'svg'); overlays['DLC'] = this.createDynamicLayer(CONFIG.layers.esri.delinquent, [0, 1], 0.4, 'svg'); // Boundary layers overlays['ForestandParks'] = this.createDynamicLayer(CONFIG.layers.esri.forestParks, [0, 3, 7], 0.5, 'svg'); overlays['Trails'] = this.createDynamicLayer(CONFIG.layers.esri.trails, [0, 1, 2, 3, 4, 5, 6, 7], 0.8); overlays['City Bounds'] = this.createDynamicLayer(CONFIG.layers.esri.politicalBoundary, [1], 0.4); overlays['Counties'] = this.createDynamicLayer(CONFIG.layers.esri.politicalBoundary, [0], 0.6); // Transportation overlays['WVDOT'] = this.createDynamicLayer(CONFIG.layers.esri.wvdot, null, 0.8); overlays['WVDOTown'] = this.createDynamicLayer(CONFIG.layers.esri.wvdotOwn, [39]); // Communication overlays['CELL SERVICE'] = L.esri.tiledMapLayer({ url: CONFIG.layers.esri.cellService, opacity: 0.6, }); overlays['INTERNET'] = this.createDynamicLayer(CONFIG.layers.esri.internet, [0, 1, 2, 3, 6], 0.8); // Terrain overlays['1ftTopo'] = L.esri.dynamicMapLayer({ url: CONFIG.layers.esri.contour1ft, useCors: true, }); // Utilities overlays['Water'] = this.createDynamicLayer(CONFIG.layers.esri.waterSewer, [0, 2, 4], 0.6, 'png', false); overlays['Sewer'] = this.createDynamicLayer(CONFIG.layers.esri.waterSewer, [1, 3, 5], 0.6, 'png', false); // Addresses overlays['Addresses'] = this.createAddressLayer(); // Oil and Gas overlays['OilAndGasWells'] = this.createOilGasLayer(); overlays['DEP OG'] = L.esri.dynamicMapLayer({ url: CONFIG.layers.esri.oilGasDep, layers: [0, 1, 2, 3, 4, 5, 6, 8, 9], useCors: false, minZoom: 12, }); // Coal layers this.initCoalLayers(); }, createDynamicLayer(url, layers = null, opacity = 1, format = 'png', useCors = true) { const options = { url, f: 'image', format, opacity, }; if (layers) { options.layers = layers; } if (!useCors) { options.useCors = false; } return L.esri.dynamicMapLayer(options); }, createAddressLayer() { return L.esri.featureLayer({ url: CONFIG.layers.esri.addresses, minZoom: 16, pointToLayer(geojson, latlng) { const props = geojson.properties; const address = `${props.FULLADDR}
${props.MUNICIPALITY}, ${props.State} ${props.Zip}`; const googleLink = `https://www.google.com/search?q=${encodeURIComponent( `${props.FULLADDR} ${props.MUNICIPALITY}, ${props.State} ${props.Zip}` )}`; const popup = `${address}
Google`; return L.marker(latlng, { icon: Icons.getIcon('wvumailboxIcon'), }).bindPopup(popup); }, }); }, createOilGasLayer() { return L.esri.featureLayer({ url: CONFIG.layers.esri.oilGasWells, useCors: false, minZoom: 12, pointToLayer(geojson, latlng) { const props = geojson.properties; const popup = ` API: ${props.api}
${props.RespParty}
${props.WellType}
${props.WellDepth}
Formation: ${props.Formation}
Status: ${props.WellStatus} `; const iconMap = { 'Never Drilled': 'neverdrilled', 'Active Well': 'activewell', 'Plugged': 'plugged', }; const iconName = iconMap[props.WellStatus] || 'allother'; return L.marker(latlng, { icon: Icons.getIcon(iconName), }).bindPopup(popup); }, }); }, initCoalLayers() { const overlays = AppState.layers.overlays; const coalConfig = CONFIG.layers.coal; const coalLayers = [ { name: 'Coal Eagle A', url: coalConfig.eagleA }, { name: 'Coal Eagle', url: coalConfig.eagle }, { name: 'Coal Low Pow', url: coalConfig.lowerPowellton }, { name: 'Coal Low War Eag', url: coalConfig.lowerWarEagle }, { name: 'Coal Pow', url: coalConfig.powellton }, { name: 'Coal Eagle Low', url: coalConfig.eagleLowerSplit }, { name: 'Coal lit eagle', url: coalConfig.littleEagle }, { name: 'Coal war eagle', url: coalConfig.middleWarEagle }, { name: 'Coal Geln Tunn', url: coalConfig.glenalumTunnel }, { name: 'Coal Peerless', url: coalConfig.peerless }, ]; coalLayers.forEach(({ name, url }) => { overlays[name] = this.createDynamicLayer(url, [0, 1, 2, 3, 4, 5], 0.4); }); }, }; // ============================================================================= // MARKER ROTATION EXTENSION // ============================================================================= const MarkerRotation = { init() { const proto_initIcon = L.Marker.prototype._initIcon; const proto_setPos = L.Marker.prototype._setPos; const oldIE = L.DomUtil.TRANSFORM === 'msTransform'; L.Marker.addInitHook(function() { const iconOptions = this.options.icon?.options; const iconAnchor = iconOptions?.iconAnchor; const anchorString = iconAnchor ? `${iconAnchor[0]}px ${iconAnchor[1]}px` : 'center bottom'; this.options.rotationOrigin = this.options.rotationOrigin || anchorString; this.options.rotationAngle = this.options.rotationAngle || 0; this.on('drag', (e) => e.target._applyRotation()); }); L.Marker.include({ _initIcon: proto_initIcon, _setPos(pos) { proto_setPos.call(this, pos); this._applyRotation(); }, _applyRotation() { if (this.options.rotationAngle) { this._icon.style[`${L.DomUtil.TRANSFORM}Origin`] = this.options.rotationOrigin; if (oldIE) { this._icon.style[L.DomUtil.TRANSFORM] = `rotate(${this.options.rotationAngle}deg)`; } else { this._icon.style[L.DomUtil.TRANSFORM] += ` rotateZ(${this.options.rotationAngle}deg)`; } } }, setRotationAngle(angle) { this.options.rotationAngle = angle; this.update(); return this; }, setRotationOrigin(origin) { this.options.rotationOrigin = origin; this.update(); return this; }, }); }, }; // ============================================================================= // EXPORT MAP // ============================================================================= const ExportMap = { export(event) { event.preventDefault(); const currentMap = { mapid: typeof mapid !== 'undefined' ? mapid : null, leaflet: { basemap: AppState.currentBaseLayer, theoverlays: {}, view: { latitude: map.getCenter().lat, longitude: map.getCenter().lng, zoom: map.getZoom(), }, }, height: $('#map').height(), width: $('#map').width(), }; AppState.activeLayers.forEach((layer, i) => { currentMap.leaflet.theoverlays[i] = layer; }); $('#currentmapfield').val(JSON.stringify(currentMap)); $('#exportform').submit(); }, }; // ============================================================================= // UTILITY FUNCTIONS // ============================================================================= const Utils = { setMapSize(size) { const [width, height] = size.split('x'); const mapDiv = $(`#${typeof mapdivid !== 'undefined' ? mapdivid : 'map'}`); mapDiv.height(height); mapDiv.width(width); map.invalidateSize(); }, }; // ============================================================================= // GLOBAL FUNCTION EXPORTS (for backward compatibility) // ============================================================================= // Export functions to global scope for external use window.exportmap = ExportMap.export.bind(ExportMap); window.leafletapp_setsize = Utils.setMapSize; window.leafletapp_onclickfun = Events.handleClick; // ============================================================================= // INITIALIZATION // ============================================================================= // Initialize marker rotation extension MarkerRotation.init(); // Initialize the map application MapApp.init();