From 14586110223a20c5b7b057899db2c23a4eb3b250 Mon Sep 17 00:00:00 2001 From: Neil Rotstan Date: Tue, 13 Nov 2018 11:54:24 -0800 Subject: [PATCH] Add expert augmented diff and attic query controls * Add new control for users in expert mode to each history entry in task history table for viewing an augmented diff between two entries or attic query data for a single entry * Selecting two separate entries will show an augmented diff of the changes between those entries in Achavi in a new browser tab * Selecting the same entry twice will perform an attic query and open the data in either JOSM (if the user has selected it as their editor) or Overpass Turbo in a new browser tab * Reorganize some of the projectController bbox code into separate methods to facilitate easier reuse --- client/app/project/project.controller.js | 202 +++++++++++++++++++---- client/app/project/project.html | 5 +- client/assets/styles/sass/_project.scss | 38 +++++ client/taskingmanager.config.json | 2 + 4 files changed, 217 insertions(+), 30 deletions(-) diff --git a/client/app/project/project.controller.js b/client/app/project/project.controller.js index 95daa4d2d8..153fb38238 100644 --- a/client/app/project/project.controller.js +++ b/client/app/project/project.controller.js @@ -79,6 +79,9 @@ vm.showLicenseModal = false; vm.lockingReason = ''; + // Augmented diff or attic query selection + vm.selectedItem = null; + //interval timer promise for autorefresh var autoRefresh = undefined; @@ -1195,15 +1198,27 @@ } /** - * View OSM changesets by getting the bounding box, transforming the coordinates to WGS84 and passing it to OSM + * Get a basic bounding box for the selected task */ - vm.viewOSMChangesets = function () { + vm.taskBBox = function() { var taskId = vm.selectedTaskData.taskId; var features = vm.taskVectorLayer.getSource().getFeatures(); var selectedFeature = taskService.getTaskFeatureById(features, taskId); - var bbox = selectedFeature.getGeometry().getExtent(); - var bboxTransformed = geospatialService.transformExtentToLatLonString(bbox); - $window.open('http://www.openstreetmap.org/history?bbox=' + bboxTransformed); + return selectedFeature.getGeometry().getExtent(); + } + + /** + * Get the task bounding box, transforming the coordinates to WGS84. + */ + vm.osmBBox = function() { + return geospatialService.transformExtentToLatLonString(vm.taskBBox()); + } + + /** + * View OSM changesets by getting the bounding box, transforming the coordinates to WGS84 and passing it to OSM + */ + vm.viewOSMChangesets = function () { + $window.open('http://www.openstreetmap.org/history?bbox=' + vm.osmBBox()); }; /** @@ -1213,31 +1228,19 @@ var queryPrefix = ''; var querySuffix = ''; var queryMiddle = ''; + // Get the bbox of the task - var taskId = vm.selectedTaskData.taskId; - var features = vm.taskVectorLayer.getSource().getFeatures(); - var selectedFeature = taskService.getTaskFeatureById(features, taskId); - var extent = selectedFeature.getGeometry().getExtent(); - var bboxArray = geospatialService.transformExtentToLatLonArray(extent); - var bbox = 'w="' + bboxArray[0] + '" s="' + bboxArray[1] + '" e="' + bboxArray[2] + '" n="' + bboxArray[3] + '"'; - // Loop through the history and get a unique list of users to pass to Overpass Turbo - var userList = []; - var history = vm.selectedTaskData.taskHistory; - if (history) { - for (var i = 0; i < history.length; i++) { - var user = history[i].actionBy; - var indexInArray = userList.indexOf(user); - if (user && indexInArray == -1) { - // user existing and not found in user list yet - var userQuery = - '' + - '' + - ''; - queryMiddle = queryMiddle + userQuery; - userList.push(user); - } - } - } + var bboxArray = vm.overpassBBox(); + var bbox = 's="' + bboxArray[0] + '" w="' + bboxArray[1] + '" n="' + bboxArray[2] + '" e="' + bboxArray[3] + '"'; + + // Add (bounded) work by participating users to query + vm.participantUsernames().forEach(function(user) { + queryMiddle = queryMiddle + + '' + + '' + + ''; + }); + var query = queryPrefix + queryMiddle + querySuffix; $window.open('http://overpass-turbo.eu/map.html?Q=' + encodeURIComponent(query)); }; @@ -1359,6 +1362,147 @@ } }; + /** + * Get the task bounding box, transforming to SWNE (min lat, min lon, max lat, max lon) array + * as preferred by Overpass. + */ + vm.overpassBBox = function() { + var arrayBBox = geospatialService.transformExtentToLatLonArray(vm.taskBBox()); + + // Transform WSEN to SWNE that Overpass prefers + return [arrayBBox[1], arrayBBox[0], arrayBBox[3], arrayBBox[2]]; + } + + /** + * Returns an array of unique usernames of users who appear in the selected task history. + */ + vm.participantUsernames = function() { + // Loop through the history and get a unique list of users to pass to Overpass Turbo + var userList = []; + var history = vm.selectedTaskData.taskHistory; + if (history) { + for (var i = 0; i < history.length; i++) { + var username = history[i].actionBy; + if (username && userList.indexOf(username) === -1) { + // user existing and not found in user list yet + userList.push(username); + } + } + } + + return userList; + } + + /** + * Returns a Moment instance representing the action date of the given + * item. For non-locking actions, the timestamp is offset by the + * configured `atticQueryOffsetMinutes` number of minutes. No offset + * is applied for locking actions. + * + * @param atticDate + */ + vm.offsetAtticDateMoment = function (item) { + if (item.action === 'LOCKED_FOR_MAPPING' || item.action === 'LOCKED_FOR_VALIDATION') { + return moment.utc(item.actionDate); + } + + return moment.utc(item.actionDate).add(configService.atticQueryOffsetMinutes, 'minutes'); + } + + /** + * Returns true if the given attic date, once offset by the configured + * `atticQueryOffsetMinutes` number of minutes, represents a date in + * the past that can be queried. Returns false if the date is still in + * the future. + * @param atticDate + */ + vm.isAtticDateLive = function (item) { + return vm.offsetAtticDateMoment(item).isSameOrBefore(moment()); + } + + /** + * View the task AOI as of the given date via Overpass attic query. + * @param atticDate + */ + vm.viewAtticOverpass = function (item) { + var adjustedDateString = vm.offsetAtticDateMoment(item).toISOString(); + var bbox = vm.overpassBBox().join(','); + var query = + '[out:xml][timeout:25][bbox:' + bbox + '][date:"' + adjustedDateString + '"];' + + '( node(' + bbox + '); <; >; );' + + 'out meta;'; + + + // Try sending to JOSM if it's user's chosen editor, otherwise Overpass Turbo. + var overpassApiURL = 'https://overpass-api.de/api/interpreter?data=' + encodeURIComponent(query); + if (vm.selectedEditor === 'josm') { + editorService.sendJOSMCmd('http://127.0.0.1:8111/import', { + new_layer: 'true', + layer_name: adjustedDateString, + layer_locked: 'true', + url: encodeURIComponent(overpassApiURL) + } + ).catch(function() { + //warn that JSOM couldn't be contacted + vm.editorStartError = 'josm-error'; + }); + } + else { + var overpassTurboURL = 'https://overpass-turbo.eu/map.html?Q=' + encodeURIComponent(query); + $window.open(overpassTurboURL); + } + }; + + /** + * Selects the given item for augmented diffing. If it's the first item, + * it becomes selected; if it's the second item, a diff is kicked off. If the + * same item is clicked twice, then an attic query is run for the item. + */ + vm.selectItemForDiff = function(item) { + if (vm.selectedItem === null) { + vm.selectedItem = item; + } + else { + if (vm.selectedItem !== item) { + vm.viewAugmentedDiff(vm.selectedItem, item); + } + else { + vm.viewAtticOverpass(item); + } + + vm.selectedItem = null; + } + } + + /** + * View augmented diff in achavi of the task AOI for the two given items + * @param firstItem + * @param secondItem + */ + vm.viewAugmentedDiff = function (firstItem, secondItem) { + // order firstItem and secondItem into earlierItem and laterItem + var earlierItem = firstItem; + var laterItem = secondItem; + + if (moment.utc(firstItem.actionDate).isAfter(moment.utc(secondItem.actionDate))) { + earlierItem = secondItem; + laterItem = firstItem; + } + + var bbox = vm.overpassBBox().join(','); + var query = + '[out:xml][timeout:25][bbox:' + bbox + ']' + + '[adiff:"' + moment.utc(earlierItem.actionDate).toISOString() + '","' + + vm.offsetAtticDateMoment(laterItem).toISOString() + '"];' + + '( node(' + bbox + '); <; >; );' + + 'out meta geom qt;'; + + // Send users to achavi for visualization of augmented diff + var overpassURL = 'https://overpass-api.de/api/interpreter?data=' + encodeURIComponent(query); + var achaviURL = 'https://overpass-api.de/achavi/?url=' + encodeURIComponent(overpassURL); + $window.open(achaviURL); + }; + /** * Set the accept license modal to visible/invisible * @param showModal diff --git a/client/app/project/project.html b/client/app/project/project.html index d0355be899..77ea41e192 100644 --- a/client/app/project/project.html +++ b/client/app/project/project.html @@ -1017,6 +1017,9 @@
{{ 'History' | translate }}
+ + + @@ -1183,4 +1186,4 @@

