module TownLookup
Shared Australian town resolution — catalog, fuzzy match, then Geocoder fallback.
Constants
- AU_STATE_NAMES
- EXTRA_TOWNS
- FUZZY_MATCH_MAX_DISTANCE
- FUZZY_MIN_WORD_LENGTH
- USAGE_MESSAGE
Public Instance Methods
Source
# File lib/town_lookup.rb, line 128 def australian_location?(result) if result.respond_to?(:country_code) && result.country_code.to_s.strip.downcase == "au" return true end result.country.to_s.downcase.include?("australia") end
Source
# File lib/town_lookup.rb, line 93 def build_catalog_result(meta, state_hint, source:, corrected_from: nil) state = state_hint || meta[:state] result = { town: meta[:label], search_town: WhatsonTowns.search_label(meta[:label], state), state: state, region: meta[:region], silent: meta[:silent] || false, disambiguate: meta[:disambiguate] && state_hint.nil?, source: source } result[:corrected_from] = corrected_from if corrected_from result end
Source
# File lib/town_lookup.rb, line 79 def catalog_entry(key) meta = WhatsonTowns::QUEENSLAND_TOWNS[key] return meta if meta extra = EXTRA_TOWNS[key] return nil unless extra { label: extra[:label], state: extra[:state], region: extra[:region] }.compact end
Source
# File lib/town_lookup.rb, line 72 def catalog_keys @catalog_keys ||= begin keys = WhatsonTowns::QUEENSLAND_TOWNS.keys + EXTRA_TOWNS.keys keys.uniq end end
Source
# File lib/town_lookup.rb, line 150 def fuzzy_match(key) query = key.to_s.strip.downcase return nil if query.length < FUZZY_MIN_WORD_LENGTH best = nil catalog_keys.each do |candidate_key| distance = levenshtein_distance(query, candidate_key) next if distance > FUZZY_MATCH_MAX_DISTANCE next if best && distance >= best[:distance] best = { key: candidate_key, distance: distance } end best end
Source
# File lib/town_lookup.rb, line 108 def geocode_town(town_text, lookup_place:) query = "#{town_text}, Australia" result = lookup_place.call(query) return nil unless result return nil unless australian_location?(result) city = result.city.to_s.strip city = TextUtil.title_words(town_text) if city.empty? state = state_from_result(result) search_town = state ? WhatsonTowns.search_label(city, state) : city { town: city, search_town: search_town, state: state, source: :geocoder, silent: false } end
Source
# File lib/town_lookup.rb, line 165 def levenshtein_distance(a, b) left = a.to_s right = b.to_s return right.length if left.empty? return left.length if right.empty? rows = left.length + 1 cols = right.length + 1 matrix = Array.new(rows) { Array.new(cols, 0) } (0...rows).each { |i| matrix[i][0] = i } (0...cols).each { |j| matrix[0][j] = j } (1...rows).each do |i| (1...cols).each do |j| cost = left[i - 1] == right[j - 1] ? 0 : 1 matrix[i][j] = [ matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost ].min end end matrix[left.length][right.length] end
Source
# File lib/town_lookup.rb, line 146 def normalize_key(text) WhatsonTowns.normalize_key(text) end
Source
# File lib/town_lookup.rb, line 43 def resolve(text, lookup_place: GeoLookup.method(:lookup_place)) town = text.to_s.strip return { error: USAGE_MESSAGE } if town.empty? town_text, state_hint = WhatsonTowns.split_state_hint(town) key = normalize_key(town_text) meta = catalog_entry(key) return build_catalog_result(meta, state_hint, source: :catalog) if meta fuzzy = fuzzy_match(key) if fuzzy meta = catalog_entry(fuzzy[:key]) return build_catalog_result(meta, state_hint, source: :fuzzy, corrected_from: town_text) if meta end geocoded = geocode_town(town_text, lookup_place: lookup_place) return geocoded if geocoded whatson = WhatsonTowns.resolve(town) return whatson.merge(source: :passthrough) unless whatson[:error] { town: town, search_town: town, source: :passthrough, silent: false } end
Source
# File lib/town_lookup.rb, line 136 def state_from_result(result) code = result.state_code.to_s.strip.downcase if result.respond_to?(:state_code) return AU_STATE_NAMES[code] if code && AU_STATE_NAMES.key?(code) state = result.state.to_s.strip if result.respond_to?(:state) return state unless state.nil? || state.empty? nil end