Skip to content

Starlink: React JS based Starlink Trajectory Visualization

1 Introdcution

  • Designed and developed a visualization dashboard using ReactJS and D3 to track satellites in real-time based on geo-location.
  • Built location, altitude, and duration based selector to refine satellite search.
  • Animated selected satellite paths on a world map using D3 to improve the user-friendliness.
  • Deployed the dashboard to Amazon Web Service for demonstration.

1.1 What is SpaceX project about

https://www.starlink.com/

1.2 Where can we get the dataset

https://www.n2yo.com/api/#positions

2 Project Set Up

2.1 Get a key

Register on N2Yo : https://www.n2yo.com/login/register/

Screenshot 2023-09-23 at 19.11.54

Screenshot 2023-09-23 at 19.12.57

2.2 Get an IDE

Webstorm or IntelliJ or others (vs code)

2.3 Create SpaceX project

Method1 (recommended) :

npx create-react-app starlink
cd starlink

Screenshot 2023-09-23 at 22.45.28

Method 2:

npm install -g create-react-app  create-react-app starlink  
cd starlink

2.4 Project Structure

image-20230923225209305

Create folders.

2.5 Move files to folders

Screenshot 2023-09-23 at 22.55.10

3 Steps - Part 1

3.1 Download starlink logo and save to src/assets/images

https://stackblitz.com/edit/react-starlink1?file=src%2Fassets%2Fimages%2Fspacex_logo.svg

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1" x="0px" y="0px" viewBox="0 0 400 50"
     xmlns="http://www.w3.org/2000/svg" >
    <title>SpaceX Logo</title>
    <g class="letter_s">
        <path fill="white" d="M37.5,30.5H10.9v-6.6h34.3c-0.9-2.8-3.8-5.4-8.9-5.4H11.4c-5.7,0-9,2.1-9,6.7v4.9c0,4,3.4,6.3,8.4,6.3h26.9v7H1.5
          c0.9,3.8,3.8,5.8,9,5.8h27.1c5.7,0,8.5-2.2,8.5-6.9v-4.9C46.1,33.1,42.8,30.8,37.5,30.5z"></path>
    </g>
    <g class="letter_p">
        <path fill="white" d="M91.8,18.6H59v30.7h9.3V37.5h24.2c6.7,0,10.4-2.3,10.4-7.7v-3.4C102.8,21.4,98.6,18.6,91.8,18.6z M94.8,28.4
          c0,2.2-0.4,3.4-4,3.4H68.3l0.1-8h22c4,0,4.5,1.2,4.5,3.3V28.4z"></path>
    </g>
    <g class="letter_a">
        <polygon fill="white" points="129.9,17.3 124.3,24.2 133.8,37.3 114,37.3 109.1,42.5 137.7,42.5 142.6,49.3 153.6,49.3     "></polygon>
    </g>
    <g class="letter_c">
        <path fill="white" d="M171.4,23.9h34.8c-0.9-3.6-4.4-5.4-9.4-5.4h-26c-4.5,0-8.8,1.8-8.8,6.7v17.2c0,4.9,4.3,6.7,8.8,6.7h26.3
          c6,0,8.1-1.7,9.1-5.8h-34.8V23.9z"></path>
    </g>
    <g class="letter_e">
        <polygon fill="white" points="228.3,43.5 228.3,34.1 247,34.1 247,28.9 218.9,28.9 218.9,49.3 260.4,49.3 260.4,43.5   "></polygon>
        <rect fill="white" x="219.9" y="18.6" width="41.9" height="5.4"></rect>
    </g>
    <g class="letter_x">
        <path fill="white" d="M287.6,18.6H273l17.2,12.6c2.5-1.7,5.4-3.5,8-5L287.6,18.6z"></path>
        <path fill="white" d="M308.8,34.3c-2.5,1.7-5,3.6-7.4,5.4l13,9.5h14.7L308.8,34.3z"></path>
    </g>
    <g class="letter_swoosh">
        <path fill="white" d="M399,0.7c-80,4.6-117,38.8-125.3,46.9l-1.7,1.6h14.8C326.8,9.1,384.3,2,399,0.7L399,0.7z"></path>
    </g>
</svg>

Screenshot 2023-09-23 at 23.41.19

3.2 Update index.js (update import path for App)

... ... 
import App from './components/App';

... ... 

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <App />
);

......

Screenshot 2023-09-23 at 23.43.35

3.3 Create Header.js Component

import React, {Component} from 'react';
import spacex_logo  from '../assets/images/spacex_logo.svg';

class Header extends Component {
    render() {
        return (
            <header className="App-header">
                <img src={spacex_logo} className="App-logo" alt="logo" />
                <p className="title">
                    StarLink Tracker
                </p>
            </header>
        );
    }
}
export default Header;

Screenshot 2023-09-23 at 23.50.12

3.4 Add Header.js to App.js

import React from 'react';
import Header from './Header';

function App() {
  return (
    <div className="App">
      <Header />
    </div>
  );
}

export default App;

Screenshot 2023-09-23 at 23.54.12

3.5 Add App.css to index.css

@import "./styles/App.css";

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
     'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
      sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
  monospace;
}

Screenshot 2023-09-24 at 00.00.00

3.6 Update App.css (override whole file)

.App {
 text-align: center;
 min-height: 100vh;
 overflow: hidden;
 display: block;
 position: relative;
 padding-bottom: 5px;
}

Screenshot 2023-09-24 at 00.18.43

3.7 Create Header.css and import it to index.css

...
@import "./styles/Header.css";
...

Screenshot 2023-09-24 at 00.21.14

3.8 Update Header.css

.App-logo {
   height: 28px;
   margin-left: 20px;
   margin-bottom: 10px;
}


.App-header {
   display: flex;
   align-items: center;
   height: 70px;
   width: 100vw;
   color: #fff;
   background-color: #00305C;
   box-shadow: rgb(220, 220, 220) 0px 2px 10px;
}

.title {
   font-size: 20px;
   font-weight: bold;
   margin: 0px;
}

