| 1 | # |
|---|
| 2 | # Copyright (C) 2007-2008 Camptocamp |
|---|
| 3 | # |
|---|
| 4 | # This file is part of MapFish Server |
|---|
| 5 | # |
|---|
| 6 | # MapFish Server is free software: you can redistribute it and/or modify |
|---|
| 7 | # it under the terms of the GNU Lesser General Public License as published by |
|---|
| 8 | # the Free Software Foundation, either version 3 of the License, or |
|---|
| 9 | # (at your option) any later version. |
|---|
| 10 | # |
|---|
| 11 | # MapFish Server is distributed in the hope that it will be useful, |
|---|
| 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 14 | # GNU Lesser General Public License for more details. |
|---|
| 15 | # |
|---|
| 16 | # You should have received a copy of the GNU Lesser General Public License |
|---|
| 17 | # along with MapFish Server. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 18 | # |
|---|
| 19 | |
|---|
| 20 | from mapfish.lib.filters import Filter |
|---|
| 21 | |
|---|
| 22 | from sqlalchemy.sql import func |
|---|
| 23 | |
|---|
| 24 | from shapely.geometry.point import Point |
|---|
| 25 | from shapely.geometry.polygon import Polygon |
|---|
| 26 | |
|---|
| 27 | class Spatial(Filter): |
|---|
| 28 | BOX = 'BOX' |
|---|
| 29 | WITHIN = 'WITHIN' |
|---|
| 30 | |
|---|
| 31 | def __init__(self, type, geom_column, **kwargs): |
|---|
| 32 | """Create a spatial filter. |
|---|
| 33 | |
|---|
| 34 | type |
|---|
| 35 | the type of filter to create. Possible values are |
|---|
| 36 | Spatial.BOX or Spatial.WITHIN. |
|---|
| 37 | |
|---|
| 38 | geom_column |
|---|
| 39 | the Column object corresponding to the geometry |
|---|
| 40 | column. |
|---|
| 41 | |
|---|
| 42 | \**kwargs |
|---|
| 43 | epsg |
|---|
| 44 | the EPSG code of the lon, lat or box values, see |
|---|
| 45 | below. |
|---|
| 46 | |
|---|
| 47 | for Spatial.BOX filter: |
|---|
| 48 | |
|---|
| 49 | box |
|---|
| 50 | a list of coordinates representing the bounding |
|---|
| 51 | box [xmin, ymin, xmax, ymax] |
|---|
| 52 | |
|---|
| 53 | for Spatial.WITHIN filter: |
|---|
| 54 | |
|---|
| 55 | lon |
|---|
| 56 | the x coordinate of the center of the search |
|---|
| 57 | region, the projection system of that coordinate |
|---|
| 58 | can be specified with the epsg key. |
|---|
| 59 | |
|---|
| 60 | lat |
|---|
| 61 | the y coordinate of the center of the search |
|---|
| 62 | region, the projection system of that coordinate |
|---|
| 63 | can be specified with the epsg key. |
|---|
| 64 | |
|---|
| 65 | tolerance |
|---|
| 66 | the tolerance around the center of the search |
|---|
| 67 | region, is expressed in the units associated with |
|---|
| 68 | the projection system of the lon/lat coordinates. |
|---|
| 69 | """ |
|---|
| 70 | self.type = type |
|---|
| 71 | self.geom_column = geom_column |
|---|
| 72 | self.values = kwargs |
|---|
| 73 | if 'epsg' in self.values: |
|---|
| 74 | self.epsg = self.values['epsg'] |
|---|
| 75 | else: |
|---|
| 76 | self.epsg = None |
|---|
| 77 | |
|---|
| 78 | def to_sql_expr(self): |
|---|
| 79 | if self.type == 'BOX': |
|---|
| 80 | return self.__to_sql_expr_box() |
|---|
| 81 | |
|---|
| 82 | if self.type == 'WITHIN': |
|---|
| 83 | return self.__to_sql_expr_within() |
|---|
| 84 | |
|---|
| 85 | def __to_sql_expr_box(self): |
|---|
| 86 | coords = self.values['box'] |
|---|
| 87 | |
|---|
| 88 | epsg = self.geom_column.type.srid |
|---|
| 89 | if self.epsg is not None: |
|---|
| 90 | epsg = self.epsg |
|---|
| 91 | |
|---|
| 92 | coords = map(float, coords) |
|---|
| 93 | |
|---|
| 94 | # define polygon from box |
|---|
| 95 | point_a = (coords[0], coords[1]) |
|---|
| 96 | point_b = (coords[0], coords[3]) |
|---|
| 97 | point_c = (coords[2], coords[3]) |
|---|
| 98 | point_d = (coords[2], coords[1]) |
|---|
| 99 | point_e = point_a |
|---|
| 100 | coords = (point_a, point_b, point_c, point_d, point_e) |
|---|
| 101 | poly = Polygon(coords) |
|---|
| 102 | pg_poly = func.geomfromtext(poly.wkt, epsg) |
|---|
| 103 | |
|---|
| 104 | if epsg != self.geom_column.type.srid: |
|---|
| 105 | pg_poly = func.transform(pg_poly, epsg) |
|---|
| 106 | |
|---|
| 107 | return func.st_intersects(self.geom_column, pg_poly) |
|---|
| 108 | |
|---|
| 109 | def __to_sql_expr_within(self): |
|---|
| 110 | lon = self.values['lon'] |
|---|
| 111 | lat = self.values['lat'] |
|---|
| 112 | tolerance = self.values['tolerance'] |
|---|
| 113 | |
|---|
| 114 | epsg = self.geom_column.type.srid |
|---|
| 115 | if self.epsg is not None: |
|---|
| 116 | epsg = self.epsg |
|---|
| 117 | |
|---|
| 118 | point = Point(lon, lat) |
|---|
| 119 | pg_point = func.pointfromtext(point.wkt, epsg) |
|---|
| 120 | |
|---|
| 121 | geom = self.geom_column |
|---|
| 122 | if epsg != self.geom_column.type.srid: |
|---|
| 123 | geom = func.transform(geom, epsg) |
|---|
| 124 | |
|---|
| 125 | if tolerance is not None and tolerance > 0: |
|---|
| 126 | e = func.distance(geom, pg_point) < tolerance |
|---|
| 127 | else: |
|---|
| 128 | e = func.within(pg_point, geom) |
|---|
| 129 | |
|---|
| 130 | return e |
|---|