كيفية رسم خط للتبع الموقع على الخريطة في Flutter

كيفية رسم خط للتبع الموقع على الخريطة في Flutter

كيفية رسم خط للتبع الموقع على الخريطة في Flutter

في هذه المقالة سوف نشرح لكم كيف تقوم برسم خط على الخريطة بين موقعين وهذا يكون عن طريق api والذي سبق وتعرفنا عليه في المقالات السابقة وفي هذه المقالة سوف نكمل معكم التعامل مع الخريطة وهذا سيكون عن طريق اننا نقوم معكم برسم الخط بين موقعين من اجل تتبع الموقع وسوف نشرح لكم في المقالات السابقة كيفية عمل tracking على الخريطة في flutter بابسط طريقة ممكنه والامر ليس بالصعب ولا المستحيل فقط يمكنك ذلك من خلال المقالات التي نقدمها لكم لرسم الخط سوف تحتاج ان تقوم بتركيب المكتبة التي تساعدك في هذه العملية وايضا سوف تحتاج ان تقوم بتفعيل خدمة التتبع في ال google maps في الحساب الخاص بك .


Flutter عبارة عن حزمة SDK أمامية لأنها تجمع بين عناصر واجهة المستخدم ومنطق الأعمال. على الرغم من أن العديد من الأشخاص يدعون أن Dart يُستخدم للواجهة الخلفية ، إلا أن الحقيقة هي أن Flutter هي واجهة أمامية بالكامل وأن Dart تستخدم فقط لمنطق الواجهة الأمامية. لكن تطوير التطبيقات الأصلية لنظامي التشغيل iOS و Android هو "ببساطة" الواجهة الأمامية.

Dart و Java و C / C ++ ولغات البرمجة الأخرى ليست سوى عدد قليل من تلك التي يمكن استخدامها لإنشاء تطبيقات الواجهة الأمامية والخلفية باستخدام Flutter ، وهو SDK عبر الأنظمة الأساسية.

من ناحية أخرى ، Flutter ليست لغة برمجة. إنها مجموعة تطوير برمجيات (SDK) تتضمن كودًا مكتوبًا مسبقًا ، وعناصر واجهة مستخدم جاهزة للاستخدام يمكن تخصيصها ، بالإضافة إلى مكتبات وأدوات ووثائق يمكن استخدامها جميعًا لإنشاء تطبيقات عبر الأنظمة الأساسية. 26 مارس 2021


تعد خرائط جوجل من أكثر خدمات الخرائط استخدامًا في العالم، حيث تقدم مجموعة واسعة من الميزات والأدوات التي تجعل من السهل على المستخدمين التنقل والتعرف على المناطق المحيطة بهم. يمكن استخدام خرائط جوجل أيضًا لإنشاء تطبيقات ذكية تساعد المستخدمين على الوصول إلى المعلومات والمحتوى بشكل أكثر سهولة.


برمجة جوجل:

تقدم جوجل مجموعة واسعة من الأدوات والتقنيات التي تساعد المطورين على إنشاء تطبيقات ذكية، بما في ذلك خرائط جوجل. يمكن استخدام خرائط جوجل لإنشاء تطبيقات متنوعة، مثل تطبيقات التنقل والتوصيل وتطبيقات العلامات التجارية والإعلانات.


برمجة تطبيقات الأجهزة الذكية:

تتطلب برمجة تطبيقات الأجهزة الذكية معرفة بإحدى لغات البرمجة الشائعة، مثل جافا سكريبت أو دارت. كما تتطلب معرفة بتقنيات وأدوات تطوير التطبيقات، مثل فلاتر.


إنشاء تطبيقات الاندرويد:

يمكن استخدام فلاتر لإنشاء تطبيقات أندرويد وiOS. فلاتر هي تقنية تطوير تطبيقات ذكية مفتوحة المصدر من جوجل، تعتمد على لغة البرمجة دارت.


برمجة تطبيقات الاجهزة الذكية:

تتضمن برمجة تطبيقات الأجهزة الذكية مجموعة من الخطوات، مثل:


  • تصميم التطبيق: يتضمن هذه الخطوة تحديد وظائف التطبيق وتصميم واجهة المستخدم.
  • تطوير التطبيق: يتضمن هذه الخطوة كتابة الكود وتنفيذه.
  • اختبار التطبيق: يتضمن هذه الخطوة اختبار التطبيق للتأكد من سلامة عمله.
  • نشر التطبيق: يتضمن هذه الخطوة نشر التطبيق على متجر التطبيقات.


