wikillm

Paper · 2023-11-28 · cs.CV, cs.AI, cs.CY, cs.LG

SatCLIP: Global, General-Purpose Location Embeddings with Satellite Imagery

Trains a location encoder by contrastive matching of lat/lon to Sentinel-2 tiles (S2-100K). Coordinates go through spherical harmonics and a SIREN MLP to 256 dimensions; a frozen MoCo image encoder is the other side. Author-reported: best on 7 of 9 downstream tasks, and the most wins on held-out-continent tests among compared location encoders.

Location encoderContrastive learningImplicit neural representation (INR)geospatiallocation-encodingsatellite-imagerycontrastive-learningimplicit-neural-representationspherical-harmonics

arXivPDFCodeMarkdown15 min read

AI-compiled by claude-haiku-5-5 on 2026-11-09, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.

In plain words

A model that predicts something about a place, such as temperature, house prices or which animals live there, usually needs the place described somehow. Raw latitude and longitude work for nearby places but fail for places far from the training data. Downloading satellite photos for every query point is slow.

SatCLIP trains a small network that turns any latitude and longitude into a short list of numbers. It learns this by matching each location with a satellite photo taken there. Locations whose photos look alike, such as two rainforests on different continents, end up with similar numbers, so the network learns something about the ground without needing a photo at prediction time.

Once trained, the location network is small enough to run without a GPU. Its output can be fed to any downstream model in place of raw coordinates, and the authors report gains both inside the training regions and in regions held out from training.

TL;DR

SatCLIP is a CLIP-style contrastive pre-training of a location encoder against Sentinel-2 image tiles. The location side uses spherical harmonics followed by a SIREN network; the image side is a frozen, MoCo-pretrained vision encoder whose last projection is trained. The 256-dimensional location embeddings are used as inputs to small MLPs on nine tasks. Results are author-reported. They are best on seven of nine tasks in the in-distribution setting, and the L=10 model wins most held-out-continent comparisons. Two gaps matter for reproduction: the default training config differs from the paper's setup, and its contrastive negatives come from 512-sample micro-batches rather than the paper's 8k batch.

Problem

Location is informative for geospatial tasks, but raw coordinates generalise poorly. Models that use them tend to work only where the evaluation area overlaps the training area, and labelled data is often sparse and patchy §1. Image-based alternatives need a download and a vision model for every query point, which is costly in low-resource settings §2. Earlier pretrained location encoders (GPS2Vec, CSP, GeoCLIP) were trained on data with strong geographic bias towards Europe and North America, or were built for a single kind of task §1 §3.1 app. B.

Method

The method has two encoders that map into a shared space of 256 dimensions §2:

  1. Location encoder. Input is a coordinate pair (longitude, latitude) §2.1. It is passed through a spherical-harmonics basis with a Legendre parameter L, then through a SIREN network with two hidden layers of 512 units and a 256-dimensional output §2.2 app. C.1. L sets the spatial resolution: the authors release models with L=10 and L=40, and L=40 is finer §2.2 app. C.2.
  2. Image encoder. ResNet18, ResNet50 or ViT16 pretrained with MoCo on Sentinel-2 imagery by Wang et al. (2022). All layers are frozen except the last linear projection to 256 dimensions §2.2 app. C.1. The ViT16 has about 22 million parameters §2.2.
  3. Objective. Within a batch of N coordinate–tile pairs, each coordinate is matched against all N tiles, and each tile against all N coordinates, with a temperature τ §2.1 eq. 1 eq. 2. The paper names the second direction (L_img) but does not write its formula out in the text §2.1. The code averages the two cross-entropy terms loss.py. The location encoder has about 1 million parameters, which keeps coordinate encoding fast and GPU-free §2 §2.2.
  4. Training. 90% of S2-100K for training, 10% for validation, batch size 8k, 500 epochs, one A100 for about two days; the checkpoint with the lowest validation loss is kept §2.3 app. C.1.
  5. Use downstream. The location embedding (or raw coordinates, for the "Identity" baseline) is the input to an MLP trained with MSE for regression and cross-entropy for classification. Hyperparameters are tuned by random search on a validation set §3.4 app. E.3. For iNaturalist, InceptionV3 image features are concatenated to the embedding §3.2.

