Offline land/sea point lookup: 182 KB global dataset, microsecond answers with confidence, built on the Trifold T3 triangular DGGS
Project description
landcheck — offline land/sea lookup
This is created as Trifold application (also test and demonstration)
Is this lat/long point on land or in the sea? gets answered anywhere on Earth in ~1–13 µs, fully offline, from a small 182 KB bundled dataset — with a confidence value for every answer. Works with Python and JavaScript.
Live in-browser demo — classify sample or your own points (CSV / GeoJSON) on a map and watch the measured lookup rate; the page embeds the real JS library and dataset.
import sys; sys.path.insert(0, "landcheck/python")
from landcheck import LandCheck
lc = LandCheck()
lc.is_land(24.7536, 59.4370) # True (lon, lat — Tallinn)
lc.check(-0.1276, 51.5072)
# LandResult(land=True, kind='land', confidence=1.0, land_fraction=1.0,
# cell='TFA95BM', refined=False)
import { LandCheck } from "./landcheck/js/landcheck.mjs";
const lc = await LandCheck.fromFile(); // NodeJs takes bundled data directly
// OR:
const lc = await LandCheck.fromUrl("/data/landsea_L10.tfls"); // browser needs to load data file online
lc.isLand(24.7536, 59.437); // true
lc.check(-0.1276, 51.5072);
// { land: true, kind: 'land', confidence: 1, landFraction: 1,
// cell: 'TFA95BM', refined: false }
How it works
The Trifold level-10 grid (~7 km triangles, 21M cells globally) is
classified against Natural Earth 1:50m land: 6.15M cells touch land,
of which 168,833 are coastal (mixed land/sea) and the rest are wholly
interior. Because Trifold addresses sort hierarchically, every cell at
any level maps to a contiguous range in the canonical level-10 index
space (face·4¹⁰ + path), so the entire classification collapses to
153,884 run-length intervals — 182 KB compressed, including a 4-bit
land-area fraction for every coastal cell.
A lookup is: locate the point's level-10 triangle (pure float math, no dependencies), then binary-search the runs.
| answer kind | meaning | land |
confidence |
|---|---|---|---|
land |
cell wholly inside land | True |
1.0 |
sea |
cell absent from dataset | False |
1.0 |
coast |
mixed cell | land_fraction >= 0.5 |
max(f, 1−f) |
coast + refined |
decided by OSM polygon test | exact | 0.99 |
Measured accuracy (30,000 uniform random points vs. exact polygon
containment in the source dataset): 99.82% agreement overall; land and
sea answers 100% correct; all residual error lives in coast
answers, which self-report their lower confidence (mean calibration
credit 0.997).
Caveats inherited from the source data: Natural Earth 1:50m treats lakes as land and omits islets below its resolution; coast cells flag exactly where such risk is concentrated.
Optional coastal refinement (OSM)
For applications that need near-exact coastlines, a second dataset
(coastal_osm_L10.tflr, 12.7 MB) stores OSM simplified land polygons
(osmdata.openstreetmap.de)
clipped to every triangle crossed by either the Natural Earth or OSM
coastline, quantized to a cell-local 16-bit grid (~0.1 m) with delta-varint
rings. When loaded, covered answers switch from the Natural Earth base or
bulk land-fraction guess to an exact point-in-polygon test. This has
99.95% agreement with full polygon containment on 4,000 random points inside the
original coastal-cell sample (~23 µs per refined lookup, Python):
lc = LandCheck(refine_path="landcheck/data/coastal_osm_L10.tflr")
await lc.loadRefinement("landcheck/data/coastal_osm_L10.tflr");
Only covered coastline cells pay the polygon-test cost. OSM can override a
base land or sea answer when its coastline crosses a triangle that Natural
Earth classified differently.
Note on dataset semantics: the base layer keeps Natural Earth's view of
the world for land/sea kinds. NE and OSM systematically disagree
about Antarctica (OSM land polygons are clipped near the pole and draw
ice-shelf edges differently). With refinement enabled, OSM is authoritative
in every covered coastline cell.
Command-line one-off checks (uses refinement automatically when the file is present):
$ python landcheck/python/landcheck.py 24.7536 59.4370
LAND kind=land confidence=1.000 land_fraction=1.0 cell=TFAVKGR refined=False
Performance
| operation | speed |
|---|---|
Python scalar is_land |
~13 µs/point (77k/s) |
Python batch is_land_batch (numpy) |
~2.8 µs/point (360k/s) |
JavaScript isLand (Node) |
~0.8 µs/point (1.2M/s) |
| dataset load | ~30 ms |
The Python library is dependency-free (stdlib only); is_land_batch
optionally uses numpy + the trifold SDK. The JS library is a single
ES module, works with browser + Node.
Files
build.py TFLS builder: compacted L10 grid GeoJSON -> landsea_L10.tfls
refine_build.py TFLR builder: land polygons + TFLS -> coastal_osm_L10.tflr
python/ landcheck.py (public API) · _fastloc.py (point location)
js/landcheck.mjs the JS library (same data, same answers)
data/ landsea_L10.tfls (bundled) · coastal_osm_L10.tflr (optional)
tests/ pytest + node:test suites, shared fixture (points.json)
Rebuild from a Trifold grid product:
python landcheck/build.py # needs data/global_tri_L10_compacted.geojson
python landcheck/refine_build.py --land osm_simplified_land_polygons.geojson
python landcheck/tests/make_fixture.py # refresh cross-language fixture
pytest landcheck/tests/ && node --test landcheck/tests/test_landcheck.mjs
Format notes
Custom data format is used to ensure compactness.
TFLS (land/sea runs): 16-byte header + zlib stream of
varint(gap), varint(length<<1 | coastal) per run, then 4-bit land
fractions for coastal cells. TFLR (refinement): 12-byte header +
zlib stream of varint(Δindex), varint(code) per covered cell, where
code 0/1 = all sea/land and code n≥2 introduces n−1 quantized
zigzag-delta rings combined by the even-odd rule. Both formats are
level-agnostic (the level lives in the header), so the same tooling can
serve an L8 (~30 KB) or L12 (~3 MB) variant.
Roadmap
- Country detection: the run-length layer maps each cell to a country id instead of a land bit (runs split at borders; border cells carry clipped boundary polygons like TFLR does for coastlines). Probably can use same formats, same lookup path, same confidence model.
- Level-12 (~1.8 km trifolds) variant for higher-precision use.
- Published packages (
pip install trifold-landcheck, npm equivalent).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file landcheck-0.1.0.tar.gz.
File metadata
- Download URL: landcheck-0.1.0.tar.gz
- Upload date:
- Size: 193.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
405d648ee8224002af7bc2cac152bbc3faeb7e27195ee0505245be7035fa068f
|
|
| MD5 |
21c6f71b20286c5607b5f9506e57f6b2
|
|
| BLAKE2b-256 |
f7492d7c49feac950bda58b6e0d9fb704f92a7e6bb4cdef8826be15ea1294959
|
File details
Details for the file landcheck-0.1.0-py3-none-any.whl.
File metadata
- Download URL: landcheck-0.1.0-py3-none-any.whl
- Upload date:
- Size: 194.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a039a0987ac182d780bcae02868eb5817430d7d9a94a54c41c38a4f83ef826d6
|
|
| MD5 |
4d33fdf82490957f0a8f4cd082b1e173
|
|
| BLAKE2b-256 |
12b0916fe4fa1e21df6d37bd4528a57eafbf41fe66c96f9e2f4cc1ccfda14168
|