diff --git a/src/mapfile.py b/src/mapfile.py index 75f7d1a29a16351bd2bc6573f8aa72621e930078..2166ad24a7a61d4bcae135e081b769b0cf6d5eed 100644 --- a/src/mapfile.py +++ b/src/mapfile.py @@ -137,8 +137,6 @@ class Layer(MetadataMixin): def add_style_sld(self, mf, s_name, new_sld): - - # Because we do not want to apply the sld to real layers by mistake # we need to rename it to something we are sure is not used. sld_layer_name = "__mra_tmp_template" @@ -150,7 +148,8 @@ class Layer(MetadataMixin): xmlsld = parseString(new_sld) try: - xmlsld.firstChild.getElementsByTagName("NamedLayer")[0].getElementsByTagName("Name")[0].firstChild.data = sld_layer_name + xmlsld.firstChild.getElementsByTagName("NamedLayer")[0]\ + .getElementsByTagName("Name")[0].firstChild.data = sld_layer_name except: raise ValueError("Bad sld (No NamedLayer/Name)") @@ -161,11 +160,11 @@ class Layer(MetadataMixin): ms_template_layer = self.ms.clone() ms_template_layer.name = sld_layer_name mf.ms.insertLayer(ms_template_layer) - + try: ms_template_layer.applySLD(new_sld, sld_layer_name) except: - raise ValueError("Unable to access storage.") + raise ValueError("Unable to access storage.") for i in xrange(ms_template_layer.numclasses): ms_class = ms_template_layer.getClass(i) @@ -333,7 +332,9 @@ class FeatureTypeModel(LayerModel): cparam = info["connectionParameters"] if cparam.get("dbtype", None) in ["postgis", "postgres", "postgresql"]: self.ms.connectiontype = mapscript.MS_POSTGIS - self.ms.connection = get_store_connection_string(info) + connection = "dbname=%s port=%s host=%s " % (cparam["database"], cparam["port"], cparam["host"]) + connection += " ".join("%s=%s" % (p, cparam[p]) for p in ["user", "password"] if p in cparam) + self.ms.connection = connection self.ms.data = "%s FROM %s" % (ds[ft_name].get_geometry_column(), ft_name) self.set_metadata("ows_extent", "%s %s %s %s" % (ft.get_extent().minX(), ft.get_extent().minY(), diff --git a/src/mra.py b/src/mra.py new file mode 100644 index 0000000000000000000000000000000000000000..a25a470d927b8a0b4eb4498e4bc6eec020b91335 --- /dev/null +++ b/src/mra.py @@ -0,0 +1,855 @@ +#!/usr/bin/env python2.7 +# -*- coding: utf-8 -*- + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# MapServer REST API is a python wrapper around MapServer which # +# allows to manipulate a mapfile in a RESTFul way. It has been # +# developped to match as close as possible the way the GeoServer # +# REST API acts. # +# # +# Copyright (C) 2011-2013 Neogeo Technologies. # +# # +# This file is part of MapServer Rest API. # +# # +# MapServer Rest API is free software: you can redistribute it # +# and/or modify it under the terms of the GNU General Public License # +# as published by the Free Software Foundation, either version 3 of # +# the License, or (at your option) any later version. # +# # +# MapServer Rest API is distributed in the hope that it will be # +# useful, but WITHOUT ANY WARRANTY; without even the implied warranty # +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + +class MetadataMixin(object): + + def __getattr__(self, attr): + if hasattr(self, "ms") and hasattr(metadata, attr): + return functools.partial(getattr(metadata, attr), self.ms) + raise AttributeError("'%s' object has no attribute '%s'" % + (type(self).__name__, attr)) + +class Layer(MetadataMixin): + def __init__(self, backend): + self.ms = backend + + def update(self, name, enabled, metadata): + self.ms.name = name + self.ms.template = "foo.html" # TODO: Support html format response + + self.enable(enabled) + self.update_metadatas(metadata) + + def enable(self, enabled=True): + requests = ["GetCapabilities", "GetMap", "GetFeatureInfo", "GetLegendGraphic"] + self.ms.status = mapscript.MS_ON if enabled else mapscript.MS_OFF + self.set_metadata("wms_enable_request", " ".join(('%s' if enabled else "!%s") % c for c in requests)) + + def get_type_name(self): + return { + 0: "POINT", + 1: "LINESTRING", + 2: "POLYGON", + 3: "RASTER", + 4: "ANNOTATION", + }[self.ms.type] + + def get_proj4(self): + return self.ms.getProjection() + + def get_wkt(self): + return tools.proj4_to_wkt(self.ms.getProjection()) + + def get_authority(self): + return tools.wkt_to_authority(self.get_wkt()) + + def get_authority_name(self): + return self.get_authority()[0] + + def get_authority_code(self): + return self.get_authority()[1] + + def get_extent(self): + extent = self.ms.getExtent() + return stores.Extent(extent.minx, extent.miny, extent.maxx, extent.maxy) + + def get_latlon_extent(self): + rect = mapscript.rectObj(*self.get_extent()) + res = rect.project(mapscript.projectionObj(self.get_proj4()), + mapscript.projectionObj('+init=epsg:4326')) + return stores.Extent(rect.minx, rect.miny, rect.maxx, rect.maxy) + + def get_fields(self): + fields = self.get_metadata("gml_include_items", "") + + if fields == "all": + # TODO: Get fields from feature type + raise NotImplemented() + elif not fields: + return [] + else: + fields = fields.split(",") + return fields + + def iter_fields(self): + return iter(self.get_fields()) + + def iter_classes(self): + for i in xrange(self.ms.numclasses): + yield Class(self.ms.getClass(i)) + + def get_styles(self): + return set(clazz.ms.group for clazz in self.iter_classes()) + + def iter_styles(self): + return iter(self.get_styles()) + + def get_SLD(self): + return self.ms.generateSLD().decode("LATIN1").encode("UTF8") + + + def add_style_sld(self, mf, s_name, new_sld): + + # Because we do not want to apply the sld to real layers by mistake + # we need to rename it to something we are sure is not used. + sld_layer_name = "__mra_tmp_template" + + # Most xml parsers will have trouble with the kind of mess we get as sld. + # Mostly because we haven't got the proper declarations, we fallback to + # an html parser, which luckily is much more forgiving. + from xml.dom.minidom import parseString + xmlsld = parseString(new_sld) + + try: + xmlsld.firstChild.getElementsByTagName("NamedLayer")[0]\ + .getElementsByTagName("Name")[0].firstChild.data = sld_layer_name + except: + raise ValueError("Bad sld (No NamedLayer/Name)") + + # Remove encoding ? + # @wapiflapi Mapscript ne gère pas les espaces... + new_sld = xmlsld.toxml() + + ms_template_layer = self.ms.clone() + ms_template_layer.name = sld_layer_name + mf.ms.insertLayer(ms_template_layer) + + try: + ms_template_layer.applySLD(new_sld, sld_layer_name) + except: + raise ValueError("Unable to access storage.") + + for i in xrange(ms_template_layer.numclasses): + ms_class = ms_template_layer.getClass(i) + ms_class.group = s_name + self.ms.insertClass(ms_class) + + mf.ms.removeLayer(ms_template_layer.index) + + def set_default_style(self, mf): + + if self.ms.type == mapscript.MS_LAYER_POINT: + self.ms.tolerance = 8 + self.ms.toleranceunits = 6 + s_name = 'default_point' + elif self.ms.type == mapscript.MS_LAYER_LINE: + self.ms.tolerance = 8 + self.ms.toleranceunits = 6 + s_name = 'default_line' + elif self.ms.type == mapscript.MS_LAYER_POLYGON: + self.ms.tolerance = 0 + self.ms.toleranceunits = 6 + s_name = 'default_polygon' + else: + return + + try: + style = open(os.path.join(os.path.dirname(__file__), "%s.sld" % s_name)).read() + except IOError, OSError: + return + + self.add_style_sld(mf, s_name, style) + self.ms.classgroup = s_name + + def remove_style(self, s_name): + for c_index in reversed(xrange(self.ms.numclasses)): + c = self.ms.getClass(c_index) + if c.group == s_name: + self.ms.removeClass(c_index) + break + else: + raise KeyError(s_name) + + +class Layergroup(object): + + def __init__(self, name, mapfile): + self.name = name + self.mapfile = mapfile + + def iter_layers(self): + return self.mapfile.iter_layers(meta={"wms_group_name":self.name}) + + def get_layers(self): + return list(self.iter_layers()) + + def add_layer(self, layer): + layer.ms.group = self.name + layer.set_metadata("wms_group_name", self.name) + for k, v in self.mapfile.get_mra_metadata("layergroups")[self.name]: + layer.set_metadata("wms_group_%s" % k, v) + self.mapfile.move_layer_down(layer.ms.name) + + def add(self, *args): + for layer in args: + if isinstance(layer, basestring): + layer = self.mapfile.get_layer(layer) + self.add_layer(layer) + + def remove_layer(self, layer): + layer.ms.group = None + for mkey in layer.get_metadata_keys(): + # (We really do not want to use iter_metadata_keys()) + if mkey.startswith("wms_group_"): + layer.del_metadata(mkey) + + def remove(self, *args): + for layer in args: + if isinstance(layer, basestring): + layer = mapfile.get_layer(layer) + self.remove_layer(layer) + + def clear(self): + # Remove all the layers from this group. + for layer in self.mapfile.iter_layers(attr={"group": self.name}): + self.remove_layer(layer) + + def get_latlon_extent(self): + layers = self.get_layers() + if not layers: + return stores.Extent(0, 0, 0, 0) + + extent = layers[0].get_latlon_extent() + for layer in layers[1:]: + e = layer.get_latlon_extent() + extent.addX(e.minX(), e.maxX()) + extent.addY(e.minY(), e.maxY()) + + return extent + + +class Mapfile(MetadataMixin): + + def __init__(self, path): + self.ms = mapscript.mapObj(self.path) + self.path = path + self.filename = os.path.basename(self.path) + + def save(self, path=None): + if path is None: + path = self.path + self.ms.save(path) + + def rawtext(self): + open(self.path, "r").read() + + + # Layers: + + def iter_ms_layers(self, attr={}, meta={}, mra={}): + def check(f, v): + return f(v) if callable(f) else f == v + + for l in xrange(self.ms.numlayers): + ms_layer = self.ms.getLayer(l) + if not all(check(checker, getattr(ms_layer, k, None)) for k, checker in attr.iteritems()): + continue + if not all(check(checker, metadata.get_metadata(ms_layer, k, None)) for k, checker in meta.iteritems()): + continue + if not all(check(checker, metadata.get_mra_metadata(ms_layer, k, None)) for k, checker in mra.iteritems()): + continue + yield ms_layer + + def iter_layers(self, **kwargs): + for ms_layer in self.iter_ms_layers(**kwargs): + yield Layer(ms_layer) + + def get_layer(self, l_name): + try: + return next(self.iter_layers(attr={"name": l_name})) + except StopIteration: + raise KeyError(l_name) + + def has_layer(self, l_name): + try: + self.get_layer(l_name) + except KeyError: + return False + else: + return True + + def create_layer(self, model, l_name, l_enabled, l_metadata={}): + if self.has_layer(l_name): + raise KeyExists(l_name) + + # Create the layer. + layer = Layer(mapscript.layerObj(self.ms)) + + dflt_metadata = { + "wms_title": l_name, + "wms_abstract": l_name, + "wms_bbox_extended": "true", + # TODO: Add other default values as above: + # wms_keywordlist, wms_keywordlist_vocabulary, wms_keywordlist_<vocabulary>_items + # wms_srs, wms_dataurl_(format|href), ows_attribution_(title|onlineresource) + # ows_attribution_logourl_(href|format|height|width), ows_identifier_(authority|value) + # ows_authorityurl_(name|href), ows_metadataurl_(type|href|format) + } + for k, v in dflt_metadata.iteritems(): + l_metadata.setdefault(k, v) + l_metadata["wms_name"] = l_name + + # Update layer. + layer.update(l_name, l_enabled, l_metadata) + + # Configure the layer according to the model. + model.configure_layer(layer, l_enabled) + + # Set default style. + layer.set_default_style(self) + + def delete_layer(self, l_name): + layer = self.get_layer(l_name) + self.ms.removeLayer(layer.ms.index) + + def move_layer_up(self, l_name): + layer = self.get_layer(l_name) + self.ms.moveLayerUp(layer.ms.index) + + def move_layer_down(self, l_name): + layer = self.get_layer(l_name) + self.ms.moveLayerDown(layer.ms.index) + + + # Layergroups + + def create_layergroup(self, lg_name, mra_metadata={}): + with self.mra_metadata("layergroups", {}) as layergroups: + if lg_name in layergroups: + raise KeyExists(lg_name) + layergroups[lg_name] = mra_metadata + return LayerGroup(lg_name, self) + + def iter_layergroups(self): + return (LayerGroup(name, self) for name in self.get_mra_metadata("layergroups", {}).iterkeys()) + + def get_layergroup(self, lg_name): + if lg_name in self.get_mra_metadata("layergroups", {}): + return LayerGroup(lg_name, self) + else: + raise KeyError(lg_name) + + def add_to_layergroup(self, lg_name, *args): + lg = self.get_layergroup(lg_name) + lg.add(*args) + + def remove_from_layergroup(self, lg_name, *args): + lg = self.get_layergroup(lg_name) + lg.remove(*args) + + def delete_layergroup(self, lg_name): + layer_group = self.get_layergroup(lg_name) + # Remove all the layers from this group. + layer_group.clear() + # Remove the group from mra metadata. + with self.mra_metadata("layergroups", {}) as layergroups: + del layergroups[lg_name] + + + + +# Workspaces are special Mapfiles that are composed of LayerModels +# which are layers that can be used to configure other layers. + + +class LayerModel(Layer): + def __init__(self, ws, *args, **kwargs): + Layer.__init__(self, *args, **kwargs) + self.ws = ws + self.name = self.get_mra_metadata("name", None) + +class FeatureTypeModel(LayerModel): + + def update(self, ds_name, ft_name, metadata): + ws = self.ws + + # Make sure the datastore exists. + ds = ws.get_datastore(ds_name) + + ft = ds[ft_name] + self.name = ft_name + + # Set basic attributes. + self.ms.name = "ft:%s:%s" % (ds_name, ft_name) + self.ms.status = mapscript.MS_OFF + self.ms.type = ft.get_geomtype_mapscript() + self.ms.setProjection(ft.get_proj4()) + self.ms.setExtent(*ft.get_extent()) + + # Configure the connection to the store. + # This is a little hacky as we have to translate stuff... + info = ws.get_datastore_info(ds_name) + cparam = info["connectionParameters"] + if cparam.get("dbtype", None) in ["postgis", "postgres", "postgresql"]: + self.ms.connectiontype = mapscript.MS_POSTGIS + connection = "dbname=%s port=%s host=%s " % (cparam["database"], cparam["port"], cparam["host"]) + connection += " ".join("%s=%s" % (p, cparam[p]) for p in ["user", "password"] if p in cparam) + self.ms.connection = connection + self.ms.data = "%s FROM %s" % (ds[ft_name].get_geometry_column(), ft_name) + self.set_metadata("ows_extent", "%s %s %s %s" % + (ft.get_extent().minX(), ft.get_extent().minY(), + ft.get_extent().maxX(), ft.get_extent().maxY())) + #elif cpram["dbtype"] in ["shp", "shapefile"]: + # TODO: clean up this fallback. + else: + self.ms.connectiontype = mapscript.MS_SHAPEFILE + url = urlparse.urlparse(cparam["url"]) + self.ms.data = tools.get_resource_path(url.path) + + # Deactivate wms and wfs requests, because we are a virtual layer. + self.set_metadatas({ + "wms_enable_request": "!GetCapabilities !GetMap !GetFeatureInfo !GetLegendGraphic", + "wfs_enable_request": "!GetCapabilities !DescribeFeatureType !GetFeature", + }) + + # Update mra metadatas, and make sure the mandatory ones are left untouched. + self.update_mra_metadatas(metadata) + self.update_mra_metadatas({"name": ft_name, "type": "featuretype", "storage": ds_name}) + + + def configure_layer(self, layer, enabled=True): + ws = self.ws + + plugins.extend("pre_configure_vector_layer", self, ws, layer) + + # We must also update all our personal attributes (type, ...) + # because we might not have been cloned. + + layer.ms.type = self.ms.type + layer.ms.setProjection(self.ms.getProjection()) + layer.ms.setExtent(self.ms.extent.minx, self.ms.extent.miny, + self.ms.extent.maxx, self.ms.extent.maxy) + layer.ms.data = self.ms.data + layer.ms.connectiontype = self.ms.connectiontype + layer.ms.connection = self.ms.connection + + layer.update_mra_metadatas({ + "name": self.get_mra_metadata("name"), + "type": self.get_mra_metadata("type"), + "storage": self.get_mra_metadata("storage"), + "workspace": self.get_mra_metadata("workspace"), + "is_model": False, + }) + + layer.update_metadatas({ + "wfs_name": layer.get_metadata("wms_name"), + "wfs_title": layer.get_metadata("wms_title"), + "wfs_abstract": layer.get_metadata("wms_abstract"), + }) + + if enabled: + layer.set_metadata("wfs_enable_request", + "GetCapabilities GetFeature DescribeFeatureType") + + # Configure the layer based on information from the store. + ds = ws.get_datastore(self.get_mra_metadata("storage")) + ft = ds[self.get_mra_metadata("name")] + + # Configure the different fields. + field_names = [] + for field in ft.iterfields(): + layer.set_metadatas({ + "gml_%s_alias" % field.get_name(): field.get_name(), + "gml_%s_type" % field.get_name(): field.get_type_gml(), + # TODO: Add gml_<field name>_precision, gml_<field name>_width + }) + field_names.append(field.get_name()) + + geometry_column = ft.get_geometry_column() + if geometry_column == None: + geometry_column = "geometry" + layer.set_metadatas({ + "ows_include_items": ",".join(field_names), + "gml_include_items": ",".join(field_names), + "gml_geometries": geometry_column, + "gml_%s_type" % geometry_column: ft.get_geomtype_gml(), + # TODO: Add gml_<geometry name>_occurances, + "wfs_srs": "EPSG:4326", + "wfs_getfeature_formatlist": "OGRGML,SHAPEZIP", + }) + + if ft.get_fid_column() != None: + layer.set_metadatas({ + "wfs_featureid": ft.get_fid_column(), + "gml_featureid": ft.get_fid_column(), + }) + + plugins.extend("post_configure_vector_layer", self, ws, ds, ft, layer) + + + +class CoverageModel(LayerModel): + """ + """ + + def update(self, ws, cs_name, c_name metadata): + ws = self.ws + + cs = ws.get_coveragestore(cs_name) + self.name = c_name + + # Set basic attributes. + self.ms.name = "c:%s:%s" % (cs_name, c_name) + self.ms.status = mapscript.MS_OFF + self.ms.type = mapscript.MS_LAYER_RASTER + self.ms.setProjection(cs.get_proj4()) + self.ms.setExtent(*cs.get_extent()) + self.ms.setProcessingKey("RESAMPLE","AVERAGE") + + # Configure the connection to the store. + # This is a little hacky as we have to translate stuff... + info = ws.get_coveragestore_info(cs_name) + cparam = info["connectionParameters"] + + #if cparam["dbtype"] in ["tif", "tiff"]: + self.ms.connectiontype = mapscript.MS_RASTER + url = urlparse.urlparse(cparam["url"]) + self.ms.data = tools.get_resource_path(url.path) + # TODO: strip extention. + #else: + # raise ValueError("Unhandled type '%s'" % cparam["dbtype"]) + + # Deactivate wms and wcs requests, because we are a virtual layer. + self.set_metadatas({ + "wms_enable_request": "!GetCapabilities !GetMap !GetFeatureInfo !GetLegendGraphic", + "wcs_enable_request": "!GetCapabilities !DescribeCoverage !GetCoverage", + }) + + # Update mra metadatas, and make sure the mandatory ones are left untouched. + self.update_mra_metadatas(metadata) + self.update_mra_metadatas({"name": c_name, "type": "coverage", "storage": cs_name, + "workspace": ws.name, "is_model": True}) + + def configure_layer(self, ws, layer, enabled=True): + ws = self.ws + + plugins.extend("pre_configure_raster_layer", self, ws, layer) + + # We must also update all our personal attributes (type, ...) + # because we might not have been cloned. + + layer.ms.type = self.ms.type + layer.ms.setProjection(self.ms.getProjection()) + layer.ms.setExtent(self.ms.extent.minx, self.ms.extent.miny, + self.ms.extent.maxx, self.ms.extent.maxy) + layer.ms.setProcessingKey("RESAMPLE","AVERAGE") + layer.ms.data = self.ms.data + layer.ms.connectiontype = self.ms.connectiontype + layer.ms.connection = self.ms.connection + + layer.update_mra_metadatas({ + "name": self.get_mra_metadata("name"), + "type": self.get_mra_metadata("type"), + "storage": self.get_mra_metadata("storage"), + "workspace": self.get_mra_metadata("workspace"), + "is_model": False, + }) + + layer.set_metadatas({ + "wfs_name": layer.get_metadata("wms_name"), + "wfs_title": layer.get_metadata("wms_title"), + "wfs_abstract": layer.get_metadata("wms_abstract"), + # TODO: wfs_keywordlist, wcs_srs, wcs_getfeature_formatlist... + }) + + if enabled: + layer.set_metadata("wcs_enable_request", "GetCapabilities GetCoverage DescribeCoverage") + + plugins.extend("post_configure_raster_layer", self, ws, layer) + + + +class Workspace(Mapfile): + + # Stores: + def get_store(self, st_type, name): + st_type = st_type if st_type.endswith("s") else st_type + "s" + cparam = get_store_connection_string(self.get_store_info(st_type, name)) + if st_type == "datastores": + return stores.Datastore(cparam) + elif st_type == "coveragestores": + return stores.Coveragestore(cparam) + else: + raise AssertionError("Unknown st_type '%s'." % st_type) + + def get_store_info(self, st_type, name): + info = self.get_mra_metadata(st_type, {})[name].copy() + info["name"] = name + return info + + def iter_store_names(self, st_type): + return self.get_mra_metadata(st_type, {}).iterkeys() + + def iter_stores(self, st_type): + return self.get_mra_metadata(st_type, {}).iteritems() + + def create_store(self, st_type, name, configuration): + with self.mra_metadata(st_type, {}) as stores: + if name in stores: + raise KeyExists(name) + stores[name] = configuration + + def update_store(self, st_type, name, configuration): + with self.mra_metadata(st_type, {}) as stores: + stores[name].update(configuration) + + def delete_store(self, st_type, name): + with self.mra_metadata(st_type, {}) as stores: + del stores[name] + + # Datastores: + + def get_datastore(self, name): + """Returns a store.Datastore object from the workspace.""" + return self.get_store("datastore", name) + + def get_datastore_info(self, name): + """Returns info for a datastore from the workspace.""" + return self.get_store_info("datastore", name) + + def iter_datastore_names(self): + """Return an iterator over the datastore names.""" + return self.iter_store_names("datastore") + + def iter_datastores(self): + """Return an iterator over the datastore (names, configuration).""" + return self.iter_stores("datastore") + + def create_datastore(self, name, configuration): + """Creates a new datastore.""" + return self.create_store("datastore", name, configuration) + + def update_datastore(self, name, configuration): + """Update a datastore.""" + return self.update_store("datastore", name, configuration) + + def delete_datastore(self, name): + """Delete a datastore.""" + try: + next(self.iter_featuretypemodels(name)) + except StopIteration: + pass # No layers use our store, all OK. + else: + raise ValueError("The datastore '%s' can't be delete because it is used." % name) + return self.delete_store("datastore", name) + + # Coveragestores (this is c/p from datastores): + + def get_coveragestore(self, name): + """Returns a store.Coveragestore object from the workspace.""" + return self.get_store("coveragestore", name) + + def get_coveragestore_info(self, name): + """Returns info for a coveragestore from the workspace.""" + return self.get_store_info("coveragestore", name) + + def iter_coveragestore_names(self): + """Return an iterator over the coveragestore names.""" + return self.iter_store_names("coveragestore") + + def iter_coveragestores(self): + """Return an iterator over the coveragestore (names, configuration).""" + return self.iter_stores("coveragestore") + + def create_coveragestore(self, name, configuration): + """Creates a new coveragestore.""" + return self.create_store("coveragestore", name, configuration) + + def update_coveragestore(self, name, configuration): + """Update a coveragestore.""" + return self.update_store("coveragestore", name, configuration) + + def delete_coveragestore(self, name): + """Delete a coveragestore.""" + try: + next(self.iter_coveragemodels(name)) + except StopIteration: + pass # No layers use our store, all OK. + else: + raise ValueError("The coveragestore '%s' can't be delete because it is used." % name) + return self.delete_store("coveragestore", name) + + # LayerModels: + + def __ms2model(self, ms_layer): + if ms_layer.name.startswith("featuretype:"): + return FeatureTypeModel(self, ms_layer) + elif ms_layer.name.startswith("coverage:"): + return CoverageModel(self, ms_layer) + else: + raise ValueError("Badly named Layer Model '%s'." % ms_layer.name) + + def iter_layermodels(self, st_type=None, store=None, **kwargs): + if st_type: + kwargs.setdefault("mra", {})["type"] = st_type + if ds_name != None: + kwargs.setdefault("mra", {})["storage"] = ds_name + for ms_layer in self.iter_ms_layers(*kwargs): + yield self.__ms2model(ms_layer) + + def get_layermodel(self, st_type, store, name): + try: + return next(self.iter_layermodels(attr={"name":"%s:%s:%s" % (st_type, store, name)})) + except StopIteration: + raise KeyError((st_type, store, name)) + + def has_layermodel(self, st_type, name, store): + try: + self.get_getlayermodel(st_type, name, store) + except KeyError: + return False + else: + return True + + def create_layermodel(self, st_type, store, name, metadata={}): + if self.has_layermodel(st_type, store, name): + raise KeyExists((st_type, store, name)) + ft = self.__ms2model(mapscript.layerObj(self.ms)) + ft.update(self, st_type, store, name, metadata) + return ft + + def update_layermodel(self, st_type, store, name, metadata={}): + ft.update(self, st_type, store, name, metadata) + + def delete_layermodel(self, st_type, ds_name, ft_name): + model = self.get_layermodel(st_type, ds_name, ft_name) + if model.get_mra_metadata("layers", []): + raise ValueError("The %s '%s' can't be delete because it is used." % (st_type, ft_name)) + self.ms.removeLayer(model.ms.index) + + + # Featuretypes + + def iter_featuretypemodels(self, ds_name=None, **kwargs): + return self.iter_layermodels("featuretype", ds_name, **kwargs) + + def get_featuretypemodel(self, ds_name, ft_name): + return self.get_layermodel("featuretype", ds_name, ft_name) + + def has_featuretypemodel(self, ds_name, ft_name): + return self.has_layermodel("featuretype", ds_name, ft_name) + + def create_featuretypemodel(self, ds_name, ft_name, metadata={}): + return self.create_layermodel("featuretype", ds_name, ft_name, metadata) + + def update_featuretypemodel(self, ds_name, ft_name, metadata={}): + return self.update_layermodel("featuretype", ds_name, ft_name, metadata={}) + + def delete_featuretypemodel(self, ds_name, ft_name): + return self.delete_layermodel("featuretype", ds_name, ft_name) + + # Coverages + + def iter_coveragemodels(self, ds_name=None, **kwargs): + return self.iter_layermodels("coverage", ds_name, **kwargs) + + def get_coveragemodel(self, ds_name, ft_name): + return self.get_layermodel("coverage", ds_name, ft_name) + + def has_coveragemodel(self, ds_name, ft_name): + return self.has_layermodel("coverage", ds_name, ft_name) + + def create_coveragemodel(self, ds_name, ft_name, metadata={}): + return self.create_layermodel("coverage", ds_name, ft_name, metadata) + + def update_coveragemodel(self, ds_name, ft_name, metadata={}): + return self.update_layermodel("coverage", ds_name, ft_name, metadata={}) + + def delete_coveragemodel(self, ds_name, ft_name): + return self.delete_layermodel("coverage", ds_name, ft_name) + + +# Finaly the global context: + +class MRA(object): + def __init__(self): + pass + + + def safe_path_join(root, *args): + full_path = os.path.realpath(os.path.join(root, *args)) + if not full_path.startswith(os.path.realpath(root)): + raise webapp.Forbidden(message="path '%s' outside root directory." % (args)) + return full_path + + def get_path(self, *args): + # TODO: get root from config. + return self.safe_path_join(root, *args) + + def get_resouces_path(self, *args): + # TODO: get root from config. (default to main_root/resources) + return self.safe_path_join(root, *args) + + # Styles: + + def get_style_path(self, *args): + # TODO: get root from config. + return self.get_resouces_path("style", *args) + + # Files: + + def get_file_path(self, *args): + return self.get_resouces_path("files", *args) + + # Available (get): + + def get_available_path(self, *args): + # TODO: get root from config. (default to main_root/available) + return self.safe_path_join(root, *args) + + def get_available(self): + path = self.get_available_path("layers.map") + # TODO: Create the thing if not existing. + return Mapfile(path) + + # Workspaces: + + def create_workspace(self, name): + path = self.get_available_path("%s.ws" % name) + # TODO: Create the thing. + return Workspace(path) + + def get_workspace(self, name): + path = self.get_available_path("%s.ws" % name) + return Workspace(path) + + def delete_workspace(self, name): + path = self.get_available_path("%s.ws" % name) + + + # Services: + + def get_service_path(self, *args): + # TODO: get root from config. (default to main_root/services) + return self.safe_path_join(root, *args) + + def get_services(self): + pass + + def get_service(self, name): + path = self.get_service_path("%s.map" % name) + return Mapfile(path) diff --git a/src/tools.py b/src/tools.py index 2e79b3e52f0a26f92f9ef9bd627aacb4526656aa..cc9c2f7993688caf5257d76e413d21b2d7e393bd 100644 --- a/src/tools.py +++ b/src/tools.py @@ -178,7 +178,7 @@ def proj4_to_wkt(proj4): def wkt_to_authority(wkt): srs = osr.SpatialReference() srs.ImportFromWkt(wkt) - + # Are there really no other with osgeo? Oo if srs.GetAuthorityCode('PROJCS') != None: