port to mapnik 3

This commit is contained in:
Paul Joyce 2015-08-07 13:25:29 +10:00
parent 6b05f19c38
commit e3cc70bee7
7 changed files with 1402 additions and 0 deletions

View file

@ -0,0 +1,63 @@
#
# This file is part of Mapnik (c++ mapping toolkit)
#
# Copyright (C) 2013 Artem Pavlenko
#
# Mapnik is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#
Import ('plugin_base')
Import ('env')
PLUGIN_NAME = 'sqlserver'
plugin_env = plugin_base.Clone()
plugin_sources = Split(
"""
%(PLUGIN_NAME)s_datasource.cpp
%(PLUGIN_NAME)s_featureset.cpp
%(PLUGIN_NAME)s_geometry_parser.cpp
""" % locals()
)
libraries = [ 'odbc' ]
libraries.append('boost_system%s' % env['BOOST_APPEND'])
libraries.append(env['ICU_LIB_NAME'])
if env['PLUGIN_LINKING'] == 'shared':
libraries.append('mapnik')
TARGET = plugin_env.SharedLibrary('../%s' % PLUGIN_NAME,
SHLIBPREFIX='',
SHLIBSUFFIX='.input',
source=plugin_sources,
LIBS=libraries,
LINKFLAGS=env['CUSTOM_LDFLAGS'])
# if the plugin links to libmapnik ensure it is built first
Depends(TARGET, env.subst('../../../src/%s' % env['MAPNIK_LIB_NAME']))
if 'uninstall' not in COMMAND_LINE_TARGETS:
env.Install(env['MAPNIK_INPUT_PLUGINS_DEST'], TARGET)
env.Alias('install', env['MAPNIK_INPUT_PLUGINS_DEST'])
plugin_obj = {
'LIBS': libraries,
'SOURCES': plugin_sources,
}
Return('plugin_obj')

View file

