Skip to content

Commit 918e81d

Browse files
committedNov 27, 2023
Live-show changed areas as user edits them
cf #69
1 parent 19df996 commit 918e81d

8 files changed

+100
-48
lines changed
 

‎web/src/App.svelte

+7-1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@
6464
function updateLayers() {
6565
mapC.updateLayers();
6666
}
67+
function updateBoundaryLayer() {
68+
mapC.updateBoundaryLayer();
69+
}
6770
function recentreMap() {
6871
mapC.recentre();
6972
}
@@ -74,7 +77,10 @@
7477
<MapC bind:this={mapC} bind:offcentre bind:activeLayer bind:opacity />
7578

7679
<div id="other-content-container">
77-
<LeftSidebar on:changeScenario={updateScenario} />
80+
<LeftSidebar
81+
on:changeScenario={updateScenario}
82+
on:updateBoundaryLayer={updateBoundaryLayer}
83+
/>
7884

7985
<div id="recentre">
8086
{#if offcentre}

‎web/src/lib/LeftSidebar.svelte

+3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@
6868
dispatch("changeScenario");
6969
}}
7070
on:import={handleImportEvent}
71+
on:changesUpdated={() => {
72+
dispatch("updateBoundaryLayer");
73+
}}
7174
/>
7275
{:else if selectedTab === "import"}
7376
<Import on:import={handleImportEvent} />

‎web/src/lib/MapC.svelte

