geonames_local 1.0.0 → 2.0.0

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.
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ gemspec # Specify gem's dependencies in mongoid_geospatial.gemspec
3
3
 
4
4
  group :development do
5
5
  gem 'mongoid', '~> 3.0'
6
+ gem 'mongoid_geospatial'
6
7
  # gem 'pg'
7
8
  gem 'rgeo'
8
9
  gem 'georuby'
data/README.rdoc CHANGED
@@ -1,6 +1,6 @@
1
1
  = Geonames Local
2
2
 
3
- Download and store (pg, mongo, tokyo) Geonames.org data.
3
+ Downloads and store Geonames.org data locally (MongoDB, PostGIS, Tokyo).
4
4
  Making every Geoname API operation possible on your servers.
5
5
  No hit limit, fast as possible.
6
6
 
@@ -10,7 +10,7 @@ No hit limit, fast as possible.
10
10
  To use one adapter, install the corresponding gem:
11
11
 
12
12
  PostgreSQL => pg
13
- MongoDB => mongo (optional: mongo_ext)
13
+ MongoDB => mongoid (optional: mongo_ext)
14
14
  Tokyo => tokyocabinet
15
15
 
16
16
  You will also need in your system:
@@ -19,17 +19,6 @@ You will also need in your system:
19
19
  * curl
20
20
 
21
21
 
22
- === PostgreSQL
23
-
24
- Be sure to use a database based on the PostGIS template.
25
-
26
-
27
- === MongoDB
28
-
29
- MongoDB 2D support is new, only mongo >= 1.3.3 mongodb gem >= 0.19.2
30
- http://github.com/mongodb/mongo-ruby-driver
31
-
32
-
33
22
  === Config YML
34
23
 
35
24
  geonames conf
@@ -45,26 +34,23 @@ If you are not sure your country code, use:
45
34
 
46
35
  geonames list <search>
47
36
 
37
+ To populate the countries database for the first time use:
48
38
 
49
- == Relational Mapping
39
+ geonames -c geoconfig.yml countries
50
40
 
51
- When using PG, this gem will (try) to relational map Geonames
52
- data on your scheme.
53
41
 
54
- == ActiveRecord
42
+ == Mongoid
55
43
 
56
- Check out lib/geonames/models/ar.rb
57
-
58
- I`m wondering the best way (and if it`s a good idea) to auto include'em on rails.
59
- For now, just copy as you see fit.
44
+ So, supposing Mongoid, something like this is possible:
60
45
 
46
+ City.first.province.country.abbr
47
+ => "BR"
61
48
 
62
- == PostgreSQL
63
49
 
64
- So, supposing ActiveRecord, something like this is possible:
50
+ == Postgis
65
51
 
66
- City.first.province.country.abbr
67
- => "BR"
52
+ TBD (by someone else)
53
+ Be sure to use a database based on the PostGIS template.
68
54
 
69
55
 
70
56
  === Migration
data/bin/geonames CHANGED
@@ -3,7 +3,8 @@
3
3
  # $KCODE = "u" # -Ku
4
4
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
5
 
6
- require "geonames_cli"
6
+ require "geonames_local"
7
+ require "geonames_local/cli"
7
8
  include Geonames
8
9
 
9
10
  Geonames::CLI.work(ARGV)
data/geonames.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  #
2
2
  # Geonames Local Config Example
3
3
  #
4
- :store: mongodb # postgres / tokyo
4
+ :store: mongodb # postgis / tokyo
5
5
  :codes: [br] # to store all countries informations in the countries table, just write [country]
6
6
  :level: city
7
7
  :min_pop: 100000
@@ -10,7 +10,7 @@
10
10
  :geom: true
11
11
  :db:
12
12
  :host: localhost
13
- :dbname: geonames_test
13
+ :name: geonames_test
14
14
  :user:
15
- :password:
15
+ :pass:
16
16
  :purge: false
@@ -5,14 +5,9 @@ $:.unshift(File.dirname(__FILE__)) unless
5
5
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
6
6
 
7
7
  # Require Libs
8
- require 'geonames_local/geonames'
9
-
10
- require 'geonames_local/adapters/mongodb'
11
-
12
- module Geonames
8
+ require 'geonames_local/features/spot'
9
+ require 'geonames_local/features/road'
10
+ require 'geonames_local/features/zone'
13
11
 
