Changeset 3757 for framework

Show
Ignore:
Timestamp:
03/28/11 21:00:24 (14 months ago)
Author:
elemoine
Message:

rely on Geo Alchemy? functions._within_distance, r=bbinet (closes #613)

Location:
framework/server/trunk
Files:
7 modified

Legend:

Unmodified
Added
Removed
  • framework/server/trunk/mapfish/protocol.py

    r3724 r3757  
    4343from geojson import Feature, FeatureCollection, loads, GeoJSON 
    4444 
    45 from mapfish.sqlalchemygeom import within_distance 
    46  
    4745 
    4846def create_geom_filter(request, mapped_class, **kwargs): 
     
    9088 
    9189    if 'additional_params' in kwargs: 
    92         return within_distance(geom_column, wkb_geometry, tolerance, 
    93                                kwargs['additional_params']) 
     90        return functions._within_distance(geom_column, wkb_geometry, tolerance, 
     91                                          kwargs['additional_params']) 
    9492    else: 
    95         return within_distance(geom_column, wkb_geometry, tolerance) 
    96   
     93        return functions._within_distance(geom_column, wkb_geometry, tolerance) 
     94 
    9795def create_attr_filter(request, mapped_class): 
    9896    """Create an ``and_`` SQLAlchemy filter (a ClauseList object) based 
  • framework/server/trunk/mapfish/sqlalchemygeom.py

    r3724 r3757  
    222222                       properties=attributes, 
    223223                       bbox=None if geometry is None else geometry.bounds) 
    224  
    225  
    226 class within_distance(BaseFunction): 
    227     """This class is used as SQLAlchemy function to query features that are  
    228     within a certain distance of a geometry.  
    229     When it is used inside a query, the SQLAlchemy compiler calls the  
    230     method __compile_within_distance. 
    231     """ 
    232     pass 
    233  
    234 @compiles(within_distance) 
    235 def __compile_within_distance(element, compiler, **kw): 
    236     if isinstance(compiler.dialect, PGDialect): 
    237         function = __within_distance_pg 
    238     elif isinstance(compiler.dialect, MySQLDialect): 
    239         function = __within_distance_mysql 
    240     elif isinstance(compiler.dialect, SQLiteDialect): 
    241         function = __within_distance_spatialite 
    242     elif isinstance(compiler.dialect, OracleDialect): 
    243         function = __within_distance_oracle 
    244     else: 
    245         raise NotImplementedError("Operation 'within_distance' is not supported by '%s'" % (compiler.dialect)) 
    246      
    247     arguments = list(element.arguments) 
    248     return compiler.process(function(compiler, parse_clause(arguments.pop(0), compiler),  
    249                                      parse_clause(arguments.pop(0), compiler), arguments.pop(0), *arguments)) 
    250      
    251 def __within_distance_pg(compiler, geom1, geom2, distance): 
    252     """Implementation of within_distance for PostGIS 
    253      
    254     ST_DWithin in early versions of PostGIS 1.3 does not work when 
    255     distance = 0. So we are directly using the (correct) internal definition. 
    256     Note that the definition changed in version 1.3.4, see also: 
    257     http://postgis.refractions.net/docs/ST_DWithin.html 
    258     """ 
    259     return and_(func.ST_Expand(geom2, distance).op('&&')(geom1), 
    260                     func.ST_Expand(geom1, distance).op('&&')(geom2), 
    261                     func.ST_Distance(geom1, geom2) <= distance) 
    262  
    263 def __within_distance_mysql(compiler, geom1, geom2, distance): 
    264     """Implementation of within_distance for MySQL 
    265      
    266     MySQL does not support the function distance, so we are doing 
    267     a kind of "mbr_within_distance". 
    268     The MBR of 'geom2' is expanded with the amount of 'distance' by 
    269     manually changing the coordinates. Then we test if 'geom1' intersects 
    270     this expanded MBR. 
    271     """ 
    272     mbr = func.ExteriorRing(func.Envelope(geom2)) 
    273      
    274     lower_left = func.StartPoint(mbr) 
    275     upper_right = func.PointN(mbr, 3) 
    276      
    277     xmin = func.X(lower_left) 
    278     ymin = func.Y(lower_left) 
    279     xmax = func.X(upper_right) 
    280     ymax = func.Y(upper_right) 
    281      
    282     return func.Intersects( 
    283             geom1, 
    284             func.GeomFromText( 
    285                 func.Concat('Polygon((', 
    286                        xmin - distance, ' ', ymin - distance, ',', 
    287                        xmax + distance, ' ', ymin - distance, ',', 
    288                        xmax + distance, ' ', ymax + distance, ',', 
    289                        xmin - distance, ' ', ymax + distance, ',', 
    290                        xmin - distance, ' ', ymin - distance, '))'), func.srid(geom2) 
    291                 )                                               
    292             ) 
    293  
    294 def __within_distance_spatialite(compiler, geom1, geom2, distance): 
    295     """Implementation of within_distance for Spatialite 
    296     """ 
    297     if isinstance(geom1, GeometryExtensionColumn) and geom1.type.spatial_index and SQLiteSpatialDialect.supports_rtree(compiler.dialect): 
    298         """If querying on a geometry column that also has a spatial index, 
    299         then make use of this index. 
    300          
    301         see: http://www.gaia-gis.it/spatialite/spatialite-tutorial-2.3.1.html#t8 and 
    302         http://groups.google.com/group/spatialite-users/browse_thread/thread/34609c7a711ac92d/7688ced3f909039c?lnk=gst&q=index#f6dbc235471574db 
    303         """ 
    304         return and_( 
    305                     func.Distance(geom1, geom2) <= distance, 
    306                     table(geom1.table.fullname, column("rowid")).c.rowid.in_( 
    307                         select([table("idx_%s_%s" % (geom1.table.fullname, geom1.key), column("pkid")).c.pkid]).where( 
    308                             and_(text('xmin') >= func.MbrMinX(geom2) - distance, 
    309                             and_(text('xmax') <= func.MbrMaxX(geom2) + distance, 
    310                             and_(text('ymin') >= func.MbrMinY(geom2) - distance, 
    311                                  text('ymax') <= func.MbrMaxY(geom2) + distance))) 
    312                             ) 
    313                         ) 
    314                     ) 
    315          
    316     else: 
    317         return func.Distance(geom1, geom2) <= distance 
    318      
    319  
    320  
    321 def __within_distance_oracle(compiler, geom1, geom2, distance, additional_params={}): 
    322     """Implementation of within_distance for Oracle 
    323      
    324     If the first parameter is a geometry column, then the Oracle operator SDO_WITHIN_DISTANCE 
    325     is called and Oracle makes use of the spatial index of this column. 
    326      
    327     If the first parameter is not a geometry column but a function, which is the case when a coordinate  
    328     transformation had to be added by the spatial filter, then the function SDO_GEOM.WITHIN_DISTANCE 
    329     is called. SDO_GEOM.WITHIN_DISTANCE does not make use of a spatial index and requires 
    330     additional parameters: either a tolerance value or a dimension information array (DIMINFO) 
    331     for both geometries. These parameters can be specified when defining the spatial filter, e.g.:: 
    332      
    333         additional_params={'tol': '0.005'} 
    334          
    335         or 
    336          
    337         from sqlalchemy.sql.expression import text 
    338         diminfo = text("MDSYS.SDO_DIM_ARRAY("\ 
    339             "MDSYS.SDO_DIM_ELEMENT('LONGITUDE', -180, 180, 0.000000005),"\ 
    340             "MDSYS.SDO_DIM_ELEMENT('LATITUDE', -90, 90, 0.000000005)"\ 
    341             ")") 
    342         additional_params={'dim1': diminfo, 'dim2': diminfo} 
    343          
    344         filter = create_default_filter(request, Spot, additional_params=additional_params) 
    345         proto.count(request, filter=filter) 
    346          
    347     For its distance calculation Oracle by default uses meter as unit for geodetic data (like EPSG:4326)  
    348     and otherwise the 'unit of measurement associated with the data'. The unit used for the 'distance' value 
    349     can be changed by adding an entry to 'additional_params'. Valid units are defined in the  
    350     view 'sdo_dist_units':: 
    351      
    352         additional_params={'params': 'unit=km'} 
    353          
    354     SDO_WITHIN_DISTANCE accepts further parameters, which can also be set using the name 'params' 
    355     together with the unit:: 
    356      
    357         additional_params={'params': 'unit=km max_resolution=10'} 
    358          
    359      
    360     Valid options for 'additional_params' are: 
    361      
    362         params 
    363             A String containing additional parameters, for example the unit. 
    364              
    365         tol 
    366             The tolerance value used for the SDO_GEOM.WITHIN_DISTANCE function call. 
    367              
    368         dim1 and dim2 
    369             If the parameter 'tol' is not set, these two parameters have to be set. 'dim1' is the DIMINFO 
    370             for the first geometry (the reprojected geometry column) and 'dim2' is the DIMINFO for 
    371             the second geometry (the input geometry from the request). Values for 'dim1' and 'dim2' 
    372             have to be SQLAlchemy expressions, either literal text (text(..)) or a select query. 
    373              
    374     Note that 'tol' or 'dim1'/'dim2' only have to be set when the input geometry from the request  
    375     uses a different CRS than the geometry column! 
    376      
    377      
    378     SDO_WITHIN_DISTANCE: http://download.oracle.com/docs/cd/E11882_01/appdev.112/e11830/sdo_operat.htm#i77653 
    379     SDO_GEOM.WITHIN_DISTANCE: http://download.oracle.com/docs/cd/E11882_01/appdev.112/e11830/sdo_objgeom.htm#i856373 
    380     DIMINFO: http://download.oracle.com/docs/cd/E11882_01/appdev.112/e11830/sdo_objrelschema.htm#i1010905 
    381     TOLERANCE: http://download.oracle.com/docs/cd/E11882_01/appdev.112/e11830/sdo_intro.htm#i884589 
    382      
    383     """ 
    384     params = additional_params.get('params', '') 
    385      
    386     if isinstance(geom1, Column): 
    387         return (func.SDO_WITHIN_DISTANCE(geom1, geom2,  
    388                                      'distance=%s %s' % (distance, params)) == 'TRUE') 
    389     else: 
    390         dim1 = additional_params.get('dim1', None) 
    391         dim2 = additional_params.get('dim2', None) 
    392          
    393         if dim1 is not None and dim2 is not None: 
    394             return (func.SDO_GEOM.WITHIN_DISTANCE(geom1, dim1,  
    395                                              distance,  
    396                                              geom2, dim2,  
    397                                              params) == 'TRUE') 
    398         else: 
    399             tol = additional_params.get('tol', None) 
    400              
    401             if tol is not None: 
    402                 return (func.SDO_GEOM.WITHIN_DISTANCE(geom1,  
    403                                              distance,  
    404                                              geom2, 
    405                                              tol,  
    406                                              params) == 'TRUE') 
    407             else: 
    408                 raise Exception('No dimension information ("dim1" and "dim2") or '\ 
    409                                 'tolerance value ("tol") specified for calling '\ 
    410                                 'SDO_GEOM.WITHIN_DISTANCE on Oracle, which is '\ 
    411                                 'required when reprojecting.') 
  • framework/server/trunk/mapfish/tests/test_mysql.py

    r3715 r3757  
    359359 
    360360        proto.read(FakeRequest({}), id=-1) 
    361  
    362  
    363     def test_within_distance(self): 
    364         """Test the MySQL implementation for within_distance""" 
    365         from mapfish.sqlalchemygeom import within_distance 
    366          
    367         eq_(session.scalar(within_distance('POINT(-88.9139332929936 42.5082802993631)',  
    368                                                                'POINT(-88.9139332929936 35.5082802993631)', 10)), True) 
    369         ok_(session.scalar(within_distance('Point(0 0)', 'Point(0 0)', 0))) 
    370         ok_(session.scalar(within_distance('Point(0 0)',  
    371                                            'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0))) 
    372         ok_(session.scalar(within_distance('Point(5 5)',  
    373                                            'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0))) 
    374         ok_(session.scalar(within_distance('Point(6 5)',  
    375                                            'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 1))) 
    376         ok_(session.scalar(within_distance('Polygon((0 0, 1 0, 1 8, 0 8, 0 0))',  
    377                                            'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0))) 
  • framework/server/trunk/mapfish/tests/test_oracle.py

    r3724 r3757  
    388388         
    389389        proto.read(FakeRequest({}), id=-1) 
    390  
    391  
    392     def test_within_distance(self): 
    393         """Test the Oracle implementation for within_distance 
    394          
    395         Because SDO_WITHIN_DISTANCE requires a spatial index for the geometry used 
    396         as first parameter, we have to insert out test geometries into tables, 
    397         unlike to the other databases. 
    398          
    399         Note that Oracle uses meter as unit for the tolerance value for geodetic coordinate 
    400         systems (like 4326)! 
    401         """ 
    402         from mapfish.sqlalchemygeom import within_distance 
    403          
    404         # test if SDO_WITHIN_DISTANCE is called correctly 
    405         eq_(session.query(Spot).filter(within_distance(Spot.spot_location, 'POINT(0 0)', 0)).count(), 1) 
    406         eq_(session.query(Spot).filter(within_distance(Spot.spot_location, 'POINT(0 0)', 0.1)).count(), 1) 
    407         eq_(session.query(Spot).filter(within_distance(Spot.spot_location, 'POINT(9 9)', 100000)).count(), 0) 
    408          
    409         eq_(session.query(Spot).filter(within_distance(Spot.spot_location,  
    410                                                        'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0)).count(), 3) 
    411          
    412         eq_(session.query(Spot).filter(within_distance(Spot.spot_location,  
    413                                                        'Polygon((-10 -10, 10 -10, 10 10, -10 10, -10 -10))', 0)).count(), 4) 
    414          
    415         eq_(session.query(Spot).filter(within_distance(Spot.spot_location,  
    416                                                        'Polygon((-10 -10, 10 -10, 10 10, -10 10, -10 -10))', 200000)).count(), 5) 
    417  
    418         # test if SDO_GEOM.WITHIN_DISTANCE is called correctly 
    419         eq_(session.scalar(select([text('1')], from_obj=['dual']).where( 
    420                                                     within_distance('POINT(0 0)', 'POINT(0 0)', 0,  
    421                                                                     {'tol' : 0.00000005}))), 1) 
    422         eq_(session.scalar(select([text('1')], from_obj=['dual']).where( 
    423                                                     within_distance('POINT(0 0)', 'POINT(0 0)', 0,  
    424                                                                     {'dim1' : text(diminfo), 
    425                                                                      'dim2' : text(diminfo)}))), 1) 
  • framework/server/trunk/mapfish/tests/test_postgis.py

    r3743 r3757  
    355355         
    356356        proto.read(FakeRequest({}), id=-1) 
    357  
    358  
    359     def test_within_distance(self): 
    360         """Test the PostGIS implementation for within_distance""" 
    361         from mapfish.sqlalchemygeom import within_distance 
    362          
    363         eq_(session.scalar(within_distance('POINT(-88.9139332929936 42.5082802993631)',  
    364                                                                'POINT(-88.9139332929936 35.5082802993631)', 10)), True) 
    365         ok_(session.scalar(within_distance('Point(0 0)', 'Point(0 0)', 0))) 
    366         ok_(session.scalar(within_distance('Point(0 0)',  
    367                                            'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0))) 
    368         ok_(session.scalar(within_distance('Point(5 5)',  
    369                                            'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0))) 
    370         ok_(session.scalar(within_distance('Point(6 5)',  
    371                                            'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 1))) 
    372         ok_(session.scalar(within_distance('Polygon((0 0, 1 0, 1 8, 0 8, 0 0))',  
    373                                            'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0))) 
  • framework/server/trunk/mapfish/tests/test_spatialite.py

    r3715 r3757  
    371371         
    372372        proto.read(FakeRequest({}), id=-1) 
    373  
    374  
    375     def test_within_distance(self): 
    376         """Test the Spatialite implementation for within_distance""" 
    377         from mapfish.sqlalchemygeom import within_distance 
    378          
    379         eq_(session.scalar(within_distance('POINT(-88.9139332929936 42.5082802993631)',  
    380                                                                'POINT(-88.9139332929936 35.5082802993631)', 10)), True) 
    381         ok_(session.scalar(within_distance('Point(0 0)', 'Point(0 0)', 0))) 
    382         ok_(session.scalar(within_distance('Point(0 0)',  
    383                                            'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0))) 
    384         ok_(session.scalar(within_distance('Point(5 5)',  
    385                                            'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0))) 
    386         ok_(session.scalar(within_distance('Point(6 5)',  
    387                                            'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 1))) 
    388         ok_(session.scalar(within_distance('Polygon((0 0, 1 0, 1 8, 0 8, 0 0))',  
    389                                            'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0))) 
  • framework/server/trunk/setup.py

    r3659 r3757  
    3939                'Pylons>=1.0,<=1.0.99', 
    4040                'geojson>=1.0,<=1.0.99', 
    41                 'GeoAlchemy>=0.4,<=0.4.99'] 
     41                'GeoAlchemy>=0.5,<=0.5.99'] 
    4242 
    4343# Shapely and Psychopg2 cannot be installed on Windows via python eggs