Fixing Meteorite Positions Stored as 0,0 or Left Blank

On this page

Problem statement

In NASA's Meteorite Landings table, 6,214 rows have a latitude of 0 and a longitude of 0, because 0,0 was entered when no position was given, and 7,315 have both columns blank. That is 29.6% of the table, and not a random 29.6%: mostly Antarctic finds and desert meteorites bought from dealers. Left in, the zeros put thousands of meteorites in the Gulf of Guinea. This page finds every kind of missing position, including a subtler one where only the latitude was lost, and recovers positions for about half of them from the other finds of the same place.

Quick answer

Treat 0,0 as missing, then drop or fill the missing rows before mapping:

zero = (met.reclat == 0) & (met.reclong == 0)
print(f"at exactly 0,0: {zero.sum():,}; no coordinates: {met.reclat.isna().sum():,}; either: {(zero | met.reclat.isna()).mean():.1%}")
good = met[~zero & met.reclat.notna()]
print(f"usable: {len(good):,}")
at exactly 0,0: 6,214; no coordinates: 7,315; either: 29.6%
usable: 32,187

6,214 rows sit at 0,0 and 7,315 have no position, 29.6% of the meteorites between them. 32,187 remain, but that is not yet the end, because some of those have only one coordinate lost.

Horizontal bars of position status: 31,963 usable, 7,315 with no coordinates, 6,214 at 0,0 and 224 with one coordinate at 0. Horizontal bars of position status: 31,963 usable, 7,315 with no coordinates, 6,214 at 0,0 and 224 with one coordinate at 0.
The positions to fix.

Three kinds of missing position

1. Who is at 0,0

met["place"] = met.name.str.replace(r"\s*[A-Z]?\d+.*$", "", regex=True)
print(met[zero].place.value_counts().head(6).to_string())
print(met[zero].fall.value_counts().to_string())
place
Northwest Africa    1750
Miller Range        1682
Yamato              1666
Dominion Range       695
Larkman Nunatak      169
Patuxent Range       129
fall
Found    6213
Fell        1

The 0,0 rows are almost all finds from collecting grounds: 1,750 Northwest Africa meteorites, whose find sites are unknown because they were bought from dealers, and thousands from Antarctic fields, Miller Range 1,682, Yamato 1,666, Dominion Range 695. Only one is a fall. Dropping them silently removes Antarctic collecting seasons and the desert trade from any map or count by region.

2. Half a zero

half = (met.reclat == 0) ^ (met.reclong == 0)
print(f"latitude or longitude alone is 0: {half.sum()}")
print(met[half].groupby(["place", "reclat", "reclong"]).size().sort_values(ascending=False).head(5).to_string())
latitude or longitude alone is 0: 224
place   reclat  reclong 
Yamato  0.0     35.66667    223
Meru    0.0     37.66667      1

224 rows have one coordinate at 0 and the other set. 223 are Yamato meteorites at latitude 0, longitude 35.67Β°, the right longitude for the Yamato Mountains with the latitude lost. A filter on (reclat == 0) & (reclong == 0) lets them through, and they land on the equator in Kenya. The other row is Meru, a real Kenyan fall that lies almost on the equator; it is the only one whose 0 is genuine.

Map of Africa and Antarctica marking 6,214 rows at 0,0 in the Gulf of Guinea, 223 Yamato rows at latitude 0 in Kenya, and an arrow to where Yamato's located meteorites lie in Antarctica. Map of Africa and Antarctica marking 6,214 rows at 0,0 in the Gulf of Guinea, 223 Yamato rows at latitude 0 in Kenya, and an arrow to where Yamato's located meteorites lie in Antarctica.
Where the placeholder positions put meteorites, and where they belong.

3. Borrowing a position from the other finds of the same place

bad = zero | met.reclat.isna() | half
ref = met[~bad].groupby("place").agg(n=("id", "size"), lat=("reclat", "median"), lon=("reclong", "median"),
                                      spread=("reclat", lambda s: s.max() - s.min()))
ref = ref[(ref.n >= 20) & (ref.spread < 2)]
fill = met[bad].join(ref, on="place", how="inner")
print(f"bad positions: {bad.sum():,}; filled from a place with 20+ located finds spread under 2 degrees: {len(fill):,}")
print(fill.place.value_counts().head(5).to_string())
bad positions: 13,753; filled from a place with 20+ located finds spread under 2 degrees: 6,972
place
Yamato                 2381
Miller Range           2004
LaPaz Icefield         1324
MacAlpine Hills         763
Grosvenor Mountains     218

Of 13,753 bad positions, 6,972 belong to a place with at least 20 located finds lying within 2 degrees of latitude of each other, and can take that place's median position: 2,381 Yamato meteorites, 2,004 from Miller Range, 1,324 from LaPaz Icefield. The rule refuses Northwest Africa, whose located members are spread across the Sahara, so a "Northwest Africa" label is not a position. Mark filled rows with a flag; their position is the collecting area's, good for a world map and useless for anything finer.

What the map would have shown

The data used on this page

NASA's Meteorite Landings table from The Meteoritical Society's records (last updated May 2013) and Natural Earth's 1:110m countries, both saved on this site on 28 October 2026.

from pathlib import Path
import json
import zipfile

import geopandas as gpd
import numpy as np
import pandas as pd
import requests

Path("data").mkdir(exist_ok=True)
# NASA's Meteorite Landings table (The Meteoritical Society's records as NASA last updated them, May 2013), every
# fireball in NASA/JPL's Fireball Data API, and Natural Earth's countries and land. Saved 28 October 2026.
# Live: https://data.nasa.gov/docs/legacy/meteorite_landings/Meteorite_Landings.csv
#       https://ssd-api.jpl.nasa.gov/fireball.api?vel-comp=true
ARCHIVE = "nasa-meteorites-2026-10-28.zip"
if not Path(f"data/{ARCHIVE}").exists():
    Path(f"data/{ARCHIVE}").write_bytes(requests.get(f"https://data.spatialworkflow.io/{ARCHIVE}", timeout=600).content)
Z = zipfile.ZipFile(f"data/{ARCHIVE}")
met = pd.read_csv(Z.open("Meteorite_Landings.csv")).rename(columns={"mass (g)": "mass_g"})
for f in ["ne_110m_admin_0_countries.zip", "ne_50m_land.zip"]:
    Path(f"data/{f}").write_bytes(Z.read(f))
print(f"Meteorite Landings: {len(met):,} meteorites")
Meteorite Landings: 45,716 meteorites

Example 1: what the map would have shown

world = gpd.read_file("data/ne_110m_admin_0_countries.zip")
g0 = gpd.GeoDataFrame(met.dropna(subset=["reclat"]), geometry=gpd.points_from_xy(met.dropna(subset=["reclat"]).reclong, met.dropna(subset=["reclat"]).reclat), crs=4326)
gulf = g0.cx[-5:5, -5:5]
print(f"points within 5 degrees of 0,0 before the fix: {len(gulf):,}; on land there: {gulf.within(world.union_all()).sum()}")
points within 5 degrees of 0,0 before the fix: 6,214; on land there: 0

Within five degrees of 0,0 the raw table has 6,214 points, every one of them the placeholder, and none on land. On a world map they draw one dot of 6,214 meteorites in the sea off West Africa, which a map with transparency or a heat map turns into the brightest spot on Earth. Testing for 0,0 is the first step of any meteorite map.

Horizontal bars of missing positions filled from the place's other finds: Yamato 2,381, Miller Range 2,004, LaPaz Icefield 1,324, MacAlpine Hills 763 and smaller fields. Horizontal bars of missing positions filled from the place's other finds: Yamato 2,381, Miller Range 2,004, LaPaz Icefield 1,324, MacAlpine Hills 763 and smaller fields.
Positions recovered from other finds of the same place.