overpass

package module
v0.0.0-...-5fb9afa Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 24, 2025 License: MIT Imports: 14 Imported by: 0

README

go-overpass

Go library for accessing the Overpass API

GoDoc Go Report Card CI

Features

  • Simple, idiomatic Go API - Clean and intuitive interface
  • Context support - Cancellation and timeouts via context.Context
  • Automatic retry with exponential backoff - Resilient queries with configurable retry logic
  • In-memory caching - Optional response caching with TTL for faster repeated queries
  • Query builder - Fluent API for constructing Overpass QL queries
  • Feature categorization - Helper methods for classifying OSM elements by tags
  • Built-in rate limiting - Respects server rate limits with configurable concurrency
  • Comprehensive error handling - Detailed error messages and error wrapping
  • Full OpenStreetMap type support - Nodes, Ways, Relations with all metadata
  • Zero external dependencies - Only uses Go standard library
  • Well-tested - Comprehensive test suite with extensive coverage

Installation

go get github.com/MeKo-Christian/go-overpass

Quick Start

Basic Query
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/MeKo-Christian/go-overpass"
)

func main() {
    client := overpass.New()

    result, err := client.QueryContext(context.Background(),
        `[out:json];node(1);out;`)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Found %d elements\n", result.Count)
}
Query with Timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

result, err := client.QueryContext(ctx,
    `[out:json];relation(1673881);>>;out body;`)
if err != nil {
    log.Fatal(err)
}
Custom Settings
client := overpass.NewWithSettings(
    "http://api.openstreetmap.fr/oapi/interpreter/",
    5, // max 5 concurrent requests
    http.DefaultClient,
)

Usage

Client Creation

Default client:

client := overpass.New()
// Uses overpass-api.de with rate limit of 1 concurrent request

Custom client:

client := overpass.NewWithSettings(
    "https://custom-endpoint.com/api",
    3, // maxParallel
    customHTTPClient,
)
Making Queries

With context (recommended):

ctx := context.Background()
result, err := client.QueryContext(ctx, "[out:json];node(1);out;")

Without context (deprecated but still works):

result, err := client.Query("[out:json];node(1);out;")
// Internally uses context.Background()

Package-level function:

result, err := overpass.QueryContext(ctx, "[out:json];node(1);out;")
// Uses default client
Overpass Turbo Macro Expansion (Subset)

The turbo subpackage provides a small, pure-Go preprocessor for common Overpass Turbo macros so you can paste Turbo queries and run them against the Overpass API.

import "github.com/MeKo-Christian/go-overpass/turbo"

res, err := turbo.Expand(`node({{bbox}});out;`, turbo.Options{
    BBox: &turbo.BBox{South: 52.5, West: 13.4, North: 52.51, East: 13.41},
})
if err != nil {
    log.Fatal(err)
}

result, err := overpass.QueryContext(ctx, res.Query)

Supported macros in this initial subset: {{bbox}}, {{center}}, {{date}}, {{date:<n unit>}}, and custom shortcuts {{key=value}}.

If the query includes {{data:overpass,server=...}}, the parsed Result exposes EndpointOverride so you can switch endpoints if desired. Use turbo.ApplyEndpointOverride to prefer the override when present.

For convenience, turbo.NewClientWithOverride can build a client using the override (falling back to your default endpoint when absent).

Geocoding macros like {{geocodeArea:...}} are supported when you provide a turbo.Geocoder implementation in turbo.Options.

Macro expansion auto-detects XML queries (e.g., <osm-script>) and will emit XML-style replacements for {{bbox}}, {{center}}, and geocode macros. You can force a format via Options.Format.

{{data:sql,server=...}} is parsed and exposed via Result.Data and Result.DataServer for Postpass-style backends. Use turbo.SQLDataConfigFromResult to extract server and params.

All {{data:...}} options are also available in typed form via Result.Data.Parsed (server and parameter map).

When multiple {{style:...}} blocks are present, the latest one is stored in Result.Style, and all of them are collected in Result.Styles.

Working with Results
result, err := client.QueryContext(ctx, query)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Timestamp: %s\n", result.Timestamp)
fmt.Printf("Total elements: %d\n", result.Count)

// Access nodes
for id, node := range result.Nodes {
    fmt.Printf("Node %d: lat=%f, lon=%f\n", id, node.Lat, node.Lon)
    for k, v := range node.Tags {
        fmt.Printf("  %s=%s\n", k, v)
    }
}

// Access ways
for id, way := range result.Ways {
    fmt.Printf("Way %d: %d nodes\n", id, len(way.Nodes))
    if way.Bounds != nil {
        fmt.Printf("  Bounds: (%f,%f) to (%f,%f)\n",
            way.Bounds.Min.Lat, way.Bounds.Min.Lon,
            way.Bounds.Max.Lat, way.Bounds.Max.Lon)
    }
}

// Access relations
for id, relation := range result.Relations {
    fmt.Printf("Relation %d: %d members\n", id, len(relation.Members))
}

Advanced Features

Retry Logic with Exponential Backoff

The library automatically retries failed requests with exponential backoff (enabled by default):

// Default retry configuration (3 retries with exponential backoff)
client := overpass.New()

// Custom retry configuration
client := overpass.NewWithRetry(
    "https://overpass-api.de/api/interpreter",
    2, // max parallel requests
    http.DefaultClient,
    overpass.RetryConfig{
        MaxRetries:        5,
        InitialBackoff:    2 * time.Second,
        MaxBackoff:        60 * time.Second,
        BackoffMultiplier: 2.0,
        Jitter:            true,
    },
)

// Disable retries
client.SetRetryConfig(overpass.RetryConfig{MaxRetries: 0})

Retry behavior:

  • Automatically retries on server errors: 429, 500, 502, 503, 504
  • Does not retry client errors: 400, 401, 403, 404
  • Respects context cancellation during backoff waits
  • Adds jitter to prevent thundering herd
In-Memory Caching

Optional response caching with TTL (disabled by default):

client := overpass.New()

// Enable caching
client.SetCacheConfig(overpass.CacheConfig{
    Enabled:    true,
    TTL:        5 * time.Minute,
    MaxEntries: 1000,
})

// Query (first call hits API, second call hits cache)
result1, _ := client.QueryContext(ctx, query)
result2, _ := client.QueryContext(ctx, query) // From cache

// Cache management
fmt.Printf("Cache size: %d\n", client.CacheSize())
client.ClearCache()

// Cleanup resources when done
defer client.Close()

Cache features:

  • Thread-safe with automatic background cleanup
  • Configurable TTL and maximum entries
  • Simple FIFO eviction when max entries exceeded
  • Cache key based on endpoint + query string
Query Builder

Fluent API for constructing Overpass QL queries:

// Build a query
query := overpass.NewQueryBuilder().
    Node().
    Way().
    BBox(52.5, 13.4, 52.51, 13.41).
    Tag("amenity", "restaurant").
    Tag("cuisine", "italian").
    OutputCenter().
    Timeout(60).
    Build()

// Execute with builder
result, err := client.QueryWithBuilder(ctx, query)

// Helper functions for common patterns
restaurants := overpass.FindRestaurants(52.5, 13.4, 52.51, 13.41)
highways := overpass.FindHighways(52.5, 13.4, 52.51, 13.41, "primary")
cafes := overpass.FindAmenity(52.5, 13.4, 52.51, 13.41, "cafe")

result, err := client.QueryContext(ctx, restaurants.Build())

Query builder features:

  • Tag filtering (exact, exists, not equal, regex)
  • Bounding box queries
  • Multiple element types (node, way, relation)
  • Output modes (body, geom, center, meta)
  • Timeout configuration
  • Helper functions for common patterns
Feature Categorization

Helper methods for classifying OSM elements by their tags:

result, err := client.QueryContext(ctx, query)

