Skip to main content

Sahib Update

How Qibla Calculation & Compass Work


1. Qibla Direction Calculation

Library Used

  • adhan — JavaScript library for Islamic calculations

How It Works

const coordinates = new Coordinates(latitude, longitude);
const qiblaAngle = Qibla(coordinates); // Returns angle in degrees (0-360)

2. Device Heading (Compass)

Library Used

  • expo-location — Expo’s location services

How It Works

Location.watchHeadingAsync((heading) => {
  const headingValue = heading.trueHeading > 0 
    ? heading.trueHeading    // True North (GPS-based)
    : heading.magHeading;    // Magnetic North (compass-based)
  
  setDeviceHeading(headingValue); // 0-360 degrees
});
  • Listens to the device’s magnetometer/compass
  • Returns the device’s heading (0–360°)
  • trueHeading: GPS-based (more accurate, requires GPS)
  • magHeading: magnetic compass (works without GPS)

3. Compass Rotation Formula

targetRotation = -deviceHeading

currentNormalized = normalizeAngle(currentRotation)  // 0-360
targetNormalized = normalizeAngle(targetRotation)    // 0-360

diff = targetNormalized - currentNormalized
if (diff > 180) diff -= 360   // Go the other way
if (diff < -180) diff += 360  // Go the other way

finalTarget = currentRotation + diff
Why negative?
  • When device points North (0°), compass should show North at top (0° rotation)
  • When device rotates 90° East, compass rotates -90° to keep North at top

4. Visual Display

Compass Circle

  • Rotates with device movement
  • Formula: rotate(-deviceHeading)
  • Keeps North at the top visually

Qibla Arrow

  • Stays fixed, pointing to qibla direction
  • Formula: rotation = qiblaDirection (e.g., 58°)
    Shows the direction to Mecca relative to the compass

    Design patterns in prayer-times service


     

    1. Template Method Pattern

    What it is: The base class defines the algorithm steps, and subclasses fill in the specific parts.
    How it works here:
    export interface PrayerTimeInput {
      coordinates: Coordinates;
      date?: Date;
      timezone: string;
      format?: string;
      adjustments?: PrayerAdjustments;
    }
    export interface PrayerTimesResult {
      fajr: Date;
      sunrise: Date;
      dhuhr: Date;
      asr: Date;
      maghrib: Date;
      isha: Date;
      formatted?: FormattedPrayerTimes;
    }
    
    export interface FormattedPrayerTimes {
      fajr: string;
      sunrise: string;
      dhuhr: string;
      asr: string;
      maghrib: string;
      isha: string;
    }
    // Base class defines the template
    abstract class PrayerTimeCalculator {
      public calculate(input: PrayerTimeInput): PrayerTimesResult {
        // Step 1: Prepare (subclass implements)
        const params = this.prepareCalculationParameters(input);
        
        // Step 2: Apply adjustments (subclass implements)
        const adjusted = this.applyAdjustments(params, input.adjustments);
        
        // Step 3: Calculate (subclass implements)
        const rawTimes = this.calculateRawPrayerTimes(input, adjusted);
        
        // Step 4 & 5: Common steps (base class handles)
        const timezoneAware = this.convertToTimezone(rawTimes, input.timezone);
        const formatted = this.formatPrayerTimes(timezoneAware, input.timezone);
        
        return { ...timezoneAware, formatted };
      }
    }
    Analogy: A recipe template where steps 1–3 are fixed, and steps vary by method

    2. Adapter Pattern

    What it is: Translates one interface to another so different systems can work together.
    Example: 
      AdhanAdapter wraps the Adhan library to match the generic interface.
      /**
       * Adapter for Adhan library
       * This adapter handles all Adhan-specific logic, making the calculator generic
       */
      export class AdhanAdapter {
        /**
         * Create Adhan Coordinates from generic coordinates
         */
        static createCoordinates(input: PrayerTimeInput): AdhanCoordinates {
          return new AdhanCoordinates(input.coordinates.latitude, input.coordinates.longitude);
        }
      
      
        /**
         * Calculate prayer times using Adhan library
         */
        static calculatePrayerTimes(
          input: PrayerTimeInput,
          params: CalculationParameters,
        ): RawPrayerTimes {
          const coordinates = AdhanAdapter.createCoordinates(input);
          const date = input.date || new Date();
          const prayerTimes = new AdhanPrayerTimes(coordinates, date, params);
      
          return {
            fajr: prayerTimes.fajr,
            sunrise: prayerTimes.sunrise,
            dhuhr: prayerTimes.dhuhr,
            asr: prayerTimes.asr,
            maghrib: prayerTimes.maghrib,
            isha: prayerTimes.isha,
          };
        }
      }
      

      3. Facade Pattern

      What it is: A simple interface that hides complexity behind the scenes.
      /**
       * Factory function to create a calculator instance based on method name
       * @param methodName - Name of the calculation method (enum or 'Dummy' string)
       * @returns Calculator instance
       */
      export const createCalculator = (methodName: CalculationMethodName | 'Dummy'): PrayerTimeCalculator => {
        switch (methodName) {
          case CalculationMethodName.MuslimWorldLeague:
            return new MuslimWorldLeagueCalculator();
          case CalculationMethodName.Egyptian:
            return new EgyptianCalculator();
            //....
          default:
            throw new Error(`Unknown calculation method: ${methodName}`);
        }
      };
      
      /**
       * Convenience function to calculate prayer times
       * @param methodName - Name of the calculation method
       * @param input - Prayer time calculation input
       * @returns Prayer times result
       */
      export const calculatePrayerTimes = (
        methodName: CalculationMethodName,
        input: PrayerTimeInput,
      ): PrayerTimesResult => {
        const calculator = createCalculator(methodName);
        return calculator.calculate(input);
      };
      
      
      Usage
      export enum CalculationMethodName {
        MuslimWorldLeague = 'MuslimWorldLeague',
        Egyptian = 'Egyptian',
        Karachi = 'Karachi',
        UmmAlQura = 'UmmAlQura',
        Dubai = 'Dubai',
        MoonsightingCommittee = 'MoonsightingCommittee',
        NorthAmerica = 'NorthAmerica',
        Kuwait = 'Kuwait',
        Qatar = 'Qatar',
        Singapore = 'Singapore',
        Tehran = 'Tehran',
        Turkey = 'Turkey',
      }
      const prayerTimesResult = calculatePrayerTimes(CalculationMethodName.MuslimWorldLeague, {
              coordinates: {
                latitude: state.latitude,
                longitude: state.longitude,
              },
              date: new Date(),
              timezone: state.timezone,
              format: 'h:mm A',
              adjustments: state.prayerAdjustments,
            });