mirror of
https://github.com/ANL-CEEESA/RELOG.git
synced 2025-12-06 07:48:50 -06:00
Casebuilder: Add MapBlock; fix zip paths
This commit is contained in:
1065
relog-web/package-lock.json
generated
1065
relog-web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,9 +13,11 @@
|
|||||||
"@testing-library/react": "^12.1.4",
|
"@testing-library/react": "^12.1.4",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"ajv": "^8.11.0",
|
"ajv": "^8.11.0",
|
||||||
"d3": "^7.3.0",
|
"d3": "^5.16.0",
|
||||||
|
"d3-array": "^2.12.1",
|
||||||
"dagre": "^0.8.5",
|
"dagre": "^0.8.5",
|
||||||
"idb": "^6.1.5",
|
"idb": "^6.1.5",
|
||||||
|
"leaflet": "^1.8.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-flow-renderer": "^9.7.4",
|
"react-flow-renderer": "^9.7.4",
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>RELOG</title>
|
<title>RELOG</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -13,6 +13,7 @@ import PlantBlock from "./PlantBlock";
|
|||||||
import ProductBlock from "./ProductBlock";
|
import ProductBlock from "./ProductBlock";
|
||||||
import { validate } from "./validate";
|
import { validate } from "./validate";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
|
import { SERVER_URL } from "..";
|
||||||
|
|
||||||
const setDefaults = (actualDict, defaultDict) => {
|
const setDefaults = (actualDict, defaultDict) => {
|
||||||
for (const [key, defaultValue] of Object.entries(defaultDict)) {
|
for (const [key, defaultValue] of Object.entries(defaultDict)) {
|
||||||
@@ -309,13 +310,13 @@ const InputPage = () => {
|
|||||||
if (valid) {
|
if (valid) {
|
||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/submit", {
|
const response = await fetch(`${SERVER_URL}/submit`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(exported),
|
body: JSON.stringify(exported),
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
history.push(`/solver/${data.job_id}`);
|
history.push(`solver/${data.job_id}`);
|
||||||
} else {
|
} else {
|
||||||
throw "Error";
|
throw "Error";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,3 +100,10 @@ body {
|
|||||||
#messageTray .message button:active {
|
#messageTray .message button:active {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nodata {
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px 0;
|
||||||
|
color: #888;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import InputPage from "./casebuilder/InputPage";
|
|||||||
import SolverPage from "./solver/SolverPage";
|
import SolverPage from "./solver/SolverPage";
|
||||||
import { Route, BrowserRouter, Switch, Redirect } from "react-router-dom";
|
import { Route, BrowserRouter, Switch, Redirect } from "react-router-dom";
|
||||||
|
|
||||||
|
export const SERVER_URL = "";
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { useEffect } from "react";
|
|||||||
import Section from "../common/Section";
|
import Section from "../common/Section";
|
||||||
import Card from "../common/Card";
|
import Card from "../common/Card";
|
||||||
import styles from "./FilesBlock.module.css";
|
import styles from "./FilesBlock.module.css";
|
||||||
import { useRef } from "react";
|
import { SERVER_URL } from "..";
|
||||||
|
|
||||||
const FilesBlock = (props) => {
|
const FilesBlock = (props) => {
|
||||||
const [filesFound, setFilesFound] = useState(false);
|
const [filesFound, setFilesFound] = useState(false);
|
||||||
|
|
||||||
const fetchFiles = async () => {
|
const fetchFiles = async () => {
|
||||||
const response = await fetch(`/jobs/${props.job}/output.json`);
|
const response = await fetch(`${SERVER_URL}/jobs/${props.job}/output.json`);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setFilesFound(true);
|
setFilesFound(true);
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,6 @@ const FilesBlock = (props) => {
|
|||||||
// Fetch files periodically from the server
|
// Fetch files periodically from the server
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchFiles();
|
fetchFiles();
|
||||||
console.log(filesFound);
|
|
||||||
if (!filesFound) {
|
if (!filesFound) {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
fetchFiles();
|
fetchFiles();
|
||||||
@@ -27,11 +26,11 @@ const FilesBlock = (props) => {
|
|||||||
}
|
}
|
||||||
}, [filesFound]);
|
}, [filesFound]);
|
||||||
|
|
||||||
let content = <div className={styles.nodata}>No files available</div>;
|
let content = <div className="nodata">No files available</div>;
|
||||||
if (filesFound) {
|
if (filesFound) {
|
||||||
content = (
|
content = (
|
||||||
<div className={styles.files}>
|
<div className={styles.files}>
|
||||||
<a href={`/jobs/${props.job}/output.zip`}>output.zip</a>
|
<a href={`${SERVER_URL}/jobs/${props.job}/output.zip`}>output.zip</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import Section from "../common/Section";
|
|||||||
import Card from "../common/Card";
|
import Card from "../common/Card";
|
||||||
import styles from "./LogBlock.module.css";
|
import styles from "./LogBlock.module.css";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
|
import { SERVER_URL } from "..";
|
||||||
|
|
||||||
const LogBlock = (props) => {
|
const LogBlock = (props) => {
|
||||||
const [log, setLog] = useState();
|
const [log, setLog] = useState();
|
||||||
const preRef = useRef(null);
|
const preRef = useRef(null);
|
||||||
|
|
||||||
const fetchLog = async () => {
|
const fetchLog = async () => {
|
||||||
const response = await fetch(`/jobs/${props.job}/solve.log`);
|
const response = await fetch(`${SERVER_URL}/jobs/${props.job}/solve.log`);
|
||||||
const data = await response.text();
|
const data = await response.text();
|
||||||
if (log !== data) {
|
if (log !== data) {
|
||||||
setLog(data);
|
setLog(data);
|
||||||
|
|||||||
238
relog-web/src/solver/MapBlock.js
Normal file
238
relog-web/src/solver/MapBlock.js
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
import * as d3 from "d3";
|
||||||
|
import { group } from "d3-array";
|
||||||
|
import * as L from "leaflet";
|
||||||
|
import "leaflet/dist/leaflet.css";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { SERVER_URL } from "..";
|
||||||
|
import Card from "../common/Card";
|
||||||
|
import Section from "../common/Section";
|
||||||
|
|
||||||
|
function drawMap(csv_plants, csv_tr) {
|
||||||
|
const mapLink = '<a href="http://openstreetmap.org">OpenStreetMap</a>';
|
||||||
|
|
||||||
|
const base = L.tileLayer(
|
||||||
|
"https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
|
||||||
|
{
|
||||||
|
attribution:
|
||||||
|
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>',
|
||||||
|
subdomains: "abcd",
|
||||||
|
maxZoom: 10,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const plant_types = [...new Set(csv_plants.map((d) => d["plant type"]))];
|
||||||
|
plant_types.push("Multiple");
|
||||||
|
const plant_color = d3
|
||||||
|
.scaleOrdinal()
|
||||||
|
.domain(plant_types)
|
||||||
|
.range([
|
||||||
|
"#558B2F",
|
||||||
|
"#FF8F00",
|
||||||
|
"#0277BD",
|
||||||
|
"#AD1457",
|
||||||
|
"#00838F",
|
||||||
|
"#4527A0",
|
||||||
|
"#C62828",
|
||||||
|
"#424242",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const plant_locations = d3
|
||||||
|
.nest()
|
||||||
|
.key((d) => d["location name"])
|
||||||
|
.rollup(function (v) {
|
||||||
|
return {
|
||||||
|
amount_processed: d3.sum(v, function (d) {
|
||||||
|
return d["amount processed (tonne)"];
|
||||||
|
}),
|
||||||
|
latitude: d3.mean(v, function (d) {
|
||||||
|
return d["latitude (deg)"];
|
||||||
|
}),
|
||||||
|
longitude: d3.mean(v, function (d) {
|
||||||
|
return d["longitude (deg)"];
|
||||||
|
}),
|
||||||
|
plant_types: [...new Set(v.map((d) => d["plant type"]))],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.entries(csv_plants);
|
||||||
|
|
||||||
|
const plant_scale = d3
|
||||||
|
.scaleSqrt()
|
||||||
|
.range([2, 10])
|
||||||
|
.domain([0, d3.max(plant_locations, (d) => d.value.amount_processed)]);
|
||||||
|
|
||||||
|
const plants_array = [];
|
||||||
|
plant_locations.forEach((d) => {
|
||||||
|
if (d.value.plant_types.length > 1) {
|
||||||
|
d.value.plant_type = "Multiple";
|
||||||
|
} else {
|
||||||
|
d.value.plant_type = d.value.plant_types[0];
|
||||||
|
}
|
||||||
|
const marker = L.circleMarker([d.value.latitude, d.value.longitude], {
|
||||||
|
id: "circleMarker",
|
||||||
|
className: "marker",
|
||||||
|
color: "#222",
|
||||||
|
weight: 1,
|
||||||
|
fillColor: plant_color(d.value.plant_type),
|
||||||
|
fillOpacity: 0.9,
|
||||||
|
radius: plant_scale(d.value.amount_processed),
|
||||||
|
});
|
||||||
|
const num = d.value.amount_processed.toFixed(2);
|
||||||
|
const num_parts = num.toString().split(".");
|
||||||
|
num_parts[0] = num_parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||||
|
marker.bindTooltip(
|
||||||
|
`<b>${d.key}</b>
|
||||||
|
<br>
|
||||||
|
Amount processed:
|
||||||
|
${num_parts.join(".")}
|
||||||
|
<br>
|
||||||
|
Plant types:
|
||||||
|
${d.value.plant_types}`
|
||||||
|
);
|
||||||
|
plants_array.push(marker);
|
||||||
|
});
|
||||||
|
|
||||||
|
const collection_centers = d3
|
||||||
|
.nest()
|
||||||
|
.key((d) => d["source location name"])
|
||||||
|
.rollup(function (v) {
|
||||||
|
return {
|
||||||
|
source_lat: d3.mean(v, (d) => d["source latitude (deg)"]),
|
||||||
|
source_long: d3.mean(v, (d) => d["source longitude (deg)"]),
|
||||||
|
amount: d3.sum(v, (d) => d["amount (tonne)"]),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.entries(csv_tr);
|
||||||
|
|
||||||
|
//Color scale for the collection centers
|
||||||
|
const colors = d3
|
||||||
|
.scaleLog()
|
||||||
|
.domain([
|
||||||
|
d3.min(collection_centers, (d) => d.value.amount),
|
||||||
|
d3.max(collection_centers, (d) => d.value.amount),
|
||||||
|
])
|
||||||
|
.range(["#777", "#777"]);
|
||||||
|
|
||||||
|
//Plot the collection centers
|
||||||
|
const collection_array = [];
|
||||||
|
collection_centers.forEach(function (d) {
|
||||||
|
const marker = L.circleMarker([d.value.source_lat, d.value.source_long], {
|
||||||
|
color: "#000",
|
||||||
|
fillColor: colors(d.value.amount),
|
||||||
|
fillOpacity: 1,
|
||||||
|
radius: 1.25,
|
||||||
|
weight: 0,
|
||||||
|
className: "marker",
|
||||||
|
});
|
||||||
|
collection_array.push(marker);
|
||||||
|
});
|
||||||
|
|
||||||
|
const transportation_lines = group(
|
||||||
|
csv_tr,
|
||||||
|
(d) => d["source location name"],
|
||||||
|
(d) => d["destination location name"]
|
||||||
|
);
|
||||||
|
|
||||||
|
//Plot the transportation lines
|
||||||
|
const transport_array = [];
|
||||||
|
transportation_lines.forEach(function (d1) {
|
||||||
|
d1.forEach(function (d2) {
|
||||||
|
const object = d2[0];
|
||||||
|
const line = L.polyline(
|
||||||
|
[
|
||||||
|
[object["source latitude (deg)"], object["source longitude (deg)"]],
|
||||||
|
[
|
||||||
|
object["destination latitude (deg)"],
|
||||||
|
object["destination longitude (deg)"],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
{
|
||||||
|
color: "#666",
|
||||||
|
stroke: true,
|
||||||
|
weight: 0.5,
|
||||||
|
opacity: Math.max(0.1, 0.5 / d1.size),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
transport_array.push(line);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const plants = L.layerGroup(plants_array);
|
||||||
|
const cities = L.layerGroup(collection_array);
|
||||||
|
const transport = L.layerGroup(transport_array);
|
||||||
|
|
||||||
|
const baseMaps = {
|
||||||
|
"Open Street Map": base,
|
||||||
|
};
|
||||||
|
const overlayMaps = {
|
||||||
|
Plants: plants,
|
||||||
|
"Collection Centers": cities,
|
||||||
|
"Transportation Lines": transport,
|
||||||
|
};
|
||||||
|
|
||||||
|
cities.on({
|
||||||
|
add: function () {
|
||||||
|
cities.eachLayer((layer) => layer.bringToBack());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
transport.on({
|
||||||
|
add: function () {
|
||||||
|
plants.eachLayer((layer) => layer.bringToFront());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function setHeight() {
|
||||||
|
let mapDiv = document.getElementById("map");
|
||||||
|
mapDiv.style.height = `${+mapDiv.offsetWidth * 0.55}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
//$(window).resize(setHeight);
|
||||||
|
|
||||||
|
setHeight();
|
||||||
|
const map = L.map("map", {
|
||||||
|
layers: [base, plants],
|
||||||
|
}).setView([37.8, -96.9], 4);
|
||||||
|
|
||||||
|
const svg6 = d3.select(map.getPanes().overlayPane).append("svg");
|
||||||
|
svg6.append("g").attr("class", "leaflet-zoom-hide");
|
||||||
|
|
||||||
|
L.control.layers(baseMaps, overlayMaps).addTo(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MapBlock = (props) => {
|
||||||
|
const [filesFound, setFilesFound] = useState(false);
|
||||||
|
|
||||||
|
const fetchFiles = () => {
|
||||||
|
const file_prefix = `${SERVER_URL}/jobs/${props.job}/case`;
|
||||||
|
d3.csv(`${file_prefix}_plants.csv`).then((csv_plants) => {
|
||||||
|
d3.csv(`${file_prefix}_tr.csv`).then((csv_tr) => {
|
||||||
|
setFilesFound(true);
|
||||||
|
drawMap(csv_plants, csv_tr, file_prefix);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch files periodically from the server
|
||||||
|
useEffect(() => {
|
||||||
|
fetchFiles();
|
||||||
|
if (!filesFound) {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
fetchFiles();
|
||||||
|
}, 1000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}
|
||||||
|
}, [filesFound]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Section title="Map" />
|
||||||
|
<Card>
|
||||||
|
<div id="map">
|
||||||
|
<div className="nodata">No data available</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MapBlock;
|
||||||
@@ -4,6 +4,7 @@ import Footer from "../common/Footer";
|
|||||||
import Header from "../common/Header";
|
import Header from "../common/Header";
|
||||||
import LogBlock from "./LogBlock";
|
import LogBlock from "./LogBlock";
|
||||||
import FilesBlock from "./FilesBlock";
|
import FilesBlock from "./FilesBlock";
|
||||||
|
import MapBlock from "./MapBlock";
|
||||||
|
|
||||||
const SolverPage = () => {
|
const SolverPage = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
@@ -16,6 +17,7 @@ const SolverPage = () => {
|
|||||||
<div id="content">
|
<div id="content">
|
||||||
<LogBlock job={params.job_id} />
|
<LogBlock job={params.job_id} />
|
||||||
<FilesBlock job={params.job_id} />
|
<FilesBlock job={params.job_id} />
|
||||||
|
<MapBlock job={params.job_id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|||||||
@@ -91,8 +91,10 @@ function solve(root, filename)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function solve_recursive(path)
|
function solve_recursive(path)
|
||||||
|
cd(path)
|
||||||
|
|
||||||
# Solve instances
|
# Solve instances
|
||||||
for (root, dirs, files) in walkdir(path)
|
for (root, dirs, files) in walkdir(".")
|
||||||
if occursin(r"scenarios"i, root)
|
if occursin(r"scenarios"i, root)
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
@@ -104,7 +106,7 @@ function solve_recursive(path)
|
|||||||
|
|
||||||
# Collect results
|
# Collect results
|
||||||
results = []
|
results = []
|
||||||
for (root, dirs, files) in walkdir(path)
|
for (root, dirs, files) in walkdir(".")
|
||||||
for filename in files
|
for filename in files
|
||||||
endswith(filename, "_plants.csv") || continue
|
endswith(filename, "_plants.csv") || continue
|
||||||
push!(
|
push!(
|
||||||
@@ -116,11 +118,11 @@ function solve_recursive(path)
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
open("$path/output.json", "w") do file
|
open("output.json", "w") do file
|
||||||
JSON.print(file, results)
|
JSON.print(file, results)
|
||||||
end
|
end
|
||||||
|
|
||||||
run(`zip -r $path/output.zip $path`)
|
run(`zip -r output.zip .`)
|
||||||
end
|
end
|
||||||
|
|
||||||
solve_recursive(ARGS[1])
|
solve_recursive(ARGS[1])
|
||||||
Reference in New Issue
Block a user