Pobranie aktualnej pogody

Pobranie aktualnej pogody
Photo by Brian McGowan / Unsplash
<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Metody Statyczne</title>

<style>
  body {
    font-family: Arial, sans-serif;
    background: #f9f9f9;
    color: #333;
    padding: 20px;
  }

</style>

</head>
<body>
<canvas id="gauge" width="320" height="160" style="margin: 0 auto;"></canvas>
<canvas id="gauge2" width="320" height="160" style="margin: 0 auto;"></canvas>

<script>
class SemicircleGauge {
  /**
   * @param {HTMLCanvasElement} canvas
   * @param {{
   *   min?: number,
   *   max?: number,
   *   value?: number,
   *   segments?: number,               // 1..4
   *   segmentColors?: string[],
   *   trackColor?: string,
   *   thickness?: number,
   *   labelFormatter?: (v:number)=>string,
   *   animationDuration?: number,
   *   fontFamily?: string,
   *   showTicks?: boolean,
   *   tickColor?: string,
   *   separatorWidth?: number,
   *   showRangeLabels?: boolean        // <<< NOWA OPCJA
   * }} opts
   */
  constructor(canvas, opts = {}) {
    if (!(canvas instanceof HTMLCanvasElement)) {
      throw new Error("SemicircleGauge: canvas is required");
    }
    this.canvas = canvas;
    this.ctx = canvas.getContext("2d");

    this.min = opts.min ?? 0;
    this.max = opts.max ?? 100;
    this._value = this._clamp(opts.value ?? this.min);

    this.segments = Math.min(4, Math.max(1, opts.segments ?? 3));
    this.segmentColors = (opts.segmentColors && opts.segmentColors.length)
      ? opts.segmentColors.slice(0, this.segments)
      : this._defaultSegmentColors(this.segments);

    this.trackColor = opts.trackColor ?? "#e6e6e9";
    this.thickness = opts.thickness ?? 18;
    this.fontFamily = opts.fontFamily ?? "system-ui, -apple-system, Segoe UI, Roboto, Arial";
    this.labelFormatter = typeof opts.labelFormatter === "function"
      ? opts.labelFormatter
      : (v) => `${Math.round(v)}`;

    this.animationDuration = Math.max(0, opts.animationDuration ?? 600);

    this.showTicks = opts.showTicks ?? false;
    this.tickColor = opts.tickColor ?? "rgba(0,0,0,.25)";
    this.separatorWidthDeg = opts.separatorWidth ?? 2.5;

    this.showRangeLabels = opts.showRangeLabels ?? true; // <<< NOWA OPCJA

    this.startAngle = Math.PI;
    this.endAngle   = 2 * Math.PI;

    this._resizeForDPR();
    this._onResize = () => {
      this._resizeForDPR();
      this.draw();
    };
    window.addEventListener("resize", this._onResize, { passive: true });

    this.draw();
  }

  destroy() {
    window.removeEventListener("resize", this._onResize);
    cancelAnimationFrame(this._rafId);
  }

  setValue(newValue, { animate = true } = {}) {
    const target = this._clamp(newValue);
    if (!animate || this.animationDuration === 0) {
      this._value = target;
      this.draw();
      return;
    }
    this._animateTo(target);
  }

  setSegments(count, colors) {
    this.segments = Math.min(4, Math.max(1, count|0));
    this.segmentColors = (colors && colors.length)
      ? colors.slice(0, this.segments)
      : this._defaultSegmentColors(this.segments);
    this.draw();
  }

  draw() {
    const ctx = this.ctx;
    const { width, height } = this.canvas;
    ctx.clearRect(0, 0, width, height);

    const cx = width / 2;
    const cy = height;
    const radius = Math.min(width / 2, height) - this._px(this.thickness/2 + 6);

    this._drawSegmentsBackground(ctx, cx, cy, radius);
    this._drawProgress(ctx, cx, cy, radius);

    if (this.showTicks) {
      this._drawTicks(ctx, cx, cy, radius);
    }

    this._drawLabel(ctx, cx, cy, radius);

    if (this.showRangeLabels) {
      this._drawRangeLabels(ctx, cx, cy, radius); // <<< NOWE
    }
  }

