├── .gitignore ├── MANIFEST.in ├── README.md ├── ckanext ├── __init__.py └── mapviews │ ├── __init__.py │ ├── plugin.py │ ├── tests │ ├── __init__.py │ └── test_view.py │ └── theme │ ├── public │ ├── choroplethmap.css │ ├── choroplethmap.js │ ├── ckan_map_modules.js │ ├── navigablemap.css │ ├── navigablemap.js │ ├── resource.config │ ├── vendor │ │ ├── backend.ckan.js │ │ ├── d3.scale.quantize.js │ │ ├── excanvas.js │ │ ├── leaflet.css │ │ ├── leaflet.js │ │ ├── leaflet.label.css │ │ ├── leaflet.label.js │ │ └── queryStringToJSON.js │ └── webassets.yml │ └── templates │ ├── base.html │ ├── choroplethmap_form.html │ ├── choroplethmap_view.html │ ├── navigablemap_form.html │ ├── navigablemap_view.html │ ├── page.html │ └── snippets │ ├── mapviews_asset.html │ └── mapviews_resource.html ├── doc ├── img │ ├── pakistan.png │ └── worldwide-internet-usage.png └── internet-users-per-100-people.csv └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | *~ 6 | dist 7 | build 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include ckanext/mapviews/theme * 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ckanext-mapviews 2 | ================ 3 | 4 | ![Pakistan choropleth map](doc/img/pakistan.png) 5 | 6 | This extension adds regular and choropleth maps to CKAN, using the new Resource 7 | View being developed on CKAN's master branch (currently unreleased). 8 | 9 | It uses [LeafletJS](http://leafletjs.com), which is compatible with all major 10 | browsers (including IE7+). 11 | 12 | Installation 13 | ------------ 14 | 15 | Clone this repository and run ```python setup.py install```. Then add 16 | ```navigablemap``` and/or ```choroplethmap``` to the list in ```ckan.plugins``` 17 | in your CKAN config file. 18 | 19 | Restart your webserver. You should see the new "Navigable Map" and/or 20 | "Choropleth Map" chart types (depending on which plugins you added to the list) 21 | as options in the view type's list on any resource that's in the DataStore. 22 | 23 | Usage 24 | ----- 25 | 26 | ### Pre-requisites 27 | 28 | To start creating choropleth maps, you need two things: the data you want to 29 | plot, and a GeoJSON defining the geographical regions you'd like to plot it. 30 | The data itself needs to be in a resource inside the DataStore, and the map 31 | needs to be in the same domain as CKAN itself (to avoid [same-origin 32 | policy](http://en.wikipedia.org/wiki/Same-origin_policy) issues). The easiest 33 | way to do so is to upload the GeoJSON as another resource. 34 | 35 | Each GeoJSON feature needs a property related to a column in the data. It can 36 | be an id, name, or anythings that uniquely identifies that feature, so we know 37 | where to plot the data. 38 | 39 | ### Example 40 | 41 | We'll create a map to try to understand the internet usage across the world. To 42 | do so, we need a worldmap in GeoJSON and the internet usage data. 43 | 44 | A good source of GeoJSON files is the [Natural Earth 45 | Data](http://naturalearthdata.com/) website. We'll be using their [world map at 46 | 1:110 million 47 | scale](https://github.com/nvkelso/natural-earth-vector/blob/master/geojson/ne_110m_admin_0_countries.geojson). 48 | 49 | We'll be plotting the [Internet usage per 100 50 | people in 2012](doc/internet-users-per-100-people.csv) all across the world. The data 51 | comes from the great [World Bank's Data 52 | Bank](http://databank.worldbank.org/data/home.aspx). It looks like this: 53 | 54 | | Country Name | Country Code | Indicator Name | Indicator Code | 2012 | ... | 55 | | -------------- | ------------ | ------------------------------- | -------------- | ---------------- | --- | 56 | | Afghanistan | AFG | Internet users (per 100 people) | IT.NET.USER.P2 | 5.45454545454545 | ... | 57 | | Albania | ALB | Internet users (per 100 people) | IT.NET.USER.P2 | 54.6559590399494 | ... | 58 | | Algeria | DZA | Internet users (per 100 people) | IT.NET.USER.P2 | 15.2280267564417 | ... | 59 | | American Samoa | ASM | Internet users (per 100 people) | IT.NET.USER.P2 | | ... | 60 | | Andorra | ADO | Internet users (per 100 people) | IT.NET.USER.P2 | 86.4344246167258 | ... | 61 | | ... | ... | ... | ... | ... | ... | 62 | 63 | To identify each country, we have its name and code. We need to have either 64 | attribute in the GeoJSON feature's properties. Opening that file, we see: 65 | 66 | ```javascript 67 | { 68 | "features": [ 69 | { 70 | "geometry": { 71 | "coordinates": [ 72 | // ... 73 | ], 74 | "type": "Polygon" 75 | }, 76 | "properties": { 77 | "name": "Afghanistan", 78 | "region_un": "Asia", 79 | "region_wb": "South Asia", 80 | "wb_a3": "AFG", 81 | // ... 82 | }, 83 | "type": "Feature" 84 | }, 85 | // ... 86 | ] 87 | } 88 | ``` 89 | 90 | We can map either ```Country Name``` with ```name```, or ```Country Code``` 91 | with ```wb_a3```. Let's use the country code. 92 | 93 | In your CKAN instance, create a new dataset (i.e. "World Bank's Indicators"), 94 | and upload two resources: the GeoJSON and the data file. 95 | 96 | Go to the data file's manage resource page and create a new ```Choropleth 97 | Map``` view. You'll see a form with a few fields. Use "Internet usage across 98 | the globe" as a title, leave the description empty (if you want). Now we need 99 | to add the GeoJSON. 100 | 101 | Select in the ```GeoJSON Resource``` field the resource you just created. The 102 | ```GeoJSON Key Field``` should be ```wb_a3```, as we found out before. We'll 103 | link that field to the ```Country Code``` column in our data, so set it 104 | in the ```Key``` field. 105 | 106 | Now, we just need to select what value we want to plot (let's use the latest 107 | year, in the ```2012``` column), and what label to use (```Country Name```). 108 | You can leave the remaining fields blank. In the end, we'll have: 109 | 110 | | Attribute | Value | 111 | | ----------------- | -------------------------------- | 112 | | GeoJSON Resource | _(Your GeoJSON Resource's name)_ | 113 | | GeoJSON Key Field | wb_a3 | 114 | | Key | Country Code | 115 | | Value | 2012 | 116 | | Label | Country Name | 117 | | Redirect to URL | | 118 | | Fields | | 119 | 120 | Click on ```Preview``` and you should see a map like: 121 | 122 | ![Worldwide Internet usage](doc/img/worldwide-internet-usage.png) 123 | 124 | Congratulations! You've just created your first choropleth map. You can go 125 | ahead and see how the maps look like in other years. We can't compare the maps 126 | directly, as the scales change depending on the data, but the difference from 127 | 2000 to 2012 is still impressive. 128 | 129 | ### Filters 130 | 131 | If the map is shown in places other than its original URL in the resource 132 | view's list (for example, inside a 133 | [ckanext-dashboard](//github.com/ckan/ckanext-dashboard) or 134 | [ckanext-page](//github.com/ckan/ckanext-pages)), its regions become clickable. 135 | 136 | When the user clicks on a region, we'll add a filter to the current page using 137 | the `Key` attribute. Using the previous example, if I clicked on Brazil, it'll 138 | add the filters `Country Code:BRA` to the current page. 139 | 140 | The user can deactivate the filters by clicking on the same region again, and 141 | can activate multiple filters. 142 | 143 | If you'd like to, when clicking on a region, redirect the user to another page 144 | with that filter set (for example, another resource view or a dashboard), 145 | you can add the target URL to the `Redirect to URL` field. If that's left 146 | blank, it'll simply add filters to the current page. 147 | 148 | To learn more about it, check the 149 | [ckanext-viewhelpers](//github.com/ckan/ckanext-viewhelpers) page. 150 | 151 | License 152 | ------- 153 | 154 | Copyright (C) 2014 Open Knowledge Foundation 155 | 156 | This program is free software: you can redistribute it and/or modify 157 | it under the terms of the GNU Affero General Public License as published 158 | by the Free Software Foundation, either version 3 of the License, or 159 | (at your option) any later version. 160 | 161 | This program is distributed in the hope that it will be useful, 162 | but WITHOUT ANY WARRANTY; without even the implied warranty of 163 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 164 | GNU Affero General Public License for more details. 165 | 166 | You should have received a copy of the GNU Affero General Public License 167 | along with this program. If not, see . 168 | -------------------------------------------------------------------------------- /ckanext/__init__.py: -------------------------------------------------------------------------------- 1 | # namespace package 2 | try: 3 | import pkg_resources 4 | pkg_resources.declare_namespace(__name__) 5 | except ImportError: 6 | import pkgutil 7 | __path__ = pkgutil.extend_path(__path__, __name__) 8 | -------------------------------------------------------------------------------- /ckanext/mapviews/__init__.py: -------------------------------------------------------------------------------- 1 | # namespace package 2 | try: 3 | import pkg_resources 4 | pkg_resources.declare_namespace(__name__) 5 | except ImportError: 6 | import pkgutil 7 | __path__ = pkgutil.extend_path(__path__, __name__) 8 | -------------------------------------------------------------------------------- /ckanext/mapviews/plugin.py: -------------------------------------------------------------------------------- 1 | import ckan.plugins as p 2 | 3 | try: 4 | import urlparse 5 | except: 6 | import urllib.parse as urlparse 7 | 8 | try: 9 | import pylons.config as config 10 | except: 11 | config = p.toolkit.config 12 | 13 | 14 | Invalid = p.toolkit.Invalid 15 | _ = p.toolkit._ 16 | not_empty = p.toolkit.get_validator('not_empty') 17 | ignore_missing = p.toolkit.get_validator('ignore_missing') 18 | aslist = p.toolkit.aslist 19 | 20 | 21 | def url_is_relative_or_in_same_domain(url): 22 | site_url = urlparse.urlparse(config.get('ckan.site_url', '')) 23 | parsed_url = urlparse.urlparse(url) 24 | 25 | is_relative = (parsed_url.netloc == '') 26 | is_in_same_domain = (parsed_url.netloc == site_url.netloc) 27 | 28 | if not (is_relative or is_in_same_domain): 29 | message = _('Must be a relative URL or in the same domain as CKAN') 30 | raise Invalid(message) 31 | 32 | return url 33 | 34 | 35 | class NavigableMap(p.SingletonPlugin): 36 | '''Creates a map view''' 37 | 38 | p.implements(p.IConfigurer, inherit=True) 39 | p.implements(p.IResourceView, inherit=True) 40 | 41 | def update_config(self, config): 42 | p.toolkit.add_template_directory(config, 'theme/templates') 43 | p.toolkit.add_resource('theme/public', 'mapviews') 44 | 45 | def info(self): 46 | schema = { 47 | 'geojson_url': [not_empty, url_is_relative_or_in_same_domain], 48 | 'geojson_key_field': [not_empty], 49 | 'resource_key_field': [not_empty], 50 | 'resource_label_field': [not_empty], 51 | 'redirect_to_url': [ignore_missing], 52 | #'filter_fields': [ignore_missing], 53 | } 54 | 55 | return {'name': 'navigable-map', 56 | 'title': 'Navigable Map', 57 | 'icon': 'map-marker', 58 | 'schema': schema, 59 | 'iframed': False, 60 | 'filterable': True, 61 | } 62 | 63 | def can_view(self, data_dict): 64 | return data_dict['resource'].get('datastore_active', False) 65 | 66 | def setup_template_variables(self, context, data_dict): 67 | resource = data_dict['resource'] 68 | resource_view = data_dict['resource_view'] 69 | #filter_fields = aslist(resource_view.get('filter_fields', [])) 70 | #resource_view['filter_fields'] = filter_fields 71 | geojson_resources = _get_geojson_resources() 72 | fields = _get_fields(resource) 73 | fields_without_id = _remove_id_and_prepare_to_template(fields) 74 | numeric_fields = _filter_numeric_fields_without_id(fields) 75 | textual_fields = _filter_textual_fields_without_id(fields) 76 | 77 | return {'resource': resource, 78 | 'resource_view': resource_view, 79 | 'geojson_resources': geojson_resources, 80 | 'fields': fields_without_id, 81 | 'numeric_fields': numeric_fields, 82 | 'textual_fields': textual_fields} 83 | 84 | def view_template(self, context, data_dict): 85 | return 'navigablemap_view.html' 86 | 87 | def form_template(self, context, data_dict): 88 | return 'navigablemap_form.html' 89 | 90 | 91 | class ChoroplethMap(NavigableMap): 92 | '''Creates a choropleth map view''' 93 | 94 | def info(self): 95 | info = super(ChoroplethMap, self).info() 96 | info['name'] = 'choropleth-map' 97 | info['title'] = 'Choropleth Map' 98 | info['schema']['resource_value_field'] = [not_empty] 99 | info['filterable'] = True 100 | 101 | return info 102 | 103 | def view_template(self, context, data_dict): 104 | return 'choroplethmap_view.html' 105 | 106 | def form_template(self, context, data_dict): 107 | return 'choroplethmap_form.html' 108 | 109 | 110 | def _get_geojson_resources(): 111 | data = { 112 | 'query': 'format:geojson', 113 | 'order_by': 'name', 114 | } 115 | result = p.toolkit.get_action('resource_search')({}, data) 116 | return [{'text': r['name'], 'value': r['url']} 117 | for r in result.get('results', [])] 118 | 119 | 120 | def _get_fields(resource): 121 | data = { 122 | 'resource_id': resource['id'], 123 | 'limit': 0 124 | } 125 | result = p.toolkit.get_action('datastore_search')({}, data) 126 | return result.get('fields', []) 127 | 128 | 129 | def _remove_id_and_prepare_to_template(fields): 130 | isnt_id = lambda v: v['id'] != '_id' 131 | return [{'value': v['id']} for v in fields if isnt_id(v)] 132 | 133 | 134 | def _filter_numeric_fields_without_id(fields): 135 | isnt_id = lambda v: v['id'] != '_id' 136 | is_numeric = lambda v: v['type'] == 'numeric' 137 | return [{'value': v['id']} for v in fields if isnt_id(v) and is_numeric(v)] 138 | 139 | 140 | def _filter_textual_fields_without_id(fields): 141 | isnt_id = lambda v: v['id'] != '_id' 142 | is_text = lambda v: v['type'] == 'text' 143 | return [{'value': v['id']} for v in fields if isnt_id(v) and is_text(v)] 144 | -------------------------------------------------------------------------------- /ckanext/mapviews/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-mapviews/caf1d0f6ab4168f3ac10a75881fa537144b01d34/ckanext/mapviews/tests/__init__.py -------------------------------------------------------------------------------- /ckanext/mapviews/tests/test_view.py: -------------------------------------------------------------------------------- 1 | import os 2 | import mock 3 | import inspect 4 | import nose.tools 5 | import pylons.config as config 6 | 7 | import ckan.plugins as p 8 | import ckanext.mapviews.plugin as plugin 9 | 10 | 11 | url_is_relative_or_in_same_domain = plugin.url_is_relative_or_in_same_domain 12 | Invalid = p.toolkit.Invalid 13 | 14 | 15 | def test_url_is_relative_or_in_same_domain_accepts_urls_on_same_domain(): 16 | site_url = config.get('ckan.site_url') 17 | url = site_url + "/dataset/something" 18 | 19 | assert url_is_relative_or_in_same_domain(url) == url 20 | 21 | 22 | def test_url_is_relative_or_in_same_domain_accepts_relative_urls(): 23 | url = "/dataset/something" 24 | assert url_is_relative_or_in_same_domain(url) == url 25 | 26 | 27 | @nose.tools.raises(Invalid) 28 | def test_url_is_relative_or_in_same_domain_raises_if_not_on_the_same_domain(): 29 | url_is_relative_or_in_same_domain("http://some-other-domain.com") 30 | 31 | 32 | class TestNavigableMap(object): 33 | 34 | @classmethod 35 | def setup_class(cls): 36 | p.load('navigablemap') 37 | cls.plugin = p.get_plugin('navigablemap') 38 | 39 | @classmethod 40 | def teardown_class(cls): 41 | p.unload('navigablemap') 42 | 43 | def test_plugin_templates_path_is_added_to_config(self): 44 | filename = inspect.getfile(inspect.currentframe()) 45 | path = os.path.dirname(filename) 46 | templates_path = os.path.abspath(path + "/../theme/templates") 47 | 48 | assert templates_path in config['extra_template_paths'], templates_path 49 | 50 | def test_can_view_is_true_when_datastore_is_active(self): 51 | active_datastore_data_dict = { 52 | 'resource': { 'datastore_active': True } 53 | } 54 | assert self.plugin.can_view(active_datastore_data_dict) 55 | 56 | def test_can_view_is_false_when_datastore_is_inactive(self): 57 | inactive_datastore_data_dict = { 58 | 'resource': { 'datastore_active': False } 59 | } 60 | assert not self.plugin.can_view(inactive_datastore_data_dict) 61 | 62 | def test_view_template_is_correct(self): 63 | view_template = self.plugin.view_template({}, {}) 64 | assert view_template == 'navigablemap_view.html' 65 | 66 | def test_form_template_is_correct(self): 67 | form_template = self.plugin.form_template({}, {}) 68 | assert form_template == 'navigablemap_form.html' 69 | 70 | def test_schema_exists(self): 71 | schema = self.plugin.info()['schema'] 72 | assert schema is not None, 'Plugin should define schema' 73 | 74 | def test_schema_has_geojson_url(self): 75 | schema = self.plugin.info()['schema'] 76 | assert schema.get('geojson_url') is not None, \ 77 | 'Schema should define "geojson_url"' 78 | 79 | def test_schema_geojson_url_isnt_empty(self): 80 | schema = self.plugin.info()['schema'] 81 | not_empty = p.toolkit.get_validator('not_empty') 82 | assert not_empty in schema['geojson_url'], \ 83 | '"geojson_url" should not be empty' 84 | 85 | def test_schema_geojson_url_is_relative_or_in_same_domain(self): 86 | schema = self.plugin.info()['schema'] 87 | 88 | assert url_is_relative_or_in_same_domain in schema['geojson_url'], \ 89 | '"geojson_url" should be relative or in same domain' 90 | 91 | def test_schema_has_geojson_key_field(self): 92 | schema = self.plugin.info()['schema'] 93 | assert schema.get('geojson_key_field') is not None, \ 94 | 'Schema should define "geojson_key_field"' 95 | 96 | def test_schema_geojson_key_field_isnt_empty(self): 97 | schema = self.plugin.info()['schema'] 98 | not_empty = p.toolkit.get_validator('not_empty') 99 | assert not_empty in schema['geojson_key_field'], \ 100 | '"geojson_key_field" should not be empty' 101 | 102 | def test_schema_has_resource_key_field(self): 103 | schema = self.plugin.info()['schema'] 104 | assert schema.get('resource_key_field') is not None, \ 105 | 'Schema should define "resource_key_field"' 106 | 107 | def test_schema_resource_key_field_isnt_empty(self): 108 | schema = self.plugin.info()['schema'] 109 | not_empty = p.toolkit.get_validator('not_empty') 110 | assert not_empty in schema['resource_key_field'], \ 111 | '"resource_key_field" should not be empty' 112 | 113 | def test_schema_has_resource_label_field(self): 114 | schema = self.plugin.info()['schema'] 115 | assert schema.get('resource_label_field') is not None, \ 116 | 'Schema should define "resource_label_field"' 117 | 118 | def test_schema_resource_label_field_isnt_empty(self): 119 | schema = self.plugin.info()['schema'] 120 | not_empty = p.toolkit.get_validator('not_empty') 121 | assert not_empty in schema['resource_label_field'], \ 122 | '"resource_label_field" should not be empty' 123 | 124 | def test_schema_has_redirect_to_url(self): 125 | schema = self.plugin.info()['schema'] 126 | assert schema.get('redirect_to_url') is not None, \ 127 | 'Schema should define "redirect_to_url"' 128 | 129 | def test_schema_redirect_to_url_isnt_required(self): 130 | schema = self.plugin.info()['schema'] 131 | ignore_missing = p.toolkit.get_validator('ignore_missing') 132 | assert ignore_missing in schema['redirect_to_url'], \ 133 | '"redirect_to_url" should not be required' 134 | 135 | def test_schema_has_filter_fields(self): 136 | schema = self.plugin.info()['schema'] 137 | assert schema.get('filter_fields') is not None, \ 138 | 'Schema should define "filter_fields"' 139 | 140 | def test_schema_filter_fields_isnt_required(self): 141 | schema = self.plugin.info()['schema'] 142 | ignore_missing = p.toolkit.get_validator('ignore_missing') 143 | assert ignore_missing in schema['filter_fields'], \ 144 | '"filter_fields" should not be required' 145 | 146 | def test_plugin_isnt_iframed(self): 147 | iframed = self.plugin.info().get('iframed', True) 148 | assert not iframed, 'Plugin should not be iframed' 149 | 150 | @mock.patch('ckan.plugins.toolkit.get_action') 151 | def test_setup_template_variables_adds_resource(self, _): 152 | resource = { 153 | 'id': 'resource_id', 154 | } 155 | 156 | template_variables = self._setup_template_variables(resource) 157 | 158 | assert 'resource' in template_variables 159 | assert template_variables['resource'] == resource 160 | 161 | @mock.patch('ckan.plugins.toolkit.get_action') 162 | def test_setup_template_variables_adds_resource_view(self, _): 163 | resource_view = { 164 | 'id': 'resource_id', 165 | 'other_attribute': 'value' 166 | } 167 | 168 | template_variables = \ 169 | self._setup_template_variables(resource_view=resource_view) 170 | 171 | assert 'resource_view' in template_variables 172 | assert template_variables['resource_view'] == resource_view 173 | 174 | @mock.patch('ckan.plugins.toolkit.get_action') 175 | def test_setup_template_variables_resource_view_converts_filter_fields_to_list(self, _): 176 | resource_view = { 177 | 'filter_fields': 'value' 178 | } 179 | 180 | template_variables = \ 181 | self._setup_template_variables(resource_view=resource_view) 182 | 183 | resource_view = template_variables['resource_view'] 184 | assert resource_view['filter_fields'] == ['value'] 185 | 186 | @mock.patch('ckan.plugins.toolkit.get_action') 187 | def test_setup_template_variables_adds_geojson_resources(self, get_action): 188 | fields = [ 189 | { 190 | 'name': 'Map', 191 | 'url': 'http://demo.ckan.org/map.json', 192 | } 193 | ] 194 | expected_fields = [{ 195 | 'text': 'Map', 196 | 'value': 'http://demo.ckan.org/map.json' 197 | }] 198 | 199 | get_action.return_value.return_value = { 200 | 'count': 1L, 201 | 'results': fields 202 | } 203 | template_variables = self._setup_template_variables() 204 | 205 | returned_fields = template_variables.get('geojson_resources') 206 | assert returned_fields is not None 207 | assert returned_fields == expected_fields 208 | 209 | @mock.patch('ckan.plugins.toolkit.get_action') 210 | def test_setup_template_variables_adds_fields_without_the_id(self, get_action): 211 | fields = [ 212 | {'id': '_id', 'type': 'int4'}, 213 | {'id': 'price', 'type': 'numeric'}, 214 | ] 215 | expected_fields = [{'value': 'price'}] 216 | 217 | get_action.return_value.return_value = { 218 | 'fields': fields, 219 | 'records': {} 220 | } 221 | template_variables = self._setup_template_variables() 222 | 223 | returned_fields = template_variables.get('fields') 224 | assert returned_fields is not None 225 | assert returned_fields == expected_fields 226 | 227 | @mock.patch('ckan.plugins.toolkit.get_action') 228 | def test_setup_template_variables_adds_numeric_fields(self, get_action): 229 | fields = [ 230 | {'id': '_id', 'type': 'int4'}, 231 | {'id': 'price', 'type': 'numeric'}, 232 | {'id': 'name', 'type': 'text'} 233 | ] 234 | expected_fields = [{'value': 'price'}] 235 | 236 | get_action.return_value.return_value = { 237 | 'fields': fields, 238 | 'records': {} 239 | } 240 | template_variables = self._setup_template_variables() 241 | 242 | returned_fields = template_variables.get('numeric_fields') 243 | assert returned_fields is not None 244 | assert returned_fields == expected_fields 245 | 246 | @mock.patch('ckan.plugins.toolkit.get_action') 247 | def test_setup_template_variables_adds_textual_fields(self, get_action): 248 | fields = [ 249 | {'id': '_id', 'type': 'int4'}, 250 | {'id': 'price', 'type': 'numeric'}, 251 | {'id': 'name', 'type': 'text'} 252 | ] 253 | expected_fields = [{'value': 'name'}] 254 | 255 | get_action.return_value.return_value = { 256 | 'fields': fields, 257 | 'records': {} 258 | } 259 | template_variables = self._setup_template_variables() 260 | 261 | returned_fields = template_variables.get('textual_fields') 262 | assert returned_fields is not None 263 | assert returned_fields == expected_fields 264 | 265 | @mock.patch('ckan.plugins.toolkit.get_action') 266 | def test_setup_template_variables_adds_default_filter_fields(self, get_action): 267 | template_variables = self._setup_template_variables() 268 | 269 | filter_fields = template_variables['resource_view'].get('filter_fields') 270 | assert filter_fields is not None 271 | assert filter_fields == [] 272 | 273 | def _setup_template_variables(self, resource={'id': 'id'}, resource_view={}): 274 | context = {} 275 | data_dict = { 276 | 'resource': resource, 277 | 'resource_view': resource_view 278 | } 279 | return self.plugin.setup_template_variables(context, data_dict) 280 | 281 | 282 | class TestChoroplethMap(object): 283 | 284 | @classmethod 285 | def setup_class(cls): 286 | p.load('choroplethmap') 287 | cls.plugin = p.get_plugin('choroplethmap') 288 | 289 | @classmethod 290 | def teardown_class(cls): 291 | p.unload('choroplethmap') 292 | 293 | def test_view_template_is_correct(self): 294 | view_template = self.plugin.view_template({}, {}) 295 | assert view_template == 'choroplethmap_view.html' 296 | 297 | def test_form_template_is_correct(self): 298 | form_template = self.plugin.form_template({}, {}) 299 | assert form_template == 'choroplethmap_form.html' 300 | 301 | def test_schema_has_resource_value_field(self): 302 | schema = self.plugin.info()['schema'] 303 | assert schema.get('resource_value_field') is not None, \ 304 | 'Schema should define "resource_value_field"' 305 | 306 | def test_schema_resource_value_field_isnt_empty(self): 307 | schema = self.plugin.info()['schema'] 308 | not_empty = p.toolkit.get_validator('not_empty') 309 | assert not_empty in schema['resource_value_field'], \ 310 | '"resource_value_field" should not be empty' 311 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/choroplethmap.css: -------------------------------------------------------------------------------- 1 | .choroplethmap .legend { 2 | margin: 0; 3 | padding: 0; 4 | list-style: none; 5 | } 6 | 7 | .choroplethmap .legend span { 8 | width: 16px; 9 | height: 16px; 10 | float: left; 11 | margin-right: 8px; 12 | border: 1px solid #969696; 13 | } 14 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/choroplethmap.js: -------------------------------------------------------------------------------- 1 | this.ckan = this.ckan || {}; 2 | this.ckan.views = this.ckan.views || {}; 3 | this.ckan.views.mapviews = this.ckan.views.mapviews || {}; 4 | 5 | this.ckan.views.mapviews.choroplethmap = (function () { 6 | 'use strict'; 7 | 8 | var noDataColor = '#F7FBFF', 9 | opacity = 0.7, 10 | colors = ['#C6DBEF', '#9ECAE1', '#6BAED6', '#4292C6', 11 | '#2171B5', '#08519C', '#08306B'], 12 | defaultStyle = { 13 | // fillColor will be set depending on the feature's value 14 | fillOpacity: opacity, 15 | }; 16 | 17 | function initialize(element, options, noDataLabel, geojson, featuresValues) { 18 | var map = ckan.views.mapviews.navigablemap(element, options, noDataLabel, geojson, featuresValues), 19 | scale = _createScale(featuresValues, geojson), 20 | onEachFeature = _onEachFeature(options.geojsonKeyField, featuresValues, noDataLabel, scale); 21 | 22 | _addLegend(map, scale, opacity, noDataLabel); 23 | 24 | $.each(map._layers, function (i, layer) { 25 | if (layer.feature !== undefined) { 26 | onEachFeature(layer.feature, layer); 27 | } 28 | }); 29 | } 30 | 31 | function _createScale(featuresValues, geojson) { 32 | var values = $.map(featuresValues, function (feature, key) { 33 | return feature.value; 34 | }).sort(function (a, b) { return a - b; }), 35 | min = values[0], 36 | max = values[values.length - 1]; 37 | 38 | return d3.scale.quantize() 39 | .domain([min, max]) 40 | .range(colors); 41 | } 42 | 43 | function _addLegend(map, scale, opacity, noDataLabel) { 44 | var legend = L.control({ position: 'bottomright' }); 45 | 46 | legend.onAdd = function (map) { 47 | var div = L.DomUtil.create('div', 'info'), 48 | ul = L.DomUtil.create('ul', 'legend'), 49 | domain = scale.domain(), 50 | range = scale.range(), 51 | min = domain[0] + 0.0000000001, 52 | max = domain[domain.length - 1], 53 | step = (max - min)/range.length, 54 | grades = $.map(range, function (_, i) { return (min + step * i); }), 55 | labels = []; 56 | 57 | div.appendChild(ul); 58 | for (var i = 0, len = grades.length; i < len; i++) { 59 | ul.innerHTML += 60 | '
  • ' + 61 | _formatNumber(grades[i]) + 62 | (grades[i + 1] ? '–' + _formatNumber(grades[i + 1]) + '
  • ' : '+'); 63 | } 64 | 65 | ul.innerHTML += 66 | '
  • ' + 67 | noDataLabel + '
  • '; 68 | 69 | return div; 70 | }; 71 | 72 | legend.addTo(map); 73 | } 74 | 75 | function _onEachFeature(geojsonKeyField, featuresValues, noDataLabel, scale) { 76 | return function (feature, layer) { 77 | var elementData = featuresValues[feature.properties[geojsonKeyField]], 78 | value = elementData && elementData.value, 79 | label = elementData && elementData.label, 80 | color = (value) ? scale(value) : noDataColor; 81 | 82 | layer.setStyle($.extend({ fillColor: color }, defaultStyle)); 83 | 84 | if (label) { 85 | var layerLabel = elementData.label + ': ' + (value || noDataLabel); 86 | layer.bindLabel(layerLabel); 87 | } else { 88 | layer.bindLabel(noDataLabel); 89 | } 90 | }; 91 | } 92 | 93 | function _formatNumber(num) { 94 | return (num % 1 ? num.toFixed(2) : num); 95 | } 96 | 97 | return initialize; 98 | })(); 99 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/ckan_map_modules.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function moduleInitializationFor(mapType) { 5 | return function ($, _) { 6 | function initialize() { 7 | var self = this, 8 | el = self.el, 9 | options = self.options, 10 | noDataLabel = self.i18n('noData'), 11 | filterFields = self.options.filterFields, 12 | resource = { 13 | id: options.resourceId, 14 | endpoint: options.endpoint || self.sandbox.client.endpoint + '/api' 15 | }; 16 | 17 | var filters = []; 18 | for (var filter in filterFields){ 19 | if (filterFields.hasOwnProperty(filter)) { 20 | filters.push({ 21 | "type": "term", 22 | "field": filter, 23 | "term": filterFields[filter] 24 | }); 25 | } 26 | }; 27 | 28 | var query = { 29 | size: 1000, 30 | filters: filters 31 | }; 32 | 33 | $.when( 34 | $.getJSON(options.geojsonUrl), 35 | recline.Backend.Ckan.query(query, resource) 36 | ).done(function (geojson, query) { 37 | var featuresValues = _mapResourceKeyFieldToValues(options.resourceKeyField, 38 | options.resourceValueField, 39 | options.resourceLabelField, 40 | options.geojsonKeyField, 41 | geojson[0], 42 | query.hits); 43 | 44 | ckan.views.mapviews[mapType](el, options, noDataLabel, geojson[0], featuresValues); 45 | }); 46 | } 47 | 48 | return { 49 | options: { 50 | i18n: { 51 | noData: _('No data') 52 | } 53 | }, 54 | initialize: initialize 55 | }; 56 | }; 57 | } 58 | 59 | function _mapResourceKeyFieldToValues(resourceKeyField, resourceValueField, resourceLabelField, geojsonKeyField, geojson, data) { 60 | var mapping = {}, 61 | geojsonKeys = _getGeojsonKeys(geojsonKeyField, geojson); 62 | 63 | $.each(data, function (i, d) { 64 | var key = d[resourceKeyField], 65 | label = d[resourceLabelField], 66 | value = d[resourceValueField]; 67 | 68 | if (geojsonKeys.indexOf(key) === -1) { 69 | return; 70 | } 71 | mapping[key] = { 72 | key: key, 73 | label: label, 74 | data: d 75 | }; 76 | 77 | if (value) { 78 | mapping[key].value = parseFloat(value); 79 | } 80 | }); 81 | 82 | return mapping; 83 | } 84 | 85 | function _getGeojsonKeys(geojsonKeyField, geojson) { 86 | var result = [], 87 | features = geojson.features, 88 | i, 89 | len = features.length; 90 | 91 | for (i = 0; i < len; i++) { 92 | result.push(features[i].properties[geojsonKeyField]); 93 | } 94 | 95 | return result; 96 | } 97 | 98 | ckan.module('navigablemap', moduleInitializationFor('navigablemap')); 99 | ckan.module('choroplethmap', moduleInitializationFor('choroplethmap')); 100 | })(); 101 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/navigablemap.css: -------------------------------------------------------------------------------- 1 | .navigablemap { 2 | height: 500px; 3 | cursor: default; 4 | } 5 | 6 | .dashboard-grid .navigablemap { 7 | width: 100%; 8 | height: 100%; 9 | } 10 | 11 | .navigablemap .non-clickable { 12 | cursor: default; 13 | } 14 | 15 | .navigablemap .info { 16 | line-height: 18px; 17 | background-color: white; 18 | padding: 0.5em; 19 | } 20 | 21 | .choropleth-map-removeField { 22 | cursor: pointer; 23 | color: #bd362f; 24 | } 25 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/navigablemap.js: -------------------------------------------------------------------------------- 1 | this.ckan = this.ckan || {}; 2 | this.ckan.views = this.ckan.views || {}; 3 | this.ckan.views.mapviews = this.ckan.views.mapviews || {}; 4 | 5 | this.ckan.views.mapviews.navigablemap = (function () { 6 | 'use strict'; 7 | 8 | var borderColor = '#031127', 9 | defaultStyle = { 10 | fillColor: '#4292C6', 11 | fillOpacity: 0.7, 12 | opacity: 0.1, 13 | weight: 2, 14 | color: borderColor 15 | }, 16 | highlightStyle = { 17 | weight: 5 18 | }, 19 | nonHighlightedStyle = { 20 | weight: 2 21 | }, 22 | activeStyle = { 23 | opacity: 1, 24 | color: '#d73027' 25 | }; 26 | 27 | function initialize(element, options, noDataLabel, geojson, featuresValues) { 28 | var elementId = element['0'].id, 29 | geojsonUrl = options.geojsonUrl, 30 | geojsonKeyField = options.geojsonKeyField, 31 | resourceKeyField = options.resourceKeyField, 32 | redirectToUrl = (options.redirectToUrl === true) ? '' : options.redirectToUrl, 33 | filterFields = options.filterFields, 34 | map = L.map(elementId), 35 | geojsonLayer, 36 | bounds, 37 | maxBounds, 38 | router; 39 | 40 | var isInOwnResourceViewPage = $(element.parent()).hasClass('ckanext-datapreview'); 41 | if (!isInOwnResourceViewPage) { 42 | router = _router(resourceKeyField, geojsonKeyField, redirectToUrl, filterFields, featuresValues); 43 | } 44 | 45 | _addBaseLayer(map); 46 | geojsonLayer = _addGeoJSONLayer(map, geojson, geojsonKeyField, noDataLabel, featuresValues, router); 47 | bounds = geojsonLayer.getBounds(); 48 | maxBounds = bounds.pad(0.1); 49 | 50 | map.fitBounds(bounds); 51 | map.setMaxBounds(maxBounds); 52 | 53 | return map; 54 | } 55 | 56 | function _addBaseLayer(map) { 57 | var attribution = '© OpenStreetMap contributors'; 58 | 59 | return L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 60 | attribution: attribution 61 | }).addTo(map); 62 | } 63 | 64 | function _addGeoJSONLayer(map, geojson, geojsonKeyField, noDataLabel, featuresValues, router) { 65 | return L.geoJson(geojson, { 66 | style: _style(), 67 | onEachFeature: _onEachFeature(geojsonKeyField, featuresValues, router, noDataLabel) 68 | }).addTo(map); 69 | } 70 | 71 | function _style() { 72 | return defaultStyle; 73 | } 74 | 75 | function _onEachFeature(geojsonKeyField, featuresValues, router, noDataLabel) { 76 | var eventsCallbacks = { 77 | mouseover: _highlightFeature, 78 | mouseout: _resetHighlight 79 | }; 80 | 81 | if (router) { 82 | eventsCallbacks.click = router.toggleActive; 83 | } 84 | 85 | return function (feature, layer) { 86 | var elementData = featuresValues[feature.properties[geojsonKeyField]]; 87 | 88 | if (router && elementData) { 89 | router.activateIfNeeded(layer); 90 | } else { 91 | layer.setStyle({ className: 'non-clickable' }); 92 | } 93 | 94 | if (elementData && elementData.label) { 95 | layer.bindLabel(elementData.label); 96 | layer.on(eventsCallbacks); 97 | } 98 | }; 99 | } 100 | 101 | function _formatNumber(num) { 102 | return (num % 1 ? num.toFixed(2) : num); 103 | } 104 | 105 | function _highlightFeature(e) { 106 | var layer = e.target; 107 | 108 | layer.setStyle(highlightStyle); 109 | 110 | if (!L.Browser.ie && !L.Browser.opera) { 111 | layer.bringToFront(); 112 | } 113 | } 114 | 115 | function _resetHighlight(e) { 116 | e.target.setStyle(nonHighlightedStyle); 117 | } 118 | 119 | function _router(resourceKeyField, geojsonKeyField, redirectToUrl, filterFields, featuresValues) { 120 | var activeFeatures = _getActiveFeatures(resourceKeyField, featuresValues), 121 | filterFieldsWithResourceKeyField = Array.isArray(filterFields) ? filterFields.slice() : []; 122 | 123 | filterFieldsWithResourceKeyField.push(resourceKeyField); 124 | 125 | function _getActiveFeatures(filterName, features) { 126 | var filters = ckan.views.filters ? ckan.views.filters.get() : {}, 127 | activeFeaturesKeys = filters[filterName] || [], 128 | result = []; 129 | 130 | $.each(activeFeaturesKeys, function (i, key) { 131 | if (features[key]) { 132 | result.push(features[key]); 133 | } 134 | }); 135 | 136 | return result; 137 | } 138 | 139 | function toggleActive(e) { 140 | var layer = e.target, 141 | id = layer.feature.properties[geojsonKeyField], 142 | feature = featuresValues[id], 143 | index = $.inArray(feature, activeFeatures); 144 | 145 | // Toggle this feature 146 | if (index !== -1) { 147 | activeFeatures.splice(index, 1); 148 | layer.setStyle(defaultStyle); 149 | } else { 150 | activeFeatures.push(feature); 151 | layer.setStyle(activeStyle); 152 | } 153 | 154 | // Update filters 155 | var updatedFilters = _array_unique($.map(activeFeatures, function (feature) { 156 | return $.map(filterFieldsWithResourceKeyField, function (field) { 157 | return feature.data[field]; 158 | }); 159 | })); 160 | 161 | ckan.views.filters.setAndRedirectTo(resourceKeyField, 162 | updatedFilters, 163 | redirectToUrl); 164 | } 165 | 166 | function activateIfNeeded(layer) { 167 | var id = layer.feature.properties[geojsonKeyField], 168 | feature = featuresValues[id]; 169 | 170 | if ($.inArray(feature, activeFeatures) !== -1) { 171 | layer.setStyle(activeStyle); 172 | } 173 | } 174 | 175 | function _array_unique(array) { 176 | var result = [], 177 | i; 178 | 179 | for (i = 0; i < array.length; i++) { 180 | if (result.indexOf(array[i]) === -1) { 181 | result.push(array[i]); 182 | } 183 | } 184 | 185 | return result; 186 | } 187 | 188 | return { 189 | toggleActive: toggleActive, 190 | activateIfNeeded: activateIfNeeded 191 | }; 192 | } 193 | 194 | return initialize; 195 | })(); 196 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/resource.config: -------------------------------------------------------------------------------- 1 | [IE conditional] 2 | 3 | lte IE 8 = 4 | vendor/excanvas.js 5 | 6 | [depends] 7 | 8 | main = base/main 9 | 10 | [groups] 11 | 12 | main = 13 | vendor/leaflet.js 14 | vendor/leaflet.css 15 | vendor/leaflet.label.js 16 | vendor/leaflet.label.css 17 | vendor/d3.scale.quantize.js 18 | vendor/backend.ckan.js 19 | vendor/queryStringToJSON.js 20 | ckan_map_modules.js 21 | 22 | navigablemap.js 23 | navigablemap.css 24 | 25 | choroplethmap.js 26 | choroplethmap.css 27 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/vendor/backend.ckan.js: -------------------------------------------------------------------------------- 1 | this.recline = this.recline || {}; 2 | this.recline.Backend = this.recline.Backend || {}; 3 | this.recline.Backend.Ckan = this.recline.Backend.Ckan || {}; 4 | 5 | (function(my) { 6 | // ## CKAN Backend 7 | // 8 | // This provides connection to the CKAN DataStore (v2) 9 | // 10 | // General notes 11 | // 12 | // We need 2 things to make most requests: 13 | // 14 | // 1. CKAN API endpoint 15 | // 2. ID of resource for which request is being made 16 | // 17 | // There are 2 ways to specify this information. 18 | // 19 | // EITHER (checked in order): 20 | // 21 | // * Every dataset must have an id equal to its resource id on the CKAN instance 22 | // * The dataset has an endpoint attribute pointing to the CKAN API endpoint 23 | // 24 | // OR: 25 | // 26 | // Set the url attribute of the dataset to point to the Resource on the CKAN instance. The endpoint and id will then be automatically computed. 27 | 28 | my.__type__ = 'ckan'; 29 | 30 | 31 | // use either jQuery or Underscore Deferred depending on what is available 32 | var underscoreOrJquery = this.jQuery || this._; 33 | var _map = underscoreOrJquery.map; 34 | var _each = function _each(list, iterator) { 35 | if (this.jQuery) { 36 | this.jQuery.each(list, function (index, value) { 37 | iterator(value, index); 38 | }); 39 | } else { 40 | this._.each(list, iterator); 41 | } 42 | } 43 | var _deferred = underscoreOrJquery.Deferred; 44 | 45 | // Default CKAN API endpoint used for requests (you can change this but it will affect every request!) 46 | // 47 | // DEPRECATION: this will be removed in v0.7. Please set endpoint attribute on dataset instead 48 | my.API_ENDPOINT = 'http://datahub.io/api'; 49 | 50 | // ### fetch 51 | my.fetch = function(dataset) { 52 | var wrapper; 53 | if (dataset.endpoint) { 54 | wrapper = my.DataStore(dataset.endpoint); 55 | } else { 56 | var out = my._parseCkanResourceUrl(dataset.url); 57 | dataset.id = out.resource_id; 58 | wrapper = my.DataStore(out.endpoint); 59 | } 60 | var dfd = new _deferred(); 61 | var jqxhr = wrapper.search({resource_id: dataset.id, limit: 0}); 62 | jqxhr.done(function(results) { 63 | // map ckan types to our usual types ... 64 | var fields = _map(results.result.fields, function(field) { 65 | field.type = field.type in CKAN_TYPES_MAP ? CKAN_TYPES_MAP[field.type] : field.type; 66 | return field; 67 | }); 68 | var out = { 69 | fields: fields, 70 | useMemoryStore: false 71 | }; 72 | dfd.resolve(out); 73 | }); 74 | return dfd.promise(); 75 | }; 76 | 77 | // only put in the module namespace so we can access for tests! 78 | my._normalizeQuery = function(queryObj, dataset) { 79 | var actualQuery = { 80 | resource_id: dataset.id, 81 | q: queryObj.q, 82 | filters: {}, 83 | limit: queryObj.size || 10, 84 | offset: queryObj.from || 0 85 | }; 86 | 87 | if (queryObj.sort && queryObj.sort.length > 0) { 88 | var _tmp = _map(queryObj.sort, function(sortObj) { 89 | return sortObj.field + ' ' + (sortObj.order || ''); 90 | }); 91 | actualQuery.sort = _tmp.join(','); 92 | } 93 | 94 | if (queryObj.filters && queryObj.filters.length > 0) { 95 | _each(queryObj.filters, function(filter) { 96 | if (filter.type === "term") { 97 | actualQuery.filters[filter.field] = filter.term; 98 | } 99 | }); 100 | } 101 | return actualQuery; 102 | }; 103 | 104 | my.query = function(queryObj, dataset) { 105 | var wrapper; 106 | if (dataset.endpoint) { 107 | wrapper = my.DataStore(dataset.endpoint); 108 | } else { 109 | var out = my._parseCkanResourceUrl(dataset.url); 110 | dataset.id = out.resource_id; 111 | wrapper = my.DataStore(out.endpoint); 112 | } 113 | var actualQuery = my._normalizeQuery(queryObj, dataset); 114 | var dfd = new _deferred(); 115 | var jqxhr = wrapper.search(actualQuery); 116 | jqxhr.done(function(results) { 117 | var out = { 118 | total: results.result.total, 119 | hits: results.result.records 120 | }; 121 | dfd.resolve(out); 122 | }); 123 | return dfd.promise(); 124 | }; 125 | 126 | my.search_sql = function(sql, dataset) { 127 | var wrapper; 128 | if (dataset.endpoint) { 129 | wrapper = my.DataStore(dataset.endpoint); 130 | } else { 131 | var out = my._parseCkanResourceUrl(dataset.url); 132 | dataset.id = out.resource_id; 133 | wrapper = my.DataStore(out.endpoint); 134 | } 135 | var dfd = new _deferred(); 136 | var jqxhr = wrapper.search_sql(sql); 137 | jqxhr.done(function(results) { 138 | var out = { 139 | hits: results.result.records 140 | }; 141 | dfd.resolve(out); 142 | }); 143 | return dfd.promise(); 144 | } 145 | 146 | // ### DataStore 147 | // 148 | // Simple wrapper around the CKAN DataStore API 149 | // 150 | // @param endpoint: CKAN api endpoint (e.g. http://datahub.io/api) 151 | my.DataStore = function(endpoint) { 152 | var that = {endpoint: endpoint || my.API_ENDPOINT}; 153 | 154 | that.search = function(data) { 155 | var searchUrl = that.endpoint + '/3/action/datastore_search'; 156 | var jqxhr = jQuery.ajax({ 157 | url: searchUrl, 158 | type: 'POST', 159 | data: JSON.stringify(data) 160 | }); 161 | return jqxhr; 162 | }; 163 | 164 | that.search_sql = function(sql) { 165 | var searchUrl = that.endpoint + '/3/action/datastore_search_sql'; 166 | var jqxhr = jQuery.ajax({ 167 | url: searchUrl, 168 | type: 'GET', 169 | data: { 170 | sql: sql 171 | } 172 | }); 173 | return jqxhr; 174 | } 175 | 176 | return that; 177 | }; 178 | 179 | // Parse a normal CKAN resource URL and return API endpoint etc 180 | // 181 | // Normal URL is something like http://demo.ckan.org/dataset/some-dataset/resource/eb23e809-ccbb-4ad1-820a-19586fc4bebd 182 | my._parseCkanResourceUrl = function(url) { 183 | parts = url.split('/'); 184 | var len = parts.length; 185 | return { 186 | resource_id: parts[len-1], 187 | endpoint: parts.slice(0,[len-4]).join('/') + '/api' 188 | }; 189 | }; 190 | 191 | var CKAN_TYPES_MAP = { 192 | 'int4': 'integer', 193 | 'int8': 'integer', 194 | 'float8': 'float' 195 | }; 196 | 197 | }(this.recline.Backend.Ckan)); 198 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/vendor/d3.scale.quantize.js: -------------------------------------------------------------------------------- 1 | // Imported from d3js 3.4.1, commit b3d9f5e6 2 | 3 | this.d3 = this.d3 || {}; 4 | this.d3.scale = this.d3.scale || {}; 5 | 6 | (function () { 7 | function d3_scaleExtent(domain) { 8 | var start = domain[0], stop = domain[domain.length - 1]; 9 | return start < stop ? [start, stop] : [stop, start]; 10 | } 11 | 12 | function d3_scaleRange(scale) { 13 | return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); 14 | } 15 | 16 | this.d3.scale.quantize = function() { 17 | return d3_scale_quantize(0, 1, [0, 1]); 18 | }; 19 | 20 | function d3_scale_quantize(x0, x1, range) { 21 | var kx, i; 22 | 23 | function scale(x) { 24 | return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; 25 | } 26 | 27 | function rescale() { 28 | kx = range.length / (x1 - x0); 29 | i = range.length - 1; 30 | return scale; 31 | } 32 | 33 | scale.domain = function(x) { 34 | if (!arguments.length) return [x0, x1]; 35 | x0 = +x[0]; 36 | x1 = +x[x.length - 1]; 37 | return rescale(); 38 | }; 39 | 40 | scale.range = function(x) { 41 | if (!arguments.length) return range; 42 | range = x; 43 | return rescale(); 44 | }; 45 | 46 | scale.invertExtent = function(y) { 47 | y = range.indexOf(y); 48 | y = y < 0 ? NaN : y / kx + x0; 49 | return [y, y + 1 / kx]; 50 | }; 51 | 52 | scale.copy = function() { 53 | return d3_scale_quantize(x0, x1, range); // copy on write 54 | }; 55 | 56 | return rescale(); 57 | } 58 | })(); 59 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/vendor/excanvas.js: -------------------------------------------------------------------------------- 1 | // Copyright 2006 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | // Known Issues: 17 | // 18 | // * Patterns are not implemented. 19 | // * Radial gradient are not implemented. The VML version of these look very 20 | // different from the canvas one. 21 | // * Clipping paths are not implemented. 22 | // * Coordsize. The width and height attribute have higher priority than the 23 | // width and height style values which isn't correct. 24 | // * Painting mode isn't implemented. 25 | // * Canvas width/height should is using content-box by default. IE in 26 | // Quirks mode will draw the canvas using border-box. Either change your 27 | // doctype to HTML5 28 | // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) 29 | // or use Box Sizing Behavior from WebFX 30 | // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) 31 | // * Non uniform scaling does not correctly scale strokes. 32 | // * Optimize. There is always room for speed improvements. 33 | 34 | // Only add this code if we do not already have a canvas implementation 35 | if (!document.createElement('canvas').getContext) { 36 | 37 | (function() { 38 | 39 | // alias some functions to make (compiled) code shorter 40 | var m = Math; 41 | var mr = m.round; 42 | var ms = m.sin; 43 | var mc = m.cos; 44 | var abs = m.abs; 45 | var sqrt = m.sqrt; 46 | 47 | // this is used for sub pixel precision 48 | var Z = 10; 49 | var Z2 = Z / 2; 50 | 51 | /** 52 | * This funtion is assigned to the elements as element.getContext(). 53 | * @this {HTMLElement} 54 | * @return {CanvasRenderingContext2D_} 55 | */ 56 | function getContext() { 57 | return this.context_ || 58 | (this.context_ = new CanvasRenderingContext2D_(this)); 59 | } 60 | 61 | var slice = Array.prototype.slice; 62 | 63 | /** 64 | * Binds a function to an object. The returned function will always use the 65 | * passed in {@code obj} as {@code this}. 66 | * 67 | * Example: 68 | * 69 | * g = bind(f, obj, a, b) 70 | * g(c, d) // will do f.call(obj, a, b, c, d) 71 | * 72 | * @param {Function} f The function to bind the object to 73 | * @param {Object} obj The object that should act as this when the function 74 | * is called 75 | * @param {*} var_args Rest arguments that will be used as the initial 76 | * arguments when the function is called 77 | * @return {Function} A new function that has bound this 78 | */ 79 | function bind(f, obj, var_args) { 80 | var a = slice.call(arguments, 2); 81 | return function() { 82 | return f.apply(obj, a.concat(slice.call(arguments))); 83 | }; 84 | } 85 | 86 | var G_vmlCanvasManager_ = { 87 | init: function(opt_doc) { 88 | if (/MSIE/.test(navigator.userAgent) && !window.opera) { 89 | var doc = opt_doc || document; 90 | // Create a dummy element so that IE will allow canvas elements to be 91 | // recognized. 92 | doc.createElement('canvas'); 93 | doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); 94 | } 95 | }, 96 | 97 | init_: function(doc) { 98 | // create xmlns 99 | if (!doc.namespaces['g_vml_']) { 100 | doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml', 101 | '#default#VML'); 102 | 103 | } 104 | if (!doc.namespaces['g_o_']) { 105 | doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office', 106 | '#default#VML'); 107 | } 108 | 109 | // Setup default CSS. Only add one style sheet per document 110 | if (!doc.styleSheets['ex_canvas_']) { 111 | var ss = doc.createStyleSheet(); 112 | ss.owningElement.id = 'ex_canvas_'; 113 | ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + 114 | // default size is 300x150 in Gecko and Opera 115 | 'text-align:left;width:300px;height:150px}' + 116 | 'g_vml_\\:*{behavior:url(#default#VML)}' + 117 | 'g_o_\\:*{behavior:url(#default#VML)}'; 118 | 119 | } 120 | 121 | // find all canvas elements 122 | var els = doc.getElementsByTagName('canvas'); 123 | for (var i = 0; i < els.length; i++) { 124 | this.initElement(els[i]); 125 | } 126 | }, 127 | 128 | /** 129 | * Public initializes a canvas element so that it can be used as canvas 130 | * element from now on. This is called automatically before the page is 131 | * loaded but if you are creating elements using createElement you need to 132 | * make sure this is called on the element. 133 | * @param {HTMLElement} el The canvas element to initialize. 134 | * @return {HTMLElement} the element that was created. 135 | */ 136 | initElement: function(el) { 137 | if (!el.getContext) { 138 | 139 | el.getContext = getContext; 140 | 141 | // Remove fallback content. There is no way to hide text nodes so we 142 | // just remove all childNodes. We could hide all elements and remove 143 | // text nodes but who really cares about the fallback content. 144 | el.innerHTML = ''; 145 | 146 | // do not use inline function because that will leak memory 147 | el.attachEvent('onpropertychange', onPropertyChange); 148 | el.attachEvent('onresize', onResize); 149 | 150 | var attrs = el.attributes; 151 | if (attrs.width && attrs.width.specified) { 152 | // TODO: use runtimeStyle and coordsize 153 | // el.getContext().setWidth_(attrs.width.nodeValue); 154 | el.style.width = attrs.width.nodeValue + 'px'; 155 | } else { 156 | el.width = el.clientWidth; 157 | } 158 | if (attrs.height && attrs.height.specified) { 159 | // TODO: use runtimeStyle and coordsize 160 | // el.getContext().setHeight_(attrs.height.nodeValue); 161 | el.style.height = attrs.height.nodeValue + 'px'; 162 | } else { 163 | el.height = el.clientHeight; 164 | } 165 | //el.getContext().setCoordsize_() 166 | } 167 | return el; 168 | } 169 | }; 170 | 171 | function onPropertyChange(e) { 172 | var el = e.srcElement; 173 | 174 | switch (e.propertyName) { 175 | case 'width': 176 | el.style.width = el.attributes.width.nodeValue + 'px'; 177 | el.getContext().clearRect(); 178 | break; 179 | case 'height': 180 | el.style.height = el.attributes.height.nodeValue + 'px'; 181 | el.getContext().clearRect(); 182 | break; 183 | } 184 | } 185 | 186 | function onResize(e) { 187 | var el = e.srcElement; 188 | if (el.firstChild) { 189 | el.firstChild.style.width = el.clientWidth + 'px'; 190 | el.firstChild.style.height = el.clientHeight + 'px'; 191 | } 192 | } 193 | 194 | G_vmlCanvasManager_.init(); 195 | 196 | // precompute "00" to "FF" 197 | var dec2hex = []; 198 | for (var i = 0; i < 16; i++) { 199 | for (var j = 0; j < 16; j++) { 200 | dec2hex[i * 16 + j] = i.toString(16) + j.toString(16); 201 | } 202 | } 203 | 204 | function createMatrixIdentity() { 205 | return [ 206 | [1, 0, 0], 207 | [0, 1, 0], 208 | [0, 0, 1] 209 | ]; 210 | } 211 | 212 | function matrixMultiply(m1, m2) { 213 | var result = createMatrixIdentity(); 214 | 215 | for (var x = 0; x < 3; x++) { 216 | for (var y = 0; y < 3; y++) { 217 | var sum = 0; 218 | 219 | for (var z = 0; z < 3; z++) { 220 | sum += m1[x][z] * m2[z][y]; 221 | } 222 | 223 | result[x][y] = sum; 224 | } 225 | } 226 | return result; 227 | } 228 | 229 | function copyState(o1, o2) { 230 | o2.fillStyle = o1.fillStyle; 231 | o2.lineCap = o1.lineCap; 232 | o2.lineJoin = o1.lineJoin; 233 | o2.lineWidth = o1.lineWidth; 234 | o2.miterLimit = o1.miterLimit; 235 | o2.shadowBlur = o1.shadowBlur; 236 | o2.shadowColor = o1.shadowColor; 237 | o2.shadowOffsetX = o1.shadowOffsetX; 238 | o2.shadowOffsetY = o1.shadowOffsetY; 239 | o2.strokeStyle = o1.strokeStyle; 240 | o2.globalAlpha = o1.globalAlpha; 241 | o2.arcScaleX_ = o1.arcScaleX_; 242 | o2.arcScaleY_ = o1.arcScaleY_; 243 | o2.lineScale_ = o1.lineScale_; 244 | } 245 | 246 | function processStyle(styleString) { 247 | var str, alpha = 1; 248 | 249 | styleString = String(styleString); 250 | if (styleString.substring(0, 3) == 'rgb') { 251 | var start = styleString.indexOf('(', 3); 252 | var end = styleString.indexOf(')', start + 1); 253 | var guts = styleString.substring(start + 1, end).split(','); 254 | 255 | str = '#'; 256 | for (var i = 0; i < 3; i++) { 257 | str += dec2hex[Number(guts[i])]; 258 | } 259 | 260 | if (guts.length == 4 && styleString.substr(3, 1) == 'a') { 261 | alpha = guts[3]; 262 | } 263 | } else { 264 | str = styleString; 265 | } 266 | 267 | return {color: str, alpha: alpha}; 268 | } 269 | 270 | function processLineCap(lineCap) { 271 | switch (lineCap) { 272 | case 'butt': 273 | return 'flat'; 274 | case 'round': 275 | return 'round'; 276 | case 'square': 277 | default: 278 | return 'square'; 279 | } 280 | } 281 | 282 | /** 283 | * This class implements CanvasRenderingContext2D interface as described by 284 | * the WHATWG. 285 | * @param {HTMLElement} surfaceElement The element that the 2D context should 286 | * be associated with 287 | */ 288 | function CanvasRenderingContext2D_(surfaceElement) { 289 | this.m_ = createMatrixIdentity(); 290 | 291 | this.mStack_ = []; 292 | this.aStack_ = []; 293 | this.currentPath_ = []; 294 | 295 | // Canvas context properties 296 | this.strokeStyle = '#000'; 297 | this.fillStyle = '#000'; 298 | 299 | this.lineWidth = 1; 300 | this.lineJoin = 'miter'; 301 | this.lineCap = 'butt'; 302 | this.miterLimit = Z * 1; 303 | this.globalAlpha = 1; 304 | this.canvas = surfaceElement; 305 | 306 | var el = surfaceElement.ownerDocument.createElement('div'); 307 | el.style.width = surfaceElement.clientWidth + 'px'; 308 | el.style.height = surfaceElement.clientHeight + 'px'; 309 | el.style.overflow = 'hidden'; 310 | el.style.position = 'absolute'; 311 | surfaceElement.appendChild(el); 312 | 313 | this.element_ = el; 314 | this.arcScaleX_ = 1; 315 | this.arcScaleY_ = 1; 316 | this.lineScale_ = 1; 317 | } 318 | 319 | var contextPrototype = CanvasRenderingContext2D_.prototype; 320 | contextPrototype.clearRect = function() { 321 | this.element_.innerHTML = ''; 322 | }; 323 | 324 | contextPrototype.beginPath = function() { 325 | // TODO: Branch current matrix so that save/restore has no effect 326 | // as per safari docs. 327 | this.currentPath_ = []; 328 | }; 329 | 330 | contextPrototype.moveTo = function(aX, aY) { 331 | var p = this.getCoords_(aX, aY); 332 | this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); 333 | this.currentX_ = p.x; 334 | this.currentY_ = p.y; 335 | }; 336 | 337 | contextPrototype.lineTo = function(aX, aY) { 338 | var p = this.getCoords_(aX, aY); 339 | this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); 340 | 341 | this.currentX_ = p.x; 342 | this.currentY_ = p.y; 343 | }; 344 | 345 | contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, 346 | aCP2x, aCP2y, 347 | aX, aY) { 348 | var p = this.getCoords_(aX, aY); 349 | var cp1 = this.getCoords_(aCP1x, aCP1y); 350 | var cp2 = this.getCoords_(aCP2x, aCP2y); 351 | bezierCurveTo(this, cp1, cp2, p); 352 | }; 353 | 354 | // Helper function that takes the already fixed cordinates. 355 | function bezierCurveTo(self, cp1, cp2, p) { 356 | self.currentPath_.push({ 357 | type: 'bezierCurveTo', 358 | cp1x: cp1.x, 359 | cp1y: cp1.y, 360 | cp2x: cp2.x, 361 | cp2y: cp2.y, 362 | x: p.x, 363 | y: p.y 364 | }); 365 | self.currentX_ = p.x; 366 | self.currentY_ = p.y; 367 | } 368 | 369 | contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { 370 | // the following is lifted almost directly from 371 | // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes 372 | 373 | var cp = this.getCoords_(aCPx, aCPy); 374 | var p = this.getCoords_(aX, aY); 375 | 376 | var cp1 = { 377 | x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), 378 | y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) 379 | }; 380 | var cp2 = { 381 | x: cp1.x + (p.x - this.currentX_) / 3.0, 382 | y: cp1.y + (p.y - this.currentY_) / 3.0 383 | }; 384 | 385 | bezierCurveTo(this, cp1, cp2, p); 386 | }; 387 | 388 | contextPrototype.arc = function(aX, aY, aRadius, 389 | aStartAngle, aEndAngle, aClockwise) { 390 | aRadius *= Z; 391 | var arcType = aClockwise ? 'at' : 'wa'; 392 | 393 | var xStart = aX + mc(aStartAngle) * aRadius - Z2; 394 | var yStart = aY + ms(aStartAngle) * aRadius - Z2; 395 | 396 | var xEnd = aX + mc(aEndAngle) * aRadius - Z2; 397 | var yEnd = aY + ms(aEndAngle) * aRadius - Z2; 398 | 399 | // IE won't render arches drawn counter clockwise if xStart == xEnd. 400 | if (xStart == xEnd && !aClockwise) { 401 | xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something 402 | // that can be represented in binary 403 | } 404 | 405 | var p = this.getCoords_(aX, aY); 406 | var pStart = this.getCoords_(xStart, yStart); 407 | var pEnd = this.getCoords_(xEnd, yEnd); 408 | 409 | this.currentPath_.push({type: arcType, 410 | x: p.x, 411 | y: p.y, 412 | radius: aRadius, 413 | xStart: pStart.x, 414 | yStart: pStart.y, 415 | xEnd: pEnd.x, 416 | yEnd: pEnd.y}); 417 | 418 | }; 419 | 420 | contextPrototype.rect = function(aX, aY, aWidth, aHeight) { 421 | this.moveTo(aX, aY); 422 | this.lineTo(aX + aWidth, aY); 423 | this.lineTo(aX + aWidth, aY + aHeight); 424 | this.lineTo(aX, aY + aHeight); 425 | this.closePath(); 426 | }; 427 | 428 | contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { 429 | var oldPath = this.currentPath_; 430 | this.beginPath(); 431 | 432 | this.moveTo(aX, aY); 433 | this.lineTo(aX + aWidth, aY); 434 | this.lineTo(aX + aWidth, aY + aHeight); 435 | this.lineTo(aX, aY + aHeight); 436 | this.closePath(); 437 | this.stroke(); 438 | 439 | this.currentPath_ = oldPath; 440 | }; 441 | 442 | contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { 443 | var oldPath = this.currentPath_; 444 | this.beginPath(); 445 | 446 | this.moveTo(aX, aY); 447 | this.lineTo(aX + aWidth, aY); 448 | this.lineTo(aX + aWidth, aY + aHeight); 449 | this.lineTo(aX, aY + aHeight); 450 | this.closePath(); 451 | this.fill(); 452 | 453 | this.currentPath_ = oldPath; 454 | }; 455 | 456 | contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { 457 | var gradient = new CanvasGradient_('gradient'); 458 | gradient.x0_ = aX0; 459 | gradient.y0_ = aY0; 460 | gradient.x1_ = aX1; 461 | gradient.y1_ = aY1; 462 | return gradient; 463 | }; 464 | 465 | contextPrototype.createRadialGradient = function(aX0, aY0, aR0, 466 | aX1, aY1, aR1) { 467 | var gradient = new CanvasGradient_('gradientradial'); 468 | gradient.x0_ = aX0; 469 | gradient.y0_ = aY0; 470 | gradient.r0_ = aR0; 471 | gradient.x1_ = aX1; 472 | gradient.y1_ = aY1; 473 | gradient.r1_ = aR1; 474 | return gradient; 475 | }; 476 | 477 | contextPrototype.drawImage = function(image, var_args) { 478 | var dx, dy, dw, dh, sx, sy, sw, sh; 479 | 480 | // to find the original width we overide the width and height 481 | var oldRuntimeWidth = image.runtimeStyle.width; 482 | var oldRuntimeHeight = image.runtimeStyle.height; 483 | image.runtimeStyle.width = 'auto'; 484 | image.runtimeStyle.height = 'auto'; 485 | 486 | // get the original size 487 | var w = image.width; 488 | var h = image.height; 489 | 490 | // and remove overides 491 | image.runtimeStyle.width = oldRuntimeWidth; 492 | image.runtimeStyle.height = oldRuntimeHeight; 493 | 494 | if (arguments.length == 3) { 495 | dx = arguments[1]; 496 | dy = arguments[2]; 497 | sx = sy = 0; 498 | sw = dw = w; 499 | sh = dh = h; 500 | } else if (arguments.length == 5) { 501 | dx = arguments[1]; 502 | dy = arguments[2]; 503 | dw = arguments[3]; 504 | dh = arguments[4]; 505 | sx = sy = 0; 506 | sw = w; 507 | sh = h; 508 | } else if (arguments.length == 9) { 509 | sx = arguments[1]; 510 | sy = arguments[2]; 511 | sw = arguments[3]; 512 | sh = arguments[4]; 513 | dx = arguments[5]; 514 | dy = arguments[6]; 515 | dw = arguments[7]; 516 | dh = arguments[8]; 517 | } else { 518 | throw Error('Invalid number of arguments'); 519 | } 520 | 521 | var d = this.getCoords_(dx, dy); 522 | 523 | var w2 = sw / 2; 524 | var h2 = sh / 2; 525 | 526 | var vmlStr = []; 527 | 528 | var W = 10; 529 | var H = 10; 530 | 531 | // For some reason that I've now forgotten, using divs didn't work 532 | vmlStr.push(' ' , 571 | '', 579 | ''); 580 | 581 | this.element_.insertAdjacentHTML('BeforeEnd', 582 | vmlStr.join('')); 583 | }; 584 | 585 | contextPrototype.stroke = function(aFill) { 586 | var lineStr = []; 587 | var lineOpen = false; 588 | var a = processStyle(aFill ? this.fillStyle : this.strokeStyle); 589 | var color = a.color; 590 | var opacity = a.alpha * this.globalAlpha; 591 | 592 | var W = 10; 593 | var H = 10; 594 | 595 | lineStr.push(''); 662 | 663 | if (!aFill) { 664 | var lineWidth = this.lineScale_ * this.lineWidth; 665 | 666 | // VML cannot correctly render a line if the width is less than 1px. 667 | // In that case, we dilute the color to make the line look thinner. 668 | if (lineWidth < 1) { 669 | opacity *= lineWidth; 670 | } 671 | 672 | lineStr.push( 673 | '' 680 | ); 681 | } else if (typeof this.fillStyle == 'object') { 682 | var fillStyle = this.fillStyle; 683 | var angle = 0; 684 | var focus = {x: 0, y: 0}; 685 | 686 | // additional offset 687 | var shift = 0; 688 | // scale factor for offset 689 | var expansion = 1; 690 | 691 | if (fillStyle.type_ == 'gradient') { 692 | var x0 = fillStyle.x0_ / this.arcScaleX_; 693 | var y0 = fillStyle.y0_ / this.arcScaleY_; 694 | var x1 = fillStyle.x1_ / this.arcScaleX_; 695 | var y1 = fillStyle.y1_ / this.arcScaleY_; 696 | var p0 = this.getCoords_(x0, y0); 697 | var p1 = this.getCoords_(x1, y1); 698 | var dx = p1.x - p0.x; 699 | var dy = p1.y - p0.y; 700 | angle = Math.atan2(dx, dy) * 180 / Math.PI; 701 | 702 | // The angle should be a non-negative number. 703 | if (angle < 0) { 704 | angle += 360; 705 | } 706 | 707 | // Very small angles produce an unexpected result because they are 708 | // converted to a scientific notation string. 709 | if (angle < 1e-6) { 710 | angle = 0; 711 | } 712 | } else { 713 | var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_); 714 | var width = max.x - min.x; 715 | var height = max.y - min.y; 716 | focus = { 717 | x: (p0.x - min.x) / width, 718 | y: (p0.y - min.y) / height 719 | }; 720 | 721 | width /= this.arcScaleX_ * Z; 722 | height /= this.arcScaleY_ * Z; 723 | var dimension = m.max(width, height); 724 | shift = 2 * fillStyle.r0_ / dimension; 725 | expansion = 2 * fillStyle.r1_ / dimension - shift; 726 | } 727 | 728 | // We need to sort the color stops in ascending order by offset, 729 | // otherwise IE won't interpret it correctly. 730 | var stops = fillStyle.colors_; 731 | stops.sort(function(cs1, cs2) { 732 | return cs1.offset - cs2.offset; 733 | }); 734 | 735 | var length = stops.length; 736 | var color1 = stops[0].color; 737 | var color2 = stops[length - 1].color; 738 | var opacity1 = stops[0].alpha * this.globalAlpha; 739 | var opacity2 = stops[length - 1].alpha * this.globalAlpha; 740 | 741 | var colors = []; 742 | for (var i = 0; i < length; i++) { 743 | var stop = stops[i]; 744 | colors.push(stop.offset * expansion + shift + ' ' + stop.color); 745 | } 746 | 747 | // When colors attribute is used, the meanings of opacity and o:opacity2 748 | // are reversed. 749 | lineStr.push(''); 758 | } else { 759 | lineStr.push(''); 761 | } 762 | 763 | lineStr.push(''); 764 | 765 | this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); 766 | }; 767 | 768 | contextPrototype.fill = function() { 769 | this.stroke(true); 770 | } 771 | 772 | contextPrototype.closePath = function() { 773 | this.currentPath_.push({type: 'close'}); 774 | }; 775 | 776 | /** 777 | * @private 778 | */ 779 | contextPrototype.getCoords_ = function(aX, aY) { 780 | var m = this.m_; 781 | return { 782 | x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, 783 | y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 784 | } 785 | }; 786 | 787 | contextPrototype.save = function() { 788 | var o = {}; 789 | copyState(this, o); 790 | this.aStack_.push(o); 791 | this.mStack_.push(this.m_); 792 | this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); 793 | }; 794 | 795 | contextPrototype.restore = function() { 796 | copyState(this.aStack_.pop(), this); 797 | this.m_ = this.mStack_.pop(); 798 | }; 799 | 800 | function matrixIsFinite(m) { 801 | for (var j = 0; j < 3; j++) { 802 | for (var k = 0; k < 2; k++) { 803 | if (!isFinite(m[j][k]) || isNaN(m[j][k])) { 804 | return false; 805 | } 806 | } 807 | } 808 | return true; 809 | } 810 | 811 | function setM(ctx, m, updateLineScale) { 812 | if (!matrixIsFinite(m)) { 813 | return; 814 | } 815 | ctx.m_ = m; 816 | 817 | if (updateLineScale) { 818 | // Get the line scale. 819 | // Determinant of this.m_ means how much the area is enlarged by the 820 | // transformation. So its square root can be used as a scale factor 821 | // for width. 822 | var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; 823 | ctx.lineScale_ = sqrt(abs(det)); 824 | } 825 | } 826 | 827 | contextPrototype.translate = function(aX, aY) { 828 | var m1 = [ 829 | [1, 0, 0], 830 | [0, 1, 0], 831 | [aX, aY, 1] 832 | ]; 833 | 834 | setM(this, matrixMultiply(m1, this.m_), false); 835 | }; 836 | 837 | contextPrototype.rotate = function(aRot) { 838 | var c = mc(aRot); 839 | var s = ms(aRot); 840 | 841 | var m1 = [ 842 | [c, s, 0], 843 | [-s, c, 0], 844 | [0, 0, 1] 845 | ]; 846 | 847 | setM(this, matrixMultiply(m1, this.m_), false); 848 | }; 849 | 850 | contextPrototype.scale = function(aX, aY) { 851 | this.arcScaleX_ *= aX; 852 | this.arcScaleY_ *= aY; 853 | var m1 = [ 854 | [aX, 0, 0], 855 | [0, aY, 0], 856 | [0, 0, 1] 857 | ]; 858 | 859 | setM(this, matrixMultiply(m1, this.m_), true); 860 | }; 861 | 862 | contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { 863 | var m1 = [ 864 | [m11, m12, 0], 865 | [m21, m22, 0], 866 | [dx, dy, 1] 867 | ]; 868 | 869 | setM(this, matrixMultiply(m1, this.m_), true); 870 | }; 871 | 872 | contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { 873 | var m = [ 874 | [m11, m12, 0], 875 | [m21, m22, 0], 876 | [dx, dy, 1] 877 | ]; 878 | 879 | setM(this, m, true); 880 | }; 881 | 882 | /******** STUBS ********/ 883 | contextPrototype.clip = function() { 884 | // TODO: Implement 885 | }; 886 | 887 | contextPrototype.arcTo = function() { 888 | // TODO: Implement 889 | }; 890 | 891 | contextPrototype.createPattern = function() { 892 | return new CanvasPattern_; 893 | }; 894 | 895 | // Gradient / Pattern Stubs 896 | function CanvasGradient_(aType) { 897 | this.type_ = aType; 898 | this.x0_ = 0; 899 | this.y0_ = 0; 900 | this.r0_ = 0; 901 | this.x1_ = 0; 902 | this.y1_ = 0; 903 | this.r1_ = 0; 904 | this.colors_ = []; 905 | } 906 | 907 | CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { 908 | aColor = processStyle(aColor); 909 | this.colors_.push({offset: aOffset, 910 | color: aColor.color, 911 | alpha: aColor.alpha}); 912 | }; 913 | 914 | function CanvasPattern_() {} 915 | 916 | // set up externs 917 | G_vmlCanvasManager = G_vmlCanvasManager_; 918 | CanvasRenderingContext2D = CanvasRenderingContext2D_; 919 | CanvasGradient = CanvasGradient_; 920 | CanvasPattern = CanvasPattern_; 921 | 922 | })(); 923 | 924 | } // if 925 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/vendor/leaflet.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-map-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-pane, 8 | .leaflet-tile-container, 9 | .leaflet-overlay-pane, 10 | .leaflet-shadow-pane, 11 | .leaflet-marker-pane, 12 | .leaflet-popup-pane, 13 | .leaflet-overlay-pane svg, 14 | .leaflet-zoom-box, 15 | .leaflet-image-layer, 16 | .leaflet-layer { 17 | position: absolute; 18 | left: 0; 19 | top: 0; 20 | } 21 | .leaflet-container { 22 | overflow: hidden; 23 | -ms-touch-action: none; 24 | } 25 | .leaflet-tile, 26 | .leaflet-marker-icon, 27 | .leaflet-marker-shadow { 28 | -webkit-user-select: none; 29 | -moz-user-select: none; 30 | user-select: none; 31 | -webkit-user-drag: none; 32 | } 33 | .leaflet-marker-icon, 34 | .leaflet-marker-shadow { 35 | display: block; 36 | } 37 | /* map is broken in FF if you have max-width: 100% on tiles */ 38 | .leaflet-container img { 39 | max-width: none !important; 40 | } 41 | /* stupid Android 2 doesn't understand "max-width: none" properly */ 42 | .leaflet-container img.leaflet-image-layer { 43 | max-width: 15000px !important; 44 | } 45 | .leaflet-tile { 46 | filter: inherit; 47 | visibility: hidden; 48 | } 49 | .leaflet-tile-loaded { 50 | visibility: inherit; 51 | } 52 | .leaflet-zoom-box { 53 | width: 0; 54 | height: 0; 55 | } 56 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ 57 | .leaflet-overlay-pane svg { 58 | -moz-user-select: none; 59 | } 60 | 61 | .leaflet-tile-pane { z-index: 2; } 62 | .leaflet-objects-pane { z-index: 3; } 63 | .leaflet-overlay-pane { z-index: 4; } 64 | .leaflet-shadow-pane { z-index: 5; } 65 | .leaflet-marker-pane { z-index: 6; } 66 | .leaflet-popup-pane { z-index: 7; } 67 | 68 | .leaflet-vml-shape { 69 | width: 1px; 70 | height: 1px; 71 | } 72 | .lvml { 73 | behavior: url(#default#VML); 74 | display: inline-block; 75 | position: absolute; 76 | } 77 | 78 | 79 | /* control positioning */ 80 | 81 | .leaflet-control { 82 | position: relative; 83 | z-index: 7; 84 | pointer-events: auto; 85 | } 86 | .leaflet-top, 87 | .leaflet-bottom { 88 | position: absolute; 89 | z-index: 1000; 90 | pointer-events: none; 91 | } 92 | .leaflet-top { 93 | top: 0; 94 | } 95 | .leaflet-right { 96 | right: 0; 97 | } 98 | .leaflet-bottom { 99 | bottom: 0; 100 | } 101 | .leaflet-left { 102 | left: 0; 103 | } 104 | .leaflet-control { 105 | float: left; 106 | clear: both; 107 | } 108 | .leaflet-right .leaflet-control { 109 | float: right; 110 | } 111 | .leaflet-top .leaflet-control { 112 | margin-top: 10px; 113 | } 114 | .leaflet-bottom .leaflet-control { 115 | margin-bottom: 10px; 116 | } 117 | .leaflet-left .leaflet-control { 118 | margin-left: 10px; 119 | } 120 | .leaflet-right .leaflet-control { 121 | margin-right: 10px; 122 | } 123 | 124 | 125 | /* zoom and fade animations */ 126 | 127 | .leaflet-fade-anim .leaflet-tile, 128 | .leaflet-fade-anim .leaflet-popup { 129 | opacity: 0; 130 | -webkit-transition: opacity 0.2s linear; 131 | -moz-transition: opacity 0.2s linear; 132 | -o-transition: opacity 0.2s linear; 133 | transition: opacity 0.2s linear; 134 | } 135 | .leaflet-fade-anim .leaflet-tile-loaded, 136 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 137 | opacity: 1; 138 | } 139 | 140 | .leaflet-zoom-anim .leaflet-zoom-animated { 141 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 142 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 143 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); 144 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 145 | } 146 | .leaflet-zoom-anim .leaflet-tile, 147 | .leaflet-pan-anim .leaflet-tile, 148 | .leaflet-touching .leaflet-zoom-animated { 149 | -webkit-transition: none; 150 | -moz-transition: none; 151 | -o-transition: none; 152 | transition: none; 153 | } 154 | 155 | .leaflet-zoom-anim .leaflet-zoom-hide { 156 | visibility: hidden; 157 | } 158 | 159 | 160 | /* cursors */ 161 | 162 | .leaflet-clickable { 163 | cursor: pointer; 164 | } 165 | .leaflet-container { 166 | cursor: -webkit-grab; 167 | cursor: -moz-grab; 168 | } 169 | .leaflet-popup-pane, 170 | .leaflet-control { 171 | cursor: auto; 172 | } 173 | .leaflet-dragging .leaflet-container, 174 | .leaflet-dragging .leaflet-clickable { 175 | cursor: move; 176 | cursor: -webkit-grabbing; 177 | cursor: -moz-grabbing; 178 | } 179 | 180 | 181 | /* visual tweaks */ 182 | 183 | .leaflet-container { 184 | background: #ddd; 185 | outline: 0; 186 | } 187 | .leaflet-container a { 188 | color: #0078A8; 189 | } 190 | .leaflet-container a.leaflet-active { 191 | outline: 2px solid orange; 192 | } 193 | .leaflet-zoom-box { 194 | border: 2px dotted #38f; 195 | background: rgba(255,255,255,0.5); 196 | } 197 | 198 | 199 | /* general typography */ 200 | .leaflet-container { 201 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; 202 | } 203 | 204 | 205 | /* general toolbar styles */ 206 | 207 | .leaflet-bar { 208 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 209 | border-radius: 4px; 210 | } 211 | .leaflet-bar a, 212 | .leaflet-bar a:hover { 213 | background-color: #fff; 214 | border-bottom: 1px solid #ccc; 215 | width: 26px; 216 | height: 26px; 217 | line-height: 26px; 218 | display: block; 219 | text-align: center; 220 | text-decoration: none; 221 | color: black; 222 | } 223 | .leaflet-bar a, 224 | .leaflet-control-layers-toggle { 225 | background-position: 50% 50%; 226 | background-repeat: no-repeat; 227 | display: block; 228 | } 229 | .leaflet-bar a:hover { 230 | background-color: #f4f4f4; 231 | } 232 | .leaflet-bar a:first-child { 233 | border-top-left-radius: 4px; 234 | border-top-right-radius: 4px; 235 | } 236 | .leaflet-bar a:last-child { 237 | border-bottom-left-radius: 4px; 238 | border-bottom-right-radius: 4px; 239 | border-bottom: none; 240 | } 241 | .leaflet-bar a.leaflet-disabled { 242 | cursor: default; 243 | background-color: #f4f4f4; 244 | color: #bbb; 245 | } 246 | 247 | .leaflet-touch .leaflet-bar a { 248 | width: 30px; 249 | height: 30px; 250 | line-height: 30px; 251 | } 252 | 253 | 254 | /* zoom control */ 255 | 256 | .leaflet-control-zoom-in, 257 | .leaflet-control-zoom-out { 258 | font: bold 18px 'Lucida Console', Monaco, monospace; 259 | text-indent: 1px; 260 | } 261 | .leaflet-control-zoom-out { 262 | font-size: 20px; 263 | } 264 | 265 | .leaflet-touch .leaflet-control-zoom-in { 266 | font-size: 22px; 267 | } 268 | .leaflet-touch .leaflet-control-zoom-out { 269 | font-size: 24px; 270 | } 271 | 272 | 273 | /* layers control */ 274 | 275 | .leaflet-control-layers { 276 | box-shadow: 0 1px 5px rgba(0,0,0,0.4); 277 | background: #fff; 278 | border-radius: 5px; 279 | } 280 | .leaflet-control-layers-toggle { 281 | background-image: url(images/layers.png); 282 | width: 36px; 283 | height: 36px; 284 | } 285 | .leaflet-retina .leaflet-control-layers-toggle { 286 | background-image: url(images/layers-2x.png); 287 | background-size: 26px 26px; 288 | } 289 | .leaflet-touch .leaflet-control-layers-toggle { 290 | width: 44px; 291 | height: 44px; 292 | } 293 | .leaflet-control-layers .leaflet-control-layers-list, 294 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 295 | display: none; 296 | } 297 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 298 | display: block; 299 | position: relative; 300 | } 301 | .leaflet-control-layers-expanded { 302 | padding: 6px 10px 6px 6px; 303 | color: #333; 304 | background: #fff; 305 | } 306 | .leaflet-control-layers-selector { 307 | margin-top: 2px; 308 | position: relative; 309 | top: 1px; 310 | } 311 | .leaflet-control-layers label { 312 | display: block; 313 | } 314 | .leaflet-control-layers-separator { 315 | height: 0; 316 | border-top: 1px solid #ddd; 317 | margin: 5px -10px 5px -6px; 318 | } 319 | 320 | 321 | /* attribution and scale controls */ 322 | 323 | .leaflet-container .leaflet-control-attribution { 324 | background: #fff; 325 | background: rgba(255, 255, 255, 0.7); 326 | margin: 0; 327 | } 328 | .leaflet-control-attribution, 329 | .leaflet-control-scale-line { 330 | padding: 0 5px; 331 | color: #333; 332 | } 333 | .leaflet-control-attribution a { 334 | text-decoration: none; 335 | } 336 | .leaflet-control-attribution a:hover { 337 | text-decoration: underline; 338 | } 339 | .leaflet-container .leaflet-control-attribution, 340 | .leaflet-container .leaflet-control-scale { 341 | font-size: 11px; 342 | } 343 | .leaflet-left .leaflet-control-scale { 344 | margin-left: 5px; 345 | } 346 | .leaflet-bottom .leaflet-control-scale { 347 | margin-bottom: 5px; 348 | } 349 | .leaflet-control-scale-line { 350 | border: 2px solid #777; 351 | border-top: none; 352 | line-height: 1.1; 353 | padding: 2px 5px 1px; 354 | font-size: 11px; 355 | white-space: nowrap; 356 | overflow: hidden; 357 | -moz-box-sizing: content-box; 358 | box-sizing: content-box; 359 | 360 | background: #fff; 361 | background: rgba(255, 255, 255, 0.5); 362 | } 363 | .leaflet-control-scale-line:not(:first-child) { 364 | border-top: 2px solid #777; 365 | border-bottom: none; 366 | margin-top: -2px; 367 | } 368 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 369 | border-bottom: 2px solid #777; 370 | } 371 | 372 | .leaflet-touch .leaflet-control-attribution, 373 | .leaflet-touch .leaflet-control-layers, 374 | .leaflet-touch .leaflet-bar { 375 | box-shadow: none; 376 | } 377 | .leaflet-touch .leaflet-control-layers, 378 | .leaflet-touch .leaflet-bar { 379 | border: 2px solid rgba(0,0,0,0.2); 380 | background-clip: padding-box; 381 | } 382 | 383 | 384 | /* popup */ 385 | 386 | .leaflet-popup { 387 | position: absolute; 388 | text-align: center; 389 | } 390 | .leaflet-popup-content-wrapper { 391 | padding: 1px; 392 | text-align: left; 393 | border-radius: 12px; 394 | } 395 | .leaflet-popup-content { 396 | margin: 13px 19px; 397 | line-height: 1.4; 398 | } 399 | .leaflet-popup-content p { 400 | margin: 18px 0; 401 | } 402 | .leaflet-popup-tip-container { 403 | margin: 0 auto; 404 | width: 40px; 405 | height: 20px; 406 | position: relative; 407 | overflow: hidden; 408 | } 409 | .leaflet-popup-tip { 410 | width: 17px; 411 | height: 17px; 412 | padding: 1px; 413 | 414 | margin: -10px auto 0; 415 | 416 | -webkit-transform: rotate(45deg); 417 | -moz-transform: rotate(45deg); 418 | -ms-transform: rotate(45deg); 419 | -o-transform: rotate(45deg); 420 | transform: rotate(45deg); 421 | } 422 | .leaflet-popup-content-wrapper, 423 | .leaflet-popup-tip { 424 | background: white; 425 | 426 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 427 | } 428 | .leaflet-container a.leaflet-popup-close-button { 429 | position: absolute; 430 | top: 0; 431 | right: 0; 432 | padding: 4px 4px 0 0; 433 | text-align: center; 434 | width: 18px; 435 | height: 14px; 436 | font: 16px/14px Tahoma, Verdana, sans-serif; 437 | color: #c3c3c3; 438 | text-decoration: none; 439 | font-weight: bold; 440 | background: transparent; 441 | } 442 | .leaflet-container a.leaflet-popup-close-button:hover { 443 | color: #999; 444 | } 445 | .leaflet-popup-scrolled { 446 | overflow: auto; 447 | border-bottom: 1px solid #ddd; 448 | border-top: 1px solid #ddd; 449 | } 450 | 451 | .leaflet-oldie .leaflet-popup-content-wrapper { 452 | zoom: 1; 453 | } 454 | .leaflet-oldie .leaflet-popup-tip { 455 | width: 24px; 456 | margin: 0 auto; 457 | 458 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 459 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 460 | } 461 | .leaflet-oldie .leaflet-popup-tip-container { 462 | margin-top: -1px; 463 | } 464 | 465 | .leaflet-oldie .leaflet-control-zoom, 466 | .leaflet-oldie .leaflet-control-layers, 467 | .leaflet-oldie .leaflet-popup-content-wrapper, 468 | .leaflet-oldie .leaflet-popup-tip { 469 | border: 1px solid #999; 470 | } 471 | 472 | 473 | /* div icon */ 474 | 475 | .leaflet-div-icon { 476 | background: #fff; 477 | border: 1px solid #666; 478 | } 479 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/vendor/leaflet.label.css: -------------------------------------------------------------------------------- 1 | .leaflet-label { 2 | background: rgb(235, 235, 235); 3 | background: rgba(235, 235, 235, 0.81); 4 | background-clip: padding-box; 5 | border-color: #777; 6 | border-color: rgba(0,0,0,0.25); 7 | border-radius: 4px; 8 | border-style: solid; 9 | border-width: 4px; 10 | color: #111; 11 | display: block; 12 | font: 12px/20px "Helvetica Neue", Arial, Helvetica, sans-serif; 13 | font-weight: bold; 14 | padding: 1px 6px; 15 | position: absolute; 16 | -webkit-user-select: none; 17 | -moz-user-select: none; 18 | -ms-user-select: none; 19 | user-select: none; 20 | white-space: nowrap; 21 | z-index: 6; 22 | } 23 | 24 | .leaflet-label.leaflet-clickable { 25 | cursor: pointer; 26 | } 27 | 28 | .leaflet-label:before, 29 | .leaflet-label:after { 30 | border-top: 6px solid transparent; 31 | border-bottom: 6px solid transparent; 32 | content: none; 33 | position: absolute; 34 | top: 5px; 35 | } 36 | 37 | .leaflet-label:before { 38 | border-right: 6px solid black; 39 | border-right-color: inherit; 40 | left: -10px; 41 | } 42 | 43 | .leaflet-label:after { 44 | border-left: 6px solid black; 45 | border-left-color: inherit; 46 | right: -10px; 47 | } 48 | 49 | .leaflet-label-right:before, 50 | .leaflet-label-left:after { 51 | content: ""; 52 | } -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/vendor/leaflet.label.js: -------------------------------------------------------------------------------- 1 | /* 2 | Leaflet.label, a plugin that adds labels to markers and vectors for Leaflet powered maps. 3 | (c) 2012-2013, Jacob Toye, Smartrak 4 | 5 | https://github.com/Leaflet/Leaflet.label 6 | http://leafletjs.com 7 | https://github.com/jacobtoye 8 | */ 9 | (function (window, document, undefined) { 10 | /* 11 | * Leaflet.label assumes that you have already included the Leaflet library. 12 | */ 13 | 14 | L.labelVersion = '0.2.1-dev'; 15 | 16 | L.Label = L.Class.extend({ 17 | 18 | includes: L.Mixin.Events, 19 | 20 | options: { 21 | className: '', 22 | clickable: false, 23 | direction: 'right', 24 | noHide: false, 25 | offset: [12, -15], // 6 (width of the label triangle) + 6 (padding) 26 | opacity: 1, 27 | zoomAnimation: true 28 | }, 29 | 30 | initialize: function (options, source) { 31 | L.setOptions(this, options); 32 | 33 | this._source = source; 34 | this._animated = L.Browser.any3d && this.options.zoomAnimation; 35 | this._isOpen = false; 36 | }, 37 | 38 | onAdd: function (map) { 39 | this._map = map; 40 | 41 | this._pane = this._source instanceof L.Marker ? map._panes.markerPane : map._panes.popupPane; 42 | 43 | if (!this._container) { 44 | this._initLayout(); 45 | } 46 | 47 | this._pane.appendChild(this._container); 48 | 49 | this._initInteraction(); 50 | 51 | this._update(); 52 | 53 | this.setOpacity(this.options.opacity); 54 | 55 | map 56 | .on('moveend', this._onMoveEnd, this) 57 | .on('viewreset', this._onViewReset, this); 58 | 59 | if (this._animated) { 60 | map.on('zoomanim', this._zoomAnimation, this); 61 | } 62 | 63 | if (L.Browser.touch && !this.options.noHide) { 64 | L.DomEvent.on(this._container, 'click', this.close, this); 65 | } 66 | }, 67 | 68 | onRemove: function (map) { 69 | this._pane.removeChild(this._container); 70 | 71 | map.off({ 72 | zoomanim: this._zoomAnimation, 73 | moveend: this._onMoveEnd, 74 | viewreset: this._onViewReset 75 | }, this); 76 | 77 | this._removeInteraction(); 78 | 79 | this._map = null; 80 | }, 81 | 82 | setLatLng: function (latlng) { 83 | this._latlng = L.latLng(latlng); 84 | if (this._map) { 85 | this._updatePosition(); 86 | } 87 | return this; 88 | }, 89 | 90 | setContent: function (content) { 91 | // Backup previous content and store new content 92 | this._previousContent = this._content; 93 | this._content = content; 94 | 95 | this._updateContent(); 96 | 97 | return this; 98 | }, 99 | 100 | close: function () { 101 | var map = this._map; 102 | 103 | if (map) { 104 | if (L.Browser.touch && !this.options.noHide) { 105 | L.DomEvent.off(this._container, 'click', this.close); 106 | } 107 | 108 | map.removeLayer(this); 109 | } 110 | }, 111 | 112 | updateZIndex: function (zIndex) { 113 | this._zIndex = zIndex; 114 | 115 | if (this._container && this._zIndex) { 116 | this._container.style.zIndex = zIndex; 117 | } 118 | }, 119 | 120 | setOpacity: function (opacity) { 121 | this.options.opacity = opacity; 122 | 123 | if (this._container) { 124 | L.DomUtil.setOpacity(this._container, opacity); 125 | } 126 | }, 127 | 128 | _initLayout: function () { 129 | this._container = L.DomUtil.create('div', 'leaflet-label ' + this.options.className + ' leaflet-zoom-animated'); 130 | this.updateZIndex(this._zIndex); 131 | }, 132 | 133 | _update: function () { 134 | if (!this._map) { return; } 135 | 136 | this._container.style.visibility = 'hidden'; 137 | 138 | this._updateContent(); 139 | this._updatePosition(); 140 | 141 | this._container.style.visibility = ''; 142 | }, 143 | 144 | _updateContent: function () { 145 | if (!this._content || !this._map || this._prevContent === this._content) { 146 | return; 147 | } 148 | 149 | if (typeof this._content === 'string') { 150 | this._container.innerHTML = this._content; 151 | 152 | this._prevContent = this._content; 153 | 154 | this._labelWidth = this._container.offsetWidth; 155 | } 156 | }, 157 | 158 | _updatePosition: function () { 159 | var pos = this._map.latLngToLayerPoint(this._latlng); 160 | 161 | this._setPosition(pos); 162 | }, 163 | 164 | _setPosition: function (pos) { 165 | var map = this._map, 166 | container = this._container, 167 | centerPoint = map.latLngToContainerPoint(map.getCenter()), 168 | labelPoint = map.layerPointToContainerPoint(pos), 169 | direction = this.options.direction, 170 | labelWidth = this._labelWidth, 171 | offset = L.point(this.options.offset); 172 | 173 | // position to the right (right or auto & needs to) 174 | if (direction === 'right' || direction === 'auto' && labelPoint.x < centerPoint.x) { 175 | L.DomUtil.addClass(container, 'leaflet-label-right'); 176 | L.DomUtil.removeClass(container, 'leaflet-label-left'); 177 | 178 | pos = pos.add(offset); 179 | } else { // position to the left 180 | L.DomUtil.addClass(container, 'leaflet-label-left'); 181 | L.DomUtil.removeClass(container, 'leaflet-label-right'); 182 | 183 | pos = pos.add(L.point(-offset.x - labelWidth, offset.y)); 184 | } 185 | 186 | L.DomUtil.setPosition(container, pos); 187 | }, 188 | 189 | _zoomAnimation: function (opt) { 190 | var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); 191 | 192 | this._setPosition(pos); 193 | }, 194 | 195 | _onMoveEnd: function () { 196 | if (!this._animated || this.options.direction === 'auto') { 197 | this._updatePosition(); 198 | } 199 | }, 200 | 201 | _onViewReset: function (e) { 202 | /* if map resets hard, we must update the label */ 203 | if (e && e.hard) { 204 | this._update(); 205 | } 206 | }, 207 | 208 | _initInteraction: function () { 209 | if (!this.options.clickable) { return; } 210 | 211 | var container = this._container, 212 | events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu']; 213 | 214 | L.DomUtil.addClass(container, 'leaflet-clickable'); 215 | L.DomEvent.on(container, 'click', this._onMouseClick, this); 216 | 217 | for (var i = 0; i < events.length; i++) { 218 | L.DomEvent.on(container, events[i], this._fireMouseEvent, this); 219 | } 220 | }, 221 | 222 | _removeInteraction: function () { 223 | if (!this.options.clickable) { return; } 224 | 225 | var container = this._container, 226 | events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu']; 227 | 228 | L.DomUtil.removeClass(container, 'leaflet-clickable'); 229 | L.DomEvent.off(container, 'click', this._onMouseClick, this); 230 | 231 | for (var i = 0; i < events.length; i++) { 232 | L.DomEvent.off(container, events[i], this._fireMouseEvent, this); 233 | } 234 | }, 235 | 236 | _onMouseClick: function (e) { 237 | if (this.hasEventListeners(e.type)) { 238 | L.DomEvent.stopPropagation(e); 239 | } 240 | 241 | this.fire(e.type, { 242 | originalEvent: e 243 | }); 244 | }, 245 | 246 | _fireMouseEvent: function (e) { 247 | this.fire(e.type, { 248 | originalEvent: e 249 | }); 250 | 251 | // TODO proper custom event propagation 252 | // this line will always be called if marker is in a FeatureGroup 253 | if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) { 254 | L.DomEvent.preventDefault(e); 255 | } 256 | if (e.type !== 'mousedown') { 257 | L.DomEvent.stopPropagation(e); 258 | } else { 259 | L.DomEvent.preventDefault(e); 260 | } 261 | } 262 | }); 263 | 264 | 265 | // This object is a mixin for L.Marker and L.CircleMarker. We declare it here as both need to include the contents. 266 | L.BaseMarkerMethods = { 267 | showLabel: function () { 268 | if (this.label && this._map) { 269 | this.label.setLatLng(this._latlng); 270 | this._map.showLabel(this.label); 271 | } 272 | 273 | return this; 274 | }, 275 | 276 | hideLabel: function () { 277 | if (this.label) { 278 | this.label.close(); 279 | } 280 | return this; 281 | }, 282 | 283 | setLabelNoHide: function (noHide) { 284 | if (this._labelNoHide === noHide) { 285 | return; 286 | } 287 | 288 | this._labelNoHide = noHide; 289 | 290 | if (noHide) { 291 | this._removeLabelRevealHandlers(); 292 | this.showLabel(); 293 | } else { 294 | this._addLabelRevealHandlers(); 295 | this.hideLabel(); 296 | } 297 | }, 298 | 299 | bindLabel: function (content, options) { 300 | var labelAnchor = this.options.icon ? this.options.icon.options.labelAnchor : this.options.labelAnchor, 301 | anchor = L.point(labelAnchor) || L.point(0, 0); 302 | 303 | anchor = anchor.add(L.Label.prototype.options.offset); 304 | 305 | if (options && options.offset) { 306 | anchor = anchor.add(options.offset); 307 | } 308 | 309 | options = L.Util.extend({offset: anchor}, options); 310 | 311 | this._labelNoHide = options.noHide; 312 | 313 | if (!this.label) { 314 | if (!this._labelNoHide) { 315 | this._addLabelRevealHandlers(); 316 | } 317 | 318 | this 319 | .on('remove', this.hideLabel, this) 320 | .on('move', this._moveLabel, this) 321 | .on('add', this._onMarkerAdd, this); 322 | 323 | this._hasLabelHandlers = true; 324 | } 325 | 326 | this.label = new L.Label(options, this) 327 | .setContent(content); 328 | 329 | return this; 330 | }, 331 | 332 | unbindLabel: function () { 333 | if (this.label) { 334 | this.hideLabel(); 335 | 336 | this.label = null; 337 | 338 | if (this._hasLabelHandlers) { 339 | if (!this._labelNoHide) { 340 | this._removeLabelRevealHandlers(); 341 | } 342 | 343 | this 344 | .off('remove', this.hideLabel, this) 345 | .off('move', this._moveLabel, this) 346 | .off('add', this._onMarkerAdd, this); 347 | } 348 | 349 | this._hasLabelHandlers = false; 350 | } 351 | return this; 352 | }, 353 | 354 | updateLabelContent: function (content) { 355 | if (this.label) { 356 | this.label.setContent(content); 357 | } 358 | }, 359 | 360 | getLabel: function () { 361 | return this.label; 362 | }, 363 | 364 | _onMarkerAdd: function () { 365 | if (this._labelNoHide) { 366 | this.showLabel(); 367 | } 368 | }, 369 | 370 | _addLabelRevealHandlers: function () { 371 | this 372 | .on('mouseover', this.showLabel, this) 373 | .on('mouseout', this.hideLabel, this); 374 | 375 | if (L.Browser.touch) { 376 | this.on('click', this.showLabel, this); 377 | } 378 | }, 379 | 380 | _removeLabelRevealHandlers: function () { 381 | this 382 | .off('mouseover', this.showLabel, this) 383 | .off('mouseout', this.hideLabel, this); 384 | 385 | if (L.Browser.touch) { 386 | this.off('click', this.showLabel, this); 387 | } 388 | }, 389 | 390 | _moveLabel: function (e) { 391 | this.label.setLatLng(e.latlng); 392 | } 393 | }; 394 | 395 | // Add in an option to icon that is used to set where the label anchor is 396 | L.Icon.Default.mergeOptions({ 397 | labelAnchor: new L.Point(9, -20) 398 | }); 399 | 400 | // Have to do this since Leaflet is loaded before this plugin and initializes 401 | // L.Marker.options.icon therefore missing our mixin above. 402 | L.Marker.mergeOptions({ 403 | icon: new L.Icon.Default() 404 | }); 405 | 406 | L.Marker.include(L.BaseMarkerMethods); 407 | L.Marker.include({ 408 | _originalUpdateZIndex: L.Marker.prototype._updateZIndex, 409 | 410 | _updateZIndex: function (offset) { 411 | var zIndex = this._zIndex + offset; 412 | 413 | this._originalUpdateZIndex(offset); 414 | 415 | if (this.label) { 416 | this.label.updateZIndex(zIndex); 417 | } 418 | }, 419 | 420 | _originalSetOpacity: L.Marker.prototype.setOpacity, 421 | 422 | setOpacity: function (opacity, labelHasSemiTransparency) { 423 | this.options.labelHasSemiTransparency = labelHasSemiTransparency; 424 | 425 | this._originalSetOpacity(opacity); 426 | }, 427 | 428 | _originalUpdateOpacity: L.Marker.prototype._updateOpacity, 429 | 430 | _updateOpacity: function () { 431 | var absoluteOpacity = this.options.opacity === 0 ? 0 : 1; 432 | 433 | this._originalUpdateOpacity(); 434 | 435 | if (this.label) { 436 | this.label.setOpacity(this.options.labelHasSemiTransparency ? this.options.opacity : absoluteOpacity); 437 | } 438 | }, 439 | 440 | _originalSetLatLng: L.Marker.prototype.setLatLng, 441 | 442 | setLatLng: function (latlng) { 443 | if (this.label && !this._labelNoHide) { 444 | this.hideLabel(); 445 | } 446 | 447 | return this._originalSetLatLng(latlng); 448 | } 449 | }); 450 | 451 | // Add in an option to icon that is used to set where the label anchor is 452 | L.CircleMarker.mergeOptions({ 453 | labelAnchor: new L.Point(0, 0) 454 | }); 455 | 456 | 457 | L.CircleMarker.include(L.BaseMarkerMethods); 458 | 459 | L.Path.include({ 460 | bindLabel: function (content, options) { 461 | if (!this.label || this.label.options !== options) { 462 | this.label = new L.Label(options, this); 463 | } 464 | 465 | this.label.setContent(content); 466 | 467 | if (!this._showLabelAdded) { 468 | this 469 | .on('mouseover', this._showLabel, this) 470 | .on('mousemove', this._moveLabel, this) 471 | .on('mouseout remove', this._hideLabel, this); 472 | 473 | if (L.Browser.touch) { 474 | this.on('click', this._showLabel, this); 475 | } 476 | this._showLabelAdded = true; 477 | } 478 | 479 | return this; 480 | }, 481 | 482 | unbindLabel: function () { 483 | if (this.label) { 484 | this._hideLabel(); 485 | this.label = null; 486 | this._showLabelAdded = false; 487 | this 488 | .off('mouseover', this._showLabel, this) 489 | .off('mousemove', this._moveLabel, this) 490 | .off('mouseout remove', this._hideLabel, this); 491 | } 492 | return this; 493 | }, 494 | 495 | updateLabelContent: function (content) { 496 | if (this.label) { 497 | this.label.setContent(content); 498 | } 499 | }, 500 | 501 | _showLabel: function (e) { 502 | this.label.setLatLng(e.latlng); 503 | this._map.showLabel(this.label); 504 | }, 505 | 506 | _moveLabel: function (e) { 507 | this.label.setLatLng(e.latlng); 508 | }, 509 | 510 | _hideLabel: function () { 511 | this.label.close(); 512 | } 513 | }); 514 | 515 | L.Map.include({ 516 | showLabel: function (label) { 517 | return this.addLayer(label); 518 | } 519 | }); 520 | 521 | L.FeatureGroup.include({ 522 | // TODO: remove this when AOP is supported in Leaflet, need this as we cannot put code in removeLayer() 523 | clearLayers: function () { 524 | this.unbindLabel(); 525 | this.eachLayer(this.removeLayer, this); 526 | return this; 527 | }, 528 | 529 | bindLabel: function (content, options) { 530 | return this.invoke('bindLabel', content, options); 531 | }, 532 | 533 | unbindLabel: function () { 534 | return this.invoke('unbindLabel'); 535 | }, 536 | 537 | updateLabelContent: function (content) { 538 | this.invoke('updateLabelContent', content); 539 | } 540 | }); 541 | 542 | }(this, document)); -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/vendor/queryStringToJSON.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Return a new JSON object of the old string. 3 | * Turns: 4 | * file.js?a=1&b.c=3.0&b.d=four&a_false_value=false&a_null_value=null 5 | * Into: 6 | * {"a":1,"b":{"c":3,"d":"four"},"a_false_value":false,"a_null_value":null} 7 | * @version 1.1.0 8 | * @date July 16, 2010 9 | * @since 1.0.0, June 30, 2010 10 | * @package jquery-sparkle {@link http://balupton.com/projects/jquery-sparkle} 11 | * @author Benjamin "balupton" Lupton {@link http://balupton.com} 12 | * @copyright (c) 2009-2010 Benjamin Arthur Lupton {@link http://balupton.com} 13 | * @license MIT License {@link http://creativecommons.org/licenses/MIT/} 14 | */ 15 | String.prototype.queryStringToJSON = String.prototype.queryStringToJSON || function ( ) 16 | { // Turns a params string or url into an array of params 17 | // Prepare 18 | var params = String(this); 19 | // Remove url if need be 20 | params = params.substring(params.indexOf('?')+1); 21 | // params = params.substring(params.indexOf('#')+1); 22 | // Change + to %20, the %20 is fixed up later with the decode 23 | params = params.replace(/\+/g, '%20'); 24 | // Do we have JSON string 25 | if ( params.substring(0,1) === '{' && params.substring(params.length-1) === '}' ) 26 | { // We have a JSON string 27 | return eval(decodeURIComponent(params)); 28 | } 29 | // We have a params string 30 | params = params.split(/\&(amp\;)?/); 31 | var json = {}; 32 | // We have params 33 | for ( var i = 0, n = params.length; i < n; ++i ) 34 | { 35 | // Adjust 36 | var param = params[i] || null; 37 | if ( param === null ) { continue; } 38 | param = param.split('='); 39 | if ( param === null ) { continue; } 40 | // ^ We now have "var=blah" into ["var","blah"] 41 | 42 | // Get 43 | var key = param[0] || null; 44 | if ( key === null ) { continue; } 45 | if ( typeof param[1] === 'undefined' ) { continue; } 46 | var value = param[1]; 47 | // ^ We now have the parts 48 | 49 | // Fix 50 | key = decodeURIComponent(key); 51 | value = decodeURIComponent(value); 52 | try { 53 | // value can be converted 54 | value = eval(value); 55 | } catch ( e ) { 56 | // value is a normal string 57 | } 58 | 59 | // Set 60 | // window.console.log({'key':key,'value':value}, split); 61 | var keys = key.split('.'); 62 | if ( keys.length === 1 ) 63 | { // Simple 64 | json[key] = value; 65 | } 66 | else 67 | { // Advanced (Recreating an object) 68 | var path = '', 69 | cmd = ''; 70 | // Ensure Path Exists 71 | $.each(keys,function(ii,key){ 72 | path += '["'+key.replace(/"/g,'\\"')+'"]'; 73 | jsonCLOSUREGLOBAL = json; // we have made this a global as closure compiler struggles with evals 74 | cmd = 'if ( typeof jsonCLOSUREGLOBAL'+path+' === "undefined" ) jsonCLOSUREGLOBAL'+path+' = {}'; 75 | eval(cmd); 76 | json = jsonCLOSUREGLOBAL; 77 | delete jsonCLOSUREGLOBAL; 78 | }); 79 | // Apply Value 80 | jsonCLOSUREGLOBAL = json; // we have made this a global as closure compiler struggles with evals 81 | valueCLOSUREGLOBAL = value; // we have made this a global as closure compiler struggles with evals 82 | cmd = 'jsonCLOSUREGLOBAL'+path+' = valueCLOSUREGLOBAL'; 83 | eval(cmd); 84 | json = jsonCLOSUREGLOBAL; 85 | delete jsonCLOSUREGLOBAL; 86 | delete valueCLOSUREGLOBAL; 87 | } 88 | // ^ We now have the parts added to your JSON object 89 | } 90 | return json; 91 | }; 92 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/public/webassets.yml: -------------------------------------------------------------------------------- 1 | main-css: 2 | output: ckanext-mapviews/main.css 3 | extra: 4 | preload: 5 | - base/main 6 | contents: 7 | - vendor/leaflet.css 8 | - vendor/leaflet.label.css 9 | - navigablemap.css 10 | - choroplethmap.css 11 | 12 | main: 13 | output: ckanext-mapviews/main.js 14 | extra: 15 | preload: 16 | - base/main 17 | contents: 18 | - vendor/leaflet.js 19 | - vendor/leaflet.label.js 20 | - vendor/d3.scale.quantize.js 21 | - vendor/backend.ckan.js 22 | - vendor/queryStringToJSON.js 23 | - ckan_map_modules.js 24 | - navigablemap.js 25 | - choroplethmap.js 26 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/templates/base.html: -------------------------------------------------------------------------------- 1 | {% ckan_extends %} 2 | 3 | {% set type = 'asset' if h.ckan_version().split('.')|map('int')|list >= [2, 9, 0] else 'resource' %} 4 | 5 | {% block styles %} 6 | {{ super() }} 7 | {% include 'snippets/mapviews_' ~ type ~ '.html' %} 8 | {% endblock %} -------------------------------------------------------------------------------- /ckanext/mapviews/theme/templates/choroplethmap_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'navigablemap_form.html' %} 2 | {% import 'macros/form.html' as form %} 3 | 4 | {% block form_required_fields %} 5 | 6 | {{ super() }} 7 | 8 | {{ 9 | form.select('resource_value_field', label=_('Value'), options=numeric_fields, 10 | selected=resource_view['resource_value_field'], is_required=true) 11 | }} 12 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/templates/choroplethmap_view.html: -------------------------------------------------------------------------------- 1 | {% set type = 'asset' if h.ckan_version().split('.')|map('int')|list >= [2, 9, 0] else 'resource' %} 2 | {% include 'snippets/mapviews_' ~ type ~ '.html' %} 3 | 4 | 18 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/templates/navigablemap_form.html: -------------------------------------------------------------------------------- 1 | {% set type = 'asset' if h.ckan_version().split('.')|map('int')|list >= [2, 9, 0] else 'resource' %} 2 | {% include 'snippets/mapviews_' ~ type ~ '.html' %} 3 | {% import 'macros/form.html' as form %} 4 | 5 | {% macro filterFields(name, label='', options='', error='', fields={}, classes=[], attrs={}, is_required=false) %} 6 | {% set classes = (classes|list) %} 7 | {% do classes.append('control-select') %} 8 | 9 | {%- set extra_html = caller() if caller -%} 10 | {% call form.input_block(id or name, label or name, error, classes, extra_html=extra_html, is_required=is_required) %} 11 |
    12 | {% for field in fields %} {{ removableSelect(name, options=options, selected=field, attrs=attrs) }} 13 | {% endfor %} 14 |
    15 | {{ _('Add Field') }} 16 | {% endcall %} 17 | {% endmacro %} 18 | 19 | {% macro removableSelect(name, options='', selected='', attrs={}) %} 20 |
    21 | 26 | 27 |
    28 | {% endmacro %} 29 | 30 | {% block form %} 31 | 32 | {% block form_required_fields %} 33 | {{ 34 | form.select('geojson_url', label=_('GeoJSON Resource'), options=geojson_resources, 35 | error=errors.geojson_url, 36 | selected=resource_view['geojson_url'], is_required=true) 37 | }} 38 | 39 | {{ 40 | form.input('geojson_key_field', label=_('GeoJSON Key Field'), 41 | error=errors.geojson_key_field, 42 | value=resource_view['geojson_key_field'], is_required=true) 43 | }} 44 | 45 | {{ 46 | form.select('resource_key_field', label=_('Key'), options=fields, 47 | selected=resource_view['resource_key_field'], is_required=true) 48 | }} 49 | 50 | {{ 51 | form.select('resource_label_field', label=_('Label'), options=textual_fields, 52 | selected=resource_view['resource_label_field'], is_required=true) 53 | }} 54 | 55 | {% endblock %} 56 | 57 | {{ 58 | form.input('redirect_to_url', label=_('Redirect to URL'), 59 | classes=['long'], 60 | value=resource_view['redirect_to_url'], 61 | placeholder=_('If left blank, clicking on a region will update the filters on the same page')) 62 | }} 63 | 64 | {% endblock %} 65 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/templates/navigablemap_view.html: -------------------------------------------------------------------------------- 1 | {% set type = 'asset' if h.ckan_version().split('.')|map('int')|list >= [2, 9, 0] else 'resource' %} 2 | {% include 'snippets/mapviews_' ~ type ~ '.html' %} 3 | 4 | 17 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/templates/page.html: -------------------------------------------------------------------------------- 1 | {% ckan_extends %} 2 | 3 | {%- block scripts %} 4 | 5 | {{ super() }} 6 | {% asset 'mapviews/main' %} 7 | 8 | {% endblock -%} 9 | -------------------------------------------------------------------------------- /ckanext/mapviews/theme/templates/snippets/mapviews_asset.html: -------------------------------------------------------------------------------- 1 | {% asset 'mapviews/main-css' %} 2 | {% asset 'mapviews/main' %} -------------------------------------------------------------------------------- /ckanext/mapviews/theme/templates/snippets/mapviews_resource.html: -------------------------------------------------------------------------------- 1 | {% resource 'mapviews/main' %} -------------------------------------------------------------------------------- /doc/img/pakistan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-mapviews/caf1d0f6ab4168f3ac10a75881fa537144b01d34/doc/img/pakistan.png -------------------------------------------------------------------------------- /doc/img/worldwide-internet-usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckan/ckanext-mapviews/caf1d0f6ab4168f3ac10a75881fa537144b01d34/doc/img/worldwide-internet-usage.png -------------------------------------------------------------------------------- /doc/internet-users-per-100-people.csv: -------------------------------------------------------------------------------- 1 | Country Name,Country Code,Indicator Name,Indicator Code,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012 2 | Afghanistan,AFG,Internet users (per 100 people),IT.NET.USER.P2,,0.00472256824217368,0.00456139517022146,0.0878912528559713,0.105809030021958,1.22414808372471,2.10712364546412,1.9,1.84,3.55,4,5,5.45454545454545 3 | Albania,ALB,Internet users (per 100 people),IT.NET.USER.P2,0.114097346552109,0.325798377067964,0.39008127343332,0.971900415195857,2.42038779776014,6.04389086404814,9.60999131577118,15.0361154084109,23.86,41.2,45,49,54.6559590399494 4 | Algeria,DZA,Internet users (per 100 people),IT.NET.USER.P2,0.491705679141589,0.646114016703792,1.59164126035832,2.19535973086144,4.63447508776537,5.84394209201256,7.37598495634886,9.45119062555304,10.18,11.23,12.5,14,15.2280267564417 5 | American Samoa,ASM,Internet users (per 100 people),IT.NET.USER.P2,,,,,,,,,,,,, 6 | Andorra,ADO,Internet users (per 100 people),IT.NET.USER.P2,10.53883560922,,11.2604687170103,13.546412876439,26.8379543894637,37.6057662174867,48.9368469989479,70.87,70.04,78.53,81,81,86.4344246167258 7 | Angola,AGO,Internet users (per 100 people),IT.NET.USER.P2,0.105045562462262,0.136013867429868,0.270376745595134,0.370682065225727,0.464814617985909,1.1433668265595,1.90764750723428,3.2,4.6,6,10,14.776,16.9372101137398 8 | Antigua and Barbuda,ATG,Internet users (per 100 people),IT.NET.USER.P2,6.48222573702907,8.89928551450583,12.5,17.2286487816884,24.2665437161785,34.7164029018124,62.6388691911313,70.06,75.03,74.2,80,82,83.7871674459974 9 | Argentina,ARG,Internet users (per 100 people),IT.NET.USER.P2,7.0386830862178,9.78080728532407,10.8821243775325,11.9136965509954,16.0366841054923,17.7205833696065,20.9272021035896,25.9466329403819,28.1126234799907,34,45,51,55.8 10 | Armenia,ARM,Internet users (per 100 people),IT.NET.USER.P2,1.30047002237784,1.63109466677715,1.9604050458212,4.57521722477781,4.8990085713054,5.25298335195804,5.63178777731609,6.02125339712607,6.21,15.3,25,32,39.1607915666556 11 | Aruba,ABW,Internet users (per 100 people),IT.NET.USER.P2,15.4428229480349,17.1,18.8,20.8,23,25.4,28,30.9,52,58,62,69,74 12 | Australia,AUS,Internet users (per 100 people),IT.NET.USER.P2,46.7561156116738,52.6892664310395,,,,63,66,69.45,71.67,74.25,76,79.5,82.3495485010082 13 | Austria,AUT,Internet users (per 100 people),IT.NET.USER.P2,33.7301329516915,39.1854501811114,36.56,42.7,54.28,58,63.6,69.37,72.87,73.45,75.17,79.8,81 14 | Azerbaijan,AZE,Internet users (per 100 people),IT.NET.USER.P2,0.147757575623251,0.305564637615618,4.99971367812957,,,8.03037535616454,11.9921773343674,14.54,17.08,27.4,46,50,54.2 15 | "Bahamas, The",BHS,Internet users (per 100 people),IT.NET.USER.P2,8,11.8,18,20,22,25,26,27,31.54,33.88,43,65,71.7482028146963 16 | Bahrain,BHR,Internet users (per 100 people),IT.NET.USER.P2,6.15373254647607,15.0386342513918,18.0507208881153,21.5549449917804,21.4586805077435,21.3037335136591,28.2439524318043,32.91,51.95,53,55,77,88 17 | Bangladesh,BGD,Internet users (per 100 people),IT.NET.USER.P2,0.0710394230507683,0.129807973947512,0.13992028850905,0.163877665498048,0.199036333744363,0.24163732563671,1,1.8,2.5,3.1,3.7,5,6.3 18 | Barbados,BRB,Internet users (per 100 people),IT.NET.USER.P2,3.97367835457927,11.9364503383984,27.836322424146,39.6896271159532,49.8,56.0708546066945,63,64.7,66.5,68.7,70.2,71.7657,73.3298136924294 19 | Belarus,BLR,Internet users (per 100 people),IT.NET.USER.P2,1.86039812615388,4.30061602151712,8.95097131467325,,,,16.2,19.7,23,27.43,31.8,39.6,46.9060058438776 20 | Belgium,BEL,Internet users (per 100 people),IT.NET.USER.P2,29.4316916924341,31.2883955056566,46.33,49.97,53.86,55.82,59.72,64.44,66,70,75,78,82 21 | Belize,BLZ,Internet users (per 100 people),IT.NET.USER.P2,5.96383530272428,,5.68425121358763,,5.7936168825996,9.21028860085089,10.4016781374062,10.86,11.31,11.73,14,18.7,25 22 | Benin,BEN,Internet users (per 100 people),IT.NET.USER.P2,0.225247851473369,0.363417890016082,0.7029455951039,0.951327114915695,1.18254096518994,1.27103143947107,1.53785434624704,1.79,1.85,2.24,3.13,3.5,3.79770474077515 23 | Bermuda,BMU,Internet users (per 100 people),IT.NET.USER.P2,42.9498600152711,47.5096998970623,52.0315973700393,56.5220121836337,60.9908670086326,65.4470657898838,69.8996551617012,74.3505940302669,82.3,83.25,84.21,88.336,91.2993045243401 24 | Bhutan,BTN,Internet users (per 100 people),IT.NET.USER.P2,0.40094444691941,0.864628564215099,1.67580258375242,2.43691239949767,3.15698419613711,3.84710674490143,4.5183172581646,5.92,6.55,7.17,13.6,21,25.434348914861 25 | Bolivia,BOL,Internet users (per 100 people),IT.NET.USER.P2,1.4427635847914,2.1204625341365,3.11719294696997,3.50859640072336,4.43992460120042,5.22758395663196,6.20067125473576,10.4992443168903,12.5,16.8,22.4,30,34.1884342305183 26 | Bosnia and Herzegovina,BIH,Internet users (per 100 people),IT.NET.USER.P2,1.08296074971207,1.20052695129649,2.64826791361244,3.96503683387051,15.4689716227665,21.3267010007738,25.1223856852117,27.92,34.66,37.74,52,60,65.3560944772989 27 | Botswana,BWA,Internet users (per 100 people),IT.NET.USER.P2,2.90266662177209,3.43088678702599,3.38592235064273,3.3451901740614,3.30488925316113,3.26255403605122,4.28993297515968,5.28,6.25,6.15,6,8,11.5 28 | Brazil,BRA,Internet users (per 100 people),IT.NET.USER.P2,2.87068515854108,4.52849486902851,9.14942508560748,13.2075861035303,19.073672274442,21.022747248827,28.1783801797929,30.88,33.83,39.22,40.65,45,49.847999364427 29 | Brunei Darussalam,BRN,Internet users (per 100 people),IT.NET.USER.P2,8.99628453448726,12.9177690823013,15.329879855735,19.5950032207342,29.7156041447864,36.4663919475782,42.1863491609307,44.68,46,49,53,56,60.2730650423193 30 | Bulgaria,BGR,Internet users (per 100 people),IT.NET.USER.P2,5.37092346908696,7.6122977487982,9.08,12.04,18.13,19.97,27.09,33.64,39.67,45,46.23,51,55.1480978664466 31 | Burkina Faso,BFA,Internet users (per 100 people),IT.NET.USER.P2,0.0770801689426014,0.157732464653607,0.200992597925001,0.373440303233526,0.400295285492178,0.469914488656657,0.632707564461829,0.75,0.92,1.13,2.4,3,3.72503491597675 32 | Burundi,BDI,Internet users (per 100 people),IT.NET.USER.P2,0.077248447383456,0.106001245363203,0.118228412267794,0.201273196735924,0.349060461879596,0.542142865759056,0.657592590351907,0.7,0.81,0.9,1,1.11,1.21999994477793 33 | Cabo Verde,CPV,Internet users (per 100 people),IT.NET.USER.P2,1.82244385164396,2.68533271272311,3.5187571749658,4.32488539053715,5.31883206957883,6.07408710659813,6.80891100753313,8.28321981953034,14,21,30,32,34.7434141953015 34 | Cambodia,KHM,Internet users (per 100 people),IT.NET.USER.P2,0.047022640068885,0.0769560555527296,0.226983237666204,0.260570197574002,0.300436644360101,0.317321781089656,0.468356720063827,0.49,0.51,0.53,1.26,3.1,4.93986176461129 35 | Cameroon,CMR,Internet users (per 100 people),IT.NET.USER.P2,0.252120077733662,0.277051321848797,0.360871584267755,0.587622611130454,0.976075415490902,1.40265422575955,2.0287447914719,2.93,3.4,3.84,4.3,5,5.69898724036494 36 | Canada,CAN,Internet users (per 100 people),IT.NET.USER.P2,51.3,60.2,61.5932992681952,64.2,65.9559634648989,71.66,72.4,73.2,76.7,80.3,80.3,83,86.7658644038714 37 | Cayman Islands,CYM,Internet users (per 100 people),IT.NET.USER.P2,,,,,,38.0343830823064,44.5,52,61,64.5,66,69.46594499,74.1265306872565 38 | Central African Republic,CAF,Internet users (per 100 people),IT.NET.USER.P2,0.0533941742150456,0.0785434278394038,0.128537116506299,0.151563337940017,0.223401395464406,0.268195609199012,0.311159173235716,0.375815961044797,1,1.8,2,2.2,3 39 | Chad,TCD,Internet users (per 100 people),IT.NET.USER.P2,0.03570707628555,0.0459341343557353,0.166069664674347,0.320308555366775,0.360920107034467,0.399257700084004,0.581045764229641,0.847224520710968,1.19,1.5,1.7,1.9,2.10000000005453 40 | Channel Islands,CHI,Internet users (per 100 people),IT.NET.USER.P2,,,,,,,,,,,,, 41 | Chile,CHL,Internet users (per 100 people),IT.NET.USER.P2,16.6,19.1,22.1,25.4737788864723,28.1779101232438,31.1753470303684,34.4977511736017,35.9,37.3,41.56,45,52.25,61.4181545577569 42 | China,CHN,Internet users (per 100 people),IT.NET.USER.P2,1.77591320677415,2.63965021485872,4.59570433077551,6.2,7.3,8.52325700269541,10.5231526193851,16,22.6,28.9,34.3,38.3,42.3001174855995 43 | Colombia,COL,Internet users (per 100 people),IT.NET.USER.P2,2.207532992624,2.85419997133433,4.6,7.38892371052557,9.1186903029666,11.0072638904585,15.3416745365682,21.8,25.6,30,36.5,40.4,48.9843188 44 | Comoros,COM,Internet users (per 100 people),IT.NET.USER.P2,0.271740607285909,0.443068372538977,0.554869674985088,0.848189370151537,1.32732717785355,2,2.2,2.5,3,3.5,5.1,5.5,5.97529630842855 45 | "Congo, Dem. Rep.",ZAR,Internet users (per 100 people),IT.NET.USER.P2,0.0059021136806903,0.0114757819210493,0.0927907024606925,0.134914837437335,0.196208375178039,0.238037798692792,0.29605361030881,0.37,0.44,0.56,0.72,1.2,1.679961014645 46 | "Congo, Rep.",COG,Internet users (per 100 people),IT.NET.USER.P2,0.0263547320580279,0.0322236164306931,0.157249517243982,0.460014186837522,1.07750492958505,1.46342005950851,2.00799007938159,2.75970437370903,4.28750990276481,4.5,5,5.6,6.10669502435836 47 | Costa Rica,CRI,Internet users (per 100 people),IT.NET.USER.P2,5.80025302331829,9.55948213497093,19.8948511346293,20.3336148143541,20.7923067055542,22.07,25.1,28.4,32.29,34.33,36.5,42.12,47.5009145244347 48 | Cote d'Ivoire,CIV,Internet users (per 100 people),IT.NET.USER.P2,0.231461670612799,0.395747702246439,0.497929360753307,0.758669629452205,0.849282414747704,1.03923820514001,1.52490079122527,1.8,1.9,2,2.1,2.2,2.37895802994429 49 | Croatia,HRV,Internet users (per 100 people),IT.NET.USER.P2,6.64488254374396,11.5585731811648,17.76,22.75,30.91,33.14,37.98,41.44,44.24,50.58,56.55,59.64,63 50 | Cuba,CUB,Internet users (per 100 people),IT.NET.USER.P2,0.541182548811058,1.07975172188907,3.77058503769014,5.2412690536929,8.40798475900703,9.73806220781346,11.1596013147617,11.69,12.94,14.33,15.9,23.23,25.6417038674676 51 | Curacao,CUW,Internet users (per 100 people),IT.NET.USER.P2,,,,,,,,,,,,, 52 | Cyprus,CYP,Internet users (per 100 people),IT.NET.USER.P2,15.2553943710137,18.8187590408455,28.32,30.09,33.83,32.81,35.83,40.77,42.31,49.81,52.99,57.68,61 53 | Czech Republic,CZE,Internet users (per 100 people),IT.NET.USER.P2,9.78052788834393,14.6971721171129,23.93,34.3,35.5,35.27,47.93,51.93,62.97,64.43,68.82,72.97,75 54 | Denmark,DNK,Internet users (per 100 people),IT.NET.USER.P2,39.1724308555053,42.9575247201878,64.25,76.26,80.93,82.74,86.65,85.03,85.02,86.84,88.72,90,93 55 | Djibouti,DJI,Internet users (per 100 people),IT.NET.USER.P2,0.194500528399956,0.343526262964202,0.48697170451894,0.625940229399888,0.781317175068661,0.953611449300569,1.27004115919322,1.62,2.26,4,6.5,7,8.26723289204519 56 | Dominica,DMA,Internet users (per 100 people),IT.NET.USER.P2,8.81484419762881,13.2452280386761,18.4248927671241,23.6204198529629,30.3196130921568,38.5436432637571,39.39817430347,40.2744630071599,41.16,42.02,47.45,51.3135,55.1770141638519 57 | Dominican Republic,DOM,Internet users (per 100 people),IT.NET.USER.P2,3.70469235587688,4.42939069666645,6.8237257325842,7.89839293716387,8.8655534607044,11.4831977789344,14.8449283654589,17.66,20.82,27.72,31.4,38.5818974054268,45 58 | Ecuador,ECU,Internet users (per 100 people),IT.NET.USER.P2,1.46218853556457,2.67044367376705,4.26079691519764,4.46022834472698,4.83444263759525,5.99425516097331,7.2,10.8,18.8,24.6,29.03,31.4,35.1345062239322 59 | "Egypt, Arab Rep.",EGY,Internet users (per 100 people),IT.NET.USER.P2,0.641265037504813,0.838945611477604,2.71999971465421,4.03788510706701,11.92,12.75,13.66,16.03,18.01,25.69,31.42,39.83,44.07 60 | El Salvador,SLV,Internet users (per 100 people),IT.NET.USER.P2,1.17739726914477,1.5,1.9,2.5,3.2,4.2,5.5,6.11,10.08,12.11,15.9,18.9,25.5 61 | Equatorial Guinea,GNQ,Internet users (per 100 people),IT.NET.USER.P2,0.132354665123789,0.16526042288306,0.321197997152044,0.520524272046806,0.843930284607049,1.14978967061811,1.27919359635686,1.55712305943539,1.82,2.13,6,11.5,13.9431821913076 62 | Eritrea,ERI,Internet users (per 100 people),IT.NET.USER.P2,0.136711941104496,0.157815289303043,0.227090137626716,,,,,,,,,0.7,0.8 63 | Estonia,EST,Internet users (per 100 people),IT.NET.USER.P2,28.5769538105646,31.5274897673756,41.52,45.32,53.2,61.45,63.51,66.19,70.58,72.5,74.1,76.5,79 64 | Ethiopia,ETH,Internet users (per 100 people),IT.NET.USER.P2,0.0152637672082567,0.0371623810686744,0.0724022618698295,0.105811658802603,0.155334520794722,0.219659818999505,0.310592656856176,0.37,0.45,0.54,0.75,1.1,1.48281013861396 65 | Faeroe Islands,FRO,Internet users (per 100 people),IT.NET.USER.P2,32.916392363397,43.2469835228993,53.2992218313613,58.9126409695337,66.5335994677312,67.9026317413939,69.359445124439,75.98,75.57,75.18,75.2,80.7321728,85.3351892392109 66 | Fiji,FJI,Internet users (per 100 people),IT.NET.USER.P2,1.4968547339902,1.8579785936093,6.15264970011985,6.72543473821551,7.4129434854202,8.45363663371359,9.6000384001536,10.8978310454645,13,17,20,28,33.7423567521104 67 | Finland,FIN,Internet users (per 100 people),IT.NET.USER.P2,37.2484617371121,43.1053633522263,62.43,69.22,72.39,74.48,79.66,80.78,83.67,82.49,86.89,89.37,91 68 | France,FRA,Internet users (per 100 people),IT.NET.USER.P2,14.3079239430677,26.3259035521688,30.18,36.14,39.15,42.87,46.87,66.09,70.68,71.58,80.1,79.58,83 69 | French Polynesia,PYF,Internet users (per 100 people),IT.NET.USER.P2,6.3570635452072,6.25158894552365,8.20021648571522,14.1242937853107,17.8845374263753,21.5421854743002,25.1079642462589,28.59,33.87,44.6,49,49,52.8766305867993 70 | Gabon,GAB,Internet users (per 100 people),IT.NET.USER.P2,1.21614061828589,1.34762022159633,1.93953010616212,2.65958659385985,2.97906980035019,4.89326474972412,5.48920080280135,5.76700457562583,6.21,6.7,7.23,8,8.61671448924674 71 | "Gambia, The",GMB,Internet users (per 100 people),IT.NET.USER.P2,0.921794919066406,1.33679116648397,1.79677988296495,2.436781193062,3.30800347812937,3.79900113882231,5.23769115841218,6.20503741852418,6.88,7.63,9.2,10.8703,12.4492287209242 72 | Georgia,GEO,Internet users (per 100 people),IT.NET.USER.P2,0.484746298540492,0.992344436138154,1.58787562980332,2.55881648068489,3.88622135350273,6.07945762869794,7.52687684487832,8.26,10.01,20.07,26.9,36.56,45.5030980139962 73 | Germany,DEU,Internet users (per 100 people),IT.NET.USER.P2,30.2163466048889,31.650939416316,48.82,55.9,64.73,68.71,72.16,75.16,78,79,82,83,84 74 | Ghana,GHA,Internet users (per 100 people),IT.NET.USER.P2,0.153615297625799,0.200008060324831,0.830284033818739,1.19305791098328,1.71679770389504,1.83119746104616,2.72317597313987,3.85,4.27,5.44,7.8,14.11,17.1076783234217 75 | Greece,GRC,Internet users (per 100 people),IT.NET.USER.P2,9.13883730776798,10.9350258080278,14.67,17.8,21.42,24,32.25,35.88,38.2,42.4,44.4,53,56 76 | Greenland,GRL,Internet users (per 100 people),IT.NET.USER.P2,31.7478112321162,35.4641368915684,44.154789028418,54.5342598293605,56.0990147610533,57.7034045008656,59.361687268664,61.07,62.82,62.83,63,64,64.8960100995989 77 | Grenada,GRD,Internet users (per 100 people),IT.NET.USER.P2,4.06390799146313,5.12810398216998,14.7588405454867,18.6451821830564,19.5706205843787,20.4878048780488,21.3959911692909,22.29,23.18,24.05,33.46,38.13,42.090255661502 78 | Guam,GUM,Internet users (per 100 people),IT.NET.USER.P2,16.1131270423389,25.3802275337398,31.1922942556271,33.7166817880876,36.1617878387908,38.559877557558,43.8514430048178,46.1504736192355,48.4187021509296,50.6420283820435,54.04,57.7,61.5341593079002 79 | Guatemala,GTM,Internet users (per 100 people),IT.NET.USER.P2,0.712332904508925,1.7382017815873,3.39174627024502,4.54885490848324,5.1,5.7,6.5,7.3,8.3,9.3,10.5,12.3,16 80 | Guinea,GIN,Internet users (per 100 people),IT.NET.USER.P2,0.0954249236063846,0.175541868491054,0.402022748744397,0.450960370842751,0.508819332302937,0.542254180996637,0.637492122987955,0.780025279059244,0.92,0.94,1,1.3,1.49014436571885 81 | Guinea-Bissau,GNB,Internet users (per 100 people),IT.NET.USER.P2,0.230103170591588,0.299569144677667,1.02299323436117,1.35419747762901,1.80814220342991,1.90136531610877,2.0571967045036,2.20630223745003,2.35488871087933,2.30328059172582,2.45,2.672,2.89399062085531 82 | Guyana,GUY,Internet users (per 100 people),IT.NET.USER.P2,6.61149156572021,13.2069864958563,,,,,,,18.2,23.9,29.9,32,34.3080462212964 83 | Haiti,HTI,Internet users (per 100 people),IT.NET.USER.P2,0.231270714628821,0.340832326173809,0.893432988337908,1.64735834557778,5.401261907622,6.37620163504938,6.79600048052951,7.2,7.6,8.1,8.37,9.5,10.8702960267506 84 | Honduras,HND,Internet users (per 100 people),IT.NET.USER.P2,1.20385599891878,1.41528193123711,2.5974025974026,4.8,5.6,6.5,7.8,9.4,9.6,9.8,11.09,15.9,18.1198701421747 85 | "Hong Kong SAR, China",HKG,Internet users (per 100 people),IT.NET.USER.P2,27.8277606812581,38.6714032309569,43.0823830278909,52.2000433497404,56.3998803935333,56.9,60.8,64.8,66.7,69.4,72,72.2,72.8 86 | Hungary,HUN,Internet users (per 100 people),IT.NET.USER.P2,6.99967635062916,14.5285543028277,16.67,21.63,27.74,38.97,47.06,53.3,61,62,65,70,72 87 | Iceland,ISL,Internet users (per 100 people),IT.NET.USER.P2,44.470533824288,49.3929953676426,79.12,83.14,83.88,87,89.51,90.6,91,93,93.39,95.02,96 88 | India,IND,Internet users (per 100 people),IT.NET.USER.P2,0.527532449930946,0.660146377009928,1.53787558175083,1.68648997063625,1.97613649190559,2.38807499995774,2.80549986534254,3.95,4.38,5.12,7.5,10.07,12.5800609138955 89 | Indonesia,IDN,Internet users (per 100 people),IT.NET.USER.P2,0.925563864466858,2.01861385948459,2.13413573295808,2.38701977959476,2.60028587633414,3.60202476259646,4.76481313366657,5.78627472934199,7.91747938492903,6.92,10.92,12.28,15.36 90 | "Iran, Islamic Rep.",IRN,Internet users (per 100 people),IT.NET.USER.P2,0.934190019959829,1.48422133163363,4.62617511492205,6.93372195304053,7.49,8.1,8.76,9.47,10.24,11.07,14.7,21,25.997636032787 91 | Iraq,IRQ,Internet users (per 100 people),IT.NET.USER.P2,,0.1,0.5,0.6,0.9,0.9,0.95234424383919,0.93,1,1.06,2.5,5,7.1 92 | Ireland,IRL,Internet users (per 100 people),IT.NET.USER.P2,17.8504672405807,23.1388121904053,25.85,34.31,36.99,41.61,54.82,60.55,65.34,67.38,69.85,76.82,79 93 | Isle of Man,IMY,Internet users (per 100 people),IT.NET.USER.P2,,,,,,,,,,,,, 94 | Israel,ISR,Internet users (per 100 people),IT.NET.USER.P2,20.8737899981674,17.3786238639049,17.7645990630493,19.5933936579649,22.7704855254035,25.1940424216829,27.8810744577346,48.1280621948445,59.39,63.12,67.5,68.9,73.3650160988905 95 | Italy,ITA,Internet users (per 100 people),IT.NET.USER.P2,23.1108742441037,27.2221169788811,28.04,29.04,33.24,35,37.99,40.79,44.53,48.83,53.68,56.8,58 96 | Jamaica,JAM,Internet users (per 100 people),IT.NET.USER.P2,3.11577802729967,3.8630203874764,6.1,7.8,10,12.8,16.4,21.1,23.6,24.3,27.67,37.49,46.5 97 | Japan,JPN,Internet users (per 100 people),IT.NET.USER.P2,29.9907403589142,38.5320608612719,46.5942011179917,48.4352658899653,62.3939296327259,66.9210661042901,68.6852703213795,74.3,75.4,78,78.21,79.0545209265596,79.05 98 | Jordan,JOR,Internet users (per 100 people),IT.NET.USER.P2,2.62327542156362,4.70571958094159,6.02553241191204,8.46600501623938,11.6587413706379,12.9328520457507,13.8671087882097,20,23,26,27.2,34.9,41 99 | Kazakhstan,KAZ,Internet users (per 100 people),IT.NET.USER.P2,0.668594402621264,1.00612421099739,1.67477099516366,2.00041475265872,2.6503946570164,2.96170749150089,3.26836905115978,4.02,11,18.2,31.6,50.6,53.3156691222464 100 | Kenya,KEN,Internet users (per 100 people),IT.NET.USER.P2,0.318059713612037,0.619782266151425,1.20777388492125,2.94190286101524,3.02352804209621,3.10189770248496,7.5337897198037,7.95,8.67,10.04,14,28,32.0954171077906 101 | Kiribati,KIR,Internet users (per 100 people),IT.NET.USER.P2,1.78522547397736,2.33745894837722,2.5,3,3.5,4,4.5,6,7,8.97,9.07,10,10.7467976841839 102 | "Korea, Dem. Rep.",PRK,Internet users (per 100 people),IT.NET.USER.P2,0,0,0,0,0,0,0,0,0,0,,, 103 | "Korea, Rep.",KOR,Internet users (per 100 people),IT.NET.USER.P2,44.7,56.6,59.4,65.5,72.7,73.5,78.1,78.8,81,81.6,83.7,83.8,84.1 104 | Kosovo,KSV,Internet users (per 100 people),IT.NET.USER.P2,,,,,,,,,,,,, 105 | Kuwait,KWT,Internet users (per 100 people),IT.NET.USER.P2,6.73139576837536,8.55179243431475,10.2489679289296,22.4029383804612,22.9271120360078,25.9261083689107,28.7911979549612,34.8,42,50.8,61.4,74.2,79.1782013155686 106 | Kyrgyz Republic,KGZ,Internet users (per 100 people),IT.NET.USER.P2,1.04140114469361,3.00294132719239,2.99926518003089,3.90876626828521,5.09038530732863,10.5338013405507,12.3069069959465,14.03,15.7,17,18.4,20,21.723508986846 107 | Lao PDR,LAO,Internet users (per 100 people),IT.NET.USER.P2,0.111044032290124,0.181664460654566,0.267899241309349,0.333912466428616,0.361434490258043,0.850357490288917,1.16989342772256,1.64,3.55,6,7,9,10.747676189122 108 | Latvia,LVA,Internet users (per 100 people),IT.NET.USER.P2,6.31906208267861,7.21934580836289,21.94,26.98,38.58,46,53.63,59.17,63.41,66.84,68.42,71.68,74 109 | Lebanon,LBN,Internet users (per 100 people),IT.NET.USER.P2,7.95274373635276,6.78321977527715,7,8,9,10.14,15,18.74,22.53,30.14,43.68,52,61.249785718304 110 | Lesotho,LSO,Internet users (per 100 people),IT.NET.USER.P2,0.211806070361977,0.261150063093855,1.08394313737534,1.53249659019509,2.17552433931097,2.58024548419449,2.97970818724486,3.44543125970873,3.58,3.72,3.86,4.2248,4.58961762996709 111 | Liberia,LBR,Internet users (per 100 people),IT.NET.USER.P2,0.0177027057169472,0.033812227177592,0.0327133071190699,0.0318689345450327,0.0310111848040233,,,0.551376580555429,0.53,0.51,2.3,3,3.7941 112 | Libya,LBY,Internet users (per 100 people),IT.NET.USER.P2,0.187042622337565,0.366533175284228,2.24441822165946,2.81451798740855,3.53283816154549,3.91778797710285,4.30105178913002,4.72199937850513,9,10.8,14,14,19.8637 113 | Liechtenstein,LIE,Internet users (per 100 people),IT.NET.USER.P2,36.5152298938015,45.1168526483592,59.4707106749926,58.8096918372148,64.0074481394199,63.3713561470216,64.2141613630526,65.080218443168,70,75,80,85,89.4077 114 | Lithuania,LTU,Internet users (per 100 people),IT.NET.USER.P2,6.42706749477765,7.17935699381369,17.69,25.91,31.23,36.22,43.9,49.9,55.22,59.76,62.12,65.05,68 115 | Luxembourg,LUX,Internet users (per 100 people),IT.NET.USER.P2,22.8873279731211,36.163422506306,39.84,54.55,65.88,70,72.51,78.92,82.23,87.31,90.62,90.89,92 116 | "Macao SAR, China",MAC,Internet users (per 100 people),IT.NET.USER.P2,13.6085897418451,22.5212167700561,25.1718801643395,25.7421239826498,31.4840973824121,34.8629271733959,46.4,47.327,49.24,54,55.198,60.2,64.2727 117 | "Macedonia, FYR",MKD,Internet users (per 100 people),IT.NET.USER.P2,2.48556631640066,3.46818460849398,17.33,19.07,24.44,26.45,28.62,36.3,46.04,51.77,51.9,56.7,63.1477 118 | Madagascar,MDG,Internet users (per 100 people),IT.NET.USER.P2,0.196394691006341,0.222511586178292,0.339720154596142,0.423252419277809,0.525353654946669,0.567721802237403,0.607552238860378,0.65,1.65,1.63,1.7,1.9,2.0549 119 | Malawi,MWI,Internet users (per 100 people),IT.NET.USER.P2,0.126780944756387,0.164020725330811,0.21509472652259,0.278815116302304,0.347505334967577,0.384489334082782,0.425137489606563,0.965864736553132,0.7,1.07,2.26,3.33,4.3506 120 | Malaysia,MYS,Internet users (per 100 people),IT.NET.USER.P2,21.3847311644538,26.6959725007084,32.3382043389359,34.9711523397291,42.2522656295248,48.629170245984,51.6379889864403,55.7,55.8,55.9,56.3,61,65.8 121 | Maldives,MDV,Internet users (per 100 people),IT.NET.USER.P2,2.2038729393788,3.61729064930367,5.3477651689359,5.97659284988557,6.58825487530861,6.86960506696215,11.0363527828647,16.3,23.2,24.8,26.53,34,38.9301 122 | Mali,MLI,Internet users (per 100 people),IT.NET.USER.P2,0.142545755049184,0.185897413256084,0.2270455784917,0.310364443230575,0.432819639900985,0.507063135952247,0.729627280833101,0.81,1.57,1.8,1.9,2,2.1689 123 | Malta,MLT,Internet users (per 100 people),IT.NET.USER.P2,13.1137087111024,17.8778483243348,28.92,31.64,34.62,41.24,40.41,46.9,50.08,58.86,63,69.22,70 124 | Marshall Islands,MHL,Internet users (per 100 people),IT.NET.USER.P2,1.53427179624871,1.7066140776699,2.33544457522934,2.5697503671072,3.59977681383754,3.8787023977433,3.79559021427832,3.95,4.6,5.6,7,8.06,10 125 | Mauritania,MRT,Internet users (per 100 people),IT.NET.USER.P2,0.192031462434805,0.261448551967195,0.363229414608579,0.424004895843197,0.481470438230954,0.669966474877597,0.97966125273203,1.43361319586788,1.87,2.28,4,4.5,5.3691 126 | Mauritius,MUS,Internet users (per 100 people),IT.NET.USER.P2,7.28153511499385,8.78090401892036,10.252624671916,12.1871062041308,13.6890885885342,15.1722287524914,16.7,20.22,21.81,22.51,28.33,34.95,41.3946 127 | Mexico,MEX,Internet users (per 100 people),IT.NET.USER.P2,5.08138415338066,7.0380231165045,11.9,12.9,14.1,17.21,19.52,20.81,21.71,26.34,31.05,34.96,38.42 128 | "Micronesia, Fed. Sts.",FSM,Internet users (per 100 people),IT.NET.USER.P2,3.73486213690137,4.66052719883673,5.57009970478472,9.23250210039423,11.0178673081514,11.8813690993008,12.7503392500979,13.6211327333981,14.49,15.35,20,22.8,25.9744230029883 129 | Moldova,MDA,Internet users (per 100 people),IT.NET.USER.P2,1.28284641670022,1.48781295215565,3.78724279887342,7.40754458415897,10.629390841654,14.6302704551796,19.6206477051634,20.45,23.39,27.5,32.3,38,43.37 130 | Monaco,MCO,Internet users (per 100 people),IT.NET.USER.P2,42.1848634460346,46.6461423640265,48.0471171729696,49.4911689195459,52.4901966838546,55.4648260561427,61.4760397135217,64.3776824034335,67.25,70.1,75,80.3,87 131 | Mongolia,MNG,Internet users (per 100 people),IT.NET.USER.P2,1.25565200358112,1.65323823906982,2.0395726034818,,,,,,9.8,10,10.2,12.5,16.4 132 | Montenegro,MNE,Internet users (per 100 people),IT.NET.USER.P2,,,,,25.3500686036232,27.1,28.9,30.8,32.9,35.1,37.5,40,56.8387825490631 133 | Morocco,MAR,Internet users (per 100 people),IT.NET.USER.P2,0.693791244805455,1.37143810096418,2.37325319241544,3.35336668122363,11.6079347729582,15.0844445240204,19.7711915653115,21.5,33.1,41.3,52,53,55 134 | Mozambique,MOZ,Internet users (per 100 people),IT.NET.USER.P2,0.109592530041641,0.160031946644069,0.259612611253739,0.419538843913707,0.679447835392438,0.854357118107528,0.842954488044236,0.91,1.56,2.68,4.17,4.3,4.8491 135 | Myanmar,MMR,Internet users (per 100 people),IT.NET.USER.P2,,0.000289277380848561,0.00042649350636189,0.0240641491408465,0.024337392002823,0.0652388555011476,0.18204833106115,0.217128445069958,0.22,0.22,0.25,0.98,1.0691 136 | Namibia,NAM,Internet users (per 100 people),IT.NET.USER.P2,1.64473954726899,2.41697944170998,2.63369976876116,3.35983988553801,3.8047156152631,4.01004664442375,4.39887064732726,4.83561077833704,5.32900377208954,6.5,11.6,12,12.9414 137 | Nepal,NPL,Internet users (per 100 people),IT.NET.USER.P2,0.204651683653029,0.240015303375743,0.312956060108532,0.382810917017044,0.449843651215974,0.82655126590551,1.14138916388191,1.41,1.73,1.97,7.93,9,11.1493 138 | Netherlands,NLD,Internet users (per 100 people),IT.NET.USER.P2,43.984351373138,49.3730621073123,61.29,64.35,68.52,81,83.7,85.82,87.42,89.63,90.72,92.3,93 139 | New Caledonia,NCL,Internet users (per 100 people),IT.NET.USER.P2,13.9395488232698,18.2371097828416,22.389797417113,26.4083344703588,30.2980462088488,32.3590147531561,33.5157146807209,35.05,34.51,33.99,42,50,58 140 | New Zealand,NZL,Internet users (per 100 people),IT.NET.USER.P2,47.3795565432096,53.2410152941279,59.0807532812931,60.9625398663824,61.8476278106481,62.7202123689541,69,69.76,72.03,79.7,83,86,89.5109 141 | Nicaragua,NIC,Internet users (per 100 people),IT.NET.USER.P2,0.980216486692385,1.44879942820716,1.71472958428668,1.88041253242301,2.32066460863941,2.56635117656203,2.8055730627337,3.9,5.3,7.3,10,10.6,13.5 142 | Niger,NER,Internet users (per 100 people),IT.NET.USER.P2,0.0362612938065891,0.10518543139701,0.127152468779619,0.155698703668983,0.189933733703092,0.221341351487395,0.294033977096223,0.390390619762082,0.7,0.76,0.83,1.3,1.4077 143 | Nigeria,NGA,Internet users (per 100 people),IT.NET.USER.P2,0.0640808079494101,0.089901370457273,0.320461975542327,0.558576244860521,1.28613764134527,3.54915571796492,5.54503608300536,6.77,15.86,20,24,28.43,32.8763 144 | Northern Mariana Islands,MNP,Internet users (per 100 people),IT.NET.USER.P2,,,,,,,,,,,,, 145 | Norway,NOR,Internet users (per 100 people),IT.NET.USER.P2,52,64,72.84,78.13,77.69,81.99,82.55,86.93,90.57,92.08,93.39,93.97,95 146 | Oman,OMN,Internet users (per 100 people),IT.NET.USER.P2,3.52042141651097,5.89384152148671,6.87339603918545,7.25574294536556,6.75885217024525,6.68358142020519,8.299716672503,16.68,20,26.8,35.8278,48,60 147 | Pakistan,PAK,Internet users (per 100 people),IT.NET.USER.P2,,1.31855077925626,2.57742673683831,5.04115812592502,6.16432098534156,6.33232909830782,6.5,6.8,7,7.5,8,9,9.9637 148 | Palau,PLW,Internet users (per 100 people),IT.NET.USER.P2,,,20.243939470621,21.6015271777354,26.9703326341025,,,,,,,, 149 | Panama,PAN,Internet users (per 100 people),IT.NET.USER.P2,6.55479647729549,7.26839987788729,8.51807781515595,9.98742407723405,11.1408625409415,11.4840092783071,17.3495661695931,22.29,33.82,39.08,40.1,42.7,45.1999 150 | Papua New Guinea,PNG,Internet users (per 100 people),IT.NET.USER.P2,0.835249302427624,0.904103545170821,1.3216569348661,1.37440466802801,1.5079127722725,1.71619047556782,1.754487260668,1.79055936140245,1.15,1.61,1.28,2,2.30195686635833 151 | Paraguay,PRY,Internet users (per 100 people),IT.NET.USER.P2,0.747630711548184,1.09879383569333,1.79496959770244,2.11194208210034,3.45243843137079,7.90700786141285,7.96207388394416,11.21,14.27,18.9,19.8,23.9,27.0758 152 | Peru,PER,Internet users (per 100 people),IT.NET.USER.P2,3.07643061137675,7.57876295717419,8.96694917040028,11.6,14.1,17.1,20.7,25.2,30.57,31.4,34.77,36,38.2 153 | Philippines,PHL,Internet users (per 100 people),IT.NET.USER.P2,1.98225319605827,2.52400566008269,4.33227574643007,4.85767226708512,5.2436284521711,5.3976363293955,5.74058632534702,5.97,6.22,9,25,29,36.2351 154 | Poland,POL,Internet users (per 100 people),IT.NET.USER.P2,7.2854287080601,9.9006697073534,21.15,24.87,32.53,38.81,44.58,48.6,53.13,58.97,62.32,64.88,65 155 | Portugal,PRT,Internet users (per 100 people),IT.NET.USER.P2,16.4304676923534,18.0871365595338,19.37,29.67,31.78,34.99,38.01,42.09,44.13,48.27,53.3,57.76,64 156 | Puerto Rico,PRI,Internet users (per 100 people),IT.NET.USER.P2,10.4746482089399,15.6300309162012,17.5476475654777,19.7070256578252,22.1307382347014,23.4000504486583,25.4424182102582,27.86,38,41.5,45.3,48,51.4114 157 | Qatar,QAT,Internet users (per 100 people),IT.NET.USER.P2,4.86367917875156,6.17026856093911,10.2261289281118,19.242336421002,20.701647851169,24.733493781051,28.9741127135829,37,44.3,53.1,81.6,86.2,88.1043668821783 158 | Romania,ROM,Internet users (per 100 people),IT.NET.USER.P2,3.61371729139783,4.53866902895312,6.58,8.9,15,21.5,24.66,28.3,32.42,36.6,39.93,44.02,50 159 | Russian Federation,RUS,Internet users (per 100 people),IT.NET.USER.P2,1.97723010897647,2.94436778664985,4.12827181796644,8.29886061553908,12.8593889008473,15.2266731976522,18.0232774617216,24.66,26.83,29,43,49,53.2748 160 | Rwanda,RWA,Internet users (per 100 people),IT.NET.USER.P2,0.0628314595107164,0.240672409832383,0.292784718792575,0.356918467272361,0.43085424337006,0.556041164839515,,2.11538717825754,4.5,7.7,8,7,8.02385427694765 161 | Samoa,WSM,Internet users (per 100 people),IT.NET.USER.P2,0.566392532680849,1.68990283058724,2.24483267578443,2.79963044878076,3.07552941044897,3.35259211246829,4.46917387320954,4.74998323535329,5.03161531623702,6,7,11,12.92249 162 | San Marino,SMR,Internet users (per 100 people),IT.NET.USER.P2,48.7994953055999,50.3416690898517,50.8348399446985,50.003453038674,50.5663430420712,50.2595641966736,50.2086593635889,50.3648221088655,54.52,54.21,,49.6,50.8826 163 | Sao Tome and Principe,STP,Internet users (per 100 people),IT.NET.USER.P2,4.6385168164075,6.31091788794615,7.580665168911,10.1618443069961,13.3228526892178,13.759484215906,14.1820197774712,14.5904831987414,15.48,16.41,18.75,20.1612,21.5724 164 | Saudi Arabia,SAU,Internet users (per 100 people),IT.NET.USER.P2,2.21069180908888,4.6810531658103,6.38470501955932,8.00158289090878,10.2345329969583,12.7050359882849,19.4595543513634,30,36,38,41,47.5,54 165 | Senegal,SEN,Internet users (per 100 people),IT.NET.USER.P2,0.403967485868965,0.983794058848003,1.00645453672323,2.10143642986685,4.38602398008563,4.78668408310535,5.61173865217952,7.7,10.6,14.5,16,17.5,19.2036 166 | Serbia,SRB,Internet users (per 100 people),IT.NET.USER.P2,,,,,23.5,26.3,27.2,33.15,35.6,38.1,40.9,42.2,48.1 167 | Seychelles,SYC,Internet users (per 100 people),IT.NET.USER.P2,7.39562918315276,11.0151029300174,14.3041708309972,14.5925043169492,24.2721392249906,25.4132681462836,34.95197117065,38.38,40.44,,41,43.16400446,47.076 168 | Sierra Leone,SLE,Internet users (per 100 people),IT.NET.USER.P2,0.118254245682183,0.160264445493032,0.176198956814076,0.190173281668183,0.203007680592588,0.215391610340129,0.227669467190838,0.239834698546233,0.25,0.26,0.58,0.9,1.3 169 | Singapore,SGP,Internet users (per 100 people),IT.NET.USER.P2,36,41.6704251756041,47,53.8379432881911,62,61,59,69.9,69,69,71,71,74.1818 170 | Sint Maarten (Dutch part),SXM,Internet users (per 100 people),IT.NET.USER.P2,,,,,,,,,,,,, 171 | Slovak Republic,SVK,Internet users (per 100 people),IT.NET.USER.P2,9.42680320061637,12.5283218484637,40.14,43.04,52.89,55.19,56.08,61.8,66.05,70,75.71,74.44,80 172 | Slovenia,SVN,Internet users (per 100 people),IT.NET.USER.P2,15.1102595640388,30.1759104700854,27.8388854180687,31.8547930120441,40.81,46.81,54.01,56.74,58,64,70,69,70 173 | Solomon Islands,SLB,Internet users (per 100 people),IT.NET.USER.P2,0.481296806114395,0.468551963584141,0.501919843401009,0.555609881855115,0.64967267325146,0.844307572805697,1.64634798857435,2,3,4,5,6,6.9974 174 | Somalia,SOM,Internet users (per 100 people),IT.NET.USER.P2,0.02,0.0790391786670095,0.115614213354469,0.376198002539086,1.05345474358422,1.07732783912096,1.10021636808277,1.12223562161375,1.14268737216185,1.16061054246061,,1.25,1.3767 175 | South Africa,ZAF,Internet users (per 100 people),IT.NET.USER.P2,5.34855973203537,6.34661931839768,6.71032243705404,7.00769172611251,8.4251186825406,7.48854252992921,7.60713967477381,8.06537517355504,8.43,10,24,33.97,41 176 | South Sudan,SSD,Internet users (per 100 people),IT.NET.USER.P2,,,,,,,,,,,,, 177 | Spain,ESP,Internet users (per 100 people),IT.NET.USER.P2,13.6249608089899,18.1487226902932,20.39,39.93,44.01,47.88,50.37,55.11,59.6,62.4,65.8,67.6,72 178 | Sri Lanka,LKA,Internet users (per 100 people),IT.NET.USER.P2,0.647410010940963,0.793821864362622,1.05042270585318,1.45858022842408,1.44615998041486,1.79204679392588,2.53756612897332,3.88,5.8,8.78,12,15,18.2854 179 | St. Kitts and Nevis,KNA,Internet users (per 100 people),IT.NET.USER.P2,5.86281024037522,7.71472655580319,21.152381758186,22.9697842928439,24.7376775443732,34,49,52,60,69,76,77.6,79.3488989552935 180 | St. Lucia,LCA,Internet users (per 100 people),IT.NET.USER.P2,5.09048334139327,8.18186395448366,14.6418357746777,20.9824734633424,21.395212362765,21.5675829464215,24.5,27.9,32,36,43.3,45,48.6281 181 | St. Martin (French part),MAF,Internet users (per 100 people),IT.NET.USER.P2,,,,,,,,,,,,, 182 | St. Vincent and the Grenadines,VCT,Internet users (per 100 people),IT.NET.USER.P2,3.24503741064558,5.09480977832946,5.54959487957379,6.46227416659743,7.37116584201749,9.19827808234299,12,16,21,31,38.5,43.01,47.52 183 | Sudan,SDN,Internet users (per 100 people),IT.NET.USER.P2,0.0257850324762484,0.140185224494161,0.439477639075583,0.538471703150458,0.791561615446385,1.29204067798853,,8.66,,,16.7,19,21 184 | Suriname,SUR,Internet users (per 100 people),IT.NET.USER.P2,2.50641105226881,3.06444838873752,4.1615687449541,4.71987539528956,6.0760478144456,6.40308628759062,9.49962694173364,14.11,21.06,31.36,31.59,32,34.6812 185 | Swaziland,SWZ,Internet users (per 100 people),IT.NET.USER.P2,0.926191777269402,1.28159005046719,1.81620380712642,2.43707385055863,3.22868507316111,3.69696107291824,3.69653878906882,4.1,6.85,8.94,11.04,18.13,20.7817825772944 186 | Sweden,SWE,Internet users (per 100 people),IT.NET.USER.P2,45.6876522122282,51.7656649372808,70.57,79.13,83.89,84.83,87.76,82.01,90,91,90,94,94 187 | Switzerland,CHE,Internet users (per 100 people),IT.NET.USER.P2,47.1,55.1,61.4,65.1,67.8,70.1,75.7,77.2,79.2,81.3,83.9,85.2,85.2 188 | Syrian Arab Republic,SYR,Internet users (per 100 people),IT.NET.USER.P2,0.181698580104333,0.353759130007247,2.09310101861765,3.39797261356623,4.32159377785571,5.64810604883917,7.83255156888787,,14,17.3,20.7,22.5,24.3001 189 | Tajikistan,TJK,Internet users (per 100 people),IT.NET.USER.P2,0.048599594582182,0.0512591325352927,0.0554628269910006,0.0645837771508553,0.0774799071356825,0.298690023682825,3.77240620666448,7.19761951797855,8.78,10.07,11.55,13.03,14.51 190 | Tanzania,TZA,Internet users (per 100 people),IT.NET.USER.P2,0.11719444010825,0.171300350668948,0.222484413923909,0.676962856510303,0.87757497099259,4.3,5.8,7.2,9,10,11,12,13.0803 191 | Thailand,THA,Internet users (per 100 people),IT.NET.USER.P2,3.68904127944164,5.55632612097332,7.53125033488567,9.29902723805965,10.6773033237281,15.0260043588909,17.1607147169285,20.03,18.2,20.1,22.4,23.7,26.5 192 | Timor-Leste,TMP,Internet users (per 100 people),IT.NET.USER.P2,,,0,,,0.0990317678179026,0.116628276525644,0.140958763923202,0.163876815618553,0.185251509799805,0.21,0.9,0.9147 193 | Togo,TGO,Internet users (per 100 people),IT.NET.USER.P2,0.8,0.9,1,1.2,1.5,1.8,2,2.2,2.4,2.6,3,3.5,4 194 | Tonga,TON,Internet users (per 100 people),IT.NET.USER.P2,2.4343980443669,2.82537183911525,2.90729731626382,2.98581736750435,3.95237389457043,4.90778276190383,5.85394409483389,7.17986532901248,8.11076994380395,10,16,25,34.8609 195 | Trinidad and Tobago,TTO,Internet users (per 100 people),IT.NET.USER.P2,7.72141147401745,15.384627218944,21.9986754804449,25.9717641091609,27.0242700782714,28.9767124326784,30.0037485791676,32.3,34.8,44.3,48.5,55.2,59.5162 196 | Tunisia,TUN,Internet users (per 100 people),IT.NET.USER.P2,2.75074029298135,4.29796603474685,5.25288729548421,6.49084579532797,8.52881775162591,9.65508654217982,12.9864086534204,17.1,27.53,34.07,36.8,39.1,41.4416 197 | Turkey,TUR,Internet users (per 100 people),IT.NET.USER.P2,3.761685035075,5.18948146078506,11.38,12.33,14.58,15.46,18.24,28.63,34.37,36.4,39.82,43.07,45.13 198 | Turkmenistan,TKM,Internet users (per 100 people),IT.NET.USER.P2,0.13328218259348,0.175209517731422,0.302132016152841,0.425182631884743,0.754053982724623,0.997256558542959,1.3195731180963,1.40636068812023,1.75,1.95,3,5,7.1958 199 | Turks and Caicos Islands,TCA,Internet users (per 100 people),IT.NET.USER.P2,,,,,,,,,,,,, 200 | Tuvalu,TUV,Internet users (per 100 people),IT.NET.USER.P2,5.24163958486214,,,,,,,10,15,20,25,30,35 201 | Uganda,UGA,Internet users (per 100 people),IT.NET.USER.P2,0.163714063074854,0.237945087191408,0.384093504951945,0.464849840113968,0.719970679914031,1.7422055032439,2.52936303826279,3.67196535074744,7.9,9.78,12.5,13.01354333,14.6896 202 | Ukraine,UKR,Internet users (per 100 people),IT.NET.USER.P2,0.716183762031043,1.23875954748114,1.87388511643427,3.14812758815439,3.4894778812677,3.749764414659,4.50612456357379,6.55,11,17.9,23.3,28.70826284,33.7 203 | United Arab Emirates,ARE,Internet users (per 100 people),IT.NET.USER.P2,23.6253008751553,26.2717542010006,28.3164853110439,29.4779534120864,30.1312961695367,40,52,61,63,64,68,78,85 204 | United Kingdom,GBR,Internet users (per 100 people),IT.NET.USER.P2,26.8217543508578,33.4810948744147,56.48,64.82,65.61,70,68.82,75.09,78.39,83.56,85,86.84,87.0162 205 | United States,USA,Internet users (per 100 people),IT.NET.USER.P2,43.0791626375201,49.0808315896951,58.7854038836952,61.6971171244207,64.7582564759896,67.968052915002,68.9311932699721,75,74,71,74,77.863021,81.0252 206 | Uruguay,URY,Internet users (per 100 people),IT.NET.USER.P2,10.5390577480141,11.1214376832783,11.4194701966853,15.9371367159756,17.0630983403661,20.0881895579305,29.4,34,39.3,41.8,46.4,51.4,55.1146 207 | Uzbekistan,UZB,Internet users (per 100 people),IT.NET.USER.P2,0.484347307694947,0.597567993875805,1.08193974248733,1.91259517784997,2.59372542084924,3.34350988825952,6.38832196706805,7.49060468138629,9.0801145234501,17.0582162104349,20,30.2,36.5213 208 | Vanuatu,VUT,Internet users (per 100 people),IT.NET.USER.P2,2.10833689115184,2.83057223877678,3.51003871071264,3.90329584542948,4.74660261917533,5.08233380768449,5.85058505850585,6.8,7.26911992337492,7.5,8,9.2,10.598 209 | "Venezuela, RB",VEN,Internet users (per 100 people),IT.NET.USER.P2,3.3595974645036,4.63600094778677,4.91044632720325,7.49996346522407,8.40446959241215,12.5529979098299,15.2247114771787,20.83,25.88,32.7,37.37,40.22,44.0456 210 | Vietnam,VNM,Internet users (per 100 people),IT.NET.USER.P2,0.254248275754934,1.26565123612317,1.85499923592581,3.78028081370641,7.64240852842363,12.7399292907031,17.2545617186662,20.7554447679753,23.92,26.55,30.65,35.07,39.49 211 | Virgin Islands (U.S.),VIR,Internet users (per 100 people),IT.NET.USER.P2,13.8150805419196,18.3757660397468,27.4944323774436,27.4290729887632,27.3770087880198,27.3443196733265,27.3326105376325,27.3393358364014,27.3617774210613,27.3965096846662,31.22,35.6,40.5479 212 | West Bank and Gaza,WBG,Internet users (per 100 people),IT.NET.USER.P2,1.11130620708969,1.83685483917877,3.10009223512517,4.13061635632919,4.40090482603223,16.005,18.41,21.176,24.358,32.23,37.4,41.08, 213 | "Yemen, Rep.",YEM,Internet users (per 100 people),IT.NET.USER.P2,0.0825003865143108,0.0908024587169292,0.518796032123435,0.604734100431821,0.881222988679516,1.04859793417826,1.24782404904485,5.01,6.89,9.96,12.35,14.905,17.4465 214 | Zambia,ZMB,Internet users (per 100 people),IT.NET.USER.P2,0.191071642503428,0.233129556342332,0.477750906947484,0.980483039426114,2.01354953218533,2.85175226129009,4.15991339393107,4.87,5.55,6.31,10,11.5,13.4682 215 | Zimbabwe,ZWE,Internet users (per 100 people),IT.NET.USER.P2,0.401433535211582,0.799846045633137,3.99435613455612,6.39478645849612,6.5640450271075,8.01597808880485,9.79184150186944,10.85,11.4,11.36,11.5,15.7,17.0908 216 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import sys, os 3 | 4 | version = '0.1' 5 | 6 | setup( 7 | name='ckanext-mapviews', 8 | version=version, 9 | description="Choropleth Map view for CKAN", 10 | long_description="""\ 11 | """, 12 | classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers 13 | keywords='', 14 | author='Vitor Baptista', 15 | author_email='vitor.baptista@okfn.org', 16 | url='https://github.com/ckan/ckanext-mapviews', 17 | license='', 18 | packages=find_packages(exclude=['*.tests']), 19 | namespace_packages=['ckanext', 'ckanext.mapviews'], 20 | include_package_data=True, 21 | zip_safe=False, 22 | install_requires=[ 23 | # -*- Extra requirements: -*- 24 | ], 25 | entry_points=\ 26 | """ 27 | [ckan.plugins] 28 | navigablemap=ckanext.mapviews.plugin:NavigableMap 29 | choroplethmap=ckanext.mapviews.plugin:ChoroplethMap 30 | """, 31 | ) 32 | --------------------------------------------------------------------------------