Common Geography Database Tasks

Description

Information on a variety of tasks including getting table information for geometry columns, defining a geography column in a SQL table, creating a location, line, or polygon in portable SQL. This page also looks at the conversion of geography objects to well-known formats, comparing geographic objects using SQL, and returning database values into a geography object.

Getting Table Information for Geometry Columns 

Alpha Anywhere SQL::TableInfo object describes a SQL table, its columns and indexes in a database independent way. Column definitions in the SQL::TableInfo objects can be populated:

dim TI as SQL::TableInfo
Connection.GetTableInfo(TI, "MyTable")
Connection.GetTableInfoFromDBF(TI, "MyTable")

Define a Geography Column in a SQL Table 

There are several functions that can be used to create a table from a SQL::TableInfo object instance. The simplest is to use the connection object and call its CreateTable function as shown below.

dim TI as SQL::TableInfo
Connection.CreateTable(TI)

The intermediate type for geography objects is predictably "geography". To set the value of a table info column you would write the following script:

dim Col as SQL::DataTypeInfo
Col.Name = "Location"
Col.IntermediateType = SQL::IntermediateType::Geography
TI.AddColumn(Col)

Here is a more complete example of creating a table with a geometry column using Xbasic:

dim TI as SQL::TableInfo
dim Col as SQL::DataTypeInfo
dim PK as SQL::IndexInfo
dim IXC as SQL::IndexcolumnInfo

TI.Name = "GeogTest"

Col = new SQL::DataTypeInfo()
Col.Name = "KeyValue"
Col.AlphaType = "C"
Col.Length = 25
Col.Nullable = .f.
TI.AddColumn(Col)

Col = new SQL::DataTypeInfo()
Col.Name = "Location"
Col.IntermediateType = SQL::IntermediateType::Geography
Col.Nullable = .f.
TI.AddColumn(Col)

PK.Name = "PK"
PK.PrimaryKey = .t.
PK.Unique     = .t.
IXC.Name = "KeyValue"
PK.AddColumn(IXC)
TI.AddIndex(PK)

CreateTestTable = Connection.CreateTable(TI)

Create a Location, Line, or Polygon in Portable SQL 

Location, Line and Polygon objects can be created using the portable SQL functions GeogCreateLocation, GeogCreateLine and GeogCreatePolygon respectively.

Each of the portable function can be used to construct a native geography object in SQL. The functions are expanded at run time into the native SQL for the target database (see the database specific notes for details). The object can then be inserted into a table column or passed to another function, such as a comparison or conversion function.

