diff --git a/.env b/.env
index daf48846..e7994b16 100644
--- a/.env
+++ b/.env
@@ -1,3 +1,3 @@
REACT_APP_MAPBOX_TOKEN=pk.eyJ1IjoicmVsbm94IiwiYSI6ImNqd2VwOTNtYjExaHkzeXBzYm1xc3E3dzQifQ.X8r8nj4-baZXSsFgctQMsg
-PUBLIC_URL=https://cityscope.media.mit.edu/CS_cityscopeJS
+PUBLIC_URL=https://cityio.media.mit.edu
SKIP_PREFLIGHT_CHECK=true
diff --git a/package.json b/package.json
index 1ee146db..c85ecf08 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "cityscopejs",
"repository": "https://github.com/CityScope/CS_cityscopeJS",
- "homepage": "https://cityscope.media.mit.edu/CS_cityscopeJS/",
+ "homepage": "https://cityio.media.mit.edu",
"dependencies": {
"@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
@@ -28,6 +28,7 @@
"react-map-gl": "7.0.19",
"react-redux": "^8.0.4",
"react-scripts": "5.0.1",
+ "react-use-websocket": "^4.5.0",
"redux": "^4.2.0",
"typescript": "^4.8.4"
},
diff --git a/src/Components/CityIO/index.js b/src/Components/CityIO/index.js
index 01ffc2f2..c993378b 100755
--- a/src/Components/CityIO/index.js
+++ b/src/Components/CityIO/index.js
@@ -1,160 +1,183 @@
+/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from "react";
-import { cityIOSettings, generalSettings } from "../../settings/settings";
+import { cityIOSettings } from "../../settings/settings";
import {
updateCityIOdata,
toggleCityIOisDone,
} from "../../redux/reducers/cityIOdataSlice";
import { useSelector, useDispatch } from "react-redux";
-import { getAPICall } from "../../utils/utils";
+import useWebSocket, { ReadyState } from "react-use-websocket"
import LoadingProgressBar from "../LoadingProgressBar";
-const removeElement = (array, elem) => {
- var index = array.indexOf(elem);
- if (index > -1) {
- array.splice(index, 1);
- }
- return array;
-};
-
const CityIO = (props) => {
+
const verbose = true; // set to true to see console logs
- const waitTimeMS = 5000;
const dispatch = useDispatch();
const cityIOdata = useSelector((state) => state.cityIOdataState.cityIOdata);
- const cityscopeProjectURL = generalSettings.csjsURL;
const { tableName } = props;
- const [mainHash, setMainHash] = useState(null);
- const [hashes, setHashes] = useState({});
+ const possibleModules = cityIOSettings.cityIO.cityIOmodules.map(module => module.name)
const [arrLoadingModules, setArrLoadingModules] = useState([]);
- const cityioURL = `${cityIOSettings.cityIO.baseURL}table/${tableName}/`;
- // test if cityIO is up and this table exists
+ // Creation of the websocket connection. TODO: change WS_URL to env or property
+ // sendJsonMessage: function that sends a message through the websocket channel
+ // lastJsonMessage: object that contains the last message received through the websocket
+ // readyState: indicates whether the WS is ready or not
+ const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket(
+ cityIOSettings.cityIO.websocketURL,
+ {
+ share: true,
+ shouldReconnect: () => true,
+ },
+ )
+
+ // When the WS connection state (readyState) changes to OPEN,
+ // the UI sends a LISTEN (SUBSCRIBE) message to CityIO with the tableName prop
useEffect(() => {
- const testCityIO = async () => {
- let test = await getAPICall(cityioURL + "meta/");
- if (test) {
- // start fetching API hashes to check for new data
- getCityIOmetaHash();
- verbose &&
- console.log(
- "%c cityIO is up, reading cityIO every " +
- cityIOSettings.cityIO.interval +
- "ms",
- "color: red"
- );
- } else {
- setArrLoadingModules([
- `cityIO might be down, please check { ${tableName} } is correct. Returning to cityScopeJS at ${cityscopeProjectURL} in ${
- waitTimeMS / 1000
- } seconds`,
- ]);
-
- new Promise((resolve) => {
- setTimeout(() => {
- window.location.assign(cityscopeProjectURL);
- }, waitTimeMS);
- resolve();
- });
- }
- };
- testCityIO();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [cityioURL]);
-
- /**
- * gets the main hash of this cityIO table
- * on a constant loop to check for updates
- */
- async function getCityIOmetaHash() {
- // recursively get hashes
- await getAPICall(cityioURL + "meta/id/").then(async (res) => {
- // is it a new hash?
- if (mainHash !== res) {
- setMainHash(res);
- }
- });
- // do it forever
- setTimeout(getCityIOmetaHash, cityIOSettings.cityIO.interval);
- }
+ console.log("Connection state changed")
+ if (readyState === ReadyState.OPEN) {
+ sendJsonMessage({
+ type: "LISTEN",
+ content: {
+ gridId: tableName,
+ },
+ })
+ setArrLoadingModules([
+ `Loading ${tableName} data.`,
+ ]);
+ }
+ }, [readyState])
+
+ // When a new WebSocket message is received (lastJsonMessage) the UI checks
+ // the type of the message and performs the suitable operation
useEffect(() => {
- //! only update if hashId changes
- if (!mainHash) {
- return;
+
+ if(lastJsonMessage == null) return;
+ console.log(`Got a new message: ${JSON.stringify(lastJsonMessage)}`)
+
+ let messageType = lastJsonMessage.type;
+
+ // If the message is of type GRID, the UI updates the GEOGRID and
+ // GEOGRIDDATA, optionally, CityIO can send saved modules
+ if (messageType === 'GRID'){
+ verbose && console.log(
+ ` --- trying to update GEOGRID --- ${JSON.stringify(lastJsonMessage.content)}`
+ );
+ setArrLoadingModules([]);
+
+ let m = {...cityIOdata, "GEOGRID": lastJsonMessage.content.GEOGRID, "GEOGRIDDATA":lastJsonMessage.content.GEOGRIDDATA, tableName: tableName };
+
+ Object.keys(lastJsonMessage.content).forEach((key)=>{
+ if(possibleModules.includes(key) && key !== 'scenarios' && key !== 'indicators'){
+ m[key] = lastJsonMessage.content[key]
+ } else if(key === 'deckgl'){
+ lastJsonMessage.content.deckgl
+ .forEach((layer) => {
+ m[layer.type]={ data: layer.data, properties: layer.properties }
+ });
+ }
+ }
+ );
+ // When we receive a GRID message, we ask for the scenarios of the table we´re
+ // connected, and for the core modules
+ sendJsonMessage({
+ type: "REQUEST_CORE_MODULES_LIST",
+ content: {},
+ })
+ sendJsonMessage({
+ type: "LIST_SCENARIOS",
+ content: {},
+ })
+ dispatch(updateCityIOdata(m));
+ verbose &&
+ console.log(
+ "%c --- done updating from cityIO ---",
+ "color: rgb(0, 255, 0)"
+ );
+ dispatch(toggleCityIOisDone(true));
}
- // if we have a new hash, start getting submodules
- getModules();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [mainHash]);
-
- async function getModules() {
- // wait to get all of this table's hashes
- const newHashes = await getAPICall(cityioURL + "meta/hashes/");
- // init array of GET promises
- const promises = [];
- // init array of modules names
- const loadingModulesArray = [];
- // get an array of modules to update
- const modulesToUpdate = cityIOSettings.cityIO.cityIOmodules.map(
- (x) => x.name
- );
- // for each of the modules in settings, add api call to promises
- modulesToUpdate.forEach((module) => {
+ // If we receive a GEOGRIDDATA_UPDATE, the UI needs to refresh
+ // the GEOGRIDDATA object
+ else if (messageType === 'GEOGRIDDATA_UPDATE'){
+ verbose && console.log(
+ ` --- trying to update GEOGRIDDATA --- ${JSON.stringify(lastJsonMessage.content)}`
+ );
+ let m = {...cityIOdata, "GEOGRIDDATA":lastJsonMessage.content };
+ dispatch(updateCityIOdata(m));
verbose &&
console.log(
- "%c checking {" + module + "} for updates...",
- "color:rgb(200, 200, 0)"
+ "%c --- done updating from cityIO ---",
+ "color: rgb(0, 255, 0)"
);
+ dispatch(toggleCityIOisDone(true));
+ }
- //add this module name to array
- // of modules that we await for
- loadingModulesArray.push(module);
-
- // if this module has an old hash
- // we assume it is about to be updated
-
- if (hashes[module] !== newHashes[module]) {
- // add this module URL to an array of GET requests
- promises.push(getAPICall(`${cityioURL}${module}/`));
- } else {
- promises.push(null);
+ // If we receive a INDICATOR (MODULE) message, the UI needs to load
+ // the module data
+ // WIP
+ else if (messageType === 'INDICATOR'){
+ verbose && console.log(
+ ` --- trying to update INDICATOR --- ${JSON.stringify(lastJsonMessage.content)}`
+ );
+ let m = {...cityIOdata}
+ if('numeric' in lastJsonMessage.content.moduleData){
+ m = {...m, "indicators":lastJsonMessage.content.moduleData.numeric, tableName: tableName };
+ }
+ if('heatmap' in lastJsonMessage.content.moduleData){
+ m = {...m, "heatmap":lastJsonMessage.content.moduleData.heatmap, tableName: tableName };
}
- setArrLoadingModules(loadingModulesArray);
- });
-
- // GET all modules data
- const modulesFromCityIO = await Promise.all(promises);
- setHashes(newHashes);
-
- // update cityio object with modules data
- let modulesData = modulesToUpdate.reduce((obj, moduleName, index) => {
- // if this module has data
- if (modulesFromCityIO[index]) {
- verbose &&
- console.log(
- "%c {" +
- moduleName +
- "} state has changed on cityIO. Getting new data...",
- "color: rgb(0, 200, 255)"
- );
- setArrLoadingModules(removeElement(arrLoadingModules, moduleName));
-
- return { ...obj, [moduleName]: modulesFromCityIO[index] };
- } else {
- return obj;
+ if('deckgl' in lastJsonMessage.content.moduleData){
+ lastJsonMessage.content.moduleData.deckgl
+ .forEach((layer) => {
+ m[layer.type]={ data: layer.data, properties: layer.properties }
+ });
}
- }, cityIOdata);
- let m = { ...modulesData, tableName: tableName };
- dispatch(updateCityIOdata(m));
- verbose &&
- console.log(
- "%c --- done updating from cityIO ---",
- "color: rgb(0, 255, 0)"
+
+ dispatch(updateCityIOdata(m));
+ verbose &&
+ console.log(
+ "%c --- done updating from cityIO ---",
+ "color: rgb(0, 255, 0)"
+ );
+ dispatch(toggleCityIOisDone(true));
+ }
+
+ // If we receive a CORE_MODULES_LIST message, the UI loads
+ // the available modules data
+ else if (messageType === 'CORE_MODULES_LIST'){
+ verbose && console.log(
+ ` --- trying to update CORE_MODULES_LIST --- ${JSON.stringify(lastJsonMessage.content)}`
);
- dispatch(toggleCityIOisDone(true));
- }
+ let m = {...cityIOdata, 'core_modules':lastJsonMessage.content }
+ dispatch(updateCityIOdata(m));
+ verbose &&
+ console.log(
+ "%c --- done updating from cityIO ---",
+ "color: rgb(0, 255, 0)"
+ );
+ dispatch(toggleCityIOisDone(true));
+ }
+
+ // If we receive a SCENARIOS message, the UI loads
+ // the available scenarios
+ else if (messageType === 'SCENARIOS'){
+ verbose && console.log(
+ ` --- trying to update SCENARIOS --- ${JSON.stringify(lastJsonMessage.content)}`
+ );
+ let m = {...cityIOdata, 'scenarios':lastJsonMessage.content }
+ dispatch(updateCityIOdata(m));
+ verbose &&
+ console.log(
+ "%c --- done updating from cityIO ---",
+ "color: rgb(0, 255, 0)"
+ );
+ dispatch(toggleCityIOisDone(true));
+ }
+
+ }, [lastJsonMessage])
return ;
+
};
export default CityIO;
diff --git a/src/settings/settings.js b/src/settings/settings.js
index 0323843d..3c979b71 100644
--- a/src/settings/settings.js
+++ b/src/settings/settings.js
@@ -7,19 +7,32 @@ const getServerLocation = () => {
const serverLocation =
"cityio_local" in parsed
- ? "http://127.0.0.1:5000/api/"
- : "https://cityio.media.mit.edu/api/";
+ ? "http://localhost:8080/api/"
+ : "https://cityio.media.mit.edu/cityio/api/";
console.log("cityIO server location: ", serverLocation);
return serverLocation;
};
+
+const getWebsocketServerLocation = () => {
+ const location = window.location;
+ const parsed = queryString.parse(location.search);
+
+ const serverLocation =
+ "cityio_local" in parsed
+ ? "ws://localhost:8080/interface"
+ : "wss://cityio.media.mit.edu/cityio/interface";
+ console.log("cityIO websocket server location: ", serverLocation);
+ return serverLocation;
+};
+
// get the location of the app (local or remote)
const getCSJSLocation = () => {
const location = window.location;
const parsed = queryString.parse(location.search);
const cityscopejs_local_url =
"cityscopejs_local" in parsed
- ? "http://localhost:3000/CS_cityscopeJS/"
- : "https://cityscope.media.mit.edu/CS_cityscopeJS/";
+ ? "http://localhost:3000/"
+ : "https://cityio.media.mit.edu";
console.log("cityScopeJS location: ", cityscopejs_local_url);
return cityscopejs_local_url;
};
@@ -33,8 +46,10 @@ export const cityIOSettings = {
"https://raw.githubusercontent.com/CityScope/CS_cityscopeJS/master/docs/",
cityIO: {
baseURL: getServerLocation(),
+ websocketURL: getWebsocketServerLocation(),
- ListOfTables: "tables/list/",
+ ListOfTables: "table/list/",
+ headers: "table/headers/",
interval: 500,
cityIOmodules: [
{ name: "header", expectUpdate: false },
@@ -126,6 +141,96 @@ export const expectedLayers = {
initState: false,
initSliderValue: 50,
},
+ ARC_LAYER_CHECKBOX: {
+ displayName: "Arc",
+ cityIOmoduleName: "arc",
+ initState: true,
+ initSliderValue: 50,
+ },
+ COLUMN_LAYER_CHECKBOX: {
+ displayName: "Column",
+ cityIOmoduleName: "column",
+ initState: true,
+ initSliderValue: 50,
+ },
+ CONTOUR_LAYER_CHECKBOX: {
+ displayName: "Contour",
+ cityIOmoduleName: "contours",
+ initState: true,
+ initSliderValue: 50,
+ },
+ GEOJSON_BASE_LAYER_CHECKBOX: {
+ displayName: "GeoJSON",
+ cityIOmoduleName: "geojsonbase",
+ initState: true,
+ initSliderValue: 50,
+ },
+ GRID_BASE_LAYER_CHECKBOX: {
+ displayName: "Grid",
+ cityIOmoduleName: "gridlayer",
+ initState: true,
+ initSliderValue: 50,
+ },
+ GRIDCELL_LAYER_CHECKBOX: {
+ displayName: "Grid cell",
+ cityIOmoduleName: "gridcell",
+ initState: true,
+ initSliderValue: 50,
+ },
+ HEATMAP_LAYER_CHECKBOX: {
+ displayName: "Heatmap",
+ cityIOmoduleName: "heatmap",
+ initState: true,
+ initSliderValue: 50,
+ },
+ HEXAGON_LAYER_CHECKBOX: {
+ displayName: "Hexagon",
+ cityIOmoduleName: "hexagon",
+ initState: true,
+ initSliderValue: 50,
+ },
+ ICON_LAYER_CHECKBOX: {
+ displayName: "Icon",
+ cityIOmoduleName: "icon",
+ initState: true,
+ initSliderValue: 100,
+ },
+ LINE_LAYER_CHECKBOX: {
+ displayName: "Lines",
+ cityIOmoduleName: "lines",
+ initState: true,
+ initSliderValue: 100,
+ },
+ PATH_LAYER_CHECKBOX: {
+ displayName: "Path",
+ cityIOmoduleName: "path",
+ initState: true,
+ initSliderValue: 75,
+ },
+ SCATTER_LAYER_CHECKBOX: {
+ displayName: "Scatter",
+ cityIOmoduleName: "scatterplot",
+ initState: true,
+ initSliderValue: 75,
+ },
+ SCENEGRAPH_LAYER_CHECKBOX: {
+ displayName: "Scene graph",
+ cityIOmoduleName: "scenegraph",
+ initState: true,
+ initSliderValue: 75,
+ },
+ MESH_LAYER_CHECKBOX: {
+ displayName: "Mesh",
+ cityIOmoduleName: "simpleMesh",
+ initState: true,
+ initSliderValue: 50,
+ },
+ TEXT_LAYER_CHECKBOX: {
+ displayName: "Text",
+ cityIOmoduleName: "textLayer",
+ initState: false,
+ initSliderValue: 100,
+ },
};
export const viewControlCheckboxes = {
diff --git a/src/views/CityIOviewer/CityIOlist.js b/src/views/CityIOviewer/CityIOlist.js
index d2a1d790..3c61e4d3 100644
--- a/src/views/CityIOviewer/CityIOlist.js
+++ b/src/views/CityIOviewer/CityIOlist.js
@@ -14,31 +14,19 @@ export default function CityIOlist(props) {
}, [tablesList]);
const fetchCityIOtables = async () => {
- // ! https://stackoverflow.com/questions/37213783/waiting-for-all-promises-called-in-a-loop-to-finish
const cityIOlistURL =
- cityIOSettings.cityIO.baseURL + cityIOSettings.cityIO.ListOfTables;
- // get all URLs
- const tablesArr = await axios.get(cityIOlistURL);
- // create array of all requests
- const requestArr = tablesArr.data.map(async (tableName) => {
- // const tableName = urlStr.split('/').pop()
- const url = `${cityIOSettings.cityIO.baseURL}table/${tableName}/`;
- return axios
- .get(`${url}GEOGRID/properties/header/`)
- .then((res) =>
- setTableList((oldArray) => [
- ...oldArray,
- { tableURL: url, tableName: tableName, tableHeader: res.data },
- ])
- )
- .catch((error) => console.log(error.toString()));
- });
-
- Promise.all(requestArr).then(() => {
- setIsLoading(false);
- return tablesList;
- });
- };
+ cityIOSettings.cityIO.baseURL + cityIOSettings.cityIO.headers;
+ // get all table headers
+ let tablesArr = await axios.get(cityIOlistURL);
+ // create array of all headers
+ tablesArr = tablesArr.data.map(table => {
+ const url = `${cityIOSettings.cityIO.baseURL}table/${table.tableName}/`;
+ table = {...table, tableURL: url}
+ return table
+ })
+ setTableList((oldArray) => [...tablesArr]);
+ setIsLoading(false);
+};
useEffect(() => {
fetchCityIOtables();
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Arc.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Arc.js
new file mode 100644
index 00000000..02391b63
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Arc.js
@@ -0,0 +1,35 @@
+import {ArcLayer} from '@deck.gl/layers';
+
+ /**
+ * Data format:
+ * [
+ * {
+ * inbound: 72633,
+ * outbound: 74735,
+ * from: {
+ * name: '19th St. Oakland (19TH)',
+ * coordinates: [-122.269029, 37.80787]
+ * },
+ * to: {
+ * name: '12th St. Oakland City Center (12TH)',
+ * coordinates: [-122.271604, 37.803664]
+ * },
+ * ...
+ * ]
+ */
+ export default function ArcBaseLayer({data, opacity}){
+ if(data.arc){
+ return new ArcLayer({
+ id: 'arc-layer',
+ data: data.arc.data,
+ pickable: true,
+ getWidth: data.arc.properties.width || 12,
+ getSourcePosition: d => d.from.coordinates,
+ getTargetPosition: d => d.to.coordinates,
+ getSourceColor: d => d.sourceColor || [255, 140, 0],
+ getTargetColor: d => d.targetColor || [55, 140, 0],
+ opacity
+ });
+
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Column.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Column.js
new file mode 100644
index 00000000..92f3ab1c
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Column.js
@@ -0,0 +1,28 @@
+import {ColumnLayer} from '@deck.gl/layers';
+
+ /**
+ * Data format:
+ * [
+ * {centroid: [-122.4, 37.7], value: 0.2},
+ * ...
+ * ]
+ */
+ export default function ColumnBaseLayer({data, opacity}){
+ if(data.column){
+ return new ColumnLayer({
+ id: 'column-layer',
+ data: data.column.data,
+ diskResolution: data.column.properties.resolution || 12,
+ radius: data.column.properties.radius || 30,
+ extruded: data.column.properties.extruded || true,
+ pickable: data.column.properties.pickable || true,
+ elevationScale: data.column.properties.elevationScale || 1,
+ getPosition: d => d.centroid,
+ getFillColor: d => [48, 128, d.value * 255, 255],
+ getLineColor: [0, 0, 0],
+ getElevation: d => d.value,
+ opacity
+ });
+
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Contour.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Contour.js
new file mode 100644
index 00000000..92582d93
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Contour.js
@@ -0,0 +1,21 @@
+import {ContourLayer} from '@deck.gl/aggregation-layers';
+
+/**
+ * Data format:
+ * [
+ * {coordinates: [-122.42177834, 37.78346622]},
+ * ...
+ * ]
+ */
+export default function ContourBaseLayer({data, opacity}){
+ if(data.contours){
+ return new ContourLayer({
+ id: 'contourLayer',
+ contours: data.contours.data,
+ cellSize: data.contours.properties.cellSize || 200,
+ getPosition: d => d.coordinates,
+ opacity
+ });
+
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/GeoJson.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/GeoJson.js
new file mode 100644
index 00000000..811359c7
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/GeoJson.js
@@ -0,0 +1,29 @@
+import {GeoJsonLayer} from '@deck.gl/layers';
+
+ /**
+ * Data format:
+ * Valid GeoJSON object
+ */
+ export default function GeoJsonBaseLayer({data, opacity}){
+ if(data.geojsonbase){
+ return new GeoJsonLayer({
+ id: 'geojson-layer',
+ data: data.geojsonbase.data,
+ pickable: data.geojsonbase.properties.pickable || true,
+ stroked: data.geojsonbase.properties.stroked || false,
+ filled: data.geojsonbase.properties.filled || true,
+ extruded: data.geojsonbase.properties.extruded || true,
+ pointType: data.geojsonbase.properties.pointType || 'circle',
+ lineWidthScale: data.geojsonbase.properties.lineWidthScale || 20,
+ lineWidthMinPixels: data.geojsonbase.properties.lineWidthMinPixels || 2,
+ getFillColor: [160, 160, 180, 200],
+ getLineColor: d => d.properties.color,
+ getPointRadius: data.geojsonbase.properties.pointRadius || 100,
+ getLineWidth: data.geojsonbase.properties.lineWidth || 1,
+ getElevation: data.geojsonbase.properties.elevation || 30,
+ opacity
+ });
+
+
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Grid.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Grid.js
new file mode 100644
index 00000000..30893ede
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Grid.js
@@ -0,0 +1,24 @@
+import {GridLayer} from '@deck.gl/aggregation-layers';
+
+ /**
+ * Data format:
+ * [
+ * {coordinates: [-122.42177834, 37.78346622]},
+ * ...
+ * ]
+ */
+ export default function GridBaseLayer({data, opacity}){
+ if(data.gridlayer){
+ return new GridLayer({
+ id: 'new-grid-layer',
+ data: data.gridlayer.data,
+ pickable: data.gridlayer.properties.pickable || true,
+ extruded: data.gridlayer.properties.extruded || true,
+ cellSize: data.gridlayer.properties.cellSize || 200,
+ elevationScale: data.gridlayer.properties.elevationScale || 4,
+ getPosition: d => d.coordinates,
+ opacity
+ });
+
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/GridCell.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/GridCell.js
new file mode 100644
index 00000000..f465e59d
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/GridCell.js
@@ -0,0 +1,26 @@
+import {GridCellLayer} from '@deck.gl/layers';
+
+ /**
+ * Data format:
+ * [
+ * {centroid: [-122.4, 37.7],
+ * value: 100},
+ * ...
+ * ]
+ */
+ export default function GridCellBaseLayer({data, opacity}){
+ if(data.gridcell){
+ return new GridCellLayer({
+ id: 'grid-cell-layer',
+ data: data.gridcell.data,
+ pickable: data.gridcell.properties.pickable || true,
+ extruded: data.gridcell.properties.extruded || true,
+ cellSize: data.gridcell.properties.cellSize || 200,
+ elevationScale: data.gridcell.properties.elevationScale || 5000,
+ getPosition: d => d.centroid,
+ getFillColor: d => [48, 128, d.value * 255, 255],
+ getElevation: d => d.value,
+ opacity
+ });
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Heatmap.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Heatmap.js
new file mode 100644
index 00000000..1e8afc59
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Heatmap.js
@@ -0,0 +1,23 @@
+import {HeatmapLayer} from '@deck.gl/aggregation-layers';
+
+ /**
+ * Data format:
+ * [
+ * {coordinates: [-122.42177834, 37.78346622], weight: 10},
+ * ...
+ * ]
+ */
+ export default function HeatmapBaseLayer({data, opacity}){
+
+ if(data.heatmap){
+
+ return new HeatmapLayer({
+ id: 'heatmapLayer',
+ data: data.heatmap.data,
+ getPosition: d => d.coordinates,
+ getWeight: d => d.weight,
+ aggregation: 'SUM',
+ opacity
+ });
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Hexagon.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Hexagon.js
new file mode 100644
index 00000000..75660aa8
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Hexagon.js
@@ -0,0 +1,23 @@
+import {HexagonLayer} from '@deck.gl/aggregation-layers';
+
+ /**
+ * Data format:
+ * [
+ * {coordinates: [-122.42177834, 37.78346622]},
+ * ...
+ * ]
+ */
+ export default function HexagonBaseLayer({data, opacity}){
+ if(data.hexagon){
+ return new HexagonLayer({
+ id: 'hexagon-layer',
+ data: data.hexagon.data,
+ pickable: data.hexagon.properties.pickable || true,
+ extruded: data.hexagon.properties.extruded || true,
+ radius: data.hexagon.properties.radius || 200,
+ elevationScale: data.hexagon.properties.elevationScale || 4,
+ getPosition: d => d.coordinates,
+ opacity
+ });
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Icon.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Icon.js
new file mode 100644
index 00000000..2f7d26ac
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Icon.js
@@ -0,0 +1,30 @@
+import {IconLayer} from '@deck.gl/layers';
+
+ /**
+ * Data format:
+ * [
+ * {coordinates: [-122.466233, 37.684638]},
+ * ...
+ * ]
+ */
+ export default function IconBaseLayer({data, opacity}){
+ if(data.icon){
+ return new IconLayer({
+ id: 'icon-layer',
+ data: data.icon.data,
+ pickable: data.icon.properties.pickable || true,
+ // iconAtlas and iconMapping are required
+ // getIcon: return a string
+ getIcon: d => ({
+ url: d.icon,
+ width: d.width || 128,
+ height: d.height || 128,
+ anchorY: d.anchorY || 128
+ }),
+ sizeScale: data.icon.properties.sizeScale || 10,
+ sizeMaxPixels: data.icon.properties.sizeMaxPixels || 10,
+ getPosition: d => [d.coordinates[0],d.coordinates[1],d.elevation || 30],
+ opacity
+ });
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Line.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Line.js
new file mode 100644
index 00000000..4c382376
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Line.js
@@ -0,0 +1,33 @@
+import {LineLayer} from '@deck.gl/layers';
+
+ /**
+ * Data format:
+ * [
+ * {
+ * inbound: 72633,
+ * outbound: 74735,
+ * from: {
+ * name: '19th St. Oakland (19TH)',
+ * coordinates: [-122.269029, 37.80787]
+ * },
+ * to: {
+ * name: '12th St. Oakland City Center (12TH)',
+ * coordinates: [-122.271604, 37.803664]
+ * },
+ * ...
+ * ]
+ */
+ export default function LineBaseLayer({data, opacity}){
+ if(data.lines){
+ return new LineLayer({
+ id: 'line-layer',
+ data: data.lines.data,
+ pickable: data.lines.properties.pickable || true,
+ getWidth: data.lines.properties.width || 50,
+ getSourcePosition: d => d.from.coordinates,
+ getTargetPosition: d => d.to.coordinates,
+ getColor: d => d.color || [200, 140, 0],
+ opacity
+ });
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Path.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Path.js
new file mode 100644
index 00000000..93350930
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Path.js
@@ -0,0 +1,29 @@
+import {PathLayer} from '@deck.gl/layers';
+
+ /**
+ * Data format:
+ * [
+ * {
+ * path: [[-122.4, 37.7], [-122.5, 37.8], [-122.6, 37.85]],
+ * name: 'Richmond - Millbrae',
+ * color: [255, 0, 0]
+ * },
+ * ...
+ * ]
+ */
+ export default function PathBaseLayer({data, opacity}){
+ if(data.path){
+ return new PathLayer({
+ id: 'path-layer',
+ data: data.path.data,
+ pickable: data.path.properties.pickable || true,
+ widthScale: data.path.properties.widthScale || 20,
+ widthMinPixels: data.path.properties.widthMinPixels || 2,
+ getPath: d => d.path,
+ getColor: d => d.color || [255, 0, 0],
+ getWidth: d => d.width || 5,
+ opacity
+ });
+
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Scatterplot.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Scatterplot.js
new file mode 100644
index 00000000..95d476d6
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Scatterplot.js
@@ -0,0 +1,31 @@
+import {ScatterplotLayer} from '@deck.gl/layers';
+
+ /**
+ * Data format:
+ * [
+ * {name: 'Colma (COLM)', address: '365 D Street, Colma CA 94014', exits: 4214, coordinates: [-122.466233, 37.684638]},
+ * ...
+ * ]
+ */
+ export default function ScatterplotBaseLayer({data, opacity}){
+ if(data.scatterplot){
+ return new ScatterplotLayer({
+ id: 'scatterplot-layer',
+ data: data.scatterplot.data,
+ pickable: data.path.properties.pickable || true,
+ stroked: data.path.properties.stroked || true,
+ filled: data.path.properties.filled || true,
+ radiusScale: data.path.properties.radiusScale || 6,
+ radiusMinPixels: data.path.properties.radiusMinPixels || 1,
+ radiusMaxPixels: data.path.properties.radiusMaxPixels || 100,
+ lineWidthMinPixels: data.path.properties.lineWidthMinPixels || 1,
+ getPosition: d => d.coordinates,
+ getRadius: d => Math.sqrt(d.exits),
+ getFillColor: d => [255, 140, 0],
+ getLineColor: d => [0, 0, 0],
+ opacity
+ });
+
+
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Scenegraph.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Scenegraph.js
new file mode 100644
index 00000000..e5b125a8
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/Scenegraph.js
@@ -0,0 +1,30 @@
+import {ScenegraphLayer} from '@deck.gl/mesh-layers';
+
+ /**
+ * Data format:
+ * [
+ * {name: 'Colma (COLM)', code:'CM', address: '365 D Street, Colma CA 94014', exits: 4214, coordinates: [-122.466233, 37.684638]},
+ * ...
+ * ]
+ */
+ export default function ScenegraphBaseLayer({data, opacity}){
+ if(data.scenegraph){
+ return new ScenegraphLayer({
+ id: 'scenegraph-layer',
+ data: data.scenegraph.data,
+ pickable: data.scenegraph.properties.pickable || true,
+ scenegraph: data.scenegraph.properties.scenegraph || 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/BoxAnimated/glTF-Binary/BoxAnimated.glb',
+ getPosition: d => d.coordinates,
+ getOrientation: d => [0, Math.random() * 180, 90],
+ _animations: {
+ '*': {speed: 5}
+ },
+ sizeScale: data.scenegraph.properties.sizeScale || 500,
+ _lighting: 'pbr',
+ opacity
+ });
+
+
+
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/SimpleMesh.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/SimpleMesh.js
new file mode 100644
index 00000000..087b257a
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/SimpleMesh.js
@@ -0,0 +1,34 @@
+import {SimpleMeshLayer} from '@deck.gl/mesh-layers';
+import {OBJLoader} from "@loaders.gl/obj";
+
+ /**
+ * Data format:
+ * [
+ * {
+ * position: [-122.45, 37.7],
+ * angle: 0,
+ * color: [255, 0, 0]
+ * },
+ * {
+ * position: [-122.46, 37.73],
+ * angle: 90,
+ * color: [0, 255, 0]
+ * },
+ * ...
+ * ]
+ */
+ export default function SimpleMeshBaseLayer({data, opacity}){
+ if(data.simpleMesh){
+ return new SimpleMeshLayer({
+ id: 'mesh-layer',
+ data: data.simpleMesh.data,
+ mesh: data.simpleMesh.properties.mesh || 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/humanoid_quad.obj',
+ getPosition: d => [d.position[0],d.position[1],0],
+ getColor: d => d.color,
+ getOrientation: d => [0, d.angle, 0],
+ loaders:[OBJLoader],
+ sizeScale: data.simpleMesh.properties.sizeScale || 1,
+ opacity
+ });
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/base/TextLayer.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/TextLayer.js
new file mode 100644
index 00000000..4cf60192
--- /dev/null
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/base/TextLayer.js
@@ -0,0 +1,31 @@
+import {TextLayer} from '@deck.gl/layers';
+/**
+ * Data format:
+ * [
+ * {name: 'Colma (COLM)', address: '365 D Street, Colma CA 94014', coordinates: [-122.466233, 37.684638]},
+ * ...
+ * ]
+ */
+ export default function TextBaseLayer({data, opacity}){
+ if(data.textLayer){
+ return new TextLayer({
+ id: 'text-layer',
+ data: data.textLayer.data,
+ pickable: data.textLayer.properties.pickable || true,
+ getPosition: d => [d.coordinates[0],d.coordinates[1],20],
+ getText: d => d.text,
+ getAngle: 0,
+ getTextAnchor: 'middle',
+ getAlignmentBaseline: 'center',
+ background: true,
+ backgroundPadding: [3,3,3,3],
+ getBackgroundColor: [255, 255, 255, 200],
+ getBorderWidth: 1,
+ billboard: true,
+ getSize: 5,
+ sizeScale: 2,
+ opacity
+ });
+
+ }
+ }
diff --git a/src/views/CityScopeJS/DeckglMap/deckglLayers/index.js b/src/views/CityScopeJS/DeckglMap/deckglLayers/index.js
index 393e0d85..2a67a92d 100644
--- a/src/views/CityScopeJS/DeckglMap/deckglLayers/index.js
+++ b/src/views/CityScopeJS/DeckglMap/deckglLayers/index.js
@@ -6,4 +6,18 @@ export { default as TextualLayer } from "./TextualLayer";
export { default as GeojsonLayer } from "./GeojsonLayer";
export { default as TileMapLayer } from "./TileMapLayer";
export { default as TrafficLayer } from "./TrafficLayer";
-
+export {default as ArcBaseLayer} from "./base/Arc"
+export {default as ColumnBaseLayer} from "./base/Column"
+export {default as ContourBaseLayer} from "./base/Contour"
+export {default as GeoJsonBaseLayer} from "./base/GeoJson"
+export {default as GridBaseLayer} from "./base/Grid"
+export {default as GridCellBaseLayer} from "./base/GridCell"
+export {default as HeatmapBaseLayer} from "./base/Heatmap"
+export {default as HexagonBaseLayer} from "./base/Hexagon"
+export {default as IconBaseLayer} from "./base/Icon"
+export {default as LineBaseLayer} from "./base/Line"
+export {default as PathBaseLayer} from "./base/Path"
+export {default as ScatterplotBaseLayer} from "./base/Scatterplot"
+export {default as ScenegraphBaseLayer} from "./base/Scenegraph"
+export {default as SimpleMeshBaseLayer} from "./base/SimpleMesh"
+export {default as TextBaseLayer} from "./base/TextLayer"
diff --git a/src/views/CityScopeJS/DeckglMap/index.js b/src/views/CityScopeJS/DeckglMap/index.js
index 0bf02e97..80bd58b4 100644
--- a/src/views/CityScopeJS/DeckglMap/index.js
+++ b/src/views/CityScopeJS/DeckglMap/index.js
@@ -2,7 +2,6 @@ import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import PaintBrush from "../../../Components/PaintBrush";
import { LayerHoveredTooltip } from "../../../Components/LayerHoveredTooltip";
-import { postToCityIO } from "../../../utils/utils";
import DeckglBase from "./DeckglBase";
import "mapbox-gl/dist/mapbox-gl.css";
import {
@@ -14,8 +13,25 @@ import {
GeojsonLayer,
TileMapLayer,
TrafficLayer,
+ ArcBaseLayer,
+ ColumnBaseLayer,
+ ContourBaseLayer,
+ GeoJsonBaseLayer,
+ GridBaseLayer,
+ GridCellBaseLayer,
+ HeatmapBaseLayer,
+ HexagonBaseLayer,
+ IconBaseLayer,
+ LineBaseLayer,
+ PathBaseLayer,
+ ScatterplotBaseLayer,
+ ScenegraphBaseLayer,
+ SimpleMeshBaseLayer,
+ TextBaseLayer
} from "./deckglLayers";
import { processGridData } from "./deckglLayers/GridLayer";
+import useWebSocket from "react-use-websocket"
+import { cityIOSettings } from "../../../settings/settings";
export default function DeckGLMap() {
// get cityio data from redux store
@@ -41,23 +57,37 @@ export default function DeckGLMap() {
const toggleRotateCamera = menuState?.viewSettingsMenuState?.ROTATE_CHECKBOX;
- // update the grid layer with every change to GEOGRIDDATA
- useEffect(() => {
- setGEOGRIDDATA(processGridData(cityIOdata));
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [cityIOdata.GEOGRIDDATA]);
+ const { sendJsonMessage } = useWebSocket(
+ cityIOSettings.cityIO.websocketURL,
+ {
+ share: true,
+ shouldReconnect: () => true,
+ },
+ )
- // post GEOGRIDDATA changes to cityIO
+ // Send changes to cityIO
useEffect(() => {
if (!editModeToggle && GEOGRIDDATA) {
let dataProps = [];
for (let i = 0; i < GEOGRIDDATA.features.length; i++) {
dataProps[i] = GEOGRIDDATA.features[i].properties;
}
- postToCityIO(dataProps, cityIOdata.tableName, "/GEOGRIDDATA/");
+ sendJsonMessage({
+ type: "UPDATE_GRID",
+ content: {
+ geogriddata: dataProps,
+ },
+ });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [editModeToggle]);
+ }, [editModeToggle])
+
+ // update the grid layer with every change to GEOGRIDDATA
+ useEffect(() => {
+ setGEOGRIDDATA(processGridData(cityIOdata));
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [cityIOdata.GEOGRIDDATA]);
+
const layersKey = {
TILE_MAP: TileMapLayer(),
@@ -146,9 +176,143 @@ export default function DeckGLMap() {
layersMenu.TRAFFIC_LAYER_CHECKBOX &&
layersMenu.TRAFFIC_LAYER_CHECKBOX.slider * 0.01,
}),
+
+ ARC: ArcBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.ARC_LAYER_CHECKBOX &&
+ layersMenu.ARC_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ COLUMN: ColumnBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.COLUMN_LAYER_CHECKBOX &&
+ layersMenu.COLUMN_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ CONTOUR: ContourBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.CONTOUR_LAYER_CHECKBOX &&
+ layersMenu.CONTOUR_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ GEOJSON_BASE: GeoJsonBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.GEOJSON_BASE_LAYER_CHECKBOX &&
+ layersMenu.GEOJSON_BASE_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ GRID_BASE: GridBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.GRID_BASE_LAYER_CHECKBOX &&
+ layersMenu.GRID_BASE_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ GRID_CELL: GridCellBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.GRIDCELL_LAYER_CHECKBOX &&
+ layersMenu.GRIDCELL_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ HEATMAP: HeatmapBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.HEATMAP_LAYER_CHECKBOX &&
+ layersMenu.HEATMAP_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ HEXAGON: HexagonBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.HEXAGON_LAYER_CHECKBOX &&
+ layersMenu.HEXAGON_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ ICON: IconBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.ICON_LAYER_CHECKBOX &&
+ layersMenu.ICON_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ LINE: LineBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.LINE_LAYER_CHECKBOX &&
+ layersMenu.LINE_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ PATH: PathBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.PATH_LAYER_CHECKBOX &&
+ layersMenu.PATH_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ SCATTERPLOT: ScatterplotBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.SCATTER_LAYER_CHECKBOX &&
+ layersMenu.SCATTER_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ SCENEGRAPH: ScenegraphBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.SCENEGRAPH_LAYER_CHECKBOX &&
+ layersMenu.SCENEGRAPH_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ MESH: SimpleMeshBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.MESH_LAYER_CHECKBOX &&
+ layersMenu.MESH_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
+ TEXT: TextBaseLayer({
+ data: cityIOdata,
+ opacity:
+ layersMenu &&
+ layersMenu.TEXT_LAYER_CHECKBOX &&
+ layersMenu.TEXT_LAYER_CHECKBOX.slider * 0.01,
+ }),
+
};
const layerOrder = [
+ "TEXT",
+ "ICON",
+ "MESH",
+ "SCENEGRAPH",
+ "SCATTERPLOT",
+ "PATH",
+ "LINE",
+ "GRID_CELL",
+ "GRID_BASE",
+ "GEOJSON_BASE",
+ "CONTOUR",
+ "COLUMN",
+ "ARC",
"ABM",
"AGGREGATED_TRIPS",
"TILE_MAP",
@@ -157,6 +321,8 @@ export default function DeckGLMap() {
"GEOJSON",
"ACCESS",
"TRAFFIC",
+ "HEATMAP",
+ "HEXAGON"
];
const renderDeckLayers = () => {
diff --git a/src/views/CityScopeJS/MenuContainer/ScenariosMenu/index.js b/src/views/CityScopeJS/MenuContainer/ScenariosMenu/index.js
index f8924afe..c86010d0 100644
--- a/src/views/CityScopeJS/MenuContainer/ScenariosMenu/index.js
+++ b/src/views/CityScopeJS/MenuContainer/ScenariosMenu/index.js
@@ -15,44 +15,54 @@ import {
Badge,
TextField,
} from "@mui/material";
+import DeleteSweepOutlinedIcon from '@mui/icons-material/DeleteSweepOutlined';
import DeleteIcon from "@mui/icons-material/Delete";
-import { postToCityIO, getModule, getTableID } from "../../../../utils/utils";
+import RestorePageOutlinedIcon from '@mui/icons-material/RestorePageOutlined';
+import useWebSocket from "react-use-websocket"
+import { cityIOSettings } from "../../../../settings/settings";
export default function ScenariosMenu() {
const [scenariosButtonsList, setScenariosButtonsList] = useState([]);
+ const [scenariosBinButtonsList, setScenariosBinButtonsList] = useState([]);
const [scenarioToRestore, setScenariosToRestore] = useState();
const [saveDialogState, setSaveDialogState] = useState(false);
const [loadDialogState, setLoadDialogState] = useState(false);
+ const [binDialogState, setBinDialogState] = useState(false);
const [scenarioTextInput, setScenarioTextInput] = useState({
name: "",
description: "",
});
// get cityIO data from redux store
const cityIOdata = useSelector((state) => state.cityIOdataState.cityIOdata);
- // get cityio name from redux store
- const cityIOtableName = useSelector(
- (state) => state.cityIOdataState.cityIOtableName
- );
+
+ const { sendJsonMessage } = useWebSocket(
+ cityIOSettings.cityIO.websocketURL,
+ {
+ share: true,
+ shouldReconnect: () => true,
+ },
+ )
const handleSaveThisState = () => {
handleClose();
- getTableID(cityIOtableName).then((id) => {
const newScenario = {
// ! to be updated from dynamic ui element
- name: scenarioTextInput.name || `${id}`,
- hash: id,
+ name: scenarioTextInput.name || `noname`,
description:
- scenarioTextInput.description || `no description for ${id} yet.`,
- };
- const tempArr = cityIOdata.scenarios ? [...cityIOdata.scenarios] : [];
- tempArr.push(newScenario);
- postToCityIO(tempArr, cityIOtableName, `/scenarios/`);
- });
+ scenarioTextInput.description || `no description yet.`,
+ }
+ sendJsonMessage({
+ type: "SAVE_SCENARIO",
+ content: newScenario,
+ })
+
+
};
const handleClose = () => {
setLoadDialogState(false);
setSaveDialogState(false);
+ setBinDialogState(false);
};
const handleOpenDialog = (scenario) => {
@@ -64,13 +74,11 @@ export default function ScenariosMenu() {
const handleRestoreThisState = async () => {
if (!scenarioToRestore) return;
- await getModule(scenarioToRestore.hash)
- .then((module) => {
- postToCityIO(module, cityIOtableName, `/GEOGRIDDATA/`);
- })
- .finally(() => {
- handleClose();
- });
+ sendJsonMessage({
+ type: "RESTORE_SCENARIO",
+ content: {name: scenarioToRestore.name},
+ })
+ handleClose();
};
const handleDeleteThisState = (scenario) => {
@@ -84,14 +92,37 @@ export default function ScenariosMenu() {
var index = tempArr.indexOf(scnToDelete[0]);
if (index !== -1) {
// remove the scenario from the array
- tempArr.splice(index, 1);
+ let scenarioToMod = tempArr[index]
+ sendJsonMessage({
+ type: "MODIFY_SCENARIO",
+ content: {name: scenarioToMod.name, isInBin:true},
+ })
+ }
+ handleClose()
+ };
+
+ const handleRestoreSce = (scenario) => {
+ // copy the scenarios array
+ const tempArr = [...cityIOdata.scenarios];
+ // find the clicked scenario in the array
+ var scnToDelete = tempArr.filter((obj) => {
+ return obj.hash === scenario.hash;
+ });
+ // find the index of the scenario to delete
+ var index = tempArr.indexOf(scnToDelete[0]);
+ if (index !== -1) {
+ // remove the scenario from the array
+ let scenarioToMod = tempArr[index]
+ sendJsonMessage({
+ type: "MODIFY_SCENARIO",
+ content: {name: scenarioToMod.name, isInBin:false},
+ })
}
- // post the new array to the server
- postToCityIO(tempArr, cityIOtableName, `/scenarios/`);
+ handleClose()
};
const createScenariosButtons = () => {
- const scenariosButtons = cityIOdata.scenarios.map((scenario, i) => {
+ const scenariosButtons = cityIOdata.scenarios.filter(x => !x.isInBin).map((scenario, i) => {
return (
{
+ const scenariosButtons = cityIOdata.scenarios.filter(x => x.isInBin).map((scenario, i) => {
+ return (
+
+
+
+
+
+ {
+ handleRestoreSce(scenario);
+ }}
+ aria-label="delete"
+ size="large"
+ >
+
+
+
+ );
+ });
+ return scenariosButtons;
+ };
+
useEffect(() => {
// check if there are any scenarios in the cityIOdata
if (!cityIOdata.scenarios) return;
const scenariosButtons = createScenariosButtons();
setScenariosButtonsList(scenariosButtons);
+ const scenariosBinButtons = createBinScenariosButtons();
+ setScenariosBinButtonsList(scenariosBinButtons);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cityIOdata]);
@@ -170,6 +257,7 @@ export default function ScenariosMenu() {
>
Save This Scenario
+
{scenariosButtonsList}
@@ -237,6 +325,22 @@ export default function ScenariosMenu() {
+
+
+
>
);
}
diff --git a/src/views/GridEditor/EditorMenu/CommitGridMenu/index.js b/src/views/GridEditor/EditorMenu/CommitGridMenu/index.js
index defc59ae..c06c99e2 100644
--- a/src/views/GridEditor/EditorMenu/CommitGridMenu/index.js
+++ b/src/views/GridEditor/EditorMenu/CommitGridMenu/index.js
@@ -10,7 +10,7 @@ import LoadingButton from "@mui/lab/LoadingButton";
const reqResponseUI = (response, tableName) => {
let cityscopeJSendpoint =
- "https://cityscope.media.mit.edu/CS_cityscopeJS/?cityscope=" + tableName;
+ "https://cityio.media.mit.edu/?cityscope=" + tableName;
// create the feedback text
let resText = (