بحث MapKit: نصائح وحيل


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


في هذا المنشور ، سأخبر قراء هبر عن ميزات البحث في MapKit وأشارك التوصيات والحيل التي قد تكون مفيدة لك.


TL ؛ DR إذا كنت لا تريد قراءة المقالة بأكملها ، فإليك نقطتان من أكثر النقاط فائدة كتعويض لقراءة المقدمة:


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

ستكون الروابط إلى الوثائق في النص لنظام Android ، ويتم استدعاء الفئات والأساليب لنظام iOS بنفس الطريقة.


ما يمكن البحث


بادئ ذي بدء ، دعنا نتحدث عما يمكن أن يفعله البحث في MapKit. يمكن أن يفعل البحث ما تتوقعه من تطبيق خريطة عندما تريد العثور على شيء هناك.



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


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


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


كيف يتم ترتيب الطلب


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


قليلا عن الأمثلة

ستكون الأمثلة في المقالة على Kotlin لتسهيل العمل مع القيم الاختيارية وكود أقل من الورق المقوى. MapKit لديه تطبيق تجريبي . يمكن استخدامه لاختبار الأمثلة ، ولكن لهذا ، يجب تحويل SearchActivity من Java إلى Kotlin. showMessage ، التي تظهر من وقت لآخر في التعليمات البرمجية ، هي أي طريقة مناسبة لك لعرض سطر من النص على الشاشة أو في السجل.


 // `searchManager`  `searchSession` –  .    //    ,     . searchManager = SearchFactory.getInstance().createSearchManager( SearchManagerType.ONLINE ) val point = Geometry.fromPoint(Point(59.95, 30.32)) searchSession = searchManager!!.submit("", point, SearchOptions(), object: Session.SearchListener { override fun onSearchError(p0: Error) { showMessage("Error") } override fun onSearchResponse(p0: Response) { showMessage("Success") } } ) 

فور submit الاتصال submit سيعود التحكم إلى الرمز الخاص بك ، وعندما يتلقى MapKit استجابة من الخادم ، سيتم استدعاء SearchListener.


تتيح لك جلسة البحث:


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

يتم إلغاء جلسة عند الإلغاء تلقائيًا. هذا يعني أنه إذا لم يتم حفظه في جانب رمز العميل ، فلن يعمل البحث.
لا تنس حفظ الجلسة ، فالجلسة هي صديقك!


خيارات البحث


توجد طريقة عامة لتكوين استعلامات البحث من خلال فئة SearchOptions ، والتي تسمح لك بتعديل معلمات الاستعلام.


  • الرئيسي لهذه المعلمات هو SearchType . يسمح لك بتحديد ما إذا كنت ترغب في رؤية الأسماء الجغرافية أو المنظمات أو وسائل النقل في الإجابة (على الأرجح لن تحتاج إلى أنواع أخرى).
  • معلمة استعلام هامة أخرى هي القصاصات . سنتحدث عنها بمزيد من التفصيل في القسم الخاص بجهاز الاستجابة.
  • إذا كنت ترغب في الحصول على الشكل الهندسي للأسماء الجغرافية (على سبيل المثال ، الشوارع أو المناطق) ، فأنت بحاجة إلى طلبها من خلال setGeometry(true) . ضع في اعتبارك أن الشكل الهندسي "ثقيل" من حيث البيانات المرسلة.
  • بشكل افتراضي ، لا setSearchClosed(true) البحث المؤسسات المغلقة (مؤقتًا أو بشكل دائم) ، ولكن إذا كنت بحاجة إليها ، فأنت بحاجة إلى تعيين setSearchClosed(true) .

بالإضافة إلى المعلمات المدرجة ، هناك بعض الأشياء الأخرى التي قد تكون مفيدة لك ، ويمكن العثور عليها في وثائق الفصل. لاحظ أنه ليست كل الاستعلامات تدعم جميع مجموعات المعلمات. تشير وثائق كل SearchManager أو أسلوب Session إلى المعلمات من SearchOptions التي يفهمها.


كيف يتم ترتيب الجواب


