Get web server going

This commit is contained in:
2025-12-04 05:23:33 -08:00
parent 6f5cf83a56
commit 2254d293a0
14 changed files with 1916 additions and 48 deletions
+248 -22
View File
@@ -11,6 +11,7 @@ let selectedLayer = null;
let acceptedFeatures = new Set();
let rejectedFeatures = new Set();
let featurePopup = null;
let layerOrder = ['diff', 'osm', 'county']; // Default layer order (top to bottom)
// Initialize map
function initMap() {
@@ -21,6 +22,16 @@ function initMap() {
subdomains: 'abcd',
maxZoom: 20
}).addTo(map);
// Create custom panes for layer ordering
map.createPane('osmPane');
map.createPane('diffPane');
map.createPane('countyPane');
// Set initial z-indices for panes
map.getPane('osmPane').style.zIndex = 400;
map.getPane('diffPane').style.zIndex = 401;
map.getPane('countyPane').style.zIndex = 402;
}
// Calculate bounds for all loaded layers
@@ -69,7 +80,7 @@ function calculateBounds() {
// Style functions
function osmStyle(feature) {
return {
color: '#8B4513',
color: '#4a4a4a',
weight: 3,
opacity: 0.7
};
@@ -87,7 +98,7 @@ function diffStyle(feature) {
if (rejectedFeatures.has(feature)) {
return {
color: '#4a4a4a',
color: '#ff8c00',
weight: 3,
opacity: 0.8
};
@@ -95,7 +106,7 @@ function diffStyle(feature) {
const isRemoved = feature.properties && (feature.properties.removed === true || feature.properties.removed === 'True');
return {
color: isRemoved ? '#dc3545' : '#28a745',
color: isRemoved ? '#ff0000' : '#00ff00',
weight: 3,
opacity: 0.8
};
@@ -103,12 +114,22 @@ function diffStyle(feature) {
function countyStyle(feature) {
return {
color: '#800080',
color: '#ff00ff',
weight: 3,
opacity: 0.7
opacity: 0.8
};
}
// Filter function for OSM features
function shouldShowOsmFeature(feature) {
const props = feature.properties || {};
const isService = props.highway === 'service';
const hideService = document.getElementById('hideService').checked;
if (isService && hideService) return false;
return true;
}
// Create layer for OSM data
function createOsmLayer() {
if (osmLayer) {
@@ -118,8 +139,53 @@ function createOsmLayer() {
if (!osmData) return;
osmLayer = L.geoJSON(osmData, {
style: osmStyle
style: osmStyle,
filter: shouldShowOsmFeature,
pane: 'osmPane',
onEachFeature: function(feature, layer) {
layer.on('click', function(e) {
L.DomEvent.stopPropagation(e);
selectFeature(feature, layer, e, 'osm');
});
layer.on('mouseover', function(e) {
if (selectedLayer !== layer) {
layer.setStyle({
weight: 5,
opacity: 1
});
}
});
layer.on('mouseout', function(e) {
if (selectedLayer !== layer) {
layer.setStyle(osmStyle(feature));
}
});
}
}).addTo(map);
updateLayerZIndex();
}
// Filter function for diff features
function shouldShowFeature(feature) {
const props = feature.properties || {};
const isRemoved = props.removed === true || props.removed === 'True';
const isService = props.highway === 'service';
const showAdded = document.getElementById('showAdded').checked;
const showRemoved = document.getElementById('showRemoved').checked;
const hideService = document.getElementById('hideService').checked;
// Check removed/added filter
if (isRemoved && !showRemoved) return false;
if (!isRemoved && !showAdded) return false;
// Check service filter
if (isService && hideService) return false;
return true;
}
// Create layer for diff data with click handlers
@@ -132,10 +198,12 @@ function createDiffLayer() {
diffLayer = L.geoJSON(diffData, {
style: diffStyle,
filter: shouldShowFeature,
pane: 'diffPane',
onEachFeature: function(feature, layer) {
layer.on('click', function(e) {
L.DomEvent.stopPropagation(e);
selectFeature(feature, layer, e);
selectFeature(feature, layer, e, 'diff');
});
layer.on('mouseover', function(e) {
@@ -154,6 +222,18 @@ function createDiffLayer() {
});
}
}).addTo(map);
updateLayerZIndex();
}
// Filter function for county features
function shouldShowCountyFeature(feature) {
const props = feature.properties || {};
const isService = props.highway === 'service';
const hideService = document.getElementById('hideService').checked;
if (isService && hideService) return false;
return true;
}
// Create layer for county data
@@ -165,24 +245,53 @@ function createCountyLayer() {
if (!countyData) return;
countyLayer = L.geoJSON(countyData, {
style: countyStyle
style: countyStyle,
filter: shouldShowCountyFeature,
pane: 'countyPane',
onEachFeature: function(feature, layer) {
layer.on('click', function(e) {
L.DomEvent.stopPropagation(e);
selectFeature(feature, layer, e, 'county');
});
layer.on('mouseover', function(e) {
if (selectedLayer !== layer) {
layer.setStyle({
weight: 5,
opacity: 1
});
}
});
layer.on('mouseout', function(e) {
if (selectedLayer !== layer) {
layer.setStyle(countyStyle(feature));
}
});
}
});
// County layer is hidden by default
if (document.getElementById('countyToggle').checked) {
countyLayer.addTo(map);
}
updateLayerZIndex();
}
// Select a feature from diff layer
function selectFeature(feature, layer, e) {
// Select a feature from any layer
function selectFeature(feature, layer, e, layerType = 'diff') {
// Deselect previous feature
if (selectedLayer) {
selectedLayer.setStyle(diffStyle(selectedLayer.feature));
// Get the appropriate style function based on previous layer type
const styleFunc = selectedLayer._layerType === 'diff' ? diffStyle :
selectedLayer._layerType === 'osm' ? osmStyle : countyStyle;
selectedLayer.setStyle(styleFunc(selectedLayer.feature));
}
selectedFeature = feature;
selectedLayer = layer;
selectedLayer._layerType = layerType; // Store layer type for later
layer.setStyle({
weight: 6,
opacity: 1,
@@ -196,12 +305,29 @@ function selectFeature(feature, layer, e) {
const isRejected = rejectedFeatures.has(feature);
let html = '<div style="font-size: 12px; max-height: 400px; overflow-y: auto;">';
html += `<div style="margin-bottom: 8px;"><strong>Status:</strong> ${isRemoved ? 'Removed' : 'Added/Modified'}</div>`;
// Display all non-null properties
// Show layer type
html += `<div style="margin-bottom: 8px;"><strong>Layer:</strong> ${layerType.toUpperCase()}</div>`;
// Only show status for diff layer
if (layerType === 'diff') {
html += `<div style="margin-bottom: 8px;"><strong>Status:</strong> ${isRemoved ? 'Removed' : 'Added/Modified'}</div>`;
}
// Display all non-null properties with custom ordering
const displayProps = Object.entries(props)
.filter(([key, value]) => value !== null && value !== undefined && key !== 'removed')
.sort(([a], [b]) => a.localeCompare(b));
.sort(([a], [b]) => {
// Priority order: name, highway, then alphabetical
const priorityOrder = { 'name': 0, 'highway': 1 };
const aPriority = priorityOrder[a] ?? 999;
const bPriority = priorityOrder[b] ?? 999;
if (aPriority !== bPriority) {
return aPriority - bPriority;
}
return a.localeCompare(b);
});
if (displayProps.length > 0) {
html += '<div style="font-size: 11px;">';
@@ -211,16 +337,20 @@ function selectFeature(feature, layer, e) {
html += '</div>';
}
if (isAccepted) {
html += '<div style="margin-top: 8px; color: #007bff; font-weight: bold;">✓ Accepted</div>';
} else if (isRejected) {
html += '<div style="margin-top: 8px; color: #4a4a4a; font-weight: bold;">✗ Rejected</div>';
// Only show accept/reject for diff layer
if (layerType === 'diff') {
if (isAccepted) {
html += '<div style="margin-top: 8px; color: #007bff; font-weight: bold;">✓ Accepted</div>';
} else if (isRejected) {
html += '<div style="margin-top: 8px; color: #4a4a4a; font-weight: bold;">✗ Rejected</div>';
}
html += '<div style="margin-top: 10px; display: flex; gap: 5px;">';
html += '<button onclick="acceptFeature()" style="flex: 1; padding: 5px; background: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer;">Accept</button>';
html += '<button onclick="rejectFeature()" style="flex: 1; padding: 5px; background: #6c757d; color: white; border: none; border-radius: 3px; cursor: pointer;">Reject</button>';
html += '</div>';
}
html += '<div style="margin-top: 10px; display: flex; gap: 5px;">';
html += '<button onclick="acceptFeature()" style="flex: 1; padding: 5px; background: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer;">Accept</button>';
html += '<button onclick="rejectFeature()" style="flex: 1; padding: 5px; background: #6c757d; color: white; border: none; border-radius: 3px; cursor: pointer;">Reject</button>';
html += '</div>';
html += '</div>';
// Remove old popup if exists
@@ -301,6 +431,10 @@ function rejectFeature() {
showStatus(`${acceptedFeatures.size} accepted, ${rejectedFeatures.size} rejected`, 'success');
}
// Expose functions globally for onclick handlers
window.acceptFeature = acceptFeature;
window.rejectFeature = rejectFeature;
// Update save button state
function updateSaveButton() {
document.getElementById('saveButton').disabled =
@@ -417,6 +551,24 @@ function showStatus(message, type) {
}, 3000);
}
// Update pane z-index based on order
function updateLayerZIndex() {
const panes = {
'osm': 'osmPane',
'diff': 'diffPane',
'county': 'countyPane'
};
// Reverse index so first item in list is on top
layerOrder.forEach((layerName, index) => {
const paneName = panes[layerName];
const pane = map.getPane(paneName);
if (pane) {
pane.style.zIndex = 400 + (layerOrder.length - 1 - index);
}
});
}
// Toggle layer visibility
function toggleLayer(layerId, layer) {
const checkbox = document.getElementById(layerId);
@@ -424,6 +576,7 @@ function toggleLayer(layerId, layer) {
if (checkbox.checked && layer) {
if (!map.hasLayer(layer)) {
map.addLayer(layer);
updateLayerZIndex();
}
} else if (layer) {
if (map.hasLayer(layer)) {
@@ -449,9 +602,82 @@ document.addEventListener('DOMContentLoaded', function() {
toggleLayer('countyToggle', countyLayer);
});
// Diff filter toggles
document.getElementById('showAdded').addEventListener('change', function() {
createDiffLayer();
});
document.getElementById('showRemoved').addEventListener('change', function() {
createDiffLayer();
});
document.getElementById('hideService').addEventListener('change', function() {
createDiffLayer();
createOsmLayer();
createCountyLayer();
});
// Load button
document.getElementById('loadButton').addEventListener('click', loadFiles);
// Save button
document.getElementById('saveButton').addEventListener('click', saveAcceptedItems);
// Drag and drop for layer reordering
const layerList = document.getElementById('layerList');
const layerItems = layerList.querySelectorAll('.layer-item');
let draggedElement = null;
layerItems.forEach(item => {
item.addEventListener('dragstart', function(e) {
draggedElement = this;
this.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
});
item.addEventListener('dragend', function(e) {
this.classList.remove('dragging');
draggedElement = null;
});
item.addEventListener('dragover', function(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
if (this === draggedElement) return;
const afterElement = getDragAfterElement(layerList, e.clientY);
if (afterElement == null) {
layerList.appendChild(draggedElement);
} else {
layerList.insertBefore(draggedElement, afterElement);
}
});
item.addEventListener('drop', function(e) {
e.preventDefault();
// Update layer order based on new DOM order
layerOrder = Array.from(layerList.querySelectorAll('.layer-item'))
.map(item => item.dataset.layer);
updateLayerZIndex();
});
});
function getDragAfterElement(container, y) {
const draggableElements = [...container.querySelectorAll('.layer-item:not(.dragging)')];
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
}, { offset: Number.NEGATIVE_INFINITY }).element;
}
});