Add generic QA error

I've converted the improveOSM errors to use this new generic QA error
structure which should allow for more general code to be used in
behaviour and UI.

Sidebar preview is currently broken, but will be fixed shortly.
This commit is contained in:
SilentSpike
2019-02-03 12:54:05 +00:00
parent 3f2abbe7c6
commit 20ed8b50c9
10 changed files with 168 additions and 62 deletions
+3 -3
View File
@@ -5,7 +5,7 @@ import {
select as d3_select
} from 'd3-selection';
import { osmEntity, osmNote, krError, iOsmError } from '../osm';
import { osmEntity, osmNote, krError, qaError } from '../osm';
import { utilKeybinding, utilRebind } from '../util';
@@ -113,7 +113,7 @@ export function behaviorHover(context) {
selector = '.data' + datum.__featurehash__;
} else if (
datum instanceof iOsmError ||
datum instanceof qaError ||
datum instanceof krError
) {
entity = datum;
@@ -187,4 +187,4 @@ export function behaviorHover(context) {
return utilRebind(behavior, dispatch, 'on');
}
}
+5 -5
View File
@@ -19,8 +19,8 @@ import {
import {
osmEntity,
osmNote,
iOsmError,
krError
krError,
qaError
} from '../osm';
@@ -171,11 +171,11 @@ export function behaviorSelect(context) {
context
.selectedNoteID(datum.id)
.enter(modeSelectNote(context, datum.id));
} else if (datum instanceof iOsmError & !isMultiselect) { // clicked an improveOSM error
} else if (datum instanceof krError & !isMultiselect) { // clicked a krError error
context
.selectedErrorID(datum.id)
.enter(modeSelectError(context, datum.id, datum.source));
} else if (datum instanceof krError & !isMultiselect) { // clicked a krError error
} else if (datum instanceof qaError & !isMultiselect) { // clicked an external QA error
context
.selectedErrorID(datum.id)
.enter(modeSelectError(context, datum.id, datum.source));
@@ -241,4 +241,4 @@ export function behaviorSelect(context) {
return behavior;
}
}
+2 -2
View File
@@ -1,11 +1,11 @@
export { osmChangeset } from './changeset';
export { osmEntity } from './entity';
export { krError } from './keepRight';
export { iOsmError } from './improveOSM';
export { osmNode } from './node';
export { osmNote } from './note';
export { osmRelation } from './relation';
export { osmWay } from './way';
export { qaError } from './qa_error';
export {
osmIntersection,
@@ -27,4 +27,4 @@ export {
osmOneWayTags,
osmPavedTags,
osmIsInterestingTag
} from './tags';
} from './tags';
+65
View File
@@ -0,0 +1,65 @@
import _extend from 'lodash-es/extend';
import { services } from '../../data/qa_errors.json';
export function qaError() {
if (!(this instanceof qaError)) {
return (new qaError()).initialize(arguments);
} else if (arguments.length) {
this.initialize(arguments);
}
}
// Generic handling for services without nice IDs
qaError.id = function() {
return qaError.id.next--;
};
qaError.id.next = -1;
_extend(qaError.prototype, {
type: 'qaError',
// All errors need a position
loc: [0, 0],
// These should be passed in, used to retrieve from qa_errors.json
service: '',
error_type: '',
initialize: function(sources) {
for (var i = 0; i < sources.length; ++i) {
var source = sources[i];
for (var prop in source) {
if (Object.prototype.hasOwnProperty.call(source, prop)) {
if (source[prop] === undefined) {
delete this[prop];
} else {
this[prop] = source[prop];
}
}
}
}
if (this.service) {
this.source = services[this.service].shortName;
if (this.error_type) {
var template = services[this.service].errorTypes[this.error_type];
this.icon = template.icon;
this.category = template.category;
}
}
// All errors must have an ID for selection
if (!this.id) {
this.id = qaError.id() + ''; // as string
}
return this;
},
update: function(attrs) {
return qaError(this, attrs); // {v: 1 + (this.v || 0)}
}
});
+35 -41
View File
@@ -9,7 +9,7 @@ import { json as d3_json } from 'd3-request';
import { request as d3_request } from 'd3-request';
import { geoExtent, geoVecAdd } from '../geo';
import { iOsmError } from '../osm';
import { qaError } from '../osm';
import { services } from './index';
import { t } from '../util/locale';
import { utilRebind, utilTiler, utilQsString } from '../util';
@@ -210,12 +210,13 @@ export default {
// One-ways can land on same segment in opposite direction
loc = preventCoincident(loc, false);
var d = new iOsmError({
var d = new qaError({
// Info required for every error
loc: loc,
comments: null,
error_subtype: '',
service: 'improveOSM',
error_type: k,
icon: '', //TODO: Find suitable icon
// Extra details needed for this service
error_key: k,
identifier: { // this is used to post changes to the error
wayId: feature.wayId,
fromNodeId: feature.fromNodeId,
@@ -243,27 +244,20 @@ export default {
// Tiles at high zoom == missing roads
if (data.tiles) {
data.tiles.forEach(function(feature) {
// Average of recorded points should land on the missing geometry
var loc = pointAverage(feature.points);
var geoType = feature.type.toLowerCase();
// Average of recorded points should land on the missing geometry
// Missing geometry could happen to land on another error
var loc = pointAverage(feature.points);
loc = preventCoincident(loc, false);
var geoType = feature.type.toLowerCase();
var geoIcons = {
road: 'maki-car',
parking: 'maki-parking',
both: 'maki-car',
path: 'maki-shoe'
};
var d = new iOsmError({
var d = new qaError({
// Info required for every error
loc: loc,
comments: null,
error_subtype: geoType,
error_type: k,
icon: geoIcons[geoType],
service: 'improveOSM',
error_type: k + '-' + geoType,
// Extra details needed for this service
error_key: k,
identifier: { x: feature.x, y: feature.y },
status: feature.status
});
@@ -281,10 +275,9 @@ export default {
// Entities at high zoom == turn restrictions
if (data.entities) {
data.entities.forEach(function(feature) {
var loc = feature.point;
// Turn restrictions could be missing at same junction
// We also want to bump the error up so node is accessible
var loc = feature.point;
loc = preventCoincident([loc.lon, loc.lat], true);
// Elements are presented in a strange way
@@ -293,24 +286,25 @@ export default {
var via_node = ids[3];
var to_way = ids[2].split(':')[1];
// Travel direction along from_way clarifies the turn restriction
var p1 = feature.segments[0].points[0];
var p2 = feature.segments[0].points[1];
var dir_of_travel = cardinalDirection(relativeBearing(p1, p2));
var d = new iOsmError({
var d = new qaError({
// Info required for every error
loc: loc,
comments: null,
error_subtype: '',
service: 'improveOSM',
error_type: k,
icon: 'temaki-junction',
// Extra details needed for this service
error_key: k,
identifier: feature.id,
object_id: via_node,
object_type: 'node',
status: feature.status
});
// Travel direction along from_way clarifies the turn restriction
var p1 = feature.segments[0].points[0];
var p2 = feature.segments[0].points[1];
var dir_of_travel = cardinalDirection(relativeBearing(p1, p2));
// Variables used in the description
d.replacements = {
num_passed: feature.numberOfPasses,
@@ -351,18 +345,18 @@ export default {
function sendPayload(err, user) {
if (err) { return callback(err, d); }
var type = d.error_type;
var url = _impOsmUrls[type] + '/comment';
var key = d.error_key;
var url = _impOsmUrls[key] + '/comment';
var payload = {
username: user.display_name
};
// Each error type has different data for identification
if (type === 'ow') {
if (key === 'ow') {
payload.roadSegments = [ d.identifier ];
} else if (type === 'mr') {
} else if (key === 'mr') {
payload.tiles = [ d.identifier ];
} else if (type === 'tr') {
} else if (key === 'tr') {
payload.targetIds = [ d.identifier ];
}
@@ -390,7 +384,7 @@ export default {
// No pretty identifier, so we just use coordinates
if (d.newStatus === 'SOLVED') {
var closedID = d.loc[1].toFixed(5) + '/' + d.loc[0].toFixed(5);
_erCache.closed[d.error_type + ':' + closedID] = true;
_erCache.closed[key + ':' + closedID] = true;
}
return callback(err, d);
@@ -417,7 +411,7 @@ export default {
// replace a single error in the cache
replaceError: function(error) {
if (!(error instanceof iOsmError) || !error.id) return;
if (!(error instanceof qaError) || !error.id) return;
_erCache.data[error.id] = error;
updateRtree(encodeErrorRtree(error), true); // true = replace
@@ -426,7 +420,7 @@ export default {
// remove a single error from the cache
removeError: function(error) {
if (!(error instanceof iOsmError) || !error.id) return;
if (!(error instanceof qaError) || !error.id) return;
delete _erCache.data[error.id];
updateRtree(encodeErrorRtree(error), false); // false = remove
+3 -2
View File
@@ -119,7 +119,8 @@ export function svgImproveOSM(projection, context, dispatch) {
'qa_error',
d.source,
'error_id-' + d.id,
'error_type-' + d.error_type + '-' + d.error_subtype
'error_type-' + d.error_type,
'category-' + d.category
].join(' ');
});
@@ -259,4 +260,4 @@ export function svgImproveOSM(projection, context, dispatch) {
return drawImproveOSM;
}
}
+2 -2
View File
@@ -17,7 +17,7 @@ export function uiImproveOsmDetails(context) {
var unknown = t('inspector.unknown');
if (!d) return unknown;
var errorType = d.error_type;
var errorType = d.error_key;
var et = dataEn.QA.improveOSM.error_types[errorType];
var detail;
@@ -124,4 +124,4 @@ export function uiImproveOsmDetails(context) {
return improveOsmDetails;
}
}
+10 -3
View File
@@ -10,7 +10,7 @@ export function uiImproveOsmHeader() {
var unknown = t('inspector.unknown');
if (!d) return unknown;
var errorType = d.error_type;
var errorType = d.error_key;
var et = dataEn.QA.improveOSM.error_types[errorType];
if (et && et.title) {
@@ -46,7 +46,14 @@ export function uiImproveOsmHeader() {
.attr('height', '30px')
.attr('viewbox', '0 0 20 30')
.attr('class', function(d) {
return 'preset-icon-28 qa_error ' + d.source + ' error_id-' + d.id + ' error_type-' + d.error_type + '-' + d.error_subtype;
return [
'preset-icon-28',
'qa_error',
d.source,
'error_id-' + d.id,
'error_type-' + d.error_type,
'category-' + d.category
].join(' ');
});
svgEnter
@@ -86,4 +93,4 @@ export function uiImproveOsmHeader() {
return improveOsmHeader;
}
}