استنادًا إلى الأسئلة الموجودة في الدعم ، يتم الخلط بين معظم المستخدمين بتنسيق استجابة البحث. إذا نظرت إلى فئة الإجابة ، فستبدو بسيطة للغاية (على الأقل ، الجزء المثير للاهتمام بالنسبة لنا):


 public class Response { public synchronized SearchMetadata getMetadata(); public synchronized GeoObjectCollection getCollection(); // ... } 

هنا getCollection() تُرجع الكائنات في الاستجابة ، و getMetadata() هي بعض البيانات الإضافية التي تحتوي ، على سبيل المثال ، على معلومات حول نافذة الاستجابة ، ونوع الترتيب وعدد النتائج التي تم العثور عليها . إذا نظرت داخل GeoObjectCollection يمكنك أن ترى أنه يحتوي على بعض Item التي يمكن أن تكون إما GeoObjectCollection أو GeoObjectCollection أخرى.


لا توجد مجموعات داخل المجموعات في البحث (على الأقل ليس بعد) ، لذلك دعونا نلقي نظرة على GeoObject . يوجد داخل الكائن اسم ( getName() ) ووصف ( getDescriptionText() ) وإطار ( getBoundingBox() ) ومجموعة من الأشكال الهندسية ( getGeometry() ) وبعض الطرق الأخرى غير الواضحة جدًا. أين أرقام هواتف المنظمة؟ كيفية فهم المدينة التي يشير إليها الاسم المختصر؟


وفقا لأساليب الكائن ، هذا ليس واضحا للغاية.


Geoobject


حان الوقت للحديث أكثر عن GeoObject .


GeoObject هو كائن "بطاقة" أساسي. يمكن أن يكون داخله حدث طريق أو كائن منفصل عن نتيجة البحث أو مناورة في الطريق أو كائن على الخريطة (POI) ، مثل نصب تذكاري أو بعض المنظمات البارزة.



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



يمكن تقسيم البيانات الوصفية إلى عدة أنواع. النوع الأول هو بيانات التعريف التي تحدد نوع الكائن الذي ينتمي إليه: أسماء المواقع الجغرافية ( ToponymObjectMetadata ) أو المؤسسات ( BusinessObjectMetadata ) أو النقل ( TransitObjectMetadata ). في البيانات الوصفية للأسماء الجغرافية ، يمكنك العثور على عنوان منظم وهندسة تفصيلية. البيانات الوصفية للمؤسسة هي ساعات العمل أو موقع الشركة. يتم تحديد البيانات الوصفية هذه حسب نوع البحث في الطلب - إذا كنت قد بحثت فقط عن أسماء المواقع الجغرافية ، فيجب أن يكون لكل كائن في الاستجابة بيانات وصفية مقابلة. إذا كنت تبحث عن أسماء الأماكن أو المنظمات ، فسيكون لكل كائن واحد على الأقل من "البيانات الوصفية".


إليك كيفية العثور على أرقام هواتف الشركة:


 val phones = response.collection.children.firstOrNull()?.obj ?.metadataContainer ?.getItem(BusinessObjectMetadata::class.java) ?.phones 

وإليك كيفية العثور على المدينة في عنوان منظم:


 val city = response.collection.children.firstOrNull()?.obj ?.metadataContainer ?.getItem(ToponymObjectMetadata::class.java) ?.address ?.components ?.firstOrNull { it.kinds.contains(Address.Component.Kind.LOCALITY) } ?.name 

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


 //       «»,     //     val uri = response.collection.children.firstOrNull()?.obj ?.metadataContainer ?.getItem(UriObjectMetadata::class.java) ?.uris ?.firstOrNull() ?.value 

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


 val point = Geometry.fromPoint(Point(59.95, 30.32)) val options = SearchOptions() options.snippets = Snippet.FUEL.value searchSession = searchManager!!.submit("", point, options, this) ... override fun onSearchResponse(response: Response) { //         showMessage(response.collection.children.firstOrNull()?.obj ?.metadataContainer ?.getItem(FuelMetadata::class.java) ?.fuels ?.joinToString("\n") { "Fuel(name=${it.name}, price=${it.price?.text})" } ?: "No fuel" ) } 

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


 //            response.metadata.businessResultMetadata?.categories //     (  )     response.metadata.toponymResultMetadata?.responseInfo?.mode 

أمثلة على الاستخدام


الآن دعنا نذهب عبر الطرق الرئيسية لفئات البحث ، ونلقي نظرة على أمثلة الاستخدام وبعض النقاط غير الواضحة المرتبطة بها.


بحث نصي


الطريقة الرئيسية للبحث النصي (وربما للبحث بأكمله) هي submit :


 Session submit( String text, Geometry geometry, SearchOptions searchOptions, SearchListener searchListener ); 

  • من المتوقع أن تحتوي معلمة text على النص الذي تريد البحث فيه.
  • المعلمة geometry أصعب قليلاً. بناءً على نوع الهندسة التي يتم نقلها ، سيتصرف البحث بشكل مختلف:
    • إذا تجاوزت نقطة ، فسيتم إجراء البحث في نافذة صغيرة بجوار هذه النقطة.
    • إذا قمت بتمرير نافذة مستطيلة ( BoundingBox ) أو مضلع من أربع نقاط ، فسيتم استخدامه كمربع بحث. مثال بسيط على هذه النافذة هو المنطقة المرئية من الخريطة.
    • أخيرًا ، إذا تجاوزت الخطوط المتعددة ، فسيتم استخدام النافذة التي تصفه كنافذة بحث ، وسيتم إجراء الترتيب مع مراعاة هذا الخط المتعدد.
  • لقد تحدثنا بالفعل عن SearchOptions و SearchListener أعلاه.

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


تحتوي طريقة submit توأم submit :


 Session submit( String text, Polyline polyline, Geometry geometry, SearchOptions searchOptions, SearchListener searchListener ); 

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


إعادة الطلبات


تتم إعادة الطلبات ، على عكس الأنواع الأخرى من الطلبات ، بمساعدة جلسة بحث ، والتي تُرجع نفس الإرسال وأخيه التوأم. جزء من أساليب الجلسة بسيط ومباشر:



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


مرشحات


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



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


أولاً ، دعنا نرى كيفية توفير الفلاتر لإعادة التشغيل الحالية:


 private fun filters(response: Response): String? { fun enumValues(filter: BusinessFilter) = filter .values .enums ?.joinToString(prefix = " -> ") { e -> e.value.id } ?: "" return response .metadata .businessResultMetadata ?.businessFilters ?.joinToString(separator = "\n") { f -> "${f.id}${enumValues(f)}" } } 

في السطر الناتج ، بالنسبة للمرشحات المنطقية ، سيتم عرض المعرف فقط ، وبالنسبة لمرشحات التعداد ، ومعرف المرشح نفسه ومعرفات القيم المتاحة. الآن ، مسلحًا بمعرفة المعرّفات المتاحة ، سنبحث عن مقاهي المطبخ الإيطالي التي تحتوي على شبكة Wi-Fi. قم أولاً بإضافة عامل تصفية منطقي:


 val boolFilter = BusinessFilter( /* id= */ "wi_fi", /* name= */ "", /* disabled= */ false, /* values= */ BusinessFilter.Values.fromBooleans( listOf(BusinessFilter.BooleanValue(true, true)) ) ) 

الآن مرشح التعداد:


 val enumFilter = BusinessFilter( /* id= */ "type_cuisine", /* name= */ "", /* disabled= */ false, /* values= */ BusinessFilter.Values.fromEnums( listOf(BusinessFilter.EnumValue( Feature.FeatureEnumValue( /* id= */ "italian_cuisine", /* name= */ "", /* imageUrlTemplate= */ "" ), true, true )) ) ) 

أخيرًا ، يمكنك إضافة فلاتر إلى الجلسة واستدعاء resubmit() :


 searchSession!!.setFilters(listOf(boolFilter, enumFilter)) searchSession!!.resubmit(this) 

يرجى ملاحظة أنه لا يمكنك تعيين عوامل تصفية للاستعلام الأول. تحتاج أولاً إلى الحصول على استجابة بحث تسرد عوامل التصفية المتاحة. وعندها فقط لتشكيل إعادة تشغيل.


نتائج إضافية

تتيح لك جلسة أخرى التحقق مما إذا كانت هناك نتائج بحث إضافية لطلبك. وإذا كانت كذلك ، احصل عليها. على سبيل المثال ، عندما تبحث عن مقهى في مدينتك ، فغالبًا لن تناسبهم جميعًا على صفحة واحدة من إجابة البحث. هناك حاجة إلى fetchNextPage و fetchNextPage لعرض الصفحات التالية في القائمة. هنا تحتاج إلى معرفة أنه أولاً ، استدعاء fetchNextPage سيؤدي إلى استثناء إذا hasNextPage أسلوب hasNextPage false . وثانيًا ، يعني استخدام هذه الأساليب أن المعلمات المتبقية لا تتغير. أي ، يتم استخدام الجلسة إما لتحسين الطلب ( resubmit() fetchNextPage() ) ، أو لاسترداد الصفحات التالية ( fetchNextPage() ). الجمع بين هذه الأوضاع ليست ضرورية.


بحث عكسي


يسمى البحث العكسي عن الراحة أيضًا submit :


 Session submit( Point point, Integer zoom, SearchOptions searchOptions, SearchListener searchListener ) 

وهي تختلف عن أنواع الاستعلامات الأخرى في أنها تتطلب نوعًا واحدًا فقط من البحث للدخول. يمكنك إما تمرير نوع GEO والبحث عن أسماء الأماكن ، أو نوع BIZ والبحث عن المؤسسات. لا يوجد ثالث.


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


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


 val point = Point(55.734, 37.588) //         «  , 16» searchSession = searchManager!!.submit(point, 16, SearchOptions(), this) //    –  " " searchSession = searchManager!!.submit(point, 14, SearchOptions(), this) 

البحث عن طريق URI


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


 searchSession = searchManager!!.resolveURI(uri, SearchOptions(), this) 

ممل بطريقة أو بأخرى.


طبقة البحث والمستقبل المشرق


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



الخلاصة


آمل أنه بعد قراءة المقالة سيكون لديك فهم لكيفية العمل مع البحث في MapKit بكامل قوته. من المؤكد أنه كانت هناك لحظات خفية وغير تافهة (على سبيل المثال ، لم نتحدث تقريبًا عن النصائح وطبقة البحث). يمكن العثور على شيء ما في الوثائق ، شيء لتوضيح ذلك في المشاريع على GitHub أو طلب دعمنا.


جرّب MapKit ، واستخدم البحث فيه وانتقل إلى الخرائط لجعلها أفضل!


ملاحظة : وتأتي أيضًا لزيارتنا في 29 نوفمبر للاستماع حول كيفية ترتيب الواجهة الخلفية لتوجيه السيارات . والتي ، بالمناسبة ، يمكن استخدامها أيضًا في MapKit ، لكن هذه قصة مختلفة تمامًا.

Source: https://habr.com/ru/post/ar428564/


All Articles