# frozen_string_literal: true module Underpass module QL # DSL for building Overpass QL queries programmatically. # # @example Query for restaurants # builder = Underpass::QL::Builder.new # builder.node('amenity' => 'restaurant') # Underpass::QL::Query.perform(bbox, builder) # # @example Proximity search with around # builder = Underpass::QL::Builder.new # builder.node('amenity' => 'cafe').around(500, 44.4268, 26.1025) class Builder # Creates a new empty builder. def initialize @statements = [] @around = nil end # Adds a node query statement. # # @param tags [Hash{String => String}] tag filters # @return [self] for method chaining def node( = {}) @statements << build_statement('node', ) self end # Adds a way query statement. # # @param tags [Hash{String => String}] tag filters # @return [self] for method chaining def way( = {}) @statements << build_statement('way', ) self end # Adds a relation query statement. # # @param tags [Hash{String => String}] tag filters # @return [self] for method chaining def relation( = {}) @statements << build_statement('relation', ) self end # Adds a node/way/relation (nwr) query statement. # # @param tags [Hash{String => String}] tag filters # @return [self] for method chaining def nwr( = {}) @statements << build_statement('nwr', ) self end # Sets a proximity filter for all statements. # # @param radius [Numeric] search radius in meters # @param lat_or_point [Numeric, RGeo::Feature::Point] latitude or an RGeo point # @param lon [Numeric, nil] longitude (required when +lat_or_point+ is numeric) # @return [self] for method chaining def around(radius, lat_or_point, lon = nil) @around = if lat_or_point.respond_to?(:y) { radius: radius, lat: lat_or_point.y, lon: lat_or_point.x } else { radius: radius, lat: lat_or_point, lon: lon } end self end # Converts the builder into an Overpass QL query string. # # @return [String] the Overpass QL query def to_ql if @around @statements.map { |s| append_around(s) }.join("\n") else @statements.join("\n") end end private def build_statement(type, ) tag_filters = .map { |k, v| "[\"#{k}\"=\"#{v}\"]" }.join "#{type}#{tag_filters};" end def append_around(statement) around_filter = "(around:#{@around[:radius]},#{@around[:lat]},#{@around[:lon]})" statement.sub(';', "#{around_filter};") end end end end