klio 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +291 -0
- package/changelog.md +0 -0
- package/ephe/seas_18.se1 +0 -0
- package/ephe/seasnam.txt +546077 -0
- package/ephe/sefstars.txt +1580 -0
- package/ephe/seleapsec.txt +6 -0
- package/ephe/semo_18.se1 +0 -0
- package/ephe/seorbel.txt +95 -0
- package/ephe/sepl_18.se1 +0 -0
- package/index.js +4 -0
- package/package.json +28 -0
- package/src/astrology/astrologyConstants.js +55 -0
- package/src/astrology/astrologyService.js +990 -0
- package/src/astrology/retrogradeService.js +267 -0
- package/src/cli/cli.js +1062 -0
- package/src/config/configService.js +401 -0
- package/src/health/fileAnalysis.js +141 -0
- package/src/health/healthAnalysis.js +410 -0
- package/src/health/healthService.js +202 -0
- package/src/main.js +4 -0
- package/src/utils/fileUtils.js +46 -0
- package/src/utils/markdownFormatter.js +161 -0
|
@@ -0,0 +1,990 @@
|
|
|
1
|
+
const swisseph = require('swisseph');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const moment = require('moment-timezone');
|
|
4
|
+
const { planets, signs, elements, decans, dignities } = require('./astrologyConstants');
|
|
5
|
+
const { loadConfig } = require('../config/configService');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
// Standardort (Berlin, Deutschland) - kann später konfiguriert werden
|
|
9
|
+
const defaultLatitude = 52.5200; // Berlin Breitengrad
|
|
10
|
+
const defaultLongitude = 13.4050; // Berlin Längengrad
|
|
11
|
+
|
|
12
|
+
// Swisseph initialisieren
|
|
13
|
+
swisseph.swe_set_ephe_path(__dirname + '/../../ephe');
|
|
14
|
+
|
|
15
|
+
// Funktion zur Berechnung der aktuellen Zeit in der konfigurierten Zeitzone
|
|
16
|
+
function getCurrentTimeInTimezone() {
|
|
17
|
+
try {
|
|
18
|
+
const configPath = path.join(__dirname, '../../astrocli-config.json');
|
|
19
|
+
if (fs.existsSync(configPath)) {
|
|
20
|
+
const configData = fs.readFileSync(configPath, 'utf8');
|
|
21
|
+
const config = JSON.parse(configData);
|
|
22
|
+
|
|
23
|
+
if (config && config.currentLocation && config.currentLocation.timezone) {
|
|
24
|
+
// Verwende die konfigurierte Zeitzone
|
|
25
|
+
const now = moment().tz(config.currentLocation.timezone);
|
|
26
|
+
return {
|
|
27
|
+
year: now.year(),
|
|
28
|
+
month: now.month() + 1, // moment Monate sind 0-basiert
|
|
29
|
+
day: now.date(),
|
|
30
|
+
hour: now.hours(),
|
|
31
|
+
minute: now.minutes()
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.log('Keine Zeitzonenkonfiguration gefunden, verwende lokale Zeit');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Fallback: Verwende lokale Systemzeit
|
|
40
|
+
const now = new Date();
|
|
41
|
+
return {
|
|
42
|
+
year: now.getFullYear(),
|
|
43
|
+
month: now.getMonth() + 1,
|
|
44
|
+
day: now.getDate(),
|
|
45
|
+
hour: now.getHours(),
|
|
46
|
+
minute: now.getMinutes()
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Funktion zum Laden der Geburtsdaten aus der Konfiguration
|
|
51
|
+
function getBirthDataFromConfig() {
|
|
52
|
+
try {
|
|
53
|
+
const configPath = path.join(__dirname, '../../astrocli-config.json');
|
|
54
|
+
if (fs.existsSync(configPath)) {
|
|
55
|
+
const configData = fs.readFileSync(configPath, 'utf8');
|
|
56
|
+
const config = JSON.parse(configData);
|
|
57
|
+
|
|
58
|
+
if (config && config.birthData) {
|
|
59
|
+
// Parse Geburtsdatum (Format: TT.MM.JJJJ)
|
|
60
|
+
const dateParts = config.birthData.date.split('.');
|
|
61
|
+
const day = parseInt(dateParts[0]);
|
|
62
|
+
const month = parseInt(dateParts[1]);
|
|
63
|
+
const year = parseInt(dateParts[2]);
|
|
64
|
+
|
|
65
|
+
// Parse Geburtszeit (Format: HH:MM)
|
|
66
|
+
const timeParts = config.birthData.time.split(':');
|
|
67
|
+
const hour = parseInt(timeParts[0]);
|
|
68
|
+
const minute = parseInt(timeParts[1]);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
year: year,
|
|
72
|
+
month: month,
|
|
73
|
+
day: day,
|
|
74
|
+
hour: hour,
|
|
75
|
+
minute: minute,
|
|
76
|
+
location: config.birthData.location
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.log('Fehler beim Laden der Geburtsdaten:', error.message);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Funktion zur Berechnung des Julian Days in UTC
|
|
88
|
+
function calculateJulianDayUTC(dateComponents, timezoneOffsetMinutes = 0) {
|
|
89
|
+
// Berechne den Julian Day in lokaler Zeit
|
|
90
|
+
const localJulianDay = swisseph.swe_julday(
|
|
91
|
+
dateComponents.year,
|
|
92
|
+
dateComponents.month,
|
|
93
|
+
dateComponents.day,
|
|
94
|
+
dateComponents.hour + dateComponents.minute / 60,
|
|
95
|
+
swisseph.SE_GREG_CAL
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Konvertiere zu UTC, indem wir die Zeitzonenverschiebung berücksichtigen
|
|
99
|
+
// Die Swiss Ephemeris erwartet UTC, also müssen wir die Zeitzone anpassen
|
|
100
|
+
// timezoneOffsetMinutes ist (Lokalzeit - UTC) in Minuten.
|
|
101
|
+
// In JS gibt getTimezoneOffset() (UTC - Lokalzeit) in Minuten zurück.
|
|
102
|
+
// Wir müssen also vorsichtig sein, welches Format wir verwenden.
|
|
103
|
+
// Wenn timezoneOffsetMinutes (Lokalzeit - UTC) ist, dann:
|
|
104
|
+
// utcJulianDay = localJulianDay - (offset / 1440)
|
|
105
|
+
const utcJulianDay = localJulianDay - (timezoneOffsetMinutes / 1440); // 1440 Minuten pro Tag
|
|
106
|
+
|
|
107
|
+
return utcJulianDay;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Hilfsfunktion zur Ermittlung des Zeitzonen-Offsets für ein bestimmtes Datum und einen Ort.
|
|
112
|
+
* @param {Object} dateComponents - {year, month, day, hour, minute}
|
|
113
|
+
* @param {string} timezone - Zeitzone (z.B. "Europe/Zurich")
|
|
114
|
+
* @returns {number} Offset in Minuten (Lokalzeit - UTC)
|
|
115
|
+
*/
|
|
116
|
+
function getTimezoneOffset(dateComponents, timezone) {
|
|
117
|
+
if (!timezone) return -new Date().getTimezoneOffset();
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const dateStr = `${dateComponents.year}-${String(dateComponents.month).padStart(2, '0')}-${String(dateComponents.day).padStart(2, '0')} ${String(dateComponents.hour).padStart(2, '0')}:${String(dateComponents.minute).padStart(2, '0')}`;
|
|
121
|
+
const m = moment.tz(dateStr, "YYYY-MM-DD HH:mm", timezone);
|
|
122
|
+
return m.utcOffset(); // Gibt Offset in Minuten zurück (z.B. 120 für UTC+2)
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error('Fehler bei der Offset-Berechnung:', error);
|
|
125
|
+
return -new Date().getTimezoneOffset();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Funktion zur Berechnung der Häuser
|
|
130
|
+
function calculateHouses(julianDay, houseSystem = 'K', useBirthLocation = false) {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
// Lade die konfigurierten Standortdaten
|
|
133
|
+
const configPath = path.join(__dirname, '../../astrocli-config.json');
|
|
134
|
+
let config;
|
|
135
|
+
try {
|
|
136
|
+
if (fs.existsSync(configPath)) {
|
|
137
|
+
const configData = fs.readFileSync(configPath, 'utf8');
|
|
138
|
+
config = JSON.parse(configData);
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.log('Keine Konfiguration gefunden, verwende Standardort (Berlin)');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let latitude, longitude, locationName;
|
|
145
|
+
|
|
146
|
+
if (useBirthLocation && config && config.birthData && config.birthData.location) {
|
|
147
|
+
// Verwende Geburtsort für Geburtscharts
|
|
148
|
+
latitude = config.birthData.location.latitude;
|
|
149
|
+
longitude = config.birthData.location.longitude;
|
|
150
|
+
locationName = `${config.birthData.location.name}, ${config.birthData.location.country}`;
|
|
151
|
+
console.log(`Verwende Geburtsort: ${locationName} (${latitude}° Breitengrad, ${longitude}° Längengrad)`);
|
|
152
|
+
} else if (useBirthLocation) {
|
|
153
|
+
// Geburtsort ist konfiguriert, aber kein Standort - verwende Standardort
|
|
154
|
+
latitude = defaultLatitude;
|
|
155
|
+
longitude = defaultLongitude;
|
|
156
|
+
locationName = 'Berlin, Deutschland';
|
|
157
|
+
console.log(`Verwende Standard-Geburtsort: ${locationName} (${latitude}° Breitengrad, ${longitude}° Längengrad)`);
|
|
158
|
+
console.log('⚠️ Kein Geburtsort in der Konfiguration gefunden.');
|
|
159
|
+
} else {
|
|
160
|
+
// Verwende aktuellen Standort für aktuelle Berechnungen
|
|
161
|
+
latitude = config && config.currentLocation ? config.currentLocation.latitude : defaultLatitude;
|
|
162
|
+
longitude = config && config.currentLocation ? config.currentLocation.longitude : defaultLongitude;
|
|
163
|
+
locationName = config && config.currentLocation ? `${config.currentLocation.name}, ${config.currentLocation.country}` : 'Berlin, Deutschland';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
swisseph.swe_houses(julianDay, latitude, longitude, houseSystem, function(result) {
|
|
167
|
+
if (result.error) {
|
|
168
|
+
console.error('Fehler bei der Hausberechnung:', result.error);
|
|
169
|
+
reject(result.error);
|
|
170
|
+
} else {
|
|
171
|
+
// Die Swiss Ephemeris gibt im result.house Objekt oft Indices als Strings zurück ("1", "2", ...)
|
|
172
|
+
// Wir konvertieren dies in ein sauberes 0-basiertes Array (0-11) für eine konsistente Verarbeitung.
|
|
173
|
+
const houseCusps = [];
|
|
174
|
+
|
|
175
|
+
// Wir versuchen zuerst die Indizes 1 bis 12 direkt abzurufen
|
|
176
|
+
for (let i = 1; i <= 12; i++) {
|
|
177
|
+
const cusp = result.house[i];
|
|
178
|
+
if (cusp !== undefined) {
|
|
179
|
+
houseCusps.push(cusp);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Falls wir nicht 12 Häuser haben (z.B. wenn das Objekt andere Keys hat),
|
|
184
|
+
// versuchen wir alle numerischen Keys zu sammeln
|
|
185
|
+
if (houseCusps.length < 12) {
|
|
186
|
+
const keys = Object.keys(result.house).filter(k => !isNaN(k)).sort((a,b) => parseInt(a) - parseInt(b));
|
|
187
|
+
|
|
188
|
+
if (keys.length >= 13) {
|
|
189
|
+
// Wahrscheinlich Index 0 = AC, 1-12 = Häuser
|
|
190
|
+
houseCusps.length = 0;
|
|
191
|
+
for (let i = 1; i <= 12; i++) {
|
|
192
|
+
const key = keys.find(k => parseInt(k) === i);
|
|
193
|
+
if (key !== undefined) {
|
|
194
|
+
houseCusps.push(result.house[key]);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} else if (keys.length === 12) {
|
|
198
|
+
// Wahrscheinlich direkt 0-11 = Häuser
|
|
199
|
+
houseCusps.length = 0;
|
|
200
|
+
for (let i = 0; i < 12; i++) {
|
|
201
|
+
houseCusps.push(result.house[keys[i]]);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Falls wir immer noch keine 12 Häuser haben (sollte nicht passieren),
|
|
207
|
+
// füllen wir mit den verfügbaren Daten auf oder 0
|
|
208
|
+
while (houseCusps.length < 12) {
|
|
209
|
+
houseCusps.push(0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const formattedResult = {
|
|
213
|
+
...result,
|
|
214
|
+
house: houseCusps
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
resolve(formattedResult);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Funktion zur Bestimmung des Hauses für einen Planeten
|
|
224
|
+
function getPlanetHouse(planetLongitude, houseCusps) {
|
|
225
|
+
// Normalisiere die Planetenlänge auf 0-360 Bereich
|
|
226
|
+
planetLongitude = planetLongitude % 360;
|
|
227
|
+
if (planetLongitude < 0) planetLongitude += 360;
|
|
228
|
+
|
|
229
|
+
// Hausgrenzen sind in houseCusps[0] bis houseCusps[11]
|
|
230
|
+
// Wir müssen finden, zwischen welchen zwei Hausspitzen der Planet liegt
|
|
231
|
+
for (let i = 0; i < 12; i++) {
|
|
232
|
+
const currentCusp = houseCusps[i];
|
|
233
|
+
const nextCusp = houseCusps[(i + 1) % 12];
|
|
234
|
+
|
|
235
|
+
// Normalisiere die Hausspitzen auf 0-360 Bereich
|
|
236
|
+
const normalizedCurrentCusp = currentCusp % 360;
|
|
237
|
+
const normalizedNextCusp = nextCusp % 360;
|
|
238
|
+
|
|
239
|
+
// Berücksichtige den Übergang über 360°
|
|
240
|
+
if (normalizedNextCusp < normalizedCurrentCusp) {
|
|
241
|
+
// Fall: Hausgrenze überquert 0°/360°
|
|
242
|
+
// Ein Planet ist in diesem Haus, wenn er >= der aktuellen Hausspitze ODER < der nächsten Hausspitze ist
|
|
243
|
+
if (planetLongitude >= normalizedCurrentCusp || planetLongitude < normalizedNextCusp) {
|
|
244
|
+
return i + 1; // Häuser sind 1-basiert
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
// Normalfall
|
|
248
|
+
if (planetLongitude >= normalizedCurrentCusp && planetLongitude < normalizedNextCusp) {
|
|
249
|
+
return i + 1; // Häuser sind 1-basiert
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Debug: Zeige die Hausspitzen an, wenn kein Haus gefunden wurde
|
|
255
|
+
console.log('Kein Haus gefunden für Planet:', planetLongitude);
|
|
256
|
+
console.log('Hausspitzen:', houseCusps);
|
|
257
|
+
|
|
258
|
+
// Fallback: Sollte nicht vorkommen
|
|
259
|
+
return 1;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Funktion zur Berechnung der astrologischen Daten
|
|
263
|
+
function getAstrologicalData(planetName, customDate = null) {
|
|
264
|
+
const planet = planets[planetName];
|
|
265
|
+
if (planet === undefined) {
|
|
266
|
+
console.error(`Ungültiger Planet: ${planetName}. Verfügbare Planeten:`, Object.keys(planets).join(', '));
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Verwende das angegebene Datum oder das aktuelle Datum (mit Zeitzonenberücksichtigung)
|
|
271
|
+
let calcYear, calcMonth, calcDay, calcHour, calcMinute;
|
|
272
|
+
|
|
273
|
+
if (customDate) {
|
|
274
|
+
calcYear = customDate.year;
|
|
275
|
+
calcMonth = customDate.month;
|
|
276
|
+
calcDay = customDate.day;
|
|
277
|
+
calcHour = customDate.hour;
|
|
278
|
+
calcMinute = customDate.minute;
|
|
279
|
+
} else {
|
|
280
|
+
// Verwende die aktuelle Zeit in der konfigurierten Zeitzone
|
|
281
|
+
const timeData = getCurrentTimeInTimezone();
|
|
282
|
+
calcYear = timeData.year;
|
|
283
|
+
calcMonth = timeData.month;
|
|
284
|
+
calcDay = timeData.day;
|
|
285
|
+
calcHour = timeData.hour;
|
|
286
|
+
calcMinute = timeData.minute;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const julianDay = swisseph.swe_julday(calcYear, calcMonth, calcDay, calcHour + calcMinute / 60, swisseph.SE_GREG_CAL);
|
|
290
|
+
const flag = swisseph.SEFLG_SWIEPH | swisseph.SEFLG_SPEED;
|
|
291
|
+
const result = swisseph.swe_calc_ut(julianDay, planet, flag);
|
|
292
|
+
|
|
293
|
+
if (result.error) {
|
|
294
|
+
console.error('Fehler bei der Berechnung:', result.error);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const longitude = result.longitude;
|
|
299
|
+
const signIndex = Math.floor(longitude / 30);
|
|
300
|
+
const degreeInSign = (longitude % 30).toFixed(2);
|
|
301
|
+
const sign = signs[signIndex];
|
|
302
|
+
const element = elements[signIndex];
|
|
303
|
+
const decan = decans[Math.floor((longitude % 30) / 10)];
|
|
304
|
+
|
|
305
|
+
const dignityInfo = dignities[planet];
|
|
306
|
+
let dignity = 'Neutral';
|
|
307
|
+
if (sign === dignityInfo.sign) {
|
|
308
|
+
dignity = 'Herrscher';
|
|
309
|
+
} else if (sign === dignityInfo.exaltation) {
|
|
310
|
+
dignity = 'Erhöhung';
|
|
311
|
+
} else if (sign === dignityInfo.fall) {
|
|
312
|
+
dignity = 'Fall';
|
|
313
|
+
} else if (sign === dignityInfo.detriment) {
|
|
314
|
+
dignity = 'Detriment';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
planet: planetName,
|
|
319
|
+
longitude,
|
|
320
|
+
sign,
|
|
321
|
+
degreeInSign,
|
|
322
|
+
dignity,
|
|
323
|
+
element,
|
|
324
|
+
decan
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Funktion zur Identifizierung kritischer Planeten
|
|
329
|
+
function getCriticalPlanets(customDate = null) {
|
|
330
|
+
const criticalPlanets = [];
|
|
331
|
+
|
|
332
|
+
// Kritische Grade: 0°, 13°, 26° (kardinale Grade) und 29° (anaretischer Grad)
|
|
333
|
+
const criticalDegrees = [0, 13, 26, 29];
|
|
334
|
+
const orb = 1; // Toleranz von 1 Grad
|
|
335
|
+
|
|
336
|
+
// Verwende das angegebene Datum oder die aktuelle Zeit
|
|
337
|
+
let timeData;
|
|
338
|
+
if (customDate) {
|
|
339
|
+
timeData = customDate;
|
|
340
|
+
} else {
|
|
341
|
+
timeData = getCurrentTimeInTimezone();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const julianDay = swisseph.swe_julday(
|
|
345
|
+
timeData.year,
|
|
346
|
+
timeData.month,
|
|
347
|
+
timeData.day,
|
|
348
|
+
timeData.hour + timeData.minute / 60,
|
|
349
|
+
swisseph.SE_GREG_CAL
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
// Berechne Positionen aller Planeten
|
|
353
|
+
for (const [name, planetId] of Object.entries(planets)) {
|
|
354
|
+
const flag = swisseph.SEFLG_SWIEPH | swisseph.SEFLG_SPEED;
|
|
355
|
+
const result = swisseph.swe_calc_ut(julianDay, planetId, flag);
|
|
356
|
+
|
|
357
|
+
if (result.error) {
|
|
358
|
+
console.error(`Fehler bei der Berechnung für ${name}:`, result.error);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const longitude = result.longitude;
|
|
363
|
+
const degreeInSign = longitude % 30;
|
|
364
|
+
const signIndex = Math.floor(longitude / 30);
|
|
365
|
+
const sign = signs[signIndex];
|
|
366
|
+
|
|
367
|
+
// Prüfe, ob der Planet auf einem kritischen Grad steht
|
|
368
|
+
const isCritical = criticalDegrees.some(criticalDegree => {
|
|
369
|
+
return Math.abs(degreeInSign - criticalDegree) <= orb;
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
if (isCritical) {
|
|
373
|
+
criticalPlanets.push({
|
|
374
|
+
name,
|
|
375
|
+
sign,
|
|
376
|
+
degree: degreeInSign.toFixed(2),
|
|
377
|
+
isCritical: true,
|
|
378
|
+
criticalType: degreeInSign >= 28.5 ? 'Anaretisch (29°)' : 'Kardinal (0°, 13°, 26°)'
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return criticalPlanets;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Funktion zur Berechnung von Planetenaspekten
|
|
387
|
+
function calculatePlanetAspects(planetName, dateComponents, useHuberOrbs = false) {
|
|
388
|
+
// Berechne Planetenposition
|
|
389
|
+
const targetPlanetData = getAstrologicalData(planetName, dateComponents);
|
|
390
|
+
const targetPlanetLongitude = targetPlanetData.longitude;
|
|
391
|
+
|
|
392
|
+
// Berechne Positionen aller anderen Planeten
|
|
393
|
+
const planetPositions = {};
|
|
394
|
+
for (const [name, planetId] of Object.entries(planets)) {
|
|
395
|
+
if (name === planetName) continue;
|
|
396
|
+
const planetData = getAstrologicalData(name, dateComponents);
|
|
397
|
+
planetPositions[name] = planetData.longitude;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Berechne Aspekte (Konjunktion, Opposition, Quadrat, Trigon, Sextil)
|
|
401
|
+
const aspects = [];
|
|
402
|
+
const aspectTypes = useHuberOrbs ? [
|
|
403
|
+
{ name: 'Konjunktion', angle: 0, orb: 8 },
|
|
404
|
+
{ name: 'Opposition', angle: 180, orb: 8 },
|
|
405
|
+
{ name: 'Quadrat', angle: 90, orb: 6 },
|
|
406
|
+
{ name: 'Trigon', angle: 120, orb: 6 },
|
|
407
|
+
{ name: 'Sextil', angle: 60, orb: 4 }
|
|
408
|
+
] : [
|
|
409
|
+
{ name: 'Konjunktion', angle: 0, orb: 10 },
|
|
410
|
+
{ name: 'Opposition', angle: 180, orb: 10 },
|
|
411
|
+
{ name: 'Quadrat', angle: 90, orb: 8 },
|
|
412
|
+
{ name: 'Trigon', angle: 120, orb: 8 },
|
|
413
|
+
{ name: 'Sextil', angle: 60, orb: 6 }
|
|
414
|
+
];
|
|
415
|
+
|
|
416
|
+
for (const [name, planetLongitude] of Object.entries(planetPositions)) {
|
|
417
|
+
const angleDiff = Math.abs(targetPlanetLongitude - planetLongitude) % 360;
|
|
418
|
+
const normalizedAngle = Math.min(angleDiff, 360 - angleDiff);
|
|
419
|
+
|
|
420
|
+
for (const aspect of aspectTypes) {
|
|
421
|
+
if (Math.abs(normalizedAngle - aspect.angle) <= aspect.orb) {
|
|
422
|
+
aspects.push({
|
|
423
|
+
type: aspect.name,
|
|
424
|
+
planet: name,
|
|
425
|
+
angle: normalizedAngle.toFixed(2),
|
|
426
|
+
orb: Math.abs(normalizedAngle - aspect.angle).toFixed(2)
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return aspects;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Funktion zur Anzeige von Planetenaspekten
|
|
436
|
+
function showPlanetAspects(planetName, dateComponents, useBirthData = false, useHuberOrbs = false) {
|
|
437
|
+
const aspects = calculatePlanetAspects(planetName, dateComponents, useHuberOrbs);
|
|
438
|
+
|
|
439
|
+
const planetLabel = planetName.charAt(0).toUpperCase() + planetName.slice(1);
|
|
440
|
+
|
|
441
|
+
console.log(`Aspekte für ${planetLabel}:`);
|
|
442
|
+
console.log('=================================================================');
|
|
443
|
+
console.log('| Aspekt | Planet | Winkel | Orb |');
|
|
444
|
+
console.log('=================================================================');
|
|
445
|
+
|
|
446
|
+
if (aspects.length === 0) {
|
|
447
|
+
console.log('Keine signifikanten Aspekte gefunden.');
|
|
448
|
+
} else {
|
|
449
|
+
aspects.forEach(aspect => {
|
|
450
|
+
const aspectName = aspect.type.padEnd(11, ' ');
|
|
451
|
+
const planetNameFormatted = aspect.planet.charAt(0).toUpperCase() + aspect.planet.slice(1);
|
|
452
|
+
const planetFormatted = planetNameFormatted.padEnd(8, ' ');
|
|
453
|
+
const angle = aspect.angle.padEnd(6, ' ');
|
|
454
|
+
const orb = aspect.orb.padEnd(4, ' ');
|
|
455
|
+
|
|
456
|
+
console.log(`| ${aspectName} | ${planetFormatted} | ${angle}° | ${orb}° |`);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
console.log('=================================================================');
|
|
461
|
+
|
|
462
|
+
if (useBirthData) {
|
|
463
|
+
console.log('\nDiese Analyse basiert auf deinem Geburtshoroskop.');
|
|
464
|
+
} else {
|
|
465
|
+
console.log('\nDiese Analyse basiert auf der aktuellen Planetenposition.');
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Funktion zur Berechnung aller aktiven Aspekte zwischen allen Planeten
|
|
470
|
+
function getAllActiveAspects(dateComponents) {
|
|
471
|
+
const allAspects = [];
|
|
472
|
+
const planetNames = Object.keys(planets);
|
|
473
|
+
const seenAspects = new Set(); // Zur Vermeidung von Duplikaten
|
|
474
|
+
|
|
475
|
+
// Berechne Aspekte für alle Planetenpaare
|
|
476
|
+
for (let i = 0; i < planetNames.length; i++) {
|
|
477
|
+
const planet1 = planetNames[i];
|
|
478
|
+
const aspects = calculatePlanetAspects(planet1, dateComponents, true); // Immer Huber-Orbs verwenden
|
|
479
|
+
|
|
480
|
+
aspects.forEach(aspect => {
|
|
481
|
+
// Erstelle eine eindeutige Kennung für den Aspekt (alphabetisch sortiert)
|
|
482
|
+
const planetPair = [planet1, aspect.planet].sort().join('-');
|
|
483
|
+
const aspectKey = `${planetPair}-${aspect.type}`;
|
|
484
|
+
|
|
485
|
+
// Füge den Aspekt nur hinzu, wenn er noch nicht existiert
|
|
486
|
+
if (!seenAspects.has(aspectKey)) {
|
|
487
|
+
seenAspects.add(aspectKey);
|
|
488
|
+
allAspects.push({
|
|
489
|
+
planet1: planet1,
|
|
490
|
+
planet2: aspect.planet,
|
|
491
|
+
type: aspect.type,
|
|
492
|
+
angle: aspect.angle,
|
|
493
|
+
orb: aspect.orb
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return allAspects;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Funktion zur Überprüfung der Rückläufigkeit eines Planeten
|
|
503
|
+
function isPlanetRetrograde(planetName, dateComponents) {
|
|
504
|
+
try {
|
|
505
|
+
const planet = planets[planetName];
|
|
506
|
+
if (planet === undefined) return false;
|
|
507
|
+
|
|
508
|
+
// Berechne die Position mit Geschwindigkeitsinformation
|
|
509
|
+
const julianDay = swisseph.swe_julday(
|
|
510
|
+
dateComponents.year,
|
|
511
|
+
dateComponents.month,
|
|
512
|
+
dateComponents.day,
|
|
513
|
+
dateComponents.hour + dateComponents.minute / 60,
|
|
514
|
+
swisseph.SE_GREG_CAL
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
const flag = swisseph.SEFLG_SWIEPH | swisseph.SEFLG_SPEED;
|
|
518
|
+
const result = swisseph.swe_calc_ut(julianDay, planet, flag);
|
|
519
|
+
|
|
520
|
+
if (result.error) {
|
|
521
|
+
console.log('Fehler bei Rückläufigkeitsprüfung:', result.error);
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Die Geschwindigkeit befindet sich in result.longitudeSpeed
|
|
526
|
+
// Swiss Ephemeris gibt die Geschwindigkeit als separate Eigenschaften zurück
|
|
527
|
+
if (result.longitudeSpeed !== undefined) {
|
|
528
|
+
return result.longitudeSpeed < 0;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return false;
|
|
532
|
+
} catch (error) {
|
|
533
|
+
console.log('Fehler in isPlanetRetrograde:', error.message);
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Funktion zur Bestimmung der Aspekt-Phase (annähernd, exakt, separativ)
|
|
539
|
+
function determineAspectPhase(planet1, planet2, dateComponents, aspectType, targetAngle) {
|
|
540
|
+
// Berechne aktuelle Positionen und Geschwindigkeiten
|
|
541
|
+
const planet1Data = getAstrologicalData(planet1, dateComponents);
|
|
542
|
+
const planet2Data = getAstrologicalData(planet2, dateComponents);
|
|
543
|
+
|
|
544
|
+
// Berechne Positionen für einen leicht späteren Zeitpunkt (1 Tag später)
|
|
545
|
+
const nextDate = {
|
|
546
|
+
year: dateComponents.year,
|
|
547
|
+
month: dateComponents.month,
|
|
548
|
+
day: dateComponents.day + 1,
|
|
549
|
+
hour: dateComponents.hour,
|
|
550
|
+
minute: dateComponents.minute
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
const planet1NextData = getAstrologicalData(planet1, nextDate);
|
|
554
|
+
const planet2NextData = getAstrologicalData(planet2, nextDate);
|
|
555
|
+
|
|
556
|
+
// Berechne aktuellen Winkel
|
|
557
|
+
const currentAngleDiff = Math.abs(planet1Data.longitude - planet2Data.longitude) % 360;
|
|
558
|
+
const currentNormalizedAngle = Math.min(currentAngleDiff, 360 - currentAngleDiff);
|
|
559
|
+
|
|
560
|
+
// Berechne zukünftigen Winkel
|
|
561
|
+
const nextAngleDiff = Math.abs(planet1NextData.longitude - planet2NextData.longitude) % 360;
|
|
562
|
+
const nextNormalizedAngle = Math.min(nextAngleDiff, 360 - nextAngleDiff);
|
|
563
|
+
|
|
564
|
+
// Bestimme die Phase basierend auf der Winkelentwicklung
|
|
565
|
+
const currentDistanceToTarget = Math.abs(currentNormalizedAngle - targetAngle);
|
|
566
|
+
const nextDistanceToTarget = Math.abs(nextNormalizedAngle - targetAngle);
|
|
567
|
+
|
|
568
|
+
// Wenn der Winkel sich dem Ziel nähert = annähernd
|
|
569
|
+
// Wenn der Winkel genau auf dem Ziel ist = exakt
|
|
570
|
+
// Wenn der Winkel sich vom Ziel entfernt = separativ
|
|
571
|
+
|
|
572
|
+
if (currentDistanceToTarget < 0.5) {
|
|
573
|
+
return 'exakt';
|
|
574
|
+
} else if (nextDistanceToTarget < currentDistanceToTarget) {
|
|
575
|
+
return 'annähernd';
|
|
576
|
+
} else {
|
|
577
|
+
return 'separativ';
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Funktion zur Analyse der Elementverteilung
|
|
582
|
+
function analyzeElementDistribution(dateComponents, useBirthData = false) {
|
|
583
|
+
const elementCounts = {
|
|
584
|
+
'Feuer': 0,
|
|
585
|
+
'Erde': 0,
|
|
586
|
+
'Luft': 0,
|
|
587
|
+
'Wasser': 0
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
const planetElements = {};
|
|
591
|
+
|
|
592
|
+
// Berechne Elemente aller Planeten
|
|
593
|
+
for (const [name, planetId] of Object.entries(planets)) {
|
|
594
|
+
const data = getAstrologicalData(name, dateComponents);
|
|
595
|
+
elementCounts[data.element]++;
|
|
596
|
+
planetElements[name] = data.element;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Berechne Gesamtzahl der Planeten
|
|
600
|
+
const totalPlanets = Object.keys(planets).length;
|
|
601
|
+
|
|
602
|
+
// Berechne Prozentsätze
|
|
603
|
+
const elementPercentages = {};
|
|
604
|
+
for (const [element, count] of Object.entries(elementCounts)) {
|
|
605
|
+
elementPercentages[element] = ((count / totalPlanets) * 100).toFixed(1);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Erstelle horizontales Chart
|
|
609
|
+
console.log('Elementverteilung der Planeten:');
|
|
610
|
+
console.log('================================================================================');
|
|
611
|
+
|
|
612
|
+
// Maximale Balkenlänge (pro Planet)
|
|
613
|
+
const maxBars = 20;
|
|
614
|
+
|
|
615
|
+
for (const [element, count] of Object.entries(elementCounts)) {
|
|
616
|
+
const percentage = elementPercentages[element];
|
|
617
|
+
const barLength = Math.round((count / totalPlanets) * maxBars);
|
|
618
|
+
const bar = '|'.repeat(barLength);
|
|
619
|
+
const paddedBar = bar.padEnd(maxBars, ' ');
|
|
620
|
+
|
|
621
|
+
console.log(`${element.padEnd(10)}: ${paddedBar} ${barLength.toString().padStart(2)}/${totalPlanets} (${percentage}%)`);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
console.log('================================================================================');
|
|
625
|
+
|
|
626
|
+
// Zeige detaillierte Planeten-Element-Zuordnung
|
|
627
|
+
console.log('\nPlaneten nach Elementen:');
|
|
628
|
+
for (const [element, count] of Object.entries(elementCounts)) {
|
|
629
|
+
const planetsInElement = Object.entries(planetElements)
|
|
630
|
+
.filter(([_, el]) => el === element)
|
|
631
|
+
.map(([name]) => name.charAt(0).toUpperCase() + name.slice(1))
|
|
632
|
+
.join(', ');
|
|
633
|
+
|
|
634
|
+
console.log(`${element}: ${planetsInElement}`);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (useBirthData) {
|
|
638
|
+
console.log('\nDiese Analyse basiert auf deinem Geburtshoroskop.');
|
|
639
|
+
} else {
|
|
640
|
+
console.log('\nDiese Analyse basiert auf der aktuellen Planetenposition.');
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return {
|
|
644
|
+
elementCounts,
|
|
645
|
+
elementPercentages,
|
|
646
|
+
planetElements
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Funktion zur Anzeige aller aktiven Aspekte
|
|
651
|
+
function showAllActiveAspects(dateComponents, useBirthData = false) {
|
|
652
|
+
const allAspects = getAllActiveAspects(dateComponents);
|
|
653
|
+
|
|
654
|
+
// Sortiere Aspekte nach Typ und dann nach Orb (genaueste zuerst)
|
|
655
|
+
const sortedAspects = [...allAspects].sort((a, b) => {
|
|
656
|
+
// Sortiere zuerst nach Aspekt-Typ
|
|
657
|
+
const aspectOrder = {
|
|
658
|
+
'Konjunktion': 1,
|
|
659
|
+
'Opposition': 2,
|
|
660
|
+
'Quadrat': 3,
|
|
661
|
+
'Trigon': 4,
|
|
662
|
+
'Sextil': 5
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
const typeCompare = aspectOrder[a.type] - aspectOrder[b.type];
|
|
666
|
+
if (typeCompare !== 0) return typeCompare;
|
|
667
|
+
|
|
668
|
+
// Dann nach Orb (kleinster Orb zuerst = genaueste Aspekte)
|
|
669
|
+
return parseFloat(a.orb) - parseFloat(b.orb);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
console.log('Aktive Aspekte (alle Planeten) - Klassifiziert nach Präzision:');
|
|
673
|
+
console.log('==========================================================================');
|
|
674
|
+
console.log('| Planet 1 | Planet 2 | Winkel | Orb | Status |');
|
|
675
|
+
console.log('==========================================================================');
|
|
676
|
+
|
|
677
|
+
if (sortedAspects.length === 0) {
|
|
678
|
+
console.log('Keine signifikanten Aspekte gefunden.');
|
|
679
|
+
} else {
|
|
680
|
+
// Gruppiere nach Aspekt-Typ für bessere Übersicht
|
|
681
|
+
const aspectsByType = {};
|
|
682
|
+
sortedAspects.forEach(aspect => {
|
|
683
|
+
if (!aspectsByType[aspect.type]) {
|
|
684
|
+
aspectsByType[aspect.type] = [];
|
|
685
|
+
}
|
|
686
|
+
aspectsByType[aspect.type].push(aspect);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// Zeige Aspekte gruppiert nach Typ
|
|
690
|
+
const aspectTypes = ['Konjunktion', 'Opposition', 'Quadrat', 'Trigon', 'Sextil'];
|
|
691
|
+
aspectTypes.forEach(type => {
|
|
692
|
+
if (aspectsByType[type]) {
|
|
693
|
+
console.log(`\n--- ${type} ---`);
|
|
694
|
+
aspectsByType[type].forEach(aspect => {
|
|
695
|
+
// Prüfe Rückläufigkeit für beide Planeten
|
|
696
|
+
const planet1Retrograde = isPlanetRetrograde(aspect.planet1, dateComponents);
|
|
697
|
+
const planet2Retrograde = isPlanetRetrograde(aspect.planet2, dateComponents);
|
|
698
|
+
|
|
699
|
+
// Formatiere Planetenamen mit Rückläufigkeitskennzeichnung (R)
|
|
700
|
+
const planet1Formatted = aspect.planet1.charAt(0).toUpperCase() + aspect.planet1.slice(1) + (planet1Retrograde ? '(R)' : '');
|
|
701
|
+
const planet1Padded = planet1Formatted.padEnd(12, ' ');
|
|
702
|
+
const planet2Formatted = aspect.planet2.charAt(0).toUpperCase() + aspect.planet2.slice(1) + (planet2Retrograde ? '(R)' : '');
|
|
703
|
+
const planet2Padded = planet2Formatted.padEnd(12, ' ');
|
|
704
|
+
const angle = aspect.angle.padEnd(6, ' ');
|
|
705
|
+
const orb = aspect.orb.padEnd(4, ' ');
|
|
706
|
+
|
|
707
|
+
// Bestimme die Aspekt-Phase basierend auf der Bewegungsrichtung
|
|
708
|
+
const aspectAngles = {
|
|
709
|
+
'Konjunktion': 0,
|
|
710
|
+
'Opposition': 180,
|
|
711
|
+
'Quadrat': 90,
|
|
712
|
+
'Trigon': 120,
|
|
713
|
+
'Sextil': 60
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
const phase = determineAspectPhase(
|
|
717
|
+
aspect.planet1,
|
|
718
|
+
aspect.planet2,
|
|
719
|
+
dateComponents,
|
|
720
|
+
aspect.type,
|
|
721
|
+
aspectAngles[aspect.type]
|
|
722
|
+
);
|
|
723
|
+
const statusPadded = phase.padEnd(11, ' ');
|
|
724
|
+
|
|
725
|
+
console.log(`| ${planet1Padded} | ${planet2Padded} | ${angle}° | ${orb}° | ${statusPadded} |`);
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
console.log('\n==========================================================================');
|
|
732
|
+
console.log(`Gesamt: ${sortedAspects.length} aktive Aspekte gefunden`);
|
|
733
|
+
|
|
734
|
+
if (useBirthData) {
|
|
735
|
+
console.log('\nDiese Analyse basiert auf deinem Geburtshoroskop.');
|
|
736
|
+
} else {
|
|
737
|
+
console.log('\nDiese Analyse basiert auf der aktuellen Planetenposition.');
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
console.log('(R) = Rückläufiger Planet');
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
module.exports = {
|
|
744
|
+
calculateHouses,
|
|
745
|
+
getPlanetHouse,
|
|
746
|
+
getAstrologicalData,
|
|
747
|
+
calculatePlanetAspects,
|
|
748
|
+
showPlanetAspects,
|
|
749
|
+
getAllActiveAspects,
|
|
750
|
+
showAllActiveAspects,
|
|
751
|
+
getBirthDataFromConfig,
|
|
752
|
+
getCriticalPlanets,
|
|
753
|
+
getCurrentTimeInTimezone,
|
|
754
|
+
getTimezoneOffset,
|
|
755
|
+
isPlanetRetrograde,
|
|
756
|
+
determineAspectPhase,
|
|
757
|
+
calculateJulianDayUTC,
|
|
758
|
+
analyzeElementDistribution,
|
|
759
|
+
getPastAspects,
|
|
760
|
+
getAspectAngle,
|
|
761
|
+
getFutureAspects
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
// Funktion zur Berechnung vergangener Aspekte zwischen zwei Planeten
|
|
765
|
+
function getPastAspects(planet1, planet2, aspectType, count, endDate = null) {
|
|
766
|
+
// Standardmäßig verwenden wir das aktuelle Datum als Enddatum
|
|
767
|
+
if (!endDate) {
|
|
768
|
+
endDate = getCurrentTimeInTimezone();
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const pastAspects = [];
|
|
772
|
+
const targetAngle = getAspectAngle(aspectType);
|
|
773
|
+
|
|
774
|
+
if (targetAngle === null) {
|
|
775
|
+
console.error(`Ungültiger Aspekt-Typ: ${aspectType}`);
|
|
776
|
+
return [];
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Wir gehen rückwärts in der Zeit, beginnend vom Enddatum
|
|
780
|
+
// Jeder Schritt ist 1 Tag rückwärts
|
|
781
|
+
let currentDate = {...endDate};
|
|
782
|
+
|
|
783
|
+
// Maximal 500 Jahre zurückgehen, um Endlosschleifen zu vermeiden
|
|
784
|
+
const maxYears = 500;
|
|
785
|
+
const daysToCheck = maxYears * 365;
|
|
786
|
+
|
|
787
|
+
// Wir verwenden eine sehr kleine Toleranz für exakte Aspekte (0.00001 Grad)
|
|
788
|
+
const exactOrb = 0.01;
|
|
789
|
+
|
|
790
|
+
// Gehe rückwärts durch die Zeit
|
|
791
|
+
for (let i = 0; i < daysToCheck && pastAspects.length < count; i++) {
|
|
792
|
+
// Berechne einen Tag früher
|
|
793
|
+
currentDate = subtractDays(currentDate, 1);
|
|
794
|
+
|
|
795
|
+
// Berechne die Positionen der beiden Planeten
|
|
796
|
+
const planet1Data = getAstrologicalData(planet1, currentDate);
|
|
797
|
+
const planet2Data = getAstrologicalData(planet2, currentDate);
|
|
798
|
+
|
|
799
|
+
// Berechne den Winkel zwischen den Planeten
|
|
800
|
+
const angleDiff = Math.abs(planet1Data.longitude - planet2Data.longitude) % 360;
|
|
801
|
+
const normalizedAngle = Math.min(angleDiff, 360 - angleDiff);
|
|
802
|
+
|
|
803
|
+
// Prüfe, ob der Winkel exakt dem Zielwinkel entspricht (mit minimaler ranz)
|
|
804
|
+
if (Math.abs(normalizedAngle - targetAngle) <= exactOrb) {
|
|
805
|
+
// Prüfe, ob dies ein exakter Aspekt ist (nicht nur annähernd)
|
|
806
|
+
// Wir prüfen auch den nächsten Tag, um sicherzustellen, dass es sich um einen exakten Punkt handelt
|
|
807
|
+
const nextDate = addDays(currentDate, 1);
|
|
808
|
+
const planet1NextData = getAstrologicalData(planet1, nextDate);
|
|
809
|
+
const planet2NextData = getAstrologicalData(planet2, nextDate);
|
|
810
|
+
|
|
811
|
+
const nextAngleDiff = Math.abs(planet1NextData.longitude - planet2NextData.longitude) % 360;
|
|
812
|
+
const nextNormalizedAngle = Math.min(nextAngleDiff, 360 - nextAngleDiff);
|
|
813
|
+
|
|
814
|
+
// Wenn der Winkel sich vom Ziel entfernt, ist dies ein exakter Aspekt
|
|
815
|
+
const currentDistance = Math.abs(normalizedAngle - targetAngle);
|
|
816
|
+
const nextDistance = Math.abs(nextNormalizedAngle - targetAngle);
|
|
817
|
+
|
|
818
|
+
if (nextDistance > currentDistance) {
|
|
819
|
+
// Dies ist ein exakter Aspekt
|
|
820
|
+
// Wir prüfen auch, ob dieser Aspekt bereits in der Liste ist (mit 3 Tagen Abstand)
|
|
821
|
+
const isDuplicate = pastAspects.some(aspect => {
|
|
822
|
+
const daysDiff = Math.abs(
|
|
823
|
+
new Date(aspect.date.year, aspect.date.month - 1, aspect.date.day) -
|
|
824
|
+
new Date(currentDate.year, currentDate.month - 1, currentDate.day)
|
|
825
|
+
) / (1000 * 60 * 60 * 24);
|
|
826
|
+
return daysDiff < 3; // Mindestens 3 Tage Abstand zwischen Aspekten
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
if (!isDuplicate) {
|
|
830
|
+
pastAspects.push({
|
|
831
|
+
date: {...currentDate},
|
|
832
|
+
planet1: planet1,
|
|
833
|
+
planet2: planet2,
|
|
834
|
+
type: aspectType,
|
|
835
|
+
angle: normalizedAngle.toFixed(2),
|
|
836
|
+
planet1Position: `${planet1Data.sign} ${planet1Data.degreeInSign}°`,
|
|
837
|
+
planet2Position: `${planet2Data.sign} ${planet2Data.degreeInSign}°`
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
return pastAspects;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Hilfsfunktion zur Bestimmung des Zielwinkels für einen Aspekt
|
|
848
|
+
function getAspectAngle(aspectType) {
|
|
849
|
+
const aspectAngles = {
|
|
850
|
+
'k': 0, // Konjunktion
|
|
851
|
+
'konjunktion': 0,
|
|
852
|
+
'o': 180, // Opposition
|
|
853
|
+
'opposition': 180,
|
|
854
|
+
'q': 90, // Quadrat
|
|
855
|
+
'quadrat': 90,
|
|
856
|
+
't': 120, // Trigon
|
|
857
|
+
'trigon': 120,
|
|
858
|
+
's': 60, // Sextil
|
|
859
|
+
'sextil': 60
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
return aspectAngles[aspectType.toLowerCase()] !== undefined
|
|
863
|
+
? aspectAngles[aspectType.toLowerCase()]
|
|
864
|
+
: null;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Hilfsfunktion zum Subtrahieren von Tagen von einem Datum (unterstützt auch gebrochene Tage)
|
|
868
|
+
function subtractDays(date, days) {
|
|
869
|
+
// Konvertiere das Datum in Millisekunden seit Epoche
|
|
870
|
+
const originalDate = new Date(date.year, date.month - 1, date.day, date.hour, date.minute);
|
|
871
|
+
const millisecondsPerDay = 24 * 60 * 60 * 1000;
|
|
872
|
+
const millisecondsToSubtract = days * millisecondsPerDay;
|
|
873
|
+
|
|
874
|
+
const newDate = new Date(originalDate.getTime() - millisecondsToSubtract);
|
|
875
|
+
return {
|
|
876
|
+
year: newDate.getFullYear(),
|
|
877
|
+
month: newDate.getMonth() + 1,
|
|
878
|
+
day: newDate.getDate(),
|
|
879
|
+
hour: newDate.getHours(),
|
|
880
|
+
minute: newDate.getMinutes()
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Hilfsfunktion zum Addieren von Tagen zu einem Datum (unterstützt auch gebrochene Tage)
|
|
885
|
+
function addDays(date, days) {
|
|
886
|
+
// Konvertiere das Datum in Millisekunden seit Epoche
|
|
887
|
+
const originalDate = new Date(date.year, date.month - 1, date.day, date.hour, date.minute);
|
|
888
|
+
const millisecondsPerDay = 24 * 60 * 60 * 1000;
|
|
889
|
+
const millisecondsToAdd = days * millisecondsPerDay;
|
|
890
|
+
|
|
891
|
+
const newDate = new Date(originalDate.getTime() + millisecondsToAdd);
|
|
892
|
+
return {
|
|
893
|
+
year: newDate.getFullYear(),
|
|
894
|
+
month: newDate.getMonth() + 1,
|
|
895
|
+
day: newDate.getDate(),
|
|
896
|
+
hour: newDate.getHours(),
|
|
897
|
+
minute: newDate.getMinutes()
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Funktion zur Berechnung zukünftiger Aspekte zwischen zwei Planeten
|
|
902
|
+
function getFutureAspects(planet1, planet2, aspectType, count, startDate = null) {
|
|
903
|
+
// Standardmäßig verwenden wir das aktuelle Datum als Startdatum
|
|
904
|
+
if (!startDate) {
|
|
905
|
+
startDate = getCurrentTimeInTimezone();
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const futureAspects = [];
|
|
909
|
+
const targetAngle = getAspectAngle(aspectType);
|
|
910
|
+
|
|
911
|
+
if (targetAngle === null) {
|
|
912
|
+
console.error(`Ungültiger Aspekt-Typ: ${aspectType}`);
|
|
913
|
+
return [];
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Wir gehen vorwärts in der Zeit, beginnend vom Startdatum
|
|
917
|
+
let currentDate = {...startDate};
|
|
918
|
+
|
|
919
|
+
// Maximal 1000 Jahre vorwärts gehen, um Endlosschleifen zu vermeiden
|
|
920
|
+
const maxYears = 1000;
|
|
921
|
+
const daysToCheck = maxYears * 365;
|
|
922
|
+
|
|
923
|
+
// Wir verwenden eine größere Toleranz für exakte Aspekte (0.5 Grad)
|
|
924
|
+
const exactOrb = 0.01;
|
|
925
|
+
|
|
926
|
+
// Bestimme die Schrittgröße basierend auf den Planeten
|
|
927
|
+
// Für langsame Planeten (Saturn, Uranus, Neptun, Pluto) verwenden wir kleinere Schritte
|
|
928
|
+
const slowPlanets = ['saturn', 'uranus', 'neptun', 'pluto'];
|
|
929
|
+
const isSlowAspect = slowPlanets.includes(planet1) && slowPlanets.includes(planet2);
|
|
930
|
+
const stepSize = isSlowAspect ? 0.1 : 1; // 0.1 Tage für langsame Planeten, 1 Tag für schnelle
|
|
931
|
+
|
|
932
|
+
// Gehe vorwärts durch die Zeit
|
|
933
|
+
let i = 0;
|
|
934
|
+
while (i < daysToCheck && futureAspects.length < count) {
|
|
935
|
+
// Berechne den nächsten Schritt
|
|
936
|
+
currentDate = addDays(currentDate, stepSize);
|
|
937
|
+
|
|
938
|
+
// Berechne die Positionen der beiden Planeten
|
|
939
|
+
const planet1Data = getAstrologicalData(planet1, currentDate);
|
|
940
|
+
const planet2Data = getAstrologicalData(planet2, currentDate);
|
|
941
|
+
|
|
942
|
+
// Berechne den Winkel zwischen den Planeten
|
|
943
|
+
const angleDiff = Math.abs(planet1Data.longitude - planet2Data.longitude) % 360;
|
|
944
|
+
const normalizedAngle = Math.min(angleDiff, 360 - angleDiff);
|
|
945
|
+
|
|
946
|
+
// Prüfe, ob der Winkel exakt dem Zielwinkel entspricht (mit minimaler Toleranz)
|
|
947
|
+
if (Math.abs(normalizedAngle - targetAngle) <= exactOrb) {
|
|
948
|
+
// Prüfe, ob dies ein exakter Aspekt ist (nicht nur annähernd)
|
|
949
|
+
// Wir prüfen auch den vorherigen Schritt, um sicherzustellen, dass es sich um einen exakten Punkt handelt
|
|
950
|
+
const prevDate = subtractDays(currentDate, stepSize);
|
|
951
|
+
const planet1PrevData = getAstrologicalData(planet1, prevDate);
|
|
952
|
+
const planet2PrevData = getAstrologicalData(planet2, prevDate);
|
|
953
|
+
|
|
954
|
+
const prevAngleDiff = Math.abs(planet1PrevData.longitude - planet2PrevData.longitude) % 360;
|
|
955
|
+
const prevNormalizedAngle = Math.min(prevAngleDiff, 360 - prevAngleDiff);
|
|
956
|
+
|
|
957
|
+
// Wenn der Winkel sich dem Ziel nähert, ist dies ein exakter Aspekt
|
|
958
|
+
const currentDistance = Math.abs(normalizedAngle - targetAngle);
|
|
959
|
+
const prevDistance = Math.abs(prevNormalizedAngle - targetAngle);
|
|
960
|
+
|
|
961
|
+
if (currentDistance < prevDistance) {
|
|
962
|
+
// Dies ist ein exakter Aspekt
|
|
963
|
+
// Wir prüfen auch, ob dieser Aspekt bereits in der Liste ist (mit 3 Tagen Abstand)
|
|
964
|
+
const isDuplicate = futureAspects.some(aspect => {
|
|
965
|
+
const daysDiff = Math.abs(
|
|
966
|
+
new Date(aspect.date.year, aspect.date.month - 1, aspect.date.day) -
|
|
967
|
+
new Date(currentDate.year, currentDate.month - 1, currentDate.day)
|
|
968
|
+
) / (1000 * 60 * 60 * 24);
|
|
969
|
+
return daysDiff < 3; // Mindestens 3 Tage Abstand zwischen Aspekten
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
if (!isDuplicate) {
|
|
973
|
+
futureAspects.push({
|
|
974
|
+
date: {...currentDate},
|
|
975
|
+
planet1: planet1,
|
|
976
|
+
planet2: planet2,
|
|
977
|
+
type: aspectType,
|
|
978
|
+
angle: normalizedAngle.toFixed(2),
|
|
979
|
+
planet1Position: `${planet1Data.sign} ${planet1Data.degreeInSign}°`,
|
|
980
|
+
planet2Position: `${planet2Data.sign} ${planet2Data.degreeInSign}°`
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
i += stepSize;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
return futureAspects;
|
|
990
|
+
}
|