برمجة تطبيق اندرويد بسيط:

يمكن إنشاء تطبيق اندرويد بسيط باستخدام فلاتر من خلال الخطوات التالية:

  1. إنشاء مشروع جديد باستخدام فلاتر.
  2. إضافة مكتبة خرائط جوجل إلى المشروع.
  3. إنشاء الخريطة وإضافة المواقع إليها.
  4. تشغيل التطبيق على جهاز أندرويد.


يعد التعامل مع خرائط جوجل باستخدام فلاتر طريقة رائعة لإنشاء تطبيقات ذكية تفاعلية. فلاتر هي تقنية قوية وسهلة الاستخدام، يمكن استخدامها لإنشاء تطبيقات متنوعة للأجهزة الذكية.


الميزات والأدوات المتوفرة في خرائط جوجل:

  • التنقل
  • البحث
  • عرض التضاريس
  • عرض الصور
  • عرض الاتجاهات
  • عرض المسافات
  • عرض الوقت المقدر للوصول
  • خطوات استخدام خرائط جوجل في فلاتر:


أمثلة لتطبيقات اندرويد التي تستخدم خرائط جوجل:

  • تطبيقات التنقل
  • تطبيقات التوصيل
  • تطبيقات العلامات التجارية والإعلانات
  • تطبيقات السياحة والسفر
  • تطبيقات التعليم
  • تطبيقات الألعاب


نصائح لبرمجة تطبيقات اندرويد باستخدام فلاتر:

  • استخدام مكتبة خرائط جوجل الرسمية.
  • اختيار لغة البرمجة المناسبة للمشروع.
  • تصميم واجهة مستخدم جذابة وسهلة الاستخدام.
  • اختبار التطبيق بعناية قبل نشره.



add package

   flutter_polyline_points: ^1.0.0


كيفية تتبع موقع المستخدم والموقع المراد الانتقال اليه عن طريق رسم خط بين الموقعين

يمكنك في البداية وضع الاكواد التاليه في المكان المخصص لها حتى تتمكن من عملية تتبع الموقع وهذا عباره عن api وملف json نحصل فيه على احداثيات الموقع والتي تصلنا من الbackend عن صيغة شفره معينه وهذه المكتبة تعمل على فك الشفره ورسم الخريطة والخط بين الموقعين وحساب المده الزمنيه ايضا والمسافه بين الموقعين .


كيفية تتبع موقع المستخدم والموقع المراد الانتقال اليه عن طريق رسم خط بين الموقعين

codes.dart


// --------------------------- Json File

import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

class PlaceDirections {
  late LatLngBounds bounds;
  late List<PointLatLng> polylinePoints;
  late String totalDistance;
  late String totalDuration;

  PlaceDirections({
    required this.bounds,
    required this.polylinePoints,
    required this.totalDistance,
    required this.totalDuration,
  });

  factory PlaceDirections.fromJson(Map<String, dynamic> json) {
    final data = Map<String, dynamic>.from(json['routes'][0]);

    final northeast = data['bounds']['northeast'];
    final southwest = data['bounds']['southwest'];
    final bounds = LatLngBounds(
      northeast: LatLng(northeast['lat'], northeast['lng']),
      southwest: LatLng(southwest['lat'], southwest['lng']),
    );

    late String distance;
    late String duration;

    if ((data['legs'] as List).isNotEmpty) {
      final leg = data['legs'][0];
      distance = leg['distance']['text'];
      duration = leg['duration']['text'];
    }

    return PlaceDirections(
      bounds: bounds,
      polylinePoints:
      PolylinePoints().decodePolyline(data['overview_polyline']['points']),
      totalDistance: distance,
      totalDuration: duration,
    );
  }
}
// --------------------------- Webservices

  Future<dynamic> getDirections(LatLng origin, LatLng destination) async {
    try {
      Response response = await dio.get(
        directionsBaseUrl,
        queryParameters: {
          'origin': '${origin.latitude},${origin.longitude}',
          'destination': '${destination.latitude},${destination.longitude}',
          'key': googleAPIKey,
        },
      );
      print("Omar I'm testing directions");
      print(response.data);
      return response.data;
    } catch (error) {
      return Future.error("Place location error : ",
          StackTrace.fromString(('this is its trace')));
    }
  }
