# Clusters & markers

> Field reference for everything a map view returns: clusters, pins, named pins, many-people pins, approximate areas, the breakdown, unplaced people and GeoJSON.

- **HTML:** https://offendersearch.app/docs/map/clusters-and-markers
- **Base URL:** https://api.offendersearch.app
- **Authentication:** `X-API-Key` request header
- **Map API reference as markdown:** https://offendersearch.app/docs/map.md

## What a view can contain

| Array | Object | When |
| --- | --- | --- |
| `clusters` | A cluster: a count for a grid cell. | Zoom 3–12, and closer views that are too dense for pins. |
| `markers` | A pin: one person at one address. | Zoom 13–14, with no name. |
| `markers` | A named pin. | Zoom 15+. |
| `markers` | A many-people pin: several people at one address. | From zoom 13. |
| `areas` | An approximate-area pin: people known only to an area. | From zoom 13. |

## Cluster

| Field | Meaning |
| --- | --- |
| `id` | An id for the cluster. Use it as a key when you redraw. |
| `lat`, `lng` | Where to draw it. |
| `count` | People in the cluster. |
| `bbox` | `west,south,east,north`: the extent of the people in the cluster. |
| `expansionZoom` | Fit the map to `bbox` at this zoom and the cluster comes apart: into smaller clusters, pins, or one many-people pin. |
| `breakdown` | The same four numbers as the view’s `breakdown`, for this cluster. |

A cluster never traps the user. People who share one address never split, so at its `expansionZoom` such a cluster becomes one pin with a count and a `groupId`.

## Pin

At zoom 13–14 a pin is a position and nothing that identifies anyone.

mode: pins

```json
"mode": "pins",
"markers": [
  {
    "id": "mk_Qa8…",
    "lat": 39.96123, "lng": -82.99881,
    "precision": "street",
    "addressType": "home"
  }
]
```

| Field | Meaning |
| --- | --- |
| `id` | The marker id, `mk_…`. Pass it to the records route. |
| `lat`, `lng` | The registered address. |
| `precision` | `street` for an exact street address. |
| `addressType` | `home`, `work`, `school` or `other`. |
| `distanceMiles` | Only with a radius request: the distance from the centre. |

A person with two mapped addresses, such as home and work, has two pins. `total` counts that person once.

## Named pin

From zoom 15 each pin also carries a label and basic fields.

mode: markers

```json
"mode": "markers",
"markers": [
  {
    "id": "mk_Zb3…",
    "lat": 39.96123, "lng": -82.99881,
    "precision": "street",
    "addressType": "home",
    "label": "Sample J. Tester",
    "housingStatus": "housed",
    "hasPhoto": true,
    "registryState": "OH"
  },
  {
    "id": null,
    "lat": 39.9678, "lng": -83.0042,
    "precision": "street",
    "count": 12,
    "groupId": "mg_8Uo…",
    "members": [
      {
        "id": "mk_…",
        "addressType": "home",
        "label": "Sample S. Demo",
        "housingStatus": null,
        "hasPhoto": false
      }
    ]
  }
],
"areas": [
  {
    "lat": 39.969, "lng": -83.0114,
    "count": 9,
    "precision": "zip",
    "label": "43215",
    "groupId": "mg_X1G…"
  }
]
```

| Field | Meaning |
| --- | --- |
| `label` | The first initial and last name, such as “J. Smith”. |
| `housingStatus` | `housed`, `homeless` or `incarcerated`. `null` when the registry does not say. `null` does not mean housed. |
| `hasPhoto` | `true` when the record has a photo. |
| `registryState` | The state whose registry lists the person. |

These fields are enough for a popup. They cost nothing beyond the map request that returned them.

## Many-people pin

Several people at one address are one pin. It has `id: null`, a `count` and a `groupId`.

| Field | Meaning |
| --- | --- |
| `id` | Always `null`. There is no single record behind this pin. |
| `count` | People at the address. |
| `groupId` | The group id, `mg_…`. Pass it to the groups route. |
| `members` | The people, inline, when there are 10 or fewer and the zoom is 15 or closer. Otherwise use the groups route. |

## Approximate-area pin

Some people are known only to the middle of a ZIP, a city or a county. People in custody and people without a fixed address are here too. They come back in `areas`, never in `markers`, so they are never drawn as an exact pin.

| Field | Meaning |
| --- | --- |
| `lat`, `lng` | The middle of the area. |
| `count` | People in it. |
| `precision` | `zip`, `city` or `county`. |
| `label` | The name of the area, such as a ZIP code. |
| `groupId` | Pass it to the groups route for the people. |

Draw an area pin so it does not look like an address: a dashed circle, a different colour, and the word “approximate”.

## The groups route

`GET /v1/map/groups/{groupId}` lists the people behind a many-people pin or an approximate-area pin. Each page is one map request.

GET /v1/map/groups/{groupId}

```bash
curl "https://api.offendersearch.app/v1/map/groups/mg_8Uo…?page=1&perPage=50" \
  -H "X-API-Key: $OFFENDERSEARCH_KEY"
```

200 OK

```json
{
  "total": 12,
  "page": 1,
  "perPage": 50,
  "totalPages": 1,
  "kind": "address",
  "named": true,
  "members": [
    {
      "id": "mk_…",
      "addressType": "home",
      "label": "Sample S. Demo",
      "housingStatus": null,
      "hasPhoto": false
    }
  ],
  "notice": "A pin shows a registered address, not a person's current location."
}
```

| Field | Meaning |
| --- | --- |
| `page`, `perPage` | Query parameters. `page` starts at 1. `perPage` is 1 to 100; the default is 50. |
| `total`, `totalPages` | People in the group, and pages at this `perPage`. |
| `kind` | `address` for a many-people pin, `area` for an approximate-area pin. |
| `named` | `true` when the group id came from a view at zoom 15 or closer. Members carry `label` only then. |
| `members[].id` | A marker id. Pass it to the records route. |

## The breakdown

`breakdown` is on the view and on every cluster. Its four numbers are not a split of the total: one person can count in two of them.

| Field | Counts |
| --- | --- |
| `street` | People with an exact street address. |
| `housed` | People the registry lists as housed. |
| `homeless` | People the registry lists as homeless. |
| `incarcerated` | People the registry lists as in custody. |

People whose registry does not state a housing status are in `total` and in none of the three housing numbers.

## Unplaced people

`unplaced` counts people in the states the view touches who have no mappable address. They are on a registry, and they are not on the map.

unplaced

```json
"unplaced": {"count": 96, "states": ["OH"]}
```

Say so next to the map, so a short list of pins is not read as the whole list. For example: “96 more people are registered in Ohio with no mappable address. Search by name to find them.” The count is for whole states, not for the box.

## GeoJSON

With `format=geojson` the answer is a `FeatureCollection` of points, with content type `application/geo+json`. It can go straight into a map library’s GeoJSON source.

format=geojson

```json
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {"type": "Point", "coordinates": [-82.9988, 39.9612]},
      "properties": {
        "cluster": true,
        "cluster_id": "c10_1103_1551",
        "point_count": 412,
        "point_count_abbreviated": "412",
        "expansion_zoom": 11,
        "bbox": [-83.06, 39.91, -82.97, 39.98],
        "breakdown_street": 412,
        "breakdown_housed": 201,
        "breakdown_homeless": 77,
        "breakdown_incarcerated": 0
      }
    }
  ],
  "meta": {"mode": "clusters", "zoom": 10, "total": 508}
}
```

| Feature | Properties |
| --- | --- |
| Cluster | `cluster: true`, `cluster_id`, `point_count`, `point_count_abbreviated`, `expansion_zoom`, `bbox`, and `breakdown_street`, `breakdown_housed`, `breakdown_homeless`, `breakdown_incarcerated`. |
| Pin | The pin’s fields, plus `kind: "marker"`. |
| Many-people pin | The pin’s fields, plus `kind: "group"`. |
| Approximate area | The area’s fields, plus `kind: "area"`. |

Everything that is not a feature, such as `mode`, `zoom`, `total`, `unplaced` and `notice`, is under `meta`. For a heat map, use the cluster features with `point_count` as the weight.

---

## Related

- Previous: [The viewport request](https://offendersearch.app/docs/map/viewport.md)
- Next: [The click sequence](https://offendersearch.app/docs/map/click-sequence.md)
- Index: [Map API reference](https://offendersearch.app/docs/map.md)
