Android Wear Notifications

Wearable devices are everywhere. People use them to get notifications from Facebook and Twitter, response to the incoming calls quickly and make valuable notes on the fly. It’s always a good idea to extend your app to be able to work with rapidly growing ecosystem of Android Wear devices. Moreover it’s very simple to implement thanks to the Android Support library.

Getting started with Android Wearable Notifications

  1. Import the necessary package and put this line to your module build.gradle file:
    compile "com.android.support:support-v4:20.0.+"
  2. Import the package declaration to your code:
    import android.support.v4.app.NotificationManagerCompat;
  3. Create a notification with NotificationCompat.Builder using the v4 support library

UV Index Wearable Notifications

Let’s look how we’ve implemented UV Index Alarm notifications for the UVIMate application that will also be sent to any Android wearable device.

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
// Show UV Index Alarm Wearable Notification
private void showUVAlarm(UVMateUVData uvdata) {
// get UV Index alarm lever for a current skin type
double level = mUVMateHelper.getUVAlarmLevel(uvdata);
// get last UV Index alarm data from settings
Date lastUVAlarmDate = new Date(mUVMateHelper.getLastUVAlarmDate());
Date currDate = new Date();
// calculate current UV Index with clouds and reflection factors
double currUV = uvdata.uviWithCloudsFactor(mUVMateHelper.useCloudsFactor(), mUVMateHelper.getArea());
// if the current sun conditions are dangerous for the skin type
if (level != -1) {
// check if the current UV Index is higher than the alarm level and
// we did not have any notification for the last 12 hours
if (currUV >= level &&
mUVMateHelper.DateDiffHours(lastUVAlarmDate, currDate) > 12) {
// build UV Index notification from string resource and cast current UV Index to string
String notification = mUVMateHelper.getNotification(uvdata.uviWithCloudsFactor(mUVMateHelper.useCloudsFactor(), mUVMateHelper.getArea()));
String strUvIndex = UVMateHelper.convertToFormat1(uvdata.uviWithCloudsFactor(mUVMateHelper.useCloudsFactor(), mUVMateHelper.getArea()));
// save current notification DateTime
mUVMateHelper.saveLastNotificationDate(currDate.getTime());
// Create notificationBuilder instance using NotificationCompat.Builder class
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(mContext)
// set title
.setContentTitle(mContext.getString(R.string.notification_index).concat(strUvIndex))
// set text
.setContentText(mContext.getString(R.string.notification_big_title))
// set small icon
.setSmallIcon(R.drawable.ic_level_notification)
// set large icon
.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launch))
// set extended text content using the "big text" style
.setStyle(new NotificationCompat.BigTextStyle().
// set extended notification content
bigText(notification).
// set the same title
setBigContentTitle(mContext.getString(R.string.notification_index).concat(strUvIndex)).
// and the same summary
setSummaryText(mContext.getString(R.string.notification_big_title)))
// specify wearable extensions
.extend(new NotificationCompat.WearableExtender().
// set notification background for wearable device
setBackground(
BitmapFactory.decodeResource(mContext.getResources(),
R.drawable.wear_back)))
.setPriority(NotificationCompat.PRIORITY_MAX);
// create notification manager
NotificationManagerCompat mNotificationManager = NotificationManagerCompat.from(mContext);
// and fire notification
mNotificationManager.notify(NOTIFICATION_UV_ALARM, notificationBuilder.build());
}
}
}

That’s all folks!

Share Comments

NodeJS Caching

What is cache?

Every caching infrastructure could be described as a set of key-value pairs allocated in memory with three basic operations:

  1. Add key-value pair
  2. Get value by key
  3. Delete value by key

The facts that all the key-value pairs reside in memory tremendously increases fetching operations compared to a traditional disk based database.

It’s always a good idea to cache everything that isn’t likely to be changed for your client.

How we use cache for UV Index API?

To calculate an actual UV Index for a current location we always need to have in place the following set of data:

  1. Ozon level and weather data, fetched from Forecast.io API
  2. Elevation level, fetched from Google API
  3. Protection time, based on range of UV Index calculations for the time period

Summarize, all these values are very good candidates to be cached and fetched directly from the memory without additional requests and calculations.

What is TTL?

TTL (time-to-live) is lifetime during witch the key-value pair will be available in a memory for fetching operation. In our case we could set the following TTLs for our cached values:

  1. Ozon level and weather data, 12min
  2. Elevation, 24h
  3. Protection time, 12h

Caching implementation for UV Index API

Let’s look on elevation and protection time caching implementation. Firstly we need to identify what value to use as a key in a pair. In our case a request could be uniquely identified by two location’s components - lat and lng. To make the possibility to hit the key more frequently for the clients who are not changing their location often we will round lat, lng to the second decimal place (that is worth up to 1.1 km and will be enough for precise weather and UV Index forecast):

1
2
var latRnd = (Math.round(lat * 100)/100).toFixed(2)
var lngRnd = (Math.round(lng * 100)/100).toFixed(2)

Now we could use these rounded location values to get an elevation and put it into the cache. The algorithm is trivial. Firstly we are trying to get the elevation value from the cache and return it immediately if it’s been already cached. Otherwise we need to call Google API, parse JSON response to get elevation value and put it into the cache with properly formed key value. Let’s look at the code below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var NodeCache = require( "node-cache" );
// set TTL in a minutes
var googleCache = new NodeCache( { stdTTL: 60 * 60 * 24 } );
//Call Google API to get the elevation for a given latitude + longitude
function getElevation(lat, lng, cb){
// check if the cache already contains the cached value for the key
// lat+lng+'elevation'
googleCache.get(lat+lng+'elevation', function(err, value){
if (err) cb(null, 0);
if (value) cb(null, value);
// build HTTP request for Google API call uing rounded location
var url = "http://maps.googleapis.com/maps/api/elevation/json?locations=" + lat + "," + lng + "&sensor=true";
request(url, function (err, response, body) {
var res = JSON.parse(body);
if (err) return cb(null, 0);
// put the value into the cache
googleCache.set(lat+lng+'elevation', res.results[0].elevation, function(err, success){
// and return the fetched value
cb(null, res.results[0].elevation);
});
});
});
}

So the next time when the user will call the UV Index API from the same location we could use cached value for elevation immediately without any additional requests.

Share Comments

Twitter Broadcasting with NodeJS

Having a lot of user’s requests with UV Index data from all around the world you could consider to create an awesome Live UV Index broadcasting on Twitter. Let’s look how to implement that idea using NodeJS.

Getting Started

First you’ll need to install an asynchronous client library for Twitter from npm:

npm install twitter

Then initialize the client with your app’s twitter keys:

1
2
3
4
5
6
7
8
var Twitter = require('twitter');
var twclient = new Twitter({
consumer_key: cfg.twitter.consumer_key,
consumer_secret: cfg.twitter.consumer_secret,
access_token_key: cfg.twitter.access_token_key,
access_token_secret: cfg.twitter.access_token_secret
});

Broadcast your personal Tweets

Now we have the Twitter client and ready to tweet something interesting for our auditory. For example let’s tweet a message with a current UV Index, max UV Index for the current location and a quick customized sun protection advice depending on the UV Index level.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// API endpoint to get UV Index. Broadcast client's UV Index on Twitter.
router.get('/getUVI/:lat/:lng', customMw.CheckApiKey, function(req, res) {
// 1. get current UV index by location
uvindex.GetUVI(req.params.lat, req.params.lng, function(err, uvidata){
if (err) { return res.json({ error: err }); }
// 2. if there is no error build a tweet message
twclient.post('statuses/update', {
status: '#UVIndex in #'+uvindex.TrimStr(uvidata.geoLocation.geocoding.join(', ')) +' is '+
Number(uvidata.uviData.uvi.toFixed(1))+'. Max #UV is '+
Number(uvidata.uviData.uviMax.toFixed(1))+'. '+
GetNotification(uvidata.uviData.uvi) // 3. Get twitter notification based on a current UV Index level
}, function(err, tweet, response) {
if (err)
logger.debug(err);
});
// 4. return response immediately to the client
res.json({ result : uvidata });
});
});

Customize your Tweeter message

And finally let’s create a helper method to customize a twitter message based on the current UV Index level and any other conditions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Get twitter notification based on a current UV Index level
function GetNotification(uvindex){
var notification = '';
if (uvindex >= 0 && uvindex < 3)
notification = 'Enjoy the #sun. No protection required';
if (uvindex >= 3 && uvindex < 6)
notification = 'Seek #shade and #slop on a hat';
if (uvindex >= 6 && uvindex < 8)
notification = '#Sunscreen protection required';
if (uvindex >= 8)
notification = '#Sunscreen, #sunglasses and a hat a must! Seek shade and stay out of the #sun';
return notification;
}

That’s all! Enjoy the Twitter Broadcasting with NodeJS.

Share Comments

Skin Types and UV Index

Fitzpatrick Skin Types Scale

Fitzpatrick phtotype scale is a most commonly used classification of a person’s skin type developed in 1975 by Harvard Medical School dermatologist Thomas Fitzpatrick, MD, PhD. The sacale classifies skin types by their response to the sun exposure and deegree of burning and taining.

Fitzpatrick Skin Types Scale

Skin Type Characteristics Origin
Type 1 Highly sensitive, always burns,
never tans
Scandinavians,
Celtic
Type 2 Very sun sensitive, burns easily,
tans minimally
Caucasians
Type 3 Sun sensitive skin, sometimes burns,
slowly tans to light brown
Darker Caucasians, Central Europe
Type 4 Minimally sun sensitive, burns minimally,
always tans to moderate brown
Caucasians, some Hispanics,
South Mediteranian, South Americans
Type 5 Sun insensitive skin, rarely burns, tans well Middle Eastern, Asia,
some Hispanics, South Africans
Type 6 Sun insensitive, never burns, deeply pigmented African, Afro-American

Skin types and UV Index

For UVIMate UV Index application we use the following table to calculate your safe sun exposure time without sunscreen based on a Skin Type:

Skin Type Max Time in Sun (min)
Type 1 166 / UVI
Type 2 200 / UVI
Type 3 266 / UVI
Type 4 333 / UVI
Type 5 533 / UVI
Type 6 1000 / UVI
Share Comments

DAU/MAU ratio for a mobile application

Stickeness Level, Sticky factor, Depth of User’s Engagement or Retention Snapshot. That’s just a bunch of definitions for one of the most important metrics of your mobile application - DAU/MAU ratio.

  • DAU is a count of daily active users of your app
  • MAU is a count of monthly active users of your app

DAU/MAU ratio of 50% basically means that the average user of your application uses it 15 days from 30. DAU/MAU ratio of < 1% means that all new coming users try your app just once and do not return. It’s no so bad if they pay you money for that but for the most apps it’s a pretty bad sign.

For mobile applications DAU/MAU ratio of 20% is considered to be as a good and decent level.

User’s Request Data Model

To calculate MAU/DAU level we firstly need to decide how to identify the Active user. For UVIMate an user who has done just one request of UV Index data during the day is considered as Active. To track user’s request we are using the following data model.

1
2
3
4
5
6
7
8
9
10
11
12
var requestSchema = mongoose.Schema({
lat: String,
lng: String,
clientData:{
deviceId: String, // unique deviceId
configs: [{
property: String,
value: String
}]
},
date: { type: Date },
});

Get Active Users for a Period

To get active users for any period of time let’s implement the reusable getUsersForPeriod function using the MongoDB agregate syntax:

1
2
3
4
5
6
7
8
9
10
exports.getUsersForPeriod = function(dateFrom, dateTo, cb){
Request.aggregate([
{ $match: {'date': {$gte: dateFrom, $lt: dateTo }}}, // fetch all requests from/to
{ $group: { _id: '$clientData.deviceId', total: { $sum: 1 }}}, // group all requests by deviceId and sum the count of requests for a group
{ $sort: { total: -1 }}
], function (err, result) {
if (err) cb(err, null);
cb(null, result);
});
};

Get MAU/DAU Index

Now we ae ready to calculate DAU/MAO ratio for your mobile app.

1
2
3
4
5
6
7
8
9
10
11
12
var today = new Date();
var yesterday = new Date();
var monthAgo = new Date();
monthAgo.setDate(monthAgo.getDate()-30);
yesterday.setDate(yesterday.getDate()-1);
User.getUsersForPeriod(yesterday, today, function(err, dauUsers){
if (err) { return res.json({ error: err }); }
User.getUsersForPeriod(monthAgo, today, function(err, mauUsers){
if (err) { res.json({ error: err }); }
res.json({ daumau: dauUsers.length/mauUsers.length * 100 });
});
});