for _, node := range result.Nodes {
    // Get high-level category
    category := node.GetCategory() // "amenity", "transportation", "natural", etc.
    subcategory := node.GetSubcategory() // "restaurant", "primary", "tree", etc.
    name := node.GetName()

    fmt.Printf("%s: %s - %s\n", category, subcategory, name)

    // Category helpers
    if node.IsAmenity() && node.IsFoodRelated() {
        fmt.Println("Found a restaurant/cafe/bar")
    }

    if node.IsTransportation() && node.IsRoad() {
        highway := node.GetSubcategory()
        fmt.Printf("Found a %s road\n", highway)
    }

    // Tag utilities
    if node.HasTag("wheelchair") {
        accessibility := node.GetTag("wheelchair", "unknown")
        fmt.Printf("Wheelchair accessible: %s\n", accessibility)
    }

    if node.MatchesFilter("amenity", "restaurant") {
        fmt.Println("This is a restaurant")
    }
}

Categorization features:

  • Recognize standard OSM tag categories
  • Priority-based categorization (highway > building > amenity)
  • Helper methods for common categories (food, education, healthcare)
  • Tag utility methods (HasTag, GetTag, MatchesFilter)

Rate Limiting

The library respects server rate limits using a semaphore-based approach:

// Allow only 1 concurrent request (default)
client := overpass.New()

// Allow up to 5 concurrent requests
client := overpass.NewWithSettings(endpoint, 5, http.DefaultClient)

Requests are automatically queued and executed according to the maxParallel setting.

Error Handling

The library provides detailed error information with error wrapping:

result, err := client.QueryContext(ctx, query)
if err != nil {
    // Check for specific error types
    if errors.Is(err, context.DeadlineExceeded) {
        log.Println("Query timed out")
    } else if errors.Is(err, context.Canceled) {
        log.Println("Query was cancelled")
    } else {
        log.Printf("Query failed: %v", err)
    }
    return
}
Server Errors

HTTP errors from the Overpass API are wrapped in ServerError:

var serverErr *overpass.ServerError
if errors.As(err, &serverErr) {
    fmt.Printf("Server returned %d: %s\n",
        serverErr.StatusCode,
        string(serverErr.Body))
}

Examples

See the examples/ directory for more usage patterns:

  • basic - Simple query demonstrating core functionality
  • timeout - Using context for timeout control
  • custom - Custom endpoint and rate limiting configuration

Migration from original repo

This fork introduces context support and breaking changes:

Breaking Changes
  1. HTTPClient interface now requires Do(*http.Request) method instead of PostForm()
    • http.DefaultClient already implements this
    • Custom implementations need to update
Migration Steps
  1. Update import:

    // Old
    import "github.com/serjvanilla/go-overpass"
    
    // New
    import "github.com/MeKo-Christian/go-overpass"
    
  2. Use QueryContext (recommended):

    // Old (still works but deprecated)
    result, err := client.Query("[out:json];node(1);out;")
    
    // New (preferred)
    ctx := context.Background()
    result, err := client.QueryContext(ctx, "[out:json];node(1);out;")
    
  3. Update custom HTTPClient if needed:

    // Old interface
    type HTTPClient interface {
        PostForm(url string, data url.Values) (*http.Response, error)
    }
    
    // New interface
    type HTTPClient interface {
        Do(req *http.Request) (*http.Response, error)
    }
    

The old Query() method still works but is deprecated. It internally calls QueryContext() with context.Background().

Requirements

  • Go 1.21 or higher
  • Queries must include [out:json] for correct JSON response format

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for details.

Development

# Run tests
go test -v ./...

# Run tests with coverage
go test -v -cover ./...

# Run integration tests (requires network)
go test -v -tags=integration ./...

# Run benchmarks
go test -bench=. ./...

# Run linter
golangci-lint run

License

MIT License - see LICENSE for details

Acknowledgments

Original library by serjvanilla

Documentation

Overview

Package overpass provides a client for using the Overpass API.

Usage:

import "github.com/MeKo-Christian/go-overpass"

Construct a new client, then use Query method on the client to receive result for your OverpassQL queries.

client := overpass.New()

//Retrieve relation with all its members, recursively.
result, _ := client.Query("[out:json];relation(1673881);>>;out body;")
//Take a note that you should use "[out:json]" in your query for correct work.

Default client uses overpass-api.de endpoint, but you can choose another with NewWithSettings method.

client := overpass.NewWithSettings("http://api.openstreetmap.fr/oapi/interpreter/", 1, http.DefaultClient)

You also can use default client directly by calling Query independently.

