api06: Fix normal way and relation uploading (i.e. not as part of a diff).
[rails.git] / app / models / relation.rb
1 class Relation < ActiveRecord::Base
2   require 'xml/libxml'
3   
4   belongs_to :user
5
6   has_many :relation_members, :foreign_key => 'id'
7   has_many :relation_tags, :foreign_key => 'id'
8
9   has_many :old_relations, :foreign_key => 'id', :order => 'version'
10
11   set_table_name 'current_relations'
12
13   def self.from_xml(xml, create=false)
14     begin
15       p = XML::Parser.new
16       p.string = xml
17       doc = p.parse
18
19       doc.find('//osm/relation').each do |pt|
20         return Relation.from_xml_node(pt, create)
21       end
22     rescue
23       return nil
24     end
25   end
26
27   def self.from_xml_node(pt, create=false)
28     relation = Relation.new
29
30     if !create and pt['id'] != '0'
31       relation.id = pt['id'].to_i
32     end
33
34     if create
35       relation.timestamp = Time.now
36       relation.visible = true
37     else
38       if pt['timestamp']
39         relation.timestamp = Time.parse(pt['timestamp'])
40       end
41     end
42
43     pt.find('tag').each do |tag|
44       relation.add_tag_keyval(tag['k'], tag['v'])
45     end
46
47     pt.find('member').each do |member|
48       relation.add_member(member['type'], member['ref'], member['role'])
49     end
50
51     return relation
52   end
53
54   def to_xml
55     doc = OSM::API.new.get_xml_doc
56     doc.root << to_xml_node()
57     return doc
58   end
59
60   def to_xml_node(user_display_name_cache = nil)
61     el1 = XML::Node.new 'relation'
62     el1['id'] = self.id.to_s
63     el1['visible'] = self.visible.to_s
64     el1['timestamp'] = self.timestamp.xmlschema
65     el1['version'] = self.version.to_s
66
67     user_display_name_cache = {} if user_display_name_cache.nil?
68     
69     if user_display_name_cache and user_display_name_cache.key?(self.user_id)
70       # use the cache if available
71     elsif self.user.data_public?
72       user_display_name_cache[self.user_id] = self.user.display_name
73     else
74       user_display_name_cache[self.user_id] = nil
75     end
76
77     el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil?
78
79     self.relation_members.each do |member|
80       p=0
81       #if visible_members
82       #  # if there is a list of visible members then use that to weed out deleted segments
83       #  if visible_members[member.member_type][member.member_id]
84       #    p=1
85       #  end
86       #else
87         # otherwise, manually go to the db to check things
88         if member.member.visible?
89           p=1
90         end
91       #end
92       if p
93         e = XML::Node.new 'member'
94         e['type'] = member.member_type
95         e['ref'] = member.member_id.to_s 
96         e['role'] = member.member_role
97         el1 << e
98        end
99     end
100
101     self.relation_tags.each do |tag|
102       e = XML::Node.new 'tag'
103       e['k'] = tag.k
104       e['v'] = tag.v
105       el1 << e
106     end
107     return el1
108   end 
109
110     
111   # collect relationships. currently done in one big block at the end;
112   # may need to move this upwards if people want automatic completion of
113   # relationships, i.e. deliver referenced objects like we do with ways... 
114   # FIXME: rip out the fucking SQL
115   def self.find_for_nodes_and_ways(node_ids, way_ids)
116     relations = []
117
118     if node_ids.length > 0
119       relations += Relation.find_by_sql("select e.* from current_relations e,current_relation_members em where " +
120             "e.visible=1 and " +
121             "em.id = e.id and em.member_type='node' and em.member_id in (#{node_ids.join(',')})")
122     end
123     if way_ids.length > 0
124       relations += Relation.find_by_sql("select e.* from current_relations e,current_relation_members em where " +
125             "e.visible=1 and " +
126             "em.id = e.id and em.member_type='way' and em.member_id in (#{way_ids.join(',')})")
127     end
128
129     relations # if you don't do this then it returns nil and not []
130   end
131
132
133   # FIXME is this really needed?
134   def members
135     unless @members
136       @members = Array.new
137       self.relation_members.each do |member|
138         @members += [[member.member_type,member.member_id,member.member_role]]
139       end
140     end
141     @members
142   end
143
144   def tags
145     unless @tags
146       @tags = Hash.new
147       self.relation_tags.each do |tag|
148         @tags[tag.k] = tag.v
149       end
150     end
151     @tags
152   end
153
154   def members=(m)
155     @members = m
156   end
157
158   def tags=(t)
159     @tags = t
160   end
161
162   def add_member(type,id,role)
163     @members = Array.new unless @members
164     @members += [[type,id,role]]
165   end
166
167   def add_tag_keyval(k, v)
168     @tags = Hash.new unless @tags
169     @tags[k] = v
170   end
171
172   def save_with_history!
173     Relation.transaction do
174       t = Time.now
175       self.version += 1
176       self.timestamp = t
177       self.save!
178
179       tags = self.tags
180       RelationTag.delete_all(['id = ?', self.id])
181       tags.each do |k,v|
182         tag = RelationTag.new
183         tag.k = k
184         tag.v = v
185         tag.id = self.id
186         tag.save!
187       end
188
189       members = self.members
190       RelationMember.delete_all(['id = ?', self.id])
191       members.each do |n|
192         mem = RelationMember.new
193         mem.id = self.id
194         mem.member_type = n[0];
195         mem.member_id = n[1];
196         mem.member_role = n[2];
197         mem.save!
198       end
199
200       old_relation = OldRelation.from_relation(self)
201       old_relation.timestamp = t
202       old_relation.save_with_dependencies!
203     end
204   end
205
206   def delete_with_history(user)
207     if self.visible
208       if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='relation' and member_id=?", self.id ])
209         raise OSM::APIPreconditionFailedError.new
210       else
211         self.user_id = user.id
212         self.tags = []
213         self.members = []
214         self.visible = false
215         save_with_history!
216       end
217     else
218       raise OSM::APIAlreadyDeletedError.new
219     end
220   end
221
222   def update_from(new_relation, user)
223     if !new_relation.preconditions_ok?
224       raise OSM::APIPreconditionFailedError.new
225     else
226       self.user_id = user.id
227       self.tags = new_relation.tags
228       self.members = new_relation.members
229       self.visible = true
230       save_with_history!
231     end
232   end
233
234   def preconditions_ok?
235     self.members.each do |m|
236       if (m[0] == "node")
237         n = Node.find(:first, :conditions => ["id = ?", m[1]])
238         unless n and n.visible 
239           return false
240         end
241       elsif (m[0] == "way")
242         w = Way.find(:first, :conditions => ["id = ?", m[1]])
243         unless w and w.visible and w.preconditions_ok?
244           return false
245         end
246       elsif (m[0] == "relation")
247         e = Relation.find(:first, :conditions => ["id = ?", m[1]])
248         unless e and e.visible and e.preconditions_ok?
249           return false
250         end
251       else
252         return false
253       end
254     end
255     return true
256   rescue
257     return false
258   end
259
260 end