diff options
Diffstat (limited to 'StarChart.py')
-rw-r--r-- | StarChart.py | 3323 |
1 files changed, 3323 insertions, 0 deletions
diff --git a/StarChart.py b/StarChart.py new file mode 100644 index 0000000..fb98510 --- /dev/null +++ b/StarChart.py @@ -0,0 +1,3323 @@ +# StarChart -- Sky chart program for visual and binocular astronomy +# +# Copyright (c) 2008 - 2010 by David A. Wallace +# Copyright (c) 2012 Walter Bender +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# CITATIONS: +# +# The algorithms herein were mostly gleaned from: +# Practical Astronomy with Your Calculator +# by Peter Duffett-Smith (3rd ed.) +# +# Keplerian Elements for Approximate Positions of the Major Planets +# http://ssd.jpl.nasa.gov/txt/aprx_pos_planets.pdf +# +# Bright Star Catalog +# http://heasarc.gsfc.nasa.gov/W3Browse/star-catalog/bsc5p.html +# +# Dimmer Stars +# The catalog of stars to magnitude 8 was extracted from the SAO catalog +# of stars at +# http://cdsarc.u-strasbg.fr/viz-bin/Cat?cat=I%2F131A&target=http +# Which was filtered to only stars of visual magnitude 8.9 or brighter and +# only those stars with a Henry Draper (HD / HDE) designator. +# +# Names of Stars +# extracted from data at http://www.alcyone.de/SIT/bsc/bsc.html and +# http://cdsarc.u-strasbg.fr/viz-bin/Cat?IV/27 (table3.dat) +# +# Messier Catalog +# extracted from data at http://astro.nineplanets.org +# +# Constellation figures -- derived from charts at +# http://www.astro.wisc.edu/~dolan/constellations/ +# and the coordinates of the stars that the line-segments interconnect. +# +# NGC Objects +# The list of NGC objects came from several sources including: +# http://www.hawastsoc.org/deepsky/bennett.html +# http://www.wikipedia.org +# +# +# ACKNOWLEDGEMENTS +# +# The author wishes to thank the following people for assistance with +# this project: +# +# Daniel Castilo and Oscar Mendez Laesprella for encouragement, +# suggestions, bug reports, beta testing and Spanish translation. +# Thanks, guys -- I couldn't have done this without you. +# +# The owner and staff of The Java Room in Chelmsford, Massachusetts +# for the coffee, wi-fi access and especially the live music. Best +# environment I've ever had for developing code in! +# +# The members and officers of the Amateur Telescope Makers of +# Boston who encouraged and educated me when I first discovered the +# wonder that is our planet's night sky. +# +# ----------------------------------------------------------------------------- +# +# INDEX +# +# (Line numbers given below are approximate, within 25 lines, usually.) +# +# Line No. Content +# -------- -------------------------------------- +# 175 Planetary Catalog data +# 275 Start of the code -- astronomical algorithms +# 475 convert az, alt to ra, dec +# 725 Definitions for the GUI controls (in toolbars, mostly) +# 775 Version and date displayed by "About" function. +# 800 Definition of pixel-to-object mapping class +# 925 Definition of object-to-pixel mapping class +# 950 Definition of Location class +# 1000 Definition for the ChartDisplay (main GUI) class +# 1075 Convert az,alt to x,y coordinates +# 1100 Convert x,y to az,alt coordinates +# 1125 Control callbacks method +# 1450 Code for "Locate" feature +# 1475 Magnification code +# 1600 Input Error reporting +# 1625 Code to draw the chart: +# 1625 The outline and field +# 1750 Plot the sky +# 1775 PlotAllStars code +# 1800 PlotAllDSOs code +# 1825 PlotAllConstellations code +# 1850 PlotAllPlanets code +# 2125 Compute Vector Of Magnified Object +# 2175 PlotMagStars code +# 2200 PlotMagDSOs code +# 2225 PlotMagPlanets code +# 2500 Draw symbol of Star +# 2525 Draw symbol of Planets (including sun and moon) +# 2675 Draw symbol of Deep Sky Objects +# 2850 Definition for the StarChartActivity class +# 3050 Read metadata +# 3075 Write metadata +# 3100 StarChart.cfg update code +# + + +# ================== NON-STANDARD BUT REQUIRED LOCALIZATION =================== + +# The settings for the observer's coordinates +try: + import observatory + longitude = observatory.data[0] + latitude = observatory.data[1] +except: + longitude = 0.0 + latitude = 0.0 + +# "nonlocal_timezone_correction" allows your XO to keep one timezone +# while the program's "now" expresses time in another timezone. This +# value is normally set to zero, but if you are traveling, you might +# find it more convenient to leave the XO's local timekeeping alone +# and simply offset the star chart's time zone so that "now" is +# correct for your current locale. +# +nonlocal_timezone_correction = 0.0 + +# If traveling, create "travel.py" to define alternate location and timezone. +# (If "travel.py" does not exist, we use our home observatory parameters.) + +try: + import travel + longitude = travel.data[0] + latitude = travel.data[1] + nonlocal_timezone_correction = travel.data[2] +except: + pass + + +# =================================== IMPORTS ================================ + +import pygtk +pygtk.require('2.0') +import gtk +import sys +import os +import gobject +from datetime import * +from time import sleep +from time import time as systime +from math import sin, cos, tan, asin, acos, atan, pi, sqrt, atan2 +from sugar.activity import activity +from sugar.activity.activity import get_bundle_path +try: + from sugar.graphics.toolbarbox import ToolbarBox + _have_toolbox = True +except ImportError: + _have_toolbox = False +if _have_toolbox: + from sugar.activity.widgets import ActivityToolbarButton + from sugar.graphics.toolbarbox import ToolbarButton + from sugar.activity.widgets import StopButton +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.palette import Palette, ToolInvoker +from sugar.graphics.icon import Icon +import logging +from gettext import gettext + +class ToggleButtonTool(ToolButton): + + def __init__(self, icon_on=None, icon_off=None, **kwargs): + super(ToolButton, self).__init__(icon_off) + + self._icon_on=icon_on + self._icon_off=icon_off + self._accelerator = None + self._tooltip = None + self._palette_invoker = ToolInvoker() + self._active = False + + gobject.GObject.__init__(self, **kwargs) + + self._palette_invoker.attach_tool(self) + + if icon_off: + self.set_icon(icon_off) + + self.connect("clicked", self._click_callback) + + def _click_callback(self, button): + self.set_active(not self._active) + + def get_active(self): + return self._active + + def set_active(self, active): + self._active = active + if self._active: + self.set_icon(self._icon_on) + else: + self.set_icon(self._icon_off) + + +# Defensive method of gettext use: does not fail if the string is not +# translated. +def _(s): + istrsTest = {} + for i in range (0,4): + istrsTest[str(i)] = str(i) + + try: + i = gettext(s) + except: + i = s + return i + + +# ============================= PLANETARY CATALOG DATA ======================= +# +# (This is the only catalog that is coded into the program as opposed +# to being imported.) +# +# Keplarian data for planets (J2000.0 epoch coordinates, except moon +# is J1990.5) +# +# FIXME: add data for dwbar, dI and dO +# +# planet arg of peri eccentricity radius (au) inclination RAAN +# mean longitude delta mean longitude +# pname wbar0 E a I0 O0 +# L dL +planets = [ + (_("Mercury"), 77.45779628, 0.20563593, 0.38709927, 7.00497902, 48.33076593, 252.25032350, 149472.67411175), + (_("Venus"), 131.60246718, 0.00677672, 0.72333566, 3.39467605, 76.67984255, 181.97909950, 58517.81538729), + (_("Earth"), 102.93768193, 0.01671123, 1.00000261, -0.00001531, 0.0, 100.46457166, 35999.37244981), + (_("Mars"), -23.94362959, 0.09339410, 1.52371034, 1.84969142, 49.55953891, -4.55343205, 19140.30268499), + (_("Jupiter"), 14.72847983, 0.04838624, 5.20288700, 1.30439695, 100.47390909, 34.39644051, 3034.74612775), + (_("Saturn"), 92.59887831, 0.05386179, 9.53667594, 2.48599187, 113.66242448, 49.95424423, 1222.49362201), + (_("Uranus"), 170.95427630, 0.04725744, 19.18916464, 0.77263783, 74.01692503, 313.23810451, 428.48202785), + ] +sun = (_("Sun"), 282.93768193, 0.01671123, 1.00000261, 0.0, 0.0, 280.46457166, 35999.37244981) + +# name mean lon lon of peri lon of Node +# Inclination Eccentricity radius (km) Parallax Offset of Epoch +# L0 P0 N0 +# I e a phi0 tau +moon = (_("Moon"), 318.351648, 36.340410, 318.510107, 5.145396, 0.054900, 384401, 0.9507, 2447891.5) + +# obliquity of J2000 epoch is 23.43928 degrees -- we need this value +# (in radians) for planetary calculations +eps = pi * 23.43928 / 180.0 + + +# ----------------------------------------------------------------------------- + +# The bright star catalog is imported from stars1.py. +import stars1 +star_chart = stars1.data + +# Appending data from supplementary catalogs is just a matter of +# importing the catalog file and updating the primary dictionary with +# the supplement. But I must allow for the possibility that the +# supplementary catalog does not exist, so the process needs a +# try/except block. + +# A supplementary catalog of stars down to magnitude 6.8 is imported +# from stars2.py. If this catalog is not present, also disable the +# "magnify" feature. +try: + can_magnify = True + import stars2 + supplement = stars2.data +except: + can_magnify = False + supplement = {} +star_chart.update(supplement) + +# The constellations figures have their own catalog. This catalog +# could potentially be replaced by locale-specific figures, but that +# will break much of the code relating to object locating and +# identifying, since the program wants to use the 88 IAU constellation +# names (or at least their abbreviations). +import constellations +figures = constellations.data + +# Here we obtain catalog data for Deep Sky Objects. dso1 is the catalog of the +# Messier objects. + +# Again, we support supplementary catalogs. (dso2 is planned to be a +# subset of the NGC catalog and dso3 ill be a subset of the IC +# catalog.) For DSOs, we allow any or all of the catalogs to be +# missing. + +# FIXME: Because the catalogs of DSOs duplicate each other, we will +# have the same object listed by multiple names. We will need to +# disambiguate somehow (especially in the "Identify" feature, where it +# would be nice to be able to list ALL the names for the object we +# find). +try: + import dso1 + dso_chart = dso1.data +except: + dso_chart = [] + +try: + import dso2 + dsupplement = dso2.data +except: + dsupplement = [] +dso_chart = dso_chart + dsupplement + +try: + import dso3 + dsupplement = dso3.data +except: + dsupplement = [] +dso_chart = dso_chart + dsupplement + +# We define a couple of dictionaries Locate will need: one is used to get a +# constellation's ID from its name and the other is used to get a planet's +# declination from its name. +abbrev_from_name = {} +dec_from_planet = {} + + +# ============================= STATE INFORMATION ============================= + +# initial settings for display options +nightvision = False +invertdisplay = False +fliphorizontally = False +drawconstellations = True +limitingmagnitude = 4.0 +saved_lmag = 4.0 +# initial settings for time +specifytime = False +saved_specifytime = False +now = datetime.utcnow() +zoneoffset = -300 + + +# ============================== START OF CODE ================================ + +# Because Python's trig is done in radians and I often need answers in +# degrees, these two functions are provided to convert between radians +# and degrees. + +def dtor(a): + return a * pi / 180.0 + + +def rtod(r): + return r * 180.0 / pi + + +# This function converts a decimal time to hours, minutes and seconds +# format and returns a time object. + +def floattotime(f): + h = int(f) + mm = (f - h) * 60.0 + m = int(mm) + s = (mm - m) * 60.0 + s = int(s) + t = time(h, m, s) + return t + + +# This function converts a decimal angle to degrees, minutes and +# seconds format and returns a string "dddmmss". + +def floattoangle(f): + h = int(f) + mm = (f - h) * 60.0 + m = int(mm) + s = (mm - m) * 60.0 + s = int(s) + return '%(degree)03dd%(minute)02dm%(second)02ds' % \ + { 'degree' : h, 'minute' : abs(m), 'second': abs(s)} + + +# Convert a degrees-minutes-seconds angle to fractional degrees. +# (This function will accept any character as separator but you must +# specify degrees, minutes and seconds, e.g.: 012d34m56s, or degrees +# and minutes, e.g.: 012d34m.) + +def angletofloat(s): + try: + d = 0.0 + i = 0 + while ((i < 4) and (i < len(s))): + c = s[i] + c = c[0] + if (c.isdigit()): + i = i + 1 + else: + break + t = s[0:i] + d = float(t) + i = i + 1 + if (i < len(s)): + t = s[i:] + s = t + i = 0 + while ((i < 3) and (i < len(s))): + c = s[i] + c = c[0] + if (c.isdigit()): + i = i + 1 + else: + break + t = s[0:i] + d = d + float(t)/60.0 + i = i + 1 + if (i < len(s)): + t = s[i:] + s = t + i = 0 + while ((i < 3) and (i < len(s))): + c = s[i] + c = c[0] + if (c.isdigit()): + i = i + 1 + else: + break + t = s[0:i] + d = d + float(t)/3600.0 + return d + except: + return -1.0 + + +# Converts a utc date-time to julian day + +def jtime(dd): + y = dd.year + m = dd.month + d = dd.day + dd.hour / 24.0 + dd.minute / 1440.0 + dd.second / 86400.0 + if (m < 3): + m = m + 12 + y = y - 1 + a = int(y / 100.0) + b = 2 - a + int(a / 4) + c = int(365.25 * y) + cd = int(30.6001 * (m + 1)) + t = b + c + cd + d + 1720994.5 + return t + + +# Convert a utc date-time to gst (Greenwitch Siderial Time) + +def gst(d): + d1 = datetime(d.year, d.month, d.day, 0, 0, 0) + t1 = d.time() + j = jtime(d1) + s = j - 2451545.0 + t = s / 36525.0 + t0 = 6.697374558 + 2400.051336 * t + 0.000025862 * t * t + while (t0 < 0.0): + t0 = t0 + 24.0 + while (t0 > 24.0): + t0 = t0 - 24.0 + ut = t1.hour + t1.minute / 60.0 + t1.second / 3600.0 + ut = ut * 1.002737909 + t0 = t0 + ut + while (t0 < 0.0): + t0 = t0 + 24.0 + while (t0 > 24.0): + t0 = t0 - 24.0 + return t0 + + +# Convert gst to lst (local siderial time) + +def lst(g): + l = longitude / 15.0 + lst = g + l + while (lst < 0.0): + lst = lst + 24.0 + while (lst > 24.0): + lst = lst - 24.0 + return lst + + +# convert RA to hour angle + +def hrangl(ra, ut): + g = gst(ut) + l = lst(g) + h = l - ra + while (h < 0.0): + h = h + 24.0 + while (h > 24.0): + h = h - 24.0 + return h + + +# Adjust J2000 equatorial coordinates for precession. + +def epochpolartonow(polar, ut): + dec = polar[1] + ra = polar[0] +# this method is an approximation which fails when the object is near +# the celestial pole. So we return the epoch's ra -- a better answer +# than what the algorithm would produce + if (dec > 88.0): + return (ra, dec) + if (dec < -88.0): + return (ra, dec) +# dec is in tolerance. + n = (ut.year - 2000.0) + (ut.month - 1.0) / \ + 12.0 + 15.0 / 360.0 + ra = ra * 15.0 + ra1 = ra + ((3.0742 + \ + 1.33589 * sin(dtor(ra)) * \ + tan(dtor(dec))) / 3600.0) * n + dec1 = dec + (20.0383 * cos(dtor(ra))) / 3600.0 * n + return (ra1 / 15.0, dec1) + + +# Convert equatorial coordinates to azimuth, altitude + +def polartoazalt(polar, ut): + ra = polar[0] + dec = dtor(polar[1]) + h = dtor(hrangl(ra, ut) * 15.0) + l = dtor(latitude) + a = asin(sin(dec) * sin(l) + cos(dec) * cos(l) * cos(h)) + az = acos((sin(dec) - sin(l) * sin(a)) / (cos(l) * cos(a))) + sh = sin(h) + if (sh > 0.0): + az = 2.0 * pi - az + return (rtod(az), rtod(a)) + + +# Convert azimuth, altitude to equatorial coordinates + +def azalttopolar(azalt, ut): + az = dtor(azalt[0]) + a = dtor(azalt[1]) + h = dtor(lst(gst(ut)) * 15.0) + l = dtor(latitude) + dec = asin(sin(a) * sin(l) + cos(a) * cos(l) * cos(az)) + hp = acos((sin(a) - sin(l) * sin(dec)) / (cos(l) * cos(dec))) + if (sin(az) > 0): + hp = 2.0 * pi - hp + ra = h - hp + if (ra < 0.0): + ra = ra + 2.0 * pi + return (rtod(ra), rtod(dec)) + + +# solve M = E - (57.29578 * e) * sin(E) for E +# (not currently used -- the planetary results are accurate enough without it.) + +def eccentric_anomaly(M, e): + dE = .05 + estar = 180.0 * e / pi +# Iterate dM = M - En - estar * sin(En); dE = dM / (1 - e * cos(En)); +# En = En + dE until abs(dE) <= 0.001 note that M and estar are in +# degrees, as is E and dE so make the appropriate conversion to +# radians before doing trig. + En = M + while (abs(dE) > .001): + dM = M - (En - estar * sin(dtor(En))) + dE = dM / (1 - e * cos(dtor(En))) + En = En + dE + return En + + +# Determine the number of minutes from GMT specified by the zone offset string. + +def parse_zone_offset(s): + try: + bneg = False + oh = 0 + om = 0 + i = 0 + if (s[0] == '-'): + bneg = True + i = 1 + elif (s[0] == '+'): + i = 1 + j = i + while (s[j].isdigit()): + j = j + 1 + if (j == i): + return (0, 0) + oh = int(s[i:j]) + i = j + if (s[i] != ':'): + return (0, 0) + i = i + 1 + om = int(s[i:]) + if (bneg): + oh = -oh + om = -om + return (oh, om) + except: + return (0, 0) + + +# Determine the datetime value represented by the timestamp string. + +def parse_timestamp(s): + try: + if ((s[4] != '/') or (s[7] != '/') or (s[10] != ',') or (s[13] != ':')): + return (2000, 1, 1, 0, 0) + Y = int(s[0:4]) + M = int(s[5:7]) + D = int(s[8:10]) + h = int(s[11:13]) + m = int(s[14:]) + return (Y, M, D, h, m) + except: + return (2000, 1, 1, 0, 0) + + +# Get the GMT datetime value from the timestamp and offset strings + +def get_time_and_UTC_offset(timestr, offsetstr): + class TZ(tzinfo): + def __init__(self, offset, name): + self.__offset = timedelta(minutes = offset) + self.__name = name + + def utcoffset(self, dt): + return self.__offset + + def tzname(self, dt): + return self.__name + + def dst(self, dt): + return timedelta(0) + +# Parse the timestamp string and the UTC offset string into year, month, day, +# hour and minute and hour and minute, respectively. + + (sy, sM, sd, sh, sm) = parse_timestamp(timestr) + (oh, om) = parse_zone_offset(offsetstr) + +# now convert the zone offset to a timezone object + + tzo = oh * 60 + om + tz = TZ(tzo, "") + +# using the parsed time and timezone object, construct a datetime object that +# represents the local time. + + lt = datetime(sy, sM, sd, sh, sm, 0, 0, tz) + +# finally, subtract the zone object from the local time to get GMT. + + gt = lt - tz.utcoffset(0) + return gt + + +# Using the current local time, the nonlocal_timezone_correction (if any) and +# the GMT time, set the text in entry3 and entry4 to the "real" local time and +# the "real" zone offset, respectively + +def set_time_and_UTC_offset(): +# convert nonlocal_timezone_correction to a time displacement + to = timedelta(0, 60 * nonlocal_timezone_correction) + gt = datetime.utcnow() + lt = datetime.fromtimestamp(systime()) + dt = lt - gt + to + tt = lt + tt = tt + to + lts = tt.strftime("%Y/%m/%d,%H:%M") + dth = dt.days * 24 + int(dt.seconds / 3600) + dtm = (dt.seconds / 60) % 60 + utos = "%d:%02d" % (dth, dtm) + return (lts, utos) + + +def syntax_check_time(): + try: + s = entry3.get_text() +# this string must conform to YYYY/MM/DD,HH:MM format and be a valid +# date and time. + if (len(s) != 16): + return False + if ((s[4] != '/') or (s[7] != '/') or (s[10] != ',') or (s[13] != ':')): + return False + z = s[0:4] + if (not z.isdigit()): + return False + y = int(z) + z = s[5:7] + if (not z.isdigit()): + return False + x = int(z) + if ((x < 1) or (x > 12)): + return False + z = s[8:10] + if (not z.isdigit()): + return False + d = int(z) + if ((d < 1) or (d > 31)): + return False + if ((x == 1) or (x == 3) or (x == 5) or (x == 7) or (x == 8) or (x == 10) or (x == 12)): + pass + elif (x == 2): + if (d > 29): + return False + elif (((y % 4) != 0) and (d > 28)): + return False + elif (((y % 100) == 0) and (d > 28)): + if ((y % 400) != 0): + return False + elif (d > 30): + return False + z = s[11:13] + if (not z.isdigit()): + return False + h = int(z) + if (h > 23): + return False + z = s[14] + if (not z.isdigit()): + return False + m = int(z) + if (m > 59): + return False + return True + except: + return False + + +def syntax_check_zone(): + try: + s = entry4.get_text() +# this string must conform to [+|-]HH:MM and must be a valid time between -14:00 and 14:00. + l = len(s) + if (l < 4): + return False + if ((s[0] == '+') or (s[0] == '-')): + p = 1 + else: + p = 0 + if ((l - p) < 4): + return False + if (not s[p].isdigit()): + return False + if (s[p+1].isdigit()): + n = 1 + else: + n = 0 + if ((l - p - n) < 4): + return False + if (s[1 + p + n] != ':'): + return False + if ((not s[p + 2 + n].isdigit()) or (not s[p + 3 + n].isdigit())): + return False + z = s[p : p + 1 + n] + if (int(z) > 14): + return False + z = s[p + 2 + n : p + n + 4] + if (int(z) > 59): + return False + return True + except: + return False + + +def get_planet_index(name): +# return the index number corresponding to the name of the planet asked for. +# (Mercury = 0, Venus = 1, Moon = 2, Mars = 3, Jupiter = 4, Saturn = 5, +# Uranus = 6, Sun = 7) + (pname, L0, P0, N0, I, e, a, phi0, tau) = moon + if (name == pname): + return 2 + (pname, wbar, e, a, I, O, L0, dL) = sun + if (name == pname): + return 7 + for i in range(len(planets)): + (pname, wbar, e, a, I, O, L0, dL) = planets[i] + if (name == pname): + return i + return 6 + + +# ----------------------------------------------------------------------------- +# +# These controls affect the program's state-variables and must be global or we +# can't set or retrieve their values everywhere necessary: +# +# controls on menubar1 (_("what")): +fullscreen = ToolButton('view-fullscreen') +button1 = ToggleButtonTool(icon_off='night-off', icon_on='night-on') +button1.set_tooltip(_("Night Vision")) +button2 = ToggleButtonTool(icon_off='invert-off', icon_on='invert-on') +button2.set_tooltip(_("Invert Display")) +button3 = ToggleButtonTool(icon_off='left-right', icon_on='right-left') +button3.set_tooltip(_("Flip L/R")) +button4 = ToggleButtonTool(icon_off='constellations-off', + icon_on='constellations-on') +button4.set_tooltip(_("Draw Constellations")) +container2 = gtk.Table(columns=6, rows=1) +label6 = gtk.Label(_("Mag:")) +rb7 = gtk.RadioButton(None, _("1")) +rb8 = gtk.RadioButton(rb7, _("2")) +rb9 = gtk.RadioButton(rb7, _("3")) +rb10 = gtk.RadioButton(rb7, _("4")) +rb11 = gtk.RadioButton(rb7, _("5")) +rb12 = gtk.RadioButton(rb7, _("6")) +# controls on menubar2 (_("where")): +container3 = gtk.VBox() +container4 = gtk.VBox() +label1 = gtk.Label(_("Longitude:")) +entry1 = gtk.Entry() +entry1.set_width_chars(10) +rb1 = gtk.RadioButton(None, _("E")) +rb2 = gtk.RadioButton(rb1, _("W")) +label2 = gtk.Label(_("Latitude:")) +entry2 = gtk.Entry() +entry2.set_width_chars(10) +rb3 = gtk.RadioButton(None, _("N")) +rb4 = gtk.RadioButton(rb3, _("S")) +icon = Icon(icon_name='dialog-ok') +button5 = gtk.Button() +button5.set_image(icon) +icon.show() +button5.set_label(_('Ok')) +button5.show() +button51 = ToolButton('home') +button51.set_tooltip(_("Make home")) +button51.show() +# controls on menubar3 (_("when")): +rb5 = gtk.RadioButton(None, _("Now")) +rb6 = gtk.RadioButton(rb5, _("Specify")) +label4 = gtk.Label(_("Time:")) +entry3 = gtk.Entry() +entry3.set_width_chars(16) +label5 = gtk.Label(_("Offset:")) +entry4 = gtk.Entry() +entry4.set_width_chars(7) +icon = Icon(icon_name='dialog-ok') +button6 = gtk.Button() +button6.set_image(icon) +icon.show() +button6.set_label(_('Ok')) +button6.show() +# controls on menubar4 (_("Locate")): +labell1 = gtk.Label(_("Object type:")) +objtypecb = gtk.combo_box_new_text() +planetscb = gtk.combo_box_new_text() +constscb = gtk.combo_box_new_text() +starscb = gtk.combo_box_new_text() +container0 = gtk.HBox() +container1 = gtk.VBox() +dsoscb = gtk.combo_box_new_text() +# controls on last menubar (_("About")): +labela1 = gtk.Label(_("Version 2.0 (build 115) of 2010.04.21.1530 UT")) +labela2 = gtk.Label(" ") +labela3 = gtk.Label(_("See http://wiki.laptop.org/go/StarChart for help.")) +labela4 = gtk.Label(" ") + +# ------------------------------------------------------------------------------- + +# Set control states and values from the state variables. + +def initialize_controls(): + button1.set_active(nightvision) + button2.set_active(invertdisplay) + button3.set_active(fliphorizontally) + button4.set_active(drawconstellations) + rb12.set_active(limitingmagnitude >= 6.0) + rb11.set_active((limitingmagnitude >= 5.0) and (limitingmagnitude < 6.0)) + rb10.set_active((limitingmagnitude >= 4.0) and (limitingmagnitude < 5.0)) + rb9.set_active((limitingmagnitude >= 3.0) and (limitingmagnitude < 4.0)) + rb8.set_active((limitingmagnitude >= 2.0) and (limitingmagnitude < 3.0)) + rb7.set_active((limitingmagnitude >= 1.0) and (limitingmagnitude < 2.0)) +# rb7.set_active(limitingmagnitude < 1.0) + entry2.set_text(floattoangle(abs(latitude))) + rb4.set_active(latitude < 0.0) + rb3.set_active(latitude >= 0.0) + entry1.set_text(floattoangle(abs(longitude))) + rb2.set_active(longitude < 0.0) + rb1.set_active(longitude >= 0.0) + rb6.set_active(specifytime) + + +# ========================= PixelsToObjectMap Class ============================ +# +# This code is central to implementing the "Identify" feature. + +class PixelsToObjectMap(): + def __init__(self, context): + self.data = {} + self.context = context + + + def clear(self): + self.data.clear() + + + def add(self, x, y, type, name): + self.data[(x, y)] = (type, name) + + + def identify(self, x, y): +# find the brightest object nearest to (x,y) in the map. report its details. +# (search to a distance of 16 pixels before giving up.) Will favor brightest +# over nearest, because of the way the search algorithm is coded. (which may be a bug) + if self.found(x, y): +# The user got lucky and hit the target dead on. + (type, name) = self.data[(x,y)] + self.report(type, name) + else: + if (self.context.chart.magnifying): + w = 24 + else: + w = 8 +# Search an area (2*w) pixels square for an object. + for i in range(1,w+1): + xm = x - i + xx = x + i + ym = y - i + yx = y + i + (xf, yf) = self.search_square(xm, xx, ym, yx) + if (xf > xx): + type = "" + name = "" + else: + (type, name) = self.data[(xf, yf)] + if (type != ""): + self.report(type, name) + else: + self.report(_("There is no object here."), "") + + + def get_count(self): + return len(self.data.keys()) + + + def found(self, x, y): + return (x, y) in self.data + + + def search_square(self, left, right, top, bottom): +# Find all objects on the search square and return the brightest in event of +# their being more than one. Return the object's (x,y). +# +# An extended object is always be favored over a point object if the square being searched +# includes the coordinates of the extended object, since the extended object is drawn +# over the point objects and therefore hides them. + lfx = right + 1 + lfy = bottom + 1 + lfmag = 100 +# For the Sun, Moon and planets we will define their magnitude to be a rough +# average: by this definition, the Sun is -27, the Moon is -6.0, Venus is -3.5, +# Mercury is -1.5, Jupiter is -0.5, Saturn is +0.5, Mars is +1.0 and Uranus is +# +5.5. These values are a bit arbitrary, especially as planets can vary quite +# a lot, but are defined this way in order to ensure we have an unambiguous +# brightness relationship, irrespective of the planet's position in its orbit or +# phase of illumination. + pmag = [ -1.5, -3.5, -6.0, 1.0, -0.5, 0.5, 5.5, -27.0 ] + for x in range(left, right + 1): + for y in range(top, bottom + 1): + if self.found(x, y): + (type, name) = self.data[(x, y)] + if (type != "star") and (type != "planet"): +# Extended objects are special-case. + if (type == "dso-messier") or (type == "dso-ngc"): +# Simply return the coordinates at which the object was found -- we don't actually need +# the object's catalog data. + return (x, y) +# This object is not an extended object and therefore we will care about the object's +# brightness. So see if this object is brighter than the last one found, if any. If so, +# replace the last-found object with this one; if not, ignore this object. + elif (type == "planet"): + mag = pmag[get_planet_index(name)] + elif (type == "star"): +# Locate the catalog entry for the star and obtain its brightness. + (ra, dec, mag, cid) = star_chart[name] + else: + mag = 99 # placeholder -- object type is unknown. + if (mag < lfmag): + lfx = x + lfy = y + lfmag = mag + return (lfx, lfy) + + + def report(self, type, name): +# FIXME: for now, we only report the object's name and type. Eventually, we will report more +# and that will mean a more elaborate, multi-line "identification" object with multiple fields. + if (name == ""): + self.context.identifyobject.set_label(type) + else: + if (type == "dso-messier") or (type == "dso-ngc"): + type = _("deep-sky object") + elif (type == "planet"): + type = _("planet") + if (name == _("Sun")): + self.context.identifyobject.set_label(_("Object is: ") + name) + return + if (name == _("Moon")): + self.context.identifyobject.set_label(_("Object is: ") + name) + return + elif (type == "star"): + type = _("star") + self.context.identifyobject.set_label(_("Object is ") + type + ": " + name) + + + +# ========================= ObjectToPixelsMap Class ============================ +# +# This code is central to implementing the "Locate" feature. + +class ObjectToPixelsMap(): + def __init__(self, context): + self.data = {} + self.context = context + + + def clear(self): + self.data.clear() + + + def add(self, type, name, x, y): + self.data[(type, name)] = (x, y) + + def locate(self, type, name): +# Return the coordinates for the specified object type and name. +# Returns (-1, -1) if the object is not in the map (i.e.: was not plotted). + if self.found(type, name): + return self.data[(type, name)] + else: + return (-1, -1) + + + def found(self, type, name): + return (type, name) in self.data + + +# ============================= Location Class ================================ +# +# This class holds the x,y pixel coordinates of the located object so that the +# plotchart() method of the ChartDisplay class can plot a cross on that object. + +class Location(): + def __init__(self, context): + self.data = [False, 0, 0] + self.context = context + + + def clear(self): + self.data = [False, 0, 0] + + + def set(self, x, y): + self.data = [True, x, y] + + + def is_set(self): + return self.data[0] + + def plot_cross(self): + if (self.is_set()): + +# Draw a cross using heavy green lines, centered on the object with arms which +# are 50 pixels long. +# FIXME: As the display updates, the cross is always at x,y but the highlighted object +# eventually drifts westward. The code needs to compensate for that somehow. +# (Or else this is a feature, since it shows how the sky moves over time.) + + x = self.data[1] + 1 + y = self.data[2] + 1 + self.context.gc.set_foreground(self.context.colors[4]) + self.context.gc.set_line_attributes(5, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.context.window.draw_line(self.context.gc, x, y - 25, x, y + 25) + self.context.window.draw_line(self.context.gc, x - 25, y, x + 25, y) + self.context.gc.set_line_attributes(1, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.context.gc.set_foreground(self.context.colors[1]) + else: + pass + + +# ============================== ChartDisplay Object ============================ + +class ChartDisplay(gtk.DrawingArea): + def __init__(self, context): + super(ChartDisplay, self).__init__() + self.context = context + self.colors = {} + self.canplot = False + self.pangolayout = self.create_pango_layout("") + self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON1_MOTION_MASK | + gtk.gdk.BUTTON2_MOTION_MASK) + self.connect("button_press_event", self.pressing) + self.magnifying = False + self.mag_center = [0, 0] + if (not specifytime): + gobject.timeout_add(60000, self.timer1_cb) + +# The pmap object maintains a map between the chart display's pixel coordinates +# (x, y) and a visible object (type, name) for the "Identify" feature. +# The omap object maintains a map between the visible object (type, name) and +# the chart display's pixel coordinates (x, y) for the "Locate" feature. + + self.pmap = PixelsToObjectMap(context) + self.omap = ObjectToPixelsMap(context) + +# The Location object stores the result of a Locate operation. + + self.location = Location(self) + + def area_expose_cb(self, area, event): + +# Determine the area we can draw upon and adjust the chart accordingly. + + rect = self.get_allocation() + self.screensize = (rect.width, rect.height) + self.margin = 40 + self.diameter = min(self.screensize[0], self.screensize[1]) - \ + 2 * self.margin + self.xoffset = (self.screensize[0] - self.diameter) / 2 - self.margin + self.yoffset = (self.screensize[1] - self.diameter) / 2 - self.margin + +# Establish color selections (need only do this once). + + if (len(self.colors) == 0): + self.gc = self.style.fg_gc[gtk.STATE_NORMAL] + self.colormap = self.gc.get_colormap() + self.colors[0] = self.colormap.alloc_color('white') + self.colors[1] = self.colormap.alloc_color('black') + self.colors[2] = self.colormap.alloc_color('red') + self.colors[3] = self.colormap.alloc_color('gray') + self.colors[4] = self.colormap.alloc_color('green') + self.canplot = True + self.plotchart() + + +# Catch the one-minute timer interrupt + + def timer1_cb(self): +# do not redraw the chart if we're not advancing time. + if (not specifytime): + self.plotchart() + return True + + +# Convert az, alt (polar) coordinates to pixel position (x, y) + + def azalttoxy(self, polar): + az = dtor(polar[0]) + alt = polar[1] + +# radius is proportional to 90-alt + + r = self.diameter / 2.0 - alt * (self.diameter / 2.0 / 90.0) + +# convert r and az to x and y +# draw the chart so east is on the right by default. +# When flipped, east is on the left, like a map. + + if (fliphorizontally): + x = int(self.diameter / 2.0 + r * sin(az)) + else: + x = int(self.diameter / 2.0 - r * sin(az)) + y = int(self.diameter / 2.0 - r * cos(az)) + +# Return the pixel coordinate relative to the center of the plot. + + return (x,y) + + +# Convert pixel position (x, y) to horizon (az, alt) coordinates + + def xytohorizon(self, (x, y)): + dy = (self.margin - 2 + self.yoffset + self.diameter / 2.0) - y + dx = (self.margin - 2 + self.xoffset + self.diameter / 2.0) - x +# Compensate for which way the chart is flipped. + if (fliphorizontally): + dx = -dx + r = sqrt(dx * dx + dy * dy) +# prevent divide-by-zero: + if (r == 0): + return (0, 90.0) + az = asin(dx / r) +# determine proper quadrant for az -- origin is north and goes clockwise + if (dx >= 0) and (dy >= 0): # north to east + pass + elif (dx >= 0) and (dy < 0): # east to south + az = pi - az + elif (dx < 0) and (dy < 0): # south to west + az = pi - az + elif (dx < 0) and (dy >= 0): # west to north + az = 2.0 * pi + az +# alt is inversely proportional to radius + alt = 90.0 - 90.0 * r / (self.diameter / 2.0) + return (rtod(az), alt) + + def callback(self, widget, data=None): + +# Handle control changes here. + + global nightvision + global invertdisplay + global fliphorizontally + global drawconstellations + global limitingmagnitude + global saved_lmag + global longitude + global latitude + global specifytime + + if (data == None): + return False + elif (data == "night vision"): + nightvision = button1.get_active() + self.plotchart() + return False + elif (data == "invert display"): + invertdisplay = button2.get_active() + self.plotchart() + return False + elif (data == "flip horizontally"): + fliphorizontally = button3.get_active() + self.plotchart() + return False + elif (data == "draw constellations"): + drawconstellations = button4.get_active() + self.plotchart() + return False + elif (data == "home location set"): + s = entry1.get_text() + if (s.find("m") >= 0): + lon = angletofloat(s) + else: + try: + lon = float(s) + except: + lon = -1.0 + if ((lon < 0.0) or (lon > 180.0)): + self.lon_error() + entry1.set_text(floattoangle(abs(longitude))) + if (longitude < 0.0): + rb2.clicked() + else: + rb1.clicked() + return True + else: + longitude = lon + entry1.set_text(floattoangle(abs(longitude))) + if (rb2.get_active()): + longitude = -longitude + s = entry2.get_text() + if (s.find("m") >= 0): + lat = angletofloat(s) + else: + try: + lat = float(s) + except: + lat = -1.0 + if ((lat < 0.0) or (lat > 90.0)): + self.lat_error() + entry2.set_text(floattoangle(abs(latitude))) + if (latitude < 0.0): + rb4.clicked() + else: + rb3.clicked() + return True + else: + latitude = lat + entry2.set_text(floattoangle(abs(latitude))) + if (rb4.get_active()): + latitude = -latitude + self.location.clear() + dsoscb.set_active(-1) + starscb.set_active(-1) + planetscb.set_active(-1) + self.context.identifyobject.set_label("") + self.context.update_config() + self.plotchart() + return False + elif (data == "location change"): + s = entry1.get_text() + if (s.find("m") >= 0): + lon = angletofloat(s) + else: + try: + lon = float(s) + except: + lon = -1.0 + if ((lon < 0.0) or (lon > 180.0)): + self.lon_error() + entry1.set_text(floattoangle(abs(longitude))) + if (longitude < 0.0): + rb2.clicked() + else: + rb1.clicked() + return True + else: + longitude = lon + entry1.set_text(floattoangle(abs(longitude))) + if (rb2.get_active()): + longitude = -longitude + s = entry2.get_text() + if (s.find("m") >= 0): + lat = angletofloat(s) + else: + try: + lat = float(s) + except: + lat = -1.0 + if ((lat < 0.0) or (lat > 90.0)): + self.lat_error() + entry2.set_text(floattoangle(abs(latitude))) + if (latitude < 0.0): + rb4.clicked() + else: + rb3.clicked() + return True + else: + latitude = lat + entry2.set_text(floattoangle(abs(latitude))) + if (rb4.get_active()): + latitude = -latitude + self.location.clear() + dsoscb.set_active(-1) + starscb.set_active(-1) + planetscb.set_active(-1) + self.context.identifyobject.set_label("") + self.plotchart() + return False + elif (data == "user time"): + specifytime = True + saved_specifytime = True + return True + elif (data == "now time"): + specifytime = False + saved_specifytime = False + now = datetime.utcnow() + (tstr, ostr) = set_time_and_UTC_offset() + entry3.set_text(tstr) + entry4.set_text(ostr) + self.plotchart() + return True + elif (data == "time change"): + specifytime = rb6.get_active() + saved_specifytime = specifytime + if (specifytime): + if (syntax_check_time() == False): + self.time_error() + specifytime = False + saved_specifytime = False + now = datetime.utcnow() + (tstr, ostr) = set_time_and_UTC_offset() + entry3.set_text(tstr) + entry4.set_text(ostr) + return True + else: + if (syntax_check_zone() == False): + self.zone_error() + specifytime = False + saved_specifytime = False + now = datetime.utcnow() + (tstr, ostr) = set_time_and_UTC_offset() + entry3.set_text(tstr) + entry4.set_text(ostr) + return True + self.location.clear() + dsoscb.set_active(-1) + starscb.set_active(-1) + planetscb.set_active(-1) + self.context.identifyobject.set_label("") + self.plotchart() + return False + elif (data == "rb7 clicked"): + limitingmagnitude = 1.0 + saved_lmag = 1.0 + self.plotchart() + return False + elif (data == "rb8 clicked"): + limitingmagnitude = 2.0 + saved_lmag = 2.0 + self.plotchart() + return False + elif (data == "rb9 clicked"): + limitingmagnitude = 3.0 + saved_lmag = 3.0 + self.plotchart() + return False + elif (data == "rb10 clicked"): + limitingmagnitude = 4.0 + saved_lmag = 4.0 + self.plotchart() + return False + elif (data == "rb11 clicked"): + limitingmagnitude = 5.0 + saved_lmag = 5.0 + self.plotchart() + return False + elif (data == "rb12 clicked"): + limitingmagnitude = 6.0 + saved_lmag = 6.0 + self.plotchart() + return False + elif (data == "objtype sel"): +# Get selection and expose object selector control(s). + selstr = objtypecb.get_active_text() + if (selstr == _("Planets")): + for i in reversed(range(len(container0.get_children()))): + container0.remove(container0.get_children()[i]) + container0.add(planetscb) + planetscb.show() + container0.show() + self.location.clear() + dsoscb.set_active(-1) + starscb.set_active(-1) + planetscb.set_active(-1) + self.context.identifyobject.set_label("") + elif (selstr == _("Stars by Constellation")): + for i in reversed(range(len(container0.get_children()))): + container0.remove(container0.get_children()[i]) + container0.add(constscb) + container0.add(starscb) + starscb.get_model().clear() + starscb.show() + constscb.show() + container0.show() + self.location.clear() + dsoscb.set_active(-1) + starscb.set_active(-1) + planetscb.set_active(-1) + self.context.identifyobject.set_label("") + elif (selstr == _("Brightest Stars")): + for i in reversed(range(len(container0.get_children()))): + container0.remove(container0.get_children()[i]) + container0.add(starscb) + starscb.get_model().clear() +# Load the combobox with the names of stars whose magnitude is +1.50 or brighter + names = [] + for name, (ra, dec, mag, cid) in star_chart.iteritems(): + if (mag <= 1.50): + names = names + [name] + for name in sorted(names): + starscb.append_text(name) + starscb.show() + constscb.show() + container0.show() + self.location.clear() + dsoscb.set_active(-1) + starscb.set_active(-1) + planetscb.set_active(-1) + self.context.identifyobject.set_label("") + elif (selstr == _("Deep-sky Objects")): + for i in reversed(range(len(container0.get_children()))): + container0.remove(container0.get_children()[i]) + container0.add(dsoscb) + dsoscb.show() + container0.show() + self.location.clear() + dsoscb.set_active(-1) + starscb.set_active(-1) + planetscb.set_active(-1) + self.context.identifyobject.set_label("") + else: + self.plotchart() + self.location.clear() + dsoscb.set_active(-1) + starscb.set_active(-1) + planetscb.set_active(-1) + self.context.identifyobject.set_label("") + return False + elif (data == "constellation sel"): + csel = constscb.get_active_text() + if (csel == None): + pass + else: + const_id = abbrev_from_name[csel] + starscb.get_model().clear() +# Load the stars combobox with the names of all stars having this constellation ID. + names = [] + for name, (ra, dec, mag, cid) in star_chart.iteritems(): + if (cid == const_id): + names = names + [name] + for name in sorted(names): + starscb.append_text(name) + starscb.show() + return False + elif (data == "planet sel"): + selstr = planetscb.get_active_text() + self.locate("planet", selstr) + return False + elif (data == "star sel"): + selstr = starscb.get_active_text() + self.locate("star", selstr) + return False + elif (data == "dso sel"): + selstr = dsoscb.get_active_text() + self.locate("dso", selstr) + return False + + + def locate(self, type, name): + if (name == None): + return + if (type == "planet"): +# Selected a planet. Get its DEC. If visible, highlight it; otherwise, call +# not_located. + dec = dec_from_planet[name] + elif (type == "star"): +# Selected a star. Determine which and get its DEC. If visible, highlight it; +# otherwise, call not_located. + (ra, dec, mag, cid) = star_chart[name] + elif (type == "dso"): +# Selected a DSO. Determine which and get its DEC. If visible, highlight it; +# otherwise, call not_located. + for i in range(len(dso_chart)): + (nM, strCon, ra, dec, mag, majA, minA, posA, strT, strN) = dso_chart[i] + if (strN == ""): + if (name == nM): + break + elif (name == strN + " (" + nM + ")"): + break + if (nM[0:1] == 'M'): + type = "dso-messier" + elif (nM[0:1] == 'N'): + type = "dso-ngc" + else: + type = "dso" + else: +# Invalid object type selection. Ignore. + return + (x, y) = self.omap.locate(type, name) + if ((x < 0) or (y < 0)): + self.not_located(self.never_visible(dec)) + else: + self.context.identifyobject.set_label("") + self.located(x, y) + + + def magnify(self, x, y): + global limitingmagnitude + global specifytime + global saved_specifytime + if (can_magnify): +# Toggle magnification state. + if (self.magnifying): + self.magnifying = False + limitingmagnitude = saved_lmag + specifytime = saved_specifytime + else: + self.magnifying = True + limitingmagnitude = 9.0 + saved_specifytime = specifytime + specifytime = True +# Save x,y. Re-plot. + self.mag_center[0] = x + self.mag_center[1] = y +# rb6.set_active(specifytime) + self.adjust_toolbars() + self.context.identifyobject.set_label("") + self.location.clear() + dsoscb.set_active(-1) + starscb.set_active(-1) + planetscb.set_active(-1) + self.plotchart() + else: + pass + + + def adjust_toolbars(self): +# Hide the "When" and "Where" toolbars when magnifying and show them when not. +# Hide the magnitude radio buttons and the draw constellations button when magnifying and +# show these controls when not. + + if (self.magnifying): + button4.hide() + label6.hide() + rb7.hide() + rb8.hide() + rb9.hide() + rb10.hide() + rb11.hide() + rb12.hide() +# FIXME: There doesn't appear to be a good way to remove and restore +# toolbars wit having to rebuild them when they need to be displayed +# again. So for now we'll leave the tab on the toolbox and simply +# hide the toolbar. Which is a bit ugly, unfortunately. +# self.context.toolbox.remove_toolbar(2) # remove Where toolbar + self.context.where_toolbar.hide() +# self.context.toolbox.remove_toolbar(2) # remove When toolbar + self.context.when_toolbar.hide() + else: + button4.show() + label6.show() + rb7.show() + rb8.show() + rb9.show() + rb10.show() + rb11.show() + rb12.show() + self.context.where_toolbar.show() + self.context.when_toolbar.show() + + + def never_visible(self, dec): + if (latitude >= 0): + return ((latitude + dec) < 0) + else: + return ((latitude + dec) > 0) + + + def not_located(self, never): + self.location.clear() + self.plotchart() + if (never): + self.context.identifyobject.set_label(_("This object is always below the horizon at your location.")) + else: + if (self.magnifying): + self.context.identifyobject.set_label(_("This object is currently not in the field of view.")) + else: + self.context.identifyobject.set_label(_("This object is currently below your horizon.")) + + + def located(self, x, y): + self.location.set(x, y) + self.plotchart() + + +# If the left mouse button is pressed, identify the object at the +# cursor position. If the right mouse button is pressed either +# Magnify (if the whole sky is shown) or revert to whole sky. + + def pressing(self, widget, event): +# Ignore any press that's outside the display circle. + dx = (self.margin - 2 + self.xoffset + self.diameter / 2) - event.x + dy = (self.margin - 2 + self.yoffset + self.diameter / 2) - event.y + r = sqrt(dx * dx + dy * dy) + if (r <= self.diameter / 2.0): + if (event.button == 3): + self.magnify(event.x, event.y) + elif (event.button == 1): + self.pmap.identify(event.x, event.y) + else: + pass + + + def get_symbol_name(self, i): + if (i == 2): + return _("Moon") + if (i == 7): + return _("Sun") + else: + (name, wbar, e, a, I, O, L0, dL) = planets[i] + return name + + +# ------------------------------------------------------------------------------ +# +# Input Error Reporting methods + + def lon_error(self): +# FIXME: gtk.gdk.Display.beep() + self.context.identifyobject.set_label(_("Input Error: The longitude was not understood.")) + + def lat_error(self): +# FIXME: gtk.gdk.Display.beep() + self.context.identifyobject.set_label(_("Input Error: The latitude was not understood.")) + + def time_error(self): +# FIXME: gtk.gdk.Display.beep() + self.context.identifyobject.set_label(_("Input Error: The time was not understood.")) + + def zone_error(self): +# FIXME: gtk.gdk.Display.beep() + self.context.identifyobject.set_label(_("Input Error: The time zone offset was not understood.")) + + +# ------------------------------------------------------------------------------- +# +# Methods for drawing the chart: + + def plotchart(self): + if (self.canplot): + self.plotfield() + if (self.magnifying): + self.plot_magnified() + else: + self.plot_whole_sky() + return True + + + def plotfield(self): + global now + global zoneoffset + +# Erase prior plot + + if (not self.canplot): + return + self.cleararea() + if (invertdisplay): + if (nightvision): + self.gc.set_foreground(self.colors[2]) + else: + self.gc.set_foreground(self.colors[0]) + else: + self.gc.set_foreground(self.colors[1]) + self.window.draw_arc(self.gc, + True, + self.xoffset + self.margin - 2, + self.yoffset + self.margin - 2, + self.diameter + 4, + self.diameter + 4, + 0, + 23040) + +# Erase the pixels-to-object and object-to-pixels maps, since objects may now +# occupy different (x, y). + + self.pmap.clear() + self.omap.clear() + +# Plot sky circle + + if (not invertdisplay): + if (nightvision): + self.gc.set_foreground(self.colors[2]) + else: + self.gc.set_foreground(self.colors[0]) + else: + self.gc.set_foreground(self.colors[1]) + self.window.draw_arc(self.gc, + False, + self.xoffset + self.margin - 2, + self.yoffset + self.margin - 2, + self.diameter + 4, + self.diameter + 4, + 0, + 23040) + +# label the cardinal points. + + if (nightvision): + self.gc.set_foreground(self.colors[2]) + else: + self.gc.set_foreground(self.colors[1]) + self.pangolayout.set_text(_("N")) + self.window.draw_layout(self.gc, + self.xoffset + self.margin + self.diameter / 2 - 10, + self.margin - 30, self.pangolayout) + self.pangolayout.set_text(_("S")) + self.window.draw_layout(self.gc, + self.xoffset + self.margin + self.diameter / 2 - 10, + 2 * self.margin + self.diameter - 30, self.pangolayout) + if (not fliphorizontally): + self.pangolayout.set_text(_("E")) + else: + self.pangolayout.set_text(_("W")) + self.window.draw_layout(self.gc, + self.xoffset + self.margin - 30, + self.margin + self.diameter / 2 - 10, self.pangolayout) + if (not fliphorizontally): + self.pangolayout.set_text(_("W")) + else: + self.pangolayout.set_text(_("E")) + self.window.draw_layout(self.gc, + self.xoffset + self.margin + self.diameter + 10, + self.margin + self.diameter / 2 - 10, self.pangolayout) + if (not invertdisplay): + if (nightvision): + self.gc.set_foreground(self.colors[2]) + else: + self.gc.set_foreground(self.colors[0]) + else: + self.gc.set_foreground(self.colors[1]) + +# Set the time of plotting (now). + + if (not specifytime): + now = datetime.utcnow() + (tstr, ostr) = set_time_and_UTC_offset() + entry3.set_text(tstr) + entry4.set_text(ostr) + else: + now = get_time_and_UTC_offset(entry3.get_text(), entry4.get_text()) + (hh, mm) = parse_zone_offset(entry4.get_text()) + zoneoffset = 60 * hh + if (hh < 0): + zoneoffset = zoneoffset - mm + else: + zoneoffset = zoneoffset + mm + return True + + + def plot_whole_sky(self): + +# Plot the entire visible sky, starting with the stars. + + self.plot_all_stars() + self.plot_all_DSOs() + self.plot_all_constellations() + self.plot_all_planets() + return True + + + def plot_magnified(self): +# Plot a circular section of the sky 3.5 degrees in radius centered about the +# coordinates saved in self.mag_center. + self.plot_mag_stars() + self.plot_mag_DSOs() +# we don't plot constellation figures in the magnified view. + self.plot_mag_planets() + return True + + + def plot_all_stars(self): + for name, (ra, dec, mag, cid) in star_chart.iteritems(): + +# convert the ra and dec from the J2000 epoch to the plot time + + polar = epochpolartonow((ra, dec), now) + +# convert the equatorial coordinates to altitude and azimuth + + azalt = polartoazalt(polar, now) + +# plot any object that is more than 1 degree above the horizon + + if (azalt[1] >= 0.0175): + starsize = 2 + int(7.0 - mag) + (px, py) = self.azalttoxy(azalt) + px = px + self.margin - 2 + self.xoffset - starsize / 2 + py = py + self.margin - 2 + self.yoffset - starsize / 2 + +# Add the star to the omap even if it was too dim to plot. + + self.omap.add("star", name, px, py) + +# if the star is bright enough, add it to pmap and plot it. + + if (mag <= limitingmagnitude): + self.pmap.add(px, py, "star", name) + self.plot_star(px, py, starsize) + + + def plot_all_DSOs(self): + +# Plot the deep sky objects. + + for i in range(len(dso_chart)): + (nM, strCon, ra, dec, mag, majA, minA, posA, strT, strN) = dso_chart[i] + +# convert the ra and dec from the J2000 epoch to the plot time + + polar = epochpolartonow((ra, dec), now) + +# convert the equatorial coordinates to altitude and azimuth + + azalt = polartoazalt(polar, now) + if (azalt[1] >= 0.0175): + (px, py) = self.azalttoxy(azalt) + px = px + self.margin - 2 + self.xoffset + py = py + self.margin - 2 + self.yoffset + self.plot_DSO(strT, majA, minA, mag, px, py) + +# Add the DSO to the maps. + + if (strN == ""): + if (nM[0:1] == 'M'): + self.pmap.add(px, py, "dso-messier", nM) + self.omap.add("dso-messier", nM, px, py) + elif (nM[0:1] == 'N'): + self.pmap.add(px, py, "dso-ngc", nM) + self.omap.add("dso-ngc", nM, px, py) + else: + self.pmap.add(px, py, "dso", nM) + self.omap.add("dso", nM, px, py) + else: + if (nM[0:1] == 'M'): + self.pmap.add(px, py, "dso-messier", strN + " (" + nM +")") + self.omap.add("dso-messier", strN + " (" + nM + ")", px, py) + elif (nM[0:1] == 'N'): + self.pmap.add(px, py, "dso-ngc", strN + " (" + nM +")") + self.omap.add("dso-ngc", strN + " (" + nM + ")", px, py) + else: + self.pmap.add(px, py, "dso", strN + " (" + nM +")") + self.omap.add("dso", strN + " (" + nM + ")", px, py) + + + def plot_all_constellations(self): + +# Plot the constellation figures. This is essentially the same process as for +# plotting a star but we have to figure out the alt/az coordinates for both ends +# of the line segment. + + if (drawconstellations): + if (not invertdisplay): + if (nightvision): + self.gc.set_foreground(self.colors[2]) + else: + self.gc.set_foreground(self.colors[0]) + else: + self.gc.set_foreground(self.colors[1]) + for code, (name, lines) in figures.iteritems(): + for i in range(len(lines)): + (ra1, dec1, ra2, dec2) = lines[i] + polar1 = epochpolartonow((ra1, dec1), now) + azalt1 = polartoazalt(polar1, now) + if (azalt1[1] >= 0.0175): + (px1, py1) = self.azalttoxy(azalt1) + px1 = px1 + self.margin - 2 + self.xoffset + py1 = py1 + self.margin - 2 + self.yoffset + polar2 = epochpolartonow((ra2, dec2), now) + azalt2 = polartoazalt(polar2, now) + if (azalt2[1] >= 0.0175): + (px2, py2) = self.azalttoxy(azalt2) + px2 = px2 + self.margin - 2 + self.xoffset + py2 = py2 + self.margin - 2 + self.yoffset + self.window.draw_line(self.gc, px1, py1, px2, py2) + + + def plot_all_planets(self): + +# Plot the planets, the moon and the sun. + +# We need to adjust the Keplerian data by the number of centuries since the +# epoch. Mostly, this causes the planets to revolve around the sun at the rate +# of their orbital period (N in the following computations), but the orbits +# themselves will also change over time. +# FIXME: for now, we only worry about orbital motion, not additionally orbital +# mutation. + + T = (jtime(now) - 2451545.0) / 36525.0 + +# Calculate the earth's heliocentric radius and longitude coordinates so we +# can figure the geocentric ecliptic coordinates of the other planets. + + (name, wbar, e, a, I, O, L0, dL) = planets[2] + N = dL * T + while (N >= 360.0): + N = N - 360.0 + while (N < 0.0): + N = N + 360.0 +# FIXME: Should adjust the orbit's inclination and orientation +# I = I0 + dI * T +# wbar = wbar0 + dwbar * T +# O = O0 + dO * T + M = N + L0 - wbar + while (M >= 360.0): + M = M - 360.0 + while (M < 0.0): + M = M + 360.0 + Le = N + 2 * rtod(e) * sin(dtor(M)) + L0 + while (Le >= 360.0): + Le = Le - 360.0 + while (Le < 0.0): + Le = Le + 360.0 + v = Le - wbar + Re = a * (1 - e * e) / (1 + e * cos(dtor(v))) + +# now plot the planets. + + for i in range(len(planets)): + if (i == 2): + continue + (name, wbar, e, a, I, O, L0, dL) = planets[i] + N = dL * T + while (N >= 360.0): + N = N - 360.0 + while (N < 0.0): + N = N + 360.0 +# FIXME: For Mercury, especially, dI, dwbar and dO are large enough to matter. +# I = I0 + dI * T +# wbar = wbar0 + dwbar * T +# O = O0 + dO * T + +# compute the mean anomaly + + M = N + L0 - wbar + while (M >= 360.0): + M = M - 360.0 + while (M < 0.0): + M = M + 360.0 + +# compute the heliocentric longitude +# FIXME: Rather than solve Keppler's equation (M = E - 180/pi * e * sin(E)) for +# E, we will use the approximation E ~ M and calculate the heliocentric +# longitude using the mean anomaly instead of the eccentric anomaly. +# This approximation is pretty close except for Mercury and Pluto which +# have values of e high enough to make a difference. + + l = N + 2 * rtod(e) * sin(dtor(M)) + L0 + while (l >= 360.0): + l = l - 360.0 + while (l < 0.0): + l = l + 360.0 + +# now calculate the actual anomaly + + v = l - wbar + +# find the planet's radial distance + + r = a * (1 - e * e) / (1 + e * cos(dtor(v))) + +# calculate the heliocentric latitude + + psi = rtod(asin(sin(dtor(l - O)) * sin(dtor(I)))) + +# project to the ecliptic + + y = sin(dtor(l - O)) * cos(dtor(I)) + x = cos(dtor(l - O)) + k = rtod(atan2(y, x)) + lprime = k + O + while (lprime >= 360.0): + lprime = lprime - 360.0 + while (lprime < 0.0): + lprime = lprime + 360.0 + rprime = r * cos(dtor(psi)) + +# Using the coordinates we already calculated for the current position of the +# earth, convert the above coordinates to geocentric latitude and longitude + + if (i < 2): + +# for inner planets, use this equation to get longitude: + + y = rprime * sin(dtor(Le - lprime)) + x = Re - rprime * cos(dtor(Le - lprime)) + k = rtod(atan2(y, x)) + lam = 180.0 + Le + k + else: + +# for outer planets, use this equation to get longitude: + + y = Re * sin(dtor(lprime - Le)) + x = rprime - Re * cos(dtor(lprime - Le)) + k = rtod(atan2(y, x)) + lam = k + lprime + while (lam >= 360.0): + lam = lam - 360.0 + while (lam < 0.0): + lam = lam + 360.0 + +# all planets use the same equation for ecliptic latitude (and this atan has no quadrant ambiguity): + + y = rprime * tan(dtor(psi)) * sin(dtor(lam - lprime)) + x = Re * sin(dtor(lprime - Le)) + beta = rtod(atan(y/x)) + +# convert lam and beta to RA and DEC + + y = sin(dtor(lam)) * cos(eps) - tan(dtor(beta)) * sin(eps) + x = cos(dtor(lam)) + k = rtod(atan2(y, x)) + ra = k / 15.0 + while (ra < 0.0): + ra = ra + 24.0 + dec = rtod(asin(sin(dtor(beta)) * cos(eps) + cos(dtor(beta)) * \ + sin(eps) * sin(dtor(lam)))) +# remember this planet's declination. Locate needs to know. + dec_from_planet[name] = dec + +# convert to azalt + + azalt = polartoazalt((ra, dec), now) + +# convert to x,y and plot if the planet is above the horizon + + if (azalt[1] >= 0.0175): + (px, py) = self.azalttoxy(azalt) + px = px + self.margin - 2 + self.xoffset + py = py + self.margin - 2 + self.yoffset + self.plot_planetary_symbol(i, px, py) + +# Add the planet to the maps + + self.pmap.add(px, py, "planet", name) + self.omap.add("planet", name, px, py) + +# Plot the sun. This is virtually the same as for a planet, but we can simplify +# some computations because the sun is by definition on the ecliptic. + + (name, wbar, e, a, I, O, L0, dL) = sun + N = dL * T + while (N >= 360.0): + N = N - 360.0 + while (N < 0.0): + N = N + 360.0 + M = N + L0 - wbar + while (M >= 360.0): + M = M - 360.0 + while (M < 0.0): + M = M + 360.0 + v = M + 2 * rtod(e) * sin(dtor(M)) + while (v >= 360.0): + v = v - 360.0 + while (lam < 0.0): + v = v + 360.0 + lam = v + wbar + while (lam >= 360.0): + lam = lam - 360.0 + while (lam < 0.0): + lam = lam + 360.0 + y = sin(dtor(lam)) * cos(eps) + x = cos(dtor(lam)) + k = rtod(atan2(y, x)) + ra = k / 15.0 + while (ra < 0.0): + ra = ra + 24.0 + +# because beta is (by definition) 0, calculating dec is far simpler: + + dec = rtod(asin(sin(sin(eps) * sin(dtor(lam))))) +# remember this planet's declination. Locate needs to know. + dec_from_planet[name] = dec + azalt = polartoazalt((ra, dec), now) + if (azalt[1] >= 0.0175): + (px, py) = self.azalttoxy(azalt) + px = px + self.margin - 2 + self.xoffset + py = py + self.margin - 2 + self.yoffset + self.plot_planetary_symbol(7, px, py) + +# Add the sun to maps + + self.pmap.add(px, py, "planet", name) + self.omap.add("planet", name, px, py) + +# Plot the moon. Since the moon's orbit varies radically over time, there are +# a lot of "fudge factor" correction terms in these computations. + + (name, L0, P0, N0, I, e, a, phi0, tau) = moon + D = jtime(now) - float(tau) + N = 360.0 / 365.242191 * D + while (N >= 360.0): + N = N - 360.0 + while (N < 0.0): + N = N + 360.0 + M = N + L0 - P0 + while (M >= 360.0): + M = M - 360.0 + while (M < 0.0): + M = M + 360.0 + Ec = 2 * rtod(e) * sin(dtor(M)) + lam = Ec + L0 + while (lam >= 360.0): + lam = lam - 360.0 + while (lam < 0.0): + lam = lam + 360.0 + l = 13.176396 * D + L0 + while (l >= 360.0): + l = l - 360.0 + while (l < 0.0): + l = l + 360.0 + Mm = l - 0.1114041 * D - P0 + while (Mm >= 360.0): + Mm = Mm - 360.0 + while (Mm < 0.0): + Mm = Mm + 360.0 + N = N0 - 0.0529539 * D + while (N >= 360.0): + N = N - 360.0 + while (N < 0.0): + N = N + 360.0 + Ev = 1.2739 * sin(dtor(2 * (1 - lam) - Mm)) + Ae = 0.1858 * sin(dtor(M)) + A3 = 0.37 * sin(dtor(M)) + Mprime = Mm + Ev - Ae - A3 + Ec = 6.2886 * sin(dtor(Mprime)) + A4 = 0.214 * sin (dtor(2 * Mprime)) + lprime = l + Ev + Ec - Ae + A4 + V = 0.6583 * sin(dtor(2 * (lprime - lam))) + lon = lprime + V + Nprime = N - 0.16 * sin(dtor(M)) + y = sin(dtor(lon - Nprime)) * cos(dtor(I)) + x = cos(dtor(lon - Nprime)) + k = rtod(atan2(y, x)) + lamM = Nprime + k + while (lamM >= 360.0): + lamM = lamM - 360.0 + while (lamM < 0.0): + lamM = lamM + 360.0 + betM = rtod(asin(sin(dtor(lon - Nprime)) * sin(dtor(I)))) + y = sin(dtor(lamM)) * cos(eps) - tan(dtor(betM)) * sin(eps) + x = cos(dtor(lamM)) + k = rtod(atan2(y, x)) + ra = k / 15.0 + while (ra < 0.0): + ra = ra + 24.0 + while (ra > 24.0): + ra = ra - 24.0 + dec = rtod(asin(sin(dtor(betM)) * cos(eps) + cos(dtor(betM)) * \ + sin(eps) * sin(dtor(lamM)))) +# FIXME: We aren't accounting for parallax: RA and DEC are actual, not apparent. +# This could introduce as much as a degree (approx.) of error. Not a big deal if +# one is not using a telescope to observe the moon, except when the +# moon's position relative to a planet or star (as in conjunctions and +# occultations) matters. + +# remember this planet's declination. Locate needs to know. + dec_from_planet[name] = dec + +# convert to azalt + + azalt = polartoazalt((ra, dec), now) + if (azalt[1] >= 0.0175): + (px, py) = self.azalttoxy(azalt) + px = px + self.margin - 2 + self.xoffset + py = py + self.margin - 2 + self.yoffset + self.plot_planetary_symbol(2, px, py) + +# Add the moon to maps + + self.pmap.add(px, py, "planet", name) + self.omap.add("planet", name, px, py) + self.gc.set_foreground(self.colors[1]) + self.location.plot_cross() + return True + + + def vector_from_fov(self, ra, dec, now): + +# The center of the FOV is self.mag_center[]; [0] is its x; [1] is its y. These +# coordinates are screen-absolute. Make them center-of-plot-relative. + + x0 = self.mag_center[0] - (self.margin - 2 + self.xoffset + self.diameter / 2) +# if (fliphorizontally): +# x0 = (self.margin - 2 + self.xoffset + self.diameter / 2) - self.mag_center[0] +# else: +# x0 = self.mag_center[0] - (self.margin - 2 + self.xoffset + self.diameter / 2) + y0 = (self.margin - 2 + self.yoffset + self.diameter / 2) - self.mag_center[1] + y0 = self.mag_center[1] - (self.margin - 2 + self.yoffset + self.diameter / 2) + +# Convert ra, dec and now to az and alt and then to x and y. + + (az, alt) = polartoazalt((ra, dec), now) + if (alt < 0.0): + return (-9999, -9999) # object cannot possibly be seen. + (x, y) = self.azalttoxy((az, alt)) + +# Determine the difference between mag_center[] and (x,y). + + x = x - self.diameter / 2 + y = y - self.diameter / 2 + dx = x - x0 + dy = y - y0 + +# Apply magnification factor (about 25X; the field of 7x binoculars is around 7 degrees). + + dx = dx * 25 + dy = dy * 25 + +# Is this object within the plot circle? + + r = sqrt(dx * dx + dy * dy) + if (r > self.diameter / 2.0): + return (-9999, -9999) # object cannot possibly be seen. + +# Return dx,dy -- this is the coordinate of the object relative to the plot center. + + return (dx, dy) + + + def plot_mag_stars(self): + +# Plot the stars within the field of view + + for name, (ra, dec, mag, cid) in star_chart.iteritems(): + +# convert the ra and dec from the J2000 epoch to the plot time + + (ra1, dec1) = epochpolartonow((ra, dec), now) + starsize = 2 + int(9.0 - mag) + (px, py) = self.vector_from_fov(ra1, dec1, now) + if (px < -9000) or (py < -9000): + continue # object is outside of the FOV or within 0.5 percent of it. + px = int(px) + self.margin - 2 + self.xoffset + self.diameter / 2 - starsize / 2 + py = int(py) + self.margin - 2 + self.yoffset + self.diameter / 2 - starsize / 2 + +# Add the star to the omap even if it was too dim to plot. + + self.omap.add("star", name, px, py) + +# All stars are bright enough in magnified mode, so add this star to pmap and plot it. + + self.pmap.add(px, py, "star", name) + self.plot_star(px, py, starsize) + + + def plot_mag_DSOs(self): + +# plot only those DSOs that are within 3.5 degrees of the magnification coordinates. + + for i in range(len(dso_chart)): + (nM, strCon, ra, dec, mag, majA, minA, posA, strT, strN) = dso_chart[i] + +# convert the ra and dec from the J2000 epoch to the plot time + + (ra1, dec1) = epochpolartonow((ra, dec), now) + (px, py) = self.vector_from_fov(ra1, dec1, now) + if (px < -9000) or (py < -9000): + continue # object is outside of the FOV or within 0.5 percent of it. + px = int(px) + self.margin - 2 + self.xoffset + self.diameter / 2 + py = int(py) + self.margin - 2 + self.yoffset + self.diameter / 2 + self.plot_DSO(strT, majA, minA, mag, px, py) + +# Add the DSO to the maps. + + if (strN == ""): + if (nM[0:1] == 'M'): + self.pmap.add(px, py, "dso-messier", nM) + self.omap.add("dso-messier", nM, px, py) + elif (nM[0:1] == 'N'): + self.pmap.add(px, py, "dso-ngc", nM) + self.omap.add("dso-ngc", nM, px, py) + else: + self.pmap.add(px, py, "dso", nM) + self.omap.add("dso", nM, px, py) + else: + if (nM[0:1] == 'M'): + self.pmap.add(px, py, "dso-messier", strN + " (" + nM +")") + self.omap.add("dso-messier", strN + " (" + nM + ")", px, py) + elif (nM[0:1] == 'N'): + self.pmap.add(px, py, "dso-ngc", strN + " (" + nM +")") + self.omap.add("dso-ngc", strN + " (" + nM + ")", px, py) + else: + self.pmap.add(px, py, "dso", strN + " (" + nM +")") + self.omap.add("dso", strN + " (" + nM + ")", px, py) + + + def plot_mag_planets(self): + +# Plot the planets which are within the field of view + + T = (jtime(now) - 2451545.0) / 36525.0 + +# Calculate the earth's heliocentric radius and longitude coordinates so we +# can figure the geocentric ecliptic coordinates of the other planets. + + (name, wbar, e, a, I, O, L0, dL) = planets[2] + N = dL * T + while (N >= 360.0): + N = N - 360.0 + while (N < 0.0): + N = N + 360.0 +# FIXME: Should adjust the orbit's inclination and orientation +# I = I0 + dI * T +# wbar = wbar0 + dwbar * T +# O = O0 + dO * T + M = N + L0 - wbar + while (M >= 360.0): + M = M - 360.0 + while (M < 0.0): + M = M + 360.0 + Le = N + 2 * rtod(e) * sin(dtor(M)) + L0 + while (Le >= 360.0): + Le = Le - 360.0 + while (Le < 0.0): + Le = Le + 360.0 + v = Le - wbar + Re = a * (1 - e * e) / (1 + e * cos(dtor(v))) + +# now plot the planets. + + for i in range(len(planets)): + if (i == 2): + continue + (name, wbar, e, a, I, O, L0, dL) = planets[i] + N = dL * T + while (N >= 360.0): + N = N - 360.0 + while (N < 0.0): + N = N + 360.0 +# FIXME: For Mercury, especially, dI, dwbar and dO are large enough to matter. +# I = I0 + dI * T +# wbar = wbar0 + dwbar * T +# O = O0 + dO * T + +# compute the mean anomaly + + M = N + L0 - wbar + while (M >= 360.0): + M = M - 360.0 + while (M < 0.0): + M = M + 360.0 + +# compute the heliocentric longitude +# FIXME: Rather than solve Keppler's equation (M = E - 180/pi * e * sin(E)) for +# E, we will use the approximation E ~ M and calculate the heliocentric +# longitude using the mean anomaly instead of the eccentric anomaly. +# This approximation is pretty close except for Mercury and Pluto which +# have values of e high enough to make a difference. + + l = N + 2 * rtod(e) * sin(dtor(M)) + L0 + while (l >= 360.0): + l = l - 360.0 + while (l < 0.0): + l = l + 360.0 + +# now calculate the actual anomaly + + v = l - wbar + +# find the planet's radial distance + + r = a * (1 - e * e) / (1 + e * cos(dtor(v))) + +# calculate the heliocentric latitude + + psi = rtod(asin(sin(dtor(l - O)) * sin(dtor(I)))) + +# project to the ecliptic + + y = sin(dtor(l - O)) * cos(dtor(I)) + x = cos(dtor(l - O)) + k = rtod(atan2(y, x)) + lprime = k + O + while (lprime >= 360.0): + lprime = lprime - 360.0 + while (lprime < 0.0): + lprime = lprime + 360.0 + rprime = r * cos(dtor(psi)) + +# Using the coordinates we already calculated for the current position of the +# earth, convert the above coordinates to geocentric latitude and longitude + + if (i < 2): + +# for inner planets, use this equation to get longitude: + + y = rprime * sin(dtor(Le - lprime)) + x = Re - rprime * cos(dtor(Le - lprime)) + k = rtod(atan2(y, x)) + lam = 180.0 + Le + k + else: + +# for outer planets, use this equation to get longitude: + + y = Re * sin(dtor(lprime - Le)) + x = rprime - Re * cos(dtor(lprime - Le)) + k = rtod(atan2(y, x)) + lam = k + lprime + while (lam >= 360.0): + lam = lam - 360.0 + while (lam < 0.0): + lam = lam + 360.0 + +# all planets use the same equation for ecliptic latitude (and this atan has no quadrant ambiguity): + + y = rprime * tan(dtor(psi)) * sin(dtor(lam - lprime)) + x = Re * sin(dtor(lprime - Le)) + beta = rtod(atan(y/x)) + +# convert lam and beta to RA and DEC + + y = sin(dtor(lam)) * cos(eps) - tan(dtor(beta)) * sin(eps) + x = cos(dtor(lam)) + k = rtod(atan2(y, x)) + ra1 = k / 15.0 + while (ra1 < 0.0): + ra1 = ra1 + 24.0 + dec1 = rtod(asin(sin(dtor(beta)) * cos(eps) + cos(dtor(beta)) * \ + sin(eps) * sin(dtor(lam)))) + +# remember this planet's declination. Locate needs to know. + + dec_from_planet[name] = dec1 + (px, py) = self.vector_from_fov(ra1, dec1, now) + if (px < -9000) or (py < -9000): + continue # object is outside of the FOV or within 0.5 percent of it. + px = int(px) + self.margin - 2 + self.xoffset + self.diameter / 2 + py = int(py) + self.margin - 2 + self.yoffset + self.diameter / 2 + self.plot_planetary_symbol(i, px, py) + +# Add the planet to the maps + + self.pmap.add(px, py, "planet", name) + self.omap.add("planet", name, px, py) + +# Plot the sun. This is virtually the same as for a planet, but we can simplify +# some computations because the sun is by definition on the ecliptic. + + (name, wbar, e, a, I, O, L0, dL) = sun + N = dL * T + while (N >= 360.0): + N = N - 360.0 + while (N < 0.0): + N = N + 360.0 + M = N + L0 - wbar + while (M >= 360.0): + M = M - 360.0 + while (M < 0.0): + M = M + 360.0 + v = M + 2 * rtod(e) * sin(dtor(M)) + while (v >= 360.0): + v = v - 360.0 + while (lam < 0.0): + v = v + 360.0 + lam = v + wbar + while (lam >= 360.0): + lam = lam - 360.0 + while (lam < 0.0): + lam = lam + 360.0 + y = sin(dtor(lam)) * cos(eps) + x = cos(dtor(lam)) + k = rtod(atan2(y, x)) + ra1 = k / 15.0 + while (ra1 < 0.0): + ra1 = ra1 + 24.0 + +# because beta is (by definition) 0, calculating dec is far simpler: + + dec1 = rtod(asin(sin(sin(eps) * sin(dtor(lam))))) + (px, py) = self.vector_from_fov(ra1, dec1, now) + if (px >= -9000) and (py >= -9000): +# object is not outside of the FOV. + px = int(px) + self.margin - 2 + self.xoffset + self.diameter / 2 + py = int(py) + self.margin - 2 + self.yoffset + self.diameter / 2 + self.plot_planetary_symbol(7, px, py) + +# Add the sun to maps + + self.pmap.add(px, py, "planet", name) + self.omap.add("planet", name, px, py) + +# Plot the moon. Since the moon's orbit varies radically over time, there are +# a lot of "fudge factor" correction terms in these computations. + + (name, L0, P0, N0, I, e, a, phi0, tau) = moon + D = jtime(now) - float(tau) + N = 360.0 / 365.242191 * D + while (N >= 360.0): + N = N - 360.0 + while (N < 0.0): + N = N + 360.0 + M = N + L0 - P0 + while (M >= 360.0): + M = M - 360.0 + while (M < 0.0): + M = M + 360.0 + Ec = 2 * rtod(e) * sin(dtor(M)) + lam = Ec + L0 + while (lam >= 360.0): + lam = lam - 360.0 + while (lam < 0.0): + lam = lam + 360.0 + l = 13.176396 * D + L0 + while (l >= 360.0): + l = l - 360.0 + while (l < 0.0): + l = l + 360.0 + Mm = l - 0.1114041 * D - P0 + while (Mm >= 360.0): + Mm = Mm - 360.0 + while (Mm < 0.0): + Mm = Mm + 360.0 + N = N0 - 0.0529539 * D + while (N >= 360.0): + N = N - 360.0 + while (N < 0.0): + N = N + 360.0 + Ev = 1.2739 * sin(dtor(2 * (1 - lam) - Mm)) + Ae = 0.1858 * sin(dtor(M)) + A3 = 0.37 * sin(dtor(M)) + Mprime = Mm + Ev - Ae - A3 + Ec = 6.2886 * sin(dtor(Mprime)) + A4 = 0.214 * sin (dtor(2 * Mprime)) + lprime = l + Ev + Ec - Ae + A4 + V = 0.6583 * sin(dtor(2 * (lprime - lam))) + lon = lprime + V + Nprime = N - 0.16 * sin(dtor(M)) + y = sin(dtor(lon - Nprime)) * cos(dtor(I)) + x = cos(dtor(lon - Nprime)) + k = rtod(atan2(y, x)) + lamM = Nprime + k + while (lamM >= 360.0): + lamM = lamM - 360.0 + while (lamM < 0.0): + lamM = lamM + 360.0 + betM = rtod(asin(sin(dtor(lon - Nprime)) * sin(dtor(I)))) + y = sin(dtor(lamM)) * cos(eps) - tan(dtor(betM)) * sin(eps) + x = cos(dtor(lamM)) + k = rtod(atan2(y, x)) + ra1 = k / 15.0 + while (ra1 < 0.0): + ra1 = ra1 + 24.0 + while (ra1 > 24.0): + ra1 = ra1 - 24.0 + dec1 = rtod(asin(sin(dtor(betM)) * cos(eps) + cos(dtor(betM)) * \ + sin(eps) * sin(dtor(lamM)))) +# FIXME: We aren't accounting for parallax: RA and DEC are actual, not apparent. +# This could introduce as much as a degree (approx.) of error. Not a big deal if +# one is not using a telescope to observe the moon, except when the +# moon's position relative to a planet or star (as in conjunctions and +# occultations) matters. + +# remember this planet's declination. Locate needs to know. + dec_from_planet[name] = dec1 + (px, py) = self.vector_from_fov(ra1, dec1, now) + if (px >= -9000) and (py >= -9000): +# object is not outside of the FOV. + px = int(px) + self.margin - 2 + self.xoffset + self.diameter / 2 + py = int(py) + self.margin - 2 + self.yoffset + self.diameter / 2 + self.plot_planetary_symbol(2, px, py) + +# Add the moon to maps + + self.pmap.add(px, py, "planet", name) + self.omap.add("planet", name, px, py) + self.gc.set_foreground(self.colors[1]) + self.location.plot_cross() + return True + + + def plot_star(self, px, py, starsize): + self.window.draw_arc(self.gc, True, + px, + py, + starsize, + starsize, + 0, + 360*64) + + + def plot_planetary_symbol(self, i, px, py): + +# i is planet number (0 = mercury, 1= venus, 2 = moon, 3 = mars, 4 = jupiter, +# 5 = saturn, 6 = uranus, 7 = sun); (px, py) is the center point of the symbol. + + if (i == 0): + +# mercury + + self.gc.set_line_attributes(2, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.window.draw_arc(self.gc, False, px-12, py-12, 24, 24, 0, 360*64) + self.window.draw_arc(self.gc, False, px-5, py-7, 10, 10, 0, 360*64) + self.window.draw_line(self.gc, px+4, py-9, px+4, py-7) + self.window.draw_line(self.gc, px-4, py-9, px-4, py-7) + self.gc.set_line_attributes(1, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.window.draw_line(self.gc, px, py+3, px, py+7) + self.window.draw_line(self.gc, px-2, py+5, px+2, py+5) + elif (i == 1): + +# venus + + self.gc.set_line_attributes(2, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.window.draw_arc(self.gc, False, px-12, py-12, 24, 24, 0, 360*64) + self.window.draw_arc(self.gc, False, px-5, py-7, 10, 10, 0, 360*64) + self.gc.set_line_attributes(1, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.window.draw_line(self.gc, px, py+3, px, py+7) + self.window.draw_line(self.gc, px-2, py+5, px+2, py+5) + elif (i == 2): + +# moon + + self.gc.set_line_attributes(2, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.window.draw_arc(self.gc, False, px-12, py-12, 24, 24, 0, 360*64) + self.window.draw_polygon(self.gc, True, ((px+1, py-11), (px+4, py-11), + (px+5, py-10), (px+6, py-9), + (px+7, py-8), (px+8, py-7), + (px+10, py-2), (px+12,py), + (px+10, py+2), (px+8, py+7), + (px+7, py+8), (px+6, py+9), + (px+5, py+10), (px+4,py+11), + (px+1, py+11), (px+4, py+4), + (px+6, py+2), (px+6, py-2), + (px+4, py-4))) + self.gc.set_line_attributes(1, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + elif (i == 3): + +# mars + + self.gc.set_line_attributes(2, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.window.draw_arc(self.gc, False, px-12, py-12, 24, 24, 0, 360*64) + self.window.draw_arc(self.gc, False, px-6, py-4, 10, 10, 0, 360*64) + self.gc.set_line_attributes(1, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.window.draw_line(self.gc, px+2, py-2, px+6, py-6) + self.window.draw_line(self.gc, px+3, py-6, px+6, py-6) + self.window.draw_line(self.gc, px+6, py-6, px+6, py-3) + elif (i == 4): + +# jupiter + + self.gc.set_line_attributes(2, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.window.draw_arc(self.gc, False, px-12, py-12, 24, 24, 0, 360*64) + self.window.draw_line(self.gc, px-6, py-6, px-4, py-8) + self.window.draw_line(self.gc, px-4, py-8, px-2, py-8) + self.window.draw_line(self.gc, px-2, py-8, px+1, py-6) + self.window.draw_line(self.gc, px+1, py-6, px-5, py+2) + self.window.draw_line(self.gc, px-5, py+2, px+7, py+2) + self.window.draw_line(self.gc, px+4, py-8, px+4, py+7) + self.gc.set_line_attributes(1, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + elif (i == 5): + +# saturn + + self.gc.set_line_attributes(2, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.window.draw_arc(self.gc, False, px-12, py-12, 24, 24, 0, 360*64) + self.window.draw_line(self.gc, px-6, py-6, px-6, py+5) + self.window.draw_line(self.gc, px-6, py, px-5, py-1) + self.window.draw_line(self.gc, px-5, py-1, px-4, py-2) + self.window.draw_line(self.gc, px-4, py-2, px-1, py-3) + self.window.draw_line(self.gc, px-1, py-3, px, py-4) + self.window.draw_line(self.gc, px, py-4, px+1, py+1) + self.window.draw_line(self.gc, px+1, py+1, px-1, py+4) + self.window.draw_line(self.gc, px-1, py+4, px, py+5) + self.window.draw_line(self.gc, px, py+5, px+6, py+4) + self.gc.set_line_attributes(1, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + elif (i == 6): + +# uranus + + self.gc.set_line_attributes(2, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.window.draw_arc(self.gc, False, px-12, py-12, 24, 24, 0, 360*64) + self.window.draw_arc(self.gc, False, px-5, py-3, 10, 10, 0, 360*64) + self.window.draw_arc(self.gc, True, px-2, py, 4, 4, 0, 360*64) + self.gc.set_line_attributes(1, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.window.draw_line(self.gc, px, py-3, px, py-9) + self.window.draw_line(self.gc, px-2, py-5, px, py-9) + self.window.draw_line(self.gc, px+2, py-5, px, py-9) + else: + +# sun + + self.gc.set_line_attributes(2, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + self.window.draw_arc(self.gc, False, px-12, py-12, 24, 24, 0, 360*64) + self.window.draw_arc(self.gc, True, px-2, py-2, 4, 4, 0, 360*64) + self.gc.set_line_attributes(1, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, + gtk.gdk.JOIN_MITER) + + + def plot_DSO(self, type, maja, mina, mag, px, py): + if (not invertdisplay): + if (nightvision): + fg_color = self.colors[2] + else: + fg_color = self.colors[0] + else: + fg_color = self.colors[1] + if (mag > 9.0) or (maja < 10.0): +# this object is too dim or too small to be interesting + pass + else: +# FIXME: The Messier Catalog should include the object's position angle and +# this code should plot it in the proper orientation. What we have here +# will always plot the object with the major axis east-west and the +# minor axis north-south. + +# We plot these objects actual size. Of course, they are mostly too small to plot. + + if (self.magnifying): + dx = int(maja / 60.0 * self.diameter / 7.0) + dy = int(mina / 60.0 * self.diameter / 7.0) + if (dx < 2) or (dy < 2): + return # too small. + else: + dx = int(maja / 60.0 * self.diameter / 180.0) + dy = int(mina / 60.0 * self.diameter / 180.0) + if (dx < 2) or (dy < 2): + return # too small. + if (type == 'Gal'): +# plot as gray ellipse with solid outline. + self.gc.set_foreground(self.colors[3]) + self.window.draw_arc(self.gc, + True, + px - int(dx / 2), + py - int(dy / 2), + dx - 1, + dy - 1, + 0, + 23040) + self.gc.set_foreground(fg_color) + self.window.draw_arc(self.gc, + False, + px - int(dx / 2), + py - int(dy / 2), + dx, + dy, + 0, + 23040) + elif (type == 'PlN'): +# plot as gray circle with central dot + self.gc.set_foreground(self.colors[3]) + self.window.draw_arc(self.gc, + True, + px - int(dx / 2), + py - int(dx / 2), + dx, + dx, + 0, + 23040) + self.gc.set_foreground(fg_color) + self.window.draw_arc(self.gc, + True, + px - 2, + py - 2, + 4, + 4, + 0, + 23040) + elif (type == 'SNR') or (type == 'OCl'): +# plot as gray circle with no outline. + self.gc.set_foreground(self.colors[3]) + self.window.draw_arc(self.gc, + True, + px - int(dx / 2), + py - int(dx / 2), + dx, + dx, + 0, + 23040) + self.gc.set_foreground(fg_color) + elif (type == 'C/N') or (type == 'DfN'): +# plot as gray rectangle with no outline. + self.gc.set_foreground(self.colors[3]) + self.window.draw_rectangle(self.gc, + True, + px - int(dx / 2), + py - int(dy / 2), + dx, + dy) + self.gc.set_foreground(fg_color) + elif (type == 'GCl'): +# plot as gray circle with outline and central dot. + self.gc.set_foreground(self.colors[3]) + self.window.draw_arc(self.gc, + True, + px - int(dx / 2), + py - int(dx / 2), + dx, + dx, + 0, + 23040) + self.gc.set_foreground(fg_color) + self.window.draw_arc(self.gc, + False, + px - int(dx / 2), + py - int(dx / 2), + dx - 1, + dx - 1, + 0, + 23040) + self.window.draw_arc(self.gc, + True, + px - 2, + py - 2, + 4, + 4, + 0, + 23040) + else: +# Dbl = double star +# ??? = unknown or unclassified object +# these are not plotted. + pass + + + def cleararea(self): + +# Clear the drawing surface + + if (nightvision): + self.gc.set_foreground(self.colors[1]) + else: + self.gc.set_foreground(self.colors[3]) + self.window.draw_rectangle(self.gc, + True, + 1, + 1, + self.screensize[0], + self.screensize[1]) + label1.queue_draw() + label2.queue_draw() + label4.queue_draw() + label5.queue_draw() + label6.queue_draw() + button1.queue_draw() + button2.queue_draw() + button3.queue_draw() + button4.queue_draw() + button5.queue_draw() + button51.queue_draw() + button6.queue_draw() + rb1.queue_draw() + rb2.queue_draw() + rb3.queue_draw() + rb4.queue_draw() + rb5.queue_draw() + rb6.queue_draw() + rb7.queue_draw() + rb8.queue_draw() + rb9.queue_draw() + rb10.queue_draw() + rb11.queue_draw() + rb12.queue_draw() + + +# ============================== StarChart Object ============================= + +class StarChart(activity.Activity): + def __init__(self, handle): + global now + global zoneoffset + global abbrev_from_name + global longitude + global latitude + activity.Activity.__init__(self, handle) + os.chdir(get_bundle_path()) + self.set_title(_("Star Chart Activity")) + +# Iniitialize time to now and offset to our zone. + + now = datetime.utcnow() + (tstr, ostr) = set_time_and_UTC_offset() + (hh, mm) = parse_zone_offset(ostr) + zoneoffset = 60 * hh + if (hh < 0): + zoneoffset = zoneoffset - mm + else: + zoneoffset = zoneoffset + mm + +# If the file StarChart.cfg exists in the Activity's data directory, get the +# longitude and latitude settings stored there. This will override coordinates +# which might have been obtained from another source (e.g.: observatory.py, which +# is now deprecated). (Since this file may not exist, the code is a bit turgid.) + + self.datafile = os.path.join(activity.get_activity_root(),\ + "data", "StarChart.cfg") + try: + f = open(self.datafile, "r") + except: + pass + else: + try: + for data in f: + if (data[0:8] == 'Latitude'): + latitude = float(data[9:]) + elif(data[0:9] == 'Longitude'): + longitude = float(data[10:]) + except: + pass + f.close() + +# Build the translation from constellation name to constellation ID (needed in +# the "Locate" feature). + + for id in sorted(figures.keys()): + (name, lines) = figures[id] + abbrev_from_name[name] = id + +# Create toolbox + + self.what_toolbar = gtk.Toolbar() + self.where_toolbar = gtk.Toolbar() + self.when_toolbar = gtk.Toolbar() + self.locate_toolbar = gtk.Toolbar() + self.about_toolbar = gtk.Toolbar() + + if _have_toolbox: + toolbox = ToolbarBox() + activity_button = ActivityToolbarButton(self) + toolbox.toolbar.insert(activity_button, 0) + activity_button.show() + + what_toolbar_button = ToolbarButton( + page=self.what_toolbar, + icon_name='toolbar-view') + self.what_toolbar.show() + toolbox.toolbar.insert(what_toolbar_button, -1) + what_toolbar_button.show() + + where_toolbar_button = ToolbarButton( + page=self.where_toolbar, + icon_name='where') + self.where_toolbar.show() + toolbox.toolbar.insert(where_toolbar_button, -1) + where_toolbar_button.show() + + when_toolbar_button = ToolbarButton( + page=self.when_toolbar, + icon_name='when') + self.when_toolbar.show() + toolbox.toolbar.insert(when_toolbar_button, -1) + when_toolbar_button.show() + + locate_toolbar_button = ToolbarButton( + page=self.locate_toolbar, + icon_name='locate') + self.locate_toolbar.show() + toolbox.toolbar.insert(locate_toolbar_button, -1) + locate_toolbar_button.show() + + about_toolbar_button = ToolbarButton( + page=self.about_toolbar, + icon_name='about') + self.about_toolbar.show() + toolbox.toolbar.insert(about_toolbar_button, -1) + about_toolbar_button.show() + + separator = gtk.SeparatorToolItem() + separator.props.draw = False + separator.set_expand(True) + toolbox.toolbar.insert(separator, -1) + + stop_button = StopButton(self) + stop_button.props.accelerator = '<Ctrl>q' + toolbox.toolbar.insert(stop_button, -1) + stop_button.show() + + self.set_toolbar_box(toolbox) + toolbox.show() + self.toolbar = toolbox.toolbar + else: + toolbox = activity.ActivityToolbox(self) + self.set_toolbox(toolbox) + +# Fill the toolbox bars + + self._toolbar_add(self.what_toolbar, fullscreen) + separator = gtk.SeparatorToolItem() + separator.props.draw = True + separator.set_expand(False) + self._toolbar_add(self.what_toolbar, separator) + self._toolbar_add(self.what_toolbar, button1) + self._toolbar_add(self.what_toolbar, button2) + self._toolbar_add(self.what_toolbar, button3) + self._toolbar_add(self.what_toolbar, button4) + separator = gtk.SeparatorToolItem() + separator.props.draw = True + separator.set_expand(False) + self._toolbar_add(self.what_toolbar, separator) + self._toolbar_add(self.what_toolbar, label6) + container2.attach(rb7, 0, 1, 0, 1) + container2.attach(rb8, 1, 2, 0, 1) + container2.attach(rb9, 2, 3, 0, 1) + container2.attach(rb10, 3, 4, 0, 1) + container2.attach(rb11, 4, 5, 0, 1) + container2.attach(rb12, 5, 6, 0, 1) + rb7.show() + rb9.show() + rb11.show() + rb8.show() + rb10.show() + rb12.show() + self._toolbar_add(self.what_toolbar, container2) + self._toolbar_add(self.where_toolbar, label1) + self._toolbar_add(self.where_toolbar, entry1) + container3.add(rb1) + rb1.show() + container3.add(rb2) + rb2.show() + self._toolbar_add(self.where_toolbar, container3) + separator = gtk.SeparatorToolItem() + separator.props.draw = False + separator.set_expand(False) + self._toolbar_add(self.where_toolbar, separator) + self._toolbar_add(self.where_toolbar, label2) + self._toolbar_add(self.where_toolbar, entry2) + container4.add(rb3) + rb3.show() + container4.add(rb4) + rb4.show() + self._toolbar_add(self.where_toolbar, container4) + separator = gtk.SeparatorToolItem() + separator.props.draw = False + separator.set_expand(False) + self._toolbar_add(self.where_toolbar, separator) + self._toolbar_add(self.where_toolbar, button5) + separator = gtk.SeparatorToolItem() + separator.props.draw = False + separator.set_expand(False) + self._toolbar_add(self.where_toolbar, separator) + self._toolbar_add(self.where_toolbar, button51) + self._toolbar_add(self.when_toolbar, rb5) + self._toolbar_add(self.when_toolbar, rb6) + self._toolbar_add(self.when_toolbar, label4) + self._toolbar_add(self.when_toolbar, entry3) + self._toolbar_add(self.when_toolbar, label5) + self._toolbar_add(self.when_toolbar, entry4) + separator = gtk.SeparatorToolItem() + separator.props.draw = False + separator.set_expand(False) + self._toolbar_add(self.when_toolbar, separator) + self._toolbar_add(self.when_toolbar, button6) +# objtypecb.append_text(_("Constellations")) + objtypecb.append_text(_("Planets")) + objtypecb.append_text(_("Stars by Constellation")) + objtypecb.append_text(_("Brightest Stars")) + objtypecb.append_text(_("Deep-sky Objects")) + self._toolbar_add(self.locate_toolbar, labell1) + self._toolbar_add(self.locate_toolbar, objtypecb) + (name, wbar, e, a, I, O, L0, dL) = sun + planetscb.append_text(name) + (name, L0, P0, N0, I, e, a, phi0, tau) = moon + planetscb.append_text(name) + for i in range(len(planets)): + if (i == 2): + pass + else: + (name, wbar, e, a, I, O, L0, dL) = planets[i] + planetscb.append_text(name) + names = [] + for code, (name, lines) in figures.iteritems(): +# lines is an array of coordinates. we ignore it. + names = names + [name] + for name in sorted(names): + constscb.append_text(name) + for i in range(len(dso_chart)): + (nM, strCon, ra, dec, mag, majA, minA, posA, strT, strN) = dso_chart[i] + if (strN == ""): + dsoscb.append_text(nM) + else: + dsoscb.append_text(strN + " (" + nM +")") + container0.add(constscb) + self._toolbar_add(self.locate_toolbar, container0) + container1.add(labela1) + labela1.show() + container1.add(labela2) + labela2.show() + container1.add(labela3) + labela3.show() + container1.add(labela4) + labela4.show() + self._toolbar_add(self.about_toolbar, container1) + if not _have_toolbox: + toolbox.add_toolbar(_("What"), self.what_toolbar) + toolbox.add_toolbar(_("Where"), self.where_toolbar) + toolbox.add_toolbar(_("When"), self.when_toolbar) + toolbox.add_toolbar(_("Locate"), self.locate_toolbar) + toolbox.add_toolbar(_("About"), self.about_toolbar) + +# Create the GUI objects. + + scrolled = gtk.ScrolledWindow() + scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scrolled.props.shadow_type = gtk.SHADOW_NONE + self.chart = ChartDisplay(self) + eb = gtk.EventBox() + vbox = gtk.VBox(False) + self.identifyobject = gtk.Label("") + vbox.pack_start(self.identifyobject, expand=False) + vbox.pack_start(self.chart) + eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("gray")) + +# Stack the GUI objects. + + scrolled.add_with_viewport(vbox) + +# Connect the control widget events. + + fullscreen.connect("clicked", self._fullscreen_callback) + button1.connect("clicked", self.chart.callback, "night vision") + button2.connect("clicked", self.chart.callback, "invert display") + button3.connect("clicked", self.chart.callback, "flip horizontally") + button4.connect("clicked", self.chart.callback, "draw constellations") + rb7.connect("clicked", self.chart.callback, "rb7 clicked") + rb8.connect("clicked", self.chart.callback, "rb8 clicked") + rb9.connect("clicked", self.chart.callback, "rb9 clicked") + rb10.connect("clicked", self.chart.callback, "rb10 clicked") + rb11.connect("clicked", self.chart.callback, "rb11 clicked") + rb12.connect("clicked", self.chart.callback, "rb12 clicked") + button5.connect("clicked", self.chart.callback, "location change") + button51.connect("clicked", self.chart.callback, "home location set") + button6.connect("clicked", self.chart.callback, "time change") + rb6.connect("clicked", self.chart.callback, "user time") + rb5.connect("clicked", self.chart.callback, "now time") + self.chart.connect("expose_event", self.chart.area_expose_cb) + objtypecb.connect("changed", self.chart.callback, "objtype sel") + constscb.connect("changed", self.chart.callback, "constellation sel") + starscb.connect("changed", self.chart.callback, "star sel") + planetscb.connect("changed", self.chart.callback, "planet sel") + dsoscb.connect("changed", self.chart.callback, "dso sel") + +# Set the canvas + + self.set_canvas(scrolled) + +# Show the GUI stack. + + toolbox.show() + self.chart.show() + eb.show() + scrolled.show() + self.show_all() + +# FIXME: We can't do sharing yet, so hide the control for it. + + self.max_participants = 1 + if not _have_toolbox: + toolbar = toolbox.get_activity_toolbar() + toolbar.share.hide() + +# Establish initial state of controls and do a plot. + + initialize_controls() + self.chart.plotchart() + + def _toolbar_add(self, toolbar, component): + item = gtk.ToolItem() + item.add(component) + toolbar.insert(item, -1) + component.show() + item.show() + + def _fullscreen_callback(self, button): + ''' Hide the Sugar toolbars. ''' + self.fullscreen() + + def read_file(self, filename): + global nightvision + global invertdisplay + global fliphorizontally + global drawconstellations + global limitingmagnitude + global saved_lmag + global latitude + global longitude + global specifytime + global saved_specifytime + global zoneoffset + global now + + f = open(filename, "r") + nightvision = bool(int(self.metadata.get('Night_Vision', '0'))) + invertdisplay = bool(int(self.metadata.get('Invert', '0'))) + fliphorizontally = bool(int(self.metadata.get('Flip', '0'))) + drawconstellations = bool(int(self.metadata.get('Constellations', '1'))) + limitingmagnitude = float(self.metadata.get('Magnitude', '4.0')) + if 'Lmag' in self.metadata: + saved_lmag = float(self.metadata.get('Lmag')) + specifytime = bool(int(self.metadata.get('Specify_Time', '0'))) + saved_specifytime = specifytime + if (specifytime): + ts = self.metadata.get('Time', now.strftime("%Y/%m/%d,%H:%M")) + zs = self.metadata.get('Zone_Offset', str(zoneoffset)) + entry3.set_text(ts) + entry4.set_text(zs) + now = get_time_and_UTC_offset(entry3.get_text(), entry4.get_text()) + (hh, mm) = parse_zone_offset(entry4.get_text()) + zoneoffset = 60 * hh + if (hh < 0): + zoneoffset = zoneoffset - mm + else: + zoneoffset = zoneoffset + mm + initialize_controls() + self.chart.plotchart() + + + def write_file(self, filename): + f = open(filename, "w") + self.metadata['Night_Vision'] = str(int(nightvision)) + self.metadata['Invert'] = str(int(invertdisplay)) + self.metadata['Flip'] = str(int(fliphorizontally)) + self.metadata['Constellations'] = str(int(drawconstellations)) + self.metadata['Magnitude'] = str(limitingmagnitude) + self.metadata['Latitude'] = str(latitude) + self.metadata['Longitude'] = str(longitude) + self.metadata['Specify_Time'] = str(int(specifytime)) +# Unlike the other settings, it's easier to store the time and zone offset as +# they are represented in the text entry controls than to attempt to convert +# now and zoneoffset into a representation of local time and offset. + self.metadata['Time'] = entry3.get_text() + self.metadata['Zone_Offset'] = entry4.get_text() + self.metadata['Lmag'] = str(saved_lmag) + f.close() + + + def update_config(self): +# Modify the values for latitude and longitude. + data = [] + +# TODO: In future releases, StarChart.cfg may contain settings for other context variables. + + self.datafile = os.path.join(activity.get_activity_root(),\ + "data", "StarChart.cfg") + try: + f = open(self.datafile, "r") + except: + f = open(self.datafile, "w") + pass + else: + try: + for old_data in f: + if (old_data[0:8] == 'Latitude'): + pass + elif(old_data[0:9] == 'Longitude'): + pass + else: + data[len(data)] = old_data + except: + pass + f.close() + f = open(self.datafile, "w") + for i in range(len(data)): + f.write(data[i]) + f.write('Latitude=' + str(latitude) + '\n') + f.write('Longitude=' + str(longitude) + '\n') + f.close() |