osm-import 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Alexey Noskov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
File without changes
data/lib/osm-import.rb ADDED
@@ -0,0 +1 @@
1
+ require 'osm_import'
@@ -0,0 +1,18 @@
1
+ require 'osm_import/mapper'
2
+
3
+ class OsmImport::Mapper::Address < OsmImport::Mapper::Base
4
+
5
+ def assigns
6
+ { "street" => "src.tags->'addr:street'", "housenumber" => "src.tags->'addr:housenumber'", "city" => "src.tags->'addr:city'" , "postcode" => "src.tags->'addr:postcode'" }
7
+ end
8
+
9
+ def fields
10
+ { "street" => "VARCHAR(255)", "housenumber" => 'VARCHAR(255)', "city" => "VARCHAR(255)", "postcode" => "VARCHAR(100)" }
11
+ end
12
+
13
+ def after_import(tt)
14
+ tt.conn.exec "UPDATE #{tt.name} SET city = NULL WHERE city = 'undefined'"
15
+ tt.conn.exec "UPDATE #{tt.name} SET city = c.name FROM raw_osm_polygon c WHERE geometry && way AND ST_Contains(way, Geometry(geometry)) AND c.place IN ('city','town','village') AND c.name IS NOT NULL AND c.name <> '' AND city IS NULL"
16
+ end
17
+
18
+ end
@@ -0,0 +1,25 @@
1
+ require 'osm_import/mapper'
2
+
3
+ class OsmImport::Mapper::Center < OsmImport::Mapper::Base
4
+
5
+ def assigns
6
+ {}
7
+ end
8
+
9
+ def fields
10
+ {}
11
+ end
12
+
13
+ def indexes
14
+ { name => "GIST(#{name})" }
15
+ end
16
+
17
+ def after_create(tt)
18
+ tt.add_geometry_column name, :point
19
+ end
20
+
21
+ def after_import(tt)
22
+ tt.conn.exec "UPDATE #{tt.name} SET #{name} = ST_PointOnSurface(Geometry(geometry)) WHERE #{name} IS NULL"
23
+ end
24
+
25
+ end
@@ -0,0 +1,91 @@
1
+ require 'osm_import/mapper'
2
+
3
+ class OsmImport::Mapper::Type < OsmImport::Mapper::Base
4
+
5
+ attr_reader :mappings
6
+
7
+ def initialize(*args)
8
+ super
9
+
10
+ @mappings = []
11
+ @multi = false
12
+ end
13
+
14
+ def assigns
15
+ if @multi
16
+ { :type => expression, :type_array => expression_multi }
17
+ else
18
+ { :type => expression }
19
+ end
20
+ end
21
+
22
+ def fields
23
+ if @multi
24
+ { :type => 'VARCHAR(100) NOT NULL', :type_array => 'VARCHAR(100)[]' }
25
+ else
26
+ { :type => 'VARCHAR(100) NOT NULL' }
27
+ end
28
+ end
29
+
30
+ def indexes
31
+ if @multi
32
+ { :type => "BTREE(type)", :type_array => "GIN(type_array)" }
33
+ else
34
+ { :type => "BTREE(type)" }
35
+ end
36
+ end
37
+
38
+ def conditions
39
+ [ @mappings.map{|m| map_cond(m)}.join(' OR ') ]
40
+ end
41
+
42
+ def expression
43
+ "CASE #{@mappings.map{|m| "WHEN #{map_cond(m)} THEN trim(regexp_replace(src.tags->'#{m[0]}', ',.*', ''))"}.join(' ')} END"
44
+ end
45
+
46
+ def expression_multi
47
+ "(#{@mappings.map{|m| "string_to_array(coalesce(CASE WHEN #{map_cond(m)} THEN replace(src.tags->'#{m[0]}',' ','') END,''),',')"}.join(' || ')})"
48
+ end
49
+
50
+ def map_cond(m)
51
+ m[1] ? "(src.tags->'#{m[0]}') IN ('#{m[1].join("','")}')" : "(src.tags->'#{m[0]}') IS NOT NULL"
52
+ end
53
+
54
+ def add_args(*args)
55
+ args.each do |arg|
56
+ if arg.is_a? Hash
57
+ arg.each(&method(:add_arg))
58
+ else
59
+ add_arg arg, nil
60
+ end
61
+ end
62
+ end
63
+
64
+ def add_arg(key, value)
65
+ key = to_key(key)
66
+
67
+ if key == 'multi'
68
+ @multi = value
69
+ else
70
+ @mappings << [key, to_keylist(value)]
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def to_key(key)
77
+ raise StandardError.new("Invalid key #{key.inspect}") unless key.is_a? String or key.is_a? Symbol
78
+ key.to_s
79
+ end
80
+
81
+ def to_keylist(list)
82
+ if list.nil?
83
+ list
84
+ elsif list.is_a? Array
85
+ list.map(&method(:to_key))
86
+ else
87
+ [to_key(list)]
88
+ end
89
+ end
90
+
91
+ end
@@ -0,0 +1,48 @@
1
+ require 'osm_import'
2
+
3
+ module OsmImport::Mapper
4
+
5
+ extend OsmImport::Autoloading
6
+
7
+ def self.new(name, type)
8
+ self[(type || name).to_s].new(name)
9
+ end
10
+
11
+ class Base < Struct.new(:name)
12
+
13
+ def fields
14
+ { name => "VARCHAR(255)" }
15
+ end
16
+
17
+ def assigns
18
+ { name => "src.tags->'#{name}'" }
19
+ end
20
+
21
+ def indexes
22
+ {}
23
+ end
24
+
25
+ def after_create(*args)
26
+ end
27
+
28
+ def after_import(*args)
29
+ end
30
+
31
+ end
32
+
33
+ class String < Base
34
+ end
35
+
36
+ class Id < Base
37
+
38
+ def fields
39
+ { name => "INT8" }
40
+ end
41
+
42
+ def assigns
43
+ { name => "(src.tags->'#{name}')::INT8" }
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,51 @@
1
+ require 'osm_import'
2
+
3
+ class OsmImport::Schema
4
+
5
+ attr_reader :tables
6
+
7
+ def self.load(file)
8
+ new { eval File.read(file) }
9
+ end
10
+
11
+ def initialize(&block)
12
+ @tables = []
13
+
14
+ dsl.instance_eval(&block) if block
15
+ end
16
+
17
+ def dsl
18
+ @dsl ||= Dsl.new self
19
+ end
20
+
21
+ class Dsl < Struct.new(:schema)
22
+
23
+ def multipolygons(name, &block)
24
+ table :multipolygons, name, &block
25
+ end
26
+
27
+ def multilines(name, &block)
28
+ table :multilines, name, &block
29
+ end
30
+
31
+ def polygons(name, &block)
32
+ table :polygons, name, &block
33
+ end
34
+
35
+ def lines(name, &block)
36
+ table :lines, name, &block
37
+ end
38
+
39
+ def points(name, &block)
40
+ table :points, name, &block
41
+ end
42
+
43
+ def table(type, name, &block)
44
+ require 'osm_import/table'
45
+
46
+ schema.tables << OsmImport::Table.new(type, name, &block)
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,56 @@
1
+ require 'osm_import'
2
+
3
+ class OsmImport::Table
4
+
5
+ attr_reader :type, :name, :type_mapper, :mappers, :after_create_callbacks, :after_import_callbacks
6
+
7
+ def initialize(type, name, &block)
8
+ require 'osm_import/mapper'
9
+
10
+ @type = type.to_s.gsub(/s$/,'')
11
+ @name = name
12
+ @type_mapper = OsmImport::Mapper.new(:type, :type)
13
+ @mappers = {}
14
+ @after_create_callbacks = []
15
+ @after_import_callbacks = []
16
+
17
+ dsl.instance_eval(&block) if block
18
+ end
19
+
20
+ def dsl
21
+ @dsl ||= Dsl.new self
22
+ end
23
+
24
+ class Dsl < Struct.new(:table)
25
+
26
+ def with(*args)
27
+ args.each do |arg|
28
+ if arg.is_a? Hash
29
+ arg.each(&method(:add_mapper))
30
+ else
31
+ add_mapper arg, nil
32
+ end
33
+ end
34
+ end
35
+
36
+ def map(*args)
37
+ table.type_mapper.add_args(*args)
38
+ end
39
+
40
+ def after_create(&block)
41
+ table.after_create_callbacks << block
42
+ end
43
+
44
+ def after_import(&block)
45
+ table.after_import_callbacks << block
46
+ end
47
+
48
+ private
49
+
50
+ def add_mapper(key, type)
51
+ table.mappers[key.to_s] = OsmImport::Mapper.new key, type
52
+ end
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,191 @@
1
+ require 'osm_import'
2
+ require 'pg'
3
+
4
+ class OsmImport::Target::Pg < Struct.new(:options)
5
+
6
+ def projection
7
+ options[:projection]
8
+ end
9
+
10
+ def prefix
11
+ 'osm_'
12
+ end
13
+
14
+ def new_prefix
15
+ 'new_osm_'
16
+ end
17
+
18
+ def import(schema)
19
+ conn = Connection.new options
20
+
21
+ conn.exec "BEGIN TRANSACTION"
22
+
23
+ puts "Dropping old tables:"
24
+
25
+ schema.tables.each do |table|
26
+ conn.exec "DROP TABLE IF EXISTS #{new_prefix}#{table.name}"
27
+ end
28
+
29
+ puts "Preparing target tables"
30
+
31
+ tts = schema.tables.map{|table| TargetTable.new self, conn, table }
32
+
33
+ puts "Creating tables and importing data:"
34
+
35
+ tts.each do |tt|
36
+ tt.create! # Create table
37
+ tt.import! # Import data to it
38
+ end
39
+
40
+ puts "Deploy tables"
41
+ tts.each do |tt|
42
+ tt.deploy!
43
+ end
44
+
45
+ puts "Restoring geometry columns"
46
+ conn.exec "TRUNCATE geometry_columns"
47
+ conn.exec "SELECT probe_geometry_columns()"
48
+
49
+ puts "Commiting"
50
+ conn.exec "COMMIT"
51
+ conn.close
52
+ end
53
+
54
+ GEOTYPES = { 'point' => 'POINT', 'line' => 'LINESTRING', 'polygon' => 'POLYGON', 'multiline' => 'MULTILINESTRING', 'multipolygon' => 'MULTIPOLYGON' }
55
+
56
+ def geometry_type(type)
57
+ GEOTYPES[type.to_s] or raise StandardError.new("Unknown geometry type #{type.inspect}")
58
+ end
59
+
60
+ class Connection < Struct.new(:options)
61
+
62
+ def initialize(*args)
63
+ super
64
+ @conn = PG.connect options[:pg]
65
+ end
66
+
67
+ def exec(q)
68
+ puts " #{q}"
69
+ @conn.exec q
70
+ end
71
+
72
+ def close
73
+ @conn.close
74
+ end
75
+
76
+ end
77
+
78
+ class TargetTable
79
+
80
+ attr_reader :target, :conn, :table, :type_mapping, :type_array_mapping, :name_mapping, :fields, :conditions, :assigns
81
+
82
+ def initialize(target, conn, table)
83
+ @target = target
84
+ @conn = conn
85
+ @table = table
86
+
87
+ @type_mapping = table.type_mapper.expression
88
+ @type_array_mapping = table.type_mapper.expression_multi
89
+ @name_mapping = "NULLIF(COALESCE(src.tags->'name:ru', src.tags->'name', src.tags->'int_name'), '')"
90
+
91
+ @fields = table.type_mapper.fields.merge :id => 'INT8 PRIMARY KEY', :name => 'VARCHAR(255)', :tags => 'HSTORE', :osm_type => 'VARCHAR(10)'
92
+ @assigns = table.type_mapper.assigns.merge :id => osm_id_expr, :name => name_mapping, :tags => 'src.tags', :osm_type => osm_type_expr
93
+ @conditions = table.type_mapper.conditions
94
+
95
+ table.mappers.each do |key, mapper|
96
+ @fields.merge! mapper.fields
97
+ @assigns.merge! mapper.assigns
98
+ end
99
+ end
100
+
101
+ def osm_id_expr
102
+ "ABS(src.osm_id)"
103
+ end
104
+
105
+ def osm_type_expr(t = table.type)
106
+ "CASE WHEN src.osm_id < 0 THEN 'relation' ELSE '#{if t.to_s == 'point' then 'node' else 'way' end}' END"
107
+ end
108
+
109
+ def add_geometry_column(column, type)
110
+ if target.projection
111
+ conn.exec "SELECT AddGeometryColumn('#{name}', '#{column}', #{target.projection}, '#{target.geometry_type type}', 2)"
112
+ else
113
+ conn.exec "ALTER TABLE #{name} ADD COLUMN #{column} GEOGRAPHY(#{target.geometry_type type}, 4326)"
114
+ end
115
+ end
116
+
117
+ def name
118
+ "#{target.new_prefix}#{table.name}"
119
+ end
120
+
121
+ def create!
122
+ conn.exec "CREATE TABLE #{name}(#{fields.map{|k,v| "#{k} #{v}"}.join(', ')})"
123
+ add_geometry_column :geometry, table.type
124
+
125
+ table.type_mapper.after_create self
126
+
127
+ table.mappers.each do |key, mapper|
128
+ mapper.after_create self
129
+ end
130
+
131
+ table.after_create_callbacks.each do |cb|
132
+ instance_eval(&cb)
133
+ end
134
+ end
135
+
136
+ def indexes
137
+ @indexes ||= begin
138
+ res = table.type_mapper.indexes
139
+ res[:geometry] = "GIST(geometry)"
140
+
141
+ table.mappers.each do |key,mapper|
142
+ res.merge mapper.indexes
143
+ end
144
+
145
+ res
146
+ end
147
+ end
148
+
149
+ def import!
150
+ unless table.type_mapper.mappings.empty?
151
+ if table.type =~ /^multi/
152
+ geometry_expr = 'ST_Multi(way)'
153
+ else
154
+ geometry_expr = 'way'
155
+ end
156
+
157
+ field_keys = fields.keys.to_a
158
+ conn.exec "INSERT INTO #{name}(geometry, #{field_keys.join(', ')}) SELECT #{geometry_expr} AS geometry, #{field_keys.map{|k| "#{assigns[k]} AS #{k}"}.join(',')} FROM raw_osm_#{table.type.gsub('multi','')} src WHERE #{conditions.join(' AND ')}"
159
+ end
160
+
161
+ # Creating indexes
162
+
163
+ indexes.each do |key, desc|
164
+ conn.exec "CREATE INDEX #{name}_#{key}_index ON #{name} USING #{desc}"
165
+ end
166
+
167
+ table.type_mapper.after_import self
168
+
169
+ table.mappers.each do |key, mapper|
170
+ mapper.after_import self
171
+ end
172
+
173
+ table.after_import_callbacks.each do |cb|
174
+ instance_eval(&cb)
175
+ end
176
+ end
177
+
178
+ def deploy!
179
+ conn.exec "DROP TABLE IF EXISTS #{target.prefix}#{table.name}"
180
+ conn.exec "ALTER TABLE #{name} RENAME TO #{target.prefix}#{table.name}"
181
+ #conn.exec "ALTER INDEX #{name}_pkey RENAME TO #{target.prefix}#{table.name}_pkey"
182
+
183
+ indexes.each do |key,_|
184
+ conn.exec "ALTER INDEX #{name}_#{key}_index RENAME TO #{target.prefix}#{table.name}_#{key}_index"
185
+ end
186
+
187
+ end
188
+
189
+ end
190
+
191
+ end
@@ -0,0 +1,11 @@
1
+ require 'osm_import'
2
+
3
+ module OsmImport::Target
4
+
5
+ extend OsmImport::Autoloading
6
+
7
+ def self.new(name, options)
8
+ self[name].new(options)
9
+ end
10
+
11
+ end
@@ -0,0 +1,9 @@
1
+ module OsmImport
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/lib/osm_import.rb ADDED
@@ -0,0 +1,37 @@
1
+ module OsmImport
2
+
3
+ module Autoloading
4
+
5
+ def const_missing(c)
6
+ require "#{underscore name}/#{underscore c}"
7
+ const_get c
8
+ end
9
+
10
+ def [](c)
11
+ const_get(camelize c)
12
+ end
13
+
14
+ private
15
+
16
+ # Based on https://github.com/intridea/omniauth/blob/v1.0.2/lib/omniauth.rb#L129-139
17
+ def camelize(str)
18
+ str.to_s.gsub(/\/(.?)/){ "::" + $1.upcase }.gsub(/(^|_)(.)/){ $2.upcase }
19
+ end
20
+
21
+ def underscore(str)
22
+ str.to_s.gsub(/::(.?)/){ "/" + $1.downcase }.gsub(/(.)([A-Z])/){ "#{$1}_#{$2.downcase}" }.downcase
23
+ end
24
+
25
+ end
26
+
27
+ def self.import(mapping_file, options = {})
28
+ require 'osm_import/schema'
29
+ require 'osm_import/target'
30
+
31
+ schema = Schema.load mapping_file
32
+ target = Target.new :pg, options
33
+
34
+ target.import(schema)
35
+ end
36
+
37
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: osm-import
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 0
10
+ version: 0.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Alexey Noskov
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-03-29 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: pg
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 51
29
+ segments:
30
+ - 0
31
+ - 11
32
+ - 0
33
+ version: 0.11.0
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 2
47
+ - 0
48
+ version: "2.0"
49
+ type: :development
50
+ version_requirements: *id002
51
+ description: Gem for importing OpenStreetMap data to PostgreSQL database
52
+ email:
53
+ - alexey.noskov@gmail.com
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files:
59
+ - README.rdoc
60
+ - MIT-LICENSE
61
+ files:
62
+ - lib/osm-import.rb
63
+ - lib/osm_import/mapper.rb
64
+ - lib/osm_import/table.rb
65
+ - lib/osm_import/mapper/type.rb
66
+ - lib/osm_import/mapper/center.rb
67
+ - lib/osm_import/mapper/address.rb
68
+ - lib/osm_import/version.rb
69
+ - lib/osm_import/target.rb
70
+ - lib/osm_import/target/pg.rb
71
+ - lib/osm_import/schema.rb
72
+ - lib/osm_import.rb
73
+ - MIT-LICENSE
74
+ - README.rdoc
75
+ homepage: http://github.com/alno/osm-import
76
+ licenses: []
77
+
78
+ post_install_message:
79
+ rdoc_options:
80
+ - --line-numbers
81
+ - --inline-source
82
+ - --title
83
+ - OSM Import
84
+ - --main
85
+ - README.rdoc
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ hash: 3
94
+ segments:
95
+ - 0
96
+ version: "0"
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ hash: 23
103
+ segments:
104
+ - 1
105
+ - 3
106
+ - 6
107
+ version: 1.3.6
108
+ requirements: []
109
+
110
+ rubyforge_project:
111
+ rubygems_version: 1.8.11
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: Gem for importing OpenStreetMap data to PostgreSQL database
115
+ test_files: []
116
+