Commits

Oliver Tonnhofer  committed b2b0dc1 Merge

merge with default

  • Participants
  • Parent commits 02caf8b, 708dcc0
  • Branches util_grids

Comments (0)

Files changed (28)

 520fb9b3d04afa450248e8857570dd5e97ec9a2d 1.3.0b1
 e358d9548c6d1d8d763f4e3ab5411b79237d4b78 1.3.0
 3d838b4b3874b8ed4c2c3186bbd507da8d32cce2 1.4.0rc1
+7e0e7405553a2954bc49c2c0538fa7baa25a2230 1.4.0
 - Stephan Holl
 - Kai Culemann
 - Miloslav Kmeť
+- Bruno Binet
-1.4.0rc1 2012-04-02
+1.4.0 2012-05-15
+~~~~~~~~~~~~~~~~~
+
+Fixes:
+
+- fix TypeError exception when auth callback returns {authorized:'full'}
+- use MAPPROXY_LIB_PATH on platforms other that win32 and darwin
+- raise config error for mapnik sources when mapnik could not be imported
+
+1.4.0rc1 2012-05-02
 ~~~~~~~~~~~~~~~~~~~
 
 Features:

File doc/auth.rst

 
 The signature of the authorization function:
 
-.. function:: authorize(service, environ, layers=[], **kw)
+.. function:: authorize(service, layers=[], environ=None, **kw)
   
   :param service: service that should be authorized
+  :param layers: list of layer names that should be authorized
   :param environ: the request environ
-  :param layers: list of layer names that should be authorized
   :rtype: dictionary with authorization information
 
   The arguments might get extended in future versions of MapProxy. Therefore you should collect further arguments in a variable keyword argument (i.e. ``**kw``). 
 # built documents.
 #
 # The short X.Y version.
-version = '1.4'
+version = '1.5'
 # The full version, including alpha/beta/rc tags.
-release = '1.4.0a'
+release = '1.5.0a'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.

File doc/configuration_examples.rst

 
 MapProxy will use HTTP POST requests in this case. You can change ``http.method``, if you want to force GET requests.
 
+.. _direct_source:
 
 Add highly dynamic layers
 =========================

File doc/sources.rst

 
 The values will also apear in the capabilities documents (i.e. WMS ScaleHint and Min/MaxScaleDenominator). The boundaries will be regarded for each source, but the values in the capabilities might differ if you combine multiple sources or if the MapProxy layer already has a ``min/max_res`` configuration.
 
-Pleas read :ref:`scale vs. resolution <scale_resolution>` for some notes on `scale`.
+Please read :ref:`scale vs. resolution <scale_resolution>` for some notes on `scale`.
 
 .. _supported_srs:
 
     
   ..  .. note:: For the configuration of SRS for MapProxy see `srs_configuration`_.
 
+``forward_req_params``
+^^^^^^^^^^^^^^^^^^^^^^
+
+.. versionadded:: 1.5.0
+
+A list with request parameters that will be forwarded to the source server (if available in the original request). A typical use case of this feature would be to forward the `TIME` parameter when working with a WMS-T server.
+
+This feature only works with :ref:`uncached sources <direct_source>`.
+
 ``supported_format``
 ^^^^^^^^^^^^^^^^^^^^
 
     coverage:
        polygons: GM.txt
        polygons_srs: EPSG:900913
+    forward_req_params: ['TIME', 'CUSTOM']
     req:
       url: http://localhost:8080/service?mycustomparam=foo
       layers: roads

File mapproxy/client/cgi.py

         assert data is None, 'POST requests not supported by CGIClient'
         
         parsed_url = urlparse(url)
-        environ = {
+        environ = os.environ.copy()
+        environ.update({
             'QUERY_STRING': parsed_url.query,
             'REQUEST_METHOD': 'GET',
             'GATEWAY_INTERFACE': 'CGI/1.1',
             'SERVER_NAME': 'localhost',
             'SERVER_PROTOCOL': 'HTTP/1.0',
             'SERVER_SOFTWARE': 'MapProxy',
-        }
+        })
         
         start_time = time.time()
         try:

File mapproxy/client/wms.py

 
 class WMSClient(object):
     def __init__(self, request_template, http_client=None,
-                 http_method=None, lock=None):
+                 http_method=None, lock=None, fwd_req_params=None):
         self.request_template = request_template
         self.http_client = http_client or HTTPClient()
         self.http_method = http_method
         self.lock = lock
+        self.fwd_req_params = fwd_req_params or set()
     
     def retrieve(self, query, format):
         if self.http_method == 'POST':
         req.params.size = query.size
         req.params.srs = query.srs.srs_code
         req.params.format = format