Screenshot 2023-09-24 at 00.34.25

3.9 Create Footer.js Component

import React, {Component} from 'react';

class Footer extends Component {
    render() {
        return (
            <footer className="footer">
                ©2020 StarLink Tracker. All Rights Reserved. Website Made by Your name
            </footer>
        );
    }
}

export default Footer;

Screenshot 2023-09-24 at 00.37.19

3.10 Add Footer.js to App.js

import React from 'react';
import Header from './Header';
import Footer from './Footer';

function App() {
  return (
    <div className="App">
      <Header />
      <Footer />
    </div>
  );
}

export default App;

Screenshot 2023-09-24 at 00.38.35

3.11 Create Footer.css and add it to index.css

...
@import "./styles/Footer.css";
...

Screenshot 2023-09-24 at 00.39.53

3.12 Update Footer.css

.footer {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
    width: 100%;
    text-align: center;
}

Screenshot 2023-09-24 at 00.47.53

npm start

Screenshot 2023-09-24 at 00.52.25

3.13 install AntDesign

https://ant.design/docs/react/introduce

npm install --save antd

Screenshot 2023-09-24 at 00.54.39

3.14 Add antd css in index.css (at the very top of the file)

@import '~antd/dist/antd.css';
... ... 

After 2023 https://ant.design/docs/react/migration-v5

Screenshot 2023-09-24 at 01.09.16

3.15 Create Main.js Component

import React, {Component} from 'react';
import { Row, Col } from 'antd';

class Main extends Component {
    render() {
        return (
            <Row>
                main
            </Row>
        );
    }
}
export default Main;

Screenshot 2023-09-24 at 01.15.32

3.16 Create Main.css and add it to index.css

...
@import "./styles/Main.css";
...

Leave Main.css empty for now

Screenshot 2023-09-24 at 01.16.51

3.17 Add Main.js to App.js

import React from 'react';
import Header from './Header';
import Main from './Main';
import Footer from './Footer';

function App() {
  return (
    <div className="App">
      <Header />
      <Main />
      <Footer />
    </div>
  );
}

export default App;

Screenshot 2023-09-24 at 01.18.45

3.18 Update Main.js

Screenshot 2023-09-24 at 01.22.48

3.19 Update Main.css

.left-side {
    padding: 20px;
}

.right-side {
    height: 760px;
}

Screenshot 2023-09-24 at 01.24.13

3.20 Create SatSetting.js Component

import React, {Component} from 'react';

class SatSetting extends Component {
    render() {
        return (
            <div>
                SatSetting
            </div>
        );
    }
}

export default SatSetting;

Screenshot 2023-09-24 at 01.27.42

3.21 Add SatSetting.js Component to Main.js

import React, {Component} from 'react';
import { Row, Col } from 'antd';
import SatSetting from './SatSetting';

class Main extends Component {
    render() {
        return (
            <Row className='main'>
                <Col span={8} className="left-side">       // 占8个单位
                    <SatSetting />
                </Col>
                <Col span={16} className="right-side">     // 占16个单位     左右1:2 的关系
                    right
                </Col>
            </Row>
        );
    }
}

export default Main;

Screenshot 2023-09-24 at 01.29.27

https://ant.design/components/grid

3.22 Update SatSetting.js Component

import React, {Component} from 'react';
import {Form, Button, InputNumber} from 'antd';

class SatSetting extends Component {
    showSatellite = values => {
        console.log('Received values of form: ', values);
    }

    render() {
        return (
            <Form
                name="wrap"
                labelCol={{
                    flex: '110px',
                }}
                labelAlign="left"
                labelWrap
                wrapperCol={{
                    flex: 1,
                }}
                colon={false}
                className="sat-setting"
                onFinish={this.showSatellite}
            >

                <Form.Item
                    label="Longitude(degrees)"
                    name="longitude"
                    rules={[
                        {
                            required: true,
                            message: "Please input your Longitude",
                        }
                    ]}
                >
                    <InputNumber min={-180} max={180}
                                 style={{width: "100%"}}
                                 placeholder="Please input Longitude"
                    />
                </Form.Item>

                <Form.Item
                    label="Latitude(degrees)"
                    name="latitude"
                    rules={[
                        {
                            required: true,
                            message: "Please input your Latitude",
                        }
                    ]}
                >
                    <InputNumber min={-90} max={90}
                                 style={{width: "100%"}}
                                 placeholder="Please input Latitude"
                    />
                </Form.Item>

                <Form.Item
                    label="Altitude(meters)"
                    name="elevation"
                    rules={[
                        {
                            required: true,
                            message: "Please input your Altitude",
                        }
                    ]}
                >
                    <InputNumber min={-413} max={8850}
                                 style={{width: "100%"}}
                                 placeholder="Please input Altitude"
                    />
                </Form.Item>

                <Form.Item
                    label="Radius(degrees)"
                    name="altitude"
                    rules={[
                        {
                            required: true,
                            message: "Please input your Radius",
                        }
                    ]}
                >
                    <InputNumber min={0} max={90}
                                 style={{width: "100%"}}
                                 placeholder="Please input Radius"
                    />
                </Form.Item>

                <Form.Item
                    label="Duration(secs)"
                    name="duration"
                    rules={[
                        {
                            required: true,
                            message: "Please input your Duration",
                        }
                    ]}
                >
                    <InputNumber min={0} max={90}
                                 style={{width: "100%"}}
                                 placeholder="Please input Duration"
                    />
                </Form.Item>

                <Form.Item className="show-nearby">
                    <Button type="primary" htmlType="submit" style={{textAlign: "center"}}>
                        Find Satellite
                    </Button>
                </Form.Item>

            </Form>
        );
    }
}

export default SatSetting;

Screenshot 2023-09-24 at 01.30.08

3.23 Create SatSetting.css and add it to index.css

.sat-setting {
    width: 100%;
    background-color: #fbfbfb;
    border: 1px solid #d9d9d9;
    padding: 10px 10px 0px 5px;
    text-align: left;
}

