DEV Community

HarmonyOS
HarmonyOS

Posted on

How to Recreate a MapCircle on Location Change in HarmonyOS Wearable Devices?

Read the original article:How to Recreate a MapCircle on Location Change in HarmonyOS Wearable Devices?

How to Recreate a MapCircle on Location Change in HarmonyOS Wearable Devices

Requirement Description

In HarmonyOS wearable devices, when drawing a MapCircle on a map, the application must handle dynamic location changes. Specifically, if the device location changes, the MapCircle should be recreated at the new location to reflect the updated position accurately. The previous MapCircle instance should be removed or invalidated before drawing the new one to avoid overlapping or outdated visual representation.

The system should ensure:

  1. Real-time updates of the MapCircle whenever the device location changes.
  2. Smooth removal of the previous MapCircle to maintain map clarity.
  3. Efficient resource management to prevent performance degradation due to frequent redraws.
  4. Compatibility with both GNSS-based and alternative location sources (e.g., Wi-Fi, paired smartphone).

This requirement ensures that the geospatial representation on the map always accurately reflects the user’s current location, enhancing usability and situational awareness.

Background Knowledge

In HarmonyOS, developers can use MapKit to draw and manage map elements, including MapCircle. A MapCircle represents a circular area on the map, which can be used for geofencing, highlighting regions, or visualizing proximity.

Using MapKit, it is possible to not only draw a MapCircle but also remove or update it dynamically. When the device location changes, the previous MapCircle can be removed and a new one can be drawn at the updated location, ensuring that the visual representation on the map always corresponds to the user’s current position.

Additionally, LocationKit provides real-time location updates from various sources such as GNSS, Wi-Fi, or a paired smartphone. By combining MapKit and LocationKit, developers can implement dynamic map behavior where MapCircle elements are continuously synchronized with the device’s location, providing accurate and up-to-date geospatial feedback to the user.

This approach is essential for applications that rely on location-aware interactions, such as wearable geofencing, navigation, or context-based alerts.

Implementation Steps

  1. Initialize Map Options and Controller
    • Retrieve map parameters from the router and store them in mapOptions.
    • Initialize the map controller and event manager through MapKit.
    • Enable location display and user controls on the map.
  2. Draw Initial MapCircle
    • Create initial MapCircle using the mapOptions center coordinates.
    • Set properties such as radius, fillColor, strokeColor, strokeWidth, clickable, visible, and zIndex.
    • Add the circle to the map using mapController.addCircle().
  3. Set Up Location Change Callback
    • Register a callback with geoLocationManager that triggers whenever the device location changes.
    • Update the currentLocation state with the new location.
    • Call centerMapOnLocation(location) to animate the map camera to the new location.
    • Call updateCirclePosition(location) to recreate the MapCircle at the updated position.
  4. Center Map on Device Location
    • Define a camera position with latitude, longitude, zoom, tilt, and bearing.
    • Use mapController.animateCamera() to smoothly move the map to the updated location.
  5. Remove and Redraw MapCircle on Location Change
    • In updateCirclePosition(location), first check if a previous MapCircle exists.
    • Remove the previous circle using this.mapCircle.remove().
    • Create a new MapCircle with the updated location coordinates.
    • Set the visual properties (radius, fillColor, strokeColor, etc.) for the new circle.
    • Add the new circle to the map and update the mapCircle reference.
  6. Start Continuous Location Updates
    • Call geoLocationManager.on('locationChange', locationRequest, locationChangeCallback) to receive ongoing location updates.
    • Ensure location updates are accurately reflected in real-time on the map.
  7. Clean Up on Component Disappear
    • When the component is about to disappear, unregister the location change callback using geoLocationManager.off().
    • This prevents memory leaks and unnecessary background location updates.
  8. Render Map Component
    • Use the MapComponent within a Stack layout.
    • Pass the mapOptions and the callback to initialize the map correctly.
    • Ensure the map occupies full width and height.

Code Snippet / Configuration

1. Imports

import { MapComponent, mapCommon, map } from '@kit.MapKit';
import { AsyncCallback} from '@kit.BasicServicesKit';
import { router } from '@kit.ArkUI';
import { geoLocationManager } from '@kit.LocationKit';
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • MapKit is used for map rendering and map elements like MapCircle.
  • AsyncCallback handles asynchronous map initialization events.
  • router retrieves page parameters.
  • geoLocationManager provides real-time location updates.

2. Component State & Variables

private mapOptions: mapCommon.MapOptions | null = null;
private callback?: AsyncCallback<map.MapComponentController>;
private mapController?: map.MapComponentController;
private mapEventManager?: map.MapEventManager;
@State currentLocation: geoLocationManager.Location | null = null;
private locationRequest: geoLocationManager.LocationRequest = {
  priority: geoLocationManager.LocationRequestPriority.ACCURACY,
  timeInterval: 0,
  distanceInterval: 0,
  maxAccuracy: 0
};
private locationChangeCallback?: (location: geoLocationManager.Location) => void;
private mapCircle?: map.MapCircle;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • mapOptions stores initial map parameters.
  • mapController and mapEventManager handle map operations and events.
  • currentLocation holds the device's current position.
  • locationRequest defines location update parameters.
  • mapCircle represents the circle drawn on the map.

