summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/i18n/en.yml31
-rw-r--r--web/lib/api/v4/relation.rb71
-rw-r--r--web/lib/api/v4/relations.rb72
-rw-r--r--web/lib/ui/relation.rb20
-rw-r--r--web/lib/utils.rb23
-rw-r--r--web/public/js/taginfo.js66
-rwxr-xr-xweb/taginfo.rb7
-rw-r--r--web/views/relation.erb26
-rw-r--r--web/views/relations.erb8
-rw-r--r--web/viewsjs/relation.js.erb39
-rw-r--r--web/viewsjs/relations.js.erb48
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(/ /, '&#x2423;').gsub(/\s/, '<span class="whitespace">&nbsp;</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">&#x2423;</span>'
+ elsif (c.match(/\s/))
+ result += '<span class="whitespace">&nbsp;</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('&#x2423;', 'badchar');
+ } else if (c.match(/\s/)) {
+ result += span('&nbsp;', '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('&#x2423;', 'badchar');
+ } else if (c.match(/\s/)) {
+ result += span('&nbsp;', '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(' &bull; ');
+}
+