Pseudocode for the objective, following §2.1 and eq. 1:

Pseudocode
# SatCLIP pretraining loss on one batch  [§2.1] [eq. 1]
# Input:  images (N, C, 256, 256), C = 12 bands [§3.1];
#         coords (N, 2) as (lon, lat) in degrees
# Output: scalar loss
# log_scale is learned in the code, starting at log(1 / 0.07)
#   [code: satclip/model.py#L330]

def satclip_loss(images, coords, log_scale):
    # Image side: frozen MoCo encoder, trained last projection  [§2.2]
    image_emb = image_encoder(images)                 # (N, 256)
    # Location side: spherical harmonics, then SIREN  [§2.2] [app. C.1]
    location_emb = location_encoder(coords)           # (N, 256)

    # Scale both sides to unit length, so the dot product is a cosine
    # [code: satclip/model.py#L366-L367]
    image_emb = image_emb / norm(image_emb)
    location_emb = location_emb / norm(location_emb)

    # Pairwise cosine similarities, divided by tau  [§2.1] [eq. 2]
    logits = exp(log_scale) * image_emb @ location_emb.T    # (N, N)

    # The true pair of row i is column i  [§2.1]
    targets = arange(N)
    # Each tile against all coordinates, and each coordinate against all tiles
    # [§2.1] [eq. 1]
    loss_per_image = cross_entropy(logits, targets)
    loss_per_coord = cross_entropy(logits.T, targets)
    # Average of the two directions, equal to the (1 / 2N) form of [eq. 1]
    return (loss_per_image + loss_per_coord) / 2

Pseudocode for the location encoder. The widths and frequencies are from the code, where the paper does not state them:

Pseudocode
# Location encoder: spherical harmonics, then a SIREN MLP  [§2.2] [app. C.1]
# Input:  coords (N, 2) as (lon, lat) in degrees
# Output: embedding (N, 256)

def location_encoder(coords, L):
    # Shift to the angles the basis uses
    # [code: satclip/positional_encoding/spherical_harmonics.py#L31-L32]
    phi = deg2rad(coords[:, 0] + 180)
    theta = deg2rad(coords[:, 1] + 90)
    # Real spherical harmonics for every degree l < L and order m in -l..l
    # [§2.2] [code: satclip/positional_encoding/spherical_harmonics.py#L34-L40]
    # This gives L * L features, so L=10 gives 100 and L=40 gives 1600
    basis = stack([sph_harm(m, l, phi, theta)
                   for l in range(L)
                   for m in range(-l, l + 1)], axis=-1)    # (N, L * L)

    # SIREN layer 1: linear, then dropout, then sin(30 x)
    # [code: satclip/location_encoder.py#L83]
    # Dropout p = 0.5, training only
    # [code: satclip/location_encoder.py#L148-L149]
    hidden = dropout(basis @ W1 + b1)
    hidden = sin(30 * hidden)

    # SIREN layer 2: linear, then dropout, then sin(1 x)
    # [code: satclip/location_encoder.py#L83]
    hidden = dropout(hidden @ W2 + b2)
    hidden = sin(hidden)

    # Output: linear map to the embedding width, no activation
    # [code: satclip/location_encoder.py#L96]
    return hidden @ W3 + b3               # (N, 256)

Hyperparameters

Values are as stated in the paper unless marked as code. "Not stated" means the paper does not give the value.