.show-nearby {
    display: flex;
    justify-content: center;
}

Screenshot 2023-09-24 at 01.30.44

Screenshot 2023-09-23 at 11.55.19 PM

export default Main; 这些要大写

Steps - Part 2

  1. Create SatelliteList.js component

Screenshot 2023-12-10 at 03.45.18

  1. Update Main.js
import React, {Component} from "react";
import { Row, Col } from 'antd';
import SatSetting from './SatSetting';
import SatelliteList from './SatelliteList';

class Main extends Component{
    render() {
        return (
            <Row className='main'>
                <Col span={8} className='left-side'>
                    <SatSetting />
                    <SatelliteList />
                </Col>
                <Col span={16} className='right-side'>
                    right
                </Col>
            </Row>
        );
    }
}
export default Main;

Screenshot 2023-12-10 at 03.56.07

  1. Create StatelliteList.css and add it to index.css

@import "./styles/SatSetting.css";

in SatelliteList.css

.sat-list-box {
    border: 2px solid #ccc;
    height: 380px;
    margin-top: 10px;
    padding: 10px;
}

.btn-container {
 text-align: center;
}

.sat-list {
 height: 300px;
 overflow: scroll;
}

.spin-box{
 display: flex;
 align-items: center;
 justify-content: center;
 height: 335px;
}

Screenshot 2023-12-10 at 10.59.12

  1. Install axios library

https://github.com/axios/axios

npm install --save axios

  1. Configure development - server

https://create-react-app.dev/docs/deployment/

https://create-react-app.dev/docs/proxying-api-requests-in-development/

Update package.json

{
  "name": "starlink",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "antd": "^5.9.2",
    "axios": "^1.6.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "proxy": "https://api.n2yo.com"
}

Screenshot 2023-12-10 at 11.44.53

  1. Create constants.js under src folder

https://www.n2yo.com/login/edit/

export const SAT_API_KEY = "YOUR KEY";

export const STARLINK_CATEGORY = "52";

export const NEARBY_SATELLITE = "rest/v1/satellite/above";

export const BASE_URL = ""; // your backend endpoint when deloy

Screenshot 2023-12-10 at 11.55.57

Screenshot 2023-12-10 at 13.09.22

  1. Update Main.js
import React, {Component} from "react";
import { Row, Col } from 'antd';
import axios from "axios"; 

import SatSetting from './SatSetting';
import SatelliteList from './SatelliteList';
import { SAT_API_KEY, BASE_URL, NEARBY_SATELLITE, STARLINK_CATEGORY} from "../constants"; 

/*
js中的import的格式是: import {模块中的方法} from '模块的路径'
其中模块的路径可以是相对路径,也可以是绝对路径,但是一定要加上模块的后缀名,否则会报错。
{} 里面的方法是模块中导出的方法,如果是默认导出的方法,那么就不需要加上{},直接导入就可以了。
import React, {Component} from "react"; 从react模块中导入React和Component方法。
React是一个类,Component是一个方法。
import {Row, Col } from 'antd'; 从antd模块中导入Row和Col方法。
 */

class Main extends Component{
 state = {
         setting: {},
         satInfo: {},
         satList: [],
         isLoadingList: false
 }

 showNearbySatellite = (setting) => {
         console.log('show nearby')
         this.setState({
                 setting: setting
         })
         this.fetchSatellite(setting);
 }

 fetchSatellite = (setting) => {
         console.log("fetching")
         const { latitude, longitude, elevation, altitude } = setting;
         const url =         `${BASE_URL}/${NEARBY_SATELLITE}/${latitude}/${longitude}/${elevation}/${altitude}/${STARLINK_CATEGORY}/$&apiKey=${SAT_API_KEY}`;
         this.setState({
                 isLoadingList: true
         });

         axios.get(url)
                 .then( res => {
                         console.log(res.data);
                         this.setState({
                                 satInfo: res.data,
                                 isLoadingList: false
                         })
                 })
                 .catch( error => {
                         this.setState({
                                 isLoadingList: false
                         });
                         console.log('err in fetch satellite -> ', error);
                 })

 }