Here are the functions again, with some examples:

  • GeogCreateLocation 

    GeogCreateLocation as Geography ( Longitude as N, Latitude as N [,SpatialReferenceID as N])

  • Constructs a geographic location from a longitude/latitude pair. For example:

    select first 1 GeogAsText(GeogCreateLocation(1,2, :SRID)) from GeogTest g
    
    select first 1 GeogAsText(GeogCreateLocation(1,2)) from GeogTest g
    
    select first 1 GeogDistanceBetween(GeogCreateLocation(1, 42, :SRID), GeogCreateLocation(10, 20, :SRID)) from GeogTest g
  • GeogCreateLine 

    GeogCreateLine as Geography ( Longitude as N, Latitude as N [...] [,SpatialReferenceID as N])

  • Constructs a geographic line from two or more longitude/latitude pairs.

    select first 1 GeogAsText(GeogCreateLine(1,2,3,4,5,6, :SRID)) from GeogTest g
    
    select first 1 GeogAsText(GeogCreateLine(1,2,3,4,5,6)) from GeogTest g
  • GeogCreatePolygon 

    GeogCreatePolygon as Geography ( Longitude as N, Latitude as N [...] [,SpatialReferenceID as N])

  • Constructs a geographic polygon from three or more longitude/latitude pairs. For example:

    select first 1 GeogAsText(GeogCreatePolygon(-70, 42, -70, 32, -60, 32, -60, 42, -70, 42, :SRID)) from GeogTest g
    
    select first 1 GeogAsText(GeogCreatePolygon(-70, 42, -70, 32, -60, 32, -60, 42, -70, 42)) from GeogTest g
    The first and last point of the polygon must be the same, and the points must be arranged in counter-clockwise order.
  • Retrieving Geography Properties 

    The properties of geography objects are stored in a single database column. To retrieve these values, use the following functions:

  • For All Objects 

    • Type - GeogType(Object) 

      The type function will return LOCATION, LINE, or POLYGON for any of these geographic objects. Although not all objects are directly supported, you may also see MULTILOCATION, MULTILINE and MULTIPOLYGON as well as other values returned. Only location, line and polygon are guaranteed to be correctly mapped for all databases. For example:

      select GeogLongitude(g.Location), GeogLatitude(g.Location) from GeogTest g 
          where GeogType(g.Location) = 'LOCATION'
    • Spatial Reference Identifier (SRID) - GeogSRID(Object) 

      The default on most databases is 4326 (1003 on DB2). For example:

      select first 1 GeogSRID(GeogCreateLocation(50, 44, :SRID)) from GeogTest g
      Unless you have a specific reason to use a different SRID than the default, we recommend this one. Using the default will get you more consistent results between database vendors.
  • For Points 

    • Longitude - GeogLongitude(Object) 

      Returns the longitude value as mapped to X or Y (depending on the database implementation). For example:

      select GeogLongitude(g.Location), GeogLatitude(g.Location) from GeogTest g 
          where GeogType(g.Location) = 'LOCATION'
      Some databases use the point value of X for longitude and some use Y. This function handles the inconsistencies.
    • Latitude - GeogLatitude(Object) 

      Returns the latitude value as mapped to X or Y (depending on the database implementation). For example:

      select GeogLongitude(g.Location), GeogLatitude(g.Location) from GeogTest g 
          where GeogType(g.Location) = 'LOCATION'
      Some databases use the point value of X for longitude and some use Y. This function handles the inconsistencies.

Converting Geography Objects to Well-Known Formats 

The Open Geospatial Consortium (OCG) standard defines several formats for serializing geography and geometry objects. There are portable SQL functions for two that are consistently supported by databases:

  • Well-known-text (WKT) - for example 'POLYGON((100 200, 110 300, 120 500))'. This is a text-only format that defines each of the objects in a way that is very readable and easy to construct.
  • Well-known-binary (WKB) - A binary equivalent of well-known-text. This format compresses the types and coordinates into standard binary format.

While you can certainly create your own well-known-text and well-known-binary strings, you will probably want to use these formats to retrieve and store values.

Here are the functions to convert objects to well-known-text and well-known-binary and back again, with some examples:

  • GeogAsBinary 

    • GeogAsBinary as C(Object as Geography)

    • Return the object description in the Well Known Binary (WKB) format.

      select  GeogAsBinary(g.Location) from GeogTest g
  • GeogAsText 

    • GeogAsText as C(Object as Geography)

    • Return the object description in the Well Known Text (WKT) format.

      select GeogAsText(g.Location) from GeogTest g
  • GeogCreateFromBinary 

    • GeogCreateFromBinary as Geography(Binary as B [, SpatialReferenceID as N])

    • Create a geography object from Well Known Binary (WKB) format. Here is an example of an Xbasic script passing a Blob representation of a point object to a SQL statement as an argument:

      dim MyBlob as B = base64decode("AQEAAAAAAAAAAAAkwAAAAAAAAChA")
      args.add("WKB", MyBlob)
      
      Connection.Execute("select GeogAsText(GeogCreateFromBinary(:WKB))" + \
          " from GeogTest g")
  • GeogCreateFromText 

    • GeogCreateFromText as Geography(Text as C [, SpatialReferenceID as N])

    • Create a geography object from Well Known Text (WKT) format. For example:

      select first 1 GeogAsText(GeogCreateFromText('POINT(-10 27)')) from GeogTest g

