37
$\begingroup$

I would like to find a good estimate of the sum total amount of payload that humans have put into space, that is, over all years, all space programs, and all types of payload that reached orbit. Obviously a large fraction of these quickly spent more of their mass as propellant or de-orbited, so it has no physically meaning interpretation.

The total number of orbital launches each year can be found on Wikipedia.

orbital launches

It would follow that if you had the payload for each one of these launches, or a good estimate of average payload, you could find the total mass that has been sent into orbit over human history. I found one source that gives the total number of orbital launches as 5,210, which I think is as of 2010. An obvious approach is to take the number of launches, and multiply by some generic payload mass to get an estimate. So far this is the best I can find online.

There is the problem of military launches, in that we categorically don't have information about those payloads. These seem to be less than 1/3rd of the launches, so it shouldn't introduce any more error than that.

I did find one source that estimates that 255 tons were put into orbit in 2007. That year is probably not representative.

$\endgroup$
4
  • 2
    $\begingroup$ If you were willing to do the legwork, the Launch Log maintained by Jonathan McDowell is fairly complete, and at least will give you an idea of the specific launch vehicle and (in some cases) payload. $\endgroup$
    – user29
    Jul 16, 2013 at 22:50
  • 1
    $\begingroup$ Do we even publicly know about all military launches? And do you count failed launches? Payloads that deorbited within hours or days? $\endgroup$
    – gerrit
    Jul 16, 2013 at 23:50
  • 1
    $\begingroup$ @gerrit Since the starting premise is that we're counting things that later went out of orbit, it seems that anything which completed a full orbit is fairly obvious to include. Between getting into space and getting into orbit... that would be less obvious, but I would exclude them. Actually, I'm surprised you didn't mention launches going from ground to an orbit other than LEO, which is technically much more difficult to deal with. In every case, one does wonder if the category will come close to the margin of error to begin with. $\endgroup$
    – AlanSE
    Jul 17, 2013 at 2:10
  • 2
    $\begingroup$ I think this is a tractable question. After all, the answer is just a single number. The details behind that number could be gathered in a spreadsheet or a gist and linked with a little summary here. I might add an as-of date to the question though. This really is the kind of question that is useful here I think. $\endgroup$
    – Erik
    Jul 20, 2013 at 16:37

5 Answers 5

35
+50
$\begingroup$

Using the links in Erik's answer and comments, I threw this together.

#!/usr/bin/python3

"""
(C) 2014 Dotan Cohen

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3.

http://www.gnu.org/licenses/gpl-3.0.html
"""

import re
import time
import urllib.request
from bs4 import BeautifulSoup


def main():

    year_links = []
    year_launches = []
    total_mass = 0

    start_url = 'http://space.skyrocket.de/directories/chronology.htm'
    year_links = get_year_links(start_url)

    for link in year_links:
        launches = get_year_launches(link)
        for launch_url in launches:
            year_launches.append( launch_url )
        
    for launch in year_launches:
        time.sleep(0.1) # Be nice to server
        total_mass += get_launch_mass(launch)

    print("Total mass: %i KG" % (total_mass, ))

    return True


def get_year_links(start_url):

    year_links = []

    html = urllib.request.urlopen(start_url).read()
    parsed_page = BeautifulSoup(html, 'lxml')
    links = parsed_page.find_all('a')

    for link in links:
        link = str(link)
        if 'doc_chr' in link:
            tmp_link = link[link.find('"')+1:]
            tmp_link = tmp_link[:tmp_link.find('"')]
            year_links.append(tmp_link)

    return year_links


def get_year_launches(year_link):

    year_launches = []

    url = 'http://space.skyrocket.de/' + year_link[3:]
    html = urllib.request.urlopen(url).read()
    parsed_page = BeautifulSoup(html, 'lxml')
    links = parsed_page.find_all('a')

    for link in links:
        link = str(link)
        if 'doc_sdat' in link:
            tmp_link = link[link.find('"')+1:]
            tmp_link = tmp_link[:tmp_link.find('"')]
            year_launches.append(tmp_link)

    return year_launches


def get_launch_mass(launch_url):

    mass = 0

    try:
        url = 'http://space.skyrocket.de/' + launch_url[3:]
        html = urllib.request.urlopen(url).read()
        parsed_page = BeautifulSoup(html, 'lxml')

        for pre_mass_element in parsed_page.find_all(text='Mass:'):
            mass_element = pre_mass_element.find_next()
            mass = int(re.search(r'(\d+)', mass_element.text).group(0))

    except Exception:
        pass

    return mass


if __name__ == '__main__':
    main()

The code took 54 minutes to run, here is the output:

Total mass: 10500435 KG

The code visits a website that lists launches by year, and from each year's page it visits each launch's dedicated webpage. From there the code finds the "Mass" table cell. It then extracts the text of the next cell, pulls out the first integer, and adds that to the running total mass. Python!

Since the answer is in code, it can be periodically rerun as new launches occur and as details of old launches are revised.

Update 25 December, 2017:

Total mass: 13367669 KG

Update 23 December, 2019:

$ time ./mass.py

Total mass: 14466896 KG

real    199m30.311s
user    26m51.576s
sys     0m38.556s

Update 15 February, 2022:

$ ./mass.py

Total mass: 14933443 KG

Update 2 August, 2023:

$ time ./mass.py

Total mass: 16333981 KG

real    363m1.754s
user    74m24.274s
sys     1m1.314s
$\endgroup$
4
  • $\begingroup$ Not every item on this page, that is shot into orbit has a mass listed. $\endgroup$
    – mike
    Apr 25, 2017 at 10:43
  • $\begingroup$ Aha, almost a year has passed and I haven't tried the soup yet. OK a good reminder. Is the ~1 hour run time mostly waiting for the website, or is a lot of that local? Also, most of a rocket's mass is fuel, so it wouldn't apply to "sent to orbit". Dry mass of upper stages sometimes makes it to a real orbit. Is there any chance of breaking it down that way? $\endgroup$
    – uhoh
    Dec 25, 2017 at 16:59
  • 1
    $\begingroup$ @uhoh: I havn't timed the components, but adding timers to the sleep (nice to websites) and http requests would be simple enough. If you can find a reliable source of data for the masses that you mention, I'll code it. $\endgroup$
    – dotancohen
    Dec 25, 2017 at 17:06
  • $\begingroup$ space.stackexchange.com/a/58307/12102 nice agreement! $\endgroup$
    – uhoh
    Feb 15, 2022 at 14:02
9
$\begingroup$

My son modified the program to dump out data yearly, and also report the number of launches with unknown payload mass. I corrected the data using:

$$m_{estimate} = m_{known}*{n\over n-n_{unknown}}$$

Where:

$m_{estimate}$ is the estimate of mass launched in a given year

$n$ is the number of launches in the year

$n_{unknown}$ is the number of launches where we don't know the payload mass in the year

$m_{known}$ is the total payload mass for those launches that list the payload mass in a year.

With this correction factor, the total tally for all years becomes 17,976,052 kg.

(Update)

I also plotted this data, fit a line to it, and asked Excel to display the line fit formula.

Finally, I found some data on objects tracked by the US Space Surveillance Network and plotted that as well, just to get some indication as to whether the estimate was in the right ballpark.

The two curves together suggest that roughly 60% of the payloads we have sent up have either de-orbited or are not being tracked for some reason.

Mass In Orbit Versus Year

Some words of caution: I do recall that a year or two after we did this work, I tried to update the estimate I didn't like the new results. When I went looking, I found several minor examples of the data in the database not being organized and sorted perfectly. This could have been due to restructuring of the database that occurred after the original attempt was made, or it could have been errors that were there in the original attempt - I just don't know. Therefore, I would advise taking these results with a grain of salt. I think this represents an 80/20 rule estimate. I would not be surprised if someone in the future generates a more accurate estimate by putting in a lot of effort, or by training an AI to sift through the available data sources.

$\endgroup$
6
$\begingroup$

Here is part of the answer: a chronologigal list of all orbital launches and launch attempts for each year. The other part is probably to cross this with the payload each mission delivered to orbit.

$\endgroup$
2
  • $\begingroup$ Yes, this is half of it. But I haven't seen any list of flights with payloads. Maybe the only option is to pair the flight list with a launch vehicle list and use the known capacity of the launch vehicles. Even if the list was incomplete, a representative of number of flights for a given payload would allow a decent estimate of average payload, but I just haven't seen this nature of data at all. $\endgroup$
    – AlanSE
    Jul 20, 2013 at 17:24
  • $\begingroup$ That list I linked provides payloads, and usually mass as well. For example: space.skyrocket.de/doc_sdat/elektro-l.htm for the first mission in 2011: space.skyrocket.de/doc_chr/lau2011.htm. $\endgroup$
    – Erik
    Jul 20, 2013 at 17:26
