diff --git a/data/core.yaml b/data/core.yaml index 61343d5dc..fce1134da 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -481,7 +481,7 @@ en: tooltip: Note data from OpenStreetMap title: OpenStreetMap notes keepRight: - tooltip: Quality Assurance data from keepright.at + tooltip: Automatically detected map issues from keepright.at title: KeepRight Issues custom: tooltip: "Drag and drop a data file onto the page, or click the button to setup" @@ -649,9 +649,7 @@ en: full_screen: Toggle Full Screen QA: keepRight: - tooltip: automatically detected errors from keepright.at - description: Keep Right - title: Edit KeepRight Error + title: KeepRight Error detail_title: Error detail_description: Description comment_header: Comment @@ -659,22 +657,20 @@ en: updateInputPlaceholder: Update the comment above to share with other users. newComment: New Comment updateComment: Update Comment - upload_explanation: Your comments will be publicly visible to all keepRight.at users. - upload_explanation_with_user: "Your comments as {user} will be publicly visible to all keepRight.at users." + upload_explanation: Your comments will be publicly visible on KeepRight. + upload_explanation_with_user: "Your comments as {user} will be publicly visible on KeepRight." resolve_comment: Comment and Resolve ignore_comment: Comment and Ignore resolve: Resolve ignore: Ignore - toggle-on: All on - toggle-off: All off entities: - node: node - way: way - relation: relation - highway: highway - cycleway: cycleway - waterway: waterway - riverbank: riverbank + node: "Node {id}" + way: "Way {id}" + relation: "Relation {id}" + highway: "Highway {id}" + cycleway: "Cycleway {id}" + waterway: "Waterway {id}" + riverbank: "Riverbank {id}" errorTypes: errors: _30: diff --git a/modules/util/keepRight/errorSchema.json b/data/keepRight.json similarity index 100% rename from modules/util/keepRight/errorSchema.json rename to data/keepRight.json diff --git a/dist/locales/en.json b/dist/locales/en.json index ed5847ee5..fa9999527 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -584,7 +584,7 @@ "title": "OpenStreetMap notes" }, "keepRight": { - "tooltip": "Quality Assurance data from keepright.at", + "tooltip": "Automatically detected map issues from keepright.at", "title": "KeepRight Issues" }, "custom": { @@ -789,9 +789,7 @@ "full_screen": "Toggle Full Screen", "QA": { "keepRight": { - "tooltip": "automatically detected errors from keepright.at", - "description": "Keep Right", - "title": "Edit KeepRight Error", + "title": "KeepRight Error", "detail_title": "Error", "detail_description": "Description", "comment_header": "Comment", @@ -799,22 +797,20 @@ "updateInputPlaceholder": "Update the comment above to share with other users.", "newComment": "New Comment", "updateComment": "Update Comment", - "upload_explanation": "Your comments will be publicly visible to all keepRight.at users.", - "upload_explanation_with_user": "Your comments as {user} will be publicly visible to all keepRight.at users.", + "upload_explanation": "Your comments will be publicly visible on KeepRight.", + "upload_explanation_with_user": "Your comments as {user} will be publicly visible on KeepRight.", "resolve_comment": "Comment and Resolve", "ignore_comment": "Comment and Ignore", "resolve": "Resolve", "ignore": "Ignore", - "toggle-on": "All on", - "toggle-off": "All off", "entities": { - "node": "node", - "way": "way", - "relation": "relation", - "highway": "highway", - "cycleway": "cycleway", - "waterway": "waterway", - "riverbank": "riverbank" + "node": "Node {id}", + "way": "Way {id}", + "relation": "Relation {id}", + "highway": "Highway {id}", + "cycleway": "Cycleway {id}", + "waterway": "Waterway {id}", + "riverbank": "Riverbank {id}" }, "errorTypes": { "errors": { diff --git a/modules/services/keepRight.js b/modules/services/keepRight.js index 6aed5b8fd..1e74627e7 100644 --- a/modules/services/keepRight.js +++ b/modules/services/keepRight.js @@ -9,11 +9,14 @@ import { json as d3_json } from 'd3-request'; import { request as d3_request } from 'd3-request'; import { geoExtent, geoVecAdd } from '../geo'; -import { services } from './index'; import { krError } from '../osm'; - +import { services } from './index'; +import { t } from '../util/locale'; import { utilRebind, utilTiler, utilQsString } from '../util'; +import { errorTypes } from '../../data/keepRight.json'; + + var tiler = utilTiler(); var dispatch = d3_dispatch('loaded'); @@ -22,6 +25,7 @@ var _krZoom = 14; var apibase = 'https://www.keepright.at/'; var defaultRuleset = [0,30,40,50,70,90,100,110,120,130,150,160,170,180,191,192,193,194,195,196,197,198,201,202,203,204,205,206,207,208,210,220,231,232,270,281,282,283,284,285,291,292,293,294,295,296,297,298,311,312,313,320,350,370,380,401,402,411,412,413]; + function abortRequest(i) { if (i) { i.abort(); @@ -58,6 +62,179 @@ function updateRtree(item, replace) { } +function tokenReplacements(datum) { + if (!(datum instanceof krError)) return; + + var replacements = {}; + var html_re = new RegExp(/<\/[a-z][\s\S]*>/); + var commonEntities = ['node', 'way', 'relation', 'highway', 'cycleway', 'waterway', 'riverbank']; + + var errorType; + var errorTemplate; + var errorDescription; + var errorRegex; + var errorMatch; + + // find the matching template from the error schema + errorType = '_' + datum.error_type; + errorTemplate = errorTypes.errors[errorType] || errorTypes.warnings[errorType]; + if (!errorTemplate) return; + + // some descriptions are just fixed text + if (!('regex' in errorTemplate)) return; + + // regex pattern should match description with variable details captured as groups + errorDescription = datum.description; + errorRegex = new RegExp(errorTemplate.description); + errorMatch = errorRegex.exec(errorDescription); + if (!errorMatch) { + // TODO: Remove, for regex dev testing + console.log('Unmatched:', errorType, errorDescription, errorRegex); + return; + } + + errorMatch.forEach(function(group, index) { + var idType; + + // index 0 is the whole match, skip it + if (!index) return; + + // link IDs if present in the group + idType = 'IDs' in errorTemplate ? errorTemplate.IDs[index-1] : ''; + if (idType && group) { + group = parseError(group, idType); + } else if (html_re.test(group)) { + // escape any html in non-IDs + group = '\\' + group + '\\'; + } + + // translate common words (e.g. node, way, relation) + if (commonEntities.includes(group)) { + group = t('QA.keepRight.entities.' + group); + } + + replacements['var' + index] = group; + }); + + return replacements; +} + + +function parseError(group, idType) { + + function fillPlaceholder(d) { return '' + d + ''; } + + // arbitrary node list of form: #ID, #ID, #ID... + function parseError211(list) { + var newList = []; + var items = list.split(', '); + + items.forEach(function(item) { + // ID has # at the front + var id = fillPlaceholder('n' + item.slice(1)); + newList.push(id); + }); + + return newList.join(', '); + } + + // arbitrary way list of form: #ID(layer),#ID(layer),#ID(layer)... + function parseError231(list) { + var newList = []; + var items = list.split(','); + + items.forEach(function(item) { + var id; + var layer; + + // item of form "#ID(layer)" + item = item.split('('); + + // ID has # at the front + id = item[0].slice(1); + id = fillPlaceholder('w' + id); + + // layer has trailing ) + layer = item[1].slice(0,-1); + + // TODO: translation + newList.push(id + ' (layer: ' + layer + ')'); + }); + + return newList.join(', '); + } + + // arbitrary node/relation list of form: from node #ID,to relation #ID,to node #ID... + function parseError294(list) { + var newList = []; + var items = list.split(','); + + items.forEach(function(item) { + var role; + var idType; + var id; + + // item of form "from/to node/relation #ID" + item = item.split(' '); + + // to/from role is more clear in quotes + role = '"' + item[0] + '"'; + + // first letter of node/relation provides the type + idType = item[1].slice(0,1); + + // ID has # at the front + id = item[2].slice(1); + id = fillPlaceholder(idType + id); + + item = [role, item[1], id].join(' '); + newList.push(item); + }); + + return newList.join(', '); + } + + // TODO: Handle error 401 template addition + + // arbitrary node list of form: #ID,#ID,#ID... + function parseWarning20(list) { + var newList = []; + var items = list.split(','); + + items.forEach(function(item) { + // ID has # at the front + var id = fillPlaceholder('n' + item.slice(1)); + newList.push(id); + }); + + return newList.join(', '); + } + + switch (idType) { + // simple case just needs a linking span + case 'n': + case 'w': + case 'r': + group = fillPlaceholder(idType + group); + break; + // some errors have more complex ID lists/variance + case '211': + group = parseError211(group); + break; + case '231': + group = parseError231(group); + break; + case '294': + group = parseError294(group); + break; + case '20': + group = parseWarning20(group); + } + + return group; +} + + export default { init: function() { if (!_krCache) { @@ -77,11 +254,8 @@ export default { // KeepRight API: http://osm.mueschelsoft.de/keepright/interfacing.php loadErrors: function(context, projection) { - var options = { - format: 'geojson' - }; + var options = { format: 'geojson' }; var rules = defaultRuleset.join(); - var that = this; // determine the needed tiles to cover the view var tiles = tiler @@ -136,6 +310,8 @@ export default { title: props.title }); + d.replacements = tokenReplacements(d); + _krCache.keepRight[d.id] = d; _krCache.rtree.insert(encodeErrorRtree(d)); }); diff --git a/modules/ui/keepRight_details.js b/modules/ui/keepRight_details.js index 0092c535d..f5edc8a23 100644 --- a/modules/ui/keepRight_details.js +++ b/modules/ui/keepRight_details.js @@ -1,7 +1,7 @@ -import { t } from '../util/locale'; -import { parseErrorDescriptions, errorTypes } from '../util'; +import { event as d3_event } from 'd3-selection'; -import { clickLink } from '../util/keepRight'; +import { errorTypes } from '../../data/keepRight.json'; +import { t } from '../util/locale'; export function uiKeepRightDetails(context) { @@ -97,12 +97,18 @@ export function uiKeepRightDetails(context) { .append('div') .attr('class', 'kr_error-details-description-text') .html(function(d) { - return t(_titleBase + _templateErrorType + '.description', parseErrorDescriptions(d)); + return t(_titleBase + _templateErrorType + '.description', d.replacements); }); description.selectAll('.kr_error_description-id') .on('click', function() { clickLink(context, this.text); }); + + function clickLink(context, id) { + d3_event.preventDefault(); + context.layers().layer('osm').enabled(true); + context.zoomToEntity(id); + } } diff --git a/modules/ui/keepRight_header.js b/modules/ui/keepRight_header.js index 978008572..878eb588a 100644 --- a/modules/ui/keepRight_header.js +++ b/modules/ui/keepRight_header.js @@ -1,10 +1,8 @@ import { t } from '../util/locale'; -import { utilEntityRoot } from '../util'; -import { clickLink } from '../util/keepRight'; import { svgIcon } from '../svg'; -export function uiKeepRightHeader(context) { +export function uiKeepRightHeader() { var _error; @@ -32,17 +30,12 @@ export function uiKeepRightHeader(context) { .attr('class', function(d) { return 'preset-icon-28 kr_error kr_error-' + d.id + ' kr_error_type_' + d.error_type; }) - .call(svgIcon('#iD-icon-bolt', 'kr_error-fill')); headerEnter .append('div') .attr('class', 'kr_error-header-label') - .text(function(d) { return t('QA.keepRight.entities.' + d.object_type) + ' '; }) - .append('span') - .append('a') - .text(function(d) { return d.object_id; }) - .on('click', function(d) { clickLink(context, (utilEntityRoot(d.object_type) + d.object_id)); }); + .text(function(d) { return t('QA.keepRight.entities.' + d.object_type, { id: d.object_id }); }); } diff --git a/modules/ui/map_data.js b/modules/ui/map_data.js index a96b255cf..a6cee9fd7 100644 --- a/modules/ui/map_data.js +++ b/modules/ui/map_data.js @@ -1,11 +1,9 @@ -import { dispatch as d3_dispatch } from 'd3-dispatch'; import { event as d3_event, select as d3_select } from 'd3-selection'; import { svgIcon } from '../svg'; -import { errorTypes } from '../util'; import { t, textDirection } from '../util/locale'; import { tooltip } from '../util/tooltip'; import { geoExtent } from '../geo'; @@ -18,8 +16,6 @@ import { uiTooltipHtml } from './tooltipHtml'; export function uiMapData(context) { - var dispatch = d3_dispatch('change'); - var key = t('map_data.key'); var features = context.features().keys(); var layers = context.layers(); @@ -485,46 +481,6 @@ export function uiMapData(context) { } - function drawQAButtons(selection) { - var QAButtons = d3_select('.layer-QA').selectAll('li').select('label').select('input'); - var buttonSection = selection.selectAll('.QA-buttons') - .data([0]); - - // var buttonSection = selection.selectAll('.QA-buttons') - // .data([0]); - - // // exit - // buttonSection.exit() - // .remove(); - - // // enter - // var buttonEnter = buttonSection.enter() - // .append('div') - // .attr('class', 'QA-buttons'); - - // buttonEnter - // .append('button') - // .attr('class', 'button QA-toggle-on action') - // .text(t('QA.keepRight.toggle-on')) - // .on('click', function() { - // QAButtons.property('checked', true); - // dispatch.call('change'); - // }); - - // buttonEnter - // .append('button') - // .attr('class', 'button QA-toggle-off action') - // .text(t('QA.keepRight.toggle-off')) - // .on('click', function() { - // QAButtons.property('checked', false); - // dispatch.call('change'); - // }); - - // buttonSection = buttonSection - // .merge(buttonEnter); - } - - function drawListItems(selection, data, type, name, change, active) { var items = selection.selectAll('li') .data(data); diff --git a/modules/util/index.js b/modules/util/index.js index b386875c3..98755addd 100644 --- a/modules/util/index.js +++ b/modules/util/index.js @@ -14,7 +14,6 @@ export { utilExternalValidationRules } from './util'; export { utilFastMouse } from './util'; export { utilFunctor } from './util'; export { utilGetAllNodes } from './util'; -export { errorTypes, parseErrorDescriptions, clickLink } from './keepRight'; export { utilGetPrototypeOf } from './util'; export { utilGetSetValue } from './get_set_value'; export { utilHashcode } from './util'; @@ -29,7 +28,6 @@ export { utilRebind } from './rebind'; export { utilSetTransform } from './util'; export { utilSessionMutex } from './session_mutex'; export { utilStringQs } from './util'; -// export { utilSuggestNames } from './suggest_names'; export { utilTagText } from './util'; export { utilTiler } from './tiler'; export { utilTriggerEvent } from './trigger_event'; diff --git a/modules/util/keepRight/index.js b/modules/util/keepRight/index.js deleted file mode 100644 index 0f95dc9a7..000000000 --- a/modules/util/keepRight/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { errorTypes } from './errorSchema.json'; -export { parseError } from './parse_error'; -export { parseErrorDescriptions, clickLink } from './keepRight_error'; \ No newline at end of file diff --git a/modules/util/keepRight/keepRight_error.js b/modules/util/keepRight/keepRight_error.js deleted file mode 100644 index 6e50728a4..000000000 --- a/modules/util/keepRight/keepRight_error.js +++ /dev/null @@ -1,80 +0,0 @@ -import { event as d3_event } from 'd3-selection'; - -import { t } from '../locale'; -import { krError } from '../../osm'; - -import { errorTypes } from './errorSchema.json'; -import { parseError } from './parse_error'; - - -export function parseErrorDescriptions(entity) { - var parsedDetails = {}; - var html_re = new RegExp(/<\/[a-z][\s\S]*>/); - var commonEntities = [ - 'node', - 'way', - 'relation', - 'highway', - 'cycleway', - 'waterway', - 'riverbank' - ]; // TODO: expand this list, or implement a different translation function - - var errorType; - var errorTemplate; - var errorDescription; - var errorRegex; - var errorMatch; - - if (!(entity instanceof krError)) return; - - // find the matching template from the error schema - errorType = '_' + entity.error_type; - errorTemplate = errorTypes.errors[errorType] || errorTypes.warnings[errorType]; - if (!errorTemplate) return; - - // some descriptions are just fixed text - if (!('regex' in errorTemplate)) return; - - // regex pattern should match description with variable details captured as groups - errorDescription = entity.description; - errorRegex = new RegExp(errorTemplate.description); - errorMatch = errorRegex.exec(errorDescription); - if (!errorMatch) { - // TODO: Remove, for regex dev testing - console.log('Unmatched:', errorType, errorDescription, errorRegex); - return; - } - - errorMatch.forEach(function(group, index) { - var idType; - - // index 0 is the whole match, skip it - if (!index) return; - - // link IDs if present in the group - idType = 'IDs' in errorTemplate ? errorTemplate.IDs[index-1] : ''; - if (idType && group) { - group = parseError(group, idType); - } else if (html_re.test(group)) { - // escape any html in non-IDs - group = '\\' + group + '\\'; - } - - // translate common words (e.g. node, way, relation) - if (commonEntities.includes(group)) { - group = t('QA.keepRight.entities.' + group); - } - - parsedDetails['var' + index] = group; - }); - - return parsedDetails; -} - - -export function clickLink(context, id) { - d3_event.preventDefault(); - context.layers().layer('osm').enabled(true); - context.zoomToEntity(id); - } diff --git a/modules/util/keepRight/parse_error.js b/modules/util/keepRight/parse_error.js deleted file mode 100644 index 37eed18dc..000000000 --- a/modules/util/keepRight/parse_error.js +++ /dev/null @@ -1,113 +0,0 @@ -export function parseError(group, idType) { - - function fillPlaceholder(d) { return '' + d + ''; } - - // arbitrary node list of form: #ID, #ID, #ID... - function parseError211(list) { - var newList = []; - var items = list.split(', '); - - items.forEach(function(item) { - // ID has # at the front - var id = fillPlaceholder('n' + item.slice(1)); - newList.push(id); - }); - - return newList.join(', '); - } - - // arbitrary way list of form: #ID(layer),#ID(layer),#ID(layer)... - function parseError231(list) { - var newList = []; - var items = list.split(','); - - items.forEach(function(item) { - var id; - var layer; - - // item of form "#ID(layer)" - item = item.split('('); - - // ID has # at the front - id = item[0].slice(1); - id = fillPlaceholder('w' + id); - - // layer has trailing ) - layer = item[1].slice(0,-1); - - // TODO: translation - newList.push(id + ' (layer: ' + layer + ')'); - }); - - return newList.join(', '); - } - - // arbitrary node/relation list of form: from node #ID,to relation #ID,to node #ID... - function parseError294(list) { - var newList = []; - var items = list.split(','); - - items.forEach(function(item) { - var role; - var idType; - var id; - - // item of form "from/to node/relation #ID" - item = item.split(' '); - - // to/from role is more clear in quotes - role = '"' + item[0] + '"'; - - // first letter of node/relation provides the type - idType = item[1].slice(0,1); - - // ID has # at the front - id = item[2].slice(1); - id = fillPlaceholder(idType + id); - - item = [role, item[1], id].join(' '); - newList.push(item); - }); - - return newList.join(', '); - } - - // TODO: Handle error 401 template addition - - // arbitrary node list of form: #ID,#ID,#ID... - function parseWarning20(list) { - var newList = []; - var items = list.split(','); - - items.forEach(function(item) { - // ID has # at the front - var id = fillPlaceholder('n' + item.slice(1)); - newList.push(id); - }); - - return newList.join(', '); - } - - switch (idType) { - // simple case just needs a linking span - case 'n': - case 'w': - case 'r': - group = fillPlaceholder(idType + group); - break; - // some errors have more complex ID lists/variance - case '211': - group = parseError211(group); - break; - case '231': - group = parseError231(group); - break; - case '294': - group = parseError294(group); - break; - case '20': - group = parseWarning20(group); - } - - return group; -} \ No newline at end of file