Something went wrong on our end
-
Maël Méliani authoredMaël Méliani authored
server.py 39.43 KiB
#!/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. #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
import os.path
import web
import json
import urlparse
import mralogs
import logging
import mapfile
import webapp
from webapp import HTTPCompatible, urlmap, get_data
import tools
import maptools
from tools import get_mapfile, get_mapfile_workspace, get_config, href, assert_is_empty
from pyxml import Entries
from extensions import plugins
mralogs.setup(get_config("logging")["level"], get_config("logging")["file"],
get_config("logging")["format"])
class index(object):
def GET(self, format):
return "This is MRA."
class mapfiles(object):
@HTTPCompatible()
def GET(self, format):
mapfiles = []
for path in tools.get_mapfile_paths():
try:
mf = mapfile.Mapfile(path)
except IOError, OSError:
continue
filename = mf.filename.replace(".map", "")
mapfiles.append({
"name": filename,
"href": "%s/maps/%s.%s" % (web.ctx.home, filename, format)
})
return {"mapfiles": mapfiles}
@HTTPCompatible()
def POST(self, format):
data = get_data(name="mapfile", mandatory=["name"], authorized=["name", "title", "abstract"])
map_name = data.pop("name")
path = tools.mk_mapfile_path(map_name)
with webapp.mightConflict(message="Mapfile '{exception}' already exists."):
maptools.create_mapfile(path, map_name, data)
webapp.Created("%s/maps/%s%s" % (web.ctx.home, map_name, (".%s" % format) if format else ""))
class named_mapfile(object):
@HTTPCompatible(authorize=["map"], default="html")
def GET(self, map_name, format, *args, **kwargs):
mf = get_mapfile(map_name)
with open(mf.path, "r") as f:
data = f.read()
return {"mapfile": ({
"name": map_name,
"href": "%s/maps/%s.map" % (web.ctx.home, map_name),
"workspaces": href("%s/maps/%s/workspaces.%s" % (web.ctx.home, map_name, format)),
"layers": href("%s/maps/%s/layers.%s" % (web.ctx.home, map_name, format)),
"layergroups": href("%s/maps/%s/layergroups.%s" % (web.ctx.home, map_name, format)),
"styles": href("%s/maps/%s/styles.%s" % (web.ctx.home, map_name, format)),
"wms_capabilities": href("%smap=%s&REQUEST=GetCapabilities&VERSION=%s&SERVICE=WMS" % (
get_config("mapserver")["url"], mf.path, get_config("mapserver")["wms_version"])),
"wfs_capabilities": href("%smap=%s&REQUEST=GetCapabilities&VERSION=%s&SERVICE=WFS" % (
get_config("mapserver")["url"], mf.path, get_config("mapserver")["wfs_version"])),
"wcs_capabilities": href("%smap=%s&REQUEST=GetCapabilities&VERSION=%s&SERVICE=WCS" % (
get_config("mapserver")["url"], mf.path, get_config("mapserver")["wcs_version"])),
})
} if format != "map" else data
@HTTPCompatible()
def PUT(self, map_name, format):
mf = get_mapfile(map_name)
path = tools.mk_mapfile_path(map_name)
data = get_data(name="mapfile", mandatory=["name"], authorized=["name", "title", "abstract"])
if map_name != data.pop("name"):
raise webapp.Forbidden("Can't change the name of a mapfile.")
mf.update(data)
mf.save()
@HTTPCompatible()
def DELETE(self, map_name, format):
mf = get_mapfile(map_name)
# TODO: We need to check if this mapfile is empty.
with webapp.mightNotFound("Mapfile", mapfile=map_name):
os.remove(mf.path)
class workspaces(object):
@HTTPCompatible()
def GET(self, map_name, format, *args, **kwargs):
mf = get_mapfile(map_name)
return {"workspaces": [{
"name": ws.name,
"href": "%s/maps/%s/workspaces/%s.%s" % (web.ctx.home, map_name, ws.name, format)
} for ws in mf.iter_workspaces()]
}
class workspace(object):
@HTTPCompatible()
def GET(self, map_name, ws_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
return {"workspace": ({
"name": ws.name,
"dataStores":
href("%s/maps/%s/workspaces/%s/datastores.%s" % (web.ctx.home, map_name, ws.name, format)),
"coverageStores":
href("%s/maps/%s/workspaces/%s/coveragestores.%s" % (web.ctx.home, map_name, ws.name, format)),
# TODO
# "wmsStores":
# href("%s/maps/%s/workspaces/%s/wmsstores.%s" % (web.ctx.home, map_name, ws.name, format))
})
}
class datastores(object):
@HTTPCompatible()
def GET(self, map_name, ws_name, format, *args, **kwargs):
mf, ws = get_mapfile_workspace(map_name, ws_name)
return {"dataStores": [{
"name": ds_name,
"href": "%s/maps/%s/workspaces/%s/datastores/%s.%s" % (
web.ctx.home, map_name, ws.name, ds_name, format)
} for ds_name in ws.iter_datastore_names()]
}
@HTTPCompatible()
def POST(self, map_name, ws_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
data = get_data(name="dataStore", mandatory=["name"], authorized=["name", "title", "abstract", "connectionParameters"])
ds_name = data.pop("name")
with webapp.mightConflict("dataStore", workspace=ws_name):
ws.create_datastore(ds_name, data)
ws.save()
webapp.Created("%s/maps/%s/workspaces/%s/datastores/%s%s" % (
web.ctx.home, map_name, ws_name, ds_name, (".%s" % format) if format else ""))
class datastore(object):
@HTTPCompatible()
def GET(self, map_name, ws_name, ds_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
with webapp.mightNotFound("dataStore", workspace=ws_name):
info = ws.get_datastore_info(ds_name)
info["href"] = "%s/maps/%s/workspaces/%s/datastores/%s/featuretypes.%s" % (
web.ctx.home, map_name, ws.name, ds_name, format)
if "connectionParameters" in info and isinstance(info["connectionParameters"], dict):
info["connectionParameters"] = Entries(info["connectionParameters"], tag_name="entry", key_name="key")
return {"dataStore": info}
@HTTPCompatible()
def PUT(self, map_name, ws_name, ds_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
data = get_data(name="dataStore", mandatory=["name"], authorized=["name", "title", "abstract", "connectionParameters"])
if ds_name != data.pop("name"):
raise webapp.Forbidden("Can't change the name of a data store.")
with webapp.mightNotFound("dataStore", workspace=ws_name):
ws.update_datastore(ds_name, data)
ws.save()
@HTTPCompatible()
def DELETE(self, map_name, ws_name, ds_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
# We need to check if this datatore is empty.
assert_is_empty(ws.iter_featuretypemodels(ds_name=ds_name), "datastore", ds_name)
with webapp.mightNotFound("dataStore", workspace=ws_name):
ws.delete_datastore(ds_name)
ws.save()
class featuretypes(object):
@HTTPCompatible()
def GET(self, map_name, ws_name, ds_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
return {"featureTypes": [{
"name": ft.name,
"href": "%s/maps/%s/workspaces/%s/datastores/%s/featuretypes/%s.%s" % (
web.ctx.home, map_name, ws.name, ds_name, ft.name, format)
} for ft in ws.iter_featuretypemodels(ds_name)]
}
@HTTPCompatible()
def POST(self, map_name, ws_name, ds_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
data = get_data(name="featureType", mandatory=["name"], authorized=["name", "title", "abstract"])
with webapp.mightConflict("featureType", datastore=ds_name):
with webapp.mightNotFound("featureType", datastore=ds_name):
ws.create_featuretypemodel(data["name"], ds_name, data)
ws.save()
webapp.Created("%s/maps/%s/workspaces/%s/datastores/%s/featuretypes/%s%s" % (
web.ctx.home, map_name, ws.name, ds_name, data["name"], (".%s" % format) if format else ""))
class featuretype(object):
@HTTPCompatible()
def GET(self, map_name, ws_name, ds_name, ft_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
ds = ws.get_datastore(ds_name)
with webapp.mightNotFound("dataStore", datastore=ds_name):
dsft = ds[ft_name]
with webapp.mightNotFound("featureType", datastore=ds_name):
ft = ws.get_featuretypemodel(ft_name, ds_name)
extent = ft.get_extent()
latlon_extent = ft.get_latlon_extent()
return {"featureType": ({
"name": ft.name,
"nativeName": ft.name,
"namespace": {
"name": map_name,
"href": "%s/maps/%s/namespaces/%s.%s" % (web.ctx.home, map_name, ws_name, format)
},
"title": ft.get_mra_metadata("title", ft.name),
"abstract": ft.get_mra_metadata("abstract", None),
"keywords": ft.get_mra_metadata("keywords", []),
"srs": "%s:%s" % (ft.get_authority()[0], ft.get_authority()[1]),
"nativeCRS": ft.get_wkt(),
"attributes": [{
"name": f.get_name(),
"minOccurs": 0 if f.is_nullable() else 1,
"maxOccurs": 1,
"nillable": f.is_nullable(),
"binding": f.get_type_name(),
"length": f.get_width(),
} for f in dsft.iterfields()],
"nativeBoundingBox": {
"minx": extent.minX(),
"miny": extent.minY(),
"maxx": extent.maxX(),
"maxy": extent.maxY(),
},
"latLonBoundingBox": {
"minx": latlon_extent.minX(),
"miny": latlon_extent.minY(),
"maxx": latlon_extent.maxX(),
"maxy": latlon_extent.maxY(),
"crs": "EPSG:4326",
},
"projectionPolicy": None,
"enabled": True,
"store": {
"name": ds_name,
"href": "%s/maps/%s/workspaces/%s/datastores/%s.%s" % (
web.ctx.home, map_name, ws_name, ds_name, format)
},
"maxFeatures": 0,
"numDecimals": 0,
})
}
@HTTPCompatible()
def PUT(self, map_name, ws_name, ds_name, ft_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
data = get_data(name="featureType", mandatory=["name"], authorized=["name", "title", "abstract"])
if ft_name != data["name"]:
raise webapp.Forbidden("Can't change the name of a feature type.")
metadata = dict((k, v) for k, v in data.iteritems() if k in ["title", "abstract"])
with webapp.mightNotFound("featureType", datastore=ds_name):
ws.update_featuretypemodel(ft_name, ds_name, metadata)
ws.save()
@HTTPCompatible()
def DELETE(self, map_name, ws_name, ds_name, ft_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
# We need to check if there are any layers using this.
assert_is_empty(mf.iter_layers(mra={"name":ft_name, "workspace":ws_name, "storage":ds_name, "type":"featuretype"}),"featuretype", ft_name)
with webapp.mightNotFound("featureType", datastore=ds_name):
ws.delete_featuretypemodel(ft_name, ds_name)
ws.save()
class coveragestores(object):
@HTTPCompatible()
def GET(self, map_name, ws_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
return {"coverageStores": [{
"name": cs_name,
"href": "%s/maps/%s/workspaces/%s/coveragestores/%s.%s" % (
web.ctx.home, map_name, ws.name, cs_name, format)
} for cs_name in ws.iter_coveragestore_names()]
}
@HTTPCompatible()
def POST(self, map_name, ws_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
data = get_data(name="coverageStore", mandatory=["name"], authorized=["name", "title", "abstract", "connectionParameters"])
cs_name = data.pop("name")
with webapp.mightConflict("coverageStore", workspace=ws_name):
ws.create_coveragestore(cs_name, data)
ws.save()
webapp.Created("%s/maps/%s/workspaces/%s/coveragestores/%s%s" % (
web.ctx.home, map_name, ws_name, cs_name, (".%s" % format) if format else ""))
class coveragestore(object):
@HTTPCompatible()
def GET(self, map_name, ws_name, cs_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
info = ws.get_coveragestore_info(cs_name)
info["href"] = "%s/maps/%s/workspaces/%s/coveragestores/%s/coverages.%s" % (
web.ctx.home, map_name, ws.name, cs_name, format)
if "connectionParameters" in info and isinstance(info["connectionParameters"], dict):
info["connectionParameters"] = Entries(info["connectionParameters"], tag_name="entry", key_name="key")
return {"coverageStore": info}
@HTTPCompatible()
def PUT(self, map_name, ws_name, cs_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
data = get_data(name="coverageStore", mandatory=["name"], authorized=["name", "title", "abstract", "connectionParameters"])
if cs_name != data.pop("name"):
raise webapp.Forbidden("Can't change the name of a coverage store.")
with webapp.mightNotFound("coverageStore", workspace=ws_name):
ws.update_coveragestore(cs_name, data)
ws.save()
@HTTPCompatible()
def DELETE(self, map_name, ws_name, cs_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
# We need to check if this datatore is empty.
assert_is_empty(ws.iter_coverages(cs_name=cs_name), "coveragestore", ds_name)
with webapp.mightNotFound("coverageStore", workspace=ws_name):
ws.delete_coveragestore(cs_name)
ws.save()
class coverages(object):
@HTTPCompatible()
def GET(self, map_name, ws_name, cs_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
return {"coverages": [{
"name": c.name,
"href": "%s/maps/%s/workspaces/%s/coveragestores/%s/coverages/%s.%s" % (
web.ctx.home, map_name, ws.name, cs_name, c.name, format)
} for c in ws.iter_coveragemodels(cs_name)]
}
@HTTPCompatible()
def POST(self, map_name, ws_name, cs_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
data = get_data(name="coverage", mandatory=["name"], authorized=["name", "title", "abstract"])
with webapp.mightConflict("coverage", coveragestore=cs_name):
ws.create_coveragemodel(data["name"], cs_name, data)
ws.save()
webapp.Created("%s/maps/%s/workspaces/%s/coveragestores/%s/coverages/%s%s" % (
web.ctx.home, map_name, ws.name, cs_name, data["name"], (".%s" % format) if format else ""))
class coverage(object):
@HTTPCompatible()
def GET(self, map_name, ws_name, cs_name, c_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
# with webapp.mightNotFound("coveragestore", workspace=ws_name):
# cs = ws.get_coveragestore(cs_name)
with webapp.mightNotFound("coverage", coveragestore=cs_name):
c = ws.get_coveragemodel(c_name, cs_name)
extent = c.get_extent()
latlon_extent = c.get_latlon_extent()
return {"coverage": ({
"name": c.name,
"nativeName": c.name,
"namespace": {
"name": map_name,
"href": "%s/maps/%s/namespaces/%s.%s" % (web.ctx.home, map_name, ws_name, format)
},
"title": c.get_mra_metadata("title", c.name),
"abstract": c.get_mra_metadata("abstract", None),
"keywords": c.get_mra_metadata("keywords", []),
"srs": "%s:%s" % (c.get_authority()[0], c.get_authority()[1]),
"nativeCRS": c.get_wkt(),
"nativeBoundingBox": {
"minx": extent.minX(),
"miny": extent.minY(),
"maxx": extent.maxX(),
"maxy": extent.maxY(),
},
"latLonBoundingBox":{
"minx": latlon_extent.minX(),
"miny": latlon_extent.minY(),
"maxx": latlon_extent.maxX(),
"maxy": latlon_extent.maxY(),
"crs": "EPSG:4326"
},
"projectionPolicy": None,
"enabled": True,
"store": {
"name": cs_name,
"href": "%s/maps/%s/workspaces/%s/coveragestores/%s.%s" % (
web.ctx.home, map_name, ws_name, cs_name, format)
}
})
}
@HTTPCompatible()
def PUT(self, map_name, ws_name, cs_name, c_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
data = get_data(name="coverage", mandatory=["name"], authorized=["name", "title", "abstract"])
if c_name != data["name"]:
raise webapp.Forbidden("Can't change the name of a coverage.")
metadata = dict((k, v) for k, v in data.iteritems() if k in ["title", "abstract"])
with webapp.mightNotFound("coverage", coveragestore=cs_name):
ws.update_coveragemodel(c_name, cs_name, metadata)
ws.save()
@HTTPCompatible()
def DELETE(self, map_name, ws_name, cs_name, c_name, format):
mf, ws = get_mapfile_workspace(map_name, ws_name)
# We need to check if there are any layers using this.
assert_is_empty(mf.iter_layers(mra={"name":c_name, "workspace":ws_name, "storage":cs_name, "type":"coverage"}),
"coverage", ft_name)
with webapp.mightNotFound("coverage", coveragestore=cs_name):
ws.delete_coveragemodel(c_name, cs_name)
ws.save()
class files(object):
@HTTPCompatible(allow_all=True)
def PUT(self, map_name, ws_name, st_type, st_name, f_type, format):
import zipfile
mf, ws = get_mapfile_workspace(map_name, ws_name)
# TODO: According to geoserv's examples we might have to handle
# directories as well as files, in that case we want to upload
# all the files from the directory.
# Lets first try to get the file.
if f_type == "file":
# Check if zip or not...
data = web.data()
elif f_type == "url":
raise NotImplemented()
elif f_type == "external":
raise NotImplemented()
# Now we make sure the store exists.
with webapp.mightNotFound(message="Store {exception} does not exist "
"and automatic creation is not yet suported."):
ws.get_store_info(st_type, st_name)
# TODO: Create the store if it does not exist.
# Then we store the file.
ext = web.ctx.env.get('CONTENT_TYPE', '').split("/")[-1]
path = tools.mk_st_data_path(ws_name, st_type, st_name, st_name + (".%s" % ext) if ext else "")
with open(path, "w") as f:
f.write(data)
# We also unzip it if its ziped.
ctype = web.ctx.env.get('CONTENT_TYPE', None)
if ctype == "application/zip":
z = zipfile.ZipFile(path)
for f in z.namelist():
fp = tools.mk_st_data_path(ws_name, st_type, st_name, f)
# If the file has the correct target we might want it.
if format and fp.endswith(format) and not tools.is_hidden(fp):
path = fp
z.extract(f, path=tools.get_st_data_path(ws_name, st_type, st_name))
# Set new connection parameters:
ws.update_store(st_type, st_name, {"connectionParameters":{"url":"file:"+tools.no_res_root(path)}})
ws.save()
# Finally we might have to configure it.
params = web.input(configure="none")
if params.configure == "first":
raise NotImplemented()
elif params.configure == "none":
pass
elif params.configure == "all":
raise NotImplemented()
else:
raise webapp.BadRequest(message="configure must be one of first, none or all.")
class styles(object):
@HTTPCompatible()
def GET(self, map_name, format):
mf = get_mapfile(map_name)
return {"styles": [{
"name": os.path.basename(os.path.basename(s_name)),
"href": "%s/maps/%s/styles/%s.%s" % (web.ctx.home, map_name, os.path.basename(s_name), format)
} for s_name in tools.iter_styles(mf)]
}
@HTTPCompatible()
def POST(self, map_name, format):
mf = get_mapfile(map_name)
params = web.input(name=None)
name = params.name
if name == None:
raise webapp.BadRequest(message="no parameter 'name' given.")
with webapp.mightConflict(message="style {exception} already exists."):
if name in tools.iter_styles(mf):
raise webapp.KeyExists(name)
data = web.data()
path = tools.mk_style_path(name)
with open(path, "w") as f:
f.write(data)
class style(object):
@HTTPCompatible(authorize=["sld"])
def GET(self, map_name, s_name, format):
mf = get_mapfile(map_name)
if format == "sld":
# We look for styles on disk and in the mapfiles.
try:
return open(tools.get_style_path(s_name)).read()
except IOError, OSError:
with webapp.mightNotFound("style", mapfile=map_name):
return mf.get_style_sld(s_name)
# We still need to check if this actually exists...
with webapp.mightNotFound("style", mapfile=map_name):
if not os.path.exists(tools.get_style_path(s_name)) and not s_name in mf.iter_styles():
raise KeyError(s_name)
return {
"name": s_name,
"sldVersion": Entries([
#TODO: Return the correct value...
"1.0.0"
], tag_name="version"),
"filename": s_name + ".sld",
"href": "%s/maps/%s/styles/%s.sld" % (web.ctx.home, map_name, s_name)
}
@HTTPCompatible()
def PUT(self, map_name, s_name, format):
path = tools.get_style_path(s_name)
try:
os.remove(path)
except OSError:
mf = get_mapfile(map_name)
if s_name in mf.iter_styles():
raise webapp.NotImplemented(message="Updating manually added styles is not implemented.")
else:
raise webapp.NotFound("style '%s' not found in mapfile '%s'." % (s_name, map_name))
data = web.data()
with open(path, "w") as f:
f.write(data)
@HTTPCompatible()
def DELETE(self, map_name, s_name, format):
mf = get_mapfile(map_name)
# OK check any(class.group == s_name for class in layer.iter_classes)
layers_using = [layer.ms.name for layer in mf.iter_layers()
if any(clazz.ms.group == s_name for clazz in layer.iter_classes())]
if layers_using:
raise webapp.Forbidden(message="Can't remove style '%s' because it is beeing used by the folowing layers: %s."
% (s_name, layers_using))
path = tools.get_style_path(s_name)
try:
os.remove(path)
except OSError:
mf = get_mapfile(map_name)
if s_name in mf.iter_styles():
raise webapp.NotImplemented(message="Deleting manually added styles is not implemented.")
else:
raise webapp.NotFound("style '%s' not found in mapfile '%s'." % (s_name, map_name))
class layers(object):
@HTTPCompatible()
def GET(self, map_name, format):
mf = get_mapfile(map_name)
return {"layers": [{
"id": layer.ms.index,
"name": layer.ms.name,
"type": layer.get_type_name(),
"href": "%s/maps/%s/layers/%s.%s" % (web.ctx.home, map_name, layer.ms.name, format),
# Do we implement styler or not ?
# "styler_href": "%s/styler/?namespace=%s&layer=%s" % (
# web.ctx.home, map_name, layer.name),
} for layer in mf.iter_layers()]
}
@HTTPCompatible()
def POST(self, map_name, format):
data = get_data(name="layer",
mandatory=["name", "resource"],
authorized=["name", "title", "abstract", "resource", "enabled"])
l_name = data.pop("name")
l_enabled = data.pop("enabled", True)
# This means we can have one mapfile for each workspace
# and if eveything uses urls it should work *almost* as is.
url = urlparse.urlparse(data["resource"]["href"])
if url.path.startswith(web.ctx.homepath):
path = url.path[len(web.ctx.homepath):]
else:
raise webapp.BadRequest(message="Resource href is not handled by MRA.")
try:
_, map_name, _, ws_name, st_type, st_name, r_type, r_name = path.rsplit("/", 7)
except ValueError:
raise webapp.NotFound(message="ressource '%s' was not found." % path)
r_name = r_name.rsplit(".", 1)[0]
mf, ws = get_mapfile_workspace(map_name, ws_name)
with webapp.mightNotFound(r_type, workspace=ws_name):
try:
model = ws.get_model(r_name, r_type[:-1], st_name)
except ValueError:
webapp.NotFound("Invalid layer model '%s'" % r_type[:-1])
with webapp.mightConflict("layer", mapfile=map_name):
mf.create_layer(ws, model, l_name, l_enabled)
mf.save()
webapp.Created("%s/maps/%s/layers/%s%s" % (web.ctx.home, map_name, l_name, (".%s" % format) if format else ""))
class layer(object):
@HTTPCompatible()
def GET(self, map_name, l_name, format):
mf = get_mapfile(map_name)
with webapp.mightNotFound("layer", mapfile=map_name):
layer = mf.get_layer(l_name)
data_type, store_type = {
"featuretype": ("featuretype", "datastore"),
"coverage": ("coverage", "coveragestore")
}[layer.get_mra_metadata("type")]
return {"layer" : ({
"name": l_name,
"path": "/",
"type": layer.get_type_name(),
"defaultStyle": {
"name": layer.ms.classgroup,
"href": "%s/maps/%s/styles/%s.%s" % (web.ctx.home, map_name, layer.ms.classgroup, format),
},
"styles": [{
"name": s_name,
"href": "%s/maps/%s/styles/%s.%s" % (web.ctx.home, map_name, s_name, format),
} for s_name in layer.iter_styles()],
"resource": {
"name": layer.get_mra_metadata("name"),
"href": "%s/maps/%s/workspaces/%s/%ss/%s/%ss/%s.%s" % (
web.ctx.home, map_name, layer.get_mra_metadata("workspace"),
store_type, layer.get_mra_metadata("storage"), data_type, layer.get_mra_metadata("name"), format),
},
"enabled": bool(layer.ms.status),
"attribution": {"logoWidth": 0, "logoHeight": 0}
})
}
@HTTPCompatible()
def PUT(self, map_name, l_name, format):
mf = get_mapfile(map_name)
data = get_data(name="layer", mandatory=["name", "resource"],
authorized=["name", "title", "abstract", "resource", "enabled"])
if l_name != data.pop("name"):
raise webapp.Forbidden("Can't change the name of a layer.")
l_enabled = data.pop("enabled", True)
# This means we can have one mapfile for each workspace
# and if eveything uses urls it should work *almost* as is.
r_href = data["resource"]["href"]
try:
_, map_name, _, ws_name, st_type, st_name, r_type, r_name = r_href.rsplit("/", 7)
except ValueError:
raise webapp.NotFound(message="ressource '%s' was not found." % r_href)
r_name = r_name.rsplit(".", 1)[0]
ws = mf.get_workspace(ws_name)
with webapp.mightNotFound(r_type, workspace=ws_name):
try:
model = ws.get_model(r_name, r_type[:-1], st_name)
except ValueError:
raise webapp.NotFound("Invalid layer model '%s'" % st_type)
with webapp.mightNotFound("layer", mapfile=map_name):
layer = mf.get_layer(l_name)
if layer.get_mra_metadata("type") != r_type:
raise webapp.BadRequest("Can't change a '%s' layer into a '%s'." % (
layer.get_mra_metadata("type"), r_type))
model.configure_layer(ws, layer, l_enabled)
mf.save()
@HTTPCompatible()
def DELETE(self, map_name, l_name, format):
mf = get_mapfile(map_name)
with webapp.mightNotFound("layer", mapfile=map_name):
mf.delete_layer(l_name)
mf.save()
class layerstyles(object):
@HTTPCompatible()
def GET(self, map_name, l_name, format):
mf = get_mapfile(map_name)
with webapp.mightNotFound("layer", mapfile=map_name):
layer = mf.get_layer(l_name)
if format == "sld":
return layer.getSLD()
return {"styles": [{
"name": s_name,
"href": "%s/maps/%s/styles/%s.%s" % (web.ctx.home, map_name, s_name, format),
} for s_name in layer.iter_styles()],
}
@HTTPCompatible()
def POST(self, map_name, l_name, format):
data = get_data(name="style", mandatory=["resource"],
authorized=["name", "title", "abstract", "resource"])
url = urlparse.urlparse(data["resource"]["href"])
if url.path.startswith(web.ctx.homepath):
path = url.path[len(web.ctx.homepath):]
else:
raise webapp.BadRequest(message="Resource href (%s) is not handled by MRA." % url.path)
try:
_, map_name, _, s_name = path.rsplit("/", 3)
except ValueError:
raise webapp.NotFound(message="ressource '%s' was not found." % path)
s_name = s_name.rsplit(".", 1)[0]
# Get the new style.
mf = get_mapfile(map_name)
try:
style = open(tools.get_style_path(s_name)).read()
except IOError, OSError:
with webapp.mightNotFound("style", mapfile=map_name):
style = mf.get_style_sld(s_name)
with webapp.mightNotFound("layer", mapfile=map_name):
layer = mf.get_layer(l_name)
layer.add_style_sld(mf, s_name, style)
mf.save()
webapp.Created("%s/maps/%s/layers/%s/layerstyles/%s%s" % (web.ctx.home, map_name, l_name, s_name, (".%s" % format) if format else ""))
class layerstyle(object):
@HTTPCompatible()
def DELETE(self, map_name, l_name, s_name, format):
mf = get_mapfile(map_name)
with webapp.mightNotFound("layer", mapfile=map_name):
layer = mf.get_layer(l_name)
with webapp.mightNotFound("style", layer=l_name):
layer.remove_style(s_name)
mf.save()
class layerfields(object):
@HTTPCompatible()
def GET(self, map_name, l_name, format):
mf = get_mapfile(map_name)
with webapp.mightNotFound("layer", mapfile=map_name):
layer = mf.get_layer(l_name)
return {"fields": [{
"name": layer.get_metadata("gml_%s_alias" % field, None),
"fieldType": layer.get_metadata("gml_%s_type" % field, None),
} for field in layer.iter_fields()]
}
class layergroups(object):
@HTTPCompatible()
def GET(self, map_name, format):
mf = get_mapfile(map_name)
return {"layerGroups" : [{
"name": lg.name,
"href": "%s/maps/%s/layergroups/%s.%s" % (web.ctx.home, map_name, lg.name, format)
} for lg in mf.iter_layergroups()]
}
@HTTPCompatible()
def POST(self, map_name, format):
mf = get_mapfile(map_name)
data = get_data(name="layerGroup", mandatory=["name"], authorized=["name", "title", "abstract", "layers"])
lg_name = data.pop("name")
layers = [mf.get_layer(l_name) for l_name in data.pop("layers", [])]
with webapp.mightConflict("layerGroup", mapfile=map_name):
lg = mf.create_layergroup(lg_name, data)
lg.add(*layers)
mf.save()
webapp.Created("%s/maps/%s/layergroups/%s%s" % (web.ctx.home, map_name, lg.name, (".%s" % format) if format else ""))
class layergroup(object):
@HTTPCompatible()
def GET(self, map_name, lg_name, format):
mf = get_mapfile(map_name)
with webapp.mightNotFound("layerGroup", mapfile=map_name):
lg = mf.get_layergroup(lg_name)
extent = lg.get_extent()
return {"layerGroup": ({
"name": lg.name,
"layers": [{
"name": layer.ms.name,
"href": "%s/maps/%s/layers/%s.%s" % (web.ctx.home, map_name, layer.ms.name, format),
# Do we implement styler or not ?
# "styler_href": "%s/styler/?namespace=%s&layer=%s" % (
# web.ctx.home, map_name, layer.name),
} for layer in lg.iter_layers()],
"bounds": {
"minx": extent.minX(),
"miny": extent.minY(),
"maxx": extent.maxX(),
"maxy": extent.maxY(),
},
# TODO: "metadata"
# TODO: "styles"
})
}
@HTTPCompatible()
def PUT(self, map_name, lg_name, format):
mf = get_mapfile(map_name)
with webapp.mightNotFound("layerGroup", mapfile=map_name):
lg = mf.get_layergroup(lg_name)
data = get_data(name="layerGroup", mandatory=["name"], authorized=["name", "title", "abstract", "layers"])
if lg_name != data.pop("name"):
raise webapp.Forbidden("Can't change the name of a layergroup.")
layers = data.pop("layers", [])
if not isinstance(layers, list) or any(not isinstance(x, basestring) for x in layers):
raise webapp.BadRequest("layers must be a list of layer names.")
lg.clear()
lg.add(*layers)
mf.save()
@HTTPCompatible()
def DELETE(self, map_name, lg_name, format):
mf = get_mapfile(map_name)
with webapp.mightNotFound("layerGroup", mapfile=map_name):
mf.delete_layergroup(lg_name)
mf.save()
# Index:
urlmap(index, "")
# Mapfiles:
urlmap(mapfiles, "maps")
urlmap(named_mapfile, "maps", ())
# Workspaces:
urlmap(workspaces, "maps", (), "workspaces")
urlmap(workspace, "maps", (), "workspaces", ())
# Datastores:
urlmap(datastores, "maps", (), "workspaces", (), "datastores")
urlmap(datastore, "maps", (), "workspaces", (), "datastores", ())
# Featuretypes:
urlmap(featuretypes, "maps", (), "workspaces", (), "datastores", (), "featuretypes")
urlmap(featuretype, "maps", (), "workspaces", (), "datastores", (), "featuretypes", ())
# Coveragestores:
urlmap(coveragestores, "maps", (), "workspaces", (), "coveragestores")
urlmap(coveragestore, "maps", (), "workspaces", (), "coveragestores", ())
# Coverages:
urlmap(coverages, "maps", (), "workspaces", (), "coveragestores", (), "coverages")
urlmap(coverage, "maps", (), "workspaces", (), "coveragestores", (), "coverages", ())
# Files:
urlmap(files, "maps", (), "workspaces", (), "(datastores|coveragestores)", (), "(file|url|external)")
# Styles:
urlmap(styles, "maps", (), "styles")
urlmap(style, "maps", (), "styles", ())
# Layers, layer styles and data fields:
urlmap(layers, "maps", (), "layers")
urlmap(layer, "maps", (), "layers", ())
urlmap(layerstyles, "maps", (), "layers", (), "styles")
urlmap(layerstyle, "maps", (), "layers", (), "styles", ())
urlmap(layerfields, "maps", (), "layers", (), "fields")
# Layergroups:
urlmap(layergroups, "maps", (), "layergroups")
urlmap(layergroup, "maps", (), "layergroups", ())
urls = tuple(urlmap)
web.config.debug = get_config("debug").get("web_debug", False)
webapp.exceptionManager.raise_all = get_config("debug").get("raise_all", False)
HTTPCompatible.return_logs = get_config("logging").get("web_logs", False)
for pdir in get_config("plugins").get("loadpaths", []):
plugins.load_plugins_dir(pdir)
app = web.application(urls, globals())
if __name__ == "__main__":
app.run()
application = app.wsgifunc()