/**
* 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();