    render() {
         const { satInfo, isLoadingList, satList, setting } = this.state;

        return (
            <Row className='main'>
                <Col span={8} className='left-side'>
                    <SatSetting />
                    <SatelliteList />
                </Col>
                <Col span={16} className='right-side'>
                    right
                </Col>
            </Row>
        );
    }
}
export default Main;
/*
class Main extends Component { ... } 定义了一个名为 Main 的类组件
extends Component 表示 Main 组件继承了 Component 
render() { ... } 定义了一个名为 render 的方法这个方法返回一个 React 元素
<Row> ... </Row> 是一个 React 元素,它是一个 HTML 元素,它的类型是 Row。
Row类型的元素有一个子元素这个子元素是 main

<Col span={8} className='left-side'> ... </Col> 是一个 React 元素,
它是一个 HTML 元素它的类型是 Col它有两个属性分别是 span  className
span 属性的值是 8className 属性的值是 left-side
<Col span={8} className='left-side'> ... </Col> 的内容是 left。

SatSetting 是一个 React 元素它是一个自定义组件它的类型是 SatSetting

Screenshot 2023-12-10 at 12.57.07

  1. Update SateSetting.js
import React, {Component} from 'react';
import {Form, Button, InputNumber} from 'antd';

class SatSetting extends Component {
    showSatellite = values => {
        console.log('Received values of form: ', values);
     this.props.onShow(values);          // here
    }

    render() {
        return (
            <Form
                name="wrap"
                labelCol={{
                    flex: '110px',
                }}
                labelAlign="left"
                labelWrap
                wrapperCol={{
                    flex: 1,
                }}
                colon={false}
                className="sat-setting"
                onFinish={this.showSatellite}
            >

                <Form.Item
                    label="Longitude(degrees)"
                    name="longitude"
                    rules={[
                        {
                            required: true,
                            message: "Please input your Longitude",
                        }
                    ]}
                >
                    <InputNumber min={-180} max={180}
                                 style={{width: "100%"}}
                                 placeholder="Please input Longitude"
                    />
                </Form.Item>

                <Form.Item
                    label="Latitude(degrees)"
                    name="latitude"
                    rules={[
                        {
                            required: true,
                            message: "Please input your Latitude",
                        }
                    ]}
                >
                    <InputNumber min={-90} max={90}
                                 style={{width: "100%"}}
                                 placeholder="Please input Latitude"
                    />
                </Form.Item>

                <Form.Item
                    label="Altitude(meters)"
                    name="elevation"
                    rules={[
                        {
                            required: true,
                            message: "Please input your Altitude",
                        }
                    ]}
                >
                    <InputNumber min={-413} max={8850}
                                 style={{width: "100%"}}
                                 placeholder="Please input Altitude"
                    />
                </Form.Item>

                <Form.Item
                    label="Radius(degrees)"
                    name="altitude"
                    rules={[
                        {
                            required: true,
                            message: "Please input your Radius",
                        }
                    ]}
                >
                    <InputNumber min={0} max={90}
                                 style={{width: "100%"}}
                                 placeholder="Please input Radius"
                    />
                </Form.Item>

                <Form.Item
                    label="Duration(secs)"
                    name="duration"
                    rules={[
                        {
                            required: true,
                            message: "Please input your Duration",
                        }
                    ]}
                >
                    <InputNumber min={0} max={90}
                                 style={{width: "100%"}}
                                 placeholder="Please input Duration"
                    />
                </Form.Item>

                <Form.Item className="show-nearby">
                    <Button type="primary" htmlType="submit" style={{textAlign: "center"}}>
                        Find Satellite
                    </Button>
                </Form.Item>

            </Form>
        );
    }
}

export default SatSetting;

Screenshot 2023-12-10 at 13.12.05

  1. Update satelliteList.js

Copy the svg from

https://stackblitz.com/edit/react-starlink2?file=src%2Fassets%2Fimages%2Fsatellite.svg

import React, {Component} from 'react';
import { List, Avatar, Button, Checkbox, Spin } from 'antd';
import satellite from "../assets/images/statellite.svg";


class SatelliteList extends Component {
    render() {
     const satList = this.props.satInfo ? this.props.satInfo.above : [];
     const { isLoad } = this.props;

        return (
            <div className="sat-list-box">
                 <Button className="sat-list-btn" type="primary">Track</Button>
         <hr/>
         {
             isLoad ?
                 <div className="spin-box">
                     <Spin tip="Loading..." size="large" />
                 </div>
                 :
                 <List
                     className="sat-list"
                     itemLayout="horizontal"
                     size="small"
                     dataSource={satList}
                     renderItem={item => (
                         <List.Item
                             actions={[<Checkbox dataInfo={item}onCharge={this.onCharge}/>]}
                         >
                         <List.Item.Meta
                             avatar={<Avatar size={50} src={satellite}/>
                             }
                         title={<p>{item.satname}</p>}
                         description={`Launch Date:$(item.lauchDate)`}
                         />
                         </List.Item>
                     )}

             />
         }
         </div>
     );
    }
}

export default SatelliteList;

Screenshot 2023-12-10 at 13.45.06

Screenshot 2023-12-10 at 13.44.50

  1. Update SatelliteList.css

    .sat-list-box {
        border: 2px solid #ccc;
        height: 380px;
        margin-top: 10px;
        padding: 10px;
    }
    
    .btn-container {
        text-align: center;
    }
    
    .sat-list {
        height: 300px;
        overflow: scroll;
    }
    
    .spin-box{
        display: flex;
        align-items: center;
        justify-content: center;
        height: 335px;
    }
    

Steps - Part 3

  1. Upate SatelliteList.js
import React, {Component} from 'react';
import { List, Avatar, Button, Checkbox, Spin } from 'antd';
import satellite from "../assets/images/statellite.svg";


class SatelliteList extends Component {
 constructor(){
     super();
     this.state = {
         selected:[]
     };
 }

 onChange = e => {
     const { dataInfo, checked } = e.target;
     const { selected } = this.state;
     const list = this.addOrRemove(dataInfo, checked, selected);
     this.setState({ selected: list })
 }

 addOrRemove = (item, status, list) => {
     const found = list.some( entry => entry.satid === item.satid);
     if(status && !found){
         list=[...list, item]    
     }

     if(!status && found){
         list = list.filter( entry => {
             return entry.satid !== item.satid;
         }); 
     }
     return list;
 }

 onShowSatMap = () => {
     this.props.onShowMap(this.state.selected);
 }

    render() {
     const satList = this.props.satInfo ? this.props.satInfo.above : [];
     const { isLoad } = this.props;

        return (
            <div className="sat-list-box">
                 <Button className="sat-list-btn" type="primary">Track</Button>
         <hr/>
         {
             isLoad ?
                 <div className="spin-box">
                     <Spin tip="Loading..." size="large" />
                 </div>
                 :
                 <List
                     className="sat-list"
                     itemLayout="horizontal"
                     size="small"
                     dataSource={satList}
                     renderItem={item => (
                         <List.Item
                             actions={[<Checkbox dataInfo={item}onCharge={this.onCharge}/>]}
                         >
                         <List.Item.Meta
                             avatar={<Avatar size={50} src={satellite}/>
                             }
                         title={<p>{item.satname}</p>}
                         description={`Launch Date:$(item.lauchDate)`}
                         />
                         </List.Item>
                     )}

             />
         }
         </div>
     );
    }
}

export default SatelliteList;

Screenshot 2023-12-10 at 14.28.50

  1. Update SatellitList.css
.sat-list-box {
    border: 2px solid #ccc;
    height: 380px;
    margin-top: 10px;
    padding: 10px;
}

.btn-container {
 text-align: center;
}

.sat-list {
 height: 310px;
 overflow: scroll;
}

.spin-box{
 display: flex;
 align-items: center;
 justify-content: center;
 height: 320px;
}
  1. Update Main.js
import React, {Component} from "react";
import { Row, Col } from 'antd';
import axios from "axios"; 

import SatSetting from './SatSetting';
import SatelliteList from './SatelliteList';
import { SAT_API_KEY, BASE_URL, NEARBY_SATELLITE, STARLINK_CATEGORY} from "../constants"; 

class Main extends Component{
 state = {
         setting: {},
         satInfo: {},
         satList: [],
         isLoadingList: false
 }

 showNearbySatellite = (setting) => {
         console.log('show nearby')
         this.setState({
                 setting: setting
         })
         this.fetchSatellite(setting);
 }

 fetchSatellite = (setting) => {
         console.log("fetching")
         const { latitude, longitude, elevation, altitude } = setting;
         const url = `${BASE_URL}/${NEARBY_SATELLITE}/${latitude}/${longitude}/${elevation}/${altitude}/${STARLINK_CATEGORY}/$&apiKey=${SAT_API_KEY}`;
         this.setState({
                 isLoadingList: true
         });

         axios.get(url)
                 .then( res => {
                         console.log(res.data);
                         this.setState({
                                 satInfo: res.data,
                                 isLoadingList: false
                         })
                 })
                 .catch( error => {
                         this.setState({
                                 isLoadingList: false
                         });
                         console.log('err in fetch satellite -> ', error);
                 })

 }

 showMap = (data) => {
     console.log('show on the map -> ', data);   
 }

    render() {
         const { satInfo, isLoadingList, satList, setting } = this.state;

        return (
            <Row className='main'>
                <Col span={8} className='left-side'>
                    <SatSetting onShow={this.showNearbySatellite} />
                    <SatelliteList satInfo={satInfo}
                                isLoad={isLoadingList}
                                onShowMap={this.showMap}
                 />
                </Col>
                <Col span={16} className='right-side'>
                    right
                </Col>
            </Row>
        );
    }
}
export default Main;
  1. Create WorldMap.js
import React, {Component} from 'react'

class WorldMap extends Component {
 redner() {
     return (
         <div>
             world map
         </div>
     )   
 }

}

export default WorldMap;

Screenshot 2023-12-10 at 14.46.26

  1. Update Main.js
import React, {Component} from "react";
import { Row, Col } from 'antd';
import axios from "axios"; 

import SatSetting from './SatSetting';
import SatelliteList from './SatelliteList';
import { SAT_API_KEY, BASE_URL, NEARBY_SATELLITE, STARLINK_CATEGORY} from "../constants"; 
import WorldMap from './WorldMap';        //

class Main extends Component{
 state = {
         setting: {},
         satInfo: {},
         satList: [],
         isLoadingList: false
 }

 showNearbySatellite = (setting) => {
         console.log('show nearby')
         this.setState({
                 setting: setting
         })
         this.fetchSatellite(setting);
 }

 fetchSatellite = (setting) => {
         console.log("fetching")
         const { latitude, longitude, elevation, altitude } = setting;
         const url = `${BASE_URL}/${NEARBY_SATELLITE}/${latitude}/${longitude}/${elevation}/${altitude}/${STARLINK_CATEGORY}/$&apiKey=${SAT_API_KEY}`;
         this.setState({
                 isLoadingList: true
         });

         axios.get(url)
                 .then( res => {
                         console.log(res.data);
                         this.setState({
                                 satInfo: res.data,
                                 isLoadingList: false
                         })
                 })
                 .catch( error => {
                         this.setState({
                                 isLoadingList: false
                         });
                         console.log('err in fetch satellite -> ', error);
                 })

 }
 //
 showMap = (data) => {
     console.log('show on the map -> ', data);   
 }
    render() {
         const { satInfo } = this.state;

        return (
            <Row className='main'>
                <Col span={8} className='left-side'>
                    <SatSetting onShow={this.showNearbySatellite} />
                    <SatelliteList satInfo={satInfo}
                                isLoad={this.state.isLoadingList}
                                onShowMap={this.showMap}
                 />
                </Col>
                <Col span={16} className='right-side'>
                    <WorldMap />
                </Col>
            </Row>
        );
    }
}
export default Main;
  1. Install libraries
  • topojson: https://github.com/topojson/topojson-client

    npm install --save topojson-client

  • d3-geo: https://github.com/d3/d3-geo

    npm install --save d3-geo

  • d3-geo-projection: https://github.com/d3/d3-geo-projection

    npm install --save d3-geo-projection

  • d3-selection: https://github.com/d3/d3-selection

    npm install --save d3-selection

  1. Update constants.js
export const SAT_API_KEY = "LT6DRG-NGLYGH-TCY8HW-54GV"; // "YOUR KEY";

export const STARLINK_CATEGORY = "52";

export const NEARBY_SATELLITE = "rest/v1/satellite/above";

export const WORLD_MAP_URL = "https://unpkg.com/world-atlas@1/world/110m.json";

export const BASE_URL = ""; // your backend endpoint when deloy
  1. Update WorldMap.js
import React, { Component } from "react";
import axios from "axios";
import { Spin } from "antd";
import { feature } from "topojson-client";
import { geoKavrayskiy7 } from "d3-geo-projection";
import { geoGraticule, geoPath } from "d3-geo";
import { select as d3Select } from "d3-selection";
import { schemeCategory10 } from "d3-scale-chromatic";
import * as d3Scale from "d3-scale";
import { timeFormat as d3TimeFormat } from "d3-time-format";

import {
 WORLD_MAP_URL,
 SATELLITE_POSITION_URL,
 SAT_API_KEY
} from "../constants";

const width = 960;
const height = 600;

class WorldMap extends Component {
 constructor() {
     super();
     this.state = {
         isLoading: false,
         isDrawing: false
     };
     this.map = null;
     this.color = d3Scale.scaleOrdinal(schemeCategory10);
     this.refMap = React.createRef();
     this.refTrack = React.createRef();
 }

 componentDidMount() {
     axios
         .get(WORLD_MAP_URL)
         .then(res => {
             const { data } = res;
             const land = feature(data, data.objects.countries).features;
             this.generateMap(land);
         })
         .catch(e => {
             console.log("err in fetch map data ", e.message);
         });
 }

 componentDidUpdate(prevProps, prevState, snapshot) {
     if (prevProps.satData !== this.props.satData) {
         const {
             latitude,
             longitude,
             elevation,
             altitude,
             duration
         } = this.props.observerData;
         const endTime = duration * 60;

         this.setState({
             isLoading: true
         });

         const urls = this.props.satData.map(sat => {
             const { satid } = sat;
             const url = `/api/${SATELLITE_POSITION_URL}/${satid}/${latitude}/${longitude}/${elevation}/${endTime}/&apiKey=${SAT_API_KEY}`;

             return axios.get(url);
         });

         Promise.all(urls)
             .then(res => {
                 const arr = res.map(sat => sat.data);
                 this.setState({
                     isLoading: false,
                     isDrawing: true
                 });

                 if (!prevState.isDrawing) {
                     this.track(arr);
                 } else {
                     const oHint = document.getElementsByClassName("hint")[0];
                     oHint.innerHTML =
                         "Please wait for these satellite animation to finish before selection new ones!";
                 }
             })
             .catch(e => {
                 console.log("err in fetch satellite position -> ", e.message);
             });
     }
 }

 track = data => {
     if (!data[0].hasOwnProperty("positions")) {
         throw new Error("no position data");
         return;
     }

     const len = data[0].positions.length;
     const { duration } = this.props.observerData;
     const { context2 } = this.map;

     let now = new Date();

     let i = 0;

     let timer = setInterval(() => {
         let ct = new Date();

         let timePassed = i === 0 ? 0 : ct - now;
         let time = new Date(now.getTime() + timePassed);

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

         context2.font = "bold 14px sans-serif";
         context2.fillStyle = "#333";
         context2.textAlign = "center";
         context2.fillText(d3TimeFormat(time), width / 2, 10);

         if (i >= len) {
             clearInterval(timer);
             this.setState({ isDrawing: false });
             const oHint = document.getElementsByClassName("hint")[0];
             oHint.innerHTML = "";
             return;
         }

         data.forEach(sat => {
             const { info, positions } = sat;
             this.drawSat(info, positions[i]);
         });

         i += 60;
     }, 1000);
 };

 drawSat = (sat, pos) => {
     const { satlongitude, satlatitude } = pos;

     if (!satlongitude || !satlatitude) return;

     const { satname } = sat;
     const nameWithNumber = satname.match(/\d+/g).join("");

     const { projection, context2 } = this.map;
     const xy = projection([satlongitude, satlatitude]);

     context2.fillStyle = this.color(nameWithNumber);
     context2.beginPath();
     context2.arc(xy[0], xy[1], 4, 0, 2 * Math.PI);
     context2.fill();

     context2.font = "bold 11px sans-serif";
     context2.textAlign = "center";
     context2.fillText(nameWithNumber, xy[0], xy[1] + 14);
 };

 render() {
     const { isLoading } = this.state;
     return (
         <div className="map-box">
             {isLoading ? (
                 <div className="spinner">
                     <Spin tip="Loading..." size="large" />
                 </div>
             ) : null}
             <canvas className="map" ref={this.refMap} />
             <canvas className="track" ref={this.refTrack} />
             <div className="hint" />
         </div>
     );
 }

 generateMap = land => {
     const projection = geoKavrayskiy7()
         .scale(170)
         .translate([width / 2, height / 2])
         .precision(0.1);

     const graticule = geoGraticule();

     const canvas = d3Select(this.refMap.current)
         .attr("width", width)
         .attr("height", height);

     const canvas2 = d3Select(this.refTrack.current)
         .attr("width", width)
         .attr("height", height);

     const context = canvas.node().getContext("2d");
     const context2 = canvas2.node().getContext("2d");

     let path = geoPath()
         .projection(projection)
         .context(context);

     land.forEach(ele => {
         context.fillStyle = "#B3DDEF";
         context.strokeStyle = "#000";
         context.globalAlpha = 0.7;
         context.beginPath();
         path(ele);
         context.fill();
         context.stroke();

         context.strokeStyle = "rgba(220, 220, 220, 0.1)";
         context.beginPath();
         path(graticule());
         context.lineWidth = 0.1;
         context.stroke();

         context.beginPath();
         context.lineWidth = 0.5;
         path(graticule.outline());
         context.stroke();
     });

     this.map = {
         projection: projection,
         graticule: graticule,
         context: context,
         context2: context2
     };
 };
}

export default WorldMap;

Steps - Part 4

  1. Update Main.js
import React, {Component} from 'react';
import { Row, Col } from 'antd';
import axios from 'axios';
import { NEARBY_SATELLITE, SAT_API_KEY, STARLINK_CATEGORY } from '../constants';
import SatSetting from './SatSetting';
import SatelliteList from './SatelliteList';
import WorldMap from './WorldMap';

class Main extends Component {
 constructor(){
     super();
     this.state = {
         satInfo: null,
         satList: null,
         setting: null,
         isLoadingList: false
     }
 }
 render() {
     const { isLoadingList, satInfo, satList, setting } = this.state;
     return (
         // grid: col-8 + col-16 = 24 in total
         // parent component is Main.js instead of App.js
         // SatSetting: Satellite settings on the left top
         // SatelliteList: Satellite lists on the left bottom
         // WorldMap: Map on the right side
         <Row className='main'>
             <Col span={8} >
                 <SatSetting onShow={this.showNearbySatellite}/>
                 <SatelliteList isLoad={isLoadingList}
                                satInfo={satInfo}
                                onShowMap={this.showMap} />
             </Col>
             <Col span={16} className="right-side">
                 <WorldMap satData={satList} observerData={setting} />
             </Col>
         </Row>
     );
 }

 showMap = (selected) => {
     this.setState(preState => ({
         ...preState,
         satList: [...selected]
     }))
 }

 showNearbySatellite = (setting) => {
     this.setState({
         isLoadingList: true,
         setting: setting
     })
     this.fetchSatellite(setting);
 }

 fetchSatellite= (setting) => {
     // parameter for apis
     const {latitude, longitude, elevation, altitude} = setting;
     // url  /api/ added
     const url = `/api/${NEARBY_SATELLITE}/${latitude}/${longitude}/${elevation}/${altitude}/${STARLINK_CATEGORY}/&apiKey=${SAT_API_KEY}`;

     this.setState({
         isLoadingList: true
     });

     // make request
     axios.get(url)
         .then(response => {
             console.log(response.data)
             this.setState({
                 satInfo: response.data,
                 isLoadingList: false
             })
         })
         .catch(error => {
             console.log('err in fetch satellite -> ', error);
         })
 }
}
export default Main;
  1. Add libraries

d3-time-format https://github.com/d3/d3-time-format

npm install --save d3-time-format

d3-scale https://github.com/d3/d3-scale

npm install --save d3-scale

d3-scale-chromatic https://github.com/d3/d3-scale-chromatic

npm install --save d3-scale-chromatic

  1. update constants.js
export const SAT_API_KEY = "LT6DRG-NGLYGH-TCY8HW-54GV"; // "YOUR KEY";
export const STARLINK_CATEGORY = "52";
export const NEARBY_SATELLITE = "rest/v1/satellite/above";
export const WORLD_MAP_URL = "https://unpkg.com/world-atlas@1.1.4/world/110m.json";
export const SATELLITE_POSITION_URL = "rest/v1/satellite/positions";
  1. Update WorldMap.js
import React, { Component } from "react";
import axios from "axios";
import { Spin } from "antd";
import { feature } from "topojson-client";
import { geoKavrayskiy7 } from "d3-geo-projection";
import { geoGraticule, geoPath } from "d3-geo";
import { select as d3Select } from "d3-selection";
import { schemeCategory10 } from "d3-scale-chromatic";
import * as d3Scale from "d3-scale";
import { timeFormat as d3TimeFormat } from "d3-time-format";

import {
 WORLD_MAP_URL,
 SATELLITE_POSITION_URL,
 SAT_API_KEY
} from "../constants";

const width = 960;
const height = 600;

class WorldMap extends Component {
 constructor() {
     super();
     this.state = {
         isLoading: false,
         isDrawing: false
     };
     this.map = null;
     this.color = d3Scale.scaleOrdinal(schemeCategory10);
     this.refMap = React.createRef();
     this.refTrack = React.createRef();
 }

 componentDidMount() {
     axios
         .get(WORLD_MAP_URL)
         .then(res => {
             const { data } = res;
             const land = feature(data, data.objects.countries).features;
             this.generateMap(land);
         })
         .catch(e => {
             console.log("err in fetch map data ", e.message);
         });
 }

 componentDidUpdate(prevProps, prevState, snapshot) {
     if (prevProps.satData !== this.props.satData) {
         const {
             latitude,
             longitude,
             elevation,
             altitude,
             duration
         } = this.props.observerData;
         const endTime = duration * 60;

         this.setState({
             isLoading: true
         });

         const urls = this.props.satData.map(sat => {
             const { satid } = sat;
             const url = `/api/${SATELLITE_POSITION_URL}/${satid}/${latitude}/${longitude}/${elevation}/${endTime}/&apiKey=${SAT_API_KEY}`;

             return axios.get(url);
         });

         Promise.all(urls)
             .then(res => {
                 const arr = res.map(sat => sat.data);
                 this.setState({
                     isLoading: false,
                     isDrawing: true
                 });

                 if (!prevState.isDrawing) {
                     this.track(arr);
                 } else {
                     const oHint = document.getElementsByClassName("hint")[0];
                     oHint.innerHTML =
                         "Please wait for these satellite animation to finish before selection new ones!";
                 }
             })
             .catch(e => {
                 console.log("err in fetch satellite position -> ", e.message);
             });
     }
 }

 track = data => {
     if (!data[0].hasOwnProperty("positions")) {
         throw new Error("no position data");
         return;
     }

     const len = data[0].positions.length;
     const { duration } = this.props.observerData;
     const { context2 } = this.map;

     let now = new Date();

     let i = 0;

     let timer = setInterval(() => {
         let ct = new Date();

         let timePassed = i === 0 ? 0 : ct - now;
         let time = new Date(now.getTime() + timePassed);

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

         context2.font = "bold 14px sans-serif";
         context2.fillStyle = "#333";
         context2.textAlign = "center";
         context2.fillText(d3TimeFormat(time), width / 2, 10);

         if (i >= len) {
             clearInterval(timer);
             this.setState({ isDrawing: false });
             const oHint = document.getElementsByClassName("hint")[0];
             oHint.innerHTML = "";
             return;
         }

         data.forEach(sat => {
             const { info, positions } = sat;
             this.drawSat(info, positions[i]);
         });

         i += 60;
     }, 1000);
 };

 drawSat = (sat, pos) => {
     const { satlongitude, satlatitude } = pos;

     if (!satlongitude || !satlatitude) return;

     const { satname } = sat;
     const nameWithNumber = satname.match(/\d+/g).join("");

     const { projection, context2 } = this.map;
     const xy = projection([satlongitude, satlatitude]);

     context2.fillStyle = this.color(nameWithNumber);
     context2.beginPath();
     context2.arc(xy[0], xy[1], 4, 0, 2 * Math.PI);
     context2.fill();

     context2.font = "bold 11px sans-serif";
     context2.textAlign = "center";
     context2.fillText(nameWithNumber, xy[0], xy[1] + 14);
 };

 render() {
     const { isLoading } = this.state;
     return (
         <div className="map-box">
             {isLoading ? (
                 <div className="spinner">
                     <Spin tip="Loading..." size="large" />
                 </div>
             ) : null}
             <canvas className="map" ref={this.refMap} />
             <canvas className="track" ref={this.refTrack} />
             <div className="hint" />
         </div>
     );
 }

 generateMap = land => {
     const projection = geoKavrayskiy7()
         .scale(170)
         .translate([width / 2, height / 2])
         .precision(0.1);

     const graticule = geoGraticule();

     const canvas = d3Select(this.refMap.current)
         .attr("width", width)
         .attr("height", height);

     const canvas2 = d3Select(this.refTrack.current)
         .attr("width", width)
         .attr("height", height);

     const context = canvas.node().getContext("2d");
     const context2 = canvas2.node().getContext("2d");

     let path = geoPath()
         .projection(projection)
         .context(context);

     land.forEach(ele => {
         context.fillStyle = "#B3DDEF";
         context.strokeStyle = "#000";
         context.globalAlpha = 0.7;
         context.beginPath();
         path(ele);
         context.fill();
         context.stroke();

         context.strokeStyle = "rgba(220, 220, 220, 0.1)";
         context.beginPath();
         path(graticule());
         context.lineWidth = 0.1;
         context.stroke();

         context.beginPath();
         context.lineWidth = 0.5;
         path(graticule.outline());
         context.stroke();
     });

     this.map = {
         projection: projection,
         graticule: graticule,
         context: context,
         context2: context2
     };
 };
}

export default WorldMap;
  1. Create WordMap.css and import it to index.css
.map-box {
 position: relative;
 height: 100%;
 width: 100%;
 Overflow: scroll;
}

.map, .track, .spinner {
 position: absolute;
 top: 0;
 left: 0;
 right: 0;
 bottom: 0;
 margin: auto;
}

.map {
 z-index: 0;
}

.track {
 z-index: 100;
}

.spinner {
 display: flex;
 align-items: center;
 justify-content: center;
 height: 600px;
 width: 960px;
}

.hint {
 color: red;
 font-size: 16px;
 font-weight: bold;
 text-align: center;
 width: 100%;
}

index.css:

@import '~antd/dist/reset.css';
/* 一定要放在第一行 ant*/
/* 来自library */
@import "./styles/App.css";
@import "./styles/Header.css";
@import "./styles/Footer.css";
@import "./styles/Main.css";
@import "./styles/SatSetting.css";
@import "./styles/SatelliteList.css";
@import "./styles/WorldMap.css";

