-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprojectThree.html
363 lines (340 loc) · 21.2 KB
/
projectThree.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
<!DOCTYPE html>
<html lang="en" style="overflow: visible">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon" />
<title>Petrol Down Under</title>
<link rel="stylesheet" href="everything.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css"/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Special+Elite&display=swap"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Rubik+Glitch&display=swap"/>
</head>
<body data-barba="wrapper">
<main data-barba="container" data-barba-namespace="home">
<div class="transition"></div>
<nav class="project-nav">
<a href="index.html">
<div class="logo-main">
<div class="gas co">
<span class="number">27</span>
<h2 class="symbol">Jf</h2>
<p class="name">58.933</p>
</div>
</div>
</a>
<div class="burger-menu">
<div class="burger-line"></div>
<div class="burger-line"></div>
<div class="burger-line"></div>
</div>
<ul class="dropdown">
<li class="dropdown-li" style="background-color: wheat;"><a href="projectOne.html" >Project 1</a></li>
<li class="dropdown-li" style="background-color: gold;"><a href="projectTwo.html">Project 2</a></li>
<li class="dropdown-li" style="background-color: tomato;"><a href="projectThree.html">Project 3</a></li>
<li class="dropdown-li" style="background-color: wheat;"><a href="projectFour.html">Project 4</a></li>
</ul>
</nav>
<div class="wrapper">
<section class="project-header">
<h1 class="project-title">
PROJECT 3: Petrol Down Under
</h1>
<h3 class="project-goal">Objective: Build an SPA with Google Maps API</h3>
<h3 class="project-tools">Tools: Express, PostgreSQL, Google Maps API, Open Meteo API, Geolocation API, Postcode API </h3>
<div class="overview-image">
<img src="images/petrol_down_under.png" alt="" />
</div>
<img class="header-bg" src="images/brain3.jpeg" alt="" />
</section>
<div class="parallaxParent darken ralof">
<div
style="
background-image: url(images/project3/upsidedown.jpeg);
"
></div>
</div>
<section class="parallax-section" style="border-top: 10px #000094; border-bottom: 10px #d60000;border-style: solid;">
<div class="parallax-content">
<div></div>
<h1>A Higher Calling</h1>
<p>
This project had three goals: to work with a single-page application (SPA), to work with APIs, and most
importantly of all, to work with a team. Together, we had to build a website that would
use data from Google Maps to display every single petrol station in Australia for users to see on a map.
Thus on this fateful day, <span class="jesus">TEAM JESUS</span> was born, formed by me and 3 other classmates. The other team in my class
was called something fancier like Team Epsilon V but I bet they never screamed their team's name when things didn't just work.
</p>
<p>
Before we got to anything new, we first had to revisit some old friends: Express and PostgreSQL.
Express was there to do its usual job of handling server-side stuff and PostgreSQL once again served as our database manager.
We then had to fill our database with over 5000 petrol stations worth of data from a file to get our project started.
</p>
<pre class="theme-atom-one-dark"><code class="hljs-code language-javascript">
const fs = require('fs');
const db = require('./index')
const data = fs.readFileSync('./db/stations.csv',
{ encoding: 'utf8', flag: 'r' });
let lines = data.split('\n')
let brandNamesList = []
async function doDatabase() {
let sql1 = "delete from stations"
let sql2 = "delete from locations"
let sql3 = "delete from owners"
let deleted = await db.query(sql1).then(() => db.query(sql2)).then(() => db.query(sql3))
let owners = await doOwners()
let locations = await doLocations()
let stations = await doStations()
}
async function doOwners() {
for (let line of lines.slice(1)) {
let sections = line.split(',')
let brandName = sections[7]
let sql
let sqlNewOwner = `
INSERT INTO owners
(brand_name)
VALUES ($1)
RETURNING *;
`
let sqlExistingOwner = `
SELECT * FROM owners
WHERE brand_name = $1;
`
if (!brandNamesList.includes(brandName)) {
brandNamesList.push(brandName)
sql = sqlNewOwner
} else {
sql = sqlExistingOwner
}
db.query(sql, [brandName])
.catch(err=>console.log(err))
}
}
async function doLocations() {
for (let line of lines.slice(1)) {
let stationObj = {}
let sections = line.split(',')
stationObj.address = sections[9]
stationObj.suburb = sections[10]
stationObj.lat = sections[sections.length - 2]
stationObj.lng = sections[sections.length - 1]
let sqlLocation = `
INSERT INTO locations
(address, suburb, lat, lng)
VALUES
($1, $2, $3, $4);
`
db.query(sqlLocation, [stationObj.address, stationObj.suburb, stationObj.lat, stationObj.lng])
.catch(err=>console.log(err))
}
}
async function doStations() {
for (let line of lines.slice(1)) {
let stationObj = {}
let sections = line.split(',')
let brandName = sections[7]
let address = sections[9]
stationObj.description = sections[2]
stationObj.name = sections[5]
let sql_ownersId = `SELECT id FROM owners WHERE brand_name = $1`
let sql_locationId = `SELECT id FROM locations WHERE address = $1`
let ownerId = await db.query(sql_ownersId, [brandName]).then(res=>res.rows[0])
let locationId = await db.query(sql_locationId, [address]).then(res=>res.rows[0])
let sqlStation = `
INSERT INTO stations
(owner_id, location_id, station_name, description)
VALUES
($1, $2, $3, $4)
`
db.query(sqlStation, [ownerId.id, locationId.id, stationObj.name, stationObj.description])
.catch(err=>console.log(err))
}
}
doDatabase()
</code></pre>
<span class="subtitle">And this is the easy part!</span>
<p>
The file we were given was a CSV, basically a file formatted like table only with commas separating data instead of columns.
We read the file using <code>fs.readFileSync</code> and then essentially sliced and spliced the data up to input in our database.
There was one major annoyance that I remember discovering. We split each row of data by commas - as that's how they're meant to be seperated -
but some stations had comments with <em>normal</em> commas inside, meaning we would split the data incorrectly. Thank goodness for Ctrl+F
or we would have to sift through 5000 pieces of data manually to figure it out.
</p>
</div>
</section>
<div class="parallaxParent">
<div style="background-image: url(images/project3/petrol_stations.png)"></div>
</div>
<section class="parallax-section" style="border-top: 10px #000094; border-bottom: 10px #d60000;border-style: solid;">
<div class="parallax-content">
<h1>Revelations</h1>
<p>
With our database populated, we could now move on to the next test of faith: using Google Maps API. This was just one of many APIs, albeit the central one,
that we were going to use for this project. All these APIs - Application Programming Interfaces - were basically ways for us to communicate with other software
to retrieve some information. In fact, we even technically made our own API with our database. For Google Maps API, we were basically getting all the stuff we needed to make a map like they do.
</p>
<pre class="theme-atom-one-dark"><code class="hljs-code language-javascript">
let map;
async function initMap() {
const { Map } = await google.maps.importLibrary("maps");
map = new Map(document.getElementById("map"), {
center: { lat: -34.397, lng: 150.644 },
zoom: 8,
});
}
initMap();
</code></pre>
<p>
This is the basic code, provided by Google themselves, to make a generic Google Map. You might have noticed the
<code>async</code> and <code>await</code> terminology in the code being used. While functions normally work line by line,
stopping for each one until its done, async await means that your code won't be held up by things that may take some time (like fetching
data from an API).
</p>
<p>
Anyway, making a basic map is quite simple. However, this little function would end up causing <span class="jesus">TEAM JESUS</span>
a lot more trouble than expected. More on that later. After this initial set up, we had to split into pairs to do some peer programming
for the rest of the assignment. Also more on that later. As a result of this though, it made our code pretty scattered; we were
given pre-assigned tasks to accomplish for this project and each feature was split into multiple steps. I could do part 1 of one feature
and then only rotate back to it at like part 4 so its a bit hard to run through all the code I did.
</p>
<p>
Nevertheless, I can at least explain all the other APIs we used. Open Meteo API gave us data about the weather which we
used to display the current temperature. Geolocation API was used to find the user's location and move the map to center on
said location. Lastly, Postcode API was used to find all the petrol stations under a certain postcode. This last one was
interesting/frustrating because unlike the other APIs, we couldn't make a direct request to Postcode API.
</p>
<pre class="theme-atom-one-dark"><code class="hljs-code language-javascript">
// postcode router
const express = require('express')
const router = express.Router()
router.get('/api/postcode/:postcode', (req, res) => {
fetch(`http://v0.postcodeapi.com.au/suburbs/${req.params.postcode}.json`)
.then(res => res.json())
.then(data => res.status(200).json(data))
})
module.exports = router
// postcode model
const divPostcode = document.querySelector('.postcode')
const formPostcode = document.querySelector('.postcode-form')
const inputPostcode = document.querySelector('.postcode-input')
const suburbList = document.querySelector('.suburb-list')
formPostcode.addEventListener('submit', handleSubmit)
async function handleSubmit(evt) {
evt.preventDefault()
let postcode = inputPostcode.value
suburbList.innerHTML = ""
// make endpoint to send a postcode then make a call to the suburb api
fetch(`/api/postcode/${postcode}`)
.then(res => res.json())
.then(data => {
for (let suburb of data) {
let name = suburb.name
let nametagLi = document.createElement('li')
nametagLi.className = 'suburb-name-li'
nametagLi.textContent = name
suburbList.appendChild(nametagLi)
}
})
}
</code></pre>
<span class="subtitle">Fetching the postcode data... from our own API endpoint that fetched postcode data... from Postcode API</span>
<p>
You see, for literally every other API we used, all we would need is the second step: using <code>fetch</code> to get the data from an API
endpoint, turning it into readable json, and then using DOM Manipulation, like in Tic-Tac-Toe, to update the site with the fetched data.
But for Postcode API? Nope. Had to use a proxy to get the data then retrieve the data from the proxy. Now before, I start ranting further,
I suppose I should bring up some code that I <em>was</em> happy about (mostly because I fixed the problems mostly by myself)...
</p>
<pre class="theme-atom-one-dark"><code class="hljs-code language-javascript">
// spotlight model
const spotlightStation = document.querySelector('.randomStation')
export async function getSpotlight() {
const result = await fetch('/api/stations/random')
const data = await result.json()
let name = data.station_name
let address = data.address
spotlightStation.innerHTML = `${name} <br /> ${address}`
return data
}
// map model
// spotlight code
import { getSpotlight } from './spotlight.js';
const spotlightStation = document.querySelector('.randomStation')
const refreshLink = document.querySelector('.refresh')
refreshLink.addEventListener('click', async () => {
spotlightData = await getSpotlight()
spotlightLat = spotlightData.lat
spotlightLng = spotlightData.lng
})
// data for spotlight
let spotlightData
let spotlightLat
let spotlightLng
// initial spotlight
spotlightData = await getSpotlight()
spotlightLat = spotlightData.lat
spotlightLng = spotlightData.lng
spotlightStation.addEventListener('click', () => goToStation(map,spotlightLat,spotlightLng))
</code></pre>
<p>
So there was this feature we had called 'Spotlight'. It would pull a random station from our database which users could then
click to move the map to. The trouble was that updating the map required access to the map object we had initialized at the
very beginning. The Spotlight data and the map were in different files so that was a problem. This was solved by importing the
function into the map file but then another problem arose: the Spotlight coordinates would only be set once.
</p>
<p>
If we refreshed the Spotlight for a new location, clicking it would still take the user back to the first Spotlight location.
This was because we were initializing the Spotlight data on the first call and we couldn't reinitialize the variables. This was
solved by initializing the variables as empty variables to be filled up later.
</p>
<p>
Lastly, we had problems getting the Spotlight to actually move since moving the map required coordinates and eventListener
functions couldn't take arguments. This was solved by just calling a function that called the moving map function.
</p>
<p>
The funny thing is that I didn't even work on the Spotlight initially so when I got to it, I had no idea what it was. In the end,
it was frustrating but ultimately satisfying to solve, which I could say for a lot of the project too.
</p>
</div>
</section>
<div class="parallaxParent">
<div style="background-image: url(images/project3/spotlight.gif)"></div>
</div>
<section class="parallax-section">
<div class="parallax-content">
<h1>Rapture</h1>
<p>
Before I close off on Petrol Down Under, I'd like to give a shout out to the rest of <span class="jesus">TEAM JESUS</span>.
Initially, I was really worried about the group project because I don't usually work well in groups. However, the peer programming
turned out to be extremely helpful; I never felt under too much pressure nor did I feel like I wasn't contributing enough. I could egg
my teammates and they could egg me back just fine. Overall, 10/10 experience. Would become a disciple again.
</p>
<p>
And now, a little angry comment about Google Maps API for having the worst variable names I have ever seen. Like seriously, why is
everything like 'xx' or 'Xh' or 'ii'? And why does it change randomly?! Our app broke once overnight because a variable changed and,
as of writing this now a month later, it's been broken yet again because of a variable change. If I never have to work with Google
Maps API ever again, it will be too soon.
</p>
<p> Anyhow, what's done is done. I forgive Google Maps. Now feel free to move on to my other projects:
<a class="bottom-link" href="projectOne" style="color: red;">Project 1: Tic-Tac-Toe</a>,
<a class="bottom-link" href="projectTwo" style="color: green;">Projecte 2: GameShare™</a>, or
<a class="bottom-link" href="projectFour" style="color: #0047ab;">Project 4</a>
</p>
</div>
</section>
</div> <!-- wrapper div -->
</main>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@barba/core"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ScrollTrigger.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/ScrollMagic.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/plugins/animation.gsap.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/plugins/debug.addIndicators.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="barba.js"></script>
<script class="main-script" src="everything.js"></script>
</body>
</html>