Grid-based point clustering
Grid-based point clustering turns a long coordinate list into a compact density summary without running an iterative clustering model.
Run — free
Choose a square cell size in decimal degrees, submit latitude and longitude records, and receive every occupied cell with its geographic bounds, member count, and original point indices. The grid is anchored globally, so separate calls use the same boundaries and can be compared or merged when they share a cell size. Processing is deterministic, local, and fast: no map provider, network lookup, random seed, or stored dataset affects the answer.
Choose a grid size that matches the question
A grid cluster is a spatial bin, not a claim that all of its members form a natural community. The cell_size value sets both height and width in decimal degrees. A larger value produces fewer, broader cells that are useful for rapid overviews; a smaller value preserves more local variation and usually creates more occupied cells. Because longitude degrees cover less physical distance as latitude approaches either pole, degree cells are angular squares rather than equal-area ground squares. That tradeoff keeps the operation transparent and inexpensive, but it matters when comparing density across widely separated latitudes. For city-scale data, begin with a modest fraction of a degree and adjust while checking the returned bounds. For worldwide data where equal physical areas are essential, project coordinates into a suitable planar or equal-area system before using a different clustering workflow. This capability deliberately accepts ordinary geographic latitude and longitude only. Its global anchor is fixed at latitude minus ninety and longitude minus one hundred eighty, so a chosen size always creates the same boundaries. Repeat jobs therefore classify identical coordinates consistently, which is valuable for cache keys, scheduled reports, regression fixtures, and partitions shared by independent workers.
Read cell counts, bounds, and membership
Each output cell includes zero-based row and column numbers, south, west, north, and east bounds, a count, and member_indices. Those indices refer to positions in the submitted points array, beginning with zero, so you can recover the exact source records without copying labels or payload fields into the result. Cells are ordered first from south to north by row and then from west to east by column. Members remain in original input order. A coordinate on an internal boundary belongs to the cell immediately north or east of that boundary; coordinates at latitude ninety or longitude one hundred eighty are clamped into the final valid row or column so no valid endpoint falls outside the grid. The returned point_count confirms how many records were processed, while occupied_cell_count shows the compressed number of nonempty bins. Empty cells are omitted because materializing a fine global grid would waste memory and obscure the useful density signal. Cell bounds at the northern and eastern edge may be narrower than cell_size when the selected size does not divide one hundred eighty or three hundred sixty exactly. Use bounds, rather than reconstructing them with unchecked floating-point arithmetic, when drawing rectangles or labeling a report.
Build reproducible density and partitioning workflows
Grid aggregation is a practical first pass for maps, sensor feeds, delivery events, field observations, and coordinate quality checks. It runs in linear time for assignment, followed by a stable sort of occupied cells, making it appropriate when a quick density table matters more than discovering irregular cluster shapes. A dashboard can shade each returned rectangle by count; a data pipeline can route source records using row and column; a test suite can compare occupied cells across releases; and an analyst can identify crowded bins before applying a more expensive method only to selected areas. The algorithm does not calculate centroids, distances between points, adjacency, or density thresholds, and it never merges neighboring occupied cells. It also does not geocode names, repair swapped coordinates, wrap longitude values, or infer whether duplicate records are accidental. Invalid or non-finite coordinates are rejected instead of silently changed. Submit batches that use the same cell_size when you intend to combine results, then add counts for matching row and column pairs and concatenate or remap membership outside the service. Interactive use in the browser is free, while a successful automated API request uses the published base price of $0.002. Identical JSON inputs always produce identical JSON results because the implementation uses no network, randomness, clock, or mutable shared state.
What you can do with it
Create a map density layer
Convert event coordinates into occupied rectangles and shade each cell according to its returned member count.
Partition location records
Use stable row and column identifiers to route nearby records into repeatable processing or storage partitions.
Screen a large spatial dataset
Find crowded and sparse areas quickly before applying slower distance-based analysis to selected subsets.
FAQ
What does an API request cost?
A successful API request costs $0.002. You can also run the same deterministic calculation in the browser.
Is cell size measured in kilometers?
No. It is measured in decimal degrees for both latitude and longitude, so physical width varies with latitude.
Where is the grid anchored?
Rows begin at latitude -90 and columns begin at longitude -180. The anchor never changes between requests.
What happens to a point on a cell boundary?
An internal boundary point enters the cell to its north or east. Latitude 90 and longitude 180 enter the final valid cell.
Are empty cells returned?
No. Only occupied cells are returned, keeping fine-grid results compact.
Does this find irregular or distance-based clusters?
No. It counts points in fixed angular grid cells and does not merge neighboring cells or calculate point-to-point distances.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/geo/point-cluster-grid \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"points":[{"lat":40.7128,"lon":-74.006},{"lat":40.7306,"lon":-73.9352},{"lat":34.0522,"lon":-118.2437}],"cell_size":1}'const res = await fetch("https://api.kit.forhosting.com/geo/point-cluster-grid", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"points": [
{
"lat": 40.7128,
"lon": -74.006
},
{
"lat": 40.7306,
"lon": -73.9352
},
{
"lat": 34.0522,
"lon": -118.2437
}
],
"cell_size": 1
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/geo/point-cluster-grid",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"points": [
{
"lat": 40.7128,
"lon": -74.006
},
{
"lat": 40.7306,
"lon": -73.9352
},
{
"lat": 34.0522,
"lon": -118.2437
}
],
"cell_size": 1
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/geo/point-cluster-grid", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"points":[{"lat":40.7128,"lon":-74.006},{"lat":40.7306,"lon":-73.9352},{"lat":34.0522,"lon":-118.2437}],"cell_size":1}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"points":[{"lat":40.7128,"lon":-74.006},{"lat":40.7306,"lon":-73.9352},{"lat":34.0522,"lon":-118.2437}],"cell_size":1}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/geo/point-cluster-grid", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"points": [
{
"lat": 40.7128,
"lon": -74.006
},
{
"lat": 40.7306,
"lon": -73.9352
},
{
"lat": 34.0522,
"lon": -118.2437
}
],
"cell_size": 1
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "geo.point_cluster_grid",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_points | 100000 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |