Fishnet grid from bounding box
Turn a geographic bounding box into a complete regular fishnet without installing GIS software or writing coordinate loops.
Run — free
Supply the western, southern, eastern, and northern edges in decimal degrees along with a square cell size. The result reports the grid dimensions and every cell's row, column, and exact bounds. Cells begin at the southwest corner, follow a predictable order, and are clipped along the far edges so the requested box is covered exactly even when its width or height is not evenly divisible by the chosen size.
Define the bounding box and cell size
Start with a conventional longitude and latitude bounding box. West and east describe longitudes from -180 to 180, while south and north describe latitudes from -90 to 90. East must be greater than west, and north must be greater than south. The capability intentionally does not interpret a west value greater than east as an antimeridian crossing, because wrapping would make column order and clipped edge behavior less obvious. Set cell_size in decimal degrees. The same value controls width and height, creating angular squares rather than squares of equal physical area. A one-degree longitude span becomes physically narrower toward the poles, so this grid is best for transparent geographic partitioning, tiling, sampling, and display preparation where angular boundaries are appropriate. If equal distances or equal areas matter, project the source extent into a suitable planar coordinate system and use a projected-grid workflow. Choose a size that produces enough detail for the task without creating unnecessary output. The request is rejected before allocation if it would exceed 10,000 cells. This explicit ceiling keeps browser, API, and downstream JSON handling predictable.
Understand coverage, clipping, and ordering
The fishnet is anchored at the exact southwest corner supplied in the request; it is not aligned to a hidden global origin. Row zero begins at south, and column zero begins at west. Each normal cell extends by cell_size toward the north and east. When the bounding-box width or height is not an exact multiple of that size, cells in the final column or row are shortened and clipped to east or north. No cell extends beyond the requested box, and no gap is left inside it. The response includes rows, columns, and cell_count so you can validate expected output volume without recounting the cells array. Cells are returned in row-major order: all columns of the southernmost row appear first, followed by the next row northward. Every record contains a zero-based row and column and four numeric bounds named west, south, east, and north. Coordinates are normalized to twelve decimal places to remove artifacts such as 0.30000000000000004 that can otherwise arise from repeated floating-point addition. Use the returned bounds directly when drawing rectangles, constructing polygons, labeling tiles, or splitting another dataset.
Use a deterministic fishnet in data workflows
A generated fishnet is useful whenever a continuous study area must become repeatable rectangular units. A mapping application can turn each bounds record into a polygon ring for visualization. A sampling workflow can attach one survey task to every row and column. An analytics pipeline can test points against returned cells, aggregate records per tile, or divide a large query into smaller geographic requests. Because the origin is the supplied bounding box, two calls produce matching cell identifiers only when west, south, east, north, and cell_size are identical; retain those inputs with any stored row and column. The operation is deterministic and uses no map provider, network request, random seed, clock, or shared state. Identical JSON input therefore gives identical grid geometry and ordering. This capability returns bounds rather than GeoJSON to keep the response compact and neutral between GIS formats. It does not calculate centroids, areas, neighbors, point membership, intersections, or coordinate transformations. Those steps can be performed downstream from the explicit bounds. Interactive browser execution is free, while each successful API request uses the published base price of $0.002.
What you can do with it
Partition a map extent
Create stable row and column tiles for batching spatial queries or distributing geographic processing work.
Build sampling units
Generate complete rectangular units across a study area before selecting or assigning field samples.
Draw a fishnet overlay
Convert each returned bounds record into a rectangle or polygon for a GIS or web-map layer.
FAQ
What does an API request cost?
Each successful API request costs $0.002. The same deterministic calculation can also run free in the browser.
What units does cell_size use?
Decimal degrees. It controls both longitude width and latitude height, so cells are angular squares rather than equal-area ground squares.
What happens when the box is not divisible by the cell size?
The last row and column are clipped to the northern and eastern bounds, preserving complete coverage without extending outside the box.
Can the bounding box cross the antimeridian?
No. East must be greater than west. Split an antimeridian-crossing extent into two conventional bounding boxes.
In what order are cells returned?
Cells use row-major order, starting with the southwest cell, moving east across each row, and then moving north.
Why is there a 10,000-cell limit?
It bounds computation and response size. Increase cell_size or divide the bounding box when a request would exceed the limit.
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/grid-from-bbox \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"west":-2,"south":50,"east":0.5,"north":52,"cell_size":1}'const res = await fetch("https://api.kit.forhosting.com/geo/grid-from-bbox", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"west": -2,
"south": 50,
"east": 0.5,
"north": 52,
"cell_size": 1
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/geo/grid-from-bbox",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"west": -2,
"south": 50,
"east": 0.5,
"north": 52,
"cell_size": 1
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/geo/grid-from-bbox", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"west":-2,"south":50,"east":0.5,"north":52,"cell_size":1}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"west":-2,"south":50,"east":0.5,"north":52,"cell_size":1}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/geo/grid-from-bbox", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"west": -2,
"south": 50,
"east": 0.5,
"north": 52,
"cell_size": 1
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "geo.grid_from_bbox",
"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_cells | 10000 |
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. |