Otvoreni kod i AGPL licenca
Svi astrološki kalkulatori i alati na ovoj stranici koriste Swiss Ephemeris © Astrodienst AG, licenciran pod AGPL v3.
U skladu s uvjetima licence, javno je dostupan izvorni kod backend dijela koji komunicira sa Swiss Ephemerisom (API i kalkulacijske funkcije).
Izvorni kod možeš pronaći u nastavku:
<?php
class Sweph {
private static $swetest_path;
private static $ephe_path;
public static function init($swetest_path = null, $ephe_path = null) {
self::$swetest_path = $swetest_path ?: __DIR__ . '/swetest';
self::$ephe_path = $ephe_path ?: __DIR__;
}
/* ------------------ PLANETI ------------------ */
public static function calc_planet($planet_code, $date, $time = '12:00') {
if ($planet_code >= 10) {
return self::extract_node_or_lilith($planet_code, $date, $time);
}
$cmd = self::$swetest_path
. " -edir" . self::$ephe_path
. " -b" . $date
. " -ut" . $time
. " -p" . $planet_code
. " -fPls -head";
exec($cmd, $output);
if (empty($output)) return ['longitude' => 0, 'retrograde' => false];
foreach ($output as $line) {
if (preg_match('/\d/', $line)) {
$parts = preg_split('/\s+/', trim($line));
if (count($parts) >= 3) {
$longitude = (float)$parts[1];
break;
}
}
}
// retrogradnost – standardni izračun
$timestamp = strtotime(str_replace('.', '-', $date));
$day_before = date('d.m.Y', $timestamp - 86400);
$day_after = date('d.m.Y', $timestamp + 86400);
$lon_before = self::get_longitude($planet_code, $day_before, $time);
$lon_after = self::get_longitude($planet_code, $day_after, $time);
// korekcija za prijelaz preko 0°/360°
if ($lon_after < $lon_before && ($lon_before - $lon_after) > 180) {
$lon_after += 360;
}
$retrograde = ($lon_before && $lon_after) ? ($lon_after < $lon_before) : false;
return [
'longitude' => $longitude ?? 0,
'retrograde' => $retrograde
];
}
/* ------------------ HELPER ------------------ */
private static function get_longitude($planet_code, $date, $time) {
$cmd = self::$swetest_path
. " -edir" . self::$ephe_path
. " -b" . $date
. " -ut" . $time
. " -p" . $planet_code
. " -fPls -head";
exec($cmd, $out);
foreach ($out as $line) {
if (preg_match('/\d/', $line)) {
$parts = preg_split('/\s+/', trim($line));
if (count($parts) >= 3) {
return (float)$parts[1];
}
}
}
return null;
}
/* ------------------ ČVOROVI I LILITH ------------------ */
private static function extract_node_or_lilith($planet_code, $date, $time) {
$cmd = self::$swetest_path
. " -edir" . self::$ephe_path
. " -b" . $date
. " -ut" . $time
. " -fPls -head";
exec($cmd, $output);
file_put_contents(__DIR__ . '/debug_nodes.txt', "CMD: $cmd\n\nOUTPUT:\n" . implode("\n", $output));
$longitude = 0;
$retrograde = false;
foreach ($output as $line) {
$line = trim(preg_replace('/\s+/', ' ', $line));
// mean Node
if ($planet_code == 10 && stripos($line, 'mean Node') === 0) {
$parts = explode(' ', $line);
$longitude = (float)$parts[2];
$retrograde = ($parts[3] < 0);
}
// južni čvor iz mean Node
if ($planet_code == 11 && stripos($line, 'mean Node') === 0) {
$parts = explode(' ', $line);
$longitude = fmod((float)$parts[2] + 180, 360);
$retrograde = true;
}
// Lilith (Mean Apogee)
if ($planet_code == 12 && stripos($line, 'mean Apogee') === 0) {
$parts = explode(' ', $line);
$longitude = (float)$parts[2];
$retrograde = ($parts[3] < 0);
}
}
return [
'longitude' => round($longitude, 3),
'retrograde' => $retrograde
];
}
/* ------------------ KUĆE (KOCH, full tolerant parser) ------------------ */
public static function calc_points($date, $time = '12:00', $lat = 0, $lon = 0, $country_code = 'HR') {
setlocale(LC_ALL, 'C');
$tz_map = [
'HR'=>'Europe/Zagreb','SI'=>'Europe/Ljubljana','RS'=>'Europe/Belgrade',
'BA'=>'Europe/Sarajevo','ME'=>'Europe/Podgorica','MK'=>'Europe/Skopje','XK'=>'Europe/Belgrade'
];
$tz = $tz_map[$country_code] ?? 'Europe/Zagreb';
// lokalno → UTC
$dt_local = new DateTime("$date $time", new DateTimeZone($tz));
$dt_utc = clone $dt_local;
$dt_utc->setTimezone(new DateTimeZone('UTC'));
$ut_time = $dt_utc->format('H:i');
// Swiss Ephemeris (lon,lat,K)
$cmd = "LANG=C " . self::$swetest_path .
" -edir" . self::$ephe_path .
" -b" . $date .
" -ut" . $ut_time .
" -house" . $lon . "," . $lat . ",K -head";
exec($cmd, $lines);
file_put_contents(__DIR__.'/debug_houses.txt',"CMD: $cmd\n\nOUTPUT:\n".implode("\n",$lines));
$houses = [];
$asc = null;
$mc = null;
foreach ($lines as $line) {
$line = trim($line);
// hvata i slučajeve s tabovima, razmacima, i znakovima ° ili �
if (preg_match('/house\s+(\d+)\s+(\d+)[^0-9]+(\d+)\'\s*(\d+\.\d+)/', $line, $m)) {
$num = (int)$m[1];
$deg = (float)$m[2];
$min = (float)$m[3];
$sec = (float)$m[4];
$houses[$num] = round($deg + $min/60 + $sec/3600, 4);
}
// Ascendant linija (npr. Ascendant 10°29'41.7826)
if (stripos($line, 'Ascendant') !== false && preg_match('/(\d+)[^0-9]+(\d+)\'\s*(\d+\.\d+)/', $line, $m)) {
$deg = (float)$m[1];
$min = (float)$m[2];
$sec = (float)$m[3];
$asc = round($deg + $min/60 + $sec/3600, 4);
}
// MC linija
if (preg_match('/\bMC\b/', $line) && preg_match('/(\d+)[^0-9]+(\d+)\'\s*(\d+\.\d+)/', $line, $m)) {
$deg = (float)$m[1];
$min = (float)$m[2];
$sec = (float)$m[3];
$mc = round($deg + $min/60 + $sec/3600, 4);
}
}
// fallback: ako prva kuća nije eksplicitno zapisana, koristi Asc
if (!isset($houses[1]) && $asc !== null) {
$houses[1] = $asc;
ksort($houses);
}
return [
'houses' => $houses,
'ASC' => $asc ?? ($houses[1] ?? null),
'MC' => $mc ?? ($houses[10] ?? null),
'tz_used' => $tz,
'ut_time' => $ut_time
];
}
/* ------------------ ASPEKTI ------------------ */
public static function calc_aspects($planets) {
$aspects = [];
$aspect_defs = [
'konjunkcija' => 0,
'sekstil' => 60,
'kvadrat' => 90,
'trigon' => 120,
'opozicija' => 180
];
$orb = [
'konjunkcija' => 8,
'sekstil' => 7,
'kvadrat' => 7,
'trigon' => 7,
'opozicija' => 8
];
foreach ($planets as $p1 => $data1) {
// ⛔ preskoči čvorove i Lilith
if ($p1 >= 10) continue;
foreach ($planets as $p2 => $data2) {
if ($p2 >= 10 || $p1 >= $p2) continue;
$diff = abs($data1['longitude'] - $data2['longitude']);
if ($diff > 180) $diff = 360 - $diff;
foreach ($aspect_defs as $name => $angle) {
if (abs($diff - $angle) <= $orb[$name]) {
$aspects[] = [
'aspect' => $name,
'planets' => [
self::get_planet_name($p1),
self::get_planet_name($p2)
],
'difference' => round($diff, 2)
];
}
}
}
}
return $aspects;
}
/* ------------------ ASPEKTI PLANETI ↔︎ OSI (ASC / MC) ------------------ */
public static function calc_axis_aspects($planets, $points) {
$aspects = [];
$aspect_defs = [
'konjunkcija' => 0,
'sekstil' => 60,
'kvadrat' => 90,
'trigon' => 120,
'opozicija' => 180
];
$orb = [
'konjunkcija' => 7,
'sekstil' => 6,
'kvadrat' => 6,
'trigon' => 6,
'opozicija' => 7
];
$axes = [
'ASC' => $points['ASC'] ?? null,
'MC' => $points['MC'] ?? null
];
foreach ($planets as $pid => $pdata) {
if ($pid >= 10) continue; // preskoči čvorove i Lilith
foreach ($axes as $axis_name => $axis_lon) {
if (!$axis_lon) continue;
$diff = abs($pdata['longitude'] - $axis_lon);
if ($diff > 180) $diff = 360 - $diff;
foreach ($aspect_defs as $asp => $angle) {
if (abs($diff - $angle) <= $orb[$asp]) {
$aspects[] = [
'planet' => self::get_planet_name($pid),
'axis' => $axis_name,
'aspect' => $asp,
'difference' => round($diff, 2)
];
}
}
}
}
return $aspects;
}
/* ------------------ ASTRO KONFIGURACIJE ------------------ */
public static function calc_configurations($planets) {
$configs = [];
// Radi samo s klasičnim planetima (Sunce–Pluton)
$filtered = array_filter($planets, fn($p, $i) => $i < 10, ARRAY_FILTER_USE_BOTH);
$names = array_keys($filtered);
// --- 1. STELIJ (3+ planeta unutar 30°) ---
$planet_list = [];
foreach ($filtered as $id => $p) {
$planet_list[] = ['id' => $id, 'lon' => $p['longitude']];
}
usort($planet_list, fn($a, $b) => $a['lon'] <=> $b['lon']);
for ($i = 0; $i < count($planet_list); $i++) {
$group = [$planet_list[$i]];
for ($j = $i + 1; $j < count($planet_list); $j++) {
$diff = abs($planet_list[$j]['lon'] - $planet_list[$i]['lon']);
if ($diff > 180) $diff = 360 - $diff;
if ($diff <= 15) {
$group[] = $planet_list[$j];
}
}
if (count($group) >= 3) {
$names_in_stellium = array_map(fn($g) => self::get_planet_name($g['id']), $group);
$longitudes = array_column($group, 'lon');
$configs[] = [
'type' => 'Stelij',
'planets' => $names_in_stellium,
'count' => count($group),
'span' => round(max($longitudes) - min($longitudes), 2),
'center' => round(array_sum($longitudes) / count($longitudes), 2)
];
}
}
// --- 2. T-KVADRAT ---
foreach ($names as $i => $a) {
foreach ($names as $j => $b) {
if ($i == $j) continue;
$angle_ab = self::angle_diff($filtered[$a]['longitude'], $filtered[$b]['longitude']);
if (abs($angle_ab - 180) > 8) continue; // mora biti opozicija
foreach ($names as $k => $c) {
if (in_array($k, [$i, $j])) continue;
$ac = self::angle_diff($filtered[$a]['longitude'], $filtered[$c]['longitude']);
$bc = self::angle_diff($filtered[$b]['longitude'], $filtered[$c]['longitude']);
if (abs($ac - 90) <= 8 && abs($bc - 90) <= 8) {
$configs[] = [
'type' => 'T-kvadrat',
'planets' => [
self::get_planet_name($a),
self::get_planet_name($b),
self::get_planet_name($c)
]
];
}
}
}
}
// --- 3. VELIKI KVADRAT (GRAND CROSS) ---
for ($a = 0; $a < count($names); $a++) {
for ($b = $a + 1; $b < count($names); $b++) {
for ($c = $b + 1; $c < count($names); $c++) {
for ($d = $c + 1; $d < count($names); $d++) {
$set = [$a, $b, $c, $d];
$ok = 0;
foreach ($set as $i1) {
foreach ($set as $i2) {
if ($i1 >= $i2) continue;
$diff = self::angle_diff($filtered[$names[$i1]]['longitude'], $filtered[$names[$i2]]['longitude']);
if (abs($diff - 90) <= 8 || abs($diff - 180) <= 8) $ok++;
}
}
if ($ok >= 10) {
$configs[] = [
'type' => 'Veliki kvadrat',
'planets' => array_map(fn($n) => self::get_planet_name($n), $set)
];
}
}
}
}
}
// --- 4. VELIKI TRIGON ---
for ($a = 0; $a < count($names); $a++) {
for ($b = $a + 1; $b < count($names); $b++) {
for ($c = $b + 1; $c < count($names); $c++) {
$λa = $filtered[$names[$a]]['longitude'];
$λb = $filtered[$names[$b]]['longitude'];
$λc = $filtered[$names[$c]]['longitude'];
$ab = self::angle_diff($λa, $λb);
$bc = self::angle_diff($λb, $λc);
$ca = self::angle_diff($λc, $λa);
if (abs($ab - 120) <= 8 && abs($bc - 120) <= 8 && abs($ca - 120) <= 8) {
$configs[] = [
'type' => 'Veliki trigon',
'planets' => [
self::get_planet_name($names[$a]),
self::get_planet_name($names[$b]),
self::get_planet_name($names[$c])
]
];
}
}
}
}
return $configs;
}
/* ------------------ HELPER ZA KUTEVE ------------------ */
private static function angle_diff($a, $b) {
$d = abs($a - $b);
return ($d > 180) ? 360 - $d : $d;
}
/* ------------------ ELEMENTI, KVALITETE I ROD ------------------ */
public static function calc_elements_qualities_gender($planets) {
$signs = ['Ovan','Bik','Blizanci','Rak','Lav','Djevica','Vaga','Škorpion','Strijelac','Jarac','Vodenjak','Ribe'];
$elements = [
'vatra' => ['Ovan', 'Lav', 'Strijelac'],
'zemlja' => ['Bik', 'Djevica', 'Jarac'],
'zrak' => ['Blizanci', 'Vaga', 'Vodenjak'],
'voda' => ['Rak', 'Škorpion', 'Ribe']
];
$qualities = [
'kardinalni' => ['Ovan', 'Rak', 'Vaga', 'Jarac'],
'fiksni' => ['Bik', 'Lav', 'Škorpion', 'Vodenjak'],
'promjenjivi' => ['Blizanci', 'Djevica', 'Strijelac', 'Ribe']
];
$gender = [
'muški' => ['Ovan','Blizanci','Lav','Vaga','Strijelac','Vodenjak'],
'ženski' => ['Bik','Rak','Djevica','Škorpion','Jarac','Ribe']
];
$elem_count = ['vatra'=>0,'zemlja'=>0,'zrak'=>0,'voda'=>0];
$qual_count = ['kardinalni'=>0,'fiksni'=>0,'promjenjivi'=>0];
$gender_count = ['muški'=>0,'ženski'=>0];
$total = 0;
foreach ($planets as $pid => $pdata) {
if ($pid >= 10) continue;
$sign_index = floor($pdata['longitude'] / 30);
$sign = $signs[$sign_index] ?? null;
if ($sign) {
foreach ($elements as $el => $list)
if (in_array($sign, $list)) $elem_count[$el]++;
foreach ($qualities as $q => $list)
if (in_array($sign, $list)) $qual_count[$q]++;
foreach ($gender as $g => $list)
if (in_array($sign, $list)) $gender_count[$g]++;
$total++;
}
}
// postotci
$elem_pct = [];
$qual_pct = [];
$gender_pct = [];
foreach ($elem_count as $k => $v) $elem_pct[$k] = $total ? round($v / $total * 100, 1) : 0;
foreach ($qual_count as $k => $v) $qual_pct[$k] = $total ? round($v / $total * 100, 1) : 0;
foreach ($gender_count as $k => $v) $gender_pct[$k] = $total ? round($v / $total * 100, 1) : 0;
return [
'elementi' => $elem_count,
'kvalitete' => $qual_count,
'rod' => $gender_count,
'elementi_postotci' => $elem_pct,
'kvalitete_postotci' => $qual_pct,
'rod_postotci' => $gender_pct
];
}
/* ------------------ DOSTOJANSTVA PLANETA ------------------ */
public static function calc_dignities($planets) {
$signs = ['Ovan','Bik','Blizanci','Rak','Lav','Djevica','Vaga','Škorpion','Strijelac','Jarac','Vodenjak','Ribe'];
$planet_dignities = [
'Sunce' => [
'sjedište' => 'Lav',
'izgon' => 'Vodenjak',
'egzaltacija' => 'Ovan',
'pad' => 'Vaga',
],
'Mjesec' => [
'sjedište' => 'Rak',
'izgon' => 'Jarac',
'egzaltacija' => 'Bik',
'pad' => 'Škorpion',
],
'Merkur' => [
'sjedište' => ['Blizanci', 'Djevica'],
'izgon' => ['Strijelac', 'Ribe'],
'egzaltacija' => 'Djevica',
'pad' => 'Ribe',
],
'Venera' => [
'sjedište' => ['Bik', 'Vaga'],
'izgon' => ['Škorpion', 'Ovan'],
'egzaltacija' => 'Ribe',
'pad' => 'Djevica',
],
'Mars' => [
'sjedište' => ['Ovan', 'Škorpion'],
'izgon' => ['Vaga', 'Bik'],
'egzaltacija' => 'Jarac',
'pad' => 'Rak',
],
'Jupiter' => [
'sjedište' => ['Strijelac', 'Ribe'],
'izgon' => ['Blizanci', 'Djevica'],
'egzaltacija' => 'Rak',
'pad' => 'Jarac',
],
'Saturn' => [
'sjedište' => ['Jarac', 'Vodenjak'],
'izgon' => ['Rak', 'Lav'],
'egzaltacija' => 'Vaga',
'pad' => 'Ovan',
],
'Uran' => [
'sjedište' => 'Vodenjak',
'izgon' => 'Lav',
'egzaltacija' => 'Škorpion',
'pad' => 'Bik',
],
'Neptun' => [
'sjedište' => 'Ribe',
'izgon' => 'Djevica',
'egzaltacija' => 'Rak',
'pad' => 'Jarac',
],
'Pluton' => [
'sjedište' => 'Škorpion',
'izgon' => 'Bik',
'egzaltacija' => 'Ovan',
'pad' => 'Vaga',
],
];
$results = [];
foreach ($planets as $pid => $pdata) {
if ($pid >= 10) continue; // preskoči čvorove i Lilith
$planet = self::get_planet_name($pid);
$sign_index = floor($pdata['longitude'] / 30);
$sign = $signs[$sign_index] ?? null;
if (!$sign || !isset($planet_dignities[$planet])) continue;
$info = $planet_dignities[$planet];
$match = function($value, $sign) {
if (is_array($value)) return in_array($sign, $value);
return $value === $sign;
};
$dignity = null;
if ($match($info['sjedište'], $sign)) $dignity = 'sjedište';
elseif ($match($info['izgon'], $sign)) $dignity = 'izgon';
elseif ($match($info['egzaltacija'], $sign)) $dignity = 'egzaltacija';
elseif ($match($info['pad'], $sign)) $dignity = 'pad';
if ($dignity) {
$results[$planet] = [
'znak' => $sign,
'status' => $dignity
];
}
}
return $results;
}
/* ------------------ UMETNUTI ZNAKOVI ------------------ */
public static function calc_intercepted_signs($houses) {
$signs = ['Ovan','Bik','Blizanci','Rak','Lav','Djevica','Vaga','Škorpion','Strijelac','Jarac','Vodenjak','Ribe'];
// 1. vrhovi kuća u stupnjevima (1–12)
ksort($houses);
$cusps = array_values($houses);
// 2. svi znakovi po 30°
$all_signs = range(0, 11);
// 3. znakovi na vrhovima kuća
$cusps_signs = [];
foreach ($cusps as $deg) {
$cusps_signs[] = floor($deg / 30);
}
// 4. znakovi koji NISU na vrhu nijedne kuće
$potential = array_diff($all_signs, array_unique($cusps_signs));
// 5. nađi koji od tih znakova su potpuno unutar neke kuće (tj. između dva vrha iste kuće)
$intercepted = [];
for ($i = 0; $i < 12; $i++) {
$start = $cusps[$i];
$end = $cusps[($i + 1) % 12];
if ($end <= $start) $end += 360;
// svi znakovi koji leže unutar tog raspona
foreach ($potential as $s) {
$sign_start = $s * 30;
$sign_end = $sign_start + 30;
// provjera leži li cijeli znak unutar jedne kuće
if ($sign_start >= $start && $sign_end <= $end) {
$intercepted[] = $signs[$s];
}
}
}
// 6. ako je jedan umetnut, dodaj i njegovu opoziciju
$final = [];
foreach ($intercepted as $s) {
$idx = array_search($s, $signs);
$opp = ($idx + 6) % 12;
$final[] = $s;
$final[] = $signs[$opp];
}
return array_values(array_unique($final));
}
/* ------------------ IMENA ------------------ */
public static function get_planet_name($code) {
$names = [
0 => 'Sunce',
1 => 'Mjesec',
2 => 'Merkur',
3 => 'Venera',
4 => 'Mars',
5 => 'Jupiter',
6 => 'Saturn',
7 => 'Uran',
8 => 'Neptun',
9 => 'Pluton',
10 => 'Sjeverni čvor',
11 => 'Južni čvor',
12 => 'Lilith'
];
return $names[$code] ?? 'Nepoznato';
}
}
?>
README:
# Ljepota Duše – Swiss Ephemeris Backend (Open Source) Ovaj kod koristi Swiss Ephemeris © Astrodienst AG, licenciran pod GNU Affero General Public License v3 (AGPL v3). ## Što sadrži ovaj paket - `sweph.php`: PHP wrapper s osnovnim funkcijama za izračun planeta, kuća i aspekata - `LICENSE`: puna AGPL v3 licenca ## Dozvoljeno - Korištenje, izmjena i redistribucija ovog koda pod uvjetima AGPL v3 - Uključivanje u open-source projekte i besplatne alate ## Nije obuhvaćeno AGPL-om Frontend dizajn, tumačenja, analize, PDF-izvoz i sustavi plaćanja pripadaju Ljepoti Duše i nisu dio ovog open-source paketa. ## Izvor licence [https://www.gnu.org/licenses/agpl-3.0.html](https://www.gnu.org/licenses/agpl-3.0.html) © Ljepota Duše – 2025.