result, _ := overpass.Query("[out:json];relation(1673881);>>;out body;")

Rate limiting

Library respects servers rate limits and will not perform more than one request simultaneously with default client. With custom client you are able to adjust that value.

Index

Constants

This section is empty.

Variables

View Source
var DefaultClient = New()

Functions

This section is empty.

Types

type BoundingBox

type BoundingBox struct {
	South, West, North, East float64
}

BoundingBox represents geographic bounds (south, west, north, east).

type Box

type Box struct {
	Min Point `json:"min"`
	Max Point `json:"max"`
}

type CacheConfig

type CacheConfig struct {
	Enabled    bool          // Enable/disable caching (default: false)
	TTL        time.Duration // Time-to-live for cache entries (default: 5 minutes)
	MaxEntries int           // Maximum cache entries (0 = unlimited, default: 1000)
}

CacheConfig holds cache behavior configuration.

func DefaultCacheConfig

func DefaultCacheConfig() CacheConfig

DefaultCacheConfig returns sensible defaults (DISABLED by default).

type Category

type Category string

Category represents high-level OSM feature category.

const (
	CategoryTransportation Category = "transportation"
	CategoryAmenity        Category = "amenity"
	CategoryNatural        Category = "natural"
	CategoryWater          Category = "water"
	CategoryBuilding       Category = "building"
	CategoryLeisure        Category = "leisure"
	CategoryLanduse        Category = "landuse"
	CategoryBoundary       Category = "boundary"
	CategoryPlace          Category = "place"
	CategoryShop           Category = "shop"
	CategoryTourism        Category = "tourism"
	CategoryUnknown        Category = "unknown"
)

type Client

type Client struct {
	// contains filtered or unexported fields
}

A Client manages communication with the Overpass API.

func New

func New() Client

New returns Client instance with default overpass-api.de endpoint.

func NewWithRetry

func NewWithRetry(
	apiEndpoint string,
	maxParallel int,
	httpClient HTTPClient,
	retryConfig RetryConfig,
) Client

NewWithRetry returns Client with custom retry configuration.

func NewWithSettings

func NewWithSettings(
	apiEndpoint string,
	maxParallel int,
	httpClient HTTPClient,
) Client

NewWithSettings returns Client with custom settings.

func (*Client) CacheSize

func (c *Client) CacheSize() int

CacheSize returns the number of cached entries.

func (*Client) ClearCache

func (c *Client) ClearCache()

ClearCache removes all cached entries.

func (*Client) Close

func (c *Client) Close()

Close stops the cache cleanup routine and releases resources.

func (*Client) Query

func (c *Client) Query(query string) (Result, error)

Query is deprecated: use QueryContext instead. It sends request to OverpassAPI with context.Background().

func (*Client) QueryContext

func (c *Client) QueryContext(ctx context.Context, query string) (Result, error)

QueryContext sends request to OverpassAPI with provided querystring and context for cancellation/timeout.

func (*Client) QueryWithBuilder

func (c *Client) QueryWithBuilder(ctx context.Context, builder *QueryBuilder) (Result, error)

QueryWithBuilder executes query from builder (convenience method).

func (*Client) SetCacheConfig

func (c *Client) SetCacheConfig(config CacheConfig)

SetCacheConfig updates the cache configuration for the client.

func (*Client) SetRetryConfig

func (c *Client) SetRetryConfig(config RetryConfig)

SetRetryConfig updates the retry configuration for the client.

type ElementType

type ElementType string

ElementType represents possible types for Overpass response elements.

const (
	ElementTypeNode     ElementType = "node"
	ElementTypeWay      ElementType = "way"
	ElementTypeRelation ElementType = "relation"
)

Possible values are node, way and relation.

type HTTPClient

type HTTPClient interface {
	Do(req *http.Request) (*http.Response, error)
}

HTTPClient interface for making HTTP requests with context support.

type Meta

type Meta struct {
	ID        int64             `json:"id"`
	Timestamp *time.Time        `json:"timestamp,omitempty"`
	Version   int64             `json:"version,omitempty"`
	Changeset int64             `json:"changeset,omitempty"`
	User      string            `json:"user,omitempty"`
	UID       int64             `json:"uid,omitempty"`
	Tags      map[string]string `json:"tags,omitempty"`
}

