diff --git a/plugins/input/postgis/postgis_datasource.cpp b/plugins/input/postgis/postgis_datasource.cpp index d95aa3392..909cd58ba 100644 --- a/plugins/input/postgis/postgis_datasource.cpp +++ b/plugins/input/postgis/postgis_datasource.cpp @@ -88,6 +88,17 @@ postgis_datasource::postgis_datasource(parameters const& params) extent_from_subquery_(*params.get("extent_from_subquery", false)), max_async_connections_(*params_.get("max_async_connection", 1)), asynchronous_request_(false), + twkb_encoding_(false), + twkb_rounding_adjustment_(*params_.get("twkb_rounding_adjustment", 0.0)), + simplify_snap_ratio_(*params_.get("simplify_snap_ratio", 1.0/40.0)), + // 1/20 of pixel seems to be a good compromise to avoid + // drop of collapsed polygons. + // See https://github.com/mapnik/mapnik/issues/1639 + // See http://trac.osgeo.org/postgis/ticket/2093 + simplify_dp_ratio_(*params_.get("simplify_dp_ratio", 1.0/20.0)), + simplify_prefilter_(*params_.get("simplify_prefilter", 0.0)), + simplify_dp_preserve_(false), + simplify_clip_resolution_(*params_.get("simplify_clip_resolution", 0.0)), // TODO - use for known tokens too: "(@\\w+|!\\w+!)" pattern_(boost::regex("(@\\w+)",boost::regex::normal | boost::regbase::icase)), // params below are for testing purposes only and may be removed at any time @@ -130,6 +141,12 @@ postgis_datasource::postgis_datasource(parameters const& params) boost::optional simplify_opt = params.get("simplify_geometries", false); simplify_geometries_ = simplify_opt && *simplify_opt; + boost::optional twkb_opt = params.get("twkb_encoding", false); + twkb_encoding_ = twkb_opt && *twkb_opt; + + boost::optional simplify_preserve_opt = params.get("simplify_dp_preserve", false); + simplify_dp_preserve_ = simplify_preserve_opt && *simplify_preserve_opt; + ConnectionManager::instance().registerPool(creator_, *initial_size, pool_max_size_); CnxPool_ptr pool = ConnectionManager::instance().getPool(creator_.id()); if (pool) @@ -794,25 +811,91 @@ featureset_ptr postgis_datasource::features_with_context(query const& q,processo const double px_gw = 1.0 / std::get<0>(q.resolution()); const double px_gh = 1.0 / std::get<1>(q.resolution()); + const double px_sz = std::min(px_gw, px_gh); - s << "SELECT ST_AsBinary("; + if (twkb_encoding_) + { + // This will only work against PostGIS 2.2, or a back-patched version + // that has (a) a ST_Simplify with a "preserve collapsed" flag and + // (b) a ST_RemoveRepeatedPoints with a tolerance parameter and + // (c) a ST_AsTWKB implementation - if (simplify_geometries_) { - s << "ST_Simplify("; + // What number of decimals of rounding does the pixel size imply? + const int twkb_rounding = -1 * std::lround(log10(px_sz) + twkb_rounding_adjustment_) + 1; + // And what's that in map units? + const double twkb_tolerance = pow(10.0, -1.0 * twkb_rounding); + + s << "SELECT ST_AsTWKB("; + s << "ST_Simplify("; + s << "ST_RemoveRepeatedPoints("; + + if (simplify_clip_resolution_ > 0.0 && simplify_clip_resolution_ > px_sz) + { + s << "ST_ClipByBox2D("; + } + s << "\"" << geometryColumn_ << "\""; + + // ! ST_ClipByBox2D() + if (simplify_clip_resolution_ > 0.0 && simplify_clip_resolution_ > px_sz) + { + s << "," << sql_bbox(box) << ")"; + } + + // ! ST_RemoveRepeatedPoints() + s << "," << twkb_tolerance << ")"; + // ! ST_Simplify(), with parameter to keep collapsed geometries + s << "," << twkb_tolerance << ",true)"; + // ! ST_TWKB() + s << "," << twkb_rounding << ") AS geom"; } + else + { + s << "SELECT ST_AsBinary("; + if (simplify_geometries_) + { + s << "ST_Simplify("; + } + if (simplify_clip_resolution_ > 0.0 && simplify_clip_resolution_ > px_sz) + { + s << "ST_ClipByBox2D("; + } + if (simplify_geometries_ && simplify_snap_ratio_ > 0.0) + { + s<< "ST_SnapToGrid("; + } - s << "\"" << geometryColumn_ << "\""; + // Geometry column! + s << "\"" << geometryColumn_ << "\""; - if (simplify_geometries_) { - // 1/20 of pixel seems to be a good compromise to avoid - // drop of collapsed polygons. - // See https://github.com/mapnik/mapnik/issues/1639 - const double tolerance = std::min(px_gw, px_gh) / 20.0; - s << ", " << tolerance << ")"; + // ! ST_SnapToGrid() + if (simplify_geometries_ && simplify_snap_ratio_ > 0.0) + { + const double tolerance = px_sz * simplify_snap_ratio_; + s << "," << tolerance << ")"; + } + + // ! ST_ClipByBox2D() + if (simplify_clip_resolution_ > 0.0 && simplify_clip_resolution_ > px_sz) + { + s << "," << sql_bbox(box) << ")"; + } + + // ! ST_Simplify() + if (simplify_geometries_) + { + const double tolerance = px_sz * simplify_dp_ratio_; + s << ", " << tolerance; + // Add parameter to ST_Simplify to keep collapsed geometries + if (simplify_dp_preserve_) + { + s << ", true"; + } + s << ")"; + } + + // ! ST_AsBinary() + s << ") AS geom"; } - - s << ") AS geom"; - mapnik::context_ptr ctx = std::make_shared(); std::set const& props = q.property_names(); std::set::const_iterator pos = props.begin(); @@ -854,7 +937,8 @@ featureset_ptr postgis_datasource::features_with_context(query const& q,processo } std::shared_ptr rs = get_resultset(conn, s.str(), pool, proc_ctx); - return std::make_shared(rs, ctx, desc_.get_encoding(), !key_field_.empty(), key_field_as_attribute_); + return std::make_shared(rs, ctx, desc_.get_encoding(), !key_field_.empty(), + key_field_as_attribute_, twkb_encoding_); } @@ -941,7 +1025,8 @@ featureset_ptr postgis_datasource::features_at_point(coord2d const& pt, double t } std::shared_ptr rs = get_resultset(conn, s.str(), pool); - return std::make_shared(rs, ctx, desc_.get_encoding(), !key_field_.empty(), key_field_as_attribute_); + return std::make_shared(rs, ctx, desc_.get_encoding(), !key_field_.empty(), + key_field_as_attribute_, twkb_encoding_); } } diff --git a/plugins/input/postgis/postgis_datasource.hpp b/plugins/input/postgis/postgis_datasource.hpp index 2f5d24f47..80ba89495 100644 --- a/plugins/input/postgis/postgis_datasource.hpp +++ b/plugins/input/postgis/postgis_datasource.hpp @@ -119,6 +119,13 @@ private: bool estimate_extent_; int max_async_connections_; bool asynchronous_request_; + bool twkb_encoding_; + mapnik::value_double twkb_rounding_adjustment_; + mapnik::value_double simplify_snap_ratio_; + mapnik::value_double simplify_dp_ratio_; + mapnik::value_double simplify_prefilter_; + bool simplify_dp_preserve_; + mapnik::value_double simplify_clip_resolution_; boost::regex pattern_; int intersect_min_scale_; int intersect_max_scale_; diff --git a/plugins/input/postgis/postgis_featureset.cpp b/plugins/input/postgis/postgis_featureset.cpp index 6d417bef1..25e2b6b4b 100644 --- a/plugins/input/postgis/postgis_featureset.cpp +++ b/plugins/input/postgis/postgis_featureset.cpp @@ -49,14 +49,16 @@ postgis_featureset::postgis_featureset(std::shared_ptr const& rs, context_ptr const& ctx, std::string const& encoding, bool key_field, - bool key_field_as_attribute) + bool key_field_as_attribute, + bool twkb_encoding) : rs_(rs), ctx_(ctx), tr_(new transcoder(encoding)), totalGeomSize_(0), feature_id_(1), key_field_(key_field), - key_field_as_attribute_(key_field_as_attribute) + key_field_as_attribute_(key_field_as_attribute), + twkb_encoding_(twkb_encoding) { } @@ -123,8 +125,14 @@ feature_ptr postgis_featureset::next() int size = rs_->getFieldLength(0); const char *data = rs_->getValue(0); - mapnik::geometry::geometry geometry = geometry_utils::from_wkb(data, size); - feature->set_geometry(std::move(geometry)); + if (twkb_encoding_ ) + { + feature->set_geometry(geometry_utils::from_twkb(data, size)); + } + else + { + feature->set_geometry(geometry_utils::from_wkb(data, size)); + } totalGeomSize_ += size; unsigned num_attrs = ctx_->size() + 1; diff --git a/plugins/input/postgis/postgis_featureset.hpp b/plugins/input/postgis/postgis_featureset.hpp index 4984ec968..9ef8cb392 100644 --- a/plugins/input/postgis/postgis_featureset.hpp +++ b/plugins/input/postgis/postgis_featureset.hpp @@ -44,7 +44,8 @@ public: context_ptr const& ctx, std::string const& encoding, bool key_field, - bool key_field_as_attribute); + bool key_field_as_attribute, + bool twkb_encoding); feature_ptr next(); ~postgis_featureset(); @@ -56,6 +57,7 @@ private: mapnik::value_integer feature_id_; bool key_field_; bool key_field_as_attribute_; + bool twkb_encoding_; }; #endif // POSTGIS_FEATURESET_HPP