14
- Adapter = Geonames::Mongodb.new
15
-
16
- end
17
-
18
- require 'geonames_local/models/mongo'
12
+ # Require Main
13
+ require 'geonames_local/geonames'
@@ -2,6 +2,14 @@
2
2
  # Geonames Local
3
3
  #
4
4
  require 'optparse'
5
+ # Require CLI Stuff
6
+ require 'geonames_local/geonames'
7
+ require 'geonames_local/data/shp'
8
+ require 'geonames_local/data/dump'
9
+ require 'geonames_local/data/export'
10
+ require 'geonames_local/cli'
11
+
12
+
5
13
  module Geonames
6
14
  class CLI
7
15
  def self.parse_options(argv)
@@ -113,76 +121,44 @@ BANNER
113
121
  # Do the magic! Import Geonames Data
114
122
  #
115
123
  else
116
- db = load_adapter(Opt[:store])
124
+ load_adapter(Opt[:store])
117
125
  info "Using adapter #{Opt[:store]}.."
118
126
 
119
- if Opt[:codes][0] == "country"
120
- Geonames::Dump.work(Opt[:codes], :dump)
127
+ if argv[0] =~ /coun|nati/
128
+ dump = Geonames::Dump.new(:country, :dump)
129
+ info "\n---\nTotal #{dump.data.length} parsed."
130
+
131
+ info "Writing to DB"
132
+ Geonames::Models::Country.from_batch(dump.data)
121
133
  else
122
- Geonames::Dump.work(Opt[:codes], :zip)
123
- Geonames::Dump.work(Opt[:codes], :dump)
124
- end
134
+ zip = Geonames::Dump.new(Opt[:codes], :zip).data
135
+ dump = Geonames::Dump.new(Opt[:codes], :dump).data
136
+ info "\n---\nTotal #{dump.size} parsed. #{zip.size} zips."
137
+
138
+ info "Join dump << zip"
139
+ dump = unify!(dump, zip).group_by(&:kind)
125
140
 
126
- info "\n---\nTotal #{Cache[:dump].length} parsed. #{Cache[:zip].length} zips."
127
- # Sync.work!
128
- info "Join dump << zip"
129
- unify!
130
- write_to_store!(db)
141
+ info "Writing provinces..."
142
+ Geonames::Models::Province.from_batch dump[:province]
143
+ info "Writing cities..."
144
+ Geonames::Models::City.from_batch dump[:city]
145
+ end
131
146
  end
132
147
  end
133
148
 
134
149
  def load_adapter(name)
135
150
  begin
136
- require "geonames_local/adapters/#{name}"
137
151
  require "geonames_local/models/#{name}"
138
- Geonames.class_eval(name.capitalize).new(Opt[:db])
139
152
  rescue LoadError
140
- puts "Can't find adapter #{name}"
153
+ puts "Can't find adapter for #{name}"
141
154
  stop!
142
155
  end
143
156
  end
144
157
 
145
- def write_to_store!(db)
146
- if Opt[:codes][0] == "country"
147
- Cache[:countries] = Cache[:dump]
148
- do_write(db, Cache[:countries])
149
- else
150
- groups = Cache[:dump].group_by(&:kind)
151
-
152
- Cache[:provinces] = groups[:provinces]
153
- Cache[:cities] = groups[:cities]
154
-
155
- do_write(db, Cache[:provinces])
156
- do_write(db, Cache[:cities])
157
- end
158
- end
159
-
160
- def do_write(db, values)
161
- return if values.empty?
162
-
163
- if Opt[:codes][0] == "country"
164
- key = table = "countries"
165
- else
166
- key = values[0].table
167
- end
168
- start = Time.now
169
- writt = 0
170
- info "\nWriting #{values.length} #{key}..."
171
- values.each do |val|
172
- meth = val.respond_to?(:gid) ? [val.gid] : [val.name, true]
173
- unless db.find(table || val.table, *meth)
174
- db.insert(table || val.table, val)
175
- writt += 1
176
- end
177
- end
178
- total = Time.now - start
179
- info "#{writt} #{key} written in #{total} sec (#{(writt/total).to_i}/s)"
180
- end
181
-
182
- def unify!
158
+ def unify! dump, zip
183
159
  start = Time.now