3. Component Lifecycle: aboutToAppear

aboutToAppear(): void {
  const params = router.getParams() as mapCommon.MapOptions
  if(params) {
    this.mapOptions = params
  }

  this.locationChangeCallback = (location: geoLocationManager.Location) => {
    this.currentLocation = location;
    this.centerMapOnLocation(location);
    this.updateCirclePosition(location);
  };

  this.callback = async (err, mapController) => {
    if (!err) {
      this.mapController = mapController;
      this.mapEventManager = this.mapController.getEventManager();
      this.mapController.setMyLocationEnabled(true);
      this.mapController.setMyLocationControlsEnabled(true);

      let mapCircleOptions: mapCommon.MapCircleOptions = {
        center: {
          latitude: this.mapOptions!.position.target.latitude,
          longitude: this.mapOptions!.position.target.longitude
        },
        radius: 100,
        clickable: true,
        fillColor: 0xFFFFC100,
        strokeColor: 0xFFFF0000,
        strokeWidth: 10,
        visible: true,
        zIndex: 5
      }

      this.mapCircle = await this.mapController.addCircle(mapCircleOptions);
      this.startLocationUpdates();

      let callback = () => {
        if (this.currentLocation) {
          this.centerMapOnLocation(this.currentLocation);
        }
      }
      this.mapEventManager.on("mapLoad", callback);
    } else {
      console.error(`Failed to initialize the map. Code: ${err.code}; message: ${err.message}`);
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Before the component appears, map parameters are retrieved.
  • locationChangeCallback updates the camera and recreates the MapCircle whenever the device location changes.
  • Initial MapCircle is drawn and location updates are started.
  • This step is critical for MapCircle recreation and dynamic updates.

4. Center Map on Location

private centerMapOnLocation(location: geoLocationManager.Location) {
  if (this.mapController) {
    const cameraPosition: mapCommon.CameraPosition = {
      target: {
        latitude: location.latitude,
        longitude: location.longitude,
      },
      zoom: 15,
      tilt: 0,
      bearing: 0
    };

    let cameraUpdate = map.newCameraPosition(cameraPosition);
    this.mapController.animateCamera(cameraUpdate, 1000);
  }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Moves the map camera to focus on the device's current location.
  • Ensures the MapCircle remains visible in the correct area.

5. Recreation of MapCircle

private async updateCirclePosition(location: geoLocationManager.Location) {
  if(this.mapController) {
    if (this.mapCircle) {
      this.mapCircle.remove();
    }

    let mapCircleOptions: mapCommon.MapCircleOptions = {
      center: {
        latitude: location.latitude,
        longitude: location.longitude
      },
      radius: 100,
      clickable: true,
      fillColor: 0xFFFFC100,
      strokeColor: 0xFFFF0000,
      strokeWidth: 10,
      visible: true,
      zIndex: 15
    }
    this.mapCircle = await this.mapController.addCircle(mapCircleOptions);
  }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Critical part: This method recreates the MapCircle every time the device location changes.
  • The previous circle is removed to prevent overlapping or outdated visuals.
  • A new circle is drawn at the updated location, ensuring the map always reflects the current position accurately.

6. Start Location Updates

private startLocationUpdates() {
  try {
    geoLocationManager.on('locationChange', this.locationRequest, this.locationChangeCallback!);
  } catch (error) {
    console.error('Location updates error:', error);
  }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Subscribes to real-time location updates.
  • Ensures updateCirclePosition is called whenever the device moves.

7. Cleanup on Component Disappear

aboutToDisappear(): void {
  if (this.locationChangeCallback) {
    geoLocationManager.off('locationChange', this.locationChangeCallback);
  }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Unsubscribes from location updates when the component is closed.
  • Prevents memory leaks and unnecessary background location processing.

8. Build Map Component

build() {
  Stack() {
    MapComponent({ mapOptions: this.mapOptions!, mapCallback: this.callback }).width('100%').height('100%');
  }.height('100%')
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Renders the map component in a full-screen stack layout.

Test Results

Here you can find the related screenshots belong to demo below;

image.pngimage.png

Limitations or Considerations

  • The technologies and APIs used in this implementation only work on real HarmonyOS wearable devices. Emulators may not provide accurate location updates.
  • Frequent recreation of MapCircle can lead to performance overhead, especially if the location changes rapidly. Optimization may be needed to avoid UI lag.
  • The MapCircle relies on location accuracy provided by geoLocationManager. If the device location is inaccurate, the circle may not be positioned correctly.
  • Network conditions or GPS availability can affect real-time updates; offline or low-signal environments may result in delayed or skipped updates.
  • Customization of MapCircle (e.g., radius, colors, z-index) must be handled carefully to maintain visual consistency when recreating circles repeatedly.

Related Documents or Links

https://developer.huawei.com/consumer/en/doc/harmonyos-guides/map-kit-guide

https://developer.huawei.com/consumer/en/doc/harmonyos-guides/map-circle

https://developer.huawei.com/consumer/en/doc/harmonyos-guides/location-guidelines

Written by Mehmet Algul

Top comments (0)