@ -0,0 +1,464 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2015 we-do-IT
*
*****************************************************************************/
#include "sqlserver_datasource.hpp"
#include "sqlserver_featureset.hpp"
#include "sqlserver_geometry_parser.hpp"
// mapnik
#include <mapnik/debug.hpp>
#include <mapnik/boolean.hpp>
#include <mapnik/sql_utils.hpp>
#include <mapnik/timer.hpp>
#include <mapnik/value_types.hpp>
#include <mapnik/geometry_envelope.hpp>
// boost
#include <boost/algorithm/string.hpp>
#include <boost/tokenizer.hpp>
#include <boost/make_shared.hpp>
// stl
#include <string>
#include <algorithm>
#include <set>
#include <sstream>
#include <iomanip>
// sql server (odbc)
#include <sqlext.h>
#include <msodbcsql.h>
using mapnik::datasource;
using mapnik::parameters;
using mapnik::query;
using mapnik::featureset_ptr;
using mapnik::layer_descriptor;
using mapnik::attribute_descriptor;
using mapnik::datasource_exception;
using mapnik::box2d;
using mapnik::coord2d;
DATASOURCE_PLUGIN(sqlserver_datasource)
// an exception class to wrap gathering the odbc error
sqlserver_datasource_exception::sqlserver_datasource_exception(std::string const& message)
: mapnik::datasource_exception("SQL Server Plugin: "+message) {
}
sqlserver_datasource_exception::sqlserver_datasource_exception(std::string const& message, SQLSMALLINT HandleType, SQLHANDLE Handle)
: mapnik::datasource_exception("SQL Server Plugin: "+message+": "+sql_diagnostics(HandleType, Handle)) {
}
sqlserver_datasource_exception::~sqlserver_datasource_exception() throw () {
}
std::string sqlserver_datasource_exception::sql_diagnostics(SQLSMALLINT HandleType, SQLHANDLE Handle) {
// Get the status records.
std::ostringstream s;
SQLCHAR SqlState[6];
SQLINTEGER NativeError;
SQLCHAR Msg[SQL_MAX_MESSAGE_LENGTH];
SQLSMALLINT MsgLen;
SQLSMALLINT i = 1;
while (SQLGetDiagRec(HandleType, Handle, i, SqlState, &NativeError, Msg, sizeof(Msg), &MsgLen) == SQL_SUCCESS) {
s << "[" << SqlState << "] ";
s << Msg;
s << " (" << NativeError << ") ";
i++;
}
return s.str();
}
// datasource
sqlserver_datasource::sqlserver_datasource(parameters const& params) :
datasource (params),
type_(datasource::Vector),
fields_(*params.get<std::string>("fields", "*")),
geometry_field_(*params.get<std::string>("geometry_field", "")),
is_geometry_(true),
extent_initialized_(false),
desc_(*params.get<std::string>("type"), *params.get<std::string>("encoding", "utf-8")),
henv_(0),
hdbc_(0)
{
#ifdef MAPNIK_STATS
mapnik::progress_timer __stats__(std::clog, "sqlserver_datasource::init");
#endif
// they must supply a table/view name or a subquery
if (params.get<std::string>("table")) {
table_ = *params.get<std::string>("table");
} else {
throw sqlserver_datasource_exception("no <table> parameter specified");
}
// they may supply an extent (and srid) to prevent querying for it
boost::optional<std::string> ext = params.get<std::string>("extent");
if (ext) {
extent_initialized_ = extent_.from_string(*ext);
}
boost::optional<mapnik::value_integer> srid = params.get<mapnik::value_integer>("srid");
if (srid) {
srid_ = *srid;
}
if (ext && !srid) {
throw sqlserver_datasource_exception("must specify <srid> parameter if <extent> parameter specified");
}
// the driver refers to an entry in odbcinst.ini http://www.unixodbc.org/odbcinst.html
std::string driver("ODBC Driver 11 for SQL Server"); // default for ms driver
if (params.get<std::string>("driver")) {
driver = *params.get<std::string>("driver");
}
// build the connection string
std::ostringstream s;
s << "Driver={" << driver << "};";
if (params.get<std::string>("server")) {
s << "Server=" << *params.get<std::string>("server") << ";";
}
if (params.get<std::string>("database")) {
s << "Database=" << *params.get<std::string>("database") << ";";
}
if (params.get<std::string>("user")) {
s << "Uid=" << *params.get<std::string>("user") << ";";
}
if (params.get<std::string>("password")) {
s << "Pwd=" << *params.get<std::string>("password") << ";";
}
if (params.get<std::string>("trusted")) {
s << "Trusted_Connection=" << *params.get<std::string>("trusted") << ";";
}
std::string InConnectionString = s.str();
MAPNIK_LOG_DEBUG(sqlserver) << "sqlserver_datasource: connection string: " << InConnectionString;
// everything returns one of these
SQLRETURN retcode;
// allocate environment handle
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv_);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not allocate environment handle"); // no diagnostics available
}
// set the ODBC version environment attribute
retcode = SQLSetEnvAttr(henv_, SQL_ATTR_ODBC_VERSION, (SQLPOINTER*)SQL_OV_ODBC3, 0);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not set ODBC version environment value", SQL_HANDLE_ENV, henv_);
}
// allocate connection handle
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv_, &hdbc_);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not allocate connection handle", SQL_HANDLE_ENV, henv_);
}
// set login timeout to 5 seconds
retcode = SQLSetConnectAttr(hdbc_, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not set connection timeout", SQL_HANDLE_DBC, hdbc_);
}
// connect to data source
SQLCHAR OutConnectionString[1024];
SQLSMALLINT OutConnectionStringLength;
retcode = SQLDriverConnect(hdbc_,
NULL,
(SQLCHAR*)InConnectionString.c_str(),
InConnectionString.length(),
OutConnectionString,
1024,
&OutConnectionStringLength,
SQL_DRIVER_NOPROMPT);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not connect", SQL_HANDLE_DBC, hdbc_);
}
MAPNIK_LOG_DEBUG(sqlserver) << "sqlserver_datasource: connected: " << OutConnectionString;
// get columns description
#ifdef MAPNIK_STATS
mapnik::progress_timer __stats__(std::clog, "sqlserver_datasource::get_column_description");
#endif
// table parameter can be a table/view name or a subquery
std::ostringstream stmt;
if (table_.find_first_of(" \t") == std::string::npos) {
// no whitespace in table_; assume a table/view name
stmt << "SELECT TOP(1) " << fields_ << " FROM " << table_;
} else {
// whitespace in table_; assume a valid query
stmt << table_;
}
MAPNIK_LOG_DEBUG(sqlserver) << "sqlserver_datasource: " << stmt.str();
// allocate statement handle
SQLHANDLE hstmt=0;
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc_, &hstmt);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not allocate statement", SQL_HANDLE_DBC, hdbc_);
}
// prepare statement
retcode = SQLPrepare(hstmt, (SQLCHAR*)stmt.str().c_str(), SQL_NTS);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not prepare statement", SQL_HANDLE_STMT, hstmt);
}
// find out how many columns in result set
SQLSMALLINT n=0;
retcode = SQLNumResultCols(hstmt, &n);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not get number of result columns", SQL_HANDLE_STMT, hstmt);
}
// get name,type for each column
for (int i=1; i<=n; i++) {
SQLCHAR ColumnName[255]; // max is currently 128 in sql server
SQLSMALLINT NameLength;
SQLSMALLINT DataType;
SQLULEN ColumnSize;
SQLSMALLINT DecimalDigits;
SQLSMALLINT Nullable;
retcode = SQLDescribeCol(hstmt, i, ColumnName, sizeof(ColumnName), &NameLength, &DataType, &ColumnSize, &DecimalDigits, &Nullable);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not describe column", SQL_HANDLE_STMT, hstmt);
}
SQLCHAR TypeName[255];
SQLSMALLINT ReturnedLength;
char geometry[] = {'g','\0','e','\0','o','\0','m','\0','e','\0','t','\0','r','\0','y','\0','\0','\0'}; // geometry
char geography[] = {'g','\0','e','\0','o','\0','g','\0','r','\0','a','\0','p','\0','h','\0','y','\0','\0','\0'}; // geography
switch (DataType) {
case SQL_CHAR:
case SQL_VARCHAR:
case -9: // NVARCHAR
desc_.add_descriptor(attribute_descriptor((char *)ColumnName,mapnik::sqlserver::String));
//MAPNIK_LOG_DEBUG(sqlserver) << "found string column: " << (char*)ColumnName;
break;
case SQL_INTEGER:
case SQL_SMALLINT:
desc_.add_descriptor(attribute_descriptor((char *)ColumnName,mapnik::sqlserver::Integer));
//MAPNIK_LOG_DEBUG(sqlserver) << "found integer column: " << (char*)ColumnName;
break;
case SQL_NUMERIC:
case SQL_DECIMAL:
case SQL_FLOAT:
case SQL_REAL:
case SQL_DOUBLE:
desc_.add_descriptor(attribute_descriptor((char *)ColumnName,mapnik::sqlserver::Double));
//MAPNIK_LOG_DEBUG(sqlserver) << "found double column: " << (char*)ColumnName;
break;
case SQL_DATETIME:
case SQL_TYPE_DATE:
case SQL_TYPE_TIME:
case SQL_TYPE_TIMESTAMP:
desc_.add_descriptor(attribute_descriptor((char *)ColumnName,mapnik::sqlserver::String));
//MAPNIK_LOG_DEBUG(sqlserver) << "found string column: " << (char*)ColumnName;
break;
case SQL_SS_UDT:
// check if it is a geometry type
retcode = SQLColAttribute(hstmt, i, SQL_CA_SS_UDT_TYPE_NAME, &TypeName, sizeof(TypeName), &ReturnedLength, NULL);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not get column attribute", SQL_HANDLE_STMT, hstmt);
}
// on linux the type name is returned as a weird string with null bytes as every second character
if (strcmp((char *)TypeName, "geometry") == 0 || std::memcmp((char*)TypeName, geometry, ReturnedLength) == 0) {
//MAPNIK_LOG_DEBUG(sqlserver) << "found geometry column: " << (char*)ColumnName;
geometry_field_ = (const char *)ColumnName;
is_geometry_ = true;
desc_.add_descriptor(attribute_descriptor((char *)ColumnName,mapnik::sqlserver::Geometry));
}
if (strcmp((char *)TypeName, "geography") == 0 || std::memcmp((char*)TypeName, geography, ReturnedLength) == 0) {
//MAPNIK_LOG_DEBUG(sqlserver) << "found geography column: " << (char*)ColumnName;
geometry_field_ = (const char *)ColumnName;
is_geometry_ = false;
desc_.add_descriptor(attribute_descriptor((char *)ColumnName,mapnik::sqlserver::Geography));
}
break;
default:
MAPNIK_LOG_WARN(sqlserver) << "sqlserver_datasource: unknown/unsupported datatype in column: " << ColumnName << " (" << DataType << ")";
desc_.add_descriptor(attribute_descriptor((char *)ColumnName,mapnik::sqlserver::Unknown));
break;
}
}
// free handle
retcode = SQLFreeStmt(hstmt, SQL_CLOSE);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not free statement handle", SQL_HANDLE_STMT, hstmt);
}
// final check
if (geometry_field_ == "") {
MAPNIK_LOG_WARN(sqlserver) << "sqlserver_datasource: no geometry column found or specified";
}
}
sqlserver_datasource::~sqlserver_datasource()
{
if (hdbc_) {
(void)SQLDisconnect(hdbc_);
(void)SQLFreeHandle(SQL_HANDLE_DBC, hdbc_);
hdbc_ = 0;
}
if (henv_) {
(void)SQLFreeHandle(SQL_HANDLE_ENV, henv_);
henv_ = 0;
}
}
const char * sqlserver_datasource::name()
{
return "sqlserver";
}
mapnik::datasource::datasource_t sqlserver_datasource::type() const
{
return type_;
}
box2d<double> sqlserver_datasource::envelope() const
{
if (extent_initialized_) return extent_;
SQLRETURN retcode;
// allocate statement handle
SQLHANDLE hstmt=0;
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc_, &hstmt);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not allocate statement", SQL_HANDLE_DBC, hdbc_);
}
// table parameter can be a table/view or a subquery
// iff a subquery, need to wrap in ()
std::ostringstream stmt;
stmt << "SELECT geometry::EnvelopeAggregate(" << geometry_field_ << ") FROM ";
if (table_.find_first_of(" \t") == std::string::npos) {
// no whitespace in table; assume a table/view name
stmt << table_;
} else {
// whitespace in table; assume a subquery
stmt << "(" << table_ << ") T";
}
MAPNIK_LOG_DEBUG(sqlserver) << "sqlserver_datasource: " << stmt.str();
// execute statement
retcode = SQLExecDirect(hstmt, (SQLCHAR*)stmt.str().c_str(), SQL_NTS);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not execute statement", SQL_HANDLE_STMT, hstmt);
}
// fetch first result (will only be one row)
retcode = SQLFetch(hstmt);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not fetch result", SQL_HANDLE_STMT, hstmt);
}
// get the row data
SQLUSMALLINT ColumnNum = 1;
SQLCHAR BinaryPtr[1024]; // envelope is a 5 point polygon; usually only 112 bytes
SQLLEN BinaryLenOrInd;
retcode = SQLGetData(hstmt, ColumnNum, SQL_C_BINARY, BinaryPtr, sizeof(BinaryPtr), &BinaryLenOrInd);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not get data", SQL_HANDLE_STMT, hstmt);
}
//MAPNIK_LOG_DEBUG(sqlserver) << "sqlserver_datasource: envelope returned " << BinaryLenOrInd << " bytes";
sqlserver_geometry_parser geometry_parser(is_geometry_ ? Geometry : Geography);
mapnik::geometry::geometry<double> geom = geometry_parser.parse(BinaryPtr, BinaryLenOrInd);
extent_ = mapnik::geometry::envelope(geom);
extent_initialized_ = true;
srid_ = geometry_parser.get_srs_id(); // get the srid of the extents; assume that is same for whole table
// free handle
retcode = SQLFreeStmt(hstmt, SQL_CLOSE);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not free statement handle", SQL_HANDLE_STMT, hstmt);
}
return extent_;
}
boost::optional<mapnik::datasource_geometry_t> sqlserver_datasource::get_geometry_type() const
{
return boost::optional<mapnik::datasource_geometry_t>();
}
layer_descriptor sqlserver_datasource::get_descriptor() const
{
return desc_;
}
featureset_ptr sqlserver_datasource::features(query const& q) const
{
#ifdef MAPNIK_STATS
mapnik::progress_timer __stats__(std::clog, "sqlserver_datasource::features");
#endif
box2d<double> const& box = q.get_bbox();
return features_in_box(box);
}
featureset_ptr sqlserver_datasource::features_at_point(coord2d const& pt, double tol) const
{
#ifdef MAPNIK_STATS
mapnik::progress_timer __stats__(std::clog, "sqlserver_datasource::features_at_point");
#endif
box2d<double> box(pt.x - tol, pt.y - tol, pt.x + tol, pt.y + tol);
return features_in_box(box);
}
featureset_ptr sqlserver_datasource::features_in_box(box2d<double> const& box) const {
std::string sql; // = table_; //populate_tokens(table_, scale_denom, box, px_gw, px_gh);
if (table_.find_first_of(" \t") == std::string::npos) {
// no whitespace in table_; assume a table/view name
sql = "SELECT * from " + table_;
} else {
// whitespace in table_; assume a subquery
sql = table_ ;
}
std::ostringstream spatial_sql;
spatial_sql << " WHERE " << geometry_field_;
spatial_sql << ".STIntersects(" ;
spatial_sql << std::setprecision(16);
spatial_sql << "geometry::STPolyFromText('POLYGON((";
spatial_sql << box.minx() << " " << box.miny() << ", ";
spatial_sql << box.minx() << " " << box.maxy() << ", ";
spatial_sql << box.maxx() << " " << box.maxy() << ", ";
spatial_sql << box.maxx() << " " << box.miny() << ", ";
spatial_sql << box.minx() << " " << box.miny() << "))'," << srid_ <<")";
spatial_sql << ") = 1";
if (boost::algorithm::ifind_first(sql, "WHERE")) {
// change existing where clause; prefix it with the spatial predicate
boost::algorithm::ireplace_first(sql, "WHERE", spatial_sql.str() + " AND ");
} else if (boost::algorithm::ifind_first(sql, table_)) {
// no where clause, so add the spatial predicate as the where clause
boost::algorithm::ireplace_first(sql, table_, table_ + " " + spatial_sql.str());
} else {
MAPNIK_LOG_WARN(sqlserver) << "sqlserver_datasource: cannot determine where to add the spatial filter clause";
}
MAPNIK_LOG_DEBUG(sqlserver) << "sqlserver_datasource: " << sql;
return std::make_shared<sqlserver_featureset>(hdbc_,
sql,
desc_);
}

