diff --git a/.travis.yml b/.travis.yml index 46bf019..8e84dac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,9 @@ +dist: xenial language: python python: - "2.7" + - "3.7" services: - postgres @@ -11,18 +13,18 @@ env: - DB=generic - DB=postgres POSTGIS_VERSION=1.5 #- DB=postgres POSTGIS_VERSION=2 - #- DB=mysql + - DB=mysql install: # install GeoAlchemy in editable mode - pip install -e . - # install nose and rednose for colorful output - - pip install nose rednose + # install pytest and nose + - pip install pytest nose # install individual dependencies - if [[ "$DB" == "postgres" ]]; then pip install psycopg2; fi - - if [[ "$DB" == "mysql" ]]; then pip install MySQL-python; fi + - if [[ "$DB" == "mysql" ]]; then pip install mysqlclient; fi # install PostGIS 2.x - if [[ "$DB" == "postgres" ]] && [[ "$POSTGIS_VERSION" != "1.5" ]]; then sudo apt-add-repository -y ppa:sharpie/for-science; fi @@ -33,10 +35,6 @@ install: - if [[ "$DB" == "postgres" ]]; then sudo apt-get install postgresql-9.1-postgis -q; fi before_script: - # activate verbose and rednose mode - - export NOSE_REDNOSE=1 - - export NOSE_VERBOSE=2 - # create database user "gis" - if [[ "$DB" == "postgres" ]]; then psql -U postgres -c "CREATE ROLE gis PASSWORD 'gis' SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN;"; fi - if [[ "$DB" == "mysql" ]]; then mysql --user=root -e "CREATE USER 'gis'@'localhost' IDENTIFIED BY 'gis';"; fi @@ -53,11 +51,11 @@ before_script: script: # run tests - - if [[ "$DB" == "generic" ]]; then python geoalchemy/tests/test_dialect.py; fi - - if [[ "$DB" == "generic" ]]; then python geoalchemy/tests/test_type.py; fi + - if [[ "$DB" == "generic" ]]; then pytest geoalchemy/tests/test_dialect.py; fi + - if [[ "$DB" == "generic" ]]; then pytest geoalchemy/tests/test_type.py; fi - - if [[ "$DB" == "postgres" ]]; then python geoalchemy/tests/test_postgis.py; fi - - if [[ "$DB" == "mysql" ]]; then python geoalchemy/tests/test_mysql.py; fi + - if [[ "$DB" == "postgres" ]]; then pytest geoalchemy/tests/test_postgis.py; fi + - if [[ "$DB" == "mysql" ]]; then pytest --tb=short geoalchemy/tests/test_mysql.py; fi notifications: email: diff --git a/doc/testing.rst b/doc/testing.rst index 87bdcaa..77de8e7 100644 --- a/doc/testing.rst +++ b/doc/testing.rst @@ -4,7 +4,7 @@ Testing GeoAlchemy Requirements ------------ -In order to test GeoAlchemy you must have nose tools installed. Also you should +In order to test GeoAlchemy you must have pytest installed. Also you should have installed SQLAlchemy, GeoAlchemy and all the DB-APIs. Refer the `installation docs`_ for details. In case you are interested in testing against only one DB-API (say Postgres), you need not have the other @@ -28,19 +28,19 @@ would use an in-memory database. Running the Tests ----------------- -The tests can be run using nosetests as: +The tests can be run using pytest as: .. code-block:: bash - $ nosetests + $ pytest For running the tests against a single database at a time: .. code-block:: bash - $ nosetests geoalchemy/tests/test_postgis.py - $ nosetests geoalchemy/tests/test_mysql.py - $ nosetests geoalchemy/tests/test_spatialite.py + $ pytest geoalchemy/tests/test_postgis.py + $ pytest geoalchemy/tests/test_mysql.py + $ pytest geoalchemy/tests/test_spatialite.py .. _`installation docs`: install.html .. _`Spatialite specific notes`: tutorial.html#notes-for-spatialite diff --git a/examples/mysql.py b/examples/mysql.py index 47ebbca..8de7478 100644 --- a/examples/mysql.py +++ b/examples/mysql.py @@ -8,7 +8,7 @@ from geoalchemy import (Geometry, Point, LineString, Polygon, GeometryColumn, GeometryDDL, WKTSpatialElement) -engine = create_engine('mysql://gis:gis@localhost/gis', echo=True) +engine = create_engine('mysql+mysqldb://gis:gis@localhost/gis', echo=True) metadata = MetaData(engine) session = sessionmaker(bind=engine)() Base = declarative_base(metadata=metadata) @@ -36,7 +36,7 @@ class Spot(Base): # enable the DDL extension, which allows CREATE/DROP operations # to work correctly. This is not needed if working with externally -# defined tables. +# defined tables. GeometryDDL(Road.__table__) GeometryDDL(Lake.__table__) GeometryDDL(Spot.__table__) diff --git a/geoalchemy/base.py b/geoalchemy/base.py index ace065d..0c2abd2 100644 --- a/geoalchemy/base.py +++ b/geoalchemy/base.py @@ -1,3 +1,4 @@ +import six from sqlalchemy.orm.properties import ColumnProperty from sqlalchemy.sql import expression, not_ from sqlalchemy.sql.expression import ColumnClause, literal @@ -8,8 +9,8 @@ from sqlalchemy.types import UserDefinedType from sqlalchemy.ext.compiler import compiles -from utils import from_wkt -from functions import functions, _get_function, BaseFunction +from .utils import from_wkt +from .functions import functions, _get_function, BaseFunction # Base classes for geoalchemy @@ -23,7 +24,7 @@ def __str__(self): def __repr__(self): return "<%s at 0x%x; %r>" % (self.__class__.__name__, id(self), self.desc) - + def __getattr__(self, name): return getattr(functions, name)(self) # @@ -31,15 +32,15 @@ def __get_wkt(self, session): """This method converts the object into a WKT geometry. It takes into account that WKTSpatialElement does not have to make a new query to retrieve the WKT geometry. - + """ if isinstance(self, WKTSpatialElement): # for WKTSpatialElement we don't need to make a new query - return self.desc + return self.desc elif isinstance(self.desc, WKTSpatialElement): return self.desc.desc else: - return session.scalar(self.wkt) + return session.scalar(self.wkt) def geom_type(self, session): wkt = self.__get_wkt(session) @@ -52,19 +53,19 @@ def coords(self, session): class WKTSpatialElement(SpatialElement, Function): """Represents a Geometry value expressed within application code; i.e. in the OGC Well Known Text (WKT) format. - - Extends expression.Function so that in a SQL expression context the value - is interpreted as 'GeomFromText(value)' or as the equivalent function in the + + Extends expression.Function so that in a SQL expression context the value + is interpreted as 'GeomFromText(value)' or as the equivalent function in the currently used database. - + """ - + def __init__(self, desc, srid=4326, geometry_type='GEOMETRY'): - assert isinstance(desc, basestring) + assert isinstance(desc, six.string_types) self.desc = desc self.srid = srid self.geometry_type = geometry_type - + Function.__init__(self, "") @property @@ -75,75 +76,75 @@ def geom_wkt(self): @compiles(WKTSpatialElement) def __compile_wktspatialelement(element, compiler, **kw): function = _get_function(element, compiler, (element.desc, element.srid), kw.get('within_columns_clause', False)) - + return compiler.process(function) class WKBSpatialElement(SpatialElement, Function): """Represents a Geometry value as expressed in the OGC Well Known Binary (WKB) format. - - Extends expression.Function so that in a SQL expression context the value - is interpreted as 'GeomFromWKB(value)' or as the equivalent function in the + + Extends expression.Function so that in a SQL expression context the value + is interpreted as 'GeomFromWKB(value)' or as the equivalent function in the currently used database . - + """ - + def __init__(self, desc, srid=4326, geometry_type='GEOMETRY'): - assert isinstance(desc, (basestring, buffer)) + assert isinstance(desc, (six.string_types, buffer)) self.desc = desc self.srid = srid self.geometry_type = geometry_type - + Function.__init__(self, "") @compiles(WKBSpatialElement) def __compile_wkbspatialelement(element, compiler, **kw): - from geoalchemy.dialect import DialectManager + from geoalchemy.dialect import DialectManager database_dialect = DialectManager.get_spatial_dialect(compiler.dialect) - - function = _get_function(element, compiler, (database_dialect.bind_wkb_value(element), + + function = _get_function(element, compiler, (database_dialect.bind_wkb_value(element), element.srid), kw.get('within_columns_clause', False)) - + return compiler.process(function) class DBSpatialElement(SpatialElement, Function): - """This class can be used to wrap a geometry returned by a + """This class can be used to wrap a geometry returned by a spatial database operation. - - For example:: - + + For example:: + element = DBSpatialElement(session.scalar(r.geom.buffer(10.0))) session.scalar(element.wkt) - + """ - + def __init__(self, desc): self.desc = desc Function.__init__(self, "", desc) @compiles(DBSpatialElement) def __compile_dbspatialelement(element, compiler, **kw): - function = _get_function(element, compiler, [literal(element.desc)], + function = _get_function(element, compiler, [literal(element.desc)], kw.get('within_columns_clause', False)) - + return compiler.process(function) class PersistentSpatialElement(SpatialElement): """Represents a Geometry value loaded from the database.""" - + def __init__(self, desc): self.desc = desc - - @property + + @property def geom_wkb(self): if self.desc is not None and isinstance(self.desc, WKBSpatialElement): return self.desc.desc else: return None - - @property + + @property def geom_wkt(self): if self.desc is not None and isinstance(self.desc, WKTSpatialElement): return self.desc.desc @@ -167,7 +168,7 @@ def __init__(self, dimension=2, srid=4326, spatial_index=True, def get_col_spec(self): return self.name - + def bind_processor(self, dialect): def process(value): if value is not None: @@ -180,7 +181,7 @@ def process(value): else: return value return process - + def result_processor(self, dialect, coltype=None): def process(value): if value is not None: @@ -205,7 +206,7 @@ def _to_gis(value, srid_db): if isinstance(value.desc, (WKBSpatialElement, WKTSpatialElement)): return _check_srid(value.desc, srid_db) return _check_srid(value, srid_db) - elif isinstance(value, basestring): + elif isinstance(value, six.string_types): return _check_srid(WKTSpatialElement(value), srid_db) elif isinstance(value, expression.ClauseElement): return value @@ -213,7 +214,7 @@ def _to_gis(value, srid_db): return None else: raise Exception("Invalid type") - + def _check_srid(spatial_element, srid_db): """Check if the SRID of the spatial element which we are about to insert into the database equals the SRID used for the geometry column. @@ -222,7 +223,7 @@ def _check_srid(spatial_element, srid_db): if srid_db is None or not hasattr(spatial_element, 'srid') or \ isinstance(spatial_element.srid, BaseFunction): return spatial_element - + if spatial_element.srid == srid_db: return spatial_element else: @@ -235,10 +236,10 @@ class RawColumn(ColumnClause): def __init__(self, column): self.column = column ColumnClause.__init__(self, column.name, column.table) - + def _make_proxy(self, selectable, name=None): return self.column._make_proxy(selectable, name) - + @compiles(RawColumn) def __compile_rawcolumn(rawcolumn, compiler, **kw): return compiler.visit_column(rawcolumn.column) @@ -246,31 +247,31 @@ def __compile_rawcolumn(rawcolumn, compiler, **kw): class SpatialComparator(ColumnProperty.Comparator): """Intercepts standard Column operators on mapped class attributes and overrides their behavior. - - A comparator class makes sure that queries like + + A comparator class makes sure that queries like "session.query(Lake).filter(Lake.lake_geom.gcontains(..)).all()" can be executed. """ - + @property def RAW(self): - """For queries like 'select extent(spots.spot_location) from spots' the + """For queries like 'select extent(spots.spot_location) from spots' the geometry column should not be surrounded by 'AsBinary(..)'. If 'RAW' is called on a geometry column, this column wont't be converted to WKB:: - - session.query(func.extent(Spot.spot_location.RAW)).first() - + + session.query(func.extent(Spot.spot_location.RAW)).first() + """ return RawColumn(self.__clause_element__()) - + def __getattr__(self, name): return getattr(functions, name)(self) - + # override the __eq__() operator (allows to use '==' on geometries) - def __eq__(self, other): + def __eq__(self, other): if other is None: return self.op("IS")(None) return functions.equals(self, other) - + def __ne__(self, other): if other is None: return self.op("IS NOT")(None) diff --git a/geoalchemy/dialect.py b/geoalchemy/dialect.py index 0f00754..cf3afc6 100644 --- a/geoalchemy/dialect.py +++ b/geoalchemy/dialect.py @@ -1,3 +1,4 @@ +import six from sqlalchemy.dialects.postgresql.base import PGDialect from sqlalchemy.dialects.sqlite.base import SQLiteDialect from sqlalchemy.dialects.mysql.base import MySQLDialect @@ -9,13 +10,13 @@ DBSpatialElement class SpatialDialect(object): - """This class bundles all required classes and methods to support + """This class bundles all required classes and methods to support a database dialect. It is supposed to be subclassed. The child classes must be added to DialectManager.__initialize_dialects(), so that they can be used. - + """ - + __functions = { functions.wkt: 'AsText', WKTSpatialElement : 'GeomFromText', @@ -63,94 +64,94 @@ class SpatialDialect(object): functions._within_distance: lambda compiler, geom1, geom2, dist: func.DWithin(geom1, geom2, dist) } - + def get_function(self, function_class): """This method is called to translate a generic function into a database dialect specific function. - + It either returns a string, a list or a method that returns a Function object. - + - String: A function name, e.g.:: - + functions.wkb: 'SDO_UTIL.TO_WKBGEOMETRY' - + - List: A list of function names that are called cascaded, e.g.:: - + functions.wkt: ['TO_CHAR', 'SDO_UTIL.TO_WKTGEOMETRY'] is compiled as: TO_CHAR(SDO_UTIL.TO_WKTGEOMETRY(..)) - + - Method: A method that accepts a list/set of arguments and returns a Function object, e.g.:: - + functions.equals : lambda params, within_column_clause : (func.SDO_EQUAL(*params) == 'TRUE') - + """ if self._get_function_mapping() is not None: if function_class in self._get_function_mapping(): if self._get_function_mapping()[function_class] is None: - raise NotImplementedError("Operation '%s' is not supported for '%s'" - % (function_class.__name__, self.__class__.__name__)) + raise NotImplementedError("Operation '%s' is not supported for '%s'" + % (function_class.__name__, self.__class__.__name__)) else: return self._get_function_mapping()[function_class] - + return SpatialDialect.__functions[function_class] - - def is_member_function(self, function_class): - """Returns True if the passed-in function should be called as member + + def is_member_function(self, function_class): + """Returns True if the passed-in function should be called as member function. E.g. ``Point.the_geom.dims`` is compiled as ``points.the_geom.Get_Dims()``. """ return False - - def is_property(self, function_class): - """Returns True if the passed-in function should be called as property, + + def is_property(self, function_class): + """Returns True if the passed-in function should be called as property, E.g. ``Point.the_geom.x`` is compiled as (for MS Server) ``points.the_geom.x``. """ return False - + def _get_function_mapping(self): """This method can be overridden in subclasses, to set a database specific name for a function, or to add new functions, that are only supported by the database. - + """ return None - + def process_result(self, value, type): """This method is called when a geometry value from the database is transformed into a SpatialElement object. It receives an WKB binary sequence, either as Buffer (for PostGIS - and Spatialite), a String (for MySQL) or a cx_Oracle.LOB (for Oracle), and is supposed to return a + and Spatialite), a String (for MySQL) or a cx_Oracle.LOB (for Oracle), and is supposed to return a subclass of SpatialElement, e.g. PGSpatialElement or MySQLSpatialElement. - + """ raise NotImplementedError("Method SpatialDialect.process_result must be implemented in subclasses.") - + def process_wkb(self, value): """This method is called from functions._WKBType.process_result_value() to convert - the result of functions.wkb() into a usable format. - + the result of functions.wkb() into a usable format. + """ return value - + def bind_wkb_value(self, wkb_element): """This method is called from base.__compile_wkbspatialelement() to insert the value of base.WKBSpatialElement into a query. - + """ return None if wkb_element is None else wkb_element.desc - + def handle_ddl_after_create(self, bind, table, column): """This method is called after the mapped table was created in the database by SQLAlchemy. It is used to create a geometry column for the created table. - + """ pass - + def handle_ddl_before_drop(self, bind, table, column): """This method is called after the mapped table was deleted from the database by SQLAlchemy. It can be used to delete the geometry column. - + """ pass @@ -158,19 +159,19 @@ def handle_ddl_before_drop(self, bind, table, column): class DialectManager(object): """This class is responsible for finding a spatial dialect (e.g. PGSpatialDialect or MySQLSpatialDialect) for a SQLAlchemy database dialect. - + It can be used by calling "DialectManager.get_spatial_dialect(dialect)", which returns the corresponding spatial dialect. The spatial dialect has to be listed in __initialize_dialects(). - + """ - - # all available spatial dialects {(SQLAlchemy dialect class: spatial dialect class)} + + # all available spatial dialects {(SQLAlchemy dialect class: spatial dialect class)} __dialects_mapping = None - - # all instantiated dialects {(spatial dialect class: spatial dialect instance)} - __spatial_dialect_instances = {} - + + # all instantiated dialects {(spatial dialect class: spatial dialect instance)} + __spatial_dialect_instances = {} + @staticmethod def __initialize_dialects(): #further spatial dialects can be added here @@ -179,7 +180,7 @@ def __initialize_dialects(): from geoalchemy.spatialite import SQLiteSpatialDialect from geoalchemy.oracle import OracleSpatialDialect from geoalchemy.mssql import MSSpatialDialect - + DialectManager.__dialects_mapping = { PGDialect: PGSpatialDialect, SQLiteDialect: SQLiteSpatialDialect, @@ -192,19 +193,19 @@ def __initialize_dialects(): def __dialects(): if DialectManager.__dialects_mapping is None: DialectManager.__initialize_dialects() - + return DialectManager.__dialects_mapping - + @staticmethod def get_spatial_dialect(dialect): """This method returns a spatial dialect instance for a given SQLAlchemy dialect. The instances are cached, so that for every spatial dialect exists only one instance. - + """ - possible_spatial_dialects = [spatial_dialect for (dialect_sqlalchemy, spatial_dialect) - in DialectManager.__dialects().iteritems() + possible_spatial_dialects = [spatial_dialect for (dialect_sqlalchemy, spatial_dialect) + in six.iteritems(DialectManager.__dialects()) if isinstance(dialect, dialect_sqlalchemy)] - + if possible_spatial_dialects: # take the first possible spatial dialect spatial_dialect = possible_spatial_dialects[0] @@ -212,8 +213,8 @@ def get_spatial_dialect(dialect): # if there is no instance for the given dialect yet, create one spatial_dialect_instance = spatial_dialect() DialectManager.__spatial_dialect_instances[spatial_dialect] = spatial_dialect_instance - + return DialectManager.__spatial_dialect_instances[spatial_dialect] else: raise NotImplementedError('Dialect "%s" is not supported by GeoAlchemy' % (dialect.name)) - + diff --git a/geoalchemy/functions.py b/geoalchemy/functions.py index 663f4ac..da17232 100644 --- a/geoalchemy/functions.py +++ b/geoalchemy/functions.py @@ -8,6 +8,7 @@ from sqlalchemy.types import NullType, TypeDecorator import types import re +import six WKT_REGEX = re.compile('.*\\(.*\\).*') @@ -15,10 +16,10 @@ def parse_clause(clause, compiler): """This method is used to translate a clause element (geometries, functions, ..). According to the type of the clause, a conversion to the database geometry type is added or the column clause (column name) or the cascaded clause element is returned. - + """ from geoalchemy.base import SpatialElement, WKTSpatialElement, WKBSpatialElement, DBSpatialElement, GeometryBase - + if hasattr(clause, '__clause_element__'): # for example a column name return clause.__clause_element__() @@ -29,21 +30,21 @@ def parse_clause(clause, compiler): if isinstance(clause, (WKTSpatialElement, WKBSpatialElement)): return clause if isinstance(clause, DBSpatialElement): - return literal(clause.desc, GeometryBase) + return literal(clause.desc, GeometryBase) return clause.desc - elif isinstance(clause, basestring) and WKT_REGEX.match(clause): + elif isinstance(clause, six.string_types) and WKT_REGEX.match(clause): return WKTSpatialElement(clause) - - # for raw parameters + + # for raw parameters return literal(clause) def _get_function(element, compiler, params, within_column_clause): - """For elements of type BaseFunction, the database specific function data - is looked up and a executable sqlalchemy.sql.expression.Function object + """For elements of type BaseFunction, the database specific function data + is looked up and a executable sqlalchemy.sql.expression.Function object is returned. """ - from geoalchemy.dialect import DialectManager + from geoalchemy.dialect import DialectManager database_dialect = DialectManager.get_spatial_dialect(compiler.dialect) for kls in element.__class__.__mro__: try: @@ -52,17 +53,17 @@ def _get_function(element, compiler, params, within_column_clause): continue if function_data is None: raise Exception("Unsupported function for this dialect") - + if isinstance(function_data, list): """if we have a list of function names, create cascaded Function objects for all function names in the list:: - + ['TO_CHAR', 'SDO_UTIL.TO_WKTGEOMETRY'] --> TO_CHAR(SDO_UTIL.TO_WKTGEOMETRY(..)) """ function = None for name in reversed(function_data): packages = name.split('.') - + if function is None: """for the innermost function use the passed-in parameters as argument, otherwise use the prior created function @@ -70,14 +71,14 @@ def _get_function(element, compiler, params, within_column_clause): args = params else: args = [function] - - function = Function(packages.pop(-1), + + function = Function(packages.pop(-1), packagenames=packages, *args ) - + return function - + elif isinstance(function_data, types.FunctionType): """if we have a function, call this function with the parameters and return the created Function object @@ -87,106 +88,106 @@ def _get_function(element, compiler, params, within_column_clause): flags = element.flags else: flags = {} - + return function_data(params, within_column_clause, **flags) - + else: packages = function_data.split('.') - - return Function(packages.pop(-1), - packagenames=packages, + + return Function(packages.pop(-1), + packagenames=packages, *params ) - + class BaseFunction(Function): """Represents a database function. - + When the function is used on a geometry column (r.geom.point_n(2) or Road.geom.point_n(2)), - additional arguments are set using __call__. The column or geometry the function is called on + additional arguments are set using __call__. The column or geometry the function is called on is stored inside the constructor (see base.SpatialComparator.__getattr__()). When the function is called directly (functions.point_n(..., 2)), all arguments are set using the constructor. """ - + def __init__(self, *arguments, **kwargs): self.arguments = arguments self.flags = kwargs.copy() - + Function.__init__(self, self.__class__.__name__, **kwargs) - + def __call__(self, *arguments, **kwargs): if len(arguments) > 0: self.arguments = self.arguments + arguments - + if len(kwargs) > 0: self.flags.update(kwargs) - + return self class ReturnsGeometryFunction(BaseFunction): """Represents a database function which returns a new geometry. - + This class adds support for "function chaining", so that a new function can called on a function that returns a geometry, for example:: - + session.scalar(r.road_geom.point_n(5).wkt) - + Note that only database generic functions (in package 'functions') can be called, but no database specific functions, like:: - + session.scalar(r.road_geom.point_n(5).sdo_geom_sdo_centroid) """ - + def __getattr__(self, name): return getattr(functions, name)(self) - + # override the __eq__() operator (allows to use '==' on geometries) - def __eq__(self, other): + def __eq__(self, other): return functions.equals(self, other) @compiles(BaseFunction) def __compile_base_function(element, compiler, **kw): - + params = [parse_clause(argument, compiler) for argument in element.arguments] - - from geoalchemy.dialect import DialectManager + + from geoalchemy.dialect import DialectManager database_dialect = DialectManager.get_spatial_dialect(compiler.dialect) - + if database_dialect.is_member_function(element.__class__): geometry = params.pop(0) function_name = database_dialect.get_function(element.__class__) - + if isinstance(function_name, str): - """If the function is defined as String (e.g. "oracle_functions.dims : 'Get_Dims'"), - we construct the function call in the query ourselves. This is because SQLAlchemy, - at this point of the compile process, does not add parenthesis for functions + """If the function is defined as String (e.g. "oracle_functions.dims : 'Get_Dims'"), + we construct the function call in the query ourselves. This is because SQLAlchemy, + at this point of the compile process, does not add parenthesis for functions without arguments when using Oracle. Otherwise let SQLAlchemy build the query. Note that this won't work for Oracle with functions without parameters.""" - + return "%s.%s(%s)" % ( compiler.process(geometry), function_name, - ", ".join([compiler.process(e) for e in params]) + ", ".join([compiler.process(e) for e in params]) ) else: function = _get_function(element, compiler, params, kw.get('within_columns_clause', False)) - + return "%s.%s" % ( compiler.process(geometry), - compiler.process(function) + compiler.process(function) ) - + elif database_dialect.is_property(element.__class__): geometry = params.pop(0) function_name = database_dialect.get_function(element.__class__) - + return "%s.%s" % ( compiler.process(geometry), - function_name + function_name ) - + else: function = _get_function(element, compiler, params, kw.get('within_columns_clause', False)) return compiler.process(function) @@ -194,38 +195,38 @@ def __compile_base_function(element, compiler, **kw): def BooleanFunction(function, compare_value = 'TRUE'): """Wrapper for database function that return 'Boolean-like' values, for example SDO_EQUAL returns the string 'TRUE'. - + This function adds the necessary comparison to ensure that in the WHERE clause the function is compared to `compare_value` while in the SELECT clause the function result is returned. - + :param function: The function that needs boolean wrapping :param compare_value: The value that the function should be compare to """ def function_handler(params, within_column_clause): return check_comparison(function(*params), within_column_clause, True, compare_value) - + return function_handler - + def check_comparison(function, within_column_clause, returns_boolean, compare_value): """Because Oracle and MS SQL do not want to know Boolean and functions return 0/1 or the string 'TRUE', - we manually have to add a comparison, but only if the function is called inside the where-clause, + we manually have to add a comparison, but only if the function is called inside the where-clause, not in the select clause. - + For example: select .. from .. where SDO_EQUAL(.., ..) = 'TRUE' select SDO_EQUAL(.., ..) from .. - + """ if returns_boolean and not within_column_clause: return (function == compare_value) - else: + else: return function class functions: """Functions that implement OGC SFS or SQL/MM and that are supported by most databases """ - + class wkt(BaseFunction): """AsText(g)""" pass @@ -234,91 +235,91 @@ class wkb(BaseFunction): """AsBinary(g)""" def __init__(self, *arguments): BaseFunction.__init__(self, type_=_WKBType, *arguments) - + class dimension(BaseFunction): """Dimension(g)""" pass - + class srid(BaseFunction): """SRID(g)""" pass - + class geometry_type(BaseFunction): """GeometryType(g)""" pass - + class is_valid(BaseFunction): """IsValid(g)""" pass - + class is_empty(BaseFunction): """IsEmpty(g)""" pass - + class is_simple(BaseFunction): """IsSimple(g)""" pass - + class is_closed(BaseFunction): """IsClosed(g)""" pass - + class is_ring(BaseFunction): """IsRing(g)""" pass - + class num_points(BaseFunction): """NumPoints(g)""" pass - + class point_n(ReturnsGeometryFunction): """PointN(g, n)""" pass - + class length(BaseFunction): """Length(g)""" pass - + class area(BaseFunction): """Area(g)""" pass - + class x(BaseFunction): """X(g)""" pass - + class y(BaseFunction): """Y(g)""" pass - + class centroid(ReturnsGeometryFunction): """Centroid(g)""" pass - + class boundary(ReturnsGeometryFunction): """Boundary""" pass - + class buffer(ReturnsGeometryFunction): """Buffer(g, n)""" pass - + class convex_hull(ReturnsGeometryFunction): """ConvexHull(g)""" pass - + class envelope(ReturnsGeometryFunction): """Envelope(g)""" pass - + class start_point(ReturnsGeometryFunction): """StartPoint(g)""" pass - + class end_point(ReturnsGeometryFunction): """EndPoint(g)""" pass - + class transform(ReturnsGeometryFunction): """Transform(g, srid)""" pass @@ -326,51 +327,51 @@ class transform(ReturnsGeometryFunction): class equals(BaseFunction): """Equals(g1, g2)""" pass - + class distance(BaseFunction): """Distance(g1, g2)""" pass - + class within_distance(BaseFunction): """DWithin(g1, g2, d)""" pass - + class disjoint(BaseFunction): """Disjoint(g1, g2)""" pass - + class intersects(BaseFunction): """Intersects(g1, g2)""" pass - + class touches(BaseFunction): """Touches(g1, g2)""" pass - + class crosses(BaseFunction): """Crosses(g1, g2)""" pass - + class within(BaseFunction): """Within(g1, g2)""" pass - + class overlaps(BaseFunction): """Overlaps(g1, g2)""" pass - + class gcontains(BaseFunction): """Contains(g1, g2)""" pass - + class covers(BaseFunction): """Covers(g1, g2)""" pass - + class covered_by(BaseFunction): """CoveredBy(g1, g2)""" pass - + class intersection(ReturnsGeometryFunction): """Intersection(g1, g2)""" pass @@ -381,7 +382,7 @@ class _within_distance(BaseFunction): don't support it correctly (PostGIS 1.3), or provide multiple implementations (Oracle).""" pass - + class union(ReturnsGeometryFunction): """Union(geometry set) @@ -391,30 +392,30 @@ class union(ReturnsGeometryFunction): *Only supported in PostgreSQL/PostGIS currently.* """ pass - + class collect(ReturnsGeometryFunction): """Collect(geometry set) - + Collect is an aggregate function. It is typically used in ``SELECT`` clauses. *Only supported in PostgreSQL/PostGIS currently.* """ pass - + class extent(BaseFunction): """Extent(geometry set) - + Extent is an aggregate function. It is typically used in ``SELECT`` clauses. *Only supported in PostgreSQL/PostGIS currently.* """ pass - + @compiles(functions._within_distance) def __compile__within_distance(element, compiler, **kw): - from geoalchemy.dialect import DialectManager + from geoalchemy.dialect import DialectManager database_dialect = DialectManager.get_spatial_dialect(compiler.dialect) for kls in element.__class__.__mro__: try: @@ -431,33 +432,33 @@ def __compile__within_distance(element, compiler, **kw): arguments.pop(0), *arguments)) class _WKBType(TypeDecorator): - """A helper type which makes sure that the WKB sequence returned from queries like + """A helper type which makes sure that the WKB sequence returned from queries like 'session.scalar(r.road_geom.wkb)', has the same type as the attribute 'geom_wkb' which is filled when querying an object of a mapped class, for example with 'r = session.query(Road).get(1)':: - + eq_(session.scalar(self.r.road_geom.wkb), self.r.road_geom.geom_wkb) - - This type had to be introduced, because for Oracle 'SDO_UTIL.TO_WKBGEOMETRY(..)' returned the type + + This type had to be introduced, because for Oracle 'SDO_UTIL.TO_WKBGEOMETRY(..)' returned the type cx_Oracle.LOB and not a buffer. - - To modify the behavior of 'process_result_value' for a specific database dialect, overwrite the + + To modify the behavior of 'process_result_value' for a specific database dialect, overwrite the method 'process_wkb' in that dialect. - + This class is used inside :class:`functions.wkb`. - + """ impl = NullType def process_result_value(self, value, dialect): if value is not None: - from geoalchemy.dialect import DialectManager + from geoalchemy.dialect import DialectManager database_dialect = DialectManager.get_spatial_dialect(dialect) - + return database_dialect.process_wkb(value) else: return value def copy(self): return _WKBType() - + diff --git a/geoalchemy/oracle.py b/geoalchemy/oracle.py index 46f6ac1..28512d9 100644 --- a/geoalchemy/oracle.py +++ b/geoalchemy/oracle.py @@ -2,24 +2,24 @@ from sqlalchemy import select, func, exc from geoalchemy.base import SpatialComparator, PersistentSpatialElement,\ GeometryBase -from geoalchemy.dialect import SpatialDialect +from geoalchemy.dialect import SpatialDialect from geoalchemy.functions import functions, BaseFunction, check_comparison, BooleanFunction from geoalchemy.geometry import LineString, MultiLineString, GeometryCollection,\ Geometry from geoalchemy.base import WKTSpatialElement, WKBSpatialElement - + import warnings from sqlalchemy.schema import Column from sqlalchemy.sql.expression import table, column, and_, text from sqlalchemy.orm.attributes import InstrumentedAttribute -"""Currently cx_Oracle does not support the insertion of NULL values into geometry columns +"""Currently cx_Oracle does not support the insertion of NULL values into geometry columns as bind parameter, see http://sourceforge.net/mailarchive/forum.php?thread_name=AANLkTikNG4brmQJiua2FQS8zUwk8rNgLHoe6SZ32f1gQ%40mail.gmail.com&forum_name=cx-oracle-users For still being able to insert NULL, this variable can be used:: - + spot_null = Spot(spot_height=None, spot_location=ORACLE_NULL_GEOMETRY) - + """ ORACLE_NULL_GEOMETRY = text('NULL') @@ -38,107 +38,107 @@ class OraclePersistentSpatialElement(PersistentSpatialElement): def __init__(self, desc): self.desc = desc - + def __getattr__(self, name): try: return PersistentSpatialElement.__getattr__(self, name) except AttributeError: return getattr(oracle_functions, name)(self) -def ST_GeometryFunction(function, returns_geometry = False, relation_function = False, +def ST_GeometryFunction(function, returns_geometry = False, relation_function = False, returns_boolean = False, compare_value = 1, default_cast = False): - """Functions inside MDSYS.OGC_* (OGC SF) and MDSYS.ST_GEOMETRY.ST_* (SQL MM) expect ST_GEOMETRY + """Functions inside MDSYS.OGC_* (OGC SF) and MDSYS.ST_GEOMETRY.ST_* (SQL MM) expect ST_GEOMETRY instead of SDO_GEOMETRY, this method adds a cast. - - Some functions like OGC_X or OGC_IsClosed only work if the geometry (the first parameter) is casted - to a ST_GEOMETRY subtype, for example: 'OGC_X(ST_POINT(SDO_GEOMETRY(..)))'. - This method tries to get the geometry type from WKTSpatialElement, WKBSpatialElement and columns and + + Some functions like OGC_X or OGC_IsClosed only work if the geometry (the first parameter) is casted + to a ST_GEOMETRY subtype, for example: 'OGC_X(ST_POINT(SDO_GEOMETRY(..)))'. + This method tries to get the geometry type from WKTSpatialElement, WKBSpatialElement and columns and adds a corresponding cast (if possible). - If the geometry type can not be identified, no cast is added and the user manually has to add + If the geometry type can not be identified, no cast is added and the user manually has to add a cast, for example: 'session.scalar(functions.x(func.ST_POINT(spot.spot_location.transform(2249)))' - + Functions like OGC_IsEmpty do not require a cast to a subtype. In this case (``default_cast = True``), a cast to 'ST_GEOMETRY' is always added. - - If ``relation_function`` is set to ``True``, a cast is also added for the second parameter. + + If ``relation_function`` is set to ``True``, a cast is also added for the second parameter. If the database function returns a new geometry (`returns_geometry = True``) a back-cast to - SDO_GEOMETRY ('ST_GEOMETRY.GET_SDO_GEOM(..)') is added. + SDO_GEOMETRY ('ST_GEOMETRY.GET_SDO_GEOM(..)') is added. """ assert(not (returns_geometry and returns_boolean)) # both can not be true - + def get_function(function, geom_cast, params, returns_geometry, relation_function): if relation_function: # cast 2nd geometry geom2_cast = cast_param(params) - + if geom2_cast is not None: function = function(geom_cast, geom2_cast, *params) else: function = function(geom_cast, *params) else: function = function(geom_cast, *params) - + if returns_geometry: # add cast to SDO_GEOMETRY return func.ST_GEOMETRY.GET_SDO_GEOM(function) else: return function - + def cast_param(params): if len(params) > 0: geom = params[0] - + if default_cast: params.pop(0) return func.ST_GEOMETRY(geom) - - elif isinstance(geom, (WKBSpatialElement, WKTSpatialElement)) and geom.geometry_type <> Geometry.name: + + elif isinstance(geom, (WKBSpatialElement, WKTSpatialElement)) and geom.geometry_type != Geometry.name: params.pop(0) return getattr(func, 'ST_%s' % (geom.geometry_type))(geom) elif isinstance(geom, Column) and isinstance(geom.type, GeometryBase): params.pop(0) return getattr(func, 'ST_%s' % (geom.type.name))(geom) - + return None - - + + def function_handler(params, within_column_clause): if len(params) > 0: geom_cast = cast_param(params) if geom_cast is not None: return check_comparison(get_function(function, geom_cast, params, returns_geometry, relation_function), within_column_clause, returns_boolean, compare_value) - + return check_comparison(function(*params), within_column_clause, returns_boolean, compare_value) - + return function_handler def DimInfoFunction(function, returns_boolean = False, compare_value = 'TRUE'): """Some Oracle functions expect a 'dimension info array' (DIMINFO) for each geometry. This method tries to append a corresponding DIMINFO for every geometry in the parameter list. - + For geometry columns a subselect is added which queries the DIMINFO from 'ALL_SDO_GEOM_METADATA'. - For WKTSpatialElement/WKBSpatialElement objects, that were queried from the database, the DIMINFO + For WKTSpatialElement/WKBSpatialElement objects, that were queried from the database, the DIMINFO will be added as text representation (if possible). - - Most functions that require DIMINFO also accept a tolerance value instead of the DIMINFO. If you want to - use a tolerance value for geometry columns or WKBSpatialElement objects that come from the database, + + Most functions that require DIMINFO also accept a tolerance value instead of the DIMINFO. If you want to + use a tolerance value for geometry columns or WKBSpatialElement objects that come from the database, the flag 'auto_diminfo' has to be set to 'False' when calling the function:: - + l = session.query(Lake).filter(Lake.lake_name=='Lake White').one() session.scalar(l.lake_geom.area) # DIMINFO is added automatically session.scalar(l.lake_geom.area(tolerance, auto_diminfo=False)) # DIMINFO is not added - + see also: http://download.oracle.com/docs/cd/E11882_01/appdev.112/e11830/sdo_objrelschema.htm#sthref300 """ - + def function_handler(params, within_column_clause, **flags): if flags.get('auto_diminfo', True): # insert diminfo for all geometries in params params = list(params) - + i = 0 length = len(params) while i < length: @@ -146,34 +146,34 @@ def function_handler(params, within_column_clause, **flags): if isinstance(params[i], (WKBSpatialElement, WKTSpatialElement)) and hasattr(params[i], 'DIMINFO'): # the attribute DIMINFO is set in OracleSpatialDialect.process_result() diminfo = params[i].DIMINFO - + elif isinstance(params[i], Column) and isinstance(params[i].type, GeometryBase): diminfo = OracleSpatialDialect.get_diminfo_select(params[i]) - + if diminfo is not None: i += 1 # insert DIMINFO after the geometry params.insert(i, diminfo) length += 1 - + i += 1 return check_comparison(function(*params), within_column_clause, returns_boolean, compare_value) - + return function_handler class oracle_functions(functions): """Functions only supported by Oracle """ - + class gtype(BaseFunction): """g.Get_GType()""" pass - + class dims(BaseFunction): """g.Get_Dims()""" pass - + class kml(BaseFunction): """TO_CHAR(SDO_UTIL.TO_KMLGEOMETRY(g))""" pass @@ -185,137 +185,137 @@ class gml(BaseFunction): class gml311(BaseFunction): """TO_CHAR(SDO_UTIL.TO_GML311GEOMETRY(g))""" pass - + # Spatial Operators # http://download.oracle.com/docs/cd/E11882_01/appdev.112/e11830/sdo_operat.htm#insertedID0 - + class sdo_filter(BaseFunction): """SDO_FILTER(g1, g2, param)""" pass - + class sdo_nn(BaseFunction): """SDO_NN(g1, g2, param [, number])""" pass - + class sdo_nn_distance(BaseFunction): """SDO_NN_DISTANCE(number)""" pass - + class sdo_relate(BaseFunction): """SDO_RELATE(g1, g2, param)""" pass - + class sdo_within_distance(BaseFunction): """SDO_WITHIN_DISTANCE(g1, g2, param)""" pass - + class sdo_anyinteract(BaseFunction): """SDO_ANYINTERACT(g1, g2)""" pass - + class sdo_contains(BaseFunction): """SDO_CONTAINS(g1, g2)""" pass - + class sdo_coveredby(BaseFunction): """SDO_COVEREDBY(g1, g2)""" pass - + class sdo_covers(BaseFunction): """SDO_COVERS(g1, g2)""" pass - + class sdo_equal(BaseFunction): """SDO_EQUAL(g1, g2)""" pass - + class sdo_inside(BaseFunction): """SDO_INSIDE(g1, g2)""" pass - + class sdo_on(BaseFunction): """SDO_ON(g1, g2)""" pass - + class sdo_overlapbdydisjoint(BaseFunction): """SDO_OVERLAPBDYDISJOINT(g1, g2)""" pass - + class sdo_overlapbdyintersect(BaseFunction): """SDO_OVERLAPBDYINTERSECT(g1, g2)""" pass - + class sdo_overlaps(BaseFunction): """SDO_OVERLAPS(g1, g2)""" pass - + class sdo_touch(BaseFunction): """SDO_TOUCH(g1, g2)""" pass - + # Selection of functions of the SDO_GEOM package # http://download.oracle.com/docs/cd/E11882_01/appdev.112/e11830/sdo_objgeom.htm#insertedID0 - + class sdo_geom_sdo_area(BaseFunction): """SDO_GEOM.SDO_AREA()""" pass - + class sdo_geom_sdo_buffer(BaseFunction): """SDO_GEOM.SDO_BUFFER()""" pass - + class sdo_geom_sdo_centroid(BaseFunction): """SDO_GEOM.SDO_CENTROID()""" pass - + class sdo_geom_sdo_concavehull(BaseFunction): """SDO_GEOM.SDO_CONCAVEHULL()""" pass - + class sdo_geom_sdo_concavehull_boundary(BaseFunction): """SDO_GEOM.SDO_CONCAVEHULL_BOUNDARY()""" pass - + class sdo_geom_sdo_convexhull(BaseFunction): """SDO_GEOM.SDO_CONVEXHULL()""" pass - + class sdo_geom_sdo_difference(BaseFunction): """SDO_GEOM.SDO_DIFFERENCE()""" pass - + class sdo_geom_sdo_distance(BaseFunction): """SDO_GEOM.SDO_DISTANCE()""" pass - + class sdo_geom_sdo_intersection(BaseFunction): """SDO_GEOM.SDO_INTERSECTION()""" pass - + class sdo_geom_sdo_length(BaseFunction): """SDO_GEOM.SDO_LENGTH()""" pass - + class sdo_geom_sdo_mbr(BaseFunction): """SDO_GEOM.SDO_MBR()""" pass - + class sdo_geom_sdo_pointonsurface(BaseFunction): """SDO_GEOM.SDO_POINTONSURFACE()""" pass - + class sdo_geom_sdo_union(BaseFunction): """SDO_GEOM.SDO_UNION()""" pass - + class sdo_geom_sdo_xor(BaseFunction): """SDO_GEOM.SDO_XOR()""" pass - + class sdo_geom_sdo_within_distance(BaseFunction): """SDO_GEOM.WITHIN_DISTANCE()""" pass - + @staticmethod def _within_distance(compiler, geom1, geom2, distance, additional_params={}): """If the first parameter is a geometry column, then the Oracle operator @@ -420,7 +420,7 @@ def _within_distance(compiler, geom1, geom2, distance, additional_params={}): class OracleSpatialDialect(SpatialDialect): """Implementation of SpatialDialect for Oracle.""" - + __functions = { functions.wkt: ['TO_CHAR', 'SDO_UTIL.TO_WKTGEOMETRY'], WKTSpatialElement : 'MDSYS.SDO_GEOMETRY', @@ -449,27 +449,27 @@ class OracleSpatialDialect(SpatialDialect): functions.end_point : ST_GeometryFunction(func.MDSYS.OGC_EndPoint, True), functions.transform : 'SDO_CS.TRANSFORM', # note: we are not using SDO_Equal because it always requires a spatial index - functions.equals : ST_GeometryFunction(func.MDSYS.OGC_EQUALS, returns_boolean=True, relation_function=True, default_cast=True), + functions.equals : ST_GeometryFunction(func.MDSYS.OGC_EQUALS, returns_boolean=True, relation_function=True, default_cast=True), functions.distance : DimInfoFunction(func.SDO_GEOM.SDO_Distance), functions.within_distance : DimInfoFunction(func.SDO_GEOM.Within_Distance, returns_boolean=True), - functions.disjoint : ST_GeometryFunction(func.MDSYS.OGC_Disjoint, relation_function=True, returns_boolean=True, default_cast=True), + functions.disjoint : ST_GeometryFunction(func.MDSYS.OGC_Disjoint, relation_function=True, returns_boolean=True, default_cast=True), functions.intersects : ST_GeometryFunction(func.MDSYS.OGC_Intersects, relation_function=True, returns_boolean=True, default_cast=True), functions.touches : ST_GeometryFunction(func.MDSYS.OGC_Touch, relation_function=True, returns_boolean=True, default_cast=True), - functions.crosses : ST_GeometryFunction(func.MDSYS.OGC_Cross, relation_function=True, returns_boolean=True, default_cast=True), - functions.within : ST_GeometryFunction(func.MDSYS.OGC_Within, relation_function=True, returns_boolean=True, default_cast=True), - functions.overlaps : ST_GeometryFunction(func.MDSYS.OGC_Overlap, relation_function=True, returns_boolean=True, default_cast=True), + functions.crosses : ST_GeometryFunction(func.MDSYS.OGC_Cross, relation_function=True, returns_boolean=True, default_cast=True), + functions.within : ST_GeometryFunction(func.MDSYS.OGC_Within, relation_function=True, returns_boolean=True, default_cast=True), + functions.overlaps : ST_GeometryFunction(func.MDSYS.OGC_Overlap, relation_function=True, returns_boolean=True, default_cast=True), functions.gcontains : ST_GeometryFunction(func.MDSYS.OGC_Contains, relation_function=True, returns_boolean=True, default_cast=True), - functions.covers : None, # use oracle_functions.sdo_covers + functions.covers : None, # use oracle_functions.sdo_covers functions.covered_by : None, # use oracle_functions.sdo_coveredby functions.intersection : DimInfoFunction(func.SDO_GEOM.SDO_INTERSECTION), # aggregate_union => SDO_AGGR_UNION ? - + oracle_functions.gtype : 'Get_GType', oracle_functions.dims : 'Get_Dims', oracle_functions.kml : ['TO_CHAR', 'SDO_UTIL.TO_KMLGEOMETRY'], oracle_functions.gml : ['TO_CHAR', 'SDO_UTIL.TO_GMLGEOMETRY'], oracle_functions.gml311 : ['TO_CHAR', 'SDO_UTIL.TO_GML311GEOMETRY'], - + oracle_functions.sdo_filter : BooleanFunction(func.SDO_FILTER), oracle_functions.sdo_nn : BooleanFunction(func.SDO_NN), oracle_functions.sdo_nn_distance : 'SDO_NN_DISTANCE', @@ -486,86 +486,86 @@ class OracleSpatialDialect(SpatialDialect): oracle_functions.sdo_overlapbdyintersect : BooleanFunction(func.SDO_OVERLAPBDYINTERSECT), oracle_functions.sdo_overlaps : BooleanFunction(func.SDO_OVERLAPS), oracle_functions.sdo_touch : BooleanFunction(func.SDO_TOUCH), - + # same as functions.area - oracle_functions.sdo_geom_sdo_area : DimInfoFunction(func.SDO_GEOM.SDO_Area), + oracle_functions.sdo_geom_sdo_area : DimInfoFunction(func.SDO_GEOM.SDO_Area), # same as functions.buffer - oracle_functions.sdo_geom_sdo_buffer : DimInfoFunction(func.SDO_GEOM.SDO_Buffer), + oracle_functions.sdo_geom_sdo_buffer : DimInfoFunction(func.SDO_GEOM.SDO_Buffer), # same as functions.centroid - oracle_functions.sdo_geom_sdo_centroid : DimInfoFunction(func.SDO_GEOM.SDO_CENTROID), - oracle_functions.sdo_geom_sdo_concavehull : 'SDO_GEOM.SDO_CONCAVEHULL', + oracle_functions.sdo_geom_sdo_centroid : DimInfoFunction(func.SDO_GEOM.SDO_CENTROID), + oracle_functions.sdo_geom_sdo_concavehull : 'SDO_GEOM.SDO_CONCAVEHULL', oracle_functions.sdo_geom_sdo_concavehull_boundary : 'SDO_GEOM.SDO_CONCAVEHULL_BOUNDARY', # same as functions.convexhull - oracle_functions.sdo_geom_sdo_convexhull : DimInfoFunction(func.SDO_GEOM.SDO_CONVEXHULL), + oracle_functions.sdo_geom_sdo_convexhull : DimInfoFunction(func.SDO_GEOM.SDO_CONVEXHULL), # same as functions.distance - oracle_functions.sdo_geom_sdo_difference : DimInfoFunction(func.SDO_GEOM.SDO_DIFFERENCE), + oracle_functions.sdo_geom_sdo_difference : DimInfoFunction(func.SDO_GEOM.SDO_DIFFERENCE), # same as functions.intersection - oracle_functions.sdo_geom_sdo_difference : DimInfoFunction(func.SDO_GEOM.SDO_INTERSECTION), + oracle_functions.sdo_geom_sdo_difference : DimInfoFunction(func.SDO_GEOM.SDO_INTERSECTION), # same as functions.length - oracle_functions.sdo_geom_sdo_length : DimInfoFunction(func.SDO_GEOM.SDO_LENGTH), - oracle_functions.sdo_geom_sdo_mbr : DimInfoFunction(func.SDO_GEOM.SDO_MBR), - oracle_functions.sdo_geom_sdo_pointonsurface : DimInfoFunction(func.SDO_GEOM.SDO_POINTONSURFACE), - oracle_functions.sdo_geom_sdo_union : DimInfoFunction(func.SDO_GEOM.SDO_UNION), - oracle_functions.sdo_geom_sdo_xor : DimInfoFunction(func.SDO_GEOM.SDO_XOR), + oracle_functions.sdo_geom_sdo_length : DimInfoFunction(func.SDO_GEOM.SDO_LENGTH), + oracle_functions.sdo_geom_sdo_mbr : DimInfoFunction(func.SDO_GEOM.SDO_MBR), + oracle_functions.sdo_geom_sdo_pointonsurface : DimInfoFunction(func.SDO_GEOM.SDO_POINTONSURFACE), + oracle_functions.sdo_geom_sdo_union : DimInfoFunction(func.SDO_GEOM.SDO_UNION), + oracle_functions.sdo_geom_sdo_xor : DimInfoFunction(func.SDO_GEOM.SDO_XOR), # same as functions.within_distance oracle_functions.sdo_geom_sdo_within_distance : DimInfoFunction(func.SDO_GEOM.Within_Distance, returns_boolean=True), functions._within_distance : oracle_functions._within_distance } - + __member_functions = ( oracle_functions.dims, oracle_functions.gtype ) - + METADATA_TABLE = table('ALL_SDO_GEOM_METADATA', column('diminfo'), column('table_name'), column('column_name')) - + def _get_function_mapping(self): return OracleSpatialDialect.__functions - + def is_member_function(self, function_class): return function_class in self.__member_functions - + def process_result(self, value, type): value = self.process_wkb(value) - wkb_element = WKBSpatialElement(value, type.srid, type.name) - + wkb_element = WKBSpatialElement(value, type.srid, type.name) + if type.kwargs.has_key("diminfo"): # also set the DIMINFO data so that in can be used in function calls, see DimInfoFunction() if not type.kwargs.has_key("diminfo_sql"): # cache the SQLAlchemy text literal type.kwargs["diminfo_sql"] = text(type.kwargs["diminfo"]) wkb_element.DIMINFO = type.kwargs["diminfo_sql"] - + return OraclePersistentSpatialElement(wkb_element) def process_wkb(self, value): - """SDO_UTIL.TO_WKBGEOMETRY(..) returns an object of cx_Oracle.LOB, which we + """SDO_UTIL.TO_WKBGEOMETRY(..) returns an object of cx_Oracle.LOB, which we will transform into a buffer. """ if value is not None: return buffer(value.read()) else: return value - + def bind_wkb_value(self, wkb_element): """Append a transformation to BLOB using the Oracle function 'TO_BLOB'. """ if wkb_element is not None and wkb_element.desc is not None: return func.TO_BLOB(wkb_element.desc) - + return None @staticmethod def get_diminfo_select(column): """Returns a select which queries the DIMINFO array from 'ALL_SDO_GEOM_METADATA' for the passed in column. - + see: http://download.oracle.com/docs/cd/E11882_01/appdev.112/e11830/sdo_objrelschema.htm#sthref300 """ if isinstance(column, InstrumentedAttribute): column = column.property.columns[0] - + return select([OracleSpatialDialect.METADATA_TABLE.c.diminfo]).where( and_(OracleSpatialDialect.METADATA_TABLE.c.table_name == column.table.name.upper(), OracleSpatialDialect.METADATA_TABLE.c.column_name == column.name.upper())) @@ -573,43 +573,43 @@ def get_diminfo_select(column): def handle_ddl_before_drop(self, bind, table, column): bind.execute("DELETE FROM USER_SDO_GEOM_METADATA WHERE table_name = '%s' AND column_name = '%s'" % (table.name.upper(), column.name.upper())) - + if column.type.spatial_index and column.type.kwargs.has_key("diminfo"): bind.execute("DROP INDEX %s_%s_sidx" % (table.name, column.name)) - - def handle_ddl_after_create(self, bind, table, column): - bind.execute("ALTER TABLE %s ADD %s %s" % + + def handle_ddl_after_create(self, bind, table, column): + bind.execute("ALTER TABLE %s ADD %s %s" % (table.name, column.name, 'SDO_GEOMETRY')) - + if not column.nullable: bind.execute("ALTER TABLE %s MODIFY %s NOT NULL" % (table.name, column.name)) - + if not column.type.kwargs.has_key("diminfo"): warnings.warn("No DIMINFO given for '%s.%s', no entry in USER_SDO_GEOM_METADATA will be made "\ - "and no spatial index will be created." % (table.name, column.name), + "and no spatial index will be created." % (table.name, column.name), exc.SAWarning, stacklevel=3) else: diminfo = column.type.kwargs["diminfo"] - + bind.execute("INSERT INTO USER_SDO_GEOM_METADATA (table_name, column_name, diminfo, srid) " + - "VALUES ('%s', '%s', %s, %s)" % + "VALUES ('%s', '%s', %s, %s)" % (table.name, column.name, diminfo, column.type.srid)) - + if column.type.spatial_index: bind.execute("CREATE INDEX %s_%s_sidx ON %s(%s) "\ - "INDEXTYPE IS MDSYS.SPATIAL_INDEX%s" % - (table.name, column.name, table.name, column.name, + "INDEXTYPE IS MDSYS.SPATIAL_INDEX%s" % + (table.name, column.name, table.name, column.name, self.__get_index_parameters(column.type))) - + def __get_index_parameters(self, type): type_name = self.__get_oracle_gtype(type) - + if type_name == None: return "" else: return " PARAMETERS ('LAYER_GTYPE=%s')" % type_name - + def __get_oracle_gtype(self, type): """Maps the GeoAlchemy types to SDO_GTYPE values: http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14255/sdo_objrelschema.htm#g1013735 diff --git a/geoalchemy/tests/test_dialect.py b/geoalchemy/tests/test_dialect.py index 2466c93..8c80bbc 100644 --- a/geoalchemy/tests/test_dialect.py +++ b/geoalchemy/tests/test_dialect.py @@ -40,12 +40,3 @@ def test_parse_clause(self): ok_(isinstance(parse_clause('GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))', None), WKTSpatialElement)) ok_(isinstance(parse_clause('GEOMETRYCOLLECTION (POINT(4 6),LINESTRING(4 6,7 10))', None), WKTSpatialElement)) ok_(not isinstance(parse_clause('unit=km arc_tolerance=0.05)', None), WKTSpatialElement)) - - -if __name__ == '__main__': - import sys - import nose - - sys.argv.append(__name__) - result = nose.run() - sys.exit(int(not result)) diff --git a/geoalchemy/tests/test_mssql.py b/geoalchemy/tests/test_mssql.py index 89f3828..1a46355 100644 --- a/geoalchemy/tests/test_mssql.py +++ b/geoalchemy/tests/test_mssql.py @@ -1,562 +1,553 @@ -# -*- coding: utf-8 -*- - -from sqlalchemy import (create_engine, MetaData, Column, Integer, String, - Numeric, func, Table, and_) -from sqlalchemy.orm import sessionmaker, mapper -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.exc import IntegrityError -from geoalchemy import GeometryColumn, Geometry, LineString, Polygon, GeometryDDL, GeometryExtensionColumn, GeometryCollection, DBSpatialElement, WKTSpatialElement, WKBSpatialElement -from geoalchemy.functions import functions -from geoalchemy.mssql import MS_SPATIAL_NULL, ms_functions, MSComparator -from unittest import TestCase -from nose.tools import eq_, ok_, raises, assert_almost_equal - -u""" -.. moduleauthor:: Mark Hall -""" - -engine = create_engine('mssql+pyodbc://gis:gis@localhost:4331/gis', echo=True) -metadata = MetaData(engine) -session = sessionmaker(bind=engine)() -Base = declarative_base(metadata=metadata) - -class Road(Base): - __tablename__ = 'ROADS' - - road_id = Column(Integer, primary_key=True) - road_name = Column(String(255)) - road_geom = GeometryColumn(LineString(2, bounding_box='(xmin=-180, ymin=-90, xmax=180, ymax=90)'), comparator=MSComparator, nullable=False) - -class Lake(Base): - __tablename__ = 'lakes' - - lake_id = Column(Integer, primary_key=True) - lake_name = Column(String(255)) - lake_geom = GeometryColumn(Polygon(2), comparator=MSComparator) - -spots_table = Table('spots', metadata, - Column('spot_id', Integer, primary_key=True), - Column('spot_height', Numeric(6, 2)), - GeometryExtensionColumn('spot_location', Geometry(2))) - -class Spot(object): - def __init__(self, spot_id=None, spot_height=None, spot_location=None): - self.spot_id = spot_id - self.spot_height = spot_height - self.spot_location = spot_location - - -mapper(Spot, spots_table, properties={ - 'spot_location': GeometryColumn(spots_table.c.spot_location, comparator=MSComparator)}) - -class Shape(Base): - __tablename__ = 'shapes' - - shape_id = Column(Integer, primary_key=True) - shape_name = Column(String(255)) - shape_geom = GeometryColumn(GeometryCollection(2)) - -# enable the DDL extension, which allows CREATE/DROP operations -# to work correctly. This is not needed if working with externally -# defined tables. -GeometryDDL(Road.__table__) -GeometryDDL(Lake.__table__) -GeometryDDL(spots_table) -GeometryDDL(Shape.__table__) - - -class TestGeometry(TestCase): - - def setUp(self): - metadata.drop_all() - metadata.create_all() - - session.add_all([ - Road(road_name='Jeff Rd', road_geom='LINESTRING(-88.9139332929936 42.5082802993631,-88.8203027197452 42.5985669235669,-88.7383759681529 42.7239650127389,-88.6113059044586 42.9680732929936,-88.3655256496815 43.1402866687898)'), - Road(road_name='Peter Rd', road_geom='LINESTRING(-88.9139332929936 42.5082802993631,-88.8203027197452 42.5985669235669,-88.7383759681529 42.7239650127389,-88.6113059044586 42.9680732929936,-88.3655256496815 43.1402866687898)'), - Road(road_name='Geordie Rd', road_geom='LINESTRING(-89.2232485796178 42.6420382611465,-89.2449842484076 42.9179140573248,-89.2316084522293 43.106847178344,-89.0710987261147 43.243949044586,-89.092834566879 43.2957802993631,-89.092834566879 43.2957802993631,-89.0309715095541 43.3175159681529)'), - Road(road_name='Paul St', road_geom='LINESTRING(-88.2652071783439 42.5584395350319,-88.1598727834395 42.6269904904459,-88.1013536751592 42.621974566879,-88.0244428471338 42.6437102356688,-88.0110670509554 42.6771497261147)'), - Road(road_name='Graeme Ave', road_geom='LINESTRING(-88.5477708726115 42.6988853949045,-88.6096339299363 42.9697452675159,-88.6029460318471 43.0884554585987,-88.5912422101911 43.187101955414)'), - Road(road_name='Phil Tce', road_geom='LINESTRING(-88.9356689617834 42.9363057770701,-88.9824842484076 43.0366242484076,-88.9222931656051 43.1085191528662,-88.8487262866242 43.0449841210191)'), - Lake(lake_name='My Lake', lake_geom='POLYGON((-88.7968950764331 43.2305732929936,-88.7935511273885 43.1553344394904,-88.716640299363 43.1570064140127,-88.7250001719745 43.2339172420382,-88.7968950764331 43.2305732929936))'), - Lake(lake_name='Lake White', lake_geom='POLYGON((-88.1147292993631 42.7540605095542,-88.1548566878981 42.7824840764331,-88.1799363057325 42.7707802547771,-88.188296178344 42.7323248407643,-88.1832802547771 42.6955414012739,-88.1565286624204 42.6771496815287,-88.1448248407643 42.6336783439491,-88.131449044586 42.5718152866242,-88.1013535031847 42.565127388535,-88.1080414012739 42.5868630573248,-88.1164012738854 42.6119426751592,-88.1080414012739 42.6520700636943,-88.0980095541401 42.6838375796178,-88.0846337579618 42.7139331210191,-88.1013535031847 42.7423566878981,-88.1147292993631 42.7540605095542))'), - Lake(lake_name='Lake Blue', lake_geom='POLYGON((-89.0694267515924 43.1335987261147,-89.1078821656051 43.1135350318471,-89.1329617834395 43.0884554140127,-89.1312898089172 43.0466560509554,-89.112898089172 43.0132165605096,-89.0694267515924 42.9898089171975,-89.0343152866242 42.953025477707,-89.0209394904459 42.9179140127389,-89.0042197452229 42.8961783439491,-88.9774681528663 42.8644108280255,-88.9440286624204 42.8292993630573,-88.9072452229299 42.8142515923567,-88.8687898089172 42.815923566879,-88.8687898089172 42.815923566879,-88.8102707006369 42.8343152866242,-88.7734872611465 42.8710987261147,-88.7517515923567 42.9145700636943,-88.7433917197452 42.9730891719745,-88.7517515923567 43.0299363057325,-88.7734872611465 43.0867834394905,-88.7885352038217 43.158678388535,-88.8738057324841 43.1620222929936,-88.947372611465 43.1937898089172,-89.0042197452229 43.2138535031847,-89.0410031847134 43.2389331210191,-89.0710987261147 43.243949044586,-89.0660828025478 43.2238853503185,-89.0543789808917 43.203821656051,-89.0376592356688 43.175398089172,-89.0292993630573 43.1519904458599,-89.0376592356688 43.1369426751592,-89.0393312101911 43.1386146496815,-89.0393312101911 43.1386146496815,-89.0510350318471 43.1335987261147,-89.0694267515924 43.1335987261147))'), - Lake(lake_name='Lake Deep', lake_geom='POLYGON((-88.9122611464968 43.038296178344,-88.9222929936306 43.0399681528663,-88.9323248407643 43.0282643312102,-88.9206210191083 43.0182324840764,-88.9105891719745 43.0165605095542,-88.9005573248408 43.0232484076433,-88.9072452229299 43.0282643312102,-88.9122611464968 43.038296178344))'), - Spot(spot_height=420.40, spot_location='POINT(-88.5945861592357 42.9480095987261)'), - Spot(spot_height=102.34, spot_location='POINT(-88.9055734203822 43.0048567324841)'), - Spot(spot_height=388.62, spot_location='POINT(-89.201512910828 43.1051752038217)'), - Spot(spot_height=454.66, spot_location='POINT(-88.3304141847134 42.6269904904459)'), - Shape(shape_name='Bus Stop', shape_geom='GEOMETRYCOLLECTION(POINT(-88.3304141847134 42.6269904904459))'), - Shape(shape_name='Jogging Track', shape_geom='GEOMETRYCOLLECTION(LINESTRING(-88.2652071783439 42.5584395350319,-88.1598727834395 42.6269904904459,-88.1013536751592 42.621974566879,-88.0244428471338 42.6437102356688,-88.0110670509554 42.6771497261147))'), - Shape(shape_name='Play Ground', shape_geom='GEOMETRYCOLLECTION(POLYGON((-88.7968950764331 43.2305732929936,-88.7935511273885 43.1553344394904,-88.716640299363 43.1570064140127,-88.7250001719745 43.2339172420382,-88.7968950764331 43.2305732929936)))'), - ]) - self.r = Road(road_name='Dave Cres', road_geom=WKTSpatialElement('LINESTRING(-88.6748409363057 43.1035032292994,-88.6464173694267 42.9981688343949,-88.607961955414 42.9680732929936,-88.5160033566879 42.9363057770701,-88.4390925286624 43.0031847579618)', 4326)) - session.add(self.r) - session.commit() - - def tearDown(self): - session.rollback() - metadata.drop_all() - - def test_geometry_type(self): - r = session.query(Road).get(1) - l = session.query(Lake).get(1) - s = session.query(Spot).get(1) - eq_(session.scalar(r.road_geom.geometry_type), 'LineString') - eq_(session.scalar(l.lake_geom.geometry_type), 'Polygon') - eq_(session.scalar(s.spot_location.geometry_type), 'Point') - eq_(session.scalar(functions.geometry_type(r.road_geom)), 'LineString') - ok_(session.query(Road).filter(Road.road_geom.geometry_type == 'LineString').first()) - - def test_wkt(self): - l = session.query(Lake).get(1) - assert session.scalar(self.r.road_geom.wkt) == 'LINESTRING (-88.6748409363057 43.1035032292994, -88.6464173694267 42.9981688343949, -88.607961955414 42.9680732929936, -88.5160033566879 42.9363057770701, -88.4390925286624 43.0031847579618)' - eq_(session.scalar(l.lake_geom.wkt),'POLYGON ((-88.7968950764331 43.2305732929936, -88.7935511273885 43.1553344394904, -88.716640299363 43.1570064140127, -88.7250001719745 43.2339172420382, -88.7968950764331 43.2305732929936))') - ok_(not session.query(Spot).filter(Spot.spot_location.wkt == 'POINT (0,0)').first()) - ok_(session.query(Spot).get(1) is - session.query(Spot).filter(Spot.spot_location == 'POINT (-88.5945861592357 42.9480095987261)').first()) - r = session.query(Road).get(1) - p = DBSpatialElement(session.scalar(r.road_geom.point_n(5))) - eq_(session.scalar(p.wkt), u'POINT (-88.3655256496815 43.1402866687898)') - eq_(session.scalar(WKTSpatialElement('POINT (-88.5769371859941 42.9915634871979)').wkt), u'POINT (-88.5769371859941 42.9915634871979)') - eq_(session.query(Spot.spot_location.wkt).filter(Spot.spot_id == 1).first(), (u'POINT (-88.5945861592357 42.9480095987261)',)) - - def test_coords(self): - eq_(self.r.road_geom.coords(session), [[-88.6748409363057,43.1035032292994],[-88.6464173694267,42.9981688343949],[-88.607961955414,42.9680732929936],[-88.5160033566879,42.9363057770701],[-88.4390925286624,43.0031847579618]]) - l = session.query(Lake).filter(Lake.lake_name=="Lake Deep").one() - eq_(l.lake_geom.coords(session), [[[-88.9122611464968,43.038296178344],[-88.9222929936306,43.0399681528663],[-88.9323248407643,43.0282643312102],[-88.9206210191083,43.0182324840764],[-88.9105891719745,43.0165605095542],[-88.9005573248408,43.0232484076433],[-88.9072452229299,43.0282643312102],[-88.9122611464968,43.038296178344]]]) - s = session.query(Spot).filter(Spot.spot_height==102.34).one() - eq_(s.spot_location.coords(session), [-88.905573420382197, 43.0048567324841]) - - def test_wkb(self): - eq_(session.scalar(WKBSpatialElement(session.scalar(self.r.road_geom.wkb)).wkt), - u'LINESTRING (-88.6748409363057 43.1035032292994, -88.6464173694267 42.9981688343949, -88.607961955414 42.9680732929936, -88.5160033566879 42.9363057770701, -88.4390925286624 43.0031847579618)') - eq_(session.scalar(self.r.road_geom.wkb), self.r.road_geom.geom_wkb) - ok_(not session.query(Spot).filter(Spot.spot_location.wkb == '101').first()) - centroid_geom = DBSpatialElement(session.scalar(session.query(Lake).first().lake_geom.centroid)) - eq_(session.scalar(WKBSpatialElement(session.scalar(centroid_geom.wkb)).wkt), u'POINT (-88.757840057564835 43.193797540630335)') - - @raises(AttributeError) - def test_svg(self): - eq_(session.scalar(self.r.road_geom.svg), 'M -88.674840936305699 -43.103503229299399 -88.6464173694267 -42.998168834394903 -88.607961955413998 -42.968073292993601 -88.516003356687904 -42.936305777070103 -88.4390925286624 -43.003184757961797') - ok_(self.r is session.query(Road).filter(Road.road_geom.svg == 'M -88.674840936305699 -43.103503229299399 -88.6464173694267 -42.998168834394903 -88.607961955413998 -42.968073292993601 -88.516003356687904 -42.936305777070103 -88.4390925286624 -43.003184757961797').first()) - eq_(session.scalar(func.svg('POINT(-88.9055734203822 43.0048567324841)')), u'cx="-88.905573420382197" cy="-43.0048567324841"') - ok_(session.query(Spot).filter(Spot.spot_location.svg == 'cx="-88.905573420382197" cy="-43.0048567324841"').first()) - - def test_gml(self): - eq_(session.scalar(self.r.road_geom.gml), '-88.6748409363057 43.1035032292994 -88.6464173694267 42.9981688343949 -88.607961955414 42.9680732929936 -88.5160033566879 42.9363057770701 -88.4390925286624 43.0031847579618') - - @raises(AttributeError) - def test_kml(self): - s = session.query(Spot).filter(Spot.spot_height==420.40).one() - eq_(session.scalar(s.spot_location.kml), u'-88.5945861592357,42.9480095987261') - - @raises(AttributeError) - def test_geojson(self): - s = session.query(Spot).filter(Spot.spot_height==420.40).one() - session.scalar(s.spot_location.geojson) - - def test_dimension(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - l = session.query(Lake).filter(Lake.lake_name=='My Lake').one() - eq_(session.scalar(r.road_geom.dimension), 1) - eq_(session.scalar(l.lake_geom.dimension), 2) - ok_(session.query(Spot).filter(Spot.spot_location.dimension == 0).first() is not None) - eq_(session.scalar(functions.dimension('POINT(-88.5945861592357 42.9480095987261)')), 0) - - def test_srid(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - eq_(session.scalar(r.road_geom.srid), 4326) - ok_(session.query(Spot).filter(Spot.spot_location.srid == 4326).first() is not None) - eq_(session.scalar(functions.srid('POINT(-88.5945861592357 42.9480095987261)')), 4326) - - def test_is_empty(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - l = session.query(Lake).filter(Lake.lake_name=='My Lake').one() - assert not session.scalar(r.road_geom.is_empty) - assert not session.scalar(l.lake_geom.is_empty) - ok_(session.query(Spot).filter(Spot.spot_location.is_empty == False).first() is not None) - eq_(session.scalar(functions.is_empty('POINT(-88.5945861592357 42.9480095987261)')), False) - - def test_is_simple(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - l = session.query(Lake).filter(Lake.lake_name=='My Lake').one() - assert session.scalar(r.road_geom.is_simple) - assert session.scalar(l.lake_geom.is_simple) - ok_(session.query(Spot).filter(Spot.spot_location.is_simple == True).first() is not None) - eq_(session.scalar(functions.is_simple('LINESTRING(1 1,2 2,2 3.5,1 3,1 2,2 1)')), False) - - def test_is_closed(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - l = session.query(Lake).filter(Lake.lake_name=='My Lake').one() - assert not session.scalar(r.road_geom.is_closed) - assert session.scalar(l.lake_geom.is_closed) - ok_(session.query(Lake).filter(Lake.lake_geom.is_closed == True).first() is not None) - eq_(session.scalar(functions.is_closed('LINESTRING(0 0, 1 1)')), False) - - def test_is_ring(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - assert not session.scalar(r.road_geom.is_ring) - ok_(session.query(Road).filter(Road.road_geom.is_ring == True).first() is None) - eq_(session.scalar(functions.is_ring('LINESTRING(0 0, 0 1, 1 0, 1 1, 0 0)')), False) - - def test_num_points(self): - l = session.query(Lake).get(1) - r = session.query(Road).get(1) - s = session.query(Spot).get(1) - ok_(session.scalar(l.lake_geom.num_points)) - eq_(session.scalar(r.road_geom.num_points), 5) - ok_(session.scalar(s.spot_location.num_points)) - ok_(session.query(Road).filter(Road.road_geom.num_points == 5).first() is not None) - eq_(session.scalar(functions.num_points('LINESTRING(77.29 29.07,77.42 29.26,77.27 29.31,77.29 29.07)')), 4) - - def test_point_n(self): - l = session.query(Lake).get(1) - r = session.query(Road).get(1) - ok_(session.scalar(l.lake_geom.point_n(1))) - ok_(session.query(Road).filter(Road.road_geom.point_n(5) == WKTSpatialElement('POINT(-88.3655256496815 43.1402866687898)')).first() is not None) - eq_(session.scalar(functions.wkt(r.road_geom.point_n(5))), u'POINT (-88.3655256496815 43.1402866687898)') - eq_(session.scalar(functions.wkt(functions.point_n('LINESTRING(77.29 29.07,77.42 29.26,77.27 29.31,77.29 29.07)', 1))) - , u'POINT (77.29 29.07)') - - def test_persistent(self): - eq_(session.scalar(self.r.road_geom.wkt), - u'LINESTRING (-88.6748409363057 43.1035032292994, -88.6464173694267 42.9981688343949, -88.607961955414 42.9680732929936, -88.5160033566879 42.9363057770701, -88.4390925286624 43.0031847579618)') - - def test_eq(self): - r1 = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - r2 = session.query(Road).filter(Road.road_geom == 'LINESTRING(-88.5477708726115 42.6988853949045,-88.6096339299363 42.9697452675159,-88.6029460318471 43.0884554585987,-88.5912422101911 43.187101955414)').one() - r3 = session.query(Road).filter(Road.road_geom == r1.road_geom).one() - ok_(r1 is r2 is r3) - - def test_length(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - assert_almost_equal(session.scalar(r.road_geom.length), 0.496071476676014) - ok_(session.query(Road).filter(Road.road_geom.length > 0).first() is not None) - assert_almost_equal(session.scalar(functions.length('LINESTRING(77.29 29.07,77.42 29.26,77.27 29.31,77.29 29.07)')), 0.62916306324869398) - - def test_area(self): - l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() - assert_almost_equal(session.scalar(l.lake_geom.area), 0.10475991566721) - ok_(session.query(Lake).filter(Lake.lake_geom.area > 0).first() is not None) - assert_almost_equal(session.scalar(functions.area(WKTSpatialElement('POLYGON((743238 2967416,743238 2967450,743265 2967450,743265.625 2967416,743238 2967416))',2249))), - 928.625) - - def test_x(self): - s = session.query(Spot).filter(Spot.spot_height==420.40).one() - eq_(session.scalar(s.spot_location.x), -88.5945861592357) - s = session.query(Spot).filter(and_(Spot.spot_location.x < 0, Spot.spot_location.y > 42)).all() - ok_(s is not None) - eq_(session.scalar(functions.x('POINT(-88.3655256496815 43.1402866687898)')), -88.3655256496815) - - def test_y(self): - s = session.query(Spot).filter(Spot.spot_height==420.40).one() - eq_(session.scalar(s.spot_location.y), 42.9480095987261) - s = session.query(Spot).filter(and_(Spot.spot_location.y < 0, Spot.spot_location.y > 42)).all() - ok_(s is not None) - eq_(session.scalar(functions.y('POINT(-88.3655256496815 43.1402866687898)')), 43.1402866687898) - - def test_centroid(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() - ok_(not session.scalar(functions.wkt(r.road_geom.centroid))) - eq_(session.scalar(functions.wkt(l.lake_geom.centroid)), u'POINT (-88.921453826951719 43.019149768468026)') - ok_(session.query(Spot).filter(Spot.spot_location.centroid == WKTSpatialElement('POINT(-88.5945861592357 42.9480095987261)')).first() is None) - ok_(session.scalar(functions.wkt(functions.centroid('MULTIPOINT ( -1 0, -1 2, -1 3, -1 4, -1 7, 0 1, 0 3, 1 1, 2 0, 6 0, 7 8, 9 8, 10 6 )'))) is None) - - def test_boundary(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - eq_(session.scalar(functions.wkt(r.road_geom.boundary)), u'MULTIPOINT ((-88.5912422100082 43.187101952731609), (-88.547770872712135 42.698885396122932))') - ok_(session.query(Road).filter(Road.road_geom.boundary == WKTSpatialElement('MULTIPOINT ((-88.5912422100082 43.187101952731609), (-88.547770872712135 42.698885396122932))')).first() is not None) - eq_(session.scalar(functions.wkt(functions.boundary('POLYGON((1 1,0 0, -1 1, 1 1))'))), - u'LINESTRING (0 0, 1 1, -1 1, 0 0)') - - def test_buffer(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - assert_almost_equal(session.scalar(functions.area(r.road_geom.buffer(10.0))), 323.99187776147323) - ok_(session.query(Spot).filter(functions.within('POINT(-88.5945861592357 42.9480095987261)', Spot.spot_location.buffer(10))).first() is not None) - assert_almost_equal(session.scalar(functions.area(functions.buffer('POINT(-88.5945861592357 42.9480095987261)', 10))), 314.12087152405275) - - def test_convex_hull(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - eq_(session.scalar(functions.wkt(r.road_geom.convex_hull)), - u'POLYGON ((-88.547770872712135 42.698885396122932, -88.5912422100082 43.187101952731609, -88.602946031838655 43.088455460965633, -88.609633930027485 42.969745270907879, -88.547770872712135 42.698885396122932))') - ok_(session.query(Spot).filter(Spot.spot_location.convex_hull == WKTSpatialElement('POINT(-88.5945861592357 42.9480095987261)')).first() is not None) - eq_(session.scalar(functions.wkt(functions.convex_hull('POINT(-88.5945861592357 42.9480095987261)'))), - u'POINT (-88.594586159235689 42.948009598726117)') - - def test_envelope(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - eq_(session.scalar(functions.wkt(r.road_geom.envelope)), - u'POLYGON ((-88.6096339299363 42.6988853949045, -88.5477708726115 42.6988853949045, -88.5477708726115 43.187101955414, -88.6096339299363 43.187101955414, -88.6096339299363 42.6988853949045))') - eq_(session.scalar(functions.geometry_type(self.r.road_geom.envelope)), 'Polygon') - ok_(session.query(Spot).filter(Spot.spot_location.envelope == WKTSpatialElement('POLYGON ((-88.9055744203822 43.0048557324841, -88.9055724203822 43.0048557324841, -88.9055724203822 43.0048577324841, -88.9055744203822 43.0048577324841, -88.9055744203822 43.0048557324841))')).first() is not None) - eq_(session.scalar(functions.wkt(functions.envelope('POINT(-88.5945861592357 42.9480095987261)'))), - u'POLYGON ((-88.5945871592357 42.948008598726105, -88.5945851592357 42.948008598726105, -88.5945851592357 42.9480105987261, -88.5945871592357 42.9480105987261, -88.5945871592357 42.948008598726105))') - - def test_start_point(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - eq_(session.scalar(functions.wkt(r.road_geom.start_point)), - u'POINT (-88.5477708726115 42.6988853949045)') - ok_(session.query(Road).filter(Road.road_geom.start_point == WKTSpatialElement('POINT(-88.9139332929936 42.5082802993631)')).first() is not None) - eq_(session.scalar(functions.wkt(functions.start_point('LINESTRING(0 1, 0 2)'))), - u'POINT (0 1)') - - def test_end_point(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - eq_(session.scalar(functions.wkt(r.road_geom.end_point)), - u'POINT (-88.5912422101911 43.187101955414)') - ok_(session.query(Road).filter(Road.road_geom.end_point == WKTSpatialElement('POINT(-88.3655256496815 43.1402866687898)')).first() is not None) - eq_(session.scalar(functions.wkt(functions.end_point('LINESTRING(0 1, 0 2)'))), - u'POINT (0 2)') - - @raises(NotImplementedError) - def test_transform(self): - spot = session.query(Spot).get(1) - # compare the coordinates using a tolerance, because they may vary on different systems - assert_almost_equal(session.scalar(functions.x(spot.spot_location.transform(2249))), -3890517.6109559298) - assert_almost_equal(session.scalar(functions.y(spot.spot_location.transform(2249))), 3627658.6746507999) - ok_(session.query(Spot).filter(Spot.spot_location.transform(2249) == WKTSpatialElement('POINT(-3890517.61095593 3627658.6746508)', 2249)).first() is not None) - eq_(session.scalar(functions.wkt(functions.transform(WKTSpatialElement('POLYGON((743238 2967416,743238 2967450,743265 2967450,743265.625 2967416,743238 2967416))', 2249), 4326))), - u'POLYGON((-71.1776848522251 42.3902896512902,-71.1776843766326 42.3903829478009,-71.1775844305465 42.3903826677917,-71.1775825927231 42.3902893647987,-71.1776848522251 42.3902896512902))') - - # Test Geometry Relationships - - def test_equals(self): - r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() - r2 = session.query(Road).filter(Road.road_name=='Peter Rd').one() - r3 = session.query(Road).filter(Road.road_name=='Paul St').one() - equal_roads = session.query(Road).filter(Road.road_geom.equals(r1.road_geom)).all() - ok_(r1 in equal_roads) - ok_(r2 in equal_roads) - ok_(r3 not in equal_roads) - ok_(session.query(Spot).filter(Spot.spot_location.equals(WKTSpatialElement('POINT(-88.5945861592357 42.9480095987261)'))).first() is not None) - eq_(session.scalar(functions.equals('POINT(-88.5945861592357 42.9480095987261)', 'POINT(-88.5945861592357 42.9480095987261)')), True) - - def test_distance(self): - r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() - r2 = session.query(Road).filter(Road.road_name=='Geordie Rd').one() - r3 = session.query(Road).filter(Road.road_name=='Peter Rd').one() - assert_almost_equal(session.scalar(r1.road_geom.distance(r2.road_geom)), 0.336997238682841) - eq_(session.scalar(r1.road_geom.distance(r3.road_geom)), 0.0) - ok_(session.query(Spot).filter(Spot.spot_location.distance(WKTSpatialElement('POINT(-88.5945861592357 42.9480095987261)')) < 10).first() is not None) - assert_almost_equal(session.scalar(functions.distance('POINT(-88.5945861592357 42.9480095987261)', 'POINT(-88.5945861592357 42.9480095987261)')), 0) - - @raises(NotImplementedError) - def test_within_distance(self): - r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() - r2 = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - r3 = session.query(Road).filter(Road.road_name=='Geordie Rd').one() - roads_within_distance = session.query(Road).filter( - Road.road_geom.within_distance(r1.road_geom, 0.20)).all() - ok_(r2 in roads_within_distance) - ok_(r3 not in roads_within_distance) - eq_(session.scalar(functions.within_distance('POINT(-88.9139332929936 42.5082802993631)', 'POINT(-88.9139332929936 35.5082802993631)', 10)), True) - - def test_disjoint(self): - r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() - r2 = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - r3 = session.query(Road).filter(Road.road_name=='Geordie Rd').one() - disjoint_roads = session.query(Road).filter(Road.road_geom.disjoint(r1.road_geom)).all() - ok_(r2 not in disjoint_roads) - ok_(r3 in disjoint_roads) - eq_(session.scalar(functions.disjoint('POINT(0 0)', 'LINESTRING ( 2 0, 0 2 )')), True) - - - def test_intersects(self): - r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() - r2 = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - r3 = session.query(Road).filter(Road.road_name=='Geordie Rd').one() - intersecting_roads = session.query(Road).filter(Road.road_geom.intersects(r1.road_geom)).all() - ok_(r2 in intersecting_roads) - ok_(r3 not in intersecting_roads) - eq_(session.scalar(functions.intersects('POINT(0 0)', 'LINESTRING ( 2 0, 0 2 )')), False) - - def test_touches(self): - l1 = session.query(Lake).filter(Lake.lake_name=='Lake White').one() - l2 = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() - r = session.query(Road).filter(Road.road_name=='Geordie Rd').one() - touching_lakes = session.query(Lake).filter(Lake.lake_geom.touches(r.road_geom)).all() - ok_(not session.scalar(l1.lake_geom.touches(r.road_geom))) - ok_(session.scalar(l2.lake_geom.touches(r.road_geom))) - ok_(l1 not in touching_lakes) - ok_(l2 in touching_lakes) - eq_(session.scalar(functions.touches('POINT(1 1)', 'LINESTRING (0 0, 1 1, 0 2)')), False) - - def test_crosses(self): - r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() - r2 = session.query(Road).filter(Road.road_name=='Paul St').one() - l = session.query(Lake).filter(Lake.lake_name=='Lake White').one() - crossing_roads = session.query(Road).filter(Road.road_geom.crosses(l.lake_geom)).all() - ok_(not session.scalar(r1.road_geom.crosses(l.lake_geom))) - ok_(session.scalar(r2.road_geom.crosses(l.lake_geom))) - ok_(r1 not in crossing_roads) - ok_(r2 in crossing_roads) - eq_(session.scalar(functions.crosses('LINESTRING(0 1, 2 1)', 'LINESTRING (0 0, 1 2, 0 2)')), True) - - def test_within(self): - l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() - p1 = session.query(Spot).filter(Spot.spot_height==102.34).one() - p2 = session.query(Spot).filter(Spot.spot_height==388.62).one() - spots_within = session.query(Spot).filter(Spot.spot_location.within(l.lake_geom)).all() - ok_(session.scalar(p1.spot_location.within(l.lake_geom))) - ok_(not session.scalar(p2.spot_location.within(l.lake_geom))) - ok_(p1 in spots_within) - ok_(p2 not in spots_within) - eq_(session.scalar(functions.within('LINESTRING(0 1, 2 1)', 'POLYGON((-1 -1, 3 -1, 3 2, -1 2, -1 -1))')), True) - buffer_geom = DBSpatialElement(session.scalar(l.lake_geom.buffer(10.0))) - spots_within = session.query(Spot).filter(l.lake_geom.within(buffer_geom)).all() - ok_(p1 in spots_within) - ok_(p2 in spots_within) - - def test_overlaps(self): - l1 = session.query(Lake).filter(Lake.lake_name=='Lake White').one() - l2 = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() - l3 = session.query(Lake).filter(Lake.lake_name=='My Lake').one() - overlapping_lakes = session.query(Lake).filter(Lake.lake_geom.overlaps(l3.lake_geom)).all() - ok_(not session.scalar(l1.lake_geom.overlaps(l3.lake_geom))) - ok_(session.scalar(l2.lake_geom.overlaps(l3.lake_geom))) - ok_(l1 not in overlapping_lakes) - ok_(l2 in overlapping_lakes) - eq_(session.scalar(functions.overlaps('POLYGON((2 1, 4 1, 4 3, 2 3, 2 1))', 'POLYGON((-1 -1, 3 -1, 3 2, -1 2, -1 -1))')), True) - - def test_contains(self): - l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() - l1 = session.query(Lake).filter(Lake.lake_name=='Lake White').one() - p1 = session.query(Spot).filter(Spot.spot_height==102.34).one() - p2 = session.query(Spot).filter(Spot.spot_height==388.62).one() - containing_lakes = session.query(Lake).filter(Lake.lake_geom.gcontains(p1.spot_location)).all() - ok_(session.scalar(l.lake_geom.gcontains(p1.spot_location))) - ok_(not session.scalar(l.lake_geom.gcontains(p2.spot_location))) - ok_(l in containing_lakes) - ok_(l1 not in containing_lakes) - ok_(session.scalar(l.lake_geom.gcontains(WKTSpatialElement('POINT(-88.9055734203822 43.0048567324841)')))) - containing_lakes = session.query(Lake).filter(Lake.lake_geom.gcontains('POINT(-88.9055734203822 43.0048567324841)')).all() - ok_(l in containing_lakes) - ok_(l1 not in containing_lakes) - spots_within = session.query(Spot).filter(l.lake_geom.gcontains(Spot.spot_location)).all() - ok_(session.scalar(l.lake_geom.gcontains(p1.spot_location))) - ok_(not session.scalar(l.lake_geom.gcontains(p2.spot_location))) - ok_(p1 in spots_within) - ok_(p2 not in spots_within) - eq_(session.scalar(functions.gcontains('LINESTRING(0 1, 2 1)', 'POLYGON((-1 -1, 3 -1, 3 2, -1 2, -1 -1))')), False) - - @raises(NotImplementedError) - def test_covers(self): - l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() - l1 = session.query(Lake).filter(Lake.lake_name=='Lake White').one() - p1 = session.query(Spot).filter(Spot.spot_height==102.34).one() - p2 = session.query(Spot).filter(Spot.spot_height==388.62).one() - covering_lakes = session.query(Lake).filter(Lake.lake_geom.covers(p1.spot_location)).all() - ok_(session.scalar(l.lake_geom.covers(p1.spot_location))) - ok_(not session.scalar(l.lake_geom.covers(p2.spot_location))) - ok_(l in covering_lakes) - ok_(l1 not in covering_lakes) - eq_(session.scalar(functions.gcontains('LINESTRING(0 1, 2 1)', 'POLYGON((-1 -1, 3 -1, 3 2, -1 2, -1 -1))')), False) - - @raises(NotImplementedError) - def test_covered_by(self): - l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() - p1 = session.query(Spot).filter(Spot.spot_height==102.34).one() - p2 = session.query(Spot).filter(Spot.spot_height==388.62).one() - covered_spots = session.query(Spot).filter(Spot.spot_location.covered_by(l.lake_geom)).all() - ok_(session.scalar(p1.spot_location.covered_by(l.lake_geom))) - ok_(not session.scalar(p2.spot_location.covered_by(l.lake_geom))) - ok_(p1 in covered_spots) - ok_(p2 not in covered_spots) - eq_(session.scalar(functions.covered_by('LINESTRING(0 1, 2 1)', 'POLYGON((-1 -1, 3 -1, 3 2, -1 2, -1 -1))')), True) - - @raises(NotImplementedError) - def test_intersection(self): - l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() - r = session.query(Road).filter(Road.road_name=='Geordie Rd').one() - s = session.query(Spot).filter(Spot.spot_height==454.66).one() - eq_(session.scalar(func.STAsText(l.lake_geom.intersection(s.spot_location))), 'GEOMETRYCOLLECTION EMPTY') - eq_(session.scalar(func.STAsText(session.scalar(l.lake_geom.intersection(r.road_geom)))), 'POINT(-89.0710987261147 43.243949044586)') - l = session.query(Lake).filter(Lake.lake_name=='Lake White').one() - r = session.query(Road).filter(Road.road_name=='Paul St').one() - eq_(session.scalar(func.STAsText(session.scalar(l.lake_geom.intersection(r.road_geom)))), 'LINESTRING(-88.1430673666454 42.6255500261493,-88.1140839697546 42.6230657349872)') - ok_(session.query(Lake).filter(Lake.lake_geom.intersection(r.road_geom) == WKTSpatialElement('LINESTRING(-88.1430673666454 42.6255500261493,-88.1140839697546 42.6230657349872)')).first() is not None) - - @raises(IntegrityError) - def test_constraint_nullable(self): - spot_null = Spot(spot_height=420.40, spot_location=MS_SPATIAL_NULL) - session.add(spot_null) - session.commit(); - ok_(True) - road_null = Road(road_name='Jeff Rd', road_geom=MS_SPATIAL_NULL) - session.add(road_null) - session.commit(); - - # Test SQL Server specific functions - - def test_text_zm(self): - engine.execute('INSERT INTO [spots] VALUES(%f, geometry::STGeomFromText(%s, %i))' % (130.23, "'POINT (-88.5945861592357 42.9480095987261 130.23 1)'", 4326)) - eq_(session.query(Spot.spot_location.text_zm.label('text_zm')).filter(Spot.spot_height==130.23).first().text_zm, u'POINT (-88.5945861592357 42.9480095987261 130.23 1)') - eq_(session.query(Spot.spot_location.text_zm.label('text_zm')).filter(Spot.spot_height==420.40).first().text_zm, u'POINT (-88.5945861592357 42.9480095987261)') - - - def test_buffer_with_tolerance(self): - r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - assert_almost_equal(session.scalar(functions.area(r.road_geom.buffer_with_tolerance(10.0, 20, 1))), 214.63894668789601) - assert_almost_equal(session.scalar(functions.area(r.road_geom.buffer_with_tolerance(10.0, 20, 0))), 214.63894668789601) - ok_(session.query(Spot).filter(functions.within('POINT(-88.5945861592357 42.9480095987261)', Spot.spot_location.buffer(10))).first() is not None) - assert_almost_equal(session.scalar(functions.area(ms_functions.buffer_with_tolerance('POINT(-88.5945861592357 42.9480095987261)', 10, 2, 0))), 306.21843345678644) - - def test_filter(self): - r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() - r2 = session.query(Road).filter(Road.road_name=='Graeme Ave').one() - r3 = session.query(Road).filter(Road.road_name=='Geordie Rd').one() - intersecting_roads = session.query(Road).filter(Road.road_geom.filter(r1.road_geom)).all() - ok_(r2 in intersecting_roads) - ok_(r3 not in intersecting_roads) - eq_(session.scalar(ms_functions.filter('POINT(0 0)', 'LINESTRING ( 2 0, 0 2 )')), False) - - def test_instance_of(self): - ok_(session.query(Road).filter(Road.road_geom.instance_of('LINESTRING')).first() is not None) - ok_(session.query(Lake).filter(Lake.lake_geom.instance_of('POLYGON')).first() is not None) - ok_(session.query(Spot).filter(Spot.spot_location.instance_of('POINT')).first() is not None) - - def test_extended_coords(self): - engine.execute('INSERT INTO [spots] VALUES(%f, geometry::STGeomFromText(%s, %i))' % (130.23, "'POINT (-88.5945861592357 42.9480095987261 130.23 1)'", 4326)) - p = session.query(Spot.spot_location.z.label('z'), Spot.spot_location.m.label('m')).filter(Spot.spot_height==130.23).first() - eq_(p.z, 130.23) - eq_(p.m, 1) - p = session.query(Spot.spot_location.z.label('z'), Spot.spot_location.m.label('m')).filter(Spot.spot_height==420.40).first() - ok_(p.z is None) - ok_(p.m is None) - - def test_make_valid(self): - session.add(Shape(shape_name=u'Invalid Shape', shape_geom=WKTSpatialElement(u'LINESTRING(0 2, 1 1, 1 0, 1 1, 2 2)'))) - invalid_line = session.query(Shape).filter(Shape.shape_name==u'Invalid Shape').first() - eq_(session.scalar(invalid_line.shape_geom.is_valid), 0) - invalid_line.shape_geom = DBSpatialElement(session.scalar(invalid_line.shape_geom.make_valid)) - valid_line = session.query(Shape).filter(Shape.shape_name==u'Invalid Shape').first() - eq_(session.scalar(valid_line.shape_geom.is_valid), 1) - - - def test_reduce(self): - r = session.query(Road).first() - eq_(session.scalar(DBSpatialElement(session.scalar(r.road_geom.reduce(0.5))).wkt), - u'LINESTRING (-88.9139332929936 42.5082802993631, -88.3655256496815 43.1402866687898)') - eq_(session.scalar(DBSpatialElement(session.scalar(r.road_geom.reduce(0.05))).wkt), - u'LINESTRING (-88.9139332929936 42.5082802993631, -88.6113059044586 42.9680732929936, -88.3655256496815 43.1402866687898)') - eq_(session.scalar(DBSpatialElement(session.scalar(r.road_geom.reduce(0.0000000000001))).wkt), - session.scalar(r.road_geom.wkt)) - - - def test_to_string(self): - engine.execute('INSERT INTO [spots] VALUES(%f, geometry::STGeomFromText(%s, %i))' % (130.23, "'POINT (-88.5945861592357 42.9480095987261 130.23 1)'", 4326)) - session.add(Lake(lake_name=u'Vanished lake', lake_geom=MS_SPATIAL_NULL)) - eq_(session.query(Spot.spot_location.text_zm.label('to_string')).filter(Spot.spot_height==130.23).first().to_string, u'POINT (-88.5945861592357 42.9480095987261 130.23 1)') - eq_(session.query(Spot.spot_location.text_zm.label('to_string')).filter(Spot.spot_height==420.40).first().to_string, u'POINT (-88.5945861592357 42.9480095987261)') - ok_(session.query(Lake.lake_geom.to_string.label('to_string')).filter(Lake.lake_name==u'Vanished lake').first().to_string is None) - - -if __name__ == '__main__': - import sys - import nose - - sys.argv.append(__name__) - result = nose.run() - sys.exit(int(not result)) +# -*- coding: utf-8 -*- + +from sqlalchemy import (create_engine, MetaData, Column, Integer, String, + Numeric, func, Table, and_) +from sqlalchemy.orm import sessionmaker, mapper +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.exc import IntegrityError +from geoalchemy import GeometryColumn, Geometry, LineString, Polygon, GeometryDDL, GeometryExtensionColumn, GeometryCollection, DBSpatialElement, WKTSpatialElement, WKBSpatialElement +from geoalchemy.functions import functions +from geoalchemy.mssql import MS_SPATIAL_NULL, ms_functions, MSComparator +from unittest import TestCase +from nose.tools import eq_, ok_, raises, assert_almost_equal + +u""" +.. moduleauthor:: Mark Hall +""" + +engine = create_engine('mssql+pyodbc://gis:gis@localhost:4331/gis', echo=True) +metadata = MetaData(engine) +session = sessionmaker(bind=engine)() +Base = declarative_base(metadata=metadata) + +class Road(Base): + __tablename__ = 'ROADS' + + road_id = Column(Integer, primary_key=True) + road_name = Column(String(255)) + road_geom = GeometryColumn(LineString(2, bounding_box='(xmin=-180, ymin=-90, xmax=180, ymax=90)'), comparator=MSComparator, nullable=False) + +class Lake(Base): + __tablename__ = 'lakes' + + lake_id = Column(Integer, primary_key=True) + lake_name = Column(String(255)) + lake_geom = GeometryColumn(Polygon(2), comparator=MSComparator) + +spots_table = Table('spots', metadata, + Column('spot_id', Integer, primary_key=True), + Column('spot_height', Numeric(6, 2)), + GeometryExtensionColumn('spot_location', Geometry(2))) + +class Spot(object): + def __init__(self, spot_id=None, spot_height=None, spot_location=None): + self.spot_id = spot_id + self.spot_height = spot_height + self.spot_location = spot_location + + +mapper(Spot, spots_table, properties={ + 'spot_location': GeometryColumn(spots_table.c.spot_location, comparator=MSComparator)}) + +class Shape(Base): + __tablename__ = 'shapes' + + shape_id = Column(Integer, primary_key=True) + shape_name = Column(String(255)) + shape_geom = GeometryColumn(GeometryCollection(2)) + +# enable the DDL extension, which allows CREATE/DROP operations +# to work correctly. This is not needed if working with externally +# defined tables. +GeometryDDL(Road.__table__) +GeometryDDL(Lake.__table__) +GeometryDDL(spots_table) +GeometryDDL(Shape.__table__) + + +class TestGeometry(TestCase): + + def setUp(self): + metadata.drop_all() + metadata.create_all() + + session.add_all([ + Road(road_name='Jeff Rd', road_geom='LINESTRING(-88.9139332929936 42.5082802993631,-88.8203027197452 42.5985669235669,-88.7383759681529 42.7239650127389,-88.6113059044586 42.9680732929936,-88.3655256496815 43.1402866687898)'), + Road(road_name='Peter Rd', road_geom='LINESTRING(-88.9139332929936 42.5082802993631,-88.8203027197452 42.5985669235669,-88.7383759681529 42.7239650127389,-88.6113059044586 42.9680732929936,-88.3655256496815 43.1402866687898)'), + Road(road_name='Geordie Rd', road_geom='LINESTRING(-89.2232485796178 42.6420382611465,-89.2449842484076 42.9179140573248,-89.2316084522293 43.106847178344,-89.0710987261147 43.243949044586,-89.092834566879 43.2957802993631,-89.092834566879 43.2957802993631,-89.0309715095541 43.3175159681529)'), + Road(road_name='Paul St', road_geom='LINESTRING(-88.2652071783439 42.5584395350319,-88.1598727834395 42.6269904904459,-88.1013536751592 42.621974566879,-88.0244428471338 42.6437102356688,-88.0110670509554 42.6771497261147)'), + Road(road_name='Graeme Ave', road_geom='LINESTRING(-88.5477708726115 42.6988853949045,-88.6096339299363 42.9697452675159,-88.6029460318471 43.0884554585987,-88.5912422101911 43.187101955414)'), + Road(road_name='Phil Tce', road_geom='LINESTRING(-88.9356689617834 42.9363057770701,-88.9824842484076 43.0366242484076,-88.9222931656051 43.1085191528662,-88.8487262866242 43.0449841210191)'), + Lake(lake_name='My Lake', lake_geom='POLYGON((-88.7968950764331 43.2305732929936,-88.7935511273885 43.1553344394904,-88.716640299363 43.1570064140127,-88.7250001719745 43.2339172420382,-88.7968950764331 43.2305732929936))'), + Lake(lake_name='Lake White', lake_geom='POLYGON((-88.1147292993631 42.7540605095542,-88.1548566878981 42.7824840764331,-88.1799363057325 42.7707802547771,-88.188296178344 42.7323248407643,-88.1832802547771 42.6955414012739,-88.1565286624204 42.6771496815287,-88.1448248407643 42.6336783439491,-88.131449044586 42.5718152866242,-88.1013535031847 42.565127388535,-88.1080414012739 42.5868630573248,-88.1164012738854 42.6119426751592,-88.1080414012739 42.6520700636943,-88.0980095541401 42.6838375796178,-88.0846337579618 42.7139331210191,-88.1013535031847 42.7423566878981,-88.1147292993631 42.7540605095542))'), + Lake(lake_name='Lake Blue', lake_geom='POLYGON((-89.0694267515924 43.1335987261147,-89.1078821656051 43.1135350318471,-89.1329617834395 43.0884554140127,-89.1312898089172 43.0466560509554,-89.112898089172 43.0132165605096,-89.0694267515924 42.9898089171975,-89.0343152866242 42.953025477707,-89.0209394904459 42.9179140127389,-89.0042197452229 42.8961783439491,-88.9774681528663 42.8644108280255,-88.9440286624204 42.8292993630573,-88.9072452229299 42.8142515923567,-88.8687898089172 42.815923566879,-88.8687898089172 42.815923566879,-88.8102707006369 42.8343152866242,-88.7734872611465 42.8710987261147,-88.7517515923567 42.9145700636943,-88.7433917197452 42.9730891719745,-88.7517515923567 43.0299363057325,-88.7734872611465 43.0867834394905,-88.7885352038217 43.158678388535,-88.8738057324841 43.1620222929936,-88.947372611465 43.1937898089172,-89.0042197452229 43.2138535031847,-89.0410031847134 43.2389331210191,-89.0710987261147 43.243949044586,-89.0660828025478 43.2238853503185,-89.0543789808917 43.203821656051,-89.0376592356688 43.175398089172,-89.0292993630573 43.1519904458599,-89.0376592356688 43.1369426751592,-89.0393312101911 43.1386146496815,-89.0393312101911 43.1386146496815,-89.0510350318471 43.1335987261147,-89.0694267515924 43.1335987261147))'), + Lake(lake_name='Lake Deep', lake_geom='POLYGON((-88.9122611464968 43.038296178344,-88.9222929936306 43.0399681528663,-88.9323248407643 43.0282643312102,-88.9206210191083 43.0182324840764,-88.9105891719745 43.0165605095542,-88.9005573248408 43.0232484076433,-88.9072452229299 43.0282643312102,-88.9122611464968 43.038296178344))'), + Spot(spot_height=420.40, spot_location='POINT(-88.5945861592357 42.9480095987261)'), + Spot(spot_height=102.34, spot_location='POINT(-88.9055734203822 43.0048567324841)'), + Spot(spot_height=388.62, spot_location='POINT(-89.201512910828 43.1051752038217)'), + Spot(spot_height=454.66, spot_location='POINT(-88.3304141847134 42.6269904904459)'), + Shape(shape_name='Bus Stop', shape_geom='GEOMETRYCOLLECTION(POINT(-88.3304141847134 42.6269904904459))'), + Shape(shape_name='Jogging Track', shape_geom='GEOMETRYCOLLECTION(LINESTRING(-88.2652071783439 42.5584395350319,-88.1598727834395 42.6269904904459,-88.1013536751592 42.621974566879,-88.0244428471338 42.6437102356688,-88.0110670509554 42.6771497261147))'), + Shape(shape_name='Play Ground', shape_geom='GEOMETRYCOLLECTION(POLYGON((-88.7968950764331 43.2305732929936,-88.7935511273885 43.1553344394904,-88.716640299363 43.1570064140127,-88.7250001719745 43.2339172420382,-88.7968950764331 43.2305732929936)))'), + ]) + self.r = Road(road_name='Dave Cres', road_geom=WKTSpatialElement('LINESTRING(-88.6748409363057 43.1035032292994,-88.6464173694267 42.9981688343949,-88.607961955414 42.9680732929936,-88.5160033566879 42.9363057770701,-88.4390925286624 43.0031847579618)', 4326)) + session.add(self.r) + session.commit() + + def tearDown(self): + session.rollback() + metadata.drop_all() + + def test_geometry_type(self): + r = session.query(Road).get(1) + l = session.query(Lake).get(1) + s = session.query(Spot).get(1) + eq_(session.scalar(r.road_geom.geometry_type), 'LineString') + eq_(session.scalar(l.lake_geom.geometry_type), 'Polygon') + eq_(session.scalar(s.spot_location.geometry_type), 'Point') + eq_(session.scalar(functions.geometry_type(r.road_geom)), 'LineString') + ok_(session.query(Road).filter(Road.road_geom.geometry_type == 'LineString').first()) + + def test_wkt(self): + l = session.query(Lake).get(1) + assert session.scalar(self.r.road_geom.wkt) == 'LINESTRING (-88.6748409363057 43.1035032292994, -88.6464173694267 42.9981688343949, -88.607961955414 42.9680732929936, -88.5160033566879 42.9363057770701, -88.4390925286624 43.0031847579618)' + eq_(session.scalar(l.lake_geom.wkt),'POLYGON ((-88.7968950764331 43.2305732929936, -88.7935511273885 43.1553344394904, -88.716640299363 43.1570064140127, -88.7250001719745 43.2339172420382, -88.7968950764331 43.2305732929936))') + ok_(not session.query(Spot).filter(Spot.spot_location.wkt == 'POINT (0,0)').first()) + ok_(session.query(Spot).get(1) is + session.query(Spot).filter(Spot.spot_location == 'POINT (-88.5945861592357 42.9480095987261)').first()) + r = session.query(Road).get(1) + p = DBSpatialElement(session.scalar(r.road_geom.point_n(5))) + eq_(session.scalar(p.wkt), u'POINT (-88.3655256496815 43.1402866687898)') + eq_(session.scalar(WKTSpatialElement('POINT (-88.5769371859941 42.9915634871979)').wkt), u'POINT (-88.5769371859941 42.9915634871979)') + eq_(session.query(Spot.spot_location.wkt).filter(Spot.spot_id == 1).first(), (u'POINT (-88.5945861592357 42.9480095987261)',)) + + def test_coords(self): + eq_(self.r.road_geom.coords(session), [[-88.6748409363057,43.1035032292994],[-88.6464173694267,42.9981688343949],[-88.607961955414,42.9680732929936],[-88.5160033566879,42.9363057770701],[-88.4390925286624,43.0031847579618]]) + l = session.query(Lake).filter(Lake.lake_name=="Lake Deep").one() + eq_(l.lake_geom.coords(session), [[[-88.9122611464968,43.038296178344],[-88.9222929936306,43.0399681528663],[-88.9323248407643,43.0282643312102],[-88.9206210191083,43.0182324840764],[-88.9105891719745,43.0165605095542],[-88.9005573248408,43.0232484076433],[-88.9072452229299,43.0282643312102],[-88.9122611464968,43.038296178344]]]) + s = session.query(Spot).filter(Spot.spot_height==102.34).one() + eq_(s.spot_location.coords(session), [-88.905573420382197, 43.0048567324841]) + + def test_wkb(self): + eq_(session.scalar(WKBSpatialElement(session.scalar(self.r.road_geom.wkb)).wkt), + u'LINESTRING (-88.6748409363057 43.1035032292994, -88.6464173694267 42.9981688343949, -88.607961955414 42.9680732929936, -88.5160033566879 42.9363057770701, -88.4390925286624 43.0031847579618)') + eq_(session.scalar(self.r.road_geom.wkb), self.r.road_geom.geom_wkb) + ok_(not session.query(Spot).filter(Spot.spot_location.wkb == '101').first()) + centroid_geom = DBSpatialElement(session.scalar(session.query(Lake).first().lake_geom.centroid)) + eq_(session.scalar(WKBSpatialElement(session.scalar(centroid_geom.wkb)).wkt), u'POINT (-88.757840057564835 43.193797540630335)') + + @raises(AttributeError) + def test_svg(self): + eq_(session.scalar(self.r.road_geom.svg), 'M -88.674840936305699 -43.103503229299399 -88.6464173694267 -42.998168834394903 -88.607961955413998 -42.968073292993601 -88.516003356687904 -42.936305777070103 -88.4390925286624 -43.003184757961797') + ok_(self.r is session.query(Road).filter(Road.road_geom.svg == 'M -88.674840936305699 -43.103503229299399 -88.6464173694267 -42.998168834394903 -88.607961955413998 -42.968073292993601 -88.516003356687904 -42.936305777070103 -88.4390925286624 -43.003184757961797').first()) + eq_(session.scalar(func.svg('POINT(-88.9055734203822 43.0048567324841)')), u'cx="-88.905573420382197" cy="-43.0048567324841"') + ok_(session.query(Spot).filter(Spot.spot_location.svg == 'cx="-88.905573420382197" cy="-43.0048567324841"').first()) + + def test_gml(self): + eq_(session.scalar(self.r.road_geom.gml), '-88.6748409363057 43.1035032292994 -88.6464173694267 42.9981688343949 -88.607961955414 42.9680732929936 -88.5160033566879 42.9363057770701 -88.4390925286624 43.0031847579618') + + @raises(AttributeError) + def test_kml(self): + s = session.query(Spot).filter(Spot.spot_height==420.40).one() + eq_(session.scalar(s.spot_location.kml), u'-88.5945861592357,42.9480095987261') + + @raises(AttributeError) + def test_geojson(self): + s = session.query(Spot).filter(Spot.spot_height==420.40).one() + session.scalar(s.spot_location.geojson) + + def test_dimension(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + l = session.query(Lake).filter(Lake.lake_name=='My Lake').one() + eq_(session.scalar(r.road_geom.dimension), 1) + eq_(session.scalar(l.lake_geom.dimension), 2) + ok_(session.query(Spot).filter(Spot.spot_location.dimension == 0).first() is not None) + eq_(session.scalar(functions.dimension('POINT(-88.5945861592357 42.9480095987261)')), 0) + + def test_srid(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + eq_(session.scalar(r.road_geom.srid), 4326) + ok_(session.query(Spot).filter(Spot.spot_location.srid == 4326).first() is not None) + eq_(session.scalar(functions.srid('POINT(-88.5945861592357 42.9480095987261)')), 4326) + + def test_is_empty(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + l = session.query(Lake).filter(Lake.lake_name=='My Lake').one() + assert not session.scalar(r.road_geom.is_empty) + assert not session.scalar(l.lake_geom.is_empty) + ok_(session.query(Spot).filter(Spot.spot_location.is_empty == False).first() is not None) + eq_(session.scalar(functions.is_empty('POINT(-88.5945861592357 42.9480095987261)')), False) + + def test_is_simple(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + l = session.query(Lake).filter(Lake.lake_name=='My Lake').one() + assert session.scalar(r.road_geom.is_simple) + assert session.scalar(l.lake_geom.is_simple) + ok_(session.query(Spot).filter(Spot.spot_location.is_simple == True).first() is not None) + eq_(session.scalar(functions.is_simple('LINESTRING(1 1,2 2,2 3.5,1 3,1 2,2 1)')), False) + + def test_is_closed(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + l = session.query(Lake).filter(Lake.lake_name=='My Lake').one() + assert not session.scalar(r.road_geom.is_closed) + assert session.scalar(l.lake_geom.is_closed) + ok_(session.query(Lake).filter(Lake.lake_geom.is_closed == True).first() is not None) + eq_(session.scalar(functions.is_closed('LINESTRING(0 0, 1 1)')), False) + + def test_is_ring(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + assert not session.scalar(r.road_geom.is_ring) + ok_(session.query(Road).filter(Road.road_geom.is_ring == True).first() is None) + eq_(session.scalar(functions.is_ring('LINESTRING(0 0, 0 1, 1 0, 1 1, 0 0)')), False) + + def test_num_points(self): + l = session.query(Lake).get(1) + r = session.query(Road).get(1) + s = session.query(Spot).get(1) + ok_(session.scalar(l.lake_geom.num_points)) + eq_(session.scalar(r.road_geom.num_points), 5) + ok_(session.scalar(s.spot_location.num_points)) + ok_(session.query(Road).filter(Road.road_geom.num_points == 5).first() is not None) + eq_(session.scalar(functions.num_points('LINESTRING(77.29 29.07,77.42 29.26,77.27 29.31,77.29 29.07)')), 4) + + def test_point_n(self): + l = session.query(Lake).get(1) + r = session.query(Road).get(1) + ok_(session.scalar(l.lake_geom.point_n(1))) + ok_(session.query(Road).filter(Road.road_geom.point_n(5) == WKTSpatialElement('POINT(-88.3655256496815 43.1402866687898)')).first() is not None) + eq_(session.scalar(functions.wkt(r.road_geom.point_n(5))), u'POINT (-88.3655256496815 43.1402866687898)') + eq_(session.scalar(functions.wkt(functions.point_n('LINESTRING(77.29 29.07,77.42 29.26,77.27 29.31,77.29 29.07)', 1))) + , u'POINT (77.29 29.07)') + + def test_persistent(self): + eq_(session.scalar(self.r.road_geom.wkt), + u'LINESTRING (-88.6748409363057 43.1035032292994, -88.6464173694267 42.9981688343949, -88.607961955414 42.9680732929936, -88.5160033566879 42.9363057770701, -88.4390925286624 43.0031847579618)') + + def test_eq(self): + r1 = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + r2 = session.query(Road).filter(Road.road_geom == 'LINESTRING(-88.5477708726115 42.6988853949045,-88.6096339299363 42.9697452675159,-88.6029460318471 43.0884554585987,-88.5912422101911 43.187101955414)').one() + r3 = session.query(Road).filter(Road.road_geom == r1.road_geom).one() + ok_(r1 is r2 is r3) + + def test_length(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + assert_almost_equal(session.scalar(r.road_geom.length), 0.496071476676014) + ok_(session.query(Road).filter(Road.road_geom.length > 0).first() is not None) + assert_almost_equal(session.scalar(functions.length('LINESTRING(77.29 29.07,77.42 29.26,77.27 29.31,77.29 29.07)')), 0.62916306324869398) + + def test_area(self): + l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() + assert_almost_equal(session.scalar(l.lake_geom.area), 0.10475991566721) + ok_(session.query(Lake).filter(Lake.lake_geom.area > 0).first() is not None) + assert_almost_equal(session.scalar(functions.area(WKTSpatialElement('POLYGON((743238 2967416,743238 2967450,743265 2967450,743265.625 2967416,743238 2967416))',2249))), + 928.625) + + def test_x(self): + s = session.query(Spot).filter(Spot.spot_height==420.40).one() + eq_(session.scalar(s.spot_location.x), -88.5945861592357) + s = session.query(Spot).filter(and_(Spot.spot_location.x < 0, Spot.spot_location.y > 42)).all() + ok_(s is not None) + eq_(session.scalar(functions.x('POINT(-88.3655256496815 43.1402866687898)')), -88.3655256496815) + + def test_y(self): + s = session.query(Spot).filter(Spot.spot_height==420.40).one() + eq_(session.scalar(s.spot_location.y), 42.9480095987261) + s = session.query(Spot).filter(and_(Spot.spot_location.y < 0, Spot.spot_location.y > 42)).all() + ok_(s is not None) + eq_(session.scalar(functions.y('POINT(-88.3655256496815 43.1402866687898)')), 43.1402866687898) + + def test_centroid(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() + ok_(not session.scalar(functions.wkt(r.road_geom.centroid))) + eq_(session.scalar(functions.wkt(l.lake_geom.centroid)), u'POINT (-88.921453826951719 43.019149768468026)') + ok_(session.query(Spot).filter(Spot.spot_location.centroid == WKTSpatialElement('POINT(-88.5945861592357 42.9480095987261)')).first() is None) + ok_(session.scalar(functions.wkt(functions.centroid('MULTIPOINT ( -1 0, -1 2, -1 3, -1 4, -1 7, 0 1, 0 3, 1 1, 2 0, 6 0, 7 8, 9 8, 10 6 )'))) is None) + + def test_boundary(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + eq_(session.scalar(functions.wkt(r.road_geom.boundary)), u'MULTIPOINT ((-88.5912422100082 43.187101952731609), (-88.547770872712135 42.698885396122932))') + ok_(session.query(Road).filter(Road.road_geom.boundary == WKTSpatialElement('MULTIPOINT ((-88.5912422100082 43.187101952731609), (-88.547770872712135 42.698885396122932))')).first() is not None) + eq_(session.scalar(functions.wkt(functions.boundary('POLYGON((1 1,0 0, -1 1, 1 1))'))), + u'LINESTRING (0 0, 1 1, -1 1, 0 0)') + + def test_buffer(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + assert_almost_equal(session.scalar(functions.area(r.road_geom.buffer(10.0))), 323.99187776147323) + ok_(session.query(Spot).filter(functions.within('POINT(-88.5945861592357 42.9480095987261)', Spot.spot_location.buffer(10))).first() is not None) + assert_almost_equal(session.scalar(functions.area(functions.buffer('POINT(-88.5945861592357 42.9480095987261)', 10))), 314.12087152405275) + + def test_convex_hull(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + eq_(session.scalar(functions.wkt(r.road_geom.convex_hull)), + u'POLYGON ((-88.547770872712135 42.698885396122932, -88.5912422100082 43.187101952731609, -88.602946031838655 43.088455460965633, -88.609633930027485 42.969745270907879, -88.547770872712135 42.698885396122932))') + ok_(session.query(Spot).filter(Spot.spot_location.convex_hull == WKTSpatialElement('POINT(-88.5945861592357 42.9480095987261)')).first() is not None) + eq_(session.scalar(functions.wkt(functions.convex_hull('POINT(-88.5945861592357 42.9480095987261)'))), + u'POINT (-88.594586159235689 42.948009598726117)') + + def test_envelope(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + eq_(session.scalar(functions.wkt(r.road_geom.envelope)), + u'POLYGON ((-88.6096339299363 42.6988853949045, -88.5477708726115 42.6988853949045, -88.5477708726115 43.187101955414, -88.6096339299363 43.187101955414, -88.6096339299363 42.6988853949045))') + eq_(session.scalar(functions.geometry_type(self.r.road_geom.envelope)), 'Polygon') + ok_(session.query(Spot).filter(Spot.spot_location.envelope == WKTSpatialElement('POLYGON ((-88.9055744203822 43.0048557324841, -88.9055724203822 43.0048557324841, -88.9055724203822 43.0048577324841, -88.9055744203822 43.0048577324841, -88.9055744203822 43.0048557324841))')).first() is not None) + eq_(session.scalar(functions.wkt(functions.envelope('POINT(-88.5945861592357 42.9480095987261)'))), + u'POLYGON ((-88.5945871592357 42.948008598726105, -88.5945851592357 42.948008598726105, -88.5945851592357 42.9480105987261, -88.5945871592357 42.9480105987261, -88.5945871592357 42.948008598726105))') + + def test_start_point(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + eq_(session.scalar(functions.wkt(r.road_geom.start_point)), + u'POINT (-88.5477708726115 42.6988853949045)') + ok_(session.query(Road).filter(Road.road_geom.start_point == WKTSpatialElement('POINT(-88.9139332929936 42.5082802993631)')).first() is not None) + eq_(session.scalar(functions.wkt(functions.start_point('LINESTRING(0 1, 0 2)'))), + u'POINT (0 1)') + + def test_end_point(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + eq_(session.scalar(functions.wkt(r.road_geom.end_point)), + u'POINT (-88.5912422101911 43.187101955414)') + ok_(session.query(Road).filter(Road.road_geom.end_point == WKTSpatialElement('POINT(-88.3655256496815 43.1402866687898)')).first() is not None) + eq_(session.scalar(functions.wkt(functions.end_point('LINESTRING(0 1, 0 2)'))), + u'POINT (0 2)') + + @raises(NotImplementedError) + def test_transform(self): + spot = session.query(Spot).get(1) + # compare the coordinates using a tolerance, because they may vary on different systems + assert_almost_equal(session.scalar(functions.x(spot.spot_location.transform(2249))), -3890517.6109559298) + assert_almost_equal(session.scalar(functions.y(spot.spot_location.transform(2249))), 3627658.6746507999) + ok_(session.query(Spot).filter(Spot.spot_location.transform(2249) == WKTSpatialElement('POINT(-3890517.61095593 3627658.6746508)', 2249)).first() is not None) + eq_(session.scalar(functions.wkt(functions.transform(WKTSpatialElement('POLYGON((743238 2967416,743238 2967450,743265 2967450,743265.625 2967416,743238 2967416))', 2249), 4326))), + u'POLYGON((-71.1776848522251 42.3902896512902,-71.1776843766326 42.3903829478009,-71.1775844305465 42.3903826677917,-71.1775825927231 42.3902893647987,-71.1776848522251 42.3902896512902))') + + # Test Geometry Relationships + + def test_equals(self): + r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() + r2 = session.query(Road).filter(Road.road_name=='Peter Rd').one() + r3 = session.query(Road).filter(Road.road_name=='Paul St').one() + equal_roads = session.query(Road).filter(Road.road_geom.equals(r1.road_geom)).all() + ok_(r1 in equal_roads) + ok_(r2 in equal_roads) + ok_(r3 not in equal_roads) + ok_(session.query(Spot).filter(Spot.spot_location.equals(WKTSpatialElement('POINT(-88.5945861592357 42.9480095987261)'))).first() is not None) + eq_(session.scalar(functions.equals('POINT(-88.5945861592357 42.9480095987261)', 'POINT(-88.5945861592357 42.9480095987261)')), True) + + def test_distance(self): + r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() + r2 = session.query(Road).filter(Road.road_name=='Geordie Rd').one() + r3 = session.query(Road).filter(Road.road_name=='Peter Rd').one() + assert_almost_equal(session.scalar(r1.road_geom.distance(r2.road_geom)), 0.336997238682841) + eq_(session.scalar(r1.road_geom.distance(r3.road_geom)), 0.0) + ok_(session.query(Spot).filter(Spot.spot_location.distance(WKTSpatialElement('POINT(-88.5945861592357 42.9480095987261)')) < 10).first() is not None) + assert_almost_equal(session.scalar(functions.distance('POINT(-88.5945861592357 42.9480095987261)', 'POINT(-88.5945861592357 42.9480095987261)')), 0) + + @raises(NotImplementedError) + def test_within_distance(self): + r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() + r2 = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + r3 = session.query(Road).filter(Road.road_name=='Geordie Rd').one() + roads_within_distance = session.query(Road).filter( + Road.road_geom.within_distance(r1.road_geom, 0.20)).all() + ok_(r2 in roads_within_distance) + ok_(r3 not in roads_within_distance) + eq_(session.scalar(functions.within_distance('POINT(-88.9139332929936 42.5082802993631)', 'POINT(-88.9139332929936 35.5082802993631)', 10)), True) + + def test_disjoint(self): + r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() + r2 = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + r3 = session.query(Road).filter(Road.road_name=='Geordie Rd').one() + disjoint_roads = session.query(Road).filter(Road.road_geom.disjoint(r1.road_geom)).all() + ok_(r2 not in disjoint_roads) + ok_(r3 in disjoint_roads) + eq_(session.scalar(functions.disjoint('POINT(0 0)', 'LINESTRING ( 2 0, 0 2 )')), True) + + + def test_intersects(self): + r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() + r2 = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + r3 = session.query(Road).filter(Road.road_name=='Geordie Rd').one() + intersecting_roads = session.query(Road).filter(Road.road_geom.intersects(r1.road_geom)).all() + ok_(r2 in intersecting_roads) + ok_(r3 not in intersecting_roads) + eq_(session.scalar(functions.intersects('POINT(0 0)', 'LINESTRING ( 2 0, 0 2 )')), False) + + def test_touches(self): + l1 = session.query(Lake).filter(Lake.lake_name=='Lake White').one() + l2 = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() + r = session.query(Road).filter(Road.road_name=='Geordie Rd').one() + touching_lakes = session.query(Lake).filter(Lake.lake_geom.touches(r.road_geom)).all() + ok_(not session.scalar(l1.lake_geom.touches(r.road_geom))) + ok_(session.scalar(l2.lake_geom.touches(r.road_geom))) + ok_(l1 not in touching_lakes) + ok_(l2 in touching_lakes) + eq_(session.scalar(functions.touches('POINT(1 1)', 'LINESTRING (0 0, 1 1, 0 2)')), False) + + def test_crosses(self): + r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() + r2 = session.query(Road).filter(Road.road_name=='Paul St').one() + l = session.query(Lake).filter(Lake.lake_name=='Lake White').one() + crossing_roads = session.query(Road).filter(Road.road_geom.crosses(l.lake_geom)).all() + ok_(not session.scalar(r1.road_geom.crosses(l.lake_geom))) + ok_(session.scalar(r2.road_geom.crosses(l.lake_geom))) + ok_(r1 not in crossing_roads) + ok_(r2 in crossing_roads) + eq_(session.scalar(functions.crosses('LINESTRING(0 1, 2 1)', 'LINESTRING (0 0, 1 2, 0 2)')), True) + + def test_within(self): + l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() + p1 = session.query(Spot).filter(Spot.spot_height==102.34).one() + p2 = session.query(Spot).filter(Spot.spot_height==388.62).one() + spots_within = session.query(Spot).filter(Spot.spot_location.within(l.lake_geom)).all() + ok_(session.scalar(p1.spot_location.within(l.lake_geom))) + ok_(not session.scalar(p2.spot_location.within(l.lake_geom))) + ok_(p1 in spots_within) + ok_(p2 not in spots_within) + eq_(session.scalar(functions.within('LINESTRING(0 1, 2 1)', 'POLYGON((-1 -1, 3 -1, 3 2, -1 2, -1 -1))')), True) + buffer_geom = DBSpatialElement(session.scalar(l.lake_geom.buffer(10.0))) + spots_within = session.query(Spot).filter(l.lake_geom.within(buffer_geom)).all() + ok_(p1 in spots_within) + ok_(p2 in spots_within) + + def test_overlaps(self): + l1 = session.query(Lake).filter(Lake.lake_name=='Lake White').one() + l2 = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() + l3 = session.query(Lake).filter(Lake.lake_name=='My Lake').one() + overlapping_lakes = session.query(Lake).filter(Lake.lake_geom.overlaps(l3.lake_geom)).all() + ok_(not session.scalar(l1.lake_geom.overlaps(l3.lake_geom))) + ok_(session.scalar(l2.lake_geom.overlaps(l3.lake_geom))) + ok_(l1 not in overlapping_lakes) + ok_(l2 in overlapping_lakes) + eq_(session.scalar(functions.overlaps('POLYGON((2 1, 4 1, 4 3, 2 3, 2 1))', 'POLYGON((-1 -1, 3 -1, 3 2, -1 2, -1 -1))')), True) + + def test_contains(self): + l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() + l1 = session.query(Lake).filter(Lake.lake_name=='Lake White').one() + p1 = session.query(Spot).filter(Spot.spot_height==102.34).one() + p2 = session.query(Spot).filter(Spot.spot_height==388.62).one() + containing_lakes = session.query(Lake).filter(Lake.lake_geom.gcontains(p1.spot_location)).all() + ok_(session.scalar(l.lake_geom.gcontains(p1.spot_location))) + ok_(not session.scalar(l.lake_geom.gcontains(p2.spot_location))) + ok_(l in containing_lakes) + ok_(l1 not in containing_lakes) + ok_(session.scalar(l.lake_geom.gcontains(WKTSpatialElement('POINT(-88.9055734203822 43.0048567324841)')))) + containing_lakes = session.query(Lake).filter(Lake.lake_geom.gcontains('POINT(-88.9055734203822 43.0048567324841)')).all() + ok_(l in containing_lakes) + ok_(l1 not in containing_lakes) + spots_within = session.query(Spot).filter(l.lake_geom.gcontains(Spot.spot_location)).all() + ok_(session.scalar(l.lake_geom.gcontains(p1.spot_location))) + ok_(not session.scalar(l.lake_geom.gcontains(p2.spot_location))) + ok_(p1 in spots_within) + ok_(p2 not in spots_within) + eq_(session.scalar(functions.gcontains('LINESTRING(0 1, 2 1)', 'POLYGON((-1 -1, 3 -1, 3 2, -1 2, -1 -1))')), False) + + @raises(NotImplementedError) + def test_covers(self): + l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() + l1 = session.query(Lake).filter(Lake.lake_name=='Lake White').one() + p1 = session.query(Spot).filter(Spot.spot_height==102.34).one() + p2 = session.query(Spot).filter(Spot.spot_height==388.62).one() + covering_lakes = session.query(Lake).filter(Lake.lake_geom.covers(p1.spot_location)).all() + ok_(session.scalar(l.lake_geom.covers(p1.spot_location))) + ok_(not session.scalar(l.lake_geom.covers(p2.spot_location))) + ok_(l in covering_lakes) + ok_(l1 not in covering_lakes) + eq_(session.scalar(functions.gcontains('LINESTRING(0 1, 2 1)', 'POLYGON((-1 -1, 3 -1, 3 2, -1 2, -1 -1))')), False) + + @raises(NotImplementedError) + def test_covered_by(self): + l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() + p1 = session.query(Spot).filter(Spot.spot_height==102.34).one() + p2 = session.query(Spot).filter(Spot.spot_height==388.62).one() + covered_spots = session.query(Spot).filter(Spot.spot_location.covered_by(l.lake_geom)).all() + ok_(session.scalar(p1.spot_location.covered_by(l.lake_geom))) + ok_(not session.scalar(p2.spot_location.covered_by(l.lake_geom))) + ok_(p1 in covered_spots) + ok_(p2 not in covered_spots) + eq_(session.scalar(functions.covered_by('LINESTRING(0 1, 2 1)', 'POLYGON((-1 -1, 3 -1, 3 2, -1 2, -1 -1))')), True) + + @raises(NotImplementedError) + def test_intersection(self): + l = session.query(Lake).filter(Lake.lake_name=='Lake Blue').one() + r = session.query(Road).filter(Road.road_name=='Geordie Rd').one() + s = session.query(Spot).filter(Spot.spot_height==454.66).one() + eq_(session.scalar(func.STAsText(l.lake_geom.intersection(s.spot_location))), 'GEOMETRYCOLLECTION EMPTY') + eq_(session.scalar(func.STAsText(session.scalar(l.lake_geom.intersection(r.road_geom)))), 'POINT(-89.0710987261147 43.243949044586)') + l = session.query(Lake).filter(Lake.lake_name=='Lake White').one() + r = session.query(Road).filter(Road.road_name=='Paul St').one() + eq_(session.scalar(func.STAsText(session.scalar(l.lake_geom.intersection(r.road_geom)))), 'LINESTRING(-88.1430673666454 42.6255500261493,-88.1140839697546 42.6230657349872)') + ok_(session.query(Lake).filter(Lake.lake_geom.intersection(r.road_geom) == WKTSpatialElement('LINESTRING(-88.1430673666454 42.6255500261493,-88.1140839697546 42.6230657349872)')).first() is not None) + + @raises(IntegrityError) + def test_constraint_nullable(self): + spot_null = Spot(spot_height=420.40, spot_location=MS_SPATIAL_NULL) + session.add(spot_null) + session.commit(); + ok_(True) + road_null = Road(road_name='Jeff Rd', road_geom=MS_SPATIAL_NULL) + session.add(road_null) + session.commit(); + + # Test SQL Server specific functions + + def test_text_zm(self): + engine.execute('INSERT INTO [spots] VALUES(%f, geometry::STGeomFromText(%s, %i))' % (130.23, "'POINT (-88.5945861592357 42.9480095987261 130.23 1)'", 4326)) + eq_(session.query(Spot.spot_location.text_zm.label('text_zm')).filter(Spot.spot_height==130.23).first().text_zm, u'POINT (-88.5945861592357 42.9480095987261 130.23 1)') + eq_(session.query(Spot.spot_location.text_zm.label('text_zm')).filter(Spot.spot_height==420.40).first().text_zm, u'POINT (-88.5945861592357 42.9480095987261)') + + + def test_buffer_with_tolerance(self): + r = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + assert_almost_equal(session.scalar(functions.area(r.road_geom.buffer_with_tolerance(10.0, 20, 1))), 214.63894668789601) + assert_almost_equal(session.scalar(functions.area(r.road_geom.buffer_with_tolerance(10.0, 20, 0))), 214.63894668789601) + ok_(session.query(Spot).filter(functions.within('POINT(-88.5945861592357 42.9480095987261)', Spot.spot_location.buffer(10))).first() is not None) + assert_almost_equal(session.scalar(functions.area(ms_functions.buffer_with_tolerance('POINT(-88.5945861592357 42.9480095987261)', 10, 2, 0))), 306.21843345678644) + + def test_filter(self): + r1 = session.query(Road).filter(Road.road_name=='Jeff Rd').one() + r2 = session.query(Road).filter(Road.road_name=='Graeme Ave').one() + r3 = session.query(Road).filter(Road.road_name=='Geordie Rd').one() + intersecting_roads = session.query(Road).filter(Road.road_geom.filter(r1.road_geom)).all() + ok_(r2 in intersecting_roads) + ok_(r3 not in intersecting_roads) + eq_(session.scalar(ms_functions.filter('POINT(0 0)', 'LINESTRING ( 2 0, 0 2 )')), False) + + def test_instance_of(self): + ok_(session.query(Road).filter(Road.road_geom.instance_of('LINESTRING')).first() is not None) + ok_(session.query(Lake).filter(Lake.lake_geom.instance_of('POLYGON')).first() is not None) + ok_(session.query(Spot).filter(Spot.spot_location.instance_of('POINT')).first() is not None) + + def test_extended_coords(self): + engine.execute('INSERT INTO [spots] VALUES(%f, geometry::STGeomFromText(%s, %i))' % (130.23, "'POINT (-88.5945861592357 42.9480095987261 130.23 1)'", 4326)) + p = session.query(Spot.spot_location.z.label('z'), Spot.spot_location.m.label('m')).filter(Spot.spot_height==130.23).first() + eq_(p.z, 130.23) + eq_(p.m, 1) + p = session.query(Spot.spot_location.z.label('z'), Spot.spot_location.m.label('m')).filter(Spot.spot_height==420.40).first() + ok_(p.z is None) + ok_(p.m is None) + + def test_make_valid(self): + session.add(Shape(shape_name=u'Invalid Shape', shape_geom=WKTSpatialElement(u'LINESTRING(0 2, 1 1, 1 0, 1 1, 2 2)'))) + invalid_line = session.query(Shape).filter(Shape.shape_name==u'Invalid Shape').first() + eq_(session.scalar(invalid_line.shape_geom.is_valid), 0) + invalid_line.shape_geom = DBSpatialElement(session.scalar(invalid_line.shape_geom.make_valid)) + valid_line = session.query(Shape).filter(Shape.shape_name==u'Invalid Shape').first() + eq_(session.scalar(valid_line.shape_geom.is_valid), 1) + + + def test_reduce(self): + r = session.query(Road).first() + eq_(session.scalar(DBSpatialElement(session.scalar(r.road_geom.reduce(0.5))).wkt), + u'LINESTRING (-88.9139332929936 42.5082802993631, -88.3655256496815 43.1402866687898)') + eq_(session.scalar(DBSpatialElement(session.scalar(r.road_geom.reduce(0.05))).wkt), + u'LINESTRING (-88.9139332929936 42.5082802993631, -88.6113059044586 42.9680732929936, -88.3655256496815 43.1402866687898)') + eq_(session.scalar(DBSpatialElement(session.scalar(r.road_geom.reduce(0.0000000000001))).wkt), + session.scalar(r.road_geom.wkt)) + + + def test_to_string(self): + engine.execute('INSERT INTO [spots] VALUES(%f, geometry::STGeomFromText(%s, %i))' % (130.23, "'POINT (-88.5945861592357 42.9480095987261 130.23 1)'", 4326)) + session.add(Lake(lake_name=u'Vanished lake', lake_geom=MS_SPATIAL_NULL)) + eq_(session.query(Spot.spot_location.text_zm.label('to_string')).filter(Spot.spot_height==130.23).first().to_string, u'POINT (-88.5945861592357 42.9480095987261 130.23 1)') + eq_(session.query(Spot.spot_location.text_zm.label('to_string')).filter(Spot.spot_height==420.40).first().to_string, u'POINT (-88.5945861592357 42.9480095987261)') + ok_(session.query(Lake.lake_geom.to_string.label('to_string')).filter(Lake.lake_name==u'Vanished lake').first().to_string is None) diff --git a/geoalchemy/tests/test_mysql.py b/geoalchemy/tests/test_mysql.py index 84e88d5..be19887 100644 --- a/geoalchemy/tests/test_mysql.py +++ b/geoalchemy/tests/test_mysql.py @@ -15,7 +15,7 @@ from geoalchemy.mysql import MySQLComparator, mysql_functions -engine = create_engine('mysql://gis:gis@localhost/gis', echo=True) +engine = create_engine('mysql+mysqldb://gis:gis@localhost/gis', echo=True) metadata = MetaData(engine) session = sessionmaker(bind=engine)() Base = declarative_base(metadata=metadata) @@ -443,12 +443,3 @@ def test_constraint_nullable(self): road_null = Road(road_name=u'Jeff Rd', road_geom=None) session.add(road_null) session.commit(); - - -if __name__ == '__main__': - import sys - import nose - - sys.argv.append(__name__) - result = nose.run() - sys.exit(int(not result)) diff --git a/geoalchemy/tests/test_oracle.py b/geoalchemy/tests/test_oracle.py index 834a9f4..2348b41 100644 --- a/geoalchemy/tests/test_oracle.py +++ b/geoalchemy/tests/test_oracle.py @@ -768,12 +768,3 @@ def test_constraint_nullable(self): # road_null = Road(road_name='Jeff Rd', road_geom=None) session.add(road_null) session.commit(); - - -if __name__ == '__main__': - import sys - import nose - - sys.argv.append(__name__) - result = nose.run() - sys.exit(int(not result)) diff --git a/geoalchemy/tests/test_postgis.py b/geoalchemy/tests/test_postgis.py index 9100f80..99baba5 100644 --- a/geoalchemy/tests/test_postgis.py +++ b/geoalchemy/tests/test_postgis.py @@ -596,12 +596,3 @@ def test_issue17(self): Lake.road.has(functions._within_distance(Road.road_geom,r1.road_geom, 0.20))).all() lakes_within_distance = session.query(Lake).filter( Lake.road.has(functions.within_distance(Road.road_geom,r1.road_geom, 0.20))).all() - - -if __name__ == '__main__': - import sys - import nose - - sys.argv.append(__name__) - result = nose.run() - sys.exit(int(not result)) diff --git a/geoalchemy/tests/test_spatialite.py b/geoalchemy/tests/test_spatialite.py index a5a5e84..0441b6e 100644 --- a/geoalchemy/tests/test_spatialite.py +++ b/geoalchemy/tests/test_spatialite.py @@ -523,12 +523,3 @@ def test_query_column_name(self): query_wkt = Select([func.wkt(spot_alias.spot_location.RAW)]).__str__() ok_('SELECT wkt(spots_1.spot_location' in query_wkt, 'Table alias is used in select clause') ok_('FROM spots AS spots_1' in query_wkt, 'Table alias is used in from clause') - - -if __name__ == '__main__': - import sys - import nose - - sys.argv.append(__name__) - result = nose.run() - sys.exit(int(not result)) diff --git a/geoalchemy/tests/test_type.py b/geoalchemy/tests/test_type.py index bbdcdfa..75885b8 100644 --- a/geoalchemy/tests/test_type.py +++ b/geoalchemy/tests/test_type.py @@ -56,12 +56,3 @@ def test_cast_geometrycollection(self): from geoalchemy.geometry import GeometryCollection c = cast('', GeometryCollection) eq_(str(c), 'CAST(:param_1 AS GEOMETRYCOLLECTION)') - - -if __name__ == '__main__': - import sys - import nose - - sys.argv.append(__name__) - result = nose.run() - sys.exit(int(not result)) diff --git a/setup.py b/setup.py index 7ac7028..24e58d9 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ zip_safe=False, install_requires=[ 'SQLAlchemy>=0.6.1', + 'six', ], entry_points=""" # -*- Entry points: -*-