refactor: enhance Overpass API handling with multiple endpoints and improved error reporting
CI / check (push) Successful in 19s
CI / deploy (push) Successful in 12s

This commit is contained in:
randy committed 2026-10-09 22:07:45 +02:00
1 parent 33be95ef59
commit efddc55ad0
2 files changed
+29 -5

No files matched your search

+22 -3
View File
@@ -12,7 +12,15 @@ module Maptool
module OverpassPois module OverpassPois
class FetchError < StandardError; end class FetchError < StandardError; end
OVERPASS_API = "https://overpass-api.de/api/interpreter".freeze # Same three public mirrors public/js/hiking_trails.js races — tried in
# turn here instead, falling through to the next on any failure: the
# shared free instances regularly 504/429 under load, and a plugin run
# is a one-off click, not worth threads to race them.
OVERPASS_APIS = [
"https://overpass-api.de/api/interpreter",
"https://overpass.kumi.systems/api/interpreter",
"https://overpass.private.coffee/api/interpreter",
].freeze
# Each entry: an Overpass tag filter -> { label:, icon:, color: }, used # Each entry: an Overpass tag filter -> { label:, icon:, color: }, used
# both to build the query (unioning every filter) and to label/style # both to build the query (unioning every filter) and to label/style
@@ -53,12 +61,23 @@ module Maptool
# bbox: [south, west, north, east] (Overpass's own axis order). # bbox: [south, west, north, east] (Overpass's own axis order).
def self.fetch(bbox, timeout: 60) def self.fetch(bbox, timeout: 60)
query = build_query(bbox, timeout: timeout) query = build_query(bbox, timeout: timeout)
uri = URI(OVERPASS_API) errors = OVERPASS_APIS.map do |url|
return post(URI(url), query, timeout)
rescue FetchError, Net::OpenTimeout, Net::ReadTimeout, SystemCallError, SocketError, OpenSSL::SSL::SSLError, JSON::ParserError => e
"#{URI(url).host}: #{e.message}"
end
raise FetchError, errors.join("; ")
end
def self.post(uri, query, timeout)
# overpass-api.de answers Net::HTTP's default "Ruby" User-Agent with # overpass-api.de answers Net::HTTP's default "Ruby" User-Agent with
# a 406 (verified 2026-10-09) — any identifying UA gets through. # a 406 (verified 2026-10-09) — any identifying UA gets through.
request = Net::HTTP::Post.new(uri, "User-Agent" => "maptool (Overpass camper POIs plugin)") request = Net::HTTP::Post.new(uri, "User-Agent" => "maptool (Overpass camper POIs plugin)")
request.set_form_data("data" => query) request.set_form_data("data" => query)
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) } # read_timeout past the query's own [timeout:] — Net::HTTP's default
# 60s equals it, so a big viewport could hit Net::ReadTimeout just as
# Overpass was about to answer (a country-sized bbox measured ~57s).
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: timeout + 30) { |http| http.request(request) }
raise FetchError, "unexpected response #{response.code}" unless response.is_a?(Net::HTTPSuccess) raise FetchError, "unexpected response #{response.code}" unless response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body) JSON.parse(response.body)
+7 -2
View File
@@ -65,8 +65,13 @@ module Maptool
south, west, north, east = bbox south, west, north, east = bbox
args = ["--south=#{south}", "--west=#{west}", "--north=#{north}", "--east=#{east}"] args = ["--south=#{south}", "--west=#{west}", "--north=#{north}", "--east=#{east}"]
stdout, _stderr, status = Open3.capture3(command, *args, chdir: plugin_dir) stdout, stderr, status = Open3.capture3(command, *args, chdir: plugin_dir)
raise RunError, "plugin exited with status #{status.exitstatus}" unless status.success? # First stderr line too — for a Ruby plugin that's the exception
# message (e.g. "...: unexpected response 406"), the only clue to why.
unless status.success?
reason = stderr.lines.first&.strip
raise RunError, ["plugin exited with status #{status.exitstatus}", reason].compact.reject(&:empty?).join(": ")
end
geojson = JSON.parse(stdout) geojson = JSON.parse(stdout)
raise RunError, "plugin did not print a GeoJSON FeatureCollection" unless geojson.is_a?(Hash) && geojson["type"] == "FeatureCollection" raise RunError, "plugin did not print a GeoJSON FeatureCollection" unless geojson.is_a?(Hash) && geojson["type"] == "FeatureCollection"