Comparing Geographic Objects Using SQL 

  • GeogDistanceBetween 

    • GeogDistanceBetween as N ( Object as Geography, Object as Geography)

    • Returns the distance between two objects in the default unit (generally meters).

  • GeogLocationIntersectsLine 

    • GeogLocationIntersectsLine as L (Location as Geography, Line as Geography, Tolerance as N)

    • Returns true if the location intersects the line or is within the tolerance distance from it. For example:

      select if(GeogLocationIntersectsLine(
              g.Location, 
              GeogCreateLine(-60, 42, -70, 42, :SRID), 3000),
          'It intersects',
          'It does not intersect') from GeogTest g
  • GeogLocationIsWithinPolygon 

    • GeogLocationIsWithinPolygon as L ( Location as Geography, Polygon as Geography, Tolerance as N)

    • Returns true if the location is contained within the polygon or within the tolerance distance from it. For example:

      select if(GeogLocationIsWithinPolygon(
              g.Location, 
              GeogCreatePolygon(-70, 44, -70, 34, -60, 34, -70, 44, :SRID), 5),
          'It is within the polygon',
          'It is not within the polygon') from GeogTest g
  • GeogLocationIsWithinRadius 

    • GeogLocationIsWithinRadius as L ( Location as Geography, Point as Geography, Radius as N, Tolerance as N)

    • Returns true if the location is within the radius defined and within the tolerance defined.

      select if(GeogLocationIsWithinRadius(
              g.Location, 
              GeogCreateLocation(-70, 41.99999, :SRID), 50), 
          'It is within the radius', 
          'It is not within the radius') from GeogTest g
      Each database handles tolerance differently (as it affects indexing performance).

Returning Database Values into the Geography Object 

Microsoft has made some of the user defined types used in SQL server available as a redistributable .Net assembly. Beginning with Alpha Five Version 11, this assembly is installed with Alpha Anywhere and the geography type is available. As a result, SQL result sets are now "geography aware". This means that they will attempt to return the .Net object of the type Microsoft::SQLServer::Types::SQLGeography wrapping database data whenever possible.

There are two functions that will try to do this:

SQL::ResultSet::DataAsGeography 

When you call SQL::ResultSet::DataAsGeography, the column value will be retrieved and converted (if possible) to a Microsoft::SQLServer::Types::SQLGeography object. The input value can be well-known-text (WKT), well-known-binary (WKB), or (for some databases) the result of simply selecting a geography column. Selecting the column directly works in SQL Server, PostgreSQL, and MySQL.

In order to be portable, you will probably want to select the column using GeogAsText or GeogAsBinary.

Here is a simple example using Xbasic:

dim Result as C
dim cn as sql::Connection
dim Geography as A ' Note that we've dimmed this as A to show the true type.

    if cn.open(ConnectionString)

    Result = Result + "Connection opened successfully." + crlf()
    cn.portableSQLEnabled = .t.

    if cn.execute("select GeogAsText(g.Location) from GeogTest g")
        Result = Result + "Query executed successfully." + crlf()
        ResultSet = cn.ResultSet 

        Geography = ResultSet.DataAsGeography(1, 4326)
        Result = Result + "Geography object is of type: " + typeof(Geography) + crlf()
        Result = Result + "Geography as XML: " + Geography.AsGML().Value + crlf()
        Result = Result + "Geography object as Text: " + Geography.ToString() + crlf()
        Result = Result + "Geography Longitude: " + Geography.Long.Value + crlf()
        Result = Result + "Geography Latitude:  " + Geography.Lat.Value + crlf(2)
    else
        Result = Result	+ "Error executing query: " + CurrentQuery + crlf() \
                        + "Result is: " + cn.callresult.text + crlf(2)
    end if

    else
    Result = Result	+ "Error opening connection:: " \
                    + ConnectionString + crlf() + "Result is: " + cn.callresult.text + crlf(2)
    end if

showvar(Result)

SQL::ResultSet::Data 

When you call SQL::ResultSet::Data, the column value will be retrieved and converted to a Microsoft::SQLServer::Types::SQLGeography object if and only if the database returns a geography type and the driver can tell that it has. When calling the Data() function, there is no guarantee that data returned will be an object. You must check the return type before calling any functions or accessing any properties on the object.

Notes:

  • Currently SQL Server is the only database that will return a recognizable geography object. It will also return a SQLGeometry object and a HierarchyId object. In the future, we hope to be able to return any SQL Server user defined type implemented in .NET for which there is a registered assembly on the client machine.
  • MySQL returns a geometry which is converted to a SQLGeometry object (from the same SQL Server assembly). Note that you could convert one object to the other using WKT or WKB formats, but you can guarantee that you get the right object by using the DataAsGeography function instead.

See Also