Merge api06 branch to trunk.

This commit is contained in:
Tom Hughes 2009-04-20 09:12:03 +00:00
commit fd6f00b069
196 changed files with 10115 additions and 1794 deletions

File diff suppressed because it is too large Load diff

View file

@ -11,12 +11,13 @@ class ApiController < ApplicationController
@@count = COUNT
# The maximum area you're allowed to request, in square degrees
MAX_REQUEST_AREA = 0.25
MAX_REQUEST_AREA = APP_CONFIG['max_request_area']
# Number of GPS trace/trackpoints returned per-page
TRACEPOINTS_PER_PAGE = 5000
TRACEPOINTS_PER_PAGE = APP_CONFIG['tracepoints_per_page']
# Get an XML response containing a list of tracepoints that have been uploaded
# within the specified bounding box, and in the specified page.
def trackpoints
@@count+=1
#retrieve the page number
@ -84,6 +85,15 @@ class ApiController < ApplicationController
render :text => doc.to_s, :content_type => "text/xml"
end
# This is probably the most common call of all. It is used for getting the
# OSM data for a specified bounding box, usually for editing. First the
# bounding box (bbox) is checked to make sure that it is sane. All nodes
# are searched, then all the ways that reference those nodes are found.
# All Nodes that are referenced by those ways are fetched and added to the list
# of nodes.
# Then all the relations that reference the already found nodes and ways are
# fetched. All the nodes and ways that are referenced by those ways are then
# fetched. Finally all the xml is returned.
def map
GC.start
@@count+=1
@ -109,18 +119,19 @@ class ApiController < ApplicationController
return
end
@nodes = Node.find_by_area(min_lat, min_lon, max_lat, max_lon, :conditions => "visible = 1", :limit => APP_CONFIG['max_number_of_nodes']+1)
# FIXME um why is this area using a different order for the lat/lon from above???
@nodes = Node.find_by_area(min_lat, min_lon, max_lat, max_lon, :conditions => {:visible => true}, :limit => APP_CONFIG['max_number_of_nodes']+1)
# get all the nodes, by tag not yet working, waiting for change from NickB
# need to be @nodes (instance var) so tests in /spec can be performed
#@nodes = Node.search(bbox, params[:tag])
node_ids = @nodes.collect(&:id)
if node_ids.length > APP_CONFIG['max_number_of_nodes']
report_error("You requested too many nodes (limit is 50,000). Either request a smaller area, or use planet.osm")
report_error("You requested too many nodes (limit is #{APP_CONFIG['max_number_of_nodes']}). Either request a smaller area, or use planet.osm")
return
end
if node_ids.length == 0
render :text => "<osm version='0.5'></osm>", :content_type => "text/xml"
render :text => "<osm version='#{API_VERSION}' generator='#{GENERATOR}'></osm>", :content_type => "text/xml"
return
end
@ -176,15 +187,15 @@ class ApiController < ApplicationController
end
end
relations = Relation.find_for_nodes(visible_nodes.keys, :conditions => "visible = 1") +
Relation.find_for_ways(way_ids, :conditions => "visible = 1")
relations = Relation.find_for_nodes(visible_nodes.keys, :conditions => {:visible => true}) +
Relation.find_for_ways(way_ids, :conditions => {:visible => true})
# 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)
relations += Relation.find_for_relations(relations.collect { |r| r.id }, :conditions => "visible = 1")
relations += Relation.find_for_relations(relations.collect { |r| r.id }, :conditions => {:visible => true})
# 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.
@ -204,6 +215,8 @@ class ApiController < ApplicationController
end
end
# Get a list of the tiles that have changed within a specified time
# period
def changes
zoom = (params[:zoom] || '12').to_i
@ -212,12 +225,12 @@ class ApiController < ApplicationController
endtime = Time.parse(params[:end])
else
hours = (params[:hours] || '1').to_i.hours
endtime = Time.now
endtime = Time.now.getutc
starttime = endtime - hours
end
if zoom >= 1 and zoom <= 16 and
endtime >= starttime and endtime - starttime <= 24.hours
endtime > starttime and endtime - starttime <= 24.hours
mask = (1 << zoom) - 1
tiles = Node.count(:conditions => ["timestamp BETWEEN ? AND ?", starttime, endtime],
@ -245,21 +258,32 @@ class ApiController < ApplicationController
render :text => doc.to_s, :content_type => "text/xml"
else
render :nothing => true, :status => :bad_request
render :text => "Requested zoom is invalid, or the supplied start is after the end time, or the start duration is more than 24 hours", :status => :bad_request
end
end
# External apps that use the api are able to query the api to find out some
# parameters of the API. It currently returns:
# * minimum and maximum API versions that can be used.
# * maximum area that can be requested in a bbox request in square degrees
# * number of tracepoints that are returned in each tracepoints page
def capabilities
doc = OSM::API.new.get_xml_doc
api = XML::Node.new 'api'
version = XML::Node.new 'version'
version['minimum'] = '0.5';
version['maximum'] = '0.5';
version['minimum'] = "#{API_VERSION}";
version['maximum'] = "#{API_VERSION}";
api << version
area = XML::Node.new 'area'
area['maximum'] = MAX_REQUEST_AREA.to_s;
api << area
tracepoints = XML::Node.new 'tracepoints'
tracepoints['per_page'] = APP_CONFIG['tracepoints_per_page'].to_s
api << tracepoints
waynodes = XML::Node.new 'waynodes'
waynodes['maximum'] = APP_CONFIG['max_number_of_way_nodes'].to_s
api << waynodes
doc.root << api

View file

@ -8,7 +8,7 @@ class ApplicationController < ActionController::Base
def authorize_web
if session[:user]
@user = User.find(session[:user], :conditions => "visible = 1")
@user = User.find(session[:user], :conditions => {:visible => true})
elsif session[:token]
@user = User.authenticate(:token => session[:token])
session[:user] = @user.id
@ -22,7 +22,11 @@ class ApplicationController < ActionController::Base
redirect_to :controller => 'user', :action => 'login', :referer => request.request_uri unless @user
end
def authorize(realm='Web Password', errormessage="Couldn't authenticate you")
##
# sets up the @user object for use by other methods. this is mostly called
# from the authorize method, but can be called elsewhere if authorisation
# is optional.
def setup_user_auth
username, passwd = get_auth_data # parse from headers
# authenticate per-scheme
if username.nil?
@ -32,6 +36,11 @@ class ApplicationController < ActionController::Base
else
@user = User.authenticate(:username => username, :password => passwd) # basic auth
end
end
def authorize(realm='Web Password', errormessage="Couldn't authenticate you")
# make the @user object from any auth sources we have
setup_user_auth
# handle authenticate pass/fail
unless @user
@ -73,13 +82,21 @@ class ApplicationController < ActionController::Base
end
end
def require_public_data
unless @user.data_public?
response.headers['Error'] = "You must make your edits public to upload new data"
render :nothing => true, :status => :forbidden
return false
end
end
# Report and error to the user
# (If anyone ever fixes Rails so it can set a http status "reason phrase",
# rather than only a status code and having the web engine make up a
# phrase from that, we can also put the error message into the status
# message. For now, rails won't let us)
def report_error(message)
render :nothing => true, :status => :bad_request
render :text => message, :status => :bad_request
# Todo: some sort of escaping of problem characters in the message
response.headers['Error'] = message
end
@ -90,6 +107,8 @@ private
def get_auth_data
if request.env.has_key? 'X-HTTP_AUTHORIZATION' # where mod_rewrite might have put it
authdata = request.env['X-HTTP_AUTHORIZATION'].to_s.split
elsif request.env.has_key? 'REDIRECT_X_HTTP_AUTHORIZATION' # mod_fcgi
authdata = request.env['REDIRECT_X_HTTP_AUTHORIZATION'].to_s.split
elsif request.env.has_key? 'HTTP_AUTHORIZATION' # regular location
authdata = request.env['HTTP_AUTHORIZATION'].to_s.split
end

View file

@ -7,103 +7,109 @@ class BrowseController < ApplicationController
def start
end
def index
@nodes = Node.find(:all, :order => "timestamp DESC", :limit=> 20)
end
def relation
begin
@relation = Relation.find(params[:id])
@name = @relation.tags['name'].to_s
if @name.length == 0:
@name = "#" + @relation.id.to_s
end
@title = 'Relation | ' + (@name)
@next = Relation.find(:first, :order => "id ASC", :conditions => [ "visible = true AND id > :id", { :id => @relation.id }] )
@prev = Relation.find(:first, :order => "id DESC", :conditions => [ "visible = true AND id < :id", { :id => @relation.id }] )
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
@relation = Relation.find(params[:id])
@name = @relation.tags['name'].to_s
if @name.length == 0:
@name = "#" + @relation.id.to_s
end
@title = 'Relation | ' + (@name)
@next = Relation.find(:first, :order => "id ASC", :conditions => [ "visible = true AND id > :id", { :id => @relation.id }] )
@prev = Relation.find(:first, :order => "id DESC", :conditions => [ "visible = true AND id < :id", { :id => @relation.id }] )
rescue ActiveRecord::RecordNotFound
@type = "relation"
render :action => "not_found", :status => :not_found
end
def relation_history
begin
@relation = Relation.find(params[:id])
@relation = Relation.find(params[:id])
@name = @relation.tags['name'].to_s
if @name.length == 0:
@name = "#" + @relation.id.to_s
end
@title = 'Relation History | ' + (@name)
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
@name = @relation.tags['name'].to_s
if @name.length == 0:
@name = "#" + @relation.id.to_s
end
@title = 'Relation History | ' + (@name)
rescue ActiveRecord::RecordNotFound
@type = "relation"
render :action => "not_found", :status => :not_found
end
def way
begin
@way = Way.find(params[:id])
@way = Way.find(params[:id])
@name = @way.tags['name'].to_s
if @name.length == 0:
@name = "#" + @way.id.to_s
end
@title = 'Way | ' + (@name)
@next = Way.find(:first, :order => "id ASC", :conditions => [ "visible = true AND id > :id", { :id => @way.id }] )
@prev = Way.find(:first, :order => "id DESC", :conditions => [ "visible = true AND id < :id", { :id => @way.id }] )
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
@name = @way.tags['name'].to_s
if @name.length == 0:
@name = "#" + @way.id.to_s
end
@title = 'Way | ' + (@name)
@next = Way.find(:first, :order => "id ASC", :conditions => [ "visible = true AND id > :id", { :id => @way.id }] )
@prev = Way.find(:first, :order => "id DESC", :conditions => [ "visible = true AND id < :id", { :id => @way.id }] )
rescue ActiveRecord::RecordNotFound
@type = "way"
render :action => "not_found", :status => :not_found
end
def way_history
begin
@way = Way.find(params[:id])
@way = Way.find(params[:id])
@name = @way.tags['name'].to_s
if @name.length == 0:
@name = "#" + @way.id.to_s
end
@title = 'Way History | ' + (@name)
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
@name = @way.tags['name'].to_s
if @name.length == 0:
@name = "#" + @way.id.to_s
end
@title = 'Way History | ' + (@name)
rescue ActiveRecord::RecordNotFound
@type = "way"
render :action => "not_found", :status => :not_found
end
def node
begin
@node = Node.find(params[:id])
@node = Node.find(params[:id])
@name = @node.tags_as_hash['name'].to_s
if @name.length == 0:
@name = "#" + @node.id.to_s
end
@title = 'Node | ' + (@name)
@next = Node.find(:first, :order => "id ASC", :conditions => [ "visible = true AND id > :id", { :id => @node.id }] )
@prev = Node.find(:first, :order => "id DESC", :conditions => [ "visible = true AND id < :id", { :id => @node.id }] )
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
@name = @node.tags_as_hash['name'].to_s
if @name.length == 0:
@name = "#" + @node.id.to_s
end
@title = 'Node | ' + (@name)
@next = Node.find(:first, :order => "id ASC", :conditions => [ "visible = true AND id > :id", { :id => @node.id }] )
@prev = Node.find(:first, :order => "id DESC", :conditions => [ "visible = true AND id < :id", { :id => @node.id }] )
rescue ActiveRecord::RecordNotFound
@type = "node"
render :action => "not_found", :status => :not_found
end
def node_history
begin
@node = Node.find(params[:id])
@node = Node.find(params[:id])
@name = @node.tags_as_hash['name'].to_s
if @name.length == 0:
@name = "#" + @node.id.to_s
end
@title = 'Node History | ' + (@name)
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
@name = @node.tags_as_hash['name'].to_s
if @name.length == 0:
@name = "#" + @node.id.to_s
end
@title = 'Node History | ' + (@name)
rescue ActiveRecord::RecordNotFound
@type = "way"
render :action => "not_found", :status => :not_found
end
def changeset
@changeset = Changeset.find(params[:id])
@node_pages, @nodes = paginate(:old_nodes, :conditions => {:changeset_id => @changeset.id}, :per_page => 20, :parameter => 'node_page')
@way_pages, @ways = paginate(:old_ways, :conditions => {:changeset_id => @changeset.id}, :per_page => 20, :parameter => 'way_page')
@relation_pages, @relations = paginate(:old_relations, :conditions => {:changeset_id => @changeset.id}, :per_page => 20, :parameter => 'relation_page')
@title = "Changeset | #{@changeset.id}"
@next = Changeset.find(:first, :order => "id ASC", :conditions => [ "id > :id", { :id => @changeset.id }] )
@prev = Changeset.find(:first, :order => "id DESC", :conditions => [ "id < :id", { :id => @changeset.id }] )
rescue ActiveRecord::RecordNotFound
@type = "changeset"
render :action => "not_found", :status => :not_found
end
end

View file

@ -0,0 +1,485 @@
# The ChangesetController is the RESTful interface to Changeset objects
class ChangesetController < ApplicationController
layout 'site'
require 'xml/libxml'
session :off, :except => [:list, :list_user, :list_bbox]
before_filter :authorize_web, :only => [:list, :list_user, :list_bbox]
before_filter :authorize, :only => [:create, :update, :delete, :upload, :include, :close]
before_filter :require_public_data, :only => [:create, :update, :delete, :upload, :include, :close]
before_filter :check_api_writable, :only => [:create, :update, :delete, :upload, :include]
before_filter :check_api_readable, :except => [:create, :update, :delete, :upload, :download, :query]
after_filter :compress_output
# Help methods for checking boundary sanity and area size
include MapBoundary
# Helper methods for checking consistency
include ConsistencyValidations
# Create a changeset from XML.
def create
if request.put?
cs = Changeset.from_xml(request.raw_post, true)
if cs
cs.user_id = @user.id
cs.save_with_tags!
render :text => cs.id.to_s, :content_type => "text/plain"
else
render :nothing => true, :status => :bad_request
end
else
render :nothing => true, :status => :method_not_allowed
end
end
##
# Return XML giving the basic info about the changeset. Does not
# return anything about the nodes, ways and relations in the changeset.
def read
begin
changeset = Changeset.find(params[:id])
render :text => changeset.to_xml.to_s, :content_type => "text/xml"
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
end
end
##
# marks a changeset as closed. this may be called multiple times
# on the same changeset, so is idempotent.
def close
unless request.put?
render :nothing => true, :status => :method_not_allowed
return
end
changeset = Changeset.find(params[:id])
check_changeset_consistency(changeset, @user)
# to close the changeset, we'll just set its closed_at time to
# now. this might not be enough if there are concurrency issues,
# but we'll have to wait and see.
changeset.set_closed_time_now
changeset.save!
render :nothing => true
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
rescue OSM::APIError => ex
render ex.render_opts
end
##
# insert a (set of) points into a changeset bounding box. this can only
# increase the size of the bounding box. this is a hint that clients can
# set either before uploading a large number of changes, or changes that
# the client (but not the server) knows will affect areas further away.
def expand_bbox
# only allow POST requests, because although this method is
# idempotent, there is no "document" to PUT really...
if request.post?
cs = Changeset.find(params[:id])
check_changeset_consistency(cs, @user)
# keep an array of lons and lats
lon = Array.new
lat = Array.new
# the request is in pseudo-osm format... this is kind-of an
# abuse, maybe should change to some other format?
doc = XML::Parser.string(request.raw_post).parse
doc.find("//osm/node").each do |n|
lon << n['lon'].to_f * GeoRecord::SCALE
lat << n['lat'].to_f * GeoRecord::SCALE
end
# add the existing bounding box to the lon-lat array
lon << cs.min_lon unless cs.min_lon.nil?
lat << cs.min_lat unless cs.min_lat.nil?
lon << cs.max_lon unless cs.max_lon.nil?
lat << cs.max_lat unless cs.max_lat.nil?
# collapse the arrays to minimum and maximum
cs.min_lon, cs.min_lat, cs.max_lon, cs.max_lat =
lon.min, lat.min, lon.max, lat.max
# save the larger bounding box and return the changeset, which
# will include the bigger bounding box.
cs.save!
render :text => cs.to_xml.to_s, :content_type => "text/xml"
else
render :nothing => true, :status => :method_not_allowed
end
rescue LibXML::XML::Error, ArgumentError => ex
raise OSM::APIBadXMLError.new("osm", xml, ex.message)
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
rescue OSM::APIError => ex
render ex.render_opts
end
##
# Upload a diff in a single transaction.
#
# This means that each change within the diff must succeed, i.e: that
# each version number mentioned is still current. Otherwise the entire
# transaction *must* be rolled back.
#
# Furthermore, each element in the diff can only reference the current
# changeset.
#
# Returns: a diffResult document, as described in
# http://wiki.openstreetmap.org/index.php/OSM_Protocol_Version_0.6
def upload
# only allow POST requests, as the upload method is most definitely
# not idempotent, as several uploads with placeholder IDs will have
# different side-effects.
# see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.2
unless request.post?
render :nothing => true, :status => :method_not_allowed
return
end
changeset = Changeset.find(params[:id])
check_changeset_consistency(changeset, @user)
diff_reader = DiffReader.new(request.raw_post, changeset)
Changeset.transaction do
result = diff_reader.commit
render :text => result.to_s, :content_type => "text/xml"
end
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
rescue OSM::APIError => ex
render ex.render_opts
end
##
# download the changeset as an osmChange document.
#
# to make it easier to revert diffs it would be better if the osmChange
# format were reversible, i.e: contained both old and new versions of
# modified elements. but it doesn't at the moment...
#
# this method cannot order the database changes fully (i.e: timestamp and
# version number may be too coarse) so the resulting diff may not apply
# to a different database. however since changesets are not atomic this
# behaviour cannot be guaranteed anyway and is the result of a design
# choice.
def download
changeset = Changeset.find(params[:id])
# get all the elements in the changeset and stick them in a big array.
elements = [changeset.old_nodes,
changeset.old_ways,
changeset.old_relations].flatten
# sort the elements by timestamp and version number, as this is the
# almost sensible ordering available. this would be much nicer if
# global (SVN-style) versioning were used - then that would be
# unambiguous.
elements.sort! do |a, b|
if (a.timestamp == b.timestamp)
a.version <=> b.version
else
a.timestamp <=> b.timestamp
end
end
# create an osmChange document for the output
result = OSM::API.new.get_xml_doc
result.root.name = "osmChange"
# generate an output element for each operation. note: we avoid looking
# at the history because it is simpler - but it would be more correct to
# check these assertions.
elements.each do |elt|
result.root <<
if (elt.version == 1)
# first version, so it must be newly-created.
created = XML::Node.new "create"
created << elt.to_xml_node
else
# get the previous version from the element history
prev_elt = elt.class.find(:first, :conditions =>
['id = ? and version = ?',
elt.id, elt.version])
unless elt.visible
# if the element isn't visible then it must have been deleted, so
# output the *previous* XML
deleted = XML::Node.new "delete"
deleted << prev_elt.to_xml_node
else
# must be a modify, for which we don't need the previous version
# yet...
modified = XML::Node.new "modify"
modified << elt.to_xml_node
end
end
end
render :text => result.to_s, :content_type => "text/xml"
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
rescue OSM::APIError => ex
render ex.render_opts
end
##
# query changesets by bounding box, time, user or open/closed status.
def query
# create the conditions that the user asked for. some or all of
# these may be nil.
conditions = conditions_bbox(params['bbox'])
conditions = cond_merge conditions, conditions_user(params['user'])
conditions = cond_merge conditions, conditions_time(params['time'])
conditions = cond_merge conditions, conditions_open(params['open'])
conditions = cond_merge conditions, conditions_closed(params['closed'])
# create the results document
results = OSM::API.new.get_xml_doc
# add all matching changesets to the XML results document
Changeset.find(:all,
:conditions => conditions,
:limit => 100,
:order => 'created_at desc').each do |cs|
results.root << cs.to_xml_node
end
render :text => results.to_s, :content_type => "text/xml"
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
rescue OSM::APIError => ex
render ex.render_opts
end
##
# updates a changeset's tags. none of the changeset's attributes are
# user-modifiable, so they will be ignored.
#
# changesets are not (yet?) versioned, so we don't have to deal with
# history tables here. changesets are locked to a single user, however.
#
# after succesful update, returns the XML of the changeset.
def update
# request *must* be a PUT.
unless request.put?
render :nothing => true, :status => :method_not_allowed
return
end
changeset = Changeset.find(params[:id])
new_changeset = Changeset.from_xml(request.raw_post)
unless new_changeset.nil?
check_changeset_consistency(changeset, @user)
changeset.update_from(new_changeset, @user)
render :text => changeset.to_xml, :mime_type => "text/xml"
else
render :nothing => true, :status => :bad_request
end
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
rescue OSM::APIError => ex
render ex.render_opts
end
##
# list edits (open changesets) in reverse chronological order
def list
conditions = conditions_nonempty
# @changesets = Changeset.find(:all, :order => "closed_at DESC", :conditions => ['closed_at < ?', DateTime.now], :limit=> 20)
#@edit_pages, @edits = paginate(:changesets,
# :include => [:user, :changeset_tags],
# :conditions => conditions,
# :order => "changesets.created_at DESC",
# :per_page => 20)
#
@edits = Changeset.find(:all,
:order => "changesets.created_at DESC",
:conditions => conditions,
:limit => 20)
end
##
# list edits (changesets) belonging to a user
def list_user
user = User.find_by_display_name(params[:display_name], :conditions => {:visible => true})
if user
@display_name = user.display_name
if not user.data_public? and @user != user
@edits = nil
render
else
conditions = cond_merge conditions, ['user_id = ?', user.id]
conditions = cond_merge conditions, conditions_nonempty
@edit_pages, @edits = paginate(:changesets,
:include => [:user, :changeset_tags],
:conditions => conditions,
:order => "changesets.created_at DESC",
:per_page => 20)
end
else
@not_found_user = params[:display_name]
render :template => 'user/no_such_user', :status => :not_found
end
end
##
# list changesets in a bbox
def list_bbox
# support 'bbox' param or alternatively 'minlon', 'minlat' etc
if params['bbox']
bbox = params['bbox']
elsif params['minlon'] and params['minlat'] and params['maxlon'] and params['maxlat']
bbox = h(params['minlon']) + ',' + h(params['minlat']) + ',' + h(params['maxlon']) + ',' + h(params['maxlat'])
else
#TODO: fix bugs in location determination for history tab (and other tabs) then uncomment this redirect
#redirect_to :action => 'list'
end
conditions = conditions_bbox(bbox);
conditions = cond_merge conditions, conditions_nonempty
@edit_pages, @edits = paginate(:changesets,
:include => [:user, :changeset_tags],
:conditions => conditions,
:order => "changesets.created_at DESC",
:per_page => 20)
@bbox = sanitise_boundaries(bbox.split(/,/)) unless bbox==nil
end
private
#------------------------------------------------------------
# utility functions below.
#------------------------------------------------------------
##
# merge two conditions
def cond_merge(a, b)
if a and b
a_str = a.shift
b_str = b.shift
return [ a_str + " AND " + b_str ] + a + b
elsif a
return a
else b
return b
end
end
##
# if a bounding box was specified then parse it and do some sanity
# checks. this is mostly the same as the map call, but without the
# area restriction.
def conditions_bbox(bbox)
unless bbox.nil?
raise OSM::APIBadUserInput.new("Bounding box should be min_lon,min_lat,max_lon,max_lat") unless bbox.count(',') == 3
bbox = sanitise_boundaries(bbox.split(/,/))
raise OSM::APIBadUserInput.new("Minimum longitude should be less than maximum.") unless bbox[0] <= bbox[2]
raise OSM::APIBadUserInput.new("Minimum latitude should be less than maximum.") unless bbox[1] <= bbox[3]
return ['min_lon < ? and max_lon > ? and min_lat < ? and max_lat > ?',
bbox[2] * GeoRecord::SCALE, bbox[0] * GeoRecord::SCALE, bbox[3]* GeoRecord::SCALE, bbox[1] * GeoRecord::SCALE]
else
return nil
end
end
##
# restrict changesets to those by a particular user
def conditions_user(user)
unless user.nil?
# user input checking, we don't have any UIDs < 1
raise OSM::APIBadUserInput.new("invalid user ID") if user.to_i < 1
u = User.find(user.to_i)
# should be able to get changesets of public users only, or
# our own changesets regardless of public-ness.
unless u.data_public?
# get optional user auth stuff so that users can see their own
# changesets if they're non-public
setup_user_auth
raise OSM::APINotFoundError if @user.nil? or @user.id != u.id
end
return ['user_id = ?', u.id]
else
return nil
end
end
##
# restrict changes to those closed during a particular time period
def conditions_time(time)
unless time.nil?
# if there is a range, i.e: comma separated, then the first is
# low, second is high - same as with bounding boxes.
if time.count(',') == 1
# check that we actually have 2 elements in the array
times = time.split(/,/)
raise OSM::APIBadUserInput.new("bad time range") if times.size != 2
from, to = times.collect { |t| DateTime.parse(t) }
return ['closed_at >= ? and created_at <= ?', from, to]
else
# if there is no comma, assume its a lower limit on time
return ['closed_at >= ?', DateTime.parse(time)]
end
else
return nil
end
# stupid DateTime seems to throw both of these for bad parsing, so
# we have to catch both and ensure the correct code path is taken.
rescue ArgumentError => ex
raise OSM::APIBadUserInput.new(ex.message.to_s)
rescue RuntimeError => ex
raise OSM::APIBadUserInput.new(ex.message.to_s)
end
##
# return changesets which are open (haven't been closed yet)
# we do this by seeing if the 'closed at' time is in the future. Also if we've
# hit the maximum number of changes then it counts as no longer open.
# if parameter 'open' is nill then open and closed changsets are returned
def conditions_open(open)
return open.nil? ? nil : ['closed_at >= ? and num_changes <= ?',
Time.now.getutc, Changeset::MAX_ELEMENTS]
end
##
# query changesets which are closed
# ('closed at' time has passed or changes limit is hit)
def conditions_closed(closed)
return closed.nil? ? nil : ['closed_at < ? and num_changes > ?',
Time.now.getutc, Changeset::MAX_ELEMENTS]
end
##
# eliminate empty changesets (where the bbox has not been set)
# this should be applied to all changeset list displays
def conditions_nonempty()
return ['min_lat IS NOT NULL']
end
end

View file

@ -0,0 +1,9 @@
class ChangesetTagController < ApplicationController
layout 'site'
def search
@tags = ChangesetTag.find(:all, :limit => 11, :conditions => ["match(v) against (?)", params[:query][:query].to_s] )
end
end

View file

@ -39,6 +39,8 @@ class DiaryEntryController < ApplicationController
redirect_to :controller => 'diary_entry', :action => 'view', :id => params[:id]
end
end
rescue ActiveRecord::RecordNotFound
render :action => "no_such_entry", :status => :not_found
end
def comment
@ -55,7 +57,7 @@ class DiaryEntryController < ApplicationController
def list
if params[:display_name]
@this_user = User.find_by_display_name(params[:display_name], :conditions => "visible = 1")
@this_user = User.find_by_display_name(params[:display_name], :conditions => {:visible => true})
if @this_user
@title = @this_user.display_name + "'s diary"
@ -71,7 +73,7 @@ class DiaryEntryController < ApplicationController
else
@title = "Users' diaries"
@entry_pages, @entries = paginate(:diary_entries, :include => :user,
:conditions => "users.visible = 1",
:conditions => ["users.visible = ?", true],
:order => 'created_at DESC',
:per_page => 20)
end
@ -79,13 +81,13 @@ class DiaryEntryController < ApplicationController
def rss
if params[:display_name]
user = User.find_by_display_name(params[:display_name], :conditions => "visible = 1")
user = User.find_by_display_name(params[:display_name], :conditions => {:visible => true})
if user
@entries = DiaryEntry.find(:all, :conditions => ['user_id = ?', user.id], :order => 'created_at DESC', :limit => 20)
@title = "OpenStreetMap diary entries for #{user.display_name}"
@description = "Recent OpenStreetmap diary entries from #{user.display_name}"
@link = "http://www.openstreetmap.org/user/#{user.display_name}/diary"
@link = "http://#{SERVER_URL}/user/#{user.display_name}/diary"
render :content_type => Mime::RSS
else
@ -93,21 +95,22 @@ class DiaryEntryController < ApplicationController
end
else
@entries = DiaryEntry.find(:all, :include => :user,
:conditions => "users.visible = 1",
:conditions => ["users.visible = ?", true],
:order => 'created_at DESC', :limit => 20)
@title = "OpenStreetMap diary entries"
@description = "Recent diary entries from users of OpenStreetMap"
@link = "http://www.openstreetmap.org/diary"
@link = "http://#{SERVER_URL}/diary"
render :content_type => Mime::RSS
end
end
def view
user = User.find_by_display_name(params[:display_name], :conditions => "visible = 1")
user = User.find_by_display_name(params[:display_name], :conditions => {:visible => true})
if user
@entry = DiaryEntry.find(:first, :conditions => ['user_id = ? AND id = ?', user.id, params[:id]])
@title = "Users' diaries | #{params[:display_name]}"
else
@not_found_user = params[:display_name]

View file

@ -2,18 +2,24 @@ class ExportController < ApplicationController
def start
end
#When the user clicks 'Export' we redirect to a URL which generates the export download
def finish
bbox = BoundingBox.new(params[:minlon], params[:minlat], params[:maxlon], params[:maxlat])
format = params[:format]
if format == "osm"
#redirect to API map get
redirect_to "http://api.openstreetmap.org/api/#{API_VERSION}/map?bbox=#{bbox}"
elsif format == "mapnik"
#redirect to a special 'export' cgi script
format = params[:mapnik_format]
scale = params[:mapnik_scale]
redirect_to "http://tile.openstreetmap.org/cgi-bin/export?bbox=#{bbox}&scale=#{scale}&format=#{format}"
elsif format == "osmarender"
#redirect to the t@h 'MapOf' service
format = params[:osmarender_format]
zoom = params[:osmarender_zoom].to_i
width = bbox.slippy_width(zoom).to_i

View file

@ -6,13 +6,18 @@ class MessageController < ApplicationController
before_filter :check_database_readable
before_filter :check_database_writable, :only => [:new, :reply, :mark]
# Allow the user to write a new message to another user. This action also
# deals with the sending of that message to the other user when the user
# clicks send.
# The user_id param is the id of the user that the message is being sent to.
def new
@title = 'send message'
@to_user = User.find(params[:user_id])
if params[:message]
@message = Message.new(params[:message])
@message.to_user_id = params[:user_id]
@message.to_user_id = @to_user.id
@message.from_user_id = @user.id
@message.sent_on = Time.now
@message.sent_on = Time.now.getutc
if @message.save
flash[:notice] = 'Message sent'
@ -22,27 +27,32 @@ class MessageController < ApplicationController
else
@title = params[:title]
end
rescue ActiveRecord::RecordNotFound
render :action => 'no_such_user', :status => :not_found
end
# Allow the user to reply to another message.
def reply
message = Message.find(params[:message_id], :conditions => ["to_user_id = ? or from_user_id = ?", @user.id, @user.id ])
@body = "On #{message.sent_on} #{message.sender.display_name} wrote:\n\n#{message.body.gsub(/^/, '> ')}"
@title = "Re: #{message.title.sub(/^Re:\s*/, '')}"
@user_id = message.from_user_id
@to_user = User.find(message.from_user_id)
render :action => 'new'
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
render :action => 'no_such_user', :status => :not_found
end
# Show a message
def read
@title = 'read message'
@message = Message.find(params[:message_id], :conditions => ["to_user_id = ? or from_user_id = ?", @user.id, @user.id ])
@message.message_read = 1 if @message.to_user_id == @user.id
@message.message_read = true if @message.to_user_id == @user.id
@message.save
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
render :action => 'no_such_user', :status => :not_found
end
# Display the list of messages that have been sent to the user.
def inbox
@title = 'inbox'
if @user and params[:display_name] == @user.display_name
@ -51,6 +61,7 @@ class MessageController < ApplicationController
end
end
# Display the list of messages that the user has sent to other users.
def outbox
@title = 'outbox'
if @user and params[:display_name] == @user.display_name
@ -59,15 +70,16 @@ class MessageController < ApplicationController
end
end
# Set the message as being read or unread.
def mark
if params[:message_id]
id = params[:message_id]
message = Message.find_by_id(id)
if params[:mark] == 'unread'
message_read = 0
message_read = false
mark_type = 'unread'
else
message_read = 1
message_read = true
mark_type = 'read'
end
message.message_read = message_read
@ -76,5 +88,7 @@ class MessageController < ApplicationController
redirect_to :controller => 'message', :action => 'inbox', :display_name => @user.display_name
end
end
rescue ActiveRecord::RecordNotFound
render :action => 'no_such_user', :status => :not_found
end
end

View file

@ -5,26 +5,28 @@ class NodeController < ApplicationController
session :off
before_filter :authorize, :only => [:create, :update, :delete]
before_filter :require_public_data, :only => [:create, :update, :delete]
before_filter :check_api_writable, :only => [:create, :update, :delete]
before_filter :check_api_readable, :except => [:create, :update, :delete]
after_filter :compress_output
# Create a node from XML.
def create
if request.put?
node = Node.from_xml(request.raw_post, true)
begin
if request.put?
node = Node.from_xml(request.raw_post, true)
if node
node.user_id = @user.id
node.visible = true
node.save_with_history!
render :text => node.id.to_s, :content_type => "text/plain"
if node
node.create_with_history @user
render :text => node.id.to_s, :content_type => "text/plain"
else
render :nothing => true, :status => :bad_request
end
else
render :nothing => true, :status => :bad_request
render :nothing => true, :status => :method_not_allowed
end
else
render :nothing => true, :status => :method_not_allowed
rescue OSM::APIError => ex
render ex.render_opts
end
end
@ -32,7 +34,7 @@ class NodeController < ApplicationController
def read
begin
node = Node.find(params[:id])
if node.visible
if node.visible?
response.headers['Last-Modified'] = node.timestamp.rfc822
render :text => node.to_xml.to_s, :content_type => "text/xml"
else
@ -42,7 +44,7 @@ class NodeController < ApplicationController
render :nothing => true, :status => :not_found
end
end
# Update a node from given XML
def update
begin
@ -50,49 +52,40 @@ class NodeController < ApplicationController
new_node = Node.from_xml(request.raw_post)
if new_node and new_node.id == node.id
node.user_id = @user.id
node.latitude = new_node.latitude
node.longitude = new_node.longitude
node.tags = new_node.tags
node.visible = true
node.save_with_history!
node.update_from(new_node, @user)
render :text => node.version.to_s, :content_type => "text/plain"
else
render :nothing => true, :status => :bad_request
end
rescue OSM::APIError => ex
render ex.render_opts
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
end
end
render :nothing => true
# Delete a node. Doesn't actually delete it, but retains its history
# in a wiki-like way. We therefore treat it like an update, so the delete
# method returns the new version number.
def delete
begin
node = Node.find(params[:id])
new_node = Node.from_xml(request.raw_post)
if new_node and new_node.id == node.id
node.delete_with_history!(new_node, @user)
render :text => node.version.to_s, :content_type => "text/plain"
else
render :nothing => true, :status => :bad_request
end
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
rescue OSM::APIError => ex
render ex.render_opts
end
end
# Delete a node. Doesn't actually delete it, but retains its history in a wiki-like way.
# FIXME remove all the fricking SQL
def delete
begin
node = Node.find(params[:id])
if node.visible
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 :text => "", :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 :text => "", :status => :precondition_failed
else
node.user_id = @user.id
node.visible = 0
node.save_with_history!
render :nothing => true
end
else
render :text => "", :status => :gone
end
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
end
end
# WTF does this do?
# Dump the details on many nodes whose ids are given in the "nodes" parameter.
def nodes
ids = params['nodes'].split(',').collect { |n| n.to_i }

View file

@ -22,4 +22,21 @@ class OldNodeController < ApplicationController
render :nothing => true, :status => :internal_server_error
end
end
def version
begin
old_node = OldNode.find(:first, :conditions => {:id => params[:id], :version => params[:version]} )
response.headers['Last-Modified'] = old_node.timestamp.rfc822
doc = OSM::API.new.get_xml_doc
doc.root << old_node.to_xml_node
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

View file

@ -2,6 +2,7 @@ class OldRelationController < ApplicationController
require 'xml/libxml'
session :off
before_filter :check_api_readable
after_filter :compress_output
def history
@ -20,4 +21,21 @@ class OldRelationController < ApplicationController
render :nothing => true, :status => :internal_server_error
end
end
def version
begin
old_relation = OldRelation.find(:first, :conditions => {:id => params[:id], :version => params[:version]} )
response.headers['Last-Modified'] = old_relation.timestamp.rfc822
doc = OSM::API.new.get_xml_doc
doc.root << old_relation.to_xml_node
render :text => doc.to_s, :content_type => "text/xml"
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
rescue
render :nothing => true, :status => :internetal_service_error
end
end
end

View file

@ -13,7 +13,7 @@ class OldWayController < ApplicationController
way.old_ways.each do |old_way|
doc.root << old_way.to_xml_node
end
end
render :text => doc.to_s, :content_type => "text/xml"
rescue ActiveRecord::RecordNotFound
@ -22,4 +22,21 @@ class OldWayController < ApplicationController
render :nothing => true, :status => :internal_server_error
end
end
def version
begin
old_way = OldWay.find(:first, :conditions => {:id => params[:id], :version => params[:version]} )
response.headers['Last-Modified'] = old_way.timestamp.rfc822
doc = OSM::API.new.get_xml_doc
doc.root << old_way.to_xml_node
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

View file

@ -3,28 +3,29 @@ class RelationController < ApplicationController
session :off
before_filter :authorize, :only => [:create, :update, :delete]
before_filter :require_public_data, :only => [:create, :update, :delete]
before_filter :check_api_writable, :only => [:create, :update, :delete]
before_filter :check_api_readable, :except => [:create, :update, :delete]
after_filter :compress_output
def create
if request.put?
relation = Relation.from_xml(request.raw_post, true)
begin
if request.put?
relation = Relation.from_xml(request.raw_post, true)
if relation
if !relation.preconditions_ok?
render :text => "", :status => :precondition_failed
else
relation.user_id = @user.id
relation.save_with_history!
render :text => relation.id.to_s, :content_type => "text/plain"
end
# We assume that an exception has been thrown if there was an error
# generating the relation
#if relation
relation.create_with_history @user
render :text => relation.id.to_s, :content_type => "text/plain"
#else
# render :text => "Couldn't get turn the input into a relation.", :status => :bad_request
#end
else
render :nothing => true, :status => :bad_request
render :nothing => true, :status => :method_not_allowed
end
else
render :nothing => true, :status => :method_not_allowed
rescue OSM::APIError => ex
render ex.render_opts
end
end
@ -45,56 +46,38 @@ class RelationController < ApplicationController
end
def update
logger.debug request.raw_post
begin
relation = Relation.find(params[:id])
new_relation = Relation.from_xml(request.raw_post)
if new_relation and new_relation.id == relation.id
if !new_relation.preconditions_ok?
render :text => "", :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
relation.update_from new_relation, @user
render :text => relation.version.to_s, :content_type => "text/plain"
else
render :nothing => true, :status => :bad_request
end
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
rescue
render :nothing => true, :status => :internal_server_error
rescue OSM::APIError => ex
render ex.render_opts
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 :text => "", :status => :precondition_failed
else
relation.user_id = @user.id
relation.tags = []
relation.members = []
relation.visible = false
relation.save_with_history!
render :nothing => true
end
new_relation = Relation.from_xml(request.raw_post)
if new_relation and new_relation.id == relation.id
relation.delete_with_history!(new_relation, @user)
render :text => relation.version.to_s, :content_type => "text/plain"
else
render :text => "", :status => :gone
render :nothing => true, :status => :bad_request
end
rescue OSM::APIError => ex
render ex.render_opts
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
rescue
render :nothing => true, :status => :internal_server_error
end
end
@ -115,12 +98,12 @@ class RelationController < ApplicationController
# 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}");
"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}");
"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");
"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.
@ -160,8 +143,7 @@ class RelationController < ApplicationController
render :text => doc.to_s, :content_type => "text/xml"
else
render :text => "", :status => :gone
render :nothing => true, :status => :gone
end
rescue ActiveRecord::RecordNotFound
@ -184,27 +166,29 @@ class RelationController < ApplicationController
render :text => doc.to_s, :content_type => "text/xml"
else
render :nothing => true, :status => :bad_request
render :text => "You need to supply a comma separated list of ids.", :status => :bad_request
end
rescue ActiveRecord::RecordNotFound
render :text => "Could not find one of the relations", :status => :not_found
end
def relations_for_way
relations_for_object("way")
relations_for_object("Way")
end
def relations_for_node
relations_for_object("node")
relations_for_object("Node")
end
def relations_for_relation
relations_for_object("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
relationids = RelationMember.find(:all, :conditions => ['member_type=? and member_id=?', objtype, params[:id]]).collect { |ws| ws.id[0] }.uniq
doc = OSM::API.new.get_xml_doc
Relation.find(relationids).each do |relation|
doc.root << relation.to_xml_node
doc.root << relation.to_xml_node if relation.visible
end
render :text => doc.to_s, :content_type => "text/xml"

View file

@ -1,8 +1,8 @@
class TraceController < ApplicationController
layout 'site'
before_filter :authorize_web
before_filter :require_user, :only => [:mine, :edit, :delete, :make_public]
before_filter :authorize_web
before_filter :require_user, :only => [:mine, :create, :edit, :delete, :make_public]
before_filter :authorize, :only => [:api_details, :api_data, :api_create]
before_filter :check_database_readable, :except => [:api_details, :api_data, :api_create]
before_filter :check_database_writable, :only => [:create, :edit, :delete, :make_public]
@ -15,7 +15,12 @@ class TraceController < ApplicationController
# from display name, pick up user id if one user's traces only
display_name = params[:display_name]
if target_user.nil? and !display_name.blank?
target_user = User.find(:first, :conditions => [ "visible = 1 and display_name = ?", display_name])
target_user = User.find(:first, :conditions => [ "visible = ? and display_name = ?", true, display_name])
if target_user.nil?
@not_found_user = display_name
render :action => 'no_such_user', :status => :not_found
return
end
end
# set title
@ -36,15 +41,15 @@ class TraceController < ApplicationController
# 4 - user's traces, not logged in as that user = all user's public traces
if target_user.nil? # all traces
if @user
conditions = ["(gpx_files.public = 1 OR gpx_files.user_id = ?)", @user.id] #1
conditions = ["(gpx_files.public = ? OR gpx_files.user_id = ?)", true, @user.id] #1
else
conditions = ["gpx_files.public = 1"] #2
conditions = ["gpx_files.public = ?", true] #2
end
else
if @user and @user == target_user
conditions = ["gpx_files.user_id = ?", @user.id] #3 (check vs user id, so no join + can't pick up non-public traces by changing name)
else
conditions = ["gpx_files.public = 1 AND gpx_files.user_id = ?", target_user.id] #4
conditions = ["gpx_files.public = ? AND gpx_files.user_id = ?", true, target_user.id] #4
end
end
@ -55,7 +60,8 @@ class TraceController < ApplicationController
conditions[0] += " AND gpx_files.id IN (#{files.join(',')})"
end
conditions[0] += " AND gpx_files.visible = 1"
conditions[0] += " AND gpx_files.visible = ?"
conditions << true
@trace_pages, @traces = paginate(:traces,
:include => [:user, :tags],
@ -100,26 +106,28 @@ class TraceController < ApplicationController
end
def create
logger.info(params[:trace][:gpx_file].class.name)
if params[:trace][:gpx_file].respond_to?(:read)
do_create(params[:trace][:gpx_file], params[:trace][:tagstring],
params[:trace][:description], params[:trace][:public])
if params[:trace]
logger.info(params[:trace][:gpx_file].class.name)
if params[:trace][:gpx_file].respond_to?(:read)
do_create(params[:trace][:gpx_file], params[:trace][:tagstring],
params[:trace][:description], params[:trace][:public])
if @trace.id
logger.info("id is #{@trace.id}")
flash[:notice] = "Your GPX file has been uploaded and is awaiting insertion in to the database. This will usually happen within half an hour, and an email will be sent to you on completion."
if @trace.id
logger.info("id is #{@trace.id}")
flash[:notice] = "Your GPX file has been uploaded and is awaiting insertion in to the database. This will usually happen within half an hour, and an email will be sent to you on completion."
redirect_to :action => 'mine'
redirect_to :action => 'mine'
end
else
@trace = Trace.new({:name => "Dummy",
:tagstring => params[:trace][:tagstring],
:description => params[:trace][:description],
:public => params[:trace][:public],
:inserted => false, :user => @user,
:timestamp => Time.now.getutc})
@trace.valid?
@trace.errors.add(:gpx_file, "can't be blank")
end
else
@trace = Trace.new({:name => "Dummy",
:tagstring => params[:trace][:tagstring],
:description => params[:trace][:description],
:public => params[:trace][:public],
:inserted => false, :user => @user,
:timestamp => Time.now})
@trace.valid?
@trace.errors.add(:gpx_file, "can't be blank")
end
end
@ -196,7 +204,7 @@ class TraceController < ApplicationController
end
def georss
conditions = ["gpx_files.public = 1"]
conditions = ["gpx_files.public = ?", true]
if params[:display_name]
conditions[0] += " AND users.display_name = ?"
@ -278,12 +286,20 @@ class TraceController < ApplicationController
def api_create
if request.post?
do_create(params[:file], params[:tags], params[:description], params[:public])
tags = params[:tags] || ""
description = params[:description] || ""
pub = params[:public] || false
if params[:file].respond_to?(:read)
do_create(params[:file], tags, description, pub)
if @trace.id
render :text => @trace.id.to_s, :content_type => "text/plain"
elsif @trace.valid?
render :nothing => true, :status => :internal_server_error
if @trace.id
render :text => @trace.id.to_s, :content_type => "text/plain"
elsif @trace.valid?
render :nothing => true, :status => :internal_server_error
else
render :nothing => true, :status => :bad_request
end
else
render :nothing => true, :status => :bad_request
end
@ -313,7 +329,7 @@ private
:public => public,
:inserted => true,
:user => @user,
:timestamp => Time.now
:timestamp => Time.now.getutc
})
# Save the trace object
@ -328,6 +344,17 @@ private
# Remove the file as we have failed to update the database
FileUtils.rm_f(filename)
end
# Finally save whether the user marked the trace as being public
if @trace.public?
if @user.trace_public_default.nil?
@user.preferences.create(:k => "gps.trace.public", :v => "default")
end
else
pref = @user.trace_public_default
pref.destroy unless pref.nil?
end
end
end

