import java.util.*; import java.text.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; public class SunStuff extends JApplet implements ActionListener { JLabel latitudeLabel, declinationLabel, altitudeLabel, daylightLabel, azimuthRiseLabel, azimuthSetLabel; JTextField latitudeField, declinationField, altitudeField, daylightField, azimuthRiseField, azimuthSetField; JComboBox tropicsComboBox, monthComboBox, dayComboBox, seasonsComboBox; String [] tropics = {"NorthPole","Arctic","Cancer","Equator","Capricorn","Antarctic","SouthPole",""}; boolean userTropics=true; double [] tropicsLats = {90, 66.6, 23.4, 0, -23.4, -66.6, -90, 88}; String [] months = {"Jan","Feb","Mar","Apr", "May","Jun","Jul","Aug", "Sep","Oct","Nov","Dec"}; String [] days = {"1","2","3","4","5","6","7","8","9","10", "11","12","13","14","15","16","17","18","19","20", "21","22","23","24","25","26","27","28","29","30","31"}; String [] seasons = {"Vernal equinox","Summer solistice","Autumnal equinox","Winter solistice",""}; int [] seasonsMonths = {2, 5, 8, 11, 0}; int [] seasonsDays = {20,19,22,19, 0}; boolean userSeasons=true; boolean userNotSeasons=true; double latitude, declination, altitude, daylight, azimuthRise, azimuthSet; DecimalFormat d1; public final int westPanelWidth=200; public void init() { d1 = new DecimalFormat ("0.0"); Container container = getContentPane(); container.setLayout(null); //** no Layout Manager JPanel latitudePanel = new JPanel(); latitudePanel.setBackground(Color.LIGHT_GRAY); latitudePanel.setBounds(0,0, westPanelWidth-10,60); container.add(latitudePanel); latitudeLabel = new JLabel( "Latitude \u00B0" ); latitudePanel.add( latitudeLabel ); latitudeField = new JTextField( "",3 ); //latitude = Double.parseDouble(latitudeField.getText()); latitudeField.setHorizontalAlignment(JTextField.RIGHT); latitudeField.addActionListener( this ); latitudePanel.add( latitudeField ); tropicsComboBox = new JComboBox(tropics); //tropicsComboBox.setSelectedIndex(2); //start at tropic of Cancer //latitudeField.setText(""+tropicsLats[tropicsComboBox.getSelectedIndex()]); latitudePanel.add( tropicsComboBox ); tropicsComboBox.addActionListener( this ); JPanel datePanel = new JPanel(); datePanel.setBackground(Color.LIGHT_GRAY); datePanel.setBounds(0,100, westPanelWidth-10,70); container.add(datePanel); monthComboBox = new JComboBox(months); monthComboBox.setSelectedIndex(5); //start June 20 datePanel.add( monthComboBox ); monthComboBox.addActionListener( this ); dayComboBox = new JComboBox(days); dayComboBox.setSelectedIndex(19); //20th datePanel.add( dayComboBox ); dayComboBox.addActionListener( this ); seasonsComboBox = new JComboBox(seasons); seasonsComboBox.setSelectedIndex(1); //summer solistice datePanel.add( seasonsComboBox ); seasonsComboBox.addActionListener( this ); JPanel gridParamsPanel = new JPanel(); gridParamsPanel.setLayout( new GridLayout(5,2) ); gridParamsPanel.setBackground(Color.LIGHT_GRAY); gridParamsPanel.setBounds(0,180, westPanelWidth-10,150); gridParamsPanel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); container.add(gridParamsPanel); declinationLabel = new JLabel( "declination \u00B0" ); gridParamsPanel.add( declinationLabel ); declinationField = new JTextField( "",3 ); declinationField.setHorizontalAlignment(JTextField.RIGHT); declinationField.setEditable(false); gridParamsPanel.add( declinationField ); altitudeLabel = new JLabel( "altitude \u00B0" ); altitudeLabel.setBackground(Color.RED); gridParamsPanel.add( altitudeLabel ); altitudeField = new JTextField( "",3 ); altitudeField.setHorizontalAlignment(JTextField.RIGHT); altitudeField.setBackground(Color.RED); altitudeField.setEditable(false); gridParamsPanel.add( altitudeField ); daylightLabel = new JLabel( "Daylight hours" ); daylightLabel.setBackground(Color.YELLOW); gridParamsPanel.add( daylightLabel ); daylightField = new JTextField( "",3 ); daylightField.setHorizontalAlignment(JTextField.RIGHT); daylightField.setBackground(Color.YELLOW); daylightField.setEditable(false); gridParamsPanel.add( daylightField ); azimuthRiseLabel = new JLabel( "azimuthRise \u00B0" ); gridParamsPanel.add( azimuthRiseLabel ); azimuthRiseField = new JTextField( "",3 ); azimuthRiseField.setHorizontalAlignment(JTextField.RIGHT); azimuthRiseField.setBackground(Color.GREEN); azimuthRiseField.setEditable(false); gridParamsPanel.add( azimuthRiseField ); azimuthSetLabel = new JLabel( "azimuthSet \u00B0" ); gridParamsPanel.add( azimuthSetLabel ); azimuthSetField = new JTextField( "",3 ); azimuthSetField.setHorizontalAlignment(JTextField.RIGHT); azimuthSetField.setBackground(Color.GREEN); azimuthSetField.setEditable(false); gridParamsPanel.add( azimuthSetField ); //latitudeField.postActionEvent(); //fires listener tropicsComboBox.setSelectedIndex(2); //as if user selects a tropic to start off } public void actionPerformed( ActionEvent event ) { double latitudeTry; if ( event.getSource()==latitudeField) { try { latitudeTry = Double.parseDouble(latitudeField.getText()); } catch (NumberFormatException e) { JOptionPane.showMessageDialog(null, "latitude must be a number between -90 (S) and 90 (N)", "SunStuff: Invalid input", JOptionPane.ERROR_MESSAGE); return; } if (latitudeTry<-90 || latitudeTry>90) { JOptionPane.showMessageDialog(null, "latitude must be a number between -90 (S) and 90 (N)", "SunStuff: Invalid input", JOptionPane.ERROR_MESSAGE); return; } latitude = latitudeTry; userTropics = false; tropicsComboBox.setSelectedIndex(7); //blank if user inputs a latitude userTropics = true; // repaint(); //then returns and does the if below } else if (event.getSource()==tropicsComboBox) { if (userTropics) { latitudeField.setText(""+tropicsLats[tropicsComboBox.getSelectedIndex()]); latitude = Double.parseDouble(latitudeField.getText()); } // repaint(); } else if (event.getSource()==seasonsComboBox) { if (userSeasons) { userNotSeasons = false; //prevent month/dayCombobox handler from doing anything monthComboBox.setSelectedIndex(seasonsMonths[seasonsComboBox.getSelectedIndex()]); dayComboBox.setSelectedIndex(seasonsDays[seasonsComboBox.getSelectedIndex()]); userNotSeasons = true; } // repaint(); } else if (event.getSource()==monthComboBox || event.getSource()==dayComboBox) { if (userNotSeasons) { userSeasons = false; //prevent event handler for seasonComboBox above from doing anything seasonsComboBox.setSelectedIndex(4); //blank userSeasons = true; } } //add code if 30 day month etc... /* if ( event.getSource()==monthComboBox || event.getSource()==dayComboBox || event.getSource()==latitudeField || event.getSource()==tropicsComboBox) { */ int m = monthComboBox.getSelectedIndex(); int d = dayComboBox.getSelectedIndex(); //JOptionPane.showMessageDialog(null, months[m]+" "+days[d]); int n = (new GregorianCalendar(2011,m,d+1)).get(Calendar.DAY_OF_YEAR) - 1; //-1 for below formula //JOptionPane.showMessageDialog(null, " "+n); //formula from wikipedia "Declination" declination = Math.toDegrees(-Math.asin(0.39779*Math.cos(Math.toRadians(0.98565*(n+10)+ 1.914*Math.sin(Math.toRadians(0.98565*(n-2))))))); declinationField.setText(d1.format(declination)); altitude = 90 - Math.abs(declination-latitude); if (altitude < 0) altitude = 0; altitudeField.setText(d1.format(altitude)); daylight = daylightHours(declination, latitude, n); daylightField.setText(d1.format(daylight)); if (daylight>0 && daylight<24) { double sunriseHourAngle=(-daylight/2) * 15; //degrees. hour angle of sunrise azimuthRise = solarAzimuthAngle(declination, latitude, daylight); //Math.toDegrees(Math.asin(-Math.sin(Math.toRadians(sunriseHourAngle))* // Math.cos(Math.toRadians(declination)))); azimuthRiseField.setText(""+d1.format(azimuthRise)); //double sunsetHourAngle=(daylight/2) * 15; //from noon (hour angle 0) //azimuthSet = Math.toDegrees(Math.asin(-Math.sin(Math.toRadians(sunsetHourAngle))* // Math.cos(Math.toRadians(declination)))); azimuthSet = 360 - azimuthRise; azimuthSetField.setText(""+d1.format(azimuthSet)); } else { //no sunrise/sunset this day azimuthRiseField.setText(""); azimuthSetField.setText(""); } repaint(); //} } public void paint(Graphics g) { super.paint(g); g.setFont( new Font( "SansSerif", Font.BOLD, 16 ) ); //int leftgap=10; int rightgap=40; int topgap=20; int w=getWidth()-westPanelWidth-rightgap; //-leftgap; int h=getHeight()-100-topgap; double xWidth = 365; double yHeight = 90; //24 for hours below double xScale = w / xWidth; //scale user to panel double yScale = h / yHeight; double xOffset = xScale - westPanelWidth; double yOffset = yScale; double tickIntX = xWidth/12.0; double tickIntY=10; int xHi=360, xLo=0, yLo=0, yHi=90; //int xAxis = (int)(h+yOffset); //y value int xAxis = (int)(h); //y value int yAxis = (int)(-xOffset); //x value g.setColor(Color.LIGHT_GRAY); //grid for (double tick=tickIntX; tick<=xHi; tick+=tickIntX) //vertical Months g.drawLine((int)(tick * xScale - xOffset),topgap,(int)(tick * xScale - xOffset),h+topgap); g.setColor(Color.PINK); for (double tick=tickIntY; tick<=yHi; tick+=tickIntY) //horizontal. altitude degrees //g.drawLine(westPanelWidth,h-(int)(tick * yScale - yOffset), // getWidth()-rightgap,h-(int)(tick * yScale - yOffset)); g.drawLine(westPanelWidth,h-(int)(tick * yScale)+topgap, getWidth()-rightgap,h-(int)(tick * yScale)+topgap); g.setColor(Color.BLACK); g.drawLine(westPanelWidth,xAxis+topgap,getWidth()-rightgap,xAxis+topgap ); //x axis g.setColor(Color.RED); g.drawLine(yAxis,topgap,yAxis,h+topgap); //y axis (altitude) //ticks on x axis. every 30 days (~month) g.setColor(Color.BLACK); for (double tick=0; tick0 && dayhours<24) { //no plot if 24-hour day or night because no sunrise or sunset //sunriseHourAngle=(-daylight/2) * 15; //degrees. hour angle of sunrise azimuthAngleDiff = 90 - solarAzimuthAngle(decl, latitude, dayhours); g.fillRect((int)(d * xScale - xOffset)-1,h-(int)((azimuthAngleDiff+90) * yScale)-1+topgap, 3,3); } } //YELLOW: daylight yHeight = 24; yScale = h / yHeight; yOffset = yScale; yLo=0; yHi=24; g.setColor(new Color(255,255,100)); tickIntY=2; for (double tick=tickIntY; tick<=yHi; tick+=tickIntY) //horizontal. hours g.drawLine(westPanelWidth,h-(int)(tick * yScale)+topgap, getWidth()-rightgap,h-(int)(tick * yScale)+topgap); g.drawLine(getWidth()-rightgap,topgap,getWidth()-rightgap,h+topgap); //vertical hours line on right //don't need ticks on y hours axis 0 to 24 // g.setColor(Color.BLACK); g.setColor(Color.YELLOW); for (double tick=0; tick<=yHi; tick+=tickIntY) { //g.setColor(Color.YELLOW); //g.drawLine(getWidth()-rightgap-3,h-(int)(tick * yScale)+topgap, // getWidth()-rightgap+3,h-(int)(tick * yScale)+topgap); g.drawString(""+(int)tick, getWidth()-rightgap+25,h-(int)(tick * yScale)+topgap+4); } g.setColor(Color.YELLOW); for (int d=0; d<=364; d++) { decl = Math.toDegrees(-Math.asin(0.39779*Math.cos(Math.toRadians(0.98565*(d+10)+ 1.914*Math.sin(Math.toRadians(0.98565*(d-2))))))); dayhours = daylightHours(decl, latitude, d); g.fillRect((int)(d * xScale - xOffset)-1,h-(int)(dayhours * yScale)-1+topgap, 3,3); } g.setFont( new Font( "Serif", Font.BOLD, 16 ) ); g.setColor(Color.RED); //g.drawString("Altitude of noon sun ",(int)(xScale - xOffset) , xAxis+topgap+70); g.drawString("Altitude of noon sun ",(int)(xScale - xOffset)+22 , 12); g.setColor(Color.YELLOW); //g.drawString("Hours of daylight",(int)(xScale - xOffset)+200 , xAxis+topgap+70); g.drawString("Hours of daylight",getWidth()-120 , 40); g.setColor(Color.GREEN); //g.drawString("Azimuth rise/set offset from due East/West",(int)(xScale - xOffset) , xAxis+topgap+90); g.drawString("Azimuth rise/set offset from due East/West",getWidth()-290 , 12); g.setFont( new Font( "Serif", Font.BOLD, 24 ) ); g.setColor(Color.BLACK); g.drawString("Latitude: "+((latitude>0)?"N":((latitude<0)?"S":""))+Math.abs(latitude), (int)(xScale - xOffset)+300 , xAxis+topgap+50); //g.drawString("Latitude: "+((latitude>0)?"N":((latitude<0)?"S":""))+Math.abs(latitude), // (int)(xScale - xOffset)+200 , 20); } public double daylightHours(double declination, double latitude, int day) { double omegaNull; double daylight=-1; if (day>91 && day<273) { //northern summer / southern winter if (latitude>-90+declination && latitude<90-declination) { omegaNull=Math.acos(-Math.tan(Math.toRadians(latitude))* Math.tan(Math.toRadians(declination))); daylight = 2 * Math.toDegrees(omegaNull) / 15; } else if (latitude > 0) //in northern i.e. Arctic daylight = 24; else daylight = 0; //Antarctic } else { //northern winter / southern summer if (latitude>-90-declination && latitude<90+declination) { omegaNull=Math.acos(-Math.tan(Math.toRadians(latitude))* Math.tan(Math.toRadians(declination))); daylight = 2 * Math.toDegrees(omegaNull) / 15; } else if (latitude > 0) //in northern i.e. Arctic daylight = 0; else daylight = 24; //Antarctic } return daylight; } public double solarAzimuthAngle(double declination, double latitude, double daylight) { double sunriseHourAngle=(-daylight/2) * 15; //degrees. hour angle of sunrise //return Math.toDegrees(Math.asin(-Math.sin(Math.toRadians(sunriseHourAngle))* // Math.cos(Math.toRadians(declination)))); return Math.toDegrees(Math.acos(Math.sin(Math.toRadians(declination)) / Math.cos(Math.toRadians(latitude)))); } }