source: TI02-CSML/trunk/services/3rdParty/tilecache-1.4/TileCache/Service.py @ 2202

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI02-CSML/trunk/services/3rdParty/tilecache-1.4/TileCache/Service.py@2202
Revision 2202, 18.3 KB checked in by lawrence, 13 years ago (diff)

Adding tilecache to the "interesting" OGC services already implemented
in python.

  • Property svn:executable set to *
Line 
1#!/usr/bin/python
2# BSD Licensed, Copyright (c) 2006 MetaCarta, Inc.
3
4import sys, cgi, time, os, traceback, ConfigParser
5import Cache, Layer
6
7cfgfiles = ("tilecache.cfg", os.path.join("..", "tilecache.cfg"))
8
9class Capabilities (object):
10    def __init__ (self, format, data):
11        self.format = format
12        self.data   = data
13
14class Request (object):
15    def __init__ (self, service):
16        self.service = service
17
18class WorldWind (Request):
19    def parse (self, fields, path, host):
20        param = {}
21
22        for key in ['t', 'l', 'x', 'y', 'request']: 
23            if fields.has_key(key.upper()):
24                param[key] = fields[key.upper()] 
25            elif fields.has_key(key):
26                param[key] = fields[key]
27            else:
28                param[key] = ""
29       
30        if param["request"] == "GetCapabilities" or param["request"] == "metadata":
31            return self.getCapabilities(host + path, param)
32        else:
33            return self.getMap(param)
34
35    def getMap (self, param):
36        layer = self.service.layers[param["t"]]
37        level = int(param["l"])
38        y = float(param["y"])
39        x = float(param["x"])
40       
41        tile  = Layer.Tile(layer, x, y, level)
42        return tile
43   
44    def getCapabilities (self, host, param):
45
46        metadata = self.service.metadata
47        if "description" in metadata:
48            description = metadata["description"]
49        else:
50            description = ""
51
52        formats = {}
53        for layer in self.service.layers.values():
54            formats[layer.format()] = 1
55        formats = formats.keys()
56        xml = """<?xml version="1.0" encoding="UTF-8" ?>
57            <LayerSet Name="TileCache" ShowAtStartup="true" ShowOnlyOneLayers="false">
58            """
59
60        for name, layer in self.service.layers.items():
61            if (layer.srs != "EPSG:4326"): continue
62            xml += """
63                <ChildLayerSet Name="%s" ShowAtStartup="false" ShowOnlyOneLayer="true">
64                <QuadTileSet ShowAtStartup="true">
65                  <Name>%s</Name>
66                  <Description>Layer: %s</Description>
67                  <DistanceAboveSurface>0</DistanceAboveSurface>
68                  <BoundingBox>
69                    <West><Value>%s</Value></West>
70                    <South><Value>%s</Value></South>
71                    <East><Value>%s</Value></East>
72                    <North><Value>%s</Value></North>
73                  </BoundingBox>
74                  <TerrainMapped>false</TerrainMapped>
75                  <!-- I have no clue what this means. -->
76                  <ImageAccessor>
77                    <LevelZeroTileSizeDegrees>%s</LevelZeroTileSizeDegrees>
78                    <NumberLevels>%s</NumberLevels>
79                    <TextureSizePixels>%s</TextureSizePixels>
80                    <ImageFileExtension>%s</ImageFileExtension>
81                    <ImageTileService>
82                      <ServerUrl>%s</ServerUrl>
83                      <DataSetName>%s</DataSetName>
84                    </ImageTileService> 
85                  </ImageAccessor>
86                  <ExtendedInformation>
87                    <Abstract>SRS:%s</Abstract>
88                    <!-- WorldWind doesn't have any place to store the SRS -->
89                  </ExtendedInformation>
90                </QuadTileSet>
91              </ChildLayerSet>
92                """ % (name, name, layer.description, float(layer.bbox[0]), float(layer.bbox[1]),
93                       float(layer.bbox[2]), float(layer.bbox[3]), layer.resolutions[0] * layer.size[0], 
94                       len(layer.resolutions), layer.size[0], layer.extension, host, 
95                       name, layer.srs)
96
97        xml += """
98            </LayerSet>"""
99
100        return Capabilities("text/xml", xml)
101
102class WMS (Request):
103    def parse (self, fields, path, host):
104        param = {}
105        for key in ['bbox', 'layers', 'request', 'version']: 
106            if fields.has_key(key.upper()):
107                param[key] = fields[key.upper()] 
108            elif fields.has_key(key):
109                param[key] = fields[key]
110            else:
111                param[key] = ""
112        if param["request"] == "GetCapabilities":
113            return self.getCapabilities(host + path, param)
114        else:
115            return self.getMap(param)
116
117    def getMap (self, param):
118        bbox  = map(float, param["bbox"].split(","))
119        layer = self.service.layers[param["layers"]]
120        tile  = layer.getTile(bbox)
121        if not tile:
122            raise Exception(
123                "couldn't calculate tile index for layer %s from (%s)"
124                % (layer.name, bbox))
125        return tile
126
127    def getCapabilities (self, host, param):
128        if host[-1] not in "?&":
129            if "?" in host:
130                host += "&"
131            else:
132                host += "?"
133
134        metadata = self.service.metadata
135        if "description" in metadata:
136            description = metadata["description"]
137        else:
138            description = ""
139
140        formats = {}
141        for layer in self.service.layers.values():
142            formats[layer.format()] = 1
143        formats = formats.keys()
144
145        xml = """<?xml version='1.0' encoding="ISO-8859-1" standalone="no" ?>
146        <!DOCTYPE WMT_MS_Capabilities SYSTEM
147            "http://schemas.opengeospatial.net/wms/%s/WMS_MS_Capabilities.dtd" [
148              <!ELEMENT VendorSpecificCapabilities (TileSet*) >
149              <!ELEMENT TileSet (SRS, BoundingBox?, Resolutions,
150                                 Width, Height, Format, Layers*, Styles*) >
151              <!ELEMENT Resolutions (#PCDATA) >
152              <!ELEMENT Width (#PCDATA) >
153              <!ELEMENT Height (#PCDATA) >
154              <!ELEMENT Layers (#PCDATA) >
155              <!ELEMENT Styles (#PCDATA) >
156        ]>
157        <WMT_MS_Capabilities version="%s"
158                             xmlns:xlink="http://www.w3.org/1999/xlink">
159          <Service>
160            <Name>OGC:WMS</Name>
161            <Title>%s</Title>
162            <OnlineResource xlink:href="%s"/>
163          </Service>
164        """ % (param["version"], param["version"], description, host)
165
166        xml += """
167          <Capability>
168            <Request>
169              <GetCapabilities>
170                <Format>application/vnd.ogc.wms_xml</Format>
171                <DCPType>
172                  <HTTP>
173                    <Get><OnlineResource xlink:href="%s"/></Get>
174                  </HTTP>
175                </DCPType>
176              </GetCapabilities>""" % (host)
177        xml += """
178              <GetMap>"""
179        for format in formats:
180            xml += """
181                <Format>%s</Format>\n""" % format
182        xml += """
183                <DCPType>
184                  <HTTP>
185                    <Get><OnlineResource xlink:href="%s"/></Get>
186                  </HTTP>
187                </DCPType>
188              </GetMap>
189            </Request>""" % (host)
190        xml += """
191            <Exception>
192              <Format>text/plain</Format>
193            </Exception>
194            <VendorSpecificCapabilities>"""
195        for name, layer in self.service.layers.items():
196            resolutions = " ".join(["%.9f" % r for r in layer.resolutions])
197            xml += """
198              <TileSet>
199                <SRS>%s</SRS>
200                <BoundingBox srs="%s" minx="%f" miny="%f"
201                                      maxx="%f" maxy="%f" />
202                <Resolutions>%s</Resolutions>
203                <Width>%d</Width>
204                <Height>%d</Height>
205                <Format>%s</Format>
206                <Layers>%s</Layers>
207                <Styles></Styles>
208              </TileSet>""" % (
209                layer.srs, layer.srs, layer.bbox[0], layer.bbox[1],
210                layer.bbox[2], layer.bbox[3], resolutions, layer.size[0],
211                layer.size[1], layer.format(), name )
212        xml += """
213            </VendorSpecificCapabilities>
214            <UserDefinedSymbolization SupportSLD="0" UserLayer="0"
215                                      UserStyle="0" RemoteWFS="0"/>"""
216        for name, layer in self.service.layers.items():
217            xml += """
218            <Layer queryable="0" opaque="0" cascaded="1">
219              <Name>%s</Name>
220              <Title>%s</Title>
221              <SRS>%s</SRS>
222              <BoundingBox srs="%s" minx="%f" miny="%f"
223                                    maxx="%f" maxy="%f" />
224            </Layer>""" % (
225                name, layer.name, layer.srs, layer.srs,
226                layer.bbox[0], layer.bbox[1], layer.bbox[2], layer.bbox[3])
227
228        xml += """
229          </Capability>
230        </WMT_MS_Capabilities>"""
231
232        return Capabilities("text/xml", xml)
233
234class TMS (Request):
235    def parse (self, fields, path, host):
236        # /1.0.0/global_mosaic/0/0/0.jpg
237        parts = filter( lambda x: x != "", path.split("/") )
238        if not host[-1] == "/": host = host + "/"
239        if len(parts) < 1:
240            return self.serverCapabilities(host)
241        elif len(parts) < 2:
242            return self.serviceCapabilities(host, self.service.layers)
243        else:
244            layer = self.service.layers[parts[1]]
245            if len(parts) < 3:
246                return self.layerCapabilities(host, layer)
247            else:
248                parts[-1] = parts[-1].split(".")[0]
249                tile = None
250                if (fields.has_key('type') and fields['type'] == 'google'):
251                    res = layer.resolutions[int(parts[2])]
252                    maxY = int((layer.bbox[3] - layer.bbox[1]) / (res * layer.size[1])) - 1
253                    tile  = Layer.Tile(layer, int(parts[3]), maxY - int(parts[4]), int(parts[2]))
254                else: 
255                    tile  = Layer.Tile(layer, int(parts[3]), int(parts[4]), int(parts[2]))
256                return tile
257
258    def serverCapabilities (self, host):
259        return Capabilities("text/xml", """<?xml version="1.0" encoding="UTF-8" ?>
260            <Services>
261                <TileMapService version="1.0.0" href="%s1.0.0/" />
262            </Services>""" % host)
263
264    def serviceCapabilities (self, host, layers):
265        xml = """<?xml version="1.0" encoding="UTF-8" ?>
266            <TileMapService version="1.0.0">
267              <TileMaps>"""
268
269        for name, layer in layers.items():
270            profile = "none"
271            if (layer.srs == "EPSG:4326"): profile = "global-geodetic"
272            elif (layer.srs == "OSGEO:41001"): profile = "global-mercator"
273            xml += """
274                <TileMap
275                   href="%s%s/"
276                   srs="%s"
277                   title="%s"
278                   profile="%s" />
279                """ % (host, name, layer.srs, layer.name, profile)
280
281        xml += """
282              </TileMaps>
283            </TileMapService>"""
284
285        return Capabilities("text/xml", xml)
286
287    def layerCapabilities (self, host, layer):
288        xml = """<?xml version="1.0" encoding="UTF-8" ?>
289            <TileMap version="1.0.0" tilemapservice="%s/1.0.0/">
290              <Title>%s</Title>
291              <Abstract>%s</Abstract>
292              <SRS>%s</SRS>
293              <BoundingBox minx="%.6f" miny="%.6f" maxx="%.6f" maxy="%.6f" />
294              <Origin x="%.6f" y="%.6f" /> 
295              <TileFormat width="%d" height="%d" mime-type="%s" extension="%s" />
296              <TileSets>
297            """ % (host, layer.name, layer.description, layer.srs, layer.bbox[0], layer.bbox[1],
298                   layer.bbox[2], layer.bbox[3], layer.bbox[0], layer.bbox[1],
299                   layer.size[0], layer.size[1], layer.format(), layer.extension)
300
301        for z, res in enumerate(layer.resolutions):
302            xml += """
303                 <TileSet href="%s%d"
304                          units-per-pixel="%.9f" order="%d" />""" % (
305                   host, z, res, z)
306               
307        xml += """
308              </TileSets>
309            </TileMap>"""
310
311        return Capabilities("text/xml", xml)
312
313class Service (object):
314    __slots__ = ("layers", "cache", "metadata")
315
316    def __init__ (self, cache, layers, metadata = {}):
317        self.cache    = cache
318        self.layers   = layers
319        self.metadata = metadata
320   
321    def _loadFromSection (cls, config, section, module, **objargs):
322        type  = config.get(section, "type")
323        objclass = getattr(module, type)
324        for opt in config.options(section):
325            if opt != "type":
326                objargs[opt] = config.get(section, opt)
327        if module is Layer:
328            return objclass(section, **objargs)
329        else:
330            return objclass(**objargs)
331    loadFromSection = classmethod(_loadFromSection)
332
333    def _load (cls, *files):
334        config = ConfigParser.ConfigParser()
335        config.read(files)
336       
337        metadata = {}
338        if config.has_section("metadata"):
339            for key in config.section("metadata"):
340                metadata[key] = config.get("metadata", key)
341
342        cache = cls.loadFromSection(config, "cache", Cache)
343
344        layers = {}
345        for section in config.sections():
346            if section in cls.__slots__: continue
347            layers[section] = cls.loadFromSection(
348                                    config, section, Layer, cache = cache)
349
350        return cls(cache, layers, metadata)
351    load = classmethod(_load)
352
353    def renderTile (self, tile, force = False):
354        from warnings import warn
355        start = time.time()
356
357        # do more cache checking here: SRS, width, height, layers
358
359        layer = tile.layer
360        image = None
361        if not force: image = self.cache.get(tile)
362        if not image:
363            data = layer.render(tile)
364            if (data): image = self.cache.set(tile, data)
365            else: raise Exception("Zero length data returned from layer.")
366            if layer.debug:
367                sys.stderr.write(
368                "Cache miss: %s, Tile: x: %s, y: %s, z: %s, time: %s\n" % (
369                    tile.bbox(), tile.x, tile.y, tile.z, (time.time() - start)) )
370        else:
371            if layer.debug:
372                sys.stderr.write(
373                "Cache hit: %s, Tile: x: %s, y: %s, z: %s, time: %s, debug: %s\n" % (
374                    tile.bbox(), tile.x, tile.y, tile.z, (time.time() - start), layer.debug) )
375       
376        return (layer.format(), image)
377
378    def dispatchRequest (self, params, path_info, host):
379        if params.has_key("service") or params.has_key("SERVICE"):
380            tile = WMS(self).parse(params, path_info, host)
381        elif params.has_key("L") or params.has_key("l"):
382            tile = WorldWind(self).parse(params, path_info, host)
383        else:
384            tile = TMS(self).parse(params, path_info, host)
385        if isinstance(tile, Layer.Tile):
386            return self.renderTile(tile, params.has_key('FORCE'))
387        else:
388            return (tile.format, tile.data)
389
390def modPythonHandler (apacheReq, service):
391    from mod_python import apache, util
392    try:
393        if apacheReq.headers_in.has_key("X-Forwarded-Host"):
394            host = "http://" + apacheReq.headers_in["X-Forwarded-Host"]
395        else:
396            host = "http://" + apacheReq.headers_in["Host"]
397        host += apacheReq.uri
398        format, image = service.dispatchRequest( 
399                                util.FieldStorage(apacheReq), 
400                                apacheReq.path_info,
401                                host )
402        apacheReq.content_type = format
403        apacheReq.send_http_header()
404        apacheReq.write(image)
405    except Exception, E:
406        apacheReq.content_type = "text/plain"
407        apacheReq.status = apache.HTTP_INTERNAL_SERVER_ERROR
408        apacheReq.send_http_header()
409        apacheReq.write("An error occurred: %s\n%s\n" % (
410            str(E), 
411            "".join(traceback.format_tb(sys.exc_traceback))))
412    return apache.OK
413
414def wsgiHandler (environ, start_response, service):
415    from paste.request import parse_formvars
416    try:
417        path_info = host = ""
418
419
420        if "PATH_INFO" in environ: 
421            path_info = environ["PATH_INFO"]
422
423        if "HTTP_X_FORWARDED_HOST" in environ:
424            host      = "http://" + environ["HTTP_X_FORWARDED_HOST"]
425        elif "HTTP_HOST" in environ:
426            host      = "http://" + environ["HTTP_HOST"]
427
428        host += environ["SCRIPT_NAME"]
429
430        fields = parse_formvars(environ)
431
432        format, image = service.dispatchRequest( fields, path_info, host )
433        start_response("200 OK", [('Content-Type',format)])
434        return [image]
435
436    except Exception, E:
437        start_response("200 OK", [('Content-Type','text/plain')])
438        return ["An error occurred: %s\n%s\n" % (
439            str(E), 
440            "".join(traceback.format_tb(sys.exc_traceback)))]
441
442def cgiHandler (service):
443    try:
444        params = {}
445        input = cgi.FieldStorage()
446        for key in input.keys(): params[key] = input[key].value
447        path_info = host = ""
448
449        if "PATH_INFO" in os.environ: 
450            path_info = os.environ["PATH_INFO"]
451
452        if "HTTP_X_FORWARDED_HOST" in os.environ:
453            host      = "http://" + os.environ["HTTP_X_FORWARDED_HOST"]
454        elif "HTTP_HOST" in os.environ:
455            host      = "http://" + os.environ["HTTP_HOST"]
456
457        host += os.environ["SCRIPT_NAME"]
458
459        format, image = service.dispatchRequest( params, path_info, host )
460        print "Content-type: %s\n" % format
461        if sys.platform == "win32":
462            binaryPrint(image)
463        else:   
464            print image
465    except Exception, E:
466        print "Content-type: text/plain\n"
467        print "An error occurred: %s\n%s\n" % (
468            str(E), 
469            "".join(traceback.format_tb(sys.exc_traceback)))
470
471theService = None
472
473def handler (apacheReq):
474    global theService
475    options = apacheReq.get_options()
476    cfgs    = cfgfiles
477    if options.has_key("TileCacheConfig"):
478        cfgs = (options["TileCacheConfig"],) + cfgs
479    if not theService:
480        theService = Service.load(*cfgs)
481    return modPythonHandler(apacheReq, theService)
482
483def wsgiApp (environ, start_response):
484    global theService
485    cfgs    = cfgfiles
486    if not theService:
487        theService = Service.load(*cfgs)
488    return wsgiHandler(environ, start_response, theService)
489
490def binaryPrint(binary_data):
491    """This function is designed to work around the fact that Python
492       in Windows does not handle binary output correctly. This function
493       will set the output to binary, and then write to stdout directly
494       rather than using print."""
495    try:
496        import msvcrt
497        msvcrt.setmode(sys.__stdout__.fileno(), os.O_BINARY)
498    except:
499        pass
500    sys.stdout.write(binary_data)   
501
502if __name__ == '__main__':
503    svc = Service.load(*cfgfiles)
504    cgiHandler(svc)
Note: See TracBrowser for help on using the repository browser.