NameValueWhereNotes
Pretraining tiles100,000 patches, 256 × 256 px, 12 bands, 10 m/px§3.1 app. AS2-100K; 83.79 GB on disk app. A
Scene filtercaptured 2021-01-01 to 2023-05-17; under 20% cloud; touches landapp. A2,359,972 candidate scenes
Nodata rulereject a patch if 10% or more of its pixels are nodataapp. Acode skips files under 10 KB s2geo_dataset.py
Train / validation split90% / 10%, random§2.3validation loss picks the checkpoint app. C.1
Embedding width256§2
Legendre parameter L10 and 40 released§2.2 app. C.1 README.mddefault config sets 32 default.yaml
SIREN hidden layers2app. C.1default config: 2 default.yaml
SIREN hidden width512app. C.1default config: 256 default.yaml
SIREN frequenciesfirst layer 30; hidden layers 1not statedlocation_encoder.py
SIREN initfirst layer uniform ±1/fan-in; others ±√(c/fan-in)/w0, c = 6not statedlocation_encoder.py
SIREN dropoutp = 0.5 on hidden layers, training onlynot statedlocation_encoder.py location_encoder.py
Image encodersResNet18, ResNet50, ViT16; MoCo on Sentinel-2§2.2frozen except last projection app. C.1
ViT16 sizeabout 22M parameters§2.2location encoder about 1M §2.2
Batch size8k§2.3 app. C.1code: 512 × 16 accumulation (see Pitfalls) default.yaml main.py
Epochs500§2.3 app. C.1
OptimiserAdamapp. C.1code: AdamW main.py
Learning rate1e-4app. C.1code default matches default.yaml
Weight decay0.01app. C.1not applied to 1-D, bias, norm or logit-scale parameters main.py
Temperature τnot stated as a number§2.1learned in code, starting at 1/0.07 model.py
Image augmentationrandom crop, horizontal and vertical flips, Gaussian blurapp. C.1transforms.py
Coordinate jitterup to about 1 kmapp. C.1code: adds a value in [0, 0.01) degrees to both axes transforms.py
Hardwareone A100, about 2 daysapp. C.1
Downstream MLPrandom search over hidden layers, widths, learning rate, weight decay; best by validation loss§3.4 app. E.310 runs per setting, mean ± SD reported
Downstream splits, RQ1train 60% (air temperature), 50% (Cali. housing, median income), 30% (others); validation 20%, 20%, 10%app. E.3remainder is test
Downstream splits, RQ2held-out continent (Africa or Asia); 1% of its points in training for Countries and Ecoregions; validation 20% or 10%app. E.3zero-shot for the other tasks §4.2

Results

All numbers are author-reported. Table 2 uses the ResNet50 image encoder. Table 3 uses the ViT16 image encoder. Both are cited from the paper.

Table 2. In-distribution downstream performance (R² for regression, accuracy in % for classification; mean ± SD over 10 runs) tab. 2 §4.1

TaskSatCLIP L=10SatCLIP L=40CSP (iNat)GPS2Vec (tag)MOSAIKS (Planet)GeoCLIP (MP-16)Identity
Air temperature0.90 ± 0.130.91 ± 0.01-0.56 ± 0.590.22 ± 0.00-0.52 ± 2.00-3.11 ± 5.240.82 ± 0.16
Median income0.42 ± 0.010.47 ± 0.12-0.01 ± 0.020.21 ± 0.000.02 ± 0.050.50 ± 0.01-0.84 ± 0.94
Cali. housing0.35 ± 0.040.57 ± 0.02-0.00 ± 0.000.71 ± 0.030.24 ± 0.020.75 ± 0.010.05 ± 0.02
Elevation0.83 ± 0.010.88 ± 0.000.11 ± 0.050.10 ± 0.000.21 ± 0.010.83 ± 0.000.25 ± 0.08
Population0.79 ± 0.000.82 ± 0.000.36 ± 0.110.25 ± 0.000.46 ± 0.020.79 ± 0.000.46 ± 0.03
Countries (%)94.28 ± 0.1896.00 ± 0.1482.11 ± 1.7270.35 ± 0.0676.16 ± 0.5090.72 ± 0.4482.94 ± 2.23
iNaturalist (%)65.69 ± 0.1866.22 ± 0.4060.47 ± 0.5658.78 ± 0.4856.73 ± 0.8062.01 ± 0.5960.83 ± 0.53
Biome (%)92.23 ± 0.2694.41 ± 0.1473.18 ± 5.5869.69 ± 0.0679.61 ± 0.4289.57 ± 0.4583.55 ± 2.43
Ecoregions (%)89.32 ± 0.3191.67 ± 0.1578.43 ± 1.7168.46 ± 0.0670.48 ± 0.2184.65 ± 0.3277.07 ± 2.54

