Add exclusions feature: map popup Exclude button, exclusions page, server API
This commit is contained in:
@@ -13,6 +13,9 @@ let rejectedFeatures = new Set();
|
||||
let featurePopup = null;
|
||||
let layerOrder = ['diff', 'osm', 'county']; // Default layer order (top to bottom)
|
||||
|
||||
// Exclusions loaded from server
|
||||
let exclusions = [];
|
||||
|
||||
// Drag selection state
|
||||
let isDragging = false;
|
||||
let dragStartPoint = null;
|
||||
@@ -425,8 +428,19 @@ function createOsmLayer() {
|
||||
updateLayerZIndex();
|
||||
}
|
||||
|
||||
// Check if a feature matches any server-side exclusion
|
||||
function isExcluded(feature) {
|
||||
if (!exclusions.length) return false;
|
||||
const props = feature.properties || {};
|
||||
return exclusions.some(excl => {
|
||||
const val = props[excl.field];
|
||||
return val !== undefined && val !== null && String(val) === String(excl.value);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter function for diff features
|
||||
function shouldShowFeature(feature) {
|
||||
if (isExcluded(feature)) return false;
|
||||
const props = feature.properties || {};
|
||||
const isRemoved = props.removed === true || props.removed === 'True';
|
||||
const isService = props.highway === 'service' || props.highway === 'track';
|
||||
@@ -803,6 +817,9 @@ function showMultiFeaturePopup(latlng) {
|
||||
html += `<button onclick="acceptAllFeatures()" style="flex: 1; padding: 5px; background: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer;">Accept${buttonText}</button>`;
|
||||
html += `<button onclick="rejectAllFeatures()" style="flex: 1; padding: 5px; background: #6c757d; color: white; border: none; border-radius: 3px; cursor: pointer;">Reject${buttonText}</button>`;
|
||||
html += '</div>';
|
||||
html += '<div style="margin-top: 5px;">';
|
||||
html += `<button onclick="excludeSelectedFeatures()" style="width: 100%; padding: 5px; background: #fd7e14; color: white; border: none; border-radius: 3px; cursor: pointer;">Exclude${buttonText} from future diffs</button>`;
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -899,9 +916,78 @@ function rejectAllFeatures() {
|
||||
showStatus(`${acceptedFeatures.size} accepted, ${rejectedFeatures.size} rejected`, 'success');
|
||||
}
|
||||
|
||||
// Exclude selected features by name/street and refresh diff layer
|
||||
async function excludeSelectedFeatures() {
|
||||
if (selectedFeatures.length === 0) return;
|
||||
|
||||
const toExclude = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const feature of selectedFeatures) {
|
||||
const props = feature.properties || {};
|
||||
let field, value;
|
||||
|
||||
if (props['name']) {
|
||||
field = 'name';
|
||||
value = props['name'];
|
||||
} else if (props['addr:street']) {
|
||||
field = 'addr:street';
|
||||
value = props['addr:street'];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = `${field}\0${value}`;
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
toExclude.push({ field, value });
|
||||
}
|
||||
}
|
||||
|
||||
if (toExclude.length === 0) {
|
||||
showStatus('No name or street property found to exclude by.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/exclusions/add', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ exclusions: toExclude })
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (data.error) {
|
||||
if (resp.status === 401) {
|
||||
showStatus('Not authenticated — please log in to exclude features.', 'error');
|
||||
} else {
|
||||
showStatus(`Error: ${data.error}`, 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
showStatus(`Error: ${err.message}`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update local list and refresh
|
||||
for (const e of toExclude) {
|
||||
if (!exclusions.some(ex => ex.field === e.field && ex.value === e.value)) {
|
||||
exclusions.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (featurePopup) map.closePopup(featurePopup);
|
||||
clearSelection();
|
||||
createDiffLayer();
|
||||
|
||||
const names = toExclude.map(e => e.value).join(', ');
|
||||
showStatus(`Excluded: ${names}`, 'success');
|
||||
}
|
||||
|
||||
// Expose functions globally for onclick handlers
|
||||
window.acceptAllFeatures = acceptAllFeatures;
|
||||
window.rejectAllFeatures = rejectAllFeatures;
|
||||
window.excludeSelectedFeatures = excludeSelectedFeatures;
|
||||
|
||||
// Update save button state
|
||||
function updateSaveButton() {
|
||||
@@ -931,6 +1017,15 @@ async function loadFiles() {
|
||||
loadedCounty = county;
|
||||
loadedDataType = dataType;
|
||||
|
||||
// Load exclusions
|
||||
try {
|
||||
const exclResp = await fetch('/api/exclusions');
|
||||
if (exclResp.ok) {
|
||||
const exclData = await exclResp.json();
|
||||
exclusions = exclData.exclusions || [];
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
// Build file paths based on county and data type
|
||||
let osmFile, diffFile, countyFile, diffAddedFile, diffRemovedFile;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user