+        # also forward dimension request params if available in the query
+        req.params.update(query.dimensions_for_params(self.fwd_req_params))
         return req
     
     def combined_client(self, other, query):
         new_req = self.request_template.copy()
         new_req.params.layers = new_req.params.layers + other.request_template.params.layers
         
-        return WMSClient(new_req, http_client=self.http_client, http_method=self.http_method)
+        return WMSClient(new_req, http_client=self.http_client,
+                http_method=self.http_method, fwd_req_params=self.fwd_req_params)
         
 
 class WMSInfoClient(object):
     def identifier(self):
         return (self.url, None)
         
-        

File mapproxy/config/loader.py

         
         http_method = self.context.globals.get_value('http.method', self.conf)
         
+        fwd_req_params = set(self.conf.get('forward_req_params', []))
+
         request = create_request(self.conf['req'], params, version=version,
             abspath=self.context.globals.abspath)
         http_client, request.url = self.http_client(request.url)
         client = WMSClient(request, http_client=http_client, 
-                           http_method=http_method, lock=lock)
+                           http_method=http_method, lock=lock,
+                           fwd_req_params=fwd_req_params)
         return WMSSource(client, image_opts=image_opts, coverage=coverage,
                          res_range=res_range, transparent_color=transparent_color,
                          transparent_color_tolerance=transparent_color_tolerance,
                          supported_srs=supported_srs,
-                         supported_formats=supported_formats or None)
+                         supported_formats=supported_formats or None,
+                         fwd_req_params=fwd_req_params)
     
     def fi_source(self, params=None):
         from mapproxy.client.wms import WMSInfoClient
         mapfile = self.context.globals.abspath(self.conf['mapfile'])
         
         if self.conf.get('use_mapnik2', False):
-            from mapproxy.source.mapnik import Mapnik2Source as MapnikSource
+            from mapproxy.source.mapnik import Mapnik2Source as MapnikSource, mapnik2 as mapnik_api
         else:
-            from mapproxy.source.mapnik import MapnikSource
+            from mapproxy.source.mapnik import MapnikSource, mapnik as mapnik_api
+        if mapnik_api is None:
+            raise ConfigurationError('Could not import Mapnik, please verify it is installed!')
         return MapnikSource(mapfile, layers=layers, image_opts=image_opts,
             coverage=coverage, res_range=res_range, lock=lock)
 

File mapproxy/config/spec.py

                 'supported_formats': [str()],
                 'supported_srs': [str()],
                 'http': http_opts,
+                'forward_req_params': [str()],
                 required('req'): {
                     required('url'): str(),
                     anything(): anything()
             validate(mapproxy_yaml_spec, data)
         except ValidationError, ex:
             for err in ex.errors:
-                print '%s: %s' % (f, err)
+                print '%s: %s' % (f, err)

File mapproxy/image/merge.py

         if len(self.layers) == 1:
             layer_img, layer = self.layers[0]
             layer_opts = layer_img.image_opts
-
             if (((layer_opts and not layer_opts.transparent) or image_opts.transparent) 
                 and (not size or size == layer_img.size)
-                and (layer and not layer.coverage or not layer.coverage.clip)):
+                and (not layer or not layer.coverage or not layer.coverage.clip)):
                 # layer is opaque, no need to make transparent or add bgcolor
                 return layer_img
         

File mapproxy/layer.py

     def get_info(self, query):
         raise NotImplementedError
 
-
 class MapQuery(object):
     """
     Internal query for a map with a specific extent, size, srs, etc.
     """
     def __init__(self, bbox, size, srs, format='image/png', transparent=False,
-                 tiled_only=False):
+                 tiled_only=False, dimensions=None):
         self.bbox = bbox
         self.size = size
         self.srs = srs
         self.format = format
         self.transparent = transparent
         self.tiled_only = tiled_only
+        self.dimensions = dimensions or {}
+
+    def dimensions_for_params(self, params):
+        """
+        Return subset of the dimensions.
+
+        >>> mq = MapQuery(None, None, None, dimensions={'foo': 1, 'bar': 2})
+        >>> mq.dimensions_for_params(set(['foo', 'baz']))
+        {'foo': 1}
+        """
+        return dict((k, v) for k, v in self.dimensions.iteritems() if k in params)
 
     def __repr__(self):
         return "MapQuery(bbox=%(bbox)s, size=%(size)s, srs=%(srs)r, format=%(format)s)" % self.__dict__

