From: Gabriel Ebner Date: Sun, 23 Sep 2007 20:37:16 +0000 (+0000) Subject: rails_port_0.5: Merge rails_port r4664. X-Git-Tag: live~8083^2~15 X-Git-Url: https://git.openstreetmap.org/rails.git/commitdiff_plain/0f91ad89663fbadca007b1c4dce03f9a76ad0e5f?hp=868ce8d467210564211f6ed68f5f40fdd444ded5 rails_port_0.5: Merge rails_port r4664. --- diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index 696407109..2e693042e 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -132,7 +132,8 @@ class ApiController < ApplicationController # check the bbox isn't too large requested_area = (max_lat-min_lat)*(max_lon-min_lon) if requested_area > MAX_REQUEST_AREA - report_error("The maximum bbox size is " + MAX_REQUEST_AREA.to_s + ", and your request was too large. Either request a smaller area, or use planet.osm") + report_error("The maximum bbox size is " + MAX_REQUEST_AREA.to_s + + ", and your request was too large. Either request a smaller area, or use planet.osm") return end @@ -147,68 +148,38 @@ class ApiController < ApplicationController end if node_ids.length == 0 - render :text => "", :content_type => "text/xml" + render :text => "", :content_type => "text/xml" return end - # grab the segments - segments = Array.new - if node_ids.length > 0 - node_ids_sql = "(#{node_ids.join(',')})" - # get the referenced segments - segments = Segment.find_by_sql "select * from current_segments where visible = 1 and (node_a in #{node_ids_sql} or node_b in #{node_ids_sql})" - end - # see if we have any missing nodes - segments_nodes = segments.collect {|segment| segment.node_a } - segments_nodes += segments.collect {|segment| segment.node_b } - - segments_nodes.uniq! - - missing_nodes = segments_nodes - node_ids - - # get missing nodes if there are any - nodes += Node.find(missing_nodes) if missing_nodes.length > 0 + relations = Array.new doc = OSM::API.new.get_xml_doc # get ways # find which ways are needed - segment_ids = segments.collect {|segment| segment.id } ways = Array.new - if segment_ids.length > 0 - way_segments = WaySegment.find_all_by_segment_id(segment_ids) - way_ids = way_segments.collect {|way_segment| way_segment.id } - ways = Way.find(way_ids) # NB: doesn't pick up segments, tags from db until accessed via way.way_segments etc. - - # seg_ids = way_segments.collect {|way_segment| way_segment.segment_id } - - list_of_way_segs = ways.collect {|way| way.way_segments} - list_of_way_segs.flatten! + if node_ids.length > 0 + way_nodes = WayNode.find_all_by_node_id(node_ids) + way_ids = way_nodes.collect {|way_node| way_node.id } + ways = Way.find(way_ids) - list_of_way_segments = list_of_way_segs.collect { |way_seg| way_seg.segment_id } + list_of_way_nodes = ways.collect { |way| + way.way_nodes.collect { |way_node| way_node.node_id } + } + list_of_way_nodes.flatten! - else - list_of_way_segments = Array.new + else + list_of_way_nodes = Array.new end - # - [0] in case some thing links to segment 0 which doesn't exist. Shouldn't actually ever happen but it does. FIXME: file a ticket for this - segments_to_fetch = (list_of_way_segments.uniq - segment_ids) - [0] + # - [0] in case some thing links to node 0 which doesn't exist. Shouldn't actually ever happen but it does. FIXME: file a ticket for this + nodes_to_fetch = (list_of_way_nodes.uniq - node_ids) - [0] - if segments_to_fetch.length > 0 - segments += Segment.find(segments_to_fetch) + if nodes_to_fetch.length > 0 + nodes += Node.find(nodes_to_fetch) end - # get more nodes - # - - segments_nodes = segments.collect {|segment| segment.node_a } - segments_nodes += segments.collect {|segment| segment.node_b } - - node_ids_a = nodes.collect {|node| node.id } - - nodes_to_get = segments_nodes - node_ids_a - nodes += Node.find(nodes_to_get) if nodes_to_get.length > 0 - visible_nodes = {} user_display_name_cache = {} @@ -219,19 +190,41 @@ class ApiController < ApplicationController end end - visible_segments = {} - - segments.each do |segment| - if visible_nodes[segment.node_a] and visible_nodes[segment.node_b] and segment.visible? - doc.root << segment.to_xml_node(user_display_name_cache) - visible_segments[segment.id] = segment - end - end - + way_ids = Array.new ways.each do |way| - doc.root << way.to_xml_node(visible_segments, user_display_name_cache) if way.visible? + if way.visible? + doc.root << way.to_xml_node(visible_nodes, user_display_name_cache) + way_ids << way.id + end end + # collect relationships. currently done in one big block at the end; + # may need to move this upwards if people want automatic completion of + # relationships, i.e. deliver referenced objects like we do with ways... + relations = Relation.find_by_sql("select e.* from current_relations e,current_relation_members em where " + + "e.visible=1 and " + + "em.id = e.id and em.member_type='node' and em.member_id in (#{visible_nodes.keys.join(',')})") + relations += Relation.find_by_sql("select e.* from current_relations e,current_relation_members em where " + + "e.visible=1 and " + + "em.id = e.id and em.member_type='way' and em.member_id in (#{way_ids.join(',')})") + # we do not normally return the "other" partners referenced by an relation, + # e.g. if we return a way A that is referenced by relation X, and there's + # another way B also referenced, that is not returned. But we do make + # an exception for cases where an relation references another *relation*; + # in that case we return that as well (but we don't go recursive here) + relation_ids = relations.collect { |relation| relation.id } + if relation_ids.length > 0 + relations += Relation.find_by_sql("select e.* from current_relations e,current_relation_members em where " + + "e.visible=1 and " + + "em.id = e.id and em.member_type='relation' and em.member_id in (#{relation_ids.join(',')})") + end + + # this "uniq" may be slightly inefficient; it may be better to first collect and output + # all node-related relations, then find the *not yet covered* way-related ones etc. + relations.uniq.each do |relation| + doc.root << relation.to_xml_node(user_display_name_cache) + end + render :text => doc.to_s, :content_type => "text/xml" #exit when we have too many requests @@ -247,8 +240,8 @@ class ApiController < ApplicationController api = XML::Node.new 'api' version = XML::Node.new 'version' - version['minimum'] = '0.4'; - version['maximum'] = '0.4'; + version['minimum'] = '0.5'; + version['maximum'] = '0.5'; api << version area = XML::Node.new 'area' area['maximum'] = MAX_REQUEST_AREA.to_s; diff --git a/app/controllers/node_controller.rb b/app/controllers/node_controller.rb index 0a3897fe9..62987cf94 100644 --- a/app/controllers/node_controller.rb +++ b/app/controllers/node_controller.rb @@ -70,7 +70,9 @@ class NodeController < ApplicationController node = Node.find(params[:id]) if node.visible - if Segment.find(:first, :conditions => [ "visible = 1 and (node_a = ? or node_b = ?)", node.id, node.id]) + if WayNode.find(:first, :joins => "INNER JOIN current_ways ON current_ways.id = current_way_nodes.id", :conditions => [ "current_ways.visible = 1 AND current_way_nodes.node_id = ?", node.id ]) + render :nothing => true, :status => :precondition_failed + elsif RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='node' and member_id=?", params[:id]]) render :nothing => true, :status => :precondition_failed else node.user_id = @user.id diff --git a/app/controllers/old_segment_controller.rb b/app/controllers/old_segment_controller.rb deleted file mode 100644 index 410760f2b..000000000 --- a/app/controllers/old_segment_controller.rb +++ /dev/null @@ -1,25 +0,0 @@ -class OldSegmentController < ApplicationController - require 'xml/libxml' - - session :off - before_filter :check_read_availability - after_filter :compress_output - - def history - begin - segment = Segment.find(params[:id]) - - doc = OSM::API.new.get_xml_doc - - segment.old_segments.each do |old_segment| - doc.root << old_segment.to_xml_node - end - - render :text => doc.to_s, :content_type => "text/xml" - rescue ActiveRecord::RecordNotFound - render :nothing => true, :status => :not_found - rescue - render :nothing => true, :status => :internal_server_error - end - end -end diff --git a/app/controllers/old_way_node_controller.rb b/app/controllers/old_way_node_controller.rb new file mode 100644 index 000000000..69fa97048 --- /dev/null +++ b/app/controllers/old_way_node_controller.rb @@ -0,0 +1,2 @@ +class OldWayNodeController < ApplicationController +end diff --git a/app/controllers/old_way_segment_controller.rb b/app/controllers/old_way_segment_controller.rb deleted file mode 100644 index 4e9404c9b..000000000 --- a/app/controllers/old_way_segment_controller.rb +++ /dev/null @@ -1,2 +0,0 @@ -class OldWaySegmentController < ApplicationController -end diff --git a/app/controllers/relation_controller.rb b/app/controllers/relation_controller.rb new file mode 100644 index 000000000..d00e9e077 --- /dev/null +++ b/app/controllers/relation_controller.rb @@ -0,0 +1,218 @@ +class RelationController < ApplicationController + require 'xml/libxml' + + before_filter :authorize, :only => [:create, :update, :delete] + before_filter :check_availability, :only => [:create, :update, :delete] + + after_filter :compress_output + + def create + if request.put? + relation = Relation.from_xml(request.raw_post, true) + + if relation + if !relation.preconditions_ok? + render :nothing => true, :status => :precondition_failed + else + relation.user_id = @user.id + relation.save_with_history! + + render :text => relation.id.to_s, :content_type => "text/plain" + end + else + render :nothing => true, :status => :bad_request + end + else + render :nothing => true, :status => :method_not_allowed + end + end + + def read + begin + relation = Relation.find(params[:id]) + + if relation.visible + render :text => relation.to_xml.to_s, :content_type => "text/xml" + else + render :nothing => true, :status => :gone + end + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => :not_found + rescue + render :nothing => true, :status => :internal_server_error + end + end + + def update + begin + relation = Relation.find(params[:id]) + + if relation.visible + new_relation = Relation.from_xml(request.raw_post) + + if new_relation and new_relation.id == relation.id + if !new_relation.preconditions_ok? + render :nothing => true, :status => :precondition_failed + else + relation.user_id = @user.id + relation.tags = new_relation.tags + relation.members = new_relation.members + relation.visible = true + relation.save_with_history! + + render :nothing => true + end + else + render :nothing => true, :status => :bad_request + end + else + render :nothing => true, :status => :gone + end + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => :not_found + rescue + render :nothing => true, :status => :internal_server_error + end + end + + def delete +#XXX check if member somewhere! + begin + relation = Relation.find(params[:id]) + + if relation.visible + 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=?", params[:id]]) + render :nothing => true, :status => :precondition_failed + else + relation.user_id = @user.id + relation.tags = [] + relation.members = [] + relation.visible = false + relation.save_with_history! + + render :nothing => true + end + else + render :nothing => true, :status => :gone + end + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => :not_found + rescue + render :nothing => true, :status => :internal_server_error + end + end + + # ----------------------------------------------------------------- + # full + # + # input parameters: id + # + # returns XML representation of one relation object plus all its + # members, plus all nodes part of member ways + # ----------------------------------------------------------------- + def full + begin + relation = Relation.find(params[:id]) + + if relation.visible + + # first collect nodes, ways, and relations referenced by this relation. + + ways = Way.find_by_sql("select w.* from current_ways w,current_relation_members rm where "+ + "rm.member_type='way' and rm.member_id=w.id and rm.id=#{relation.id}"); + nodes = Node.find_by_sql("select n.* from current_nodes n,current_relation_members rm where "+ + "rm.member_type='node' and rm.member_id=n.id and rm.id=#{relation.id}"); + # note query is built to exclude self just in case. + relations = Relation.find_by_sql("select r.* from current_relations r,current_relation_members rm where "+ + "rm.member_type='relation' and rm.member_id=r.id and rm.id=#{relation.id} and r.id<>rm.id"); + + # now additionally collect nodes referenced by ways. Note how we recursively + # evaluate ways but NOT relations. + + node_ids = nodes.collect {|node| node.id } + way_node_ids = ways.collect { |way| + way.way_nodes.collect { |way_node| way_node.node_id } + } + way_node_ids.flatten! + way_node_ids.uniq + way_node_ids -= node_ids + nodes += Node.find(way_node_ids) + + # create XML. + doc = OSM::API.new.get_xml_doc + user_display_name_cache = {} + + nodes.each do |node| + if node.visible? # should be unnecessary if data is consistent. + doc.root << node.to_xml_node(user_display_name_cache) + end + end + ways.each do |way| + if way.visible? # should be unnecessary if data is consistent. + doc.root << way.to_xml_node(user_display_name_cache) + end + end + relations.each do |rel| + if rel.visible? # should be unnecessary if data is consistent. + doc.root << rel.to_xml_node(user_display_name_cache) + end + end + # finally add self and output + doc.root << relation.to_xml_node(user_display_name_cache) + render :text => doc.to_s, :content_type => "text/xml" + + else + + render :nothing => true, :status => :gone + end + + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => :not_found + + rescue + render :nothing => true, :status => :internal_server_error + end + end + + def relations + ids = params['relations'].split(',').collect { |w| w.to_i } + + if ids.length > 0 + doc = OSM::API.new.get_xml_doc + + Relation.find(ids).each do |relation| + doc.root << relation.to_xml_node + end + + render :text => doc.to_s, :content_type => "text/xml" + else + render :nothing => true, :status => :bad_request + end + end + + def relations_for_way + relations_for_object("way") + end + def relations_for_node + relations_for_object("node") + end + def relations_for_relation + relations_for_object("relation") + end + + def relations_for_object(objtype) + relationids = RelationMember.find(:all, :conditions => ['member_type=? and member_id=?', objtype, params[:id]]).collect { |ws| ws.id }.uniq + + if relationids.length > 0 + doc = OSM::API.new.get_xml_doc + + Relation.find(relationids).each do |relation| + doc.root << relation.to_xml_node + end + + render :text => doc.to_s, :content_type => "text/xml" + else + render :nothing => true, :status => :not_found + end + end +end diff --git a/app/controllers/relation_member_controller.rb b/app/controllers/relation_member_controller.rb new file mode 100644 index 000000000..3eb7fcaa1 --- /dev/null +++ b/app/controllers/relation_member_controller.rb @@ -0,0 +1,5 @@ +class RelationMemberController < ApplicationController + + + +end diff --git a/app/controllers/relation_tag_controller.rb b/app/controllers/relation_tag_controller.rb new file mode 100644 index 000000000..c58364c4a --- /dev/null +++ b/app/controllers/relation_tag_controller.rb @@ -0,0 +1,9 @@ +class RelationTagController < ApplicationController + layout 'site' + + def search + @tags = RelationTag.find(:all, :limit => 11, :conditions => ["match(v) against (?)", params[:query][:query].to_s] ) + end + + +end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index de305fca3..b3e7ca97b 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -1,26 +1,23 @@ class SearchController < ApplicationController - # Support searching for nodes, segments, ways, or all + # Support searching for nodes, ways, or all # Can search by tag k, v, or both (type->k,value->v) # Can search by name (k=name,v=....) after_filter :compress_output def search_all - do_search(true,true,true) + do_search(true,true) end def search_ways - do_search(true,false,false) - end - def search_segments - do_search(false,true,false) + do_search(true,false) end def search_nodes - do_search(false,false,true) + do_search(false,true) end - def do_search(do_ways,do_segments,do_nodes) + def do_search(do_ways,do_nodes) type = params['type'] value = params['value'] unless type or value @@ -33,7 +30,6 @@ class SearchController < ApplicationController way_ids = Array.new ways = Array.new - segments = Array.new nodes = Array.new # Matching for tags table @@ -75,21 +71,13 @@ class SearchController < ApplicationController ways = Way.find(:all, :conditions => cond_tbl, :limit => 100) end - # Now, segments matching - if do_segments - segments = Segment.find(:all, :conditions => cond_tags, :limit => 500) - end - # Now, nodes if do_nodes nodes = Node.find(:all, :conditions => cond_tags, :limit => 2000) end - # Fetch any segments needed for our ways (only have matching segments so far) - segments += Segment.find(ways.collect { |w| w.segs }.uniq) - - # Fetch any nodes needed for our segments (only have matching nodes so far) - nodes += Node.find(segments.collect { |s| [s.node_a, s.node_b] }.flatten.uniq) + # Fetch any node needed for our ways (only have matching nodes so far) + nodes += Node.find(ways.collect { |w| w.nds }.uniq) # Print user_display_name_cache = {} @@ -98,10 +86,6 @@ class SearchController < ApplicationController doc.root << node.to_xml_node(user_display_name_cache) end - segments.each do |segment| - doc.root << segment.to_xml_node(user_display_name_cache) - end - ways.each do |way| doc.root << way.to_xml_node(user_display_name_cache) end diff --git a/app/controllers/segment_controller.rb b/app/controllers/segment_controller.rb deleted file mode 100644 index b547d5cd1..000000000 --- a/app/controllers/segment_controller.rb +++ /dev/null @@ -1,135 +0,0 @@ -class SegmentController < ApplicationController - require 'xml/libxml' - - session :off - before_filter :authorize, :only => [:create, :update, :delete] - before_filter :check_write_availability, :only => [:create, :update, :delete] - before_filter :check_read_availability, :except => [:create, :update, :delete] - after_filter :compress_output - - def create - if request.put? - segment = Segment.from_xml(request.raw_post, true) - - if segment - if segment.node_a == segment.node_b - render :nothing => true, :status => :expectation_failed - elsif !segment.preconditions_ok? - render :nothing => true, :status => :precondition_failed - else - segment.user_id = @user.id - segment.from_node = Node.find(segment.node_a.to_i) - segment.to_node = Node.find(segment.node_b.to_i) - segment.save_with_history! - - render :text => segment.id.to_s, :content_type => "text/plain" - end - else - render :nothing => true, :status => :bad_request - end - else - render :nothing => true, :status => :method_not_allowed - end - end - - def read - begin - segment = Segment.find(params[:id]) - - if segment.visible - render :text => segment.to_xml.to_s, :content_type => "text/xml" - else - render :nothing => true, :status => :gone - end - rescue ActiveRecord::RecordNotFound - render :nothing => true, :status => :not_found - end - end - - def update - begin - segment = Segment.find(params[:id]) - - if segment.visible - new_segment = Segment.from_xml(request.raw_post) - - if new_segment and new_segment.id == segment.id - if new_segment.node_a == new_segment.node_b - render :nothing => true, :status => :expectation_failed - elsif !new_segment.preconditions_ok? - render :nothing => true, :status => :precondition_failed - else - segment.user_id = @user.id - segment.node_a = new_segment.node_a - segment.node_b = new_segment.node_b - segment.tags = new_segment.tags - segment.visible = new_segment.visible - segment.save_with_history! - - render :nothing => true - end - else - render :nothing => true, :status => :bad_request - end - else - render :nothing => true, :status => :gone - end - rescue ActiveRecord::RecordNotFound - render :nothing => true, :status => :not_found - end - end - - def delete - begin - segment = Segment.find(params[:id]) - - if segment.visible - if WaySegment.find(:first, :joins => "INNER JOIN current_ways ON current_ways.id = current_way_segments.id", :conditions => [ "current_ways.visible = 1 AND current_way_segments.segment_id = ?", segment.id ]) - render :nothing => true, :status => :precondition_failed - else - segment.user_id = @user.id - segment.visible = 0 - segment.save_with_history! - - render :nothing => true - end - else - render :nothing => true, :status => :gone - end - rescue ActiveRecord::RecordNotFound - render :nothing => true, :status => :not_found - end - end - - def segments - ids = params['segments'].split(',').collect { |s| s.to_i } - - if ids.length > 0 - doc = OSM::API.new.get_xml_doc - - Segment.find(ids).each do |segment| - doc.root << segment.to_xml_node - end - - render :text => doc.to_s, :content_type => "text/xml" - else - render :nothing => true, :status => :bad_request - end - end - - def segments_for_node - segmentids = Segment.find(:all, :conditions => ['node_a = ? OR node_b = ?', params[:id], params[:id]]).collect { |s| s.id }.uniq - - if segmentids.length > 0 - doc = OSM::API.new.get_xml_doc - - Segment.find(segmentids).each do |segment| - doc.root << segment.to_xml_node - end - - render :text => doc.to_s, :content_type => "text/xml" - else - render :nothing => true, :status => :bad_request - end - end -end diff --git a/app/controllers/site_controller.rb b/app/controllers/site_controller.rb index 748200834..767220c79 100644 --- a/app/controllers/site_controller.rb +++ b/app/controllers/site_controller.rb @@ -6,7 +6,7 @@ class SiteController < ApplicationController way = Way.find(params[:id]) begin - node = way.way_segments.first.segment.from_node + node = way.way_nodes.first.node redirect_to :controller => 'site', :action => 'index', :lat => node.latitude, :lon => node.longitude, :zoom => 6 rescue redirect_to :back diff --git a/app/controllers/swf_controller.rb b/app/controllers/swf_controller.rb index 47ddcf902..40969c20f 100644 --- a/app/controllers/swf_controller.rb +++ b/app/controllers/swf_controller.rb @@ -92,7 +92,7 @@ class SwfController < ApplicationController sql="SELECT cn1.latitude*0.0000001 AS lat1,cn1.longitude*0.0000001 AS lon1,"+ " cn2.latitude*0.0000001 AS lat2,cn2.longitude*0.0000001 AS lon2 "+ " FROM current_segments "+ - " LEFT OUTER JOIN current_way_segments"+ + " LEFT OUTER JOIN current_way_nodes"+ " ON segment_id=current_segments.id,"+ " current_nodes AS cn1,current_nodes AS cn2"+ " WHERE "+OSM.sql_for_area(ymin,xmin,ymax,xmax,"cn1.")+ diff --git a/app/controllers/way_controller.rb b/app/controllers/way_controller.rb index d569991af..7b7dbe81c 100644 --- a/app/controllers/way_controller.rb +++ b/app/controllers/way_controller.rb @@ -55,7 +55,7 @@ class WayController < ApplicationController else way.user_id = @user.id way.tags = new_way.tags - way.segs = new_way.segs + way.nds = new_way.nds way.visible = true way.save_with_history! @@ -77,18 +77,24 @@ class WayController < ApplicationController way = Way.find(params[:id]) if way.visible - way.user_id = @user.id - way.tags = [] - way.segs = [] - way.visible = false - way.save_with_history! + if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='way' and member_id=?", params[:id]]) + render :nothing => true, :status => :precondition_failed + else + way.user_id = @user.id + way.tags = [] + way.nds = [] + way.visible = false + way.save_with_history! - render :nothing => true + render :nothing => true + end else render :nothing => true, :status => :gone end rescue ActiveRecord::RecordNotFound render :nothing => true, :status => :not_found + rescue => ex + puts ex end end @@ -97,23 +103,14 @@ class WayController < ApplicationController way = Way.find(params[:id]) if way.visible - # In future, we might want to do all the data fetch in one step - seg_ids = way.segs + [-1] - segments = Segment.find_by_sql "select * from current_segments where visible = 1 and id IN (#{seg_ids.join(',')})" - - node_ids = segments.collect {|segment| segment.node_a } - node_ids += segments.collect {|segment| segment.node_b } - node_ids += [-1] - nodes = Node.find(:all, :conditions => "visible = 1 AND id IN (#{node_ids.join(',')})") + nd_ids = way.nds + [-1] + nodes = Node.find(:all, :conditions => "visible = 1 AND id IN (#{nd_ids.join(',')})") # Render doc = OSM::API.new.get_xml_doc nodes.each do |node| doc.root << node.to_xml_node() end - segments.each do |segment| - doc.root << segment.to_xml_node() - end doc.root << way.to_xml_node() render :text => doc.to_s, :content_type => "text/xml" @@ -145,8 +142,8 @@ class WayController < ApplicationController end end - def ways_for_segment - wayids = WaySegment.find(:all, :conditions => ['segment_id = ?', params[:id]]).collect { |ws| ws.id }.uniq + def ways_for_node + wayids = WayNode.find(:all, :conditions => ['node_id = ?', params[:id]]).collect { |ws| ws.id }.uniq if wayids.length > 0 doc = OSM::API.new.get_xml_doc diff --git a/app/controllers/way_node_controller.rb b/app/controllers/way_node_controller.rb new file mode 100644 index 000000000..41549ed8f --- /dev/null +++ b/app/controllers/way_node_controller.rb @@ -0,0 +1,5 @@ +class WayNodeController < ApplicationController + + + +end diff --git a/app/controllers/way_segment_controller.rb b/app/controllers/way_segment_controller.rb deleted file mode 100644 index e37303f3e..000000000 --- a/app/controllers/way_segment_controller.rb +++ /dev/null @@ -1,5 +0,0 @@ -class WaySegmentController < ApplicationController - - - -end diff --git a/app/helpers/old_segment_helper.rb b/app/helpers/old_segment_helper.rb deleted file mode 100644 index 3650f0a50..000000000 --- a/app/helpers/old_segment_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module OldSegmentHelper -end diff --git a/app/helpers/old_way_node_helper.rb b/app/helpers/old_way_node_helper.rb new file mode 100644 index 000000000..933d6613f --- /dev/null +++ b/app/helpers/old_way_node_helper.rb @@ -0,0 +1,2 @@ +module OldWayNodeHelper +end diff --git a/app/helpers/old_way_segment_helper.rb b/app/helpers/old_way_segment_helper.rb deleted file mode 100644 index 8299bdd27..000000000 --- a/app/helpers/old_way_segment_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module OldWaySegmentHelper -end diff --git a/app/helpers/segment_helper.rb b/app/helpers/segment_helper.rb deleted file mode 100644 index d6be137a8..000000000 --- a/app/helpers/segment_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module SegmentHelper -end diff --git a/app/helpers/way_node_helper.rb b/app/helpers/way_node_helper.rb new file mode 100644 index 000000000..884dbccb8 --- /dev/null +++ b/app/helpers/way_node_helper.rb @@ -0,0 +1,2 @@ +module WayNodeHelper +end diff --git a/app/helpers/way_segment_helper.rb b/app/helpers/way_segment_helper.rb deleted file mode 100644 index f2ebd3960..000000000 --- a/app/helpers/way_segment_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module WaySegmentHelper -end diff --git a/app/models/old_relation.rb b/app/models/old_relation.rb new file mode 100644 index 000000000..6da7814c2 --- /dev/null +++ b/app/models/old_relation.rb @@ -0,0 +1,111 @@ +class OldRelation < ActiveRecord::Base + set_table_name 'relations' + + belongs_to :user + + def self.from_relation(relation) + old_relation = OldRelation.new + old_relation.visible = relation.visible + old_relation.user_id = relation.user_id + old_relation.timestamp = relation.timestamp + old_relation.id = relation.id + old_relation.members = relation.members + old_relation.tags = relation.tags + return old_relation + end + + def save_with_dependencies! + + # see comment in old_way.rb ;-) + save! + clear_aggregation_cache + clear_association_cache + @attributes.update(OldRelation.find(:first, :conditions => ['id = ? AND timestamp = ?', self.id, self.timestamp]).instance_variable_get('@attributes')) + + # ok, you can touch from here on + + self.tags.each do |k,v| + tag = OldRelationTag.new + tag.k = k + tag.v = v + tag.id = self.id + tag.version = self.version + tag.save! + end + + i = 1 + self.members.each do |m| + member = OldRelationMember.new + member.id = self.id + member.member_type = m[0] + member.member_id = m[1] + member.member_role = m[2] + member.version = self.version + member.save! + end + end + + def members + unless @members + @members = Array.new + OldRelationMember.find(:all, :conditions => ["id = ? AND version = ?", self.id, self.version]).each do |m| + @members += [[m.type,m.id,m.role]] + end + end + @members + end + + def tags + unless @tags + @tags = Hash.new + OldRelationTag.find(:all, :conditions => ["id = ? AND version = ?", self.id, self.version]).each do |tag| + @tags[tag.k] = tag.v + end + end + @tags = Hash.new unless @tags + @tags + end + + def members=(s) + @members = s + end + + def tags=(t) + @tags = t + end + +# has_many :relation_segments, :class_name => 'OldRelationSegment', :foreign_key => 'id' +# has_many :relation_tags, :class_name => 'OldRelationTag', :foreign_key => 'id' + + def old_members + OldRelationMember.find(:all, :conditions => ['id = ? AND version = ?', self.id, self.version]) + end + + def old_tags + OldRelationTag.find(:all, :conditions => ['id = ? AND version = ?', self.id, self.version]) + end + + def to_xml_node + el1 = XML::Node.new 'relation' + el1['id'] = self.id.to_s + el1['visible'] = self.visible.to_s + el1['timestamp'] = self.timestamp.xmlschema + el1['user'] = self.user.display_name if self.user.data_public? + + self.old_members.each do |member| + e = XML::Node.new 'member' + e['type'] = member.member_type.to_s + e['ref'] = member.member_id.to_s # "id" is considered uncool here as it should be unique in XML + e['role'] = member.member_role.to_s + el1 << e + end + + self.old_tags.each do |tag| + e = XML::Node.new 'tag' + e['k'] = tag.k + e['v'] = tag.v + el1 << e + end + return el1 + end +end diff --git a/app/models/old_relation_member.rb b/app/models/old_relation_member.rb new file mode 100644 index 000000000..9e2cce4c2 --- /dev/null +++ b/app/models/old_relation_member.rb @@ -0,0 +1,6 @@ +class OldRelationMember < ActiveRecord::Base + belongs_to :user + + set_table_name 'relation_members' + +end diff --git a/app/models/old_relation_tag.rb b/app/models/old_relation_tag.rb new file mode 100644 index 000000000..6ad4b452e --- /dev/null +++ b/app/models/old_relation_tag.rb @@ -0,0 +1,6 @@ +class OldRelationTag < ActiveRecord::Base + belongs_to :user + + set_table_name 'relation_tags' + +end diff --git a/app/models/old_segment.rb b/app/models/old_segment.rb deleted file mode 100644 index c243c522d..000000000 --- a/app/models/old_segment.rb +++ /dev/null @@ -1,33 +0,0 @@ -class OldSegment < ActiveRecord::Base - set_table_name 'segments' - - validates_presence_of :user_id, :timestamp - validates_inclusion_of :visible, :in => [ true, false ] - validates_numericality_of :node_a, :node_b - - belongs_to :user - - def self.from_segment(segment) - old_segment = OldSegment.new - old_segment.node_a = segment.node_a - old_segment.node_b = segment.node_b - old_segment.visible = segment.visible - old_segment.tags = segment.tags - old_segment.timestamp = segment.timestamp - old_segment.user_id = segment.user_id - old_segment.id = segment.id - return old_segment - end - - def to_xml_node - el1 = XML::Node.new 'segment' - el1['id'] = self.id.to_s - el1['from'] = self.node_a.to_s - el1['to'] = self.node_b.to_s - el1['user'] = self.user.display_name if self.user.data_public? - Segment.split_tags(el1, self.tags) - el1['visible'] = self.visible.to_s - el1['timestamp'] = self.timestamp.xmlschema - return el1 - end -end diff --git a/app/models/old_way.rb b/app/models/old_way.rb index e4df25816..bb267dffb 100644 --- a/app/models/old_way.rb +++ b/app/models/old_way.rb @@ -9,7 +9,7 @@ class OldWay < ActiveRecord::Base old_way.user_id = way.user_id old_way.timestamp = way.timestamp old_way.id = way.id - old_way.segs = way.segs + old_way.nds = way.nds old_way.tags = way.tags return old_way end @@ -39,23 +39,25 @@ class OldWay < ActiveRecord::Base end i = 1 - self.segs.each do |n| - seg = OldWaySegment.new - seg.id = self.id - seg.segment_id = n - seg.version = self.version - seg.save! + self.nds.each do |n| + nd = OldWayNode.new + nd.id = self.id + nd.node_id = n + nd.sequence_id = i + nd.version = self.version + nd.save! + i += 1 end end - def segs - unless @segs - @segs = Array.new - OldWaySegment.find(:all, :conditions => ["id = ? AND version = ?", self.id, self.version], :order => "sequence_id").each do |seg| - @segs += [seg.segment_id] + def nds + unless @nds + @nds = Array.new + OldWayNode.find(:all, :conditions => ["id = ? AND version = ?", self.id, self.version], :order => "sequence_id").each do |nd| + @nds += [nd.node_id] end end - @segs + @nds end def tags @@ -69,19 +71,19 @@ class OldWay < ActiveRecord::Base @tags end - def segs=(s) - @segs = s + def nds=(s) + @nds = s end def tags=(t) @tags = t end -# has_many :way_segments, :class_name => 'OldWaySegment', :foreign_key => 'id' +# has_many :way_nodes, :class_name => 'OldWayNode', :foreign_key => 'id' # has_many :way_tags, :class_name => 'OldWayTag', :foreign_key => 'id' - def old_segments - OldWaySegment.find(:all, :conditions => ['id = ? AND version = ?', self.id, self.version]) + def old_nodes + OldWayNode.find(:all, :conditions => ['id = ? AND version = ?', self.id, self.version]) end def old_tags @@ -95,9 +97,9 @@ class OldWay < ActiveRecord::Base el1['timestamp'] = self.timestamp.xmlschema el1['user'] = self.user.display_name if self.user.data_public? - self.old_segments.each do |seg| # FIXME need to make sure they come back in the right order - e = XML::Node.new 'seg' - e['id'] = seg.segment_id.to_s + self.old_nodes.each do |nd| # FIXME need to make sure they come back in the right order + e = XML::Node.new 'nd' + e['id'] = nd.node_id.to_s el1 << e end diff --git a/app/models/old_way_node.rb b/app/models/old_way_node.rb new file mode 100644 index 000000000..5260ee698 --- /dev/null +++ b/app/models/old_way_node.rb @@ -0,0 +1,6 @@ +class OldWayNode < ActiveRecord::Base + belongs_to :user + + set_table_name 'way_nodes' + +end diff --git a/app/models/old_way_segment.rb b/app/models/old_way_segment.rb deleted file mode 100644 index 45d1ce009..000000000 --- a/app/models/old_way_segment.rb +++ /dev/null @@ -1,6 +0,0 @@ -class OldWaySegment < ActiveRecord::Base - belongs_to :user - - set_table_name 'way_segments' - -end diff --git a/app/models/relation.rb b/app/models/relation.rb new file mode 100644 index 000000000..a5d463ffb --- /dev/null +++ b/app/models/relation.rb @@ -0,0 +1,207 @@ +class Relation < ActiveRecord::Base + require 'xml/libxml' + + belongs_to :user + + has_many :relation_members, :foreign_key => 'id' + has_many :relation_tags, :foreign_key => 'id' + + has_many :old_relations, :foreign_key => 'id', :order => 'version' + + set_table_name 'current_relations' + + def self.from_xml(xml, create=false) + begin + p = XML::Parser.new + p.string = xml + doc = p.parse + + relation = Relation.new + + doc.find('//osm/relation').each do |pt| + if !create and pt['id'] != '0' + relation.id = pt['id'].to_i + end + + if create + relation.timestamp = Time.now + relation.visible = true + else + if pt['timestamp'] + relation.timestamp = Time.parse(pt['timestamp']) + end + end + + pt.find('tag').each do |tag| + relation.add_tag_keyval(tag['k'], tag['v']) + end + + pt.find('member').each do |member| + relation.add_member(member['type'], member['ref'], member['role']) + end + end + rescue + relation = nil + end + + return relation + end + + def to_xml + doc = OSM::API.new.get_xml_doc + doc.root << to_xml_node() + return doc + end + + def to_xml_node(user_display_name_cache = nil) + el1 = XML::Node.new 'relation' + el1['id'] = self.id.to_s + el1['visible'] = self.visible.to_s + el1['timestamp'] = self.timestamp.xmlschema + + user_display_name_cache = {} if user_display_name_cache.nil? + + if user_display_name_cache and user_display_name_cache.key?(self.user_id) + # use the cache if available + elsif self.user.data_public? + user_display_name_cache[self.user_id] = self.user.display_name + else + user_display_name_cache[self.user_id] = nil + end + + el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil? + + self.relation_members.each do |member| + p=0 + #if visible_members + # # if there is a list of visible members then use that to weed out deleted segments + # if visible_members[member.member_type][member.member_id] + # p=1 + # end + #else + # otherwise, manually go to the db to check things + if member.member.visible? + p=1 + end + #end + if p + e = XML::Node.new 'member' + e['type'] = member.member_type + e['ref'] = member.member_id.to_s + e['role'] = member.member_role + el1 << e + end + end + + self.relation_tags.each do |tag| + e = XML::Node.new 'tag' + e['k'] = tag.k + e['v'] = tag.v + el1 << e + end + return el1 + end + + # FIXME is this really needed? + def members + unless @members + @members = Array.new + self.relation_members.each do |member| + @members += [[member.member_type,member.member_id,member.member_role]] + end + end + @members + end + + def tags + unless @tags + @tags = Hash.new + self.relation_tags.each do |tag| + @tags[tag.k] = tag.v + end + end + @tags + end + + def members=(m) + @members = m + end + + def tags=(t) + @tags = t + end + + def add_member(type,id,role) + @members = Array.new unless @members + @members += [[type,id,role]] + end + + def add_tag_keyval(k, v) + @tags = Hash.new unless @tags + @tags[k] = v + end + + def save_with_history! + Relation.transaction do + t = Time.now + self.timestamp = t + self.save! + + tags = self.tags + + RelationTag.delete_all(['id = ?', self.id]) + + tags.each do |k,v| + tag = RelationTag.new + tag.k = k + tag.v = v + tag.id = self.id + tag.save! + end + + members = self.members + + RelationMember.delete_all(['id = ?', self.id]) + + members.each do |n| + mem = RelationMember.new + mem.id = self.id + mem.member_type = n[0]; + mem.member_id = n[1]; + mem.member_role = n[2]; + mem.save! + end + + old_relation = OldRelation.from_relation(self) + old_relation.timestamp = t + old_relation.save_with_dependencies! + end + end + + def preconditions_ok? + self.members.each do |m| + if (m[0] == "node") + n = Node.find(:first, :conditions => ["id = ?", m[1]]) + unless n and n.visible + return false + end + elsif (m[0] == "way") + w = Way.find(:first, :conditions => ["id = ?", m[1]]) + unless w and w.visible and w.preconditions_ok? + return false + end + elsif (m[0] == "relation") + e = Relation.find(:first, :conditions => ["id = ?", m[1]]) + unless e and e.visible and e.preconditions_ok? + return false + end + else + return false + end + end + return true + rescue + return false + end + +end diff --git a/app/models/relation_member.rb b/app/models/relation_member.rb new file mode 100644 index 000000000..79102853e --- /dev/null +++ b/app/models/relation_member.rb @@ -0,0 +1,30 @@ +class RelationMember < ActiveRecord::Base + set_table_name 'current_relation_members' + + # problem with RelationMember is that it may link to any one + # object (a node, a way, another relation), and belongs_to is + # not flexible enough for that. So we do this, which is ugly, + # but fortunately rails won't actually run the SQL behind that + # unless someone really accesses .node, .way, or + # .relation - which is what we do below based on member_type. + # (and no: the :condition on belongs_to doesn't work here as + # it is a condition on the *referenced* object not the + # *referencing* object!) + + belongs_to :node, :foreign_key => "member_id" + belongs_to :way, :foreign_key => "member_id" + belongs_to :relation, :foreign_key => "member_id" + + # so we define this "member" function that returns whatever it + # is. + + def member() + return (member_type == "node") ? node : (member_type == "way") ? way : relation + end + + # NOTE - relations are SUBJECTS of memberships. The fact that nodes, + # ways, and relations can be the OBJECT of a membership, + # i.e. a node/way/relation can be referenced throgh a + # RelationMember object, is NOT modelled in rails, i.e. these links + # have to be resolved manually, on demand. +end diff --git a/app/models/relation_tag.rb b/app/models/relation_tag.rb new file mode 100644 index 000000000..939165ebd --- /dev/null +++ b/app/models/relation_tag.rb @@ -0,0 +1,6 @@ +class RelationTag < ActiveRecord::Base + set_table_name 'current_relation_tags' + + belongs_to :relation, :foreign_key => 'id' + +end diff --git a/app/models/segment.rb b/app/models/segment.rb deleted file mode 100644 index 6916b79b1..000000000 --- a/app/models/segment.rb +++ /dev/null @@ -1,121 +0,0 @@ -class Segment < ActiveRecord::Base - require 'xml/libxml' - set_table_name 'current_segments' - - validates_presence_of :user_id, :timestamp - validates_inclusion_of :visible, :in => [ true, false ] - validates_numericality_of :node_a, :node_b - - has_many :old_segments, :foreign_key => :id - belongs_to :user - - # using belongs_to :foreign_key = 'node_*', since if use has_one :foreign_key = 'id', segment preconditions? fails checking for segment id in node table - belongs_to :from_node, :class_name => 'Node', :foreign_key => 'node_a' - belongs_to :to_node, :class_name => 'Node', :foreign_key => 'node_b' - - def self.from_xml(xml, create=false) - begin - p = XML::Parser.new - p.string = xml - doc = p.parse - - segment = Segment.new - - doc.find('//osm/segment').each do |pt| - segment.node_a = pt['from'].to_i - segment.node_b = pt['to'].to_i - - unless create - if pt['id'] != '0' - segment.id = pt['id'].to_i - end - end - - segment.visible = true - - if create - segment.timestamp = Time.now - else - if pt['timestamp'] - segment.timestamp = Time.parse(pt['timestamp']) - end - end - - tags = [] - - pt.find('tag').each do |tag| - tags << [tag['k'],tag['v']] - end - - tags = tags.collect { |k,v| "#{k}=#{v}" }.join(';') - tags = '' if tags.nil? - - segment.tags = tags - end - rescue - segment = nil - end - - return segment - end - - def save_with_history! - Segment.transaction do - self.timestamp = Time.now - self.save! - old_segment = OldSegment.from_segment(self) - old_segment.save! - end - end - - def to_xml - doc = OSM::API.new.get_xml_doc - doc.root << to_xml_node() - return doc - end - - def to_xml_node(user_display_name_cache = nil) - el1 = XML::Node.new 'segment' - el1['id'] = self.id.to_s - el1['from'] = self.node_a.to_s - el1['to'] = self.node_b.to_s - - user_display_name_cache = {} if user_display_name_cache.nil? - - if user_display_name_cache and user_display_name_cache.key?(self.user_id) - # use the cache if available - elsif self.user.data_public? - user_display_name_cache[self.user_id] = self.user.display_name - else - user_display_name_cache[self.user_id] = nil - end - - el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil? - - Segment.split_tags(el1, self.tags) - el1['visible'] = self.visible.to_s - el1['timestamp'] = self.timestamp.xmlschema - return el1 - end - - def self.split_tags(el, tags) - tags.split(';').each do |tag| - parts = tag.split('=') - key = '' - val = '' - key = parts[0].strip unless parts[0].nil? - val = parts[1].strip unless parts[1].nil? - if key != '' && val != '' - el2 = XML::Node.new('tag') - el2['k'] = key.to_s - el2['v'] = val.to_s - el << el2 - end - end - end - - def preconditions_ok? - from_node and from_node.visible and to_node and to_node.visible - end - -end diff --git a/app/models/way.rb b/app/models/way.rb index c65119235..e0a445074 100644 --- a/app/models/way.rb +++ b/app/models/way.rb @@ -3,7 +3,7 @@ class Way < ActiveRecord::Base belongs_to :user - has_many :way_segments, :foreign_key => 'id', :order => 'sequence_id' + has_many :way_nodes, :foreign_key => 'id', :order => 'sequence_id' has_many :way_tags, :foreign_key => 'id' has_many :old_ways, :foreign_key => 'id', :order => 'version' @@ -36,8 +36,8 @@ class Way < ActiveRecord::Base way.add_tag_keyval(tag['k'], tag['v']) end - pt.find('seg').each do |seg| - way.add_seg_num(seg['id']) + pt.find('nd').each do |nd| + way.add_nd_num(nd['ref']) end end rescue @@ -53,7 +53,7 @@ class Way < ActiveRecord::Base return doc end - def to_xml_node(visible_segments = nil, user_display_name_cache = nil) + def to_xml_node(visible_nodes = nil, user_display_name_cache = nil) el1 = XML::Node.new 'way' el1['id'] = self.id.to_s el1['visible'] = self.visible.to_s @@ -71,26 +71,26 @@ class Way < ActiveRecord::Base el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil? - # make sure segments are output in sequence_id order - ordered_segments = [] - self.way_segments.each do |seg| - if visible_segments - # if there is a list of visible segments then use that to weed out deleted segments - if visible_segments[seg.segment_id] - ordered_segments[seg.sequence_id] = seg.segment_id.to_s + # make sure nodes are output in sequence_id order + ordered_nodes = [] + self.way_nodes.each do |nd| + if visible_nodes + # if there is a list of visible nodes then use that to weed out deleted nodes + if visible_nodes[nd.node_id] + ordered_nodes[nd.sequence_id] = nd.node_id.to_s end else # otherwise, manually go to the db to check things - if seg.segment.visible? and seg.segment.from_node.visible? and seg.segment.to_node.visible? - ordered_segments[seg.sequence_id] = seg.segment_id.to_s + if nd.node.visible? and nd.node.visible? + ordered_nodes[nd.sequence_id] = nd.node_id.to_s end end end - ordered_segments.each do |seg_id| - if seg_id and seg_id != '0' - e = XML::Node.new 'seg' - e['id'] = seg_id + ordered_nodes.each do |nd_id| + if nd_id and nd_id != '0' + e = XML::Node.new 'nd' + e['ref'] = nd_id el1 << e end end @@ -104,14 +104,14 @@ class Way < ActiveRecord::Base return el1 end - def segs - unless @segs - @segs = Array.new - self.way_segments.each do |seg| - @segs += [seg.segment_id] + def nds + unless @nds + @nds = Array.new + self.way_nodes.each do |nd| + @nds += [nd.node_id] end end - @segs + @nds end def tags @@ -124,17 +124,17 @@ class Way < ActiveRecord::Base @tags end - def segs=(s) - @segs = s + def nds=(s) + @nds = s end def tags=(t) @tags = t end - def add_seg_num(n) - @segs = Array.new unless @segs - @segs << n.to_i + def add_nd_num(n) + @nds = Array.new unless @nds + @nds << n.to_i end def add_tag_keyval(k, v) @@ -164,18 +164,18 @@ class Way < ActiveRecord::Base end end - WaySegment.transaction do - segs = self.segs + WayNode.transaction do + nds = self.nds - WaySegment.delete_all(['id = ?', self.id]) + WayNode.delete_all(['id = ?', self.id]) i = 1 - segs.each do |n| - seg = WaySegment.new - seg.id = self.id - seg.segment_id = n - seg.sequence_id = i - seg.save! + nds.each do |n| + nd = WayNode.new + nd.id = self.id + nd.node_id = n + nd.sequence_id = i + nd.save! i += 1 end end @@ -186,10 +186,10 @@ class Way < ActiveRecord::Base end def preconditions_ok? - return false if self.segs.empty? - self.segs.each do |n| - segment = Segment.find(:first, :conditions => ["id = ?", n]) - unless segment and segment.visible and segment.preconditions_ok? + return false if self.nds.empty? + self.nds.each do |n| + node = Node.find(:first, :conditions => ["id = ?", n]) + unless node and node.visible return false end end diff --git a/app/models/way_node.rb b/app/models/way_node.rb new file mode 100644 index 000000000..06515fc20 --- /dev/null +++ b/app/models/way_node.rb @@ -0,0 +1,5 @@ +class WayNode < ActiveRecord::Base + set_table_name 'current_way_nodes' + + belongs_to :node +end diff --git a/app/models/way_segment.rb b/app/models/way_segment.rb deleted file mode 100644 index bc81f6b26..000000000 --- a/app/models/way_segment.rb +++ /dev/null @@ -1,5 +0,0 @@ -class WaySegment < ActiveRecord::Base - set_table_name 'current_way_segments' - - belongs_to :segment -end diff --git a/config/environment.rb b/config/environment.rb index e58631901..0d98c70bd 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -17,7 +17,7 @@ RAILS_GEM_VERSION = '1.2.3' require File.join(File.dirname(__FILE__), 'boot') # Application constants needed for routes.rb - must go before Initializer call -API_VERSION = ENV['OSM_API_VERSION'] || '0.4' +API_VERSION = ENV['OSM_API_VERSION'] || '0.5' # Custom logger class to format messages sensibly class OSMLogger < Logger diff --git a/config/routes.rb b/config/routes.rb index 0cbdd36a4..a1ddb4f49 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,21 +2,13 @@ ActionController::Routing::Routes.draw do |map| # API map.connect "api/#{API_VERSION}/node/create", :controller => 'node', :action => 'create' - map.connect "api/#{API_VERSION}/node/:id/segments", :controller => 'segment', :action => 'segments_for_node', :id => /\d+/ + map.connect "api/#{API_VERSION}/node/:id/ways", :controller => 'way', :action => 'ways_for_node', :id => /\d+/ map.connect "api/#{API_VERSION}/node/:id/history", :controller => 'old_node', :action => 'history', :id => /\d+/ map.connect "api/#{API_VERSION}/node/:id", :controller => 'node', :action => 'read', :id => /\d+/, :conditions => { :method => :get } map.connect "api/#{API_VERSION}/node/:id", :controller => 'node', :action => 'update', :id => /\d+/, :conditions => { :method => :put } map.connect "api/#{API_VERSION}/node/:id", :controller => 'node', :action => 'delete', :id => /\d+/, :conditions => { :method => :delete } map.connect "api/#{API_VERSION}/nodes", :controller => 'node', :action => 'nodes', :id => nil - map.connect "api/#{API_VERSION}/segment/create", :controller => 'segment', :action => 'create' - map.connect "api/#{API_VERSION}/segment/:id/ways", :controller => 'way', :action => 'ways_for_segment', :id => /\d+/ - map.connect "api/#{API_VERSION}/segment/:id/history", :controller => 'old_segment', :action => 'history', :id => /\d+/ - map.connect "api/#{API_VERSION}/segment/:id", :controller => 'segment', :action => 'read', :id => /\d+/, :conditions => { :method => :get } - map.connect "api/#{API_VERSION}/segment/:id", :controller => 'segment', :action => 'update', :id => /\d+/, :conditions => { :method => :put } - map.connect "api/#{API_VERSION}/segment/:id", :controller => 'segment', :action => 'delete', :id => /\d+/, :conditions => { :method => :delete } - map.connect "api/#{API_VERSION}/segments", :controller => 'segment', :action => 'segments', :id => nil - map.connect "api/#{API_VERSION}/way/create", :controller => 'way', :action => 'create' map.connect "api/#{API_VERSION}/way/:id/history", :controller => 'old_way', :action => 'history', :id => /\d+/ map.connect "api/#{API_VERSION}/way/:id/full", :controller => 'way', :action => 'full', :id => /\d+/ @@ -26,6 +18,13 @@ ActionController::Routing::Routes.draw do |map| map.connect "api/#{API_VERSION}/ways", :controller => 'way', :action => 'ways', :id => nil map.connect "api/#{API_VERSION}/capabilities", :controller => 'api', :action => 'capabilities' + map.connect "api/#{API_VERSION}/relation/create", :controller => 'relation', :action => 'create' + map.connect "api/#{API_VERSION}/relation/:id/history", :controller => 'old_relation', :action => 'history', :id => /\d+/ + map.connect "api/#{API_VERSION}/relation/:id/full", :controller => 'relation', :action => 'full', :id => /\d+/ + map.connect "api/#{API_VERSION}/relation/:id", :controller => 'relation', :action => 'read', :id => /\d+/, :conditions => { :method => :get } + map.connect "api/#{API_VERSION}/relation/:id", :controller => 'relation', :action => 'update', :id => /\d+/, :conditions => { :method => :put } + map.connect "api/#{API_VERSION}/relation/:id", :controller => 'relation', :action => 'delete', :id => /\d+/, :conditions => { :method => :delete } + map.connect "api/#{API_VERSION}/relations", :controller => 'relation', :action => 'relations', :id => nil map.connect "api/#{API_VERSION}/map", :controller => 'api', :action => 'map' @@ -33,7 +32,6 @@ ActionController::Routing::Routes.draw do |map| map.connect "api/#{API_VERSION}/search", :controller => 'search', :action => 'search_all' map.connect "api/#{API_VERSION}/ways/search", :controller => 'search', :action => 'search_ways' - map.connect "api/#{API_VERSION}/segments/search", :controller => 'search', :action => 'search_segments' map.connect "api/#{API_VERSION}/nodes/search", :controller => 'search', :action => 'search_nodes' map.connect "api/#{API_VERSION}/user/details", :controller => 'user', :action => 'api_details' diff --git a/db/migrate/007_add_relations.rb b/db/migrate/007_add_relations.rb new file mode 100644 index 000000000..a30642e32 --- /dev/null +++ b/db/migrate/007_add_relations.rb @@ -0,0 +1,83 @@ +class AddRelations < ActiveRecord::Migration + def self.up + # a relation can have members much like a way can have nodes. + # differences: + # way: only nodes / relation: any kind of member + # way: ordered sequence of nodes / relation: free-form "role" string + create_table "current_relation_members", innodb_table do |t| + t.column "id", :bigint, :limit => 64, :null => false + t.column "member_type", :string, :limit => 11, :null => false + t.column "member_id", :bigint, :limit => 11, :null => false + t.column "member_role", :string + end + # enums work like strings but are more efficient + execute "alter table current_relation_members change column member_type member_type enum('node','way','relation');" + + add_primary_key "current_relation_members", ["id", "member_type", "member_id", "member_role"] + add_index "current_relation_members", ["member_type", "member_id"], :name => "current_relation_members_member_idx" + # the following is obsolete given the primary key, is it not? + # add_index "current_relation_members", ["id"], :name => "current_relation_members_id_idx" + create_table "current_relation_tags", myisam_table do |t| + t.column "id", :bigint, :limit => 64, :null => false + t.column "k", :string, :default => "", :null => false + t.column "v", :string, :default => "", :null => false + end + + add_index "current_relation_tags", ["id"], :name => "current_relation_tags_id_idx" + execute "CREATE FULLTEXT INDEX `current_relation_tags_v_idx` ON `current_relation_tags` (`v`)" + + create_table "current_relations", innodb_table do |t| + t.column "id", :bigint, :limit => 64, :null => false + t.column "user_id", :bigint, :limit => 20, :null => false + t.column "timestamp", :datetime, :null => false + t.column "visible", :boolean, :null => false + end + + add_primary_key "current_relations", ["id"] + change_column "current_relations", "id", :bigint, :limit => 64, :null => false, :options => "AUTO_INCREMENT" + + create_table "relation_members", myisam_table do |t| + t.column "id", :bigint, :limit => 64, :default => 0, :null => false + t.column "member_type", :string, :limit => 11, :null => false + t.column "member_id", :bigint, :limit => 11, :null => false + t.column "member_role", :string + t.column "version", :bigint, :limit => 20, :default => 0, :null => false + end + + execute "alter table relation_members change column member_type member_type enum('node','way','relation');" + add_primary_key "relation_members", ["id", "version", "member_type", "member_id", "member_role"] + add_index "relation_members", ["member_type", "member_id"], :name => "relation_members_member_idx" + + create_table "relation_tags", myisam_table do |t| + t.column "id", :bigint, :limit => 64, :default => 0, :null => false + t.column "k", :string, :null => false, :default => "" + t.column "v", :string, :null => false, :default => "" + t.column "version", :bigint, :limit => 20, :null => false + end + + add_index "relation_tags", ["id", "version"], :name => "relation_tags_id_version_idx" + + create_table "relations", myisam_table do |t| + t.column "id", :bigint, :limit => 64, :null => false, :default => 0 + t.column "user_id", :bigint, :limit => 20, :null => false + t.column "timestamp", :datetime, :null => false + t.column "version", :bigint, :limit => 20, :null => false + t.column "visible", :boolean, :null => false, :default => true + end + + add_primary_key "relations", ["id", "version"] + add_index "relations", ["timestamp"], :name => "relations_timestamp_idx" + + change_column "relations", "version", :bigint, :limit => 20, :null => false, :options => "AUTO_INCREMENT" + end + + + def self.down + drop_table :relations + drop_table :current_relations + drop_table :relation_tags + drop_table :current_relation_tags + drop_table :relation_members + drop_table :current_relation_members + end +end diff --git a/db/migrate/008_remove_segments.rb b/db/migrate/008_remove_segments.rb new file mode 100644 index 000000000..cfa99adad --- /dev/null +++ b/db/migrate/008_remove_segments.rb @@ -0,0 +1,89 @@ +require 'lib/migrate' + +class RemoveSegments < ActiveRecord::Migration + def self.up + have_segs = select_value("SELECT count(*) FROM current_segments").to_i != 0 + + if have_segs + prefix = File.join Dir.tmpdir, "008_remove_segments.#{$$}." + + cmd = "db/migrate/008_remove_segments_helper" + src = "#{cmd}.cc" + if not File.exists? cmd or File.mtime(cmd) < File.mtime(src) then + system 'c++ -O3 -Wall `mysql_config --cflags --libs` ' + + "#{src} -o #{cmd}" or fail + end + + conn_opts = ActiveRecord::Base.connection. + instance_eval { @connection_options } + args = conn_opts.map { |arg| arg.to_s } + [prefix] + fail "#{cmd} failed" unless system cmd, *args + + tempfiles = ['ways', 'way_nodes', 'way_tags', + 'relations', 'relation_members', 'relation_tags']. + map { |base| prefix + base } + ways, way_nodes, way_tags, + relations, relation_members, relation_tags = tempfiles + end + + drop_table :segments + drop_table :way_segments + create_table :way_nodes, myisam_table do |t| + t.column :id, :bigint, :limit => 64, :null => false + t.column :node_id, :bigint, :limit => 64, :null => false + t.column :version, :bigint, :limit => 20, :null => false + t.column :sequence_id, :bigint, :limit => 11, :null => false + end + add_primary_key :way_nodes, [:id, :version, :sequence_id] + + drop_table :current_segments + drop_table :current_way_segments + create_table :current_way_nodes, innodb_table do |t| + t.column :id, :bigint, :limit => 64, :null => false + t.column :node_id, :bigint, :limit => 64, :null => false + t.column :sequence_id, :bigint, :limit => 11, :null => false + end + add_primary_key :current_way_nodes, [:id, :sequence_id] + + execute "TRUNCATE way_tags" + execute "TRUNCATE ways" + execute "TRUNCATE current_way_tags" + execute "TRUNCATE current_ways" + + # now get the data back + csvopts = "FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\"' LINES TERMINATED BY '\\n'" + + tempfiles.each { |fn| File.chmod 0644, fn } if have_segs + + if have_segs + execute "LOAD DATA LOCAL INFILE '#{ways}' INTO TABLE ways #{csvopts} (id, user_id, timestamp) SET visible = 1, version = 1" + execute "LOAD DATA LOCAL INFILE '#{way_nodes}' INTO TABLE way_nodes #{csvopts} (id, node_id, sequence_id) SET version = 1" + execute "LOAD DATA LOCAL INFILE '#{way_tags}' INTO TABLE way_tags #{csvopts} (id, k, v) SET version = 1" + + execute "INSERT INTO current_ways SELECT id, user_id, timestamp, visible FROM ways" + execute "INSERT INTO current_way_nodes SELECT id, node_id, sequence_id FROM way_nodes" + execute "INSERT INTO current_way_tags SELECT id, k, v FROM way_tags" + end + + # and then readd the index + add_index :current_way_nodes, [:node_id], :name => "current_way_nodes_node_idx" + + if have_segs + execute "LOAD DATA LOCAL INFILE '#{relations}' INTO TABLE relations #{csvopts} (id, user_id, timestamp) SET visible = 1, version = 1" + execute "LOAD DATA LOCAL INFILE '#{relation_members}' INTO TABLE relation_members #{csvopts} (id, member_type, member_id, member_role) SET version = 1" + execute "LOAD DATA LOCAL INFILE '#{relation_tags}' INTO TABLE relation_tags #{csvopts} (id, k, v) SET version = 1" + + # FIXME: This will only work if there were no relations before the + # migration! + execute "INSERT INTO current_relations SELECT id, user_id, timestamp, visible FROM relations" + execute "INSERT INTO current_relation_members SELECT id, member_type, member_id, member_role FROM relation_members" + execute "INSERT INTO current_relation_tags SELECT id, k, v FROM relation_tags" + end + + tempfiles.each { |fn| File.unlink fn } if have_segs + end + + def self.down + raise IrreversibleMigration.new + end +end diff --git a/db/migrate/008_remove_segments_helper.cc b/db/migrate/008_remove_segments_helper.cc new file mode 100644 index 000000000..8f9c25bd5 --- /dev/null +++ b/db/migrate/008_remove_segments_helper.cc @@ -0,0 +1,670 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __amd64__ + +#define F_U64 "%lu" +#define F_U32 "%u" + +#else + +#define F_U64 "%Lu" +#define F_U32 "%u" + +#endif + +using namespace std; + +template +static T parse(const char *str) { + istringstream in(str); + T t; + in >> t; + return t; +} + +static void exit_mysql_err(MYSQL *mysql) { + const char *err = mysql_error(mysql); + if (err) { + fprintf(stderr, "008_remove_segments_helper: MySQL error: %s\n", err); + } else { + fprintf(stderr, "008_remove_segments_helper: MySQL error\n"); + } + abort(); + exit(EXIT_FAILURE); +} + +static void exit_stmt_err(MYSQL_STMT *stmt) { + const char *err = mysql_stmt_error(stmt); + if (err) { + fprintf(stderr, "008_remove_segments_helper: MySQL stmt error: %s\n", err); + } else { + fprintf(stderr, "008_remove_segments_helper: MySQL stmt error\n"); + } + abort(); + exit(EXIT_FAILURE); +} + +struct segment { + uint32_t from, to; +}; + +struct data { + MYSQL *mysql, *mysql2; + + uint64_t seg_maxid, way_maxid; + uint64_t new_way_id; + uint64_t new_relation_id; + + size_t segs_len; + struct segment *segs; + unsigned char *rem_segs; + + FILE *ways, *way_nodes, *way_tags, + *relations, *relation_members, *relation_tags; +}; + +static uint64_t select_u64(MYSQL *mysql, const char *q) { + MYSQL_RES *res; + MYSQL_ROW row; + uint64_t ret; + + if (mysql_query(mysql, q)) + exit_mysql_err(mysql); + + res = mysql_store_result(mysql); + if (!res) exit_mysql_err(mysql); + + row = mysql_fetch_row(res); + if (!row) exit_mysql_err(mysql); + + if (row[0]) { + ret = parse(row[0]); + } else { + ret = 0; + } + + mysql_free_result(res); + + return ret; +} + +static void find_maxids(struct data *d) { + d->seg_maxid = select_u64(d->mysql, "SELECT max(id) FROM current_segments"); + d->segs_len = d->seg_maxid + 1; + d->way_maxid = select_u64(d->mysql, "SELECT max(id) FROM current_ways"); + d->new_way_id = d->way_maxid + 1; + d->new_relation_id = select_u64(d->mysql, "SELECT max(id) FROM current_relations") + 1; +} + +static void populate_segs(struct data *d) { + MYSQL_RES *res; + MYSQL_ROW row; + size_t id; + + d->segs = (segment *) malloc(sizeof(struct segment) * d->segs_len); + memset(d->segs, 0, sizeof(struct segment) * d->segs_len); + + d->rem_segs = (unsigned char *) malloc(d->segs_len); + memset(d->rem_segs, 0, d->segs_len); + + if (mysql_query(d->mysql, "SELECT id, node_a, node_b " + "FROM current_segments WHERE visible")) + exit_mysql_err(d->mysql); + + res = mysql_use_result(d->mysql); + if (!res) exit_mysql_err(d->mysql); + + while ((row = mysql_fetch_row(res))) { + id = parse(row[0]); + if (id >= d->segs_len) continue; + d->segs[id].from = parse(row[1]); + d->segs[id].to = parse(row[2]); + d->rem_segs[id] = 1; + } + if (mysql_errno(d->mysql)) exit_mysql_err(d->mysql); + + mysql_free_result(res); +} + +static void write_csv_col(FILE *f, const char *str, char end) { + char *out = (char *) malloc(2 * strlen(str) + 4); + char *o = out; + size_t len; + + *(o++) = '\"'; + for (; *str; str++) { + if (*str == '\0') { + break; + } else if (*str == '\"') { + *(o++) = '\"'; + *(o++) = '\"'; + } else { + *(o++) = *str; + } + } + *(o++) = '\"'; + *(o++) = end; + *(o++) = '\0'; + + len = strlen(out); + if (fwrite(out, len, 1, f) != 1) { + perror("fwrite"); + exit(EXIT_FAILURE); + } + + free(out); +} + +static void convert_ways(struct data *d) { + MYSQL_RES *res; + MYSQL_ROW row; + MYSQL_STMT *load_segs, *load_tags; + const char + load_segs_stmt[] = "SELECT segment_id FROM current_way_segments " + "WHERE id = ? ORDER BY sequence_id", + load_tags_stmt[] = "SELECT k, v FROM current_way_tags WHERE id = ?"; + char *k, *v; + const size_t max_tag_len = 1 << 16; + long long mysql_id, mysql_seg_id; + unsigned long res_len; + my_bool res_error; + MYSQL_BIND bind[1], seg_bind[1], tag_bind[2]; + + /* F***ing libmysql only support fixed size buffers for string results of + * prepared statements. So allocate 65k for the tag key and the tag value + * and hope it'll suffice. */ + k = (char *) malloc(max_tag_len); + v = (char *) malloc(max_tag_len); + + load_segs = mysql_stmt_init(d->mysql2); + if (!load_segs) exit_mysql_err(d->mysql2); + if (mysql_stmt_prepare(load_segs, load_segs_stmt, sizeof(load_segs_stmt))) + exit_stmt_err(load_segs); + + memset(bind, 0, sizeof(bind)); + bind[0].buffer_type = MYSQL_TYPE_LONGLONG; + bind[0].buffer = (char *) &mysql_id; + bind[0].is_null = 0; + bind[0].length = 0; + if (mysql_stmt_bind_param(load_segs, bind)) + exit_stmt_err(load_segs); + + memset(seg_bind, 0, sizeof(seg_bind)); + seg_bind[0].buffer_type = MYSQL_TYPE_LONGLONG; + seg_bind[0].buffer = (char *) &mysql_seg_id; + seg_bind[0].is_null = 0; + seg_bind[0].length = 0; + seg_bind[0].error = &res_error; + if (mysql_stmt_bind_result(load_segs, seg_bind)) + exit_stmt_err(load_segs); + + load_tags = mysql_stmt_init(d->mysql2); + if (!load_tags) exit_mysql_err(d->mysql2); + if (mysql_stmt_prepare(load_tags, load_tags_stmt, sizeof(load_tags_stmt))) + exit_stmt_err(load_tags); + + memset(bind, 0, sizeof(bind)); + bind[0].buffer_type = MYSQL_TYPE_LONGLONG; + bind[0].buffer = (char *) &mysql_id; + bind[0].is_null = 0; + bind[0].length = 0; + + if (mysql_stmt_bind_param(load_tags, bind)) + exit_stmt_err(load_tags); + + memset(tag_bind, 0, sizeof(tag_bind)); + tag_bind[0].buffer_type = MYSQL_TYPE_STRING; + tag_bind[0].buffer = k; + tag_bind[0].is_null = 0; + tag_bind[0].length = &res_len; + tag_bind[0].error = &res_error; + tag_bind[0].buffer_length = max_tag_len; + tag_bind[1].buffer_type = MYSQL_TYPE_STRING; + tag_bind[1].buffer = v; + tag_bind[1].is_null = 0; + tag_bind[1].length = &res_len; + tag_bind[1].error = &res_error; + tag_bind[1].buffer_length = max_tag_len; + if (mysql_stmt_bind_result(load_tags, tag_bind)) + exit_stmt_err(load_tags); + + if (mysql_query(d->mysql, "SELECT id, user_id, timestamp " + "FROM current_ways WHERE visible")) + exit_mysql_err(d->mysql); + + res = mysql_use_result(d->mysql); + if (!res) exit_mysql_err(d->mysql); + + while ((row = mysql_fetch_row(res))) { + uint64_t id; + const char *user_id, *timestamp; + + id = parse(row[0]); + user_id = row[1]; + timestamp = row[2]; + + mysql_id = (long long) id; + + if (mysql_stmt_execute(load_segs)) + exit_stmt_err(load_segs); + + if (mysql_stmt_store_result(load_segs)) + exit_stmt_err(load_segs); + + list segs; + while (!mysql_stmt_fetch(load_segs)) { + if (((uint64_t) mysql_seg_id) >= d->segs_len) continue; + segs.push_back(d->segs[mysql_seg_id]); + d->rem_segs[mysql_seg_id] = 0; + } + + list > node_lists; + while (segs.size()) { + list node_list; + node_list.push_back(segs.front().from); + node_list.push_back(segs.front().to); + segs.pop_front(); + while (true) { + bool found = false; + for (list::iterator it = segs.begin(); + it != segs.end(); ) { + if (it->from == node_list.back()) { + node_list.push_back(it->to); + segs.erase(it++); + found = true; + } else if (it->to == node_list.front()) { + node_list.insert(node_list.begin(), it->from); + segs.erase(it++); + found = true; + } else { + ++it; + } + } + if (!found) break; + } + node_lists.push_back(node_list); + } + + vector ids; ids.reserve(node_lists.size()); + bool orig_id_used = false; + for (list >::iterator it = node_lists.begin(); + it != node_lists.end(); ++it) { + uint64_t way_id; + int sid; + if (orig_id_used) { + way_id = d->new_way_id++; + } else { + way_id = id; + orig_id_used = true; + } + ids.push_back(way_id); + + fprintf(d->ways, "\"" F_U64 "\",", way_id); + write_csv_col(d->ways, user_id, ','); + write_csv_col(d->ways, timestamp, '\n'); + + sid = 1; + for (list::iterator nit = it->begin(); + nit != it->end(); ++nit) { + fprintf(d->way_nodes, "\"" F_U64 "\",\"" F_U32 "\",\"%i\"\n", way_id, *nit, sid++); + } + } + + if (mysql_stmt_execute(load_tags)) + exit_stmt_err(load_tags); + + if (mysql_stmt_store_result(load_tags)) + exit_stmt_err(load_tags); + + bool multiple_parts = ids.size() > 1, + create_multipolygon = false; + + while (!mysql_stmt_fetch(load_tags)) { + if (multiple_parts && !create_multipolygon) { + if (!strcmp(k, "natural")) { + if (strcmp(v, "coastline")) { + create_multipolygon = true; + } + } else if (!strcmp(k, "waterway")) { + if (!strcmp(v, "riverbank")) { + create_multipolygon = true; + } + } else if (!strcmp(k, "leisure") || !strcmp(k, "landuse") + || !strcmp(k, "sport") || !strcmp(k, "amenity") + || !strcmp(k, "tourism") || !strcmp(k, "building")) { + create_multipolygon = true; + } + } + + for (vector::iterator it = ids.begin(); + it != ids.end(); ++it) { + fprintf(d->way_tags, "\"" F_U64 "\",", *it); + write_csv_col(d->way_tags, k, ','); + write_csv_col(d->way_tags, v, '\n'); + } + } + + if (multiple_parts && create_multipolygon) { + uint64_t ent_id = d->new_relation_id++; + + fprintf(d->relations, "\"" F_U64 "\",", ent_id); + write_csv_col(d->relations, user_id, ','); + write_csv_col(d->relations, timestamp, '\n'); + + fprintf(d->relation_tags, + "\"" F_U64 "\",\"type\",\"multipolygon\"\n", ent_id); + + for (vector::iterator it = ids.begin(); + it != ids.end(); ++it) { + fprintf(d->relation_members, + "\"" F_U64 "\",\"way\",\"" F_U64 "\",\"\"\n", ent_id, *it); + } + } + } + if (mysql_errno(d->mysql)) exit_stmt_err(load_tags); + + mysql_stmt_close(load_segs); + mysql_stmt_close(load_tags); + + mysql_free_result(res); + free(k); + free(v); +} + +static int read_seg_tags(char **tags, char **k, char **v) { + if (!**tags) return 0; + char *i = strchr(*tags, ';'); + if (!i) i = *tags + strlen(*tags); + char *j = strchr(*tags, '='); + *k = *tags; + if (j && j < i) { + *v = j + 1; + } else { + *v = i; + } + *tags = *i ? i + 1 : i; + *i = '\0'; + if (j) *j = '\0'; + return 1; +} + +static void mark_tagged_segs(struct data *d) { + MYSQL_RES *res; + MYSQL_ROW row; + MYSQL_STMT *way_tags; + const char + way_tags_stmt[] = "SELECT k, v FROM current_way_segments INNER JOIN " + "current_way_tags ON current_way_segments.id = " + "current_way_tags.id WHERE segment_id = ?"; + char *wk, *wv; + const size_t max_tag_len = 1 << 16; + long long mysql_seg_id; + unsigned long res_len; + my_bool res_error; + MYSQL_BIND in_bind[1], out_bind[1]; + + /* F***ing libmysql only support fixed size buffers for string results of + * prepared statements. So allocate 65k for the tag key and the tag value + * and hope it'll suffice. */ + wk = (char *) malloc(max_tag_len); + wv = (char *) malloc(max_tag_len); + + way_tags = mysql_stmt_init(d->mysql2); + if (!way_tags) exit_mysql_err(d->mysql2); + if (mysql_stmt_prepare(way_tags, way_tags_stmt, sizeof(way_tags_stmt))) + exit_stmt_err(way_tags); + + memset(in_bind, 0, sizeof(in_bind)); + in_bind[0].buffer_type = MYSQL_TYPE_LONGLONG; + in_bind[0].buffer = (char *) &mysql_seg_id; + in_bind[0].is_null = 0; + in_bind[0].length = 0; + + if (mysql_stmt_bind_param(way_tags, in_bind)) + exit_stmt_err(way_tags); + + memset(out_bind, 0, sizeof(out_bind)); + out_bind[0].buffer_type = MYSQL_TYPE_STRING; + out_bind[0].buffer = wk; + out_bind[0].is_null = 0; + out_bind[0].length = &res_len; + out_bind[0].error = &res_error; + out_bind[0].buffer_length = max_tag_len; + out_bind[1].buffer_type = MYSQL_TYPE_STRING; + out_bind[1].buffer = wv; + out_bind[1].is_null = 0; + out_bind[1].length = &res_len; + out_bind[1].error = &res_error; + out_bind[1].buffer_length = max_tag_len; + if (mysql_stmt_bind_result(way_tags, out_bind)) + exit_stmt_err(way_tags); + + if (mysql_query(d->mysql, "SELECT id, tags FROM current_segments " + "WHERE visible && tags != '' && tags != 'created_by=JOSM'")) + exit_mysql_err(d->mysql); + + res = mysql_use_result(d->mysql); + if (!res) exit_mysql_err(d->mysql); + + while ((row = mysql_fetch_row(res))) { + size_t id = parse(row[0]); + if (d->rem_segs[id]) continue; + + map interesting_tags; + + char *tags_it = row[1], *k, *v; + while (read_seg_tags(&tags_it, &k, &v)) { + if (strcmp(k, "created_by") && + strcmp(k, "tiger:county") && + strcmp(k, "tiger:upload_uuid") && + strcmp(k, "converted_by") && + (strcmp(k, "natural") || strcmp(v, "coastline")) && + (strcmp(k, "source") || strcmp(v, "PGS"))) { + interesting_tags.insert(make_pair(string(k), string(v))); + } + } + + if (interesting_tags.size() == 0) continue; + + mysql_seg_id = id; + + if (mysql_stmt_execute(way_tags)) + exit_stmt_err(way_tags); + + if (mysql_stmt_store_result(way_tags)) + exit_stmt_err(way_tags); + + while (!mysql_stmt_fetch(way_tags)) { + for (map::iterator it = interesting_tags.find(wk); + it != interesting_tags.end() && it->first == wk; ++it) { + if (it->second == wv) { + interesting_tags.erase(it); + break; + } + } + } + + if (interesting_tags.size() > 0) { + d->rem_segs[id] = 1; + } + } + + mysql_free_result(res); + + mysql_stmt_close(way_tags); + free(wk); + free(wv); +} + +static void convert_remaining_segs(struct data *d) { + MYSQL_STMT *load_seg; + MYSQL_BIND args[1], res[3]; + const size_t max_tag_len = 1 << 16; + char *tags, timestamp[100]; + char *k, *v; + int user_id; + long long mysql_id; + unsigned long res_len; + my_bool res_error; + const char load_seg_stmt[] = + "SELECT user_id, tags, CAST(timestamp AS CHAR) FROM current_segments " + "WHERE visible && id = ?"; + + tags = (char *) malloc(max_tag_len); + + load_seg = mysql_stmt_init(d->mysql); + if (!load_seg) exit_mysql_err(d->mysql); + if (mysql_stmt_prepare(load_seg, load_seg_stmt, sizeof(load_seg_stmt))) + exit_stmt_err(load_seg); + + memset(args, 0, sizeof(args)); + args[0].buffer_type = MYSQL_TYPE_LONGLONG; + args[0].buffer = (char *) &mysql_id; + args[0].is_null = 0; + args[0].length = 0; + if (mysql_stmt_bind_param(load_seg, args)) + exit_stmt_err(load_seg); + + memset(res, 0, sizeof(res)); + res[0].buffer_type = MYSQL_TYPE_LONG; + res[0].buffer = (char *) &user_id; + res[0].is_null = 0; + res[0].length = 0; + res[0].error = &res_error; + res[1].buffer_type = MYSQL_TYPE_STRING; + res[1].buffer = tags; + res[1].is_null = 0; + res[1].length = &res_len; + res[1].error = &res_error; + res[1].buffer_length = max_tag_len; + res[2].buffer_type = MYSQL_TYPE_STRING; + res[2].buffer = timestamp; + res[2].is_null = 0; + res[2].length = &res_len; + res[2].error = &res_error; + res[2].buffer_length = sizeof(timestamp); + if (mysql_stmt_bind_result(load_seg, res)) + exit_stmt_err(load_seg); + + for (size_t seg_id = 0; seg_id < d->segs_len; seg_id++) { + if (!d->rem_segs[seg_id]) continue; + segment seg = d->segs[seg_id]; + + mysql_id = seg_id; + if (mysql_stmt_execute(load_seg)) exit_stmt_err(load_seg); + if (mysql_stmt_store_result(load_seg)) exit_stmt_err(load_seg); + + while (!mysql_stmt_fetch(load_seg)) { + uint64_t way_id = d->new_way_id++; + + fprintf(d->ways, "\"" F_U64 "\",\"%i\",", way_id, user_id); + write_csv_col(d->ways, timestamp, '\n'); + + fprintf(d->way_nodes, "\"" F_U64 "\",\"" F_U32 "\",\"%i\"\n", way_id, seg.from, 1); + fprintf(d->way_nodes, "\"" F_U64 "\",\"" F_U32 "\",\"%i\"\n", way_id, seg.to, 2); + + char *tags_it = tags; + while (read_seg_tags(&tags_it, &k, &v)) { + fprintf(d->way_tags, "\"" F_U64 "\",", way_id); + write_csv_col(d->way_tags, k, ','); + write_csv_col(d->way_tags, v, '\n'); + } + } + } + + mysql_stmt_close(load_seg); + + free(tags); +} + +static MYSQL *connect_to_mysql(char **argv) { + MYSQL *mysql = mysql_init(NULL); + if (!mysql) exit_mysql_err(mysql); + + if (!mysql_real_connect(mysql, argv[1], argv[2], argv[3], argv[4], + argv[5][0] ? atoi(argv[5]) : 0, argv[6][0] ? argv[6] : NULL, 0)) + exit_mysql_err(mysql); + + if (mysql_set_character_set(mysql, "utf8")) + exit_mysql_err(mysql); + + return mysql; +} + +static void open_file(FILE **f, char *fn) { + *f = fopen(fn, "w+"); + if (!*f) { + perror("fopen"); + exit(EXIT_FAILURE); + } +} + +int main(int argc, char **argv) { + struct data data; + struct data *d = &data; + size_t prefix_len; + char *tempfn; + + if (argc != 8) { + printf("Usage: 008_remove_segments_helper host user passwd database port socket prefix\n"); + exit(EXIT_FAILURE); + } + + d->mysql = connect_to_mysql(argv); + d->mysql2 = connect_to_mysql(argv); + + prefix_len = strlen(argv[7]); + tempfn = (char *) malloc(prefix_len + 15); + strcpy(tempfn, argv[7]); + + strcpy(tempfn + prefix_len, "ways"); + open_file(&d->ways, tempfn); + + strcpy(tempfn + prefix_len, "way_nodes"); + open_file(&d->way_nodes, tempfn); + + strcpy(tempfn + prefix_len, "way_tags"); + open_file(&d->way_tags, tempfn); + + strcpy(tempfn + prefix_len, "relations"); + open_file(&d->relations, tempfn); + + strcpy(tempfn + prefix_len, "relation_members"); + open_file(&d->relation_members, tempfn); + + strcpy(tempfn + prefix_len, "relation_tags"); + open_file(&d->relation_tags, tempfn); + + free(tempfn); + + find_maxids(d); + populate_segs(d); + convert_ways(d); + mark_tagged_segs(d); + convert_remaining_segs(d); + + mysql_close(d->mysql); + mysql_close(d->mysql2); + + fclose(d->ways); + fclose(d->way_nodes); + fclose(d->way_tags); + + fclose(d->relations); + fclose(d->relation_members); + fclose(d->relation_tags); + + free(d->segs); + free(d->rem_segs); + + exit(EXIT_SUCCESS); +} diff --git a/script/statistics b/script/statistics index 3f0c433f2..a703b3a0d 100755 --- a/script/statistics +++ b/script/statistics @@ -18,7 +18,6 @@ begin user_count = User.count(:conditions => "active = true") tracepoint_count = Tracepoint.count() node_count = Node.count(:conditions => "visible = true") - segment_count = Segment.count(:conditions => "visible = true") way_count = Way.count(:conditions => "visible = true") tagged_way_count = Way.count(:conditions => "visible = true AND EXISTS (SELECT * FROM current_way_tags WHERE id = current_ways.id AND k <> 'created_by')") @@ -26,7 +25,6 @@ begin puts "Number of users#{user_count}" puts "Number of uploaded GPS points#{tracepoint_count}" puts "Number of nodes#{node_count}" - puts "Number of segments#{segment_count}" puts "Number of ways#{way_count}" puts "Number of ways with tags#{tagged_way_count}" puts "" diff --git a/test/fixtures/current_relation_members.yml b/test/fixtures/current_relation_members.yml new file mode 100644 index 000000000..bddc8a0dd --- /dev/null +++ b/test/fixtures/current_relation_members.yml @@ -0,0 +1,23 @@ +t1: + id: 1 + member_role: "some" + member_type: "way" + member_id: 3 + +t2: + id: 1 + member_role: "some" + member_type: "node" + member_id: 5 + +t3: + id: 1 + member_role: "some" + member_type: "relation" + member_id: 3 + +t4: + id: 3 + member_role: "some" + member_type: "node" + member_id: 5 diff --git a/test/fixtures/current_relation_tags.yml b/test/fixtures/current_relation_tags.yml new file mode 100644 index 000000000..aaf06a397 --- /dev/null +++ b/test/fixtures/current_relation_tags.yml @@ -0,0 +1,14 @@ +t1: + id: 1 + k: test + v: yes + +t2: + id: 2 + k: test + v: yes + +t2: + id: 3 + k: test + v: yes diff --git a/test/fixtures/current_relations.yml b/test/fixtures/current_relations.yml new file mode 100644 index 000000000..c1f77d428 --- /dev/null +++ b/test/fixtures/current_relations.yml @@ -0,0 +1,17 @@ +visible_relation: + id: 1 + user_id: 1 + timestamp: 2007-01-01 00:00:00 + visible: 1 + +invisible_relation: + id: 2 + user_id: 1 + timestamp: 2007-01-01 00:00:00 + visible: 0 + +used_relation: + id: 3 + user_id: 1 + timestamp: 2007-01-01 00:00:00 + visible: 1 diff --git a/test/fixtures/current_segments.yml b/test/fixtures/current_segments.yml deleted file mode 100644 index ce3758360..000000000 --- a/test/fixtures/current_segments.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html -visible_segment: - id: 1 - node_a: 3 - node_b: 4 - user_id: 1 - visible: 1 - tags: test=yes - timestamp: 2007-01-01 00:00:00 - -invisible_segment: - id: 2 - node_a: 3 - node_b: 4 - user_id: 1 - visible: 0 - tags: test=yes - timestamp: 2007-01-01 00:00:00 - -used_segment: - id: 3 - node_a: 3 - node_b: 4 - user_id: 1 - visible: 1 - tags: test=yes - timestamp: 2007-01-01 00:00:00 diff --git a/test/fixtures/current_way_nodes.yml b/test/fixtures/current_way_nodes.yml new file mode 100644 index 000000000..ce394edbe --- /dev/null +++ b/test/fixtures/current_way_nodes.yml @@ -0,0 +1,14 @@ +t1: + id: 1 + node_id: 3 + sequence_id: 1 + +t2: + id: 2 + node_id: 3 + sequence_id: 1 + +t3: + id: 3 + node_id: 3 + sequence_id: 1 diff --git a/test/fixtures/current_way_segments.yml b/test/fixtures/current_way_segments.yml deleted file mode 100644 index db82de5fc..000000000 --- a/test/fixtures/current_way_segments.yml +++ /dev/null @@ -1,9 +0,0 @@ -t1: - id: 1 - segment_id: 3 - sequence_id: 1 - -t2: - id: 2 - segment_id: 3 - sequence_id: 1 diff --git a/test/fixtures/current_way_tags.yml b/test/fixtures/current_way_tags.yml index 05d876fd8..375247ea2 100644 --- a/test/fixtures/current_way_tags.yml +++ b/test/fixtures/current_way_tags.yml @@ -3,7 +3,13 @@ t1: k: test v: yes -t1: +t2: id: 2 k: test v: yes + +t3: + id: 3 + k: test + v: yes + diff --git a/test/fixtures/current_ways.yml b/test/fixtures/current_ways.yml index 97bc485ba..b129d7f45 100644 --- a/test/fixtures/current_ways.yml +++ b/test/fixtures/current_ways.yml @@ -9,3 +9,10 @@ invisible_way: user_id: 1 timestamp: 2007-01-01 00:00:00 visible: 0 + +used_way: + id: 3 + user_id: 1 + timestamp: 2007-01-01 00:00:00 + visible: 1 + diff --git a/test/fixtures/relation_members.yml b/test/fixtures/relation_members.yml new file mode 100644 index 000000000..27e8b533a --- /dev/null +++ b/test/fixtures/relation_members.yml @@ -0,0 +1,24 @@ +t1: + id: 1 + member_role: "some" + member_type: "way" + member_id: 3 + version: 1 +t2: + id: 1 + member_role: "some" + member_type: "node" + member_id: 5 + version: 1 +t3: + id: 1 + member_role: "some" + member_type: "relation" + member_id: 3 + version: 1 +t4: + id: 3 + member_role: "some" + member_type: "node" + member_id: 5 + version: 1 diff --git a/test/fixtures/relation_tags.yml b/test/fixtures/relation_tags.yml new file mode 100644 index 000000000..39f4bd5de --- /dev/null +++ b/test/fixtures/relation_tags.yml @@ -0,0 +1,17 @@ +t1: + id: 1 + k: test + v: yes + version: 1 + +t2: + id: 2 + k: test + v: yes + version: 1 + +t3: + id: 3 + k: test + v: yes + version: 1 diff --git a/test/fixtures/relations.yml b/test/fixtures/relations.yml new file mode 100644 index 000000000..cf1d1ff56 --- /dev/null +++ b/test/fixtures/relations.yml @@ -0,0 +1,20 @@ +visible_relation: + id: 1 + user_id: 1 + timestamp: 2007-01-01 00:00:00 + visible: 1 + version: 1 + +invisible_relation: + id: 2 + user_id: 1 + timestamp: 2007-01-01 00:00:00 + visible: 0 + version: 1 + +used_relation: + id: 3 + user_id: 1 + timestamp: 2007-01-01 00:00:00 + visible: 1 + version: 1 diff --git a/test/fixtures/segments.yml b/test/fixtures/segments.yml deleted file mode 100644 index ce3758360..000000000 --- a/test/fixtures/segments.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html -visible_segment: - id: 1 - node_a: 3 - node_b: 4 - user_id: 1 - visible: 1 - tags: test=yes - timestamp: 2007-01-01 00:00:00 - -invisible_segment: - id: 2 - node_a: 3 - node_b: 4 - user_id: 1 - visible: 0 - tags: test=yes - timestamp: 2007-01-01 00:00:00 - -used_segment: - id: 3 - node_a: 3 - node_b: 4 - user_id: 1 - visible: 1 - tags: test=yes - timestamp: 2007-01-01 00:00:00 diff --git a/test/fixtures/way_segments.yml b/test/fixtures/way_nodes.yml similarity index 50% rename from test/fixtures/way_segments.yml rename to test/fixtures/way_nodes.yml index bbefff6dc..caeac16b1 100644 --- a/test/fixtures/way_segments.yml +++ b/test/fixtures/way_nodes.yml @@ -1,11 +1,17 @@ t1: id: 1 - segment_id: 3 + node_id: 3 sequence_id: 1 version: 1 t2: id: 2 - segment_id: 3 + node_id: 3 + sequence_id: 1 + version: 1 + +t3: + id: 3 + node_id: 3 sequence_id: 1 version: 1 diff --git a/test/fixtures/way_tags.yml b/test/fixtures/way_tags.yml index d1c69a6d4..39f4bd5de 100644 --- a/test/fixtures/way_tags.yml +++ b/test/fixtures/way_tags.yml @@ -9,3 +9,9 @@ t2: k: test v: yes version: 1 + +t3: + id: 3 + k: test + v: yes + version: 1 diff --git a/test/fixtures/ways.yml b/test/fixtures/ways.yml index 14489850a..c8cf6dcf4 100644 --- a/test/fixtures/ways.yml +++ b/test/fixtures/ways.yml @@ -11,3 +11,11 @@ invisible_way: timestamp: 2007-01-01 00:00:00 visible: 0 version: 1 + +used_way: + id: 3 + user_id: 1 + timestamp: 2007-01-01 00:00:00 + visible: 0 + version: 1 + diff --git a/test/functional/api_controller_test.rb b/test/functional/api_controller_test.rb new file mode 100644 index 000000000..05cbe2af0 --- /dev/null +++ b/test/functional/api_controller_test.rb @@ -0,0 +1,34 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'api_controller' + +# Re-raise errors caught by the controller. +class ApiController; def rescue_action(e) raise e end; end + +class ApiControllerTest < Test::Unit::TestCase + api_fixtures + + def setup + @controller = ApiController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def basic_authorization(user, pass) + @request.env["HTTP_AUTHORIZATION"] = "Basic %s" % Base64.encode64("#{user}:#{pass}") + end + + # ------------------------------------- + # Test reading a bounding box. + # ------------------------------------- + + def test_map + node = current_nodes(:used_node_1) + bbox = "#{node.latitude-0.1},#{node.longitude-0.1},#{node.latitude+0.1},#{node.longitude+0.1}" + get :map, :bbox => bbox + if $VERBOSE + print @response.body + end + assert_response :success + end + +end diff --git a/test/functional/relation_controller_test.rb b/test/functional/relation_controller_test.rb new file mode 100644 index 000000000..8f8b72770 --- /dev/null +++ b/test/functional/relation_controller_test.rb @@ -0,0 +1,209 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'relation_controller' + +# Re-raise errors caught by the controller. +class RelationController; def rescue_action(e) raise e end; end + +class RelationControllerTest < Test::Unit::TestCase + api_fixtures + fixtures :relations, :current_relations, :relation_members, :current_relation_members, :relation_tags, :current_relation_tags + set_fixture_class :current_relations => :Relation + set_fixture_class :relations => :OldRelation + + def setup + @controller = RelationController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def basic_authorization(user, pass) + @request.env["HTTP_AUTHORIZATION"] = "Basic %s" % Base64.encode64("#{user}:#{pass}") + end + + def content(c) + @request.env["RAW_POST_DATA"] = c + end + + # ------------------------------------- + # Test reading relations. + # ------------------------------------- + + def test_read + # check that a visible relation is returned properly + get :read, :id => current_relations(:visible_relation).id + assert_response :success + + # check that an invisible relation is not returned + get :read, :id => current_relations(:invisible_relation).id + assert_response :gone + + # check chat a non-existent relation is not returned + get :read, :id => 0 + assert_response :not_found + + # check the "relations for node" mode + get :relations_for_node, :id => current_nodes(:node_used_by_relationship).id + assert_response :success + # FIXME check whether this contains the stuff we want! + if $VERBOSE + print @response.body + end + + # check the "relations for way" mode + get :relations_for_way, :id => current_ways(:used_way).id + assert_response :success + # FIXME check whether this contains the stuff we want! + if $VERBOSE + print @response.body + end + + + # check the "relations for relation" mode + get :relations_for_relation, :id => current_relations(:used_relation).id + assert_response :success + # FIXME check whether this contains the stuff we want! + if $VERBOSE + print @response.body + end + + # check the "full" mode + get :full, :id => current_relations(:visible_relation).id + assert_response :success + # FIXME check whether this contains the stuff we want! + if $VERBOSE + print @response.body + end + end + + # ------------------------------------- + # Test simple relation creation. + # ------------------------------------- + + def test_create + basic_authorization "test@openstreetmap.org", "test" + + # create an relation without members + content "" + put :create + # hope for success + assert_response :success, + "relation upload did not return success status" + # read id of created relation and search for it + relationid = @response.body + checkrelation = Relation.find(relationid) + assert_not_nil checkrelation, + "uploaded relation not found in data base after upload" + # compare values + assert_equal checkrelation.members.length, 0, + "saved relation contains members but should not" + assert_equal checkrelation.tags.length, 1, + "saved relation does not contain exactly one tag" + assert_equal users(:normal_user).id, checkrelation.user_id, + "saved relation does not belong to user that created it" + assert_equal true, checkrelation.visible, + "saved relation is not visible" + # ok the relation is there but can we also retrieve it? + get :read, :id => relationid + assert_response :success + + + # create an relation with a node as member + nid = current_nodes(:used_node_1).id + content "" + + "" + put :create + # hope for success + assert_response :success, + "relation upload did not return success status" + # read id of created relation and search for it + relationid = @response.body + checkrelation = Relation.find(relationid) + assert_not_nil checkrelation, + "uploaded relation not found in data base after upload" + # compare values + assert_equal checkrelation.members.length, 1, + "saved relation does not contain exactly one member" + assert_equal checkrelation.tags.length, 1, + "saved relation does not contain exactly one tag" + assert_equal users(:normal_user).id, checkrelation.user_id, + "saved relation does not belong to user that created it" + assert_equal true, checkrelation.visible, + "saved relation is not visible" + # ok the relation is there but can we also retrieve it? + + get :read, :id => relationid + assert_response :success + + # create an relation with a way and a node as members + nid = current_nodes(:used_node_1).id + wid = current_ways(:used_way).id + content "" + + "" + + "" + put :create + # hope for success + assert_response :success, + "relation upload did not return success status" + # read id of created relation and search for it + relationid = @response.body + checkrelation = Relation.find(relationid) + assert_not_nil checkrelation, + "uploaded relation not found in data base after upload" + # compare values + assert_equal checkrelation.members.length, 2, + "saved relation does not have exactly two members" + assert_equal checkrelation.tags.length, 1, + "saved relation does not contain exactly one tag" + assert_equal users(:normal_user).id, checkrelation.user_id, + "saved relation does not belong to user that created it" + assert_equal true, checkrelation.visible, + "saved relation is not visible" + # ok the relation is there but can we also retrieve it? + get :read, :id => relationid + assert_response :success + + end + + # ------------------------------------- + # Test creating some invalid relations. + # ------------------------------------- + + def test_create_invalid + basic_authorization "test@openstreetmap.org", "test" + + # create a relation with non-existing node as member + content "" + put :create + # expect failure + assert_response :precondition_failed, + "relation upload with invalid node did not return 'precondition failed'" + end + + # ------------------------------------- + # Test deleting relations. + # ------------------------------------- + + def test_delete + return true + + # first try to delete relation without auth + delete :delete, :id => current_relations(:visible_relation).id + assert_response :unauthorized + + # now set auth + basic_authorization("test@openstreetmap.org", "test"); + + # this should work + delete :delete, :id => current_relations(:visible_relation).id + assert_response :success + + # this won't work since the relation is already deleted + delete :delete, :id => current_relations(:invisible_relation).id + assert_response :gone + + # this won't work since the relation never existed + delete :delete, :id => 0 + assert_response :not_found + end + +end diff --git a/test/functional/segment_controller_test.rb b/test/functional/segment_controller_test.rb deleted file mode 100644 index 1ff26eff0..000000000 --- a/test/functional/segment_controller_test.rb +++ /dev/null @@ -1,98 +0,0 @@ -require File.dirname(__FILE__) + '/../test_helper' -require 'segment_controller' - -# Re-raise errors caught by the controller. -class SegmentController; def rescue_action(e) raise e end; end - -class SegmentControllerTest < Test::Unit::TestCase - api_fixtures - - def setup - @controller = SegmentController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - def test_create - # cannot read password from fixture as it is stored as MD5 digest - basic_authorization("test@openstreetmap.org", "test"); - na = current_nodes(:used_node_1).id - nb = current_nodes(:used_node_2).id - content("") - put :create - # hope for success - assert_response :success, "segment upload did not return success status" - # read id of created segment and search for it - segmentid = @response.body - checksegment = Segment.find(segmentid) - assert_not_nil checksegment, "uploaded segment not found in data base after upload" - # compare values - assert_equal na, checksegment.node_a, "saved segment does not match requested from-node" - assert_equal nb, checksegment.node_b, "saved segment does not match requested to-node" - assert_equal users(:normal_user).id, checksegment.user_id, "saved segment does not belong to user that created it" - assert_equal true, checksegment.visible, "saved segment is not visible" - end - - def test_create_invalid - basic_authorization("test@openstreetmap.org", "test"); - # create a segment with one invalid node - na = current_nodes(:used_node_1).id - nb = 0 - content("") - put :create - # expect failure - assert_response :precondition_failed, "upload of invalid segment did not return 'precondition failed'" - end - - def test_read - # check that a visible segment is returned properly - get :read, :id => current_segments(:visible_segment).id - assert_response :success - # TODO: check for tag in return data - - # check that an invisible segment is not returned - get :read, :id => current_segments(:invisible_segment).id - assert_response :gone - - # check chat a non-existent segment is not returned - get :read, :id => 0 - assert_response :not_found - end - - # this tests deletion restrictions - basic deletion is tested in the unit - # tests for segment! - def test_delete - - # first try to delete segment without auth - delete :delete, :id => current_segments(:visible_segment).id - assert_response :unauthorized - - # now set auth - basic_authorization("test@openstreetmap.org", "test"); - - # this should work - delete :delete, :id => current_segments(:visible_segment).id - assert_response :success - - # this won't work since the segment is already deleted - delete :delete, :id => current_segments(:invisible_segment).id - assert_response :gone - - # this won't work since the segment never existed - delete :delete, :id => 0 - assert_response :not_found - - # this won't work since the segment is in use - delete :delete, :id => current_segments(:used_segment).id - assert_response :precondition_failed - end - - - def basic_authorization(user, pass) - @request.env["HTTP_AUTHORIZATION"] = "Basic %s" % Base64.encode64("#{user}:#{pass}") - end - - def content(c) - @request.env["RAW_POST_DATA"] = c - end -end diff --git a/test/functional/way_controller_test.rb b/test/functional/way_controller_test.rb index c5a3f0b8e..933dfb542 100644 --- a/test/functional/way_controller_test.rb +++ b/test/functional/way_controller_test.rb @@ -5,11 +5,7 @@ require 'way_controller' class WayController; def rescue_action(e) raise e end; end class WayControllerTest < Test::Unit::TestCase - fixtures :current_nodes, :nodes, :users, :current_segments, :segments, :ways, :current_ways, :way_tags, :current_way_tags, :way_segments, :current_way_segments - set_fixture_class :current_ways => :Way - set_fixture_class :ways => :OldWay - set_fixture_class :current_segments => :Segment - set_fixture_class :segments => :OldSegment + api_fixtures def setup @controller = WayController.new @@ -41,6 +37,18 @@ class WayControllerTest < Test::Unit::TestCase # check chat a non-existent way is not returned get :read, :id => 0 assert_response :not_found + + # check the "ways for node" mode + get :ways_for_node, :id => current_nodes(:used_node_1).id + assert_response :success + # FIXME check whether this contains the stuff we want! + print @response.body + + # check the "full" mode + get :full, :id => current_ways(:visible_way).id + assert_response :success + # FIXME check whether this contains the stuff we want! + print @response.body end # ------------------------------------- @@ -48,11 +56,12 @@ class WayControllerTest < Test::Unit::TestCase # ------------------------------------- def test_create - sid = current_segments(:used_segment).id + nid1 = current_nodes(:used_node_1).id + nid2 = current_nodes(:used_node_2).id basic_authorization "test@openstreetmap.org", "test" - # create a way with pre-existing segment - content "" + # create a way with pre-existing nodes + content "" put :create # hope for success assert_response :success, @@ -63,10 +72,12 @@ class WayControllerTest < Test::Unit::TestCase assert_not_nil checkway, "uploaded way not found in data base after upload" # compare values - assert_equal checkway.segs.length, 1, - "saved way does not contain exactly one segment" - assert_equal checkway.segs[0], sid, - "saved way does not contain the right segment" + assert_equal checkway.nds.length, 2, + "saved way does not contain exactly one node" + assert_equal checkway.nds[0], nid1, + "saved way does not contain the right node on pos 0" + assert_equal checkway.nds[1], nid2, + "saved way does not contain the right node on pos 1" assert_equal users(:normal_user).id, checkway.user_id, "saved way does not belong to user that created it" assert_equal true, checkway.visible, @@ -80,27 +91,19 @@ class WayControllerTest < Test::Unit::TestCase def test_create_invalid basic_authorization "test@openstreetmap.org", "test" - # create a way with non-existing segment - content "" + # create a way with non-existing node + content "" put :create # expect failure assert_response :precondition_failed, - "way upload with invalid segment did not return 'precondition failed'" + "way upload with invalid node did not return 'precondition failed'" - # create a way with no segments + # create a way with no nodes content "" put :create # expect failure assert_response :precondition_failed, - "way upload with no segments did not return 'precondition failed'" - - # create a way that has the same segment, twice - # (commented out - this is currently allowed!) - #sid = current_segments(:used_segment).id - #content "" - #put :create - #assert_response :internal_server_error, - # "way upload with double segment did not return 'internal server error'" + "way upload with no node did not return 'precondition failed'" end # ------------------------------------- diff --git a/test/test_helper.rb b/test/test_helper.rb index a441122ab..f3baf4ff2 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -32,18 +32,14 @@ class Test::Unit::TestCase set_fixture_class :current_nodes => :Node set_fixture_class :nodes => :OldNode - fixtures :current_segments, :segments - set_fixture_class :current_segments => :Segment - set_fixture_class :segments => :OldSegment - - fixtures :current_ways, :current_way_segments, :current_way_tags + fixtures :current_ways, :current_way_nodes, :current_way_tags set_fixture_class :current_ways => :Way - set_fixture_class :current_way_segments => :WaySegment + set_fixture_class :current_way_nodes => :WayNode set_fixture_class :current_way_tags => :WayTag - fixtures :ways, :way_segments, :way_tags + fixtures :ways, :way_nodes, :way_tags set_fixture_class :ways => :OldWay - set_fixture_class :way_segments => :OldWaySegment + set_fixture_class :way_nodes => :OldWayNode set_fixture_class :way_tags => :OldWayTag end diff --git a/test/unit/node_test.rb b/test/unit/node_test.rb index b3e834370..95321b5cf 100644 --- a/test/unit/node_test.rb +++ b/test/unit/node_test.rb @@ -11,7 +11,7 @@ class NodeTest < Test::Unit::TestCase :user_id => users(:normal_user).id, :visible => 1, :tags => "") - assert node_template.save_with_history + assert node_template.save_with_history! node = Node.find(node_template.id) assert_not_nil node @@ -44,7 +44,7 @@ class NodeTest < Test::Unit::TestCase node_template.latitude = 12.3456 node_template.longitude = 65.4321 node_template.tags = "updated=yes" - assert node_template.save_with_history + assert node_template.save_with_history! node = Node.find(node_template.id) assert_not_nil node @@ -76,7 +76,7 @@ class NodeTest < Test::Unit::TestCase assert_not_nil old_node_template node_template.visible = 0 - assert node_template.save_with_history + assert node_template.save_with_history! node = Node.find(node_template.id) assert_not_nil node diff --git a/test/unit/segment_test.rb b/test/unit/segment_test.rb deleted file mode 100644 index e8af3df6d..000000000 --- a/test/unit/segment_test.rb +++ /dev/null @@ -1,104 +0,0 @@ -require File.dirname(__FILE__) + '/../test_helper' - -class SegmentTest < Test::Unit::TestCase - fixtures :current_nodes, :nodes, :current_segments, :segments, :users - set_fixture_class :current_segments => :Segment - set_fixture_class :segments => :OldSegment - set_fixture_class :current_nodes => :Node - set_fixture_class :nodes => :OldNode - - def test_create - - segment_template = Segment.new(:node_a => nodes(:used_node_1).id, - :node_b => nodes(:used_node_2).id, - :user_id => users(:normal_user).id, - :visible => 1, - :tags => "") - assert segment_template.save_with_history - - segment = Segment.find(segment_template.id) - assert_not_nil segment - assert_equal segment_template.node_a, segment.node_a - assert_equal segment_template.node_b, segment.node_b - assert_equal segment_template.user_id, segment.user_id - assert_equal segment_template.visible, segment.visible - assert_equal segment_template.tags, segment.tags - assert_equal segment_template.timestamp.to_i, segment.timestamp.to_i - - assert_equal OldSegment.find(:all, :conditions => [ "id = ?", segment_template.id ]).length, 1 - old_segment = OldSegment.find(:first, :conditions => [ "id = ?", segment_template.id ]) - assert_not_nil old_segment - assert_equal segment_template.node_a, old_segment.node_a - assert_equal segment_template.node_b, old_segment.node_b - assert_equal segment_template.user_id, old_segment.user_id - assert_equal segment_template.visible, old_segment.visible - assert_equal segment_template.tags, old_segment.tags - assert_equal segment_template.timestamp.to_i, old_segment.timestamp.to_i - end - - def test_update - segment_template = Segment.find(1) - assert_not_nil segment_template - - assert_equal OldSegment.find(:all, :conditions => [ "id = ?", segment_template.id ]).length, 1 - old_segment_template = OldSegment.find(:first, :conditions => [ "id = ?", segment_template.id ]) - assert_not_nil old_segment_template - - segment_template.node_a = nodes(:used_node_2).id - segment_template.node_b = nodes(:used_node_1).id - segment_template.tags = "updated=yes" - assert segment_template.save_with_history - - segment = Segment.find(segment_template.id) - assert_not_nil segment - assert_equal segment_template.node_a, segment.node_a - assert_equal segment_template.node_b, segment.node_b - assert_equal segment_template.user_id, segment.user_id - assert_equal segment_template.visible, segment.visible - assert_equal segment_template.tags, segment.tags - assert_equal segment_template.timestamp.to_i, segment.timestamp.to_i - - assert_equal OldSegment.find(:all, :conditions => [ "id = ?", segment_template.id ]).length, 2 - assert_equal OldSegment.find(:all, :conditions => [ "id = ? and timestamp = ?", segment_template.id, segment_template.timestamp ]).length, 1 - old_segment = OldSegment.find(:first, :conditions => [ "id = ? and timestamp = ?", segment_template.id, segment_template.timestamp ]) - assert_not_nil old_segment - assert_equal segment_template.node_a, old_segment.node_a - assert_equal segment_template.node_b, old_segment.node_b - assert_equal segment_template.user_id, old_segment.user_id - assert_equal segment_template.visible, old_segment.visible - assert_equal segment_template.tags, old_segment.tags - assert_equal segment_template.timestamp.to_i, old_segment.timestamp.to_i - end - - def test_delete - segment_template = Segment.find(1) - assert_not_nil segment_template - - assert_equal OldSegment.find(:all, :conditions => [ "id = ?", segment_template.id ]).length, 1 - old_segment_template = OldSegment.find(:first, :conditions => [ "id = ?", segment_template.id ]) - assert_not_nil old_segment_template - - segment_template.visible = 0 - assert segment_template.save_with_history - - segment = Segment.find(segment_template.id) - assert_not_nil segment - assert_equal segment_template.node_a, segment.node_a - assert_equal segment_template.node_b, segment.node_b - assert_equal segment_template.user_id, segment.user_id - assert_equal segment_template.visible, segment.visible - assert_equal segment_template.tags, segment.tags - assert_equal segment_template.timestamp.to_i, segment.timestamp.to_i - - assert_equal OldSegment.find(:all, :conditions => [ "id = ?", segment_template.id ]).length, 2 - assert_equal OldSegment.find(:all, :conditions => [ "id = ? and timestamp = ?", segment_template.id, segment_template.timestamp ]).length, 1 - old_segment = OldSegment.find(:first, :conditions => [ "id = ? and timestamp = ?", segment_template.id, segment_template.timestamp ]) - assert_not_nil old_segment - assert_equal segment_template.node_a, old_segment.node_a - assert_equal segment_template.node_b, old_segment.node_b - assert_equal segment_template.user_id, old_segment.user_id - assert_equal segment_template.visible, old_segment.visible - assert_equal segment_template.tags, old_segment.tags - assert_equal segment_template.timestamp.to_i, old_segment.timestamp.to_i - end -end