diff options
author | Jochen Topf <jochen@topf.org> | 2013-01-14 19:53:21 +0100 |
---|---|---|
committer | Jochen Topf <jochen@topf.org> | 2013-01-14 19:53:21 +0100 |
commit | 9f39838e8ad5b6b1dda21013152055f8a008acc9 (patch) | |
tree | 7a30495cee48c7b103e7a8f5ba71f34bbbf65dc6 | |
parent | 4298e373d2c7ebbd8e2c82dda6cff54f6c3995b0 (diff) | |
download | taginfo-9f39838e8ad5b6b1dda21013152055f8a008acc9.tar taginfo-9f39838e8ad5b6b1dda21013152055f8a008acc9.tar.gz |
Add new relations section
There is a whole new relations section which gives information about different
relation types. There is a list of all relation types and there is a page for
each relation type which currently contains an empty overview tab and a roles
tab with a list of roles used with this relation type. There is currently no
link that leads to this section as it is not finished yet.
-rw-r--r-- | web/i18n/en.yml | 31 | ||||
-rw-r--r-- | web/lib/api/v4/relation.rb | 71 | ||||
-rw-r--r-- | web/lib/api/v4/relations.rb | 72 | ||||
-rw-r--r-- | web/lib/ui/relation.rb | 20 | ||||
-rw-r--r-- | web/lib/utils.rb | 23 | ||||
-rw-r--r-- | web/public/js/taginfo.js | 66 | ||||
-rwxr-xr-x | web/taginfo.rb | 7 | ||||
-rw-r--r-- | web/views/relation.erb | 26 | ||||
-rw-r--r-- | web/views/relations.erb | 8 | ||||
-rw-r--r-- | web/viewsjs/relation.js.erb | 39 | ||||
-rw-r--r-- | web/viewsjs/relations.js.erb | 48 |
11 files changed, 409 insertions, 2 deletions
diff --git a/web/i18n/en.yml b/web/i18n/en.yml index a1c5ad5..dfabf9f 100644 --- a/web/i18n/en.yml +++ b/web/i18n/en.yml @@ -12,6 +12,15 @@ osm: ways: Ways relation: Relation relations: Relations + relation_type: Relation type + relation_types: Relation types + relation_member: Relation member + relation_members: Relation members + relation_member_nodes: Member nodes + relation_member_ways: Member ways + relation_member_relations: Member relations + relation_member_role: Role + relation_member_roles: Roles object: Object objects: Objects all: All @@ -33,6 +42,7 @@ taginfo: test: Test map: Map maps: Maps + relations: Relations combinations: Combinations key_combinations: Combinations overview: Overview @@ -130,6 +140,15 @@ pages: tags: intro: | This table shows the most common tags in the database. + relations: + name: Relation types + intro: Information about the different types of relations (indicated by the <a href="/keys/type?filter=relations">type</a> tag). + relations_of_type_tooltip: Number of relations of this type (and as percentage of all relations). + prevalent_roles: Prevalent roles + prevalent_roles_tooltip: Prevalent roles for this type of relation. + no_information: No information + roles_less_than_one_percent: no roles with more than 1% + empty_role: empty role key: description_from_wiki: Description of this key from the wiki (if available in your chosen language, otherwise in English). no_description_in_wiki: No description for this key in the wiki. @@ -197,6 +216,18 @@ pages: choice: | Choose style: no_styles: No JOSM styles for this tag. + relation: + name: Relation type + overview: + tab: Overview + title: Overview + roles: + tab: Roles + title: Member roles + objects_tooltip: Relation members with this role (and as percentage of all members) for this relation type. + nodes_tooltip: Relation members of type node with this role (and as percentage of all members of type node) for this relation type. + ways_tooltip: Relation members of type way with this role (and as percentage of all members of type way) for this relation type. + relations_tooltip: Relation members of type relation with this role (and as percentage of all members of type relation) for this relation type. flexigrid: pagetext: Page diff --git a/web/lib/api/v4/relation.rb b/web/lib/api/v4/relation.rb new file mode 100644 index 0000000..4569be3 --- /dev/null +++ b/web/lib/api/v4/relation.rb @@ -0,0 +1,71 @@ +# web/lib/api/v4/relation.rb + +class Taginfo < Sinatra::Base + + api(4, 'relation/roles', { + :description => 'Member role statistics for a relation of given type.', + :parameters => { + :type => 'Relation type (required).', + :query => 'Only show results where the role matches this query (substring match, optional).' + }, + :paging => :optional, + :sort => %w( role count_all_members count_node_members count_way_members count_relation_members ), + :result => paging_results([ + [:relation_type, :STRING, 'Relation type'], + [:role, :STRING, 'Relation member role.'], + [:count_all_members, :INT, 'Number of members with this role.'], + [:count_all_members_fraction, :FLOAT, 'Number of members with this role devided by all members.'], + [:count_node_members, :INT, 'Number of members of type node with this role.'], + [:count_node_members_fraction, :FLOAT, 'Number of members of type node with this role devided by all members of type node.'], + [:count_way_members, :INT, 'Number of members of type way with this role.'], + [:count_way_members_fraction, :FLOAT, 'Number of members of type way with this role devided by all members of type way.'], + [:count_relation_members, :INT, 'Number of members of type relation with this role.'], + [:count_relation_members_fraction, :FLOAT, 'Number of members of type relation with this role devided by all members of type relation.'] + ]), + :example => { :role => 'multipolygon', :page => 1, :rp => 10 }, + :ui => '/reports/relation_types#roles' + }) do + rtype = params[:type] + + relation_type_info = @db.select('SELECT * FROM relation_types'). + condition("rtype=?", rtype). + execute()[0] + + total = @db.count('relation_roles'). + condition("rtype=?", rtype). + condition_if("role LIKE '%' || ? || '%'", params[:query]). + get_first_value().to_i + + res = @db.select('SELECT * FROM relation_roles'). + condition("rtype=?", rtype). + condition_if("role LIKE '%' || ? || '%'", params[:query]). + order_by(@ap.sortname, @ap.sortorder) { |o| + o.role + o.count_all_members :count_all + o.count_node_members :count_nodes + o.count_way_members :count_ways + o.count_relation_members :count_relations + }. + paging(@ap). + execute() + + return { + :page => @ap.page, + :rp => @ap.results_per_page, + :total => total, + :data => res.map{ |row| { + :relation_type => row['rtype'], + :role => row['role'], + :count_all_members => row['count_all'].to_i, + :count_all_members_fraction => (row['count_all'].to_f / relation_type_info['members_all'].to_i).round_to(4), + :count_node_members => row['count_nodes'].to_i, + :count_node_members_fraction => (row['count_nodes'].to_f / relation_type_info['members_nodes'].to_i).round_to(4), + :count_way_members => row['count_ways'].to_i, + :count_way_members_fraction => (row['count_ways'].to_f / relation_type_info['members_ways'].to_i).round_to(4), + :count_relation_members => row['count_relations'].to_i, + :count_relation_members_fraction => (row['count_relations'].to_f / relation_type_info['members_relations'].to_i).round_to(4), + } } + }.to_json + end + +end diff --git a/web/lib/api/v4/relations.rb b/web/lib/api/v4/relations.rb new file mode 100644 index 0000000..143ee05 --- /dev/null +++ b/web/lib/api/v4/relations.rb @@ -0,0 +1,72 @@ +# web/lib/api/v4/relations.rb + +class Taginfo < Sinatra::Base + + api(4, 'relations/all', { + :description => 'Information about the different relation types.', + :parameters => { + :query => 'Only show results where the relation type matches this query (substring match, optional).' + }, + :paging => :optional, + :sort => %w( relation_type count ), + :result => paging_results([ + [:relation_type, :STRING, 'Relation type'], + [:count, :INT, 'Number of relations with this type.'], + [:count_fraction, :INT, 'Number of relations with this type divided by the overall number of relations.'], + [:prevalent_roles, :ARRAY, 'Prevalent member roles.', [ + [:role, :STRING, 'Member role'], + [:count, :INT, 'Number of members with this role.'], + [:fraction, :FLOAT, 'Number of members with this role divided by all members.'] + ]] + ]), + :notes => "prevalent_roles can be null if taginfo doesn't have role information for this relation type, or an empty array when there are no roles with more than 1% of members", + :example => { :page => 1, :rp => 10 }, + :ui => '/reports/relation_types#types' + }) do + total = @db.count('relation_types'). + condition_if("rtype LIKE '%' || ? || '%'", params[:query]). + get_first_value().to_i + + res = @db.select('SELECT * FROM relation_types'). + condition_if("rtype LIKE '%' || ? || '%'", params[:query]). + order_by(@ap.sortname, @ap.sortorder) { |o| + o.relation_type :rtype + o.count + }. + paging(@ap). + execute() + + all_relations = @db.stats('relations') + + prevroles = @db.select('SELECT rtype, role, count, fraction FROM db.prevalent_roles'). + condition("rtype IN (#{ res.map{ |row| "'" + SQLite3::Database.quote(row['rtype']) + "'" }.join(',') })"). + order_by([:count], 'DESC'). + execute() + + pr = {} + res.each do |row| + pr[row['rtype']] = [] + end + + prevroles.each do |pv| + rtype = pv['rtype'] + pv.delete_if{ |k,v| k.is_a?(Integer) || k == 'rtype' } + pv['count'] = pv['count'].to_i + pv['fraction'] = pv['fraction'].to_f + pr[rtype] << pv + end + + return { + :page => @ap.page, + :rp => @ap.results_per_page, + :total => total, + :data => res.map{ |row| { + :relation_type => row['rtype'], + :count => row['count'].to_i, + :count_fraction => row['count'].to_i / all_relations, + :prevalent_roles => row['members_all'] ? pr[row['rtype']][0,10] : nil + } } + }.to_json + end + +end diff --git a/web/lib/ui/relation.rb b/web/lib/ui/relation.rb new file mode 100644 index 0000000..ab3bc8f --- /dev/null +++ b/web/lib/ui/relation.rb @@ -0,0 +1,20 @@ +# web/lib/ui/relations.rb +class Taginfo < Sinatra::Base + + get %r{^/relations/(.*)} do |rtype| + if params[:rtype].nil? + @rtype = rtype + else + @rtype = params[:rtype] + end + + @title = [escape_html(@rtype), t.osm.relations] + section :relations + + @desc = 'XXX' + + javascript "#{ r18n.locale.code }/relation" + erb :relation + end + +end diff --git a/web/lib/utils.rb b/web/lib/utils.rb index c27241e..9c7464f 100644 --- a/web/lib/utils.rb +++ b/web/lib/utils.rb @@ -142,6 +142,29 @@ def pp_value(value) return escape_html(value).gsub(/ /, '␣').gsub(/\s/, '<span class="whitespace"> </span>') end +def pp_rtype(rtype) + if rtype == '' + return '<span class="badchar empty">empty string</span>' + end + + pp_chars = '!"#$%&()*+,/;<=>?@[\\]^`{|}~' + "'"; + + result = '' + rtype.each_char do |c| + if (!pp_chars.index(c).nil?) + result += '<span class="badchar">' + c + '</span>' + elsif (c == ' ') + result += '<span class="badchar">␣</span>' + elsif (c.match(/\s/)) + result += '<span class="whitespace"> </span>' + else + result += c; + end + end + + return result; +end + def link_to_key(key, tab='') k = escape(key) diff --git a/web/public/js/taginfo.js b/web/public/js/taginfo.js index 99d7043..5908c2d 100644 --- a/web/public/js/taginfo.js +++ b/web/public/js/taginfo.js @@ -214,6 +214,15 @@ function url_for_tag(key, value) { } } +function url_for_rtype(rtype) { + var t = encodeURIComponent(rtype); + if (rtype.match(/[=\/]/)) { + return '/relations/?rtype=' + t; + } else { + return '/relations/' + t; + } +} + function link_to_value_with_title(key, value, extra) { return link( url_for_tag(key, value), @@ -265,6 +274,54 @@ function pp_value(value) { return pp_value_replace(value); } +function pp_rtype(rtype) { + if (rtype == '') { + return span(texts.misc.empty_string, 'badchar empty'); + } + + var result = '', + length = rtype.length; + + for (var i=0; i<length; i++) { + var c = rtype.charAt(i); + if (pp_chars.indexOf(c) != -1) { + result += span(c, 'badchar'); + } else if (c == ' ') { + result += span('␣', 'badchar'); + } else if (c.match(/\s/)) { + result += span(' ', 'whitespace'); + } else { + result += c; + } + } + + return result; +} + +function pp_role(role) { + if (role == '') { + return span(texts.misc.empty_string, 'badchar empty'); + } + + var result = '', + length = role.length; + + for (var i=0; i<length; i++) { + var c = role.charAt(i); + if (pp_chars.indexOf(c) != -1) { + result += span(c, 'badchar'); + } else if (c == ' ') { + result += span('␣', 'badchar'); + } else if (c.match(/\s/)) { + result += span(' ', 'whitespace'); + } else { + result += c; + } + } + + return result; +} + function link_to_key(key, highlight) { return link( url_for_key(key), @@ -297,6 +354,15 @@ function link_to_key_or_tag(key, value) { return link; } +function link_to_rtype(rtype, highlight) { + return link( + url_for_rtype(rtype), + highlight === undefined ? + pp_rtype(rtype) : + rtype.replace(new RegExp('(' + highlight + ')', 'gi'), "<b>$1</b>") + ); +} + /* ============================ */ var flexigrid_defaults = { diff --git a/web/taginfo.rb b/web/taginfo.rb index ab6a96e..d686174 100755 --- a/web/taginfo.rb +++ b/web/taginfo.rb @@ -156,9 +156,9 @@ class Taginfo < Sinatra::Base #------------------------------------- - %w(about download keys sources tags).each do |page| + %w(about download keys relations sources tags).each do |page| get '/' + page do - @title = (page =~ /^(keys|tags)$/) ? t.osm[page] : t.taginfo[page] + @title = (page =~ /^(keys|tags|relations)$/) ? t.osm[page] : t.taginfo[page] if File.exists?("viewsjs/#{ page }.js.erb") javascript "#{ r18n.locale.code }/#{ page }" end @@ -190,6 +190,8 @@ class Taginfo < Sinatra::Base load 'lib/api/v4/key.rb' load 'lib/api/v4/keys.rb' # load 'lib/api/v4/langtag.rb' + load 'lib/api/v4/relation.rb' + load 'lib/api/v4/relations.rb' load 'lib/api/v4/search.rb' load 'lib/api/v4/site.rb' load 'lib/api/v4/tag.rb' @@ -198,6 +200,7 @@ class Taginfo < Sinatra::Base load 'lib/ui/embed.rb' load 'lib/ui/keys_tags.rb' + load 'lib/ui/relation.rb' load 'lib/ui/reports.rb' load 'lib/ui/search.rb' load 'lib/ui/taginfo.rb' diff --git a/web/views/relation.erb b/web/views/relation.erb new file mode 100644 index 0000000..e2e60bc --- /dev/null +++ b/web/views/relation.erb @@ -0,0 +1,26 @@ +<div class="pre"> + <h1><%= t.pages.relation.name %> '<%= pp_rtype(@rtype) %>'</h1> + <p><%= @desc %></p> +</div> +<div id="tabs"> + <ul> + <li><a href="#overview"><%= t.pages.relation.overview.tab %></a></li> + <li><a href="#roles"><%= t.pages.relation.roles.tab %></a></li> + </ul> + <div id="overview"> + <h2><%= t.pages.relation.overview.title %></h2> + </div> + <div id="roles"> + <h2><%= t.pages.relation.roles.title %></h2> + <table id="grid-roles"> + </table> + </div> +</div> +<% javascript do + JS.raw(<<"JAVASCRIPT") +function page_init2() { + init_tabs([#{ @rtype.to_json }]); +} +JAVASCRIPT +end +%> diff --git a/web/views/relations.erb b/web/views/relations.erb new file mode 100644 index 0000000..663f24d --- /dev/null +++ b/web/views/relations.erb @@ -0,0 +1,8 @@ +<div class="pre"> + <h1 class="section"><%= t.pages.relations.name %></h1> + <p><%= t.pages.relations.intro %></p> +</div> +<div class="box resize"> + <table id="grid-relations"> + </table> +</div> diff --git a/web/viewsjs/relation.js.erb b/web/viewsjs/relation.js.erb new file mode 100644 index 0000000..c33073e --- /dev/null +++ b/web/viewsjs/relation.js.erb @@ -0,0 +1,39 @@ +<% + osm = @trans.t.osm + page = @trans.t.pages.relation + %> +var create_flexigrid_for = { + roles: function(rtype) { + create_flexigrid('grid-roles', { + url: '/api/4/relation/roles?type=' + encodeURIComponent(rtype), + colModel: [ + { display: '<%= osm.relation_member_role %>', name: 'role', width: 250, sortable: true }, + { display: '<span title="<%= page.roles.objects_tooltip %>"><img src="/img/types/all.16.png" width="16" height="16" alt=""/> <%= osm.relation_members %></span>', name: 'count_all_members', width: 250, sortable: true, align: 'center' }, + { display: '<span title="<%= page.roles.nodes_tooltip %>"><img src="/img/types/node.16.png" width="16" height="16" alt=""/> <%= osm.relation_member_nodes %></span>', name: 'count_node_members', width: 250, sortable: true, align: 'center' }, + { display: '<span title="<%= page.roles.ways_tooltip %>"><img src="/img/types/way.16.png" width="16" height="16" alt=""/> <%= osm.relation_member_ways %></span>', name: 'count_way_members', width: 250, sortable: true, align: 'center' }, + { display: '<span title="<%= page.roles.relations_tooltip %>"><img src="/img/types/relation.16.png" width="16" height="16" alt=""/> <%= osm.relation_member_relations %></span>', name: 'count_relation_members', width: 250, sortable: true, align: 'center' }, + ], + searchitems: [ + { display: '<%= osm.relation_member_role %>', name: 'role' } + ], + sortname: 'count_all_members', + sortorder: 'desc', + preProcess: function(data) { + data.rows = jQuery.map(data.data, function(row, i) { + return { 'cell': [ + pp_role(row.role), + print_value_with_percent(row.count_all_members, row.count_all_members_fraction), + print_value_with_percent(row.count_node_members, row.count_node_members_fraction), + print_value_with_percent(row.count_way_members, row.count_way_members_fraction), + print_value_with_percent(row.count_relation_members, row.count_relation_members_fraction) + ] }; + }); + return data; + } + }); + } +}; + +function page_init() { + page_init2(); +} diff --git a/web/viewsjs/relations.js.erb b/web/viewsjs/relations.js.erb new file mode 100644 index 0000000..147d590 --- /dev/null +++ b/web/viewsjs/relations.js.erb @@ -0,0 +1,48 @@ +<% + osm = @trans.t.osm + page = @trans.t.pages.relations + %> +function page_init() { + create_flexigrid('grid-relations', { + url: '/api/4/relations/all', + colModel: [ + { display: '<%= osm.relation_type %>', name: 'relation_type', width: 220, sortable: true }, + { display: '<span title="<%= page.relations_of_type_tooltip %>"><%= osm.relations %></span>', name: 'count', width: 250, sortable: true, align: 'center' }, + { display: '<%= osm.tag %>', name: 'tag', width: 250, sortable: false }, + { display: '<span title="<%= page.prevalent_roles_tooltip %>"><%= page.prevalent_roles %></span>', name: 'prevalent_roles', width: 550, sortable: false } + ], + searchitems: [ + { display: '<%= osm.relation_type %>', name: 'relation_type' } + ], + sortname: 'count', + sortorder: 'desc', + preProcess: function(data) { + data.rows = jQuery.map(data.data, function(row, i) { + return { 'cell': [ + link_to_rtype(row.relation_type), + print_value_with_percent(row.count, row.count_fraction), + link_to_tag('type', row.relation_type), + print_prevalent_role_list(row.prevalent_roles) + ] }; + }); + return data; + } + }); +}; + +function print_prevalent_role_list(list) { + if (list === null) { + return empty('<%= page.no_information %>'); + } + if (list.length == 0) { + return empty(page.roles_less_than_one_percent); + } + return jQuery.map(list, function(item, i) { + if (item.role) { + return tag('span', item.role, { tipsy: 'e', title: html_escape(item.role) + ' (' + item.fraction.print_as_percent() + ')' }); + } else { + return tag('span', empty('<%= page.empty_role %>'), { tipsy: 'e', title: '(' + item.fraction.print_as_percent() + ')' }); + } + }).join(' • '); +} + |