{{ projectCtrl.license.name }}

{{ 'Dismiss' | translate }} - \ No newline at end of file + diff --git a/client/assets/styles/sass/_project.scss b/client/assets/styles/sass/_project.scss index 0f9f49b0e4..ca9103424b 100644 --- a/client/assets/styles/sass/_project.scss +++ b/client/assets/styles/sass/_project.scss @@ -115,6 +115,44 @@ } a { color: $link-color; + + &.diff-control { + display: inline-block; + margin-left: 0.5em; + border: 2px solid $orange; + border-radius: 4px; + + &:before { + content: '\B1'; + display: inline-block; + text-align: center; + width: 1.5em; + background-color: $orange; + color: white; + transition: all 0.3s ease 0s; + } + + &.selected { + border-color: darken($link-color, 13%); + + &:before { + content: '\2713'; + border-radius: 4px; + color: darken($link-color, 13%); + background-color: white; + } + } + + &:hover { + text-decoration: none; + + &:before { + border-radius: 4px; + color: $orange; + background-color: white; + } + } + } } } diff --git a/client/taskingmanager.config.json b/client/taskingmanager.config.json index b913435cc7..37389de42f 100644 --- a/client/taskingmanager.config.json +++ b/client/taskingmanager.config.json @@ -26,6 +26,7 @@ "imagerySet": "Aerial" } ], + "atticQueryOffsetMinutes": 10, "maxCommentLength": 5000, "maxChatLength": 5000 } @@ -58,6 +59,7 @@ "imagerySet": "Aerial" } ], + "atticQueryOffsetMinutes": 10, "maxCommentLength": 5000, "maxChatLength": 5000 }