Module: HasGeoLookup::DataCoverage

Extended by:
DataCoverage
Included in:
DataCoverage
Defined in:
lib/has_geo_lookup/concern.rb

Overview

Utilities for analyzing geographic data coverage and availability

This module provides methods to check the availability of GeoBoundaries.org and Geonames.org data for different countries. These utilities are gem-ready and don't depend on any specific application models or business logic.

Examples:

Check coverage for a country

HasGeoLookup::DataCoverage.coverage_status('US')
# => {boundaries: true, geonames: true, complete: true}

Check individual data sources

HasGeoLookup::DataCoverage.has_boundary_data?('FR')   # => true
HasGeoLookup::DataCoverage.has_geonames_data?('FR')   # => false

Instance Method Summary collapse

Instance Method Details

#boundary_coverage_by_level(iso2) ⇒ Hash

Get detailed boundary coverage by ADM level for a country

Examples:

boundary_coverage_by_level('US')
# => { 'ADM1' => 51, 'ADM2' => 3142, 'ADM3' => 0, 'ADM4' => 0, 'ADM5' => 0 }

Parameters:

  • 2-letter country code

Returns:

  • Hash with ADM levels as keys and counts as values



820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
# File 'lib/has_geo_lookup/concern.rb', line 820

def boundary_coverage_by_level(iso2)
  # Special cases for territories
  territories_without_boundaries = %w[PR VI GU AS MP TC]
  if territories_without_boundaries.include?(iso2)
    return %w[ADM1 ADM2 ADM3 ADM4 ADM5].index_with { |_| 1 } # Assume coverage
  end
  
  # Convert ISO2 to ISO3
  country = Iso3166.for_code(iso2)
  return {} unless country
  
  iso3 = country.code3
  
  # Count boundaries by level for this country
  boundaries_by_level = Geoboundary.where("shape_iso LIKE ? OR shape_group LIKE ?", "%#{iso3}%", "%#{iso3}%")
                                  .group(:level)
                                  .count
  
  # Ensure all ADM levels are represented (with 0 counts for missing)
  %w[ADM1 ADM2 ADM3 ADM4 ADM5].index_with do |level|
    boundaries_by_level[level] || 0
  end
end

#coverage_status(iso2) ⇒ Hash

Get comprehensive coverage status for a country

Examples:

coverage_status('US')
# => {
#   boundaries: true,
#   geonames: true,
#   complete: false,
#   boundary_coverage: { 'ADM1' => 51, 'ADM2' => 3142, 'ADM3' => 0, 'ADM4' => 0, 'ADM5' => 0 },
#   missing_adm_levels: ['ADM3', 'ADM4', 'ADM5']
# }

Parameters:

  • 2-letter country code

Returns:

  • Coverage status with detailed boundary information



882
883
884
885
886
887
888
889
890
891
892
893
894
895
# File 'lib/has_geo_lookup/concern.rb', line 882

def coverage_status(iso2)
  boundaries = has_boundary_data?(iso2)
  geonames = has_geonames_data?(iso2)
  boundary_coverage = boundary_coverage_by_level(iso2)
  missing_levels = missing_adm_levels(iso2)
  
  {
    boundaries: boundaries,
    geonames: geonames, 
    complete: boundaries && geonames && missing_levels.empty?,
    boundary_coverage: boundary_coverage,
    missing_adm_levels: missing_levels
  }
end

#has_boundary_data?(iso2, level = nil) ⇒ Boolean

Check if boundary data exists for a country

Parameters:

  • 2-letter country code

  • (defaults to: nil)

    Optional specific ADM level to check (e.g., "ADM2")

Returns:

  • true if boundary data exists



795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
# File 'lib/has_geo_lookup/concern.rb', line 795

def has_boundary_data?(iso2, level = nil)
  # Special cases for territories that don't have separate boundary data
  territories_without_boundaries = %w[PR VI GU AS MP TC] # US territories + others
  return true if territories_without_boundaries.include?(iso2)
  
  # Convert ISO2 to ISO3 to check boundaries
  country = Iso3166.for_code(iso2)
  return true unless country # If we can't convert, assume it exists to avoid infinite loops
  
  iso3 = country.code3
  
  # Build query for boundaries with optional level filter
  query = Geoboundary.where("shape_iso LIKE ? OR shape_group LIKE ?", "%#{iso3}%", "%#{iso3}%")
  query = query.where(level: level) if level
  
  query.exists?
end

#has_geonames_data?(iso2) ⇒ Boolean

Check if geonames data exists for a country

Parameters:

  • 2-letter country code

Returns:

  • true if geonames data exists



860
861
862
863
864
865
866
867
# File 'lib/has_geo_lookup/concern.rb', line 860

def has_geonames_data?(iso2)
  # Special cases for territories that might not have separate geonames data
  territories_without_separate_geonames = %w[PR VI GU AS MP] # US territories
  return true if territories_without_separate_geonames.include?(iso2)
  
  # Check if we have geonames data for this country
  Geoname.where(country_code: iso2).exists?
end

#missing_adm_levels(iso2) ⇒ Array<String>

Get list of missing ADM levels for a country

Examples:

missing_adm_levels('US')
# => ['ADM3', 'ADM4', 'ADM5']

Parameters:

  • 2-letter country code

Returns:

  • Array of missing ADM level strings



851
852
853
854
# File 'lib/has_geo_lookup/concern.rb', line 851

def missing_adm_levels(iso2)
  coverage = boundary_coverage_by_level(iso2)
  coverage.select { |_level, count| count == 0 }.keys
end