├── .gitignore
├── .postcssrc
├── .eslintrc
├── image
└── screenshot.jpg
├── README.md
├── netlify.toml
├── src
├── index.html
├── modules
│ ├── renderInfo.js
│ └── mapStyle.js
├── index.css
└── main.js
├── package.json
└── functions
└── api.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .cache
4 |
--------------------------------------------------------------------------------
/.postcssrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["autoprefixer"]
3 | }
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "afuh",
3 | "env": {
4 | "browser": true
5 | }
6 | }
--------------------------------------------------------------------------------
/image/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afuh/iss/HEAD/image/screenshot.jpg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Where is the International Space Station right now?
2 |
3 | **View it [here](https://iss.axelfuhrmann.com/)**
4 |
5 | 
6 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [dev]
2 | command = "npm run dev"
3 | targetPort = 3000
4 | framework = "#custom"
5 | [build]
6 | publish = "dist"
7 | functions = "functions"
8 | framework = "#custom"
9 | [[redirects]]
10 | from = "/api"
11 | to = "/.netlify/functions/api"
12 | status = 200
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Current position of the ISS
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iss",
3 | "version": "1.0.0",
4 | "description": "Current position of the ISS",
5 | "main": "dist/index.html",
6 | "scripts": {
7 | "dev": "parcel src/index.html --port 3000",
8 | "build": "parcel build src/index.html",
9 | "prebuild": "rm -rf dist"
10 | },
11 | "devDependencies": {
12 | "autoprefixer": "^9.8.6",
13 | "babel-eslint": "^10.1.0",
14 | "eslint": "^7.9.0",
15 | "eslint-config-afuh": "^0.2.0",
16 | "eslint-plugin-import": "^2.22.0",
17 | "parcel-bundler": "^1.12.4"
18 | },
19 | "dependencies": {
20 | "isomorphic-fetch": "^2.2.1"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/functions/api.js:
--------------------------------------------------------------------------------
1 | const fetch = require('isomorphic-fetch')
2 |
3 | const fetcher = url => fetch(url).then(res => res.json())
4 |
5 | const OPEN_NOTIFY = 'http://api.open-notify.org'
6 | const WHERE_THE_ISS_AT = 'https://api.wheretheiss.at/v1'
7 |
8 | exports.handler = async () => {
9 | try {
10 | const [info, astros] = await Promise.all([
11 | fetcher(`${WHERE_THE_ISS_AT}/satellites/25544`),
12 | fetcher(`${OPEN_NOTIFY}/astros.json`)
13 | ])
14 |
15 | return {
16 | statusCode: 200,
17 | body: JSON.stringify({ info, people: astros.people })
18 | }
19 | } catch (err) {
20 | console.log(err)
21 | return {
22 | statusCode: 500,
23 | body: JSON.stringify({ error: err.message })
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/modules/renderInfo.js:
--------------------------------------------------------------------------------
1 | import project from '../../package.json'
2 |
3 | const wiki = 'https://en.wikipedia.org/wiki/'
4 |
5 | const renderInfoBox = ({ people, info }) => {
6 | const el = document.querySelector('div.info')
7 | el.style = "display: flex;"
8 |
9 | const humans = people.map((human) => `
10 |
11 | ${human.name}
12 | `
13 | ).join(' ')
14 |
15 | el.innerHTML = `
16 |
17 |
18 |
19 | -
20 | Latitude:
21 | ${Number(info.latitude).toFixed(2)}
22 |
23 | -
24 | Longitude:
25 | ${Number(info.longitude).toFixed(2)}
26 |
27 | -
28 | Altitude:
29 | ${Math.round(info.altitude)} Kms
30 |
31 | -
32 | Speed:
33 | ${Math.round(info.velocity)} Km/h
34 |
35 | -
36 | Visibility:
37 | ${info.visibility}
38 |
39 |
40 |
41 |
42 |
48 | `
49 | }
50 |
51 | export default renderInfoBox
52 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --primary: #00BCD4;
3 | --transition: all 0.2s;
4 | --unit: 6px;
5 | }
6 |
7 | * {
8 | box-sizing: border-box;
9 | text-decoration: none;
10 | }
11 |
12 | body {
13 | margin: 0;
14 | padding: 0;
15 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;
16 | }
17 |
18 | a, span {
19 | color: whitesmoke;
20 | }
21 |
22 | ul {
23 | padding-left: 0;
24 | margin: 0;
25 | }
26 |
27 | ul li {
28 | list-style-type: none;
29 | }
30 |
31 | #map {
32 | width: 100%;
33 | height: 100vh;
34 | }
35 |
36 | .info {
37 | border-top-right-radius: var(--unit);
38 | border-bottom-right-radius: var(--unit);
39 | flex-flow: column;
40 | padding: calc(var(--unit)*3);
41 | position: absolute;
42 | top: 25%;
43 | left: 0;
44 | background: #000;
45 | opacity: 0.1;
46 | transition: var(--transition);
47 | }
48 |
49 | .info:hover {
50 | opacity: 0.8;
51 | }
52 |
53 | .info .block:not(:last-child) {
54 | margin-bottom: calc(var(--unit)*3);
55 | }
56 |
57 | .info .header {
58 | display: block;
59 | font-weight: 700;
60 | font-size: calc(var(--unit)*3);
61 | margin-bottom: calc(var(--unit)*3);
62 | }
63 |
64 | .info .item {
65 | font-weight: 200;
66 | margin-bottom: calc(var(--unit)*2);
67 | }
68 |
69 | .info .item .title {
70 | font-weight: 700;
71 | }
72 |
73 | .info .humans .item {
74 | font-weight: 500;
75 | }
76 |
77 | .info .header li.name span {
78 | font-weight: 200;
79 | }
80 |
81 | .info .human-length {
82 | color: var(--primary);
83 | }
84 |
85 | .info a {
86 | text-decoration: none;
87 | border-bottom: 1px solid var(--primary);
88 | transition: var(--transition);
89 | }
90 |
91 | a:hover {
92 | color: var(--primary);
93 | border: none;
94 | }
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | /*global google*/
2 | import 'regenerator-runtime/runtime'
3 |
4 | import './index.css'
5 | import styles from './modules/mapStyle'
6 | import renderInfoBox from './modules/renderInfo'
7 |
8 | const REFRESH_TIME = 5000
9 | const ROOT = document.getElementById('map')
10 | const COLOR = '#fff'
11 |
12 | const fetchApi = () => fetch('/api').then(res => res.json())
13 | const getLatLng = ({ info }) => new google.maps.LatLng(info.latitude, info.longitude)
14 |
15 | const init = ({ center }) => {
16 | const path = [center]
17 |
18 | return {
19 | circle(map, data) {
20 | const drawCircle = new google.maps.Circle({
21 | strokeColor: COLOR,
22 | strokeOpacity: 0.8,
23 | strokeWeight: 2,
24 | fillOpacity: 1,
25 | fillColor: COLOR,
26 | center,
27 | radius: 20 * 1000
28 | })
29 |
30 | drawCircle.setMap(map)
31 | renderInfoBox(data)
32 | },
33 | line(map, data) {
34 | path.push(getLatLng({ info: data.info }))
35 |
36 | const drawLine = new google.maps.Polyline({
37 | path,
38 | strokeColor: COLOR,
39 | strokeOpacity: 1,
40 | strokeWeight: 5
41 | })
42 |
43 | drawLine.setMap(map)
44 | renderInfoBox(data)
45 | path.shift()
46 | }
47 | }
48 | }
49 |
50 | window.onload = async () => {
51 | const data = await fetchApi()
52 | const center = getLatLng({ info: data.info })
53 | const render = init({ center })
54 |
55 | const map = new google.maps.Map(ROOT, {
56 | center,
57 | zoom: 4,
58 | scrollwheel: false,
59 | streetViewControl: false,
60 | fullscreenControl: true,
61 | styles: styles[data.info.visibility]
62 | })
63 |
64 | render.circle(map, data)
65 |
66 | setInterval(async () => {
67 | const data = await fetchApi()
68 | render.line(map, data)
69 | }, REFRESH_TIME)
70 | }
71 |
--------------------------------------------------------------------------------
/src/modules/mapStyle.js:
--------------------------------------------------------------------------------
1 | export default {
2 | daylight: [
3 | {
4 | 'elementType': 'geometry',
5 | 'stylers': [
6 | {
7 | 'color': '#ebe3cd'
8 | }
9 | ]
10 | },
11 | {
12 | 'elementType': 'labels.text.fill',
13 | 'stylers': [
14 | {
15 | 'color': '#523735'
16 | }
17 | ]
18 | },
19 | {
20 | 'elementType': 'labels.text.stroke',
21 | 'stylers': [
22 | {
23 | 'color': '#f5f1e6'
24 | }
25 | ]
26 | },
27 | {
28 | 'featureType': 'administrative',
29 | 'elementType': 'geometry',
30 | 'stylers': [
31 | {
32 | 'visibility': 'off'
33 | }
34 | ]
35 | },
36 | {
37 | 'featureType': 'administrative',
38 | 'elementType': 'geometry.stroke',
39 | 'stylers': [
40 | {
41 | 'color': '#c9b2a6'
42 | }
43 | ]
44 | },
45 | {
46 | 'featureType': 'administrative.land_parcel',
47 | 'elementType': 'geometry.stroke',
48 | 'stylers': [
49 | {
50 | 'color': '#dcd2be'
51 | }
52 | ]
53 | },
54 | {
55 | 'featureType': 'administrative.land_parcel',
56 | 'elementType': 'labels.text.fill',
57 | 'stylers': [
58 | {
59 | 'color': '#ae9e90'
60 | }
61 | ]
62 | },
63 | {
64 | 'featureType': 'landscape.natural',
65 | 'elementType': 'geometry',
66 | 'stylers': [
67 | {
68 | 'color': '#dfd2ae'
69 | }
70 | ]
71 | },
72 | {
73 | 'featureType': 'poi',
74 | 'stylers': [
75 | {
76 | 'visibility': 'off'
77 | }
78 | ]
79 | },
80 | {
81 | 'featureType': 'poi',
82 | 'elementType': 'geometry',
83 | 'stylers': [
84 | {
85 | 'color': '#dfd2ae'
86 | }
87 | ]
88 | },
89 | {
90 | 'featureType': 'poi',
91 | 'elementType': 'labels.text.fill',
92 | 'stylers': [
93 | {
94 | 'color': '#93817c'
95 | }
96 | ]
97 | },
98 | {
99 | 'featureType': 'poi.park',
100 | 'elementType': 'geometry.fill',
101 | 'stylers': [
102 | {
103 | 'color': '#a5b076'
104 | }
105 | ]
106 | },
107 | {
108 | 'featureType': 'poi.park',
109 | 'elementType': 'labels.text.fill',
110 | 'stylers': [
111 | {
112 | 'color': '#447530'
113 | }
114 | ]
115 | },
116 | {
117 | 'featureType': 'road',
118 | 'stylers': [
119 | {
120 | 'visibility': 'off'
121 | }
122 | ]
123 | },
124 | {
125 | 'featureType': 'road',
126 | 'elementType': 'geometry',
127 | 'stylers': [
128 | {
129 | 'color': '#f5f1e6'
130 | }
131 | ]
132 | },
133 | {
134 | 'featureType': 'road',
135 | 'elementType': 'labels.icon',
136 | 'stylers': [
137 | {
138 | 'visibility': 'off'
139 | }
140 | ]
141 | },
142 | {
143 | 'featureType': 'road.arterial',
144 | 'elementType': 'geometry',
145 | 'stylers': [
146 | {
147 | 'color': '#fdfcf8'
148 | }
149 | ]
150 | },
151 | {
152 | 'featureType': 'road.highway',
153 | 'elementType': 'geometry',
154 | 'stylers': [
155 | {
156 | 'color': '#f8c967'
157 | }
158 | ]
159 | },
160 | {
161 | 'featureType': 'road.highway',
162 | 'elementType': 'geometry.stroke',
163 | 'stylers': [
164 | {
165 | 'color': '#e9bc62'
166 | }
167 | ]
168 | },
169 | {
170 | 'featureType': 'road.highway.controlled_access',
171 | 'elementType': 'geometry',
172 | 'stylers': [
173 | {
174 | 'color': '#e98d58'
175 | }
176 | ]
177 | },
178 | {
179 | 'featureType': 'road.highway.controlled_access',
180 | 'elementType': 'geometry.stroke',
181 | 'stylers': [
182 | {
183 | 'color': '#db8555'
184 | }
185 | ]
186 | },
187 | {
188 | 'featureType': 'road.local',
189 | 'elementType': 'labels.text.fill',
190 | 'stylers': [
191 | {
192 | 'color': '#806b63'
193 | }
194 | ]
195 | },
196 | {
197 | 'featureType': 'transit',
198 | 'stylers': [
199 | {
200 | 'visibility': 'off'
201 | }
202 | ]
203 | },
204 | {
205 | 'featureType': 'transit.line',
206 | 'elementType': 'geometry',
207 | 'stylers': [
208 | {
209 | 'color': '#dfd2ae'
210 | }
211 | ]
212 | },
213 | {
214 | 'featureType': 'transit.line',
215 | 'elementType': 'labels.text.fill',
216 | 'stylers': [
217 | {
218 | 'color': '#8f7d77'
219 | }
220 | ]
221 | },
222 | {
223 | 'featureType': 'transit.line',
224 | 'elementType': 'labels.text.stroke',
225 | 'stylers': [
226 | {
227 | 'color': '#ebe3cd'
228 | }
229 | ]
230 | },
231 | {
232 | 'featureType': 'transit.station',
233 | 'elementType': 'geometry',
234 | 'stylers': [
235 | {
236 | 'color': '#dfd2ae'
237 | }
238 | ]
239 | },
240 | {
241 | 'featureType': 'water',
242 | 'elementType': 'geometry.fill',
243 | 'stylers': [
244 | {
245 | 'color': '#b9d3c2'
246 | }
247 | ]
248 | },
249 | {
250 | 'featureType': 'water',
251 | 'elementType': 'labels.text.fill',
252 | 'stylers': [
253 | {
254 | 'color': '#92998d'
255 | }
256 | ]
257 | }
258 | ],
259 | eclipsed: [
260 | {
261 | 'elementType': 'geometry',
262 | 'stylers': [
263 | {
264 | 'color': '#242f3e'
265 | }
266 | ]
267 | },
268 | {
269 | 'elementType': 'labels.text.fill',
270 | 'stylers': [
271 | {
272 | 'color': '#746855'
273 | }
274 | ]
275 | },
276 | {
277 | 'elementType': 'labels.text.stroke',
278 | 'stylers': [
279 | {
280 | 'color': '#242f3e'
281 | }
282 | ]
283 | },
284 | {
285 | 'featureType': 'administrative',
286 | 'elementType': 'geometry',
287 | 'stylers': [
288 | {
289 | 'visibility': 'off'
290 | }
291 | ]
292 | },
293 | {
294 | 'featureType': 'administrative.locality',
295 | 'elementType': 'labels.text.fill',
296 | 'stylers': [
297 | {
298 | 'color': '#d59563'
299 | }
300 | ]
301 | },
302 | {
303 | 'featureType': 'poi',
304 | 'stylers': [
305 | {
306 | 'visibility': 'off'
307 | }
308 | ]
309 | },
310 | {
311 | 'featureType': 'poi',
312 | 'elementType': 'labels.text.fill',
313 | 'stylers': [
314 | {
315 | 'color': '#d59563'
316 | }
317 | ]
318 | },
319 | {
320 | 'featureType': 'poi.park',
321 | 'elementType': 'geometry',
322 | 'stylers': [
323 | {
324 | 'color': '#263c3f'
325 | }
326 | ]
327 | },
328 | {
329 | 'featureType': 'poi.park',
330 | 'elementType': 'labels.text.fill',
331 | 'stylers': [
332 | {
333 | 'color': '#6b9a76'
334 | }
335 | ]
336 | },
337 | {
338 | 'featureType': 'road',
339 | 'stylers': [
340 | {
341 | 'visibility': 'off'
342 | }
343 | ]
344 | },
345 | {
346 | 'featureType': 'road',
347 | 'elementType': 'geometry',
348 | 'stylers': [
349 | {
350 | 'color': '#38414e'
351 | }
352 | ]
353 | },
354 | {
355 | 'featureType': 'road',
356 | 'elementType': 'geometry.stroke',
357 | 'stylers': [
358 | {
359 | 'color': '#212a37'
360 | }
361 | ]
362 | },
363 | {
364 | 'featureType': 'road',
365 | 'elementType': 'labels.icon',
366 | 'stylers': [
367 | {
368 | 'visibility': 'off'
369 | }
370 | ]
371 | },
372 | {
373 | 'featureType': 'road',
374 | 'elementType': 'labels.text.fill',
375 | 'stylers': [
376 | {
377 | 'color': '#9ca5b3'
378 | }
379 | ]
380 | },
381 | {
382 | 'featureType': 'road.highway',
383 | 'elementType': 'geometry',
384 | 'stylers': [
385 | {
386 | 'color': '#746855'
387 | }
388 | ]
389 | },
390 | {
391 | 'featureType': 'road.highway',
392 | 'elementType': 'geometry.stroke',
393 | 'stylers': [
394 | {
395 | 'color': '#1f2835'
396 | }
397 | ]
398 | },
399 | {
400 | 'featureType': 'road.highway',
401 | 'elementType': 'labels.text.fill',
402 | 'stylers': [
403 | {
404 | 'color': '#f3d19c'
405 | }
406 | ]
407 | },
408 | {
409 | 'featureType': 'transit',
410 | 'stylers': [
411 | {
412 | 'visibility': 'off'
413 | }
414 | ]
415 | },
416 | {
417 | 'featureType': 'transit',
418 | 'elementType': 'geometry',
419 | 'stylers': [
420 | {
421 | 'color': '#2f3948'
422 | }
423 | ]
424 | },
425 | {
426 | 'featureType': 'transit.station',
427 | 'elementType': 'labels.text.fill',
428 | 'stylers': [
429 | {
430 | 'color': '#d59563'
431 | }
432 | ]
433 | },
434 | {
435 | 'featureType': 'water',
436 | 'elementType': 'geometry',
437 | 'stylers': [
438 | {
439 | 'color': '#17263c'
440 | }
441 | ]
442 | },
443 | {
444 | 'featureType': 'water',
445 | 'elementType': 'labels.text.fill',
446 | 'stylers': [
447 | {
448 | 'color': '#515c6d'
449 | }
450 | ]
451 | },
452 | {
453 | 'featureType': 'water',
454 | 'elementType': 'labels.text.stroke',
455 | 'stylers': [
456 | {
457 | 'color': '#17263c'
458 | }
459 | ]
460 | }
461 | ] }
--------------------------------------------------------------------------------