-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathsolarEclipses_makeKml.py
More file actions
executable file
·304 lines (262 loc) · 10.1 KB
/
solarEclipses_makeKml.py
File metadata and controls
executable file
·304 lines (262 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# solarEclipses_makeKml.py
#
# The python script in this file makes eclipse simulations.
#
# Copyright (C) 2012-2020 Dominic Ford <dcf21-www@dcford.org.uk>
#
# This code 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.
#
# You should have received a copy of the GNU General Public License along with
# this file; if not, write to the Free Software Foundation, Inc., 51 Franklin
# Street, Fifth Floor, Boston, MA 02110-1301, USA
# ----------------------------------------------------------------------------
"""
Make KML files describing the paths of solar eclipses, from JSON equivalents
"""
import glob
import json
import logging
import os
import sys
import zipfile
src_path = os.getcwd()
out_path = os.path.join(src_path, "output")
class KMLTemplate:
"""
Template to use for producing KML files describing the paths of eclipses
"""
kml_document = """\
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
<name><![CDATA[{title}]]></name>
<description><![CDATA[{description}]]></description>
<atom:author>
<atom:name>Dominic Ford</atom:name>
</atom:author>
<atom:link href="https://in-the-sky.org/eclipses" />
<Style id="eclipseOuter">
<PolyStyle>
<color>40000000</color>
<fill>1</fill>
<outline>1</outline>
</PolyStyle>
<LineStyle>
<color>FF00FFFF</color>
<width>2</width>
</LineStyle>
</Style>
<Style id="eclipseInner">
<PolyStyle>
<color>18000000</color>
<fill>1</fill>
<outline>1</outline>
</PolyStyle>
<LineStyle>
<color>FF00FF00</color>
<width>1</width>
</LineStyle>
</Style>
<Style id="eclipseTotal">
<LineStyle>
<color>FF0000FF</color>
<width>2</width>
</LineStyle>
</Style>
<Style id="eclipseAnnular">
<LineStyle>
<color>FFFF0000</color>
<width>2</width>
</LineStyle>
</Style>
{body}
</Document>
</kml>
"""
kml_path = """
<Placemark>
<name>{path_title}</name>
<styleUrl>#{path_style}</styleUrl>
<LineString>
<coordinates>
{point_string}
</coordinates>
</LineString>
</Placemark>
"""
kml_polygon = """
<Placemark>
<name>{polygon_title}</name>
<styleUrl>#{polygon_style}</styleUrl>
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>
{point_string}
</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
"""
def approx(flt, n=2):
"""
Round a floating point number to a set number of decimal places in order to make more compact JSON output.
:param flt:
Input floating-point value
:param n:
Number of decimal places to round to
:return:
Rounded floating point value
"""
divisor = float(pow(10, n))
if (type(flt) != int) and (type(flt) != float):
print("Warning: approx() passed argument of type <{}>".format(type(flt)))
return 0
return int(flt * divisor) / divisor
def generate_point_string(point_list):
"""
Turn a list of (longitude, latitude) points, each in degrees, into a text string for inclusion in a KML file.
KML files don't like paths switching from longitude +180 to -180 and vice versa, so we smooth over these transitions
by instead going out of the range +/-180 degrees.
:param point_list:
List of (longitude, latitude) points, each in degrees
:return:
Textual representation
"""
previous_longitude = None
point_string = ""
for point in point_list:
longitude, latitude = point
# Check whether longitude has flipped across the discontinuity at +/- 180 degrees
if previous_longitude is not None:
while longitude <= previous_longitude - 180:
longitude += 360
while longitude > previous_longitude + 180:
longitude -= 360
previous_longitude = longitude
# Add point to KML file
point_string += "{0:.4f},{1:.4f}\n".format(longitude, latitude)
return point_string
def make_kml():
"""
Make KML files describing the paths of solar eclipses, from JSON equivalents.
:return:
None
"""
# Read JSON files produced by <solarEclipses.py> describing the path of each solar eclipse
json_in_path = os.path.join(out_path, "*_path.json")
filenames = glob.glob(json_in_path)
json_data = []
for filename in filenames:
item = json.loads(open(filename).read())
item['filename'] = filename
json_data.append(item)
# Sort items in order of computational time, and name the worst offenders
json_data.sort(key=lambda x: x['compute_duration'])
json_data.reverse()
item_count = 10
for number, item in enumerate(json_data[:item_count]):
logging.info("Longest compute times {n}/{c}: {k} - {t} ({d:.1f} min)".
format(n=number + 1, c=len(json_data), k=item['event_key'], t=item['event_title'],
d=item['compute_duration'] / 60.))
# Sort items in order of duration error, and name the worst offenders
json_data.sort(key=lambda x: abs(x['event_duration_ford'] - x['event_duration_espenak']))
json_data.reverse()
item_count = 25
for number, item in enumerate(json_data[:item_count]):
logging.info("Worst durations {n}/{c}: {k} - {t} ({d1:5.1f} vs {d2:5.1f})".
format(n=number + 1, c=len(json_data), k=item['event_key'], t=item['event_title'],
d1=item['event_duration_ford'], d2=item['event_duration_espenak']))
# Sort items into chronological order
json_data.sort(key=lambda x: x['filename'])
# Compile list of paths of all total / annular eclipses
paths_all = []
for item in json_data:
# Ignore partial eclipses, with no path
if len(item['path']) == 0:
continue
# Calculate midpoint lat/lng
flattened_path = [point for path in item['path'] for point in path[1]]
midpoint = [approx(x) for x in flattened_path[int(len(flattened_path) / 2)][1:3]]
path_list = []
for x in item['path']:
# Shorten paths to include only every fifth point
points = [[approx(p[1]), approx(p[2])] for p in x[1][::5]]
# Make sure we include the final point
points.append([approx(x[1][-1][1]), approx(x[1][-1][2])])
# Add this path to the list of paths
path_list.append([x[0], points])
# Append item to list
paths_all.append({
'paths': path_list,
'event_key': item['event_key'],
'event_title': item['event_title'],
'month': item['event_month'],
'year': int(item['event_month'][-4:]),
'max_duration': item['event_duration_espenak'],
'mean_position': midpoint
})
# Write list of paths
filename = os.path.join(out_path, "paths_all.json")
with open(filename, "w") as f:
f.write(json.dumps(paths_all, separators=(',', ':')))
# Produce KML files
kml_template = KMLTemplate()
for item in json_data:
# Write HTML description for this eclipse
event_title = "{}, {}".format(item['event_title'], item['event_month'])
event_description = """
<div style="font-size: 13pt; text-align: left; white-space: nowrap;">
<p>
© 2011-2019 <a href="https://in-the-sky.org/eclipses">Dominic Ford / In-The-Sky.org</a>
</p><p>
Calculated using Dominic Ford's <a href="https://github.com/dcf21/eclipse-simulator">Eclipse Simulator</a>
<br />
based on the <a href="https://github.com/dcf21/ephemeris-compute-de430">JPL DE430 ephemeris</a>.
</p><p>
Downloaded from <a href="https://in-the-sky.org/eclipses">In-The-Sky.org</a>
</p>
</div>
"""
kml_body = ""
# Add paths where eclipse is total or annular
for path in item['path']:
point_string = generate_point_string(point_list=[point[1:3] for point in path[1]])
kml_body += kml_template.kml_path.format(path_title="Central path, {}".format(item['event_month']),
path_style="eclipseTotal" if path[0] else "eclipseAnnular",
point_string=point_string)
# Add contours
for contour in item['contours']:
if len(contour[1]) > 0:
point_string = generate_point_string(point_list=[point[0:2] for point in contour[1]])
kml_body += kml_template.kml_polygon.format(polygon_title=contour[0],
polygon_style=(
"eclipseOuter" if contour[0] == "Partial eclipse"
else "eclipseInner"),
point_string=point_string)
# Write KMZ output
file_stub = os.path.split(item['filename'])[1][:-5]
filename = os.path.join(out_path, "{}.kmz".format(file_stub))
with zipfile.ZipFile(filename, "w", zipfile.ZIP_DEFLATED) as myzip:
kml_filename = "{}.kml".format(file_stub)
with myzip.open(kml_filename, "w") as f:
f.write(kml_template.kml_document.format(
title=event_title,
description=event_description,
body=kml_body
).encode('utf-8'))
# Do it right away if we're run as a script
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO,
stream=sys.stdout,
format='[%(asctime)s] %(levelname)s:%(filename)s:%(message)s',
datefmt='%d/%m/%Y %H:%M:%S')
logger = logging.getLogger(__name__)
logger.info(__doc__.strip())
make_kml()