Zum Inhalt springen

Implementing the Map-based House Search Effect in HarmonyOS Next

The commonly used map-based house search function involves adding custom markers for regions, business districts, properties, etc., on the map, combined with the filtering logic of the application. Here is a simple implementation of adding region/business district and property markers using HarmonyOS ArkUI.

map find house

1. Enable Map Service

Register the application on the Huawei Developer Website, then enable the Map Kit service in My Projects > My Apps > API Management.

2. Map-Related Configuration

Configure the client_id in the module.json5 file of the entry module. The client_id can be found in Project Settings > General > App. You may need to configure the public key fingerprint; refer to the developer website for details.

"module": {
  "name": "xxxx",
  "type": "entry",
  "description": "xxxx",
  "mainElement": "xxxx",
  "deviceTypes": [
    "phone",
    "tablet"
  ],
  "pages": "xxxx",
  "abilities": [],
  "metadata": [
    {
      "name": "client_id",
      "value": "xxxxxx"  // Configure with the obtained Client ID
    }
  ]
}

3. Create a Map

Use the MapComponent to create a map, which requires two parameters: mapOptions and mapCallback. Override the lifecycle methods onPageShow and onPageHide to display and hide the map. If the map does not display, refer to the official website for troubleshooting.

MapComponent(mapOptions: mapCommon.MapOptions, mapCallback: AsyncCallback<map.MapComponentController>)

// Map initialization parameters: set the map center coordinates and zoom level
this.mapOptions = {
  position: {
    target: {
      latitude: 39.9,
      longitude: 116.4
    },
    zoom: 10
  }
};

// Map initialization callback
this.callback = async (err, mapController) => {
  if (!err) {
    // Get the map controller to operate the map
    this.mapController = mapController;
    this.mapEventManager = this.mapController.getEventManager();
    const callback = () => {
      console.info(this.TAG, `on-mapLoad`);
    }
    this.mapEventManager.on("mapLoad", callback);
  }
};

// Triggered each time the page is displayed (including routing and app foreground scenarios), only valid for @Entry-decorated custom components
onPageShow(): void {
  // Bring the map to the foreground
  if (this.mapController) {
    this.mapController.show();
  }
}

// Triggered each time the page is hidden (including routing and app background scenarios), only valid for @Entry-decorated custom components
onPageHide(): void {
  // Send the map to the background
  if (this.mapController) {
    this.mapController.hide();
  }
}

build() {
  Stack() {
    // Initialize the map by calling MapComponent
    MapComponent({ mapOptions: this.mapOptions, mapCallback: this.callback }).width('100%').height('100%');
  }.height('100%')
}

4. Display My Location Icon on the Map

First, dynamically request location permissions. After obtaining the specific location coordinates, use the map operation class mapController to call setMyLocation to set the location coordinates and animateCamera to move the map to the location (with a configurable zoom level) to display a location icon.

static LocationPermissions: Array<Permissions> =
  ['ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION'];

// Start location positioning
startLocation() {
  // Request location permissions
  AgentUtil.requestPermissions(AgentUtil.LocationPermissions, async () => {
    // Location permission granted
    // Enable the my-location layer (mapController is obtained as described in the "Display Map" section)
    this.mapController?.setMyLocationEnabled(true);
    // Enable the my-location button
    this.mapController?.setMyLocationControlsEnabled(true);

    // Get the user's location coordinates
    const location = await geoLocationManager.getCurrentLocation();
    // Convert coordinate system (if needed)
    // const wgs84LatLng = AgentUtil.wgs84LatLng(location.latitude, location.longitude);
    // location.latitude = wgs84LatLng.latitude;
    // location.longitude = wgs84LatLng.longitude;
    // Set the user's location
    this.mapController?.setMyLocation(location);

    setTimeout(() => {
      // Move the map to the target location
      this.mapController?.animateCamera(map.newLatLng(location, 15));
    }, 100);
  });
}

5. Listen for Map Movement Stop

