0c927c11c7d0c35fca3624661887f60116213323
[rails.git] / app / models / way.rb
1 class Way < ActiveRecord::Base
2   require 'xml/libxml'
3
4   belongs_to :user
5
6   has_many :nodes, :through => :way_nodes, :order => 'sequence_id'
7   has_many :way_nodes, :foreign_key => 'id', :order => 'sequence_id'
8   has_many :way_tags, :foreign_key => 'id'
9
10   has_many :old_ways, :foreign_key => 'id', :order => 'version'
11
12   set_table_name 'current_ways'
13
14   def self.from_xml(xml, create=false)
15     begin
16       p = XML::Parser.new
17       p.string = xml
18       doc = p.parse
19
20       doc.find('//osm/way').each do |pt|
21         way = Way.from_xml_node pt, create
22       end
23     rescue
24       way = nil
25     end
26
27     return way
28   end
29
30   def self.from_xml_node(pt, create=false)
31     way = Way.new
32
33     if !create and pt['id'] != '0'
34       way.id = pt['id'].to_i
35     end
36
37     if create
38       way.timestamp = Time.now
39       way.visible = true
40     else
41       if pt['timestamp']
42         way.timestamp = Time.parse(pt['timestamp'])
43       end
44     end
45
46     pt.find('tag').each do |tag|
47       way.add_tag_keyval(tag['k'], tag['v'])
48     end
49
50     pt.find('nd').each do |nd|
51       way.add_nd_num(nd['ref'])
52     end
53
54     return way
55   end
56
57   # Find a way given it's ID, and in a single SQL call also grab its nodes
58   #
59   
60   # You can't pull in all the tags too unless we put a sequence_id on the way_tags table and have a multipart key
61   def self.find_eager(id)
62     way = Way.find(id, :include => {:way_nodes => :node})
63     #If waytag had a multipart key that was real, you could do this:
64     #way = Way.find(id, :include => [:way_tags, {:way_nodes => :node}])
65   end
66
67   # Find a way given it's ID, and in a single SQL call also grab its nodes and tags
68   def to_xml
69     doc = OSM::API.new.get_xml_doc
70     doc.root << to_xml_node()
71     return doc
72   end
73
74   def to_xml_node(visible_nodes = nil, user_display_name_cache = nil)
75     el1 = XML::Node.new 'way'
76     el1['id'] = self.id.to_s
77     el1['visible'] = self.visible.to_s
78     el1['timestamp'] = self.timestamp.xmlschema
79     el1['version'] = self.version.to_s
80
81     user_display_name_cache = {} if user_display_name_cache.nil?
82
83     if user_display_name_cache and user_display_name_cache.key?(self.user_id)
84       # use the cache if available
85     elsif self.user.data_public?
86       user_display_name_cache[self.user_id] = self.user.display_name
87     else
88       user_display_name_cache[self.user_id] = nil
89     end
90
91     el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil?
92
93     # make sure nodes are output in sequence_id order
94     ordered_nodes = []
95     self.way_nodes.each do |nd|
96       if visible_nodes
97         # if there is a list of visible nodes then use that to weed out deleted nodes
98         if visible_nodes[nd.node_id]
99           ordered_nodes[nd.sequence_id] = nd.node_id.to_s
100         end
101       else
102         # otherwise, manually go to the db to check things
103         if nd.node.visible? and nd.node.visible?
104           ordered_nodes[nd.sequence_id] = nd.node_id.to_s
105         end
106       end
107     end
108
109     ordered_nodes.each do |nd_id|
110       if nd_id and nd_id != '0'
111         e = XML::Node.new 'nd'
112         e['ref'] = nd_id
113         el1 << e
114       end
115     end
116
117     self.way_tags.each do |tag|
118       e = XML::Node.new 'tag'
119       e['k'] = tag.k
120       e['v'] = tag.v
121       el1 << e
122     end
123     return el1
124   end 
125
126   def nds
127     unless @nds
128       @nds = Array.new
129       self.way_nodes.each do |nd|
130         @nds += [nd.node_id]
131       end
132     end
133     @nds
134   end
135
136   def tags
137     unless @tags
138       @tags = {}
139       self.way_tags.each do |tag|
140         @tags[tag.k] = tag.v
141       end
142     end
143     @tags
144   end
145
146   def nds=(s)
147     @nds = s
148   end
149
150   def tags=(t)
151     @tags = t
152   end
153
154   def add_nd_num(n)
155     @nds = Array.new unless @nds
156     @nds << n.to_i
157   end
158
159   def add_tag_keyval(k, v)
160     @tags = Hash.new unless @tags
161     @tags[k] = v
162   end
163
164   def save_with_history!
165     t = Time.now
166
167     Way.transaction do
168       self.version += 1
169       self.timestamp = t
170       self.save!
171
172       tags = self.tags
173       WayTag.delete_all(['id = ?', self.id])
174       tags.each do |k,v|
175         tag = WayTag.new
176         tag.k = k
177         tag.v = v
178         tag.id = self.id
179         tag.save!
180       end
181
182       nds = self.nds
183       WayNode.delete_all(['id = ?', self.id])
184       sequence = 1
185       nds.each do |n|
186         nd = WayNode.new
187         nd.id = [self.id, sequence]
188         nd.node_id = n
189         nd.save!
190         sequence += 1
191       end
192
193       old_way = OldWay.from_way(self)
194       old_way.timestamp = t
195       old_way.save_with_dependencies!
196     end
197   end
198
199   def preconditions_ok?
200     return false if self.nds.empty?
201     self.nds.each do |n|
202       node = Node.find(:first, :conditions => ["id = ?", n])
203       unless node and node.visible
204         return false
205       end
206     end
207     return true
208   end
209
210   # Delete the way and it's relations, but don't really delete it - set its visibility to false and update the history etc to maintain wiki-like functionality.
211   def delete_with_relations_and_history(user)
212     if self.visible
213           # FIXME
214           # this should actually delete the relations,
215           # not just throw a PreconditionFailed if it's a member of a relation!!
216       if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id",
217                              :conditions => [ "visible = 1 AND member_type='way' and member_id=?", self.id])
218         raise OSM::APIPreconditionFailedError
219       # end FIXME
220       else
221         self.user_id = user.id
222         self.tags = []
223         self.nds = []
224         self.visible = false
225         self.save_with_history!
226       end
227     else
228       raise OSM::APIAlreadyDeletedError
229     end
230   end
231
232   # delete a way and it's nodes that aren't part of other ways, with history
233   def delete_with_relations_and_nodes_and_history(user)
234     
235     node_ids = self.nodes.collect {|node| node.id }
236     node_ids_not_to_delete = []
237     way_nodes = WayNode.find(:all, :conditions => "node_id in (#{node_ids.join(',')}) and id != #{self.id}")
238     
239     node_ids_not_to_delete = way_nodes.collect {|way_node| way_node.node_id}
240
241     node_ids_to_delete = node_ids - node_ids_not_to_delete
242
243     # delete the nodes not used by other ways
244     node_ids_to_delete.each do |node_id|
245       n = Node.find(node_id)
246       n.user_id = user.id
247       n.visible = false
248       n.save_with_history!
249     end
250     
251     self.user_id = user.id
252
253     self.delete_with_relations_and_history(user)
254
255   end
256 end