Last active
October 27, 2025 14:46
-
-
Save fommil/0c346d749ed1bbc3b7399875add5ea7b to your computer and use it in GitHub Desktop.
Pocket Sky Atlas planner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Code | Alias | Name | |
|---|---|---|---|
| M1 | NGC1952 | Crab nebula | |
| M2 | NGC7089 | ||
| M3 | NGC5272 | ||
| M4 | NGC6121 | ||
| M5 | NGC5904 | ||
| M6 | NGC6405 | Butterfly cluster | |
| M7 | NGC6475 | Ptolemy cluster | |
| M8 | NGC6523 | Lagoon nebula | |
| M9 | NGC6333 | ||
| M10 | NGC6254 | ||
| M11 | NGC6705 | Wild Duck cluster | |
| M12 | NGC6218 | Gumball Globular cluster | |
| M13 | NGC6205 | Hercules globular cluster | |
| M14 | NGC6402 | ||
| M15 | NGC7078 | ||
| M16 | NGC6611 | Eagle nebula | |
| M17 | NGC6618 | Omega nebula | |
| M18 | NGC6613 | Black Swan cluster | |
| M19 | NGC6273 | ||
| M20 | NGC6514 | Trifid nebula | |
| M21 | NGC6531 | ||
| M22 | NGC6656 | Sagittarius cluster | |
| M23 | NGC6494 | ||
| M24 | IC4715 | Small Sagittarius Star Cloud | |
| M25 | IC4725 | ||
| M26 | NGC6694 | ||
| M27 | NGC6853 | Dumbbell nebula | |
| M28 | NGC6626 | ||
| M29 | NGC6913 | Cooling Tower | |
| M30 | NGC7099 | ||
| M31 | NGC224 | Andromeda galaxy | |
| M32 | NGC221 | ||
| M33 | NGC598 | Triangulum galaxy | |
| M34 | NGC1039 | ||
| M35 | NGC2168 | ||
| M36 | NGC1960 | ||
| M37 | NGC2099 | Starfish Cluster | |
| M38 | NGC1912 | ||
| M39 | NGC7092 | ||
| M41 | NGC2287 | ||
| M42 | NGC1976 | Orion nebula | |
| M43 | NGC1982 | De Mairan's nebula | |
| M44 | NGC2632 | Beehive cluster | |
| M46 | NGC2437 | ||
| M47 | NGC2422 | ||
| M48 | NGC2548 | ||
| M49 | NGC4472 | ||
| M50 | NGC2323 | ||
| M51 | NGC5194 | Whirlpool galaxy | |
| M52 | NGC7654 | ||
| M53 | NGC5024 | ||
| M54 | NGC6715 | ||
| M55 | NGC6809 | ||
| M56 | NGC6779 | ||
| M57 | NGC6720 | Ring nebula | |
| M58 | NGC4579 | ||
| M59 | NGC4621 | ||
| M60 | NGC4649 | ||
| M61 | NGC4303 | ||
| M62 | NGC6266 | ||
| M63 | NGC5055 | Sunflower galaxy | |
| M64 | NGC4826 | Black-eye galaxy | |
| M65 | NGC3623 | Leo Triplet | |
| M66 | NGC3627 | Leo Triplet | |
| M67 | NGC2682 | ||
| M68 | NGC4590 | ||
| M69 | NGC6637 | ||
| M70 | NGC6681 | ||
| M71 | NGC6838 | ||
| M72 | NGC6981 | ||
| M73 | NGC6994 | ||
| M74 | NGC628 | Phantom Galaxy | |
| M75 | NGC6864 | ||
| M76 | NGC650 | Little Dumbbell | |
| M77 | NGC1068 | ||
| M78 | NGC2068 | ||
| M79 | NGC1904 | ||
| M80 | NGC6093 | ||
| M81 | NGC3031 | Bode's galaxy | |
| M82 | NGC3034 | Cigar galaxy | |
| M83 | NGC5236 | Southern Pinwheel galaxy | |
| M84 | NGC4374 | ||
| M85 | NGC4382 | ||
| M86 | NGC4406 | ||
| M87 | NGC4486 | ||
| M88 | NGC4501 | ||
| M89 | NGC4552 | ||
| M90 | NGC4569 | ||
| M91 | NGC4548 | ||
| M92 | NGC6341 | ||
| M93 | NGC2447 | ||
| M94 | NGC4736 | ||
| M95 | NGC3351 | ||
| M96 | NGC3368 | ||
| M97 | NGC3587 | Owl nebula | |
| M98 | NGC4192 | ||
| M99 | NGC4254 | ||
| M100 | NGC4321 | ||
| M101 | NGC5457 | Pinwheel galaxy | |
| M102 | NGC5866 | Spindle galaxy | |
| M103 | NGC581 | ||
| M104 | NGC4594 | Sombrero galaxy | |
| M105 | NGC3379 | ||
| M106 | NGC4258 | ||
| M107 | NGC6171 | ||
| M108 | NGC3556 | ||
| M109 | NGC3992 | ||
| M110 | NGC205 | ||
| C1 | NGC188 | Polarissima Cluster | |
| C2 | NGC40 | Bow-Tie Nebula | |
| C3 | NGC4236 | ||
| C4 | NGC7023 | Iris Nebula | |
| C5 | IC342 | Hidden Galaxy | |
| C6 | NGC6543 | Cat's Eye Nebula | |
| C7 | NGC2403 | ||
| C8 | NGC559 | ||
| C10 | NGC663 | ||
| C11 | NGC7635 | Bubble Nebula | |
| C12 | NGC6946 | Fireworks Galaxy | |
| C13 | NGC457 | Owl Cluster, E.T. Cluster | |
| C14 | NGC869 | Double Cluster | |
| C15 | NGC6826 | Blinking Planetary | |
| C16 | NGC7243 | ||
| C17 | NGC147 | ||
| C18 | NGC185 | ||
| C19 | IC5146 | Cocoon Nebula | |
| C20 | NGC7000 | North America Nebula | |
| C21 | NGC4449 | ||
| C22 | NGC7662 | Blue Snowball | |
| C23 | NGC891 | Silver Sliver Galaxy | |
| C24 | NGC1275 | Perseus A | |
| C25 | NGC2419 | ||
| C26 | NGC4244 | ||
| C27 | NGC6888 | Crescent Nebula | |
| C28 | NGC752 | ||
| C29 | NGC5005 | ||
| C30 | NGC7331 | ||
| C31 | IC405 | Flaming Star Nebula | |
| C32 | NGC4631 | Whale Galaxy | |
| C33 | NGC6992 | East Veil Nebula | |
| C34 | NGC6960 | West Veil Nebula | |
| C35 | NGC4889 | Coma B | |
| C36 | NGC4559 | ||
| C37 | NGC6882 | ||
| C38 | NGC4565 | Needle Galaxy | |
| C39 | NGC2392 | Eskimo Nebula, Clown Face Nebula | |
| C40 | NGC3626 | ||
| C42 | NGC7006 | ||
| C43 | NGC7814 | ||
| C44 | NGC7479 | Superman Galaxy | |
| C45 | NGC5248 | ||
| C46 | NGC2261 | Hubble's Variable Nebula | |
| C47 | NGC6934 | ||
| C48 | NGC2775 | ||
| C49 | NGC2237 | Rosette Nebula | |
| C50 | NGC2239 | Satellite Cluster | |
| C51 | IC1613 | ||
| C52 | NGC4697 | ||
| C53 | NGC3115 | Spindle Galaxy | |
| C54 | NGC2506 | ||
| C55 | NGC7009 | Saturn Nebula | |
| C56 | NGC246 | Skull Nebula | |
| C57 | NGC6822 | Barnard's Galaxy | |
| C58 | NGC2360 | Caroline's Cluster | |
| C59 | NGC3242 | Ghost of Jupiter | |
| C60 | NGC4038 | Antennae Galaxies | |
| C61 | NGC4039 | Antennae Galaxies | |
| C62 | NGC247 | ||
| C63 | NGC7293 | Helix Nebula | |
| C64 | NGC2362 | Tau Canis Majoris Cluster | |
| C65 | NGC253 | Sculptor Galaxy | |
| C66 | NGC5694 | ||
| C67 | NGC1097 | ||
| C68 | NGC6729 | R CrA Nebula | |
| C69 | NGC6302 | Bug Nebula | |
| C70 | NGC300 | Sculptor Pinwheel Galaxy | |
| C71 | NGC2477 | ||
| C72 | NGC55 | String of Pearls Galaxy | |
| C73 | NGC1851 | ||
| C74 | NGC3132 | Eight Burst Nebula | |
| C75 | NGC6124 | ||
| C76 | NGC6231 | ||
| C77 | NGC5128 | Centaurus A | |
| C78 | NGC6541 | ||
| C79 | NGC3201 | ||
| C80 | NGC5139 | Omega Centauri | |
| C81 | NGC6352 | ||
| C82 | NGC6193 | ||
| C83 | NGC4945 | ||
| C84 | NGC5286 | ||
| C85 | IC2391 | Omicron Velorum Cluster | |
| C86 | NGC6397 | ||
| C87 | NGC1261 | ||
| C88 | NGC5823 | ||
| C89 | NGC6087 | S Normae Cluster | |
| C90 | NGC2867 | ||
| C91 | NGC3532 | Wishing Well Cluster | |
| C92 | NGC3372 | Eta Carinae Nebula | |
| C93 | NGC6752 | Great Peacock Globular | |
| C94 | NGC4755 | Jewel Box | |
| C95 | NGC6025 | ||
| C96 | NGC2516 | Southern Beehive Cluster | |
| C97 | NGC3766 | Pearl Cluster | |
| C98 | NGC4609 | ||
| C100 | IC2944 | Lambda Centauri Nebula | |
| C101 | NGC6744 | ||
| C102 | IC2602 | Theta Car Cluster | |
| C103 | NGC2070 | Tarantula Nebula | |
| C104 | NGC362 | ||
| C105 | NGC4833 | ||
| C106 | NGC104 | 47 Tucanae | |
| C107 | NGC6101 | ||
| C108 | NGC4372 | ||
| C109 | NGC3195 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # This script reads all the NGC/IC catalog entries and filters them with rules | |
| # similar to those that capture the Messier and Caldwell entries, differing by | |
| # type. It then outputs the entries in increasing DEC in tables that correspond | |
| # to the pages of the Pocket Sky Atlas, along with other useful information such | |
| # as type, magnitude and size. This makes it a useful table for unplanned / | |
| # adhoc star gazing with no technology to hand. The tables are used to select | |
| # targets, and the atlas is used to actually find them; this effectively | |
| # produces a very practical index. | |
| # | |
| # The sectors are: | |
| # | |
| # - Nh - (N + 3)h | |
| # - split into: | |
| # - 90...60 (plus all RA to 80), one chart. | |
| # - 60...30 (3h...1.5h <=> 1.5h...0h page split, repeated) | |
| # - 30...0 | |
| # - 0...-30 | |
| # - 30...-60 | |
| # -60...-90 (plus all RA from -80), one chart. | |
| # | |
| # Page number 1 is 0h. | |
| # Page number 11 is 3h, etc, until page 80. | |
| # | |
| # The txt file must be extracted from https://www.saguaroastro.org/sac-downloads/ | |
| # and placed in this folder. | |
| # | |
| # The following data can be used (with small changes) but isn't great for observation: | |
| # | |
| # wget https://github.com/mattiaverga/OpenNGC/raw/refs/tags/v20231203/database_files/NGC.csv | |
| import csv | |
| import math | |
| import re | |
| # A zero or positive number will remove from the northern sky. | |
| # A negative number will remove from the southern sky. | |
| # Use 90 or higher to see everything. | |
| my_limit = -20 | |
| # Manually created file with lookup from NGC/IC to Messier/Caldwell. | |
| # Some entries are not present because they don't have an NGC/IC. | |
| # | |
| # We use these to rewrite the name of entries. | |
| # | |
| # Code,Alias,Name | |
| codes = {} | |
| pops = {} | |
| with open("names.csv", newline="") as f: | |
| r = csv.DictReader(f) | |
| for row in r: | |
| codes[row["Alias"]] = row["Code"] | |
| pops[row["Alias"]] = row["Name"] | |
| ngc = [] | |
| # From OpenNGC | |
| # | |
| # Name;Type;RA;Dec;Const;MajAx;MinAx;PosAng;B-Mag;V-Mag;J-Mag;H-Mag;K-Mag;SurfBr;Hubble;Pax;Pm-RA;Pm-Dec;RadVel;Redshift;Cstar U-Mag;Cstar B-Mag;Cstar V-Mag;M;NGC;IC;Cstar Names;Identifiers;Common names;NED notes;OpenNGC notes;Sources | |
| def parse_openngc(): | |
| with open("NGC.csv", newline="") as f: | |
| r = csv.DictReader(f, delimiter=";") | |
| for row in r: | |
| if row["Type"] in {"NonEx", "Dup"}: | |
| continue | |
| name = row.get("Name").strip() | |
| name = re.sub("IC[0]+", "IC", name) | |
| name = re.sub("NGC[0]+", "NGC", name) | |
| row["Name"] = re.sub("NGC", "", name) | |
| if name in codes: | |
| cat = codes.pop(name) | |
| # print(f"found {name} as {cat}") | |
| row["Index"] = name | |
| row["Name"] = cat | |
| row["Popular"] = pops[name] | |
| ra = row.get("RA") | |
| h, m, s = [float(x) for x in ra.split(":")] | |
| row["ra_deg"] = (h + m/60.0 + s/3600.0) | |
| dec = row.get("Dec").strip() | |
| sign = -1 if dec.startswith("-") else 1 | |
| parts = dec.replace("+", "").replace("-", "").split(":") | |
| d, m, s = [float(x) for x in parts] | |
| dec = sign * (d + m/60.0 + s/3600.0) | |
| row["dec_deg"] = dec | |
| if abs(my_limit) >= 90 or (my_limit <= 0 and dec > my_limit) or (my_limit > 0 and dec < my_limit): | |
| ngc.append(row) | |
| sac_to_openngc_type = { | |
| "GALXY": "G", | |
| "1STAR": "*", | |
| "2STAR": "**", | |
| "3STAR": "Other", | |
| "4STAR": "Other", | |
| "8STAR": "Other", | |
| "NONEX": "NonEx", | |
| "G+C+N": "*Ass", | |
| "OPNCL": "OCl", | |
| "PLNNB": "PN", | |
| "GALCL": "GGroup", | |
| "GLOCL": "GCl", | |
| "ASTER": "Other", | |
| "DRKNB": "Neb", | |
| "BRTNB": "Neb", | |
| "SNREM": "Nova", | |
| "CL+NB": "Cl+N", | |
| "QUASR": "Other", | |
| "GX+DN": "Neb", | |
| "LMCOC": "OCl", | |
| "LMCGC": "GCl", | |
| "LMCDN": "Neb", | |
| "LMCCN": "*Ass", | |
| "GX+GC": "GCl", | |
| "SMCCN": "*Ass", | |
| "SMCGC": "GCl", | |
| "SMCOC": "OCl", | |
| "SMCDN": "Neb" | |
| } | |
| # From SAC | |
| # | |
| # And coerced into the fieldnames used by OpenNGC and by this script. | |
| # | |
| #|OBJECT|OTHER|TYPE|CON|RA|DEC|MAG|SUBR|U2K|TI|SIZE_MAX|SIZE_MIN|PA|CLASS|NSTS|BRSTR|BCHM|NGC DESCR|NOTES| | |
| # PK131+2.1|Abell3|PLNNB|CAS|0212.2|+6409|18.2|16|17|1|60s|||3b||18.8|||| | |
| def parse_sac(): | |
| with open("SAC_DeepSky_Ver81_QCQ.TXT", newline="") as f: | |
| fields = [c.replace("\"", "").strip() for c in f.readline().split(",") if c.strip()] | |
| r = csv.DictReader(f, delimiter=",", fieldnames=fields) | |
| next(r) | |
| for row in r: | |
| row["Name"] = row["OBJECT"].replace(" ", "") | |
| row["Type"] = sac_to_openngc_type[row["TYPE"].strip()] | |
| if row["Type"] in {"NonEx", "Dup"}: | |
| continue | |
| name = row["Name"] | |
| if name in {"NGC651"}: | |
| # not flagged as dup | |
| continue | |
| row["Name"] = re.sub("NGC", "", name) | |
| if name in codes: | |
| cat = codes.pop(name) | |
| row["Index"] = name | |
| row["Name"] = cat | |
| row["Popular"] = pops[name] | |
| else: | |
| # sometimes Other is much nicer | |
| other = row["OTHER"].strip() | |
| if other.startswith("Abell") and name.startswith("PK"): | |
| row["Name"] = other | |
| row["V-Mag"] = float(row["MAG"].strip()) | |
| def parse_size(v): | |
| if not v: | |
| return None | |
| elif v.endswith("s"): | |
| return float(v[:-1].strip()) / 60.0 | |
| elif v.endswith("m"): | |
| return float(v[:-1].strip()) | |
| row["MajAx"] = parse_size(row["SIZE_MAX"].strip()) | |
| row["MinAx"] = parse_size(row["SIZE_MIN"].strip()) | |
| v = float(row["SUBR"].strip()) | |
| if v < 99: | |
| row["SurfBr"] = v | |
| ra = row["RA"].strip() | |
| h, m = [float(x) for x in ra.split()] | |
| ra = (h + m / 60.0) | |
| row["ra_deg"] = ra | |
| frac, whole = math.modf(ra) | |
| s = frac * 60 | |
| row["RA"] = f"{round(h)}:{round(m)}:{round(s)}" | |
| dec = row["DEC"].strip() | |
| sign = -1 if dec.startswith("-") else 1 | |
| d, m = [float(x) for x in dec.replace('-', '').replace('+', '').split()] | |
| dec = sign * (d + m / 60.0) | |
| row["dec_deg"] = dec | |
| notes = row["NOTES"].strip() | |
| if notes: | |
| note = notes.split(";")[0] | |
| if not any(ch.isdigit() for ch in note): | |
| row["Common names"] = notes.replace(";", ",") | |
| if abs(my_limit) >= 90 or (my_limit <= 0 and dec > my_limit) or (my_limit > 0 and dec < my_limit): | |
| ngc.append(row) | |
| #parse_openngc() | |
| parse_sac() | |
| if len(codes) > 0: | |
| print(f"[WARNING] the following Catalog entries were not found: {codes}") | |
| # why the db doesn't have this is frustrating... | |
| def mu_from_fallback(m, a, b): | |
| if m < 99: | |
| if a > 0 and b > 0: | |
| return round(m + 2.5 * math.log10(a * b) + 8.63, 2) | |
| if a > 0 or b > 0: | |
| d = a or b | |
| return round(m + 5*math.log10(d) + 8.63, 2) | |
| return None | |
| def visual_filter(row, debug=False): | |
| name = row["Name"] | |
| m = float(row.get("V-Mag") or row.get("B-Mag") or 99) | |
| A = float(row.get("MajAx") or 0) | |
| B = float(row.get("MinAx") or 0) | |
| mu = float(row.get("SurfBr") or mu_from_fallback(m, A, B) or 99) | |
| typ = (row.get("Type") or "") | |
| size = max(A, B) | |
| if debug: | |
| return f"m={m}, A={A}, B={B}, mu={mu}, typ={typ}, size={size}" | |
| if re.match(r'^[MC]\d+', name): | |
| # force inclusion of the known catalog entries | |
| # print(f"{name} m={m}, A={A}, B={B}, mu={mu}, typ={typ}, size={size}") | |
| return True | |
| elif typ in {"G", "GPair", "GGroup", "GTrpl"}: | |
| # C5 sets the bar at mu=25, but we don't want to miss M101 big fuzzies | |
| return ((m <= 11 or mu <= 15) and 5 <= size) or 15 <= size and mu < 25 | |
| elif typ == "GCl": | |
| # size is fine, because this naturally enforces a brightness | |
| return 5 <= size | |
| elif typ == "OCl": | |
| # C8 sets the bar at m=10 | |
| # M29 sets the bar at size=3.6 | |
| # C94 (size=7.8), C102 doesn't have any m data | |
| # print(f"{name} is {mu}") | |
| return 10 <= size and mu < 15 | |
| elif typ == "PN": | |
| # I struggle to see anything smaller than | |
| # the Cats Eye Nebula (size = 0.9, mu = 17) | |
| return (m <= 10.0 or mu <= 18) and 0.9 <= size | |
| elif typ in {"HII", "RfN", "SNR", "Neb", "Cl+N", "*Ass", "EMN"}: | |
| # C31 sets the bar at mu=27 | |
| # M78 sets the bar at size=4.5 | |
| # | |
| # C49 (NGC2237) and C68 doesn't have mu, so we have a fallback for big things | |
| # | |
| # C46 is bright but small, matching globular cluster rules | |
| return mu <= 20 and 5 <= size | |
| else: | |
| if typ not in {"*", "**", "Other", "Nova"}: | |
| print(f"[WARNING] unknown type {typ}") | |
| exit(1) | |
| return False | |
| filtered = [e for e in ngc if visual_filter(e)] | |
| def for_pages(ra_min, ra_max, dec_min, dec_max): | |
| if ra_min < 0: | |
| ra_min = 24.0 - ra_min | |
| if ra_max > 24: | |
| ra_max = ra_max - 24.0 | |
| low, high = sorted((ra_min, ra_max)) | |
| return [e for e in filtered if low <= e["ra_deg"] <= high and dec_min <= e["dec_deg"] <= dec_max] | |
| def dedupe(lst): | |
| deduped = [] | |
| for e in lst: | |
| if e not in deduped: | |
| deduped.append(e) | |
| return deduped | |
| print('<style>') | |
| print('@page { margin-left: 5cm; }') | |
| print('h1 { font-size: 60%; }') | |
| print('.no_break { break-inside: avoid; }') | |
| print('table { border-collapse: collapse; width: 100%; text-align: left; border: 0; font-size: 60%; }') | |
| print('th, td { border: 0; white-space: nowrap; }') | |
| print('thead th { border-bottom: 2px solid #000; }') | |
| print('table tr:nth-child(even) { background-color: #f0f0f0; }') | |
| print('tbody tr + tr td { border-top: 1px solid #ccc; }') | |
| print('</style><body>\n') | |
| def render_size(i): | |
| arcmin, arcsec_dec = divmod(i, 1) | |
| degs, arcmin = divmod(arcmin, 60) | |
| arcsec = arcsec_dec * 60 | |
| d = "" | |
| m = "" | |
| s = "" | |
| if degs > 0: | |
| d = f"{round(degs)}°" | |
| if arcmin > 0 or (degs > 0 and arcsec > 0): | |
| m = f"{round(arcmin)}'" | |
| if arcsec > 0 and degs == 0: | |
| s = f"{round(arcsec)}\"" | |
| return f"{d}{m}{s}" | |
| for page in range(1, 81): | |
| relevant = [] | |
| ra = int((page - 1) / 10) * 3 | |
| dec = 90 | |
| # the commented lines here are an alternative way of constructing the data | |
| # that tries to put more into the sheets, with heavy duplication on the | |
| # boundaries, as it captures more of what's physically on the pages instead | |
| # of falling exactly within the RA boundaries. I don't want to delete it | |
| # because it took me a long time to figure out the bounds by hand! | |
| if page % 10 == 1: | |
| # opening page | |
| dec = 90 | |
| # relevant = dedupe(for_pages(ra, ra + 3, 55, 90) + for_pages(ra - 1, ra + 4, 60, 70) + for_pages(ra - 2, ra + 5, 70, 80) + for_pages(0, 24, 80, 90)) | |
| relevant = for_pages(0, 24, 80, 90) | |
| elif page % 10 == 2: | |
| dec = 60 | |
| # relevant = dedupe(for_pages(ra - 0.5, ra + 3.5, 25, 30) + for_pages(ra - 1, ra + 4, 30, 60)) | |
| elif page % 10 == 4: | |
| dec = 30 | |
| # relevant = for_pages(ra - 0.2, ra + 3.2, -5, 35) | |
| elif page % 10 == 6: | |
| dec = 0 | |
| # relevant = for_pages(ra - 0.2, ra + 3.2, -35, 5) | |
| elif page % 10 == 8: | |
| dec = -30 | |
| # relevant = dedupe(for_pages(ra - 0.5, ra + 3.5, -30, -25) + for_pages(ra - 1, ra + 4, -60, -30)) | |
| elif page % 10 == 0: | |
| dec = -60 | |
| # closing page | |
| # relevant = dedupe(for_pages(ra, ra + 3, -90, -55) + for_pages(ra - 1, ra + 4, -70, -60) + for_pages(ra - 2, ra + 5, -80, -70) + for_pages(0, 24, -90, -80)) | |
| relevant = for_pages(0, 24, -90, -80) | |
| else: | |
| # right side pages are included in the lefts | |
| continue | |
| relevant = dedupe(relevant + for_pages(ra, ra + 3, dec - 30, dec)) | |
| if not relevant: | |
| continue | |
| relevant.sort(key=lambda x: x["dec_deg"], reverse=True) | |
| if page > 1 and page % 10 == 1: | |
| print('\n\n<div style="page-break-after: always;"></div>\n') | |
| print('<div class="no_break">') | |
| print(f"<h1>Chart {page} ({ra}<sup>h</sup>...{ra+3}<sup>h</sup> / {dec-30}°...{dec}°)</h1>") | |
| print('<table><thead>') | |
| print('<tr><th>Name</th><th>RA</th><th>Dec</th><th>Type</th><th>Mag</th><th>Size</th><th>Bright</th><th>Notes</th></tr>') | |
| print('</thead><tbody>') | |
| for e in relevant: | |
| name = e["Name"] | |
| ra = e["RA"] | |
| h, m, s = [float(x) for x in ra.split(":")] | |
| ra = f"{h:g}h {m:g}m" | |
| dec = round(e["dec_deg"], 1) | |
| if dec >= 0: | |
| dec = f"+{dec}" | |
| typ = e["Type"] | |
| mag = round(float(e.get("V-Mag") or e.get("B-Mag") or 99), 1) | |
| a = round(float(e.get("MajAx") or 0), 1) | |
| b = round(float(e.get("MinAx") or 0), 1) | |
| bright = e.get("SurfBr") or mu_from_fallback(mag, a, b) or "-" | |
| if bright != "-": | |
| bright = round(float(bright), 1) | |
| if mag >= 99: | |
| mag = "-" | |
| size = "-" | |
| if a > 0 and b > 0: | |
| #size = f"{render_size(a)}x{render_size(b)}" | |
| size = render_size(max(a, b)) | |
| elif a: | |
| size = render_size(a) | |
| elif b: | |
| size = render_size(b) | |
| notes = e.get("Index") or "" | |
| if (common := e.get("Popular")): | |
| notes += "; " if notes else "" | |
| notes += common | |
| elif (common := e.get("Common names")): | |
| # these are usually lower quality notes | |
| notes += "; " if notes else "" | |
| notes += common | |
| notes = notes[:30] | |
| if not notes: | |
| notes = "-" | |
| print(f"<tr><td>{name}</td><td>{ra}</td><td>{dec}</td><td>{typ}</td><td>{mag}</td><td>{size}</td><td>{bright}</td><td>{notes}</tr>") | |
| print('</tbody></table></div>') | |
| print('</body>') | |
| # Local Variables: | |
| # compile-command: "./planner.py > plan.html && firefox plan.html" | |
| # End: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment