2011-04-11 18:23:27 +02:00
|
|
|
import os
|
2012-03-07 16:07:37 +01:00
|
|
|
import re
|
2011-04-11 18:23:27 +02:00
|
|
|
from logging import getLogger
|
2012-02-21 12:05:13 +01:00
|
|
|
from pylons import config
|
2011-09-30 12:33:36 +02:00
|
|
|
from pylons.i18n import _
|
2011-04-11 18:23:27 +02:00
|
|
|
from genshi.input import HTML
|
|
|
|
from genshi.filters import Transformer
|
|
|
|
|
|
|
|
import ckan.lib.helpers as h
|
|
|
|
|
2011-12-13 18:40:47 +01:00
|
|
|
from ckan.lib.search import SearchError
|
2011-09-29 16:40:49 +02:00
|
|
|
from ckan.lib.helpers import json
|
|
|
|
|
2011-12-13 18:40:47 +01:00
|
|
|
from ckan import model
|
|
|
|
|
2011-04-11 18:23:27 +02:00
|
|
|
from ckan.plugins import implements, SingletonPlugin
|
2012-02-07 17:50:46 +01:00
|
|
|
from ckan.plugins import IRoutes
|
|
|
|
from ckan.plugins import IConfigurable, IConfigurer
|
2011-10-28 14:03:31 +02:00
|
|
|
from ckan.plugins import IGenshiStreamFilter
|
2011-09-29 16:40:49 +02:00
|
|
|
from ckan.plugins import IPackageController
|
|
|
|
|
|
|
|
from ckan.logic import ValidationError
|
2011-04-11 18:23:27 +02:00
|
|
|
|
|
|
|
import html
|
|
|
|
|
2012-01-17 18:20:16 +01:00
|
|
|
from ckanext.spatial.lib import save_package_extent,validate_bbox, bbox_query
|
2012-02-07 17:50:46 +01:00
|
|
|
from ckanext.spatial.model import setup as setup_model
|
|
|
|
|
2011-09-29 16:40:49 +02:00
|
|
|
|
2011-04-11 18:23:27 +02:00
|
|
|
log = getLogger(__name__)
|
|
|
|
|
2012-03-07 16:07:37 +01:00
|
|
|
def package_error_summary(error_dict):
|
|
|
|
''' Do some i18n stuff on the error_dict keys '''
|
|
|
|
|
|
|
|
def prettify(field_name):
|
|
|
|
field_name = re.sub('(?<!\w)[Uu]rl(?!\w)', 'URL',
|
|
|
|
field_name.replace('_', ' ').capitalize())
|
|
|
|
return _(field_name.replace('_', ' '))
|
|
|
|
|
|
|
|
summary = {}
|
|
|
|
for key, error in error_dict.iteritems():
|
|
|
|
if key == 'resources':
|
|
|
|
summary[_('Resources')] = _('Package resource(s) invalid')
|
|
|
|
elif key == 'extras':
|
|
|
|
summary[_('Extras')] = _('Missing Value')
|
|
|
|
elif key == 'extras_validation':
|
|
|
|
summary[_('Extras')] = error[0]
|
|
|
|
else:
|
|
|
|
summary[_(prettify(key))] = error[0]
|
|
|
|
return summary
|
|
|
|
|
2012-02-21 13:00:47 +01:00
|
|
|
class SpatialMetadata(SingletonPlugin):
|
2011-04-11 19:04:28 +02:00
|
|
|
|
2011-09-29 16:40:49 +02:00
|
|
|
implements(IPackageController, inherit=True)
|
2012-02-07 17:50:46 +01:00
|
|
|
implements(IConfigurable, inherit=True)
|
|
|
|
|
|
|
|
def configure(self, config):
|
|
|
|
|
|
|
|
if not config.get('ckan.spatial.testing',False):
|
|
|
|
setup_model()
|
2011-04-11 19:04:28 +02:00
|
|
|
|
|
|
|
|
2011-09-29 16:40:49 +02:00
|
|
|
def create(self, package):
|
|
|
|
self.check_spatial_extra(package)
|
|
|
|
|
|
|
|
def edit(self, package):
|
|
|
|
self.check_spatial_extra(package)
|
|
|
|
|
|
|
|
def check_spatial_extra(self,package):
|
2011-10-17 00:40:19 +02:00
|
|
|
if not package.id:
|
|
|
|
log.warning('Couldn\'t store spatial extent because no id was provided for the package')
|
|
|
|
return
|
|
|
|
|
|
|
|
# TODO: deleted extra
|
2011-09-29 16:40:49 +02:00
|
|
|
for extra in package.extras_list:
|
|
|
|
if extra.key == 'spatial':
|
|
|
|
if extra.state == 'active':
|
|
|
|
try:
|
|
|
|
log.debug('Received: %r' % extra.value)
|
|
|
|
geometry = json.loads(extra.value)
|
|
|
|
except ValueError,e:
|
|
|
|
error_dict = {'spatial':[u'Error decoding JSON object: %s' % str(e)]}
|
|
|
|
raise ValidationError(error_dict, error_summary=package_error_summary(error_dict))
|
|
|
|
except TypeError,e:
|
|
|
|
error_dict = {'spatial':[u'Error decoding JSON object: %s' % str(e)]}
|
|
|
|
raise ValidationError(error_dict, error_summary=package_error_summary(error_dict))
|
|
|
|
|
|
|
|
try:
|
|
|
|
save_package_extent(package.id,geometry)
|
|
|
|
|
|
|
|
except ValueError,e:
|
|
|
|
error_dict = {'spatial':[u'Error creating geometry: %s' % str(e)]}
|
|
|
|
raise ValidationError(error_dict, error_summary=package_error_summary(error_dict))
|
|
|
|
except Exception, e:
|
|
|
|
error_dict = {'spatial':[u'Error: %s' % str(e)]}
|
|
|
|
raise ValidationError(error_dict, error_summary=package_error_summary(error_dict))
|
|
|
|
|
|
|
|
elif extra.state == 'deleted':
|
|
|
|
# Delete extent from table
|
|
|
|
save_package_extent(package.id,None)
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
def delete(self, package):
|
|
|
|
save_package_extent(package.id,None)
|
2011-04-11 19:04:28 +02:00
|
|
|
|
2012-02-21 13:00:47 +01:00
|
|
|
class SpatialQuery(SingletonPlugin):
|
|
|
|
|
|
|
|
implements(IRoutes, inherit=True)
|
|
|
|
implements(IPackageController, inherit=True)
|
|
|
|
|
|
|
|
def before_map(self, map):
|
|
|
|
|
|
|
|
map.connect('api_spatial_query', '/api/2/search/{register:dataset|package}/geo',
|
|
|
|
controller='ckanext.spatial.controllers.api:ApiController',
|
|
|
|
action='spatial_query')
|
|
|
|
return map
|
|
|
|
|
2012-01-17 18:20:16 +01:00
|
|
|
def before_search(self,search_params):
|
2011-12-13 18:40:47 +01:00
|
|
|
if 'extras' in search_params and 'ext_bbox' in search_params['extras'] \
|
|
|
|
and search_params['extras']['ext_bbox']:
|
|
|
|
|
|
|
|
bbox = validate_bbox(search_params['extras']['ext_bbox'])
|
|
|
|
if not bbox:
|
|
|
|
raise SearchError('Wrong bounding box provided')
|
|
|
|
|
|
|
|
extents = bbox_query(bbox)
|
|
|
|
|
2012-01-17 18:20:16 +01:00
|
|
|
if extents.count() == 0:
|
|
|
|
# We don't need to perform the search
|
|
|
|
search_params['abort_search'] = True
|
|
|
|
else:
|
|
|
|
# We'll perform the existing search but also filtering by the ids
|
|
|
|
# of datasets within the bbox
|
|
|
|
bbox_query_ids = [extent.package_id for extent in extents]
|
2011-12-13 18:40:47 +01:00
|
|
|
|
2012-01-17 18:20:16 +01:00
|
|
|
q = search_params.get('q','')
|
|
|
|
new_q = '%s AND ' % q if q else ''
|
|
|
|
new_q += '(%s)' % ' OR '.join(['id:%s' % id for id in bbox_query_ids])
|
2011-12-13 18:40:47 +01:00
|
|
|
|
2012-01-17 18:20:16 +01:00
|
|
|
search_params['q'] = new_q
|
2011-12-13 18:40:47 +01:00
|
|
|
|
2012-01-17 18:20:16 +01:00
|
|
|
return search_params
|
2011-12-13 18:40:47 +01:00
|
|
|
|
2012-02-21 13:00:47 +01:00
|
|
|
class SpatialQueryWidget(SingletonPlugin):
|
|
|
|
|
|
|
|
implements(IGenshiStreamFilter)
|
|
|
|
|
2011-12-13 18:40:47 +01:00
|
|
|
def filter(self, stream):
|
|
|
|
from pylons import request, tmpl_context as c
|
|
|
|
routes = request.environ.get('pylons.routes_dict')
|
|
|
|
if routes.get('controller') == 'package' and \
|
|
|
|
routes.get('action') == 'search':
|
|
|
|
|
2012-02-21 12:05:13 +01:00
|
|
|
data = {
|
|
|
|
'bbox': request.params.get('ext_bbox',''),
|
|
|
|
'default_extent': config.get('ckan.spatial.default_map_extent','')
|
|
|
|
}
|
2011-12-13 18:40:47 +01:00
|
|
|
stream = stream | Transformer('body//div[@id="dataset-search-ext"]')\
|
|
|
|
.append(HTML(html.SPATIAL_SEARCH_FORM % data))
|
2012-01-18 12:03:21 +01:00
|
|
|
stream = stream | Transformer('head')\
|
|
|
|
.append(HTML(html.SPATIAL_SEARCH_FORM_EXTRA_HEADER % data))
|
|
|
|
stream = stream | Transformer('body')\
|
|
|
|
.append(HTML(html.SPATIAL_SEARCH_FORM_EXTRA_FOOTER % data))
|
2011-12-13 18:40:47 +01:00
|
|
|
|
|
|
|
return stream
|
|
|
|
|
2011-04-11 19:04:28 +02:00
|
|
|
|
2011-09-30 12:33:36 +02:00
|
|
|
class DatasetExtentMap(SingletonPlugin):
|
|
|
|
|
|
|
|
implements(IGenshiStreamFilter)
|
|
|
|
implements(IConfigurer, inherit=True)
|
|
|
|
|
|
|
|
def filter(self, stream):
|
|
|
|
from pylons import request, tmpl_context as c
|
2012-04-27 14:06:20 +02:00
|
|
|
|
2012-04-27 11:44:15 +02:00
|
|
|
route_dict = request.environ.get('pylons.routes_dict')
|
|
|
|
route = '%s/%s' % (route_dict.get('controller'), route_dict.get('action'))
|
|
|
|
routes_to_filter = config.get('ckan.spatial.dataset_extent_map.routes', 'package/read').split(' ')
|
|
|
|
if route in routes_to_filter and c.pkg.id:
|
2011-09-30 12:33:36 +02:00
|
|
|
|
|
|
|
extent = c.pkg.extras.get('spatial',None)
|
|
|
|
if extent:
|
2012-05-22 11:51:44 +02:00
|
|
|
map_element_id = config.get('ckan.spatial.dataset_extent_map.element_id', 'dataset')
|
|
|
|
title = config.get('ckan.spatial.dataset_extent_map.title', 'Geographic extent')
|
|
|
|
body_html = html.PACKAGE_MAP_EXTENDED if title else html.PACKAGE_MAP_BASIC
|
|
|
|
map_type = config.get('ckan.spatial.dataset_extent_map.map_type', 'osm')
|
|
|
|
if map_type == 'osm':
|
|
|
|
js_library_links = '<script type="text/javascript" src="/ckanext/spatial/js/openlayers/OpenLayers_dataset_map.js"></script>'
|
|
|
|
map_attribution = html.MAP_ATTRIBUTION_OSM
|
|
|
|
elif map_type == 'os':
|
|
|
|
js_library_links = '<script src="http://osinspiremappingprod.ordnancesurvey.co.uk/libraries/openlayers-openlayers-56e25fc/lib/OpenLayers.js" type="text/javascript"></script>'
|
|
|
|
map_attribution = '' # done in the js instead
|
|
|
|
|
2011-09-30 12:33:36 +02:00
|
|
|
data = {'extent': extent,
|
2012-05-22 11:51:44 +02:00
|
|
|
'title': _(title),
|
2012-04-27 14:06:20 +02:00
|
|
|
'map_type': map_type,
|
|
|
|
'js_library_links': js_library_links,
|
2012-05-22 11:51:44 +02:00
|
|
|
'map_attribution': map_attribution,
|
|
|
|
'element_id': map_element_id}
|
|
|
|
stream = stream | Transformer('body//div[@id="%s"]' % map_element_id)\
|
|
|
|
.append(HTML(body_html % data))
|
2011-09-30 12:33:36 +02:00
|
|
|
stream = stream | Transformer('head')\
|
|
|
|
.append(HTML(html.PACKAGE_MAP_EXTRA_HEADER % data))
|
|
|
|
stream = stream | Transformer('body')\
|
|
|
|
.append(HTML(html.PACKAGE_MAP_EXTRA_FOOTER % data))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return stream
|
|
|
|
|
|
|
|
def update_config(self, config):
|
|
|
|
here = os.path.dirname(__file__)
|
|
|
|
|
|
|
|
template_dir = os.path.join(here, 'templates')
|
|
|
|
public_dir = os.path.join(here, 'public')
|
|
|
|
|
|
|
|
if config.get('extra_template_paths'):
|
|
|
|
config['extra_template_paths'] += ','+template_dir
|
|
|
|
else:
|
|
|
|
config['extra_template_paths'] = template_dir
|
|
|
|
if config.get('extra_public_paths'):
|
|
|
|
config['extra_public_paths'] += ','+public_dir
|
|
|
|
else:
|
|
|
|
config['extra_public_paths'] = public_dir
|
|
|
|
|