mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-12 16:52:50 +00:00
Remove references to ImproveOSM
This commit is contained in:
@@ -193,7 +193,6 @@ translated to one or more languages.
|
||||
| ✅ | OSM community index | | |
|
||||
| ✅ | iD validation issues | | |
|
||||
| ✅ | KeepRight issues | | |
|
||||
| ✅ | ImproveOSM issues | | |
|
||||
| ✅ | Osmose issues | Translated strings are [provided by Osmose](https://www.transifex.com/openstreetmap-france/osmose/) itself, not iD | |
|
||||
|
||||
### Language Coverage
|
||||
|
||||
@@ -33,8 +33,7 @@
|
||||
/* No interactivity except what we specifically allow */
|
||||
.data-layer.osm *,
|
||||
.data-layer.notes *,
|
||||
.data-layer.keepRight *,
|
||||
.data-layer.improveOSM * {
|
||||
.data-layer.keepRight * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
.qa-header-icon .qaItem-fill,
|
||||
.layer-keepRight .qaItem .qaItem-fill,
|
||||
.layer-improveOSM .qaItem .qaItem-fill,
|
||||
.layer-osmose .qaItem .qaItem-fill {
|
||||
stroke: #333;
|
||||
stroke-width: 1.3px; /* NOTE: likely a better way to scale the icon stroke */
|
||||
@@ -127,30 +126,6 @@
|
||||
color: #c35;
|
||||
}
|
||||
|
||||
/* ImproveOSM Issues
|
||||
------------------------------------------------------- */
|
||||
|
||||
.improveOSM.itemType-ow { /* missing one way */
|
||||
color: #1E90FF;
|
||||
}
|
||||
|
||||
.improveOSM.itemType-mr-road { /* missing road */
|
||||
color: #B452CD;
|
||||
}
|
||||
.improveOSM.itemType-mr-path { /* missing path */
|
||||
color: #A0522D;
|
||||
}
|
||||
.improveOSM.itemType-mr-parking { /* missing parking */
|
||||
color: #EEEE00;
|
||||
}
|
||||
.improveOSM.itemType-mr-both { /* missing road+parking */
|
||||
color: #FFA500;
|
||||
}
|
||||
|
||||
.improveOSM.itemType-tr { /* missing turn restriction */
|
||||
color: #EC1C24;
|
||||
}
|
||||
|
||||
/* Custom Map Data (geojson, gpx, kml, vector tile) */
|
||||
.layer-mapdata {
|
||||
pointer-events: none;
|
||||
|
||||
@@ -839,9 +839,6 @@ en:
|
||||
keepRight:
|
||||
tooltip: Data issues detected by keepright.at
|
||||
title: KeepRight Issues
|
||||
improveOSM:
|
||||
tooltip: Missing data detected by improveosm.org
|
||||
title: ImproveOSM Issues
|
||||
osmose:
|
||||
tooltip: Data issues detected by osmose.openstreetmap.fr
|
||||
title: Osmose Issues
|
||||
@@ -1090,33 +1087,6 @@ en:
|
||||
elems_title: Features
|
||||
fix_title: Fix Guidelines
|
||||
trap_title: Common Mistakes
|
||||
improveOSM:
|
||||
title: ImproveOSM Detection
|
||||
geometry_types:
|
||||
path: paths
|
||||
parking: parking
|
||||
road: roads
|
||||
both: roads and parking
|
||||
directions:
|
||||
east: east
|
||||
north: north
|
||||
northeast: northeast
|
||||
northwest: northwest
|
||||
south: south
|
||||
southeast: southeast
|
||||
southwest: southwest
|
||||
west: west
|
||||
error_types:
|
||||
ow:
|
||||
title: Missing One-way
|
||||
description: 'Along this section of {highway}, {percentage}% of {num_trips} recorded trips travel from {from_node} to {to_node}. There may be missing a "oneway" tag.'
|
||||
mr:
|
||||
title: Missing Geometry
|
||||
description: '{num_trips} recorded trips in this area suggest there may be unmapped {geometry_type} here.'
|
||||
description_alt: 'Data from a 3rd party suggests there may be unmapped {geometry_type} here.'
|
||||
tr:
|
||||
title: Missing Turn Restriction
|
||||
description: '{num_passed} of {num_trips} recorded trips (travelling {travel_direction}) make a turn from {from_way} to {to_way} at {junction}. There may be a missing "{turn_restriction}" restriction.'
|
||||
keepRight:
|
||||
title: KeepRight
|
||||
detail_description: Description
|
||||
@@ -1679,7 +1649,7 @@ en:
|
||||
title: Quality Assurance
|
||||
intro: "*Quality Assurance* (Q/A) tools can find improper tags, disconnected roads, and other issues with OpenStreetMap, which mappers can then fix. To view existing Q/A issues, open the {data_icon} **{map_data}** panel and enable a specific Q/A layer."
|
||||
tools_h: "Tools"
|
||||
tools: "The following tools are currently supported: [KeepRight](https://www.keepright.at/), [ImproveOSM](https://improveosm.org/en/) and [Osmose](https://osmose.openstreetmap.fr/)."
|
||||
tools: "The following tools are currently supported: [KeepRight](https://www.keepright.at/) and [Osmose](https://osmose.openstreetmap.fr/)."
|
||||
issues_h: "Handling Issues"
|
||||
issues: "Handling Q/A issues is similar to handling notes. Select a marker to view the issue details in the sidebar. Each tool has its own capabilities, but generally you can comment and/or close an issue."
|
||||
field:
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
{
|
||||
"improveOSM": {
|
||||
"icons": {
|
||||
"ow": "fas-long-arrow-alt-right",
|
||||
"mr-both": "maki-car",
|
||||
"mr-parking": "maki-parking",
|
||||
"mr-path": "maki-shoe",
|
||||
"mr-road": "maki-car",
|
||||
"tr": "temaki-junction"
|
||||
}
|
||||
},
|
||||
"osmose": {
|
||||
"icons": {
|
||||
"0-1": "maki-home",
|
||||
|
||||
@@ -12,7 +12,6 @@ import { services } from '../services';
|
||||
import { modeBrowse } from './browse';
|
||||
import { modeDragNode } from './drag_node';
|
||||
import { modeDragNote } from './drag_note';
|
||||
import { uiImproveOsmEditor } from '../ui/improveOSM_editor';
|
||||
import { uiKeepRightEditor } from '../ui/keepRight_editor';
|
||||
import { uiOsmoseEditor } from '../ui/osmose_editor';
|
||||
import { utilKeybinding } from '../util';
|
||||
@@ -29,16 +28,6 @@ export function modeSelectError(context, selectedErrorID, selectedErrorService)
|
||||
var errorService = services[selectedErrorService];
|
||||
var errorEditor;
|
||||
switch (selectedErrorService) {
|
||||
case 'improveOSM':
|
||||
errorEditor = uiImproveOsmEditor(context)
|
||||
.on('change', function() {
|
||||
context.map().pan([0,0]); // trigger a redraw
|
||||
var error = checkSelectedID();
|
||||
if (!error) return;
|
||||
context.ui().sidebar
|
||||
.show(errorEditor.error(error));
|
||||
});
|
||||
break;
|
||||
case 'keepRight':
|
||||
errorEditor = uiKeepRightEditor(context)
|
||||
.on('change', function() {
|
||||
|
||||
@@ -1,481 +0,0 @@
|
||||
import RBush from 'rbush';
|
||||
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { json as d3_json } from 'd3-fetch';
|
||||
|
||||
import { fileFetcher } from '../core/file_fetcher';
|
||||
import { geoExtent, geoVecAdd, geoVecScale } from '../geo';
|
||||
import { QAItem } from '../osm';
|
||||
import { serviceOsm } from './index';
|
||||
import { t } from '../core/localizer';
|
||||
import { utilRebind, utilTiler, utilQsString } from '../util';
|
||||
|
||||
|
||||
const tiler = utilTiler();
|
||||
const dispatch = d3_dispatch('loaded');
|
||||
const _tileZoom = 14;
|
||||
const _impOsmUrls = {
|
||||
ow: 'https://community.improveosm.org/directionOfFlowService',
|
||||
mr: 'https://community.improveosm.org/missingGeoService',
|
||||
tr: 'https://community.improveosm.org/turnRestrictionService'
|
||||
};
|
||||
let _impOsmData = { icons: {} };
|
||||
|
||||
|
||||
// This gets reassigned if reset
|
||||
let _cache;
|
||||
|
||||
function abortRequest(i) {
|
||||
Object.values(i).forEach(controller => {
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function abortUnwantedRequests(cache, tiles) {
|
||||
Object.keys(cache.inflightTile).forEach(k => {
|
||||
const wanted = tiles.find(tile => k === tile.id);
|
||||
if (!wanted) {
|
||||
abortRequest(cache.inflightTile[k]);
|
||||
delete cache.inflightTile[k];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function encodeIssueRtree(d) {
|
||||
return { minX: d.loc[0], minY: d.loc[1], maxX: d.loc[0], maxY: d.loc[1], data: d };
|
||||
}
|
||||
|
||||
// Replace or remove QAItem from rtree
|
||||
function updateRtree(item, replace) {
|
||||
_cache.rtree.remove(item, (a, b) => a.data.id === b.data.id);
|
||||
|
||||
if (replace) {
|
||||
_cache.rtree.insert(item);
|
||||
}
|
||||
}
|
||||
|
||||
function linkErrorObject(d) {
|
||||
return { html: `<a class="error_object_link">${d}</a>` };
|
||||
}
|
||||
|
||||
function linkEntity(d) {
|
||||
return { html: `<a class="error_entity_link">${d}</a>` };
|
||||
}
|
||||
|
||||
function pointAverage(points) {
|
||||
if (points.length) {
|
||||
const sum = points.reduce(
|
||||
(acc, point) => geoVecAdd(acc, [point.lon, point.lat]),
|
||||
[0,0]
|
||||
);
|
||||
return geoVecScale(sum, 1 / points.length);
|
||||
} else {
|
||||
return [0,0];
|
||||
}
|
||||
}
|
||||
|
||||
function relativeBearing(p1, p2) {
|
||||
let angle = Math.atan2(p2.lon - p1.lon, p2.lat - p1.lat);
|
||||
if (angle < 0) {
|
||||
angle += 2 * Math.PI;
|
||||
}
|
||||
|
||||
// Return degrees
|
||||
return angle * 180 / Math.PI;
|
||||
}
|
||||
|
||||
// Assuming range [0,360)
|
||||
function cardinalDirection(bearing) {
|
||||
const dir = 45 * Math.round(bearing / 45);
|
||||
const compass = {
|
||||
0: 'north',
|
||||
45: 'northeast',
|
||||
90: 'east',
|
||||
135: 'southeast',
|
||||
180: 'south',
|
||||
225: 'southwest',
|
||||
270: 'west',
|
||||
315: 'northwest',
|
||||
360: 'north'
|
||||
};
|
||||
|
||||
return t(`QA.improveOSM.directions.${compass[dir]}`);
|
||||
}
|
||||
|
||||
// Errors shouldn't obscure each other
|
||||
function preventCoincident(loc, bumpUp) {
|
||||
let coincident = false;
|
||||
do {
|
||||
// first time, move marker up. after that, move marker right.
|
||||
let delta = coincident ? [0.00001, 0] :
|
||||
bumpUp ? [0, 0.00001] :
|
||||
[0, 0];
|
||||
loc = geoVecAdd(loc, delta);
|
||||
let bbox = geoExtent(loc).bbox();
|
||||
coincident = _cache.rtree.search(bbox).length;
|
||||
} while (coincident);
|
||||
|
||||
return loc;
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'improveOSM',
|
||||
|
||||
init() {
|
||||
fileFetcher.get('qa_data')
|
||||
.then(d => _impOsmData = d.improveOSM);
|
||||
|
||||
if (!_cache) {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
this.event = utilRebind(this, dispatch, 'on');
|
||||
},
|
||||
|
||||
reset() {
|
||||
if (_cache) {
|
||||
Object.values(_cache.inflightTile).forEach(abortRequest);
|
||||
}
|
||||
_cache = {
|
||||
data: {},
|
||||
loadedTile: {},
|
||||
inflightTile: {},
|
||||
inflightPost: {},
|
||||
closed: {},
|
||||
rtree: new RBush()
|
||||
};
|
||||
},
|
||||
|
||||
loadIssues(projection) {
|
||||
const options = {
|
||||
client: 'iD',
|
||||
status: 'OPEN',
|
||||
zoom: '19' // Use a high zoom so that clusters aren't returned
|
||||
};
|
||||
|
||||
// determine the needed tiles to cover the view
|
||||
const tiles = tiler
|
||||
.zoomExtent([_tileZoom, _tileZoom])
|
||||
.getTiles(projection);
|
||||
|
||||
// abort inflight requests that are no longer needed
|
||||
abortUnwantedRequests(_cache, tiles);
|
||||
|
||||
// issue new requests..
|
||||
tiles.forEach(tile => {
|
||||
if (_cache.loadedTile[tile.id] || _cache.inflightTile[tile.id]) return;
|
||||
|
||||
const [ east, north, west, south ] = tile.extent.rectangle();
|
||||
const params = Object.assign({}, options, { east, south, west, north });
|
||||
|
||||
// 3 separate requests to store for each tile
|
||||
const requests = {};
|
||||
|
||||
Object.keys(_impOsmUrls).forEach(k => {
|
||||
// We exclude WATER from missing geometry as it doesn't seem useful
|
||||
// We use most confident one-way and turn restrictions only, still have false positives
|
||||
const kParams = Object.assign({},
|
||||
params,
|
||||
(k === 'mr') ? { type: 'PARKING,ROAD,BOTH,PATH' } : { confidenceLevel: 'C1' }
|
||||
);
|
||||
const url = `${_impOsmUrls[k]}/search?` + utilQsString(kParams);
|
||||
const controller = new AbortController();
|
||||
|
||||
requests[k] = controller;
|
||||
|
||||
d3_json(url, { signal: controller.signal })
|
||||
.then(data => {
|
||||
delete _cache.inflightTile[tile.id][k];
|
||||
if (!Object.keys(_cache.inflightTile[tile.id]).length) {
|
||||
delete _cache.inflightTile[tile.id];
|
||||
_cache.loadedTile[tile.id] = true;
|
||||
}
|
||||
|
||||
// Road segments at high zoom == oneways
|
||||
if (data.roadSegments) {
|
||||
data.roadSegments.forEach(feature => {
|
||||
// Position error at the approximate middle of the segment
|
||||
const { points, wayId, fromNodeId, toNodeId } = feature;
|
||||
const itemId = `${wayId}${fromNodeId}${toNodeId}`;
|
||||
let mid = points.length / 2;
|
||||
let loc;
|
||||
|
||||
// Even number of points, find midpoint of the middle two
|
||||
// Odd number of points, use position of very middle point
|
||||
if (mid % 1 === 0) {
|
||||
loc = pointAverage([points[mid - 1], points[mid]]);
|
||||
} else {
|
||||
mid = points[Math.floor(mid)];
|
||||
loc = [mid.lon, mid.lat];
|
||||
}
|
||||
|
||||
// One-ways can land on same segment in opposite direction
|
||||
loc = preventCoincident(loc, false);
|
||||
|
||||
let d = new QAItem(loc, this, k, itemId, {
|
||||
issueKey: k, // used as a category
|
||||
identifier: { // used to post changes
|
||||
wayId,
|
||||
fromNodeId,
|
||||
toNodeId
|
||||
},
|
||||
objectId: wayId,
|
||||
objectType: 'way'
|
||||
});
|
||||
|
||||
// Variables used in the description
|
||||
d.replacements = {
|
||||
percentage: feature.percentOfTrips,
|
||||
num_trips: feature.numberOfTrips,
|
||||
highway: linkErrorObject(t('QA.keepRight.error_parts.highway')),
|
||||
from_node: linkEntity('n' + feature.fromNodeId),
|
||||
to_node: linkEntity('n' + feature.toNodeId)
|
||||
};
|
||||
|
||||
_cache.data[d.id] = d;
|
||||
_cache.rtree.insert(encodeIssueRtree(d));
|
||||
});
|
||||
}
|
||||
|
||||
// Tiles at high zoom == missing roads
|
||||
if (data.tiles) {
|
||||
data.tiles.forEach(feature => {
|
||||
const { type, x, y, numberOfTrips } = feature;
|
||||
const geoType = type.toLowerCase();
|
||||
const itemId = `${geoType}${x}${y}${numberOfTrips}`;
|
||||
|
||||
// Average of recorded points should land on the missing geometry
|
||||
// Missing geometry could happen to land on another error
|
||||
let loc = pointAverage(feature.points);
|
||||
loc = preventCoincident(loc, false);
|
||||
|
||||
let d = new QAItem(loc, this, `${k}-${geoType}`, itemId, {
|
||||
issueKey: k,
|
||||
identifier: { x, y }
|
||||
});
|
||||
|
||||
d.replacements = {
|
||||
num_trips: numberOfTrips,
|
||||
geometry_type: t(`QA.improveOSM.geometry_types.${geoType}`)
|
||||
};
|
||||
|
||||
// -1 trips indicates data came from a 3rd party
|
||||
if (numberOfTrips === -1) {
|
||||
d.desc = t('QA.improveOSM.error_types.mr.description_alt', d.replacements);
|
||||
}
|
||||
|
||||
_cache.data[d.id] = d;
|
||||
_cache.rtree.insert(encodeIssueRtree(d));
|
||||
});
|
||||
}
|
||||
|
||||
// Entities at high zoom == turn restrictions
|
||||
if (data.entities) {
|
||||
data.entities.forEach(feature => {
|
||||
const { point, id, segments, numberOfPasses, turnType } = feature;
|
||||
const itemId = `${id.replace(/[,:+#]/g, '_')}`;
|
||||
|
||||
// Turn restrictions could be missing at same junction
|
||||
// We also want to bump the error up so node is accessible
|
||||
const loc = preventCoincident([point.lon, point.lat], true);
|
||||
|
||||
// Elements are presented in a strange way
|
||||
const ids = id.split(',');
|
||||
const from_way = ids[0];
|
||||
const via_node = ids[3];
|
||||
const to_way = ids[2].split(':')[1];
|
||||
|
||||
let d = new QAItem(loc, this, k, itemId, {
|
||||
issueKey: k,
|
||||
identifier: id,
|
||||
objectId: via_node,
|
||||
objectType: 'node'
|
||||
});
|
||||
|
||||
// Travel direction along from_way clarifies the turn restriction
|
||||
const [ p1, p2 ] = segments[0].points;
|
||||
const dir_of_travel = cardinalDirection(relativeBearing(p1, p2));
|
||||
|
||||
// Variables used in the description
|
||||
d.replacements = {
|
||||
num_passed: numberOfPasses,
|
||||
num_trips: segments[0].numberOfTrips,
|
||||
turn_restriction: turnType.toLowerCase(),
|
||||
from_way: linkEntity('w' + from_way),
|
||||
to_way: linkEntity('w' + to_way),
|
||||
travel_direction: dir_of_travel,
|
||||
junction: linkErrorObject(t('QA.keepRight.error_parts.this_node'))
|
||||
};
|
||||
|
||||
_cache.data[d.id] = d;
|
||||
_cache.rtree.insert(encodeIssueRtree(d));
|
||||
dispatch.call('loaded');
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
delete _cache.inflightTile[tile.id][k];
|
||||
if (!Object.keys(_cache.inflightTile[tile.id]).length) {
|
||||
delete _cache.inflightTile[tile.id];
|
||||
_cache.loadedTile[tile.id] = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
_cache.inflightTile[tile.id] = requests;
|
||||
});
|
||||
},
|
||||
|
||||
getComments(item) {
|
||||
// If comments already retrieved no need to do so again
|
||||
if (item.comments) {
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
|
||||
const key = item.issueKey;
|
||||
let qParams = {};
|
||||
|
||||
if (key === 'ow') {
|
||||
qParams = item.identifier;
|
||||
} else if (key === 'mr') {
|
||||
qParams.tileX = item.identifier.x;
|
||||
qParams.tileY = item.identifier.y;
|
||||
} else if (key === 'tr') {
|
||||
qParams.targetId = item.identifier;
|
||||
}
|
||||
|
||||
const url = `${_impOsmUrls[key]}/retrieveComments?` + utilQsString(qParams);
|
||||
const cacheComments = data => {
|
||||
// Assign directly for immediate use afterwards
|
||||
// comments are served newest to oldest
|
||||
item.comments = data.comments ? data.comments.reverse() : [];
|
||||
this.replaceItem(item);
|
||||
};
|
||||
|
||||
return d3_json(url).then(cacheComments).then(() => item);
|
||||
},
|
||||
|
||||
postUpdate(d, callback) {
|
||||
if (!serviceOsm.authenticated()) { // Username required in payload
|
||||
return callback({ message: 'Not Authenticated', status: -3}, d);
|
||||
}
|
||||
if (_cache.inflightPost[d.id]) {
|
||||
return callback({ message: 'Error update already inflight', status: -2 }, d);
|
||||
}
|
||||
|
||||
// Payload can only be sent once username is established
|
||||
serviceOsm.userDetails(sendPayload.bind(this));
|
||||
|
||||
function sendPayload(err, user) {
|
||||
if (err) { return callback(err, d); }
|
||||
|
||||
const key = d.issueKey;
|
||||
const url = `${_impOsmUrls[key]}/comment`;
|
||||
const payload = {
|
||||
username: user.display_name,
|
||||
targetIds: [ d.identifier ]
|
||||
};
|
||||
|
||||
if (d.newStatus) {
|
||||
payload.status = d.newStatus;
|
||||
payload.text = 'status changed';
|
||||
}
|
||||
|
||||
// Comment take place of default text
|
||||
if (d.newComment) {
|
||||
payload.text = d.newComment;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
_cache.inflightPost[d.id] = controller;
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
signal: controller.signal,
|
||||
body: JSON.stringify(payload)
|
||||
};
|
||||
|
||||
d3_json(url, options)
|
||||
.then(() => {
|
||||
delete _cache.inflightPost[d.id];
|
||||
|
||||
// Just a comment, update error in cache
|
||||
if (!d.newStatus) {
|
||||
const now = new Date();
|
||||
let comments = d.comments ? d.comments : [];
|
||||
|
||||
comments.push({
|
||||
username: payload.username,
|
||||
text: payload.text,
|
||||
timestamp: now.getTime() / 1000
|
||||
});
|
||||
|
||||
this.replaceItem(d.update({
|
||||
comments: comments,
|
||||
newComment: undefined
|
||||
}));
|
||||
} else {
|
||||
this.removeItem(d);
|
||||
if (d.newStatus === 'SOLVED') {
|
||||
// Keep track of the number of issues closed per type to tag the changeset
|
||||
if (!(d.issueKey in _cache.closed)) {
|
||||
_cache.closed[d.issueKey] = 0;
|
||||
}
|
||||
_cache.closed[d.issueKey] += 1;
|
||||
}
|
||||
}
|
||||
if (callback) callback(null, d);
|
||||
})
|
||||
.catch(err => {
|
||||
delete _cache.inflightPost[d.id];
|
||||
if (callback) callback(err.message);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Get all cached QAItems covering the viewport
|
||||
getItems(projection) {
|
||||
const viewport = projection.clipExtent();
|
||||
const min = [viewport[0][0], viewport[1][1]];
|
||||
const max = [viewport[1][0], viewport[0][1]];
|
||||
const bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
|
||||
|
||||
return _cache.rtree.search(bbox).map(d => d.data);
|
||||
},
|
||||
|
||||
// Get a QAItem from cache
|
||||
// NOTE: Don't change method name until UI v3 is merged
|
||||
getError(id) {
|
||||
return _cache.data[id];
|
||||
},
|
||||
|
||||
// get the name of the icon to display for this item
|
||||
getIcon(itemType) {
|
||||
return _impOsmData.icons[itemType];
|
||||
},
|
||||
|
||||
// Replace a single QAItem in the cache
|
||||
replaceItem(issue) {
|
||||
if (!(issue instanceof QAItem) || !issue.id) return;
|
||||
|
||||
_cache.data[issue.id] = issue;
|
||||
updateRtree(encodeIssueRtree(issue), true); // true = replace
|
||||
return issue;
|
||||
},
|
||||
|
||||
// Remove a single QAItem from the cache
|
||||
removeItem(issue) {
|
||||
if (!(issue instanceof QAItem) || !issue.id) return;
|
||||
|
||||
delete _cache.data[issue.id];
|
||||
updateRtree(encodeIssueRtree(issue), false); // false = remove
|
||||
},
|
||||
|
||||
// Used to populate `closed:improveosm:*` changeset tags
|
||||
getClosedCounts() {
|
||||
return _cache.closed;
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import serviceKeepRight from './keepRight';
|
||||
import serviceImproveOSM from './improveOSM';
|
||||
import serviceOsmose from './osmose';
|
||||
import serviceMapillary from './mapillary';
|
||||
import serviceMapRules from './maprules';
|
||||
@@ -20,7 +19,6 @@ import serviceMapilio from './mapilio';
|
||||
export let services = {
|
||||
geocoder: serviceNominatim,
|
||||
keepRight: serviceKeepRight,
|
||||
improveOSM: serviceImproveOSM,
|
||||
osmose: serviceOsmose,
|
||||
mapillary: serviceMapillary,
|
||||
nsi: serviceNsi,
|
||||
@@ -39,7 +37,6 @@ export let services = {
|
||||
|
||||
export {
|
||||
serviceKeepRight,
|
||||
serviceImproveOSM,
|
||||
serviceOsmose,
|
||||
serviceMapillary,
|
||||
serviceMapRules,
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
import _throttle from 'lodash-es/throttle';
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { modeBrowse } from '../modes/browse';
|
||||
import { svgPointTransform } from './helpers';
|
||||
import { services } from '../services';
|
||||
|
||||
let _layerEnabled = false;
|
||||
let _qaService;
|
||||
|
||||
export function svgImproveOSM(projection, context, dispatch) {
|
||||
const throttledRedraw = _throttle(() => dispatch.call('change'), 1000);
|
||||
const minZoom = 12;
|
||||
|
||||
let touchLayer = d3_select(null);
|
||||
let drawLayer = d3_select(null);
|
||||
let layerVisible = false;
|
||||
|
||||
function markerPath(selection, klass) {
|
||||
selection
|
||||
.attr('class', klass)
|
||||
.attr('transform', 'translate(-10, -28)')
|
||||
.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');
|
||||
}
|
||||
|
||||
// Loosely-coupled improveOSM service for fetching issues
|
||||
function getService() {
|
||||
if (services.improveOSM && !_qaService) {
|
||||
_qaService = services.improveOSM;
|
||||
_qaService.on('loaded', throttledRedraw);
|
||||
} else if (!services.improveOSM && _qaService) {
|
||||
_qaService = null;
|
||||
}
|
||||
|
||||
return _qaService;
|
||||
}
|
||||
|
||||
// Show the markers
|
||||
function editOn() {
|
||||
if (!layerVisible) {
|
||||
layerVisible = true;
|
||||
drawLayer
|
||||
.style('display', 'block');
|
||||
}
|
||||
}
|
||||
|
||||
// Immediately remove the markers and their touch targets
|
||||
function editOff() {
|
||||
if (layerVisible) {
|
||||
layerVisible = false;
|
||||
drawLayer
|
||||
.style('display', 'none');
|
||||
drawLayer.selectAll('.qaItem.improveOSM')
|
||||
.remove();
|
||||
touchLayer.selectAll('.qaItem.improveOSM')
|
||||
.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Enable the layer. This shows the markers and transitions them to visible.
|
||||
function layerOn() {
|
||||
editOn();
|
||||
|
||||
drawLayer
|
||||
.style('opacity', 0)
|
||||
.transition()
|
||||
.duration(250)
|
||||
.style('opacity', 1)
|
||||
.on('end interrupt', () => dispatch.call('change'));
|
||||
}
|
||||
|
||||
// Disable the layer. This transitions the layer invisible and then hides the markers.
|
||||
function layerOff() {
|
||||
throttledRedraw.cancel();
|
||||
drawLayer.interrupt();
|
||||
touchLayer.selectAll('.qaItem.improveOSM')
|
||||
.remove();
|
||||
|
||||
drawLayer
|
||||
.transition()
|
||||
.duration(250)
|
||||
.style('opacity', 0)
|
||||
.on('end interrupt', () => {
|
||||
editOff();
|
||||
dispatch.call('change');
|
||||
});
|
||||
}
|
||||
|
||||
// Update the issue markers
|
||||
function updateMarkers() {
|
||||
if (!layerVisible || !_layerEnabled) return;
|
||||
|
||||
const service = getService();
|
||||
const selectedID = context.selectedErrorID();
|
||||
const data = (service ? service.getItems(projection) : []);
|
||||
const getTransform = svgPointTransform(projection);
|
||||
|
||||
// Draw markers..
|
||||
const markers = drawLayer.selectAll('.qaItem.improveOSM')
|
||||
.data(data, d => d.id);
|
||||
|
||||
// exit
|
||||
markers.exit()
|
||||
.remove();
|
||||
|
||||
// enter
|
||||
const markersEnter = markers.enter()
|
||||
.append('g')
|
||||
.attr('class', d => `qaItem ${d.service} itemId-${d.id} itemType-${d.itemType}`);
|
||||
|
||||
markersEnter
|
||||
.append('polygon')
|
||||
.call(markerPath, 'shadow');
|
||||
|
||||
markersEnter
|
||||
.append('ellipse')
|
||||
.attr('cx', 0)
|
||||
.attr('cy', 0)
|
||||
.attr('rx', 4.5)
|
||||
.attr('ry', 2)
|
||||
.attr('class', 'stroke');
|
||||
|
||||
markersEnter
|
||||
.append('polygon')
|
||||
.attr('fill', 'currentColor')
|
||||
.call(markerPath, 'qaItem-fill');
|
||||
|
||||
markersEnter
|
||||
.append('use')
|
||||
.attr('class', 'icon-annotation')
|
||||
.attr('transform', 'translate(-6, -22)')
|
||||
.attr('width', '12px')
|
||||
.attr('height', '12px')
|
||||
.attr('xlink:href', d => d.icon ? '#' + d.icon : '');
|
||||
|
||||
// update
|
||||
markers
|
||||
.merge(markersEnter)
|
||||
.sort(sortY)
|
||||
.classed('selected', d => d.id === selectedID)
|
||||
.attr('transform', getTransform);
|
||||
|
||||
|
||||
// Draw targets..
|
||||
if (touchLayer.empty()) return;
|
||||
const fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
|
||||
|
||||
const targets = touchLayer.selectAll('.qaItem.improveOSM')
|
||||
.data(data, d => d.id);
|
||||
|
||||
// exit
|
||||
targets.exit()
|
||||
.remove();
|
||||
|
||||
// enter/update
|
||||
targets.enter()
|
||||
.append('rect')
|
||||
.attr('width', '20px')
|
||||
.attr('height', '30px')
|
||||
.attr('x', '-10px')
|
||||
.attr('y', '-28px')
|
||||
.merge(targets)
|
||||
.sort(sortY)
|
||||
.attr('class', d => `qaItem ${d.service} target ${fillClass} itemId-${d.id}`)
|
||||
.attr('transform', getTransform);
|
||||
|
||||
function sortY(a, b) {
|
||||
return (a.id === selectedID) ? 1
|
||||
: (b.id === selectedID) ? -1
|
||||
: b.loc[1] - a.loc[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the ImproveOSM layer and schedule loading issues and updating markers.
|
||||
function drawImproveOSM(selection) {
|
||||
const service = getService();
|
||||
|
||||
const surface = context.surface();
|
||||
if (surface && !surface.empty()) {
|
||||
touchLayer = surface.selectAll('.data-layer.touch .layer-touch.markers');
|
||||
}
|
||||
|
||||
drawLayer = selection.selectAll('.layer-improveOSM')
|
||||
.data(service ? [0] : []);
|
||||
|
||||
drawLayer.exit()
|
||||
.remove();
|
||||
|
||||
drawLayer = drawLayer.enter()
|
||||
.append('g')
|
||||
.attr('class', 'layer-improveOSM')
|
||||
.style('display', _layerEnabled ? 'block' : 'none')
|
||||
.merge(drawLayer);
|
||||
|
||||
if (_layerEnabled) {
|
||||
if (service && ~~context.map().zoom() >= minZoom) {
|
||||
editOn();
|
||||
service.loadIssues(projection);
|
||||
updateMarkers();
|
||||
} else {
|
||||
editOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Toggles the layer on and off
|
||||
drawImproveOSM.enabled = function(val) {
|
||||
if (!arguments.length) return _layerEnabled;
|
||||
|
||||
_layerEnabled = val;
|
||||
if (_layerEnabled) {
|
||||
layerOn();
|
||||
} else {
|
||||
layerOff();
|
||||
if (context.selectedErrorID()) {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
}
|
||||
|
||||
dispatch.call('change');
|
||||
return this;
|
||||
};
|
||||
|
||||
drawImproveOSM.supported = () => !!getService();
|
||||
|
||||
return drawImproveOSM;
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import { svgLocalPhotos} from './local_photos';
|
||||
import { svgDebug } from './debug';
|
||||
import { svgGeolocate } from './geolocate';
|
||||
import { svgKeepRight } from './keepRight';
|
||||
import { svgImproveOSM } from './improveOSM';
|
||||
import { svgOsmose } from './osmose';
|
||||
import { svgStreetside } from './streetside';
|
||||
import { svgVegbilder} from './vegbilder';
|
||||
@@ -31,7 +30,6 @@ export function svgLayers(projection, context) {
|
||||
{ id: 'notes', layer: svgNotes(projection, context, dispatch) },
|
||||
{ id: 'data', layer: svgData(projection, context, dispatch) },
|
||||
{ id: 'keepRight', layer: svgKeepRight(projection, context, dispatch) },
|
||||
{ id: 'improveOSM', layer: svgImproveOSM(projection, context, dispatch) },
|
||||
{ id: 'osmose', layer: svgOsmose(projection, context, dispatch) },
|
||||
{ id: 'streetside', layer: svgStreetside(projection, context, dispatch)},
|
||||
{ id: 'mapillary', layer: svgMapillaryImages(projection, context, dispatch) },
|
||||
|
||||
@@ -27,7 +27,6 @@ var readOnlyTags = [
|
||||
/^resolved:/,
|
||||
/^closed:note$/,
|
||||
/^closed:keepright$/,
|
||||
/^closed:improveosm:/,
|
||||
/^closed:osmose:/
|
||||
];
|
||||
|
||||
@@ -155,12 +154,6 @@ export function uiCommit(context) {
|
||||
tags['closed:keepright'] = context.cleanTagValue(krClosed.join(';'));
|
||||
}
|
||||
}
|
||||
if (services.improveOSM) {
|
||||
var iOsmClosed = services.improveOSM.getClosedCounts();
|
||||
for (itemType in iOsmClosed) {
|
||||
tags['closed:improveosm:' + itemType] = context.cleanTagValue(iOsmClosed[itemType].toString());
|
||||
}
|
||||
}
|
||||
if (services.osmose) {
|
||||
var osmoseClosed = services.osmose.getClosedCounts();
|
||||
for (itemType in osmoseClosed) {
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { t, localizer } from '../core/localizer';
|
||||
import { svgIcon } from '../svg/icon';
|
||||
import { services } from '../services';
|
||||
|
||||
export function uiImproveOsmComments() {
|
||||
let _qaItem;
|
||||
|
||||
function issueComments(selection) {
|
||||
// make the div immediately so it appears above the buttons
|
||||
let comments = selection.selectAll('.comments-container')
|
||||
.data([0]);
|
||||
|
||||
comments = comments.enter()
|
||||
.append('div')
|
||||
.attr('class', 'comments-container')
|
||||
.merge(comments);
|
||||
|
||||
// must retrieve comments from API before they can be displayed
|
||||
services.improveOSM.getComments(_qaItem)
|
||||
.then(d => {
|
||||
if (!d.comments) return; // nothing to do here
|
||||
|
||||
const commentEnter = comments.selectAll('.comment')
|
||||
.data(d.comments)
|
||||
.enter()
|
||||
.append('div')
|
||||
.attr('class', 'comment');
|
||||
|
||||
commentEnter
|
||||
.append('div')
|
||||
.attr('class', 'comment-avatar')
|
||||
.call(svgIcon('#iD-icon-avatar', 'comment-avatar-icon'));
|
||||
|
||||
const mainEnter = commentEnter
|
||||
.append('div')
|
||||
.attr('class', 'comment-main');
|
||||
|
||||
const metadataEnter = mainEnter
|
||||
.append('div')
|
||||
.attr('class', 'comment-metadata');
|
||||
|
||||
metadataEnter
|
||||
.append('div')
|
||||
.attr('class', 'comment-author')
|
||||
.each(function(d) {
|
||||
const osm = services.osm;
|
||||
let selection = d3_select(this);
|
||||
if (osm && d.username) {
|
||||
selection = selection
|
||||
.append('a')
|
||||
.attr('class', 'comment-author-link')
|
||||
.attr('href', osm.userURL(d.username))
|
||||
.attr('target', '_blank');
|
||||
}
|
||||
selection
|
||||
.text(d => d.username);
|
||||
});
|
||||
|
||||
metadataEnter
|
||||
.append('div')
|
||||
.attr('class', 'comment-date')
|
||||
.html(d => t.html('note.status.commented', { when: localeDateString(d.timestamp) }));
|
||||
|
||||
mainEnter
|
||||
.append('div')
|
||||
.attr('class', 'comment-text')
|
||||
.append('p')
|
||||
.text(d => d.text);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err); // eslint-disable-line no-console
|
||||
});
|
||||
}
|
||||
|
||||
function localeDateString(s) {
|
||||
if (!s) return null;
|
||||
const options = { day: 'numeric', month: 'short', year: 'numeric' };
|
||||
const d = new Date(s * 1000); // timestamp is served in seconds, date takes ms
|
||||
if (isNaN(d.getTime())) return null;
|
||||
return d.toLocaleDateString(localizer.localeCode(), options);
|
||||
}
|
||||
|
||||
issueComments.issue = function(val) {
|
||||
if (!arguments.length) return _qaItem;
|
||||
_qaItem = val;
|
||||
return issueComments;
|
||||
};
|
||||
|
||||
return issueComments;
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import {
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
|
||||
import { presetManager } from '../presets';
|
||||
import { modeSelect } from '../modes/select';
|
||||
import { t } from '../core/localizer';
|
||||
import { utilDisplayName, utilHighlightEntities, utilEntityRoot } from '../util';
|
||||
|
||||
export function uiImproveOsmDetails(context) {
|
||||
let _qaItem;
|
||||
|
||||
|
||||
function issueDetail(d) {
|
||||
if (d.desc) return d.desc;
|
||||
const issueKey = d.issueKey;
|
||||
d.replacements = d.replacements || {};
|
||||
d.replacements.default = { html: t.html('inspector.unknown') }; // special key `default` works as a fallback string
|
||||
return t.html(`QA.improveOSM.error_types.${issueKey}.description`, d.replacements);
|
||||
}
|
||||
|
||||
|
||||
function improveOsmDetails(selection) {
|
||||
const details = selection.selectAll('.error-details')
|
||||
.data(
|
||||
(_qaItem ? [_qaItem] : []),
|
||||
d => `${d.id}-${d.status || 0}`
|
||||
);
|
||||
|
||||
details.exit()
|
||||
.remove();
|
||||
|
||||
const detailsEnter = details.enter()
|
||||
.append('div')
|
||||
.attr('class', 'error-details qa-details-container');
|
||||
|
||||
|
||||
// description
|
||||
const descriptionEnter = detailsEnter
|
||||
.append('div')
|
||||
.attr('class', 'qa-details-subsection');
|
||||
|
||||
descriptionEnter
|
||||
.append('h4')
|
||||
.call(t.append('QA.keepRight.detail_description'));
|
||||
|
||||
descriptionEnter
|
||||
.append('div')
|
||||
.attr('class', 'qa-details-description-text')
|
||||
.html(issueDetail);
|
||||
|
||||
// If there are entity links in the error message..
|
||||
let relatedEntities = [];
|
||||
descriptionEnter.selectAll('.error_entity_link, .error_object_link')
|
||||
.attr('href', '#')
|
||||
.each(function() {
|
||||
const link = d3_select(this);
|
||||
const isObjectLink = link.classed('error_object_link');
|
||||
const entityID = isObjectLink ?
|
||||
(utilEntityRoot(_qaItem.objectType) + _qaItem.objectId)
|
||||
: this.textContent;
|
||||
const entity = context.hasEntity(entityID);
|
||||
|
||||
relatedEntities.push(entityID);
|
||||
|
||||
// Add click handler
|
||||
link
|
||||
.on('mouseenter', () => {
|
||||
utilHighlightEntities([entityID], true, context);
|
||||
})
|
||||
.on('mouseleave', () => {
|
||||
utilHighlightEntities([entityID], false, context);
|
||||
})
|
||||
.on('click', (d3_event) => {
|
||||
d3_event.preventDefault();
|
||||
|
||||
utilHighlightEntities([entityID], false, context);
|
||||
|
||||
const osmlayer = context.layers().layer('osm');
|
||||
if (!osmlayer.enabled()) {
|
||||
osmlayer.enabled(true);
|
||||
}
|
||||
|
||||
context.map().centerZoom(_qaItem.loc, 20);
|
||||
|
||||
if (entity) {
|
||||
context.enter(modeSelect(context, [entityID]));
|
||||
} else {
|
||||
context.loadEntity(entityID, (err, result) => {
|
||||
if (err) return;
|
||||
const entity = result.data.find(e => e.id === entityID);
|
||||
if (entity) context.enter(modeSelect(context, [entityID]));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Replace with friendly name if possible
|
||||
// (The entity may not yet be loaded into the graph)
|
||||
if (entity) {
|
||||
let name = utilDisplayName(entity); // try to use common name
|
||||
|
||||
if (!name && !isObjectLink) {
|
||||
const preset = presetManager.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
|
||||
}
|
||||
|
||||
improveOsmDetails.issue = function(val) {
|
||||
if (!arguments.length) return _qaItem;
|
||||
_qaItem = val;
|
||||
return improveOsmDetails;
|
||||
};
|
||||
|
||||
return improveOsmDetails;
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { t } from '../core/localizer';
|
||||
import { services } from '../services';
|
||||
import { modeBrowse } from '../modes/browse';
|
||||
import { svgIcon } from '../svg/icon';
|
||||
|
||||
import { uiImproveOsmComments } from './improveOSM_comments';
|
||||
import { uiImproveOsmDetails } from './improveOSM_details';
|
||||
import { uiImproveOsmHeader } from './improveOSM_header';
|
||||
|
||||
import { utilNoAuto, utilRebind } from '../util';
|
||||
|
||||
export function uiImproveOsmEditor(context) {
|
||||
const dispatch = d3_dispatch('change');
|
||||
const qaDetails = uiImproveOsmDetails(context);
|
||||
const qaComments = uiImproveOsmComments(context);
|
||||
const qaHeader = uiImproveOsmHeader(context);
|
||||
|
||||
let _qaItem;
|
||||
|
||||
function improveOsmEditor(selection) {
|
||||
|
||||
const headerEnter = selection.selectAll('.header')
|
||||
.data([0])
|
||||
.enter()
|
||||
.append('div')
|
||||
.attr('class', 'header fillL');
|
||||
|
||||
headerEnter
|
||||
.append('button')
|
||||
.attr('class', 'close')
|
||||
.attr('title', t('icons.close'))
|
||||
.on('click', () => context.enter(modeBrowse(context)))
|
||||
.call(svgIcon('#iD-icon-close'));
|
||||
|
||||
headerEnter
|
||||
.append('h2')
|
||||
.call(t.append('QA.improveOSM.title'));
|
||||
|
||||
let body = selection.selectAll('.body')
|
||||
.data([0]);
|
||||
|
||||
body = body.enter()
|
||||
.append('div')
|
||||
.attr('class', 'body')
|
||||
.merge(body);
|
||||
|
||||
const editor = body.selectAll('.qa-editor')
|
||||
.data([0]);
|
||||
|
||||
editor.enter()
|
||||
.append('div')
|
||||
.attr('class', 'modal-section qa-editor')
|
||||
.merge(editor)
|
||||
.call(qaHeader.issue(_qaItem))
|
||||
.call(qaDetails.issue(_qaItem))
|
||||
.call(qaComments.issue(_qaItem))
|
||||
.call(improveOsmSaveSection);
|
||||
}
|
||||
|
||||
function improveOsmSaveSection(selection) {
|
||||
const isSelected = (_qaItem && _qaItem.id === context.selectedErrorID());
|
||||
const isShown = (_qaItem && (isSelected || _qaItem.newComment || _qaItem.comment));
|
||||
let saveSection = selection.selectAll('.qa-save')
|
||||
.data(
|
||||
(isShown ? [_qaItem] : []),
|
||||
d => `${d.id}-${d.status || 0}`
|
||||
);
|
||||
|
||||
// exit
|
||||
saveSection.exit()
|
||||
.remove();
|
||||
|
||||
// enter
|
||||
const saveSectionEnter = saveSection.enter()
|
||||
.append('div')
|
||||
.attr('class', 'qa-save save-section cf');
|
||||
|
||||
saveSectionEnter
|
||||
.append('h4')
|
||||
.attr('class', '.qa-save-header')
|
||||
.call(t.append('note.newComment'));
|
||||
|
||||
saveSectionEnter
|
||||
.append('textarea')
|
||||
.attr('class', 'new-comment-input')
|
||||
.attr('placeholder', t('QA.keepRight.comment_placeholder'))
|
||||
.attr('maxlength', 1000)
|
||||
.property('value', d => d.newComment)
|
||||
.call(utilNoAuto)
|
||||
.on('input', changeInput)
|
||||
.on('blur', changeInput);
|
||||
|
||||
// update
|
||||
saveSection = saveSectionEnter
|
||||
.merge(saveSection)
|
||||
.call(qaSaveButtons);
|
||||
|
||||
function changeInput() {
|
||||
const input = d3_select(this);
|
||||
let val = input.property('value').trim();
|
||||
|
||||
if (val === '') {
|
||||
val = undefined;
|
||||
}
|
||||
|
||||
// store the unsaved comment with the issue itself
|
||||
_qaItem = _qaItem.update({ newComment: val });
|
||||
|
||||
const qaService = services.improveOSM;
|
||||
if (qaService) {
|
||||
qaService.replaceItem(_qaItem);
|
||||
}
|
||||
|
||||
saveSection
|
||||
.call(qaSaveButtons);
|
||||
}
|
||||
}
|
||||
|
||||
function qaSaveButtons(selection) {
|
||||
const isSelected = (_qaItem && _qaItem.id === context.selectedErrorID());
|
||||
let buttonSection = selection.selectAll('.buttons')
|
||||
.data((isSelected ? [_qaItem] : []), d => d.status + d.id);
|
||||
|
||||
// exit
|
||||
buttonSection.exit()
|
||||
.remove();
|
||||
|
||||
// enter
|
||||
const buttonEnter = buttonSection.enter()
|
||||
.append('div')
|
||||
.attr('class', 'buttons');
|
||||
|
||||
buttonEnter
|
||||
.append('button')
|
||||
.attr('class', 'button comment-button action')
|
||||
.call(t.append('QA.keepRight.save_comment'));
|
||||
|
||||
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('.comment-button')
|
||||
.attr('disabled', d => d.newComment ? null : true)
|
||||
.on('click.comment', function(d3_event, d) {
|
||||
this.blur(); // avoid keeping focus on the button - #4641
|
||||
const qaService = services.improveOSM;
|
||||
if (qaService) {
|
||||
qaService.postUpdate(d, (err, item) => dispatch.call('change', item));
|
||||
}
|
||||
});
|
||||
|
||||
buttonSection.select('.close-button')
|
||||
.html(d => {
|
||||
const andComment = (d.newComment ? '_comment' : '');
|
||||
return t.html(`QA.keepRight.close${andComment}`);
|
||||
})
|
||||
.on('click.close', function(d3_event, d) {
|
||||
this.blur(); // avoid keeping focus on the button - #4641
|
||||
const qaService = services.improveOSM;
|
||||
if (qaService) {
|
||||
d.newStatus = 'SOLVED';
|
||||
qaService.postUpdate(d, (err, item) => dispatch.call('change', item));
|
||||
}
|
||||
});
|
||||
|
||||
buttonSection.select('.ignore-button')
|
||||
.html(d => {
|
||||
const andComment = (d.newComment ? '_comment' : '');
|
||||
return t.html(`QA.keepRight.ignore${andComment}`);
|
||||
})
|
||||
.on('click.ignore', function(d3_event, d) {
|
||||
this.blur(); // avoid keeping focus on the button - #4641
|
||||
const qaService = services.improveOSM;
|
||||
if (qaService) {
|
||||
d.newStatus = 'INVALID';
|
||||
qaService.postUpdate(d, (err, item) => dispatch.call('change', item));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE: Don't change method name until UI v3 is merged
|
||||
improveOsmEditor.error = function(val) {
|
||||
if (!arguments.length) return _qaItem;
|
||||
_qaItem = val;
|
||||
return improveOsmEditor;
|
||||
};
|
||||
|
||||
return utilRebind(improveOsmEditor, dispatch, 'on');
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { t } from '../core/localizer';
|
||||
|
||||
|
||||
export function uiImproveOsmHeader() {
|
||||
let _qaItem;
|
||||
|
||||
|
||||
function issueTitle(d) {
|
||||
const issueKey = d.issueKey;
|
||||
d.replacements = d.replacements || {};
|
||||
d.replacements.default = { html: t.html('inspector.unknown') }; // special key `default` works as a fallback string
|
||||
return t.html(`QA.improveOSM.error_types.${issueKey}.title`, d.replacements);
|
||||
}
|
||||
|
||||
|
||||
function improveOsmHeader(selection) {
|
||||
const header = selection.selectAll('.qa-header')
|
||||
.data(
|
||||
(_qaItem ? [_qaItem] : []),
|
||||
d => `${d.id}-${d.status || 0}`
|
||||
);
|
||||
|
||||
header.exit()
|
||||
.remove();
|
||||
|
||||
const headerEnter = header.enter()
|
||||
.append('div')
|
||||
.attr('class', 'qa-header');
|
||||
|
||||
const svgEnter = headerEnter
|
||||
.append('div')
|
||||
.attr('class', 'qa-header-icon')
|
||||
.classed('new', d => d.id < 0)
|
||||
.append('svg')
|
||||
.attr('width', '20px')
|
||||
.attr('height', '30px')
|
||||
.attr('viewbox', '0 0 20 30')
|
||||
.attr('class', d => `preset-icon-28 qaItem ${d.service} itemId-${d.id} itemType-${d.itemType}`);
|
||||
|
||||
svgEnter
|
||||
.append('polygon')
|
||||
.attr('fill', 'currentColor')
|
||||
.attr('class', 'qaItem-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', '12px')
|
||||
.attr('height', '12px')
|
||||
.attr('transform', 'translate(4, 5.5)')
|
||||
.attr('xlink:href', d => d.icon ? '#' + d.icon : '');
|
||||
|
||||
headerEnter
|
||||
.append('div')
|
||||
.attr('class', 'qa-header-label')
|
||||
.html(issueTitle);
|
||||
}
|
||||
|
||||
improveOsmHeader.issue = function(val) {
|
||||
if (!arguments.length) return _qaItem;
|
||||
_qaItem = val;
|
||||
return improveOsmHeader;
|
||||
};
|
||||
|
||||
return improveOsmHeader;
|
||||
}
|
||||
@@ -23,10 +23,6 @@ export { uiFlash } from './flash';
|
||||
export { uiFormFields } from './form_fields';
|
||||
export { uiFullScreen } from './full_screen';
|
||||
export { uiGeolocate } from './geolocate';
|
||||
export { uiImproveOsmComments } from './improveOSM_comments';
|
||||
export { uiImproveOsmDetails } from './improveOSM_details';
|
||||
export { uiImproveOsmEditor } from './improveOSM_editor';
|
||||
export { uiImproveOsmHeader } from './improveOSM_header';
|
||||
export { uiInfo } from './info';
|
||||
export { uiInspector } from './inspector';
|
||||
export { uiIssuesInfo } from './issues_info';
|
||||
|
||||
@@ -128,7 +128,7 @@ export function uiSectionDataLayers(context) {
|
||||
}
|
||||
|
||||
function drawQAItems(selection) {
|
||||
var qaKeys = ['keepRight', 'improveOSM', 'osmose'];
|
||||
var qaKeys = ['keepRight', 'osmose'];
|
||||
var qaLayers = layers.all().filter(function(obj) { return qaKeys.indexOf(obj.id) !== -1; });
|
||||
|
||||
var ul = selection
|
||||
|
||||
@@ -12,7 +12,6 @@ import { services } from '../services';
|
||||
import { uiDataEditor } from './data_editor';
|
||||
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';
|
||||
@@ -23,7 +22,6 @@ export function uiSidebar(context) {
|
||||
var inspector = uiInspector(context);
|
||||
var dataEditor = uiDataEditor(context);
|
||||
var noteEditor = uiNoteEditor(context);
|
||||
var improveOsmEditor = uiImproveOsmEditor(context);
|
||||
var keepRightEditor = uiKeepRightEditor(context);
|
||||
var osmoseEditor = uiOsmoseEditor(context);
|
||||
var _current;
|
||||
@@ -211,10 +209,8 @@ export function uiSidebar(context) {
|
||||
var errEditor;
|
||||
if (datum.service === 'keepRight') {
|
||||
errEditor = keepRightEditor;
|
||||
} else if (datum.service === 'osmose') {
|
||||
errEditor = osmoseEditor;
|
||||
} else {
|
||||
errEditor = improveOsmEditor;
|
||||
errEditor = osmoseEditor;
|
||||
}
|
||||
|
||||
context.container().selectAll('.qaItem.' + datum.service)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="right-long" class="svg-inline--fa fa-right-long" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M334.5 414c8.8 3.8 19 2 26-4.6l144-136c4.8-4.5 7.5-10.8 7.5-17.4s-2.7-12.9-7.5-17.4l-144-136c-7-6.6-17.2-8.4-26-4.6s-14.5 12.5-14.5 22l0 72L32 192c-17.7 0-32 14.3-32 32l0 64c0 17.7 14.3 32 32 32l288 0 0 72c0 9.6 5.7 18.2 14.5 22z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 461 B |
@@ -26,25 +26,24 @@ describe('iD.svgLayers', function () {
|
||||
it('creates default data layers', function () {
|
||||
container.call(iD.svgLayers(projection, context));
|
||||
var nodes = container.selectAll('svg .data-layer').nodes();
|
||||
expect(nodes.length).to.eql(18);
|
||||
expect(nodes.length).to.eql(17);
|
||||
expect(d3.select(nodes[0]).classed('osm')).to.be.true;
|
||||
expect(d3.select(nodes[1]).classed('notes')).to.be.true;
|
||||
expect(d3.select(nodes[2]).classed('data')).to.be.true;
|
||||
expect(d3.select(nodes[3]).classed('keepRight')).to.be.true;
|
||||
expect(d3.select(nodes[4]).classed('improveOSM')).to.be.true;
|
||||
expect(d3.select(nodes[5]).classed('osmose')).to.be.true;
|
||||
expect(d3.select(nodes[6]).classed('streetside')).to.be.true;
|
||||
expect(d3.select(nodes[7]).classed('mapillary')).to.be.true;
|
||||
expect(d3.select(nodes[8]).classed('mapillary-position')).to.be.true;
|
||||
expect(d3.select(nodes[9]).classed('mapillary-map-features')).to.be.true;
|
||||
expect(d3.select(nodes[10]).classed('mapillary-signs')).to.be.true;
|
||||
expect(d3.select(nodes[11]).classed('kartaview')).to.be.true;
|
||||
expect(d3.select(nodes[12]).classed('mapilio')).to.be.true;
|
||||
expect(d3.select(nodes[13]).classed('vegbilder')).to.be.true;
|
||||
expect(d3.select(nodes[14]).classed('local-photos')).to.be.true;
|
||||
expect(d3.select(nodes[15]).classed('debug')).to.be.true;
|
||||
expect(d3.select(nodes[16]).classed('geolocate')).to.be.true;
|
||||
expect(d3.select(nodes[17]).classed('touch')).to.be.true;
|
||||
expect(d3.select(nodes[4]).classed('osmose')).to.be.true;
|
||||
expect(d3.select(nodes[5]).classed('streetside')).to.be.true;
|
||||
expect(d3.select(nodes[6]).classed('mapillary')).to.be.true;
|
||||
expect(d3.select(nodes[7]).classed('mapillary-position')).to.be.true;
|
||||
expect(d3.select(nodes[8]).classed('mapillary-map-features')).to.be.true;
|
||||
expect(d3.select(nodes[9]).classed('mapillary-signs')).to.be.true;
|
||||
expect(d3.select(nodes[10]).classed('kartaview')).to.be.true;
|
||||
expect(d3.select(nodes[11]).classed('mapilio')).to.be.true;
|
||||
expect(d3.select(nodes[12]).classed('vegbilder')).to.be.true;
|
||||
expect(d3.select(nodes[13]).classed('local-photos')).to.be.true;
|
||||
expect(d3.select(nodes[14]).classed('debug')).to.be.true;
|
||||
expect(d3.select(nodes[15]).classed('geolocate')).to.be.true;
|
||||
expect(d3.select(nodes[16]).classed('touch')).to.be.true;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user