Meta contains fields common for all OSM types.

func (*Meta) GetCategory

func (m *Meta) GetCategory() Category

GetCategory returns high-level category based on OSM tags.

func (*Meta) GetName

func (m *Meta) GetName() string

GetName returns the name tag value if present.

func (*Meta) GetSubcategory

func (m *Meta) GetSubcategory() string

GetSubcategory returns detailed subcategory (tag value).

func (*Meta) GetTag

func (m *Meta) GetTag(key, defaultValue string) string

GetTag returns tag value with default fallback.

func (*Meta) HasTag

func (m *Meta) HasTag(key string) bool

HasTag checks if specific tag exists.

func (*Meta) IsAmenity

func (m *Meta) IsAmenity() bool

IsAmenity checks if element is an amenity.

func (*Meta) IsBuilding

func (m *Meta) IsBuilding() bool

IsBuilding checks if element is a building.

func (*Meta) IsEducation

func (m *Meta) IsEducation() bool

IsEducation checks if amenity is education-related.

func (*Meta) IsFoodRelated

func (m *Meta) IsFoodRelated() bool

IsFoodRelated checks if amenity is food/drink related.

func (*Meta) IsHealthcare

func (m *Meta) IsHealthcare() bool

IsHealthcare checks if amenity is healthcare-related.

func (*Meta) IsNatural

func (m *Meta) IsNatural() bool

IsNatural checks if element is natural feature.

func (*Meta) IsRailway

func (m *Meta) IsRailway() bool

IsRailway checks if element is railway.

func (*Meta) IsRoad

func (m *Meta) IsRoad() bool

IsRoad checks if element is a road (highway).

func (*Meta) IsTransportation

func (m *Meta) IsTransportation() bool

IsTransportation checks if element is transportation-related.

func (*Meta) IsWater

func (m *Meta) IsWater() bool

IsWater checks if element is water-related.

func (*Meta) MatchesFilter

func (m *Meta) MatchesFilter(key, value string) bool

MatchesFilter checks if element matches tag filter.

type Node

type Node struct {
	Meta

	Lat float64 `json:"lat"`
	Lon float64 `json:"lon"`
}

Node represents OSM node type.

type Point

type Point struct {
	Lat float64 `json:"lat"`
	Lon float64 `json:"lon"`
}

type QueryBuilder

type QueryBuilder struct {
	// contains filtered or unexported fields
}

QueryBuilder provides fluent API for building Overpass QL queries.

func FindAmenity

func FindAmenity(south, west, north, east float64, amenityType string) *QueryBuilder

FindAmenity creates query for amenity type in bounding box.

func FindByTag

func FindByTag(south, west, north, east float64, key, value string) *QueryBuilder

FindByTag creates query for elements with specific tag in bounding box.

func FindHighways

func FindHighways(south, west, north, east float64, highwayType string) *QueryBuilder

FindHighways creates query for highways in bounding box.

func FindRestaurants

func FindRestaurants(south, west, north, east float64) *QueryBuilder

FindRestaurants creates query for restaurants in bounding box.

func NewQueryBuilder

func NewQueryBuilder() *QueryBuilder

NewQueryBuilder creates new query builder with [out:json] default.

func (*QueryBuilder) BBox

func (qb *QueryBuilder) BBox(south, west, north, east float64) *QueryBuilder

BBox sets bounding box constraint.

func (*QueryBuilder) Build

func (qb *QueryBuilder) Build() string

Build constructs the Overpass QL query string.

func (*QueryBuilder) Node

func (qb *QueryBuilder) Node() *QueryBuilder

Node adds node element type to query.

func (*QueryBuilder) Output

func (qb *QueryBuilder) Output(mode string) *QueryBuilder

Output sets output mode (body, skel, ids, tags, meta, center, geom, bb).

func (*QueryBuilder) OutputBody

func (qb *QueryBuilder) OutputBody() *QueryBuilder

OutputBody outputs all information (default).

func (*QueryBuilder) OutputCenter

func (qb *QueryBuilder) OutputCenter() *QueryBuilder

