gralhix #004
gralhix004 | Geolocating Random Islet Image Using Geometry & CUDA GPU Programming
16-08-2026
NOTE: this is a genuine human work, didnt use LLM generation.
I'm writing this page as a writeup for this challenge gralhix 004 made by Sofia Santos | Gralhix.
You can view, clone and locally try all code files and the final report with all instructions here at github.
Task briefing:
This is a photo of a resort located on an island.
a) What is the name of the resort?
b) What are the coordinates of the island?
c) In which cardinal direction was the camera facing when the photo was taken?
In my opinion, solving this challenge with google lens is wasting a fun opportunity, so decided to solve it with math and programming.
a] Metadata
Of course, first thing u look for is the metadata. Ran that on my linux void:
> exiftool main.png
File Type : WEBP (lossless)<br>MIME Type : image/webp<br>Image Width : 736<br>Image Height : 515
As expected, nothing useful here. No EXIF, no GPS, no camera make or model.
b] Building the fingerprint
U can see from the img, there are 3 landmasses:
P0: the islet itself,
P1: the right island,
P2: the left front island ( having mountain peak )
I couldnt make a correct perspective model of birdview of this image, as clearly the image is taken by a drone and cant estimate the elevation at all (and not found in the metadata).
So I had to estimate that by intuition, I just want the relative distances between the 3 islands and angles of that triangle.
I built a small click GUI 01_triangle_gui.py that records pixel coordinates for each point in order and computes the triangle's geometry.
Since clicking exact centers by eye isn't perfectly precise, I added a ±20% tolerance band around both values when searching.
c] SEARCH
With the fingerprint locked in, the next step is checking every real landmass on Earth against it !
I used OpenStreetMap's split land polygon set as the dataset land-polygons-split-4326, full global coastline vectors in WGS84 which has size of 882 MB.
I created heuristic filters (all by just intuition and non tangible proofs), spent days (yea full days) tweaking values and tons of trial and error 😭 untill I got this working filters recipe.
01] Tropical latitude bounding box
$$ -30° \le latitude \le 30° $$
the islet in the photo reads as tropical, so I decided that anything outside the tropics is thrown out immediately, before doing any expensive geometry work.
Exactly 141,131 land polygons survive that band filter.
02] Local density filter
$$ N_{5\text{km}}(p) \le 10 $$
$ N_{5\text{km}}(p) $ counts how many other centroids fall within 5km of point (p). Cap is 10: if an islet has more than 10 neighbors that close, it's sitting in a dense reef field, a crowded coastline or a archipelago clutter, not a small isolated 3-4 island group like the photo shows.
This dropped candidates down to 51,576.
03] Clustering
For every surviving point, find every other point within 20km (heuristic, by eye from the image). If it has at least 2 neighbors that close (3 points total), it's a cluster. Points with no cluster of 3+ nearby are dropped, they can't form a triangle at all.
tree = cKDTree(f_coords)<br>neigh = tree.query_ball_point(<br>f_coords,<br>CLUSTER_RADIUS_KM / 111.0)<br>clusters = set(tuple(sorted(n)) for n in neigh if len(n) >= 3)
$$ \left|\{q : \text{dist}(p,q) \le 20\,\text{km}\}\right| \ge 3 $$
That collapses down to 23,500 clusters.
04] Generating Triplets
For every cluster, every combination of 3 points inside it becomes a candidate triangle. That's $ C(n, 3) $, which explodes fast for big clusters, for example: a cluster of 60 points already gives 34,220 triples on its own. So each cluster gets capped at 60 points first, sampled by size, not randomly.
$$ \binom{n}{3} = \frac{n(n-1)(n-2)}{6} $$
def stratified_sample(idx_arr, area_arr, cap):<br>order = np.argsort(area_arr[idx_arr])<br>n_small = cap // 3<br>n_large = cap // 3<br>n_mid = cap - n_small - n_large<br>mid_start = max(0, (len(idx_arr) - n_large - n_mid) // 2)<br>keep = np.unique(np.concatenate([<br>order[:n_small],<br>order[-n_large:],<br>order[mid_start:mid_start + n_mid],<br>]))<br>return idx_arr[keep]
def gen_cluster_triples(idx_arr):<br>local = np.array(list(<br>itertools.combinations(range(len(idx_arr)), 3)),<br>dtype=np.int64)<br>return idx_arr[local]
The sampling takes a third small islands, a third large, a third from the middle of the size distribution, instead of the full cluster or a random cut.
23,500 clusters produce 80,690,777 triples total !!
05] Matching, on the GPU
I gave every triple one CUDA thread. Each thread sorts its 3 points by land area to pick out P0 (smallest, the resort islet), then uses the winding direction of the other two to assign P1 and P2:
long long i = blockIdx.x * (long long)blockDim.x + threadIdx.x;<br>if (i >= n_triples) return;
int pos[3] = {0, 1, 2};<br>for (int a1 = 1; a1 3; a1++)<br>int key = pos[a1];<br>double keyval = a[key];<br>int j = a1 - 1;<br>while (j >= 0 && a[pos[j]] > keyval)<br>pos[j + 1] =...