File mapproxy/request/base.py

     >>> 'a' in d and 'b' in d
     True
     """
-    def __init__(self, mapping=()):
+    def _gen_dict(self, mapping=()):
         """A `NoCaseMultiDict` can be constructed from an iterable of
         ``(key, value)`` tuples or a dict.
         """
                 itr = iter(mapping)
             for key, value in itr:
                 tmp.setdefault(key.lower(), (key, []))[1].append(value)
-        dict.__init__(self, tmp)
-    
+        return tmp
+
+    def __init__(self, mapping=()):
+        """A `NoCaseMultiDict` can be constructed from an iterable of
+        ``(key, value)`` tuples or a dict.
+        """
+        dict.__init__(self, self._gen_dict(mapping))
+
+    def update(self, mapping=(), append=False):
+        """A `NoCaseMultiDict` can be updated from an iterable of
+        ``(key, value)`` tuples or a dict.
+        """
+        for _, (key, values) in self._gen_dict(mapping).iteritems():
+            self.set(key, values, append=append, unpack=True)
+
     def __getitem__(self, key):
         """
         Return the first data value for this key.
         """
         self.params.set(key, value, append=append, unpack=unpack)
     
+    def update(self, mapping=(), append=False):
+        """
+        Update internal request parameters from an iterable of ``(key, value)``
+        tuples or a dict.
+
+        If `append` is ``True`` the value will be added to other values for
+        this `key`.
+        """
+        self.params.update(mapping, append=append)
+
     def __getattr__(self, name):
         if name in self:
             return self[name]

File mapproxy/service/demo.py

             elif isinstance(map_layer, WMSSource):
                 if map_layer.supported_srs:
                     for supported_srs in map_layer.supported_srs:
-                        cached_srs.append(supported_srs)
+                        cached_srs.append(supported_srs.srs_code)
                         
         uncached_srs = []
         
             if srs_code not in cached_srs:
                 uncached_srs.append(srs_code)
         
-        sorted_cached_srs = sorted(cached_srs, key=lambda epsg_code:get_epsg_num(epsg_code))
-        sorted_uncached_srs = sorted(uncached_srs, key=lambda epsg_code:get_epsg_num(epsg_code))
-        
+        sorted_cached_srs = sorted(cached_srs, key=lambda srs: get_epsg_num(srs))
+        sorted_uncached_srs = sorted(uncached_srs, key=lambda srs: get_epsg_num(srs))
         sorted_cached_srs = [(s + '*', s) for s in sorted_cached_srs]
         sorted_uncached_srs = [(s, s) for s in sorted_uncached_srs]
         return sorted_cached_srs + sorted_uncached_srs

File mapproxy/service/wms.py

         for layers in actual_layers.values():
             render_layers.extend(layers)
 
+        self.update_query_with_fwd_params(query, params=params,
+            layers=render_layers)
+
         raise_source_errors =  True if self.on_error == 'raise' else False
         renderer = LayerRenderer(render_layers, query, map_request,
                                  raise_source_errors=raise_source_errors,
         request.validate_format(self.image_formats)
         request.validate_srs(self.srs)
     
+    def update_query_with_fwd_params(self, query, params, layers):
+        # forward relevant request params into MapQuery.dimensions
+        for layer in layers:
+            if not hasattr(layer, 'fwd_req_params'):
+                continue
+            for p in layer.fwd_req_params:
+                if p in params:
+                    query.dimensions[p] = params[p]
+
     def check_featureinfo_request(self, request):
         self.validate_layers(request)
         request.validate_srs(self.srs)
             if result['authorized'] == 'unauthenticated':
                 raise RequestError('unauthorized', status=401)
             if result['authorized'] == 'full':
-                return PERMIT_ALL_LAYERS
+                return PERMIT_ALL_LAYERS, None
             layers = {}
             if result['authorized'] == 'partial':
                 for layer_name, permissions in result['layers'].iteritems():

File mapproxy/source/wms.py

     supports_meta_tiles = True
     def __init__(self, client, image_opts=None, coverage=None, res_range=None,
                  transparent_color=None, transparent_color_tolerance=None,
-                 supported_srs=None, supported_formats=None):
+                 supported_srs=None, supported_formats=None, fwd_req_params=None):
         Source.__init__(self, image_opts=image_opts)
         self.client = client
         self.supported_srs = supported_srs or []
         self.supported_formats = supported_formats or []
+        self.fwd_req_params = fwd_req_params or set()
         
         self.transparent_color = transparent_color
         self.transparent_color_tolerance = transparent_color_tolerance
         # else
         return self.supported_srs[0]
     
-    def _is_compatible(self, other):
+    def _is_compatible(self, other, query):
         if not isinstance(other, WMSSource):
             return False
         
         
         if self.coverage != other.coverage:
             return False
-        
+
+
+        if (query.dimensions_for_params(self.fwd_req_params) != 
+            query.dimensions_for_params(other.fwd_req_params)):
+            return False
+
         return True
         
     def combined_layer(self, other, query):
-        if not self._is_compatible(other):
+        if not self._is_compatible(other, query):
             return None
         
         client = self.client.combined_client(other.client, query)
         return WMSSource(client, image_opts=self.image_opts,
             transparent_color=self.transparent_color,
             transparent_color_tolerance=self.transparent_color_tolerance,
-            res_range=self.res_range,
-            coverage=self.coverage,
+            res_range=self.res_range, coverage=self.coverage,
+            fwd_req_params=self.fwd_req_params,
         )
         
 class WMSInfoSource(InfoSource):

File mapproxy/srs.py

     maxy = max(bbox1[3], bbox2[3])
     return (minx, miny, maxx, maxy)
 
-def bbox_equals(src_bbox, dst_bbox, x_delta, y_delta=None):
+def bbox_equals(src_bbox, dst_bbox, x_delta=None, y_delta=None):
     """
     Compares two bbox and checks if they are equal, or nearly equal.
     
     :param x_delta: how precise the comparison should be.