View file

@ -0,0 +1,102 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2015 we-do-IT
*
*****************************************************************************/
#ifndef SQLSERVER_DATASOURCE_HPP
#define SQLSERVER_DATASOURCE_HPP
// mapnik
#include <mapnik/datasource.hpp>
#include <mapnik/params.hpp>
#include <mapnik/query.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/box2d.hpp>
#include <mapnik/coord.hpp>
#include <mapnik/feature_layer_desc.hpp>
#include <mapnik/value_types.hpp>
// boost
#include <boost/optional.hpp>
#include <boost/shared_ptr.hpp>
// stl
#include <vector>
#include <string>
// sql server (via odbc)
#ifdef _WINDOWS
#include <windows.h>
#endif
#include "sql.h"
// extended from mapnik/attribute_descriptor.hpp (added 8,9)
namespace mapnik {
namespace sqlserver {
enum eAttributeTypeEx {
Integer=1,
Float=2,
Double=3,
String=4,
Boolean=5,
Geometry=6,
Object=7,
Geography=8,
Unknown=9
};
}
}
// SQL Server datasource for Mapnik
class sqlserver_datasource : public mapnik::datasource
{
public:
sqlserver_datasource(mapnik::parameters const& params);
virtual ~sqlserver_datasource ();
static const char * name();
mapnik::datasource::datasource_t type() const;
mapnik::featureset_ptr features(mapnik::query const& q) const;
mapnik::featureset_ptr features_at_point(mapnik::coord2d const& pt, double tol = 0) const;
mapnik::box2d<double> envelope() const;
boost::optional<mapnik::datasource_geometry_t> get_geometry_type() const;
mapnik::layer_descriptor get_descriptor() const;
private:
mapnik::datasource::datasource_t type_;
std::string table_;
std::string fields_;
std::string geometry_field_;
bool is_geometry_; // true=geometry, false=geography
mutable bool extent_initialized_;
mutable mapnik::box2d<double> extent_;
mutable int srid_;
mapnik::layer_descriptor desc_;
SQLHENV henv_;
SQLHDBC hdbc_;
mapnik::featureset_ptr features_in_box(mapnik::box2d<double> const& box) const;
};
// if anything goes wrong in the sqlserver_datasource class, one of these
// exceptions will be thrown. usually the message will include details from
// the SQL Server error
class sqlserver_datasource_exception : public mapnik::datasource_exception
{
public:
sqlserver_datasource_exception(std::string const& message);
sqlserver_datasource_exception(std::string const& message, SQLSMALLINT HandleType, SQLHANDLE Handle);
virtual ~sqlserver_datasource_exception() throw();
private:
static std::string sql_diagnostics(SQLSMALLINT HandleType, SQLHANDLE Handle);
};
#endif // SQLSERVER_DATASOURCE_HPP