The authors state SatCLIP is best on seven of nine tasks. On Cali. housing and median income, GeoCLIP is equal or better §4.1. L=40 beats L=10 on the regional tasks §4.1.

Table 3. Geographic adaptation to a held-out continent (R² for regression, accuracy in % for classification; * zero-shot, † few-shot with 1% of test-continent points; ViT16 encoder) tab. 3 §4.2

Test continent / taskSatCLIP L=10SatCLIP L=40CSP (iNat)GPS2Vec (tag)MOSAIKS (Planet)GeoCLIP (MP-16)Identity
Asia: Air temperature*0.75 ± 0.050.63 ± 0.04-0.50 ± 1.32-3.95 ± 4.89-2.13 ± 3.500.77 ± 0.280.20 ± 1.64
Asia: Elevation*0.46 ± 0.090.48 ± 0.07-0.26 ± 0.03-0.29 ± 0.01-0.07 ± 0.060.50 ± 0.03-0.16 ± 0.06
Asia: Pop. density*0.42 ± 0.080.45 ± 0.04-1.02 ± 0.32-0.37 ± 0.040.05 ± 0.120.38 ± 0.040.03 ± 0.07
Asia: Countries† (%)36.90 ± 4.3219.17 ± 2.821.28 ± 0.011.12 ± 0.001.56 ± 0.4723.12 ± 2.501.24 ± 0.12
Asia: iNaturalist* (%)19.60 ± 0.7820.91 ± 0.7721.49 ± 0.8517.52 ± 0.3816.14 ± 0.4220.94 ± 0.3821.08 ± 0.69
Asia: Biome* (%)25.89 ± 2.7916.44 ± 1.213.00 ± 2.601.76 ± 0.0437.81 ± 4.4731.67 ± 1.916.24 ± 2.71
Asia: Ecoregions† (%)21.02 ± 1.0910.86 ± 1.191.41 ± 0.141.49 ± 0.031.36 ± 0.106.65 ± 1.031.52 ± 0.47
Africa: Air temperature*-4.71 ± 2.29-1.48 ± 0.70-2.67 ± 5.80-7.91 ± 0.04-17.43 ± 18.37-9.91 ± 28.82-27.36 ± 39.46
Africa: Elevation*-1.80 ± 1.74-0.21 ± 0.09-1.20 ± 0.55-0.13 ± 0.06-0.79 ± 0.43-0.34 ± 0.10-2.43 ± 2.67
Africa: Pop. density*0.17 ± 0.120.18 ± 0.09-0.31 ± 0.16-0.34 ± 0.020.15 ± 0.050.32 ± 0.03-0.50 ± 0.34
Africa: Countries† (%)30.65 ± 4.2310.22 ± 1.620.45 ± 0.040.47 ± 0.010.48 ± 0.0010.32 ± 2.752.74 ± 2.52
Africa: iNaturalist* (%)9.53 ± 0.576.23 ± 0.478.65 ± 0.527.47 ± 0.535.18 ± 0.387.69 ± 0.309.96 ± 0.33
Africa: Biome* (%)35.72 ± 5.4812.34 ± 1.751.09 ± 0.481.29 ± 0.0449.86 ± 1.5728.28 ± 3.061.46 ± 0.67
Africa: Ecoregions† (%)32.03 ± 1.1912.91 ± 1.630.94 ± 0.040.88 ± 0.010.92 ± 0.1212.41 ± 2.207.72 ± 3.93
Wins (of 14 rows)8510242

The authors say the L=10 model generalises better and that L=40 is better for interpolation §4.2 §4.4. Several location encoders do worse than raw coordinates on held-out continents §4.2.

Table 4. Vision encoder and L (accuracy in %, mean ± SD; in-distribution classification tasks) tab. 4 §4.4

