block by rveciana d5a398bdb55a6caec3e3931f347e4b70

ramadan times along meridian 30

Full Screen

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

index.html

<!DOCTYPE html>
<meta charset="utf-8">

<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//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>

PrayTimes.js

//--------------------- 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();