On the 16.12.2016 the DAU/MAU ratio for the UVIMate application is close to 35%.

Share Comments

UV Index API

UV Index API is a simple and quick Web JSON API that gives you all UV Index data for the current location. It’s free, ready to use and built based on NodeJS.

UV Index Weather Providers

UV Index API is a good alternative for other weather APIs like OpenWeatherMap that includes UV Index data to the expensive plan (470 USD/month). Other UV Index API’s like Australian ARPASNA and USA EPA provide data just for a list of main state cities.

UV Index API Data

UV Index API works for any location on the globe and provides the following UV Index and weather data:

  • Location geocoding and altitude
  • Clear sky UV Index, max UV Index, max UV Index day time (UTC)
  • UV Index forecast for the next 1, 3, 5, 15, 30, 60 days
  • Sunrize and sunset time in the location
  • Burn time (in munites) for different skin types for the current UV Index
  • Recomended protection day time, from/to (UTC)
  • Weather information for the location including ozone level and cloud cover (using Forecast.io data)

UV Index API Request/Response

To get access to the UV Index API you’ll need to login with your Facebook account and get a developer API Key. Then you could call UV Index API to get UV Index forecast using the following request syntax:

1
2
# API Key: e5605ae0-3e4f-ffff-9335-fbcd5146dc7c
curl --header "x-access-token: e5605ae0-3e4f-ffff-aaaa-fbcd5146dc7c" https://uvimate.herokuapp.com/api/getUVI/-31.9604/115.8721

The response will contain all UV Index data in a JSON format:

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
{
"result": {
"location": "East Perth",
"altitude": 4.269564151763916,
"date": "2016-12-14T22:49:27.586Z",
"geoLocation": {
"geocoding": [
"East Perth",
"WA",
"AU"
],
"lat": "-31.9604",
"lng": "115.8721"
},
"uviData": {
"uvi": 0.9381324107417164,
"uviMax": 13.97,
"uviMaxTime": "2016-12-15T04:13:02.242Z"
},
"sunTime": {
"sunrise": "2016-12-14T21:06:19.559Z",
"sunset": "2016-12-15T11:19:44.925Z"
},
"burnTime": {
"celtic": 178,
"pale": 213,
"caucasian": 284,
"mediterranean": 355,
"southAfrican": 569,
"negro": 1066
},
"protectionTime": {
"protectionFrom": "2016-12-15T00:14:40.289Z",
"protectionTo": "2016-12-15T08:29:40.289Z"
},
"sunAdvice": {
"advice": "A typical T‐shirt has an SPF rating lower than 15, so use other types of protection as well"
},
"weather": {
"time": 1481755479,
"summary": "Clear",
"icon": "clear-day",
"precipIntensity": 0,
"precipProbability": 0,
"temperature": 21.08,
"apparentTemperature": 21.08,
"dewPoint": 13.57,
"humidity": 0.62,
"windSpeed": 3.68,
"windBearing": 33,
"visibility": 9.99,
"cloudCover": 0.03,
"pressure": 1018.9,
"ozone": 264.74
},
"uviForecast": [
{
"date": "2016-12-15T00:00:00.000Z",
"uviMax": 13.97
},
{
"date": "2016-12-17T00:00:00.000Z",
"uviMax": 13.97
},
{
"date": "2016-12-19T00:00:00.000Z",
"uviMax": 13.97
},
{
"date": "2016-12-29T00:00:00.000Z",
"uviMax": 13.97
},
{
"date": "2017-01-13T00:00:00.000Z",
"uviMax": 13.75
},
{
"date": "2017-02-12T00:00:00.000Z",
"uviMax": 12.47
}
],
}
}

Share Comments

How to Track Mobile Apps Installations

As a mobile developer you’ll probably want to track your app installations on a backend side and have all the installation data in your MongoDB database. The reason behind that could be a desire to provide a Trial period functionality for your users, sending app’s events statistic from the app’s instance, or simple curiosity how many new installations you have for the particular period of time. Let’s look how we implemented the app’s instalaltions tracking for Android UV Index and Weather Widget.

App’s installations tracking data model