+19-14
Original file line numberDiff line numberDiff line change
@@ -313,10 +313,10 @@
313313
// Generate the LineString layer showing the boundary of the changed
314314
// areas.
315315
const diffedBoundaries = getInputDiffBoundaries(
316-
$allScenarios.get($scenarioName),
316+
$allScenarios.get($scenarioName).changes,
317317
$compareScenarioName === null
318318
? null
319-
: $allScenarios.get($compareScenarioName)
319+
: $allScenarios.get($compareScenarioName).changes
320320
);
321321
map.addSource("boundary", {
322322
type: "geojson",
@@ -360,6 +360,21 @@
360360
}
361361
}
362362
363+
export function updateBoundaryLayer() {
364+
// Update the LineString layer showing the boundary of the changed
365+
// areas.
366+
const diffedBoundaries = getInputDiffBoundaries(
367+
$allScenarios.get($scenarioName).changes,
368+
$compareScenarioName === null
369+
? null
370+
: $allScenarios.get($compareScenarioName).changes
371+
);
372+
const boundarySource = map.getSource(
373+
"boundary"
374+
) as maplibregl.GeoJSONSource;
375+
boundarySource.setData(diffedBoundaries);
376+
}
377+
363378
// Update layer styles. This is quite a general function --- it updates the
364379
// fill colours and opacity again according to the underlying data as well
365380
// as the opacity slider.
@@ -380,17 +395,8 @@
380395
}
381396
map.setPaintProperty("line-layer", "line-opacity", opacity);
382397
}
383-
// Update the LineString layer
384-
const diffedBoundaries = getInputDiffBoundaries(
385-
$allScenarios.get($scenarioName),
386-
$compareScenarioName === null
387-
? null
388-
: $allScenarios.get($compareScenarioName)
389-
);
390-
const boundarySource = map.getSource(
391-
"boundary"
392-
) as maplibregl.GeoJSONSource;
393-
boundarySource.setData(diffedBoundaries);
398+
// Update boundary layer.
399+
updateBoundaryLayer();
394400
// Update the click popup if necessary. This bit is required because
395401
// the click popup contains e.g. indicator values
396402
if (clickPopup !== null) {
@@ -457,7 +463,6 @@
457463
}
458464
}
459465
460-
// Declare variables for $: block
461466
let oldClickedIds: number[] = [];
462467
let clickedIds: number[] = [];
463468
$: {

‎web/src/lib/leftSidebar/Create.svelte

+15-14
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import ErrorScreen from "src/lib/reusable/ErrorScreen.svelte";
77
import {
88
type ValuesObject,
9-
type ScenarioChanges,
109
type ScenarioObject,
1110
type Scenario,
1211
config,
@@ -42,8 +41,6 @@
4241
let userChangesPromptText: string =
4342
"Are you sure you want to go back? All changes will be lost.";
4443
let userChangesPresent: boolean = false;
45-
// Changes that user has made relative to baseline
46-
let changes: ScenarioChanges = new Map();
4744
4845
// Deselect OAs if the user navigates away
4946
onDestroy(() => {
@@ -53,10 +50,12 @@
5350
function returnToSelection() {
5451
if (userChangesPresent) {
5552
if (window.confirm(userChangesPromptText)) {
56-
changes = new Map();
5753
userChangesPresent = false;
5854
$clickedOAs = [];
5955
step = "choose";
56+
// Reset scenario name to default
57+
$scenarioName = config.referenceScenarioObject.metadata.name;
58+
dispatch("changeScenario");
6059
}
6160
} else {
6261
$clickedOAs = [];
@@ -68,7 +67,6 @@
6867
dispatch("changeScenario", {});
6968
step = "modify"; // move on to the next step
7069
$clickedOAs = []; // deselect any OAs
71-
changes = new Map($allScenarios.get($scenarioName).changes);
7270
}
7371
7472
function handleResult(values: ValuesObject, changesJson: string) {
@@ -100,10 +98,8 @@
10098
newScenario.metadata.name = `${newScenario.metadata.name}_${i}`;
10199
}
102100
$allScenarios.set(newScenario.metadata.name, newScenario);
103-
// Get rid of changes; this also ensures that the "are you
104-
// sure" confirmation prompt doesn't show up.
101+
// Suppress "are you sure" confirmation prompt
105102
userChangesPresent = false;
106-
changes = new Map();
107103
// Display the new scenario on the map.
108104
dispatch("import", { name: newScenario.metadata.name });
109105
}
@@ -135,10 +131,12 @@
135131
136132
function acceptChangesAndCalculate() {
137133
const changedJson = JSON.stringify({
138-
scenario_json: toChangesObject(changes),
134+
scenario_json: toChangesObject(
135+
$allScenarios.get($scenarioName).changes
136+
),
139137
});
138+
console.log("changedJson", changedJson);
140139
step = "calc"; // move on
141-
changes = new Map(); // reset changes
142140
$clickedOAs = []; // deselect any OAs
143141
144142
// Create a new Controller each time the button is pressed
@@ -162,9 +160,10 @@
162160
.catch((err) => handleError(err));
163161
} else if (runner === "azure" || runner === "local") {
164162
// REST API
165-
const url = runner === "azure"
166-
? config.webApiUrl // deployed to Azure
167-
: "/api/"; // Docker, or local dev: this is a proxy to the backend on localhost:5174
163+
const url =
164+
runner === "azure"
165+
? config.webApiUrl // deployed to Azure
166+
: "/api/"; // Docker, or local dev: this is a proxy to the backend on localhost:5174
168167
fetch(url, {
169168
method: "POST",
170169
headers: { "Content-Type": "application/json" },
@@ -186,8 +185,10 @@ Create your own scenario by modifying an existing one.
186185
{#if step === "modify"}
187186
<ModifyOutputAreas
188187
bind:userChangesPresent
189-
bind:changes
190188
on:returnToSelection={returnToSelection}
189+
on:changesUpdated={() => {
190+
dispatch("changesUpdated");
191+
}}
191192
on:proceedToMetadata={() => (step = "metadata")}
192193
/>
193194
{/if}

‎web/src/lib/leftSidebar/create/ChooseStartingScenario.svelte

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
<script lang="ts">
22
import { createEventDispatcher } from "svelte";
3-
import { allScenarios, scenarioName, compareScenarioName } from "src/stores";
3+
import {
4+
allScenarios,
5+
scenarioName,
6+
compareScenarioName,
7+
} from "src/stores";
8+
import { copyScenario } from "src/utils/scenarios";
49
const dispatch = createEventDispatcher();
510
dispatch("changeScenario", {});
611
712
function setScenario(event: Event) {
813
let button = event.target as HTMLButtonElement;
9-
$scenarioName = button.value;
14+
const customScenarioName = "custom_in_progress";
15+
$allScenarios.set(
16+
customScenarioName,
17+
copyScenario($allScenarios.get(button.value))
18+
);
19+
$scenarioName = customScenarioName;
1020
$compareScenarioName = null;
1121
dispatch("changeScenario", {});
1222
}
@@ -16,7 +26,9 @@
1626

1727
<div id="starting-scenario-buttons">
1828
{#each [...$allScenarios.entries()] as [name, scenario]}
19-
<button value={name} on:click={setScenario}>{scenario.metadata.long}</button>
29+
<button value={name} on:click={setScenario}
30+
>{scenario.metadata.long}</button
31+
>
2032
{/each}
2133
</div>
2234

‎web/src/lib/leftSidebar/create/ModifyOutputAreas.svelte

+13-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { onMount, onDestroy } from "svelte";
77
import {
88
allScenarios,
9+
scenarioName,
910
customScenarioInProgress,
1011
clickedOAs,
1112
} from "src/stores";
@@ -16,7 +17,8 @@
1617
} from "src/data/config";
1718
1819
// The actual changes
19-
export let changes: ScenarioChanges;
20+
export let changes: ScenarioChanges =
21+
$allScenarios.get($scenarioName).changes;
2022
// Flag to determine whether changes were made relative to the scenario the
2123
// user started from.
2224
export let userChangesPresent: boolean;
@@ -257,7 +259,12 @@
257259
changes.set(oa.name, userSetChanges);
258260
});
259261
}
262+
// TODO REMOVE LOGGING
263+
console.log("changes");
260264
logChanges(changes);
265+
console.log("allScenarios.get('baseline').changes");
266+
logChanges($allScenarios.get("baseline").changes);
267+
dispatch("changesUpdated");
261268
userChangesPresent = true;
262269
}
263270
@@ -304,7 +311,6 @@
304311
305312
// Determine what should be shown in the UI based on the current changes of
306313
// all the clicked OAs.
307-
// TODO: Fix ugly code repetition (!)
308314
function loadOAChangesToUI() {
309315
console.log("loadOAChangesToUI");
310316
@@ -449,12 +455,13 @@
449455
if (sigModified && sigState.kind !== "MultipleDifferent") {
450456
// Box was ticked, there is a single underlying signature -- set it
451457
sig = referenceSig;
452-
}
453-
else if (sigModified && sigState.kind === "MultipleDifferent") {
458+
} else if (
459+
sigModified &&
460+
sigState.kind === "MultipleDifferent"
461+
) {
454462
// Box was ticked, but there are multiple underlying signatures
455463
sig = null;
456-
}
457-
else {
464+
} else {
458465
// Box was unticked
459466
sig = null;
460467
}

‎web/src/utils/geojson.ts

+7-10
Original file line numberDiff line numberDiff line change
@@ -134,28 +134,25 @@ function mapsAreEqual<K, V>(m1: Map<K, V>, m2: Map<K, V>): boolean {
134134
* collating the areas where the values differ, and then performing a union of
135135
* these areas.
136136
*
137-
* @param scenarioName First scenario
138-
* @param compareScenarioName Second scenario
137+
* @param changes Changes from the first scenario
138+
* @param compareChanges Changes from the second scenario
139139
* @returns A GeoJSON FeatureCollection containing the boundaries of the areas
140140
* as a MultiPolygon.
141141
*/
142142
export function getInputDiffBoundaries(
143-
scenario: Scenario,
144-
compareScenario: Scenario | null
143+
changes: ScenarioChanges,
144+
compareChanges: ScenarioChanges | null
145145
): GeoJSON.FeatureCollection {
146-
const changes: ScenarioChanges = scenario.changes;
147-
const cChanges: ScenarioChanges = compareScenario === null
148-
? new Map()
149-
: compareScenario.changes;
146+
if (compareChanges === null) compareChanges = new Map();
150147

151148
// Determine OAs which are different
152149
const allPossibleOAs: Set<string> = new Set([
153-
...changes.keys(), ...cChanges.keys()
150+
...changes.keys(), ...compareChanges.keys()
154151
]);
155152
const differentOAs: Set<string> = new Set();
156153
for (const oa of allPossibleOAs) {
157154
const m1 = changes.get(oa);
158-
const m2 = cChanges.get(oa);
155+
const m2 = compareChanges.get(oa);
159156
if (m1 === undefined && m2 === undefined) {
160157
// Both undefined - no changes occurred wrt baseline
161158
continue;

‎web/src/utils/scenarios.ts

+21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
type LayerName, type MacroVar,
3+
type ScenarioMetadata,
34
type ScenarioChanges, type ScenarioValues, type Scenario,
45
type ChangesObject, type ValuesObject, type ScenarioObject,
56
type ScaleFactorMap,
@@ -224,3 +225,23 @@ export function toScenarioObject(
224225
values: toValuesObject(scenario.values, scaleFactors)
225226
};
226227
}
228+
229+
export function copyScenario(
230+
scenario: Scenario,
231+
): Scenario {
232+
const metadata = {
233+
name: scenario.metadata.name,
234+
short: scenario.metadata.short,
235+
long: scenario.metadata.long,
236+
description: scenario.metadata.description
237+
}
238+
const changes = new Map();
239+
for (const [oa, oaMap] of scenario.changes.entries()) {
240+
changes.set(oa, new Map(oaMap));
241+
}
242+
const values = new Map();
243+
for (const [oa, oaMap] of scenario.values.entries()) {
244+
values.set(oa, new Map(oaMap));
245+
}
246+
return { metadata, changes, values };
247+
}

0 commit comments

Comments
 (0)