184
- Cache[:dump].map! do |spot|
185
- if other = Cache[:zip].find { |d| d.code == spot.code }
160
+ dump.map! do |spot|
161
+ if other = zip.find { |d| d.code == spot.code }
186
162
  spot.zip = other.zip
187
163
  spot
188
164
  else
@@ -190,6 +166,7 @@ BANNER
190
166
  end
191
167
  end
192
168
  info "Done. #{(Time.now-start).to_i}s"
169
+ dump
193
170
  end
194
171
 
195
172
  def stop!
@@ -3,23 +3,36 @@ module Geonames
3
3
  URL = "http://download.geonames.org/export/"
4
4
  TMP = "/tmp/geonames/"
5
5
 
6
- def self.work(codes=:all, kind=:dump)
7
- new(codes, kind)
8
- end
9
-
10
6
  def initialize(codes, kind)
11
7
  @codes = codes
12
8
  @kind = kind
9
+ @data = []
13
10
  if codes.respond_to? :each
14
11
  for code in codes
15
- info "\nWorking on #{kind} for #{code}"
16
- file = get_file(code)
17
- download file
18
- uncompress file unless code == "country"
19
- parse file
12
+ work code
20
13
  end
14
+ elsif codes == :country
15
+ countries
21
16
  end
17
+ end
18
+
19
+ def countries
20
+ info "\nDumping country database"
21
+ file = get_file('country')
22
+ download file
23
+ parse file
24
+ end
25
+
26
+ def work code
27
+ info "\nWorking on #{@kind} for #{code}"
28
+ file = get_file(code)
29
+ download file
30
+ uncompress file
31
+ parse file
32
+ end
22
33
 
34
+ def data
35
+ @data
23
36
  end
24
37
 
25
38
  def get_file(code)
@@ -43,7 +56,7 @@ module Geonames
43
56
  return if l =~ /^#|^iso/i
44
57
  if @kind == :dump
45
58
  if l =~ /^\D/
46
- return Country.parse(l)
59
+ return l
47
60
  else
48
61
  if Opt[:level] != "all"
49
62
  return unless l =~ /ADM\d/ # ADM2 => cities
@@ -59,7 +72,7 @@ module Geonames
59
72
  File.open("/tmp/geonames/#{@kind}/#{file.gsub("zip", "txt")}") do |f|
60
73
  while line = f.gets
61
74
  if record = parse_line(line)
62
- Cache[@kind] << record
75
+ @data << record
63
76
  red += 1
64
77
  end
65
78
  end
@@ -63,7 +63,7 @@ module Geonames
63
63
  end
64
64
 
65
65
  def write
66
- db = Postgres.new(Opt[:db])
66
+ db = Postgis.new(Opt[:db])
67
67
  Geonames::CLI.do_write(db, Cache[:zones])
68
68
  Geonames::CLI.do_write(db, reduce!)
69
69
  end
@@ -11,16 +11,16 @@ module Geonames
11
11
  # = Geonames Spot
12
12
  #
13
13
  # Every geoname type will be parsed as a spot
14
- def initialize(params=nil, k=nil)
14
+ def initialize(params = nil, kind = nil)
15
15
  return unless params.instance_of? String
16
- k == :zip ? parse_zip(params) : parse(params)
16
+ kind == :zip ? parse_zip(params) : parse(params)
17
17
  if @kind == :province
18
18
  @name.gsub!(/Estado d\w\s/, "")
19
19
  @abbr = get_abbr
20
20
  end
21
21
  end
22
22
 
23
- # Geonames donest have province/state abbr..#fail!
23
+ # Geonames does not have province/state abbr..#fail!
24
24
  # This works 75% of the time in brazil heh
25
25
  def get_abbr
26
26
  s = @name.split(" ")
@@ -49,14 +49,11 @@ module Geonames
49
49
  #
50
50
  # Parse Geonames Zip Export
51
51
  def parse_zip(row)
52
- # country, zip, @name, province, cc, dunno, adm1, adm2, lat, lon = row.split(/\t/)
53
52
  # country, zip, @name, state, state_code, procince, province_code, community, community_code, lat, lon, acc = row.split(/\t/)
54
- country, zip, @name, adm1, adm1_code, adm2, adm2_code, adm3, adm3_code, lat, lon, acc = row.split(/\t/)
53
+ country, zip, @name, a1, @code, a2, a2c, a3, a3c, lat, lon, acc = row.split(/\t/)
55
54
  parse_geom(lat, lon)
