| 1 | # -*- coding: ISO-8859-1 -*- |
|---|
| 2 | ############################################################################## |
|---|
| 3 | # |
|---|
| 4 | # Copyright (c) 2005-2006 Kai Hänninen <kai.hanninen@mbconcert.fi> |
|---|
| 5 | # |
|---|
| 6 | # This file is part of PrimaGIS. |
|---|
| 7 | # |
|---|
| 8 | # PrimaGIS is free software; you can redistribute it and/or modify |
|---|
| 9 | # it under the terms of the GNU General Public License as published by |
|---|
| 10 | # the Free Software Foundation; either version 2 of the License, or |
|---|
| 11 | # (at your option) any later version. |
|---|
| 12 | # |
|---|
| 13 | # PrimaGIS is distributed in the hope that it will be useful, |
|---|
| 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 16 | # GNU General Public License for more details. |
|---|
| 17 | # |
|---|
| 18 | # You should have received a copy of the GNU General Public License |
|---|
| 19 | # along with PrimaGIS; if not, write to the Free Software |
|---|
| 20 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|---|
| 21 | # |
|---|
| 22 | # $Id$ |
|---|
| 23 | # |
|---|
| 24 | ############################################################################## |
|---|
| 25 | try: |
|---|
| 26 | import pkg_resources |
|---|
| 27 | try: |
|---|
| 28 | pkg_resources.require('PCL-Core') |
|---|
| 29 | pkg_resources.require('OWSLib') |
|---|
| 30 | except pkg_resources.DistributionNotFound: |
|---|
| 31 | pass |
|---|
| 32 | except ImportError: |
|---|
| 33 | pass |
|---|
| 34 | |
|---|
| 35 | from zope.interface import implements, directlyProvides |
|---|
| 36 | |
|---|
| 37 | from Products.Archetypes.public import Schema |
|---|
| 38 | from Products.Archetypes.public import BooleanWidget, BooleanField |
|---|
| 39 | from Products.Archetypes.public import StringField, StringWidget, SelectionWidget |
|---|
| 40 | from Products.Archetypes.public import DisplayList, registerType |
|---|
| 41 | from Products.Archetypes.utils import shasattr |
|---|
| 42 | |
|---|
| 43 | from Products.CMFCore import permissions |
|---|
| 44 | from Products.CMFCore.utils import getToolByName |
|---|
| 45 | |
|---|
| 46 | from Products.PrimaGIS import log |
|---|
| 47 | from Products.PrimaGIS.layer import BaseLayer, BaseLayerSchema, OWSSchema |
|---|
| 48 | from Products.PrimaGIS.config import PROJECTNAME |
|---|
| 49 | from Products.PrimaGIS.interfaces import IPrimaGISLayer |
|---|
| 50 | from Products.PrimaGIS.widget import DataSourceSelectionWidget |
|---|
| 51 | |
|---|
| 52 | from AccessControl import ClassSecurityInfo |
|---|
| 53 | from cartography import styles |
|---|
| 54 | from cartography.geometry import Point |
|---|
| 55 | from cartography.context.interfaces import IOWSMapRequest |
|---|
| 56 | from cartography.data.interfaces import IOWSStore, IFeatureStore |
|---|
| 57 | from owslib import wms, wfs |
|---|
| 58 | |
|---|
| 59 | |
|---|
| 60 | # Compile the PrimaGISLayer schema |
|---|
| 61 | PrimaGISLayerSchema = BaseLayerSchema.copy() + \ |
|---|
| 62 | Schema(( |
|---|
| 63 | StringField( |
|---|
| 64 | 'dataSourceIdentifier', |
|---|
| 65 | required=True, |
|---|
| 66 | widget=DataSourceSelectionWidget( |
|---|
| 67 | label="Data source", |
|---|
| 68 | label_msgid="label_data_source", |
|---|
| 69 | description=("Select the data source for this " |
|---|
| 70 | "layer by first selecting the " |
|---|
| 71 | "datastore and then the appropriate " |
|---|
| 72 | "feature type."), |
|---|
| 73 | description_msgid="help_data_source", |
|---|
| 74 | i18n_domain="primagis" |
|---|
| 75 | ), |
|---|
| 76 | ), |
|---|
| 77 | )) + \ |
|---|
| 78 | OWSSchema.copy() |
|---|
| 79 | |
|---|
| 80 | class PrimaGISLayer(BaseLayer): |
|---|
| 81 | """ |
|---|
| 82 | This object represents a layer in a PrimaGIS. |
|---|
| 83 | A layer uses data from a connected data store and applies (optional) styling to |
|---|
| 84 | the features. |
|---|
| 85 | """ |
|---|
| 86 | implements(IPrimaGISLayer) |
|---|
| 87 | schema = PrimaGISLayerSchema |
|---|
| 88 | allowed_content_types = ['TextSymbolizer', 'PolygonSymbolizer', |
|---|
| 89 | 'PointSymbolizer', 'LineSymbolizer'] |
|---|
| 90 | |
|---|
| 91 | actions = ( |
|---|
| 92 | { 'id' : 'view', |
|---|
| 93 | 'name' : 'View', |
|---|
| 94 | 'action' : 'string:${object_url}/primagislayer_view', |
|---|
| 95 | 'permissions' : (permissions.View,), |
|---|
| 96 | 'category' : 'object' |
|---|
| 97 | }, |
|---|
| 98 | { 'id' : 'ows', |
|---|
| 99 | 'name' : 'OWS properties', |
|---|
| 100 | 'action' : 'string:${object_url}/primagis_ows', |
|---|
| 101 | 'permissions' : (permissions.ModifyPortalContent,), |
|---|
| 102 | 'condition' : 'python:object.isWMS() or object.isWFS()', |
|---|
| 103 | 'category' : 'object' |
|---|
| 104 | }, |
|---|
| 105 | { 'id' : 'style', |
|---|
| 106 | 'name' : 'Style', |
|---|
| 107 | 'action' : 'string:${object_url}/primagis_style', |
|---|
| 108 | 'permissions' : (permissions.ModifyPortalContent,), |
|---|
| 109 | 'condition' : 'python:not object.isWMS()', |
|---|
| 110 | 'category' : 'object' |
|---|
| 111 | }, |
|---|
| 112 | ) |
|---|
| 113 | |
|---|
| 114 | content_icon = "primagislayer_icon.png" |
|---|
| 115 | portal_type = meta_type = 'PrimaGISLayer' |
|---|
| 116 | archetype_name = 'PrimaGIS Layer' |
|---|
| 117 | _at_rename_after_creation = True |
|---|
| 118 | |
|---|
| 119 | security = ClassSecurityInfo() |
|---|
| 120 | |
|---|
| 121 | security.declarePrivate('_dataSourceVocabulary') |
|---|
| 122 | def _dataSourceVocabulary(self): |
|---|
| 123 | """ |
|---|
| 124 | Returns vocabulary for the data source selection. |
|---|
| 125 | """ |
|---|
| 126 | sources = DisplayList() |
|---|
| 127 | maptool = getToolByName(self, 'portal_gis') |
|---|
| 128 | |
|---|
| 129 | for dsproxy in maptool.getDataStores(): |
|---|
| 130 | proxy_type = dsproxy.__class__.__name__ |
|---|
| 131 | proxy_name = dsproxy.title_or_id() |
|---|
| 132 | |
|---|
| 133 | try: |
|---|
| 134 | for typename in dsproxy.typenames(): |
|---|
| 135 | sources.add('%s:%s' % (dsproxy.getId(), typename), |
|---|
| 136 | '[%s] %s :: %s' % (proxy_type, |
|---|
| 137 | proxy_name, |
|---|
| 138 | typename)) |
|---|
| 139 | except AttributeError: |
|---|
| 140 | # Not all data stores implement the typenames() method |
|---|
| 141 | log("%s (%s) does not support typenames()" % (proxy_type, dsproxy.getId())) |
|---|
| 142 | sources.add(dsproxy.getId(), |
|---|
| 143 | '[%s] %s' % (proxy_type, proxy_name)) |
|---|
| 144 | return sources |
|---|
| 145 | |
|---|
| 146 | security.declareProtected(permissions.ModifyPortalContent, 'setOwsSRS') |
|---|
| 147 | def setOwsSRS(self, value): |
|---|
| 148 | """ |
|---|
| 149 | Overridden mutator for setting the OWS SRS property. |
|---|
| 150 | """ |
|---|
| 151 | self.ows_srs = value |
|---|
| 152 | self.owsSRS = value |
|---|
| 153 | |
|---|
| 154 | security.declareProtected(permissions.ModifyPortalContent, 'setOwsFormat') |
|---|
| 155 | def setOwsFormat(self, value): |
|---|
| 156 | """ |
|---|
| 157 | Overridden mutator for setting the OWS image format property. |
|---|
| 158 | """ |
|---|
| 159 | self.ows_format = value |
|---|
| 160 | self.owsFormat = value |
|---|
| 161 | |
|---|
| 162 | # Begin ZCO.interfaces.ILayerProxy methods |
|---|
| 163 | security.declarePublic('getDataStore') |
|---|
| 164 | def getDataStore(self): |
|---|
| 165 | """ |
|---|
| 166 | Return ILayerProxy's DataStore |
|---|
| 167 | """ |
|---|
| 168 | datastore_id, typename = self.parseDataSource() |
|---|
| 169 | # The LayerOverviewer methods depend on a 'typename' attribute. |
|---|
| 170 | self.typename = typename |
|---|
| 171 | # Delegate the request back to the map tool |
|---|
| 172 | if shasattr(self, 'ows_srs') and shasattr(self, 'ows_format'): |
|---|
| 173 | directlyProvides(self, IOWSMapRequest) |
|---|
| 174 | return getToolByName(self, 'portal_gis').getDataStore(datastore_id) |
|---|
| 175 | |
|---|
| 176 | security.declarePublic('layer') |
|---|
| 177 | def layer(self): |
|---|
| 178 | """ |
|---|
| 179 | Return ILayerProxy's subject, an instance of PCL's Layer |
|---|
| 180 | """ |
|---|
| 181 | datastore_id, typename = self.parseDataSource() |
|---|
| 182 | if self.getUsePythonFilters(): |
|---|
| 183 | classifier = 'INTERNAL' |
|---|
| 184 | else: |
|---|
| 185 | classifier = 'EXTERNAL' |
|---|
| 186 | |
|---|
| 187 | if self.getOwsStyle(): |
|---|
| 188 | typename = '%s/%s' % (typename, self.getOwsStyle()) |
|---|
| 189 | layer = styles.Layer(self.getDataStore().datastore(), typename, |
|---|
| 190 | classifier=classifier) |
|---|
| 191 | |
|---|
| 192 | # Add the OWS properties if necessary |
|---|
| 193 | if self.getOwsSRS(): |
|---|
| 194 | layer.ows_srs = self.getOwsSRS() |
|---|
| 195 | if self.getOwsFormat(): |
|---|
| 196 | layer.ows_format = self.getOwsFormat() |
|---|
| 197 | if shasattr(layer, 'ows_srs') and shasattr(layer, 'ows_format'): |
|---|
| 198 | directlyProvides(layer, IOWSMapRequest) |
|---|
| 199 | return layer |
|---|
| 200 | # End ILayerProxy methods |
|---|
| 201 | |
|---|
| 202 | security.declarePublic('getGeometryType') |
|---|
| 203 | def getGeometryType(self): |
|---|
| 204 | """ |
|---|
| 205 | Returns the geometry type that is used by the data source defined for |
|---|
| 206 | this layer. |
|---|
| 207 | """ |
|---|
| 208 | try: |
|---|
| 209 | datastore_id, typename = self.parseDataSource() |
|---|
| 210 | geom = self.getDataStore().datastore().featuretype(typename).defaultgeometry() |
|---|
| 211 | datatype = getattr(geom, 'homotype', None) |
|---|
| 212 | # Map the datatype returned by PCL to the form understood by PrimaGIS |
|---|
| 213 | if datatype.upper().find('POINT') > -1: |
|---|
| 214 | return 'POINT' |
|---|
| 215 | if datatype.upper().find('LINE') > -1: |
|---|
| 216 | return 'LINE' |
|---|
| 217 | if datatype.upper().find('POLYGON') > -1: |
|---|
| 218 | return 'POLYGON' |
|---|
| 219 | return None |
|---|
| 220 | except AttributeError: |
|---|
| 221 | return None |
|---|
| 222 | |
|---|
| 223 | security.declareProtected(permissions.ModifyPortalContent, 'parseDataSource') |
|---|
| 224 | def parseDataSource(self): |
|---|
| 225 | """ |
|---|
| 226 | Returns the typename and datastore path set for this layer. |
|---|
| 227 | """ |
|---|
| 228 | ds_id = self.getDataSourceIdentifier().split(":") |
|---|
| 229 | if not ds_id: |
|---|
| 230 | return None, None |
|---|
| 231 | #raise ValueError, "No data source set for PrimaGISLayer at %s" % ("/".join(self.getPhysicalPath())) |
|---|
| 232 | |
|---|
| 233 | if len(ds_id) > 1: |
|---|
| 234 | # There is a typename definition |
|---|
| 235 | datastore_id, typename = ds_id |
|---|
| 236 | else: |
|---|
| 237 | typename = None |
|---|
| 238 | datastore_id = ds_id[0] |
|---|
| 239 | |
|---|
| 240 | return datastore_id, typename |
|---|
| 241 | |
|---|
| 242 | |
|---|
| 243 | security.declarePrivate('bestbbox') |
|---|
| 244 | def bestbbox(self): |
|---|
| 245 | """ |
|---|
| 246 | Overrides ZCO.overview.LayerOverviewer.bestbbox(). |
|---|
| 247 | """ |
|---|
| 248 | return self.getDataStore().datastore().bounds(self.typename).totuple() |
|---|
| 249 | # the map SRS |
|---|
| 250 | source = self.getSpatialReferenceCode() |
|---|
| 251 | # the layer SRS |
|---|
| 252 | if self.isWMS() or self.isWFS(): |
|---|
| 253 | target = self.getOwsSRS() |
|---|
| 254 | else: |
|---|
| 255 | store = self.getDataStore().datastore() |
|---|
| 256 | if IOWSStore.providedBy(store) or IFeatureStore.providedBy(store): |
|---|
| 257 | dsource = self.getDataStore().datastore().source(self.typename) |
|---|
| 258 | srs = dsource.featuretype(self.typename).defaultsrs() |
|---|
| 259 | target = srs.tostring().replace('+init=epsg', 'EPSG') |
|---|
| 260 | else: |
|---|
| 261 | target = store.info().get('srs') |
|---|
| 262 | |
|---|
| 263 | bbox = self.getDefaultExtent() |
|---|
| 264 | min_p = self.reprojectPoint(Point(bbox.minx, bbox.miny), |
|---|
| 265 | srcSRS=source, |
|---|
| 266 | tgtSRS=target) |
|---|
| 267 | max_p = self.reprojectPoint(Point(bbox.maxx, bbox.maxy), |
|---|
| 268 | srcSRS=source, |
|---|
| 269 | tgtSRS=target) |
|---|
| 270 | |
|---|
| 271 | return (min_p.x, min_p.y, max_p.x, max_p.y) |
|---|
| 272 | |
|---|
| 273 | security.declarePublic('isRaster') |
|---|
| 274 | def isRaster(self): |
|---|
| 275 | """Returns True if this layer is a raster layer.""" |
|---|
| 276 | storeclass = self.getDataStore().__class__.__name__ |
|---|
| 277 | return storeclass.startswith('WMS') or storeclass.startswith('DiskRaster') |
|---|
| 278 | |
|---|
| 279 | security.declarePublic('isWMS') |
|---|
| 280 | def isWMS(self): |
|---|
| 281 | """ |
|---|
| 282 | Returns True if this layer is a WMS layer. |
|---|
| 283 | """ |
|---|
| 284 | return self.getDataStore().__class__.__name__.startswith('WMS') |
|---|
| 285 | |
|---|
| 286 | security.declarePublic('isWFS') |
|---|
| 287 | def isWFS(self): |
|---|
| 288 | """ |
|---|
| 289 | Returns True if this layer is a WFS layer. |
|---|
| 290 | """ |
|---|
| 291 | return self.getDataStore().__class__.__name__.startswith('WFS') |
|---|
| 292 | |
|---|
| 293 | security.declareProtected(permissions.ModifyPortalContent, 'getAvailableOWSSRS') |
|---|
| 294 | def getAvailableOWSSRS(self, override_cache=False): |
|---|
| 295 | """ |
|---|
| 296 | Returns a list of available spatial reference systems for this OWS |
|---|
| 297 | layer. |
|---|
| 298 | |
|---|
| 299 | Parameters |
|---|
| 300 | ---------- |
|---|
| 301 | override_cache : boolean |
|---|
| 302 | Force PrimaGISLayer to refresh the information from the OWS source. |
|---|
| 303 | PrimaGISLayer will cache the information by default. |
|---|
| 304 | """ |
|---|
| 305 | if self.isWMS(): |
|---|
| 306 | if override_cache or getattr(self, '_v_wms', None) is None: |
|---|
| 307 | self._readWMSCapabilities() |
|---|
| 308 | storeid, typename = self.parseDataSource() |
|---|
| 309 | return self._v_wms.capabilities.getContentByName(typename).crsOptions |
|---|
| 310 | else: |
|---|
| 311 | return ('Automatic SRS value parsing is not supported for WFS layers yet',) |
|---|
| 312 | |
|---|
| 313 | security.declareProtected(permissions.ModifyPortalContent, 'getAvailableOWSFormats') |
|---|
| 314 | def getAvailableOWSFormats(self, override_cache=False): |
|---|
| 315 | """ |
|---|
| 316 | Returns a list of available |
|---|
| 317 | |
|---|
| 318 | Parameters |
|---|
| 319 | ---------- |
|---|
| 320 | override_cache : boolean |
|---|
| 321 | Force PrimaGISLayer to refresh the information from the OWS source. |
|---|
| 322 | PrimaGISLayer will cache the information by default. |
|---|
| 323 | """ |
|---|
| 324 | if self.isWMS(): |
|---|
| 325 | if override_cache or getattr(self, '_v_wms', None) is None: |
|---|
| 326 | self._readWMSCapabilities() |
|---|
| 327 | return self._v_wms.capabilities.getOperationByName('GetMap').formatOptions |
|---|
| 328 | |
|---|
| 329 | security.declareProtected(permissions.ModifyPortalContent, 'getAvailableOWSStyles') |
|---|
| 330 | def getAvailableOWSStyles(self, override_cache=False): |
|---|
| 331 | """XXX""" |
|---|
| 332 | if self.isWMS(): |
|---|
| 333 | if override_cache or getattr(self, '_v_wms', None) is None: |
|---|
| 334 | self._readWMSCapabilities() |
|---|
| 335 | storeid, typename = self.parseDataSource() |
|---|
| 336 | styles = self._v_wms.capabilities.getContentByName(typename).styles |
|---|
| 337 | return [{'id':style,'title':styles[style].get('title',style)} |
|---|
| 338 | for style in styles] |
|---|
| 339 | return [] |
|---|
| 340 | |
|---|
| 341 | security.declareProtected(permissions.ModifyPortalContent, 'getWFSFeatureSchema') |
|---|
| 342 | def getWFSFeatureSchema(self, override_cache=False): |
|---|
| 343 | """ |
|---|
| 344 | Returns the URL to the WFS feature type schema. |
|---|
| 345 | """ |
|---|
| 346 | if self.isWFS(): |
|---|
| 347 | if override_cache or getattr(self, '_v_wfs_infoset', None) is None: |
|---|
| 348 | self._readWFSCapabilities() |
|---|
| 349 | return self._v_wfs_infoset.getCapabilityInfo().get('description') |
|---|
| 350 | return None |
|---|
| 351 | |
|---|
| 352 | security.declarePrivate('_readWMSCapabilities') |
|---|
| 353 | def _readWMSCapabilities(self): |
|---|
| 354 | """ |
|---|
| 355 | Reads the WMS Capabilities information. |
|---|
| 356 | """ |
|---|
| 357 | dsproxy = self.getDataStore() |
|---|
| 358 | self._v_wms = wms.WebMapService(dsproxy.url, dsproxy.version) |
|---|
| 359 | |
|---|
| 360 | security.declarePrivate('_readWFSCapabilities') |
|---|
| 361 | def _readWFSCapabilities(self): |
|---|
| 362 | """ |
|---|
| 363 | Reads the WFS Capabilities information. |
|---|
| 364 | """ |
|---|
| 365 | dsproxy = self.getDataStore() |
|---|
| 366 | reader = wfs.WFSCapabilitiesReader(dsproxy.version) |
|---|
| 367 | self._v_wfs_infoset = reader.read(dsproxy.url) |
|---|
| 368 | |
|---|
| 369 | |
|---|
| 370 | registerType(PrimaGISLayer, PROJECTNAME) |
|---|