View file

@ -83,7 +83,7 @@ class UserController < ApplicationController
def lost_password
@title = 'lost password'
if params[:user] and params[:user][:email]
user = User.find_by_email(params[:user][:email], :conditions => "visible = 1")
user = User.find_by_email(params[:user][:email], :conditions => {:visible => true})
if user
token = user.tokens.create
@ -120,9 +120,21 @@ class UserController < ApplicationController
def new
@title = 'create account'
# The user is logged in already, so don't show them the signup page, instead
# send them to the home page
redirect_to :controller => 'site', :action => 'index' if session[:user]
end
def login
if session[:user]
# The user is logged in already, if the referer param exists, redirect them to that
if params[:referer]
redirect_to params[:referer]
else
redirect_to :controller => 'site', :action => 'index'
end
return
end
@title = 'login'
if params[:user]
email_or_display_name = params[:user][:email]
@ -223,7 +235,7 @@ class UserController < ApplicationController
end
def view
@this_user = User.find_by_display_name(params[:display_name], :conditions => "visible = 1")
@this_user = User.find_by_display_name(params[:display_name], :conditions => {:visible => true})
if @this_user
@title = @this_user.display_name
@ -236,7 +248,7 @@ class UserController < ApplicationController
def make_friend
if params[:display_name]
name = params[:display_name]
new_friend = User.find_by_display_name(name, :conditions => "visible = 1")
new_friend = User.find_by_display_name(name, :conditions => {:visible => true})
friend = Friend.new
friend.user_id = @user.id
friend.friend_user_id = new_friend.id
@ -258,7 +270,7 @@ class UserController < ApplicationController
def remove_friend
if params[:display_name]
name = params[:display_name]
friend = User.find_by_display_name(name, :conditions => "visible = 1")
friend = User.find_by_display_name(name, :conditions => {:visible => true})
if @user.is_friends_with?(friend)
Friend.delete_all "user_id = #{@user.id} AND friend_user_id = #{friend.id}"
flash[:notice] = "#{friend.display_name} was removed from your friends."

View file