TaskViT16 L=10ResNet18 L=10ResNet50 L=10ViT16 L=40ResNet18 L=40ResNet50 L=40
Countries93.97 ± 0.3093.92 ± 0.3094.28 ± 0.1895.77 ± 0.1495.92 ± 0.1096.00 ± 0.14
iNaturalist65.69 ± 0.5065.56 ± 0.2965.50 ± 0.4365.98 ± 0.6166.40 ± 0.4966.03 ± 0.54
Biomes92.07 ± 0.2292.10 ± 0.2392.23 ± 0.2694.27 ± 0.1594.33 ± 0.1094.41 ± 0.14
Ecoregions89.53 ± 0.2889.57 ± 0.2389.32 ± 0.3191.61 ± 0.2291.53 ± 0.1591.67 ± 0.15

The authors find the vision encoder changes results by less than 1%, while L changes them more §4.4.

Qualitative results: a PCA of the embeddings rendered as RGB and cosine similarity maps are shown in fig. 6 §4.3. The Congo Basin embedding is similar to other equatorial locations, particularly the Amazon and Indonesia; the US East Coast embedding is similar to densely populated, industrialised areas such as central Europe and northern China §4.3 fig. 6.

Baseline comparison, as described by the authors §3.3 tab. 1: CSP (iNaturalist, FMoW), GPS2Vec (YFCC100M tags and images), GeoCLIP (MP-16) and MOSAIKS (Planet Basemaps 2019 Q3, image features, no location encoder). Identity is a downstream model on raw coordinates.

Paper vs code

Compared against the official repository microsoft/satclip at commit 68c73233426d465bcdf5914abf13ebc88ba60f5e, licensed MIT (Microsoft Corporation). The paper links this repository in its appendix and conclusion app. A §6, and the authors are at Microsoft and affiliated institutions. The code covers pretraining, the positional encodings, the SIREN, the loss and the dataset loader. I did not find a downstream-training module in the tree; the downstream data is linked from the README README.md, and the repository has example notebooks. The downstream MLP settings are therefore taken from the paper only. I did not download the released checkpoints, so the hyperparameters behind the released L=10 and L=40 models are not verified.

  • Batch size. Paper: 8k §2.3 app. C.1. Default config: batch_size 512 with accumulate_grad_batches 16 default.yaml main.py. The loss is computed per micro-batch of 512 main.py, so the negatives per step are 512, not 8192.
  • Image encoder. Paper: MoCo-pretrained, frozen except the last projection §2.2 app. C.1. Default config: vision_layers: 4, vision_width: 128, vision_patch_size: 32, which builds a ViT from scratch with no freezing default.yaml model.py. The frozen MoCo branches exist model.py, but the default config does not select them.
  • ViT16 size. Paper: about 22M parameters §2.2. Code: MoCo ViT-Small/16 model.py. README: "ViT-B/16" README.md. ViT-B is the larger model, so the README and the code disagree on which backbone the released ViT16 checkpoints use.
  • SIREN width. Paper: 512 hidden units app. C.1. Default config and main.py default: 256 default.yaml main.py.
  • Legendre parameter. Paper releases L=10 and L=40 §2.2 tab. 3. Default config: 32 default.yaml. README example: 10 README.md.
  • Optimiser. Paper: Adam, weight decay 0.01 app. C.1. Code: AdamW, with weight decay excluded from 1-D, bias, norm and logit-scale parameters main.py main.py.
  • Temperature. Paper: τ is a hyperparameter with no number given §2.1. Code: a learned log-scale starting at log(1/0.07), excluded from weight decay model.py main.py.
  • Loss. Paper: (1 / 2N) times the sum over both directions eq. 1. Code: the mean of the two cross-entropy terms loss.py. The two are the same quantity.
  • Channels. Paper: 12 bands §3.1. Code: 13 channels, with a zero-filled band inserted at index 10 transforms.py default.yaml. The paper's band list in app. A names B06 twice and omits B05.
  • Normalisation. Paper: not stated. Code: divide by 10000 and nothing else in the default path transforms.py. The per-channel mean and standard deviation in transforms.py are not used by default s2geo_dataset.py.
  • Coordinate jitter. Paper: shifts "up to about 1 km" app. C.1. Code: adds a value in [0, 0.01) degrees to both longitude and latitude transforms.py. The shift is never negative and is in degrees, not metres.
  • Dropout in SIREN. Not in the paper. Code applies dropout with p = 0.5 to the SIREN hidden layers during training location_encoder.py location_encoder.py.
  • Checkpoint selection and epochs. Paper: lowest validation loss, 500 epochs app. C.1. Code: ModelCheckpoint monitors val_loss, and max_epochs is 500 default.yaml default.yaml. Consistent.
  • Learning rate, weight decay, split, embedding width. Paper and code agree: 1e-4, 0.01, 90/10, 256 app. C.1 default.yaml s2geo_dataset.py.
  • L_img. Paper: named, with the formula left out of the text §2.1. Code: symmetric cross-entropy loss.py.

Pitfalls

  • Negatives per micro-batch. The paper's 8k batch gives 8k negatives. The default config produces 512 negatives per step, with gradients accumulated over 16 micro-batches main.py main.py. To reproduce the paper, one step must compute the logits over all 8k embeddings at once. Accumulation does not do this.
  • Default config is not the paper's model. A from-scratch, unfrozen ViT is not the MoCo-frozen encoder the paper trains default.yaml. Use the moco_* branches for the paper's setup model.py.
  • Spherical harmonics size. Degrees run from 0 to L−1, so L=10 gives 100 features and L=40 gives 1600 spherical_harmonics.py spherical_harmonics.py. The paper's table caption uses the same convention ("maximum number of Legendre polynomial degrees") app. E.4.
  • Harmonic angle convention. Input is (lon, lat) in degrees. The code adds 180 to longitude and 90 to latitude before converting to radians spherical_harmonics.py. Pass the columns in the other order and the embedding is silently wrong.
  • SIREN initialisation and frequency. The first layer uses frequency 30, the hidden layer frequency 1, and the uniform init uses c = 6 location_encoder.py location_encoder.py. The paper does not give these.
  • Float64 location path. The location encoder runs in double precision and the embeddings are cast to float afterwards model.py model.py model.py.
  • Jitter is one-sided. Training coordinates are only ever moved east and north of the tile centre, never west or south transforms.py. Copy the jitter only with that bias in mind.
  • Band order. The code relies on channel index to mean band, with B10 zero-filled at index 10 transforms.py. The paper's band list has a typo app. A. Verify the band order against your data before inference.
  • Which results use which encoder. Table 2 uses ResNet50; Table 3 uses ViT16 tab. 2 tab. 3. The headline numbers are not from one model. Compare within a table.
  • Results are author-reported, and two tasks are regional. Cali. housing covers California only and median income covers the continental US only §4.1 app. E.2. The held-out tests hold out Africa or Asia, one at a time app. E.3. The Identity baseline on raw coordinates is strong on some tasks tab. 2.
  • Released checkpoints are not verified here. The README table lists six checkpoints README.md. I did not download or inspect them. Their hyperparameters may differ from both the paper and the default config.
  • Licence of S2-100K. The paper does not state a licence for the dataset app. A. It is distributed on Hugging Face README.md. Check that licence before commercial reuse of the tiles. This is separate from the paper's CC BY 4.0.
  • Version. This card follows arXiv v3, the version fetched. The repository's README cites a conference version README.md. It may differ from v3.

Lineage

  • Builds on: CLIP, the image–text contrastive objective this work adapts to image–coordinate pairs §1 §2.1 (Radford et al., 2021); the spherical-harmonics location encoder of Rußwurm et al. (2023) §2.2; SIREN, the sinusoidal network of Sitzmann et al. (2020) §2.2; MoCo, the pretraining method of He et al. (2020), and its Sentinel-2 weights from Wang et al. (2022) §2.2.
  • Compared against: CSP (Mai et al., 2023), GPS2Vec (Yin et al., 2019), GeoCLIP (Cepeda et al., 2023), MOSAIKS (Rolf et al., 2021), and an Identity baseline on raw coordinates §3.3 tab. 1.
  • Superseded by: not stated in the paper.

Source and license

Adapted from “SatCLIP: Global, General-Purpose Location Embeddings with Satellite Imagery” by Konstantin Klemmer, Esther Rolf, Caleb Robinson, Lester Mackey, Marc Rußwurm (arXiv:2311.17179), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/microsoft/satclip at commit 68c73233426d (MIT). Any quoted code is shown under that license.