Indoor positioning

ArcGIS IPS is an indoor positioning system that allows you to locate yourself and others inside a building in real time. Similar to GPS, it puts a blue dot on indoor maps and uses location services to help you navigate to any point of interest or destination. In addition, ArcGIS IPS supports:

  • Wayfinding
  • Location tracking and sharing
  • Location data collection
  • Analytics

IPS-aware maps allow you to build apps that work with ArcGIS IPS to locate yourself inside a building.

Indoor positioning in a mobile app

IPS-aware maps

To create an IPS-aware map, see Get started with ArcGIS IPS. Here, you will find information about making a map IPS-aware, configuring, and sharing IPS-aware maps with ArcGIS Pro.

All IPS-aware maps must conform to the ArcGIS IPS Information Model that specify components such as the:

  • ArcGIS IPS Data Model - Tables and feature classes that enable indoor positioning and maintain up-to-date information about the beacon infrastructure.
  • IPS quality dataset - Point feature classes used to assess the performance of the ArcGIS IPS deployment.
  • Beacons - Bluetooth Low Energy (BLE) beacons.
  • Floor plan - Sites, facilities, levels, and other details.
  • Transitions - Exits and entrances.
  • Pathways - Line features that the device location can snap to.

Once the map is IPS-aware, you can then use ArcGIS IPS Setup apps to plan and perform survey recordings inside facilities where you want to enable ArcGIS IPS. You can collect radio signals from Bluetooth Low Energy (BLE) beacons inside your buildings. For details, see ArcGIS IPS Setup for use with Android or ArcGIS IPS Setup for use with iOS. When using an IPS-aware map you should also consider the following device limitations:

  • This API supports both Wi-Fi and BLE for indoor positioning on Android devices. When using ArcGIS IPS, we recommend that you only employ one of these systems in a facility.

  • Android 9 introduced Wi-Fi scan throttling to limit the number of Wi-Fi scans. To use Wi-Fi IPS, Android devices (9 or higher) must enable developer mode and disable Wi-Fi scan throttling (Developer Options > Networking > Wi-Fi scan throttling).

  • Wi-Fi IPS is not supported on iOS devices because Apple blocks 3rd-party software from using Wi-Fi for positioning.

  • If your facility has a mix of beacon-based IPS and Apple indoors positioning, the IndoorsLocationDataSource will utilize the beacon-based IPS, in preference.

Add indoor positioning to your app

Each IPS-aware map contains map layers to visualize indoor space and has access to indoor positioning data that determines the device location. To display the device location, create the indoor location data source from the indoor positioning definition of the IPS-aware map, as follows:

  1. Load the IPS-aware map and add it into the map view. This is a web map created with ArcGIS Pro that is hosted as a portal item in ArcGIS Online or in ArcGIS Enterprise.

  2. Obtain an indoor positioning definition from the loaded Map::indoorPositioningDefinition(). If this value is null, the map isn't IPS-aware and can't be used for indoor positioning.

  3. Load the indoor positioning definition to avoid any delay when the IndoorsLocationDataSource is started.

  4. Create an IndoorsLocationDataSource using the IndoorPositioningDefinition.

  5. Assign the IndoorsLocationDataSource to the map view's LocationDisplay.
  6. Start the location display's data source and set the LocationDisplayAutoPanMode. The device location will appear on the display as a blue dot and update as the user moves throughout the space.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
  // Get the indoor positioning definition from the map.
  IndoorPositioningDefinition* indoorPositionDefinition = m_map->indoorPositioningDefinition();

  // Display a message to the user if the map doesn't contain an indoor positioning definition.
  if (indoorPositionDefinition == nullptr)
  {
    qDebug() << "Map does not contain an indoor positioning definition";
    return;
  }

  // Load the indoor positioning definition.
  indoorPositionDefinition->load();

  // Create a new indoors location data source using the indoor positioning definition.
  IndoorsLocationDataSource* indoorsLocationDataSource = new IndoorsLocationDataSource(indoorPositionDefinition, this);

  // Set the data source on the mapviews location display to the indoors location data source.
  m_mapView->locationDisplay()->setDataSource(indoorsLocationDataSource);

  // The slot which will receive location, heading, and status updates from the data source.
  connect(m_mapView->locationDisplay(), &LocationDisplay::locationChanged, this, &DeviceLocation::locationChangedHandler);

  // Start the location display on the map view.
  m_mapView->locationDisplay()->start();

Handle location change

You can handle a status changed event so that the IndoorsLocationDataSource is notified when the location data source starts, stops, or fails to start.

An IPS location populates additional properties with the current floor and the transmitter (beacon) count. When the floor changes, you can update the map to filter the display of features for the current floor. The floor value returned with the location is an integer that represents the vertical offset, where 0 is the ground floor. This value increases by one for each floor above the ground floor and decreases by one for each floor below.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
    // Test if the current floor is matches the desired "floor" key, if not change it.
    if (locationProperties["floor"] != m_currentFloor)
    {
        m_currentFloor = locationProperties["floor"].toInt();
        changeFloorDisplay();
    }

    // Get the key/value pairs additional meta-data and properties about the source of the location.
    locationProperties = location.additionalSourceProperties();

    // Set the "horizontalAccuracy" for the locaiton properties.
    locationProperties["horizontalAccuracy"] = QVariant::fromValue(location.horizontalAccuracy());


    // Call the locationPropertiesChanged signal.
    emit locationPropertiesChanged();

In addition to getting the floor, you can get the position source, which will be BLE (Bluetooth Low Energy) when using IPS and GNSS (Global Navigation Satellite Systems) when using GPS. You can also get the count of transmitters (beacons) or satellites used to determine the location.

You can use a definition expression to filter layers in the map to only show features for the current floor. For efficiency, you should only filter features when the floor changes rather than with each location update. Depending on the schema for your floor-aware data, you may need to map the vertical offset value to a level ID in order to filter features by floor (level).

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
    // Loop thru the operational layers in the map.
    for (Layer* layer : *(m_map->operationalLayers()))
    {
        // Filter applicable layers to show features for the current floor (level).
        if (layer->layerType() == LayerType::FeatureLayer && (layer->name() == "Details" || layer->name() == "Units" || layer->name() == "Levels"))
        {
            // Cast the layer to a feature layer.
            FeatureLayer* featureLayer = static_cast<FeatureLayer*>(layer);

            // Set the definition expression on the feature layer (restricts what is shown in the map).
            featureLayer->setDefinitionExpression(QString{"VERTICAL_ORDER = %1"}.arg(m_currentFloor));
        }
    }

App permissions

If your app is using Bluetooth on the device to scan for beacon signals or GPS, make sure to add the appropriate permissions.

Android: Add these permissions to your AndroidManifest.xml file.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
android.permission.BLUETOOTH_ADMIN
android.permission.BLUETOOTH
android.permission.ACCESS_FINE_LOCATION

<!-- Needed if your app targets Android 12 or higher -->
android.permission.BLUETOOTH_SCAN
android.permission.ACCESS_COARSE_LOCATION

iOS: Add this key to your app's Info.plist file.

Use dark colors for code blocksCopy
1
2
3
4
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Bluetooth access is required for indoor positioning</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Location services are required for GNSS positioning</string>

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.