For the complete documentation index, see llms.txt. This page is also available as Markdown.

Geo-fence

Resolve a coordinate to the map that covers it, and localize without naming a map.

Overview

A geo-fence is the real-world area a map covers. It follows the shape of the scan rather than a circle drawn around a pin, so it stays accurate on sites that are long, irregular, or built on several levels.

It answers a question a GPS fix alone cannot: which of my maps is this person standing in?

That unlocks two things:

  • Localize by coordinate. Send a position instead of a mapCode and let the server pick the map.

  • Resolve once, cache it. Ask what is nearby at the start of a session, then query that target for the rest of it.

Prerequisite: geo-reference the map

A map with no geo-reference has no fence, and is invisible to everything on this page. Nothing below works until the map has real-world coordinates and a heading.

There are three ways to get there, depending on what you have:

You have
Use

A surveyed E57 in a known grid

Declare it at upload with source.metadata.crs. The scan georeferences itself.

An existing map and some known points

Georeference Map, which solves origin and heading from ground control points.

A rough position and a compass bearing

Place the map over satellite view in the Developer Portal (below), or auto geo-reference.

When a fence is available

Fences are produced for you. You never create or upload one.

A map has a fence once it is active and carries both coordinates and a heading. Changing either of those refreshes the fence, and it is briefly unavailable while that happens.

geoFenceReady on the map tells you where things stand. While it is false, geoFence is null and the map is not matched by anything on this page.

Generating a fence manually

A fence is produced when a map's placement is saved in the Developer Portal. There is no separate action for it.

Heading is not captured during a scan and defaults to 0, so a new map is usually positioned correctly but oriented incorrectly, and has no fence until its placement is corrected.

1. Open the map over satellite view

Open the map in the Developer Portal and switch the viewer to Satellite. A map without a fence shows only Satellite tiles, Terrain and Mesh opacity in the View panel.

The viewer View panel without a Geo-fence row
No Geo-fence row: this map has no fence.

2. Correct the placement

Search for the site by name or lat, long, then align the mesh with the imagery using the gizmo or the Placement fields. See How to Manually Align Scans for the handles and fields in detail.

3. Save

Save is enabled once the placement changes. Saving writes the coordinates and heading, and starts fence generation.

The Placement panel with Save enabled
Save is enabled once the placement changes.

4. Verify the fence

Generation runs in the background and completes within a few seconds for most maps. Reopen the viewer: the View panel now includes a Geo-fence toggle, with the vertex count of the traced outline.

The View panel showing the Geo-fence toggle and vertex count
The Geo-fence toggle appears once a fence exists.

Enabling it draws the fence over the imagery.

The geo-fence drawn over satellite imagery, following the shape of the mesh
The fence follows the shape of the scan.

The outline should follow the building. If it does not, the map's location or heading is incorrect — correct the placement and save again.

The map is now returned by nearby and can be localized against with geoLocation.

Reading a fence

Returns the polygon as a GeoJSON Feature, so it can be handed straight to a mapping library, along with a bbox and center for fitting the view.

Get a map's geo-fence polygon

get

The real-world area a map covers, as a GeoJSON Feature ready to hand to a mapping library, plus a bbox and centre for framing it. geoFenceReady is false and geoFence is null while a map has no fence, including the short period after its coordinates or heading change.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
mapIdstringRequired

The ID or mapCode of the map

Responses
200

Geo-fence state for the map

application/json
mapCodestringRequiredExample: MAP_63ERQ0V9XSJD
mapNamestringRequiredExample: Ferry new
geoFenceReadybooleanRequired

True only when a polygon exists and is current for the map's present location and heading. Every other case is false with a null geoFence.

Example: true
vertexCountintegerOptional

Points in the ring, including the repeated closing point.

Example: 276
generatedAtstring · date-timeOptionalExample: 2026-09-15T13:04:47.778Z
bboxnumber[]Optional

[minLon, minLat, maxLon, maxLat], for fitBounds.

Example: [-122.3951151,37.79467653,-122.39322079,37.79593026]
get/vps/map/{mapId}/geo-fence
GET /v1/vps/map/{mapId}/geo-fence HTTP/1.1
Host: api.multiset.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "mapCode": "MAP_63ERQ0V9XSJD",
  "mapName": "Ferry new",
  "geoFenceReady": true,
  "vertexCount": 276,
  "generatedAt": "2026-09-15T13:04:47.778Z",
  "bbox": [
    -122.3951151,
    37.79467653,
    -122.39322079,
    37.79593026
  ],
  "center": {
    "latitude": 37.795303395,
    "longitude": -122.394167945
  },
  "geoFence": {
    "type": "Feature",
    "properties": {
      "mapCode": "MAP_63ERQ0V9XSJD",
      "mapName": "Ferry new",
      "source": "mesh"
    },
    "geometry": {
      "type": "Polygon",
      "coordinates": [
        [
          [
            -122.395,
            37.794692
          ],
          [
            -122.3949,
            37.7947
          ]
        ]
      ]
    }
  }
}