5
$\begingroup$

I've updated Dotancohen's answer to be asynchronous, report progress, and save everything to a csv. A reminder that if you crank up the max connections, you risk getting throttled.

#!/usr/bin/python3

"""
(C) 2023 Christopher Overbeck

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3.

http://www.gnu.org/licenses/gpl-3.0.html
"""

import re
import time
from bs4 import BeautifulSoup
import aiohttp
import asyncio
import csv

# extension of Semaphore that also throttles the number of requests per second
class ThrottledSemaphore(asyncio.Semaphore):
    def __init__(self, value, rate):
        super().__init__(value)
        self.rate = rate
        self.last_request = 0

    async def acquire(self):
        await super().acquire()
        now = time.time()
        if now - self.last_request < self.rate:
            await asyncio.sleep(self.rate - (now - self.last_request))
        self.last_request = time.time()

async def main():
    # prints the mass launched for each year. Also reports number of launches with unknown (0) mass
    start_url = 'http://space.skyrocket.de/directories/chronology.htm'
    max_concurrent_connections = 5
    max_connections_per_second = 10
    semaphore = ThrottledSemaphore(max_concurrent_connections, 1/max_connections_per_second)

    async with aiohttp.ClientSession() as session:
        year_links = await get_year_links(session, start_url)
        coro = [get_year(semaphore, session, link) for link in year_links]
        tasks = [asyncio.create_task(c) for c in coro]
        done, pending = await asyncio.wait(tasks)
        years = [task.result() for task in done]
        print("---")
        for year in years:
            print(f"{year[0]}: {year[1]} KG, {year[2]} launches, {year[3]} unknown mass")
        print("---")

        with open('launches.csv', 'w') as f:
            writer = csv.writer(f)
            writer.writerow(['year', 'total_mass', 'num_launches', 'num_unknown_mass'])
            for year in years:
                writer.writerow([year[0], year[1], year[2], year[3]])


    while True:
        await asyncio.sleep(1)

async def get_year_links(client_session: aiohttp.ClientSession, start_url: str):
    """Returns a list of links to each year's launches"""
    year_links = []
    async with client_session.get(start_url) as response:
        html = await response.text()
        parsed_page = BeautifulSoup(html, features="html.parser")
        links = parsed_page.find_all('a')
        for link in links:
            link = str(link)
            if 'doc_chr' in link:
                tmp_link = link[link.find('"')+1:]
                tmp_link = tmp_link[:tmp_link.find('"')]
                year_links.append(tmp_link)
    return year_links

class Counter:
    def __init__(self, max_count: int, label: str = None):
        self.count = 0
        self.max_count = max_count
        self.label = label

    def increment(self):
        self.count += 1
        print(f"{self.label} {self.count}/{self.max_count}")

async def get_year(semaphore: asyncio.Semaphore, client_session: aiohttp.ClientSession, link: str):
    """Returns (year, total_mass, num_launches, num_unknown_mass) for the passed year link"""
    # links look like ../doc_chr/lau1957.htm
    year = int(re.search(r'(\d+)', link).group(0))
    year_launches = []
    async with semaphore:
        async with client_session.get('http://space.skyrocket.de/' + link[3:]) as response:
            html = await response.text()
            parsed_page = BeautifulSoup(html, features="html.parser")
            links = parsed_page.find_all('a')
            for link in links:
                link = str(link)
                if 'doc_sdat' in link:
                    tmp_link = link[link.find('"')+1:]
                    tmp_link = tmp_link[:tmp_link.find('"')]
                    year_launches.append(tmp_link)
    
    counter = Counter(len(year_launches), f"{year}")
    tasks = [asyncio.create_task(get_launch_mass(semaphore, client_session, launch, counter)) for launch in year_launches]
    done, pending = await asyncio.wait(tasks)
    total_mass = [task.result() for task in done]
    return (year, sum(total_mass), len(total_mass), total_mass.count(0))

async def get_launch_mass(semaphore: asyncio.Semaphore, client_session: aiohttp.ClientSession, launch_url: str, counter: Counter):
    """Returns the mass of a given launch
    0 if the mass is unknown"""
    mass = 0
    try:
        async with semaphore:
            # print(f"Getting mass for {launch_url}")
            async with client_session.get('http://space.skyrocket.de/' + launch_url[3:]) as response:
                html = await response.text()
                parsed_page = BeautifulSoup(html, features="html.parser")
                for pre_mass_element in parsed_page.find_all(text='Mass:'):
                    mass_element = pre_mass_element.find_next()
                    mass = int(re.search(r'(\d+)', mass_element.text).group(0))
            # print(f"Got mass of {mass}KG for {launch_url}")
    except Exception:
        # print(f"Error getting mass for {launch_url}")
        pass
    counter.increment()
    return mass

if __name__ == '__main__':
    asyncio.run(main())

Further work would be to check what data is already in the .csv and not re-download it.

Latest numbers as of 33/7/2023 are:

year    kilograms   launches    launches (unknown mass) launches (known Mass)   tons
1957    593 3   0   3   0.593
1958    7949    28  0   28  7.949
1959    1807    26  11  15  1.807
1960    6247    42  22  20  6.247
1961    24730   57  29  28  24.73
1962    41195   97  38  59  41.195
1963    39151   92  44  48  39.151
1964    136533  123 51  72  136.533
1965    168776  175 63  112 168.776
1966    149511  169 78  91  149.511
1967    107891  172 76  96  107.891
1968    206164  161 72  89  206.164
1969    201531  150 83  67  201.531
1970    203083  142 44  98  203.083
1971    363504  169 35  134 363.504
1972    345503  139 25  114 345.503
1973    467589  146 26  120 467.589
1974    262949  139 33  106 262.949
1975    324619  163 30  133 324.619
1976    310576  161 31  130 310.576
1977    294836  142 37  105 294.836
1978    314011  168 25  143 314.011
1979    309134  130 20  110 309.134
1980    290488  147 34  113 290.488
1981    317619  163 30  133 317.619
1982    306014  162 38  124 306.014
1983    298387  162 44  118 298.387
1984    235147  174 69  105 235.147
1985    201050  170 73  97  201.05
1986    173208  151 54  97  173.208
1987    292243  139 56  83  292.243
1988    269515  143 57  86  269.515
1989    212900  140 50  90  212.9
1990    215418  171 52  119 215.418
1991    201880  147 41  106 201.88
1992    211497  139 36  103 211.497
1993    152476  117 36  81  152.476
1994    211163  128 36  92  211.163
1995    198780  108 38  70  198.78
1996    171492  107 32  75  171.492
1997    241191  167 23  144 241.191
1998    205990  189 26  163 205.99
1999    183739  144 14  130 183.739
2000    243520  130 33  97  243.52
2001    161116  92  22  70  161.116
2002    200274  99  18  81  200.274
2003    144080  92  10  82  144.08
2004    146822  77  11  66  146.822
2005    199409  78  12  66  199.409
2006    185818  117 23  94  185.818
2007    195537  121 16  105 195.537
2008    230607  109 18  91  230.607
2009    213258  134 34  100 213.258
2010    203525  130 33  97  203.525
2011    283264  141 29  112 283.264
2012    284184  140 31  109 284.184
2013    298580  216 36  180 298.58
2014    258115  302 134 168 258.115
2015    289758  271 108 163 289.758
2016    289479  227 76  151 289.479
2017    322093  489 111 378 322.093
2018    298791  486 147 339 298.791
2019    318977  519 157 362 318.977
2020    475129  1316    166 1150    475.129
2021    610041  1891    370 1521    610.041
2022    917148  2566    361 2205    917.148
2023    368809  873 160 713 368.809

Summing to 16016.413 metric tons total.

$\endgroup$
1
  • $\begingroup$ "import re"; pip finds nothing named 're' $\endgroup$
    – diox8tony
    Oct 7 at 13:43
3
$\begingroup$

Here's an independent estimate.

From Elon Musk's 2022-02-11 Starship Update video presentation after about 16:00 there's a graphic showing 15,517 tons to orbit.

That agrees nicely with @dotancohen's 14,933 tons and sort of with phil1008's ballpark estimate of 17,976 tons.

screenshot from Elon Musk's 2022-02-11 Starship Update

$\endgroup$
2
  • 1
    $\begingroup$ I would love to know which sources Elon was using. Any Hawthorne IP addresses in the Space.SE logs? )) $\endgroup$
    – dotancohen
    Feb 15, 2022 at 15:30
  • 1
    $\begingroup$ @dotancohen one should check for Scott Manley's as well :-) $\endgroup$
    – uhoh
    Feb 15, 2022 at 21:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.