Prerequisites:
- Ruby
- sqlite3
- Rails Installer
Install the app
rails new air_quality_app
serve the application
cd air_quality_app
bin/rails server
visit your browser with this link
Create a controller
To create FeedsController and its index action, run the controller generator in the terminal.
bin/rails generate controller Feeds index
install the Waqi Ruby SDK
Add this line to your application's Gemfile:
gem 'waqi-ruby'
And then execute:
bundle
Or install it yourself as:
gem install waqi-ruby
You need a token to conveniently access the Air QUality Open Data Platform API. Visit , you can check it out for more details.
install tailwindcss
bundle add tailwindcss-rails
rails tailwindcss:install
The above commands will create files and folders necessary to kickstart designing with tailwind: app/views/layouts/application.html.erb, app/assets/builds/, config/tailwind.config.js, app/assets/stylesheets/application.tailwind.css, Procfile.dev, bin/dev.
Additionally, you can install
Let's interact with api by submitting a city name and get a result.
First, modify the form tag to conform to rails code standard!
...
<%= form_with url: "/feeds/index", method: :get do |form| %>
<%= form.label :city, "Search", class:"mb-2 text-sm font-medium text-gray-900 sr-only dark:text-gray-300" %>
<div class="relative">
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
<svg class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
</div>
<%= form.text_field :city, placeholder: "enter name of city...", autofocus: true, required: true, value: @city,
class:"block p-4 pl-10 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
%>
<%= form.submit "Search", class: "text-white absolute right-2.5 bottom-2.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" %>
</div>
<% end %>
<div class="text-center">
<p><%= @feeds %></p>
</div>
You should notice the form_url tag has the url '/feeds/index'. After submitting the form the request goes to the index action on feeds_controller.
Modify the controller to accept the request and connect to Air Quality API to get a result:
# app/controllers/feeds_controller.rb
require 'waqi'
class FeedsController < ApplicationController
before_action :set_client
def index
@city = request.query_parameters['city']
if !@city.blank?
@feeds = @waqi_client.get_city_feed(@city)
end
end
private
def set_client
@waqi_client = Waqi::Client::new(api_key: Rails.application.credentials[:air_quality_data_token])
end
end
The above code does the following:
- Loads the
You can beautify your result and integrate a Google line chart to display daily forecast for Ozone and Particulate matter level:
Add a script tag to
app/views/layouts/application.html.erbfile
CODE...
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load('current', {'packages':['line']});
var forecast = document.getElementById('line_top_x').getAttribute('data-forecast')
if(forecast.length) {
google.charts.setOnLoadCallback(drawChart);
}
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Date');
data.addColumn('number', 'Ozone (o3)');
data.addColumn('number', 'Particulate Matter 10 (pm10)');
data.addColumn('number', 'Particulate Matter 25 (pm25)');
data.addRows(JSON.parse(forecast));
var options = {
width: 900,
height: 500,
axes: {
x: {
0: {side: 'top'}
}
}
};
var chart = new google.charts.Line(document.getElementById('line_top_x'));
chart.draw(data, google.charts.Line.convertOptions(options));
}
</script>
</body>
</html>
The script loads the chart library and the forecast data is inserted by getting the data attribute on the page
app/views/feeds/index.html.erb:
CODEvar forecast = document.getElementById('line_top_x').getAttribute('data-forecast')
Insert this code snippet below into the city_feed views page:
CODE<% if @isError %>
<div class="m-4 p-2 text-red-500">
<span>Error: <%= @response["data"] %></span>
</div>
<% else %>
<div class="text-center grid grid-flow-row-dense grid-cols-4 grid-rows-4">
<p><span class="font-black">City: </span> <%= @city_info["name"] %></p>
<p><span class="font-black">Latitude: </span><%= @city_info["geo"][0] %></p>
<p><span class="font-black">Longitude: </span><%= @city_info["geo"][1] %></p>
<% if !@iaqi.blank? %>
<% @iaqi.each do |iaqi| %>
<% extra = determineLabel(iaqi["name"]).to_h %>
<p><span class="font-black"><%= extra["label"] %>: </span><%= iaqi["value"] %> <%= extra["unit"] %></p>
<% end %>
<% end %>
</div>
<div class="text-center">
<h3> Daily forecast for Ozone, Particulate Matter 10 and 25</h3>
<div id="line_top_x" style="margin-right: auto;margin-left: auto;display: table;" data-forecast="<%= @daily_forecast %>"></div>
</div>
<% end %>
The code above handles the error if there's any and also display the city, latitude, longitude, and other air quality information for a particular station.
This line of code
<% extra = determineLabel(iaqi["name"]).to_h %>is used to format the data with readable texts and provide more information to the user. You can update theapp/helpers/application_helper.rbwith the code below. This is a helper class.
CODEmodule ApplicationHelper
def determineLabel(name)
case name
when "co"
{ "label" => "Carbon monoxide", "unit" => "ppm" }
when "no2"
{ "label" => "Nitrogen dioxide", "unit" => "ppb" }
when "pm10"
{ "label" => "PM10", "unit" => "µg/m³" }
when "pm25"
{ "label" => "PM25", "unit" => "µg/m³" }
when "o3"
{ "label" => "Ozone", "unit" => "ppb" }
when "w"
{ "label" => "Wind Speed", "unit" => "m/s" }
when "t"
{ "label" => "Temperature", "unit" => "°C" }
when "p"
{ "label" => "Air pressure", "unit" => "mb" }
when "so2"
{ "label" => "Sulfur dioxide", "unit" => "ppb" }
when "h"
{ "label" => "Humidity", "unit" => "%"}
when "dew"
{ "label" => "Dew", "unit" => "°C"}
end
end
end
Update the index method on FeedsController file:
CODEdef index
@city = request.query_parameters['city']
if !@city.blank?
response = @waqi_client.get_city_feed(@city)
if response["status"] == "ok"
@response = response["data"]
@city_info = response["data"]["city"]
@iaqi = response["data"]["iaqi"].map do |key, value|
{ "name" => key.to_s, "value" => value["v"] }
end
@forecast = response["data"]["forecast"]["daily"]
@daily_forecast = format_forecast_data(@forecast)
elsif response["status"] == "error"
@isError = true
@response = response
end
end
end
private
...
def format_forecast_data(air_quality_data)
# Extract days from "o3", "pm10", and "pm25" arrays
o3_days = air_quality_data["o3"].map { |data| data["day"] } || []
pm10_days = air_quality_data["pm10"].map { |data| data["day"] } || []
pm25_days = air_quality_data["pm25"].map { |data| data["day"] } || []
# Find intersection of the three arrays
identical_days = o3_days & pm10_days & pm25_days
formatted_parameter_data = []
# Extract required values and push into new array
identical_days.each do |date|
o3_index = air_quality_data["o3"].find_index { |entry| entry["day"] == date }
pm10_index = air_quality_data["pm10"].find_index { |entry| entry["day"] == date }
pm25_index = air_quality_data["pm25"].find_index { |entry| entry["day"] == date }
formatted_parameter_data << [date, air_quality_data["o3"][o3_index]["avg"], air_quality_data["pm10"][pm10_index]["avg"], air_quality_data["pm25"][pm25_index]["avg"]]
end
formatted_parameter_data
end
such as Lat/Lng based Geolocalized feed, IP based Geolocalized feed, etc.
The complete source code can be found on github
↗ Original-Artikel auf dev.to lesenVollständiger Original-BerichtAusführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
SOCIAL SHARE CARD GENERATOR