// --------------------------- MapsRepository

  // حساب المسافه بين موقعين
  Future<PlaceDirections> getDirections(
      LatLng origin, LatLng destination) async {
    final directions =
    await placesWebservices.getDirections(origin, destination);

    return PlaceDirections.fromJson(directions);
  }
// --------------------------- MapsCubit

  void emitPlaceDirections(LatLng origin, LatLng destination) {
    mapsRepository.getDirections(origin, destination).then((directions) {
      emit(DirectionsLoaded(directions));
    });
  }
  

عرض الخريطة مع تصميم خط بين الموقعين موقع المستخدم والموقع المراد الانتقال اليه مع حساب المده الزمنيه والمسافه

عرض الخريطة مع تصميم خط بين الموقعين موقع المستخدم والموقع المراد الانتقال اليه مع حساب المده الزمنيه والمسافه

فقط في هذه المرحلة قمنا بتنفيذ التصميم ال ui على الخريطة وكل خطوة قمنا بها تم كتابة كومنت فوقها من اجل توضيح الفكرة التي تقوم بها .حتى تسهل عليكم تنفيذ العملية بابسط طريقة ممكن وتسهل عملية الفهم لديكم .


android sdk manager تحميل flutter developers applications create app android android studio mac


ui.dart


class MainMaps extends StatefulWidget {
  const MainMaps({Key? key}) : super(key: key);

  @override
  State<MainMaps> createState() => _MainMapsState();
}

class _MainMapsState extends State<MainMaps> {
  // التعرف على موقع المستخدم
  static Position? position;
  // الانتقال المباشر الى موقع المستخدم بمجرد فتح التطبيق
  final Completer<GoogleMapController> _controllerMaps = Completer();
  // المتحكم في عمليات البحث داخل التطبيق
  FloatingSearchBarController controller = FloatingSearchBarController();
  // اضافة علامه على الخريطة للموقع الذي تم البحث والانتقال اليه
  late Marker searchedPlaceMarker;
  // اضافة علامه على الخريطة لموقعك
  late Marker currentLocationMarker;
  // هل يتم عرض العناصر على الخريطة
  var progressIndicator = false;
  // هل تم اضافة الوقت والمسافه الى الخريطة
  var isTimeAndDistanceVisible = false;
  // التاكد هل تم النقر على الخريطة ام لا
  var isSearchedPlaceMarkerClicked = false;
  // العناصر التي تظهر بعد البحث عليها
  List<PlaceSuggestion> places = [];
  // اضافة علامه على الخريطة ومن نوع set لكي لا تتكرر
  Set<Marker> markers = {};
  // قيمة المكان الذي تم النقر عليه
  late Place selectedPlace;
  // تحريك الكاميرا الى الموقع الذي تم النقر عليه بعد البحث
  late CameraPosition goToSearchedForPlace;
  // معلوماات عن الموقع الذي تم البحث عنه
  late PlaceSuggestion placeSuggestion;
  late List<LatLng> polylinePoints;
  PlaceDirections? placeDirections;

  // موقع المستخدم الحالي على الخريطة
  static final CameraPosition _myCurrentLocationNow = CameraPosition(
    //عرض موقع المستخدم على الخريطة مع بعض الخصائص مثل نسبة التقريب
    // نسبة جعل الكاميرا مائله
    bearing: 0.0,
    // موقع المستخدم
    target: LatLng(position!.latitude, position!.longitude),
    tilt: 0.0,
    // نسبة التقريب
    zoom: 18,
  );

  // تحريك الكاميرا الى موقع المستخدم
  Future<void> getMyLocationNow() async {
    // الحصول على موقع المستخدم بمجرد فتح الخريطة وتحديث الصفحة بالموقع الحالي
    position = await LocationHelper.getCurrentLocation().whenComplete(() => setState((){}));
  }

  // تحريك الكاميرا الى الموقع الذي تم الحصول على متغيراته
  Future<void> _goToMyLocationNow() async {
    // الانتقال الى موقعي بمجرد تنفيذ هذه الوظيفة
    final GoogleMapController controller = await _controllerMaps.future;
    // الانتقال الى موقع المستخدم الموجود في CameraPosition مع انميشن بسيط لحركة الكاميرا
    controller.animateCamera(CameraUpdate.newCameraPosition(_myCurrentLocationNow));
  }

  // الانتقال الى الموقع الذي تم البحث عنده بعد النقر عليه
  void buildCameraNewPosition() {
    goToSearchedForPlace = CameraPosition(
      bearing: 0.0,
      tilt: 0.0,
      //
      target: LatLng(
        selectedPlace.result.geometry.location.lat,
        selectedPlace.result.geometry.location.lng,
      ),
      zoom: 13,
    );
  }

  //  الانتقال الى الموقع الذي تم البحث عنه و اضافة علامه على الخريطو
  Future<void> goToMySearchedForLocation() async {
    // بناء كاميرا جديده للتحريك
    buildCameraNewPosition();
    // تحريك الكاميرا
    final GoogleMapController controller = await _controllerMaps.future;
    controller
        .animateCamera(CameraUpdate.newCameraPosition(goToSearchedForPlace));
    // اضافة علامه على الخريطة
    buildSearchedPlaceMarker();
  }

  // اضافة علامه على الخريطة لموقع المستخدم
  void buildCurrentLocationMarker() {
    currentLocationMarker = Marker(
      position: LatLng(position!.latitude, position!.longitude),
      markerId: MarkerId('2'),
      onTap: () {},
      infoWindow: InfoWindow(title: "Your current Location"),
      icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed),
    );
    addMarkerToMarkersAndUpdateUI(currentLocationMarker);
  }

  // اضافة علامه على الخريطة للموقع الذي تم البحث عنه
  void buildSearchedPlaceMarker() {
    searchedPlaceMarker = Marker(
      position: goToSearchedForPlace.target,
      markerId: MarkerId('1'),
      onTap: () {
        buildCurrentLocationMarker();
        // show time and distance
        setState(() {
          isSearchedPlaceMarkerClicked = true;
          isTimeAndDistanceVisible = true;
        });
      },
      infoWindow: InfoWindow(title: "${placeSuggestion.description}"),
      icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed),
    );

    addMarkerToMarkersAndUpdateUI(searchedPlaceMarker);
  }

  // اضافة علامة داخل ال list of Marker على الخريطة
  void addMarkerToMarkersAndUpdateUI(Marker marker) {
    setState(() {
      markers.add(marker);
    });
  }

  // البحث عن مجموعه من المدن في الخريطة في شريط البحث
  void getPlacesSuggestions(String query) {
    final sessionToken = Uuid().v4();
    BlocProvider.of<MapsCubit>(context)
        .emitPlaceSuggestions(query, sessionToken);
  }

  // ازالة جميع العلامات على الخريطة
  void removeAllMarkersAndUpdateUI() {
    setState(() {
      markers.clear();
    });
  }

  // عرض تفاصيل الموقع الذي تم النقر عليه بعد البحث
  void getSelectedPlaceLocation() {
    final sessionToken = Uuid().v4();
    BlocProvider.of<MapsCubit>(context)
        .emitPlaceLocation(placeSuggestion.placeId, sessionToken);
  }

  // حساب المسافه بين موقعك والموقع الذي تم الانتقال اليه
  void getDirections() {
    BlocProvider.of<MapsCubit>(context).emitPlaceDirections(
      LatLng(position!.latitude, position!.longitude),
      LatLng(selectedPlace.result.geometry.location.lat,
          selectedPlace.result.geometry.location.lng),
    );
  }

  // اضافة الوقت والمسافه الى الشاشة
  void getPolylinePoints() {
    polylinePoints = placeDirections!.polylinePoints
        .map((e) => LatLng(e.latitude, e.longitude))
        .toList();
  }

  @override
  void initState() {
    super.initState();
    // عرض بيانات الموقع الحالي للمستخدم عند تشغيل التطبيق
    getMyLocationNow();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        fit: StackFit.expand,
        children: [
          position == null ? const Center(child: CircularProgressIndicator()) :
              // عرض الخريطة
          buildMaps(),
          // اضافة شريط للبحث في الاعلى
          buildFloatingSearchBar(),
          // اضافة زر للانتقال الى الموقع الحالي للمستخدم
          isSearchedPlaceMarkerClicked
          // عرض المسافة والوقت اذا كانت القيمة صحيحه
              ? DistanceAndTime(
            isTimeAndDistanceVisible: isTimeAndDistanceVisible,
            placeDirections: placeDirections,
          )
              : Container(),
        ],
      ),
      // بناء شريط جانبي
      drawer: MyDrawer(),
      floatingActionButton: FloatingActionButton(
        onPressed: _goToMyLocationNow,
        child: const Icon(Icons.location_searching),
      ),
    );
  }

  // الخريطة
  Widget buildMaps(){
    return GoogleMap(
      // نوع الخريطة
      mapType: MapType.normal,
      // وجود علامة زرقاء لاظهار موقعك الحالي على الخريطة
      myLocationEnabled: true,
      // اضافة زر في اعلى الخريطة للانتقال المباشر الى موقعك الحالي
      myLocationButtonEnabled: false,
      // وجود علامة التكبير والتصغير للخريطة في اسفل الخريطة
      zoomControlsEnabled: false,
      // الحصول على موقع المستخدم بمجرد فتح الخريطة وتحديث الصفحة بالموقع الحالي
      initialCameraPosition: _myCurrentLocationNow,

      onMapCreated: (GoogleMapController controller){
        _controllerMaps.complete(controller);
      },

    );
  }

  // البحث
  Widget buildFloatingSearchBar() {
    final isPortrait =
        MediaQuery.of(context).orientation == Orientation.portrait;

    return FloatingSearchBar(
      controller: controller,
      elevation: 6,
      hintStyle: TextStyle(fontSize: 18),
      queryStyle: TextStyle(fontSize: 18),
      hint: 'Find a place..',
      border: BorderSide(style: BorderStyle.none),
      margins: EdgeInsets.fromLTRB(20, 70, 20, 0),
      padding: EdgeInsets.fromLTRB(2, 0, 2, 0),
      height: 52,
      iconColor: Colors.blue[200],
      scrollPadding: const EdgeInsets.only(top: 16, bottom: 56),
      transitionDuration: const Duration(milliseconds: 600),
      transitionCurve: Curves.easeInOut,
      physics: const BouncingScrollPhysics(),
      axisAlignment: isPortrait ? 0.0 : -1.0,
      openAxisAlignment: 0.0,
      width: isPortrait ? 600 : 500,
      debounceDelay: const Duration(milliseconds: 500),
      progress: progressIndicator,
      onQueryChanged: (query) {
        getPlacesSuggestions(query);
      },
      onFocusChanged: (_) {
        // hide distance and time row
        setState(() {
          isTimeAndDistanceVisible = false;
        });
      },
      transition: CircularFloatingSearchBarTransition(),
      actions: [
        FloatingSearchBarAction(
          showIfOpened: false,
          child: CircularButton(
              icon: Icon(Icons.place, color: Colors.black.withOpacity(0.6)),
              onPressed: () {}),
        ),
      ],
      builder: (context, transition) {
        return ClipRRect(
          borderRadius: BorderRadius.circular(8),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: [
              buildSuggestionsBloc(),
              buildSelectedPlaceLocationBloc(),
              buildDirectionsBloc(),
            ],
          ),
        );
      },
    );
  }

  //  حساب المسافة والوقت بين المنطقتين
  Widget buildDirectionsBloc() {
    return BlocListener<MapsCubit, MapsState>(
      listener: (context, state) {
        if (state is DirectionsLoaded) {
          placeDirections = (state).placeDirections;

          getPolylinePoints();
        }
      },
      child: Container(),
    );
  }

  // عندما يتم النقر على العنصر بعد البحث
  Widget buildSelectedPlaceLocationBloc() {
    return BlocListener<MapsCubit, MapsState>(
      listener: (context, state) {
        if (state is PlaceLocationLoaded) {
          selectedPlace = (state).place;
          // يتم الانتقال اليه
          goToMySearchedForLocation();
          // يتم حساب المسافه بين الطرفين
          getDirections();
        }
      },
      child: Container(),
    );
  }

  // عندما يتم النقر على عنصر بعد البحث
  Widget buildSuggestionsBloc() {
    return BlocBuilder<MapsCubit, MapsState>(
      builder: (context, state) {
        if (state is PlacesLoaded) {
          places = (state).places;
          if (places.length != 0) {
            return buildPlacesList();
          } else {
            return Container();
          }
        } else {
          return Container();
        }
      },
    );
  }

  // عرض قائمه بجميع العناصر التي تم البحث عنها
  Widget buildPlacesList() {
    return ListView.builder(
        itemBuilder: (ctx, index) {
          return InkWell(
            onTap: () async {
              placeSuggestion = places[index];
              controller.close();
              getSelectedPlaceLocation();
              polylinePoints.clear();
              removeAllMarkersAndUpdateUI();
            },
            child: PlaceItem(
              suggestion: places[index],
            ),
          );
        },
        itemCount: places.length,
        shrinkWrap: true,
        physics: const ClampingScrollPhysics());
  }

}

تعليقات