Read the original article:Continuous Location Tracking with Geofence Alerts
Continuous Location Tracking with Geofence Alerts
Requirement Description
Wearable apps often need continuous location tracking for fitness and navigation. Beyond just fetching the user's coordinates once, applications must also montior entry and exit from defined geographic areas (geofences).
This scenario shows how to:
- Request user permission for location services.
- Start continuous location updates.
- Define a geofence (circle area)
- Trigger custom logic when entering/exiting the area.
- Stop updates when no longer needed.
Background Knowledge
- Location data is accessed via @ohos.geoLocationManager.
- Needs runtime permission handling.
- Updates may consume significant battery. Only subscribe when required.
- Geofencing is not directly built-in, must be implemented with math.
Implementation Steps
1.Request user authorization using a UIAbility;
//EntryAbility.ets
// Use UIExtensionAbility: Replace import { UIAbility } from '@kit.AbilityKit' with import { UIExtensionAbility } from '@kit.AbilityKit'.
import { abilityAccessCtrl, common, Permissions, UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
const permissions: Array<Permissions> = ['ohos.permission.LOCATION','ohos.permission.APPROXIMATELY_LOCATION'];
// Use UIExtensionAbility: Replace common.UIAbilityContext with common.UIExtensionContext.
function reqPermissionsFromUser(permissions: Array<Permissions>, context: common.UIAbilityContext): void {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// The return value of requestPermissionsFromUser determines whether to display a dialog box to request user authorization.
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// If the user grants the permission, the application continues the operation.
} else {
// If the user denies the permission, a message is displayed, indicating that user authorization is required and instructing the user to set the permission in Settings.
return;
}
}
// Authorization is successful.
}).catch((err: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
})
}
// Use UIExtensionAbility: Replace UIAbility with UIExtensionAbility.
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage): void {
// ...
windowStage.loadContent('pages/Index', (err, data) => {
reqPermissionsFromUser(permissions, this.context);
// ...
});
}
// ...
}
2.Applying for Permissions: ohos.permission.APPROXIMATELY_LOCATION, ohos.permission.APPROXIMATELY_LOCATION and ohos.permission.LOCATION.
//module.json5
"requestPermissions": [
{
"name": "ohos.permission.LOCATION",
"reason": "$string:location_permission_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.APPROXIMATELY_LOCATION",
"reason": "$string:location_permission_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
],
3.Create a location request with interval.
4.Implement Haversine distance calculation to simulate geofence detection.
5.Display updates in UI and stop listener in onDisappear.
Code Snippet / Configuration
//MainPage.ets
import geoLocationManager from '@ohos.geoLocationManager';
import {BusinessError} from '@ohos.base'
interface GeoFenceType {
lat: number;
lon: number;
radius: number;
}
//Example geofence
const GEOFENCE: GeoFenceType = {lat: 22.87707, lon: 113.88187, radius: 100}
@Entry
@Component
struct MainPage {
@State latitude: string = "-"
@State longitude: string = "-"
@State insideFence: boolean = false
async aboutToAppear() {
//Use ContinuousLocationRequest as the input parameter.
let request:geoLocationManager.ContinuousLocationRequest = {'interval': 1, 'locationScenario': geoLocationManager.UserActivityScenario.NAVIGATION};
let locationCallback = (location:geoLocationManager.Location):void => {
console.info('locationCallback: data: ' + JSON.stringify(location));
this.latitude = location.latitude.toFixed(5)
this.longitude = location.longitude.toFixed(5)
const inside = this.isInsideGeofence(location.latitude, location.longitude)
this.insideFence = inside
}
try {
geoLocationManager.on('locationChange', request, locationCallback);
}catch (err){
let e: BusinessError = err as BusinessError
console.error(e.code + e.message)
}
}
aboutToDisappear() {
let locationChange = (location:geoLocationManager.Location):void => {
console.info('locationChange: data: ' + JSON.stringify(location));
};
geoLocationManager.off('locationChange',locationChange)
}
private isInsideGeofence(lat: number, lon: number) : boolean{
const R = 6371000;//Earth radius in meters
const dLat = (lat - GEOFENCE.lat) * Math.PI / 180
const dLon = (lon - GEOFENCE.lon) * Math.PI / 180
const a = Math.sin(dLat/2)**2 +
Math.cos(GEOFENCE.lat * Math.PI/180) * Math.cos(lat * Math.PI/180) *
Math.sin(dLon/2)**2
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
const distance = R * c
return distance <= GEOFENCE.radius
}
build() {
Column({space:6}){
Text(`Latitude: ${this.latitude}`).fontSize(12)
Text(`Longitude: ${this.longitude}`).fontSize(12)
Text(`Inside Geofence: ${this.insideFence ? 'YES' : 'NO'}`).fontSize(12)
} .justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
}
Test Results
Limitations or Considerations
Not applicable to apps targeting API Level < 11.

Top comments (0)