Highlight possibly-conflicting (nearby) addresses in yellow

This commit is contained in:
zyphlar
2026-08-07 10:29:26 -07:00
parent acdce05ec1
commit a7d6db8177
4 changed files with 80 additions and 19 deletions
+50 -13
View File
@@ -312,8 +312,10 @@ function diffStyle(feature) {
}
const isRemoved = feature.properties && (feature.properties.removed === true || feature.properties.removed === 'True');
const isConflicted = !isRemoved && feature.properties &&
(feature.properties.conflict === true || feature.properties.conflict === 'True');
return {
color: isRemoved ? '#ff0000' : '#00ff00',
color: isRemoved ? '#ff0000' : (isConflicted ? '#c9a800' : '#00ff00'),
weight: 3,
opacity: 0.8
};
@@ -344,10 +346,12 @@ function diffMarkerStyle(feature) {
}
const isRemoved = feature.properties && (feature.properties.removed === true || feature.properties.removed === 'True');
const isConflicted = !isRemoved && feature.properties &&
(feature.properties.conflict === true || feature.properties.conflict === 'True');
return {
radius: 6,
fillColor: isRemoved ? '#ff0000' : '#00ff00',
color: isRemoved ? '#cc0000' : '#00cc00',
fillColor: isRemoved ? '#ff0000' : (isConflicted ? '#ffeb3b' : '#00ff00'),
color: isRemoved ? '#cc0000' : (isConflicted ? '#c9a800' : '#00cc00'),
weight: 1,
opacity: 0.8,
fillOpacity: 0.7
@@ -457,17 +461,20 @@ function shouldShowFeature(feature) {
if (isExcluded(feature)) return false;
const props = feature.properties || {};
const isRemoved = props.removed === true || props.removed === 'True';
const isConflicted = !isRemoved && (props.conflict === true || props.conflict === 'True');
const isService = props.highway === 'service' || props.highway === 'track';
const isUnclassified = props.highway === 'unclassified';
const showAdded = document.getElementById('showAdded').checked;
const showConflicted = document.getElementById('showConflicted').checked;
const showRemoved = document.getElementById('showRemoved').checked;
const hideService = document.getElementById('hideService').checked;
const hideUnclassified = document.getElementById('hideUnclassified').checked;
// Check removed/added filter
// Check removed/added/conflicted filter
if (isRemoved && !showRemoved) return false;
if (!isRemoved && !showAdded) return false;
if (isConflicted && !showConflicted) return false;
if (!isRemoved && !isConflicted && !showAdded) return false;
// Check service/track and unclassified filters
if (isService && hideService) return false;
@@ -809,7 +816,10 @@ function showMultiFeaturePopup(latlng) {
if (selectedFeatures.length === 1) {
// Single selection - show specific status
const isRemoved = selectedFeatures[0].properties && (selectedFeatures[0].properties.removed === true || selectedFeatures[0].properties.removed === 'True');
html += `<div style="margin-top: 8px;"><strong>Status:</strong> ${isRemoved ? 'Removed (Red)' : 'Added (Green)'}</div>`;
const isConflicted = !isRemoved && selectedFeatures[0].properties &&
(selectedFeatures[0].properties.conflict === true || selectedFeatures[0].properties.conflict === 'True');
const statusLabel = isRemoved ? 'Removed (Red)' : (isConflicted ? 'Possibly Conflicted (Yellow)' : 'Added (Green)');
html += `<div style="margin-top: 8px;"><strong>Status:</strong> ${statusLabel}</div>`;
if (!isRemoved && osmVertices.length > 0) {
html += connectivitySection(selectedFeatures[0]);
}
@@ -1491,6 +1501,10 @@ function computeRemovalPlan(removedFeat, acceptedAddedFeats, nodeRefCount) {
const osmNodes = props.osm_nodes;
const wayCoords = removedFeat.geometry && removedFeat.geometry.coordinates;
if (removedFeat.geometry && removedFeat.geometry.type === 'Point') {
return { type: 'deleteNode', osmId, osmVersion, description: `Delete node${osmId ? ' ' + osmId : ''}` };
}
const tags = {};
Object.entries(props).forEach(([k, v]) => {
if (!['removed', 'status', 'accepted', 'osm_id', 'osm_type', 'osm_nodes', 'osm_version', 'osm_node_versions'].includes(k) &&
@@ -1641,22 +1655,29 @@ function generateOsmXml(acceptedAdded, removalPlans, checkedPlanIndices) {
}
const newWays = [];
const newNodes = []; // standalone point features (e.g. addresses), not way vertices
const modifiedWays = [];
const splitTailWays = [];
const deleteWayIds = []; // [{id, version}]
const deleteNodeIds = new Map(); // id -> version
acceptedAdded.forEach(feat => {
const rawCoords = feat.geometry.coordinates;
const midCoords = (feat._snappedCoords || rawCoords).slice(1, -1);
const startId = allocEndpointId(rawCoords[0], feat._snapAEnabled !== false ? feat._snapA : null);
const endId = allocEndpointId(rawCoords[rawCoords.length - 1], feat._snapBEnabled !== false ? feat._snapB : null);
const ndRefs = [startId, ...midCoords.map(c => allocNodeId(c, null)), endId];
const tags = {};
Object.entries(feat.properties || {}).forEach(([k, v]) => {
if (!['removed', 'status', 'accepted', 'osm_id', 'osm_type', 'osm_nodes', 'osm_version', 'osm_node_versions'].includes(k) &&
v !== null && v !== undefined && v !== '') tags[k] = String(v);
});
if (feat.geometry.type === 'Point') {
newNodes.push({ id: nextNewId--, coord: feat.geometry.coordinates, tags });
return;
}
const rawCoords = feat.geometry.coordinates;
const midCoords = (feat._snappedCoords || rawCoords).slice(1, -1);
const startId = allocEndpointId(rawCoords[0], feat._snapAEnabled !== false ? feat._snapA : null);
const endId = allocEndpointId(rawCoords[rawCoords.length - 1], feat._snapBEnabled !== false ? feat._snapB : null);
const ndRefs = [startId, ...midCoords.map(c => allocNodeId(c, null)), endId];
newWays.push({ id: nextNewId--, ndRefs, tags });
});
@@ -1689,7 +1710,9 @@ function generateOsmXml(acceptedAdded, removalPlans, checkedPlanIndices) {
removalPlans.forEach((plan, i) => {
if (!checkedPlanIndices.has(i) || !plan.osmId) return;
if (plan.type === 'delete') {
if (plan.type === 'deleteNode') {
deleteNodeIds.set(plan.osmId, plan.osmVersion || 1);
} else if (plan.type === 'delete') {
deleteWayIds.push({ id: plan.osmId, version: plan.osmVersion || 1 });
if (plan.deleteNodeIds) plan.deleteNodeIds.forEach(nid => deleteNodeIds.set(nid, osmNodeVersionById.get(nid) || 1));
} else if (plan.type === 'split') {
@@ -1730,6 +1753,15 @@ function generateOsmXml(acceptedAdded, removalPlans, checkedPlanIndices) {
return x + ' </way>\n';
}
function nodeXml(n) {
let x = ` <node id="${n.id}" action="create" lat="${n.coord[1].toFixed(7)}" lon="${n.coord[0].toFixed(7)}"`;
const tagEntries = Object.entries(n.tags);
if (tagEntries.length === 0) return x + '/>\n';
x += '>\n';
tagEntries.forEach(([k, v]) => { x += ` <tag k="${esc(k)}" v="${esc(v)}"/>\n`; });
return x + ' </node>\n';
}
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<osm version="0.6" generator="osm-import-tools">\n\n';
referencedExisting.forEach(({ coord, version }, nid) => {
@@ -1740,7 +1772,8 @@ function generateOsmXml(acceptedAdded, removalPlans, checkedPlanIndices) {
const [lon, lat] = key.split(',').map(Number);
xml += ` <node id="${id}" action="create" lat="${lat.toFixed(7)}" lon="${lon.toFixed(7)}"/>\n`;
});
if (referencedExisting.size > 0 || [...nodeRegistry.values()].some(id => id < 0)) xml += '\n';
newNodes.forEach(n => { xml += nodeXml(n); });
if (referencedExisting.size > 0 || [...nodeRegistry.values()].some(id => id < 0) || newNodes.length > 0) xml += '\n';
newWays.forEach(w => { xml += wayXml(w, 'create'); });
modifiedWays.forEach(w => { xml += wayXml(w, 'modify'); });
@@ -2113,6 +2146,10 @@ document.addEventListener('DOMContentLoaded', function() {
createDiffLayer();
});
document.getElementById('showConflicted').addEventListener('change', function() {
createDiffLayer();
});
document.getElementById('showRemoved').addEventListener('change', function() {
createDiffLayer();
});