@ -5,11 +5,9 @@ class UserPreferenceController < ApplicationController
def read_one
pref = UserPreference.find(@user.id, params[:preference_key])
if pref
render :text => pref.v.to_s
else
render :text => 'OH NOES! PREF NOT FOUND!', :status => 404
end
render :text => pref.v.to_s
rescue ActiveRecord::RecordNotFound => ex
render :text => 'OH NOES! PREF NOT FOUND!', :status => :not_found
end
def update_one
@ -32,6 +30,8 @@ class UserPreferenceController < ApplicationController
UserPreference.delete(@user.id, params[:preference_key])
render :nothing => true
rescue ActiveRecord::RecordNotFound => ex
render :text => "param: #{params[:preference_key]} not found", :status => :not_found
end
# print out all the preferences as a big xml block
@ -54,46 +54,44 @@ class UserPreferenceController < ApplicationController
def update
begin
p = XML::Parser.string(request.raw_post)
doc = p.parse
rescue LibXML::XML::Error, ArgumentError => ex
raise OSM::APIBadXMLError.new("preferences", xml, ex.message)
end
doc = p.parse
prefs = []
prefs = []
keyhash = {}
keyhash = {}
doc.find('//preferences/preference').each do |pt|
pref = UserPreference.new
doc.find('//preferences/preference').each do |pt|
pref = UserPreference.new
unless keyhash[pt['k']].nil? # already have that key
render :text => 'OH NOES! CAN HAS UNIQUE KEYS?', :status => :not_acceptable
return
end
keyhash[pt['k']] = 1
pref.k = pt['k']
pref.v = pt['v']
pref.user_id = @user.id
prefs << pref
unless keyhash[pt['k']].nil? # already have that key
render :text => 'OH NOES! CAN HAS UNIQUE KEYS?', :status => :not_acceptable
end
if prefs.size > 150
render :text => 'Too many preferences', :status => :request_entity_too_large
return
end
keyhash[pt['k']] = 1
# kill the existing ones
UserPreference.delete_all(['user_id = ?', @user.id])
# save the new ones
prefs.each do |pref|
pref.save!
end
rescue Exception => ex
render :text => 'OH NOES! FAIL!: ' + ex.to_s, :status => :internal_server_error
return
pref.k = pt['k']
pref.v = pt['v']
pref.user_id = @user.id
prefs << pref
end
if prefs.size > 150
render :text => 'Too many preferences', :status => :request_entity_too_large
end
# kill the existing ones
UserPreference.delete_all(['user_id = ?', @user.id])
# save the new ones
prefs.each do |pref|
pref.save!
end
render :nothing => true
rescue Exception => ex
render :text => 'OH NOES! FAIL!: ' + ex.to_s, :status => :internal_server_error
end
end

View file

@ -3,28 +3,28 @@ class WayController < ApplicationController
session :off
before_filter :authorize, :only => [:create, :update, :delete]
before_filter :require_public_data, :only => [:create, :update, :delete]
before_filter :check_api_writable, :only => [:create, :update, :delete]
before_filter :check_api_readable, :except => [:create, :update, :delete]
after_filter :compress_output
def create
if request.put?
way = Way.from_xml(request.raw_post, true)
if way
if !way.preconditions_ok?
render :text => "", :status => :precondition_failed
else
way.user_id = @user.id
way.save_with_history!
begin
if request.put?
way = Way.from_xml(request.raw_post, true)
if way
way.create_with_history @user
render :text => way.id.to_s, :content_type => "text/plain"
else
render :nothing => true, :status => :bad_request
end
else
render :nothing => true, :status => :bad_request
render :nothing => true, :status => :method_not_allowed
end
else
render :nothing => true, :status => :method_not_allowed
rescue OSM::APIError => ex
logger.warn request.raw_post
render ex.render_opts
end
end
@ -39,6 +39,8 @@ class WayController < ApplicationController
else
render :text => "", :status => :gone
end
rescue OSM::APIError => ex
render ex.render_opts
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
end
@ -50,20 +52,14 @@ class WayController < ApplicationController
new_way = Way.from_xml(request.raw_post)
if new_way and new_way.id == way.id
if !new_way.preconditions_ok?
render :text => "", :status => :precondition_failed
else
way.user_id = @user.id
way.tags = new_way.tags
way.nds = new_way.nds
way.visible = true
way.save_with_history!
render :nothing => true
end
way.update_from(new_way, @user)
render :text => way.version.to_s, :content_type => "text/plain"
else
render :nothing => true, :status => :bad_request
end
rescue OSM::APIError => ex
logger.warn request.raw_post
render ex.render_opts
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
end
@ -73,14 +69,16 @@ class WayController < ApplicationController
def delete
begin
way = Way.find(params[:id])
way.delete_with_relations_and_history(@user)
new_way = Way.from_xml(request.raw_post)
# if we get here, all is fine, otherwise something will catch below.
render :nothing => true
rescue OSM::APIAlreadyDeletedError
render :text => "", :status => :gone
rescue OSM::APIPreconditionFailedError
render :text => "", :status => :precondition_failed
if new_way and new_way.id == way.id
way.delete_with_history!(new_way, @user)
render :text => way.version.to_s, :content_type => "text/plain"
else
render :nothing => true, :status => :bad_request
end
rescue OSM::APIError => ex
render ex.render_opts
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => :not_found
end
@ -92,7 +90,7 @@ class WayController < ApplicationController
if way.visible
nd_ids = way.nds + [-1]
nodes = Node.find(:all, :conditions => "visible = 1 AND id IN (#{nd_ids.join(',')})")
nodes = Node.find(:all, :conditions => ["visible = ? AND id IN (#{nd_ids.join(',')})", true])
# Render
doc = OSM::API.new.get_xml_doc
@ -130,13 +128,19 @@ class WayController < ApplicationController
end
end
##
# returns all the ways which are currently using the node given in the
# :id parameter. note that this used to return deleted ways as well, but
# this seemed not to be the expected behaviour, so it was removed.
def ways_for_node
wayids = WayNode.find(:all, :conditions => ['node_id = ?', params[:id]]).collect { |ws| ws.id[0] }.uniq
wayids = WayNode.find(:all,
:conditions => ['node_id = ?', params[:id]]
).collect { |ws| ws.id[0] }.uniq
doc = OSM::API.new.get_xml_doc
Way.find(wayids).each do |way|
doc.root << way.to_xml_node
doc.root << way.to_xml_node if way.visible
end
render :text => doc.to_s, :content_type => "text/xml"

View file

@ -1,2 +1,5 @@
module BrowseHelper
def link_to_page(page, page_param)
return link_to(page, page_param => page)
end
end

View file

@ -1,13 +1,23 @@
class Acl < ActiveRecord::Base
def self.find_by_address(address, options)
self.with_scope(:find => {:conditions => ["inet_aton(?) & netmask = address", address]}) do
self.with_scope(:find => {:conditions => ["#{inet_aton} & netmask = address", address]}) do
return self.find(:first, options)
end
end
def self.find_all_by_address(address, options)
self.with_scope(:find => {:conditions => ["inet_aton(?) & netmask = address", address]}) do
self.with_scope(:find => {:conditions => ["#{inet_aton} & netmask = address", address]}) do
return self.find(:all, options)
end
end
private
def self.inet_aton
if self.connection.adapter_name == "MySQL"
"inet_aton(?)"
else
"?"
end
end
end

241
app/models/changeset.rb Normal file
View file

@ -0,0 +1,241 @@
class Changeset < ActiveRecord::Base
require 'xml/libxml'
belongs_to :user
has_many :changeset_tags, :foreign_key => 'id'
has_many :nodes
has_many :ways
has_many :relations
has_many :old_nodes
has_many :old_ways
has_many :old_relations
validates_presence_of :id, :on => :update
validates_presence_of :user_id, :created_at, :closed_at, :num_changes
validates_uniqueness_of :id
validates_numericality_of :id, :on => :update, :integer_only => true
validates_numericality_of :min_lat, :max_lat, :min_lon, :max_lat, :allow_nil => true, :integer_only => true
validates_numericality_of :user_id, :integer_only => true
validates_numericality_of :num_changes, :integer_only => true, :greater_than_or_equal_to => 0
validates_associated :user
# over-expansion factor to use when updating the bounding box
EXPAND = 0.1
# maximum number of elements allowed in a changeset
MAX_ELEMENTS = 50000
# maximum time a changeset is allowed to be open for.
MAX_TIME_OPEN = 1.day
# idle timeout increment, one hour seems reasonable.
IDLE_TIMEOUT = 1.hour
# Use a method like this, so that we can easily change how we
# determine whether a changeset is open, without breaking code in at
# least 6 controllers
def is_open?
# a changeset is open (that is, it will accept further changes) when
# it has not yet run out of time and its capacity is small enough.
# note that this may not be a hard limit - due to timing changes and
# concurrency it is possible that some changesets may be slightly
# longer than strictly allowed or have slightly more changes in them.
return ((closed_at > Time.now.getutc) and (num_changes <= MAX_ELEMENTS))
end
def set_closed_time_now
if is_open?
self.closed_at = Time.now.getutc
end
end
def self.from_xml(xml, create=false)
begin
p = XML::Parser.string(xml)
doc = p.parse
cs = Changeset.new
doc.find('//osm/changeset').each do |pt|
if create
cs.created_at = Time.now.getutc
# initial close time is 1h ahead, but will be increased on each
# modification.
cs.closed_at = cs.created_at + IDLE_TIMEOUT
# initially we have no changes in a changeset
cs.num_changes = 0
end
pt.find('tag').each do |tag|
cs.add_tag_keyval(tag['k'], tag['v'])
end
end
rescue Exception => ex
cs = nil
end
return cs
end
##
# returns the bounding box of the changeset. it is possible that some
# or all of the values will be nil, indicating that they are undefined.
def bbox
@bbox ||= [ min_lon, min_lat, max_lon, max_lat ]
end
def has_valid_bbox?
not bbox.include? nil
end
##
# expand the bounding box to include the given bounding box. also,
# expand a little bit more in the direction of the expansion, so that
# further expansions may be unnecessary. this is an optimisation
# suggested on the wiki page by kleptog.
def update_bbox!(array)
# ensure that bbox is cached and has no nils in it. if there are any
# nils, just use the bounding box update to write over them.
@bbox = bbox.zip(array).collect { |a, b| a.nil? ? b : a }
# FIXME - this looks nasty and violates DRY... is there any prettier
# way to do this?
@bbox[0] = array[0] + EXPAND * (@bbox[0] - @bbox[2]) if array[0] < @bbox[0]
@bbox[1] = array[1] + EXPAND * (@bbox[1] - @bbox[3]) if array[1] < @bbox[1]
@bbox[2] = array[2] + EXPAND * (@bbox[2] - @bbox[0]) if array[2] > @bbox[2]
@bbox[3] = array[3] + EXPAND * (@bbox[3] - @bbox[1]) if array[3] > @bbox[3]
# update active record. rails 2.1's dirty handling should take care of
# whether this object needs saving or not.
self.min_lon, self.min_lat, self.max_lon, self.max_lat = @bbox
end
##
# the number of elements is also passed in so that we can ensure that
# a single changeset doesn't contain too many elements. this, of course,
# destroys the optimisation described in the bbox method above.
def add_changes!(elements)
self.num_changes += elements
end
def tags_as_hash
return tags
end
def tags
unless @tags
@tags = {}
self.changeset_tags.each do |tag|
@tags[tag.k] = tag.v
end
end
@tags
end
def tags=(t)
@tags = t
end
def add_tag_keyval(k, v)
@tags = Hash.new unless @tags
@tags[k] = v
end
def save_with_tags!
t = Time.now.getutc
# do the changeset update and the changeset tags update in the
# same transaction to ensure consistency.
Changeset.transaction do
# set the auto-close time to be one hour in the future unless
# that would make it more than 24h long, in which case clip to
# 24h, as this has been decided is a reasonable time limit.
if (closed_at - created_at) > (MAX_TIME_OPEN - IDLE_TIMEOUT)
self.closed_at = created_at + MAX_TIME_OPEN
else
self.closed_at = Time.now.getutc + IDLE_TIMEOUT
end
self.save!
tags = self.tags
ChangesetTag.delete_all(['id = ?', self.id])
tags.each do |k,v|
tag = ChangesetTag.new
tag.k = k
tag.v = v
tag.id = self.id
tag.save!
end
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 'changeset'
el1['id'] = self.id.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?
el1['uid'] = self.user_id.to_s if self.user.data_public?
self.tags.each do |k,v|
el2 = XML::Node.new('tag')
el2['k'] = k.to_s
el2['v'] = v.to_s
el1 << el2
end
el1['created_at'] = self.created_at.xmlschema
el1['closed_at'] = self.closed_at.xmlschema unless is_open?
el1['open'] = is_open?.to_s
el1['min_lon'] = (bbox[0].to_f / GeoRecord::SCALE).to_s unless bbox[0].nil?
el1['min_lat'] = (bbox[1].to_f / GeoRecord::SCALE).to_s unless bbox[1].nil?
el1['max_lon'] = (bbox[2].to_f / GeoRecord::SCALE).to_s unless bbox[2].nil?
el1['max_lat'] = (bbox[3].to_f / GeoRecord::SCALE).to_s unless bbox[3].nil?
# NOTE: changesets don't include the XML of the changes within them,
# they are just structures for tagging. to get the osmChange of a
# changeset, see the download method of the controller.
return el1
end
##
# update this instance from another instance given and the user who is
# doing the updating. note that this method is not for updating the
# bounding box, only the tags of the changeset.
def update_from(other, user)
# ensure that only the user who opened the changeset may modify it.
unless user.id == self.user_id
raise OSM::APIUserChangesetMismatchError.new
end
# can't change a closed changeset
unless is_open?
raise OSM::APIChangesetAlreadyClosedError.new(self)
end
# copy the other's tags
self.tags = other.tags
save_with_tags!
end
end

View file

@ -0,0 +1,8 @@
class ChangesetTag < ActiveRecord::Base
belongs_to :changeset, :foreign_key => 'id'
validates_presence_of :id
validates_length_of :k, :v, :maximum => 255, :allow_blank => true
validates_uniqueness_of :id, :scope => :k
validates_numericality_of :id, :only_integer => true
end

View file

@ -1,11 +1,15 @@
class DiaryEntry < ActiveRecord::Base
belongs_to :user
has_many :diary_comments, :include => :user,
:conditions => "users.visible = 1",
:conditions => ["users.visible = ?", true],
:order => "diary_comments.id"
validates_presence_of :title, :body
validates_numericality_of :latitude, :allow_nil => true
validates_numericality_of :longitude, :allow_nil => true
validates_length_of :title, :within => 1..255
validates_length_of :language, :within => 2..3, :allow_nil => true
validates_numericality_of :latitude, :allow_nil => true,
:greater_than_or_equal_to => -90, :less_than_or_equal_to => 90
validates_numericality_of :longitude, :allow_nil => true,
:greater_than_or_equal_to => -180, :less_than_or_equal_to => 180
validates_associated :user
end

View file

@ -1,8 +1,12 @@
require 'validators'
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => "User", :foreign_key => :from_user_id
belongs_to :recipient, :class_name => "User", :foreign_key => :to_user_id
validates_presence_of :title, :body, :sent_on
validates_presence_of :title, :body, :sent_on, :sender, :recipient
validates_length_of :title, :within => 1..255
validates_inclusion_of :message_read, :in => [ true, false ]
validates_associated :sender, :recipient
validates_as_utf8 :title
end

View file

@ -2,27 +2,34 @@ class Node < ActiveRecord::Base
require 'xml/libxml'
include GeoRecord
include ConsistencyValidations
set_table_name 'current_nodes'
validates_presence_of :user_id, :timestamp
validates_inclusion_of :visible, :in => [ true, false ]
validates_numericality_of :latitude, :longitude
validate :validate_position
belongs_to :user
belongs_to :changeset
has_many :old_nodes, :foreign_key => :id
has_many :way_nodes
has_many :ways, :through => :way_nodes
has_many :node_tags, :foreign_key => :id
has_many :old_way_nodes
has_many :ways_via_history, :class_name=> "Way", :through => :old_way_nodes, :source => :way
has_many :containing_relation_members, :class_name => "RelationMember", :as => :member
has_many :containing_relations, :class_name => "Relation", :through => :containing_relation_members, :source => :relation, :extend => ObjectFinder
validates_presence_of :id, :on => :update
validates_presence_of :timestamp,:version, :changeset_id
validates_uniqueness_of :id
validates_inclusion_of :visible, :in => [ true, false ]
validates_numericality_of :latitude, :longitude, :changeset_id, :version, :integer_only => true
validates_numericality_of :id, :on => :update, :integer_only => true
validate :validate_position
validates_associated :changeset
# Sanity check the latitude and longitude and add an error if it's broken
def validate_position
errors.add_to_base("Node is not in the world") unless in_world?
@ -50,7 +57,7 @@ class Node < ActiveRecord::Base
#conditions = keys.join(' AND ')
find_by_area(min_lat, min_lon, max_lat, max_lon,
:conditions => 'visible = 1',
:conditions => {:visible => true},
:limit => APP_CONFIG['max_number_of_nodes']+1)
end
@ -59,83 +66,150 @@ class Node < ActiveRecord::Base
begin
p = XML::Parser.string(xml)
doc = p.parse
node = Node.new
doc.find('//osm/node').each do |pt|
node.lat = pt['lat'].to_f
node.lon = pt['lon'].to_f
return nil unless node.in_world?
unless create
if pt['id'] != '0'
node.id = pt['id'].to_i
end
end
node.visible = pt['visible'] and pt['visible'] == 'true'
if create
node.timestamp = Time.now
else
if pt['timestamp']
node.timestamp = Time.parse(pt['timestamp'])
end
end
tags = []
pt.find('tag').each do |tag|
tags << [tag['k'],tag['v']]
end
node.tags = Tags.join(tags)
return Node.from_xml_node(pt, create)
end
rescue
node = nil
rescue LibXML::XML::Error, ArgumentError => ex
raise OSM::APIBadXMLError.new("node", xml, ex.message)
end
end
def self.from_xml_node(pt, create=false)
node = Node.new
raise OSM::APIBadXMLError.new("node", pt, "lat missing") if pt['lat'].nil?
raise OSM::APIBadXMLError.new("node", pt, "lon missing") if pt['lon'].nil?
node.lat = pt['lat'].to_f
node.lon = pt['lon'].to_f
raise OSM::APIBadXMLError.new("node", pt, "changeset id missing") if pt['changeset'].nil?
node.changeset_id = pt['changeset'].to_i
raise OSM::APIBadUserInput.new("The node is outside this world") unless node.in_world?
# version must be present unless creating
raise OSM::APIBadXMLError.new("node", pt, "Version is required when updating") unless create or not pt['version'].nil?
node.version = create ? 0 : pt['version'].to_i
unless create
if pt['id'] != '0'
node.id = pt['id'].to_i
end
end
# visible if it says it is, or as the default if the attribute
# is missing.
# Don't need to set the visibility, when it is set explicitly in the create/update/delete
#node.visible = pt['visible'].nil? or pt['visible'] == 'true'
# We don't care about the time, as it is explicitly set on create/update/delete
tags = []
pt.find('tag').each do |tag|
node.add_tag_key_val(tag['k'],tag['v'])
end
return node
end
# Save this node with the appropriate OldNode object to represent it's history.
def save_with_history!
##
# the bounding box around a node, which is used for determining the changeset's
# bounding box
def bbox
[ longitude, latitude, longitude, latitude ]
end
# Should probably be renamed delete_from to come in line with update
def delete_with_history!(new_node, user)
unless self.visible
raise OSM::APIAlreadyDeletedError.new
end
# need to start the transaction here, so that the database can
# provide repeatable reads for the used-by checks. this means it
# shouldn't be possible to get race conditions.
Node.transaction do
self.timestamp = Time.now
self.save!
old_node = OldNode.from_node(self)
old_node.save!
check_consistency(self, new_node, user)
if WayNode.find(:first, :joins => "INNER JOIN current_ways ON current_ways.id = current_way_nodes.id", :conditions => [ "current_ways.visible = ? AND current_way_nodes.node_id = ?", true, self.id ])
raise OSM::APIPreconditionFailedError.new
elsif RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = ? AND member_type='Node' and member_id=? ", true, self.id])
raise OSM::APIPreconditionFailedError.new
else
self.changeset_id = new_node.changeset_id
self.visible = false
# update the changeset with the deleted position
changeset.update_bbox!(bbox)
save_with_history!
end
end
end
# Turn this Node in to a complete OSM XML object with <osm> wrapper
def update_from(new_node, user)
check_consistency(self, new_node, user)
# update changeset first
self.changeset_id = new_node.changeset_id
self.changeset = new_node.changeset
# update changeset bbox with *old* position first
changeset.update_bbox!(bbox);
# FIXME logic needs to be double checked
self.latitude = new_node.latitude
self.longitude = new_node.longitude
self.tags = new_node.tags
self.visible = true
# update changeset bbox with *new* position
changeset.update_bbox!(bbox);
save_with_history!
end
def create_with_history(user)
check_create_consistency(self, user)
self.version = 0
self.visible = true
# update the changeset to include the new location
changeset.update_bbox!(bbox)
save_with_history!
end
def to_xml
doc = OSM::API.new.get_xml_doc
doc.root << to_xml_node()
return doc
end
# Turn this Node in to an XML Node without the <osm> wrapper.
def to_xml_node(user_display_name_cache = nil)
el1 = XML::Node.new 'node'
el1['id'] = self.id.to_s
el1['lat'] = self.lat.to_s
el1['lon'] = self.lon.to_s
el1['version'] = self.version.to_s
el1['changeset'] = self.changeset_id.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)
if user_display_name_cache and user_display_name_cache.key?(self.changeset.user_id)
# use the cache if available
elsif self.user.data_public?
user_display_name_cache[self.user_id] = self.user.display_name
elsif self.changeset.user.data_public?
user_display_name_cache[self.changeset.user_id] = self.changeset.user.display_name
else
user_display_name_cache[self.user_id] = nil
user_display_name_cache[self.changeset.user_id] = nil
end
el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil?
if not user_display_name_cache[self.changeset.user_id].nil?
el1['user'] = user_display_name_cache[self.changeset.user_id]
el1['uid'] = self.changeset.user_id.to_s
end
Tags.split(self.tags) do |k,v|
self.tags.each do |k,v|
el2 = XML::Node.new('tag')
el2['k'] = k.to_s
el2['v'] = v.to_s
@ -147,12 +221,79 @@ class Node < ActiveRecord::Base
return el1
end
# Return the node's tags as a Hash of keys and their values
def tags_as_hash
hash = {}
Tags.split(self.tags) do |k,v|
hash[k] = v
end
hash
return tags
end
def tags
unless @tags
@tags = {}
self.node_tags.each do |tag|
@tags[tag.k] = tag.v
end
end
@tags
end
def tags=(t)
@tags = t
end
def add_tag_key_val(k,v)
@tags = Hash.new unless @tags
# duplicate tags are now forbidden, so we can't allow values
# in the hash to be overwritten.
raise OSM::APIDuplicateTagsError.new("node", self.id, k) if @tags.include? k
@tags[k] = v
end
##
# are the preconditions OK? this is mainly here to keep the duck
# typing interface the same between nodes, ways and relations.
def preconditions_ok?
in_world?
end
##
# dummy method to make the interfaces of node, way and relation
# more consistent.
def fix_placeholders!(id_map)
# nodes don't refer to anything, so there is nothing to do here
end
private
def save_with_history!
t = Time.now.getutc
Node.transaction do
self.version += 1
self.timestamp = t
self.save!
# Create a NodeTag
tags = self.tags
NodeTag.delete_all(['id = ?', self.id])
tags.each do |k,v|
tag = NodeTag.new
tag.k = k
tag.v = v
tag.id = self.id
tag.save!
end
# Create an OldNode
old_node = OldNode.from_node(self)
old_node.timestamp = t
old_node.save_with_dependencies!
# tell the changeset we updated one element only
changeset.add_changes! 1
# save the changeset in case of bounding box updates
changeset.save!
end
end
end

View file

