Light vs Dark, the epic battle

Yea, you read that title correctly, light vs. dark! Over the past 2 weeks, my professor gave us what is essentially Pokemon Go, but with bridges around Toronto. It’s a VERY cool application, and I suggest checking it out here if you’re interested.

In the week he showed this project to us, he issues us this challenge. To add automatic theme changing to the application based on the current time of day. So if it’s light outside, use a light map with dark icons, and if it’s dark outside, dark map with light icons; so it can be easier on the eyes. A very interesting task, as I’ve never done something like this before. So I knew for a fact, I’ll come to learn something new!

Finding Clues

Now the first step to any problem is to first understand the problem, of course. So I forked my professor’s repo and cloned said fork. Then I started digging! Looking for clues and the places where he would set the icons and the map of our application, which was actually very simple. In a couple of minutes, I had found it. Found where he would make the magic happened.

My professor had a folder called ‘map’, and it was in this folder he wrote 4 different files. The implementation of this is quite genius, actually, A+ to him! So he had 4 files, base-ui.js, debug-ui.js, default-ui.js, and index.js. All these files were in one folder, and this is how he would build his map. In his base-ui file, he would load the map, set the user’s current location, and add a marker at said location. Then depending on what command you use to run the program (either npm start or npm run debug), it would select either debug-ui or default-ui; and they would all be called by index.js. Like I said, really genius implementation!

So that was my first clue, I found where he was selecting the map to display, now I just had to find where he would set his markers! I kept looking and looking, and noticed a file called troll-bride.js, now this file was outside of the map directory, so it was interesting. It was related to the contents of the map, but it wasn’t in the map folder, so that struck me as odd. But that file was indeed the one I was looking for. Because it was there that he was setting up his icons! Have a look

'use strict';
const Bridge = require('./bridge');
const log = require('./log');
const map = require('./map');
const svgMarker = require('./svg-marker');
const db = require('./db');
/**
* A decorated Bridge type, with extra behaviour for interacting on the map.
*/
class TrollBridge extends Bridge {
// Namespace a key for use in idb
get idbKey() {
return `bridge::${this.id}`;
}
// Turn an IDB Key back into an ID
static idFromIdbKey(key) {
return key.replace(/bridge::/, '');
}
get streetViewUrl() {
let lat = this.lat;
let lng = this.lng;
// https://developers.google.com/maps/documentation/urls/guide
return `https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=${lat},${lng}&heading=-45`;
}
get cardUrl() {
// The unique card image URL for this bridge
return `../data/cards/${this.id}.svg`;
}
get cardImgEl() {
let img = document.createElement('img');
img.src = this.cardUrl;
return img;
}
// Show the appropriate icon for this bridge: locked or unlocked, depending
// on whether or not the user has collected this bridge in the past.
show(callback) {
let bridge = this;
callback = callback || function() {};
// Don't add a marker for this bridge if we already have one.
if (bridge.marker) {
log.debug('Skipping adding bridge marker, already exists');
return;
}
let addMarker = icon => {
bridge.marker = map.addMarker(
bridge.lat,
bridge.lng,
bridge.title,
icon,
bridge.cardUrl,
bridge.streetViewUrl
);
log.debug('Added bridge to map', bridge);
};
// See if the user has already collected this bridge (check idb)
// before and use locked or unlocked icon depending on the answer.
db
.get(bridge.idbKey)
.then(val => {
if (val) {
addMarker(svgMarker.unlocked);
} else {
addMarker(svgMarker.locked);
}
callback(null);
})
.catch(err => {
log.error(`Unable to read key '${bridge.idbkey}' from idb: ${err}`);
// Default to locked so we at least show something
addMarker(svgMarker.locked);
callback(err);
});
}
// The user has collected this bridge, unlock it and persist to idb
unlock(callback) {
let bridge = this;
callback = callback || function() {};
// The marker should already exist. If it doesn't, that's a bug, bail with an error
if (!bridge.marker) {
log.error('Called unlock() on Bridge instance with no marker');
return;
}
// Set key in idb indicating that we've collected this bridge
db
.set(bridge.idbKey, new Date())
.then(() => {
bridge.marker.setIcon(svgMarker.unlocked);
log.info('Unlocked bridge', bridge);
callback(null);
})
.catch(err => {
log.error(`Unable to set key '${bridge.idbkey}' in idb: ${err}`);
callback(err);
});
}
// Create new Bridge from existing JS Object (e.g., parsed JSON)
static fromObject(obj) {
return new TrollBridge(
obj.id,
obj.name,
obj.lat,
obj.lng,
obj.year,
obj.length,
obj.width
);
}
}
module.exports = TrollBridge;

view raw
troll-bridge.js
hosted with ❤ by GitHub

It’s rather long, but this is definitely worth looking at. So I knew where to go to get the desired results. I found the files necessary to make this program work, but that wasn’t all.

Praising the sun

Now how would you be able to tell what time of the day it is? I mean, you can tell by going outside or even checking on the internet, or even your clock, but how would you let a computer know? How would you let a computer know when it’s dark outside? How would you let it know that it’s not dark outside? How would you let it know it’s dusk, or that it’s dawn? Enter the amazing library known as Suncalc. Using Suncalc, I was able to determine what the sunrise and sunset times are for where the current user is. All I had to do was give it a latitude and longitude. Seems simple at first, but how would I construct this? Well let me show you!

'use strict';
const SunCalc = require('suncalc');
let isLight = false;
/**
* Calculate the current time of day and figure out if it's dark or light outside.
* @param {*} myLat
* @param {*} myLng
*/
let calculateCurrentTime = (myLat, myLng) => {
let tempCurrentTime = SunCalc.getTimes(new Date(), myLat, myLng);
let userDawn = tempCurrentTime.dawn;
let userDusk = tempCurrentTime.dusk;
let myDate = new Date();
if (myDate.getTime() <= userDusk.getTime() && myDate.getTime() >= userDawn.getTime()) {
isLight = true;
}
}
module.exports.calculateCurrentTime = calculateCurrentTime;
module.exports.isLight = () => isLight;

view raw
timeofday.js
hosted with ❤ by GitHub

So this was my way of determining the time of day, and whether or not there is light outside. I created a module that would do the task for me, and return the appropriate boolean. If you look at the code, it’s actually very simple, really. Remove all the Java-esc comments and you have about 15 lines or so? Remove all the unnecessary spaces, and you even have less. So really, not a hard module to wrap your head around. There is a function called calculateCurrentTime and when it gets called, it sets the value of isLight to true or false. Simple.

Now how was I going to make this work with the rest of the code? All I have so far is a module that is capable of telling you if it’s light or dark outside, doesn’t really change anything. I had to go back to the base-ui.js file to change the map depending on light or dark.

At the very top line this js file was this line of code
const tileUrl = 'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png'; Now this was a good implementation for only using one map, but in this case we’ll be using two. So I needed a to let this variable decide what map to use based on the light or dark idea. And my answer was in that same file.

This is how my professor was setting the map.

init(lat, lng) {
let mapEl = document.createElement('div');
mapEl.id = 'map';
document.body.appendChild(mapEl);
// http://leafletjs.com/reference-1.3.0.html#map
let map = (this.map = leaflet.map(mapEl, this.options));
leaflet.tileLayer(tileUrl, { attribution }).addTo(map);
map.setView([lat, lng], defaultZoomLevel);
// http://leafletjs.com/reference-1.3.0.html#map-event
let onMapChange = () => this.emit('update', map.getBounds());
map.on('viewreset', onMapChange);
map.on('moveend', onMapChange);
// Show a marker at our current location
this.currentLocationMarker = leaflet
.marker([lat, lng], {
title: 'Current Location',
icon: svgMarker.location
})
.addTo(map);
log.info(`Map initialized with centre lat=${lat}, lng=${lng}`);
}

view raw
base-ui.js
hosted with ❤ by GitHub

That’s good, and it worked, but not for what we were about to do. We had to determine whether to use a dark map or a light map, and we needed to do it on the fly. So enter my fix.

const timeofday = require('../timeofday.js');
init(lat, lng) {
let mapEl = document.createElement('div');
mapEl.id = 'map';
document.body.appendChild(mapEl);
// http://leafletjs.com/reference-1.3.0.html#map
let map = (this.map = leaflet.map(mapEl, this.options));
// We decide what map to use based on time of day
let tileUrl = timeofday.isLight() ? 'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png' : 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png';
leaflet.tileLayer(tileUrl, { attribution }).addTo(map);
map.setView([lat, lng], defaultZoomLevel);
// http://leafletjs.com/reference-1.3.0.html#map-event
let onMapChange = () => this.emit('update', map.getBounds());
map.on('viewreset', onMapChange);
map.on('moveend', onMapChange);
// Show a marker at our current location
this.currentLocationMarker = leaflet
.marker([lat, lng], {
title: 'Current Location',
icon: timeofday.isLight() ? svgMarker.location : svgMarker.locationWhite
})
.addTo(map);
log.info(`Map initialized with centre lat=${lat}, lng=${lng}`);
}

view raw
base-ui-ak.js
hosted with ❤ by GitHub

And there you have. I move the const line into the function that calls it (line 11), so that way that value is determined WHEN it’s actually needed, not before. And at the same time, I also determine what color icon to use for the player’s position at line 24. So far, so good! I forgot to show you the code for the icons!

'use strict';
// Using Material Icons as inline SVG – https://material.io/icons/
const leaflet = require('leaflet');
// Read contents of SVG files from bundle as Data URLs
const locationSvgUrl = require('../icons/material-icons/location.svg');
const lockedSvgUrl = require('../icons/material-icons/locked.svg');
const unlockedSvgUrl = require('../icons/material-icons/unlocked.svg');
// Read white icons for when it's dark out
const locationSvgWhite = require('../icons/material-icons/location-white.svg');
const lockedSvgWhite = require('../icons/material-icons/locked-white.svg');
const unlockedSvgWhite = require('../icons/material-icons/unlocked-white.svg');
// All icons share the same size, define it once
const iconSize = [25, 25];
// Expose custom Leaflet Icons to be used in our markers
module.exports.location = leaflet.icon({
iconUrl: locationSvgUrl,
iconSize
});
// Expose custom Leaflet Icons to be used in our markers
module.exports.locationWhite = leaflet.icon({
iconUrl: locationSvgWhite,
iconSize
});
module.exports.locked = leaflet.icon({
iconUrl: lockedSvgUrl,
iconSize
});
module.exports.lockedWhite = leaflet.icon({
iconUrl: lockedSvgWhite,
iconSize
});
module.exports.unlocked = leaflet.icon({
iconUrl: unlockedSvgUrl,
iconSize
});
module.exports.unlockedWhite = leaflet.icon({
iconUrl: unlockedSvgWhite,
iconSize
});

view raw
svg-marker.js
hosted with ❤ by GitHub

Now you may be wondering, why did I do it like this, why didn’t I just use that logic of

module.exports.locked = leaflet.icon({
iconUrl: timeofday.isLight() ? locked : lockedWhite,
iconSize
});

And the reason to that is because of two words. ASYNCHRONOUS CODE! See for some weird reason, if I had implemented it like this, and would let the svg-marker.js file handle it, it would for some reason always pick the wrong icons. I’m not even kidding! It would always pick the wrong icon, every flipping time! So I figured, rather than give it the task of deciding, why don’t I just export everything, and let someone else handle decided what to pick, so I did!

Making it all work

So we were done-ish. I could now change the map, and the current position marker. But what I couldn’t do was change the locked/unlocked icons from light to dark or vice versa. To do that, I had to head back to the toll-bride.js file! And really, just apply the logic of the ternary if

db
.get(bridge.idbKey)
.then(val => {
if (val) {
addMarker(timeofday.isLight() ? svgMarker.unlocked : svgMarker.unlockedWhite);
} else {
addMarker(timeofday.isLight() ? svgMarker.locked : svgMarker.lockedWhite);
}
callback(null);
})
.catch(err => {
log.error(`Unable to read key '${bridge.idbkey}' from idb: ${err}`);
// Default to locked so we at least show something
addMarker(timeofday.isLight() ? svgMarker.locked : svgMarker.lockedWhite);
callback(err);
});

view raw
troll-bridge-ak.js
hosted with ❤ by GitHub

Now there is more to this code of course, but as I’ve used gists a lot, I’m trying to cut down! But you get the idea.

At this point, my code was working.

Dark

Light

 

House Keeping

All that was left at this point was to take care of a few house keeping tasks. The first would be to run the prettier package to have it properly format my code for me into something my professor would be proud of. And the second would be to run eslint. My professor had certain rules he had in his eslint file in the project that would make sure that certain habits (like putting random console.logs) certain students had would be eliminated.

ccc947de82b33eb2d0c51a68002b0118

c1cfeb8506abeec039d353be6e85d8a5

Well you see, those console logs were already THERE BEFORE I EVEN TOUCHED IT!!! NO seriously, my professor had those, so I had nothing to change on my end, haha!

 

Conclusion

Honestly, this was REALLY fun. It was very interesting to see how something like changing the theme of an application on the fly could be done, and above all, to do it with something as cool as the light of day. I learnt quite a bit about the constraints of synchronous code, and how to properly write modules. Overall, I say this was a successful task.

If you’d like to look at the branch in which I made the fix, click here.

I’d like to thank you for taking the time to read this far. If you have any questions, please don’t hesitate to leave them in the comments section!

One thought on “Light vs Dark, the epic battle

  1. Pingback: Playing with the big bois – A. Kabia

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s