To track all the installations data we firstly need to create a suitable data model using NodeJS and Mongoose.

1
2
3
4
5
6
7
8
9
10
11
12
var installationSchema = mongoose.Schema({
//uniqueId of the apps device
deviceId: String,
//uniqueId of user account
accountId: String,
//any valuable user's system information, like OS, OS version and etc.
system: String,
//one of the most imnportant fields,
// using it we could use to calculate an app's retention level and identify cohorts of users
installationDate: { type: Date },
premium: Boolean
});

API for app’s installations tracking

On the API level we basically need to add just one method to track all app’s installations:

  1. Firtsly, we need to check that we are going to process a requst from a valid and active client app. For that we could use the CheckApiKey middleware.
  2. For the new client the method will create a new record in the DB and send operation’s status back to the app.
  3. For an existed user this method needs to find the client’s installation record in the MongoDB, performs some business login against the installation’s data or simple returns it to the app for furhter client’s validation.
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
var express = require('express')
, router = express.Router()
, Installation = require('../models/installation');
// check the client ApiKey first and proceed if ApiKey is valid
router.get('/checkInstallation/:system/:deviceId/:accountId', customMw.CheckApiKey, function(req, res) {
// trying to find client's record in the DB by deviceId and accountId
Installation.findByDeviceIdAndAccountId(req.params.deviceId, req.params.accountId, function(err, installation){
// if can't connect to DB, return error and handle it on a client side
if (err) { return res.json({ error: err }); }
// if there is no installation record, new client
if (installation == null){
var newInstallation = {
deviceId: req.params.deviceId,
accountId: req.params.accountId,
system: req.params.system
}
// create installation record in DB
Installation.create(newInstallation, function(err, installation){
if (err) { return res.json({ error: err }); }
// return installation object as a result, could be processed on a client side
return res.json({result: installation});
});
}
else {
// return the existed installation record on a client side
return res.json({result: installation});
}
});
});

So now we could get installations data from the backend and ready to implement some app’s logic.

Process installation data on the app’s side

Let’s look how UVIMate checks that apps trial period has finished and updates the UI accordingly. Please follow the code comments within the code snippet for clarrification:

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
// Use AsyncTask to get InstallationData object from API
class UVMateCheckInstallationTask extends AsyncTask<Void, Void, UVMateInstallationData> {
protected UVMateInstallationData doInBackground(Void... params) {
UVMateHelper helper = new UVMateHelper(mContext);
// Use DataFetcher class to call API and read JSON response from API
UVMateDataFetcher fetcher = new UVMateDataFetcher(mContext);
// return InstallationData to the client
return fetcher.GetInstallationData(helper);
}
// process fetched installationData...
protected void onPostExecute(UVMateInstallationData installData) {
if (installData != null){
// if we've got API response
mInstallData = installData;
// check if the Trial period has expired?
mIsExpired = UVMateHelper.isTrialExpired(installData.installationDate);
// if a premium client
if (installData.premium){
// just render the app logic and return
setSettingsVisability(true);
return;
}
// if it's not a premium client
if (mIsExpired){
// if Trial is expired - disable some UI
setSettingsVisability(false);
// Show Get Pro dialog box
showGetPro();
return;
}
// Show a customise user's notification based on the left Trial's days count
if (!mIsExpired && helper.leftTrialDays(installData.installationDate) < DAYS_LEFT_NOTIFICATION)
Toast.makeText(mContext, mContext.getString(R.string.app_get_pro), Toast.LENGTH_LONG).show();
// Trial is not expired - just go straight to work
setSettingsVisability(true);
return;
}
else
{
// can't get installation data - do something
mIsExpired = false;
Toast.makeText(mContext, mContext.getString(R.string.app_no_internet), Toast.LENGTH_LONG).show();
}
}
}

This code is pretty straight foward and will complittelly depend from your app’s business logic.
In the next article we will look how to use this installation data to calculate your Android Application MAU/DAU index.

Share Comments

What is the UV Index?

Let’s try to remember when have you had a sunburn for the last time. Was it just a year or a couple of mounths ago or yesterday? And did you know that sunburn causes 95% of melanomas, that is considered as the most deadly form of skin cancer… A little bit scarry, yeah?

Sun, sunburn and skin cancer