@ -1,25 +1,21 @@
class OldNode < ActiveRecord::Base
include GeoRecord
include ConsistencyValidations
set_table_name 'nodes'
validates_presence_of :user_id, :timestamp
validates_presence_of :changeset_id, :timestamp
validates_inclusion_of :visible, :in => [ true, false ]
validates_numericality_of :latitude, :longitude
validate :validate_position
validates_associated :changeset
belongs_to :user
belongs_to :changeset
def validate_position
errors.add_to_base("Node is not in the world") unless in_world?
end
def in_world?
return false if self.lat < -90 or self.lat > 90
return false if self.lon < -180 or self.lon > 180
return true
end
def self.from_node(node)
old_node = OldNode.new
old_node.latitude = node.latitude
@ -27,19 +23,30 @@ class OldNode < ActiveRecord::Base
old_node.visible = node.visible
old_node.tags = node.tags
old_node.timestamp = node.timestamp
old_node.user_id = node.user_id
old_node.changeset_id = node.changeset_id
old_node.id = node.id
old_node.version = node.version
return old_node
end
def to_xml
doc = OSM::API.new.get_xml_doc
doc.root << to_xml_node()
return doc
end
def to_xml_node
el1 = XML::Node.new 'node'
el1['id'] = self.id.to_s
el1['lat'] = self.lat.to_s
el1['lon'] = self.lon.to_s
el1['user'] = self.user.display_name if self.user.data_public?
el1['changeset'] = self.changeset.id.to_s
if self.changeset.user.data_public?
el1['user'] = self.changeset.user.display_name
el1['uid'] = self.changeset.user.id.to_s
end
Tags.split(self.tags) do |k,v|
self.tags.each do |k,v|
el2 = XML::Node.new('tag')
el2['k'] = k.to_s
el2['v'] = v.to_s
@ -48,24 +55,54 @@ class OldNode < ActiveRecord::Base
el1['visible'] = self.visible.to_s
el1['timestamp'] = self.timestamp.xmlschema
el1['version'] = self.version.to_s
return el1
end
def tags_as_hash
hash = {}
Tags.split(self.tags) do |k,v|
hash[k] = v
def save_with_dependencies!
save!
#not sure whats going on here
clear_aggregation_cache
clear_association_cache
#ok from here
@attributes.update(OldNode.find(:first, :conditions => ['id = ? AND timestamp = ? AND version = ?', self.id, self.timestamp, self.version]).instance_variable_get('@attributes'))
self.tags.each do |k,v|
tag = OldNodeTag.new
tag.k = k
tag.v = v
tag.id = self.id
tag.version = self.version
tag.save!
end
hash
end
# Pretend we're not in any ways
def ways
return []
def tags
unless @tags
@tags = Hash.new
OldNodeTag.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
# Pretend we're not in any relations
def containing_relation_members
return []
def tags=(t)
@tags = t
end
def tags_as_hash
return self.tags
end
# Pretend we're not in any ways
def ways
return []
end
# Pretend we're not in any relations
def containing_relation_members
return []
end
end

View file

@ -1,14 +1,19 @@
class OldRelation < ActiveRecord::Base
include ConsistencyValidations
set_table_name 'relations'
belongs_to :user
belongs_to :changeset
validates_associated :changeset
def self.from_relation(relation)
old_relation = OldRelation.new
old_relation.visible = relation.visible
old_relation.user_id = relation.user_id
old_relation.changeset_id = relation.changeset_id
old_relation.timestamp = relation.timestamp
old_relation.id = relation.id
old_relation.version = relation.version
old_relation.members = relation.members
old_relation.tags = relation.tags
return old_relation
@ -33,14 +38,12 @@ class OldRelation < ActiveRecord::Base
tag.save!
end
i = 1
self.members.each do |m|
self.members.each_with_index do |m,i|
member = OldRelationMember.new
member.id = self.id
member.member_type = m[0]
member.id = [self.id, self.version, i]
member.member_type = m[0].classify
member.member_id = m[1]
member.member_role = m[2]
member.version = self.version
member.save!
end
end
@ -48,7 +51,7 @@ class OldRelation < ActiveRecord::Base
def members
unless @members
@members = Array.new
OldRelationMember.find(:all, :conditions => ["id = ? AND version = ?", self.id, self.version]).each do |m|
OldRelationMember.find(:all, :conditions => ["id = ? AND version = ?", self.id, self.version], :order => "sequence_id").each do |m|
@members += [[m.type,m.id,m.role]]
end
end
@ -85,16 +88,27 @@ class OldRelation < ActiveRecord::Base
OldRelationTag.find(:all, :conditions => ['id = ? AND version = ?', self.id, self.version])
end
def to_xml
doc = OSM::API.new.get_xml_doc
doc.root << to_xml_node()
return doc
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?
if self.changeset.user.data_public?
el1['user'] = self.changeset.user.display_name
el1['uid'] = self.changeset.user.id.to_s
end
el1['version'] = self.version.to_s
el1['changeset'] = self.changeset_id.to_s
self.old_members.each do |member|
e = XML::Node.new 'member'
e['type'] = member.member_type.to_s
e['type'] = member.member_type.to_s.downcase
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

View file

@ -1,3 +1,6 @@
class OldRelationMember < ActiveRecord::Base
set_table_name 'relation_members'
set_primary_keys :id, :version, :sequence_id
belongs_to :relation, :foreign_key=> :id
end

View file

@ -1,3 +1,10 @@
class OldRelationTag < ActiveRecord::Base
set_table_name 'relation_tags'
belongs_to :old_relation, :foreign_key => [:id, :version]
validates_presence_of :id, :version
validates_length_of :k, :v, :maximum => 255, :allow_blank => true
validates_uniqueness_of :id, :scope => [:k, :version]
validates_numericality_of :id, :version, :only_integer => true
end

View file

@ -1,14 +1,19 @@
class OldWay < ActiveRecord::Base
include ConsistencyValidations
set_table_name 'ways'
belongs_to :user
belongs_to :changeset
validates_associated :changeset
def self.from_way(way)
old_way = OldWay.new
old_way.visible = way.visible
old_way.user_id = way.user_id
old_way.changeset_id = way.changeset_id
old_way.timestamp = way.timestamp
old_way.id = way.id
old_way.version = way.version
old_way.nds = way.nds
old_way.tags = way.tags
return old_way
@ -93,7 +98,12 @@ class OldWay < ActiveRecord::Base
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?
if self.changeset.user.data_public?
el1['user'] = self.changeset.user.display_name
el1['uid'] = self.changeset.user.id.to_s
end
el1['version'] = self.version.to_s
el1['changeset'] = self.changeset.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'
@ -114,27 +124,29 @@ class OldWay < ActiveRecord::Base
# For get_nodes_undelete, uses same nodes, even if they've moved since
# For get_nodes_revert, allocates new ids
# Currently returns Potlatch-style array
# where [5] indicates whether latest version is usable as is (boolean)
# (i.e. is it visible? are we actually reverting to an earlier version?)
def get_nodes_undelete
points = []
self.nds.each do |n|
node=Node.find(n)
points << [node.lon, node.lat, n, node.visible ? 1 : 0, node.tags_as_hash]
points << [node.lon, node.lat, n, node.version, node.tags_as_hash, node.visible]
end
points
end
def get_nodes_revert
def get_nodes_revert(timestamp)
points=[]
self.nds.each do |n|
oldnode=OldNode.find(:first, :conditions=>['id=? AND timestamp<=?',n,self.timestamp], :order=>"timestamp DESC")
oldnode=OldNode.find(:first, :conditions=>['id=? AND timestamp<=?',n,timestamp], :order=>"timestamp DESC")
curnode=Node.find(n)
id=n; v=curnode.visible ? 1 : 0
id=n; reuse=curnode.visible
if oldnode.lat!=curnode.lat or oldnode.lon!=curnode.lon or oldnode.tags!=curnode.tags then
# node has changed: if it's in other ways, give it a new id
if curnode.ways-[self.id] then id=-1; v=nil end
if curnode.ways-[self.id] then id=-1; reuse=false end
end
points << [oldnode.lon, oldnode.lat, id, v, oldnode.tags_as_hash]
points << [oldnode.lon, oldnode.lat, id, curnode.version, oldnode.tags_as_hash, reuse]
end
points
end

View file

@ -1,6 +1,10 @@
class OldWayTag < ActiveRecord::Base
belongs_to :user
set_table_name 'way_tags'
belongs_to :old_way, :foreign_key => [:id, :version]
validates_presence_of :id
validates_length_of :k, :v, :maximum => 255, :allow_blank => true
validates_uniqueness_of :id, :scope => [:k, :version]
validates_numericality_of :id, :version, :only_integer => true
end

View file

@ -1,50 +1,82 @@
class Relation < ActiveRecord::Base
require 'xml/libxml'
include ConsistencyValidations
set_table_name 'current_relations'
belongs_to :user
belongs_to :changeset
has_many :old_relations, :foreign_key => 'id', :order => 'version'
has_many :relation_members, :foreign_key => 'id'
has_many :relation_members, :foreign_key => 'id', :order => 'sequence_id'
has_many :relation_tags, :foreign_key => 'id'
has_many :containing_relation_members, :class_name => "RelationMember", :as => :member
has_many :containing_relations, :class_name => "Relation", :through => :containing_relation_members, :source => :relation, :extend => ObjectFinder
validates_presence_of :id, :on => :update
validates_presence_of :timestamp,:version, :changeset_id
validates_uniqueness_of :id
validates_inclusion_of :visible, :in => [ true, false ]
validates_numericality_of :id, :on => :update, :integer_only => true
validates_numericality_of :changeset_id, :version, :integer_only => true
validates_associated :changeset
TYPES = ["node", "way", "relation"]
def self.from_xml(xml, create=false)
begin
p = XML::Parser.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
return Relation.from_xml_node(pt, create)
end
rescue
relation = nil
rescue LibXML::XML::Error, ArgumentError => ex
raise OSM::APIBadXMLError.new("relation", xml, ex.message)
end
end
def self.from_xml_node(pt, create=false)
relation = Relation.new
if !create and pt['id'] != '0'
relation.id = pt['id'].to_i
end
raise OSM::APIBadXMLError.new("relation", pt, "You are missing the required changeset in the relation") if pt['changeset'].nil?
relation.changeset_id = pt['changeset']
# The follow block does not need to be executed because they are dealt with
# in create_with_history, update_from and delete_with_history
if create
relation.timestamp = Time.now.getutc
relation.visible = true
relation.version = 0
else
if pt['timestamp']
relation.timestamp = Time.parse(pt['timestamp'])
end
relation.version = pt['version']
end
pt.find('tag').each do |tag|
relation.add_tag_keyval(tag['k'], tag['v'])
end
pt.find('member').each do |member|
#member_type =
logger.debug "each member"
raise OSM::APIBadXMLError.new("relation", pt, "The #{member['type']} is not allowed only, #{TYPES.inspect} allowed") unless TYPES.include? member['type']
logger.debug "after raise"
#member_ref = member['ref']
#member_role
member['role'] ||= "" # Allow the upload to not include this, in which case we default to an empty string.
logger.debug member['role']
relation.add_member(member['type'].classify, member['ref'], member['role'])
end
raise OSM::APIBadUserInput.new("Some bad xml in relation") if relation.nil?
return relation
end
@ -60,18 +92,23 @@ class Relation < ActiveRecord::Base
el1['id'] = self.id.to_s
el1['visible'] = self.visible.to_s
el1['timestamp'] = self.timestamp.xmlschema
el1['version'] = self.version.to_s
el1['changeset'] = self.changeset_id.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)
if user_display_name_cache and user_display_name_cache.key?(self.changeset.user_id)
# use the cache if available
elsif self.user.data_public?
user_display_name_cache[self.user_id] = self.user.display_name
elsif self.changeset.user.data_public?
user_display_name_cache[self.changeset.user_id] = self.changeset.user.display_name
else
user_display_name_cache[self.user_id] = nil
user_display_name_cache[self.changeset.user_id] = nil
end
el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil?
if not user_display_name_cache[self.changeset.user_id].nil?
el1['user'] = user_display_name_cache[self.changeset.user_id]
el1['uid'] = self.changeset.user_id.to_s
end
self.relation_members.each do |member|
p=0
@ -88,7 +125,7 @@ class Relation < ActiveRecord::Base
#end
if p
e = XML::Node.new 'member'
e['type'] = member.member_type
e['type'] = member.member_type.downcase
e['ref'] = member.member_id.to_s
e['role'] = member.member_role
el1 << e
@ -108,7 +145,7 @@ class Relation < ActiveRecord::Base
if ids.empty?
return []
else
self.with_scope(:find => { :joins => "INNER JOIN current_relation_members ON current_relation_members.id = current_relations.id", :conditions => "current_relation_members.member_type = 'node' AND current_relation_members.member_id IN (#{ids.join(',')})" }) do
self.with_scope(:find => { :joins => "INNER JOIN current_relation_members ON current_relation_members.id = current_relations.id", :conditions => "current_relation_members.member_type = 'Node' AND current_relation_members.member_id IN (#{ids.join(',')})" }) do
return self.find(:all, options)
end
end
@ -118,7 +155,7 @@ class Relation < ActiveRecord::Base
if ids.empty?
return []
else
self.with_scope(:find => { :joins => "INNER JOIN current_relation_members ON current_relation_members.id = current_relations.id", :conditions => "current_relation_members.member_type = 'way' AND current_relation_members.member_id IN (#{ids.join(',')})" }) do
self.with_scope(:find => { :joins => "INNER JOIN current_relation_members ON current_relation_members.id = current_relations.id", :conditions => "current_relation_members.member_type = 'Way' AND current_relation_members.member_id IN (#{ids.join(',')})" }) do
return self.find(:all, options)
end
end
@ -128,7 +165,7 @@ class Relation < ActiveRecord::Base
if ids.empty?
return []
else
self.with_scope(:find => { :joins => "INNER JOIN current_relation_members ON current_relation_members.id = current_relations.id", :conditions => "current_relation_members.member_type = 'relation' AND current_relation_members.member_id IN (#{ids.join(',')})" }) do
self.with_scope(:find => { :joins => "INNER JOIN current_relation_members ON current_relation_members.id = current_relations.id", :conditions => "current_relation_members.member_type = 'Relation' AND current_relation_members.member_id IN (#{ids.join(',')})" }) do
return self.find(:all, options)
end
end
@ -170,94 +207,97 @@ class Relation < ActiveRecord::Base
def add_tag_keyval(k, v)
@tags = Hash.new unless @tags
# duplicate tags are now forbidden, so we can't allow values
# in the hash to be overwritten.
raise OSM::APIDuplicateTagsError.new("relation", self.id, k) if @tags.include? k
@tags[k] = v
end
def save_with_history!
Relation.transaction do
t = Time.now
self.timestamp = t
self.save!
##
# updates the changeset bounding box to contain the bounding box of
# the element with given +type+ and +id+. this only works with nodes
# and ways at the moment, as they're the only elements to respond to
# the :bbox call.
def update_changeset_element(type, id)
element = Kernel.const_get(type.capitalize).find(id)
changeset.update_bbox! element.bbox
end
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!
def delete_with_history!(new_relation, user)
unless self.visible
raise OSM::APIAlreadyDeletedError.new
end
# need to start the transaction here, so that the database can
# provide repeatable reads for the used-by checks. this means it
# shouldn't be possible to get race conditions.
Relation.transaction do
check_consistency(self, new_relation, user)
# This will check to see if this relation is used by another relation
if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = ? AND member_type='Relation' and member_id=? ", true, self.id ])
raise OSM::APIPreconditionFailedError.new("The relation #{new_relation.id} is a used in another relation")
end
self.changeset_id = new_relation.changeset_id
self.tags = {}
self.members = []
self.visible = false
save_with_history!
end
end
def update_from(new_relation, user)
check_consistency(self, new_relation, user)
if !new_relation.preconditions_ok?
raise OSM::APIPreconditionFailedError.new
end
self.changeset_id = new_relation.changeset_id
self.changeset = new_relation.changeset
self.tags = new_relation.tags
self.members = new_relation.members
self.visible = true
save_with_history!
end
def create_with_history(user)
check_create_consistency(self, user)
if !self.preconditions_ok?
raise OSM::APIPreconditionFailedError.new
end
self.version = 0
self.visible = true
save_with_history!
end
def preconditions_ok?
# These are hastables that store an id in the index of all
# the nodes/way/relations that have already been added.
# Once we know the id of the node/way/relation exists
# we check to see if it is already existing in the hashtable
# if it does, then we return false. Otherwise
# we add it to the relevant hash table, with the value true..
# If the member is valid and visible then we add it to the
# relevant hash table, with the value true as a cache.
# Thus if you have nodes with the ids of 50 and 1 already in the
# relation, then the hash table nodes would contain:
# => {50=>true, 1=>true}
nodes = Hash.new
ways = Hash.new
relations = Hash.new
elements = { :node => Hash.new, :way => Hash.new, :relation => Hash.new }
self.members.each do |m|
if (m[0] == "node")
n = Node.find(:first, :conditions => ["id = ?", m[1]])
unless n and n.visible
# find the hash for the element type or die
logger.debug m[0]
hash = elements[m[0].downcase.to_sym] or return false
# unless its in the cache already
unless hash.key? m[1]
# use reflection to look up the appropriate class
model = Kernel.const_get(m[0].capitalize)
# get the element with that ID
element = model.find(m[1])
# and check that it is OK to use.
unless element and element.visible? and element.preconditions_ok?
return false
end
if nodes[m[1]]
return false
else
nodes[m[1]] = true
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
if ways[m[1]]
return false
else
ways[m[1]] = true
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
if relations[m[1]]
return false
else
relations[m[1]] = true
end
else
return false
hash[m[1]] = true
end
end
return true
rescue
return false
@ -267,4 +307,152 @@ class Relation < ActiveRecord::Base
def tags_as_hash
return self.tags
end
##
# if any members are referenced by placeholder IDs (i.e: negative) then
# this calling this method will fix them using the map from placeholders
# to IDs +id_map+.
def fix_placeholders!(id_map)
self.members.map! do |type, id, role|
old_id = id.to_i
if old_id < 0
new_id = id_map[type.downcase.to_sym][old_id]
raise "invalid placeholder" if new_id.nil?
[type, new_id, role]
else
[type, id, role]
end
end
end
private
def save_with_history!
Relation.transaction do
# have to be a little bit clever here - to detect if any tags
# changed then we have to monitor their before and after state.
tags_changed = false
t = Time.now.getutc
self.version += 1
self.timestamp = t
self.save!
tags = self.tags
self.relation_tags.each do |old_tag|
key = old_tag.k
# if we can match the tags we currently have to the list
# of old tags, then we never set the tags_changed flag. but
# if any are different then set the flag and do the DB
# update.
if tags.has_key? key
# rails 2.1 dirty handling should take care of making this
# somewhat efficient... hopefully...
old_tag.v = tags[key]
tags_changed |= old_tag.changed?
old_tag.save!
# remove from the map, so that we can expect an empty map
# at the end if there are no new tags
tags.delete key
else
# this means a tag was deleted
tags_changed = true
RelationTag.delete_all ['id = ? and k = ?', self.id, old_tag.k]
end
end
# if there are left-over tags then they are new and will have to
# be added.
tags_changed |= (not tags.empty?)
tags.each do |k,v|
tag = RelationTag.new
tag.k = k
tag.v = v
tag.id = self.id
tag.save!
end
# reload, so that all of the members are accessible in their
# new state.
self.reload
# same pattern as before, but this time we're collecting the
# changed members in an array, as the bounding box updates for
# elements are per-element, not blanked on/off like for tags.
changed_members = Array.new
members = Hash.new
self.members.each do |m|
# should be: h[[m.id, m.type]] = m.role, but someone prefers arrays
members[[m[1], m[0]]] = m[2]
end
relation_members.each do |old_member|
key = [old_member.member_id.to_s, old_member.member_type]
if members.has_key? key
members.delete key
else
changed_members << key
end
end
# any remaining members must be new additions
changed_members += members.keys
# update the members. first delete all the old members, as the new
# members may be in a different order and i don't feel like implementing
# a longest common subsequence algorithm to optimise this.
members = self.members
RelationMember.delete_all(:id => self.id)
members.each_with_index do |m,i|
mem = RelationMember.new
mem.id = [self.id, i]
mem.member_type = m[0]
mem.member_id = m[1]
mem.member_role = m[2]
mem.save!
end
old_relation = OldRelation.from_relation(self)
old_relation.timestamp = t
old_relation.save_with_dependencies!
# update the bbox of the changeset and save it too.
# discussion on the mailing list gave the following definition for
# the bounding box update procedure of a relation:
#
# adding or removing nodes or ways from a relation causes them to be
# added to the changeset bounding box. adding a relation member or
# changing tag values causes all node and way members to be added to the
# bounding box. this is similar to how the map call does things and is
# reasonable on the assumption that adding or removing members doesn't
# materially change the rest of the relation.
any_relations =
changed_members.collect { |id,type| type == "relation" }.
inject(false) { |b,s| b or s }
if tags_changed or any_relations
# add all non-relation bounding boxes to the changeset
# FIXME: check for tag changes along with element deletions and
# make sure that the deleted element's bounding box is hit.
self.members.each do |type, id, role|
if type != "Relation"
update_changeset_element(type, id)
end
end
else
# add only changed members to the changeset
changed_members.each do |id, type|
if type != "Relation"
update_changeset_element(type, id)
end
end
end
# tell the changeset we updated one element only
changeset.add_changes! 1
# save the (maybe updated) changeset bounding box
changeset.save!
end
end
end

View file

@ -1,19 +1,20 @@
class RelationMember < ActiveRecord::Base
set_table_name 'current_relation_members'
set_primary_keys :id, :sequence_id
belongs_to :member, :polymorphic => true, :foreign_type => :member_class
belongs_to :relation, :foreign_key => :id
def after_find
self[:member_class] = self.member_type.capitalize
self[:member_class] = self.member_type.classify
end
def after_initialize
self[:member_class] = self.member_type.capitalize
self[:member_class] = self.member_type.classify unless self.member_type.nil?
end
def before_save
self.member_type = self[:member_class].downcase
self.member_type = self[:member_class].classify
end
def member_type=(type)

View file

@ -3,4 +3,8 @@ class RelationTag < ActiveRecord::Base
belongs_to :relation, :foreign_key => 'id'
validates_presence_of :id
validates_length_of :k, :v, :maximum => 255, :allow_blank => true
validates_uniqueness_of :id, :scope => :k
validates_numericality_of :id, :only_integer => true
end

View file

@ -3,6 +3,8 @@ class Trace < ActiveRecord::Base
validates_presence_of :user_id, :name, :timestamp
validates_presence_of :description, :on => :create
validates_length_of :name, :maximum => 255
validates_length_of :description, :maximum => 255
# validates_numericality_of :latitude, :longitude
validates_inclusion_of :public, :inserted, :in => [ true, false]

View file

@ -2,6 +2,7 @@ class Tracetag < ActiveRecord::Base
set_table_name 'gpx_file_tags'
validates_format_of :tag, :with => /^[^\/;.,?]*$/
validates_length_of :tag, :within => 1..255
belongs_to :trace, :foreign_key => 'gpx_id'
end

View file

@ -4,19 +4,21 @@ class User < ActiveRecord::Base
has_many :traces
has_many :diary_entries, :order => 'created_at DESC'
has_many :messages, :foreign_key => :to_user_id, :order => 'sent_on DESC'
has_many :new_messages, :class_name => "Message", :foreign_key => :to_user_id, :conditions => "message_read = 0", :order => 'sent_on DESC'
has_many :new_messages, :class_name => "Message", :foreign_key => :to_user_id, :conditions => {:message_read => false}, :order => 'sent_on DESC'
has_many :sent_messages, :class_name => "Message", :foreign_key => :from_user_id, :order => 'sent_on DESC'
has_many :friends, :include => :befriendee, :conditions => "users.visible = 1"
has_many :friends, :include => :befriendee, :conditions => ["users.visible = ?", true]
has_many :tokens, :class_name => "UserToken"
has_many :preferences, :class_name => "UserPreference"
has_many :changesets
validates_presence_of :email, :display_name
validates_confirmation_of :email, :message => 'Email addresses must match'
validates_confirmation_of :pass_crypt, :message => 'Password must match the confirmation password'
validates_uniqueness_of :display_name, :allow_nil => true
validates_uniqueness_of :email
validates_length_of :pass_crypt, :minimum => 8
validates_length_of :display_name, :minimum => 3, :allow_nil => true
validates_length_of :pass_crypt, :within => 8..255
validates_length_of :display_name, :within => 3..255, :allow_nil => true
validates_length_of :email, :within => 6..255
validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
validates_format_of :display_name, :with => /^[^\/;.,?]*$/
validates_numericality_of :home_lat, :allow_nil => true
@ -28,7 +30,7 @@ class User < ActiveRecord::Base
file_column :image, :magick => { :geometry => "100x100>" }
def after_initialize
self.creation_time = Time.now if self.creation_time.nil?
self.creation_time = Time.now.getutc if self.creation_time.nil?
end
def encrypt_password
@ -80,7 +82,7 @@ class User < ActiveRecord::Base
if self.home_lon and self.home_lat
gc = OSM::GreatCircle.new(self.home_lat, self.home_lon)
bounds = gc.bounds(radius)
nearby = User.find(:all, :conditions => "visible = 1 and home_lat between #{bounds[:minlat]} and #{bounds[:maxlat]} and home_lon between #{bounds[:minlon]} and #{bounds[:maxlon]} and data_public = 1 and id != #{self.id}")
nearby = User.find(:all, :conditions => ["visible = ? and home_lat between #{bounds[:minlat]} and #{bounds[:maxlat]} and home_lon between #{bounds[:minlon]} and #{bounds[:maxlon]} and data_public = ? and id != #{self.id}", true, true])
nearby.delete_if { |u| gc.distance(u.home_lat, u.home_lon) > radius }
nearby.sort! { |u1,u2| gc.distance(u1.home_lat, u1.home_lon) <=> gc.distance(u2.home_lat, u2.home_lon) }
else
@ -104,6 +106,10 @@ class User < ActiveRecord::Base
return false
end
def trace_public_default
return self.preferences.find(:first, :conditions => {:k => "gps.trace.public", :v => "default"})
end
def delete
self.active = false
self.display_name = "user_#{self.id}"

