Skip to content

Commit

Permalink
Map.js: created separate file to make Control.js clean.
Browse files Browse the repository at this point in the history
  • Loading branch information
SanjitRaman committed Jun 19, 2024
1 parent da9babb commit f47e817
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 122 deletions.
10 changes: 5 additions & 5 deletions server/website/public/data/map_processed/map.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"image": "map_processed/map.png",
"resolution": 0.1,
"resolution": 0.05,
"origin": [
-0.5,
-0.5,
0.0
-2.09,
-2.35,
0
],
"waypoints": [
{
Expand All @@ -13,7 +13,7 @@
"image": "data/incidents/incident1.jpg"
},
{
"x": 0.3,
"x": 3,
"y": 0.3,
"image": "data/incidents/incident2.jpg"
}
Expand Down
Binary file modified server/website/public/data/map_processed/map.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 9 additions & 1 deletion server/website/public/data/map_raw/map.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@ resolution: 0.05
origin: [-2.09, -2.35, 0]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.25
free_thresh: 0.25

waypoints:
- x: -0.3
y: -0.3
image: data/incidents/incident1.jpg
- x: 0.3
y: 0.3
image: data/incidents/incident2.jpg
Empty file.
76 changes: 76 additions & 0 deletions server/website/src/components/Map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useState, useEffect } from 'react';

const Map = ({ selectedIncidentProp }) => {
const [mapData, setMapData] = useState(null);
const [mapImage, setMapImage] = useState(null);
const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 });

const handleWaypointClick = (incidentImage) => {
selectedIncidentProp(incidentImage); // Propagate the selected incident to the parent
};

useEffect(() => {
// Fetch map information from JSON file
fetch('/data/map_processed/map.json')
.then(response => response.json())
.then(data => setMapData(data))
.catch(error => console.error('Error loading map information:', error));

// Fetch map image
fetch('/data/map_processed/map.png')
.then(response => response.blob())
.then(images => {
// Then create a local URL for that image and set it
let outside = URL.createObjectURL(images);
setMapImage(outside);

// Create an image object to get the dimensions
const img = new Image();
img.src = outside;
img.onload = () => {
setImageDimensions({ width: img.width, height: img.height });
};
})
}, []);

const convertCoordsToPixels = (x, y, resolution, origin, imgWidth, imgHeight) => {
const x_pixel = (x - origin[0]) / resolution;
const y_pixel = imgHeight - (y - origin[1]) / resolution;
return { x_pixel, y_pixel };
};

return (
<div style={{ position: 'relative' }}>
{mapImage && <img src={mapImage} alt="Map" style={{ width: '100%' }} />}
{mapData && imageDimensions.width > 0 && imageDimensions.height > 0 && mapData.waypoints.map((waypoint, index) => {
const { x_pixel, y_pixel } = convertCoordsToPixels(
waypoint.x,
waypoint.y,
mapData.resolution,
mapData.origin,
imageDimensions.width,
imageDimensions.height
);
return (
<div
key={index}
style={{
position: 'absolute',
left: `${x_pixel}px`,
top: `${y_pixel}px`,
width: '10px',
height: '10px',
backgroundColor: 'red',
borderRadius: '50%',
cursor: 'pointer',
transform: 'translate(-50%, -50%)'
}}
onClick={() => handleWaypointClick(waypoint.image)} // Propagate the incident image URL to the parent component
/>
);
})}
</div>
);
};

export default Map;
38 changes: 1 addition & 37 deletions server/website/src/pages/Control.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@
padding-left: 10px;
}

.camera-feed,
.map {
.camera-feed {
border: 1px solid black;
padding: 0;
/* Remove padding */
margin-bottom: 10px;
flex: 1;
min-height: 0;
Expand Down Expand Up @@ -63,7 +61,6 @@
border-radius: 5px;
flex: 1;
overflow-y: auto;
/* Ensure the terminal is scrollable */
display: flex;
flex-direction: column;
}
Expand Down Expand Up @@ -123,39 +120,6 @@
color: #1e1e1e;
}

/* Map Styles */
#map-container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}

#map {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
}

.waypoint {
position: absolute;
width: 10px;
height: 10px;
background-color: red;
border-radius: 50%;
cursor: pointer;
}

#incident-image {
width: 100%;
height: 100%;
object-fit: cover;
/* Ensure the image covers the entire area */
}

#camera-image {
display: none;
margin-top: 20px;
Expand Down
85 changes: 6 additions & 79 deletions server/website/src/pages/Control.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { useEffect, useContext, useState, useRef, useCallback } from 'react';
import React, { useEffect, useContext, useState, useRef } from 'react';
import './Control.css';
import TopBar from '../components/TopBar';
import WASDControl from '../components/WASDControl';
import { initializeMQTT, sendCLICommand } from '../utils/mqttServiceControl';
import { GlobalContext } from '../context/GlobalState';
import { fetchData } from '../utils/fetchTerminalCommands';
import Map from '../components/Map';