56
55
  # @code = adm1
57
- # @kind = :city
58
- @code = adm1_code
59
- @kind = :cities
56
+ @kind = :city
60
57
  @zip = zip.split("-")[0]
61
58
  end
62
59
 
@@ -82,39 +79,17 @@ module Geonames
82
79
  Time.utc(*@up.split("-"))
83
80
  end
84
81
 
85
- # For tokyo
86
- def to_hash
87
- { "id" => @geoname_id, "gid" => @geoname_id.to_s, "kind" => @kind.to_s,
88
- "name" => @name, "ascii" => @ascii, "country" => @country,
89
- "geom" => [@geom.x, @geom.y], "tz" => @tz }
90
- end
91
-
82
+ # Translate geonames ADMx to models
92
83
  def human_code(code)
93
84
  case code
94
- when 'ADM1' then :provinces
95
- when 'ADM2', 'ADM3', 'ADM4' then :cities
85
+ when 'ADM1' then :province
86
+ when 'ADM2', 'ADM3', 'ADM4' then :city
96
87
  else :other
97
88
  end
98
89
  end
99
90
 
100
91
  class << self
101
92
 
102
- def all
103
- Adapter.all(@coll)
104
- end
105
-
106
- def first
107
- from_hash(Adapter.first(@coll))
108
- end
109
-
110
- def find(id)
111
- Adapter.find(@coll, id)
112
- end
113
-
114
- def find_by_name(name)
115
- Adapter.find_by_name(@coll, name).map { |hsh| from_hash(hsh) }
116
- end
117
-
118
93
  def nearest(x,y)
119
94
  from_hash(Adapter.find_near(@coll, x, y, 1)[0])
120
95
  end
@@ -1,11 +1,6 @@
1
- require 'rubygems'
2
1
  require 'logger'
3
2
  require 'yaml'
4
3
 
5
- require 'geonames_local/features/spot'
6
- require 'geonames_local/features/road'
7
- require 'geonames_local/features/zone'
8
-
9
4
  module Geonames
10
5
  Opt = {}
11
6
  Cache = {:dump => [], :zip => [], :roads => [], :zones => []}
@@ -1,62 +1,180 @@
1
+ require 'mongoid'
2
+ require 'mongoid_geospatial'
3
+
4
+ Mongoid.configure do |config|
5
+ #config.master = Mongo::Connection.new.db("symbolize_test")
6
+ config.connect_to(Opt[:db][:name])
7
+ end
8
+
1
9
  module Geonames
2
10
  module Models
3
- module Mongo
4
11
 
5
- class City < Geonames::Spot
6
- set_coll "cities"
12
+
13
+ class City < Geonames::Spot
14
+ include Mongoid::Document
15
+ include Mongoid::Geospatial
16
+ store_in :collection => "cities"
17
+
18
+ field :ascii, type: String
19
+ field :slug, type: String
20
+ field :name, type: String
21
+ field :abbr, type: String
22
+ field :area
23
+ field :gid, type: Integer
24
+ field :zip, type: Integer
25
+ field :geom, type: Point, spatial: true
26
+
27
+ belongs_to :province
28
+ belongs_to :country, index: true
29
+ # has_many :hoods
30
+
31
+ before_validation :set_defaults
32
+
33
+ validates :name, :country, presence: true
34
+ validates :name, :uniqueness => { :scope => :province_id }
35
+
36
+ index name: 1
37
+ index slug: 1
38
+ index geom: '2d'
39
+
40
+ #spatial_index :geom
41
+
42
+ scope :ordered, order_by(name: 1)
43
+
44
+ def set_defaults
45
+ self.country ||= province.try(:country)
46
+ self.slug ||= name.try(:downcase) # don't use slugize
7
47
  end
8
48
 