View file

@ -0,0 +1,179 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2015 we-do-IT
*
*****************************************************************************/
#include "sqlserver_featureset.hpp"
#include "sqlserver_datasource.hpp"
#include "sqlserver_geometry_parser.hpp"
// mapnik
#include <mapnik/global.hpp>
#include <mapnik/debug.hpp>
#include <mapnik/box2d.hpp>
#include <mapnik/geometry.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/feature_layer_desc.hpp>
#include <mapnik/wkb.hpp>
#include <mapnik/unicode.hpp>
#include <mapnik/feature_factory.hpp>
// sql server (odbc)
#include <sqlext.h>
#include <msodbcsql.h>
using mapnik::query;
using mapnik::box2d;
using mapnik::feature_ptr;
using mapnik::geometry_utils;
using mapnik::transcoder;
using mapnik::feature_factory;
using mapnik::attribute_descriptor;
sqlserver_featureset::sqlserver_featureset(SQLHDBC hdbc,
std::string const& sqlstring,
mapnik::layer_descriptor const& desc
)
: hstmt_(0),
desc_(desc),
tr_(new transcoder(desc.get_encoding())),
feature_id_(1)
{
SQLRETURN retcode;
// allocate statement handle
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt_);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not allocate statement", SQL_HANDLE_DBC, hdbc);
}
// execute statement
retcode = SQLExecDirect(hstmt_, (SQLCHAR*)sqlstring.c_str(), SQL_NTS);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not execute statement", SQL_HANDLE_STMT, hstmt_);
}
std::vector<attribute_descriptor>::const_iterator itr = desc_.get_descriptors().begin();
std::vector<attribute_descriptor>::const_iterator end = desc_.get_descriptors().end();
ctx_ = std::make_shared<mapnik::context_type>();
while (itr != end) {
ctx_->push(itr->get_name());
++itr;
}
}
sqlserver_featureset::~sqlserver_featureset() {
if (hstmt_) {
(void)SQLFreeStmt(hstmt_, SQL_CLOSE);
hstmt_ = 0;
}
}
feature_ptr sqlserver_featureset::next()
{
SQLRETURN retcode;
// fetch next result
retcode = SQLFetch(hstmt_);
if (retcode == SQL_NO_DATA) {
// normal end of recordset
return feature_ptr();
}
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not fetch result", SQL_HANDLE_STMT, hstmt_);
}
// create an empty feature with the next id
feature_ptr feature(feature_factory::create(ctx_, feature_id_));
// populate feature geometry and attributes from this row
std::vector<attribute_descriptor>::const_iterator itr = desc_.get_descriptors().begin();
std::vector<attribute_descriptor>::const_iterator end = desc_.get_descriptors().end();
SQLUSMALLINT ColumnNum=1;
while (itr != end) {
SQLCHAR sval[2048];
long ival;
double dval;
SQLCHAR *BinaryPtr = NULL; // Allocate dynamically
SQLLEN BinaryLenOrInd;
SQLLEN LenOrInd;
switch (itr->get_type()) {
case mapnik::sqlserver::String:
retcode = SQLGetData(hstmt_, ColumnNum, SQL_C_CHAR, sval, sizeof(sval), &LenOrInd);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not get string data", SQL_HANDLE_STMT, hstmt_);
}
feature->put(itr->get_name(), (UnicodeString)tr_->transcode((char*)sval));
break;
case mapnik::sqlserver::Integer:
retcode = SQLGetData(hstmt_, ColumnNum, SQL_C_SLONG, &ival, sizeof(ival), &LenOrInd);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not get int data", SQL_HANDLE_STMT, hstmt_);
}
feature->put(itr->get_name(), static_cast<mapnik::value_integer>(ival));
break;
case mapnik::sqlserver::Double:
retcode = SQLGetData(hstmt_, ColumnNum, SQL_C_DOUBLE, &dval, sizeof(dval), &LenOrInd);
if (!SQL_SUCCEEDED(retcode)) {
throw sqlserver_datasource_exception("could not get double data", SQL_HANDLE_STMT, hstmt_);
}
feature->put(itr->get_name(), dval);
break;
case mapnik::sqlserver::Geometry:
case mapnik::sqlserver::Geography: {
// Call SQLGetData with a zero buffer size to determine the amount of data that's waiting.
SQLCHAR DummyBinaryPtr[10]; // cannot pass a NULL pointer, even though we pass length of zero
retcode = SQLGetData(hstmt_, ColumnNum, SQL_C_BINARY, DummyBinaryPtr, 0, &BinaryLenOrInd);
if (retcode != SQL_SUCCESS_WITH_INFO) {
throw sqlserver_datasource_exception("could not get geometry data - failed to get buffer length", SQL_HANDLE_STMT, hstmt_);
}
// allocate a suitably sized buffer
BinaryPtr = new SQLCHAR[BinaryLenOrInd];
// get the geometry data
retcode = SQLGetData(hstmt_, ColumnNum, SQL_C_BINARY, BinaryPtr, BinaryLenOrInd, &BinaryLenOrInd);
if (!SQL_SUCCEEDED(retcode)) {
delete[] BinaryPtr;
BinaryPtr = NULL;
throw sqlserver_datasource_exception("could not get geometry data into buffer", SQL_HANDLE_STMT, hstmt_);
}
// attempt to parse
try {
sqlserver_geometry_parser geometry_parser((itr->get_type() == mapnik::sqlserver::Geometry ? Geometry : Geography));
mapnik::geometry::geometry<double> geom = geometry_parser.parse(BinaryPtr, BinaryLenOrInd);
feature->set_geometry(std::move(geom));
} catch (mapnik::datasource_exception e) {
// Cleanup and rethrow the caught exception
delete[] BinaryPtr;
BinaryPtr = NULL;
throw;
}
// normal cleanup
delete[] BinaryPtr;
BinaryPtr = NULL;
break;
}
default:
MAPNIK_LOG_WARN(sqlserver) << "sqlserver_datasource: unknown/unsupported datatype in column: " << itr->get_name() << " (" << itr->get_type() << ")";
break;
}
++ColumnNum;
++itr;
}
++feature_id_;
return feature;
}