OutputCenter outputs center point only.

func (*QueryBuilder) OutputGeom

func (qb *QueryBuilder) OutputGeom() *QueryBuilder

OutputGeom outputs with geometry (for ways/relations).

func (*QueryBuilder) OutputMeta

func (qb *QueryBuilder) OutputMeta() *QueryBuilder

OutputMeta outputs with metadata.

func (*QueryBuilder) Relation

func (qb *QueryBuilder) Relation() *QueryBuilder

Relation adds relation element type to query.

func (*QueryBuilder) String

func (qb *QueryBuilder) String() string

String implements Stringer interface.

func (*QueryBuilder) Tag

func (qb *QueryBuilder) Tag(key, value string) *QueryBuilder

Tag adds exact tag match filter.

func (*QueryBuilder) TagExists

func (qb *QueryBuilder) TagExists(key string) *QueryBuilder

TagExists adds filter for tag existence (any value).

func (*QueryBuilder) TagNot

func (qb *QueryBuilder) TagNot(key, value string) *QueryBuilder

TagNot adds negative tag match filter.

func (*QueryBuilder) TagRegex

func (qb *QueryBuilder) TagRegex(key, pattern string) *QueryBuilder

TagRegex adds regex tag value filter.

func (*QueryBuilder) Timeout

func (qb *QueryBuilder) Timeout(seconds int) *QueryBuilder

Timeout sets query timeout in seconds.

func (*QueryBuilder) Way

func (qb *QueryBuilder) Way() *QueryBuilder

Way adds way element type to query.

type Relation

type Relation struct {
	Meta

	Members []RelationMember `json:"members,omitempty"`
	Bounds  *Box             `json:"bounds,omitempty"`
}

Relation represents OSM relation type.

type RelationMember

type RelationMember struct {
	Type     ElementType `json:"type"`
	Node     *Node       `json:"node,omitempty"`
	Way      *Way        `json:"way,omitempty"`
	Relation *Relation   `json:"relation,omitempty"`
	Role     string      `json:"role,omitempty"`
}

RelationMember represents OSM relation member type.

type Result

type Result struct {
	Timestamp time.Time           `json:"timestamp"`
	Count     int                 `json:"count"`
	Nodes     map[int64]*Node     `json:"nodes,omitempty"`
	Ways      map[int64]*Way      `json:"ways,omitempty"`
	Relations map[int64]*Relation `json:"relations,omitempty"`
}

Result returned by Query and contains parsed result of Overpass query.

func Query

func Query(query string) (Result, error)

Query is deprecated: use QueryContext instead. It runs query with default client using context.Background().

func QueryContext

func QueryContext(ctx context.Context, query string) (Result, error)

QueryContext runs query with context using default client.

func QueryWithBuilder

func QueryWithBuilder(ctx context.Context, builder *QueryBuilder) (Result, error)

QueryWithBuilder executes query from builder using DefaultClient.

type RetryConfig

type RetryConfig struct {
	MaxRetries        int           // Maximum retry attempts (default: 3)
	InitialBackoff    time.Duration // Initial backoff duration (default: 1s)
	MaxBackoff        time.Duration // Maximum backoff duration (default: 30s)
	BackoffMultiplier float64       // Backoff multiplier (default: 2.0)
	Jitter            bool          // Add randomization to prevent thundering herd (default: true)
}

RetryConfig holds retry behavior configuration.

func DefaultRetryConfig

func DefaultRetryConfig() RetryConfig

DefaultRetryConfig returns sensible defaults.

type ServerError

type ServerError struct {
	StatusCode int
	Body       []byte
}

func (*ServerError) Error

func (e *ServerError) Error() string

type TagFilter

type TagFilter struct {
	Key      string
	Value    string
	Operator string // "=", "!=", "~", "exists"
}

TagFilter represents OSM tag filtering.

type Way

type Way struct {
	Meta

	Nodes    []*Node `json:"nodes,omitempty"`
	Bounds   *Box    `json:"bounds,omitempty"`
	Geometry []Point `json:"geometry,omitempty"`
}

Way represents OSM way type.

Directories

Path Synopsis
example
area_query command
basic command
custom_client command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL