DEV Community

Cover image for HarmonyOS NEXT Development Case: Latitude and Longitude Distance Calculation
zhongcx
zhongcx

Posted on

HarmonyOS NEXT Development Case: Latitude and Longitude Distance Calculation

Image description

The following example demonstrates how to implement a distance calculator between geographic coordinates using HarmonyOS NEXT's declarative development paradigm. This case leverages ArkUI components and the MapKit module to create a responsive interface with real-time distance calculation capabilities.

Key Features

  • Dual-column input for coordinates (longitude/latitude)
  • Real-time distance calculation using MapKit APIs
  • Example preset (Beijing to Shanghai)
  • Responsive UI with focus states
  • Data clearing functionality
  • Kilometer-based distance display

Code Implementation (with English Comments)

import { mapCommon } from '@kit.MapKit'; // Import map common module
import { map } from '@kit.MapKit'; // Import map module

@Entry // Entry decorator for application entry component
@Component // Component decorator
struct DistanceCalculator { // Distance calculator component structure
  @State private primaryColor: string = '#fea024'; // Primary theme color
  @State private fontColor: string = "#2e2e2e"; // Main text color
  @State private isStartFocused: boolean = false; // Start point input focus state
  @State private isEndFocused: boolean = false; // End point input focus state
  @State private isSecondStartFocused: boolean = false; // Second start point focus
  @State private isSecondEndFocused: boolean = false; // Second end point focus
  @State private baseSpacing: number = 30; // Base spacing unit
  @State @Watch('onInputChange') private startLongitude: string = ""; // Start longitude
  @State @Watch('onInputChange') private startLatitude: string = ""; // Start latitude
  @State @Watch('onInputChange') private endLongitude: string = ""; // End longitude
  @State @Watch('onInputChange') private endLatitude: string = ""; // End latitude
  @State distance: number = 0; // Calculated distance

  aboutToAppear(): void { // Component lifecycle hook
    this.onInputChange(); // Initial calculation
  }

  onInputChange() { // Input change handler
    let fromLatLng: mapCommon.LatLng = { // Start coordinate object
      latitude: Number(this.startLatitude),
      longitude: Number(this.startLongitude)
    };
    let toLatLng: mapCommon.LatLng = { // End coordinate object
      latitude: Number(this.endLatitude),
      longitude: Number(this.endLongitude)
    };
    this.distance = map.calculateDistance(fromLatLng, toLatLng); // Calculate distance
  }

