Update to use libxml 1.0.0 gem.
[rails.git] / app / models / relation.rb
1 class Relation < ActiveRecord::Base
2   require 'xml/libxml'
3   
4   set_table_name 'current_relations'
5
6   belongs_to :user
7
8   has_many :old_relations, :foreign_key => 'id', :order => 'version'
9
10   has_many :relation_members, :foreign_key => 'id'
11   has_many :relation_tags, :foreign_key => 'id'
12
13   has_many :containing_relation_members, :class_name => "RelationMember", :as => :member
14   has_many :containing_relations, :class_name => "Relation", :through => :containing_relation_members, :source => :relation, :extend => ObjectFinder
15
16   def self.from_xml(xml, create=false)
17     begin
18       p = XML::Parser.string(xml)
19       doc = p.parse
20
21       relation = Relation.new
22
23       doc.find('//osm/relation').each do |pt|
24         if !create and pt['id'] != '0'
25           relation.id = pt['id'].to_i
26         end
27
28         if create
29           relation.timestamp = Time.now
30           relation.visible = true
31         else
32           if pt['timestamp']
33             relation.timestamp = Time.parse(pt['timestamp'])
34           end
35         end
36
37         pt.find('tag').each do |tag|
38           relation.add_tag_keyval(tag['k'], tag['v'])
39         end
40
41         pt.find('member').each do |member|
42           relation.add_member(member['type'], member['ref'], member['role'])
43         end
44       end
45     rescue
46       relation = nil
47     end
48
49     return relation
50   end
51
52   def to_xml
53     doc = OSM::API.new.get_xml_doc
54     doc.root << to_xml_node()
55     return doc
56   end
57
58   def to_xml_node(user_display_name_cache = nil)
59     el1 = XML::Node.new 'relation'
60     el1['id'] = self.id.to_s
61     el1['visible'] = self.visible.to_s
62     el1['timestamp'] = self.timestamp.xmlschema
63
64     user_display_name_cache = {} if user_display_name_cache.nil?
65     
66     if user_display_name_cache and user_display_name_cache.key?(self.user_id)
67       # use the cache if available
68     elsif self.user.data_public?
69       user_display_name_cache[self.user_id] = self.user.display_name
70     else
71       user_display_name_cache[self.user_id] = nil
72     end
73
74     el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil?
75
76     self.relation_members.each do |member|
77       p=0
78       #if visible_members
79       #  # if there is a list of visible members then use that to weed out deleted segments
80       #  if visible_members[member.member_type][member.member_id]
81       #    p=1
82       #  end
83       #else
84         # otherwise, manually go to the db to check things
85         if member.member.visible?
86           p=1
87         end
88       #end
89       if p
90         e = XML::Node.new 'member'
91         e['type'] = member.member_type
92         e['ref'] = member.member_id.to_s 
93         e['role'] = member.member_role
94         el1 << e
95        end
96     end
97
98     self.relation_tags.each do |tag|
99       e = XML::Node.new 'tag'
100       e['k'] = tag.k
101       e['v'] = tag.v
102       el1 << e
103     end
104     return el1
105   end 
106
107   def self.find_for_nodes(ids, options = {})
108     if ids.empty?
109       return []
110     else
111       self.with_scope(:find => { :joins => "INNER JOIN current_relation_members ON current_relation_members.id = current_relations.id", :conditions => "current_relation_members.member_type = 'node' AND current_relation_members.member_id IN (#{ids.join(',')})" }) do
112         return self.find(:all, options)
113       end
114     end
115   end
116
117   def self.find_for_ways(ids, options = {})
118     if ids.empty?
119       return []
120     else
121       self.with_scope(:find => { :joins => "INNER JOIN current_relation_members ON current_relation_members.id = current_relations.id", :conditions => "current_relation_members.member_type = 'way' AND current_relation_members.member_id IN (#{ids.join(',')})" }) do
122         return self.find(:all, options)
123       end
124     end
125   end
126
127   def self.find_for_relations(ids, options = {})
128     if ids.empty?
129       return []
130     else
131       self.with_scope(:find => { :joins => "INNER JOIN current_relation_members ON current_relation_members.id = current_relations.id", :conditions => "current_relation_members.member_type = 'relation' AND current_relation_members.member_id IN (#{ids.join(',')})" }) do
132         return self.find(:all, options)
133       end
134     end
135   end
136
137   # FIXME is this really needed?
138   def members
139     unless @members
140       @members = Array.new
141       self.relation_members.each do |member|
142         @members += [[member.member_type,member.member_id,member.member_role]]
143       end
144     end
145     @members
146   end
147
148   def tags
149     unless @tags
150       @tags = Hash.new
151       self.relation_tags.each do |tag|
152         @tags[tag.k] = tag.v
153       end
154     end
155     @tags
156   end
157
158   def members=(m)
159     @members = m
160   end
161
162   def tags=(t)
163     @tags = t
164   end
165
166   def add_member(type,id,role)
167     @members = Array.new unless @members
168     @members += [[type,id,role]]
169   end
170
171   def add_tag_keyval(k, v)
172     @tags = Hash.new unless @tags
173     @tags[k] = v
174   end
175
176   def save_with_history!
177     Relation.transaction do
178       t = Time.now
179       self.timestamp = t
180       self.save!
181
182       tags = self.tags
183
184       RelationTag.delete_all(['id = ?', self.id])
185
186       tags.each do |k,v|
187         tag = RelationTag.new
188         tag.k = k
189         tag.v = v
190         tag.id = self.id
191         tag.save!
192       end
193
194       members = self.members
195
196       RelationMember.delete_all(['id = ?', self.id])
197
198       members.each do |n|
199         mem = RelationMember.new
200         mem.id = self.id
201         mem.member_type = n[0];
202         mem.member_id = n[1];
203         mem.member_role = n[2];
204         mem.save!
205       end
206
207       old_relation = OldRelation.from_relation(self)
208       old_relation.timestamp = t
209       old_relation.save_with_dependencies!
210     end
211   end
212
213   def preconditions_ok?
214     # These are hastables that store an id in the index of all 
215     # the nodes/way/relations that have already been added.
216     # Once we know the id of the node/way/relation exists
217     # we check to see if it is already existing in the hashtable
218     # if it does, then we return false. Otherwise
219     # we add it to the relevant hash table, with the value true..
220     # Thus if you have nodes with the ids of 50 and 1 already in the
221     # relation, then the hash table nodes would contain:
222     # => {50=>true, 1=>true}
223     nodes = Hash.new
224     ways = Hash.new
225     relations = Hash.new
226     self.members.each do |m|
227       if (m[0] == "node")
228         n = Node.find(:first, :conditions => ["id = ?", m[1]])
229         unless n and n.visible 
230           return false
231         end
232         if nodes[m[1]]
233           return false
234         else
235           nodes[m[1]] = true
236         end
237       elsif (m[0] == "way")
238         w = Way.find(:first, :conditions => ["id = ?", m[1]])
239         unless w and w.visible and w.preconditions_ok?
240           return false
241         end
242         if ways[m[1]]
243           return false
244         else
245           ways[m[1]] = true
246         end
247       elsif (m[0] == "relation")
248         e = Relation.find(:first, :conditions => ["id = ?", m[1]])
249         unless e and e.visible and e.preconditions_ok?
250           return false
251         end
252         if relations[m[1]]
253           return false
254         else
255           relations[m[1]] = true
256         end
257       else
258         return false
259       end
260     end
261     return true
262   rescue
263     return false
264   end
265
266   # Temporary method to match interface to nodes
267   def tags_as_hash
268     return self.tags
269   end
270 end