  _drawSegmentsBackground(ctx, cx, cy, r) {
    const segCount = this.segments;
    const totalAngle = this.endAngle - this.startAngle;
    const sepRad = this._deg2rad(this.separatorWidthDeg);
    const usableAngle = totalAngle - sepRad * (segCount - 1);
    const segAngle = usableAngle / segCount;

    let a = this.startAngle;
    for (let i = 0; i < segCount; i++) {
      const start = a;
      const end = start + segAngle;
      ctx.beginPath();
      ctx.arc(cx, cy, r, start, end);
      ctx.lineWidth = this._px(this.thickness);
      ctx.strokeStyle = this.trackColor;
      ctx.lineCap = "round";
      ctx.stroke();

      a = end + (i < segCount - 1 ? sepRad : 0);
    }
  }

  _drawProgress(ctx, cx, cy, r) {
    const norm = (this._value - this.min) / (this.max - this.min || 1);
    const clampNorm = Math.max(0, Math.min(1, norm));
    const angle = this.startAngle + clampNorm * (this.endAngle - this.startAngle);

    const segIndex = this._segmentIndexForNorm(clampNorm);
    const color = this.segmentColors[segIndex];

    ctx.beginPath();
    ctx.arc(cx, cy, r, this.startAngle, angle);
    ctx.lineWidth = this._px(this.thickness);
    ctx.strokeStyle = color;
    ctx.lineCap = "round";
    ctx.stroke();
  }

  _drawTicks(ctx, cx, cy, r) {
    const steps = 5;
    for (let i = 0; i <= steps; i++) {
      const t = i / steps;
      const a = this.startAngle + t * (this.endAngle - this.startAngle);
      const inner = r - this._px(this.thickness/2) - this._px(6);
      const outer = inner - this._px(i % steps === 0 ? 10 : 6);

      const x1 = cx + inner * Math.cos(a);
      const y1 = cy + inner * Math.sin(a);
      const x2 = cx + outer * Math.cos(a);
      const y2 = cy + outer * Math.sin(a);

      ctx.beginPath();
      ctx.moveTo(x1, y1);
      ctx.lineTo(x2, y2);
      ctx.lineWidth = this._px(2);
      ctx.strokeStyle = this.tickColor;
      ctx.stroke();
    }
  }

  _drawLabel(ctx, cx, cy, r) {
    const label = this.labelFormatter(this._value);
    ctx.save();
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";

    ctx.shadowColor = "rgba(0,0,0,0.08)";
    ctx.shadowBlur = this._px(6);

    const fontSize = Math.max(14, Math.floor(this._cssPxToDevice(22)));
    ctx.font = `600 ${fontSize}px ${this.fontFamily}`;
    ctx.fillStyle = "#111";
    const y = cy - r / 2.2;
    ctx.fillText(label, cx, y);

    ctx.shadowBlur = 0;
    const subSize = Math.max(10, Math.floor(this._cssPxToDevice(12)));
    ctx.font = `400 ${subSize}px ${this.fontFamily}`;
    ctx.fillStyle = "rgba(0,0,0,.55)";
    ctx.fillText(`${this.min} — ${this.max}`, cx, y + this._px(20));
    ctx.restore();
  }

  _drawRangeLabels(ctx, cx, cy, r) {
    ctx.save();
    ctx.fillStyle = "#333";
    ctx.font = `400 ${this._cssPxToDevice(12)}px ${this.fontFamily}`;
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";

    const offset = r + this._px(this.thickness/2) + this._px(12);

    // min (lewy koniec)
    const xMin = cx + offset * Math.cos(this.startAngle);
    const yMin = cy + offset * Math.sin(this.startAngle);
    ctx.fillText(this.min, xMin, yMin);

    // max (prawy koniec)
    const xMax = cx + offset * Math.cos(this.endAngle);
    const yMax = cy + offset * Math.sin(this.endAngle);
    ctx.fillText(this.max, xMax, yMax);

    ctx.restore();
  }

  _segmentIndexForNorm(norm) {
    if (this.segments === 1) return 0;
    const segSize = 1 / this.segments;
    let idx = Math.floor(norm / segSize);
    if (idx >= this.segments) idx = this.segments - 1;
    return idx;
  }

  _defaultSegmentColors(n) {
    const presets = {
      1: ["#2ecc71"],
      2: ["#e74c3c", "#2ecc71"],
      3: ["#e74c3c", "#f1c40f", "#2ecc71"],
      4: ["#e74c3c", "#e67e22", "#f1c40f", "#2ecc71"],
    };
    return presets[n] ?? presets[3];
  }

  _deg2rad(d) { return d * Math.PI / 180; }
  _clamp(v) { return Math.min(this.max, Math.max(this.min, v)); }

