aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/mrCoopDiff.js91
-rw-r--r--www/mrPreview.html110
2 files changed, 201 insertions, 0 deletions
diff --git a/bin/mrCoopDiff.js b/bin/mrCoopDiff.js
new file mode 100755
index 0000000..080aa28
--- /dev/null
+++ b/bin/mrCoopDiff.js
@@ -0,0 +1,91 @@
+#!/usr/bin/env node
+
+/**
+ * Takes a MapRoulette Cooperative Challenge file and creates a JSON with before and after for visualisation
+ */
+
+const fs = require('fs')
+const { Readable, Transform, pipeline } = require('stream')
+const ndjson = require('ndjson')
+const cloneDeep = require('clone-deep')
+
+const argv = require('yargs/yargs')(process.argv.slice(2))
+ .argv
+
+if (argv._.length < 2) {
+ console.error("Usage: ./mrCoopDiff.js input.json output.json")
+ process.exit(1)
+}
+
+const inputFile = argv._[0]
+const outputFile = argv._[1]
+
+if (!fs.existsSync(inputFile)) {
+ console.error(`${inputFile} not found`)
+ process.exit(1)
+}
+
+let challengeCount = 0
+const features = {}
+
+const applyOperations = new Transform({
+ readableObjectMode: true,
+ writableObjectMode: true,
+ transform(challenge, encoding, callback) {
+ challengeCount++
+
+ if (!argv.quiet) {
+ if (process.stdout.isTTY && challengeCount % 1000 === 0) {
+ process.stdout.write(` ${challengeCount.toLocaleString()}\r`)
+ }
+ }
+
+ for (const feature of challenge.features) {
+ const key = `${feature.properties['@type']}/${feature.properties['@id']}`
+ if (key in features) {
+ console.log(`${key} was found a second time`)
+ }
+
+ features[key] = {
+ before: feature
+ }
+ }
+
+ if (challenge && challenge.cooperativeWork && challenge.cooperativeWork.operations) {
+ for (const operation of challenge.cooperativeWork.operations) {
+ if (operation.operationType === 'modifyElement' && operation.data) {
+ const id = operation.data.id
+ if (id in features) {
+ const afterFeature = cloneDeep(features[id].before)
+ for (const featureOperation of operation.data.operations) {
+ if (featureOperation.operation === 'setTags') {
+ for (const [key, value] of Object.entries(featureOperation.data)) {
+ afterFeature.properties[key] = value
+ }
+ }
+ }
+ features[id].after = afterFeature
+ }
+ }
+ }
+ }
+
+ callback()
+ }
+})
+
+console.log('Converting challenges into diff previews')
+pipeline(
+ fs.createReadStream(inputFile),
+ ndjson.parse(),
+ applyOperations,
+ err => {
+ if (err) {
+ console.log(err)
+ process.exit(1)
+ } else {
+ fs.writeFileSync(outputFile, JSON.stringify(features))
+ process.exit(0)
+ }
+ }
+)
diff --git a/www/mrPreview.html b/www/mrPreview.html
new file mode 100644
index 0000000..8fcc3fa
--- /dev/null
+++ b/www/mrPreview.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset='utf-8' />
+ <title>MapRoulette Preview</title>
+ <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
+ <style>
+ body { margin:0; padding:0; }
+ #map { position:absolute; top:0; bottom:0; width:100%; }
+
+ tr:nth-child(odd) {
+ background-color: #eee
+ }
+ tr:nth-child(even) {
+ background-color: #ddd;
+ }
+
+ .modify {
+ background-color: orange;
+ }
+ .add {
+ background-color: lightgreen;
+ }
+ .remove {
+ background-color: red;
+ }
+ .row {
+ margin: 10px;
+ border-width: 1px;
+ border-color: black;
+ border-style: solid;
+ }
+ </style>
+</head>
+<body>
+ <div id="content"></div>
+<script>
+ fetch('changes.json')
+ .then(res => res.json())
+ .then(changes => {
+ renderChanges(changes)
+ })
+ function renderChanges(changes) {
+ const content = document.getElementById('content')
+ for (const [id, change] of Object.entries(changes)) {
+ const header = document.createElement('h2')
+ header.textContent = id
+ content.appendChild(header)
+
+ const table = document.createElement('table')
+
+ // table header
+ const headerRow = document.createElement('tr')
+
+ const headerTag = document.createElement('th')
+ headerTag.textContent = 'Tag'
+ headerRow.appendChild(headerTag)
+
+ const headerBefore = document.createElement('th')
+ headerBefore.textContent = 'Before'
+ headerRow.appendChild(headerBefore)
+
+ const headerAfter = document.createElement('th')
+ headerAfter.textContent = 'After'
+ headerRow.appendChild(headerAfter)
+
+ table.appendChild(headerRow)
+
+ // properties
+ const distinctKeys = [...new Set([...Object.keys(change.before.properties), ...Object.keys(change.after.properties)])]
+ for (const key of distinctKeys) {
+ const tr = document.createElement('tr')
+
+ const tdKey = document.createElement('td')
+ tdKey.textContent = key
+
+ const tdBefore = document.createElement('td')
+ const beforeValue = change.before.properties[key]
+ tdBefore.textContent = beforeValue
+
+ const tdAfter = document.createElement('td')
+ const afterValue = change.after.properties[key]
+ tdAfter.textContent = afterValue
+
+ if (beforeValue && afterValue && beforeValue !== afterValue) {
+ tdBefore.classList.add('modify')
+ tdAfter.classList.add('modify')
+ }
+
+ if (!beforeValue && afterValue) {
+ tdAfter.classList.add('add')
+ }
+
+ if (beforeValue && !afterValue) {
+ tdAfter.classList.add('remove')
+ }
+
+ tr.appendChild(tdKey)
+ tr.appendChild(tdBefore)
+ tr.appendChild(tdAfter)
+
+ table.appendChild(tr)
+ }
+
+ content.appendChild(table)
+ }
+ }
+</script>
+</body>
+</html>