mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-10 21:32:23 +00:00
feat(satellites): file satellites under six classes with a toggle each
The satellites layer now sorts every object into Stations, Imaging, Telecom, Starlink, Probes or Debris, each with a chip and a live count on the layer row. Stations and Imaging show by default; the Starlink chip is the former DENSE catalog switch. Hidden classes keep their points, so a toggle needs no reload, and they leave the detection overlay and the voice position lists. A class comes, strongest evidence first, from CelesTrak SATCAT (object type and status, served by a new /api/celestrak-satcat/<group> proxy route and cached for a day), the bundled UCS Satellite Database pack (operator, owner country and purpose), name patterns for later launches, and the CelesTrak group. The core catalog also reads the resource, weather, planet, sar and science groups. Payloads with no evidence of life launched 20 or more years ago are filed as debris. The UCS pack, its build script and its data credit are added.
This commit is contained in:
1 parent
1b9bb844e6
commit
c56f098d07
44 files changed
+2530
-362
No files matched your search
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
'''Maintainer-only UCS Satellite Database pack build (openpyxl).
|
||||
|
||||
Reads the UCS Excel release and writes the compact pack the satellites layer
|
||||
loads: one row per satellite, keyed by NORAD number, with string-table indexes
|
||||
for the repeated text columns. Starlink rows are left out: the layer files
|
||||
every STARLINK object under its own class and operator without them.
|
||||
|
||||
Usage: python scripts/build-ucs-satellites.py "<path>/UCS-Satellite-Database 5-1-2023.xlsx"
|
||||
'''
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import openpyxl
|
||||
|
||||
LOG = logging.getLogger('build-ucs-satellites')
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
PACK = ROOT / 'src/data/local_data/ucs_satellites'
|
||||
VERSION = 'UCS-Satellite-Database 5-1-2023'
|
||||
DATA_CURRENT_TO = '2023-05-01'
|
||||
URL = 'https://www.ucsusa.org/satellite_database'
|
||||
|
||||
# Output column -> spreadsheet header.
|
||||
COLUMNS = {
|
||||
'operator': 'Operator/Owner',
|
||||
'operatorCountry': 'Country of Operator/Owner',
|
||||
'purpose': 'Purpose',
|
||||
'detailedPurpose': 'Detailed Purpose',
|
||||
'users': 'Users',
|
||||
'orbitClass': 'Class of Orbit',
|
||||
}
|
||||
STARLINK = re.compile(r'\bstarlink\b', re.I)
|
||||
|
||||
|
||||
def clean(value: object) -> str:
|
||||
'''One cell as tidy text; UCS's placeholders for nothing become ''.'''
|
||||
text = re.sub(r'\s+', ' ', str(value or '')).strip()
|
||||
return '' if text.lower() in ('', 'none', 'n/a') else text
|
||||
|
||||
|
||||
def build(xlsx: Path) -> dict:
|
||||
'''Read the release's first sheet into the pack the layer loads.'''
|
||||
sheet = openpyxl.load_workbook(xlsx, read_only=True, data_only=True).worksheets[0]
|
||||
rows = sheet.iter_rows(values_only=True)
|
||||
header = {str(h).strip(): i for i, h in enumerate(next(rows)) if h}
|
||||
names = [header['Current Official Name of Satellite'], header['Name of Satellite, Alternate Names']]
|
||||
norad_col = header['NORAD Number']
|
||||
tables = {key: [''] for key in COLUMNS}
|
||||
index = {key: {'': 0} for key in COLUMNS}
|
||||
records = {}
|
||||
source_rows = skipped_starlink = invalid_norad = 0
|
||||
for row in rows:
|
||||
if not any(row):
|
||||
continue
|
||||
source_rows += 1
|
||||
if any(STARLINK.search(clean(row[i])) for i in names):
|
||||
skipped_starlink += 1
|
||||
continue
|
||||
try:
|
||||
norad = int(float(row[norad_col]))
|
||||
except (TypeError, ValueError):
|
||||
invalid_norad += 1
|
||||
continue
|
||||
record = [norad]
|
||||
for key, column in COLUMNS.items():
|
||||
text = clean(row[header[column]])
|
||||
if key == 'orbitClass':
|
||||
text = text.upper()
|
||||
if text not in index[key]:
|
||||
index[key][text] = len(tables[key])
|
||||
tables[key].append(text)
|
||||
record.append(index[key][text])
|
||||
# A duplicate NORAD number keeps the first row, as UCS lists it.
|
||||
records.setdefault(norad, record)
|
||||
ordered = [records[n] for n in sorted(records)]
|
||||
return {
|
||||
'meta': {
|
||||
'source': 'Union of Concerned Scientists Satellite Database',
|
||||
'version': VERSION,
|
||||
'url': URL,
|
||||
'dataCurrentTo': DATA_CURRENT_TO,
|
||||
'license': 'Free, unrestricted use; acknowledgement requested (UCS database manual, section 3)',
|
||||
'curation': {
|
||||
'sourceRows': source_rows,
|
||||
'starlinkRowsDropped': skipped_starlink,
|
||||
'invalidNoradDropped': invalid_norad,
|
||||
'duplicateNoradDropped': source_rows - skipped_starlink - invalid_norad - len(ordered),
|
||||
'columns': COLUMNS,
|
||||
},
|
||||
'recordCount': len(ordered),
|
||||
},
|
||||
'fields': ['noradId', *COLUMNS],
|
||||
'strings': tables,
|
||||
'records': ordered,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
'''Build the pack from the release given on the command line.'''
|
||||
logging.basicConfig(level=logging.INFO, format='%(message)s')
|
||||
parser = argparse.ArgumentParser(description='Build the UCS satellite pack from the UCS Excel release.')
|
||||
parser.add_argument('xlsx', type=Path, help='UCS-Satellite-Database <version>.xlsx')
|
||||
pack = build(parser.parse_args().xlsx)
|
||||
PACK.mkdir(parents=True, exist_ok=True)
|
||||
out = PACK / 'ucs_satellites.json'
|
||||
# LF on every platform: the repository stores text with LF endings.
|
||||
out.write_text(
|
||||
json.dumps(pack, ensure_ascii=False, separators=(',', ':')) + '\n',
|
||||
encoding='utf-8',
|
||||
newline='\n',
|
||||
)
|
||||
LOG.info('%s: %d records, %d bytes', out.relative_to(ROOT), pack['meta']['recordCount'], out.stat().st_size)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1660,7 +1660,11 @@
|
||||
"src/i18n/locales/uk.json",
|
||||
"src/i18n/locales/zh-CN.json",
|
||||
"src/i18n/locales/zh-Hant.json",
|
||||
"src/ui/unitLabels.js"
|
||||
"src/ui/unitLabels.js",
|
||||
"src/layers/satellites/classes.js",
|
||||
"src/data/ucsSatellites.js",
|
||||
"src/data/bundledJson.js",
|
||||
"src/data/retryableLoad.js"
|
||||
],
|
||||
"external": ["cesium", "satellite.js"]
|
||||
},
|
||||
@@ -3348,7 +3352,9 @@
|
||||
"src/ui/wildlifeSightingPlacement.js",
|
||||
"src/overlays/worldOverlayChrome.js",
|
||||
"src/layers/wildlifeSightings/photoText.js",
|
||||
"src/layers/wildlifeSightings/performance.js"
|
||||
"src/layers/wildlifeSightings/performance.js",
|
||||
"src/layers/satellites/classes.js",
|
||||
"src/data/ucsSatellites.js"
|
||||
],
|
||||
"external": [
|
||||
"hls.js",
|
||||
@@ -3999,7 +4005,9 @@
|
||||
"src/layers/wildlifeSightings/sprites.js",
|
||||
"src/overlays/worldOverlayChrome.js",
|
||||
"src/layers/wildlifeSightings/photoText.js",
|
||||
"src/layers/wildlifeSightings/performance.js"
|
||||
"src/layers/wildlifeSightings/performance.js",
|
||||
"src/layers/satellites/classes.js",
|
||||
"src/data/ucsSatellites.js"
|
||||
],
|
||||
"external": [
|
||||
"hls.js",
|
||||
@@ -4501,7 +4509,13 @@
|
||||
},
|
||||
"satellites-source": {
|
||||
"exports": ["./layers/satellites/source"],
|
||||
"modules": ["src/layers/satellites/source.js"],
|
||||
"modules": [
|
||||
"src/layers/satellites/source.js",
|
||||
"src/layers/satellites/classes.js",
|
||||
"src/data/ucsSatellites.js",
|
||||
"src/data/bundledJson.js",
|
||||
"src/data/retryableLoad.js"
|
||||
],
|
||||
"external": []
|
||||
},
|
||||
"traffic-source-factory": {
|
||||
@@ -5118,7 +5132,9 @@
|
||||
"src/layers/alpr/records.js",
|
||||
"src/layers/alpr/tileRecords.js",
|
||||
"src/sources/infrastructureData.js",
|
||||
"src/sources/eventPacks.js"
|
||||
"src/sources/eventPacks.js",
|
||||
"src/layers/satellites/classes.js",
|
||||
"src/data/ucsSatellites.js"
|
||||
],
|
||||
"external": ["@mapbox/vector-tile", "pbf"]
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user