/** * SunrisePixels<p> * * Compute the shape of the sun's terminator (sunrise and sunset lines) * and darken the earth map accordingly. This class creates the image, * but does not draw it. It really should be an Image<something> subclass, * but I can't figure out the right design.<p> * * This is in the utility Astro Bean so it can be used by all of the * display applets (SimpleSun, SunSphere, SunClock). These applets * will be used to test this class - there is no specific main or * test class. * * Based, in part, on sample code in The Java Class Libraries (Chen and * Lee), pp. 753 ff. * * Copyright &copy; 1996-1998 Martin Minow. All Rights Reserved.<p> * World map Copyright © 1992-1998 Apple Computer Inc. All Rights Reserved.<p> * * Permission to use, copy, modify, and redistribute this software and its * documentation for personal, non-commercial use is hereby granted provided that * this copyright notice and appropriate documentation appears in all copies. This * software may not be distributed for fee or as part of commercial, "shareware," * and/or not-for-profit endevors including, but not limited to, CD-ROM collections, * online databases, and subscription services without specific license.<p> * * @author <a href="mailto:minow@merrymeet.com">Martin Minow</a> * @version 1.1 (Java 1.0.2 only) * 1996.12.30 */package org.minow.applets.sunsphere;import java.beans.*;import java.util.*;import java.awt.*;import java.awt.image.*;/** * SunrisePixels manages all tasks relating to determining the location * of the sunrise/sunset/twilight terminator and constructing a (rectangular) * world map showing the terminator in its proper location. Note that the * terminator location is only dependent on GMT time: SunriseCanvas uses * a low-precision (but very fast) algorithm that does not provide an accurate * sunrise/sunset time for any particular location. * * Initialization: *	sunrisePixels = new sunrisePixels(originalImage); *	    Where   originalImage	The world map itself. * Date or time changes: *	sunrisePixels.setDate(new Date()); * * Repaint: *	g.drawImage(sunrisePixels.getImage(), x, y, observer); * Interesting accessors: *	getImagePixels			Return the converted pixel vector. *					This is the parameter passed to *					MemoryImageSource. *	 */class SunrisePixels extends Object		implements org.minow.applets.sunsphere.SunSphereConfig{    public static final double	SUN_RISE_SET		= -(50.0 / 60.0);    public static final double	CIVIL_TWILIGHT		= -6.0;    /**     * ABOVE_HORIZON and BELOW_HORIZON are returned for sun and moon     * calculations where the astronomical object does not cross the     * horizon.     */    public static final double	ABOVE_HORIZON	= Double.POSITIVE_INFINITY;    public static final double	BELOW_HORIZON	= Double.NEGATIVE_INFINITY;    /*     * Special index values for the image conversion.     */    protected static final int	DARK			= -1;    protected static final int	BRIGHT			= -2;    /*     * DAYLIGHT, TWILIGHT, and NIGHT are divisors used to darken pixels.     */    protected static final int	DAYLIGHT	= 0;	/* No darkening	    */    protected static final int	TWILIGHT	= 2;	/* Pixel / 2	    */    protected static final int	NIGHT		= 3;	/* Pixel / 3	    */    /*     * These are now imported from SunSphereConfig     */    // protected static final float twilightFactor = 0.75F; /* Halfway to darkness */    // protected static final float nightFactor	= 0.5F; /* Completely dark */        protected static final int	MAX_EVENTS	= 5;    protected static final int	LOC		= 0;    protected static final int	DIV		= 1;        /*     * Variables for the map image     * eventIndex and eventDivisor designate the transition between day,     * twilight, and night. eventIndex is the x-pixel value, eventDivisor is     * one of DAYLIGHT, TWILIGHT, or NIGHT. These values are computed on a     * row by row (i.e., latitude) basis. nEvents is the number of events.     */    protected double		timeOfDayGMT;	/* Time (to warp the image) */    protected int[]		eventIndex	= new int[MAX_EVENTS];    protected int[]		eventDivisor	= new int[MAX_EVENTS];    protected int		nEvents;        protected int[]		imagePixels;	/* Source image (pixel vector)	*/    protected int[]		sunrisePixels;	/* Image after transformation	*/    protected int		imageWidth;	/* Image width in pixels	*/    protected int		imageHeight;	/* Image height in pixels	*/    protected Image		sunriseImage;    /**     * The Java Date (as a long number of milliseconds past the epoch)     * drives the algorithm.     */    protected long		currentTime	= System.currentTimeMillis();        /**     * Create the SunrisePixels class.     * @param	imagePixels	The original world map image. This is an unrolled     *				pixel vector where each pixel defines a specific     *				latitude and longitude. The world map is imaged     *				in the "Plate-Coree" or orthographic projection,     *				which is linear in both latitude and longitude.     * @param	imageWidth	The width of the image in pixels.     * @param	imageHeight	The height of the image in pixels     */    public SunrisePixels(	    int[]		imagePixels,	    int			imageWidth,	    int			imageHeight	)    {    	this.imagePixels	= imagePixels;    	this.imageWidth		= imageWidth;    	this.imageHeight	= imageHeight;	sunrisePixels		= new int[imagePixels.length];	setCurrentTime(System.currentTimeMillis());    }    /**     * Accessors.     */    public long getCurrentTime()    {    	return (currentTime);    }    public int getImageWidth()    {	return (imageWidth);    }    public int getImageHeight()    {	return (imageHeight);    }    public int[] getImagePixels()    {    	return (imagePixels);    }    /*     * Return the converted image pixel vector     */    public int[] getSunrisePixels()    {	return (sunrisePixels);    }    /**     * Return the converted image.     */    public Image getSunriseImage()    {    	try {	    MemoryImageSource imageSource = new MemoryImageSource(			imageWidth,			imageHeight,			sunrisePixels,			0,			imageWidth		);	    sunriseImage = Toolkit.getDefaultToolkit().createImage(imageSource);	} catch (Exception e) {	    System.out.println("SunrisePixels: new MemoryImageSource failed: " + e);	    System.out.println("imageWidth = " + imageWidth	    	    + ", imageHeight = " + imageHeight	    	    + ", sunrisePixels.length = " + sunrisePixels.length	    	);	    throw new RuntimeException(e.toString());	}	return (sunriseImage);    }    /**     * Set the date - this will rebuild the map.     */    public void setCurrentTime(	    long		currentTime	)    {    	this.currentTime	= currentTime;	float[] hsb		= new float[3]; /* For HSB color    */	double	timeOfDay	= (double) ((currentTime / 60000L) % 1440L);	timeOfDayGMT		= Utility.MOD(((timeOfDay + 720.0)) / 60.0, 24.0);	double MJD		= SunSphereRiseSet.getMJD(new Date(currentTime));	/*	 * It would be faster to compute sunrise/sunset from the equator	 * outwards. That way, we don't need to recompute higher latitudes	 * part of the year. This, however, is much simpler.	 */	int	i		= 0;		/* mapPixels[] index	    */	for (int y = 0; y < imageHeight; y++) { /* Latitude (in pixels)	    */	    /*	     * pixelToLatitude is specific to the image projection.	     */	    double latitude = pixelToLatitude(y);	    /*	     * The sunrise algorithm breaks down for the North and South	     * poles. Fiddle the extreme latitudes just a bit. This would	     * only affect a single y-pixel line.	     *//*	    if (latitude >= 89.9) {		latitude = 89.9;	    }	    else if (latitude <= -89.9) {		latitude = -89.0;	    }*/	    nEvents		= 0;	    double sunrise	= SunSphereRiseSet.sunRiseSet(		MJD, 0.0, latitude, 0.0, SUN_RISE_SET, SunSphereRiseSet.RISING);	    double sunset	= SunSphereRiseSet.sunRiseSet(		MJD, 0.0, latitude, 0.0, SUN_RISE_SET, SunSphereRiseSet.SETTING);	    double morning	= SunSphereRiseSet.sunRiseSet(		MJD, 0.0, latitude, 0.0, CIVIL_TWILIGHT, SunSphereRiseSet.RISING);	    double evening	= SunSphereRiseSet.sunRiseSet(		MJD, 0.0, latitude, 0.0, CIVIL_TWILIGHT, SunSphereRiseSet.SETTING);	    if (morning == SunSphereRiseSet.ABOVE_HORIZON && evening == SunSphereRiseSet.ABOVE_HORIZON) {		store(imageWidth, DAYLIGHT);	    }	    else if (morning == SunSphereRiseSet.BELOW_HORIZON && evening == SunSphereRiseSet.BELOW_HORIZON) {		store(imageWidth, NIGHT);	    }	    else {		if (isEvent(morning) && isEvent(evening)) {		    store(timeToPixel(morning), NIGHT);		    if (isEvent(sunrise) && isEvent(sunset)) {			store(timeToPixel(sunrise), TWILIGHT);			store(timeToPixel(sunset), DAYLIGHT);		    }		    store(timeToPixel(evening), TWILIGHT);		}		else {		    store(timeToPixel(sunrise), TWILIGHT);		    store(timeToPixel(sunset), DAYLIGHT);		}		store(imageWidth, eventDivisor[0]); /* Wrap around  */	    }	    int		j	    = 0;	    for (int x = 0; x < imageWidth; x++) {		int	rgb	    = imagePixels[i];		while (j < MAX_EVENTS && x > eventIndex[j]) {		    j++;		}		int divisor = eventDivisor[j];		if (divisor != DAYLIGHT) {		    int	    alpha   = rgb & 0xFF000000;		    int	    red	    = (rgb & 0x00FF0000) >> 16;		    int	    green   = (rgb & 0x0000FF00) >>  8;		    int	    blue    = (rgb & 0x000000FF);		    Color.RGBtoHSB(red, green, blue, hsb);		    switch (divisor) {		    case TWILIGHT:			hsb[2] *= twilightFactor;			break;		    case NIGHT:			hsb[2] *= nightFactor;			break;		    }		    rgb = (Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]) & 0x00FFFFFF) | alpha;		}		sunrisePixels[i++] = rgb;	    }	}    }    protected void store(	    int			imageX,	    int			divisor	)    {	    int		    i;	    for (i = nEvents; i > 0 && imageX < eventIndex[i - 1]; --i) {		eventIndex[i]	    = eventIndex[i - 1];		eventDivisor[i]	    = eventDivisor[i - 1];	    }	    eventIndex[i]	    = imageX;	    eventDivisor[i]	    = divisor;	    nEvents++;    }    /**     * Convert a time in the range 0.0 to 24.0 to a pixel longitude in     * the current image. This function is only valid for the SunClock     * world map projection, in which the longitude is proportionate to     * the pixel offset from the midpoint of the image. Note, further,     * that this function is not valid for SunSphereRiseSet.ABOVE_HORIZON or SunSphereRiseSet.BELOW_HORIZON.     */    protected int timeToPixel(	    double		theTime	)    {	/*	 * Convert theTime fraction to a fraction of a day then	 * convert it to a pixel offset within the current image	 */	theTime		    = Utility.MOD((theTime - timeOfDayGMT) / 24.0, 1.0);	return ((int) (theTime * imageWidth));    }    /**     * Return the latitude corresponding to a a pixel. This function is specific     * to the Apple world map, which uses a non-standard (and non-recommended)     * equirectangular cylindrical projection.     * @param yPixel	    The pixel offset (0 .. imageHeight)     * @param imageHeight   The image height.     * @return	The latitude in the range of +90 (North Pole) to -90 (South Pole)     */    protected double pixelToLatitude(	    int			yPixel	)    {	double result = 90.0 - (((double) yPixel) / ((double) imageHeight) * 180.0);	return (result);    }    /**     * Is this value above the horizon?     */    public static boolean isEvent(	    double		value	)    {	return (value != ABOVE_HORIZON && value != BELOW_HORIZON);    }}