-                    should be reasonable small, like a tenth of a pixle
+                    should be reasonable small, like a tenth of a pixel.
+                    defaults to 1/1.000.000th of the width.
     :type x_delta: bbox units
     
     >>> src_bbox = (939258.20356824622, 6887893.4928338043, 
     >>> bbox_equals(src_bbox, dst_bbox, 0.0001)
     False
     """
+    if x_delta is None:
+        x_delta = abs(src_bbox[0] - src_bbox[2]) / 1000000.0
     if y_delta is None:
         y_delta = x_delta
     return (abs(src_bbox[0] - dst_bbox[0]) < x_delta and
             abs(src_bbox[2] - dst_bbox[2]) < y_delta and
             abs(src_bbox[3] - dst_bbox[3]) < y_delta)
 
-
 def make_lin_transf(src_bbox, dst_bbox):
     """
     Create a transformation function that transforms linear between two

File mapproxy/test/http.py

         HTTPServer_.handle_error(self, request, client_address)
 
 class ThreadedStopableHTTPServer(threading.Thread):
-    def __init__(self, address, requests_responses, unordered=False):
+    def __init__(self, address, requests_responses, unordered=False, query_comparator=None):
         threading.Thread.__init__(self, **{'group': None})
         self.requests_responses = requests_responses
         self.sucess = False
         self.shutdown = False
-        self.httpd = HTTPServer(address, mock_http_handler(requests_responses, unordered=unordered))
+        self.httpd = HTTPServer(address,mock_http_handler(requests_responses,
+            unordered=unordered, query_comparator=query_comparator))
         self.httpd.timeout = 1.0
         self.out = self.httpd.out = StringIO()
     
         # force socket close so next test can bind to same address
         self.httpd.socket.close()
 
-def mock_http_handler(requests_responses, unordered=False):
+def mock_http_handler(requests_responses, unordered=False, query_comparator=None):
+    if query_comparator is None:
+        query_comparator = query_eq
     class MockHTTPHandler(BaseHTTPRequestHandler):
         def do_GET(self):
             self.query_data = self.path
             if unordered:
                 for req_resp in requests_responses:
                     req, resp = req_resp
-                    if query_eq(req['path'], self.query_data):
+                    if query_comparator(req['path'], self.query_data):
                         requests_responses.remove(req_resp)
                         return req, resp
                 return None, None
                     self.end_headers()
                     self.wfile.write('no access')
                     return
-            if not query_eq(req['path'], self.query_data):
+            if not query_comparator(req['path'], self.query_data):
                 print >>self.server.out, 'got request      ', self.query_data
                 print >>self.server.out, 'expected request ', req['path']
                 query_actual = set(query_to_dict(self.query_data).items())
         assert self._thread.sucess, ('requests to mock httpd did not '
             'match expectations:\n' + self._thread.out.read())
 
+
+def wms_query_eq(expected, actual):
+    """
+    >>> wms_query_eq('bAR=baz&foo=bizz&bbOX=0,0,100000,100000', 'foO=bizz&BBOx=-.0001,0.01,99999.99,100000.09&bar=baz')
+    True
+    >>> wms_query_eq('bAR=baz&foo=bizz&bbOX=0,0,100000,100000', 'foO=bizz&BBOx=-.0001,0.01,99999.99,100000.11&bar=baz')
+    False
+    >>> wms_query_eq('/service?bar=baz&fOO=bizz', 'foo=bizz&bar=baz')
+    False
+    >>> wms_query_eq('/1/2/3.png', '/1/2/3.png')
+    True
+    >>> wms_query_eq('/1/2/3.png', '/1/2/0.png')
+    False
+    """
+    from mapproxy.srs import bbox_equals
+    if path_from_query(expected) != path_from_query(actual):
+        return False
+
+    expected = query_to_dict(expected)
+    actual = query_to_dict(actual)
+    
+    if 'bbox' in expected and 'bbox' in actual:
+        expected = expected.copy()
+        expected_bbox = map(float, expected.pop('bbox').split(','))
+        actual = actual.copy()
+        actual_bbox = map(float, actual.pop('bbox').split(','))
+        if expected != actual:
+            return False
+        if not bbox_equals(expected_bbox, actual_bbox):
+            return False
+    else:
+        if expected != actual:
+            return False
+    
+    return True
+
 def query_eq(expected, actual):
     """
     >>> query_eq('bAR=baz&foo=bizz', 'foO=bizz&bar=baz')
     assert parts1[4] == parts2[4], '%s != %s (%s)' % (url1, url2, 'fragment')
 
 @contextmanager
-def mock_httpd(address, requests_responses, unordered=False):
-    t = ThreadedStopableHTTPServer(address, requests_responses, unordered=unordered)
+def mock_httpd(address, requests_responses, unordered=False, bbox_aware_query_comparator=False):
+    if bbox_aware_query_comparator:
+        query_comparator = wms_query_eq
+    else:
+        query_comparator = query_eq
+    t = ThreadedStopableHTTPServer(address, requests_responses, unordered=unordered,
+        query_comparator=query_comparator)
     t.start()
     try:
         yield

File mapproxy/test/system/fixture/combined_sources.yaml

     title: layer with transparent_color
     sources: [wms_iopts2]
 
+  - name: layer_fwdparams1
+    title: Uncached layer with fwdparams 1
+    sources: [wms_fwdparams1, wms_fwdparams2]
+  - name: layer_fwdparams2
+    title: Uncached layer with fwdparams 2
+    sources: [wms_fwdparams3]
 
 caches:
   wms_cache:
     req:
       url: http://localhost:42423/service_a
       layers: a_iopts_two
-      transparent: True
+      transparent: True
+
+  wms_fwdparams1:
+    type: wms
+    forward_req_params: ['time']
+    req:
+      url: http://localhost:42423/service_a
+      layers: a_one
+      transparent: True
+  wms_fwdparams2:
+    type: wms
+    forward_req_params: ['time', 'vendor']
+    req:
+      url: http://localhost:42423/service_a
+      layers: a_two
+      transparent: True
+  wms_fwdparams3:
+    type: wms
+    forward_req_params: ['time', 'vendor']
+    req:
+      url: http://localhost:42423/service_a
+      layers: a_three,a_four
+      transparent: True

File mapproxy/test/system/fixture/layer.yaml

   - name: direct
     title: Direct Layer
     sources: [direct]
+  - name: direct_fwd_params
+    title: Direct Forward Params Layer
+    sources: [direct_fwd_params]
   - name: wms_cache
     title: WMS Cache Layer with direct access from level 8
     sources: [wms_cache]
     req:
       url: http://localhost:42423/service
       layers: bar
+  direct_fwd_params:
+    type: wms
+    forward_req_params: ['time']
+    req:
+      url: http://localhost:42423/service
+      layers: bar
   wms_cache:
     type: wms
     supported_srs: ['EPSG:900913', 'EPSG:4326']
     req:
       url: http://localhost:42423/service
       layers: foo,bar
-      
+

File mapproxy/test/system/test_combined_sources.py

                 resp.content_type = 'image/png'
                 data = StringIO(resp.body)
                 assert is_png(data)
-                
+
+    def test_combined_same_fwd_req_params(self):
+        # merged to one request because all layers share the same time param in
+        # fwd_req_params config
+        with tmp_image((200, 200), format='png') as img:
+            img = img.read()
+            expected_req = [({'path': '/service_a?SERVICE=WMS&FORMAT=image%2Fpng'
+                                  '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles='
+                                  '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0'
+                                  '&WIDTH=200&transparent=True&TIME=20041012'
+                                  '&layers=a_one,a_two,a_three,a_four'},
+                             {'body': img, 'headers': {'content-type': 'image/png'}}),
+                            ]
+
+            with mock_httpd(('localhost', 42423), expected_req):
+                self.common_map_req.params.layers = 'layer_fwdparams1,layer_fwdparams2'
+                self.common_map_req.params['time'] = '20041012'
+                self.common_map_req.params.transparent = True
+                resp = self.app.get(self.common_map_req)
+                resp.content_type = 'image/png'
+                data = StringIO(resp.body)
+                assert is_png(data)
+
+    def test_combined_no_fwd_req_params(self):
+        # merged to one request because no vendor param is set
+        with tmp_image((200, 200), format='png') as img:
+            img = img.read()
+            expected_req = [({'path': '/service_a?SERVICE=WMS&FORMAT=image%2Fpng'
+                                  '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles='
+                                  '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0'
+                                  '&WIDTH=200&transparent=True'
+                                  '&layers=a_one,a_two,a_four'},
+                             {'body': img, 'headers': {'content-type': 'image/png'}}),
+                            ]
+
+            with mock_httpd(('localhost', 42423), expected_req):
+                self.common_map_req.params.layers = 'layer_fwdparams1,single'
+                self.common_map_req.params.transparent = True
+                resp = self.app.get(self.common_map_req)
+                resp.content_type = 'image/png'
+                data = StringIO(resp.body)
+                assert is_png(data)
+
+    def test_combined_mixed_fwd_req_params(self):
+        # not merged to one request because fwd_req_params are different
+        common_params = (r'/service_a?SERVICE=WMS&FORMAT=image%2Fpng'
+                                  '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles='
+                                  '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0'
+                                  '&WIDTH=200&transparent=True')
+
+        with tmp_image((200, 200), format='png') as img:
+            img = img.read()
+            expected_req = [({'path': common_params + '&layers=a_one&TIME=20041012'},
+                             {'body': img, 'headers': {'content-type': 'image/png'}}),
+                             ({'path': common_params + '&layers=a_two&TIME=20041012&VENDOR=foo'},
+                             {'body': img, 'headers': {'content-type': 'image/png'}}),
+                             ({'path': common_params + '&layers=a_four'},
+                             {'body': img, 'headers': {'content-type': 'image/png'}}),
+                            ]
+
+            with mock_httpd(('localhost', 42423), expected_req):
+                self.common_map_req.params.layers = 'layer_fwdparams1,single'
+                self.common_map_req.params['time'] = '20041012'
+                self.common_map_req.params['vendor'] = 'foo'
+                self.common_map_req.params.transparent = True
+                resp = self.app.get(self.common_map_req)
+                resp.content_type = 'image/png'
+                data = StringIO(resp.body)
+                assert is_png(data)
+

File mapproxy/test/system/test_wms.py

                       namespaces=dict(xlink="http://www.w3.org/1999/xlink"))[0],
             'http://localhost/service?')
         layer_names = set(xml.xpath('//Layer/Layer/Name/text()'))
-        expected_names = set(['direct', 'wms_cache', 'wms_cache_100', 'wms_cache_130',
-            'wms_cache_transparent', 'wms_merge', 'tms_cache', 'wms_cache_multi',
+        expected_names = set(['direct_fwd_params', 'direct', 'wms_cache',
+            'wms_cache_100', 'wms_cache_130', 'wms_cache_transparent',
+            'wms_merge', 'tms_cache', 'wms_cache_multi',
             'wms_cache_link_single', 'wms_cache_110'])
         eq_(layer_names, expected_names)
         assert validate_with_dtd(xml, dtd_name='wms/1.1.1/WMS_MS_Capabilities.dtd')
                 resp = self.app.get(self.common_map_req)
                 assert 35000 < int(resp.headers['Content-length']) < 75000
                 eq_(resp.content_type, 'image/png')
-    
+
+    def test_get_map_direct_fwd_params_layer(self):
+        expected_req = ({'path': r'/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fpng'
+                                    '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles='
+                                    '&VERSION=1.1.1&BBOX=-180.0,0.0,0.0,80.0'
+                                    '&WIDTH=200&TIME=20041012'},
+                        {'body': 'whatever'})
+        with mock_httpd(('localhost', 42423), [expected_req]):
+            self.common_map_req.params['layers'] = 'direct_fwd_params'
+            self.common_map_req.params['time'] = '20041012'
+            resp = self.app.get(self.common_map_req)
+
     def test_get_map_use_direct_from_level(self):
         with tmp_image((200, 200), format='png') as img:
             expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng'
                 assert resp.body == img.read()
                 is_png(img)
                 eq_(resp.content_type, 'image/png')
-        
+
     def test_get_map_use_direct_from_level_with_transform(self):
         with tmp_image((200, 200), format='png') as img:
             expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng'
-                                      '&REQUEST=GetMap&HEIGHT=303&SRS=EPSG%3A900913&styles='
-                                      '&VERSION=1.1.1&BBOX=1110868.98971,6444038.14317,1229263.18538,6623564.86585'
-                                      '&WIDTH=200'},
+                                      '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A900913&styles='
+                                      '&VERSION=1.1.1&BBOX=908822.944624,7004479.85652,920282.144964,7014491.63726'
+                                      '&WIDTH=229'},
                             {'body': img.read(), 'headers': {'content-type': 'image/png'}})
-            with mock_httpd(('localhost', 42423), [expected_req]):
-                self.common_map_req.params['bbox'] = '3570269,5540889,3643458,5653553'
-                self.common_map_req.params['srs'] = 'EPSG:31467'
+            with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True):
+                self.common_map_req.params['bbox'] = '444122.311736,5885498.04243,450943.508884,5891425.10484'
+                self.common_map_req.params['srs'] = 'EPSG:25832'
                 resp = self.app.get(self.common_map_req)
                 img.seek(0)
                 assert resp.body != img.read()
     def test_get_featureinfo_transformed(self):
         expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng'
                                   '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913'
-                                  '&BBOX=1110868.98971,6444038.14317,1229263.18538,6623564.86585'
+                                  '&BBOX=5197367.93088,5312902.73895,5311885.44223,5434731.78213'
                                   '&styles=&VERSION=1.1.1'
-                                  '&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=22'},
+                                  '&WIDTH=200&QUERY_LAYERS=foo,bar&X=14&Y=78'},
                         {'body': 'info', 'headers': {'content-type': 'text/plain'}})
         
         # out fi point at x=10,y=20
-        p_31467  = (3570269+10*(3643458 - 3570269)/200, 5540889+20*(5653553 - 5540889)/200)
+        p_25832  = (3570269+10*(3643458 - 3570269)/200, 5540889+20*(5614078 - 5540889)/200)
         # the transformed fi point at x=10,y=22
-        p_900913 = (1110868.98971+10*(1229263.18538 - 1110868.98971)/200,
-                    6444038.14317+22*(6623564.86585 - 6444038.14317)/200)
+        p_900913 = (5197367.93088+14*(5311885.44223 - 5197367.93088)/200,
+                    5312902.73895+78*(5434731.78213 - 5312902.73895)/200)
+
         # are they the same?
-        assert_almost_equal(SRS(31467).transform_to(SRS(900913), p_31467)[0], p_900913[0], -2)
-        assert_almost_equal(SRS(31467).transform_to(SRS(900913), p_31467)[1], p_900913[1], -2)
+        # check with tolerance: pixel resolution is ~570 and x/y position is rounded to pizel
+        assert abs(SRS(25832).transform_to(SRS(900913), p_25832)[0] - p_900913[0]) < 570/2
+        assert abs(SRS(25832).transform_to(SRS(900913), p_25832)[1] - p_900913[1]) < 570/2
         
-        with mock_httpd(('localhost', 42423), [expected_req]):
-            self.common_fi_req.params['bbox'] = '3570269,5540889,3643458,5653553'
-            self.common_fi_req.params['srs'] = 'EPSG:31467'
+        with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True):
+            self.common_fi_req.params['bbox'] = '3570269,5540889,3643458,5614078'
+            self.common_fi_req.params['srs'] = 'EPSG:25832'
+            self.common_fi_req.params.pos = 10, 20
             resp = self.app.get(self.common_fi_req)
             eq_(resp.content_type, 'text/plain')
             eq_(resp.body, 'info')
         assert_almost_equal(float(llbox.attrib['maxx']), 180.0, 6)
         
         layer_names = set(xml.xpath('//Layer/Layer/Name/text()'))
-        expected_names = set(['direct', 'wms_cache', 'wms_cache_100', 'wms_cache_130',
-            'wms_cache_transparent', 'wms_merge', 'tms_cache', 'wms_cache_multi',
+        expected_names = set(['direct_fwd_params', 'direct', 'wms_cache',
+            'wms_cache_100', 'wms_cache_130', 'wms_cache_transparent',
+            'wms_merge', 'tms_cache', 'wms_cache_multi',
             'wms_cache_link_single', 'wms_cache_110'])
         eq_(layer_names, expected_names)
         assert validate_with_dtd(xml, dtd_name='wms/1.1.0/capabilities_1_1_0.dtd')
         eq_(xml.xpath('/WMT_MS_Capabilities/Service/Title/text()')[0],
             u'MapProxy test fixture \u2603')
         layer_names = set(xml.xpath('//Layer/Layer/Name/text()'))
-        expected_names = set(['direct', 'wms_cache', 'wms_cache_100', 'wms_cache_130',
-            'wms_cache_transparent', 'wms_merge', 'tms_cache', 'wms_cache_multi',
+        expected_names = set(['direct_fwd_params', 'direct', 'wms_cache',
+            'wms_cache_100', 'wms_cache_130', 'wms_cache_transparent',
+            'wms_merge', 'tms_cache', 'wms_cache_multi',
             'wms_cache_link_single', 'wms_cache_110'])
         eq_(layer_names, expected_names)
         #TODO srs
 
         layer_names = set(xml.xpath('//wms:Layer/wms:Layer/wms:Name/text()',
                                     namespaces=ns130))
-        expected_names = set(['direct', 'wms_cache', 'wms_cache_100', 'wms_cache_130',
-            'wms_cache_transparent', 'wms_merge', 'tms_cache', 'wms_cache_multi',
+        expected_names = set(['direct_fwd_params', 'direct', 'wms_cache',
+            'wms_cache_100', 'wms_cache_130', 'wms_cache_transparent',
+            'wms_merge', 'tms_cache', 'wms_cache_multi',
             'wms_cache_link_single', 'wms_cache_110'])
         eq_(layer_names, expected_names)
         assert is_130_capa(xml)
                 self.common_map_req.params['format'] = 'image/png'
                 resp = self.app.get(self.common_map_req)
                 eq_(resp.content_type, 'image/png')
-            
+

File mapproxy/test/unit/test_auth.py

         req = Request(env)
         return wms_request(req)
 
+    def test_allow_all(self):
+        def auth(service, layers, **kw):
+            eq_(layers, 'layer1a layer1b'.split())
+            return { 'authorized': 'full' }
+        self.server.map(self.map_request('layer1', auth))
+        assert self.layers['layer1a'].requested
+        assert self.layers['layer1b'].requested
+
     def test_root_with_partial_sublayers(self):
         # filter out sublayer layer1b
         def auth(service, layers, **kw):

File mapproxy/test/unit/test_client.py

     def test_transform_fi_request_supported_srs(self):
         req = WMS111FeatureInfoRequest(url=TESTSERVER_URL + '/service?map=foo', param={'layers':'foo'})
         http = MockHTTPClient()
-        wms = WMSInfoClient(req, http_client=http, supported_srs=[SRS(31467)])
+        wms = WMSInfoClient(req, http_client=http, supported_srs=[SRS(25832)])
         fi_req = InfoQuery((8, 50, 9, 51), (512, 512),
                            SRS(4326), (256, 256), 'text/plain')
         
         
         assert_query_eq(http.requested[0],
             TESTSERVER_URL+'/service?map=foo&LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
-                           '&REQUEST=GetFeatureInfo&HEIGHT=512&SRS=EPSG%3A31467&info_format=text/plain'
+                           '&REQUEST=GetFeatureInfo&HEIGHT=512&SRS=EPSG%3A25832&info_format=text/plain'
                            '&query_layers=foo'
                            '&VERSION=1.1.1&WIDTH=512&STYLES=&x=259&y=255'
-                           '&BBOX=3428376.92835,5540409.81393,3500072.08248,5652124.61616')
+                           '&BBOX=428333.552496,5538630.70275,500000.0,5650300.78652')
 
     def test_transform_fi_request(self):
-        req = WMS111FeatureInfoRequest(url=TESTSERVER_URL + '/service?map=foo', param={'layers':'foo', 'srs': 'EPSG:31467'})
+        req = WMS111FeatureInfoRequest(url=TESTSERVER_URL + '/service?map=foo', param={'layers':'foo', 'srs': 'EPSG:25832'})
         http = MockHTTPClient()
         wms = WMSInfoClient(req, http_client=http)
         fi_req = InfoQuery((8, 50, 9, 51), (512, 512),
         
         assert_query_eq(http.requested[0],
             TESTSERVER_URL+'/service?map=foo&LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
-                           '&REQUEST=GetFeatureInfo&HEIGHT=512&SRS=EPSG%3A31467&info_format=text/plain'
+                           '&REQUEST=GetFeatureInfo&HEIGHT=512&SRS=EPSG%3A25832&info_format=text/plain'
                            '&query_layers=foo'
                            '&VERSION=1.1.1&WIDTH=512&STYLES=&x=259&y=255'
-                           '&BBOX=3428376.92835,5540409.81393,3500072.08248,5652124.61616')
+                           '&BBOX=428333.552496,5538630.70275,500000.0,5650300.78652')
 
 class TestWMSMapRequest100(object):
     def setup(self):

File mapproxy/util/lib.py

     win32=dict(
         paths = [os.path.dirname(os.__file__) + '/../../../DLLs'],
         exts = ['.dll']
-    )
+    ),
+    other=dict(
+        paths = [], # MAPPROXY_LIB_PATH will add paths here
+        exts = ['.so']
+    ),
 )
 
 additional_lib_path = os.environ.get('MAPPROXY_LIB_PATH')
         paths = locations_conf[sys.platform]['paths']
         exts = locations_conf[sys.platform]['exts']
         lib_path = find_library(lib_name, paths, exts)
+    else:
+        paths = locations_conf['other']['paths']
+        exts = locations_conf['other']['exts']
+        lib_path = find_library(lib_name, paths, exts)
     
     if lib_path:
         return CDLL(lib_path)
 def upload_final_sdist_command():
     sh('python setup.py egg_info -b "" -D sdist upload')
 
+def register_command():
+    sh('python setup.py egg_info -b "" -D register')
+
 def link_latest_command(ver=None):
     if ver is None:
         ver = version()
 
 setup(
     name='MapProxy',
-    version="1.4.0a",
+    version="1.5.0a",
     description='An accelerating proxy for web map services',
     long_description=long_description(7),
     author='Oliver Tonnhofer',