geonames_local 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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