├── assets
├── borders.png
├── circle.png
├── earthmap.png
├── night2048.jpg
├── earthmap-high.jpg
└── theme.css
├── screenshots
├── 01.png
├── 02.png
├── 03.png
├── 04.png
├── 05.png
├── 06.png
├── 07.png
├── 08.png
├── 09.png
├── 10.png
└── 11.png
├── docs
├── circle.f4683951.png
├── earthmap-high.602450bd.jpg
├── index.html
├── satellite-tracker.6463a969.css
└── satellite-tracker.6463a969.css.map
├── .babelrc
├── index.js
├── index.html
├── Search
├── SearchBox.js
├── Search.js
└── SearchResults.js
├── Options
├── DateSlider.css
└── DateSlider.js
├── fork.js
├── .gitignore
├── highlights.js
├── Info.js
├── .vscode
└── launch.json
├── Selection
└── SelectedStations.js
├── package.json
├── samples
└── iss-1day
│ └── index.js
├── LICENSE.md
├── tle.js
├── readme.md
├── App.js
└── engine.js
/assets/borders.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/assets/borders.png
--------------------------------------------------------------------------------
/assets/circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/assets/circle.png
--------------------------------------------------------------------------------
/screenshots/01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/screenshots/01.png
--------------------------------------------------------------------------------
/screenshots/02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/screenshots/02.png
--------------------------------------------------------------------------------
/screenshots/03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/screenshots/03.png
--------------------------------------------------------------------------------
/screenshots/04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/screenshots/04.png
--------------------------------------------------------------------------------
/screenshots/05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/screenshots/05.png
--------------------------------------------------------------------------------
/screenshots/06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/screenshots/06.png
--------------------------------------------------------------------------------
/screenshots/07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/screenshots/07.png
--------------------------------------------------------------------------------
/screenshots/08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/screenshots/08.png
--------------------------------------------------------------------------------
/screenshots/09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/screenshots/09.png
--------------------------------------------------------------------------------
/screenshots/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/screenshots/10.png
--------------------------------------------------------------------------------
/screenshots/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/screenshots/11.png
--------------------------------------------------------------------------------
/assets/earthmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/assets/earthmap.png
--------------------------------------------------------------------------------
/assets/night2048.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/assets/night2048.jpg
--------------------------------------------------------------------------------
/assets/earthmap-high.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/assets/earthmap-high.jpg
--------------------------------------------------------------------------------
/docs/circle.f4683951.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/docs/circle.f4683951.png
--------------------------------------------------------------------------------
/docs/earthmap-high.602450bd.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsuarezv/satellite-tracker/HEAD/docs/earthmap-high.602450bd.jpg
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react",
4 | "@babel/preset-env"
5 | ],
6 | "plugins": [
7 | [
8 | "transform-class-properties"
9 | ]
10 | ]
11 | }
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { render } from "react-dom";
3 | import App from "./App";
4 |
5 |
6 | render(, document.getElementById("root"));
7 |
8 | if (module.hot) {
9 | module.hot.accept();
10 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
197 |
198 |
199 |
200 |
201 |
202 | {UseDateSlider &&
}
203 |
this.el = c} style={{ width: '99%', height: '99%' }} />
204 |
205 | )
206 | }
207 | }
208 |
209 |
210 |
211 | export default App;
--------------------------------------------------------------------------------
/engine.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
3 | import earthmap from './assets/earthmap-high.jpg';
4 | import circle from './assets/circle.png';
5 | import { parseTleFile as parseTleFile, getPositionFromTle } from "./tle";
6 | import { earthRadius } from "satellite.js/lib/constants";
7 | import * as satellite from 'satellite.js/lib/index';
8 |
9 | const SatelliteSize = 50;
10 | const MinutesPerDay = 1440;
11 | const ixpdotp = MinutesPerDay / (2.0 * 3.141592654) ;
12 |
13 | let TargetDate = new Date();
14 |
15 | const defaultOptions = {
16 | backgroundColor: 0x041119,
17 | defaultSatelliteColor: 0xff0000,
18 | onStationClicked: null
19 | }
20 |
21 | const defaultStationOptions = {
22 | orbitMinutes: 0,
23 | satelliteSize: 50
24 | }
25 |
26 | export class Engine {
27 |
28 | stations = [];
29 | referenceFrame = 1;
30 |
31 | initialize(container, options = {}) {
32 | this.el = container;
33 | this.raycaster = new THREE.Raycaster();
34 | this.options = { ...defaultOptions, ...options };
35 |
36 | this._setupScene();
37 | this._setupLights();
38 | this._addBaseObjects();
39 |
40 | this.render();
41 |
42 | window.addEventListener('resize', this.handleWindowResize);
43 | window.addEventListener('pointerdown', this.handleMouseDown);
44 | }
45 |
46 | dispose() {
47 | window.removeEventListener('pointerdown', this.handleMouseDown);
48 | window.removeEventListener('resize', this.handleWindowResize);
49 | //window.cancelAnimationFrame(this.requestID);
50 |
51 | this.raycaster = null;
52 | this.el = null;
53 |
54 | this.controls.dispose();
55 | }
56 |
57 | handleWindowResize = () => {
58 | const width = this.el.clientWidth;
59 | const height = this.el.clientHeight;
60 |
61 | this.renderer.setSize(width, height);
62 | this.camera.aspect = width / height;
63 | this.camera.updateProjectionMatrix();
64 |
65 | this.render();
66 | };
67 |
68 | handleMouseDown = (e) => {
69 | const mouse = new THREE.Vector2(
70 | (e.clientX / this.el.clientWidth ) * 2 - 1,
71 | -(e.clientY / this.el.clientHeight ) * 2 + 1 );
72 |
73 | this.raycaster.setFromCamera(mouse, this.camera);
74 |
75 | let station = null;
76 |
77 | var intersects = this.raycaster.intersectObjects(this.scene.children, true);
78 | if (intersects && intersects.length > 0) {
79 | const picked = intersects[0].object;
80 | if (picked) {
81 | station = this._findStationFromMesh(picked);
82 | }
83 | }
84 |
85 | const cb = this.options.onStationClicked;
86 | if (cb) cb(station);
87 | }
88 |
89 |
90 | // __ API _________________________________________________________________
91 |
92 |
93 | addSatellite = (station, color, size) => {
94 |
95 | //const sat = this._getSatelliteMesh(color, size);
96 | const sat = this._getSatelliteSprite(color, size);
97 | const pos = this._getSatellitePositionFromTle(station);
98 | if (!pos) return;
99 | //const pos = { x: Math.random() * 20000 - 10000, y: Math.random() * 20000 - 10000 , z: Math.random() * 20000 - 10000, }
100 |
101 | sat.position.set(pos.x, pos.y, pos.z);
102 | station.mesh = sat;
103 |
104 | this.stations.push(station);
105 |
106 | if (station.orbitMinutes > 0) this.addOrbit(station);
107 |
108 | this.earth.add(sat);
109 | }
110 |
111 | loadLteFileStations = (url, color, stationOptions) => {
112 | const options = { ...defaultStationOptions, ...stationOptions };
113 |
114 | return fetch(url).then(res => {
115 | if (res.ok) {
116 | return res.text().then(text => {
117 | return this._addTleFileStations(text, color, options);
118 |
119 | });
120 | }
121 | });
122 | }
123 |
124 | addOrbit = (station) => {
125 | if (station.orbitMinutes > 0) return;
126 |
127 | const revsPerDay = station.satrec.no * ixpdotp;
128 | const intervalMinutes = 1;
129 | const minutes = station.orbitMinutes || MinutesPerDay / revsPerDay;
130 | const initialDate = new Date();
131 |
132 | //console.log('revsPerDay', revsPerDay, 'minutes', minutes);
133 |
134 | if (!this.orbitMaterial) {
135 | this.orbitMaterial = new THREE.LineBasicMaterial({color: 0x999999, opacity: 1.0, transparent: true });
136 | }
137 |
138 | var points = [];
139 |
140 | for (var i = 0; i <= minutes; i += intervalMinutes) {
141 | const date = new Date(initialDate.getTime() + i * 60000);
142 |
143 | const pos = getPositionFromTle(station, date, this.referenceFrame);
144 | if (!pos) continue;
145 |
146 | points.push(new THREE.Vector3(pos.x, pos.y, pos.z));
147 | }
148 |
149 | const geometry = new THREE.BufferGeometry().setFromPoints(points);
150 | var orbitCurve = new THREE.Line(geometry, this.orbitMaterial);
151 | station.orbit = orbitCurve;
152 | station.mesh.material = this.selectedMaterial;
153 |
154 | this.earth.add(orbitCurve);
155 | this.render();
156 | }
157 |
158 | removeOrbit = (station) => {
159 | if (!station || !station.orbit) return;
160 |
161 | this.earth.remove(station.orbit);
162 | station.orbit.geometry.dispose();
163 | station.orbit = null;
164 | station.mesh.material = this.material;
165 | this.render();
166 | }
167 |
168 | highlightStation = (station) => {
169 | station.mesh.material = this.highlightedMaterial;
170 | }
171 |
172 | clearStationHighlight = (station) => {
173 | station.mesh.material = this.material;
174 | }
175 |
176 | setReferenceFrame = (type) => {
177 | this.referenceFrame = type;
178 | }
179 |
180 | _addTleFileStations = (lteFileContent, color, stationOptions) => {
181 | const stations = parseTleFile(lteFileContent, stationOptions);
182 |
183 | const { satelliteSize } = stationOptions;
184 |
185 | stations.forEach(s => {
186 | this.addSatellite(s, color, satelliteSize);
187 | });
188 |
189 | this.render();
190 |
191 | return stations;
192 | }
193 |
194 |
195 |
196 | _getSatelliteMesh = (color, size) => {
197 | color = color || this.options.defaultSatelliteColor;
198 | size = size || SatelliteSize;
199 |
200 | if (!this.geometry) {
201 |
202 | this.geometry = new THREE.BoxBufferGeometry(size, size, size);
203 | this.material = new THREE.MeshPhongMaterial({
204 | color: color,
205 | emissive: 0xFF4040,
206 | flatShading: false,
207 | side: THREE.DoubleSide,
208 | });
209 | }
210 |
211 | return new THREE.Mesh(this.geometry, this.material);
212 | }
213 |
214 | _setupSpriteMaterials = (color) => {
215 | if (this.material && this.lastColor === color) return;
216 |
217 | this._satelliteSprite = new THREE.TextureLoader().load(circle, this.render);
218 | this.selectedMaterial = new THREE.SpriteMaterial({
219 | map: this._satelliteSprite,
220 | color: 0xFF0000,
221 | sizeAttenuation: false
222 | });
223 | this.highlightedMaterial = new THREE.SpriteMaterial({
224 | map: this._satelliteSprite,
225 | color: 0xfca300,
226 | sizeAttenuation: false
227 | });
228 | this.material = new THREE.SpriteMaterial({
229 | map: this._satelliteSprite,
230 | color: color,
231 | sizeAttenuation: false
232 | });
233 | this.lastColor = color;
234 | }
235 |
236 | _getSatelliteSprite = (color, size) => {
237 | const SpriteScaleFactor = 5000;
238 |
239 | this._setupSpriteMaterials(color);
240 |
241 | const result = new THREE.Sprite(this.material);
242 | result.scale.set(size / SpriteScaleFactor, size / SpriteScaleFactor, 1);
243 | return result;
244 | }
245 |
246 | _getSatellitePositionFromTle = (station, date) => {
247 | date = date || TargetDate;
248 | return getPositionFromTle(station, date, this.referenceFrame);
249 | }
250 |
251 | updateSatellitePosition = (station, date) => {
252 | date = date || TargetDate;
253 |
254 | const pos = getPositionFromTle(station, date, this.referenceFrame);
255 | if (!pos) return;
256 |
257 | station.mesh.position.set(pos.x, pos.y, pos.z);
258 | }
259 |
260 |
261 | updateAllPositions = (date) => {
262 | if (!this.stations) return;
263 |
264 | this.stations.forEach(station => {
265 | this.updateSatellitePosition(station, date);
266 | });
267 |
268 | if (this.referenceFrame === 2)
269 | this._updateEarthRotation(date);
270 | else
271 | this.render();
272 | }
273 |
274 | _updateEarthRotation = (date) => {
275 | const gst = satellite.gstime(date)
276 | this.earthMesh.setRotationFromEuler(new THREE.Euler( 0, gst, 0));
277 |
278 | this.render();
279 | }
280 |
281 |
282 | // __ Scene _______________________________________________________________
283 |
284 |
285 | _setupScene = () => {
286 | const width = this.el.clientWidth;
287 | const height = this.el.clientHeight;
288 |
289 | this.scene = new THREE.Scene();
290 |
291 | this._setupCamera(width, height);
292 |
293 | this.renderer = new THREE.WebGLRenderer({
294 | logarithmicDepthBuffer: true,
295 | antialias: true
296 | });
297 |
298 | this.renderer.setClearColor(new THREE.Color(this.options.backgroundColor));
299 | this.renderer.setSize(width, height);
300 |
301 | this.el.appendChild(this.renderer.domElement);
302 | };
303 |
304 | _setupCamera(width, height) {
305 | var NEAR = 1e-6, FAR = 1e27;
306 | this.camera = new THREE.PerspectiveCamera(54, width / height, NEAR, FAR);
307 | this.controls = new OrbitControls(this.camera, this.el);
308 | this.controls.enablePan = false;
309 | this.controls.addEventListener('change', () => this.render());
310 | this.camera.position.z = -15000;
311 | this.camera.position.x = 15000;
312 | this.camera.lookAt(0, 0, 0);
313 | }
314 |
315 | _setupLights = () => {
316 | const sun = new THREE.PointLight(0xffffff, 1, 0);
317 | //sun.position.set(0, 0, -149400000);
318 | sun.position.set(0, 59333894, -137112541);
319 |
320 | const ambient = new THREE.AmbientLight(0x909090);
321 |
322 | this.scene.add(sun);
323 | this.scene.add(ambient);
324 | }
325 |
326 | _addBaseObjects = () => {
327 | this._addEarth();
328 | };
329 |
330 | render = () => {
331 | this.renderer.render(this.scene, this.camera);
332 | //this.requestID = window.requestAnimationFrame(this._animationLoop);
333 | };
334 |
335 |
336 |
337 | // __ Scene contents ______________________________________________________
338 |
339 |
340 | _addEarth = () => {
341 | const textLoader = new THREE.TextureLoader();
342 |
343 | const group = new THREE.Group();
344 |
345 | // Planet
346 | let geometry = new THREE.SphereGeometry(earthRadius, 50, 50);
347 | let material = new THREE.MeshPhongMaterial({
348 | //color: 0x156289,
349 | //emissive: 0x072534,
350 | side: THREE.DoubleSide,
351 | flatShading: false,
352 | map: textLoader.load(earthmap, this.render)
353 | });
354 |
355 | this.earthMesh = new THREE.Mesh(geometry, material);
356 | group.add(this.earthMesh);
357 |
358 | // // Axis
359 | // material = new THREE.LineBasicMaterial({color: 0xffffff});
360 | // geometry = new THREE.Geometry();
361 | // geometry.vertices.push(
362 | // new THREE.Vector3(0, -7000, 0),
363 | // new THREE.Vector3(0, 7000, 0)
364 | // );
365 |
366 | // var earthRotationAxis = new THREE.Line(geometry, material);
367 | // group.add(earthRotationAxis);
368 |
369 | this.earth = group;
370 | this.scene.add(this.earth);
371 |
372 | }
373 |
374 | _findStationFromMesh = (threeObject) => {
375 | for (var i = 0; i < this.stations.length; ++i) {
376 | const s = this.stations[i];
377 |
378 | if (s.mesh === threeObject) return s;
379 | }
380 |
381 | return null;
382 | }
383 | }
--------------------------------------------------------------------------------