The unpleasant truth that our higly belowed sun is not just an our warm friend, but sometimes it’s a killer. Just look at this unpleasant statistic from Australian Cancer Council:

  • More than 1700 Australians die from skin cancer each year, and two out of three Australians will get a skin cancer before the age of 70.
  • Cancer is a leading cause of death in Australia – more than 44,000 people died from cancer in 2013. Cancer accounted for about 3 in 10 deaths in Australia.

To mesure the harmful ultraviolet radiation from the sun the UV Index Scale was developed by Canadian scientists in 1992.

UV Index scale

The UV Index scale is a simple way to predict the amount of skin damaging by overexposure to uv radiation from the sun.
UV Index Scale
A UV Index with value more than 5 is considered as dangerous for a light-skinned individual.


The best way to protect yourself from UV radiation, sunburn and mitigate a risk of skin cancer is develop a bunch of simple, sun-wise habits based on your local UV Index. Current UV Index is usually provided by local weather forecasts and always available by UVIMate mobile application.

Share Comments

UV Index on a Cloudy Day

Clouds are different. So is their effect to the UV Index. On average, clouds do reduce UV radiation, but it still far away from stops the skin damage.

A lot of people think they can’t get a sunburn on a cloudy day. Unfortunately, it doesn’t work this way.

Clouds factor is affected by many things, for example cloud’s thickness, cloud’s position in the atmosphere and clouds configuration on the sky. Patchy clouds could even intensify radiation from the sun by the effect of a reflection factor. In that case the cloud works just like a mirror and focus all the sun rays to the ground.

For UVIMate mobile application we have implemented a very simple but effective model to calculate a current UV Index with clouds factor. Basically we need to know a current UV level and local clouds coverage (from 0 to 1) provided by UV Index API, thus a new UV Index could be calculated as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// - Clear skies allow virtually 100% of UV to pass through, cloud cover (CC) < 0.2,
// - scattered clouds transmit 89%, CC > 0.2, < 0.7,
// - broken clouds transmit 73%, CC > 0.7, < 0.9,
// - and overcast skies transmit 31%, CC > 0.9.
// Resource: https://www.epa.gov/sunsafety/calculating-uv-index-0
public double calculateCloudsFactor()
{
if (cloudCover < 0.2)
return uvi;
if (cloudCover >= 0.2 && cloudCover < 0.7)
return uvi*0.89;
if (cloudCover >= 0.7 && cloudCover < 0.9)
return uvi*0.73;
if (cloudCover >= 0.9)
return uvi*0.31;
return uvi;
}
Share Comments

UV Index & Reflection

Different kinds of reflective surfaces produce a different effect on a current ultravialet index (UV Index). Coarse and soft surfaces like grass are basically bounced less UV radiation than hard or smooth surfaces like concret. Less reflective colours could also be used to minimize exposure to UV rays.

UV Index reflective surfaces *

Material Reflected level (%)
Asphat New black roadway 4.1
Old grey roadway 8.9
Concrete Footpath 8.2 – 12.0
Grass Grassland 0.8 – 1.6
Lawn 2.0 – 5.0
Fibreglass Fibreglass boat deck 9.1
Paint White house pain 22.0
Sand Wet beach sand 7.1
Dry beach sand 15.0 – 18.0
Sand Wet beach sand 7.1
Soil Clay/humus 4.0-6.0
Water Open water 3.3
Open ocean 8.0
Sea surf/white foam 25.0-30.0
Snow Old snow 50
New snow 88
Wood Wood boat deck 6.6

UV rays can be reflected off of open water, grass, sand, snow, concrete and painted walls. You can get a sunburn in the shade or when skiing on a cold, winter day.

UV Index Reflection Calculation

UVIMate mobile application is calculating UV Index with reflection factor based on the current area conditions with the following alghoritm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public double getReflectionFactor(String currentArea)
{
if (currentArea.equals("city")))
return 1.10;
if (currentArea.equals("grassy_land")))
return 1.05;
if (currentArea.equals("sandbeach")))
return 1.20;
if (currentArea.equals("open_water")))
return 1.08;
if (currentArea.equals("snow")))
return 1.6;
if (currentArea.equals("not_set")))
return 1.0;
return 1.0;
}
Share Comments