View file

@ -1,6 +1,9 @@
class UserPreference < ActiveRecord::Base
set_primary_keys :user_id, :k
belongs_to :user
validates_length_of :k, :within => 1..255
validates_length_of :v, :within => 1..255
# Turn this Node in to an XML Node without the <osm> wrapper.
def to_xml_node

View file

@ -1,9 +1,11 @@
class Way < ActiveRecord::Base
require 'xml/libxml'
include ConsistencyValidations
set_table_name 'current_ways'
belongs_to :user
belongs_to :changeset
has_many :old_ways, :foreign_key => 'id', :order => 'version'
@ -15,37 +17,56 @@ class Way < ActiveRecord::Base
has_many :containing_relation_members, :class_name => "RelationMember", :as => :member
has_many :containing_relations, :class_name => "Relation", :through => :containing_relation_members, :source => :relation, :extend => ObjectFinder
validates_presence_of :id, :on => :update
validates_presence_of :changeset_id,:version, :timestamp
validates_uniqueness_of :id
validates_inclusion_of :visible, :in => [ true, false ]
validates_numericality_of :changeset_id, :version, :integer_only => true
validates_numericality_of :id, :on => :update, :integer_only => true
validates_associated :changeset
def self.from_xml(xml, create=false)
begin
p = XML::Parser.string(xml)
doc = p.parse
way = Way.new
doc.find('//osm/way').each do |pt|
if !create and pt['id'] != '0'
way.id = pt['id'].to_i
end
if create
way.timestamp = Time.now
way.visible = true
else
if pt['timestamp']
way.timestamp = Time.parse(pt['timestamp'])
end
end
pt.find('tag').each do |tag|
way.add_tag_keyval(tag['k'], tag['v'])
end
pt.find('nd').each do |nd|
way.add_nd_num(nd['ref'])
end
return Way.from_xml_node(pt, create)
end
rescue
way = nil
rescue LibXML::XML::Error, ArgumentError => ex
raise OSM::APIBadXMLError.new("way", xml, ex.message)
end
end
def self.from_xml_node(pt, create=false)
way = Way.new
if !create and pt['id'] != '0'
way.id = pt['id'].to_i
end
way.version = pt['version']
raise OSM::APIBadXMLError.new("node", pt, "Changeset is required") if pt['changeset'].nil?
way.changeset_id = pt['changeset']
# This next section isn't required for the create, update, or delete of ways
if create
way.timestamp = Time.now.getutc
way.visible = true
else
if pt['timestamp']
way.timestamp = Time.parse(pt['timestamp'])
end
# if visible isn't present then it defaults to true
way.visible = (pt['visible'] or true)
end
pt.find('tag').each do |tag|
way.add_tag_keyval(tag['k'], tag['v'])
end
pt.find('nd').each do |nd|
way.add_nd_num(nd['ref'])
end
return way
@ -73,18 +94,23 @@ class Way < ActiveRecord::Base
el1['id'] = self.id.to_s
el1['visible'] = self.visible.to_s
el1['timestamp'] = self.timestamp.xmlschema
el1['version'] = self.version.to_s
el1['changeset'] = self.changeset_id.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)
if user_display_name_cache and user_display_name_cache.key?(self.changeset.user_id)
# use the cache if available
elsif self.user.data_public?
user_display_name_cache[self.user_id] = self.user.display_name
elsif self.changeset.user.data_public?
user_display_name_cache[self.changeset.user_id] = self.changeset.user.display_name
else
user_display_name_cache[self.user_id] = nil
user_display_name_cache[self.changeset.user_id] = nil
end
el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil?
if not user_display_name_cache[self.changeset.user_id].nil?
el1['user'] = user_display_name_cache[self.changeset.user_id]
el1['uid'] = self.changeset.user_id.to_s
end
# make sure nodes are output in sequence_id order
ordered_nodes = []
@ -96,7 +122,7 @@ class Way < ActiveRecord::Base
end
else
# otherwise, manually go to the db to check things
if nd.node.visible? and nd.node.visible?
if nd.node and nd.node.visible?
ordered_nodes[nd.sequence_id] = nd.node_id.to_s
end
end
@ -154,99 +180,86 @@ class Way < ActiveRecord::Base
def add_tag_keyval(k, v)
@tags = Hash.new unless @tags
# duplicate tags are now forbidden, so we can't allow values
# in the hash to be overwritten.
raise OSM::APIDuplicateTagsError.new("way", self.id, k) if @tags.include? k
@tags[k] = v
end
def save_with_history!
t = Time.now
##
# the integer coords (i.e: unscaled) bounding box of the way, assuming
# straight line segments.
def bbox
lons = nodes.collect { |n| n.longitude }
lats = nodes.collect { |n| n.latitude }
[ lons.min, lats.min, lons.max, lats.max ]
end
Way.transaction do
self.timestamp = t
self.save!
def update_from(new_way, user)
check_consistency(self, new_way, user)
if !new_way.preconditions_ok?
raise OSM::APIPreconditionFailedError.new
end
WayTag.transaction do
tags = self.tags
self.changeset_id = new_way.changeset_id
self.changeset = new_way.changeset
self.tags = new_way.tags
self.nds = new_way.nds
self.visible = true
save_with_history!
end
WayTag.delete_all(['id = ?', self.id])
tags.each do |k,v|
tag = WayTag.new
tag.k = k
tag.v = v
tag.id = self.id
tag.save!
end
def create_with_history(user)
check_create_consistency(self, user)
if !self.preconditions_ok?
raise OSM::APIPreconditionFailedError.new
end
WayNode.transaction do
nds = self.nds
WayNode.delete_all(['id = ?', self.id])
sequence = 1
nds.each do |n|
nd = WayNode.new
nd.id = [self.id, sequence]
nd.node_id = n
nd.save!
sequence += 1
end
end
old_way = OldWay.from_way(self)
old_way.timestamp = t
old_way.save_with_dependencies!
self.version = 0
self.visible = true
save_with_history!
end
def preconditions_ok?
return false if self.nds.empty?
if self.nds.length > APP_CONFIG['max_number_of_way_nodes']
raise OSM::APITooManyWayNodesError.new(self.nds.count, APP_CONFIG['max_number_of_way_nodes'])
end
self.nds.each do |n|
node = Node.find(:first, :conditions => ["id = ?", n])
unless node and node.visible
return false
raise OSM::APIPreconditionFailedError.new("The node with id #{n} either does not exist, or is not visible")
end
end
return true
end
# Delete the way and it's relations, but don't really delete it - set its visibility to false and update the history etc to maintain wiki-like functionality.
def delete_with_relations_and_history(user)
if self.visible
# FIXME
# this should actually delete the relations,
# not just throw a PreconditionFailed if it's a member of a relation!!
def delete_with_history!(new_way, user)
unless self.visible
raise OSM::APIAlreadyDeletedError
end
# need to start the transaction here, so that the database can
# provide repeatable reads for the used-by checks. this means it
# shouldn't be possible to get race conditions.
Way.transaction do
check_consistency(self, new_way, user)
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=?", self.id])
raise OSM::APIPreconditionFailedError
# end FIXME
:conditions => [ "visible = ? AND member_type='Way' and member_id=? ", true, self.id])
raise OSM::APIPreconditionFailedError.new("You need to make sure that this way is not a member of a relation.")
else
self.user_id = user.id
self.changeset_id = new_way.changeset_id
self.changeset = new_way.changeset
self.tags = []
self.nds = []
self.visible = false
self.save_with_history!
save_with_history!
end
else
raise OSM::APIAlreadyDeletedError
end
end
# delete a way and it's nodes that aren't part of other ways, with history
def delete_with_relations_and_nodes_and_history(user)
# delete the nodes not used by other ways
self.unshared_node_ids.each do |node_id|
n = Node.find(node_id)
n.user_id = user.id
n.visible = false
n.save_with_history!
end
self.user_id = user.id
self.delete_with_relations_and_history(user)
end
# Find nodes that belong to this way only
def unshared_node_ids
node_ids = self.nodes.collect { |node| node.id }
@ -263,4 +276,78 @@ class Way < ActiveRecord::Base
def tags_as_hash
return self.tags
end
##
# if any referenced nodes are placeholder IDs (i.e: are negative) then
# this calling this method will fix them using the map from placeholders
# to IDs +id_map+.
def fix_placeholders!(id_map)
self.nds.map! do |node_id|
if node_id < 0
new_id = id_map[:node][node_id]
raise "invalid placeholder for #{node_id.inspect}: #{new_id.inspect}" if new_id.nil?
new_id
else
node_id
end
end
end
private
def save_with_history!
t = Time.now.getutc
# update the bounding box, note that this has to be done both before
# and after the save, so that nodes from both versions are included in the
# bbox. we use a copy of the changeset so that it isn't reloaded
# later in the save.
cs = self.changeset
cs.update_bbox!(bbox) unless nodes.empty?
Way.transaction do
self.version += 1
self.timestamp = t
self.save!
tags = self.tags
WayTag.delete_all(['id = ?', self.id])
tags.each do |k,v|
tag = WayTag.new
tag.k = k
tag.v = v
tag.id = self.id
tag.save!
end
nds = self.nds
WayNode.delete_all(['id = ?', self.id])
sequence = 1
nds.each do |n|
nd = WayNode.new
nd.id = [self.id, sequence]
nd.node_id = n
nd.save!
sequence += 1
end
old_way = OldWay.from_way(self)
old_way.timestamp = t
old_way.save_with_dependencies!
# reload the way so that the nodes array points to the correct
# new set of nodes.
self.reload
# update and commit the bounding box, now that way nodes
# have been updated and we're in a transaction.
cs.update_bbox!(bbox) unless nodes.empty?
# tell the changeset we updated one element only
cs.add_changes! 1
cs.save!
end
end
end

View file

@ -6,4 +6,9 @@ class WayTag < ActiveRecord::Base
# FIXME add a real multipart key to waytags so that we can do eager loadin
belongs_to :way, :foreign_key => 'id'
validates_presence_of :id
validates_length_of :k, :v, :maximum => 255, :allow_blank => true
validates_uniqueness_of :id, :scope => :k
validates_numericality_of :id, :only_integer => true
end

View file

@ -0,0 +1,95 @@
<table>
<tr>
<th>Created at:</th>
<td><%= h(changeset_details.created_at) %></td>
</tr>
<tr>
<th>Closed at:</th>
<td><%= h(changeset_details.closed_at) %></td>
</tr>
<% if changeset_details.user.data_public? %>
<tr>
<th>Belongs to:</th>
<td><%= link_to h(changeset_details.user.display_name), :controller => "user", :action => "view", :display_name => changeset_details.user.display_name %></td>
</tr>
<% end %>
<%= render :partial => "tag_details", :object => changeset_details %>
<tr>
<th>Bounding box:</th>
<% unless changeset_details.has_valid_bbox? %>
<td>No bounding box has been stored for this changeset.</td>
<% else
minlon = changeset_details.min_lon/GeoRecord::SCALE.to_f
minlat = changeset_details.min_lat/GeoRecord::SCALE.to_f
maxlon = changeset_details.max_lon/GeoRecord::SCALE.to_f
maxlat = changeset_details.max_lat/GeoRecord::SCALE.to_f
%>
<td>
<table>
<tr>
<td colspan="3" style="text-align:center"><%=maxlat -%></td>
</tr>
<tr>
<td><%=minlon -%></td>
<td>(<a href='/?minlon=<%= minlon %>&minlat=<%= minlat %>&maxlon=<%= maxlon %>&maxlat=<%= maxlat %>&box=yes' title='show area box'>box</a>)</td>
<td><%=maxlon -%></td>
</tr>
<tr>
<td colspan="3" style="text-align:center"><%= minlon -%></td>
</tr>
</table>
</td>
<% end %>
</tr>
<% unless @nodes.empty? %>
<tr valign="top">
<th>Has the following <%= @node_pages.item_count %> nodes:</th>
<td>
<table padding="0">
<% @nodes.each do |node| %>
<tr><td><%= link_to "Node #{node.id.to_s}, version #{node.version.to_s}", :action => "node", :id => node.id.to_s %></td></tr>
<% end %>
</table>
</td>
</tr>
<%= render :partial => 'paging_nav', :locals => { :pages => @node_pages, :page_param => "node_page"} %>
<% end %>
<% unless @ways.empty? %>
<tr valign="top">
<th>Has the following <%= @way_pages.item_count %> ways:</th>
<td>
<table padding="0">
<% @ways.each do |way| %>
<tr><td><%= link_to "Way #{way.id.to_s}, version #{way.version.to_s}", :action => "way", :id => way.id.to_s %></td></tr>
<% end %>
<%=
#render :partial => "containing_relation", :collection => changeset_details.containing_relation_members
%>
</table>
</td>
</tr>
<%= render :partial => 'paging_nav', :locals => { :pages => @way_pages, :page_param => "way_page" } %>
<% end %>
<% unless @relations.empty? %>
<tr valign="top">
<th>Has the following <%= @relation_pages.item_count %> relations:</th>
<td>
<table padding="0">
<% @relations.each do |relation| %>
<tr><td><%= link_to "Relation #{relation.id.to_s}, version #{relation.version.to_s}", :action => "relation", :id => relation.id.to_s %></td></tr>
<% end %>
</table>
</td>
</tr>
<%= render :partial => 'paging_nav', :locals => { :pages => @relation_pages, :page_param => "relation_page" } %>
<% end %>
</table>

View file

@ -3,20 +3,21 @@
<td><%= h(common_details.timestamp) %></td>
</tr>
<% if common_details.user.data_public %>
<% if common_details.changeset.user.data_public? %>
<tr>
<th>Edited by:</th>
<td><%= link_to h(common_details.user.display_name), :controller => "user", :action => "view", :display_name => common_details.user.display_name %></td>
<td><%= link_to h(common_details.changeset.user.display_name), :controller => "user", :action => "view", :display_name => common_details.changeset.user.display_name %></td>
</tr>
<% end %>
<% unless common_details.tags_as_hash.empty? %>
<tr valign="top">
<th>Tags:</th>
<td>
<table padding="0">
<%= render :partial => "tag", :collection => common_details.tags_as_hash %>
</table>
</td>
</tr>
<% end %>
<tr>
<th>Version:</th>
<td><%= h(common_details.version) %></td>
</tr>
<tr>
<th>In changeset:</th>
<td><%= link_to common_details.changeset_id, :action => :changeset, :id => common_details.changeset_id %></td>
</tr>
<%= render :partial => "tag_details", :object => common_details %>

View file

