Skip to content

Instantly share code, notes, and snippets.

@rveciana
Last active January 25, 2017 16:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rveciana/d5a398bdb55a6caec3e3931f347e4b70 to your computer and use it in GitHub Desktop.
Save rveciana/d5a398bdb55a6caec3e3931f347e4b70 to your computer and use it in GitHub Desktop.
ramadan times along meridian 30
licence: mit
height: 550

This map shows the times when muslims are fasting during ramadan along the meridian 30.

The times are calculated using the PrayTimes library, I've taken some ideas from this block and adjusted the projection using this other block.

The times are in local time, so some countries are not in the "natural" timezone. Besides, some countries apply the DST and others don't. The information is taken from these sources:

https://en.wikipedia.org/wiki/Daylight_saving_time_in_Egypt

https://en.wikipedia.org/wiki/Time_zone#/media/File:Standard_World_Time_Zones.png

https://en.wikipedia.org/wiki/Daylight_saving_time_by_country#/media/File:DST_Countries_Map.png

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<script src="//d3js.org/d3.geo.projection.v0.min.js"></script>
<script type="text/javascript" src="PrayTimes.js"></script>
<script>
var places = [{"name": "Mkambati", "country": "South Africa", "lat":-31.3, "lon":30, "tz":2},
{"name": "Shurugwi", "country": "Zimbabwe", "lat": -19.677, "lon": 29.97, "tz":2},
{"name": "Chingombe", "country": "Zambia", "lat": -14.42, "lon": 29.98, "tz":2},
{"name": "Kazuramimba", "country": "Tanzania", "lat": -5, "lon": 30.02, "tz":3},
{"name": "Rutana", "country": "Burundi", "lat": -3.92, "lon": 29.99, "tz":2},
{"name": "Tumba", "country": "Rwanda", "lat": -1.69, "lon": 29.96, "tz":2},
{"name": "Chanjojo", "country": "Uganda", "lat": 0.16, "lon": 30.01, "tz":3},
{"name": "Ndakala", "country": "Democratic Republic of the Congo", "lat": 2.88, "lon": 30.06, "tz":2},
{"name": "Koch", "country": "South Sudan", "lat": 8.6, "lon": 29.99, "tz":3},
{"name": "El Obeid", "country": "Sudan", "lat": 13.22, "lon": 30.18, "tz":3},
{"name": "El Mandara", "country": "Egypt", "lat": 31.27, "lon": 30.0, "tz":2, "tz":2},
{"name": "Kütahya", "country": "Turkey", "lat": 39.43, "lon": 29.99, "tz":3},
{"name": "Palanca", "country": "Moldavia", "lat": 46.4, "lon": 30.08, "tz":3},
{"name": "Fastivets", "country": "Ukraine", "lat": 50.06, "lon": 30.03, "tz":3},
{"name": "Smaljani", "country": "Belarus", "lat": 54.59, "lon": 30.05, "tz":3},
{"name": "Sestroretsk", "country": "Russia", "lat": 60.1, "lon": 29.97, "tz":3},
{"name": "Lieksa", "country": "Finland", "lat": 63.31, "lon": 30.02, "tz":3},
{"name": "Vadsø", "country": "Norway", "lat": 70.08, "lon": 29.72, "tz":3}
]
var width = 960,
height = 550;
var projection = d3.geo.satellite()
.distance(1.5)
.scale(787)
.translate([width/2,height/3.5])
.center([-2, 6])
.tilt(30)
.clipAngle(Math.acos(1 / 1.5) * 180 / Math.PI - 1e-6)
.precision(.1);
var canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
var graticule = d3.geo.graticule();
var path = d3.geo.path()
.projection(projection)
.context(context);
var x = width/2,
y = height/3,
// Radii of the start glow.
innerRadius = height/4,
c // Radii of the end glow.
outerRadius = height/1.5,
// Radius of the entire circle.
radius = 200;
d3.json("world-110m.json", function(error, data) {
var countries = topojson.object(data, data.objects.countries);
var i = -1, n=places.length, times;
var meridian = {
type: "LineString",
coordinates: [
[30, places[0].lat], [30, places[n-1].lat]
]
}
draw_map(0, [-1*places[0].lon, -1*places[0].lat, -20])
function draw_map(i, rotation){
projection.rotate(rotation);
context.clearRect(0, 0, width, height);
context.beginPath();
path({type: "Sphere"});
var gradient = context.createRadialGradient(x, y, innerRadius, x, y, outerRadius);
gradient.addColorStop(0, 'rgb(240, 240, 255)');
gradient.addColorStop(1, 'rgb(200, 200, 220)');
context.fillStyle = gradient;
context.fill();
context.strokeStyle = '#aaa';
context.fillStyle = '#ccc';
context.beginPath();
path(graticule());
context.lineWidth = 0.2;
context.strokeStyle = 'rgba(30,30,30, 0.5)';
context.stroke();
context.beginPath();
path(countries);
context.fill();
context.beginPath();
path(countries);
context.stroke();
context.setLineDash([5, 2]);
context.lineWidth = 0.7;
context.strokeStyle = 'rgba(255,30,30, 0.5)';
context.beginPath();
path(meridian);
context.stroke();
context.setLineDash([]);
};
(function transition() {
d3.transition()
.duration(1250)
.each("start", function() {
i = (i + 1) % n;
var latNext = places[i].lat;
r = d3.interpolate(projection.rotate(), [-1*places[i].lon, -1*places[i].lat, -20]);
times = prayTimes.getTimes(new Date(), [places[i].lat, places[i].lon], places[i].tz, 0);
})
.tween("rotate", function() {
return function(t) {
draw_map(i, r(t))
//drawing the circle and labels
context.font="15px Helvetica";
var projected = projection([places[i].lon, places[i].lat]);
context.fillStyle = "rgba(70, 70, 70, "+t+")";
context.beginPath();
context.arc(projected[0], projected[1], 4, 0, 2 * Math.PI, false);
context.fill();
context.fillText(places[i].name, projected[0] + 7, projected[1] - 3);
context.fillText(places[i].country, projected[0] + 7, projected[1] + 13);
context.fillStyle = "rgba(150, 70, 70, "+t+")";
context.fillText("Fajr", projected[0] - 100, projected[1] - 3);
context.fillText(times['fajr'], projected[0] - 45, projected[1] - 3);
context.fillText("Maghrib", projected[0] - 100, projected[1] + 13);
context.fillText(times['maghrib'], projected[0] - 45, projected[1] + 13);
if(i>0){
var projected = projection([places[i-1].lon, places[i-1].lat]);
context.fillStyle = "rgba(70, 70, 70, "+(1-t)+")";
context.beginPath();
context.arc(projected[0], projected[1], 4, 0, 2 * Math.PI, false);
context.fill();
context.fillText(places[i-1].name, projected[0] + 7, projected[1] - 3);
context.fillText(places[i-1].country, projected[0] + 7, projected[1] + 13);
context.fillStyle = "rgba(150, 70, 70, "+(1-t)+")";
context.fillText("Fajr", projected[0] - 100, projected[1] - 3);
context.fillText(times['fajr'], projected[0] - 45, projected[1] - 3);
context.fillText("Maghrib", projected[0] - 100, projected[1] + 13);
context.fillText(times['maghrib'], projected[0] - 45, projected[1] + 13);
}
};
})
.transition()
.each("end", transition);
})();
});
</script>
//--------------------- Copyright Block ----------------------
/*
PrayTimes.js: Prayer Times Calculator (ver 2.3)
Copyright (C) 2007-2011 PrayTimes.org
Developer: Hamid Zarrabi-Zadeh
License: GNU LGPL v3.0
TERMS OF USE:
Permission is granted to use this code, with or
without modification, in any website or application
provided that credit is given to the original work
with a link back to PrayTimes.org.
This program is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY.
PLEASE DO NOT REMOVE THIS COPYRIGHT BLOCK.
*/
//--------------------- Help and Manual ----------------------
/*
User's Manual:
http://praytimes.org/manual
Calculation Formulas:
http://praytimes.org/calculation
//------------------------ User Interface -------------------------
getTimes (date, coordinates [, timeZone [, dst [, timeFormat]]])
setMethod (method) // set calculation method
adjust (parameters) // adjust calculation parameters
tune (offsets) // tune times by given offsets
getMethod () // get calculation method
getSetting () // get current calculation parameters
getOffsets () // get current time offsets
//------------------------- Sample Usage --------------------------
var PT = new PrayTimes('ISNA');
var times = PT.getTimes(new Date(), [43, -80], -5);
document.write('Sunrise = '+ times.sunrise)
*/
//----------------------- PrayTimes Class ------------------------
function PrayTimes(method) {
//------------------------ Constants --------------------------
var
// Time Names
timeNames = {
imsak : 'Imsak',
fajr : 'Fajr',
sunrise : 'Sunrise',
dhuhr : 'Dhuhr',
asr : 'Asr',
sunset : 'Sunset',
maghrib : 'Maghrib',
isha : 'Isha',
midnight : 'Midnight'
},
// Calculation Methods
methods = {
MWL: {
name: 'Muslim World League',
params: { fajr: 18, isha: 17 } },
ISNA: {
name: 'Islamic Society of North America (ISNA)',
params: { fajr: 15, isha: 15 } },
Egypt: {
name: 'Egyptian General Authority of Survey',
params: { fajr: 19.5, isha: 17.5 } },
Makkah: {
name: 'Umm Al-Qura University, Makkah',
params: { fajr: 18.5, isha: '90 min' } }, // fajr was 19 degrees before 1430 hijri
Karachi: {
name: 'University of Islamic Sciences, Karachi',
params: { fajr: 18, isha: 18 } },
Tehran: {
name: 'Institute of Geophysics, University of Tehran',
params: { fajr: 17.7, isha: 14, maghrib: 4.5, midnight: 'Jafari' } }, // isha is not explicitly specified in this method
Jafari: {
name: 'Shia Ithna-Ashari, Leva Institute, Qum',
params: { fajr: 16, isha: 14, maghrib: 4, midnight: 'Jafari' } }
},
// Default Parameters in Calculation Methods
defaultParams = {
maghrib: '0 min', midnight: 'Standard'
},
//----------------------- Parameter Values ----------------------
/*
// Asr Juristic Methods
asrJuristics = [
'Standard', // Shafi`i, Maliki, Ja`fari, Hanbali
'Hanafi' // Hanafi
],
// Midnight Mode
midnightMethods = [
'Standard', // Mid Sunset to Sunrise
'Jafari' // Mid Sunset to Fajr
],
// Adjust Methods for Higher Latitudes
highLatMethods = [
'NightMiddle', // middle of night
'AngleBased', // angle/60th of night
'OneSeventh', // 1/7th of night
'None' // No adjustment
],
// Time Formats
timeFormats = [
'24h', // 24-hour format
'12h', // 12-hour format
'12hNS', // 12-hour format with no suffix
'Float' // floating point number
],
*/
//---------------------- Default Settings --------------------
calcMethod = 'MWL',
// do not change anything here; use adjust method instead
setting = {
imsak : '10 min',
dhuhr : '0 min',
asr : 'Standard',
highLats : 'NightMiddle'
},
timeFormat = '24h',
timeSuffixes = ['am', 'pm'],
invalidTime = '-----',
numIterations = 1,
offset = {},
//----------------------- Local Variables ---------------------
lat, lng, elv, // coordinates
timeZone, jDate; // time variables
//---------------------- Initialization -----------------------
// set methods defaults
var defParams = defaultParams;
for (var i in methods) {
var params = methods[i].params;
for (var j in defParams)
if ((typeof(params[j]) == 'undefined'))
params[j] = defParams[j];
};
// initialize settings
calcMethod = methods[method] ? method : calcMethod;
var params = methods[calcMethod].params;
for (var id in params)
setting[id] = params[id];
// init time offsets
for (var i in timeNames)
offset[i] = 0;
//----------------------- Public Functions ------------------------
return {
// set calculation method
setMethod: function(method) {
if (methods[method]) {
this.adjust(methods[method].params);
calcMethod = method;
}
},
// set calculating parameters
adjust: function(params) {
for (var id in params)
setting[id] = params[id];
},
// set time offsets
tune: function(timeOffsets) {
for (var i in timeOffsets)
offset[i] = timeOffsets[i];
},
// get current calculation method
getMethod: function() { return calcMethod; },
// get current setting
getSetting: function() { return setting; },
// get current time offsets
getOffsets: function() { return offset; },
// get default calc parametrs
getDefaults: function() { return methods; },
// return prayer times for a given date
getTimes: function(date, coords, timezone, dst, format) {
lat = 1* coords[0];
lng = 1* coords[1];
elv = coords[2] ? 1* coords[2] : 0;
timeFormat = format || timeFormat;
if (date.constructor === Date)
date = [date.getFullYear(), date.getMonth()+ 1, date.getDate()];
if (typeof(timezone) == 'undefined' || timezone == 'auto')
timezone = this.getTimeZone(date);
if (typeof(dst) == 'undefined' || dst == 'auto')
dst = this.getDst(date);
timeZone = 1* timezone+ (1* dst ? 1 : 0);
jDate = this.julian(date[0], date[1], date[2])- lng/ (15* 24);
return this.computeTimes();
},
// convert float time to the given format (see timeFormats)
getFormattedTime: function(time, format, suffixes) {
if (isNaN(time))
return invalidTime;
if (format == 'Float') return time;
suffixes = suffixes || timeSuffixes;
time = DMath.fixHour(time+ 0.5/ 60); // add 0.5 minutes to round
var hours = Math.floor(time);
var minutes = Math.floor((time- hours)* 60);
var suffix = (format == '12h') ? suffixes[hours < 12 ? 0 : 1] : '';
var hour = (format == '24h') ? this.twoDigitsFormat(hours) : ((hours+ 12 -1)% 12+ 1);
return hour+ ':'+ this.twoDigitsFormat(minutes)+ (suffix ? ' '+ suffix : '');
},
//---------------------- Calculation Functions -----------------------
// compute mid-day time
midDay: function(time) {
var eqt = this.sunPosition(jDate+ time).equation;
var noon = DMath.fixHour(12- eqt);
return noon;
},
// compute the time at which sun reaches a specific angle below horizon
sunAngleTime: function(angle, time, direction) {
var decl = this.sunPosition(jDate+ time).declination;
var noon = this.midDay(time);
var t = 1/15* DMath.arccos((-DMath.sin(angle)- DMath.sin(decl)* DMath.sin(lat))/
(DMath.cos(decl)* DMath.cos(lat)));
return noon+ (direction == 'ccw' ? -t : t);
},
// compute asr time
asrTime: function(factor, time) {
var decl = this.sunPosition(jDate+ time).declination;
var angle = -DMath.arccot(factor+ DMath.tan(Math.abs(lat- decl)));
return this.sunAngleTime(angle, time);
},
// compute declination angle of sun and equation of time
// Ref: http://aa.usno.navy.mil/faq/docs/SunApprox.php
sunPosition: function(jd) {
var D = jd - 2451545.0;
var g = DMath.fixAngle(357.529 + 0.98560028* D);
var q = DMath.fixAngle(280.459 + 0.98564736* D);
var L = DMath.fixAngle(q + 1.915* DMath.sin(g) + 0.020* DMath.sin(2*g));
var R = 1.00014 - 0.01671* DMath.cos(g) - 0.00014* DMath.cos(2*g);
var e = 23.439 - 0.00000036* D;
var RA = DMath.arctan2(DMath.cos(e)* DMath.sin(L), DMath.cos(L))/ 15;
var eqt = q/15 - DMath.fixHour(RA);
var decl = DMath.arcsin(DMath.sin(e)* DMath.sin(L));
return {declination: decl, equation: eqt};
},
// convert Gregorian date to Julian day
// Ref: Astronomical Algorithms by Jean Meeus
julian: function(year, month, day) {
if (month <= 2) {
year -= 1;
month += 12;
};
var A = Math.floor(year/ 100);
var B = 2- A+ Math.floor(A/ 4);
var JD = Math.floor(365.25* (year+ 4716))+ Math.floor(30.6001* (month+ 1))+ day+ B- 1524.5;
return JD;
},
//---------------------- Compute Prayer Times -----------------------
// compute prayer times at given julian date
computePrayerTimes: function(times) {
times = this.dayPortion(times);
var params = setting;
var imsak = this.sunAngleTime(this.eval(params.imsak), times.imsak, 'ccw');
var fajr = this.sunAngleTime(this.eval(params.fajr), times.fajr, 'ccw');
var sunrise = this.sunAngleTime(this.riseSetAngle(), times.sunrise, 'ccw');
var dhuhr = this.midDay(times.dhuhr);
var asr = this.asrTime(this.asrFactor(params.asr), times.asr);
var sunset = this.sunAngleTime(this.riseSetAngle(), times.sunset);;
var maghrib = this.sunAngleTime(this.eval(params.maghrib), times.maghrib);
var isha = this.sunAngleTime(this.eval(params.isha), times.isha);
return {
imsak: imsak, fajr: fajr, sunrise: sunrise, dhuhr: dhuhr,
asr: asr, sunset: sunset, maghrib: maghrib, isha: isha
};
},
// compute prayer times
computeTimes: function() {
// default times
var times = {
imsak: 5, fajr: 5, sunrise: 6, dhuhr: 12,
asr: 13, sunset: 18, maghrib: 18, isha: 18
};
// main iterations
for (var i=1 ; i<=numIterations ; i++)
times = this.computePrayerTimes(times);
times = this.adjustTimes(times);
// add midnight time
times.midnight = (setting.midnight == 'Jafari') ?
times.sunset+ this.timeDiff(times.sunset, times.fajr)/ 2 :
times.sunset+ this.timeDiff(times.sunset, times.sunrise)/ 2;
times = this.tuneTimes(times);
return this.modifyFormats(times);
},
// adjust times
adjustTimes: function(times) {
var params = setting;
for (var i in times)
times[i] += timeZone- lng/ 15;
if (params.highLats != 'None')
times = this.adjustHighLats(times);
if (this.isMin(params.imsak))
times.imsak = times.fajr- this.eval(params.imsak)/ 60;
if (this.isMin(params.maghrib))
times.maghrib = times.sunset+ this.eval(params.maghrib)/ 60;
if (this.isMin(params.isha))
times.isha = times.maghrib+ this.eval(params.isha)/ 60;
times.dhuhr += this.eval(params.dhuhr)/ 60;
return times;
},
// get asr shadow factor
asrFactor: function(asrParam) {
var factor = {Standard: 1, Hanafi: 2}[asrParam];
return factor || this.eval(asrParam);
},
// return sun angle for sunset/sunrise
riseSetAngle: function() {
//var earthRad = 6371009; // in meters
//var angle = DMath.arccos(earthRad/(earthRad+ elv));
var angle = 0.0347* Math.sqrt(elv); // an approximation
return 0.833+ angle;
},
// apply offsets to the times
tuneTimes: function(times) {
for (var i in times)
times[i] += offset[i]/ 60;
return times;
},
// convert times to given time format
modifyFormats: function(times) {
for (var i in times)
times[i] = this.getFormattedTime(times[i], timeFormat);
return times;
},
// adjust times for locations in higher latitudes
adjustHighLats: function(times) {
var params = setting;
var nightTime = this.timeDiff(times.sunset, times.sunrise);
times.imsak = this.adjustHLTime(times.imsak, times.sunrise, this.eval(params.imsak), nightTime, 'ccw');
times.fajr = this.adjustHLTime(times.fajr, times.sunrise, this.eval(params.fajr), nightTime, 'ccw');
times.isha = this.adjustHLTime(times.isha, times.sunset, this.eval(params.isha), nightTime);
times.maghrib = this.adjustHLTime(times.maghrib, times.sunset, this.eval(params.maghrib), nightTime);
return times;
},
// adjust a time for higher latitudes
adjustHLTime: function(time, base, angle, night, direction) {
var portion = this.nightPortion(angle, night);
var timeDiff = (direction == 'ccw') ?
this.timeDiff(time, base):
this.timeDiff(base, time);
if (isNaN(time) || timeDiff > portion)
time = base+ (direction == 'ccw' ? -portion : portion);
return time;
},
// the night portion used for adjusting times in higher latitudes
nightPortion: function(angle, night) {
var method = setting.highLats;
var portion = 1/2 // MidNight
if (method == 'AngleBased')
portion = 1/60* angle;
if (method == 'OneSeventh')
portion = 1/7;
return portion* night;
},
// convert hours to day portions
dayPortion: function(times) {
for (var i in times)
times[i] /= 24;
return times;
},
//---------------------- Time Zone Functions -----------------------
// get local time zone
getTimeZone: function(date) {
var year = date[0];
var t1 = this.gmtOffset([year, 0, 1]);
var t2 = this.gmtOffset([year, 6, 1]);
return Math.min(t1, t2);
},
// get daylight saving for a given date
getDst: function(date) {
return 1* (this.gmtOffset(date) != this.getTimeZone(date));
},
// GMT offset for a given date
gmtOffset: function(date) {
var localDate = new Date(date[0], date[1]- 1, date[2], 12, 0, 0, 0);
var GMTString = localDate.toGMTString();
var GMTDate = new Date(GMTString.substring(0, GMTString.lastIndexOf(' ')- 1));
var hoursDiff = (localDate- GMTDate) / (1000* 60* 60);
return hoursDiff;
},
//---------------------- Misc Functions -----------------------
// convert given string into a number
eval: function(str) {
return 1* (str+ '').split(/[^0-9.+-]/)[0];
},
// detect if input contains 'min'
isMin: function(arg) {
return (arg+ '').indexOf('min') != -1;
},
// compute the difference between two times
timeDiff: function(time1, time2) {
return DMath.fixHour(time2- time1);
},
// add a leading 0 if necessary
twoDigitsFormat: function(num) {
return (num <10) ? '0'+ num : num;
}
}}
//---------------------- Degree-Based Math Class -----------------------
var DMath = {
dtr: function(d) { return (d * Math.PI) / 180.0; },
rtd: function(r) { return (r * 180.0) / Math.PI; },
sin: function(d) { return Math.sin(this.dtr(d)); },
cos: function(d) { return Math.cos(this.dtr(d)); },
tan: function(d) { return Math.tan(this.dtr(d)); },
arcsin: function(d) { return this.rtd(Math.asin(d)); },
arccos: function(d) { return this.rtd(Math.acos(d)); },
arctan: function(d) { return this.rtd(Math.atan(d)); },
arccot: function(x) { return this.rtd(Math.atan(1/x)); },
arctan2: function(y, x) { return this.rtd(Math.atan2(y, x)); },
fixAngle: function(a) { return this.fix(a, 360); },
fixHour: function(a) { return this.fix(a, 24 ); },
fix: function(a, b) {
a = a- b* (Math.floor(a/ b));
return (a < 0) ? a+ b : a;
}
}
//---------------------- Init Object -----------------------
var prayTimes = new PrayTimes();
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment