{
  "slug": "geo-cluster",
  "name": "gizza-ai/geo-cluster",
  "version": "0.1.0",
  "title": "Geo Cluster — DBSCAN & Grid Clustering for Lat/Long Points — gizza.ai",
  "description": "Cluster latitude and longitude points by real ground distance with DBSCAN or a grid. Get cluster assignments and centroids, entirely in your browser.",
  "tags": [
    "geo cluster",
    "dbscan clustering",
    "cluster gps points",
    "latitude longitude clustering",
    "cluster coordinates",
    "geospatial clustering",
    "cluster centroids"
  ],
  "category": "data",
  "urls": {
    "page": "https://gizza.ai/tools/geo-cluster/",
    "markdown": "https://gizza.ai/tools/geo-cluster/index.md",
    "descriptor": "https://gizza.ai/tools/geo-cluster/tool.json",
    "deep_link_example": "https://gizza.ai/tools/geo-cluster/?points=51.5074%2C-0.1278%2CCharing%20Cross%0A51.5079%2C-0.1281%2CNelson%27s%20Column%0A51.5155%2C-0.1410%2COxford%20Circus%0A51.5161%2C-0.1415%2CRegent%20Street%0A40.7128%2C-74.0060%2CNew%20York&method=dbscan&radius=500&units=m&min_points=2&coord_order=lat_lon&output=text"
  },
  "cli": "gizza tool geo-cluster \"51.5074,-0.1278,Charing Cross\n51.5079,-0.1281,Nelson's Column\n51.5155,-0.1410,Oxford Circus\n51.5161,-0.1415,Regent Street\n40.7128,-74.0060,New York\"",
  "tool": {
    "description": "Group latitude/longitude points that sit near each other on the ground and report which cluster each point belongs to, plus each cluster's centroid. Distance is haversine great-circle distance, so radius is a real ground distance in the unit you choose (m, km, mi, ft, nmi) rather than degrees — one degree of longitude is ~111 km at the equator but ~28 km at 75° latitude, which is why clustering raw lat/lon columns in Euclidean space gets the wrong answer. method='dbscan' (default) is density clustering: a point with min_points neighbours inside radius is a core point, cores that reach each other merge into arbitrarily shaped clusters, and points that meet no core are reported as unclustered noise. method='grid' instead bins points into fixed cells radius across, latitude-compensated so cells stay roughly square. points accepts CSV `lat,lon[,label]` rows, a JSON array of pairs or {lat,lon,label} objects, or GeoJSON (Point/MultiPoint/Feature/FeatureCollection); coord_order switches CSV and JSON pairs between lat,lon and lon,lat. output is 'text' (default summary), 'csv' (one row per point), 'json' (points plus per-cluster centroid, radius, bbox and members) or 'geojson' (point features plus centroid features). Centroids are spherical means, so a cluster straddling the antimeridian is handled correctly, and results are deterministic: clusters are numbered by their first member in input order and a border point joins the first cluster that reaches it. Up to 10000 points. Runs locally.",
    "parameters": {
      "additionalProperties": false,
      "properties": {
        "coord_order": {
          "default": "lat_lon",
          "description": "Which value comes first in the CSV rows and JSON pairs: 'lat_lon' (default) reads '51.5,-0.1' as latitude then longitude; 'lon_lat' reads it as longitude then latitude. GeoJSON input always uses [longitude, latitude] per RFC 7946 and ignores this setting, as do JSON objects with named lat/lon fields.",
          "enum": [
            "lat_lon",
            "lon_lat"
          ],
          "type": "string"
        },
        "method": {
          "default": "dbscan",
          "description": "Clustering algorithm. 'dbscan' (default) is density clustering: a point with at least min_points neighbours inside radius is a core point, cores that reach each other merge into one cluster of any shape, points merely reached by a core join as border points, and everything else is unclustered noise. 'grid' bins points into fixed cells radius across (cell width divided by cos(latitude) so cells stay roughly square on the ground) and calls every cell holding at least min_points points a cluster — cheaper and reproducible, but clusters can never span a cell boundary.",
          "enum": [
            "dbscan",
            "grid"
          ],
          "type": "string"
        },
        "min_points": {
          "default": 2,
          "description": "Minimum number of points a cluster must contain, counting the point itself (the DBSCAN core-point threshold, as in PostGIS and scikit-learn). Default 2. Raise it to demand denser groups and push sparse points into the unclustered class; set it to 1 to keep every point, so nothing is ever reported as noise.",
          "minimum": 1,
          "type": "integer"
        },
        "output": {
          "default": "text",
          "description": "Result format. 'text' (default) is a readable summary: counts, then one line per cluster with its size, centroid and radius. 'csv' is one row per input point with its cluster id (or 'noise'), cluster size and centroid — paste-ready for a spreadsheet. 'json' carries the full result: every point with its cluster (null for noise) plus per-cluster centroid, radius, bounding box and member list. 'geojson' is a FeatureCollection of the input points (each tagged with its cluster or 'noise') followed by one centroid feature per cluster, ready to drop on a map.",
          "enum": [
            "text",
            "csv",
            "json",
            "geojson"
          ],
          "type": "string"
        },
        "points": {
          "description": "The points to cluster, in any one of three forms: CSV/TSV rows of `lat,lon` with an optional third label field (e.g. '51.5,-0.1,Shop'), a JSON array of pairs or objects (e.g. [[51.5,-0.1],{\"lat\":51.51,\"lng\":-0.1,\"label\":\"Depot\"}]), or GeoJSON (a Point, MultiPoint, Feature, FeatureCollection or GeometryCollection — a feature's name/label/title property becomes the point label). The form is auto-detected. Blank lines, '#' comments and one header row are skipped. Maximum 10000 points.",
          "type": "string"
        },
        "radius": {
          "default": 500.0,
          "description": "Neighbourhood radius for dbscan, or the cell size for grid, expressed in units — a real ground distance measured with the haversine formula, not degrees. Default 500 (metres). Pick roughly the distance at which two points count as 'the same place': ~50-200 m for store visits, ~1-5 km for city districts. Must be positive and no larger than half the Earth's circumference.",
          "type": "number"
        },
        "units": {
          "default": "m",
          "description": "Distance unit for radius and for every distance in the output: 'm' metres (default), 'km' kilometres, 'mi' statute miles, 'ft' feet, 'nmi' nautical miles.",
          "enum": [
            "m",
            "km",
            "mi",
            "ft",
            "nmi"
          ],
          "type": "string"
        }
      },
      "required": [
        "points"
      ],
      "type": "object"
    }
  }
}