|
| 1 | +# Core Logic |
| 2 | + |
| 3 | +To obtain the pinpoint weather data, earth location is a required parameter. For this, the longitude and latitude would suffice programmatically. For the interactive option, a physical postal address (partial address of City, State, or Country) is required. Optionally, you could also register with the google map service (*additional charge will incur*). Then, integrate google-map API where the user can point to a location on the map to trigger a localized weather report. |
| 4 | + |
| 5 | +* The geolocation of a known address can be obtained by using the client-side javascript [Windows Navigator](https://www.w3schools.com/jsref/obj_navigator.asp). Your browser uses different types and sources of information to identify your location. These include your IP address, geolocation via HTML5 in your browser, and your PC's language and time settings. For more detail on how Navigator.geolocation is determined, [read more here](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition). |
| 6 | + |
| 7 | +Sample code for client-side geolocation: |
| 8 | + |
| 9 | +```c |
| 10 | + <script type=javascript> |
| 11 | + if (navigator.geolocation) { |
| 12 | + navigator.geolocation.getCurrentPosition(showPosition); |
| 13 | + } else { |
| 14 | + document.getElementById("geo-coord").innerHTML = |
| 15 | + "Geolocation is not supported by this browser."; |
| 16 | + } |
| 17 | + |
| 18 | + function showPosition(position) { |
| 19 | + document.getElementById("geo-coord").innerHTML = |
| 20 | + "Latitude: " + position.coords.latitude + |
| 21 | + "Longitude: " + position.coords.longitude; |
| 22 | + } |
| 23 | + </script> |
| 24 | +``` |
| 25 | +
|
| 26 | +* The API query for weather data can be describe as follow. |
| 27 | +
|
| 28 | + > <https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/LOCATION?unitGroup=metric&key=API_ACCESS_KEY&contentType=json> |
| 29 | +
|
| 30 | + Where, **LOCATION** can be City, State |
| 31 | +
|
| 32 | + and **API_ACCESS_KEY** can be obtained from registering with an API provider. |
| 33 | +
|
| 34 | +* With the return JSON object representing the weather data, you wrap the individual data objects in a grid to make it user-friendly. |
| 35 | +
|
| 36 | +<br /> |
| 37 | +
|
| 38 | +# Getting Real Data from A Paid API Provider |
| 39 | +
|
| 40 | +Replace freecodecamp proxy URI (file: public/scripts.js) for weather data provider via a paid subscription. In this project, you can use the API service from the [VisualCrossing.com](https://www.visualcrossing.com). The good news is if you registered to use the free tier, a good fit for demonstration purposes, there is no charge as long as you stay within the allowed call limit. Read [here](https://www.visualcrossing.com/resources/blog/five-easy-weather-api-calls-to-get-the-weather-data-you-need/) to learn ways to make weather API calls. |
| 41 | +
|
| 42 | +<br /> |
| 43 | +
|
| 44 | +# Steps to Integrate A New API Provider |
| 45 | +
|
| 46 | +1. Register with a weather reporting service. Consult with their API documentation on how to call their APIs. |
| 47 | +2. Obtain the API access key from the same provider above. |
| 48 | +3. Build an API https query and manually test it out. |
| 49 | +4. Write an async function to incorporate the API call and retrieve the weather data. |
| 50 | +
|
| 51 | +
|
| 52 | +> On the server, the `API_KEY` is hidden in the `.env` file. By design, environment variables are not accessible to the client. For the client to directly query weather data, the API_KEY has to be available on the browser. This will break the security of our application! |
| 53 | +
|
| 54 | +**Options**: |
| 55 | +
|
| 56 | +* Make the API calls on the server-side and then send response containing the weather data to the client; |
| 57 | +* or, require the client to use their own API keys; |
| 58 | +* or, using the serverless option with locked down to a service. *This bad option multiplies your cost as your user base grows*. |
| 59 | +
|
| 60 | +**Decision:** |
| 61 | +
|
| 62 | +You would want to live free and die harder, let's make the client calls the server which in turn requests weather data and sends responses back. The client then can parse the response into a grid for display. This means the server will need to provide GET requests! |
| 63 | +
|
| 64 | +--- |
| 65 | +
|
| 66 | +**Options**: |
| 67 | +
|
| 68 | +You need to implement a middleware to retrieve weather data and serve it to the client using either |
| 69 | +- `EJS` embedded templates |
| 70 | +- or `ReactJS`. |
| 71 | +
|
| 72 | +Obviously, many developers have accountered this issue. Lucky for us, `EJS`, `ReactJS` typically solves this problem. EJS is an easier choice having less of a learning curve. |
| 73 | +
|
| 74 | +**Decision:** |
| 75 | +Let's introduce the Embedded Javascript ([EJS](https://www.npmjs.com/package/ejs)) middleware template rendering weather data upon client requestt. |
| 76 | +
|
| 77 | +e.g. |
| 78 | +```c |
| 79 | + app.post("/geoLngLatWeather", (req, res) => {...}) |
| 80 | +
|
| 81 | + app.get("/geoLngLatWeather", (req, res) => {...}) |
| 82 | +
|
| 83 | + app.get("/physicalAddrWeather", (req, res, next) => {...}) |
| 84 | +``` |
| 85 | +<br /> |
| 86 | + |
| 87 | +<strong><span style="color:gray; font-size:18px"> Staying within the Free Tier Limit</span> </strong> |
| 88 | + |
| 89 | +A typical free daily allowance cycle consists of up to 1000 free API queries. In this case, the user has to wait until the beginning of the next free tier cycle. To avoid being charged, this source code has a *a server-side accounting logic* to pause querying the weathercrossing API once its periodic allowance has been reached. Unpause is automatic once the clock rolls to the beginning of a new daily free tier cycle. |
| 90 | + |
| 91 | +>*Potential development needed to notify the server admin if demands grew and if there are ways to monetize it.* |
| 92 | +
|
| 93 | +<strong><span style="color:gray; font-size:18px">Caching the Weather Data</span></strong> |
| 94 | + |
| 95 | +Weather forecast data from `visualcrossing.com` typically contains information spanning 15 days, each day contains 24 hourly objects. Additional queries within the same period would usually result in the same data. We need to cache this information so as not to incur needless and sometimes costly API calls. |
| 96 | + |
| 97 | +We could elect to use the DOM Storage API, also called the [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API) methods: `Window.sessionStorage` where data persists as long as the session is running, or `Window.localStorage` which has no expiration. Note: when the last private tab is closed, data stored in the localStorage object of a site opened in a private tab or incognito mode is cleared. |
| 98 | + |
| 99 | +<br /> |
| 100 | + |
| 101 | +<span style="color:green">**IMPORTANT:**</span> Web Storage API needs to be implemented on the browser side (client with the DOM). In this case, we make sure to place the javascript script at the bottom of the HTML body section. |
| 102 | + |
| 103 | +>Another option is to use no-sql storage such as `redis`. A topic not in the scope of this exercise. |
| 104 | +
|
| 105 | +A sample of Web Storage API test: |
| 106 | + |
| 107 | +```c |
| 108 | +/* client-side script */ |
| 109 | + <script type=javascript> |
| 110 | + function storageAvailable(type) { |
| 111 | + let storage; |
| 112 | + try { |
| 113 | + storage = window[type]; |
| 114 | + const x = '__storage_test__'; |
| 115 | + storage.setItem(x, x); |
| 116 | + storage.removeItem(x); |
| 117 | + return true; |
| 118 | + } |
| 119 | + catch (e) { |
| 120 | + return e instanceof DOMException && ( |
| 121 | + // everything except Firefox |
| 122 | + e.code === 22 || |
| 123 | + // Firefox |
| 124 | + e.code === 1014 || |
| 125 | + // test name field too, because code might not be present |
| 126 | + // everything except Firefox |
| 127 | + e.name === 'QuotaExceededError' || |
| 128 | + // Firefox |
| 129 | + e.name === 'NS_ERROR_DOM_QUOTA_REACHED') && |
| 130 | + // acknowledge QuotaExceededError only if there's something already stored |
| 131 | + (storage && storage.length !== 0); |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + if (storageAvailable(methodString)) { |
| 136 | + // Yippee! We can use localStorage awesomeness |
| 137 | + // Save data to sessionStorage |
| 138 | + sessionStorage.setItem("lw_name", "marcus"); |
| 139 | + |
| 140 | + // Get saved data from sessionStorage |
| 141 | + let data = sessionStorage.getItem("lw_name"); |
| 142 | + |
| 143 | + alert(methodString +' says my name is ' + data); |
| 144 | + // Remove saved data from sessionStorage |
| 145 | + sessionStorage.removeItem("lw_name"); |
| 146 | + } |
| 147 | + else { |
| 148 | + // Too bad, no localStorage for us |
| 149 | + alert(methodString + ' says Web Storage API is disabled.') |
| 150 | + } |
| 151 | + </script> |
| 152 | +``` |
| 153 | +
|
| 154 | +
|
| 155 | +## Next Phases |
| 156 | +
|
| 157 | +The followings are potential developments in subsequent iterations of this weather report project: |
| 158 | +
|
| 159 | +* Integrate the server with auth0 and/or passport authentication |
| 160 | +
|
| 161 | +* Registered members have access to a full-blown dashboard with maritime alerts, historical weather data, extended forecasts, etc. |
| 162 | +
|
| 163 | +* Build and make mobile apps available to subscribed members. |
0 commit comments