package AstroUI; import java.applet.Applet; import java.awt.Image; import java.awt.Font; import java.awt.MediaTracker; import java.awt.Color; import java.awt.Graphics; import java.awt.Component; import java.net.URL; import java.io.BufferedReader; import java.io.StringReader; import AstroOL.Calculator; import AstroOL.ICalculator; import AstroOL.LowPrecCalculator; import HttpRequestor; /** Dieses Applet dient der Repräsentation eines Horoskops. Das Applet ist über das Javascript/Java-Interface des Browsers sowie über einen umfangreichen Satz von Parametern weitgehend extern steuerbar. So kann es sowohl für interaktive Anwendungen als auch in statischen HTML-Seiten als Graphik zur Präsentation bestimmter Horoskope verwendet werden. Der Vorteil, ein Applet statt eines "ausgehärteten" GIF zu benutzen, besteht darin, dass die Graphik zum Zeitpunkt des Aufrufs der Webseite im Browser aus den als Applet-Parameter mitgegebenen Horoskopdaten erstellt wird. Die Horoskopdaten selbst, aber auch spezielle Präsentierungsdaten (sollen die Positionen mit angezeigt werden? Welche Hintergrundfarbe soll verwendet werden? u.a.) sind in der HTML-Quelle mit wenig Aufwand änderbar <p> Das Applet nimmt entweder einen String mit Datum, Uhrzeit und Ort entgegen, berechnet die resultierenden Planeten- und Hauspositionen mittels einer ICalculator-Instanz und zeichnet sich dann — ebensogut ist es aber möglich, die Planeten- und Hauspositionen extern mitzugeben. <p> Die Kommunikation mehrerer Applets auf einer HTML-Seite wird durch das Interface {@link ILoadListener} bestimmt. Das HoroApplet implementiert dieses Interface zwar nicht, informiert aber andere Applets, die dieses Interface implementiert haben, über die erfolgreiche Beendigung des Ladevorgangs. Dies löst das Problem, dass es beim Laden einer Seite nicht definiert ist, welches Applet als erstes aufgebaut ist und damit in der Lage ist, auf Java- Anweisungen von anderen Applets zu reagieren. */ public class HoroApplet extends java.applet.Applet { /** Bilder der zwölf Tierkreiszeichen (im GIF-Format) */ public static Image[] signImage = new Image[12]; /** Bilder der zehn astrologischen Planeten (im GIF-Format) */ public static Image[] planetImage = new Image[10]; /** Ekliptikale Längen der zehn astrologischen Planeten */ public double pl[] = new double[10]; /** Ekliptikale Längen der zwölf Häuserspitzen, gezählt ab Index 1 */ public double h[] = new double[13]; /** Julianisches Datum (in <i>Weltzeit</i>, nicht Ephemeridenzeit). */ public double xjd; /** Extern mitgegebene Caption, anstelle der automatisch generierten */ public String caption; /** Geographische Länge */ public double lon; /** Geographische Breite */ public double lat; /** Kritischer Abstand (Ü;berlappung) zwischen zwei Planeten */ protected static double criticalDistance = 8.; /** noHouses = true: Keine Häuser darstellen. Wenn der Array houses ein Element house[1] enthält, so wird dieses zur Orientierung des Horoskops verwendet, <i>als wäre dies der Aszendent</i>. Der entsprechende Längenwert wird also links dargestellt. Wenn <tt>houses[]</tt> leer ist, kommt der Widderpunkt links zur Darstellung, entsprechend der Haus-Zeichen-Analogie. */ public boolean noHouses = false; protected Graphics g; protected Image bi; protected static final String houses = "ASC IIIII IV V VI"; protected static final double factor105 = 1.05; protected static final double factor1 = .85; protected static final double factor_tick1 = .83; protected static final double factor_tick2 = .81; protected static final double factor_planet = .75; protected static final double factor2 = .2; protected static final double factor_hno = .15; protected static final Font courierplain12 = new Font("Courier",Font.PLAIN, 12); protected static final Font courierbold12 = new Font("Courier",Font.BOLD, 12); protected static final Font courierbold8 = new Font("Courier",Font.BOLD, 8); protected int r,d=0; protected int xm, ym; protected int height, width; protected long precision = Calculator.SEC; protected Color bgcolor,bgcolor2; protected double rd=1d; protected double plGraph[] = { 0., 30., 60., 90., 120., 150., 180., 210., 240., 270. }; protected double tile = 0.77d; protected static MediaTracker tracker; /** called = true: Planeten- und Hauspositionen sowie caption werden mit JavaScript gesteuert, das heisst: In diesem Modus rechnet das Applet nicht selbst. Das Flag wird von den aufgerufenen Methoden setPlanetsAndHouses() und setCaption() intern gesetzt. */ protected boolean called = false; /** Die Zeile mit Ort und Zeit soll weggelassen werden */ protected boolean noTimePlace = false; /** Instanz des Planeten- und Hausberechners */ protected ICalculator calc; // Defaultkonstruktor public HoroApplet() { } /* Testmethode für Bugfixing etc. public static void main(String[] argv) { int i; HoroApplet horoa = new HoroApplet(); String servletURL = "http://localhost:8080/examples/servlet/Ephemeris?planets&houses&jd=2438498.438888889&lon=7.266666666666667&lat=52.333333333333336"; Calculator.getPlanetsAndHouses( servletURL, horoa.pl, horoa.h); horoa.plGraph = horoa.getPlGraph( horoa.pl ); for (i=0;i<10;i++) System.out.println(horoa.pl[i] + "\t-> " + horoa.plGraph[i]); } */ /** Das Applet initialisieren. In der init()-Methode werden die Tierkreis- und Planetensymbole geladen, falls dies noch nicht erfolgt ist. Das Laden erfolgt synchron — vor der Programmfortsetzung wird abgewartet, bis der Ladevorgang abgeschlossen ist. Die Graphiken müssen, von der Codebase ausgehend, im Unterverzeichnis <code>./rsc/mime</code> zu finden sein (oder im .jar-File unter diesem relativen Pfad abgelegt sein). <p> Darüberhinaus wird die korrekte <code>ICalculator</code>-Implementierung anhand des Applet-Parameters <code>calculator</code> identifiziert und eine <code>ICalculator</code>-Instanz erzeugt. Die Hintergrundfarbe (Parmeter <code>bgcolor</code> und das Platzverhätnis der Horoskopgraphik zum Datenteil (Parameter <code>tile</code>) wird errechnet. Intern benötigte Attribute werden initialisiert. */ public void init() { int i, fontHeight, fontAscent; String s; // Tierkreis- und Planetenbilder holen if (HoroApplet.signImage[0] == null) { if (tracker == null) tracker = new MediaTracker( (Component) this ); try { URL ressourceURL = new URL( getCodeBase(), "rsc/mime/" ); for ( i = 0; i < 12; i++) { HoroApplet.signImage[i] = this.getImage( ressourceURL, Calculator.sknam.substring(2*i,2*i+2)+".gif" ); tracker.addImage(signImage[i],0); } for ( i = 0; i < 10; i++ ) { HoroApplet.planetImage[i] = this.getImage( ressourceURL, Calculator.pknam.substring(2*i,2*i+2)+".gif" ); tracker.addImage(planetImage[i],0); } } // Abfangen der potentiellen MalformedURLException // ist zwar syntaktisch vorgeschrieben, hier aber überflüssig catch (Exception e) { System.err.println("Fehler beim Laden der Ressourcen"); } } // Warten, bis alle Bilder fertiggeladen waitForImages(); // Genauigkeit s = getParameter("precision"); if ((s!=null) && (s.toLowerCase().startsWith("min"))) precision = 0; // Instanz des Rechners für Planeten- und Hausberechnungen s = getParameter("calculator"); if (s == null) // Calculator ist der Default calc = (ICalculator) new Calculator(); else if (s.equals("LowPrecCalculator") || s.equals("AstroOL.LowPrecCalculator")) // LowPrecCalculator für eine billige Ephemeride am Frontend calc = (ICalculator) new LowPrecCalculator(); else // Generische Instanziierung eines anderen Calculators durch // Aufruf des Defaultkonstruktors try { calc = (ICalculator) Class.forName(s).newInstance(); } catch (Exception ex) { ex.printStackTrace(); calc = (ICalculator) new Calculator(); } // Falls vorhanden, dem Calculator die servletURL mitteilen if ((s = getParameter("servletURL")) != null) { if (!s.substring(0,7).equals("http://")) { // Relative URL mit Bezug zur Codebase des Applets berechnen try { s = new URL( getCodeBase(), s).toString(); } catch (java.net.MalformedURLException ex) { System.out.println("Bad URL:"+s); } } calc.setServletURL(s); } // Hintergrundfarbe s = getParameter("bgcolor"); if ( s != null) if (s.charAt(0)=='#') bgcolor = new Color(Integer.parseInt(s.substring(1),16)); else bgcolor = new Color(Integer.getInteger(s,new Integer(52479)).intValue()); else bgcolor = new Color(52479); // Meine Lieblingsfarbe // Zweite Hintergrundfarbe (für Leiste mit Planetenpositionen) s = getParameter("bgcolor2"); if ( s != null) if (s.charAt(0)=='#') bgcolor2 = new Color(Integer.parseInt(s.substring(1),16)); else bgcolor2 = new Color(Integer.getInteger(s,new Integer(52479)).intValue()); else bgcolor2 = Color.yellow; // Wenn Planeten und Häuser von aussen mitgegeben werden, // will man eventuell keinen Zeit/Ort-String verwenden noTimePlace = false; if (!called && getParameter("noTimePlace")!=null) noTimePlace = getParameter("noTimePlace").equals("true"); // Häuser darstellen? if ((s=getParameter("noHouses"))!=null) noHouses = Boolean.valueOf(s).booleanValue(); // Wenn eine Unterschrift statisch mitgegeben wurde if (!called) caption = getParameter("caption"); // Systemmeldungen an die Konsole durchreichen String messages = getParameter( "messages" ); if (messages != null) System.out.println( messages ); // Hoehe und Breite gemäss tile-Parameter height = getSize().height; width = getSize().width; String tileS = getParameter( "tile" ); if (tileS != null) tile = (new Double(tileS)).doubleValue(); // Breite der fürs Horoskop vorgesehenen Zeichenfläche width = (int)(width*tile); // Resultierender Radius des Horoskops // rd ist mit 1 vorbelegt in Klasse HoroApplet.java // rd ist mit 0.9 vorbelegt in Klasse HoroCompare.java rd *= ( ( ( height < width ) ? height : width ) ) / 2; // ... muss etwas kleiner gewählt werden, damit die Figur nicht // am Rand klebt rd *= .84; // Mittelpunktskoordinaten r = (int) rd; xm = (int)(width * .515); ym = (int) (rd * 1.15); // (int)(height * .515); // Berechne einen gefälligen Zwischenraum zwischen der Horoskopgraphik // und ihrem Untertitel g = this.getGraphics(); g.setFont(courierbold12); fontAscent = g.getFontMetrics().getAscent(); fontHeight = g.getFontMetrics().getHeight(); d = (height - 2*r // Durchmesser des Horoskops - 2*fontHeight // Platz für Schrift MC / IC - ( 2*fontHeight + fontAscent) // Platz für zwei Unterschriften ) / 4; // Freien Platz in vier Teile aufteilen ym = d + r + fontHeight; } /** Startmethode <br> Die Horoskopgrafik, einmal erzeugt, wird aus Effizienzgründen nicht wieder neu berechnet, wenn die Start-Methode später noch einmal gerufen wird. <br> In der Startmethode wird der Parameter <tt>timePlace</tt> ausgewertet - aus ihm werden Datum, Zeit und Ort abgeleitet. <br> Für die Planetenstände und Häuser gibt es folgende Optionen: <ul> <li>Übergabe als komma-separierte Liste in den Parametern <tt>planets</tt> und <tt>houses</tt> - oder <li>automatische Berechnung, wenn diese Daten fehlen. </ul> */ public void start() { // Wird dieses Applet von einem anderen gerufen oder aktualisiert? String caller = getParameter("caller"); // Ja - dann Notify-Methode aufrufen if (caller != null) notify(caller); if (!noTimePlace && !called) setTimePlace(getParameter("timePlace")); else doShow(); } /** Das rufende Applet über den Abschluß des Ladevorgangs informieren. Näheres hierzu in der Dokumentation des {@link ILoadListener ILoadListener} Interface. */ public void notify(String iCaller) { ILoadListener caller; try { caller = (ILoadListener) getAppletContext().getApplet(iCaller); caller.onLoad(this); // Applet war noch nicht geladen? Dann wird es selbst die // Synchronisation vornehmen, wenn es geladen ist } catch (Exception e) {System.out.println("x2");} } /** Aktualisierungsmethode. Planetenpositionen neu beschaffen oder berechnen, danach das Applet neu zeichnen. Der Aufruf dieser Methode kann nötig sein, wenn die Zeichenfläche des Applets durch andere Objekte der HTML-Seite benutzt oder verändert wird. */ public void doShow() { getPlanetsAndHouses(); // Alles neu zeichnen drawAll( ); // Paint aufrufen update(this.getGraphics()); } /** Haus- und Planetenpositionen direkt angeben.<br> In manchen Fällen ist es gewünscht, die Planetenstände dem Applet direkt mitzugeben, zum Beispiel weil es sich um hypothetische Stände handelt (beispielsweise das berühmte Thema Mundi, mit fiktiven, rein symbolischen Planetenständen, oder die vorgeschobenen oder primären Stände zu einem Horoskop, die keine reale Entsprechung mit dem Sternenhimmel haben), oder weil man die Stände mit einem eigenen Berechnungsprogramm ermittelt hat.<br> In diesen Fällen sind die Stände dem Applet mit der Methode setPlanetsAndHouses() mitzuteilen. Die Methode nimmt einen String entgegen, der die Planeten- und Hauslängen enthält.<br> @param response Enthält, von den Schlüsselwörtern "planets:" und "houses:" gefolgt, die 10 bzw. 12 Positionen als komma-getrennte Liste von Doubles.<br> Beispiel: "planets:15.1,30,45,60,75,90,105,120,135,150<br> houses:0,30,60,90,120,150,180,210,240,270,300,330,360" */ public void setPlanetsAndHouses( String response ) { Calculator.analyzeString(new BufferedReader(new StringReader(response)), h, pl); called = true; } /** Mit dieser Methode kann dem Applet ein neuer Datum/Zeit/Ort-String übergeben werden. Das Applet zeichnet sich dann neu. Zur Berechnung der Planetenstände wird die aktuelle calc-Instanz verwendet. */ public void setTimePlace(String s) { double[] tp = new double[3]; String servletURL; if (!Calculator.parseTimePlace(s,tp)) { xjd = Calculator.thisjd(); lat = Calculator.DEFAULT_LAT; lon = Calculator.DEFAULT_LON; } else { xjd = tp[0]; lon = tp[1]; lat = tp[2]; } doShow(); } /** Übergabe neuer Horoskopdaten - Julianisches Datum, Länge und Breite. Das Applet zeichnet sich dann neu. Zur Berechnung der Planetenstände wird die aktuelle calc-Instanz verwendet. */ public void setTimePlace(double xjd, double lon, double lat) { this.xjd = xjd; this.lon = lon; this.lat = lat; doShow(); } /** Eine Unterschrift des Horoskops von aussen setzen */ public void setCaption(String iCaption) { this.caption = iCaption; noTimePlace = false; called = true; } private void getPlanetsAndHouses() { double[] h0 = new double[13]; int i; // Parameter werden dem Applet übergeben if (!called) { if ( (Calculator.parseDoubleList(getParameter("planets"),pl) == 10) && (Calculator.parseDoubleList(getParameter("houses"),h) == 12) ) { // Rechts-Shift der Häuser for( i = 11; i >= 0; i--) h[i+1] = h[i]; h[0] = 0.; } // Planetenpositionen durch den Rechner ermitteln else { calc.getPlanetsAndHouses( xjd, lon, lat, pl, h); if (noHouses && ((i=Calculator.parseDoubleList(getParameter("houses"),h0)) > 0) ) { // Rechts-Shift der Häuser for( --i; i >= 0; i--) h[i+1] = h0[i]; h[0] = 0.; } } } } protected void drawAll( ) { try { // Positionen der graphischen Darstellung ermitteln plGraph = this.getPlGraph(pl); // Buffered Image bereits vorhanden? Nein, dann erzeugen if (bi == null) bi = createImage(this.getSize().width,this.getSize().height); // Graphische Zeichenfläche (Canvas) holen g = bi.getGraphics(); if (g != null) { // Hintergrundfarbe setzen g.setColor(bgcolor); g.fillRect(0,0,width,height); // Hintergrundfarbe für Positionsleiste g.setColor(bgcolor2); g.fillRect(width,0,this.getSize().width-width, height); // Zeichenfarbe schwarz g.setColor(Color.black); // Horoskopfigur zeichnen drawHoroscope(); // Positionen zeichnen if (tile < 1) drawPositions(); } } catch (Exception ex) {ex.printStackTrace();} } /** Applet-Information holen */ public String getAppletInfo() { return "Horoskop-Applet\nCopyright 2002-2005 Rüdiger Plantiko\nRehweg 19\nCH-8400 Winterthur"; } /** x-Koordinate aus Polarkoordinaten. Das Argument ist eine ekliptikale Länge. Die Koordinate wird so berechnet, dass der Aszendent auf der angezeigten Zeichenfläche horizontal nach links gerichtet ist.*/ protected int x( double l_, double r_ ) { return (int) (rd*(1.-Calculator.cos(h[1]-l_)*r_)) + (xm - r); } /** y-Koordinate aus Polarkoordinaten. Das Argument ist eine ekliptikale Länge. Die Koordinate wird so berechnet, dass der Aszendent auf der angezeigten Zeichenfläche horizontal nach links gerichtet ist.*/ protected int y( double l_, double r_ ) { return (int) (rd*(1.-Calculator.sin(h[1]-l_)*r_)) + (ym - r); } /** Einen Kreis vom Radius r zeichnen */ protected void circle( int r ) { // Circles, circles, circles g.drawOval(xm-r,ym-r,2*r,2*r); } /** Eine Strecke auf einer Ursprungsgeraden in Richtung der ekliptikalen Länge l zeichnen. Die Abstände des Anfangs- und Endpunktes vom Mittelpunkt sind durch <code>r_from</code> und <code>r_to</code> gegeben. */ protected void line( double l, double r_from, double r_to ) { int x1, y1, x2, y2; double c = Calculator.cos(h[1]-l), s = Calculator.sin(h[1]-l); x1 = (int) ( rd * ( 1. - c * r_from)) + ( xm - r) ; y1 = (int) ( rd * ( 1. - s * r_from)) + ( ym - r) ; x2 = (int) ( rd * ( 1. - c * r_to)) + ( xm - r) ; y2 = (int) ( rd * ( 1. - s * r_to)) + ( ym - r) ; g.drawLine(x1, y1, x2, y2); } /** Horoskopgraphik aktualisieren. */ public void update(Graphics pg) { paint(pg); } /** Zeit/Ort-String zurückgeben. Es wird die Information über Zeit und Ort, wie sie auch unterhalb des Horoskopkreises angezeigt wird, zurückgegeben.<p> Die Funktion ist für den Einsatz des Applets im Zusammenspiel mit JavaScript gedacht. */ public String getTimePlace() { return Calculator.renderTimePlace(xjd, lon, lat, precision); } /** Aktuelles Julianisches Datum zurückgeben.<p> Die Funktion ist für den Einsatz des Applets im Zusammenspiel mit JavaScript gedacht. */ public String getJd() { return Double.toString(xjd); } /** Aktuelles Julianisches Datum, Länge und Breite als String (durch Zeilenumbruch getrennt) zurückgeben. Die Funktion ist für den Einsatz des Applets im Zusammenspiel mit JavaScript gedacht. */ public String getTriple() { return Double.toString(xjd) + "\n" + Double.toString(lon) + "\n" + Double.toString(lat) ; } protected static boolean waitForImages() { try { tracker.waitForAll(); } catch(InterruptedException ie) {} return(!tracker.isErrorAny()); } public void paint( Graphics pg ) { if (bi != null) pg.drawImage(bi,0,0,getSize().width,getSize().height,null); } // paint method protected void drawPositions() { int i; int halign1 = width * 102 / 100, halign2 = width * 110 / 100, voffset = 30; g.setFont(courierplain12); for (i = 0; i < 10; i++) { g.drawImage( HoroApplet.planetImage[i], halign1, 20*i + voffset, this ); g.drawString(Calculator.dms(pl[i],Calculator.ECL+precision), halign2, 20*i + 13 + voffset ); } if (!noHouses) for (i = 0; i < 6; i++) { g.setFont(courierbold12); g.drawString(houses.substring(3*i,3*i+3), halign1, 20*i + 223 + voffset ); g.setFont(courierplain12); g.drawString(Calculator.dms(h[i+1],Calculator.ECL+precision), halign2, 20*i+223 + voffset ); } g.setFont(courierbold12); g.drawString("Länge",halign2, voffset - 7); } protected void drawHoroscope() { double l; int i, stringwidth; circle( this.r ); circle( (int)( rd * factor1) ); circle( (int)( rd * factor2) ); if (!noHouses) { line( h[1], 1., -1. ); line( h[10], 1., -1. ); line( h[2], factor1, -factor1 ); line( h[3], factor1, -factor1 ); line( h[11], factor1, -factor1 ); line( h[12], factor1, -factor1 ); } for (l = 0.; l < 360.; l+= 30.) line( l, 1., factor1); for (i = 0; i < 12; i++) { g.drawImage( HoroApplet.signImage[i], x(15.+30.*i,0.93) - HoroApplet.signImage[i].getWidth(this)/2, y(15.+30.*i,0.93) - HoroApplet.signImage[i].getHeight(this)/2, this ); } for (i = 0; i < 10; i++) { line( pl[i], factor1, factor_tick1); g.drawLine(x(pl[i], factor_tick1), y(pl[i], factor_tick1), x(plGraph[i], factor_tick2), y(plGraph[i], factor_tick2)); g.drawImage( HoroApplet.planetImage[i], x(plGraph[i],factor_planet) - HoroApplet.planetImage[i].getWidth(this)/2, y(plGraph[i],factor_planet) - HoroApplet.planetImage[i].getHeight(this)/2, this ); } if (!noHouses) { g.setFont(courierbold8); l = h[1] + Calculator.arcdiff(h[2],h[1])/2.; int h1 = 3, w1 = 3, h2 = 3, w2 = 4; g.drawString("1",x( l, factor_hno)-w1,y( l,factor_hno)+h1); g.drawString("7",x( l, -factor_hno)-w1,y( l, -factor_hno)+h1); l = h[2] + Calculator.arcdiff(h[3],h[2])/2.; g.drawString("2",x( l, factor_hno)-w1,y( l,factor_hno)+h1); g.drawString("8",x( l, -factor_hno)-w1,y( l,-factor_hno)+h1); l = h[3] + Calculator.arcdiff(h[10]+180.,h[3])/2.; g.drawString("3",x( l, factor_hno)-w1,y( l,factor_hno)+h1); g.drawString("9",x( l, -factor_hno)-w1,y( l,-factor_hno)+h1); l = Calculator.fmod360(h[10]+180) + Calculator.arcdiff(h[11],h[10])/2.; g.drawString("4",x( l, factor_hno)-w1,y( l,factor_hno)+h1); g.drawString("10",x( l, -factor_hno)-w2,y( l,-factor_hno)+h2); l = h[11] + Calculator.arcdiff(h[12],h[11])/2.; g.drawString("11",x( l, factor_hno)-w2,y( l,factor_hno)+h2); g.drawString("5",x( l, -factor_hno)-w2,y( l,-factor_hno)+h2); l = h[12] + Calculator.arcdiff(h[1],h[12])/2.; g.drawString("12",x( l, factor_hno)-w2,y( l,factor_hno)+h2); g.drawString("6",x( l, -factor_hno)-w2,y( l,-factor_hno)+h2); g.setFont(courierbold12); g.drawString("ASC", x( h[1], factor105 )-15, y( h[1], factor105 )+3); g.drawString("MC", x( h[10], factor105 )-12, y( h[10], factor105 )); } g.setFont(courierbold12); try { if (caption==null) { if (!noTimePlace) { stringwidth = ((precision==Calculator.SEC)?41:30)*g.getFontMetrics().charWidth('1'); double[] tp = { xjd, lon, lat}; g.drawString( Calculator.renderTimePlace(tp, precision), xm - stringwidth/2, height - d-g.getFontMetrics().getHeight() ); } } else { if (!noTimePlace) // String zentriert schreiben g.drawString( caption, xm - (int) g.getFontMetrics().stringWidth(caption.trim())/2 , height - d-g.getFontMetrics().getHeight() ); } } catch (java.lang.NullPointerException e) {} } /** Ausrichtungsfunktion der Planetenkoordinaten. Um eine möglichst überlappungsfreie Darstellung der einzelnen Planetensymbole zu gewährleisten, werden die Planeten dort, wo sie sich häufen, auseinandergezogen. Dies geschieht durch Identifizierung von Clustern, also Ketten von Planeten, die einer vom nächsten einen hinreichend kleinen Abstand aufweisen. Von diesen Clustern wird der Schwerpunkt berechnet, und die Längen der am Cluster beteiligten Planeten werden vom Schwerpunkt aus so weit gestreckt, dass der kritische Abstand nicht mehr unterschritten wird. Dabei können sich natürlich am Rand neue Cluster bilden, so dass das Verfahren letztlich iterativ arbeiten muss: Bestehende Cluster müssen soweit gestreckt werden, bis sich schliesslich keine neuen Cluster mehr bilden. */ protected double[] getPlGraph(double[] p_pl){ double[] plGraph = new double[10]; double[] plSorted = new double[10]; long[] plLongInd = new long[10]; int i,z=0; int[] index; for (i = 0; i < 10; i++ ) plLongInd[i] = i + 100*((long)(10*p_pl[i])); Calculator.sort(plLongInd); for (i = 0; i < 10; i++ ) plSorted[i] = ((double)(plLongInd[i])/100)/10.; do { index = getCluster( plSorted ); if ((++z>50)||(index[0] == index[1])) break; resolveCluster( index, plSorted ); } while (true); for (i = 0; i < 10; i++) plGraph[(int)(plLongInd[i]%100)] = plSorted[i]; return plGraph; } protected int[] getCluster(double[] p) { // Länge des Clusters int n = 0, z = 0; // Es wird ein Array der Länge 2 für die Unter- und Obergrenze // des ermittelten Clusters definiert. Dies hat den Vorteil, dass // die Zahlen per Referenz an den Cluster-Auflöser übergeben werden // können, da bei der Clusterauflösung eventuell weitere Elemente // zum Cluster hinzukommen - Unter- und Obergrenze müssen also in // der Funktion resolveClustert() noch änderbar sein int[] i = new int[2]; // Eine grösstmögliche Kette von Elementen ermitteln, deren Abstände // jeweils kleiner als die criticalDistance sind i[0] = 0; i[1] = 0; do { if (++z > 50) break; n = 0; while ((Calculator.arcdiffAbs(p[(i[0]+n+1)%10],p[(i[0]+n)%10]) < criticalDistance) && (n < 9)) n++; if (n==0) i[0]++; } while ((n==0) && (i[0] < 9)); i[1] = (i[0]+n)%10; // Wurde ein Cluster ab i[0]=0 ermittelt, so muss auch in rückwärtiger // Richtung gesucht werden if ((n>0) && (i[0]==0)) while((Calculator.arcdiffAbs(p[(i[0]+9)%10],p[i[0]]) < criticalDistance) && (i[0] > 1)) i[0] = (i[0]+9)%10; // Unter- und Obergrenze zurückgeben // Wurde kein Cluster gefunden, so ist i[0] = i[1] = 0 return i; } protected void resolveCluster(int[] i, double[] pExt) { int j, n=0, z=0; double centerOfMass,outsideOfCluster_Start,outsideOfCluster_Size; boolean leave1 = false, leave2 = false; double[] p = new double[10]; // Für einen einelementigen Cluster ist nichts zu tun if (i[0] == i[1]) return; // Schleife wird verlassen, wenn zwei Leave-Bedingungen erfüllt sind while ( (!leave1 || !leave2) && (++z < 50) ) { // Stände-Array kopieren for (j=0;j<10;j++) p[j] = pExt[j]; // Anzahl der Elemente im Cluster n = (i[1]-i[0]+10)%10 + 1; // Berechne den Schwerpunkt des Clusters centerOfMass = p[i[0]]; for (j = 1 ; j < n; j++ ) centerOfMass += (Calculator.arcdiff(p[(i[0]+j)%10],p[(i[0]+j+9)%10])*(double)(n-j))/n; // Berechne die korrigierten Längen der Elemente des Clusters for (j = 0; j < n; j++ ) p[(i[0]+j)%10] = centerOfMass - (double)(((double)n-1.)/2. - (double)j)*criticalDistance; // Stelle fest, ob weitere Elemente in den Cluster hineinzunehmen sind outsideOfCluster_Start = p[i[1]] + criticalDistance; outsideOfCluster_Size = Calculator.fmod360(p[i[0]]-outsideOfCluster_Start-criticalDistance); // Das dem Cluster nächstfolgende Element liegt in dem vom Cluster // überstrichenen Bereich: Cluster vergrössern if (!(leave1 = (Calculator.fmod360(p[(i[1]+1)%10]-outsideOfCluster_Start) < outsideOfCluster_Size))) i[1] = (i[1] + 1 ) % 10; // Das dem Cluster vorangehende Element liegt in dem vom Cluster // überstrichenen Bereich: Cluster vergrössern if (!(leave2 = (Calculator.fmod360(p[(i[0]+9)%10]-outsideOfCluster_Start) < outsideOfCluster_Size))) i[0] = (i[0] + 9 ) % 10; } // Die veränderten Werte in den übergebenen Array zurückkopieren for (j = 0; j < n; j++) pExt[(j+i[0])%10] = p[(j+i[0])%10]; } }