After the map moves to the location coordinates, use mapController.on("cameraIdle", () => {}) to listen for when the map stops moving. In the callback, implement the logic to display markers based on the map zoom level (e.g., display regions/districts at lower zooms and properties at higher zooms), and further refine business logic such as business district layers.

this.mapEventManager.on("cameraIdle", () => {
  // The map has stopped moving; perform operations
  const position = this.mapController?.getCameraPosition();
  const currentZoom = position?.zoom ?? 0;
  if (currentZoom < 14) {
    // Display region/business district markers
    this.addAreaMarkers();
  } else {
    // Display property markers
    this.addHouseMarkers();
  }
});

6. Customize Markers

Custom Circular Marker

Used to display regions and business districts. Since Map Kit does not support custom styles for markers, only icon settings, use a @Builder custom component to draw a circular marker:

@Builder
BuilderMapCircularLabel(text: string, id: string) {
  Stack() {
    // Draw a circular background
    Circle({ width: 70, height: 70 })
      .shadow({ radius: 8 })
      .fill($r("app.color.main_color"));
    Text(text)
      .fontSize(12)
      .fontColor($r("app.color.white"))
      .textAlign(TextAlign.Center)
      .padding({ left: 4, right: 4 });
  }.id(id);
}

Custom Property Display Marker

Draw a small triangle at the bottom using a Path path.

/**
 * Rectangular marker with a small triangle indicator
 * @param text
 */
@Builder
BuilderMapRectLabel(text: string, id: string) {
  Column() {
    Text(text)
      .fontSize(14)
      .fontColor($r("app.color.white"))
      .backgroundColor($r("app.color.main_color"))
      .shadow({ radius: 5, color: $r("app.color.white") })
      .borderRadius(14)
      .padding({ left: 15, right: 15, top: 4, bottom: 4 });
    // Draw a small triangle using path commands
    Path()
      .width(12)
      .height(6)
      .commands('M0 0 L30 0 L15 15 Z')
      .fill($r("app.color.main_color"))
      .strokeWidth(0);
  }.id(id).alignItems(HorizontalAlign.Center);
}

7. Display Custom Markers on the Map

Use the screenshot class componentSnapshot to generate a snapshot of the custom component via createFromBuilder. In the callback, obtain the PixelMap object, set it as the icon in MarkerOptions, and call addMarker to generate the marker on the map.

componentSnapshot.createFromBuilder(() => {
  // The custom component to generate a PixelMap image
  this.BuilderMapRectLabel(text, houseId);
}, async (error, imagePixelMap) => {
  if (error) {
    LogUtil.debug("snapshot failure: " + JSON.stringify(error));
    return;
  }

  const markerOptions: mapCommon.MarkerOptions = {
    position: latLng,
    icon: imagePixelMap, // Custom component converted to PixelMap
    title: houseId,       // Pass custom parameters to bind with the component for click operations
  };
  if (!this.rectMarkers.find(marker => marker.getTitle() === houseId)) {
    // Add the marker if it doesn't exist in the marker collection
    const marker = await this.mapController?.addMarker(markerOptions);
    marker?.setClickable(true);
    if (marker) {
      this.rectMarkers.push(marker);
    }
  }
});

8. Listen for Marker Click Events

Use mapEventManager.on("markerClick", () => {}) to listen for marker clicks. In the callback, handle business logic based on the map level (e.g., navigate to the property layer when a region/business district marker is clicked).

this.mapEventManager.on("markerClick", (marker) => {
  if (marker.getTitle().length === 2) {
    // Clicked a region/business district marker: switch to the property layer and display property information
    this.mapController?.animateCamera(map.newLatLng({
      latitude: 30.513746,
      longitude: 114.403009
    }, 15));
  } else {
    // Clicked a property marker: perform subsequent operations
    ToastUtil.showToast(marker?.getTitle());
  }
});

Summary

The above is the basic process for implementing map-based house search with HarmonyOS ArkUI. Details can be customized according to business needs. Currently, custom markers are displayed by generating images from custom components, which may affect performance with a large number of markers. Optimization can be done by displaying only markers within the current screen, removing those outside the screen when scrolling, and reusing generated PixelMap objects to avoid repeated creation.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert