From cb28c3b496d4f7a318ea8a2a28a87e683df0f016 Mon Sep 17 00:00:00 2001 From: Craig de Stigter Date: Fri, 8 May 2020 07:44:03 +1200 Subject: [PATCH] Fix HTML diff output Generate it from the `geojson` diff rather than the `json` one, because the `json` one now uses hexWKB geometries. Because the layout's a bit different, this means the JS in the HTML template needs to do a bit more work, but it's quite achievable. Yay for ES7 --- sno/diff-view.html | 75 +++++++++++++++++++++++++++++++--------------- sno/diff_output.py | 43 ++++++++++++++++---------- 2 files changed, 79 insertions(+), 39 deletions(-) diff --git a/sno/diff-view.html b/sno/diff-view.html index e56d3ffa5..9679d8577 100644 --- a/sno/diff-view.html +++ b/sno/diff-view.html @@ -125,8 +125,8 @@ }).addTo(map) var layerGroup = L.featureGroup() - for (let [dataset, diff] of Object.entries(DATA['sno.diff/v1+hexwkb'])) { - if (!diff.featureChanges.length) { + for (let [dataset, diff] of Object.entries(DATA)) { + if (!diff.features.length) { continue } @@ -139,16 +139,18 @@ layers[dataset] = {} featureMap[dataset] = {} - for (let change of diff.featureChanges) { - const fOld = change['-'] - const fNew = change['+'] - if (fOld && fNew) { - fc['updateOld'].push(fOld) - fc['updateNew'].push(fNew) - } else if (fOld) { - fc['delete'].push(fOld) - } else if (fNew) { - fc['insert'].push(fNew) + for (let change of diff.features) { + const id = change['id'] + if (id.startsWith('U+::')) { + fc['updateNew'].push(change) + } else if (id.startsWith('U-::')) { + fc['updateOld'].push(change) + } else if (id.startsWith('D::')) { + fc['delete'].push(change) + } else if (id.startsWith('I::')) { + fc['insert'].push(change) + } else { + console.log('unknown ID format: ' + id) } } @@ -224,14 +226,40 @@ } selectedFeature = [row, f] } + function getFeaturesByRealId(diff) { + let featuresByRealId = {} + for (let fc of diff.features) { + const id = fc['id'] + const realId = id.split('::')[1] + if (id.startsWith('U')) { + if (!featuresByRealId[realId]) { + featuresByRealId[realId] = [null, null] + } + if (id.startsWith('U+::')) { + // 'new' + featuresByRealId[realId][1] = fc + } else { + // 'old' + featuresByRealId[realId][0] = fc + } + } else if (id.startsWith('D::')) { + featuresByRealId[realId] = [fc, null] + } else if (id.startsWith('I::')) { + featuresByRealId[realId] = [fc, null] + } else { + console.log('unknown ID format: ' + id) + continue + } + } + return featuresByRealId + } - function getSchema(diff) { + function getSchema(featuresByRealId) { // if schema has changed then it'll show up in every single feature let oldSchema = null let newSchema = null - for (let change of diff.featureChanges) { - const fOld = change['-'] - const fNew = change['+'] + for (let [realId, change] of Object.entries(featuresByRealId)) { + let [fOld, fNew] = change if (fOld && !oldSchema) { oldSchema = Object.keys(fOld.properties) oldSchema.splice(0, 0, GEOM) @@ -273,8 +301,8 @@ return isArrayEqual(a.coordinates, b.coordinates) } - for (let [dataset, diff] of Object.entries(DATA['sno.diff/v1+hexwkb'])) { - if (!diff.featureChanges.length) { + for (let [dataset, diff] of Object.entries(DATA)) { + if (!diff.features.length) { continue } @@ -288,7 +316,8 @@ let thead = table.createTHead() let row = thead.insertRow() - let schema = getSchema(diff) + let features = getFeaturesByRealId(diff) + let schema = getSchema(features) for (let col of schema) { let th = document.createElement("th") @@ -300,10 +329,9 @@ } let tbody = table.createTBody() - for (let fc of diff.featureChanges) { - let fOld = fc['-'] - let fNew = fc['+'] - let change; + for (let [realId, fc] of Object.entries(features)) { + let [fOld, fNew] = fc + let change if (fOld && fNew) { change = 'update' } else if (fOld) { @@ -311,7 +339,6 @@ } else { change = 'insert' } - fOld = fOld || {properties: {}} fNew = fNew || {properties: {}} let oldRow = document.createElement('tr') diff --git a/sno/diff_output.py b/sno/diff_output.py index 5429aa31e..fec460398 100644 --- a/sno/diff_output.py +++ b/sno/diff_output.py @@ -1,13 +1,14 @@ import contextlib import io import json +import os import string import sys +import tempfile import webbrowser from pathlib import Path import click -from osgeo import ogr from . import gpkg from .output_util import dump_json_output, resolve_output_path @@ -154,7 +155,7 @@ def repr_row(row, prefix="", exclude=None): @contextlib.contextmanager -def diff_output_geojson(*, output_path, dataset_count, json_style, **kwargs): +def diff_output_geojson(*, output_path, dataset_count, json_style='pretty', **kwargs): """ Contextmanager. @@ -196,6 +197,8 @@ def diff_output_geojson(*, output_path, dataset_count, json_style, **kwargs): def _out(dataset, diff): if not output_path or output_path == '-': fp = sys.stdout + elif isinstance(output_path, io.StringIO): + fp = output_path elif output_path.is_dir(): fp = (output_path / f"{dataset.name}.geojson").open("w") else: @@ -343,13 +346,6 @@ def diff_output_html(*, output_path, repo, base, target, dataset_count, **kwargs raise click.BadParameter( "Directory is not valid for --output with --html", param_hint="--output" ) - - json_data = io.StringIO() - with diff_output_json( - output_path=json_data, dataset_count=dataset_count - ) as json_writer: - yield json_writer - with open( Path(__file__).resolve().with_name("diff-view.html"), "r", encoding="utf8" ) as ft: @@ -357,13 +353,30 @@ def diff_output_html(*, output_path, repo, base, target, dataset_count, **kwargs title = f"{Path(repo.path).name}: {base.short_id} .. {target.short_id if target else 'working-copy'}" - if not output_path: - output_path = Path(repo.path) / "DIFF.html" - fo = resolve_output_path(output_path) + with tempfile.TemporaryDirectory() as tempdir: + tempdir = Path(tempdir) + # Write a bunch of geojson files to a temporary directory + with diff_output_geojson( + output_path=tempdir, dataset_count=dataset_count, json_style="extracompact", + ) as json_writer: + yield json_writer - fo.write( - template.substitute({"title": title, "geojson_data": json_data.getvalue()}) - ) + if not output_path: + output_path = Path(repo.path) / "DIFF.html" + fo = resolve_output_path(output_path) + + # Read all the geojson back in, and stick them in a dict + all_datasets_geojson = {} + for filename in os.listdir(tempdir): + with open(tempdir / filename) as json_file: + all_datasets_geojson[os.path.splitext(filename)[0]] = json.load( + json_file + ) + fo.write( + template.substitute( + {"title": title, "geojson_data": json.dumps(all_datasets_geojson)} + ) + ) if fo != sys.stdout: fo.close() webbrowser.open_new(f"file://{output_path.resolve()}")