Accepts either the mapId or the mapCode.

Finding what is nearby

Given a coordinate, returns the ranked candidates around it. Use this once per session and cache the answer, rather than paying for the lookup on every frame.

List maps and map sets near a coordinate

get

The maps and map sets whose area is within a radius of a point, nearest first. Use it once per session to turn a coordinate into something you can query, then reuse that code. Results are grouped by what you would actually query, so a building whose floors share a map set returns the set rather than each floor. type tells you whether to send back a mapCode or a mapSetCode. Where several maps cover the same spot, every candidate is listed. distanceMeters is 0 when the point is inside an area.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Query parameters
latitudenumber · min: -90 · max: 90RequiredExample: 37.794853
longitudenumber · min: -180 · max: 180RequiredExample: -122.394573
radiusnumber · min: 5 · max: 1000Optional

Search radius in metres, measured to the fence edge.

Default: 100
limitinteger · min: 1 · max: 50Optional

Maximum fences considered before grouping, so a map set may collapse several.

Default: 10
Responses
200

Ranked candidates, nearest first. An empty results array means nothing is in range.

application/json
latitudenumberOptional
longitudenumberOptional
radiusMetersnumberOptional
countintegerOptional
get/vps/map/nearby
GET /v1/vps/map/nearby?latitude=1&longitude=1 HTTP/1.1
Host: api.multiset.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "latitude": 37.794853,
  "longitude": -122.394573,
  "radiusMeters": 200,
  "count": 2,
  "results": [
    {
      "rank": 1,
      "type": "map",
      "mapCode": "MAP_63ERQ0V9XSJD",
      "name": "Ferry new",
      "distanceMeters": 0,
      "containsPoint": true
    },
    {
      "rank": 2,
      "type": "mapSet",
      "mapSetCode": "SET_TESTAB12",
      "name": "HSR Office",
      "distanceMeters": 5.9,
      "containsPoint": false,
      "matchedMaps": [
        {
          "mapCode": "MAP_FLOOR1",
          "distanceMeters": 5.9
        },
        {
          "mapCode": "MAP_FLOOR2",
          "distanceMeters": 12.7
        }
      ]
    }
  ]
}

Three details worth knowing:

  • Results are grouped by what you would query. A building whose floors share a map set returns one mapSet entry listing its matched maps, not one entry per floor. Querying a single floor is what breaks indoor localization, so the grouping steers you towards the right target. type tells you whether to send mapCode or mapSetCode.

  • Distance is measured to the fence edge, so distanceMeters is 0 whenever the point is inside, and containsPoint is simply distanceMeters == 0.

  • Ambiguity is surfaced, not hidden. Where fences overlap, every candidate is listed. A query by coordinate would have silently picked one.

The polygons themselves are not returned here. Fetch one from the geo-fence endpoint when you need to draw it.

Localizing by coordinate

Pass geoLocation to Map Query instead of naming a map. The nearest map whose fence is in range is resolved and queried for you.

If the resolved map belongs to a map set, the whole set is queried, which is what you want indoors.

Field
Meaning

geoLocation

[latitude, longitude] in WGS 84 degrees. Mutually exclusive with mapId, mapCode, mapSetId and mapSetCode.

geoSearchRadius

Overrides the search radius in metres for this request only. Ignored unless geoLocation is supplied. Defaults to 25 m.

A successful response carries resolvedFrom, naming the map the coordinates landed on:

Only active maps in your account with geoFenceReady: true are considered. When nothing is in range you get a 404.

geoLocation doubles as a geoHint. If you supply neither geoHint nor hintPosition, the coordinate is reused as the localization prior, and hintRadius then defaults to 100 m rather than the usual 25 m, because a raw GPS fix is far less precise than a hint you calculated yourself.

Supply geoHint explicitly when you have a better position than the device's GPS. See GeoHint in Localization.

Which approach to use

Situation
Do this

You already know the map

Send mapCode or mapSetCode as before. Nothing changes.

A user opens your app somewhere in a large estate

Call nearby once, pick a target, then query that target for the session.

One-off query and you do not want to manage state

Send geoLocation on the query itself.

Fences overlap and you need to choose deliberately

Call nearby, which lists every candidate, rather than letting the query pick.

Drawing a fence

To draw a fence in your own application, fetch the Feature and hand it to your mapping library, using bbox with fitBounds to frame it.

For a quick visual check of a single map, the Developer Portal already draws it — see Generating a fence manually.

Last updated

Was this helpful?