/* @import 用于导入其他样式表。 */
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

Deploy

Starlink/SpaceX project review

  1. Describe the Componet tree
  2. Describe the dataflow
  3. How to we get data for starlink satellites
  4. How do we draw on the canvas

React

  1. Q: Can you explain what virtual DOM is in React and how this technique can improve render performance?

A: You tell React what state you want the UI to be in, and it makes sure the DOM matches that state. Representation of a UI is kept in memory and synced with the "real" DOM by a library - ReactDOM. React DOM compares the element and its children to the previous one, and only applies the DOM updates necessary to bring the DOM to the desired state.

https://reactjs.org/docs/faq-internals.html#what-is-the-virtual-dom

  1. Q: What is the difference between props and state in React?

A: Props are read-only, State is not read-only but you can't directly modify the State object, you will have to use the setState() method provided by React. Changes on either Props or State will trigger the re-render of the component.

  1. Q: Can you list some common React Component Life Cycles and explain what they do ?

A: (Understand this diagram, focus on the common ones: didMount, didUpdate. willUnmount)

https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

https://reactjs.org/docs/react-component.html#the-component-lifecycle

  1. Q: Given the scenario, could you please implement this feature using React?
  • Clarify the requirements of this feature. What does the user need, how does each module interact with the user.
  • Design and think LOUD! Tell the interviewer how you wanna design this UI, to be more specific, draw the React component hierarchy out and discuss with the interviewer to finalize what each component does.
  • Go for the implementation, don't panic, ask questions like "can I use ", "do we need to consider..." whenever they come to you and block the implementation.

