مكتبة Java (Java SDK)
مكتبة Java (Java SDK)
مكتبة io.navvo:navvo-java الرسمية هي عميل من جهة الخادم بلغة Java لخدمة
Navvo على الويب. وهي مكتوبة بأنواع محدّدة، وتعيد المحاولة تلقائيًا، وآمنة
للاستخدام المتزامن، ومبنية على JDK 11 الصِّرف (java.net.http.HttpClient)
مع محلّل JSON صغير مضمّن — بلا أي تبعيات خارجية (لا Jackson ولا Guava). وهي
متوافقة مع Kotlin، وتوفّر دوالّ متزامنة ونظائر غير متزامنة عبر
CompletableFuture. لا تحتوي على واجهة خرائط ولا عرض بلاطات — لعرض خريطة في
المتصفح استخدم خرائط Navvo.
يتطلّب: Java 11+ (يعمل على 11 / 17 / 21) · عنوان الأساس: https://navvo.io/api/v1
التثبيت
<dependency>
<groupId>io.navvo</groupId>
<artifactId>navvo-java</artifactId>
<version>0.3.0</version>
</dependency>
implementation 'io.navvo:navvo-java:0.3.0'
import io.navvo.NavvoClient;
import io.navvo.model.*;
المصادقة
تُرسَل كل الاستدعاءات عبر ترويسة x-api-key. مرّر المفتاح صراحةً، أو استخدم
NavvoClient.fromEnv() لقراءته من متغيّر البيئة NAVVO_API_KEY. يتم التحقق من
شكل المفتاح (nvvo_<48 hex>) مسبقًا، ولا يُسجَّل المفتاح أبدًا — إذ يُخفى إلى
nvvo_****<آخر 4> في كل مكان.
NavvoClient navvo = NavvoClient.fromEnv(); // يستخدم $NAVVO_API_KEY
NavvoClient navvo = NavvoClient.builder()
.apiKey("nvvo_your_key_here")
.build();
لا تضع المكتبة المفتاح في عنوان URL إطلاقًا — باستثناء styleUrl() التي تبني
عنوان نمط خريطة قابلًا للجلب من المتصفح حيث يجب أن
يُمرَّر المفتاح بصيغة ?api_key= (لا يمكن للمتصفحات ضبط الترويسات على طلبات
البلاطات/الأنماط).
البدء السريع
import io.navvo.NavvoClient;
import io.navvo.model.*;
import java.util.*;
public class Demo {
public static void main(String[] args) {
try (NavvoClient navvo = NavvoClient.fromEnv()) {
// ترميز جغرافي أمامي — تُحوَّل الإحداثيات النصّية إلى أرقام تلقائيًا.
List<GeocodeResult> hits = navvo.geocode(
GeocodeQuery.builder().q("مستشفى العبدلي, عمّان").limit(5).build());
GeocodeResult top = hits.get(0);
System.out.println(top.lat() + "," + top.lng() + " " + top.displayName());
// خطّط مسارًا — تمرّر LatLng(lat, lng) وتتولّى المكتبة ترتيب lon/lat على السلك.
RoutePlan plan = navvo.route(RoutePlanRequest.builder()
.addLocation(LatLng.named(31.95, 35.91, "أ"))
.addLocation(LatLng.named(31.99, 35.95, "ب"))
.costing("auto")
.build());
System.out.println("الوقت المتوقّع بالثواني: " + plan.durationSeconds());
// فكّ ترميز خط المسار (polyline6) إلى نقاط.
List<LatLng> line = plan.geometry();
System.out.println("عدد نقاط الشكل: " + line.size());
// إضافات حيّة (NavvoObject مع منفذ raw() الاحتياطي).
NavvoObject wx = navvo.weatherPoint(31.95, 35.91, "metric");
System.out.println(wx.raw());
}
}
}
الإعداد
اضبط عبر الباني (builder):
NavvoClient navvo = NavvoClient.builder()
.apiKey(System.getenv("NAVVO_API_KEY"))
.baseUrl("https://navvo.io/api/v1")
.timeout(java.time.Duration.ofSeconds(15))
.connectTimeout(java.time.Duration.ofSeconds(5))
.userAgent("acme-dispatch/2.3")
.retry(RetryConfig.builder()
.retries(3)
.baseDelay(java.time.Duration.ofMillis(500))
.maxDelay(java.time.Duration.ofSeconds(20))
.factor(2).jitter(true).respectRetryAfter(true)
.build())
.build();
استخدم navvo.withApiKey(key) لاستنساخ عميل بمفتاح مختلف (خوادم متعدّدة
المستأجرين). كائن NavvoClient غير قابل للتغيير وآمن للاستخدام المتزامن عبر
الخيوط، وهو AutoCloseable للاستخدام مع try-with-resources.
الإحداثيات — لن تنعكس أبدًا
تمرّر النقاط دائمًا كـ LatLng.of(lat, lng) (أو LatLng.named(lat, lng, name)
لنقطة طريق). تتولّى المكتبة كل اختلافات صيغة السلك: {lon, lat} لـ
route/trafficRoute، و{lat, lon} لحمولات المرور المباشرة
(simpleRoute/matrix/optimizeStops)، ومعاملات lat/lon منفصلة للترميز
العكسي، وwest,south,east,north لـ BBox، و[lng, lat] لـ GeoJSON، وصيغة WKT
POINT(lng lat) لمركز المكان (افكُكها بـ place.centerLatLng()).
LatLng amman = LatLng.of(31.9539, 35.9106);
BBox box = BBox.fromCorners(35.85, 31.92, 35.98, 32.02); // west, south, east, north
إعادة المحاولة والأخطاء
تُعاد محاولة الأعطال العابرة (5xx، 408، 429/الحصّة، انقطاعات الشبكة، وأغلفة
available:false) بتراجع أُسّي مع تشويش كامل، مع احترام Retry-After؛ أمّا أخطاء
العميل (400/401/403/404) فلا يُعاد محاولتها. كل خطأ هو نوع فرعي غير مُتحقَّق
من NavvoException:
try {
navvo.geocode(GeocodeQuery.of("مستشفى العبدلي, عمّان"));
} catch (NavvoRateLimitException e) {
log.warn("تم تجاوز الحد، أعد المحاولة بعد {}", e.retryAfter());
} catch (NavvoScopeException e) {
log.error("نطاق/حصّة: {} (ناقص {})", e.reason(), e.scope());
} catch (NavvoServiceUnavailableException e) {
log.error("محرّك الترميز الجغرافي معطّل — استخدم بديلًا");
}
يُفكّ غلاف الترميز/المسارات ({ provider, available, data }) تلقائيًا — ويصبح
جسم available:false استثناءً من نوع NavvoServiceUnavailableException — ويعيد
المعرّف المجهول للمكان Optional فارغًا (قيمة null لطيفة، وليست خطأً).
| الاستثناء | المصدر |
|---|---|
NavvoConfigException | مفتاح خاطئ/مفقود، خيار خاطئ، عنوان أساس غير HTTPS |
NavvoValidationException | 400 (وبقية 4xx غير القابلة لإعادة المحاولة) |
NavvoAuthException | 401 |
NavvoScopeException | 403 (يحمل reason()، scope()) |
NavvoNotFoundException | 404 |
NavvoRateLimitException | 429 (يحمل retryAfter()، limit()، remaining()، resetAt()) |
NavvoServiceUnavailableException | available:false، 502/503 |
NavvoServerException | بقية 5xx |
NavvoTimeoutException / NavvoNetworkException | مهلة / نقل |
يُرسَل المفتاح فقط في ترويسة x-api-key ولا يظهر في أي خطأ؛ تحمل الأخطاء
redactedKey() (nvvo_****<آخر 4>).
البحث
تُعيد search() استجابة GET /search في كائن SearchResponse بأنواع محدّدة، بما فيها queryId() الذي تشير إليه أحداث النقر اللاحقة. ولا يُضاف إلى الطلب أيّ خيار لم تضبطه:
SearchResponse res = navvo.search("pharmacy", SearchOptions.builder()
.near(LatLng.of(31.9539, 35.9106)) // رتّب الأماكن الأقرب أولًا
.radiusKm(3).sort(SearchSort.DISTANCE) // كلاهما يحتاج near
.openNow(true).minRating(4)
.amenities("wheelchair-accessible-entrance")
.facets(SearchFacetName.CATEGORY, SearchFacetName.AMENITIES)
.highlight(true)
.build());
res.queryId(); // لاستخدامه مع sendSearchEvent()
for (Place p : res.places().items()) // قد تحمل distanceKm() و imageUrl() و highlight() و promoted()
System.out.println(p.nameEn() + " · " + p.distanceKm() + " km");
res.didYouMean(); res.suggestions(); // عند انعدام النتائج فقط: تصحيح إملائي وأماكن شائعة
navvo.search("rumi caf", SearchOptions.builder().mode(SearchMode.TYPEAHEAD).build()); // أثناء الكتابة
navvo.search("quiet place to work with good coffee",
SearchOptions.builder().semantic(SearchSemantic.ON).build()); // المطابقة حسب المعنى
navvo.search("pharmacy", SearchOptions.builder().analytics(false).build()); // أبقِ هذا الاستدعاء خارج التحليلات
navvo.searchSuggestions("pha", "en", 6); // GET /search/suggestions
navvo.similarPlaces(SimilarPlacesOptions.builder().slug("jordan-museum-994700").limit(6).build()); // GET /search/similar
- إن
SearchResponseكائن من النوعNavvoObject، فتبقى الشيفرة المكتوبة للإصدار 0.1 (get(...)وraw()) تُترجَم وتعمل، بما فيها الأصناف المترجَمة سابقًا. - الحقول
res.facets()وres.understanding()وres.index()بأنواع محدّدة أيضًا، وتكون قيمةres.index().mode()هي"hybrid"عندما تُسهم المطابقة حسب المعنى. - مع مفتاح API لا تعمل المطابقة حسب المعنى إلا مع
semantic(SearchSemantic.ON)، ولا تعمل أبدًا في نمط الكتابة. - تُعيد
searchSuggestions(null)قائمة بداية من التصنيفات اليومية والأماكن المعروفة. وتُعيدsimilarPlaces()القيمتينitems()وsource()("vectors": مقارنة حسب المعنى، أو"nearby": البديل الاحتياطي)، والمكان غير المعروف يرميNavvoNotFoundException. - قبل إرسال أي طلب، ترمي
search()الاستثناءNavvoValidationExceptionللاستعلام الفارغ، وNavvoConfigExceptionلنقطة أصل خارج الكرة الأرضية أو لقيمة خارج مجموعات الخيارات الثابتة أو لمعرّف جلسة غير صالح.
تصل الإبرازات مُهرَّبة بترميز HTML، والكلمات المطابقة محاطة بالوسم <em>. وتقسم highlight().nameEnSegments() وnameArSegments() الإبراز إلى مقاطع نصية تعرضها بأي صيغة:
for (Place p : res.places().items())
if (p.highlight() != null)
for (HighlightSegment seg : p.highlight().nameEnSegments())
System.out.print(seg.match() ? "[" + seg.text() + "]" : seg.text());
مرشّح السعر وتفاصيل النتيجة
منذ الإصدار 0.3.0 يحتفظ price(...) بالأماكن التي تقع في أي من مستويات السعر المحدَّدة: 1 حتى 5 دنانير للشخص، و2 من 5 إلى 10، و3 من 10 إلى 20، و4 أكثر من ذلك. ويمكن أيضًا أن يحمل كل Place ما تعرضه قائمة النتائج:
SearchResponse res = navvo.search("مقهى", SearchOptions.builder()
.near(LatLng.of(31.9539, 35.9106))
.price(1, 2)
.build());
for (Place p : res.places().items()) {
System.out.println(p.nameEn() + " · " + p.categoryName() + " · " + p.priceRange() + " · " + p.reviewCount());
PlaceHoursToday h = p.hours(); // اليوم، بتوقيت الأردن؛ لا يكون null، و isEmpty() عند عدم وجود ساعات
if (!h.isEmpty()) System.out.println(h.state() + " " + h.closesAt() + " " + h.opensAt() + " " + h.opensDay());
}
navvo.similarPlaces(SimilarPlacesOptions.builder()
.slug("jordan-museum-994700").locale("ar").build()); // categoryName() و address() بالعربية
وتتوفّر أيضًا priceLevel() وaddress() وarea() وhighlights()، ويعيد كل منها null أو قائمة فارغة إذا لم تتوفّر بياناته للمكان. ويكون h.isOpen() صحيحًا ما دام المكان مفتوحًا أو على وشك الإغلاق (isClosing(): يغلق خلال 45 دقيقة). ويدلّ alwaysOpen() على مكان مفتوح على مدار الساعة، وhoursMayVary() على عطلة رسمية. ويرمي المستوى الواقع خارج 1–4 الاستثناء NavvoConfigException قبل إرسال أي شيء. راجع تفاصيل النتيجة.
النقرات والتحويلات
لترى نسبة النقر والتحويلات لمشروعك في بوابة المطوّرين، أرسل معرّف جلسة مع البحث، واحدًا لكلّ جلسة مستخدم وفقط إذا وافق مستخدمك على ذلك. ثم أبلِغ عمّا فتحه المستخدم:
String session = NavvoClient.createSearchSessionId(); // واحد لكل جلسة مستخدم، وبموافقة المستخدم فقط
SearchResponse res = navvo.search("rumi cafe", SearchOptions.builder().session(session).build());
Place hit = res.places().items().get(0); // النتيجة التي فتحها المستخدم
navvo.sendSearchEvent(SearchEvent.click(res.queryId())
.placeId(hit.id()).position(res.positionOf(0)).build());
navvo.sendSearchEvent(SearchEvent.conversion(res.queryId(), SearchEventName.DIRECTIONS)
.placeId(hit.id()).position(res.positionOf(0)).build());
- تُقبل الأحداث خلال 7 أيام من البحث، وفقط إذا حمل البحث معرّف جلسة، وفقط بمفتاح API نفسه.
- ترسل
sendSearchEvents()عدّة أحداث معًا، في طلبات لا يتجاوز كلٌّ منها 20 حدثًا و16 كيلوبايت، وتجمعaccepted()وrejected()وreasons(). وأسماء التحويلات هيDIRECTIONSوNAVIGATEوSAVEوCALLوWEBSITEوSHARE. - تتحقّق
NavvoClient.isValidSearchSessionId(id)من المعرّف، وترميsearch()الاستثناءNavvoConfigExceptionللمعرّف غير الصالح، أمّاcreateSearchSessionId()فتولّد دائمًا معرّفًا صالحًا. - يُبقي
NavvoClient.builder().searchAnalytics(false)كل ما يبحث عنه العميل خارج التحليلات، وتُرفض الأحداث التي يرسلها عميل كهذا، فأرسل النقرات من عميل أُنشئ دون هذا الخيار.
راجع أحداث البحث لمعرفة ما يُقبل ولماذا قد يُرفض حدث، وتحليلات البحث والخصوصية لمعرفة قواعد الموافقة وما تسجّله Navvo.
خريطة الدوالّ
| المجال | الدوالّ |
|---|---|
| البلاطات والإعداد | styles، styleUrl، mapConfig |
| البحث والترميز الجغرافي | search، searchSuggestions، similarPlaces، sendSearchEvent، sendSearchEvents، geocode، reverseGeocode، geocodeBatch، reverseGeocodeBatch |
| الأماكن | places، placesStream، place، placeCategories، placeFacets، placeThumb، placeHours، placePopularTimes، placeAttributes، placeReviewsSummary، placePhotos، placeNearby، placesReachable |
| المسارات | route، simpleRoute، matrix، isochrone، mapMatch، optimizeStops، optimize، routingProfiles |
| حركة المرور والحوادث | trafficRoute، trafficSummary، incidents، incidentTypes، reportIncident، roadEdits، routeRisk |
| الطقس وجودة الهواء | weatherPoint، weatherForecast، weatherCities، airQualityCities |
| المركبات الكهربائية والنقل | evStations، transitStops، gbfs |
تُقابل كل دالّة نقطة النهاية الموثّقة في مرجع الـ API. تكشف
النتائج المُنمَّطة عن raw() الذي يحوي الجسم الكامل غير المُنمَّط للتوافق
المستقبلي؛ وتعيد الاستجابات الأقل بنيةً كائن NavvoObject الذي يعيد raw() فيه
الشجرة المُحلَّلة. تتطلّب optimize نطاق optimize الاختياري؛ وتتطلّب
trafficRoute نطاقَي routing وtraffic معًا. راجع
تحسين المسارات و
حركة المرور والحوادث.
الترقيم الصفحي وغير المتزامن
نقطة GET /places مُرقَّمة صفحيًا؛ وتمرّ placesStream(query) على كل الصفحات
بكسل:
navvo.placesStream(PlaceListQuery.builder().category("restaurants").limit(100).build())
.forEach(p -> System.out.println(p.nameEn()));
تملك الدوالّ الأساسية نظائر غير متزامنة تعيد CompletableFuture:
navvo.geocodeAsync(GeocodeQuery.of("عمّان"));
navvo.routeAsync(req);
navvo.placeAsync("ardh-cafe-ard-kafyh-2bd24a");
البناء بدون Maven
تُترجَم المكتبة بـ JDK وحده:
javac --release 11 -d build/classes $(find src/main/java -name '*.java')
jar --create --file navvo-java-0.3.0.jar -C build/classes .
java -cp navvo-java-0.3.0.jar io.navvo.examples.Main # اختبار سريع دون شبكة
ويُرفَق ملف pom.xml للنشر إلى Maven Central (mvn clean package يُنتج الحزمة
الثنائية وحِزمتَي -sources و-javadoc).
النشر والإصدار
تُنشَر الحزمة على Maven Central. الإصدار الحالي هو 0.3.0:
<dependency>
<groupId>io.navvo</groupId>
<artifactId>navvo-java</artifactId>
<version>0.3.0</version>
</dependency>