From 78b440ffc1e0448f8a6d73c8821dd58634ffb475 Mon Sep 17 00:00:00 2001 From: Frederik Ramm Date: Wed, 29 Aug 2007 22:00:26 +0000 Subject: [PATCH] preliminary commit 0.5 API with relations / untested after entity-relation rename, some changes still in queue --- app/controllers/amf_controller.rb | 30 +- app/controllers/api_controller.rb | 112 ++-- app/controllers/node_controller.rb | 4 +- app/controllers/old_way_node_controller.rb | 2 + app/controllers/relation_controller.rb | 181 ++++++ app/controllers/relation_member_controller.rb | 5 + app/controllers/relation_tag_controller.rb | 9 + app/controllers/search_controller.rb | 30 +- app/controllers/site_controller.rb | 2 +- app/controllers/swf_controller.rb | 2 +- app/controllers/way_controller.rb | 39 +- app/controllers/way_node_controller.rb | 5 + app/helpers/old_way_node_helper.rb | 2 + app/helpers/way_node_helper.rb | 2 + app/models/old_relation.rb | 111 ++++ app/models/old_relation_member.rb | 6 + app/models/old_relation_tag.rb | 6 + app/models/old_way.rb | 44 +- app/models/old_way_node.rb | 6 + app/models/relation.rb | 213 +++++++ app/models/relation_member.rb | 30 + app/models/relation_tag.rb | 6 + app/models/way.rb | 83 +-- app/models/way_node.rb | 5 + config/environment.rb | 2 +- config/routes.rb | 18 +- db/migrate/005_add_relations.rb | 82 +++ db/migrate/006_remove_segments.rb | 81 +++ db/migrate/006_remove_segments_helper.cc | 574 ++++++++++++++++++ script/statistics | 2 - test/fixtures/current_entity_members.yml | 5 + test/fixtures/current_entity_tags.yml | 9 + test/fixtures/current_relations.yml | 11 + test/fixtures/current_way_nodes.yml | 14 + test/fixtures/current_way_tags.yml | 8 +- test/fixtures/current_ways.yml | 7 + test/fixtures/entity_members.yml | 6 + test/fixtures/entity_tags.yml | 11 + test/fixtures/relations.yml | 13 + test/fixtures/way_nodes.yml | 17 + test/fixtures/way_tags.yml | 6 + test/fixtures/ways.yml | 8 + test/functional/api_controller_test.rb | 34 ++ test/functional/relation_controller_test.rb | 176 ++++++ test/functional/way_controller_test.rb | 41 +- test/test_helper.rb | 12 +- 46 files changed, 1841 insertions(+), 231 deletions(-) create mode 100644 app/controllers/old_way_node_controller.rb create mode 100644 app/controllers/relation_controller.rb create mode 100644 app/controllers/relation_member_controller.rb create mode 100644 app/controllers/relation_tag_controller.rb create mode 100644 app/controllers/way_node_controller.rb create mode 100644 app/helpers/old_way_node_helper.rb create mode 100644 app/helpers/way_node_helper.rb create mode 100644 app/models/old_relation.rb create mode 100644 app/models/old_relation_member.rb create mode 100644 app/models/old_relation_tag.rb create mode 100644 app/models/old_way_node.rb create mode 100644 app/models/relation.rb create mode 100644 app/models/relation_member.rb create mode 100644 app/models/relation_tag.rb create mode 100644 app/models/way_node.rb create mode 100644 db/migrate/005_add_relations.rb create mode 100644 db/migrate/006_remove_segments.rb create mode 100644 db/migrate/006_remove_segments_helper.cc create mode 100644 test/fixtures/current_entity_members.yml create mode 100644 test/fixtures/current_entity_tags.yml create mode 100644 test/fixtures/current_relations.yml create mode 100644 test/fixtures/current_way_nodes.yml create mode 100644 test/fixtures/entity_members.yml create mode 100644 test/fixtures/entity_tags.yml create mode 100644 test/fixtures/relations.yml create mode 100644 test/fixtures/way_nodes.yml create mode 100644 test/functional/api_controller_test.rb create mode 100644 test/functional/relation_controller_test.rb diff --git a/app/controllers/amf_controller.rb b/app/controllers/amf_controller.rb index 8db813b38..d30043e59 100644 --- a/app/controllers/amf_controller.rb +++ b/app/controllers/amf_controller.rb @@ -195,12 +195,12 @@ EOF RAILS_DEFAULT_LOGGER.info(" Message: whichways, bbox=#{xmin},#{ymin},#{xmax},#{ymax}") - waylist=WaySegment.find_by_sql("SELECT DISTINCT current_way_segments.id AS wayid"+ - " FROM current_way_segments,current_segments,current_nodes,current_ways "+ + waylist=WayNode.find_by_sql("SELECT DISTINCT current_way_nodes.id AS wayid"+ + " FROM current_way_nodes,current_segments,current_nodes,current_ways "+ " WHERE segment_id=current_segments.id "+ " AND current_segments.visible=1 "+ " AND node_a=current_nodes.id "+ - " AND current_ways.id=current_way_segments.id "+ + " AND current_ways.id=current_way_nodes.id "+ " AND current_ways.visible=1 "+ " AND (latitude BETWEEN "+ymin.to_s+" AND "+ymax.to_s+") "+ " AND (longitude BETWEEN "+xmin.to_s+" AND "+xmax.to_s+")") @@ -403,7 +403,7 @@ EOF ActiveRecord::Base.connection.execute("DROP TABLE #{db_uqs}") ActiveRecord::Base.connection.execute("DROP TABLE #{db_uqn}") - # insert new version of route into way_segments + # insert new version of route into way_nodes insertsql ='' currentsql='' @@ -417,9 +417,9 @@ EOF sequence +=1 end - ActiveRecord::Base.connection.execute("DELETE FROM current_way_segments WHERE id=#{way}"); - ActiveRecord::Base.connection.insert("INSERT INTO way_segments (id,segment_id,version ) VALUES #{insertsql}"); - ActiveRecord::Base.connection.insert("INSERT INTO current_way_segments (id,segment_id,sequence_id) VALUES #{currentsql}"); + ActiveRecord::Base.connection.execute("DELETE FROM current_way_nodes WHERE id=#{way}"); + ActiveRecord::Base.connection.insert("INSERT INTO way_nodes (id,segment_id,version ) VALUES #{insertsql}"); + ActiveRecord::Base.connection.insert("INSERT INTO current_way_nodes (id,segment_id,sequence_id) VALUES #{currentsql}"); # -- 7. insert new way tags @@ -503,7 +503,7 @@ EOF ActiveRecord::Base.connection.insert("INSERT INTO ways (id,user_id,timestamp,visible) VALUES (#{way},#{uid},#{db_now},0)") ActiveRecord::Base.connection.update("UPDATE current_ways SET user_id=#{uid},timestamp=#{db_now},visible=0 WHERE id=#{way}") - ActiveRecord::Base.connection.execute("DELETE FROM current_way_segments WHERE id=#{way}") + ActiveRecord::Base.connection.execute("DELETE FROM current_way_nodes WHERE id=#{way}") ActiveRecord::Base.connection.execute("DELETE FROM current_way_tags WHERE id=#{way}") way @@ -537,7 +537,7 @@ def makeway(args) FROM current_nodes AS cn1, current_nodes AS cn2, current_segments AS cs - LEFT OUTER JOIN current_way_segments ON segment_id=cs.id + LEFT OUTER JOIN current_way_nodes ON segment_id=cs.id WHERE (cn1.longitude BETWEEN #{xs1} AND #{xs2}) AND (cn1.latitude BETWEEN #{ys1} AND #{ys2}) AND segment_id IS NULL @@ -603,7 +603,7 @@ def findconnect(id,nodesused,lookfor,toreverse,baselong,basey,masterscale) FROM current_nodes AS cn1, current_nodes AS cn2, current_segments AS cs - LEFT OUTER JOIN current_way_segments ON segment_id=cs.id + LEFT OUTER JOIN current_way_nodes ON segment_id=cs.id WHERE segment_id IS NULL AND cs.visible=1 AND cn1.id=node_a AND cn1.visible=1 @@ -615,7 +615,7 @@ def findconnect(id,nodesused,lookfor,toreverse,baselong,basey,masterscale) FROM current_nodes AS cn1, current_nodes AS cn2, current_segments AS cs - LEFT OUTER JOIN current_way_segments ON segment_id=cs.id + LEFT OUTER JOIN current_way_nodes ON segment_id=cs.id WHERE segment_id IS NULL AND cs.visible=1 AND cn1.id=node_a AND cn1.visible=1 @@ -663,8 +663,8 @@ end def readwayquery(id) ActiveRecord::Base.connection.select_all "SELECT n1.latitude AS lat1,n1.longitude AS long1,n1.id AS id1,n1.tags as tags1, "+ " n2.latitude AS lat2,n2.longitude AS long2,n2.id AS id2,n2.tags as tags2,segment_id "+ - " FROM current_way_segments,current_segments,current_nodes AS n1,current_nodes AS n2 "+ - " WHERE current_way_segments.id=#{id} "+ + " FROM current_way_nodes,current_segments,current_nodes AS n1,current_nodes AS n2 "+ + " WHERE current_way_nodes.id=#{id} "+ " AND segment_id=current_segments.id "+ " AND current_segments.visible=1 "+ " AND n1.id=node_a and n2.id=node_b "+ @@ -677,9 +677,9 @@ def createuniquesegments(way,uqs_name,seglist) sql=<<-EOF CREATE TEMPORARY TABLE #{uqs_name} SELECT a.segment_id - FROM (SELECT DISTINCT segment_id FROM current_way_segments + FROM (SELECT DISTINCT segment_id FROM current_way_nodes WHERE id = #{way}) a - LEFT JOIN current_way_segments b + LEFT JOIN current_way_nodes b ON b.segment_id = a.segment_id AND b.id != #{way} WHERE b.segment_id IS NULL diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index 7e8017f4e..44ae65285 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -136,12 +136,14 @@ 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 # get all the nodes - nodes = Node.find(:all, :conditions => ['latitude > ? AND longitude > ? AND latitude < ? AND longitude < ? AND visible = 1', min_lat, min_lon, max_lat, max_lon]) + nodes = Node.find(:all, :conditions => + ['latitude > ? AND longitude > ? AND latitude < ? AND longitude < ? AND visible = 1', min_lat, min_lon, max_lat, max_lon]) node_ids = nodes.collect {|node| node.id } @@ -151,68 +153,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 = {} @@ -223,18 +195,40 @@ 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 + way_ids = Array.new + ways.each do |way| + 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 - ways.each do |way| - doc.root << way.to_xml_node(visible_segments, user_display_name_cache) if way.visible? - 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" @@ -251,8 +245,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 5d50df7e6..8bfe2a612 100644 --- a/app/controllers/node_controller.rb +++ b/app/controllers/node_controller.rb @@ -79,7 +79,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 + else if 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_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/relation_controller.rb b/app/controllers/relation_controller.rb new file mode 100644 index 000000000..f9d1586b8 --- /dev/null +++ b/app/controllers/relation_controller.rb @@ -0,0 +1,181 @@ +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 + + if relation.save_with_history + render :text => relation.id.to_s, :content_type => "text/plain" + else + print "save error\n"; + render :nothing => true, :status => :internal_server_error + end + 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 + + if relation.save_with_history + render :nothing => true + else + render :nothing => true, :status => :internal_server_error + end + 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 + + if relation.save_with_history + render :nothing => true + else + render :nothing => true, :status => :internal_server_error + end + 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 full + begin + relation = Relation.find(params[:id]) + + if relation.visible + # In future, we might want to do all the data fetch in one step + seg_ids = relation.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(',')})") + + # 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 << relation.to_xml_node() + + 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_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 => :bad_request + 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/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 b8208050c..33e2ee4a6 100644 --- a/app/controllers/swf_controller.rb +++ b/app/controllers/swf_controller.rb @@ -94,7 +94,7 @@ class SwfController < ApplicationController sql="SELECT cn1.latitude AS lat1,cn1.longitude AS lon1,"+ " cn2.latitude AS lat2,cn2.longitude 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 (cn1.longitude BETWEEN #{xmin} AND #{xmax})"+ diff --git a/app/controllers/way_controller.rb b/app/controllers/way_controller.rb index e7d6cf87b..2ef960c6e 100644 --- a/app/controllers/way_controller.rb +++ b/app/controllers/way_controller.rb @@ -59,7 +59,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 if way.save_with_history @@ -86,15 +86,19 @@ class WayController < ApplicationController way = Way.find(params[:id]) if way.visible - way.user_id = @user.id - way.tags = [] - way.segs = [] - way.visible = false - - if way.save_with_history - render :nothing => true + 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 - render :nothing => true, :status => :internal_server_error + way.user_id = @user.id + way.tags = [] + way.nds = [] + way.visible = false + + if way.save_with_history + render :nothing => true + else + render :nothing => true, :status => :internal_server_error + end end else render :nothing => true, :status => :gone @@ -111,23 +115,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" @@ -161,8 +156,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/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/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/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_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/relation.rb b/app/models/relation.rb new file mode 100644 index 000000000..f74a149ec --- /dev/null +++ b/app/models/relation.rb @@ -0,0 +1,213 @@ +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 + begin + 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 + + return true + rescue Exception => ex + return nil + 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/way.rb b/app/models/way.rb index 09d1dafa0..bb99ba33e 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['id']) 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['id'] = 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) @@ -161,17 +161,17 @@ class Way < ActiveRecord::Base tag.save! end - segs = self.segs + 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 @@ -181,16 +181,17 @@ class Way < ActiveRecord::Base end return true - rescue + rescue => ex + puts ex return nil end 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/config/environment.rb b/config/environment.rb index 45d03d2ad..570cb04b5 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -14,7 +14,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 b9d5d41c9..9aef0c0d5 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/nodes", :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/005_add_relations.rb b/db/migrate/005_add_relations.rb new file mode 100644 index 000000000..ad396fc17 --- /dev/null +++ b/db/migrate/005_add_relations.rb @@ -0,0 +1,82 @@ +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 + t.column "member_type", :string, :limit => 11 + t.column "member_id", :bigint, :limit => 11 + 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_index "current_relation_members", ["member_type", "member_id"], :name => "current_relation_members_member_idx" + 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 + 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", myisam_table do |t| + t.column "id", :bigint, :limit => 64, :null => false + t.column "user_id", :bigint, :limit => 20 + t.column "timestamp", :datetime + t.column "visible", :boolean + 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 + t.column "member_id", :bigint, :limit => 11 + t.column "member_role", :string + t.column "version", :bigint, :limit => 20, :default => 0, :null => false + end + + add_primary_key "relation_members", ["id", "version", "member_id", "member_role"] + execute "alter table relation_members change column member_type member_type enum('node','way','relation');" + + create_table "relation_tags", myisam_table do |t| + t.column "id", :bigint, :limit => 64, :default => 0, :null => false + t.column "k", :string + t.column "v", :string + t.column "version", :bigint, :limit => 20 + 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, :default => 0, :null => false + t.column "user_id", :bigint, :limit => 20 + t.column "timestamp", :datetime + t.column "version", :bigint, :limit => 20, :null => false + t.column "visible", :boolean, :default => true + end + + add_primary_key "relations", ["id", "version"] + add_index "relations", ["id"], :name => "relations_id_version_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/006_remove_segments.rb b/db/migrate/006_remove_segments.rb new file mode 100644 index 000000000..76a3161d1 --- /dev/null +++ b/db/migrate/006_remove_segments.rb @@ -0,0 +1,81 @@ +require 'lib/migrate' + +class RemoveSegments < ActiveRecord::Migration + def self.up + prefix = File.join Dir.tmpdir, "006_remove_segments.#{$$}." + + cmd = "db/migrate/006_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 + + 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 "DELETE FROM way_tags" + execute "DELETE FROM ways" + execute "DELETE FROM current_way_tags" + execute "DELETE FROM 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 } + + 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" + + # and then readd the index + add_index :current_way_nodes, [:node_id], :name => "current_way_nodes_node_idx" + + 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" + + tempfiles.each { |fn| File.unlink fn } + end + + def self.down + raise IrreversibleMigration.new + end +end diff --git a/db/migrate/006_remove_segments_helper.cc b/db/migrate/006_remove_segments_helper.cc new file mode 100644 index 000000000..937aa50cb --- /dev/null +++ b/db/migrate/006_remove_segments_helper.cc @@ -0,0 +1,574 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +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, "005_remove_segments_helper: MySQL error: %s\n", err); + } else { + fprintf(stderr, "005_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, "005_remove_segments_helper: MySQL stmt error: %s\n", err); + } else { + fprintf(stderr, "005_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(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(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 (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, "\"%Lu\",", 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, "\"%Lu\",\"%lu\",\"%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, "\"%Lu\",", *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, "\"%Lu\",", ent_id); + write_csv_col(d->relations, user_id, ','); + write_csv_col(d->relations, timestamp, '\n'); + + fprintf(d->relation_tags, + "\"%Lu\",\"type\",\"multipolygon\"\n", ent_id); + + for (vector::iterator it = ids.begin(); + it != ids.end(); ++it) { + fprintf(d->relation_members, + "\"%Lu\",\"way\",\"%Lu\",\"\"\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; + + 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; + char *tags_it = row[1], *k, *v; + while (read_seg_tags(&tags_it, &k, &v)) { + if (!strcmp(k, "created_by")) { + d->rem_segs[id] = 1; + break; + } + } + } + + mysql_free_result(res); +} + +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, "\"%Lu\",\"%i\",", way_id, user_id); + write_csv_col(d->ways, timestamp, '\n'); + + fprintf(d->way_nodes, "\"%Lu\",\"%lu\",\"%i\"\n", way_id, seg.from, 1); + fprintf(d->way_nodes, "\"%Lu\",\"%lu\",\"%i\"\n", way_id, seg.to, 2); + + char *tags_it = tags; + while (read_seg_tags(&tags_it, &k, &v)) { + fprintf(d->way_tags, "\"%Lu\",", 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: 006_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_entity_members.yml b/test/fixtures/current_entity_members.yml new file mode 100644 index 000000000..7921ac145 --- /dev/null +++ b/test/fixtures/current_entity_members.yml @@ -0,0 +1,5 @@ +t1: + id: 1 + member_role: "some" + member_type: "node" + member_id: 3 diff --git a/test/fixtures/current_entity_tags.yml b/test/fixtures/current_entity_tags.yml new file mode 100644 index 000000000..ba795b698 --- /dev/null +++ b/test/fixtures/current_entity_tags.yml @@ -0,0 +1,9 @@ +t1: + id: 1 + k: test + v: yes + +t2: + id: 2 + k: test + v: yes diff --git a/test/fixtures/current_relations.yml b/test/fixtures/current_relations.yml new file mode 100644 index 000000000..67c8ddb95 --- /dev/null +++ b/test/fixtures/current_relations.yml @@ -0,0 +1,11 @@ +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 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_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/entity_members.yml b/test/fixtures/entity_members.yml new file mode 100644 index 000000000..f424e6a51 --- /dev/null +++ b/test/fixtures/entity_members.yml @@ -0,0 +1,6 @@ +t1: + id: 1 + member_role: "some" + member_type: "node" + member_id: 3 + version: 1 diff --git a/test/fixtures/entity_tags.yml b/test/fixtures/entity_tags.yml new file mode 100644 index 000000000..d1c69a6d4 --- /dev/null +++ b/test/fixtures/entity_tags.yml @@ -0,0 +1,11 @@ +t1: + id: 1 + k: test + v: yes + version: 1 + +t2: + id: 2 + k: test + v: yes + version: 1 diff --git a/test/fixtures/relations.yml b/test/fixtures/relations.yml new file mode 100644 index 000000000..f1e4026ff --- /dev/null +++ b/test/fixtures/relations.yml @@ -0,0 +1,13 @@ +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 diff --git a/test/fixtures/way_nodes.yml b/test/fixtures/way_nodes.yml new file mode 100644 index 000000000..caeac16b1 --- /dev/null +++ b/test/fixtures/way_nodes.yml @@ -0,0 +1,17 @@ +t1: + id: 1 + node_id: 3 + sequence_id: 1 + version: 1 + +t2: + id: 2 + 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..d5a4d9105 --- /dev/null +++ b/test/functional/relation_controller_test.rb @@ -0,0 +1,176 @@ +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 + 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/way_controller_test.rb b/test/functional/way_controller_test.rb index c5a3f0b8e..afe9e8652 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 @@ -48,11 +44,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 +60,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 +79,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 -- 2.39.5