Newer
Older
# -*- 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. #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
import os
import re
import mapscript
import urlparse
import stores
import metadata
import functools
from webapp import KeyExists
Wannes Rombouts
committed
import tools
Wannes Rombouts
committed
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))
def get_store_connection_string(info):
cparam = info["connectionParameters"]
if cparam.get("dbtype", "") == "postgis":
# First mandatory
url = "PG:dbname=%s port=%s host=%s " % (cparam["database"], cparam["port"], cparam["host"])
# Then optionals:
url += " ".join("%s=%s" % (p, cparam[p]) for p in ["user", "password"] if p in cparam)
return url
elif "url" in cparam:
url = urlparse.urlparse(cparam["url"])
if url.scheme != "file" or url.netloc:
raise ValueError("Only local files are suported.")
Wannes Rombouts
committed
return tools.get_resource_path(url.path)
else:
raise ValueError("Unhandled type '%s'" % cparam.get("dbtype", "<unknown>"))
class Class(object):
"""
"""
def __init__(self, backend):
self.ms = backend
class Layer(MetadataMixin):
"""
"""
def __init__(self, backend):
self.ms = backend
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_extent(self):
extent = self.ms.getExtent()
Maël Méliani
committed
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)
Wannes Rombouts
committed
def get_fields(self):
fields = self.get_metadata("gml_include_items", "")
if fields == "all":
# TODO: Get fields from feature type
raise NotImplemented()
Wannes Rombouts
committed
elif not fields:
return []
else:
Wannes Rombouts
committed
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"
Wannes Rombouts
committed
# 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)
Wannes Rombouts
committed
xmlsld.firstChild.getElementsByTagName("NamedLayer")[0]\
.getElementsByTagName("Name")[0].firstChild.data = sld_layer_name
Wannes Rombouts
committed
raise ValueError("Bad sld (No NamedLayer/Name)")
# Remove encoding ?
# @wapiflapi Mapscript ne gère pas les espaces...
new_sld = xmlsld.toxml()
Wannes Rombouts
committed
ms_template_layer = self.ms.clone()
ms_template_layer.name = sld_layer_name
mf.ms.insertLayer(ms_template_layer)
Wannes Rombouts
committed
try:
ms_template_layer.applySLD(new_sld, sld_layer_name)
except:
Wannes Rombouts
committed
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)
Maël Méliani
committed
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)
Wannes Rombouts
committed
def update(self, metadata):
self.update_metadatas(metadata)
class LayerGroup(object):
"""
"""
# TODO: We need to handle the order of the layers in a group.
def __init__(self, name, mapfile):
"""
"""
self.name = name
self.mapfile = mapfile
def iter_layers(self):
Maël Méliani
committed
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
Maël Méliani
committed
layer.set_metadata("wms_group_name", self.name)
for k, v in self.mapfile.get_mra_metadata("layergroups")[self.name]:
Maël Méliani
committed
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):
Maël Méliani
committed
layer = self.mapfile.get_layer(layer)
self.add_layer(layer)
def remove_layer(self, layer):
Maël Méliani
committed
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.
Maël Méliani
committed
for layer in self.mapfile.iter_layers(attr={"group": self.name}):
self.remove_layer(layer)
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:]:
extent.addX(e.minX(), e.maxX())
extent.addY(e.minY(), e.maxY())
return extent
class LayerModel(MetadataMixin):
"""
"""
def __init__(self, backend):
self.ms = backend
Wannes Rombouts
committed
self.name = self.get_mra_metadata("name", None)
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.ms.getProjection()),
mapscript.projectionObj('+init=epsg:4326'))
return stores.Extent(rect.minx, rect.miny, rect.maxx, rect.maxy)
Maël Méliani
committed
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]
Maël Méliani
committed
class FeatureTypeModel(LayerModel):
"""
"""
def update(self, ws, ft_name, ds_name, metadata):
ds = ws.get_datastore(ds_name)
ft = ds[ft_name]
self.name = ft_name
# Set basic attributes.
Wannes Rombouts
committed
self.ms.name = "ft:%s:%s:%s" % (ws.name, 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
Wannes Rombouts
committed
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)
Wannes Rombouts
committed
self.set_metadata("ows_extent", "%s %s %s %s" %
Maël Méliani
committed
(ft.get_extent().minX(), ft.get_extent().minY(),
#elif cpram["dbtype"] in ["shp", "shapefile"]:
else:
self.ms.connectiontype = mapscript.MS_SHAPEFILE
url = urlparse.urlparse(cparam["url"])
Wannes Rombouts
committed
self.ms.data = tools.get_resource_path(url.path)
# TODO: strip extention.
#else:
# raise ValueError("Unhandled type '%s'" % info["dbtype"])
# 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.
Wannes Rombouts
committed
self.update_mra_metadatas(metadata)
self.update_mra_metadatas({"name": ft_name, "type": "featuretype", "storage": ds_name,
"workspace": ws.name, "is_model": True})
Wannes Rombouts
committed
def configure_layer(self, ws, layer, enabled=True):
plugins.extend("pre_configure_vector_layer", self, ws, layer)
Wannes Rombouts
committed
# We must also update all our personal attributes (type, ...)
Wannes Rombouts
committed
# because we might not have been cloned.
Wannes Rombouts
committed
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:
Wannes Rombouts
committed
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"
"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",
})
Wannes Rombouts
committed
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)
Maël Méliani
committed
class CoverageModel(LayerModel):
"""
"""
def update(self, ws, c_name, cs_name, metadata):
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"])
Wannes Rombouts
committed
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.
Wannes Rombouts
committed
self.update_mra_metadatas(metadata)
self.update_mra_metadatas({"name": c_name, "type": "coverage", "storage": cs_name,
"workspace": ws.name, "is_model": True})
Wannes Rombouts
committed
def configure_layer(self, ws, layer, enabled=True):
plugins.extend("pre_configure_raster_layer", self, ws, layer)
Wannes Rombouts
committed
# We must also update all our personal attributes (type, ...)
Wannes Rombouts
committed
# because we might not have been cloned.
Wannes Rombouts
committed
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(object):
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
pass
class MapfileWorkspace(Workspace):
""" A workspace representing a whole mapfile.
This is currently the only existing type of workspace,
but there should be others that can handle subsets of
the mapfile.
"""
def __init__(self, mapfile):
# We are obvliously the default workspace.
self.name = mapfile.get_default_workspace_name()
self.mapfile = mapfile
def save(self):
"""Saves the workspace to disk, same as calling save on the
associated mapfile.
"""
self.mapfile.save()
# 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)
def get_store_info(self, st_type, name):
st_type = st_type if st_type.endswith("s") else st_type + "s"
info = self.mapfile.get_mra_metadata(st_type, {})[name].copy()
info["name"] = name
return info
def iter_store_names(self, st_type):
st_type = st_type if st_type.endswith("s") else st_type + "s"
return self.mapfile.get_mra_metadata(st_type, {}).iterkeys()
def iter_stores(self, st_type):
st_type = st_type if st_type.endswith("s") else st_type + "s"
return self.mapfile.get_mra_metadata(st_type, {}).iteritems()
def create_store(self, st_type, name, configuration):
st_type = st_type if st_type.endswith("s") else st_type + "s"
with self.mapfile.mra_metadata(st_type, {}) as stores:
if name in stores:
raise KeyExists(name)
stores[name] = configuration
def update_store(self, st_type, name, configuration):
st_type = st_type if st_type.endswith("s") else st_type + "s"
with self.mapfile.mra_metadata(st_type, {}) as stores:
stores[name].update(configuration)
def delete_store(self, st_type, name):
st_type = st_type if st_type.endswith("s") else st_type + "s"
with self.mapfile.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("datastores", name)
def get_datastore_info(self, name):
"""Returns info for a datastore from the workspace."""
return self.get_store_info("datastores", name)
def iter_datastore_names(self):
"""Return an iterator over the datastore names."""
return self.iter_store_names("datastores")
def iter_datastores(self):
"""Return an iterator over the datastore (names, configuration)."""
return self.iter_stores("datastores")
def create_datastore(self, name, configuration):
"""Creates a new datastore."""
return self.create_store("datastores", name, configuration)
def update_datastore(self, name, configuration):
"""Update a datastore."""
return self.update_store("datastores", name, configuration)
def delete_datastore(self, name):
"""Delete a datastore."""
return self.delete_store("datastores", 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("coveragestores", name)
def get_coveragestore_info(self, name):
"""Returns info for a coveragestore from the workspace."""
return self.get_store_info("coveragestores", name)
def iter_coveragestore_names(self):
"""Return an iterator over the coveragestore names."""
return self.iter_store_names("coveragestores")
def iter_coveragestores(self):
"""Return an iterator over the coveragestore (names, configuration)."""
return self.iter_stores("coveragestores")
def create_coveragestore(self, name, configuration):
"""Creates a new coveragestore."""
return self.create_store("coveragestores", name, configuration)
def update_coveragestore(self, name, configuration):
"""Update a coveragestore."""
return self.update_store("coveragestores", name, configuration)
def delete_coveragestore(self, name):
"""Delete a coveragestore."""
return self.delete_store("coveragestores", name)
# Feature types
Maël Méliani
committed
def iter_featuretypemodels(self, ds_name=None, **kwargs):
kwargs.setdefault("mra", {}).update({"type":"featuretype", "is_model":True})
if ds_name != None:
kwargs["mra"].update({"storage":ds_name, "workspace":self.name})
for ms_layer in self.mapfile.iter_ms_layers(**kwargs):
Maël Méliani
committed
yield FeatureTypeModel(ms_layer)
Maël Méliani
committed
def get_featuretypemodel(self, ft_name, ds_name):
# Improvement: Use get by name combined with a coverage-specific naming.
try:
Maël Méliani
committed
return next(self.iter_featuretypemodels(ds_name, mra={"name":ft_name}))
except StopIteration:
raise KeyError((ds_name, ft_name))
Maël Méliani
committed
def has_featuretypemodel(self, ft_name, ds_name):
# Improvement: See get_featuretypemodel
try:
Maël Méliani
committed
self.get_featuretypemodel(ft_name, ds_name)
except KeyError:
return False
else:
return True
Maël Méliani
committed
def create_featuretypemodel(self, ft_name, ds_name, metadata={}):
if self.has_featuretypemodel(ft_name, ds_name):
raise KeyExists(ft_name)
Maël Méliani
committed
ft = FeatureTypeModel(mapscript.layerObj(self.mapfile.ms))
ft.update(self, ft_name, ds_name, metadata)
return ft
Maël Méliani
committed
def update_featuretypemodel(self, ft_name, ds_name, metadata={}):
ft = self.get_featuretypemodel(ft_name, ds_name)
ft.update(self, ft_name, ds_name, metadata)
Maël Méliani
committed
def delete_featuretypemodel(self, ft_name, ds_name):
try:
next(self.mapfile.iter_layers(mra={"workspace":self.name, "type":"featuretype",
"storage":ds_name, "name":ft_name}))
except StopIteration:
pass # No layers use our featuretyp, all OK.
else:
raise ValueError("The featuretype '%s' can't be delete because it is used." % ft_name)
Maël Méliani
committed
ft = self.get_featuretypemodel(ft_name, ds_name)
self.mapfile.ms.removeLayer(ft.ms.index)
# Coverages
Maël Méliani
committed
def iter_coveragemodels(self, cs_name=None, **kwargs):
kwargs.setdefault("mra", {}).update({"type":"coverage", "is_model":True})
if cs_name != None:
kwargs["mra"].update({"storage":cs_name, "workspace":self.name})
for ms_layer in self.mapfile.iter_ms_layers(**kwargs):
Maël Méliani
committed
yield CoverageModel(ms_layer)
Maël Méliani
committed
def get_coveragemodel(self, c_name, cs_name):
# Improvement: Use get by name combined with a coverage-specific naming.
try:
Maël Méliani
committed
return next(self.iter_coveragemodels(cs_name, mra={"name":c_name}))
except StopIteration:
raise KeyError((cs_name, c_name))
Maël Méliani
committed
def has_coveragemodel(self, c_name, cs_name):
# Improvement: See get_coveragemodel
try:
Maël Méliani
committed
self.get_coveragemodel(c_name, cs_name)
except KeyError:
return False
else:
return True
Maël Méliani
committed
def create_coveragemodel(self, c_name, cs_name, metadata={}):
if self.has_coveragemodel(c_name, cs_name):
raise KeyExists(c_name)
Maël Méliani
committed
c = CoverageModel(mapscript.layerObj(self.mapfile.ms))
c.update(self, c_name, cs_name, metadata)
return c
Maël Méliani
committed
def update_coveragemodel(self, c_name, cs_name, metadata={}):
c = self.get_coveragemodel(c_name, cs_name)
c.update(self, c_name, cs_name, metadata)
Maël Méliani
committed
def delete_coveragemodel(self, c_name, cs_name):
try:
next(self.mapfile.iter_layers(mra={"workspace":self.name, "type":"coverage",
"storage":cs_name, "name":c_name}))
except StopIteration:
pass # No layers use our featuretyp, all OK.
else:
raise ValueError("The coverage '%s' can't be delete because it is used." % c_name)
Maël Méliani
committed
c = self.get_coveragemodel(c_name, cs_name)
self.mapfile.ms.removeLayer(c.ms.index)
# All the above :)
def get_model(self, m_name, s_type, s_name):
if s_type == "coverage":
Maël Méliani
committed
return self.get_coveragemodel(m_name, s_name)
elif s_type == "featuretype":
Maël Méliani
committed
return self.get_featuretypemodel(m_name, s_name)
else:
raise ValueError("Bad storage type '%s'." % s_type)
def create_mapfile(path, map_name, data):
if os.path.exists("%s.map" % path):
raise KeyExists(map_name)
mf = mapscript.mapObj()
mf.name = map_name
Wannes Rombouts
committed
# The following could be defined in <mapfile.py>:
mf.web.metadata.set("ows_name", map_name)
mf.web.metadata.set("ows_title", data.get("title", map_name))
mf.web.metadata.set("ows_abstract", data.get("abstract", ""))
# Set default values:
# It should be configurable to the future.
# mf.web.metadata.set("ows_keywordlist", "")
# mf.web.metadata.set("ows_keywordlist_vocabulary", "")
# + ows_keywordlist_[vocabulary’s name]_items
# mf.web.metadata.set("wms_onlineresource", "")
# mf.web.metadata.set("wfs_onlineresource", "")
# mf.web.metadata.set("wms_service_onlineresource", "")
# mf.web.metadata.set("wfs_service_onlineresource", "")
mf.web.metadata.set("wms_srs", "EPSG:4326")
mf.web.metadata.set("wfs_srs", "EPSG:4326")
mf.web.metadata.set("wms_bbox_extended", "true")
# mf.web.metadata.set("wms_resx", "")
# mf.web.metadata.set("wms_resy", "")
Wannes Rombouts
committed
mf.web.metadata.set("ows_schemas_location",
"http://schemas.opengeospatial.net")
mf.web.metadata.set("ows_updatesequence", "foo")
mf.web.metadata.set("ows_addresstype", "foo")
mf.web.metadata.set("ows_address", "foo")
mf.web.metadata.set("ows_city", "foo")
mf.web.metadata.set("ows_stateorprovince", "foo")
mf.web.metadata.set("ows_postcode", "foo")
mf.web.metadata.set("ows_contactperson", "foo")
mf.web.metadata.set("ows_contactposition", "foo")
mf.web.metadata.set("ows_contactorganization", "foo")
mf.web.metadata.set("ows_contactelectronicmailaddress", "foo")
mf.web.metadata.set("ows_contactfacsimiletelephone", "foo")
mf.web.metadata.set("ows_contactvoicetelephone", "foo")
mf.web.metadata.set("wms_fees", "none")
mf.web.metadata.set("wfs_fees", "none")
mf.web.metadata.set("wms_accessconstraints", "none")
mf.web.metadata.set("wfs_accessconstraints", "none")
# mf.web.metadata.set("ows_attribution_logourl_format", "")
# mf.web.metadata.set("ows_attribution_logourl_height", "")
# mf.web.metadata.set("ows_attribution_logourl_href", "")
# mf.web.metadata.set("ows_attribution_logourl_width", "")
# mf.web.metadata.set("ows_attribution_onlineresource", "")
# mf.web.metadata.set("ows_attribution_title", "")
Wannes Rombouts
committed
mf.web.metadata.set("wms_enable_request",
"GetCapabilities GetMap GetFeatureInfo GetLegendGraphic")
Wannes Rombouts
committed
mf.web.metadata.set("wfs_enable_request",
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
"GetCapabilities DescribeFeatureType GetFeature")
mf.web.metadata.set("ows_sld_enabled", "true")
mf.web.metadata.set("wms_getcapabilities_version", "1.3.0")
mf.web.metadata.set("wfs_getcapabilities_version", "1.0.0")
# mf.web.metadata.set("wms_getmap_formatlist", "")
# mf.web.metadata.set("wms_getlegendgraphic_formatlist", "")
mf.web.metadata.set("wms_feature_info_mime_type",
"application/vnd.ogc.gml,text/plain")
# TODO: text/html
mf.web.metadata.set("wms_encoding", "UTF-8")
mf.web.metadata.set("wfs_encoding", "UTF-8")
# mf.web.metadata.set("wms_timeformat", "")
# mf.web.metadata.set("wms_languages", "")
# mf.web.metadata.set("wms_layerlimit", "")
# mf.web.metadata.set("wms_rootlayer_abstract", "")
# mf.web.metadata.set("wms_rootlayer_keywordlist", "")
# mf.web.metadata.set("wms_rootlayer_title", "")
# mf.web.metadata.set("wfs_maxfeatures", "")
# mf.web.metadata.set("wfs_feature_collection", "")
# mf.web.metadata.set("wfs_namespace_uri", "")
# mf.web.metadata.set("wfs_namespace_prefix", "")
mf.status = mapscript.MS_ON
mf.setSize(256,256)
mf.maxsize = 4096
mf.resolution = 96
mf.imagetype = 'png'
mf.imagecolor.setRGB(255,255,255)
mf.setProjection("init=epsg:4326")
mf.setExtent(-180,-90,180,90)
mf.units = mapscript.MS_DD
mf.save("%s.map" % path)
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
class Mapfile(MetadataMixin):
"""
"""
def __init__(self, path, root=None):
if root != None:
full_path = os.path.realpath(os.path.join(root, "%s.map" % path))
if not full_path.startswith(root):
raise IOError("mapfile '%s' outside root directory." % (path))
path = full_path
if isinstance(path, mapscript.mapObj):
self.path = None
self.ms = path
else:
self.path = path
self.ms = mapscript.mapObj(self.path)
self.filename = os.path.basename(self.path)
# We have one workspace that represents the file.
self.__default_workspace = MapfileWorkspace(self)
def save(self, path=None):
if path is None:
path = self.path
self.ms.save(path)
def rawtext(self):
open(self.path, "r").read()
def update(self, configuration):
raise NotImplemented()
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
# 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):
kwargs.setdefault("mra", {}).update({"is_model": lambda x: x != True})
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 move_layer_down(self, l_name):
layer = self.get_layer(l_name)
self.ms.moveLayerDown(layer.ms.index)
def has_layer(self, l_name):
try:
self.get_layer(l_name)
except KeyError:
return False
else:
return True
def get_layer_wsm(self, l_name):
layer = self.get_layer(l_name)
ws = self.get_workspace(layer.get_mra_metadata("workspace"))
model = ws.get_model(m_name=layer.get_mra_metadata("name"),
s_type=layer.get_mra_metadata("type"),
s_name=layer.get_mra_metadata("storage"))
return layer, ws, model
Wannes Rombouts
committed
def create_layer(self, ws, model, l_name, l_enabled, l_metadata={}):
# First create the layer, then configure it.
if self.has_layer(l_name):
raise KeyExists(l_name)
# We still clone, because we have to start from something,
# but everything should be configured() anyway.
layer = Layer(model.ms.clone())
self.ms.insertLayer(layer.ms)
Wannes Rombouts
committed
Wannes Rombouts
committed
layer.ms.name = l_name
Wannes Rombouts
committed
layer.enable(l_enabled)
# is queryable (by default):
layer.ms.template = "foo.html" # TODO: Support html format response
Wannes Rombouts
committed
l_metadata["wms_name"] = l_name
Wannes Rombouts
committed
l_metadata.setdefault("wms_title", l_name)
l_metadata.setdefault("wms_abstract", l_name)
l_metadata.setdefault("wms_bbox_extended", "true")
# TODO: Add other default values as above:
Wannes Rombouts
committed
# 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)
Wannes Rombouts
committed
# TODO: pass correct data (title, abstract, ...) to layer.update()
layer.update(l_metadata)
Wannes Rombouts
committed
Wannes Rombouts
committed
model.configure_layer(ws, layer, l_enabled)
Wannes Rombouts
committed
def delete_layer(self, l_name):
layer = self.get_layer(l_name)
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
self.ms.removeLayer(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)
Maël Méliani
committed
def delete_layergroup(self, lg_name):
layer_group = self.get_layergroup(lg_name)
# Remove all the layers from this group.
for layer in self.iter_layers(attr={"group": layer_group.name}):
layer_group.remove(layer)
# Remove the group from mra metadata.
with self.mra_metadata("layergroups", {}) as layergroups:
Maël Méliani
committed
del layergroups[lg_name]