مكتبة Go (Go SDK)
مكتبة Go (Go SDK)
وحدة github.com/navvo/navvo-go الرسمية (الحزمة navvo) هي عميل من جهة
الخادم بلغة Go لخدمة Navvo على الويب. وهي مكتوبة بأنواع محدّدة، وتعيد المحاولة
تلقائيًا، وآمنة للاستخدام المتزامن، وتعتمد على المكتبة القياسية فقط
(net/http، encoding/json، context) — بلا أي تبعيات خارجية. يأخذ كل
استدعاء وسيطًا أوّلًا من نوع context.Context. لا تحتوي على واجهة خرائط ولا
عرض بلاطات — لعرض خريطة في المتصفح استخدم خرائط Navvo.
يتطلّب: Go 1.21+ · عنوان الأساس: https://navvo.io/api/v1
التثبيت
go get github.com/navvo/navvo-go
import navvo "github.com/navvo/navvo-go"
المصادقة
تُرسَل كل الاستدعاءات عبر ترويسة x-api-key. مرّر المفتاح صراحةً، أو مرّر ""
لقراءته من متغيّر البيئة NAVVO_API_KEY. يتم التحقق من شكل المفتاح
(nvvo_<48 hex>) مسبقًا.
c, err := navvo.New("") // يستخدم $NAVVO_API_KEY
c, err := navvo.New("nvvo_your_key_here") // صريح
لا تضع المكتبة المفتاح في عنوان URL إطلاقًا — باستثناء StyleURL() التي تبني
عنوان نمط خريطة يجلبه المتصفح، حيث يجب أن يمرّر
المفتاح كـ ?api_key= (لا يستطيع المتصفح ضبط ترويسات على طلبات البلاطات/الأنماط).
بداية سريعة
package main
import (
"context"
"fmt"
"log"
"time"
navvo "github.com/navvo/navvo-go"
)
func main() {
c, err := navvo.New("") // يقرأ NAVVO_API_KEY
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// ترميز جغرافي أمامي — تُحوّل الإحداثيات النصّية إلى أرقام نيابةً عنك.
hits, err := c.Geocode(ctx, "مستشفى العبدلي، عمّان", &navvo.GeocodeOptions{Limit: 5})
if err != nil {
log.Fatal(err)
}
fmt.Println(hits[0].Lat, hits[0].Lng, hits[0].DisplayName)
// خطّط مسارًا — تمرّر {Lat, Lng} وتتولّى المكتبة ترتيب lon/lat على الشبكة.
plan, err := c.Route(ctx, navvo.RoutePlanRequest{
Locations: []navvo.LatLng{
{Lat: 31.95, Lng: 35.91, Name: "أ"},
{Lat: 31.99, Lng: 35.95, Name: "ب"},
},
Costing: "auto",
})
if err != nil {
log.Fatal(err)
}
fmt.Println("الزمن المقدّر (ثوانٍ):", plan.Trip().Summary.Time)
// فكّ ترميز المسار (polyline6) إلى نقاط.
line := plan.Geometry()
fmt.Println("عدد نقاط الشكل:", len(line))
// إضافات حيّة تراعي الظروف.
wx, _ := c.WeatherPoint(ctx, 31.95, 35.91, "metric")
fmt.Println(wx.Temperature, wx.Unit)
}
الإعداد
تقبل New خيارات دالّية:
c, err := navvo.New(
"",
navvo.WithBaseURL("https://navvo.io/api/v1"),
navvo.WithTimeout(15*time.Second),
navvo.WithUserAgent("acme-dispatch/2.3"),
navvo.WithRetry(navvo.RetryConfig{
Retries: 3, BaseDelay: 500 * time.Millisecond, Factor: 2, Jitter: true, MaxDelay: 20 * time.Second,
}),
)
استخدم navvo.WithHTTPClient(myClient) لتمرير *http.Client خاص بك، و
c.WithAPIKey(key) لاستنساخ عميل بمفتاح مختلف (خوادم متعددة المستأجرين). و
*Client آمن للاستخدام المتزامن عبر عدّة goroutines.
الإحداثيات — لا تنعكس أبدًا
تمرّر النقاط دائمًا كـ navvo.LatLng{Lat, Lng}. وتتولّى المكتبة كل اختلافات
صيغة الشبكة: {lon, lat} لـ Route/TrafficRoute، و{lat, lon} لحمولات
المرور المباشرة (SimpleRoute/Matrix/Optimize)، ومعاملا lat/lon
المنفصلان للترميز العكسي، وwest,south,east,north لـ BBox، و[lng, lat] لـ
GeoJSON، وصيغة WKT POINT(lng lat) لمركز المكان (حلّلها بـ
navvo.ParseWKTPoint(place.Center) أو place.CenterLatLng()).
amman := navvo.LatLng{Lat: 31.9539, Lng: 35.9106}
box := navvo.BBox{West: 35.85, South: 31.92, East: 35.98, North: 32.02}
إعادة المحاولة والأخطاء
تُعاد محاولة الأعطال العابرة (5xx، 408، 429/الحصّة، انقطاعات الشبكة،
وأظرف available:false) بتراجع أُسّي وعشوَنة كاملة مع احترام Retry-After؛ ولا
تُعاد محاولة أخطاء العميل (400/401/403/404) أبدًا. يلفّ كل خطأ
*navvo.APIError ويدعم errors.Is / errors.As:
hits, err := c.Geocode(ctx, "مستشفى العبدلي، عمّان", nil)
if err != nil {
var rl *navvo.RateLimitError
var scope *navvo.ScopeError
switch {
case errors.As(err, &rl):
log.Printf("تم تجاوز الحدّ، أعد المحاولة بعد %v", rl.RetryAfter)
case errors.As(err, &scope):
log.Printf("مشكلة نطاق/حصّة: %s (ينقص %s)", scope.Reason, scope.Scope)
case errors.Is(err, navvo.ErrServiceUnavailable):
log.Print("مزوّد الترميز الجغرافي متوقّف — استخدم بديلًا")
default:
return err
}
}
يُفكّ غلاف الترميز/المسارات ({ provider, available, data }) نيابةً عنك —
فيصبح جسم available:false خطأً من نوع *navvo.ServiceUnavailableError — ويعيد
slug غير معروف للمكان قيمة nil (قيمة فارغة ناعمة، لا خطأً). أَلغِ أو حدّد مهلة
أي استدعاء عبر context.Context الذي تمرّره.
| الخطأ | الثابت | من |
|---|---|---|
*ConfigError | ErrConfig | مفتاح خاطئ/مفقود أو خيار خاطئ |
*ValidationError | ErrValidation | 400 (يحمل Fields) |
*AuthError | ErrUnauthorized | 401 |
*ScopeError | ErrForbidden | 403 (يحمل Reason، Scope) |
*NotFoundError | ErrNotFound | 404 |
*RateLimitError | ErrRateLimited | 429 (يحمل RetryAfter) |
*ServiceUnavailableError | ErrServiceUnavailable | available:false، 502/503 |
*ServerError | ErrServer | بقية 5xx |
*TimeoutError / *NetworkError | ErrTimeout / ErrNetwork | مهلة / نقل |
يُرسَل المفتاح فقط في ترويسة x-api-key ولا يظهر في أي خطأ؛ وتحمل الأخطاء
RedactedKey (nvvo_****<last4>).
البحث
تُعيد Search نتيجة GET /search بأنواع محدّدة، بما فيها QueryID الذي تشير إليه أحداث النقر اللاحقة. ولا تُرسَل الخيارات المتروكة على قيمتها الصفرية:
res, err := c.Search(ctx, "pharmacy", &navvo.SearchOptions{
Near: &navvo.LatLng{Lat: 31.9539, Lng: 35.9106}, // رتّب الأماكن الأقرب أولًا
RadiusKm: 3, // كلٌّ من RadiusKm و SortDistance يحتاج Near
Sort: navvo.SearchSortDistance,
OpenNow: navvo.Bool(true),
MinRating: 4,
Amenities: []string{"wheelchair-accessible-entrance"},
Facets: []navvo.SearchFacet{navvo.SearchFacetCategory, navvo.SearchFacetAmenities},
Highlight: navvo.Bool(true),
})
if err != nil {
return err
}
fmt.Println(res.QueryID) // لاستخدامه مع SendSearchEvent
for i, p := range res.Places.Items {
// تكون DistanceKm و Highlight بقيمة nil حين لا تنطبقان، وتكون ImageURL فارغة حين لا توجد صورة.
fmt.Println(res.Places.Position(i), p.NameEn, p.DistanceKm, p.ImageURL, p.Promoted)
}
fmt.Println(res.DidYouMean, len(res.Suggestions)) // عند انعدام النتائج فقط: تصحيح إملائي وأماكن شائعة
c.Search(ctx, "rumi caf", &navvo.SearchOptions{Mode: navvo.SearchModeTypeahead}) // أثناء الكتابة
c.Search(ctx, "quiet place to work with good coffee", &navvo.SearchOptions{Semantic: navvo.SearchSemanticOn}) // المطابقة حسب المعنى
c.Search(ctx, "pharmacy", &navvo.SearchOptions{Analytics: navvo.Bool(false)}) // أبقِ هذا الاستدعاء خارج التحليلات
c.SearchSuggestions(ctx, "pha", &navvo.SearchSuggestionsOptions{Limit: 6}) // GET /search/suggestions
c.SimilarPlaces(ctx, navvo.SimilarPlacesOptions{Slug: "jordan-museum-994700", Limit: 6}) // GET /search/similar
- تبيّن
res.QueryUnderstanding()كيف قُرئ الاستعلام (اللغة والنية والتصنيف والمنطقة و«مفتوح الآن»)، وتكون قيمةres.IndexStatus().Modeهي"hybrid"عندما تُسهم المطابقة حسب المعنى. ويحملres.Rawالجسم كاملًا. - مع مفتاح API لا تعمل المطابقة حسب المعنى إلا مع
Semantic: navvo.SearchSemanticOn، ولا تعمل أبدًا في نمط الكتابة. - تُعيد
SearchSuggestionsباستعلام فارغ قائمة بداية من التصنيفات اليومية والأماكن المعروفة. وتأخذSimilarPlacesمعرّفًاIDأوSlugوتُعيدItemsوSource(SimilarSourceVectors: مقارنة حسب المعنى، أوSimilarSourceNearby: البديل الاحتياطي)، والمكان غير المعروف يُعيد خطأً من النوع*NotFoundError. - ترفض
Searchالاستعلام الفارغ، ونقطة الأصل الواقعة خارج الكرة الأرضية، ومعرّف الجلسة غير الصالح بخطأ من النوع*ValidationErrorقبل إرسال أي شيء.
تصل الإبرازات مُهرَّبة بترميز HTML، والكلمات المطابقة محاطة بالوسم <em>. وتقسم ParseHighlight الإبراز إلى مقاطع نصية، فتعرضها نصًّا بدل الوثوق بالسلسلة على أنها ترميز:
for _, p := range res.Places.Items {
if p.Highlight != nil { // nil حين لا يطابق أي اسم، أو حين لم تُطلب الإبرازات
for _, seg := range navvo.ParseHighlight(p.Highlight.NameEn) {
fmt.Print(seg.Text, seg.Match)
}
}
}
مرشّح السعر وتفاصيل النتيجة
منذ الإصدار v0.3.0 يحتفظ Price بالأماكن التي تقع في أي من مستويات السعر المحدَّدة: 1 حتى 5 دنانير للشخص، و2 من 5 إلى 10، و3 من 10 إلى 20، و4 أكثر من ذلك. ويمكن أيضًا أن يحمل كل SearchItem ما تعرضه قائمة النتائج، وتعني القيمة الصفرية (0 أو "" أو nil) أن بياناته غير متوفّرة للمكان:
res, err := c.Search(ctx, "مقهى", &navvo.SearchOptions{
Near: &navvo.LatLng{Lat: 31.9539, Lng: 35.9106},
Price: []int{1, 2},
})
if err != nil {
return err
}
for _, p := range res.Places.Items {
fmt.Println(p.NameEn, p.CategoryName, p.PriceRange, p.PriceLevel, p.ReviewCount, p.Address, p.Area, p.Highlights)
if p.Hours != nil { // اليوم، بتوقيت الأردن
fmt.Println(p.Hours.State, p.Hours.ClosesAt, p.Hours.OpensAt, p.Hours.OpensDay)
}
}
c.SimilarPlaces(ctx, navvo.SimilarPlacesOptions{Slug: "jordan-museum-994700", Locale: "ar"}) // CategoryName و Address بالعربية
تكون قيمة Hours.State إما navvo.SearchHoursOpen أو navvo.SearchHoursClosing (يغلق خلال 45 دقيقة) أو navvo.SearchHoursClosed. ويدلّ Hours.AlwaysOpen على مكان مفتوح على مدار الساعة، وHours.HoursMayVary على عطلة رسمية. ويعيد المستوى الواقع خارج 1–4 خطأً من نوع *ValidationError قبل إرسال أي شيء. راجع تفاصيل النتيجة.
النقرات والتحويلات
لترى نسبة النقر والتحويلات لمشروعك في بوابة المطوّرين، أرسل معرّف جلسة مع البحث، واحدًا لكلّ جلسة مستخدم وفقط إذا وافق مستخدمك على ذلك. ثم أبلِغ عمّا فتحه المستخدم:
session := navvo.NewSearchSessionID() // واحد لكل جلسة مستخدم، وبموافقة المستخدم فقط
res, err := c.Search(ctx, "rumi cafe", &navvo.SearchOptions{Session: session})
if err != nil || len(res.Places.Items) == 0 {
return err
}
hit, position := res.Places.Items[0], res.Places.Position(0) // النتيجة التي فتحها المستخدم
c.SendSearchEvent(ctx, navvo.SearchEvent{
QueryID: res.QueryID, Type: navvo.SearchEventClick, PlaceID: hit.ID, Position: position,
})
c.SendSearchEvent(ctx, navvo.SearchEvent{
QueryID: res.QueryID, Type: navvo.SearchEventConversion, Name: navvo.SearchEventDirections,
PlaceID: hit.ID, Position: position,
})
- تُقبل الأحداث خلال 7 أيام من البحث، وفقط إذا حمل البحث معرّف جلسة، وفقط بمفتاح API نفسه.
- ترسل
SendSearchEventsعدّة أحداث معًا، في طلبات لا يتجاوز كلٌّ منها 20 حدثًا و16 كيلوبايت، وتجمعAcceptedوRejectedوReasons. وأسماء التحويلات هيSearchEventDirectionsوSearchEventNavigateوSearchEventSaveوSearchEventCallوSearchEventWebsiteوSearchEventShare. - تولّد
NewSearchSessionID()دائمًا معرّفًا صالحًا، وتتحقّقValidSearchSessionID(id)من معرّف: من 16 إلى 64 حرفًا منA-Z a-z 0-9 _ -بلا 6 أرقام متتالية. - يُبقي
navvo.WithSearchAnalytics(false)كل ما يبحث عنه العميل خارج التحليلات، وتُرفض الأحداث التي يرسلها عميل كهذا، فأرسل النقرات من عميل أُنشئ دون هذا الخيار.
راجع أحداث البحث لمعرفة ما يُقبل ولماذا قد يُرفض حدث، وتحليلات البحث والخصوصية لمعرفة قواعد الموافقة وما تسجّله Navvo.
خريطة الدوال
| المجال | الدوال |
|---|---|
| البلاطات والإعداد | Styles، StyleURL، MapConfig |
| البحث والترميز الجغرافي | Search، SearchSuggestions، SimilarPlaces، SendSearchEvent، SendSearchEvents، Geocode، ReverseGeocode، GeocodeBatch |
| الأماكن | ListPlaces، Place، PlaceThumb، PlaceCategories |
| المسارات | Route، SimpleRoute، Matrix، Optimize، RoutingProfiles |
| حركة المرور والحوادث | TrafficRoute، TrafficSummary، Incidents، IncidentTypes، ReportIncident، RoadEdits، RouteRisk |
| الطقس وجودة الهواء | WeatherPoint، WeatherForecast، WeatherCities، AirQualityCities |
| المركبات الكهربائية والنقل | EVStations، TransitStops، GBFS |
تقابل كل دالة النقطة الطرفية الموثّقة في مرجع الـ API.
تكشف النتائج عن حقل Raw (map[string]any) يحوي الجسم الكامل غير المُنمّط
للتوافق المستقبلي. تتطلّب Optimize نطاق optimize الاختياري؛ وتتطلّب
TrafficRoute كلًّا من routing وtraffic. راجع
تحسين المسارات و
حركة المرور والحوادث.
النشر والإصدارات
تُنشَر الوحدة عبر وسوم Git. الإصدار الحالي هو v0.3.0:
go get github.com/navvo/[email protected]