@ -2,7 +2,7 @@
<%= javascript_include_tag '/openlayers/OpenStreetMap.js' %>
<%= javascript_include_tag 'map.js' %>
<td align="right">
<% if map.visible %>
<% if map.instance_of? Changeset or map.visible %>
<div id="small_map" style="width:250px; height: 300px; border: solid 1px black">
</div>
<span id="loading">Loading...</span>
@ -13,6 +13,25 @@
</td>
<script type="text/javascript">
function init() {
var map = createMap("small_map", {
controls: [ new OpenLayers.Control.Navigation() ]
});
<% if map.instance_of? Changeset %>
var minlon = <%= map.min_lon / GeoRecord::SCALE.to_f %>;
var minlat = <%= map.min_lat / GeoRecord::SCALE.to_f %>;
var maxlon = <%= map.max_lon / GeoRecord::SCALE.to_f %>;
var maxlat = <%= map.max_lat / GeoRecord::SCALE.to_f %>;
var bbox = new OpenLayers.Bounds(minlon, minlat, maxlon, maxlat);
setMapExtent(bbox);
addBoxToMap(bbox);
$("loading").innerHTML = "";
$("larger_map").href = '/?minlon='+minlon+'&minlat='+minlat+'&maxlon='+maxlon+'&maxlat='+maxlat+'&box=yes';
$("larger_map").innerHTML = "View Larger Map";
<% else %>
var obj_type = "<%= map.class.name.downcase %>";
var obj_id = <%= map.id %>;
var url = "/api/<%= "#{API_VERSION}" %>/<%= map.class.name.downcase %>/<%= map.id %>";
@ -21,10 +40,6 @@
url += "/full";
}
var map = createMap("small_map", {
controls: [ new OpenLayers.Control.Navigation() ]
});
var osm_layer = new OpenLayers.Layer.GML("OSM", url, {
format: OpenLayers.Format.OSM,
projection: new OpenLayers.Projection("EPSG:4326")
@ -58,6 +73,7 @@
osm_layer.loadGML();
osm_layer.loaded = true;
<% end %>
}
window.onload = init;

View file

@ -0,0 +1,15 @@
<tr><td colspan='2'>
<% current_page = pages.current_page %>
Showing page
<%= current_page.number %> (<%= current_page.first_item %><%
if (current_page.first_item < current_page.last_item) # if more than 1 trace on page
%>-<%= current_page.last_item %><%
end %>
of <%= pages.item_count %>)
<% if pages.page_count > 1 %>
| <%= pagination_links_each(pages, {}) { |n| link_to_page(n, page_param) } %>
<% end %>
</td>
</tr>

View file

@ -0,0 +1,10 @@
<% unless tag_details.tags_as_hash.empty? %>
<tr valign="top">
<th>Tags:</th>
<td>
<table padding="0">
<%= render :partial => "tag", :collection => tag_details.tags_as_hash %>
</table>
</td>
</tr>
<% end %>

View file

@ -0,0 +1,23 @@
<table width="100%">
<tr>
<td>
<h2>Changeset: <%= h(@changeset.id) %></h2>
</td>
<td>
<%= render :partial => "navigation" %>
</td>
</tr>
<tr valign="top">
<td>
<%= render :partial => "changeset_details", :object => @changeset %>
<hr />
Download
<%= link_to "Changeset XML", :controller => "changeset", :action => "read" %>
or
<%= link_to "osmChange XML", :controller => "changeset", :action => "download" %>
</td>
<% if @changeset.has_valid_bbox? %>
<%= render :partial => "map", :object => @changeset %>
<% end %>
</tr>
</table>

View file

@ -1,12 +0,0 @@
<h2><%= @nodes.length %> Recently Changed Nodes</h2>
<ul>
<% @nodes.each do |node|
name = node.tags_as_hash['name'].to_s
if name.length == 0:
name = "(No name)"
end
name = name + " - " + node.id.to_s
%>
<li><%= link_to h(name), :action => "node", :id => node.id %></li>
<% end %>
</ul>

View file

@ -0,0 +1 @@
<p>Sorry, the <%= @type -%> with the id <%= params[:id] -%>, could not be found.</p>

View file

@ -189,7 +189,7 @@ page << <<EOJ
if (size > 0.25) {
setStatus("Unable to load: Bounding box size of " + size + " is too large (must be smaller than 0.25)");
} else {
loadGML("/api/0.5/map?bbox=" + projected.toBBOX());
loadGML("/api/#{API_VERSION}/map?bbox=" + projected.toBBOX());
}
}
@ -393,7 +393,7 @@ page << <<EOJ
this.link.href = "";
this.link.innerHTML = "Wait...";
new Ajax.Request("/api/0.5/" + this.type + "/" + this.feature.osm_id + "/history", {
new Ajax.Request("/api/#{API_VERSION}/" + this.type + "/" + this.feature.osm_id + "/history", {
onComplete: OpenLayers.Function.bind(displayHistory, this)
});

View file

@ -0,0 +1,49 @@
<tr>
<% cl = cycle('table0', 'table1') %>
<td class="<%= cl %>">
#<%= changeset.id %>
</td>
<td class="<%= cl %>">
<% if changeset.closed_at > DateTime.now %> (still editing)
<% else %><%= changeset.closed_at.strftime("%d %b %Y %H:%M") %><% end %>
</td>
<%if showusername %>
<td class="<%= cl %>">
<% if changeset.user.data_public? %>
<%= link_to h(changeset.user.display_name), :controller => "user", :action => "view", :display_name => changeset.user.display_name %>
<% else %>
<i>Anonymous</i>
<% end %>
</td>
<% end %>
<td class="<%= cl %>">
<% if changeset.tags['comment'] %>
<%= h(changeset.tags['comment']) %>
<% else %>
(none)
<% end %>
</td>
<td class="<%= cl %>">
<% if changeset.min_lat.nil? %>
(no edits)
<% else
minlon = changeset.min_lon/GeoRecord::SCALE.to_f
minlat = changeset.min_lat/GeoRecord::SCALE.to_f
maxlon = changeset.max_lon/GeoRecord::SCALE.to_f
maxlat = changeset.max_lat/GeoRecord::SCALE.to_f
%>
(<a href='/?minlon=<%= minlon %>&minlat=<%= minlat %>&maxlon=<%= maxlon %>&maxlat=<%= maxlat %>&box=yes' title='show area box'><%= format("%0.3f",minlon) -%>,<%= format("%0.3f",minlat) -%>,<%= format("%0.3f",maxlon) -%>,<%= format("%0.3f",maxlat) -%></a>)
<% end %>
</td>
<td class="<%= cl %>">
<%= link_to 'more', {:controller => 'browse', :action => 'changeset', :id => changeset.id}, {:title => 'View changeset details'} %>
</td>
</tr>

View file

@ -0,0 +1,18 @@
<% current_page = @edit_pages.current_page %>
Showing page
<%= current_page.number %> (<%= current_page.first_item %><%
if (current_page.first_item < current_page.last_item) # if more than 1 changeset on page
%>-<%= current_page.last_item %><%
end %>
of <%= @edit_pages.item_count %>)
<%
if @edit_pages.page_count > 1
bboxparam = h(params['bbox'])
bboxparam = nil if bboxparam==""
%>
| <%= pagination_links_each(@edit_pages, {}) { |n| link_to(n, :display_name => @display_name, :bbox => bboxparam , :page => n) } %>
<%
end
%>

View file

@ -0,0 +1,19 @@
<h1>Recent Changes</h1>
<p>Recently closed changesets:</p>
<table id="keyvalue" cellpadding="3">
<tr>
<th>ID</th>
<th>Saved at</th>
<th>User</th>
<th>Comment</th>
<th>Area</th>
<th></th>
</tr>
<%= render :partial => 'changeset', :locals => {:showusername => true}, :collection => @edits unless @edits.nil? %>
</table>
<p>
For more changesets, select a user and view their edits, or see the editing 'history' of a specific area.
</p>
<br>

View file

@ -0,0 +1,53 @@
<h1>History</h1>
<%
if @bbox!=nil
minlon = @bbox[0]
minlat = @bbox[1]
maxlon = @bbox[2]
maxlat = @bbox[3]
%>
<p>
Changesets within the area:
(<a href='/?minlon=<%= minlon %>&minlat=<%= minlat %>&maxlon=<%= maxlon %>&maxlat=<%= maxlat %>&box=yes' title='show area box'><%= format("%0.3f",minlon) -%>,<%= format("%0.3f",minlat) -%>,<%= format("%0.3f",maxlon) -%>,<%= format("%0.3f",maxlat) -%></a>)
</p>
<% if @edits.nil? or @edits.empty? %>
<p><b>No changesets</b></p>
<% else %>
<%= render :partial => 'changeset_paging_nav' %>
<table id="keyvalue" cellpadding="3">
<tr>
<th>ID</th>
<th>Saved at</th>
<th>User</th>
<th>Comment</th>
<th>Area</th>
<th></th>
</tr>
<%= render :partial => 'changeset', :locals => {:showusername => true}, :collection => @edits unless @edits.nil? %>
</table>
<%= render :partial => 'changeset_paging_nav' %>
<p>For all changes everywhere see <%= link_to("Recent Changes", :controller => "browse", :action => "changesets") %> </p>
<%
end
else
#bbox is nil. happens if the user surfs to this page directly.
%>
<p>No area specified</p>
<p>First use the <a href="/" title="view the map">view tab</a> to pan and zoom to an area of interest, then click the history tab.</p>
<p>Alternatively view all <%= link_to("recent changes", :controller => "browse", :action => "changesets") %> </p>
<%
end
%>
<br>

View file

@ -0,0 +1,22 @@
<h1>Edits by <%= link_to(h(@display_name), {:controller=>'user', :action=>'view', :display_name=>@display_name}) %></h1>
<% if not @edits or @edits.empty? %>
<p><b>No visible edits by <%= h(@display_name) %>.</b></p>
<% else %>
<%= render :partial => 'changeset_paging_nav' %>
<table id="keyvalue" cellpadding="3">
<tr>
<th>ID</th>
<th>Saved at</th>
<th>Comment</th>
<th>Area</th>
<th></th>
</tr>
<%= render :partial => 'changeset', :locals => {:showusername => false}, :collection => @edits %>
</table>
<%= render :partial => 'changeset_paging_nav' %>
<% end %>
<p>For changes by all users see <%= link_to("Recent Changes", :controller => "browse", :action => "changesets") %> </p>
<br>

View file

@ -5,24 +5,28 @@
<% form_for :diary_entry do |f| %>
<table>
<tr valign="top">
<th>Subject</th>
<td class="fieldName">Subject:</td>
<td><%= f.text_field :title, :size => 60 %></td>
</tr>
<tr valign="top">
<th>Body</th>
<td class="fieldName">Body:</td>
<td><%= f.text_area :body, :cols => 80 %></td>
</tr>
<tr valign="top">
<th>Location</th>
<td class="fieldName">Location:</td>
<td>
<div id="map" style="border: 1px solid black; position: relative; width : 90%; height : 400px; display: none;"></div>
<span class="location">Latitude: <%= f.text_field :latitude, :size => 20, :id => "latitude" %> Longitude: <%= f.text_field :longitude, :size => 20, :id => "longitude" %></span>
<a href="javascript:openMap()" id="usemap">use map</a>
<br/><br/>
</td>
</tr>
<tr>
<th></th>
<td><%= submit_tag 'Save' %></td>
<td></td>
<td>
<%= submit_tag 'Save' %>
<%# TODO: button should say 'publish' or 'save changes' depending on new/edit state %>
</td>
</tr>
</table>
<% end %>

View file

@ -4,29 +4,33 @@
<%= image_tag url_for_file_column(@this_user, "image") %>
<% end %>
<br />
<% if @this_user %>
<% if @user == @this_user %>
<%= link_to 'New diary entry', :controller => 'diary_entry', :action => 'new', :display_name => @user.display_name %>
<%= link_to image_tag("new.png", :border=>0) + 'New diary entry', {:controller => 'diary_entry', :action => 'new', :display_name => @user.display_name}, {:title => 'Compose a new entry in your user diary'} %>
<% end %>
<% else %>
<% if @user %>
<%= link_to 'New diary entry', :controller => 'diary_entry', :action => 'new', :display_name => @user.display_name %>
<%= link_to image_tag("new.png", :border=>0) + 'New diary entry', {:controller => 'diary_entry', :action => 'new', :display_name => @user.display_name}, {:title => 'Compose a new entry in your user diary'} %>
<% end %>
<% end %>
<h3>Recent diary entries:</h3>
<%= render :partial => 'diary_entry', :collection => @entries %>
<% if @entries.empty? %>
<p>No diary entries</p>
<% else %>
<p>Recent diary entries:</p>
<%= link_to "Older Entries", { :page => @entry_pages.current.next } if @entry_pages.current.next %>
<% if @entry_pages.current.next and @entry_pages.current.previous %>
|
<hr />
<%= render :partial => 'diary_entry', :collection => @entries %>
<%= link_to "Older Entries", { :page => @entry_pages.current.next } if @entry_pages.current.next %>
<% if @entry_pages.current.next and @entry_pages.current.previous %>|<% end %>
<%= link_to "Newer Entries", { :page => @entry_pages.current.previous } if @entry_pages.current.previous %>
<br />
<% end %>
<%= link_to "Newer Entries", { :page => @entry_pages.current.previous } if @entry_pages.current.previous %>
<br />
<%= rss_link_to :action => 'rss' %>

View file

@ -0,0 +1,2 @@
<h2>No entry with the id: <%= h(params[:id]) %></h2>
<p>Sorry, there is no diary entry or comment with the id <%=h params[:id] -%>, or no id was given. Please check your spelling, or maybe the link you clicked is wrong.</p>

View file

@ -7,6 +7,7 @@
<%= stylesheet_link_tag 'site' %>
<%= stylesheet_link_tag 'print', :media => "print" %>
<%= tag("link", { :rel => "search", :type => "application/opensearchdescription+xml", :title => "OpenStreetMap Search", :href => "/opensearch/osm.xml" }) %>
<%= tag("meta", { :name => "description", :content => "OpenStreetMap is the free wiki world map." }) %>
<%= yield :head %>
<title>OpenStreetMap<%= ' | '+ h(@title) if @title %></title>
</head>
@ -37,16 +38,19 @@
<%
viewclass = ''
editclass = ''
historyclass = ''
exportclass = ''
traceclass = ''
viewclass = 'active' if params['controller'] == 'site' and params['action'] == 'index'
editclass = 'active' if params['controller'] == 'site' and params['action'] == 'edit'
historyclass = 'active' if params['controller'] == 'changeset' and params['action'] == 'list_bbox'
exportclass = 'active' if params['controller'] == 'site' and params['action'] == 'export'
traceclass = 'active' if params['controller'] == 'trace'
diaryclass = 'active' if params['controller'] == 'diary_entry'
%>
<li><%= link_to 'View', {:controller => 'site', :action => 'index'}, {:id => 'viewanchor', :title => 'view maps', :class => viewclass} %></li>
<li><%= link_to 'Edit', {:controller => 'site', :action => 'edit'}, {:id => 'editanchor', :title => 'edit maps', :class => editclass} %></li>
<li><%= link_to 'History', {:controller => 'history' }, {:id => 'historyanchor', :title => 'changeset history', :class => historyclass} %></li>
<% if params['controller'] == 'site' and (params['action'] == 'index' or params['action'] == 'export') %>
<li><%= link_to_remote 'Export', {:url => {:controller => 'export', :action => 'start'}}, {:id => 'exportanchor', :title => 'export map data', :class => exportclass, :href => url_for(:controller => 'site', :action => 'export')} %></li>
<% else %>
@ -126,7 +130,7 @@
</div>
<div id="cclogo" class="button" style="width: 88px">
<a href="http://creativecommons.org/licenses/by-sa/2.0/"><img src="/images/cc_button.png" border="0" alt="" /></a>
<%= link_to image_tag("cc_button.png", :alt => "CC by-sa 2.0", :border => "0"), "http://creativecommons.org/licenses/by-sa/2.0/" %>
</div>
</center>
</div>

View file

@ -1,9 +1,10 @@
<% this_colour = cycle('lightgrey', 'white') # can only call once for some dumb reason %>
<% this_colour = cycle('lightgrey', 'white') # can only call once for some dumb reason
%>
<tr class="inbox-row<%= "-unread" if not message_summary.message_read? %>">
<td class="inbox-sender" bgcolor='<%= this_colour %>'><%= link_to h(message_summary.sender.display_name), :controller => 'user', :action => message_summary.sender.display_name %></td>
<td class="inbox-subject" bgcolor='<%= this_colour %>'><%= link_to h(message_summary.title), :controller => 'message', :action => 'read', :message_id => message_summary.id %></td>
<td class="inbox-sent" bgcolor='<%= this_colour %>'><%= message_summary.sent_on %></td>
<td class="inbox-sender" bgcolor="<%= this_colour %>"><%= link_to h(message_summary.sender.display_name), :controller => 'user', :action => message_summary.sender.display_name %></td>
<td class="inbox-subject" bgcolor="<%= this_colour %>"><%= link_to h(message_summary.title), :controller => 'message', :action => 'read', :message_id => message_summary.id %></td>
<td class="inbox-sent" bgcolor="<%= this_colour %>"><%= message_summary.sent_on %></td>
<% if message_summary.message_read? %>
<td><%= button_to 'Mark as unread', :controller => 'message', :action => 'mark', :message_id => message_summary.id, :mark => 'unread' %></td>
<% else %>

View file

@ -1,7 +1,8 @@
<% this_colour = cycle('lightgrey', 'white') # can only call once for some dumb reason %>
<% this_colour = cycle('lightgrey', 'white') # can only call once for some dumb reason
%>
<tr class="inbox-row">
<td class="inbox-sender" bgcolor='<%= this_colour %>'><%= link_to h(sent_message_summary.recipient.display_name), :controller => 'user', :action => sent_message_summary.recipient.display_name %></td>
<td class="inbox-subject" bgcolor='<%= this_colour %>'><%= link_to h(sent_message_summary.title), :controller => 'message', :action => 'read', :message_id => sent_message_summary.id %></td>
<td class="inbox-sent" bgcolor='<%= this_colour %>'><%= sent_message_summary.sent_on %></td>
<td class="inbox-sender" bgcolor="<%= this_colour %>"><%= link_to h(sent_message_summary.recipient.display_name), :controller => 'user', :action => sent_message_summary.recipient.display_name %></td>
<td class="inbox-subject" bgcolor="<%= this_colour %>"><%= link_to h(sent_message_summary.title), :controller => 'message', :action => 'read', :message_id => sent_message_summary.id %></td>
<td class="inbox-sent" bgcolor="<%= this_colour %>"><%= sent_message_summary.sent_on %></td>
</tr>

View file

@ -1,7 +1,4 @@
<% user_id = params[:user_id] || @user_id %>
<% display_name = User.find_by_id(user_id).display_name %>
<h2>Send a new message to <%= h(display_name) %></h2>
<h2>Send a new message to <%= h(@to_user.display_name) %></h2>
<% if params[:display_name] %>
<p>Writing a new message to <%= h(params[:display_name]) %></p>
@ -10,7 +7,7 @@
<%= error_messages_for 'message' %>
<% form_for :message, :url => { :action => "new", :user_id => user_id } do |f| %>
<% form_for :message, :url => { :action => "new", :user_id => @to_user.id } do |f| %>
<table>
<tr valign="top">
<th>Subject</th>

View file

@ -0,0 +1,2 @@
<h1>No such user or message</h1>
<p>Sorry there is no user or message with that name or id</p>

View file

@ -21,21 +21,37 @@
<%= render :partial => 'sidebar', :locals => { :onopen => "resizeMap();", :onclose => "resizeMap();" } %>
<%= render :partial => 'search' %>
<% session[:token] = @user.tokens.create.token unless session[:token] %>
<%
session[:token] = @user.tokens.create.token unless session[:token]
<% if params['mlon'] and params['mlat'] %>
<% lon = h(params['mlon']) %>
<% lat = h(params['mlat']) %>
<% zoom = h(params['zoom'] || '14') %>
<% elsif @user and params['lon'].nil? and params['lat'].nil? and params['gpx'].nil? %>
<% lon = @user.home_lon %>
<% lat = @user.home_lat %>
<% zoom = '14' %>
<% else %>
<% lon = h(params['lon'] || 'null') %>
<% lat = h(params['lat'] || 'null') %>
<% zoom = h(params['zoom'] || '14') %>
<% end %>
# Decide on a lat lon to initialise potlatch with. Various ways of doing this
if params['lon'] and params['lat']
lon = h(params['lon'])
lat = h(params['lat'])
zoom = h(params['zoom'])
elsif params['mlon'] and params['mlat']
lon = h(params['mlon'])
lat = h(params['mlat'])
zoom = h(params['zoom'])
elsif params['gpx']
#use gpx id to locate (dealt with below)
elsif cookies.key?("_osm_location")
lon,lat,zoom,layers = cookies["_osm_location"].split("|")
elsif @user and !@user.home_lon.nil? and !@user.home_lat.nil?
lon = @user.home_lon
lat = @user.home_lat
else
#catch all. Do nothing. lat=nil, lon=nil
#Currently this results in potlatch starting up at 0,0 (Atlantic ocean).
end
zoom='14' if zoom.nil?
%>
<div id="map">
You need a Flash player to use Potlatch, the

View file

@ -32,51 +32,59 @@
</table>
</div>
<% if params['mlon'] and params['mlat'] %>
<% marker = true %>
<% mlon = h(params['mlon']) %>
<% mlat = h(params['mlat']) %>
<% end %>
<%
if params['mlon'] and params['mlat']
marker = true
mlon = h(params['mlon'])
mlat = h(params['mlat'])
end
<% if params['minlon'] and params['minlat'] and params['maxlon'] and params['maxlat'] %>
<% bbox = true %>
<% minlon = h(params['minlon']) %>
<% minlat = h(params['minlat']) %>
<% maxlon = h(params['maxlon']) %>
<% maxlat = h(params['maxlat']) %>
<% end %>
if params['minlon'] and params['minlat'] and params['maxlon'] and params['maxlat']
bbox = true
minlon = h(params['minlon'])
minlat = h(params['minlat'])
maxlon = h(params['maxlon'])
maxlat = h(params['maxlat'])
box = true if params['box']=="yes"
end
<% if params['lon'] and params['lat'] %>
<% lon = h(params['lon']) %>
<% lat = h(params['lat']) %>
<% zoom = h(params['zoom'] || '5') %>
<% layers = h(params['layers']) %>
<% elsif params['mlon'] and params['mlat'] %>
<% lon = h(params['mlon']) %>
<% lat = h(params['mlat']) %>
<% zoom = h(params['zoom'] || '12') %>
<% layers = h(params['layers']) %>
<% elsif cookies.key?("_osm_location") %>
<% lon,lat,zoom,layers = cookies["_osm_location"].split("|") %>
<% elsif @user and !@user.home_lon.nil? and !@user.home_lat.nil? %>
<% lon = @user.home_lon %>
<% lat = @user.home_lat %>
<% zoom = '10' %>
<% else %>
<% session[:location] = OSM::IPLocation(request.env['REMOTE_ADDR']) unless session[:location] %>
<% if session[:location] %>
<% bbox = true %>
<% minlon = session[:location][:minlon] %>
<% minlat = session[:location][:minlat] %>
<% maxlon = session[:location][:maxlon] %>
<% maxlat = session[:location][:maxlat] %>
<% else %>
<% lon = '-0.1' %>
<% lat = '51.5' %>
<% zoom = h(params['zoom'] || '5') %>
<% end %>
<% layers = h(params['layers']) %>
<% end %>
# Decide on a lat lon to initialise the map with. Various ways of doing this
if params['lon'] and params['lat']
lon = h(params['lon'])
lat = h(params['lat'])
zoom = h(params['zoom'] || '5')
layers = h(params['layers'])
elsif params['mlon'] and params['mlat']
lon = h(params['mlon'])
lat = h(params['mlat'])
zoom = h(params['zoom'] || '12')
layers = h(params['layers'])
elsif cookies.key?("_osm_location")
lon,lat,zoom,layers = cookies["_osm_location"].split("|")
elsif @user and !@user.home_lon.nil? and !@user.home_lat.nil?
lon = @user.home_lon
lat = @user.home_lat
zoom = '10'
else
session[:location] = OSM::IPLocation(request.env['REMOTE_ADDR']) unless session[:location]
if session[:location]
bbox = true
minlon = session[:location][:minlon]
minlat = session[:location][:minlat]
maxlon = session[:location][:maxlon]
maxlat = session[:location][:maxlat]
else
lon = '-0.1'
lat = '51.5'
zoom = h(params['zoom'] || '5')
end
layers = h(params['layers'])
end
%>
<%= javascript_include_tag '/openlayers/OpenLayers.js' %>
<%= javascript_include_tag '/openlayers/OpenStreetMap.js' %>
@ -102,13 +110,17 @@
var bbox = new OpenLayers.Bounds(<%= minlon %>, <%= minlat %>, <%= maxlon %>, <%= maxlat %>);
setMapExtent(bbox);
<% if box %>
// IE requires Vector layers be initialised on page load, and not under deferred script conditions
Event.observe(window, 'load', function() {addBoxToMap(bbox)});
<% end %>
<% else %>
var centre = new OpenLayers.LonLat(<%= lon %>, <%= lat %>);
var zoom = <%= zoom %>;
<% if params['scale'] and params['scale'].length > 0 then %>
<% if params['scale'] and params['scale'].length > 0 then %>
zoom = scaleToZoom(<%= params['scale'].to_f() %>);
<% end %>
<% end %>
setMapCenter(centre, zoom);
<% end %>
@ -157,8 +169,9 @@
var lonlat = getMapCenter();
var zoom = map.getZoom();
var layers = getMapLayers();
var extents = getMapExtent();
updatelinks(lonlat.lon, lonlat.lat, zoom, layers);
updatelinks(lonlat.lon, lonlat.lat, zoom, layers, extents);
document.cookie = "_osm_location=" + lonlat.lon + "|" + lonlat.lat + "|" + zoom + "|" + layers;
}

View file

@ -17,9 +17,9 @@
<% end %>
</p>
<% if @user and @user.traces.count(:conditions => "inserted=0") > 4 %>
<% if @user and @user.traces.count(:conditions => ["inserted=?", false]) > 4 %>
<p>
You have <%= @user.traces.count(:conditions => "inserted=0") %> traces
You have <%= @user.traces.count(:conditions => ["inserted=?", false]) %> traces
waiting for upload. Please consider waiting for these to finish before
uploading any more, so as not to block the queue for other users.
</p>

View file

@ -0,0 +1,2 @@
<h2><%= h(@not_found_user) %></h2>
<p>Sorry, there is no user with the name <%= @not_found_user -%>. Please check your spelling, or maybe the link you clicked is wrong.</p>

View file

@ -56,7 +56,9 @@
var nearmarker = addMarkerToMap(new OpenLayers.LonLat(nearest[i].home_lon, nearest[i].home_lat), near_icon.clone(), description);
}
map.events.register("click", map, setHome);
if (document.getElementById('updatehome')) {
map.events.register("click", map, setHome);
}
}
function setHome( e ) {

View file

@ -1,34 +1,51 @@
<h2>User details</h2>
<h2>My settings</h2>
<%= error_messages_for 'user' %>
<% form_for :user, @user do |f| %>
<table style="width : 100%">
<tr><td>Email</td><td><%= f.text_field :email %></td></tr>
<tr><td>Mapper since</td><td><%= @user.creation_time %> (<%= time_ago_in_words(@user.creation_time) %> ago)</td></tr>
<tr><td>Display Name</td><td><%= f.text_field :display_name %></td></tr>
<tr><td>Password</td><td><%= f.password_field :pass_crypt, {:value => '', :size => 50, :maxlength => 255} %></td></tr>
<tr><td>Confirm Password</td><td><%= f.password_field :pass_crypt_confirmation, {:value => '', :size => 50, :maxlength => 255} %></td></tr>
<table id="accountForm">
<tr><td class="fieldName">Display Name : </td><td><%= f.text_field :display_name %></td></tr>
<tr><td class="fieldName">Email : </td><td><%= f.text_field :email, {:size => 50, :maxlength => 255} %> <span class="minorNote">(never displayed publicly)</span></td></tr>
<tr><td class="fieldName" style="padding-bottom:0px;">Password : </td><td style="padding-bottom:0px;"><%= f.password_field :pass_crypt, {:value => '', :size => 30, :maxlength => 255} %></td></tr>
<tr><td class="fieldName">Confirm Password : </td><td><%= f.password_field :pass_crypt_confirmation, {:value => '', :size => 30, :maxlength => 255} %></td></tr>
<tr><td valign="top">Description</td><td><%= f.text_area :description, :class => "editDescription" %></td></tr>
<tr>
<td class="fieldName" valign="top">Public editing :</td>
<td>
<% if @user.data_public? %>
Enabled. Not anonymous <span class="minorNote">(<a href="http://wiki.openstreetmap.org/index.php/Disabling_anonymous_edits" target="_new">what's this?</a>)</span>
<% else %>
Disabled and anonymous. <span class="minorNote">(<a href="#public">why's this bad?</a>)</span>
<% end %>
</td>
</tr>
<tr id="homerow" <% unless @user.home_lat and @user.home_lon %> class="nohome" <%end%> ><td>Your home</td><td><em class="message">You have not entered your home location.</em><span class="location">Latitude: <%= f.text_field :home_lat, :size => 20, :id => "home_lat" %> Longitude <%= f.text_field :home_lon, :size => 20, :id => "home_lon" %></span></td></tr>
<tr><td class="fieldName" valign="top">Profile Description : </td><td><%= f.text_area :description, :rows => '5', :cols => '60' %><br /><br /></td></tr>
<tr id="homerow" <% unless @user.home_lat and @user.home_lon %> class="nohome" <%end%> ><td class="fieldName">Home Location : </td><td><em class="message">You have not entered your home location.</em><span class="location">Latitude: <%= f.text_field :home_lat, :size => 20, :id => "home_lat" %> Longitude <%= f.text_field :home_lon, :size => 20, :id => "home_lon" %></span></td></tr>
<tr><td></td><td>
<p>Update home location when I click on the map? <input type="checkbox" value="1" <% unless @user.home_lat and @user.home_lon %> checked="checked" <% end %> id="updatehome" /> </p>
<div id="map" style="border: 1px solid black; position: relative; width : 90%; height : 400px;"></div>
<div id="map" style="border:1px solid black; position:relative; width:500px; height:400px;"></div>
</td></tr>
<tr><td></td><td align=right><br/></br><%= submit_tag 'Save Changes' %></td></tr>
</table>
<%= submit_tag 'Save Changes' %>
<br/>
<% end %>
<%= render :partial => 'friend_map' %>
<% unless @user.data_public? %>
<a name="public"></a>
<h2>Public editing</h2>
<% if @user.data_public? %>
All your edits are public.
<% else %>
Currently your edits are anonymous and people can't send you messages or see your location. To show what you edited and allow people to contact you through the website, click the button below.
<b>You will need to do this if you want to use the online editor and it is encouraged</b> (<a href="http://wiki.openstreetmap.org/index.php/Disabling_anonymous_edits">find out why</a>).
This action cannot be reversed and all new users are now public by default.
Currently your edits are anonymous and people can't send you messages or see your location. To show what you edited and allow people to contact you through the website, click the button below.
<b>You will need to do this if you want to use the online editor and it is encouraged to do so</b> (<a href="http://wiki.openstreetmap.org/index.php/Disabling_anonymous_edits">find out why</a>).<br />
Your email address will not be revealed by becoming public.<br />
This action cannot be reversed and all new users are now public by default.<br />
<br /><br />
<%= button_to "Make all my edits public", :action => :go_public %>
<% end %>
<br/>
<br/>
<%= link_to 'return to profile', :controller => 'user', :action => @user.display_name %>
<br/>
<br/>

View file

@ -1,13 +1,13 @@
<h1>Login:</h1><br />
Please login or <%= link_to 'create an account', :controller => 'user', :action => 'new' %>.<br />
<h1>Login</h1>
<p>Please login or <%= link_to 'create an account', :controller => 'user', :action => 'new' %>.</p>
<% form_tag :action => 'login' do %>
<%= hidden_field_tag('referer', h(params[:referer])) %>
<table>
<tr><td>Email Address or username:</td><td><%= text_field('user', 'email',{:size => 50, :maxlength => 255}) %></td></tr>
<tr><td>Password:</td><td><%= password_field('user', 'password',{:size => 50, :maxlength => 255}) %></td></tr>
<tr><td class="fieldName">Email Address or Username:</td><td><%= text_field('user', 'email',{:size => 50, :maxlength => 255, :tabindex => 1}) %></td></tr>
<tr><td class="fieldName">Password:</td><td><%= password_field('user', 'password',{:size => 28, :maxlength => 255, :tabindex => 2}) %> <span class="minorNote">(<%= link_to 'Lost your password?', :controller => 'user', :action => 'lost_password' %>)</span></td></tr>
<tr><td colspan=2>&nbsp;<!--vertical spacer--></td></tr>
<tr><td></td><td align="right"><%= submit_tag 'Login', :tabindex => 3 %></td></tr>
</table>
<br />
<%= submit_tag 'Login' %>
<% end %> (<%= link_to 'Lost your password?', :controller => 'user', :action => 'lost_password' %>)
<% end %>

View file

@ -1,4 +1,4 @@
<h1>Create a user account</h1>
<h1>Create a User Account</h1>
<% if Acl.find_by_address(request.remote_ip, :conditions => {:k => "no_account_creation"}) %>
@ -27,17 +27,19 @@
<%= error_messages_for 'user' %>
<% form_tag :action => 'save' do %>
<table>
<tr><td>Email Address</td><td><%= text_field('user', 'email',{:size => 50, :maxlength => 255}) %></td></tr>
<tr><td>Confirm Email Address</td><td><%= text_field('user', 'email_confirmation',{:size => 50, :maxlength => 255}) %></td></tr>
<tr><td>Display Name</td><td><%= text_field('user', 'display_name',{:size => 50, :maxlength => 255}) %></td></tr>
<tr><td>Password</td><td><%= password_field('user', 'pass_crypt',{:size => 50, :maxlength => 255}) %></td></tr>
<tr><td>Confirm Password</td><td><%= password_field('user', 'pass_crypt_confirmation',{:size => 50, :maxlength => 255}) %></td></tr>
<table id="loginForm">
<tr><td class="fieldName">Email Address : </td><td><%= text_field('user', 'email',{:size => 50, :maxlength => 255, :tabindex => 1}) %></td></tr>
<tr><td class="fieldName">Confirm Email Address : </td><td><%= text_field('user', 'email_confirmation',{:size => 50, :maxlength => 255, :tabindex => 2}) %></td></tr>
<tr><td></td><td><span class="minorNote">Not displayed publicly (see <a href="http://wiki.openstreetmap.org/index.php/Privacy_Policy" title="wiki privacy policy including section on email addresses">privacy policy)</span></td></tr>
<tr><td colspan=2>&nbsp;<!--vertical spacer--></td></tr>
<tr><td class="fieldName">Display Name : </td><td><%= text_field('user', 'display_name',{:size => 30, :maxlength => 255, :tabindex => 3}) %></td></tr>
<tr><td colspan=2>&nbsp;<!--vertical spacer--></td></tr>
<tr><td class="fieldName">Password : </td><td><%= password_field('user', 'pass_crypt',{:size => 30, :maxlength => 255, :tabindex => 4}) %></td></tr>
<tr><td class="fieldName">Confirm Password : </td><td><%= password_field('user', 'pass_crypt_confirmation',{:size => 30, :maxlength => 255, :tabindex => 5}) %></td></tr>
<tr><td colspan=2>&nbsp;<!--vertical spacer--></td></tr>
<tr><td></td><td align=right><input type="submit" value="Signup" tabindex="6"></td></tr>
</table>
<br>
<br>
<input type="submit" value="Signup">
<% end %>
<% end %>

View file

@ -2,13 +2,17 @@
<h2><%= h(@this_user.display_name) %></h2>
<div id="userinformation">
<% if @user and @this_user.id == @user.id %>
<!-- Displaying user's own profile page -->
<%= link_to 'my diary', :controller => 'diary_entry', :action => 'list', :display_name => @user.display_name %>
| <%= link_to 'new diary entry', :controller => 'diary_entry', :action => 'new', :display_name => @user.display_name %>
| <%= link_to 'my edits', :controller => 'changeset', :action => 'list_user', :display_name => @user.display_name %>
| <%= link_to 'my traces', :controller => 'trace', :action=>'mine' %>
| <%= link_to 'my settings', :controller => 'user', :action => 'account', :display_name => @user.display_name %>
<% else %>
<!-- Displaying another user's profile page -->
<%= link_to 'send message', :controller => 'message', :action => 'new', :user_id => @this_user.id %>
| <%= link_to 'diary', :controller => 'diary_entry', :action => 'list', :display_name => @this_user.display_name %>
| <%= link_to 'edits', :controller => 'changeset', :action => 'list_user', :display_name => @this_user.display_name %>
| <%= link_to 'traces', :controller => 'trace', :action => 'view', :display_name => @this_user.display_name %>
| <% if @user and @user.is_friends_with?(@this_user) %>
<%= link_to 'remove as friend', :controller => 'user', :action => 'remove_friend', :display_name => @this_user.display_name %>
@ -18,6 +22,12 @@
<% end %>
</div>
<% if @this_user != nil %>
<P>
<b>Mapper since : </b><%= @this_user.creation_time %> (<%= time_ago_in_words(@this_user.creation_time) %> ago)
</P>
<% end %>
<h3>User image</h3>
<% if @this_user.image %>
<%= image_tag url_for_file_column(@this_user, "image") %>
@ -92,3 +102,9 @@
<% end %>
<% end %>
<% end %>
<br/>
<br/>
<% if @user and @this_user.id == @user.id %>
<%= link_to 'change your settings', :controller => 'user', :action => 'account', :display_name => @user.display_name %>
<% end %>

View file

@ -3,8 +3,10 @@ standard_settings: &standard_settings
max_request_area: 0.25
# Number of GPS trace/trackpoints returned per-page
tracepoints_per_page: 5000
# Maximum number of nodes
# Maximum number of nodes that will be returned by the api in a map request
max_number_of_nodes: 50000
# Maximum number of nodes that can be in a way (checked on save)
max_number_of_way_nodes: 2000
development:
<<: *standard_settings

View file

@ -16,6 +16,7 @@ development:
username: openstreetmap
password: openstreetmap
host: localhost
encoding: utf8
# Warning: The database defined as 'test' will be erased and
# re-generated from your development database when you run 'rake'.
@ -23,14 +24,15 @@ development:
test:
adapter: mysql
database: osm_test
username: root
password:
username: osm_test
password: osm_test
host: localhost
encoding: utf8
production:
adapter: mysql
database: openstreetmap
username: openstreetmap
password: openstreetmap
host: db.openstreetmap.org
database: osm
username: osm
password: osm
host: localhost
encoding: utf8

View file

@ -5,13 +5,16 @@
ENV['RAILS_ENV'] ||= 'production'
# Specifies gem version of Rails to use when vendor/rails is not present
RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION
RAILS_GEM_VERSION = '2.1.2' unless defined? RAILS_GEM_VERSION
# Set the server URL
SERVER_URL = ENV['OSM_SERVER_URL'] || 'www.openstreetmap.org'
# Set the generator
GENERATOR = ENV['OSM_SERVER_GENERATOR'] || 'OpenStreetMap server'
# Application constants needed for routes.rb - must go before Initializer call
API_VERSION = ENV['OSM_API_VERSION'] || '0.5'
API_VERSION = ENV['OSM_API_VERSION'] || '0.6'
# Set application status - possible settings are:
#
@ -38,6 +41,16 @@ Rails::Initializer.run do |config|
config.frameworks -= [ :active_record ]
end
# Specify gems that this application depends on.
# They can then be installed with "rake gems:install" on new installations.
# config.gem "bj"
# config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
# config.gem "aws-s3", :lib => "aws/s3"
config.gem 'composite_primary_keys', :version => '1.1.0'
config.gem 'libxml-ruby', :version => '>= 1.1.1', :lib => 'libxml'
config.gem 'rmagick', :lib => 'RMagick'
config.gem 'mysql'
# Only load the plugins named here, in the order given. By default, all plugins
# in vendor/plugins are loaded in alphabetical order.
# :all can be used as a placeholder for all plugins not explicitly named
@ -64,6 +77,12 @@ Rails::Initializer.run do |config|
# (create the session table with 'rake db:sessions:create')
config.action_controller.session_store = :sql_session_store
# We will use the old style of migrations, rather than the newer
# timestamped migrations that were introduced with Rails 2.1, as
# it will be confusing to have the numbered and timestamped migrations
# together in the same folder.
config.active_record.timestamped_migrations = false
# Use SQL instead of Active Record's schema dumper when creating the test database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
@ -73,5 +92,5 @@ Rails::Initializer.run do |config|
# config.active_record.observers = :cacher, :garbage_collector
# Make Active Record use UTC-base instead of local time
# config.active_record.default_timezone = :utc
config.active_record.default_timezone = :utc
end

View file

@ -12,7 +12,6 @@ config.whiny_nils = true
config.action_controller.consider_all_requests_local = true
config.action_view.debug_rjs = true
config.action_controller.perform_caching = false
config.action_view.cache_template_extensions = false
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false
config.action_mailer.raise_delivery_errors = false

View file

@ -1,3 +0,0 @@
require 'rubygems'
gem 'composite_primary_keys', '= 0.9.93'
require 'composite_primary_keys'

View file

@ -1,7 +1,3 @@
require 'rubygems'
gem 'libxml-ruby', '>= 1.1.1'
require 'libxml'
# This is required otherwise libxml writes out memory errors to
# the standard output and exits uncleanly
LibXML::XML::Error.set_handler do |message|

View file

@ -1,2 +1,7 @@
# Use the MySQL interface for SqlSessionStore
SqlSessionStore.session_class = MysqlSession
# Work out which session store adapter to use
environment = Rails.configuration.environment
adapter = Rails.configuration.database_configuration[environment]["adapter"]
session_class = adapter + "_session"
# Configure SqlSessionStore
SqlSessionStore.session_class = session_class.camelize.constantize

View file

@ -12,6 +12,7 @@ secondary 0xFDBF6F 1 -
tertiary 0xFEFECB 1 -
unclassified 0xE8E8E8 1 -
residential 0xE8E8E8 1 -
road 0xAAAAAA 1 -
footway 0xFF6644 - -
cycleway 0xFF6644 - -
bridleway 0xFF6644 - -

View file

@ -2,11 +2,21 @@ ActionController::Routing::Routes.draw do |map|
# API
map.connect "api/capabilities", :controller => 'api', :action => 'capabilities'
map.connect "api/#{API_VERSION}/changeset/create", :controller => 'changeset', :action => 'create'
map.connect "api/#{API_VERSION}/changeset/:id/upload", :controller => 'changeset', :action => 'upload', :id => /\d+/
map.connect "api/#{API_VERSION}/changeset/:id/download", :controller => 'changeset', :action => 'download', :id => /\d+/
map.connect "api/#{API_VERSION}/changeset/:id/expand_bbox", :controller => 'changeset', :action => 'expand_bbox', :id => /\d+/
map.connect "api/#{API_VERSION}/changeset/:id", :controller => 'changeset', :action => 'read', :id => /\d+/, :conditions => { :method => :get }
map.connect "api/#{API_VERSION}/changeset/:id", :controller => 'changeset', :action => 'update', :id => /\d+/, :conditions => { :method => :put }
map.connect "api/#{API_VERSION}/changeset/:id/close", :controller => 'changeset', :action => 'close', :id =>/\d+/
map.connect "api/#{API_VERSION}/changesets", :controller => 'changeset', :action => 'query'
map.connect "api/#{API_VERSION}/node/create", :controller => 'node', :action => 'create'
map.connect "api/#{API_VERSION}/node/:id/ways", :controller => 'way', :action => 'ways_for_node', :id => /\d+/
map.connect "api/#{API_VERSION}/node/:id/relations", :controller => 'relation', :action => 'relations_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/:version", :controller => 'old_node', :action => 'version', :id => /\d+/, :version => /\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 }
@ -16,12 +26,12 @@ ActionController::Routing::Routes.draw do |map|
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+/
map.connect "api/#{API_VERSION}/way/:id/relations", :controller => 'relation', :action => 'relations_for_way', :id => /\d+/
map.connect "api/#{API_VERSION}/way/:id/:version", :controller => 'old_way', :action => 'version', :id => /\d+/, :version => /\d+/
map.connect "api/#{API_VERSION}/way/:id", :controller => 'way', :action => 'read', :id => /\d+/, :conditions => { :method => :get }
map.connect "api/#{API_VERSION}/way/:id", :controller => 'way', :action => 'update', :id => /\d+/, :conditions => { :method => :put }
map.connect "api/#{API_VERSION}/way/:id", :controller => 'way', :action => 'delete', :id => /\d+/, :conditions => { :method => :delete }
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/relations", :controller => 'relation', :action => 'relations_for_relation', :id => /\d+/
map.connect "api/#{API_VERSION}/relation/:id/history", :controller => 'old_relation', :action => 'history', :id => /\d+/
@ -54,14 +64,14 @@ ActionController::Routing::Routes.draw do |map|
map.connect "api/#{API_VERSION}/gpx/:id/details", :controller => 'trace', :action => 'api_details'
map.connect "api/#{API_VERSION}/gpx/:id/data", :controller => 'trace', :action => 'api_data'
# Potlatch API
# AMF (ActionScript) API
map.connect "api/#{API_VERSION}/amf/read", :controller =>'amf', :action =>'amf_read'
map.connect "api/#{API_VERSION}/amf/write", :controller =>'amf', :action =>'amf_write'
map.connect "api/#{API_VERSION}/swf/trackpoints", :controller =>'swf', :action =>'trackpoints'
# Data browsing
map.connect '/browse', :controller => 'browse', :action => 'index'
map.connect '/browse', :controller => 'changeset', :action => 'list'
map.connect '/browse/start', :controller => 'browse', :action => 'start'
map.connect '/browse/way/:id', :controller => 'browse', :action => 'way', :id => /\d+/
map.connect '/browse/way/:id/history', :controller => 'browse', :action => 'way_history', :id => /\d+/
@ -69,11 +79,13 @@ ActionController::Routing::Routes.draw do |map|
map.connect '/browse/node/:id/history', :controller => 'browse', :action => 'node_history', :id => /\d+/
map.connect '/browse/relation/:id', :controller => 'browse', :action => 'relation', :id => /\d+/
map.connect '/browse/relation/:id/history', :controller => 'browse', :action => 'relation_history', :id => /\d+/
map.connect '/browse/changeset/:id', :controller => 'browse', :action => 'changeset', :id => /\d+/
map.connect '/browse/changesets', :controller => 'changeset', :action => 'list'
# web site
map.connect '/', :controller => 'site', :action => 'index'
map.connect '/edit', :controller => 'site', :action => 'edit'
map.connect '/history', :controller => 'changeset', :action => 'list_bbox'
map.connect '/export', :controller => 'site', :action => 'export'
map.connect '/login', :controller => 'user', :action => 'login'
map.connect '/logout', :controller => 'user', :action => 'logout'
@ -90,6 +102,7 @@ ActionController::Routing::Routes.draw do |map|
map.connect '/index.html', :controller => 'site', :action => 'index'
map.connect '/edit.html', :controller => 'site', :action => 'edit'
map.connect '/history.html', :controller => 'changeset', :action => 'list_bbox'
map.connect '/export.html', :controller => 'site', :action => 'export'
map.connect '/search.html', :controller => 'way_tag', :action => 'search'
map.connect '/login.html', :controller => 'user', :action => 'login'
@ -126,6 +139,7 @@ ActionController::Routing::Routes.draw do |map|
# user pages
map.connect '/user/:display_name', :controller => 'user', :action => 'view'
map.connect '/user/:display_name/edits', :controller => 'changeset', :action => 'list_user'
map.connect '/user/:display_name/make_friend', :controller => 'user', :action => 'make_friend'
map.connect '/user/:display_name/remove_friend', :controller => 'user', :action => 'remove_friend'
map.connect '/user/:display_name/diary', :controller => 'diary_entry', :action => 'list'
@ -140,7 +154,8 @@ ActionController::Routing::Routes.draw do |map|
map.connect '/diary/rss', :controller => 'diary_entry', :action => 'rss'
map.connect '/diary/:language', :controller => 'diary_entry', :action => 'list'
map.connect '/diary/:language/rss', :controller => 'diary_entry', :action => 'rss'
# test pages
map.connect '/test/populate/:table/:from/:count', :controller => 'test', :action => 'populate'
map.connect '/test/populate/:table/:count', :controller => 'test', :action => 'populate', :from => 1

View file

@ -25,12 +25,12 @@ $ mysql -u <uid> -p
> flush privileges;
> exit
Creating functions
====================
Creating functions For MySQL
==============================
Run this command in the db/functions directory:
$ make
$ make libmyosm.so
Make sure the db/functions directory is on the MySQL server's library
path and restart the MySQL server.
@ -49,6 +49,22 @@ $ mysql -u <uid> -p openstreetmap
> create function maptile_for_point returns integer soname 'libmyosm.so';
> exit
Creating functions for PgSQL
==============================
Run this command in the db/functions directory:
$ make libpgosm.so
Now create the function as follows:
$ psql openstreetmap
(This may need authentication or a -u <dbowneruid>)
> CREATE FUNCTION maptile_for_point(int8, int8, int4) RETURNS int4
AS '/path/to/rails-port/db/functions/libpgosm.so', 'maptile_for_point'
LANGUAGE C STRICT;
Creating database skeleton tables
===================================

View file

@ -7,11 +7,22 @@ else
LDFLAGS=-shared
endif
libmyosm.so: quadtile.o maptile.o
cc ${LDFLAGS} -o libmyosm.so quadtile.o maptile.o
all: libmyosm.so libpgosm.so
clean:
$(RM) *.so *.o
libmyosm.so: quadtile.o maptile-mysql.o
cc ${LDFLAGS} -o libmyosm.so quadtile.o maptile-mysql.o
libpgosm.so: maptile-pgsql.o
cc ${LDFLAGS} -o libpgosm.so maptile-pgsql.o
quadtile.o: quadtile.c ${QTDIR}/quad_tile.h
cc `mysql_config --include` -I${QTDIR} -fPIC -O3 -c -o quadtile.o quadtile.c
maptile.o: maptile.c
cc `mysql_config --include` -fPIC -O3 -c -o maptile.o maptile.c
maptile-mysql.o: maptile.c
cc `mysql_config --include` -fPIC -O3 -DUSE_MYSQL -c -o maptile-mysql.o maptile.c
maptile-pgsql.o: maptile.c
cc -I `pg_config --includedir-server` -O3 -fPIC -DUSE_PGSQL -c -o maptile-pgsql.o maptile.c

View file

@ -1,3 +1,31 @@
#ifndef USE_MYSQL
#ifndef USE_PGSQL
#error One of USE_MYSQL or USE_PGSQL must be defined
#endif
#endif
#include <math.h>
/* The real maptile-for-point functionality is here */
static long long internal_maptile_for_point(double lat, double lon, long long zoom)
{
double scale = pow(2, zoom);
double r_per_d = M_PI / 180;
unsigned int x;
unsigned int y;
x = floor((lon + 180.0) * scale / 360.0);
y = floor((1 - log(tan(lat * r_per_d) + 1.0 / cos(lat * r_per_d)) / M_PI) * scale / 2.0);
return (x << zoom) | y;
}
#ifdef USE_MYSQL
#ifdef USE_PGSQL
#error ONLY one of USE_MYSQL and USE_PGSQL should be defined
#endif
#include <my_global.h>
#include <my_sys.h>
#include <m_string.h>
@ -27,13 +55,39 @@ long long maptile_for_point(UDF_INIT *initid, UDF_ARGS *args, char *is_null, cha
double lat = *(long long *)args->args[0] / 10000000.0;
double lon = *(long long *)args->args[1] / 10000000.0;
long long zoom = *(long long *)args->args[2];
double scale = pow(2, zoom);
double r_per_d = M_PI / 180;
unsigned int x;
unsigned int y;
x = floor((lon + 180.0) * scale / 360.0);
y = floor((1 - log(tan(lat * r_per_d) + 1.0 / cos(lat * r_per_d)) / M_PI) * scale / 2.0);
return (x << zoom) | y;
return internal_maptile_for_point(lat, lon, zoom);
}
#endif
#ifdef USE_PGSQL
#include <postgres.h>
#include <fmgr.h>
Datum
maptile_for_point(PG_FUNCTION_ARGS)
{
double lat = PG_GETARG_INT64(0) / 10000000.0;
double lon = PG_GETARG_INT64(1) / 10000000.0;
int zoom = PG_GETARG_INT32(2);
PG_RETURN_INT32(internal_maptile_for_point(lat, lon, zoom));
}
PG_FUNCTION_INFO_V1(maptile_for_point);
/*
* To bind this into PGSQL, try something like:
*
* CREATE FUNCTION maptile_for_point(int8, int8, int4) RETURNS int4
* AS '/path/to/rails-port/db/functions/libpgosm', 'maptile_for_point'
* LANGUAGE C STRICT;
*
* (without all the *s)
*/
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
#endif

View file

@ -16,7 +16,7 @@ class CreateOsmDb < ActiveRecord::Migration
add_index "current_nodes", ["latitude", "longitude"], :name => "current_nodes_lat_lon_idx"
add_index "current_nodes", ["timestamp"], :name => "current_nodes_timestamp_idx"
change_column "current_nodes", "id", :bigint, :limit => 64, :null => false, :options => "AUTO_INCREMENT"
change_column :current_nodes, :id, :bigint_auto_64
create_table "current_segments", innodb_table do |t|
t.column "id", :bigint, :limit => 64, :null => false
@ -32,7 +32,7 @@ class CreateOsmDb < ActiveRecord::Migration
add_index "current_segments", ["node_a"], :name => "current_segments_a_idx"
add_index "current_segments", ["node_b"], :name => "current_segments_b_idx"
change_column "current_segments", "id", :bigint, :limit => 64, :null => false, :options => "AUTO_INCREMENT"
change_column :current_segments, :id, :bigint_auto_64
create_table "current_way_segments", innodb_table do |t|
t.column "id", :bigint, :limit => 64
@ -50,21 +50,17 @@ class CreateOsmDb < ActiveRecord::Migration
end
add_index "current_way_tags", ["id"], :name => "current_way_tags_id_idx"
execute "CREATE FULLTEXT INDEX `current_way_tags_v_idx` ON `current_way_tags` (`v`)"
add_fulltext_index "current_way_tags", "v"
create_table "current_ways", myisam_table do |t|
t.column "id", :bigint, :limit => 64, :null => false
t.column "id", :bigint_pk_64, :null => false
t.column "user_id", :bigint, :limit => 20
t.column "timestamp", :datetime
t.column "visible", :boolean
end
add_primary_key "current_ways", ["id"]
change_column "current_ways", "id", :bigint, :limit => 64, :null => false, :options => "AUTO_INCREMENT"
create_table "diary_entries", myisam_table do |t|
t.column "id", :bigint, :limit => 20, :null => false
t.column "id", :bigint_pk, :null => false
t.column "user_id", :bigint, :limit => 20, :null => false
t.column "title", :string
t.column "body", :text
@ -72,21 +68,14 @@ class CreateOsmDb < ActiveRecord::Migration
t.column "updated_at", :datetime
end
add_primary_key "diary_entries", ["id"]
change_column "diary_entries", "id", :bigint, :limit => 20, :null => false, :options => "AUTO_INCREMENT"
create_table "friends", myisam_table do |t|
t.column "id", :bigint, :limit => 20, :null => false
t.column "id", :bigint_pk, :null => false
t.column "user_id", :bigint, :limit => 20, :null => false
t.column "friend_user_id", :bigint, :limit => 20, :null => false
end
add_primary_key "friends", ["id"]
add_index "friends", ["friend_user_id"], :name => "user_id_idx"
change_column "friends", "id", :bigint, :limit => 20, :null => false, :options => "AUTO_INCREMENT"
create_table "gps_points", myisam_table do |t|
t.column "altitude", :float
t.column "user_id", :integer, :limit => 20
@ -104,16 +93,13 @@ class CreateOsmDb < ActiveRecord::Migration
create_table "gpx_file_tags", myisam_table do |t|
t.column "gpx_id", :bigint, :limit => 64, :default => 0, :null => false
t.column "tag", :string
t.column "id", :integer, :limit => 20, :null => false
t.column "id", :bigint_pk, :null => false
end
add_primary_key "gpx_file_tags", ["id"]
add_index "gpx_file_tags", ["gpx_id"], :name => "gpx_file_tags_gpxid_idx"
change_column "gpx_file_tags", "id", :integer, :limit => 20, :null => false, :options => "AUTO_INCREMENT"
create_table "gpx_files", myisam_table do |t|
t.column "id", :bigint, :limit => 64, :null => false
t.column "id", :bigint_pk_64, :null => false
t.column "user_id", :bigint, :limit => 20
t.column "visible", :boolean, :default => true, :null => false
t.column "name", :string, :default => "", :null => false
@ -126,12 +112,9 @@ class CreateOsmDb < ActiveRecord::Migration
t.column "inserted", :boolean
end
add_primary_key "gpx_files", ["id"]
add_index "gpx_files", ["timestamp"], :name => "gpx_files_timestamp_idx"
add_index "gpx_files", ["visible", "public"], :name => "gpx_files_visible_public_idx"
change_column "gpx_files", "id", :bigint, :limit => 64, :null => false, :options => "AUTO_INCREMENT"
create_table "gpx_pending_files", myisam_table do |t|
t.column "originalname", :string
t.column "tmpname", :string
@ -139,7 +122,7 @@ class CreateOsmDb < ActiveRecord::Migration
end
create_table "messages", myisam_table do |t|
t.column "id", :bigint, :limit => 20, :null => false
t.column "id", :bigint_pk, :null => false
t.column "user_id", :bigint, :limit => 20, :null => false
t.column "from_user_id", :bigint, :limit => 20, :null => false
t.column "from_display_name", :string, :default => ""
@ -150,21 +133,14 @@ class CreateOsmDb < ActiveRecord::Migration
t.column "to_user_id", :bigint, :limit => 20, :null => false
end
add_primary_key "messages", ["id"]
add_index "messages", ["from_display_name"], :name => "from_name_idx"
change_column "messages", "id", :bigint, :limit => 20, :null => false, :options => "AUTO_INCREMENT"
create_table "meta_areas", myisam_table do |t|
t.column "id", :bigint, :limit => 64, :null => false
t.column "id", :bigint_pk_64, :null => false
t.column "user_id", :bigint, :limit => 20
t.column "timestamp", :datetime
end
add_primary_key "meta_areas", ["id"]
change_column "meta_areas", "id", :bigint, :limit => 64, :null => false, :options => "AUTO_INCREMENT"
create_table "nodes", myisam_table do |t|
t.column "id", :bigint, :limit => 64
t.column "latitude", :double
@ -194,7 +170,7 @@ class CreateOsmDb < ActiveRecord::Migration
create_table "users", innodb_table do |t|
t.column "email", :string
t.column "id", :bigint, :limit => 20, :null => false
t.column "id", :bigint_pk, :null => false
t.column "token", :string
t.column "active", :integer, :default => 0, :null => false
t.column "pass_crypt", :string
@ -211,12 +187,9 @@ class CreateOsmDb < ActiveRecord::Migration
t.column "home_zoom", :integer, :limit => 2, :default => 3
end
add_primary_key "users", ["id"]
add_index "users", ["email"], :name => "users_email_idx"
add_index "users", ["display_name"], :name => "users_display_name_idx"
change_column "users", "id", :bigint, :limit => 20, :null => false, :options => "AUTO_INCREMENT"
create_table "way_segments", myisam_table do |t|
t.column "id", :bigint, :limit => 64, :default => 0, :null => false
t.column "segment_id", :integer
@ -226,7 +199,7 @@ class CreateOsmDb < ActiveRecord::Migration
add_primary_key "way_segments", ["id", "version", "sequence_id"]
change_column "way_segments", "sequence_id", :bigint, :limit => 11, :null => false, :options => "AUTO_INCREMENT"
change_column "way_segments", "sequence_id", :bigint_auto_11
create_table "way_tags", myisam_table do |t|
t.column "id", :bigint, :limit => 64, :default => 0, :null => false
@ -248,7 +221,7 @@ class CreateOsmDb < ActiveRecord::Migration
add_primary_key "ways", ["id", "version"]
add_index "ways", ["id"], :name => "ways_id_version_idx"
change_column "ways", "version", :bigint, :limit => 20, :null => false, :options => "AUTO_INCREMENT"
change_column "ways", "version", :bigint_auto_20
end
def self.down

View file

@ -29,7 +29,7 @@ class CleanupOsmDb < ActiveRecord::Migration
change_column "current_ways", "user_id", :bigint, :limit => 20, :null => false
change_column "current_ways", "timestamp", :datetime, :null => false
change_column "current_ways", "visible", :boolean, :null => false
execute "ALTER TABLE current_ways ENGINE = InnoDB"
change_engine "current_ways", "InnoDB"
change_column "diary_entries", "title", :string, :null => false
change_column "diary_entries", "body", :text, :null => false
@ -39,7 +39,9 @@ class CleanupOsmDb < ActiveRecord::Migration
add_index "friends", ["user_id"], :name => "friends_user_id_idx"
remove_index "gps_points", :name => "points_uid_idx"
remove_index "gps_points", :name => "points_idx"
remove_column "gps_points", "user_id"
add_index "gps_points", ["latitude", "longitude"], :name => "points_idx"
change_column "gps_points", "trackid", :integer, :null => false
change_column "gps_points", "latitude", :integer, :null => false
change_column "gps_points", "longitude", :integer, :null => false
@ -87,8 +89,8 @@ class CleanupOsmDb < ActiveRecord::Migration
change_column "users", "creation_time", :datetime, :null => false
change_column "users", "display_name", :string, :default => "", :null => false
change_column "users", "data_public", :boolean, :default => false, :null => false
change_column "users", "home_lat", :double
change_column "users", "home_lon", :double
change_column "users", "home_lat", :double, :default => nil
change_column "users", "home_lon", :double, :default => nil
remove_index "users", :name => "users_email_idx"
add_index "users", ["email"], :name => "users_email_idx", :unique => true
remove_index "users", :name => "users_display_name_idx"
@ -150,15 +152,11 @@ class CleanupOsmDb < ActiveRecord::Migration
change_column "nodes", "id", :bigint, :limit => 64
create_table "meta_areas", myisam_table do |t|
t.column "id", :bigint, :limit => 64, :null => false
t.column "id", :bigint_pk_64, :null => false
t.column "user_id", :bigint, :limit => 20
t.column "timestamp", :datetime
end
add_primary_key "meta_areas", ["id"]
change_column "meta_areas", "id", :bigint, :limit => 64, :null => false, :options => "AUTO_INCREMENT"
remove_index "messages", :name => "messages_to_user_id_idx"
change_column "messages", "message_read", :boolean, :default => false
change_column "messages", "sent_on", :datetime
@ -195,7 +193,7 @@ class CleanupOsmDb < ActiveRecord::Migration
change_column "diary_entries", "body", :text
change_column "diary_entries", "title", :string, :default => nil
execute "ALTER TABLE current_ways ENGINE = MyISAM"
change_engine "current_ways", "MyISAM"
change_column "current_ways", "visible", :boolean
change_column "current_ways", "timestamp", :datetime
change_column "current_ways", "user_id", :bigint, :limit => 20
@ -223,6 +221,6 @@ class CleanupOsmDb < ActiveRecord::Migration
change_column "current_nodes", "user_id", :bigint, :limit => 20
change_column "current_nodes", "longitude", :double
change_column "current_nodes", "latitude", :double
change_column "current_nodes", "id", :bigint, :limit => 64, :null => false, :options => "AUTO_INCREMENT"
change_column "current_nodes", "id", :bigint_auto_64
end
end

View file

@ -1,6 +1,6 @@
class SqlSessionStoreSetup < ActiveRecord::Migration
def self.up
create_table "sessions", :options => "ENGINE=InnoDB" do |t|
create_table "sessions", :options => innodb_option do |t|
t.column "session_id", :string
t.column "data", :text
t.column "created_at", :timestamp

View file

@ -13,18 +13,15 @@ class UserEnhancements < ActiveRecord::Migration
add_primary_key "user_preferences", ["user_id", "k"]
create_table "user_tokens", innodb_table do |t|
t.column "id", :bigint, :limit => 20, :null => false
t.column "id", :bigint_pk, :null => false
t.column "user_id", :bigint, :limit => 20, :null => false
t.column "token", :string, :null => false
t.column "expiry", :datetime, :null => false
end
add_primary_key "user_tokens", ["id"]
add_index "user_tokens", ["token"], :name => "user_tokens_token_idx", :unique => true
add_index "user_tokens", ["user_id"], :name => "user_tokens_user_id_idx"
change_column "user_tokens", "id", :bigint, :limit => 20, :null => false, :options => "AUTO_INCREMENT"
User.find(:all, :conditions => "token is not null").each do |user|
UserToken.create(:user_id => user.id, :token => user.token, :expiry => 1.week.from_now)
end

View file

@ -1,6 +1,6 @@
class TileTracepoints < ActiveRecord::Migration
def self.up
add_column "gps_points", "tile", :integer, :null => false, :unsigned => true
add_column "gps_points", "tile", :four_byte_unsigned
add_index "gps_points", ["tile"], :name => "points_tile_idx"
remove_index "gps_points", :name => "points_idx"

View file

@ -33,10 +33,12 @@ class TileNodes < ActiveRecord::Migration
end
def self.up
remove_index "current_nodes", :name => "current_nodes_timestamp_idx"
rename_table "current_nodes", "current_nodes_v5"
create_table "current_nodes", innodb_table do |t|
t.column "id", :bigint, :limit => 64, :null => false
t.column "id", :bigint_pk_64, :null => false
t.column "latitude", :integer, :null => false
t.column "longitude", :integer, :null => false
t.column "user_id", :bigint, :limit => 20, :null => false
@ -46,17 +48,17 @@ class TileNodes < ActiveRecord::Migration
t.column "tile", :integer, :null => false
end
add_primary_key "current_nodes", ["id"]
add_index "current_nodes", ["timestamp"], :name => "current_nodes_timestamp_idx"
add_index "current_nodes", ["tile"], :name => "current_nodes_tile_idx"
change_column "current_nodes", "id", :bigint, :limit => 64, :null => false, :options => "AUTO_INCREMENT"
change_column "current_nodes", "tile", :integer, :null => false, :unsigned => true
change_column "current_nodes", "tile", :four_byte_unsigned
upgrade_table "current_nodes_v5", "current_nodes", Node
drop_table "current_nodes_v5"
remove_index "nodes", :name=> "nodes_uid_idx"
remove_index "nodes", :name=> "nodes_timestamp_idx"
rename_table "nodes", "nodes_v5"
create_table "nodes", myisam_table do |t|
@ -74,7 +76,7 @@ class TileNodes < ActiveRecord::Migration
add_index "nodes", ["timestamp"], :name => "nodes_timestamp_idx"
add_index "nodes", ["tile"], :name => "nodes_tile_idx"
change_column "nodes", "tile", :integer, :null => false, :unsigned => true
change_column "nodes", "tile", :four_byte_unsigned
upgrade_table "nodes_v5", "nodes", OldNode
@ -85,7 +87,7 @@ class TileNodes < ActiveRecord::Migration
rename_table "current_nodes", "current_nodes_v6"
create_table "current_nodes", innodb_table do |t|
t.column "id", :bigint, :limit => 64, :null => false
t.column "id", :bigint_pk_64, :null => false
t.column "latitude", :double, :null => false
t.column "longitude", :double, :null => false
t.column "user_id", :bigint, :limit => 20, :null => false
@ -94,12 +96,9 @@ class TileNodes < ActiveRecord::Migration
t.column "timestamp", :datetime, :null => false
end
add_primary_key "current_nodes", ["id"]
add_index "current_nodes", ["latitude", "longitude"], :name => "current_nodes_lat_lon_idx"
add_index "current_nodes", ["timestamp"], :name => "current_nodes_timestamp_idx"
change_column "current_nodes", "id", :bigint, :limit => 64, :null => false, :options => "AUTO_INCREMENT"
downgrade_table "current_nodes_v6", "current_nodes"
drop_table "current_nodes_v6"

View file

@ -11,7 +11,7 @@ class AddRelations < ActiveRecord::Migration
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');"
alter_column_nwr_enum :current_relation_members, :member_type
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"
@ -24,18 +24,15 @@ class AddRelations < ActiveRecord::Migration
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`)"
add_fulltext_index "current_relation_tags", "v"
create_table "current_relations", innodb_table do |t|
t.column "id", :bigint, :limit => 64, :null => false
t.column "id", :bigint_pk_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
@ -44,7 +41,7 @@ class AddRelations < ActiveRecord::Migration
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');"
alter_column_nwr_enum :relation_members, :member_type
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"
@ -68,7 +65,7 @@ class AddRelations < ActiveRecord::Migration
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"
change_column "relations", "version", :bigint_auto_20
end

View file

@ -1,7 +1,7 @@
class DiaryComments < ActiveRecord::Migration
def self.up
create_table "diary_comments", myisam_table do |t|
t.column "id", :bigint, :limit => 20, :null => false
t.column "id", :bigint_pk, :null => false
t.column "diary_entry_id", :bigint, :limit => 20, :null => false
t.column "user_id", :bigint, :limit => 20, :null => false
t.column "body", :text, :null => false
@ -9,10 +9,8 @@ class DiaryComments < ActiveRecord::Migration
t.column "updated_at", :datetime, :null => false
end
add_primary_key "diary_comments", ["id"]
add_index "diary_comments", ["diary_entry_id", "id"], :name => "diary_comments_entry_id_idx", :unique => true
change_column "diary_comments", "id", :bigint, :limit => 20, :null => false, :options => "AUTO_INCREMENT"
end
def self.down

View file

@ -1,7 +1,7 @@
class AddEmailValid < ActiveRecord::Migration
def self.up
add_column "users", "email_valid", :boolean, :default => false, :null => false
User.update_all("email_valid = active")
User.update_all("email_valid = (active != 0)") #email_valid is :boolean, but active is :integer. "email_valid = active" (see r11802 or earlier) will fail for stricter dbs than mysql
end
def self.down

View file

@ -1,7 +1,7 @@
class AddUserVisible < ActiveRecord::Migration
def self.up
add_column "users", "visible", :boolean, :default => true, :null => false
User.update_all("visible = 1")
User.update_all(:visible => true)
end
def self.down

View file

@ -1,19 +1,14 @@
class CreateAcls < ActiveRecord::Migration
def self.up
create_table "acls", myisam_table do |t|
t.column "id", :integer, :null => false
t.column "address", :integer, :null => false
t.column "netmask", :integer, :null => false
t.column "k", :string, :null => false
t.column "id", :integer_pk, :null => false
t.column "address", :inet, :null => false
t.column "netmask", :inet, :null => false
t.column "k", :string, :null => false
t.column "v", :string
end
add_primary_key "acls", ["id"]
add_index "acls", ["k"], :name => "acls_k_idx"
change_column "acls", "id", :integer, :null => false, :options => "AUTO_INCREMENT"
change_column "acls", "address", :integer, :null => false, :unsigned => true
change_column "acls", "netmask", :integer, :null => false, :unsigned => true
end
def self.down

View file

@ -0,0 +1,11 @@
class AddTimestampIndexes < ActiveRecord::Migration
def self.up
add_index :current_ways, :timestamp, :name => :current_ways_timestamp_idx
add_index :current_relations, :timestamp, :name => :current_relations_timestamp_idx
end
def self.down
remove_index :current_ways, :name => :current_ways_timestamp_idx
remove_index :current_relations, :name => :current_relations_timestamp_idx
end
end

View file

@ -0,0 +1,60 @@
class PopulateNodeTagsAndRemove < ActiveRecord::Migration
def self.up
have_nodes = select_value("SELECT count(*) FROM current_nodes").to_i != 0
if have_nodes
prefix = File.join Dir.tmpdir, "020_populate_node_tags_and_remove.#{$$}."
cmd = "db/migrate/020_populate_node_tags_and_remove_helper"
src = "#{cmd}.c"
if not File.exists? cmd or File.mtime(cmd) < File.mtime(src) then
system 'cc -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 = ['nodes', 'node_tags', 'current_nodes', 'current_node_tags'].
map { |base| prefix + base }
nodes, node_tags, current_nodes, current_node_tags = tempfiles
end
execute "TRUNCATE nodes"
remove_column :nodes, :tags
remove_column :current_nodes, :tags
add_column :nodes, :version, :bigint, :limit => 20, :null => false
create_table :current_node_tags, innodb_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
create_table :node_tags, innodb_table do |t|
t.column :id, :bigint, :limit => 64, :null => false
t.column :version, :bigint, :limit => 20, :null => false
t.column :k, :string, :default => "", :null => false
t.column :v, :string, :default => "", :null => false
end
# now get the data back
csvopts = "FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\"' LINES TERMINATED BY '\\n'"
if have_nodes
execute "LOAD DATA INFILE '#{nodes}' INTO TABLE nodes #{csvopts} (id, latitude, longitude, user_id, visible, timestamp, tile, version)";
execute "LOAD DATA INFILE '#{node_tags}' INTO TABLE node_tags #{csvopts} (id, version, k, v)"
execute "LOAD DATA INFILE '#{current_node_tags}' INTO TABLE current_node_tags #{csvopts} (id, k, v)"
end
tempfiles.each { |fn| File.unlink fn } if have_nodes
end
def self.down
raise IrreversibleMigration.new
# add_column :nodes, "tags", :text, :default => "", :null => false
# add_column :current_nodes, "tags", :text, :default => "", :null => false
end
end

View file

@ -0,0 +1,241 @@
#include <mysql.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void exit_mysql_err(MYSQL *mysql) {
const char *err = mysql_error(mysql);
if (err) {
fprintf(stderr, "019_populate_node_tags_and_remove_helper: MySQL error: %s\n", err);
} else {
fprintf(stderr, "019_populate_node_tags_and_remove_helper: MySQL error\n");
}
abort();
exit(EXIT_FAILURE);
}
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 unescape(char *str) {
char *i = str, *o = str, tmp;
while (*i) {
if (*i == '\\') {
i++;
switch (tmp = *i++) {
case 's': *o++ = ';'; break;
case 'e': *o++ = '='; break;
case '\\': *o++ = '\\'; break;
default: *o++ = tmp; break;
}
} else {
*o++ = *i++;
}
}
}
static int read_node_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';
unescape(*k);
unescape(*v);
return 1;
}
struct data {
MYSQL *mysql;
size_t version_size;
uint16_t *version;
};
static void proc_nodes(struct data *d, const char *tbl, FILE *out, FILE *out_tags, int hist) {
MYSQL_RES *res;
MYSQL_ROW row;
char query[256];
snprintf(query, sizeof(query), "SELECT id, latitude, longitude, "
"user_id, visible, tags, timestamp, tile FROM %s", tbl);
if (mysql_query(d->mysql, query))
exit_mysql_err(d->mysql);
res = mysql_use_result(d->mysql);
if (!res) exit_mysql_err(d->mysql);
while ((row = mysql_fetch_row(res))) {
unsigned long id = strtoul(row[0], NULL, 10);
uint32_t version;
if (id >= d->version_size) {
fprintf(stderr, "preallocated nodes size exceeded");
abort();
}
if (hist) {
version = ++(d->version[id]);
fprintf(out, "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%u\"\n",
row[0], row[1], row[2], row[3], row[4], row[6], row[7], version);
} else {
/*fprintf(out, "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n",
row[0], row[1], row[2], row[3], row[4], row[6], row[7]);*/
}
char *tags_it = row[5], *k, *v;
while (read_node_tags(&tags_it, &k, &v)) {
if (hist) {
fprintf(out_tags, "\"%s\",\"%u\",", row[0], version);
} else {
fprintf(out_tags, "\"%s\",", row[0]);
}
write_csv_col(out_tags, k, ',');
write_csv_col(out_tags, v, '\n');
}
}
if (mysql_errno(d->mysql)) exit_mysql_err(d->mysql);
mysql_free_result(res);
}
static size_t select_size(MYSQL *mysql, const char *q) {
MYSQL_RES *res;
MYSQL_ROW row;
size_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 = strtoul(row[0], NULL, 10);
} else {
ret = 0;
}
mysql_free_result(res);
return ret;
}
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) {
size_t prefix_len;
FILE *current_nodes, *current_node_tags, *nodes, *node_tags;
char *tempfn;
struct data data, *d = &data;
if (argc != 8) {
printf("Usage: 019_populate_node_tags_and_remove_helper host user passwd database port socket prefix\n");
exit(EXIT_FAILURE);
}
d->mysql = connect_to_mysql(argv);
d->version_size = 1 + select_size(d->mysql, "SELECT max(id) FROM current_nodes");
d->version = (uint16_t *) malloc(sizeof(uint16_t) * d->version_size);
if (!d->version) {
perror("malloc");
abort();
exit(EXIT_FAILURE);
}
memset(d->version, 0, sizeof(uint16_t) * d->version_size);
prefix_len = strlen(argv[7]);
tempfn = (char *) malloc(prefix_len + 32);
strcpy(tempfn, argv[7]);
strcpy(tempfn + prefix_len, "current_nodes");
open_file(&current_nodes, tempfn);
strcpy(tempfn + prefix_len, "current_node_tags");
open_file(&current_node_tags, tempfn);
strcpy(tempfn + prefix_len, "nodes");
open_file(&nodes, tempfn);
strcpy(tempfn + prefix_len, "node_tags");
open_file(&node_tags, tempfn);
free(tempfn);
proc_nodes(d, "nodes", nodes, node_tags, 1);
proc_nodes(d, "current_nodes", current_nodes, current_node_tags, 0);
free(d->version);
mysql_close(d->mysql);
fclose(current_nodes);
fclose(current_node_tags);
fclose(nodes);
fclose(node_tags);
exit(EXIT_SUCCESS);
}

View file

@ -0,0 +1,45 @@
class MoveToInnodb < ActiveRecord::Migration
@@conv_tables = ['nodes', 'ways', 'way_tags', 'way_nodes',
'current_way_tags', 'relation_members',
'relations', 'relation_tags', 'current_relation_tags']
@@ver_tbl = ['nodes', 'ways', 'relations']
def self.up
remove_index :current_way_tags, :name=> :current_way_tags_v_idx
remove_index :current_relation_tags, :name=> :current_relation_tags_v_idx
@@ver_tbl.each { |tbl|
change_column tbl, "version", :bigint, :limit => 20, :null => false
}
@@conv_tables.each { |tbl|
change_engine (tbl, "InnoDB")
}
@@ver_tbl.each { |tbl|
add_column "current_#{tbl}", "version", :bigint, :limit => 20, :null => false
# As the initial version of all nodes, ways and relations is 0, we set the
# current version to something less so that we can update the version in
# batches of 10000
tbl.classify.constantize.update_all("version=-1")
while tbl.classify.constantize.count(:conditions => {:version => -1}) > 0
tbl.classify.constantize.update_all("version=(SELECT max(version) FROM #{tbl} WHERE #{tbl}.id = current_#{tbl}.id)", {:version => -1}, :limit => 10000)
end
# execute "UPDATE current_#{tbl} SET version = " +
# "(SELECT max(version) FROM #{tbl} WHERE #{tbl}.id = current_#{tbl}.id)"
# The above update causes a MySQL error:
# -- add_column("current_nodes", "version", :bigint, {:null=>false, :limit=>20})
# -> 1410.9152s
# -- execute("UPDATE current_nodes SET version = (SELECT max(version) FROM nodes WHERE nodes.id = current_nodes.id)")
# rake aborted!
# Mysql::Error: The total number of locks exceeds the lock table size: UPDATE current_nodes SET version = (SELECT max(version) FROM nodes WHERE nodes.id = current_nodes.id)
# The above rails version will take longer, however will no run out of locks
}
end
def self.down
raise IrreversibleMigration.new
end
end

View file

@ -0,0 +1,50 @@
class KeyConstraints < ActiveRecord::Migration
def self.up
# Primary keys
add_primary_key :current_node_tags, [:id, :k]
add_primary_key :current_way_tags, [:id, :k]
add_primary_key :current_relation_tags, [:id, :k]
add_primary_key :node_tags, [:id, :version, :k]
add_primary_key :way_tags, [:id, :version, :k]
add_primary_key :relation_tags, [:id, :version, :k]
add_primary_key :nodes, [:id, :version]
# Remove indexes superseded by primary keys
remove_index :current_way_tags, :name => :current_way_tags_id_idx
remove_index :current_relation_tags, :name => :current_relation_tags_id_idx
remove_index :way_tags, :name => :way_tags_id_version_idx
remove_index :relation_tags, :name => :relation_tags_id_version_idx
remove_index :nodes, :name => :nodes_uid_idx
# Foreign keys (between ways, way_tags, way_nodes, etc.)
add_foreign_key :current_node_tags, [:id], :current_nodes
add_foreign_key :node_tags, [:id, :version], :nodes
add_foreign_key :current_way_tags, [:id], :current_ways
add_foreign_key :current_way_nodes, [:id], :current_ways
add_foreign_key :way_tags, [:id, :version], :ways
add_foreign_key :way_nodes, [:id, :version], :ways
add_foreign_key :current_relation_tags, [:id], :current_relations
add_foreign_key :current_relation_members, [:id], :current_relations
add_foreign_key :relation_tags, [:id, :version], :relations
add_foreign_key :relation_members, [:id, :version], :relations
# Foreign keys (between different types of primitives)
add_foreign_key :current_way_nodes, [:node_id], :current_nodes, [:id]
# FIXME: We don't have foreign keys for relation members since the id
# might point to a different table depending on the `type' column.
# We'd probably need different current_relation_member_nodes,
# current_relation_member_ways and current_relation_member_relations
# tables for this to work cleanly.
end
def self.down
raise IrreversibleMigration.new
end
end

View file

@ -0,0 +1,46 @@
class AddChangesets < ActiveRecord::Migration
@@conv_user_tables = ['current_nodes',
'current_relations', 'current_ways', 'nodes', 'relations', 'ways' ]
def self.up
create_table "changesets", innodb_table do |t|
t.column "id", :bigint_pk, :null => false
t.column "user_id", :bigint, :limit => 20, :null => false
t.column "created_at", :datetime, :null => false
t.column "open", :boolean, :null => false, :default => true
t.column "min_lat", :integer, :null => true
t.column "max_lat", :integer, :null => true
t.column "min_lon", :integer, :null => true
t.column "max_lon", :integer, :null => true
end
create_table "changeset_tags", innodb_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 "changeset_tags", ["id"], :name => "changeset_tags_id_idx"
#
# Initially we will have one changeset for every user containing
# all edits up to the API change,
# all the changesets will have the id of the user that made them.
# We need to generate a changeset for each user in the database
execute "INSERT INTO changesets (id, user_id, created_at, open)" +
"SELECT id, id, creation_time, false from users;"
@@conv_user_tables.each { |tbl|
rename_column tbl, :user_id, :changeset_id
#foreign keys too
add_foreign_key tbl, [:changeset_id], :changesets, [:id]
}
end
def self.down
# It's not easy to generate the user ids from the changesets
raise IrreversibleMigration.new
#drop_table "changesets"
#drop_table "changeset_tags"
end
end

Some files were not shown because too many files have changed in this diff Show more