const Control = () => {
const { runId, batteryPercentage, setBatteryPercentage } = useContext(GlobalContext);
Expand All @@ -13,19 +14,15 @@ const Control = () => {
const terminalRef = useRef(null);
const terminalEndRef = useRef(null);
const [isManualScroll, setIsManualScroll] = useState(false);
const [mapInfo, setMapInfo] = useState(null);
const [incidentImage, setIncidentImage] = useState(null);
const canvasRef = useRef(null);
const [selectedIncident, setSelectedIncident] = useState(null);

// Handler for incoming debug messages
const handleDebugMessage = (message) => {
setTerminalMessages(prevMessages => {
const messages = [...prevMessages, { ...message, type: 'received' }];
return messages.length > 20 ? messages.slice(messages.length - 20) : messages;
});
};

// Fetch initial data and initialize MQTT on component mount or runId change
useEffect(() => {
if (runId) {
fetchData(runId)
Expand All @@ -38,22 +35,14 @@ const Control = () => {
});
initializeMQTT(setBatteryPercentage, handleDebugMessage, runId);
}

// Fetch map information from JSON file
fetch('/data/map_processed/map.json')
.then(response => response.json())
.then(data => setMapInfo(data))
.catch(error => console.error('Error loading map information:', error));
}, [runId, setBatteryPercentage]);

// Scroll terminal to the latest message if not in manual scroll mode
useEffect(() => {
if (!isManualScroll && terminalEndRef.current) {
terminalEndRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, [terminalMessages, isManualScroll]);

// Handle scroll to determine if the user has manually scrolled
const handleScroll = () => {
if (terminalRef.current) {
const { scrollTop, scrollHeight, clientHeight } = terminalRef.current;
Expand All @@ -62,12 +51,10 @@ const Control = () => {
}
};

// Handle touch move to set manual scroll mode
const handleTouchMove = () => {
setIsManualScroll(true);
};

// Send CLI command and add the message to the terminal
const handleSendCLICommand = () => {
if (cliInput.trim() !== '') {
const message = { timestamp: new Date().toLocaleTimeString(), text: cliInput, type: 'sent' };
Expand All @@ -77,79 +64,23 @@ const Control = () => {
});
sendCLICommand(cliInput, runId);
setCliInput('');
setIsManualScroll(false); // Reset manual scroll state when sending a command
setIsManualScroll(false);
}
};

// Handle Enter key press to send CLI command
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
handleSendCLICommand();
}
};

// Function to convert map coordinates to pixel positions
const mapToPixel = (x, y, mapWidth, mapHeight, mapResolution, mapOrigin) => {
const pixelX = (x - mapOrigin[0]) / mapResolution;
const pixelY = mapHeight - (y - mapOrigin[1]) / mapResolution; // Invert Y axis
return { pixelX, pixelY };
};

// Function to draw the map on the canvas
const drawMap = useCallback((mapImage, mapResolution, mapOrigin, waypoints) => {
const canvas = canvasRef.current;
const context = canvas.getContext('2d');

// Draw the map image on the canvas
const img = new Image();
img.src = mapImage;
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
context.drawImage(img, 0, 0);

// Draw waypoints on the canvas
waypoints.forEach((waypoint) => {
const { x, y, image } = waypoint;
const { pixelX, pixelY } = mapToPixel(x, y, img.width, img.height, mapResolution, mapOrigin);

context.beginPath();
context.arc(pixelX, pixelY, 5, 0, 2 * Math.PI);
context.fillStyle = 'red';
context.fill();
context.closePath();

// Add click event listener to waypoints
canvas.addEventListener('click', (event) => {
const rect = canvas.getBoundingClientRect();
const clickX = event.clientX - rect.left;
const clickY = event.clientY - rect.top;

if (Math.sqrt((clickX - pixelX) ** 2 + (clickY - pixelY) ** 2) < 5) {
setIncidentImage(image);
}
});
});
};
}, []);

useEffect(() => {
if (mapInfo) {
drawMap('/data/map_processed/map.png', mapInfo.resolution, mapInfo.origin, mapInfo.waypoints);
}
}, [mapInfo, drawMap]);

return (
<div className="control-container">
<div className="control-left">
<div className="camera-feed">
<TopBar batteryPercentage={batteryPercentage} />
{incidentImage ? (
<img id="incident-image" src={incidentImage} alt="Incident view" />
) : (
<p>Select an incident to view.</p>
)}
{selectedIncident ? <img src={selectedIncident} alt="Selected Incident" /> : <p>Select an incident to view.</p>}
</div>
<div className="terminal-container">
<h2 className="terminal-title">TERMINAL</h2>
Expand Down Expand Up @@ -182,11 +113,7 @@ const Control = () => {
</div>
</div>
<div className="control-right">
<div className="map">
<div id="map-container">
<canvas ref={canvasRef} id="map-canvas" />
</div>
</div>
<Map selectedIncidentProp={setSelectedIncident} />
</div>
<WASDControl />
</div>
Expand Down

0 comments on commit f47e817

Please sign in to comment.