[merge] from feature-1272-store-extents-from-form
This commit is contained in:
commit
3a8f6c9cf6
102
README.rst
102
README.rst
|
@ -3,8 +3,11 @@ ckanext-spatial - Geo related plugins for CKAN
|
||||||
==============================================
|
==============================================
|
||||||
|
|
||||||
This extension contains plugins that add geospatial capabilities to CKAN.
|
This extension contains plugins that add geospatial capabilities to CKAN.
|
||||||
Currently, there are a WMS previewer (`wms_preview`) and a spatial query
|
The following plugins are currently available:
|
||||||
API call (`spatial_query`) available.
|
|
||||||
|
* Automatic geo-indexing and spatial API call (`spatial_query`).
|
||||||
|
* Map widget showing a package extent (`dataset_extent_map`).
|
||||||
|
* A Web Map Service (WMS) previewer (`wms_preview`).
|
||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
============
|
============
|
||||||
|
@ -12,7 +15,13 @@ Dependencies
|
||||||
You will need CKAN installed. The present module should be installed at least
|
You will need CKAN installed. The present module should be installed at least
|
||||||
with `setup.py develop` if not installed in the normal way with
|
with `setup.py develop` if not installed in the normal way with
|
||||||
`setup.py install` or using pip or easy_install.
|
`setup.py install` or using pip or easy_install.
|
||||||
|
|
||||||
|
The extension uses the GeoAlchemy_ and Shapely_ libraries. You can install them
|
||||||
|
via `pip install -r pip-requirements.txt` from the extension directory.
|
||||||
|
|
||||||
|
.. _GeoAlchemy: http://www.geoalchemy.org
|
||||||
|
.. _Shapely: https://github.com/sgillies/shapely
|
||||||
|
|
||||||
If you want to use the spatial search API, you will need PostGIS installed
|
If you want to use the spatial search API, you will need PostGIS installed
|
||||||
and enable the spatial features of your PostgreSQL database. See the
|
and enable the spatial features of your PostgreSQL database. See the
|
||||||
"Setting up PostGIS" section for details.
|
"Setting up PostGIS" section for details.
|
||||||
|
@ -25,14 +34,15 @@ DB tables running the following command (with your python env activated)::
|
||||||
|
|
||||||
paster spatial initdb [srid] --config=../ckan/development.ini
|
paster spatial initdb [srid] --config=../ckan/development.ini
|
||||||
|
|
||||||
You can define the SRID of the geometry column. Default is 4258.
|
You can define the SRID of the geometry column. Default is 4326. If you are not
|
||||||
|
familiar with projections, we recommend to use the default value.
|
||||||
|
|
||||||
Problems you may find::
|
Problems you may find::
|
||||||
|
|
||||||
LINE 1: SELECT AddGeometryColumn('package_extent','the_geom', E'4258...
|
LINE 1: SELECT AddGeometryColumn('package_extent','the_geom', E'4326...
|
||||||
^
|
^
|
||||||
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
||||||
"SELECT AddGeometryColumn('package_extent','the_geom', %s, 'POLYGON', 2)" ('4258',)
|
"SELECT AddGeometryColumn('package_extent','the_geom', %s, 'GEOMETRY', 2)" ('4326',)
|
||||||
|
|
||||||
PostGIS was not installed correctly. Please check the "Setting up PostGIS" section.
|
PostGIS was not installed correctly. Please check the "Setting up PostGIS" section.
|
||||||
|
|
||||||
|
@ -43,16 +53,17 @@ The user accessing the ckan database needs to be owner (or have
|
||||||
permissions) of the geometry_columns and spatial_ref_sys tables
|
permissions) of the geometry_columns and spatial_ref_sys tables
|
||||||
|
|
||||||
|
|
||||||
Plugins are configured as follows in the CKAN ini file::
|
Plugins are configured as follows in the CKAN ini file (Add only the ones you
|
||||||
|
are interested in)::
|
||||||
|
|
||||||
ckan.plugins = wms_preview spatial_query
|
ckan.plugins = wms_preview spatial_query dataset_extent_map
|
||||||
|
|
||||||
If you are using the spatial search feature, you can define the projection
|
If you are using the spatial search feature, you can define the projection
|
||||||
in which extents are stored in the database with the following option. Use
|
in which extents are stored in the database with the following option. Use
|
||||||
the EPSG code as an integer (e.g 4326, 4258, 27700, etc). It defaults to
|
the EPSG code as an integer (e.g 4326, 4258, 27700, etc). It defaults to
|
||||||
4258::
|
4326::
|
||||||
|
|
||||||
ckan.spatial.srid = 4258
|
ckan.spatial.srid = 4326
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,11 +76,11 @@ The following operations can be run from the command line using the
|
||||||
initdb [srid]
|
initdb [srid]
|
||||||
- Creates the necessary tables. You must have PostGIS installed
|
- Creates the necessary tables. You must have PostGIS installed
|
||||||
and configured in the database.
|
and configured in the database.
|
||||||
You can privide the SRID of the geometry column. Default is 4258.
|
You can privide the SRID of the geometry column. Default is 4326.
|
||||||
|
|
||||||
extents
|
extents
|
||||||
- creates or updates the extent geometry column for packages with
|
- creates or updates the extent geometry column for packages with
|
||||||
a bounding box defined in extras
|
an extent defined in the 'spatial' extra.
|
||||||
|
|
||||||
The commands should be run from the ckanext-spatial directory and expect
|
The commands should be run from the ckanext-spatial directory and expect
|
||||||
a development.ini file to be present. Most of the time you will specify
|
a development.ini file to be present. Most of the time you will specify
|
||||||
|
@ -78,8 +89,11 @@ the config explicitly though::
|
||||||
paster extents update --config=../ckan/development.ini
|
paster extents update --config=../ckan/development.ini
|
||||||
|
|
||||||
|
|
||||||
API
|
Spatial Query
|
||||||
===
|
=============
|
||||||
|
|
||||||
|
To enable the spatial query you need to add the `spatial_query` plugin to your
|
||||||
|
ini file (See 'Configuration').
|
||||||
|
|
||||||
The extension adds the following call to the CKAN search API, which returns
|
The extension adds the following call to the CKAN search API, which returns
|
||||||
packages with an extent that intersects with the bounding box provided::
|
packages with an extent that intersects with the bounding box provided::
|
||||||
|
@ -90,9 +104,52 @@ If the bounding box coordinates are not in the same projection as the one
|
||||||
defined in the database, a CRS must be provided, in one of the following
|
defined in the database, a CRS must be provided, in one of the following
|
||||||
forms:
|
forms:
|
||||||
|
|
||||||
- urn:ogc:def:crs:EPSG::4258
|
- urn:ogc:def:crs:EPSG::4326
|
||||||
- EPSG:4258
|
- EPSG:4326
|
||||||
- 4258
|
- 4326
|
||||||
|
|
||||||
|
Geo-Indexing your packages
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
In order to make a package queryable by location, an special extra must
|
||||||
|
be defined, with its key named 'spatial'. The value must be a valid GeoJSON_
|
||||||
|
geometry, for example::
|
||||||
|
|
||||||
|
{"type":"Polygon","coordinates":[[[2.05827, 49.8625],[2.05827, 55.7447], [-6.41736, 55.7447], [-6.41736, 49.8625], [2.05827, 49.8625]]]}
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
{ "type": "Point", "coordinates": [-3.145,53.078] }
|
||||||
|
|
||||||
|
.. _GeoJSON: http://geojson.org
|
||||||
|
|
||||||
|
Every time a package is created, updated or deleted, the extension will synchronize
|
||||||
|
the information stored in the extra with the geometry table.
|
||||||
|
|
||||||
|
|
||||||
|
Dataset Map Widget
|
||||||
|
==================
|
||||||
|
|
||||||
|
To enable the dataset map you need to add the `dataset_map` plugin to your
|
||||||
|
ini file (See 'Configuration'). You need to load the `spatial_query` plugin also.
|
||||||
|
|
||||||
|
When the plugin is enabled, if datasets contain a 'spatial' extra like the one
|
||||||
|
described in the previous section, a map will be shown on the dataset details page.
|
||||||
|
|
||||||
|
|
||||||
|
WMS Previewer
|
||||||
|
=============
|
||||||
|
|
||||||
|
To enable the WMS previewer you need to add the `wms_preview` plugin to your
|
||||||
|
ini file (See 'Configuration').
|
||||||
|
|
||||||
|
Please note that this is an experimental plugin and may be unstable.
|
||||||
|
|
||||||
|
When the plugin is enabled, if datasets contain a resource that has 'WMS' format,
|
||||||
|
a 'View available WMS layers' link will be displayed on the dataset details page.
|
||||||
|
It forwards to a simple map viewer that will attempt to load the remote service
|
||||||
|
layers, based on the GetCapabilities response.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Setting up PostGIS
|
Setting up PostGIS
|
||||||
|
@ -158,7 +215,7 @@ Setting up a spatial table
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
**Note:** If you run the ``initdb`` command, the table was already created for
|
**Note:** If you run the ``initdb`` command, the table was already created for
|
||||||
you. This sections just describes what's going on for those who want to know
|
you. This section just describes what's going on for those who want to know
|
||||||
more.
|
more.
|
||||||
|
|
||||||
To be able to store geometries and perform spatial operations, PostGIS
|
To be able to store geometries and perform spatial operations, PostGIS
|
||||||
|
@ -171,11 +228,12 @@ added via the ``AddGeometryColumn`` function::
|
||||||
|
|
||||||
ALTER TABLE package_extent OWNER TO [your_user];
|
ALTER TABLE package_extent OWNER TO [your_user];
|
||||||
|
|
||||||
SELECT AddGeometryColumn('package_extent','the_geom', 4258, 'POLYGON', 2);
|
SELECT AddGeometryColumn('package_extent','the_geom', 4326, 'POLYGON', 2);
|
||||||
|
|
||||||
This will add a geometry column in the ``package_extent`` table called
|
This will add a geometry column in the ``package_extent`` table called
|
||||||
``the_geom``, with the spatial reference system EPSG:4258. The stored
|
``the_geom``, with the spatial reference system EPSG:4326. The stored
|
||||||
geometries will be polygons, with 2 dimensions.
|
geometries will be polygons, with 2 dimensions (The actual table on CKAN
|
||||||
|
uses the GEOMETRY type to support multiple geometry types).
|
||||||
|
|
||||||
Have a look a the table definition, and see how PostGIS has created
|
Have a look a the table definition, and see how PostGIS has created
|
||||||
three constraints to ensure that the geometries follow the parameters
|
three constraints to ensure that the geometries follow the parameters
|
||||||
|
@ -193,4 +251,4 @@ defined in the geometry column creation::
|
||||||
Check constraints:
|
Check constraints:
|
||||||
"enforce_dims_the_geom" CHECK (st_ndims(the_geom) = 2)
|
"enforce_dims_the_geom" CHECK (st_ndims(the_geom) = 2)
|
||||||
"enforce_geotype_the_geom" CHECK (geometrytype(the_geom) = 'POLYGON'::text OR the_geom IS NULL)
|
"enforce_geotype_the_geom" CHECK (geometrytype(the_geom) = 'POLYGON'::text OR the_geom IS NULL)
|
||||||
"enforce_srid_the_geom" CHECK (st_srid(the_geom) = 4258)
|
"enforce_srid_the_geom" CHECK (st_srid(the_geom) = 4326)
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
import logging
|
||||||
|
|
||||||
from ckan.lib.cli import CkanCommand
|
from ckan.lib.cli import CkanCommand
|
||||||
from ckanext.spatial.lib import save_extent
|
from ckan.lib.helpers import json
|
||||||
|
from ckanext.spatial.lib import save_package_extent
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Spatial(CkanCommand):
|
class Spatial(CkanCommand):
|
||||||
'''Performs spatially related operations.
|
'''Performs spatially related operations.
|
||||||
|
@ -12,11 +15,11 @@ class Spatial(CkanCommand):
|
||||||
spatial initdb [srid]
|
spatial initdb [srid]
|
||||||
Creates the necessary tables. You must have PostGIS installed
|
Creates the necessary tables. You must have PostGIS installed
|
||||||
and configured in the database.
|
and configured in the database.
|
||||||
You can provide the SRID of the geometry column. Default is 4258.
|
You can provide the SRID of the geometry column. Default is 4326.
|
||||||
|
|
||||||
spatial extents
|
spatial extents
|
||||||
Creates or updates the extent geometry column for packages with
|
Creates or updates the extent geometry column for packages with
|
||||||
a bounding box defined in extras
|
an extent defined in the 'spatial' extra.
|
||||||
|
|
||||||
The commands should be run from the ckanext-spatial directory and expect
|
The commands should be run from the ckanext-spatial directory and expect
|
||||||
a development.ini file to be present. Most of the time you will
|
a development.ini file to be present. Most of the time you will
|
||||||
|
@ -63,19 +66,32 @@ class Spatial(CkanCommand):
|
||||||
conn = Session.connection()
|
conn = Session.connection()
|
||||||
packages = [extra.package \
|
packages = [extra.package \
|
||||||
for extra in \
|
for extra in \
|
||||||
Session.query(PackageExtra).filter(PackageExtra.key == 'bbox-east-long').all()]
|
Session.query(PackageExtra).filter(PackageExtra.key == 'spatial').all()]
|
||||||
|
|
||||||
error = False
|
errors = []
|
||||||
|
count = 0
|
||||||
for package in packages:
|
for package in packages:
|
||||||
try:
|
try:
|
||||||
save_extent(package)
|
value = package.extras['spatial']
|
||||||
except:
|
log.debug('Received: %r' % value)
|
||||||
errors = True
|
geometry = json.loads(value)
|
||||||
|
|
||||||
if error:
|
count += 1
|
||||||
msg = "There was an error saving the package extent. Have you set up the package_extent table in the DB?"
|
except ValueError,e:
|
||||||
else:
|
errors.append(u'Package %s - Error decoding JSON object: %s' % (package.id,str(e)))
|
||||||
msg = "Done. Extents generated for %i packages" % len(packages)
|
except TypeError,e:
|
||||||
|
errors.append(u'Package %s - Error decoding JSON object: %s' % (package.id,str(e)))
|
||||||
|
|
||||||
|
save_package_extent(package.id,geometry)
|
||||||
|
|
||||||
|
|
||||||
|
Session.commit()
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
msg = 'Errors were found:\n%s' % '\n'.join(errors)
|
||||||
|
print msg
|
||||||
|
|
||||||
|
msg = "Done. Extents generated for %i out of %i packages" % (count,len(packages))
|
||||||
|
|
||||||
print msg
|
print msg
|
||||||
|
|
||||||
|
|
|
@ -1,64 +1,52 @@
|
||||||
from ckan.lib.helpers import json
|
from string import Template
|
||||||
import ckan.lib.helpers as h
|
|
||||||
from ckan.lib.base import c, g, request, \
|
|
||||||
response, session, render, config, abort, redirect
|
|
||||||
|
|
||||||
|
from ckan.lib.base import request, config, abort
|
||||||
from ckan.controllers.api import ApiController as BaseApiController
|
from ckan.controllers.api import ApiController as BaseApiController
|
||||||
|
|
||||||
from ckan.model import Session
|
from ckan.model import Session
|
||||||
|
|
||||||
from ckanext.spatial.lib import get_srid
|
from ckanext.spatial.lib import get_srid
|
||||||
|
from ckanext.spatial.model import PackageExtent
|
||||||
|
|
||||||
|
from geoalchemy import WKTSpatialElement, functions
|
||||||
|
|
||||||
class ApiController(BaseApiController):
|
class ApiController(BaseApiController):
|
||||||
|
|
||||||
db_srid = int(config.get('ckan.spatial.srid', '4258'))
|
db_srid = int(config.get('ckan.spatial.srid', '4326'))
|
||||||
|
|
||||||
|
bbox_template = Template('POLYGON (($minx $miny, $minx $maxy, $maxx $maxy, $maxx $miny, $minx $miny))')
|
||||||
|
|
||||||
def spatial_query(self):
|
def spatial_query(self):
|
||||||
|
|
||||||
|
error_400_msg = 'Please provide a suitable bbox parameter [minx,miny,maxx,maxy]'
|
||||||
|
|
||||||
if not 'bbox' in request.params:
|
if not 'bbox' in request.params:
|
||||||
abort(400)
|
abort(400,error_400_msg)
|
||||||
|
|
||||||
bbox = request.params['bbox'].split(',')
|
bbox = request.params['bbox'].split(',')
|
||||||
if len(bbox) is not 4:
|
if len(bbox) is not 4:
|
||||||
abort(400)
|
abort(400,error_400_msg)
|
||||||
|
|
||||||
minx = float(bbox[0])
|
try:
|
||||||
miny = float(bbox[1])
|
minx = float(bbox[0])
|
||||||
maxx = float(bbox[2])
|
miny = float(bbox[1])
|
||||||
maxy = float(bbox[3])
|
maxx = float(bbox[2])
|
||||||
|
maxy = float(bbox[3])
|
||||||
|
except ValueError,e:
|
||||||
|
abort(400,error_400_msg)
|
||||||
|
|
||||||
|
|
||||||
|
wkt = self.bbox_template.substitute(minx=minx,miny=miny,maxx=maxx,maxy=maxy)
|
||||||
|
|
||||||
params = {'minx':minx,'miny':miny,'maxx':maxx,'maxy':maxy,'db_srid':self.db_srid}
|
|
||||||
|
|
||||||
srid = get_srid(request.params.get('crs')) if 'crs' in request.params else None
|
srid = get_srid(request.params.get('crs')) if 'crs' in request.params else None
|
||||||
if srid and srid != self.db_srid:
|
if srid and srid != self.db_srid:
|
||||||
# The input bounding box is defined in another projection, we need
|
# Input geometry needs to be transformed to the one used on the database
|
||||||
# to transform it
|
input_geometry = functions.transform(WKTSpatialElement(wkt,srid),self.db_srid)
|
||||||
statement = """SELECT package_id FROM package_extent WHERE
|
|
||||||
ST_Intersects(
|
|
||||||
ST_Transform(
|
|
||||||
ST_GeomFromText('POLYGON ((%(minx)s %(miny)s,
|
|
||||||
%(maxx)s %(miny)s,
|
|
||||||
%(maxx)s %(maxy)s,
|
|
||||||
%(minx)s %(maxy)s,
|
|
||||||
%(minx)s %(miny)s))',%(srid)s),
|
|
||||||
%(db_srid)s)
|
|
||||||
,the_geom)"""
|
|
||||||
params.update({'srid': srid})
|
|
||||||
else:
|
else:
|
||||||
statement = """SELECT package_id FROM package_extent WHERE
|
input_geometry = WKTSpatialElement(wkt,self.db_srid)
|
||||||
ST_Intersects(
|
|
||||||
ST_GeomFromText('POLYGON ((%(minx)s %(miny)s,
|
extents = Session.query(PackageExtent).filter(PackageExtent.the_geom.intersects(input_geometry))
|
||||||
%(maxx)s %(miny)s,
|
ids = [extent.package_id for extent in extents]
|
||||||
%(maxx)s %(maxy)s,
|
|
||||||
%(minx)s %(maxy)s,
|
|
||||||
%(minx)s %(miny)s))',%(db_srid)s),
|
|
||||||
the_geom)"""
|
|
||||||
conn = Session.connection()
|
|
||||||
rows = conn.execute(statement,params)
|
|
||||||
ids = [row['package_id'] for row in rows]
|
|
||||||
|
|
||||||
output = dict(count=len(ids),results=ids)
|
output = dict(count=len(ids),results=ids)
|
||||||
|
|
||||||
return self._finish_ok(output)
|
return self._finish_ok(output)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,6 @@ from ckan.model import Package
|
||||||
|
|
||||||
class ViewController(BaseController):
|
class ViewController(BaseController):
|
||||||
|
|
||||||
def __before__(self, action, **env):
|
|
||||||
super(ViewController, self).__before__(action, **env)
|
|
||||||
# All calls to this controller must be with a sysadmin key
|
|
||||||
if not self.authorizer.is_sysadmin(c.user):
|
|
||||||
response_msg = _('Not authorized to see this page')
|
|
||||||
status = 401
|
|
||||||
abort(status, response_msg)
|
|
||||||
|
|
||||||
def wms_preview(self,id):
|
def wms_preview(self,id):
|
||||||
#check if package exists
|
#check if package exists
|
||||||
c.pkg = Package.get(id)
|
c.pkg = Package.get(id)
|
||||||
|
@ -26,10 +18,10 @@ class ViewController(BaseController):
|
||||||
|
|
||||||
for res in c.pkg.resources:
|
for res in c.pkg.resources:
|
||||||
if res.format == "WMS":
|
if res.format == "WMS":
|
||||||
c.wms = res
|
c.wms_url = res.url if not '?' in res.url else res.url.split('?')[0]
|
||||||
break
|
break
|
||||||
if not c.wms:
|
if not c.wms_url:
|
||||||
abort(400, 'This package does not have a WMS')
|
abort(400, 'This package does not have a WMS resource')
|
||||||
|
|
||||||
return render('ckanext/spatial/wms_preview.html')
|
return render('ckanext/spatial/wms_preview.html')
|
||||||
|
|
||||||
|
|
|
@ -3,3 +3,30 @@ MAP_VIEW="""
|
||||||
<a href="/package/%(name)s/map">View available WMS layers »</a>
|
<a href="/package/%(name)s/map">View available WMS layers »</a>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
PACKAGE_MAP="""
|
||||||
|
<hr class="cleared" />
|
||||||
|
<div class="dataset-map subsection">
|
||||||
|
<h3>%(title)s</h3>
|
||||||
|
<div id="dataset-map-container"></div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
PACKAGE_MAP_EXTRA_HEADER="""
|
||||||
|
<link type="text/css" rel="stylesheet" media="all" href="/ckanext/spatial/css/dataset_map.css" />
|
||||||
|
"""
|
||||||
|
|
||||||
|
PACKAGE_MAP_EXTRA_FOOTER="""
|
||||||
|
<script type="text/javascript" src="/ckanext/spatial/js/openlayers/OpenLayers_dataset_map.js"></script>
|
||||||
|
<script type="text/javascript" src="/ckanext/spatial/js/dataset_map.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
//<![CDATA[
|
||||||
|
$(document).ready(function(){
|
||||||
|
CKAN.DatasetMap.extent = '%(extent)s';
|
||||||
|
CKAN.DatasetMap.setup();
|
||||||
|
})
|
||||||
|
//]]>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
from ckan.model import Session, repo
|
import logging
|
||||||
from ckan.model import Package
|
|
||||||
|
from ckan.model import Session
|
||||||
from ckan.lib.base import config
|
from ckan.lib.base import config
|
||||||
|
|
||||||
|
from ckanext.spatial.model import PackageExtent
|
||||||
|
from shapely.geometry import asShape
|
||||||
|
|
||||||
log = __import__("logging").getLogger(__name__)
|
from geoalchemy import WKTSpatialElement
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
def get_srid(crs):
|
def get_srid(crs):
|
||||||
"""Returns the SRID for the provided CRS definition
|
"""Returns the SRID for the provided CRS definition
|
||||||
The CRS can be defined in the following formats
|
The CRS can be defined in the following formats
|
||||||
- urn:ogc:def:crs:EPSG::4258
|
- urn:ogc:def:crs:EPSG::4326
|
||||||
- EPSG:4258
|
- EPSG:4326
|
||||||
- 4258
|
- 4326
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if ':' in crs:
|
if ':' in crs:
|
||||||
|
@ -20,95 +24,51 @@ def get_srid(crs):
|
||||||
else:
|
else:
|
||||||
srid = crs
|
srid = crs
|
||||||
|
|
||||||
return srid
|
return int(srid)
|
||||||
|
|
||||||
def save_extent(package,extent=False):
|
def save_package_extent(package_id, geometry = None, srid = None):
|
||||||
'''Updates the package extent in the package_extent geometry column
|
'''Adds, updates or deletes the package extent geometry.
|
||||||
If no extent provided (as a dict with minx,miny,maxx,maxy and srid keys),
|
|
||||||
the values stored in the package extras are used'''
|
|
||||||
|
|
||||||
db_srid = int(config.get('ckan.spatial.srid', '4258'))
|
package_id: Package unique identifier
|
||||||
conn = Session.connection()
|
geometry: a Python object implementing the Python Geo Interface
|
||||||
|
(i.e a loaded GeoJSON object)
|
||||||
|
srid: The spatial reference in which the geometry is provided.
|
||||||
|
If None, it defaults to the DB srid.
|
||||||
|
|
||||||
srid = None
|
Will throw ValueError if the geometry object does not provide a geo interface.
|
||||||
if extent:
|
|
||||||
minx = extent['minx']
|
|
||||||
miny = extent['miny']
|
|
||||||
maxx = extent['maxx']
|
|
||||||
maxy = extent['maxy']
|
|
||||||
if 'srid' in extent:
|
|
||||||
srid = extent['srid']
|
|
||||||
else:
|
|
||||||
minx = float(package.extras.get('bbox-east-long'))
|
|
||||||
miny = float(package.extras.get('bbox-south-lat'))
|
|
||||||
maxx = float(package.extras.get('bbox-west-long'))
|
|
||||||
maxy = float(package.extras.get('bbox-north-lat'))
|
|
||||||
|
|
||||||
if srid:
|
'''
|
||||||
srid = str(srid)
|
db_srid = int(config.get('ckan.spatial.srid', '4326'))
|
||||||
try:
|
|
||||||
|
|
||||||
# Check if extent already exists
|
|
||||||
rows = conn.execute('SELECT package_id FROM package_extent WHERE package_id = %s',package.id).fetchall()
|
|
||||||
update =(len(rows) > 0)
|
|
||||||
|
|
||||||
params = {'id':package.id, 'minx':minx,'miny':miny,'maxx':maxx,'maxy':maxy, 'db_srid': db_srid}
|
existing_package_extent = Session.query(PackageExtent).filter(PackageExtent.package_id==package_id).first()
|
||||||
|
|
||||||
if update:
|
if geometry:
|
||||||
# Update
|
shape = asShape(geometry)
|
||||||
if srid and srid != db_srid:
|
|
||||||
# We need to reproject the input geometry
|
if not srid:
|
||||||
statement = """UPDATE package_extent SET
|
srid = db_srid
|
||||||
the_geom = ST_Transform(
|
|
||||||
ST_GeomFromText('POLYGON ((%(minx)s %(miny)s,
|
package_extent = PackageExtent(package_id=package_id,the_geom=WKTSpatialElement(shape.wkt, srid))
|
||||||
%(maxx)s %(miny)s,
|
|
||||||
%(maxx)s %(maxy)s,
|
# Check if extent exists
|
||||||
%(minx)s %(maxy)s,
|
if existing_package_extent:
|
||||||
%(minx)s %(miny)s))',%(srid)s),
|
|
||||||
%(db_srid)s)
|
# If extent exists but we received no geometry, we'll delete the existing one
|
||||||
WHERE package_id = %(id)s
|
if not geometry:
|
||||||
"""
|
existing_package_extent.delete()
|
||||||
params.update({'srid': srid})
|
log.debug('Deleted extent for package %s' % package_id)
|
||||||
else:
|
|
||||||
statement = """UPDATE package_extent SET
|
|
||||||
the_geom = ST_GeomFromText('POLYGON ((%(minx)s %(miny)s,
|
|
||||||
%(maxx)s %(miny)s,
|
|
||||||
%(maxx)s %(maxy)s,
|
|
||||||
%(minx)s %(maxy)s,
|
|
||||||
%(minx)s %(miny)s))',%(db_srid)s)
|
|
||||||
WHERE package_id = %(id)s
|
|
||||||
"""
|
|
||||||
msg = 'Updated extent for package %s'
|
|
||||||
else:
|
else:
|
||||||
# Insert
|
# Check if extent changed
|
||||||
if srid and srid != db_srid:
|
if Session.scalar(package_extent.the_geom.wkt) <> Session.scalar(existing_package_extent.the_geom.wkt):
|
||||||
# We need to reproject the input geometry
|
# Update extent
|
||||||
statement = """INSERT INTO package_extent (package_id,the_geom) VALUES (
|
existing_package_extent.the_geom = package_extent.the_geom
|
||||||
%(id)s,
|
existing_package_extent.save()
|
||||||
ST_Transform(
|
log.debug('Updated extent for package %s' % package_id)
|
||||||
ST_GeomFromText('POLYGON ((%(minx)s %(miny)s,
|
|
||||||
%(maxx)s %(miny)s,
|
|
||||||
%(maxx)s %(maxy)s,
|
|
||||||
%(minx)s %(maxy)s,
|
|
||||||
%(minx)s %(miny)s))',%(srid)s),
|
|
||||||
%(db_srid))
|
|
||||||
)"""
|
|
||||||
params.update({'srid': srid})
|
|
||||||
else:
|
else:
|
||||||
statement = """INSERT INTO package_extent (package_id,the_geom) VALUES (
|
log.debug('Extent for package %s unchanged' % package_id)
|
||||||
%(id)s,
|
elif geometry:
|
||||||
ST_GeomFromText('POLYGON ((%(minx)s %(miny)s,
|
# Insert extent
|
||||||
%(maxx)s %(miny)s,
|
Session.add(package_extent)
|
||||||
%(maxx)s %(maxy)s,
|
log.debug('Created new extent for package %s' % package_id)
|
||||||
%(minx)s %(maxy)s,
|
|
||||||
%(minx)s %(miny)s))',%(db_srid)s))"""
|
|
||||||
msg = 'Created new extent for package %s'
|
|
||||||
|
|
||||||
conn.execute(statement,params)
|
|
||||||
|
|
||||||
Session.commit()
|
|
||||||
log.info(msg, package.id)
|
|
||||||
return package
|
|
||||||
except Exception,e:
|
|
||||||
log.error('An error occurred when saving the extent for package %s: %r' % (package.id,e))
|
|
||||||
raise Exception
|
|
||||||
|
|
|
@ -1,20 +1,41 @@
|
||||||
|
from ckan.lib.base import config
|
||||||
from ckan.model import Session
|
from ckan.model import Session
|
||||||
|
from ckan.model.meta import *
|
||||||
|
from ckan.model.domain_object import DomainObject
|
||||||
|
from geoalchemy import *
|
||||||
|
from geoalchemy.postgis import PGComparator
|
||||||
|
|
||||||
DEFAULT_SRID = 4258
|
db_srid = int(config.get('ckan.spatial.srid', '4326'))
|
||||||
|
package_extent_table = Table('package_extent', metadata,
|
||||||
|
Column('package_id', types.UnicodeText, primary_key=True),
|
||||||
|
GeometryExtensionColumn('the_geom', Geometry(2,srid=db_srid)))
|
||||||
|
|
||||||
def setup(srid=4258):
|
class PackageExtent(DomainObject):
|
||||||
|
def __init__(self, package_id=None, the_geom=None):
|
||||||
|
self.package_id = package_id
|
||||||
|
self.the_geom = the_geom
|
||||||
|
|
||||||
|
mapper(PackageExtent, package_extent_table, properties={
|
||||||
|
'the_geom': GeometryColumn(package_extent_table.c.the_geom,
|
||||||
|
comparator=PGComparator)})
|
||||||
|
|
||||||
|
# enable the DDL extension
|
||||||
|
GeometryDDL(package_extent_table)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_SRID = 4326
|
||||||
|
|
||||||
|
def setup(srid=None):
|
||||||
|
|
||||||
if not srid:
|
if not srid:
|
||||||
srid = DEFAULT_SRID
|
srid = DEFAULT_SRID
|
||||||
|
|
||||||
srid = str(srid)
|
srid = str(srid)
|
||||||
|
|
||||||
connection = Session.connection()
|
connection = Session.connection()
|
||||||
connection.execute("""CREATE TABLE package_extent(
|
connection.execute('CREATE TABLE package_extent(package_id text PRIMARY KEY)')
|
||||||
package_id text PRIMARY KEY
|
|
||||||
);""")
|
|
||||||
|
|
||||||
#connection.execute('ALTER TABLE package_extent OWNER TO ?',user_name);
|
connection.execute('SELECT AddGeometryColumn(\'package_extent\',\'the_geom\', %s, \'GEOMETRY\', 2)',srid)
|
||||||
|
|
||||||
connection.execute('SELECT AddGeometryColumn(\'package_extent\',\'the_geom\', %s, \'POLYGON\', 2)',srid)
|
|
||||||
|
|
||||||
Session.commit()
|
Session.commit()
|
||||||
|
|
|
@ -1,39 +1,130 @@
|
||||||
import os
|
import os
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
from pylons.i18n import _
|
||||||
from genshi.input import HTML
|
from genshi.input import HTML
|
||||||
from genshi.filters import Transformer
|
from genshi.filters import Transformer
|
||||||
|
|
||||||
import ckan.lib.helpers as h
|
import ckan.lib.helpers as h
|
||||||
|
|
||||||
|
from ckan.lib.helpers import json
|
||||||
|
|
||||||
from ckan.plugins import implements, SingletonPlugin
|
from ckan.plugins import implements, SingletonPlugin
|
||||||
from ckan.plugins import IRoutes, IConfigurer
|
from ckan.plugins import IRoutes, IConfigurer
|
||||||
from ckan.plugins import IConfigurable, IGenshiStreamFilter
|
from ckan.plugins import IConfigurable, IGenshiStreamFilter
|
||||||
|
from ckan.plugins import IPackageController
|
||||||
|
|
||||||
|
from ckan.logic import ValidationError
|
||||||
|
from ckan.logic.action.update import package_error_summary
|
||||||
|
|
||||||
import html
|
import html
|
||||||
|
|
||||||
|
from ckanext.spatial.lib import save_package_extent
|
||||||
|
|
||||||
log = getLogger(__name__)
|
log = getLogger(__name__)
|
||||||
|
|
||||||
class SpatialQuery(SingletonPlugin):
|
class SpatialQuery(SingletonPlugin):
|
||||||
|
|
||||||
implements(IRoutes, inherit=True)
|
implements(IRoutes, inherit=True)
|
||||||
|
implements(IPackageController, inherit=True)
|
||||||
|
|
||||||
def before_map(self, map):
|
def before_map(self, map):
|
||||||
|
|
||||||
map.connect('api_spatial_query', '/api/2/search/package/geo',
|
map.connect('api_spatial_query', '/api/2/search/package/geo',
|
||||||
controller='ckanext.spatial.controllers.api:ApiController',
|
controller='ckanext.spatial.controllers.api:ApiController',
|
||||||
action='spatial_query')
|
action='spatial_query')
|
||||||
|
|
||||||
return map
|
return map
|
||||||
|
|
||||||
|
def create(self, package):
|
||||||
|
self.check_spatial_extra(package)
|
||||||
|
|
||||||
|
def edit(self, package):
|
||||||
|
self.check_spatial_extra(package)
|
||||||
|
|
||||||
|
def check_spatial_extra(self,package):
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class DatasetExtentMap(SingletonPlugin):
|
||||||
|
|
||||||
|
implements(IGenshiStreamFilter)
|
||||||
|
implements(IConfigurer, inherit=True)
|
||||||
|
|
||||||
|
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') == 'read' and c.pkg.id:
|
||||||
|
|
||||||
|
extent = c.pkg.extras.get('spatial',None)
|
||||||
|
if extent:
|
||||||
|
data = {'extent': extent,
|
||||||
|
'title': _('Geographic extent')}
|
||||||
|
stream = stream | Transformer('body//div[@class="dataset"]')\
|
||||||
|
.append(HTML(html.PACKAGE_MAP % data))
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WMSPreview(SingletonPlugin):
|
class WMSPreview(SingletonPlugin):
|
||||||
|
|
||||||
implements(IGenshiStreamFilter)
|
implements(IGenshiStreamFilter)
|
||||||
implements(IRoutes, inherit=True)
|
implements(IRoutes, inherit=True)
|
||||||
implements(IConfigurer, inherit=True)
|
implements(IConfigurer, inherit=True)
|
||||||
|
|
||||||
def filter(self, stream):
|
def filter(self, stream):
|
||||||
from pylons import request, tmpl_context as c
|
from pylons import request, tmpl_context as c
|
||||||
routes = request.environ.get('pylons.routes_dict')
|
routes = request.environ.get('pylons.routes_dict')
|
||||||
|
@ -41,13 +132,14 @@ class WMSPreview(SingletonPlugin):
|
||||||
if routes.get('controller') == 'package' and \
|
if routes.get('controller') == 'package' and \
|
||||||
routes.get('action') == 'read' and c.pkg.id:
|
routes.get('action') == 'read' and c.pkg.id:
|
||||||
|
|
||||||
is_inspire = (c.pkg.extras.get('INSPIRE') == 'True')
|
for res in c.pkg.resources:
|
||||||
# TODO: What about WFS, WCS...
|
if res.format == "WMS":
|
||||||
is_wms = (c.pkg.extras.get('resource-type') == 'service')
|
data = {'name': c.pkg.name}
|
||||||
if is_inspire and is_wms:
|
stream = stream | Transformer('body//div[@class="resources subsection"]')\
|
||||||
data = {'name': c.pkg.name}
|
.append(HTML(html.MAP_VIEW % data))
|
||||||
stream = stream | Transformer('body//div[@class="resources subsection"]')\
|
|
||||||
.append(HTML(html.MAP_VIEW % data))
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return stream
|
return stream
|
||||||
|
@ -65,7 +157,7 @@ class WMSPreview(SingletonPlugin):
|
||||||
map.connect('api_spatial_query', '/api/2/search/package/geo',
|
map.connect('api_spatial_query', '/api/2/search/package/geo',
|
||||||
controller='ckanext.spatial.controllers.api:ApiController',
|
controller='ckanext.spatial.controllers.api:ApiController',
|
||||||
action='spatial_query')
|
action='spatial_query')
|
||||||
|
|
||||||
return map
|
return map
|
||||||
|
|
||||||
def update_config(self, config):
|
def update_config(self, config):
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
.dataset-map {
|
||||||
|
padding-bottom: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.olControlAttribution {
|
||||||
|
left: 5px;
|
||||||
|
right: inherit;
|
||||||
|
bottom: 3px !important;
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
var CKAN = CKAN || {};
|
||||||
|
|
||||||
|
CKAN.DatasetMap = function($){
|
||||||
|
|
||||||
|
// Private
|
||||||
|
|
||||||
|
var getGeomType = function(feature){
|
||||||
|
return feature.geometry.CLASS_NAME.split(".").pop().toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
var getStyle = function(geom_type){
|
||||||
|
var styles = CKAN.DatasetMap.styles;
|
||||||
|
var style = (styles[geom_type]) ? styles[geom_type] : styles["default"];
|
||||||
|
|
||||||
|
return new OpenLayers.StyleMap(OpenLayers.Util.applyDefaults(
|
||||||
|
style, OpenLayers.Feature.Vector.style["default"]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public
|
||||||
|
return {
|
||||||
|
map: null,
|
||||||
|
|
||||||
|
extent: null,
|
||||||
|
|
||||||
|
styles: {
|
||||||
|
"point":{
|
||||||
|
"externalGraphic": "/ckanext/spatial/marker.png",
|
||||||
|
"graphicWidth":14,
|
||||||
|
"graphicHeight":25,
|
||||||
|
"fillOpacity":1
|
||||||
|
},
|
||||||
|
"default":{
|
||||||
|
// "fillColor":"#ee9900",
|
||||||
|
"fillColor":"#FCF6CF",
|
||||||
|
"strokeColor":"#B52",
|
||||||
|
"strokeWidth":2,
|
||||||
|
"fillOpacity":0.4,
|
||||||
|
// "pointRadius":7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setup: function(){
|
||||||
|
if (!this.extent)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Setup some sizes
|
||||||
|
var w = $("#dataset").width();
|
||||||
|
if (w > 1024) w = 1024;
|
||||||
|
$("#dataset-map-container").width(w);
|
||||||
|
$("#dataset-map-container").height(w/3);
|
||||||
|
|
||||||
|
|
||||||
|
var layers = [
|
||||||
|
new OpenLayers.Layer.OSM()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
// Create a new map
|
||||||
|
this.map = new OpenLayers.Map("dataset-map-container" ,
|
||||||
|
{
|
||||||
|
"projection": new OpenLayers.Projection("EPSG:900913"),
|
||||||
|
"displayProjection": new OpenLayers.Projection("EPSG:4326"),
|
||||||
|
"units": "m",
|
||||||
|
"numZoomLevels": 18,
|
||||||
|
"maxResolution": 156543.0339,
|
||||||
|
"maxExtent": new OpenLayers.Bounds(-20037508, -20037508, 20037508, 20037508.34),
|
||||||
|
"controls": [
|
||||||
|
new OpenLayers.Control.Attribution(),
|
||||||
|
new OpenLayers.Control.PanZoom(),
|
||||||
|
new OpenLayers.Control.Navigation()
|
||||||
|
],
|
||||||
|
"theme":"/ckanext/spatial/js/openlayers/theme/default/style.css"
|
||||||
|
});
|
||||||
|
this.map.addLayers(layers);
|
||||||
|
|
||||||
|
var geojson_format = new OpenLayers.Format.GeoJSON({
|
||||||
|
"internalProjection": new OpenLayers.Projection("EPSG:900913"),
|
||||||
|
"externalProjection": new OpenLayers.Projection("EPSG:4326")
|
||||||
|
});
|
||||||
|
var features = geojson_format.read(this.extent)
|
||||||
|
var geom_type = getGeomType(features[0])
|
||||||
|
|
||||||
|
var vector_layer = new OpenLayers.Layer.Vector("Dataset Extent",
|
||||||
|
{
|
||||||
|
"projection": new OpenLayers.Projection("EPSG:4326"),
|
||||||
|
"styleMap": getStyle(geom_type)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.map.addLayer(vector_layer);
|
||||||
|
vector_layer.addFeatures(features);
|
||||||
|
if (geom_type == "point"){
|
||||||
|
this.map.setCenter(new OpenLayers.LonLat(features[0].geometry.x,features[0].geometry.y),
|
||||||
|
this.map.numZoomLevels/2)
|
||||||
|
} else {
|
||||||
|
this.map.zoomToExtent(vector_layer.getDataExtent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(jQuery)
|
||||||
|
|
||||||
|
|
||||||
|
OpenLayers.ImgPath = "/ckanext/spatial/js/openlayers/img/";
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,20 +1,30 @@
|
||||||
This is a custom build of the OpenLayers Javascript mapping library,
|
These are custom builds of the OpenLayers Javascript mapping library,
|
||||||
slimmed down to only the features we need.
|
slimmed down to only contain the features we need.
|
||||||
|
|
||||||
The file ckan.cfg contains the build profile used to build OpenLayers.
|
The files *.cfg contain the build profile used to build OpenLayers.
|
||||||
In order to add more functionality, new classes must be added in the
|
In order to add more functionality, new classes must be added in the
|
||||||
build profile, and then run the build command from the OpenLayers
|
build profile, and then run the build command from the OpenLayers
|
||||||
distribution:
|
distribution:
|
||||||
|
|
||||||
1. svn co http://svn.openlayers.org/trunk/openlayers
|
1. Download OpenLayers source code from http://openlayers.org
|
||||||
|
|
||||||
2. Modify ckan.cfg
|
2. Modify the cfg file
|
||||||
|
|
||||||
3. Go to build/ and execute::
|
3. Go to build/ and execute::
|
||||||
|
|
||||||
python build.py {path-to-ckan.cfg} {output-file}
|
python build.py {path-to-ckan.cfg} {output-file}
|
||||||
|
|
||||||
|
These builds have been obtained using the Closure Compiler. Please refer
|
||||||
|
to the build/README.txt on the OpenLayers Source Code for more details.
|
||||||
|
|
||||||
|
python build.py -c closure {path-to-ckan.cfg} {output-file}
|
||||||
|
|
||||||
|
|
||||||
The theme used for the OpenLayers controls is the "dark" theme made available
|
The theme used for the OpenLayers controls is the "dark" theme made available
|
||||||
by Development Seed under the BSD License:
|
by Development Seed under the BSD License:
|
||||||
|
|
||||||
https://github.com/developmentseed/openlayers_themes/blob/master/LICENSE.txt
|
https://github.com/developmentseed/openlayers_themes/blob/master/LICENSE.txt
|
||||||
|
|
||||||
|
The default map marker is derived from an icon available at The Noun Project:
|
||||||
|
|
||||||
|
http://thenounproject.com/
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
[first]
|
||||||
|
OpenLayers/SingleFile.js
|
||||||
|
OpenLayers.js
|
||||||
|
OpenLayers/BaseTypes.js
|
||||||
|
OpenLayers/BaseTypes/Class.js
|
||||||
|
OpenLayers/Util.js
|
||||||
|
|
||||||
|
[last]
|
||||||
|
|
||||||
|
[include]
|
||||||
|
OpenLayers/Console.js
|
||||||
|
OpenLayers/Ajax.js
|
||||||
|
OpenLayers/Events.js
|
||||||
|
OpenLayers/Map.js
|
||||||
|
OpenLayers/Renderer/SVG.js
|
||||||
|
OpenLayers/Renderer/VML.js
|
||||||
|
OpenLayers/Layer.js
|
||||||
|
OpenLayers/Layer/XYZ.js
|
||||||
|
OpenLayers/Layer/SphericalMercator.js
|
||||||
|
OpenLayers/Layer/Vector.js
|
||||||
|
OpenLayers/Control/Navigation.js
|
||||||
|
OpenLayers/Control/PanZoom.js
|
||||||
|
OpenLayers/Control/PanZoomBar.js
|
||||||
|
OpenLayers/Control/Scale.js
|
||||||
|
OpenLayers/Control/MousePosition.js
|
||||||
|
OpenLayers/Control/LayerSwitcher.js
|
||||||
|
OpenLayers/Control/ArgParser.js
|
||||||
|
OpenLayers/Control/Attribution.js
|
||||||
|
OpenLayers/Format/GeoJSON.js
|
||||||
|
|
||||||
|
[exclude]
|
||||||
|
Rico/Corner.js
|
||||||
|
Firebug/firebug.js
|
||||||
|
Firebug/firebugx.js
|
||||||
|
OpenLayers/Lang/de.js
|
||||||
|
OpenLayers/Lang/en-CA.js
|
||||||
|
OpenLayers/Lang/fr.js
|
||||||
|
OpenLayers/Lang/cs-CZ.js
|
|
@ -0,0 +1,40 @@
|
||||||
|
[first]
|
||||||
|
OpenLayers/SingleFile.js
|
||||||
|
OpenLayers.js
|
||||||
|
OpenLayers/BaseTypes.js
|
||||||
|
OpenLayers/BaseTypes/Class.js
|
||||||
|
OpenLayers/Util.js
|
||||||
|
Rico/Corner.js
|
||||||
|
|
||||||
|
[last]
|
||||||
|
|
||||||
|
[include]
|
||||||
|
OpenLayers/Console.js
|
||||||
|
OpenLayers/Ajax.js
|
||||||
|
OpenLayers/Events.js
|
||||||
|
OpenLayers/Map.js
|
||||||
|
OpenLayers/Layer.js
|
||||||
|
OpenLayers/Layer/Grid.js
|
||||||
|
OpenLayers/Layer/HTTPRequest.js
|
||||||
|
OpenLayers/Layer/WMS.js
|
||||||
|
OpenLayers/Layer/WMS/Untiled.js
|
||||||
|
OpenLayers/Tile.js
|
||||||
|
OpenLayers/Tile/Image.js
|
||||||
|
OpenLayers/Control/Navigation.js
|
||||||
|
OpenLayers/Control/PanZoom.js
|
||||||
|
OpenLayers/Control/PanZoomBar.js
|
||||||
|
OpenLayers/Control/Scale.js
|
||||||
|
OpenLayers/Control/MousePosition.js
|
||||||
|
OpenLayers/Control/LayerSwitcher.js
|
||||||
|
OpenLayers/Format/XML.js
|
||||||
|
OpenLayers/Format/WMSCapabilities/v1_1_1.js
|
||||||
|
OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js
|
||||||
|
OpenLayers/Format/WMSCapabilities/v1_3_0.js
|
||||||
|
|
||||||
|
[exclude]
|
||||||
|
Firebug/firebug.js
|
||||||
|
Firebug/firebugx.js
|
||||||
|
OpenLayers/Lang/de.js
|
||||||
|
OpenLayers/Lang/en-CA.js
|
||||||
|
OpenLayers/Lang/fr.js
|
||||||
|
OpenLayers/Lang/cs-CZ.js
|
|
@ -98,9 +98,8 @@ CKAN.WMSPreview = function($){
|
||||||
olLayers.push(dummyLayer);
|
olLayers.push(dummyLayer);
|
||||||
|
|
||||||
// Setup some sizes
|
// Setup some sizes
|
||||||
var w = $("#container").width() * 0.50;
|
w = $("#content").width();
|
||||||
if (w > 1024) w = 1024;
|
if (w > 1024) w = 1024;
|
||||||
$("#content").width($("#container").width());
|
|
||||||
$("#map").width(w);
|
$("#map").width(w);
|
||||||
$("#map").height(500);
|
$("#map").height(500);
|
||||||
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 973 B |
|
@ -6,11 +6,22 @@
|
||||||
<py:def function="page_title">${c.pkg.title or c.pkg.name} - WMS preview</py:def>
|
<py:def function="page_title">${c.pkg.title or c.pkg.name} - WMS preview</py:def>
|
||||||
|
|
||||||
<py:def function="optional_head">
|
<py:def function="optional_head">
|
||||||
<link type="text/css" rel="stylesheet" media="all" href="/ckanext/spatial/wms_preview.css" />
|
<link type="text/css" rel="stylesheet" media="all" href="/ckanext/spatial/css/wms_preview.css" />
|
||||||
<script type="text/javascript" src="/ckanext/spatial/js/openlayers/OpenLayers_ckan.js"></script>
|
|
||||||
<script type="text/javascript" src="/ckanext/spatial/js/wms_preview.js"></script>
|
|
||||||
</py:def>
|
</py:def>
|
||||||
|
<py:def function="optional_footer">
|
||||||
|
<link type="text/css" rel="stylesheet" media="all" href="/ckanext/spatial/css/wms_preview.css" />
|
||||||
|
<script type="text/javascript" src="/ckanext/spatial/js/openlayers/OpenLayers_wms_preview.js"></script>
|
||||||
|
<script type="text/javascript" src="/ckanext/spatial/js/wms_preview.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
//<![CDATA[
|
||||||
|
$(document).ready(function(){
|
||||||
|
CKAN.WMSPreview.setup("${c.wms_url}");
|
||||||
|
})
|
||||||
|
//]]>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</py:def>
|
||||||
|
|
||||||
<div py:match="content">
|
<div py:match="content">
|
||||||
<div class="map-view-content">
|
<div class="map-view-content">
|
||||||
<h2 class="head">
|
<h2 class="head">
|
||||||
|
@ -18,9 +29,9 @@
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<!-- Source URL -->
|
<!-- Source URL -->
|
||||||
<div class="url" py:if="c.pkg.url">
|
<div class="url" py:if="c.wms_url">
|
||||||
<p>
|
<p>
|
||||||
Source: <a href="${c.wms.url}" target="_blank">${c.wms.url}</a>
|
Source: <a href="${c.wms_url}" target="_blank">${c.wms_url}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
|
@ -31,13 +42,6 @@
|
||||||
</p>
|
</p>
|
||||||
<div class="package subsection">
|
<div class="package subsection">
|
||||||
<h3>WMS preview</h3>
|
<h3>WMS preview</h3>
|
||||||
<script type="text/javascript">
|
|
||||||
//<![CDATA[
|
|
||||||
$(document).ready(function(){
|
|
||||||
CKAN.WMSPreview.setup("${c.wms.url}");
|
|
||||||
})
|
|
||||||
//]]>
|
|
||||||
</script>
|
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
<div id="layers"></div>
|
<div id="layers"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
GeoAlchemy>=0.6
|
||||||
|
Shapely>=1.2.13
|
3
setup.py
3
setup.py
|
@ -1,7 +1,7 @@
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
import sys, os
|
import sys, os
|
||||||
|
|
||||||
version = '0.1'
|
version = '0.2'
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='ckanext-spatial',
|
name='ckanext-spatial',
|
||||||
|
@ -28,6 +28,7 @@ setup(
|
||||||
# Add plugins here, eg
|
# Add plugins here, eg
|
||||||
wms_preview=ckanext.spatial.plugin:WMSPreview
|
wms_preview=ckanext.spatial.plugin:WMSPreview
|
||||||
spatial_query=ckanext.spatial.plugin:SpatialQuery
|
spatial_query=ckanext.spatial.plugin:SpatialQuery
|
||||||
|
dataset_extent_map=ckanext.spatial.plugin:DatasetExtentMap
|
||||||
[paste.paster_command]
|
[paste.paster_command]
|
||||||
spatial=ckanext.spatial.commands.spatial:Spatial
|
spatial=ckanext.spatial.commands.spatial:Spatial
|
||||||
""",
|
""",
|
||||||
|
|
Loading…
Reference in New Issue