underpass 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +82 -13
- data/lib/underpass/client.rb +20 -8
- data/lib/underpass/configuration.rb +5 -0
- data/lib/underpass/error_parser.rb +140 -0
- data/lib/underpass/errors.rb +62 -1
- data/lib/underpass/ql/query.rb +19 -0
- data/lib/underpass/version.rb +1 -1
- data/lib/underpass.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e5920d16962c26dd7711747653a0ce891250e870d2a675f51402354306a3e93
|
|
4
|
+
data.tar.gz: 1770e8e1e320916d9a53673df975ec1a7293d0d1064bdb0d68c2ab50cd168aeb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f67e73666224ec23cf123b2803438a4b893359039da09fd9209506dea49b2b8685b04a8287955bc2c811224d786a9ca17be7ebcd22fc64242000147d10fe0ae8
|
|
7
|
+
data.tar.gz: dcbf032eae5a06497068f36861073dfa846d889c4bbab7681db81127a878824a70edb43dffb022bcae188afb8a40b2bea7deae6d3be9fc4cd429246fdf2e8fc4
|
data/README.md
CHANGED
|
@@ -34,25 +34,26 @@ WKT
|
|
|
34
34
|
bbox = RGeo::Geographic.spherical_factory.parse_wkt(wkt)
|
|
35
35
|
|
|
36
36
|
# Query using raw Overpass QL
|
|
37
|
-
query = 'way["heritage:operator"="lmi"]["
|
|
37
|
+
query = 'way["heritage:operator"="lmi"]["heritage"="2"];'
|
|
38
38
|
features = Underpass::QL::Query.perform(bbox, query)
|
|
39
39
|
|
|
40
40
|
# Each result is a Feature with geometry and OSM tags
|
|
41
41
|
features.each do |f|
|
|
42
42
|
puts f.geometry.as_text # => "POLYGON ((...)"
|
|
43
|
-
puts f.properties[:name] # => "
|
|
44
|
-
puts f.id # =>
|
|
43
|
+
puts f.properties[:name] # => "Biserica Romano-Catolică"
|
|
44
|
+
puts f.id # => 186213580
|
|
45
45
|
puts f.type # => "way"
|
|
46
46
|
end
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
See [more usage examples](usage-examples.md).
|
|
49
|
+
See [more usage examples](docs/usage-examples.md).
|
|
50
50
|
|
|
51
|
-
For comprehensive examples with real data covering all return types and functionality, see the [usage-examples.md](usage-examples.md) file which includes examples for:
|
|
51
|
+
For comprehensive examples with real data covering all return types and functionality, see the [usage-examples.md](docs/usage-examples.md) file which includes examples for:
|
|
52
52
|
- Node queries (Point geometries) - restaurants, cafes, etc.
|
|
53
53
|
- Way queries (LineString/Polygon geometries) - roads, buildings, parks
|
|
54
54
|
- Relation queries (MultiPolygon/MultiLineString geometries) - lakes, bus routes
|
|
55
55
|
- Area queries using `perform_in_area`
|
|
56
|
+
- Raw queries using `perform_raw` with inline bounding boxes
|
|
56
57
|
- Around queries for proximity search
|
|
57
58
|
- Builder DSL for constructing queries
|
|
58
59
|
- Post-query filtering
|
|
@@ -90,9 +91,9 @@ query = Underpass::QL::Builder.new
|
|
|
90
91
|
|
|
91
92
|
# Multiple tag filters
|
|
92
93
|
query = Underpass::QL::Builder.new
|
|
93
|
-
.way('heritage:operator': 'lmi',
|
|
94
|
+
.way('heritage:operator': 'lmi', heritage: '2')
|
|
94
95
|
.to_ql
|
|
95
|
-
# => 'way["heritage:operator"="lmi"]["
|
|
96
|
+
# => 'way["heritage:operator"="lmi"]["heritage"="2"];'
|
|
96
97
|
|
|
97
98
|
# nwr (node/way/relation) shorthand
|
|
98
99
|
query = Underpass::QL::Builder.new
|
|
@@ -105,6 +106,27 @@ builder = Underpass::QL::Builder.new.way(building: 'yes')
|
|
|
105
106
|
features = Underpass::QL::Query.perform(bbox, builder)
|
|
106
107
|
```
|
|
107
108
|
|
|
109
|
+
### Bounding Box Queries with Builder DSL
|
|
110
|
+
|
|
111
|
+
The Builder DSL is designed to work with `Query.perform`. Define a bounding box
|
|
112
|
+
as an RGeo geometry, build your query with the DSL, and pass both to `perform`:
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
# Define a bounding box
|
|
116
|
+
wkt = 'POLYGON ((26.08 44.42, 26.12 44.42, 26.12 44.45, 26.08 44.45, 26.08 44.42))'
|
|
117
|
+
bbox = RGeo::Geographic.spherical_factory.parse_wkt(wkt)
|
|
118
|
+
|
|
119
|
+
# Build the query
|
|
120
|
+
builder = Underpass::QL::Builder.new.node(amenity: 'cafe')
|
|
121
|
+
|
|
122
|
+
# Execute — the bounding box constrains results spatially
|
|
123
|
+
cafes = Underpass::QL::Query.perform(bbox, builder)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Note: The Builder DSL generates the Overpass QL query body (e.g. `node["amenity"="cafe"];`),
|
|
127
|
+
while the bounding box is applied as a separate spatial constraint by `Query.perform`.
|
|
128
|
+
You can inspect the generated query with `builder.to_ql`.
|
|
129
|
+
|
|
108
130
|
## Proximity Queries (Around)
|
|
109
131
|
|
|
110
132
|
Find elements within a radius (in meters) of a point:
|
|
@@ -158,6 +180,21 @@ builder = Underpass::QL::Builder.new.node(amenity: 'restaurant')
|
|
|
158
180
|
features = Underpass::QL::Query.perform_in_area('Romania', builder)
|
|
159
181
|
```
|
|
160
182
|
|
|
183
|
+
## Raw Queries (Inline Bounding Box)
|
|
184
|
+
|
|
185
|
+
When your query already contains its own bounding box (or other spatial filters)
|
|
186
|
+
inline, use `perform_raw` to skip the global bounding box:
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
query = 'node["name"="Peak"]["natural"="peak"](47.0,25.0,47.1,25.1);'
|
|
190
|
+
features = Underpass::QL::Query.perform_raw(query)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The query body is wrapped in the standard Overpass request template (output format,
|
|
194
|
+
timeout, recurse) but no global bounding box is added. This is useful when you
|
|
195
|
+
construct queries with element-level bounding boxes or other spatial constraints
|
|
196
|
+
that don't fit the `perform` / `perform_in_area` patterns.
|
|
197
|
+
|
|
161
198
|
## Relation Support
|
|
162
199
|
|
|
163
200
|
### Multipolygon Relations
|
|
@@ -334,6 +371,16 @@ Underpass.configure do |c|
|
|
|
334
371
|
end
|
|
335
372
|
```
|
|
336
373
|
|
|
374
|
+
### Custom Max Retries
|
|
375
|
+
|
|
376
|
+
Change the maximum number of retry attempts for rate limiting and timeout errors (default: 3):
|
|
377
|
+
|
|
378
|
+
```ruby
|
|
379
|
+
Underpass.configure do |c|
|
|
380
|
+
c.max_retries = 5
|
|
381
|
+
end
|
|
382
|
+
```
|
|
383
|
+
|
|
337
384
|
### Reset Configuration
|
|
338
385
|
|
|
339
386
|
```ruby
|
|
@@ -344,24 +391,46 @@ Underpass.reset_configuration!
|
|
|
344
391
|
|
|
345
392
|
The client automatically retries on transient errors with exponential backoff:
|
|
346
393
|
|
|
347
|
-
- **HTTP 429** (rate limited) -- retries up to
|
|
348
|
-
- **HTTP 504** (gateway timeout) -- retries up to
|
|
394
|
+
- **HTTP 429** (rate limited) -- retries up to `max_retries` times (default: 3), then raises `Underpass::RateLimitError`
|
|
395
|
+
- **HTTP 504** (gateway timeout) -- retries up to `max_retries` times (default: 3), then raises `Underpass::TimeoutError`
|
|
349
396
|
- **Other errors** -- raises `Underpass::ApiError` immediately
|
|
350
397
|
|
|
351
398
|
All errors inherit from `Underpass::Error`, which inherits from `StandardError`.
|
|
352
399
|
|
|
400
|
+
### Structured Error Data
|
|
401
|
+
|
|
402
|
+
Errors include structured data parsed from Overpass API responses:
|
|
403
|
+
|
|
353
404
|
```ruby
|
|
354
405
|
begin
|
|
355
406
|
features = Underpass::QL::Query.perform(bbox, query)
|
|
407
|
+
rescue Underpass::TimeoutError => e
|
|
408
|
+
e.code # => "timeout"
|
|
409
|
+
e.error_message # => "Query timed out in \"query\" at line 3 after 25 seconds."
|
|
410
|
+
e.details # => { line: 3, timeout_seconds: 25 }
|
|
411
|
+
e.http_status # => 504
|
|
412
|
+
|
|
413
|
+
# Convert to hash or JSON for logging/APIs
|
|
414
|
+
e.to_h # => { code: "timeout", message: "...", details: {...} }
|
|
415
|
+
e.to_json # => JSON string
|
|
356
416
|
rescue Underpass::RateLimitError
|
|
357
417
|
puts "Rate limited by the Overpass API, try again later"
|
|
358
|
-
rescue Underpass::TimeoutError
|
|
359
|
-
puts "Query timed out, try a smaller bounding box"
|
|
360
418
|
rescue Underpass::ApiError => e
|
|
361
|
-
puts "API error: #{e.
|
|
419
|
+
puts "API error (#{e.code}): #{e.error_message}"
|
|
362
420
|
end
|
|
363
421
|
```
|
|
364
422
|
|
|
423
|
+
### Error Codes
|
|
424
|
+
|
|
425
|
+
| Code | Description | HTTP Status |
|
|
426
|
+
|------|-------------|-------------|
|
|
427
|
+
| `timeout` | Query exceeded time limit | 504 |
|
|
428
|
+
| `memory` | Query exceeded memory limit | 504 |
|
|
429
|
+
| `rate_limit` | Too many requests | 429 |
|
|
430
|
+
| `syntax` | Query syntax error | 400 |
|
|
431
|
+
| `runtime` | Other runtime errors | varies |
|
|
432
|
+
| `unknown` | Unparseable error | varies |
|
|
433
|
+
|
|
365
434
|
## Response Caching
|
|
366
435
|
|
|
367
436
|
Enable in-memory caching to avoid redundant API calls during development:
|
|
@@ -409,7 +478,7 @@ Have a look at the [issue tracker](https://github.com/haiafara/underpass/issues)
|
|
|
409
478
|
|
|
410
479
|
## Comprehensive Examples
|
|
411
480
|
|
|
412
|
-
For detailed, working examples with real data that cover all return types and functionality of the library, see the [usage-examples.md](usage-examples.md) file. These examples demonstrate:
|
|
481
|
+
For detailed, working examples with real data that cover all return types and functionality of the library, see the [usage-examples.md](docs/usage-examples.md) file. These examples demonstrate:
|
|
413
482
|
|
|
414
483
|
- **Node queries** (Point geometries) - restaurants, cafes, bus stops
|
|
415
484
|
- **Way queries** (LineString geometries) - primary roads, highways
|
data/lib/underpass/client.rb
CHANGED
|
@@ -10,20 +10,18 @@ module Underpass
|
|
|
10
10
|
# Handles caching of responses and automatic retries with exponential
|
|
11
11
|
# backoff for rate limiting (429) and timeout (504) responses.
|
|
12
12
|
class Client
|
|
13
|
-
# @return [Integer] default maximum number of retries
|
|
14
|
-
MAX_RETRIES = 3
|
|
15
|
-
|
|
16
13
|
# Performs the API request with automatic retries for rate limiting and timeouts.
|
|
17
14
|
#
|
|
18
15
|
# Results are cached when a {Cache} instance is configured via {Underpass.cache}.
|
|
19
16
|
#
|
|
20
17
|
# @param request [QL::Request] the prepared Overpass query request
|
|
21
|
-
# @param max_retries [Integer] maximum number of retry attempts
|
|
18
|
+
# @param max_retries [Integer] maximum number of retry attempts (defaults to configured value)
|
|
22
19
|
# @return [Net::HTTPResponse] the API response
|
|
23
20
|
# @raise [RateLimitError] when rate limited after exhausting retries
|
|
24
21
|
# @raise [TimeoutError] when the API times out after exhausting retries
|
|
25
22
|
# @raise [ApiError] when the API returns an unexpected error
|
|
26
|
-
def self.perform(request, max_retries:
|
|
23
|
+
def self.perform(request, max_retries: nil)
|
|
24
|
+
max_retries = Underpass.configuration.max_retries if max_retries.nil?
|
|
27
25
|
cache_key = Digest::SHA256.hexdigest(request.to_query)
|
|
28
26
|
cached = Underpass.cache&.fetch(cache_key)
|
|
29
27
|
return cached if cached
|
|
@@ -54,14 +52,28 @@ module Underpass
|
|
|
54
52
|
private_class_method :post_request
|
|
55
53
|
|
|
56
54
|
def self.handle_error(response, retries, max_retries)
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
status_code = response.code.to_i
|
|
56
|
+
error_class = { 429 => RateLimitError, 504 => TimeoutError }[status_code] || ApiError
|
|
57
|
+
|
|
58
|
+
raise build_error(error_class, response.body, status_code) if error_class == ApiError
|
|
59
59
|
|
|
60
60
|
retries += 1
|
|
61
|
-
raise error_class,
|
|
61
|
+
raise build_error(error_class, response.body, status_code) if retries > max_retries
|
|
62
62
|
|
|
63
63
|
retries
|
|
64
64
|
end
|
|
65
65
|
private_class_method :handle_error
|
|
66
|
+
|
|
67
|
+
def self.build_error(error_class, body, status_code)
|
|
68
|
+
parsed = ErrorParser.parse(body, status_code)
|
|
69
|
+
error_class.new(
|
|
70
|
+
parsed[:message],
|
|
71
|
+
code: parsed[:code],
|
|
72
|
+
error_message: parsed[:message],
|
|
73
|
+
details: parsed[:details],
|
|
74
|
+
http_status: status_code
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
private_class_method :build_error
|
|
66
78
|
end
|
|
67
79
|
end
|
|
@@ -7,6 +7,7 @@ module Underpass
|
|
|
7
7
|
# Underpass.configure do |config|
|
|
8
8
|
# config.api_endpoint = 'https://overpass.kumi.systems/api/interpreter'
|
|
9
9
|
# config.timeout = 30
|
|
10
|
+
# config.max_retries = 5
|
|
10
11
|
# end
|
|
11
12
|
class Configuration
|
|
12
13
|
# @return [String] the Overpass API endpoint URL
|
|
@@ -15,9 +16,13 @@ module Underpass
|
|
|
15
16
|
# @return [Integer] the query timeout in seconds
|
|
16
17
|
attr_accessor :timeout
|
|
17
18
|
|
|
19
|
+
# @return [Integer] maximum number of retry attempts for rate limiting and timeouts
|
|
20
|
+
attr_accessor :max_retries
|
|
21
|
+
|
|
18
22
|
def initialize
|
|
19
23
|
@api_endpoint = 'https://overpass-api.de/api/interpreter'
|
|
20
24
|
@timeout = 25
|
|
25
|
+
@max_retries = 3
|
|
21
26
|
end
|
|
22
27
|
end
|
|
23
28
|
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Underpass
|
|
4
|
+
# Parses HTML error responses from the Overpass API into structured data.
|
|
5
|
+
#
|
|
6
|
+
# The Overpass API returns HTML error pages when queries fail. This class
|
|
7
|
+
# extracts error information from those responses and returns structured
|
|
8
|
+
# hashes with code, message, and details.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# result = ErrorParser.parse("<html>...runtime error: Query timed out...</html>", 504)
|
|
12
|
+
# result[:code] # => "timeout"
|
|
13
|
+
# result[:message] # => "Query timed out in \"query\" at line 3 after 25 seconds."
|
|
14
|
+
# result[:details] # => { line: 3, timeout_seconds: 25 }
|
|
15
|
+
class ErrorParser
|
|
16
|
+
# Known error patterns from Overpass API responses
|
|
17
|
+
PATTERNS = {
|
|
18
|
+
timeout: /Query timed out.*?at line (\d+) after (\d+) seconds/i,
|
|
19
|
+
memory: /Query run out of memory.*?(\d+)\s*MB/i,
|
|
20
|
+
syntax: /parse error:?\s*(.+)/i,
|
|
21
|
+
runtime: /runtime error:?\s*(.+)/i
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
# Parses an error response body and returns structured error data.
|
|
25
|
+
#
|
|
26
|
+
# @param response_body [String] the raw response body (usually HTML)
|
|
27
|
+
# @param status_code [Integer] the HTTP status code
|
|
28
|
+
# @return [Hash] structured error data with :code, :message, and :details keys
|
|
29
|
+
def self.parse(response_body, status_code)
|
|
30
|
+
return rate_limit_result if status_code == 429
|
|
31
|
+
|
|
32
|
+
text = extract_error_text(response_body)
|
|
33
|
+
parse_error_text(text, status_code)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Extracts error text from HTML or returns the body as-is.
|
|
37
|
+
#
|
|
38
|
+
# @param body [String] the response body
|
|
39
|
+
# @return [String] the extracted error text
|
|
40
|
+
def self.extract_error_text(body)
|
|
41
|
+
return '' if body.nil? || body.empty?
|
|
42
|
+
|
|
43
|
+
# Try to extract text from <strong> tags (common Overpass error format)
|
|
44
|
+
if (match = body.match(%r{<strong[^>]*>(.*?)</strong>}im))
|
|
45
|
+
return match[1].gsub(/<[^>]+>/, '').strip
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Try to extract from <p> tags
|
|
49
|
+
if (match = body.match(%r{<p[^>]*>(.*?)</p>}im))
|
|
50
|
+
return match[1].gsub(/<[^>]+>/, '').strip
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Fall back to stripping all HTML tags
|
|
54
|
+
body.gsub(/<[^>]+>/, ' ').gsub(/\s+/, ' ').strip
|
|
55
|
+
end
|
|
56
|
+
private_class_method :extract_error_text
|
|
57
|
+
|
|
58
|
+
# Parses the error text against known patterns.
|
|
59
|
+
#
|
|
60
|
+
# @param text [String] the extracted error text
|
|
61
|
+
# @param status_code [Integer] the HTTP status code
|
|
62
|
+
# @return [Hash] structured error data
|
|
63
|
+
def self.parse_error_text(text, status_code)
|
|
64
|
+
parse_timeout(text) ||
|
|
65
|
+
parse_memory(text) ||
|
|
66
|
+
parse_syntax(text) ||
|
|
67
|
+
parse_runtime(text) ||
|
|
68
|
+
unknown_result(text, status_code)
|
|
69
|
+
end
|
|
70
|
+
private_class_method :parse_error_text
|
|
71
|
+
|
|
72
|
+
def self.parse_timeout(text)
|
|
73
|
+
return unless (match = text.match(PATTERNS[:timeout]))
|
|
74
|
+
|
|
75
|
+
{ code: 'timeout', message: text, details: { line: match[1].to_i, timeout_seconds: match[2].to_i } }
|
|
76
|
+
end
|
|
77
|
+
private_class_method :parse_timeout
|
|
78
|
+
|
|
79
|
+
def self.parse_memory(text)
|
|
80
|
+
return unless (match = text.match(PATTERNS[:memory]))
|
|
81
|
+
|
|
82
|
+
{ code: 'memory', message: text, details: { memory_mb: match[1].to_i } }
|
|
83
|
+
end
|
|
84
|
+
private_class_method :parse_memory
|
|
85
|
+
|
|
86
|
+
def self.parse_syntax(text)
|
|
87
|
+
return unless (match = text.match(PATTERNS[:syntax]))
|
|
88
|
+
|
|
89
|
+
{ code: 'syntax', message: match[1].strip, details: extract_syntax_details(match[1]) }
|
|
90
|
+
end
|
|
91
|
+
private_class_method :parse_syntax
|
|
92
|
+
|
|
93
|
+
def self.parse_runtime(text)
|
|
94
|
+
return unless (match = text.match(PATTERNS[:runtime]))
|
|
95
|
+
|
|
96
|
+
{ code: 'runtime', message: match[1].strip, details: {} }
|
|
97
|
+
end
|
|
98
|
+
private_class_method :parse_runtime
|
|
99
|
+
|
|
100
|
+
# Extracts line number from syntax error messages if present.
|
|
101
|
+
#
|
|
102
|
+
# @param message [String] the error message
|
|
103
|
+
# @return [Hash] details hash with line number if found
|
|
104
|
+
def self.extract_syntax_details(message)
|
|
105
|
+
if (match = message.match(/line\s+(\d+)/i))
|
|
106
|
+
{ line: match[1].to_i }
|
|
107
|
+
else
|
|
108
|
+
{}
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
private_class_method :extract_syntax_details
|
|
112
|
+
|
|
113
|
+
# Returns a rate limit error result.
|
|
114
|
+
#
|
|
115
|
+
# @return [Hash] structured error data for rate limiting
|
|
116
|
+
def self.rate_limit_result
|
|
117
|
+
{
|
|
118
|
+
code: 'rate_limit',
|
|
119
|
+
message: 'Rate limited by the Overpass API',
|
|
120
|
+
details: {}
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
private_class_method :rate_limit_result
|
|
124
|
+
|
|
125
|
+
# Returns an unknown error result.
|
|
126
|
+
#
|
|
127
|
+
# @param text [String] the error text
|
|
128
|
+
# @param status_code [Integer] the HTTP status code
|
|
129
|
+
# @return [Hash] structured error data for unknown errors
|
|
130
|
+
def self.unknown_result(text, status_code)
|
|
131
|
+
message = text.empty? ? "HTTP #{status_code} error" : text
|
|
132
|
+
{
|
|
133
|
+
code: 'unknown',
|
|
134
|
+
message: message,
|
|
135
|
+
details: {}
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
private_class_method :unknown_result
|
|
139
|
+
end
|
|
140
|
+
end
|
data/lib/underpass/errors.rb
CHANGED
|
@@ -1,8 +1,69 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
3
5
|
module Underpass
|
|
4
6
|
# Base error class for all Underpass errors.
|
|
5
|
-
|
|
7
|
+
#
|
|
8
|
+
# Provides structured error data parsed from Overpass API responses.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# begin
|
|
12
|
+
# features = Underpass::QL::Query.perform(bbox, query)
|
|
13
|
+
# rescue Underpass::Error => e
|
|
14
|
+
# e.code # => "timeout"
|
|
15
|
+
# e.error_message # => "Query timed out..."
|
|
16
|
+
# e.details # => { line: 3, timeout_seconds: 25 }
|
|
17
|
+
# e.http_status # => 504
|
|
18
|
+
# e.to_h # => { code: "timeout", message: "...", details: {...} }
|
|
19
|
+
# end
|
|
20
|
+
class Error < StandardError
|
|
21
|
+
# @return [String, nil] the error code (e.g., "timeout", "memory", "syntax")
|
|
22
|
+
attr_reader :code
|
|
23
|
+
|
|
24
|
+
# @return [String, nil] the human-readable error message
|
|
25
|
+
attr_reader :error_message
|
|
26
|
+
|
|
27
|
+
# @return [Hash] additional error details (varies by error type)
|
|
28
|
+
attr_reader :details
|
|
29
|
+
|
|
30
|
+
# @return [Integer, nil] the HTTP status code from the API response
|
|
31
|
+
attr_reader :http_status
|
|
32
|
+
|
|
33
|
+
# Creates a new error with optional structured data.
|
|
34
|
+
#
|
|
35
|
+
# @param message [String, nil] the error message (used by StandardError)
|
|
36
|
+
# @param code [String, nil] the error code
|
|
37
|
+
# @param error_message [String, nil] the detailed error message
|
|
38
|
+
# @param details [Hash] additional error details
|
|
39
|
+
# @param http_status [Integer, nil] the HTTP status code
|
|
40
|
+
def initialize(message = nil, code: nil, error_message: nil, details: {}, http_status: nil)
|
|
41
|
+
@code = code
|
|
42
|
+
@error_message = error_message || message
|
|
43
|
+
@details = details || {}
|
|
44
|
+
@http_status = http_status
|
|
45
|
+
super(@error_message || message)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Returns a hash representation of the error.
|
|
49
|
+
#
|
|
50
|
+
# @return [Hash] the error as a hash with :code, :message, and :details keys
|
|
51
|
+
def to_h
|
|
52
|
+
{
|
|
53
|
+
code: code,
|
|
54
|
+
message: error_message,
|
|
55
|
+
details: details
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns a JSON representation of the error.
|
|
60
|
+
#
|
|
61
|
+
# @param args [Array] arguments passed to JSON.generate
|
|
62
|
+
# @return [String] the error as a JSON string
|
|
63
|
+
def to_json(*)
|
|
64
|
+
to_h.to_json(*)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
6
67
|
|
|
7
68
|
# Raised when the Overpass API returns a 429 rate limit response.
|
|
8
69
|
class RateLimitError < Error; end
|
data/lib/underpass/ql/query.rb
CHANGED
|
@@ -13,6 +13,10 @@ module Underpass
|
|
|
13
13
|
#
|
|
14
14
|
# @example Query with a named area
|
|
15
15
|
# features = Underpass::QL::Query.perform_in_area('Romania', 'node["place"="city"];')
|
|
16
|
+
#
|
|
17
|
+
# @example Raw query with inline bounding box
|
|
18
|
+
# query = 'node["name"="Peak"]["natural"="peak"](47.0,25.0,47.1,25.1);'
|
|
19
|
+
# features = Underpass::QL::Query.perform_raw(query)
|
|
16
20
|
class Query
|
|
17
21
|
# Queries the Overpass API within a bounding box.
|
|
18
22
|
#
|
|
@@ -43,6 +47,21 @@ module Underpass
|
|
|
43
47
|
execute(request, query_string)
|
|
44
48
|
end
|
|
45
49
|
|
|
50
|
+
# Executes a pre-built query body that includes its own inline bbox.
|
|
51
|
+
# Wraps it in the standard Request template for output format and timeout,
|
|
52
|
+
# without adding a global bounding box.
|
|
53
|
+
#
|
|
54
|
+
# @param query_body [String] Overpass QL body with inline bbox
|
|
55
|
+
# (e.g. +'node["name"="Peak"]["natural"="peak"](47.0,25.0,47.1,25.1);'+)
|
|
56
|
+
# @return [Array<Feature>] the matched features
|
|
57
|
+
# @raise [RateLimitError] when rate limited after exhausting retries
|
|
58
|
+
# @raise [TimeoutError] when the API times out after exhausting retries
|
|
59
|
+
# @raise [ApiError] when the API returns an unexpected error
|
|
60
|
+
def self.perform_raw(query_body)
|
|
61
|
+
request = Underpass::QL::Request.new(query_body, nil)
|
|
62
|
+
execute(request, query_body)
|
|
63
|
+
end
|
|
64
|
+
|
|
46
65
|
def self.resolve_query(query)
|
|
47
66
|
query.respond_to?(:to_ql) ? query.to_ql : query
|
|
48
67
|
end
|
data/lib/underpass/version.rb
CHANGED
data/lib/underpass.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: underpass
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Janos Rusiczki
|
|
@@ -50,6 +50,7 @@ files:
|
|
|
50
50
|
- lib/underpass/cache.rb
|
|
51
51
|
- lib/underpass/client.rb
|
|
52
52
|
- lib/underpass/configuration.rb
|
|
53
|
+
- lib/underpass/error_parser.rb
|
|
53
54
|
- lib/underpass/errors.rb
|
|
54
55
|
- lib/underpass/feature.rb
|
|
55
56
|
- lib/underpass/filter.rb
|