Add Osmose issues UI and filtering

Filters out errors not present in the data .json file to enable
selective support since Osmose has a wide variety of errors which may be
too advanced for iD.

Also added processing for the elements associated with an error for
forced visibility and highlighting.
This commit is contained in:
SilentSpike
2019-12-07 19:07:53 +00:00
parent e11d97b38c
commit 09e7b23665
9 changed files with 468 additions and 22 deletions
+4
View File
@@ -159,6 +159,10 @@
color: #FFFFFF;
}
.osmose.category-structure {
color: #F9C700;
}
/* Custom Map Data (geojson, gpx, kml, vector tile) */
.layer-mapdata {
pointer-events: none;
+6
View File
@@ -827,6 +827,12 @@ en:
cannot_zoom: "Cannot zoom out further in current mode."
full_screen: Toggle Full Screen
QA:
osmose:
title: Osmose
error_types:
1070-1:
title: 'Highway intersecting building'
description: '{1} intersects with {0}.'
improveOSM:
title: ImproveOSM Detection
geometry_types:
+8
View File
@@ -32,6 +32,14 @@
"errorTypes": {
}
},
"osmose": {
"errorTypes": {
"1070-1": {
"icon": "maki-home",
"category": "structure"
}
}
}
}
}
+9
View File
@@ -1031,6 +1031,15 @@
"cannot_zoom": "Cannot zoom out further in current mode.",
"full_screen": "Toggle Full Screen",
"QA": {
"osmose": {
"title": "Osmose",
"error_types": {
"1070-1": {
"title": "Highway intersecting building",
"description": "{1} intersects with {0}."
}
}
},
"improveOSM": {
"title": "ImproveOSM Detection",
"geometry_types": {
+27 -19
View File
@@ -7,7 +7,7 @@ import { geoExtent, geoVecAdd, geoVecScale } from '../geo';
import { qaError } from '../osm';
import { t } from '../util/locale';
import { utilRebind, utilTiler, utilQsString } from '../util';
import { services } from '../../data/qa_errors.json';
var tiler = utilTiler();
var dispatch = d3_dispatch('loaded');
@@ -130,29 +130,37 @@ export default {
if (data.issues) {
data.issues.forEach(function(issue) {
// Elements provided as string, separated by _ character
var elems = issue.elems.split('_');
var elems = issue.elems.split('_').map(function(i) {
return i.substring(0,1) + i.replace(/node|way|relation/, '')
});
var loc = [issue.lon, issue.lat];
// Item is the type of error, w/ class tells us the sub-type
var type = [issue.item, issue.classs].join('-');
loc = preventCoincident(loc, true);
// Filter out unsupported error types (some are too specific or advanced)
if (services.osmose.errorTypes[type]) {
loc = preventCoincident(loc, true);
var d = new qaError({
// Info required for every error
loc: loc,
service: 'osmose',
error_type: [issue.item, issue.classs].join('-'),
// Extra details needed for this service
identifier: issue.id, // this is used to post changes to the error
elems: elems
//object_id: elems[0],
//object_type: elems[0].substring(0,1)
});
var d = new qaError({
// Info required for every error
loc: loc,
service: 'osmose',
error_type: type,
// Extra details needed for this service
identifier: issue.id, // this is used to post changes to the error
elems: elems,
object_id: elems.length ? elems[0].substring(1) : '',
object_type: elems.length ? elems[0].substring(0,1) : ''
});
// Variables used in the description
d.replacements = {
};
// Variables used in the description
d.replacements = elems.map(function(i) {
return linkEntity(i)
});
_erCache.data[d.id] = d;
_erCache.rtree.insert(encodeErrorRtree(d));
_erCache.data[d.id] = d;
_erCache.rtree.insert(encodeErrorRtree(d));
}
});
}
})
+135
View File
@@ -0,0 +1,135 @@
import {
event as d3_event,
select as d3_select
} from 'd3-selection';
import { dataEn } from '../../data';
import { modeSelect } from '../modes/select';
import { t } from '../util/locale';
import { utilDisplayName, utilEntityOrMemberSelector, utilEntityRoot } from '../util';
export function uiOsmoseDetails(context) {
var _error;
function errorDetail(d) {
var unknown = t('inspector.unknown');
if (!d) return unknown;
if (d.desc) return d.desc;
var errorType = d.error_type;
var et = dataEn.QA.osmose.error_types[errorType];
var detail;
if (et && et.description) {
detail = t('QA.osmose.error_types.' + errorType + '.description', d.replacements);
} else {
detail = unknown;
}
return detail;
}
function osmoseDetails(selection) {
var details = selection.selectAll('.error-details')
.data(
(_error ? [_error] : []),
function(d) { return d.id + '-' + (d.status || 0); }
);
details.exit()
.remove();
var detailsEnter = details.enter()
.append('div')
.attr('class', 'error-details error-details-container');
// description
var descriptionEnter = detailsEnter
.append('div')
.attr('class', 'error-details-description');
descriptionEnter
.append('h4')
.text(function() { return t('QA.keepRight.detail_description'); });
descriptionEnter
.append('div')
.attr('class', 'error-details-description-text')
.html(errorDetail);
// If there are entity links in the error message..
var relatedEntities = _error.elems;
descriptionEnter.selectAll('.error_entity_link, .error_object_link')
.each(function() {
var link = d3_select(this);
var isObjectLink = link.classed('error_object_link');
var entityID = isObjectLink ?
(_error.object_type + _error.object_id)
: this.textContent;
var entity = context.hasEntity(entityID);
// Add click handler
link
.on('mouseenter', function() {
context.surface().selectAll(utilEntityOrMemberSelector([entityID], context.graph()))
.classed('hover', true);
})
.on('mouseleave', function() {
context.surface().selectAll('.hover')
.classed('hover', false);
})
.on('click', function() {
d3_event.preventDefault();
var osmlayer = context.layers().layer('osm');
if (!osmlayer.enabled()) {
osmlayer.enabled(true);
}
context.map().centerZoom(_error.loc, 20);
if (entity) {
context.enter(modeSelect(context, [entityID]));
} else {
context.loadEntity(entityID, function() {
context.enter(modeSelect(context, [entityID]));
});
}
});
// Replace with friendly name if possible
// (The entity may not yet be loaded into the graph)
if (entity) {
var name = utilDisplayName(entity); // try to use common name
if (!name && !isObjectLink) {
var preset = context.presets().match(entity, context.graph());
name = preset && !preset.isFallback() && preset.name(); // fallback to preset name
}
if (name) {
this.innerText = name;
}
}
});
// Don't hide entities related to this error - #5880
context.features().forceVisible(relatedEntities);
context.map().pan([0,0]); // trigger a redraw
}
osmoseDetails.error = function(val) {
if (!arguments.length) return _error;
_error = val;
return osmoseDetails;
};
return osmoseDetails;
}
+170
View File
@@ -0,0 +1,170 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { t } from '../util/locale';
import { services } from '../services';
import { modeBrowse } from '../modes/browse';
import { svgIcon } from '../svg/icon';
import { uiOsmoseDetails } from './osmose_details';
import { uiOsmoseHeader } from './osmose_header';
import { uiQuickLinks } from './quick_links';
import { uiTooltipHtml } from './tooltipHtml';
import { utilRebind } from '../util';
export function uiOsmoseEditor(context) {
var dispatch = d3_dispatch('change');
var errorDetails = uiOsmoseDetails(context);
var errorHeader = uiOsmoseHeader(context);
var quickLinks = uiQuickLinks();
var _error;
function osmoseEditor(selection) {
// quick links
var choices = [{
id: 'zoom_to',
label: 'inspector.zoom_to.title',
tooltip: function() {
return uiTooltipHtml(t('inspector.zoom_to.tooltip_issue'), t('inspector.zoom_to.key'));
},
click: function zoomTo() {
context.mode().zoomToSelected();
}
}];
var header = selection.selectAll('.header')
.data([0]);
var headerEnter = header.enter()
.append('div')
.attr('class', 'header fillL');
headerEnter
.append('button')
.attr('class', 'fr error-editor-close')
.on('click', function() {
context.enter(modeBrowse(context));
})
.call(svgIcon('#iD-icon-close'));
headerEnter
.append('h3')
.text(t('QA.osmose.title'));
var body = selection.selectAll('.body')
.data([0]);
body = body.enter()
.append('div')
.attr('class', 'body')
.merge(body);
var editor = body.selectAll('.error-editor')
.data([0]);
editor.enter()
.append('div')
.attr('class', 'modal-section error-editor')
.merge(editor)
.call(errorHeader.error(_error))
.call(quickLinks.choices(choices))
.call(errorDetails.error(_error))
.call(osmoseSaveSection);
}
function osmoseSaveSection(selection) {
var isSelected = (_error && _error.id === context.selectedErrorID());
var isShown = (_error && isSelected);
var saveSection = selection.selectAll('.error-save')
.data(
(isShown ? [_error] : []),
function(d) { return d.id + '-' + (d.status || 0); }
);
// exit
saveSection.exit()
.remove();
// enter
var saveSectionEnter = saveSection.enter()
.append('div')
.attr('class', 'error-save save-section cf');
// update
saveSection = saveSectionEnter
.merge(saveSection)
.call(errorSaveButtons);
}
function errorSaveButtons(selection) {
var isSelected = (_error && _error.id === context.selectedErrorID());
var buttonSection = selection.selectAll('.buttons')
.data((isSelected ? [_error] : []), function(d) { return d.status + d.id; });
// exit
buttonSection.exit()
.remove();
// enter
var buttonEnter = buttonSection.enter()
.append('div')
.attr('class', 'buttons');
buttonEnter
.append('button')
.attr('class', 'button close-button action');
buttonEnter
.append('button')
.attr('class', 'button ignore-button action');
// update
buttonSection = buttonSection
.merge(buttonEnter);
buttonSection.select('.close-button')
.text(function(d) {
return t('QA.keepRight.close');
})
.on('click.close', function(d) {
this.blur(); // avoid keeping focus on the button - #4641
var errorService = services.osmose;
if (errorService) {
d.newStatus = '/done';
errorService.postUpdate(d, function(err, error) {
dispatch.call('change', error);
});
}
});
buttonSection.select('.ignore-button')
.text(function(d) {
return t('QA.keepRight.ignore');
})
.on('click.ignore', function(d) {
this.blur(); // avoid keeping focus on the button - #4641
var errorService = services.osmose;
if (errorService) {
d.newStatus = '/false';
errorService.postUpdate(d, function(err, error) {
dispatch.call('change', error);
});
}
});
}
osmoseEditor.error = function(val) {
if (!arguments.length) return _error;
_error = val;
return osmoseEditor;
};
return utilRebind(osmoseEditor, dispatch, 'on');
}
+97
View File
@@ -0,0 +1,97 @@
import { dataEn } from '../../data';
import { t } from '../util/locale';
export function uiOsmoseHeader() {
var _error;
function errorTitle(d) {
var unknown = t('inspector.unknown');
if (!d) return unknown;
var errorType = d.error_type;
var et = dataEn.QA.osmose.error_types[errorType];
if (et && et.title) {
return t('QA.osmose.error_types.' + errorType + '.title');
} else {
return unknown;
}
}
function osmoseHeader(selection) {
var header = selection.selectAll('.error-header')
.data(
(_error ? [_error] : []),
function(d) { return d.id + '-' + (d.status || 0); }
);
header.exit()
.remove();
var headerEnter = header.enter()
.append('div')
.attr('class', 'error-header');
var iconEnter = headerEnter
.append('div')
.attr('class', 'error-header-icon')
.classed('new', function(d) { return d.id < 0; });
var svgEnter = iconEnter
.append('svg')
.attr('width', '20px')
.attr('height', '30px')
.attr('viewbox', '0 0 20 30')
.attr('class', function(d) {
return [
'preset-icon-28',
'qa_error',
d.service,
'error_id-' + d.id,
'error_type-' + d.error_type,
'category-' + d.category
].join(' ');
});
svgEnter
.append('polygon')
.attr('fill', 'currentColor')
.attr('class', 'qa_error-fill')
.attr('points', '16,3 4,3 1,6 1,17 4,20 7,20 10,27 13,20 16,20 19,17.033 19,6');
svgEnter
.append('use')
.attr('class', 'icon-annotation')
.attr('width', '11px')
.attr('height', '11px')
.attr('transform', 'translate(4.5, 7)')
.attr('xlink:href', function(d) {
var picon = d.icon;
if (!picon) {
return '';
} else {
var isMaki = /^maki-/.test(picon);
return '#' + picon + (isMaki ? '-11' : '');
}
});
headerEnter
.append('div')
.attr('class', 'error-header-label')
.text(errorTitle);
}
osmoseHeader.error = function(val) {
if (!arguments.length) return _error;
_error = val;
return osmoseHeader;
};
return osmoseHeader;
}
+12 -3
View File
@@ -16,6 +16,7 @@ import { uiFeatureList } from './feature_list';
import { uiInspector } from './inspector';
import { uiImproveOsmEditor } from './improveOSM_editor';
import { uiKeepRightEditor } from './keepRight_editor';
import { uiOsmoseEditor } from './osmose_editor';
import { uiNoteEditor } from './note_editor';
import { textDirection } from '../util/locale';
@@ -26,6 +27,7 @@ export function uiSidebar(context) {
var noteEditor = uiNoteEditor(context);
var improveOsmEditor = uiImproveOsmEditor(context);
var keepRightEditor = uiKeepRightEditor(context);
var osmoseEditor = uiOsmoseEditor(context);
var _current;
var _wasData = false;
var _wasNote = false;
@@ -147,8 +149,15 @@ export function uiSidebar(context) {
datum = errService.getError(datum.id);
}
// Temporary solution while only two services
var errEditor = (datum.service === 'keepRight') ? keepRightEditor : improveOsmEditor;
// Currently only three possible services
var errEditor;
if (datum.service === 'keepRight') {
errEditor = keepRightEditor;
} else if (datum.service === 'osmose') {
errEditor = osmoseEditor;
} else {
errEditor = improveOsmEditor;
}
d3_selectAll('.qa_error.' + datum.service)
.classed('hover', function(d) { return d.id === datum.id; });
@@ -357,4 +366,4 @@ export function uiSidebar(context) {
sidebar.toggle = function() {};
return sidebar;
}
}