Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: frontend v1 #3

Merged
merged 10 commits into from
Jan 2, 2022
Prev Previous commit
Next Next commit
feat: satellite name in API response & polar view
  • Loading branch information
SharkEzz committed Jan 2, 2022
commit 039deb46c378d8d5b69b15309c40dcb72ef7cb94
6 changes: 6 additions & 0 deletions dto/Tracking.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ type TrackingResponse struct {
sgp4.Observation
GeneratedAt time.Time
}

type ObservationWsResponse struct {
SatelliteName string
GeneratedAt time.Time
sgp4.Observation
}
8 changes: 7 additions & 1 deletion handlers/TrackingHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handlers

import (
"net/http"
"strings"
"time"

"github.com/SharkEzz/sattrack/dto"
Expand Down Expand Up @@ -75,7 +76,12 @@ func HandleWsTracking(c *websocket.Conn, db *gorm.DB) {

for {
observation := sgp4.ObservationFromLocation(lat, lng, alt)
if err := c.WriteJSON(observation); err != nil {
responseDto := dto.ObservationWsResponse{
SatelliteName: strings.TrimSpace(tle.Name()),
GeneratedAt: time.Now().UTC(),
Observation: observation,
}
if err := c.WriteJSON(responseDto); err != nil {
return
}
if _, _, err = c.ReadMessage(); err != nil {
Expand Down
8 changes: 8 additions & 0 deletions js/hooks/useWebsocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { useRef, useState } from 'react';
function useWebsocket() {
const [opened, setOpened] = useState(false);
const [message, setMessage] = useState(null);
const [isOpening, setIsOpening] = useState(false);
const [isClosing, setIsClosing] = useState(false);
const websocket = useRef(null);

/**
Expand All @@ -15,14 +17,17 @@ function useWebsocket() {

const handleClose = () => {
setOpened(false);
setIsClosing(false);
};

const handleOpen = () => {
setOpened(true);
setIsOpening(false);
};

const closeConnection = () => {
websocket.current.close();
setIsClosing(true);
};

/**
Expand All @@ -42,9 +47,12 @@ function useWebsocket() {
websocket.current.onopen = handleOpen;
websocket.current.onmessage = handleMessage;
websocket.current.onerror = closeConnection;
setIsOpening(true);
};

return {
isOpening,
isClosing,
opened,
message,
openConnection,
Expand Down
89 changes: 89 additions & 0 deletions js/src/components/UI/PolarView.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { useEffect, useRef, useCallback } from 'react';
import PropTypes from 'prop-types';
import azelToXY from '../../utils/azelToXY';

function PolarView({ azimuth, elevation }) {
const canvasRef = useRef(null);

const draw = useCallback(
(canvas) => {
const ctx = canvas.getContext('2d');
ctx.font = '12px sans-serif';
const { height } = canvas.getBoundingClientRect();
const { width } = canvas.getBoundingClientRect();
const centerX = width / 2;
const centerY = height / 2;
const offset = 15;
const circleRadius = width / 2 - offset;

const { x, y } = azelToXY(
centerX,
centerY,
circleRadius,
azimuth,
elevation,
);

ctx.clearRect(0, 0, width, height);

ctx.strokeStyle = '#00000066';

// Draw circles
ctx.beginPath();
ctx.arc(centerX, centerY, circleRadius, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc(centerX, centerY, width / 3.3, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc(centerX, centerY, width / 6.6, 0, 2 * Math.PI);
ctx.stroke();
// Draw lines
ctx.beginPath();
ctx.moveTo(centerX, offset);
ctx.lineTo(centerX, height - offset);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(15, centerY);
ctx.lineTo(width - offset, centerY);
ctx.stroke();
// Draw N/S/E/W
ctx.fillStyle = '#00000099';
ctx.beginPath();
ctx.fillText('N', centerX - 4, offset - 5);
ctx.beginPath();
ctx.fillText('S', centerX - 4, height - 1);
ctx.beginPath();
ctx.fillText('E', width - offset + 5, centerY + 4);
ctx.beginPath();
ctx.fillText('W', 0, centerY + 5);
// Draw satellite position
ctx.fillStyle = '#FF5500';
if (x !== null || y !== null) {
ctx.beginPath();
ctx.arc(x, y, 5, 0, 2 * Math.PI);
ctx.fill();
}
},
[azimuth, elevation],
);

useEffect(() => {
if (canvasRef.current) {
draw(canvasRef.current);
}
}, [canvasRef, draw]);

return (
<div className="w-100 text-center">
<canvas className="mb-3" height="320" width="320" ref={canvasRef} />
</div>
);
};

PolarView.propTypes = {
azimuth: PropTypes.number.isRequired,
elevation: PropTypes.number.isRequired,
};

export default PolarView;
25 changes: 20 additions & 5 deletions js/src/components/UI/TrackingForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ function TrackingForm({
closeConnection,
getLocation,
location,
isOpening,
isClosing,
}) {
const {
register,
Expand All @@ -28,7 +30,7 @@ function TrackingForm({
const handleCloseConnection = (e) => {
e.preventDefault();
closeConnection();
}
};

const handleOpenConnection = ({ satellite, lat, lng, alt }) => {
openConnection('ws:https://127.0.0.1:8000/ws/tracking', satellite, {
Expand All @@ -50,7 +52,7 @@ function TrackingForm({
required: 'You must select a satellite',
})}
>
<option value={25544}>ISS</option>
<option value={33591}>ISS</option>
</Form.Select>
</Form.Group>
<hr />
Expand All @@ -59,7 +61,7 @@ function TrackingForm({
<Col>
<h4>Location</h4>
</Col>
<Col style={{ textAlign: 'right' }}>
<Col className="text-end">
<Button size="sm" onClick={getLocation}>
Localize
</Button>
Expand Down Expand Up @@ -116,11 +118,22 @@ function TrackingForm({
<Card.Footer>
<div className="d-flex justify-content-end">
{opened ? (
<Button variant="danger" size="sm" type="button" onClick={handleCloseConnection}>
<Button
variant="danger"
size="sm"
type="button"
onClick={handleCloseConnection}
disabled={isClosing}
>
Stop
</Button>
) : (
<Button variant="success" size="sm" type="submit">
<Button
variant="success"
size="sm"
type="submit"
disabled={isOpening}
>
Start
</Button>
)}
Expand All @@ -141,6 +154,8 @@ TrackingForm.propTypes = {
lng: PropTypes.number,
alt: PropTypes.number,
}),
isOpening: PropTypes.bool.isRequired,
isClosing: PropTypes.bool.isRequired,
};

TrackingForm.defaultProps = {
Expand Down
51 changes: 48 additions & 3 deletions js/src/pages/TrackingPage.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Card, Col, Container, Row } from 'react-bootstrap';
import { Badge, Card, Col, Container, Row } from 'react-bootstrap';
import useLocation from '../../hooks/useLocation';
import useWebsocket from '../../hooks/useWebsocket';
import PolarView from '../components/UI/PolarView';
import TrackingForm from '../components/UI/TrackingForm';

function TrackingPage() {
const { opened, openConnection, closeConnection, message } = useWebsocket();
const { opened, openConnection, closeConnection, message, isOpening, isClosing } = useWebsocket();
const { getLocation, location } = useLocation();

return (
Expand All @@ -13,6 +14,8 @@ function TrackingPage() {
<Col lg={5} md={12}>
<TrackingForm
opened={opened}
isOpening={isOpening}
isClosing={isClosing}
openConnection={openConnection}
closeConnection={closeConnection}
location={location}
Expand All @@ -22,7 +25,49 @@ function TrackingPage() {
<Col lg={7} md={12}>
<Card>
<Card.Header>Tracking panel</Card.Header>
<Card.Body>{message?.SatLat}</Card.Body>
<Card.Body>
<h4>
{message ? message.SatelliteName : 'No satellite tracking'}
</h4>
<Row>
<Col md={6} sm={12}>
{message && (
<>
<Row>
<Col sm={4}>Azimuth:</Col>
<Col>
<Badge>{message.Azimuth.toFixed(2)}</Badge>
</Col>
</Row>
<Row>
<Col sm={4}>Elevation:</Col>
<Col>
<Badge>{message.Elevation.toFixed(2)}</Badge>
</Col>
</Row>
<Row>
<Col sm={4}>Latitude:</Col>
<Col>
<Badge>{message.SatLat.toFixed(2)}</Badge>
</Col>
</Row>
<Row>
<Col sm={4}>Longitude:</Col>
<Col>
<Badge>{message.SatLng.toFixed(2)}</Badge>
</Col>
</Row>
</>
)}
</Col>
<Col md={6} sm={12}>
<PolarView
azimuth={message ? Number(message.Azimuth.toFixed(2)) : null}
elevation={message ? Number(message.Elevation.toFixed(2)) : null}
/>
</Col>
</Row>
</Card.Body>
</Card>
</Col>
</Row>
Expand Down
35 changes: 35 additions & 0 deletions js/src/utils/azelToXY.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Return the polar coordinates in a circle using the current azimuth and elevation
*
* @param {Number} centerX Polar view circle center X position
* @param {Number} centerY Polar view circle center Y position
* @param {Number} radius Polar view circle radius
* @param {Number} azimuth Current azimuth (in deg)
* @param {Number} elevation Current elevation (in deg)
* @returns {{x: Number, y: Number}} The polar coordinates of the tracked element
*/
const azelToXY = (centerX, centerY, radius, azimuth, elevation) => {
if (azimuth < 0 || elevation < 0) {
return {
x: null,
y: null,
};
}

// Convert to radian
const az = (azimuth * Math.PI) / 180;
const el = (elevation * Math.PI) / 180;

const rel = radius - (2 * radius * el) / Math.PI;

// Compute X and Y positions
const x = centerX + rel * Math.sin(az);
const y = centerY - rel * Math.cos(az);

return {
x,
y,
};
};

export default azelToXY;