View file

@ -0,0 +1,49 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2015 we-do-IT
*
*****************************************************************************/
#ifndef SQLSERVER_FEATURESET_HPP
#define SQLSERVER_FEATURESET_HPP
// mapnik
#include <mapnik/feature.hpp>
#include <mapnik/datasource.hpp>
#include <mapnik/geometry.hpp>
#include <mapnik/unicode.hpp>
#include <mapnik/attribute_descriptor.hpp>
// boost
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
// sql server (via odbc)
#ifdef _WINDOWS
#include <windows.h>
#endif
#include "sql.h"
#include <utility>
#include <vector>
class sqlserver_featureset : public mapnik::Featureset
{
public:
sqlserver_featureset(SQLHDBC hdbc,
std::string const& sqlstring,
mapnik::layer_descriptor const& desc);
virtual ~sqlserver_featureset();
mapnik::feature_ptr next();
private:
SQLHANDLE hstmt_;
mapnik::layer_descriptor desc_;
boost::scoped_ptr<mapnik::transcoder> tr_;
mapnik::value_integer feature_id_;
mapnik::context_ptr ctx_;
};
#endif // SQLSERVER_FEATURESET_HPP

View file

@ -0,0 +1,500 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* adapted from ogrmssqlgeometryparser.cpp, part of OGR
* original copyright notice follows
*
*****************************************************************************/
/******************************************************************************
* $Id: ogrmssqlgeometryparser.cpp 24918 2012-09-07 12:02:01Z tamas $
*
* Project: MSSQL Spatial driver
* Purpose: Implements ogrmssqlgeometryparser class to parse native SqlGeometries.
* Author: Tamas Szekeres, szekerest at gmail.com
*
******************************************************************************
* Copyright (c) 2010, Tamas Szekeres
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
****************************************************************************/
#include "sqlserver_geometry_parser.hpp"
#include "sqlserver_datasource.hpp"
#include <mapnik/datasource.hpp>
class sqlserver_geometry_parser_exception : public sqlserver_datasource_exception
{
public:
sqlserver_geometry_parser_exception(std::string const& message)
: sqlserver_datasource_exception("Geometry Parser: "+message) {}
virtual ~sqlserver_geometry_parser_exception() throw() {}
};
/* SqlGeometry serialization format
Simple Point (SerializationProps & IsSinglePoint)
[SRID][0x01][SerializationProps][Point][z][m]
Simple Line Segment (SerializationProps & IsSingleLineSegment)
[SRID][0x01][SerializationProps][Point1][Point2][z1][z2][m1][m2]
Complex Geometries
[SRID][0x01][SerializationProps][NumPoints][Point1]..[PointN][z1]..[zN][m1]..[mN]
[NumFigures][Figure]..[Figure][NumShapes][Shape]..[Shape]
SRID
Spatial Reference Id (4 bytes)
SerializationProps (bitmask) 1 byte
0x01 = HasZValues
0x02 = HasMValues
0x04 = IsValid
0x08 = IsSinglePoint
0x10 = IsSingleLineSegment
0x20 = IsWholeGlobe
Point (2-4)x8 bytes, size depends on SerializationProps & HasZValues & HasMValues
[x][y] - SqlGeometry
[latitude][longitude] - SqlGeography
Figure
[FigureAttribute][PointOffset]
FigureAttribute (1 byte)
0x00 = Interior Ring
0x01 = Stroke
0x02 = Exterior Ring
Shape
[ParentFigureOffset][FigureOffset][ShapeType]
ShapeType (1 byte)
0x00 = Unknown
0x01 = Point
0x02 = LineString
0x03 = Polygon
0x04 = MultiPoint
0x05 = MultiLineString
0x06 = MultiPolygon
0x07 = GeometryCollection
*/
/************************************************************************/
/* Geometry parser macros */
/************************************************************************/
#define SP_NONE 0
#define SP_HASZVALUES 1
#define SP_HASMVALUES 2
#define SP_ISVALID 4
#define SP_ISSINGLEPOINT 8
#define SP_ISSINGLELINESEGMENT 0x10
#define SP_ISWHOLEGLOBE 0x20
#define ST_UNKNOWN 0
#define ST_POINT 1
#define ST_LINESTRING 2
#define ST_POLYGON 3
#define ST_MULTIPOINT 4
#define ST_MULTILINESTRING 5
#define ST_MULTIPOLYGON 6
#define ST_GEOMETRYCOLLECTION 7
#define ReadInt32(nPos) (*((unsigned int*)(pszData + (nPos))))
#define ReadByte(nPos) (pszData[nPos])
#define ReadDouble(nPos) (*((double*)(pszData + (nPos))))
#define ParentOffset(iShape) (ReadInt32(nShapePos + (iShape) * 9 ))
#define FigureOffset(iShape) (ReadInt32(nShapePos + (iShape) * 9 + 4))
#define ShapeType(iShape) (ReadByte(nShapePos + (iShape) * 9 + 8))
#define NextFigureOffset(iShape) (iShape + 1 < nNumShapes? FigureOffset((iShape) +1) : nNumFigures)
#define FigureAttribute(iFigure) (ReadByte(nFigurePos + (iFigure) * 5))
#define PointOffset(iFigure) (ReadInt32(nFigurePos + (iFigure) * 5 + 1))
#define NextPointOffset(iFigure) (iFigure + 1 < nNumFigures? PointOffset((iFigure) +1) : nNumPoints)
#define ReadX(iPoint) (ReadDouble(nPointPos + 16 * (iPoint)))
#define ReadY(iPoint) (ReadDouble(nPointPos + 16 * (iPoint) + 8))
#define ReadZ(iPoint) (ReadDouble(nPointPos + 16 * nNumPoints + 8 * (iPoint)))
#define ReadM(iPoint) (ReadDouble(nPointPos + 24 * nNumPoints + 8 * (iPoint)))
/************************************************************************/
/* sqlserver_geometry_parser() */
/************************************************************************/
sqlserver_geometry_parser::sqlserver_geometry_parser(spatial_data_type columnType)
{
colType = columnType;
}
/************************************************************************/
/* ReadPoint() */
/************************************************************************/
mapnik::geometry::point<double> sqlserver_geometry_parser::ReadPoint(int iShape)
{
mapnik::geometry::point<double> geom;
int iFigure = FigureOffset(iShape);
if ( iFigure < nNumFigures ) {
int iPoint = PointOffset(iFigure);
if ( iPoint < nNumPoints ) {
if (colType == Geography) {
geom.x = ReadY(iPoint);
geom.y = ReadX(iPoint);
} else {
geom.x = ReadX(iPoint);
geom.y = ReadY(iPoint);
}
}
}
return geom;
}
/************************************************************************/
/* ReadMultiPoint() */
/************************************************************************/
mapnik::geometry::multi_point<double> sqlserver_geometry_parser::ReadMultiPoint(int iShape)
{
mapnik::geometry::multi_point<double> geom;
for (int i = iShape + 1; i < nNumShapes; i++) {
if (ParentOffset(i) == (unsigned int)iShape) {
if ( ShapeType(i) == ST_POINT ) {
geom.emplace_back(ReadPoint(i));
}
}
}
return geom;
}
/************************************************************************/
/* ReadLineString() */
/************************************************************************/
mapnik::geometry::line_string<double> sqlserver_geometry_parser::ReadLineString(int iShape)
{
mapnik::geometry::line_string<double> geom;
int iFigure = FigureOffset(iShape);
int iPoint = PointOffset(iFigure);
int iNextPoint = NextPointOffset(iFigure);
while (iPoint < iNextPoint) {
if (colType == Geography) {
geom.emplace_back(ReadY(iPoint), ReadX(iPoint));
} else {
geom.emplace_back(ReadX(iPoint), ReadY(iPoint));
}
++iPoint;
}
return geom;
}
/************************************************************************/
/* ReadMultiLineString() */
/************************************************************************/
mapnik::geometry::multi_line_string<double> sqlserver_geometry_parser::ReadMultiLineString(int iShape)
{
mapnik::geometry::multi_line_string<double> geom;
for (int i = iShape + 1; i < nNumShapes; i++) {
if (ParentOffset(i) == (unsigned int)iShape) {
if ( ShapeType(i) == ST_LINESTRING ) {
geom.emplace_back(ReadLineString(i));
}
}
}
return geom;
}
/************************************************************************/
/* ReadPolygon() */
/************************************************************************/
mapnik::geometry::polygon<double> sqlserver_geometry_parser::ReadPolygon(int iShape)
{
mapnik::geometry::polygon<double> geom;
int iNextFigure = NextFigureOffset(iShape);
for (int iFigure = FigureOffset(iShape); iFigure < iNextFigure; iFigure++) {
mapnik::geometry::linear_ring<double> ring;
int iPoint = PointOffset(iFigure);
int iNextPoint = NextPointOffset(iFigure);
while (iPoint < iNextPoint) {
if (colType == Geography) {
ring.emplace_back(ReadY(iPoint), ReadX(iPoint));
} else {
ring.emplace_back(ReadX(iPoint), ReadY(iPoint));
}
++iPoint;
}
if (iFigure == 0) {
geom.set_exterior_ring(std::move(ring));
} else {
geom.add_hole(std::move(ring));
}
}
return geom;
}
/************************************************************************/
/* ReadMultiPolygon() */
/************************************************************************/
mapnik::geometry::multi_polygon<double> sqlserver_geometry_parser::ReadMultiPolygon(int iShape)
{
mapnik::geometry::multi_polygon<double> geom;
for (int i = iShape + 1; i < nNumShapes; i++) {
if (ParentOffset(i) == (unsigned int)iShape) {
if ( ShapeType(i) == ST_POLYGON ) {
geom.emplace_back(ReadPolygon(i));
}
}
}
return geom;
}
/************************************************************************/
/* ReadGeometryCollection() */
/************************************************************************/
mapnik::geometry::geometry_collection<double> sqlserver_geometry_parser::ReadGeometryCollection(int iShape)
{
mapnik::geometry::geometry_collection<double> geom;
for (int i = iShape + 1; i < nNumShapes; i++) {
mapnik::geometry::geometry<double> shape = mapnik::geometry::geometry_empty();
if (ParentOffset(i) == (unsigned int)iShape) {
switch (ShapeType(i))
{
case ST_POINT:
shape = ReadPoint(i);
break;
case ST_LINESTRING:
shape = ReadLineString(i);
break;
case ST_POLYGON:
shape = ReadPolygon(i);
break;
case ST_MULTIPOINT:
shape = ReadMultiPoint(i);
break;
case ST_MULTILINESTRING:
shape = ReadMultiLineString(i);
break;
case ST_MULTIPOLYGON:
shape = ReadMultiPolygon(i);
break;
case ST_GEOMETRYCOLLECTION:
shape = ReadGeometryCollection(i);
break;
}
}
geom.push_back(shape);
}
return geom;
}
/************************************************************************/
/* parse_sql_geometry() */
/************************************************************************/
mapnik::geometry::geometry<double> sqlserver_geometry_parser::parse(unsigned char* pszInput, int nLen)
{
if (nLen < 10) {
throw sqlserver_geometry_parser_exception("not enough data, nLen < 10");
}
pszData = pszInput;
/* store the SRS id for further use */
nSRSId = ReadInt32(0);
if ( ReadByte(4) != 1 )
{
throw sqlserver_geometry_parser_exception("corrupt data, ReadByte(4) != 1");
}
chProps = ReadByte(5);
if ( chProps & SP_HASMVALUES )
nPointSize = 32;
else if ( chProps & SP_HASZVALUES )
nPointSize = 24;
else
nPointSize = 16;
mapnik::geometry::geometry<double> geom;
if ( chProps & SP_ISSINGLEPOINT )
{
// single point geometry
nNumPoints = 1;
nPointPos = 6;
if (nLen < 6 + nPointSize)
{
throw sqlserver_geometry_parser_exception("not enough data, nLen < 6 + nPointSize");
}
mapnik::geometry::point<double> point;
if (colType == Geography)
{
point.x = ReadY(0);
point.y = ReadX(0);
}
else
{
point.x = ReadX(0);
point.y = ReadY(0);
}
geom = point;
}
else if ( chProps & SP_ISSINGLELINESEGMENT )
{
// single line segment with 2 points
nNumPoints = 2;
nPointPos = 6;
if (nLen < 6 + 2 * nPointSize)
{
throw sqlserver_geometry_parser_exception("not enough data, nLen < 6 + 2 * nPointSize");
}
mapnik::geometry::line_string<double> line;
if (colType == Geography)
{
line.emplace_back(ReadY(0), ReadX(0));
line.emplace_back(ReadY(1), ReadX(1));
}
else
{
line.emplace_back(ReadX(0), ReadY(0));
line.emplace_back(ReadX(1), ReadY(1));
}
geom = line;
}
else
{
// complex geometries
nNumPoints = ReadInt32(6);
if ( nNumPoints <= 0 )
{
throw sqlserver_geometry_parser_exception("negative number of points, nNumPoints <= 0");
}
// position of the point array
nPointPos = 10;
// position of the figures
nFigurePos = nPointPos + nPointSize * nNumPoints + 4;
if (nLen < nFigurePos)
{
throw sqlserver_geometry_parser_exception("not enough data, nLen < nFigurePos");
}
nNumFigures = ReadInt32(nFigurePos - 4);
if ( nNumFigures <= 0 )
{
throw sqlserver_geometry_parser_exception("negative number of figures, nNumFigures <= 0");
}
// position of the shapes
nShapePos = nFigurePos + 5 * nNumFigures + 4;
if (nLen < nShapePos)
{
throw sqlserver_geometry_parser_exception("not enough data, nLen < nShapePos");
}
nNumShapes = ReadInt32(nShapePos - 4);
if (nLen < nShapePos + 9 * nNumShapes)
{
throw sqlserver_geometry_parser_exception("not enough data, nLen < nShapePos + 9 * nNumShapes");
}
if ( nNumShapes <= 0 )
{
throw sqlserver_geometry_parser_exception("negative number of shapes, nNumShapes <= 0");
}
// pick up the root shape
if ( ParentOffset(0) != 0xFFFFFFFF)
{
throw sqlserver_geometry_parser_exception("corrupt data, ParentOffset(0) != 0xFFFFFFFF");
}
// determine the shape type
switch (ShapeType(0))
{
case ST_POINT:
geom = ReadPoint(0);
break;
case ST_LINESTRING:
geom = ReadLineString(0);
break;
case ST_POLYGON:
geom = ReadPolygon(0);
break;
case ST_MULTIPOINT:
geom = ReadMultiPoint(0);
break;
case ST_MULTILINESTRING:
geom = ReadMultiLineString(0);
break;
case ST_MULTIPOLYGON:
geom = ReadMultiPolygon(0);
break;
case ST_GEOMETRYCOLLECTION:
geom = ReadGeometryCollection(0);
break;
default:
throw sqlserver_geometry_parser_exception("unsupported geometry type");
}
}
return geom;
}

