Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PostGIS+PGraster: pass exact floating-point numbers in SQL queries #4089

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions plugins/input/pgcommon/sql_utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2019 Artem Pavlenko
*
* This library 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
*
*****************************************************************************/

#ifndef MAPNIK_PLUGINS_INPUT_PGCOMMON_SQL_UTILS_HPP
#define MAPNIK_PLUGINS_INPUT_PGCOMMON_SQL_UTILS_HPP

// mapnik
#include <mapnik/geometry/box2d.hpp>

// stl
#include <algorithm>
#include <cstdio>
#include <ostream>

namespace mapnik { namespace pgcommon {


// sql_float4

struct sql_float4
{
float value;
};

inline sql_float4 sql_float(float value)
{
return {value};
}

inline std::ostream & operator << (std::ostream & os, sql_float4 sf)
{
char const fmt[] = "float4 '%a'";
char buf[sizeof(fmt) + 25];
int len = std::snprintf(buf, sizeof(buf), fmt, sf.value);
return os.write(buf, std::min(len, int(sizeof(buf)) - 1));
}


// sql_float8

struct sql_float8
{
double value;
};

inline sql_float8 sql_float(double value)
{
return {value};
}

inline std::ostream & operator << (std::ostream & os, sql_float8 sf)
{
char const fmt[] = "float8 '%a'";
char buf[sizeof(fmt) + 25];
int len = std::snprintf(buf, sizeof(buf), fmt, sf.value);
return os.write(buf, std::min(len, int(sizeof(buf)) - 1));
}


// sql_bbox

struct sql_bbox : box2d<double>
{
int srid_;

sql_bbox(box2d<double> const& box, int srid)
: box2d<double>(box), srid_(srid) {}
};

inline std::ostream & operator << (std::ostream & os, sql_bbox const& box)
{
char const fmt[] = "ST_MakeEnvelope(float8 '%a', float8 '%a', "
"float8 '%a', float8 '%a', %d)";
char buf[sizeof(fmt) + 4 * 25 + sizeof(int[3])];
int len = std::snprintf(buf, sizeof(buf), fmt,
box.minx(), box.miny(), box.maxx(), box.maxy(),
std::max(box.srid_, 0));
return os.write(buf, std::min(len, int(sizeof(buf)) - 1));
}


}} // namespace mapnik::pgcommon

#endif // MAPNIK_PLUGINS_INPUT_PGCOMMON_SQL_UTILS_HPP
35 changes: 12 additions & 23 deletions plugins/input/pgraster/pgraster_datasource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
*
*****************************************************************************/

#include "../pgcommon/sql_utils.hpp"
#include "../postgis/connection_manager.hpp"
#include "../postgis/asyncresultset.hpp"
#include "pgraster_datasource.hpp"
Expand Down Expand Up @@ -58,6 +59,8 @@ const std::string pgraster_datasource::SPATIAL_REF_SYS = "spatial_ref_system";

using std::shared_ptr;
using mapnik::attribute_descriptor;
using mapnik::pgcommon::sql_bbox;
using mapnik::pgcommon::sql_float;
using mapnik::sql_utils::identifier;
using mapnik::sql_utils::literal;
using mapnik::value_integer;
Expand Down Expand Up @@ -589,17 +592,6 @@ layer_descriptor pgraster_datasource::get_descriptor() const
return desc_;
}

std::string pgraster_datasource::sql_bbox(box2d<double> const& env) const
{
std::ostringstream b;
b.precision(16);
b << "ST_MakeEnvelope(";
b << env.minx() << "," << env.miny() << ",";
b << env.maxx() << "," << env.maxy() << ",";
b << std::max(srid_, 0) << ")";
return b.str();
}

std::string pgraster_datasource::populate_tokens(std::string const& sql) const
{
return populate_tokens(sql, FLT_MAX,
Expand All @@ -620,9 +612,6 @@ std::string pgraster_datasource::populate_tokens(std::string const& sql,
char const* start = sql.data();
char const* end = start + sql.size();

populated_sql.precision(16);
populated_sql << std::showpoint;

while (std::regex_search(start, end, m, re_tokens_))
{
populated_sql.write(start, m[0].first - start);
Expand All @@ -645,20 +634,20 @@ std::string pgraster_datasource::populate_tokens(std::string const& sql,
}
else if (boost::algorithm::equals(m1, "bbox"))
{
populated_sql << sql_bbox(env);
populated_sql << sql_bbox(env, srid_);
intersect = false;
}
else if (boost::algorithm::equals(m1, "pixel_height"))
{
populated_sql << pixel_height;
populated_sql << sql_float(pixel_height);
}
else if (boost::algorithm::equals(m1, "pixel_width"))
{
populated_sql << pixel_width;
populated_sql << sql_float(pixel_width);
}
else if (boost::algorithm::equals(m1, "scale_denominator"))
{
populated_sql << scale_denom;
populated_sql << sql_float(scale_denom);
}
else
{
Expand All @@ -674,7 +663,7 @@ std::string pgraster_datasource::populate_tokens(std::string const& sql,
{
populated_sql << " WHERE ST_Intersects("
<< identifier(geometryColumn_) << ", "
<< sql_bbox(env) << ")";
<< sql_bbox(env, srid_) << ")";
}
else if (intersect_max_scale_ > 0 && (scale_denom >= intersect_max_scale_))
{
Expand All @@ -684,7 +673,7 @@ std::string pgraster_datasource::populate_tokens(std::string const& sql,
{
populated_sql << " WHERE "
<< identifier(geometryColumn_) << " && "
<< sql_bbox(env);
<< sql_bbox(env, srid_);
}
}

Expand Down Expand Up @@ -889,7 +878,7 @@ featureset_ptr pgraster_datasource::features_with_context(query const& q,process
s << identifier(col);

if (clip_rasters_) {
s << ", ST_Expand(" << sql_bbox(box)
s << ", ST_Expand(" << sql_bbox(box, srid_)
<< ", greatest(abs(ST_ScaleX("
<< identifier(col) << ")), abs(ST_ScaleY("
<< identifier(col) << ")))))";
Expand All @@ -898,9 +887,9 @@ featureset_ptr pgraster_datasource::features_with_context(query const& q,process
if (prescale_rasters_) {
const double scale = std::min(px_gw, px_gh);
s << ", least(1.0, abs(ST_ScaleX(" << identifier(col)
<< "))::float8/" << scale
<< "))::float8/" << sql_float(scale)
<< "), least(1.0, abs(ST_ScaleY(" << identifier(col)
<< "))::float8/" << scale << "))";
<< "))::float8/" << sql_float(scale) << "))";
// TODO: if band_ is given, we'll interpret as indexed so
// the rescaling must NOT ruin it (use algorithm mode!)
}
Expand Down
1 change: 0 additions & 1 deletion plugins/input/pgraster/pgraster_datasource.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ class pgraster_datasource : public datasource
layer_descriptor get_descriptor() const;

private:
std::string sql_bbox(box2d<double> const& env) const;
std::string populate_tokens(std::string const& sql, double scale_denom,
box2d<double> const& env,
double pixel_width, double pixel_height,
Expand Down
42 changes: 15 additions & 27 deletions plugins/input/postgis/postgis_datasource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
*
*****************************************************************************/

#include "../pgcommon/sql_utils.hpp"
#include "connection_manager.hpp"
#include "postgis_datasource.hpp"
#include "postgis_featureset.hpp"
#include "asyncresultset.hpp"


// mapnik
#include <mapnik/debug.hpp>
#include <mapnik/global.hpp>
Expand Down Expand Up @@ -55,6 +55,8 @@ const std::string postgis_datasource::SPATIAL_REF_SYS = "spatial_ref_system";

using std::shared_ptr;
using mapnik::attribute_descriptor;
using mapnik::pgcommon::sql_bbox;
using mapnik::pgcommon::sql_float;
using mapnik::sql_utils::identifier;
using mapnik::sql_utils::literal;

Expand Down Expand Up @@ -499,17 +501,6 @@ layer_descriptor postgis_datasource::get_descriptor() const
return desc_;
}

std::string postgis_datasource::sql_bbox(box2d<double> const& env) const
{
std::ostringstream b;
b.precision(16);
b << "ST_MakeEnvelope(";
b << env.minx() << "," << env.miny() << ",";
b << env.maxx() << "," << env.maxy() << ",";
b << std::max(srid_, 0) << ")";
return b.str();
}

std::string postgis_datasource::populate_tokens(std::string const& sql) const
{
return populate_tokens(sql, FLT_MAX,
Expand All @@ -531,9 +522,6 @@ std::string postgis_datasource::populate_tokens(
char const* start = sql.data();
char const* end = start + sql.size();

populated_sql.precision(16);
populated_sql << std::showpoint;

while (std::regex_search(start, end, m, re_tokens_))
{
populated_sql.write(start, m[0].first - start);
Expand All @@ -556,20 +544,20 @@ std::string postgis_datasource::populate_tokens(
}
else if (boost::algorithm::equals(m1, "bbox"))
{
populated_sql << sql_bbox(env);
populated_sql << sql_bbox(env, srid_);
intersect = false;
}
else if (boost::algorithm::equals(m1, "pixel_height"))
{
populated_sql << pixel_height;
populated_sql << sql_float(pixel_height);
}
else if (boost::algorithm::equals(m1, "pixel_width"))
{
populated_sql << pixel_width;
populated_sql << sql_float(pixel_width);
}
else if (boost::algorithm::equals(m1, "scale_denominator"))
{
populated_sql << scale_denom;
populated_sql << sql_float(scale_denom);
}
else
{
Expand All @@ -585,7 +573,7 @@ std::string postgis_datasource::populate_tokens(
{
populated_sql << " WHERE ST_Intersects("
<< identifier(geometryColumn_) << ", "
<< sql_bbox(env) << ")";
<< sql_bbox(env, srid_) << ")";
}
else if (intersect_max_scale_ > 0 && (scale_denom >= intersect_max_scale_))
{
Expand All @@ -595,7 +583,7 @@ std::string postgis_datasource::populate_tokens(
{
populated_sql << " WHERE "
<< identifier(geometryColumn_) << " && "
<< sql_bbox(env);
<< sql_bbox(env, srid_);
}
}

Expand Down Expand Up @@ -785,13 +773,13 @@ featureset_ptr postgis_datasource::features_with_context(query const& q,processo
// ! ST_ClipByBox2D()
if (simplify_clip_resolution_ > 0.0 && simplify_clip_resolution_ > px_sz)
{
s << "," << sql_bbox(box) << ")";
s << "," << sql_bbox(box, srid_) << ")";
}

// ! ST_RemoveRepeatedPoints()
s << "," << twkb_tolerance << ")";
s << "," << sql_float(twkb_tolerance) << ")";
// ! ST_Simplify(), with parameter to keep collapsed geometries
s << "," << twkb_tolerance << ",true)";
s << "," << sql_float(twkb_tolerance) << ",true)";
// ! ST_TWKB()
s << "," << twkb_rounding << ") AS geom";
}
Expand All @@ -818,20 +806,20 @@ featureset_ptr postgis_datasource::features_with_context(query const& q,processo
if (simplify_geometries_ && simplify_snap_ratio_ > 0.0)
{
const double tolerance = px_sz * simplify_snap_ratio_;
s << "," << tolerance << ")";
s << "," << sql_float(tolerance) << ")";
}

// ! ST_ClipByBox2D()
if (simplify_clip_resolution_ > 0.0 && simplify_clip_resolution_ > px_sz)
{
s << "," << sql_bbox(box) << ")";
s << "," << sql_bbox(box, srid_) << ")";
}

// ! ST_Simplify()
if (simplify_geometries_)
{
const double tolerance = px_sz * simplify_dp_ratio_;
s << ", " << tolerance;
s << ", " << sql_float(tolerance);
// Add parameter to ST_Simplify to keep collapsed geometries
if (simplify_dp_preserve_)
{
Expand Down
1 change: 0 additions & 1 deletion plugins/input/postgis/postgis_datasource.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ class postgis_datasource : public datasource
layer_descriptor get_descriptor() const;

private:
std::string sql_bbox(box2d<double> const& env) const;
std::string populate_tokens(std::string const& sql,
double scale_denom,
box2d<double> const& env,
Expand Down
2 changes: 1 addition & 1 deletion test/data
18 changes: 15 additions & 3 deletions test/unit/datasource/postgis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,18 @@ TEST_CASE("postgis") {
auto ds = mapnik::datasource_cache::instance().create(params);
}

SECTION("Postgis select from empty table")
{
mapnik::parameters params(base_params);
params["table"] = "test_empty_table";
auto ds = mapnik::datasource_cache::instance().create(params);
REQUIRE(ds != nullptr);
mapnik::query qry(ds->envelope());
auto featureset = ds->features(qry);
auto feature = featureset->next();
CHECK(feature == nullptr);
}

SECTION("Postgis dataset geometry type")
{
mapnik::parameters params(base_params);
Expand Down Expand Up @@ -304,9 +316,9 @@ TEST_CASE("postgis") {
auto feature = featureset->next();
CHECKED_IF(feature != nullptr)
{
CHECK(feature->get("t_pixel_width").to_string() == "numeric");
CHECK(feature->get("t_pixel_height").to_string() == "numeric");
CHECK(feature->get("t_scale_denom").to_string() == "numeric");
CHECK(feature->get("t_pixel_width").to_string() == "double precision");
CHECK(feature->get("t_pixel_height").to_string() == "double precision");
CHECK(feature->get("t_scale_denom").to_string() == "double precision");
}
}

Expand Down