  build() { // UI construction
    Column() { // Main vertical layout
      // Header section
      Text("Coordinate Distance Calculator")
        .width('100%')
        .height(54)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .backgroundColor(Color.White)
        .textAlign(TextAlign.Center)
        .fontColor(this.fontColor);

      // Input area
      Column() {
        Row() { // Example presets row
          Text('Example (Beijing->Shanghai)')
            .fontColor("#5871ce")
            .fontSize(18)
            .padding(`${this.baseSpacing / 2}lpx`)
            .backgroundColor("#f2f1fd")
            .borderRadius(5)
            .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 })
            .onClick(() => { // Preset coordinates
              this.startLongitude = "116.4074"; // Beijing longitude
              this.startLatitude = "39.9042"; // Beijing latitude
              this.endLongitude = "121.4737"; // Shanghai longitude
              this.endLatitude = "31.2304"; // Shanghai latitude
            });

          Blank(); // Spacer

          Text('Clear All')
            .fontColor("#e48742")
            .fontSize(18)
            .padding(`${this.baseSpacing / 2}lpx`)
            .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 })
            .backgroundColor("#ffefe6")
            .borderRadius(5)
            .onClick(() => { // Clear inputs
              this.startLongitude = "";
              this.startLatitude = "";
              this.endLongitude = "";
              this.endLatitude = "";
            });
        }.height(45)
        .justifyContent(FlexAlign.SpaceBetween)
        .width('100%');

        Divider().margin({ top: 5, bottom: 5 });

        // Start point inputs
        Row() {
          Text('Start Point')
            .fontWeight(FontWeight.Bold)
            .fontSize(18)
            .fontColor(this.fontColor);
        }.margin({ bottom: `${this.baseSpacing}lpx`, top: `${this.baseSpacing}lpx` });

        Row() { // Longitude/Latitude inputs
          TextInput({ text: $$this.startLongitude, placeholder: 'Longitude' })
            .caretColor(this.primaryColor)
            .layoutWeight(1)
            .type(InputType.NUMBER_DECIMAL)
            .placeholderColor(this.isStartFocused ? this.primaryColor : Color.Gray)
            .fontColor(this.isStartFocused ? this.primaryColor : this.fontColor)
            .borderColor(this.isStartFocused ? this.primaryColor : Color.Gray)
            .borderWidth(1)
            .borderRadius(10)
            .backgroundColor(Color.White)
            .showUnderline(false)
            .onBlur(() => this.isStartFocused = false)
            .onFocus(() => this.isStartFocused = true);

          Line().width(10);

          TextInput({ text: $$this.startLatitude, placeholder: 'Latitude' })
            .caretColor(this.primaryColor)
            .layoutWeight(1)
            .type(InputType.NUMBER_DECIMAL)
            .placeholderColor(this.isEndFocused ? this.primaryColor : Color.Gray)
            .fontColor(this.isEndFocused ? this.primaryColor : this.fontColor)
            .borderColor(this.isEndFocused ? this.primaryColor : Color.Gray)
            .borderWidth(1)
            .borderRadius(10)
            .backgroundColor(Color.White)
            .showUnderline(false)
            .onBlur(() => this.isEndFocused = false)
            .onFocus(() => this.isEndFocused = true);
        }

        // End point inputs
        Text('End Point')
          .fontWeight(FontWeight.Bold)
          .fontSize(18)
          .fontColor(this.fontColor)
          .margin({ bottom: `${this.baseSpacing}lpx`, top: `${this.baseSpacing}lpx` });

        Row() {
          TextInput({ text: $$this.endLongitude, placeholder: 'Longitude' })
            .caretColor(this.primaryColor)
            .layoutWeight(1)
            .type(InputType.NUMBER_DECIMAL)
            .placeholderColor(this.isSecondStartFocused ? this.primaryColor : Color.Gray)
            .fontColor(this.isSecondStartFocused ? this.primaryColor : this.fontColor)
            .borderColor(this.isSecondStartFocused ? this.primaryColor : Color.Gray)
            .borderWidth(1)
            .borderRadius(10)
            .backgroundColor(Color.White)
            .showUnderline(false)
            .onBlur(() => this.isSecondStartFocused = false)
            .onFocus(() => this.isSecondStartFocused = true);

          Line().width(10);

          TextInput({ text: $$this.endLatitude, placeholder: 'Latitude' })
            .caretColor(this.primaryColor)
            .layoutWeight(1)
            .type(InputType.NUMBER_DECIMAL)
            .placeholderColor(this.isSecondEndFocused ? this.primaryColor : Color.Gray)
            .fontColor(this.isSecondEndFocused ? this.primaryColor : this.fontColor)
            .borderColor(this.isSecondEndFocused ? this.primaryColor : Color.Gray)
            .borderWidth(1)
            .borderRadius(10)
            .backgroundColor(Color.White)
            .showUnderline(false)
            .onBlur(() => this.isSecondEndFocused = false)
            .onFocus(() => this.isSecondEndFocused = true);
        }
      }.width('650lpx')
      .padding(`${this.baseSpacing}lpx`)
      .margin({ top: 20 })
      .backgroundColor(Color.White)
      .borderRadius(10)
      .alignItems(HorizontalAlign.Start);

      // Result display
      Column() {
        Text() {
          Span(`Distance: `)
          Span(`${(this.distance / 1000).toFixed(2)} `).fontColor(this.primaryColor)
          Span(`kilometers`)
        }
        .fontWeight(FontWeight.Bold)
        .fontSize(18)
        .fontColor(this.fontColor);
      }.width('650lpx')
      .backgroundColor(Color.White)
      .borderRadius(10)
      .padding(`${this.baseSpacing}lpx`)
      .margin({ top: `${this.baseSpacing}lpx` })
      .alignItems(HorizontalAlign.Start);
    }
    .height('100%')
    .width('100%')
    .backgroundColor("#eff0f3");
  }
}
Enter fullscreen mode Exit fullscreen mode

Implementation Details

  1. State Management:
  2. Uses @State decorators for reactive UI updates
  3. @Watch decorator triggers recalculation on input changes
  4. Manages focus states for visual feedback

  5. Map Integration:

  6. Utilizes map.calculateDistance() from MapKit

  7. Converts string inputs to numeric coordinates

  8. Returns distance in meters (converted to kilometers)

  9. UI Features:

  10. Responsive layout using percentage-based widths

  11. Visual feedback for input focus states

  12. Clean material design-inspired aesthetics

  13. Adaptive color schemes

  14. Smooth animations for user interactions

  15. Validation:

  16. Input type restricted to decimal numbers

  17. Automatic handling of invalid inputs (returns 0 distance)

  18. Empty state management

This implementation demonstrates HarmonyOS NEXT's capabilities in creating sophisticated location-based applications with clean, maintainable code. The declarative UI approach combined with reactive programming patterns enables efficient development of complex interactive applications.

Top comments (0)