mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-15 05:30:35 +02:00
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:
@@ -159,6 +159,10 @@
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.osmose.category-structure {
|
||||
color: #F9C700;
|
||||
}
|
||||
|
||||
/* Custom Map Data (geojson, gpx, kml, vector tile) */
|
||||
.layer-mapdata {
|
||||
pointer-events: none;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -32,6 +32,14 @@
|
||||
"errorTypes": {
|
||||
|
||||
}
|
||||
},
|
||||
"osmose": {
|
||||
"errorTypes": {
|
||||
"1070-1": {
|
||||
"icon": "maki-home",
|
||||
"category": "structure"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+9
@@ -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
@@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user