9
- class Country < Geonames::Spot
10
- attr_accessor :code, :name, :gid, :iso, :capital, :pop
11
- set_coll "countries"
49
+ def self.search(search, page)
50
+ cities = search ? where(:field => /#{search}/i) : all
51
+ cities.page(page)
52
+ end
12
53
 
13
- def self.parse(row)
14
- new(row)
15
- end
54
+ def <=> other
55
+ self.name <=> other.name
56
+ end
16
57
 
17
- def initialize(params)
18
- parse(params)
19
- end
58
+ def to_s
59
+ "#{name}/#{abbr}"
60
+ end
20
61
 
21
- def parse
22
- @iso, @iso3, @ison, @fips, @name, @capital, @area, @pop, continent, tld,
23
- currency, phone, postal, langs, gid, neighbours = row.split(/\t/)
24
- @code = iso
25
- end
62
+ def self.from_batch data
63
+ data.each do |city|
64
+ info "Writing city #{city.name}"
65
+ next unless city.country
66
+ city = new.parse(city)
67
+ city.country = city.province.country
68
+ city.save
69
+ end
70
+ end
26
71
 
27
- def cities
28
- # qry.addcond("country", TBDQRY::QSTREQ, @code)
29
- end
72
+ def parse(spot)
73
+ self.name, self.ascii = spot.name, spot.ascii
74
+ self.code, self.gid = spot.code, spot.gid
75
+ self.province = Province.find_by(code: spot.province)
76
+ self.geom = [spot.lon, spot.lat]
77
+ self.abbr = province.abbr
78
+ self
79
+ end
30
80
 
31
- def to_hash
32
- { "gid" => @gid.to_s, "name" => @name, "kind" => "country", "code" => @code}
33
81
  end
34
82
 
35
- def export
36
- [@gid, @code, @name]
37
- end
38
83
 
39
- def export_header
40
- ["gid", "code", "name"]
41
- end
42
- end
84
+ class Province < Geonames::Spot
85
+ include Mongoid::Document
86
+ store_in :collection => "provinces"
87
+
88
+ field :gid, type: Integer # geonames id
89
+ field :code, type: String
90
+ field :name, type: String
91
+ field :abbr, type: String
92
+ field :codes, type: Array # phone code
93
+
94
+ belongs_to :country
95
+ has_many :cities
96
+
97
+ validates :name, presence: true
98
+ validates :country, presence: true
43
99
 
44
- class Province
45
- attr_accessor :code, :name, :gid
100
+ index name: 1
101
+ index codes: 1
46
102
 
47
- def self.all
48
- Tokyo.new.all({ :kind => "province" }).map do |c|
49
- new(c)
103
+ scope :ordered, order_by(name: 1)
104
+
105
+ def self.from_batch data
106
+ data.each do |province|
107
+ info "Writing province #{province.name}"
108
+ next unless province.country
109
+ province = new.parse(province)
110
+ province.country = Country.find_by(abbr: /#{province.country}/i)
111
+ province.save!
112
+ end
113
+ end
114
+
115
+ def parse(spot)
116
+ self.code, self.name = spot.province, spot.name
117
+ self.gid = spot.gid
118
+ self
50
119
  end
120
+
51
121
  end
52
122
 
53
- def initialize(params)
54
- @code = params["code"]
55
- @name = params["name"]
56
- @gid = params["gid"]
123
+
124
+ class Country < Geonames::Spot
125
+ include Mongoid::Document
126
+ store_in :collection => "countries"
127
+
128
+ field :gid, type: Integer # geonames id
129
+ field :name, type: String
130
+ field :abbr, type: String
131
+ field :code # optional phone/whatever code
132
+
133
+ has_many :cities
134
+ has_many :provinces
135
+
136
+ validates :abbr, :name, presence: true, uniqueness: true
137
+
138
+ index name: 1
139
+ index code: 1
140
+
141
+ scope :ordered, order_by(name: 1)
142
+
143
+
144
+ def parse row
145
+ self.abbr, @iso3, @ison, @fips, self.name, @capital, @area, @pop, continent, tld,
146
+ currency, phone, postal, langs, gid, neighbours = row.split(/\t/)
147
+ self
148
+ end
149
+
150
+ def self.from_batch data
151
+ data.each do |spot|
152
+ new.parse(spot).save
153
+ end
154
+ end
155
+
156
+ def to_hash
157
+ { "gid" => @gid.to_s, "name" => @name, "kind" => "country", "code" => @code}
158
+ end
159
+
160
+ def export
161
+ [@gid, @code, @name]
162
+ end
163
+
164
+ def export_header
165
+ ["gid", "code", "name"]
166
+ end
57
167
  end
58
168
 
59
- end
169
+
170
+
171
+ class Zip
172
+ include Mongoid::Document
173
+
174
+ field :code
175
+ belongs_to :city
176
+
60
177
  end
178
+
61
179
  end
62
180
  end