  _resizeForDPR() {
    const dpr = window.devicePixelRatio || 1;
    const cssWidth = this.canvas.clientWidth || this.canvas.width;
    const cssHeight = this.canvas.clientHeight || this.canvas.height;
    this.canvas.width  = Math.round(cssWidth * dpr);
    this.canvas.height = Math.round(cssHeight * dpr);
    this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  }

  _px(cssPx) { return cssPx; }
  _cssPxToDevice(cssPx) { return cssPx; }

  _animateTo(target) {
    cancelAnimationFrame(this._rafId);
    const start = performance.now();
    const from = this._value;
    const delta = target - from;
    const dur = this.animationDuration;

    const ease = (t) => t < 0.5 ? 2*t*t : -1 + (4 - 2*t)*t;

    const step = (now) => {
      const t = Math.min(1, (now - start) / dur);
      this._value = from + delta * ease(t);
      this.draw();
      if (t < 1) {
        this._rafId = requestAnimationFrame(step);
      }
    };
    this._rafId = requestAnimationFrame(step);
  }
}

// --- PRZYKŁAD UŻYCIA ---
const canvas = document.getElementById("gauge");
const canvas2 = document.getElementById("gauge2");
const gauge = new SemicircleGauge(canvas, {
  min: -20,
  max: 70,
  value: 35,
  segments: 3,
  segmentColors: ["#e74c3c","#f1c40f","#2ecc71"],
  thickness: 30,
  animationDuration: 500,
  showTicks: true,
  showRangeLabels: true, // <<< OPCJA
  labelFormatter: (v) => `${Math.round(v)}°C`
});

const gauge2 = new SemicircleGauge(canvas2, {
  min: 0,
  max: 100,
  value: 35,
  segments: 3,
  segmentColors: ["#e74c3c","#f1c40f","#2ecc71"],
  thickness: 30,
  animationDuration: 500,
  showTicks: true,
  showRangeLabels: true, // <<< OPCJA
  labelFormatter: (v) => `${Math.round(v)}%`
});


// Aktualizacja wartości (bez przeładowania strony)

// setInterval(() => gauge.setValue(Math.random() * 70 - 20), 6000);
// setInterval(() => gauge2.setValue(Math.random() * 100), 6000);

    // --- Funkcja do pobrania aktualnej pogody ---
    async function getActualWeather() {
      try {
        const response = await fetch("https://api.open-meteo.com/v1/forecast?latitude=52.237049&longitude=21.017532&hourly=rain,weather_code,wind_speed_10m,surface_pressure,apparent_temperature,relative_humidity_2m&forecast_days=1");
        const data = await response.json();
        console.log('Data:', data);

        // Pobieram biezaca date i godzine
        const now = new Date();
        // Sprawdzam ktora jest godzina
        let currentHour = now.getHours();
        // Sprawdzam, ile jest minut, jesli powyzej 30 to dodaje 1 godzine
        const minutes = now.getMinutes();
        if (minutes > 30) {
          currentHour++;
        }
        //Sprawdzam, czy danych z api sa potrzebne informacje, jesli nie ma to pojawia sie error i zatrzymujemy dzialanie programu
        if (!data.hourly.apparent_temperature || //brak temperatury
            !data.hourly.relative_humidity_2m )//brak wilgotnosci 
            {
          console.error("Brak danych pogodowych dla godziny:", currentHour);
          return;
        }
//  Pobieram temperature i wilgotnosc 
        const temperature = data.hourly.apparent_temperature[currentHour];
        const humidity = data.hourly.relative_humidity_2m[currentHour];
//Walidacja temperatury i wilgotnosci
    if (temperature == null || isNaN(temperature)) {
      console.error("Nieprawidlowa temperatura:", temperature);
      return;
    }
    if (humidity == null || isNaN(humidity)) {
      console.error("Nieprawidlowa wilgotność:", humidity);
      return;
    }
    // jesli przejdzie walidacje to wyswuetli poprawne dane w konsoli
        console.log(`Godzina: ${currentHour}:00 | Temperatura: ${temperature} °C | Wilgotność: ${humidity} %`);
// przypisanie wartosci temperatury i wilgotnosci do wskaznikow
        gauge.setValue(temperature);
        gauge2.setValue(humidity);

      } catch (error) {
        console.error("Błąd pobierania danych pogodowych:", error);
      }
    }

    getActualWeather();


</script>

</body>
</html>