View file

@ -0,0 +1,45 @@
// mapnik
#include <mapnik/geometry.hpp>
enum spatial_data_type {
Geometry,
Geography
};
class sqlserver_geometry_parser
{
protected:
unsigned char* pszData;
/* serialization propeties */
char chProps;
/* point array */
int nPointSize;
int nPointPos;
int nNumPoints;
/* figure array */
int nFigurePos;
int nNumFigures;
/* shape array */
int nShapePos;
int nNumShapes;
int nSRSId;
/* geometry or geography */
spatial_data_type colType;
protected:
mapnik::geometry::point<double> ReadPoint(int iShape);
mapnik::geometry::multi_point<double> ReadMultiPoint(int iShape);
mapnik::geometry::line_string<double> ReadLineString(int iShape);
mapnik::geometry::multi_line_string<double> ReadMultiLineString(int iShape);
mapnik::geometry::polygon<double> ReadPolygon(int iShape);
mapnik::geometry::multi_polygon<double> ReadMultiPolygon(int iShape);
mapnik::geometry::geometry_collection<double> ReadGeometryCollection(int iShape);
public:
sqlserver_geometry_parser(spatial_data_type columnType);
mapnik::geometry::geometry<double> parse(unsigned char* pszInput, int nLen);
int get_srs_id() { return nSRSId; };
};