Javascript

  1. Q: How many different ways we have to define a variable in Javascript?

A: Let, Const, Var(Describe the difference in these 3 categories: Scope, Reinitialization, Reference before declaration)

https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/

  1. Q: Could you list the key difference between Java and Javascript?

A: Java code needs to be compiled before run, JS doesn't. Java is statically typed while Javascript is dynamically typed. Java runs on JVM while JS runs on browser or nodeJs.

  1. Q: What is a callback function?

A: A callback is a plain JavaScript function passed to some method as an argument or option. It is a function that is to be executed after another function has finished executing, hence the name call back`. In JavaScript, functions are objects. Because of this, functions can take functions as arguments, and can be returned by other functions.

  1. Q: How do we get Data from the backend using JS usually?

A: By making AJAX calls, nowadays the latest web api we use for this is Fetch API https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

HTML

  1. Q: Explain the new <canvas> element in HTML5?

A: HTML5 provides the new <canvas> element to facilitate 2D drawing. It uses the tag <canvas> which helps in drawing graphics using Java scripts.

  1. Q: Which tag is for hyperlinks in HTML, and what are the common attributes of this element?

A: <a> tag defines hyperlinks in HTML. In terms of attributes, "href" takes the value for the target web address, "target" specifies the browsing context, for instance, target="_blank" will go to the link in a new browser tab.

  1. Q: Describe what HTML is?

A: HTML (Hypertext Markup Language) is not a programming language. It is a markup language that tells web browsers how to structure the web pages you visit.

  1. Q: Block versus Inline element?

A: Block-level elements form a visible block on a page. A block-level element appears on a new line following the content that precedes it. Any content that follows a block-level element also appears on a new line. Example - DIV

Inline elements are contained within block-level elements, and surround only small parts of the document's content. (not entire paragraphs or groupings of content) An inline element will not cause a new line to appear in the document. Example - Span

CSS

  1. Q: Explain the conflicting rules in CSS
  • A: cascade - at a very simple level this means that the order of CSS rules matters; when two rules apply that have equal specificity the one that comes that last in the CSS is the one that will be used.
  • Specificity - look up the specificity table, the selector with higher specificity value wins.
  1. Q: What is the BOX Model in CSS?

A: Box Model (content, padding, border, margin)

  1. Q: What is the selector in CSS?

A: Selector (type/class/ID, attribute, Pseudo-classes and pseudo-elements, Combinators)