diff --git a/.gitignore b/.gitignore index 5d2536c3e..fe40dfa29 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,11 @@ demo/c++/demo.tif demo/c++/demo.jpg demo/c++/demo.png demo/c++/demo256.png +demo/viewer/Makefile +demo/viewer/Makefile.Debug +demo/viewer/Makefile.Release +demo/viewer/release/ +demo/viewer/ui_about.h +demo/viewer/ui_info.h +demo/viewer/ui_layer_info.h tests/cpp_tests/*-bin diff --git a/bindings/python/mapnik_line_symbolizer.cpp b/bindings/python/mapnik_line_symbolizer.cpp index e7c9a58be..e87c52bec 100644 --- a/bindings/python/mapnik_line_symbolizer.cpp +++ b/bindings/python/mapnik_line_symbolizer.cpp @@ -57,7 +57,7 @@ void export_line_symbolizer() "Set/get the rasterization method of the line of the point") .add_property("stroke",make_function (&line_symbolizer::get_stroke, - return_value_policy()), + return_value_policy()), &line_symbolizer::set_stroke) .add_property("smooth", &line_symbolizer::smooth, diff --git a/bindings/python/mapnik_markers_symbolizer.cpp b/bindings/python/mapnik_markers_symbolizer.cpp index 9613e7476..2ab881c05 100644 --- a/bindings/python/mapnik_markers_symbolizer.cpp +++ b/bindings/python/mapnik_markers_symbolizer.cpp @@ -92,6 +92,7 @@ void export_markers_symbolizer() mapnik::enumeration_("marker_placement") .value("POINT_PLACEMENT",mapnik::MARKER_POINT_PLACEMENT) + .value("INTERIOR_PLACEMENT",mapnik::MARKER_INTERIOR_PLACEMENT) .value("LINE_PLACEMENT",mapnik::MARKER_LINE_PLACEMENT) ; diff --git a/bindings/python/mapnik_python.cpp b/bindings/python/mapnik_python.cpp index 883946427..71231aa25 100644 --- a/bindings/python/mapnik_python.cpp +++ b/bindings/python/mapnik_python.cpp @@ -608,6 +608,7 @@ BOOST_PYTHON_MODULE(_mapnik) def("has_cairo", &has_cairo, "Get cairo library status"); def("has_pycairo", &has_pycairo, "Get pycairo module status"); + python_optional(); python_optional(); python_optional >(); python_optional(); diff --git a/include/mapnik/marker_helpers.hpp b/include/mapnik/marker_helpers.hpp index 4d7a2be78..f22dd6ec5 100644 --- a/include/mapnik/marker_helpers.hpp +++ b/include/mapnik/marker_helpers.hpp @@ -27,12 +27,51 @@ #include #include #include +#include +#include + +// agg +#include "agg_ellipse.h" // boost #include namespace mapnik { +template +void build_ellipse(T const& sym, mapnik::feature_impl const& feature, svg_storage_type & marker_ellipse, svg::svg_path_adapter & svg_path) +{ + expression_ptr const& width_expr = sym.get_width(); + expression_ptr const& height_expr = sym.get_height(); + double width = 0; + double height = 0; + if (width_expr && height_expr) + { + width = boost::apply_visitor(evaluate(feature), *width_expr).to_double(); + height = boost::apply_visitor(evaluate(feature), *height_expr).to_double(); + } + else if (width_expr) + { + width = boost::apply_visitor(evaluate(feature), *width_expr).to_double(); + height = width; + } + else if (height_expr) + { + height = boost::apply_visitor(evaluate(feature), *height_expr).to_double(); + width = height; + } + svg::svg_converter_type styled_svg(svg_path, marker_ellipse.attributes()); + styled_svg.push_attr(); + styled_svg.begin_path(); + agg::ellipse c(0, 0, width/2.0, height/2.0); + styled_svg.storage().concat_path(c); + styled_svg.end_path(); + styled_svg.pop_attr(); + double lox,loy,hix,hiy; + styled_svg.bounding_rect(&lox, &loy, &hix, &hiy); + marker_ellipse.set_bounding_box(lox,loy,hix,hiy); +} + template bool push_explicit_style(Attr const& src, Attr & dst, markers_symbolizer const& sym) { @@ -42,10 +81,12 @@ bool push_explicit_style(Attr const& src, Attr & dst, markers_symbolizer const& boost::optional const& fill_opacity = sym.get_fill_opacity(); if (strk || fill || opacity || fill_opacity) { + bool success = false; for(unsigned i = 0; i < src.size(); ++i) { - mapnik::svg::path_attributes attr = src[i]; - + success = true; + dst.push_back(src[i]); + mapnik::svg::path_attributes & attr = dst.last(); if (attr.stroke_flag) { // TODO - stroke attributes need to be boost::optional @@ -87,9 +128,8 @@ bool push_explicit_style(Attr const& src, Attr & dst, markers_symbolizer const& attr.fill_opacity = *fill_opacity; } } - dst.push_back(attr); } - return true; + return success; } return false; } diff --git a/include/mapnik/markers_placement.hpp b/include/mapnik/markers_placement.hpp index a0828caba..5ac83a22d 100644 --- a/include/mapnik/markers_placement.hpp +++ b/include/mapnik/markers_placement.hpp @@ -24,13 +24,26 @@ #define MAPNIK_MARKERS_PLACEMENT_HPP // mapnik +#include +#include +#include +#include +#include //round #include // boost #include // agg +#include "agg_basics.h" +#include "agg_conv_clip_polygon.h" +#include "agg_conv_clip_polyline.h" +#include "agg_trans_affine.h" #include "agg_conv_transform.h" +#include "agg_conv_smooth_poly1.h" + +// stl +#include namespace mapnik { @@ -47,12 +60,40 @@ public: * converted to a positive value with similar magnitude, but * choosen to optimize marker placement. 0 = no markers */ - markers_placement(Locator &locator, box2d const& size, agg::trans_affine const& tr, Detector &detector, double spacing, double max_error, bool allow_overlap); + markers_placement(Locator &locator, box2d const& size, agg::trans_affine const& tr, Detector &detector, double spacing, double max_error, bool allow_overlap) + : locator_(locator), + size_(size), + tr_(tr), + detector_(detector), + max_error_(max_error), + allow_overlap_(allow_overlap) + { + marker_width_ = (size_ * tr_).width(); + if (spacing >= 0) + { + spacing_ = spacing; + } else if (spacing < 0) + { + spacing_ = find_optimal_spacing(-spacing); + } + rewind(); + } + /** Start again at first marker. * \note Returns the same list of markers only works when they were NOT added * to the detector. */ - void rewind(); + void rewind() + { + locator_.rewind(0); + //Get first point + done_ = agg::is_stop(locator_.vertex(&next_x, &next_y)) || spacing_ < marker_width_; + last_x = next_x; + last_y = next_y; // Force request of new segment + error_ = 0; + marker_nr_ = 0; + } + /** Get a point where the marker should be placed. * Each time this function is called a new point is returned. * \param x Return value for x position @@ -61,7 +102,115 @@ public: * \param add_to_detector Add selected position to detector * \return True if a place is found, false if none is found. */ - bool get_point(double & x, double & y, double & angle, bool add_to_detector = true); + bool get_point(double & x, double & y, double & angle, bool add_to_detector = true) + { + if (done_) return false; + unsigned cmd; + /* This functions starts at the position of the previous marker, + walks along the path, counting how far it has to go in spacing_left. + If one marker can't be placed at the position it should go to it is + moved a bit. The error is compensated for in the next call to this + function. + + error > 0: Marker too near to the end of the path. + error = 0: Perfect position. + error < 0: Marker too near to the beginning of the path. + */ + if (marker_nr_++ == 0) + { + //First marker + spacing_left_ = spacing_ / 2; + } else + { + spacing_left_ = spacing_; + } + spacing_left_ -= error_; + error_ = 0; + //Loop exits when a position is found or when no more segments are available + while (true) + { + //Do not place markers too close to the beginning of a segment + if (spacing_left_ < marker_width_/2) + { + set_spacing_left(marker_width_/2); //Only moves forward + } + //Error for this marker is too large. Skip to the next position. + if (abs(error_) > max_error_ * spacing_) + { + if (error_ > spacing_) + { + error_ = 0; //Avoid moving backwards + MAPNIK_LOG_WARN(markers_placement) << "Extremely large error in markers_placement. Please file a bug report."; + } + spacing_left_ += spacing_ - error_; + error_ = 0; + } + double dx = next_x - last_x; + double dy = next_y - last_y; + double segment_length = std::sqrt(dx * dx + dy * dy); + if (segment_length <= spacing_left_) + { + //Segment is to short to place marker. Find next segment + spacing_left_ -= segment_length; + last_x = next_x; + last_y = next_y; + while (agg::is_move_to(cmd = locator_.vertex(&next_x, &next_y))) + { + //Skip over "move" commands + last_x = next_x; + last_y = next_y; + } + if (agg::is_stop(cmd)) + { + done_ = true; + return false; + } + continue; //Try again + } + /* At this point we know the following things: + - segment_length > spacing_left + - error is small enough + - at least half a marker fits into this segment + */ + //Check if marker really fits in this segment + if (segment_length < marker_width_) + { + //Segment to short => Skip this segment + set_spacing_left(segment_length + marker_width_/2); //Only moves forward + continue; + } else if (segment_length - spacing_left_ < marker_width_/2) + { + //Segment is long enough, but we are to close to the end + //Note: This function moves backwards. This could lead to an infinite + // loop when another function adds a positive offset. Therefore we + // only move backwards when there is no offset + if (error_ == 0) + { + set_spacing_left(segment_length - marker_width_/2, true); + } else + { + //Skip this segment + set_spacing_left(segment_length + marker_width_/2); //Only moves forward + } + continue; //Force checking of max_error constraint + } + angle = atan2(dy, dx); + x = last_x + dx * (spacing_left_ / segment_length); + y = last_y + dy * (spacing_left_ / segment_length); + box2d box = perform_transform(angle, x, y); + if (!allow_overlap_ && !detector_.has_placement(box)) + { + //10.0 is the approxmiate number of positions tried and choosen arbitrarily + set_spacing_left(spacing_left_ + spacing_ * max_error_ / 10.0); //Only moves forward + continue; + } + if (add_to_detector) detector_.insert(box); + last_x = x; + last_y = y; + return true; + } + } + private: Locator &locator_; box2d size_; @@ -82,11 +231,69 @@ private: unsigned marker_nr_; /** Rotates the size_ box and translates the position. */ - box2d perform_transform(double angle, double dx, double dy); + box2d perform_transform(double angle, double dx, double dy) + { + double x1 = size_.minx(); + double x2 = size_.maxx(); + double y1 = size_.miny(); + double y2 = size_.maxy(); + agg::trans_affine tr = tr_ * agg::trans_affine_rotation(angle).translate(dx, dy); + double xA = x1, yA = y1, xB = x2, yB = y1, xC = x2, yC = y2, xD = x1, yD = y2; + tr.transform(&xA, &yA); + tr.transform(&xB, &yB); + tr.transform(&xC, &yC); + tr.transform(&xD, &yD); + box2d result(xA, yA, xC, yC); + result.expand_to_include(xB, yB); + result.expand_to_include(xD, yD); + return result; + } + /** Automatically chooses spacing. */ - double find_optimal_spacing(double s); + double find_optimal_spacing(double s) + { + rewind(); + //Calculate total path length + unsigned cmd = agg::path_cmd_move_to; + double length = 0; + while (!agg::is_stop(cmd)) + { + double dx = next_x - last_x; + double dy = next_y - last_y; + length += std::sqrt(dx * dx + dy * dy); + last_x = next_x; + last_y = next_y; + while (agg::is_move_to(cmd = locator_.vertex(&next_x, &next_y))) + { + //Skip over "move" commands + last_x = next_x; + last_y = next_y; + } + } + unsigned points = round(length / s); + if (points == 0) return 0.0; //Path to short + return length / points; + } + /** Set spacing_left_, adjusts error_ and performs sanity checks. */ - void set_spacing_left(double sl, bool allow_negative=false); + void set_spacing_left(double sl, bool allow_negative=false) + { + double delta_error = sl - spacing_left_; + if (!allow_negative && delta_error < 0) + { + MAPNIK_LOG_WARN(markers_placement) << "Unexpected negative error in markers_placement. Please file a bug report."; + return; + } + #ifdef MAPNIK_DEBUG + if (delta_error == 0.0) + { + MAPNIK_LOG_WARN(markers_placement) << "Not moving at all in set_spacing_left()! Please file a bug report."; + } + #endif + error_ += delta_error; + spacing_left_ = sl; + } + }; } diff --git a/include/mapnik/markers_symbolizer.hpp b/include/mapnik/markers_symbolizer.hpp index d11cf6f8f..0a441a98d 100644 --- a/include/mapnik/markers_symbolizer.hpp +++ b/include/mapnik/markers_symbolizer.hpp @@ -39,6 +39,7 @@ namespace mapnik { // TODO - consider merging with text_symbolizer label_placement_e enum marker_placement_enum { MARKER_POINT_PLACEMENT, + MARKER_INTERIOR_PLACEMENT, MARKER_LINE_PLACEMENT, marker_placement_enum_MAX }; diff --git a/include/mapnik/svg/svg_converter.hpp b/include/mapnik/svg/svg_converter.hpp index 7e61a09ff..4ba4c4bff 100644 --- a/include/mapnik/svg/svg_converter.hpp +++ b/include/mapnik/svg/svg_converter.hpp @@ -66,10 +66,10 @@ public: { throw std::runtime_error("end_path : The path was not begun"); } - path_attributes attr = cur_attr(); - unsigned idx = attributes_[attributes_.size() - 1].index; + path_attributes& attr = attributes_[attributes_.size() - 1]; + unsigned idx = attr.index; + attr = cur_attr(); attr.index = idx; - attributes_[attributes_.size() - 1] = attr; } void move_to(double x, double y, bool rel=false) // M, m diff --git a/src/agg/process_markers_symbolizer.cpp b/src/agg/process_markers_symbolizer.cpp index bec7f92e1..451323f5b 100644 --- a/src/agg/process_markers_symbolizer.cpp +++ b/src/agg/process_markers_symbolizer.cpp @@ -32,7 +32,9 @@ #include #include #include +#include #include +#include #include #include @@ -92,11 +94,18 @@ struct vector_markers_rasterizer_dispatch { marker_placement_e placement_method = sym_.get_marker_placement(); - if (placement_method == MARKER_POINT_PLACEMENT) + if (placement_method != MARKER_LINE_PLACEMENT) { double x,y; path.rewind(0); - label::interior_position(path, x, y); + if (placement_method == MARKER_INTERIOR_PLACEMENT) + { + label::interior_position(path, x, y); + } + else + { + label::centroid(path, x, y); + } agg::trans_affine matrix = marker_trans_; matrix.translate(x,y); box2d transformed_bbox = bbox_ * matrix; @@ -229,11 +238,18 @@ struct raster_markers_rasterizer_dispatch marker_placement_e placement_method = sym_.get_marker_placement(); box2d bbox_(0,0, src_.width(),src_.height()); - if (placement_method == MARKER_POINT_PLACEMENT) + if (placement_method != MARKER_LINE_PLACEMENT) { double x,y; path.rewind(0); - label::interior_position(path, x, y); + if (placement_method == MARKER_INTERIOR_PLACEMENT) + { + label::interior_position(path, x, y); + } + else + { + label::centroid(path, x, y); + } agg::trans_affine matrix = marker_trans_; matrix.translate(x,y); box2d transformed_bbox = bbox_ * matrix; @@ -293,7 +309,7 @@ void agg_renderer::process(markers_symbolizer const& sym, typedef agg::pixfmt_custom_blend_rgba pixfmt_comp_type; typedef agg::renderer_base renderer_base; typedef label_collision_detector4 detector_type; - typedef boost::mpl::vector conv_types; + typedef boost::mpl::vector conv_types; std::string filename = path_processor_type::evaluate(*sym.get_filename(), feature); @@ -313,34 +329,89 @@ void agg_renderer::process(markers_symbolizer const& sym, { using namespace mapnik::svg; typedef agg::renderer_scanline_aa_solid renderer_type; + typedef agg::pod_bvector svg_attribute_type; typedef svg_renderer, + svg_attribute_type, renderer_type, agg::pixfmt_rgba32 > svg_renderer_type; - typedef vector_markers_rasterizer_dispatch dispatch_type; - box2d const& bbox = (*mark)->bounding_box(); - setup_label_transform(tr, bbox, feature, sym); - coord2d center = bbox.center(); - agg::trans_affine_translation recenter(-center.x, -center.y); - agg::trans_affine marker_trans = recenter * tr; - boost::optional vector_marker = (*mark)->get_vector_data(); - vertex_stl_adapter stl_storage((*vector_marker)->source()); - svg_path_adapter svg_path(stl_storage); - agg::pod_bvector attributes; - bool result = push_explicit_style( (*vector_marker)->attributes(), attributes, sym); - svg_renderer_type svg_renderer(svg_path, result ? attributes : (*vector_marker)->attributes()); - dispatch_type rasterizer_dispatch(*current_buffer_,svg_renderer,*ras_ptr, - bbox, marker_trans, sym, *detector_, scale_factor_); - vertex_converter, dispatch_type, markers_symbolizer, - CoordTransform, proj_transform, agg::trans_affine, conv_types> - converter(query_extent_* 1.1,rasterizer_dispatch, sym,t_,prj_trans,tr,scale_factor_); - if (sym.clip()) converter.template set(); //optional clip (default: true) - converter.template set(); //always transform - if (sym.smooth() > 0.0) converter.template set(); // optional smooth converter + typedef vector_markers_rasterizer_dispatch dispatch_type; + boost::optional const& stock_vector_marker = (*mark)->get_vector_data(); + expression_ptr const& width_expr = sym.get_width(); + expression_ptr const& height_expr = sym.get_height(); - BOOST_FOREACH(geometry_type & geom, feature.paths()) + // special case for simple ellipse markers + // to allow for full control over rx/ry dimensions + if (filename == "shape://ellipse" + && (width_expr || height_expr)) { - converter.apply(geom); + svg_storage_type marker_ellipse; + vertex_stl_adapter stl_storage(marker_ellipse.source()); + svg_path_adapter svg_path(stl_storage); + build_ellipse(sym, feature, marker_ellipse, svg_path); + svg_attribute_type attributes; + bool result = push_explicit_style( (*stock_vector_marker)->attributes(), attributes, sym); + svg_renderer_type svg_renderer(svg_path, result ? attributes : (*stock_vector_marker)->attributes()); + evaluate_transform(tr, feature, sym.get_image_transform()); + box2d bbox = marker_ellipse.bounding_box(); + coord2d center = bbox.center(); + agg::trans_affine_translation recenter(-center.x, -center.y); + agg::trans_affine marker_trans = recenter * tr; + dispatch_type rasterizer_dispatch(*current_buffer_,svg_renderer,*ras_ptr, + bbox, marker_trans, sym, *detector_, scale_factor_); + vertex_converter, dispatch_type, markers_symbolizer, + CoordTransform, proj_transform, agg::trans_affine, conv_types> + converter(query_extent_* 1.1,rasterizer_dispatch, sym,t_,prj_trans,tr,scale_factor_); + if (sym.clip() && feature.paths().size() > 0) // optional clip (default: true) + { + eGeomType type = feature.paths()[0].type(); + if (type == Polygon) + converter.template set(); + else if (type == LineString) + converter.template set(); + // don't clip if type==Point + } + converter.template set(); //always transform + if (sym.smooth() > 0.0) converter.template set(); // optional smooth converter + BOOST_FOREACH(geometry_type & geom, feature.paths()) + { + converter.apply(geom); + } + } + else + { + box2d const& bbox = (*mark)->bounding_box(); + setup_label_transform(tr, bbox, feature, sym); + coord2d center = bbox.center(); + agg::trans_affine_translation recenter(-center.x, -center.y); + agg::trans_affine marker_trans = recenter * tr; + vertex_stl_adapter stl_storage((*stock_vector_marker)->source()); + svg_path_adapter svg_path(stl_storage); + svg_attribute_type attributes; + bool result = push_explicit_style( (*stock_vector_marker)->attributes(), attributes, sym); + svg_renderer_type svg_renderer(svg_path, result ? attributes : (*stock_vector_marker)->attributes()); + dispatch_type rasterizer_dispatch(*current_buffer_,svg_renderer,*ras_ptr, + bbox, marker_trans, sym, *detector_, scale_factor_); + vertex_converter, dispatch_type, markers_symbolizer, + CoordTransform, proj_transform, agg::trans_affine, conv_types> + converter(query_extent_* 1.1,rasterizer_dispatch, sym,t_,prj_trans,tr,scale_factor_); + if (sym.clip() && feature.paths().size() > 0) // optional clip (default: true) + { + eGeomType type = feature.paths()[0].type(); + if (type == Polygon) + converter.template set(); + else if (type == LineString) + converter.template set(); + // don't clip if type==Point + } + converter.template set(); //always transform + if (sym.smooth() > 0.0) converter.template set(); // optional smooth converter + BOOST_FOREACH(geometry_type & geom, feature.paths()) + { + converter.apply(geom); + } } } else // raster markers @@ -357,7 +428,16 @@ void agg_renderer::process(markers_symbolizer const& sym, vertex_converter, dispatch_type, markers_symbolizer, CoordTransform, proj_transform, agg::trans_affine, conv_types> converter(query_extent_* 1.1, rasterizer_dispatch, sym,t_,prj_trans,tr,scale_factor_); - if (sym.clip()) converter.template set(); //optional clip (default: true) + + if (sym.clip() && feature.paths().size() > 0) // optional clip (default: true) + { + eGeomType type = feature.paths()[0].type(); + if (type == Polygon) + converter.template set(); + else if (type == LineString) + converter.template set(); + // don't clip if type==Point + } converter.template set(); //always transform if (sym.smooth() > 0.0) converter.template set(); // optional smooth converter diff --git a/src/build.py b/src/build.py index a56aac0ea..5a74be3c3 100644 --- a/src/build.py +++ b/src/build.py @@ -172,7 +172,6 @@ source = Split( json/feature_grammar.cpp json/feature_collection_parser.cpp json/geojson_generator.cpp - markers_placement.cpp processed_text.cpp formatting/base.cpp formatting/expression.cpp diff --git a/src/load_map.cpp b/src/load_map.cpp index 315e85e9a..f834e8202 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -889,7 +889,7 @@ void map_parser::parse_point_symbolizer(rule & rule, xml_node const & sym) symbol.set_ignore_placement(* ignore_placement); } point_placement_e placement = - sym.get_attr("placement", CENTROID_POINT_PLACEMENT); + sym.get_attr("placement", symbol.get_point_placement()); symbol.set_point_placement(placement); if (file && !file->empty()) @@ -1064,7 +1064,7 @@ void map_parser::parse_markers_symbolizer(rule & rule, xml_node const& node) sym.set_stroke(strk); } - marker_placement_e placement = node.get_attr("placement", MARKER_POINT_PLACEMENT); + marker_placement_e placement = node.get_attr("placement",sym.get_marker_placement()); sym.set_marker_placement(placement); parse_symbolizer_base(sym, node); rule.append(sym); diff --git a/src/markers_placement.cpp b/src/markers_placement.cpp deleted file mode 100644 index 6ea4b0e14..000000000 --- a/src/markers_placement.cpp +++ /dev/null @@ -1,253 +0,0 @@ -// mapnik -#include -#include -#include -#include -#include //round -// agg -#include "agg_basics.h" -#include "agg_conv_clip_polyline.h" -#include "agg_trans_affine.h" -#include "agg_conv_transform.h" -#include "agg_conv_smooth_poly1.h" -// stl -#include - -namespace mapnik -{ -template -markers_placement::markers_placement( - Locator &locator, box2d const& size, agg::trans_affine const& tr, Detector &detector, double spacing, double max_error, bool allow_overlap) - : locator_(locator), size_(size), tr_(tr), detector_(detector), max_error_(max_error), allow_overlap_(allow_overlap) -{ - marker_width_ = (size_ * tr_).width(); - if (spacing >= 0) - { - spacing_ = spacing; - } else if (spacing < 0) - { - spacing_ = find_optimal_spacing(-spacing); - } - rewind(); -} - -template -double markers_placement::find_optimal_spacing(double s) -{ - rewind(); - //Calculate total path length - unsigned cmd = agg::path_cmd_move_to; - double length = 0; - while (!agg::is_stop(cmd)) - { - double dx = next_x - last_x; - double dy = next_y - last_y; - length += std::sqrt(dx * dx + dy * dy); - last_x = next_x; - last_y = next_y; - while (agg::is_move_to(cmd = locator_.vertex(&next_x, &next_y))) - { - //Skip over "move" commands - last_x = next_x; - last_y = next_y; - } - } - unsigned points = round(length / s); - if (points == 0) return 0.0; //Path to short - return length / points; -} - - -template -void markers_placement::rewind() -{ - locator_.rewind(0); - //Get first point - done_ = agg::is_stop(locator_.vertex(&next_x, &next_y)) || spacing_ < marker_width_; - last_x = next_x; - last_y = next_y; // Force request of new segment - error_ = 0; - marker_nr_ = 0; -} - -template -bool markers_placement::get_point( - double & x, double & y, double & angle, bool add_to_detector) -{ - if (done_) return false; - - unsigned cmd; - - /* This functions starts at the position of the previous marker, - walks along the path, counting how far it has to go in spacing_left. - If one marker can't be placed at the position it should go to it is - moved a bit. The error is compensated for in the next call to this - function. - - error > 0: Marker too near to the end of the path. - error = 0: Perfect position. - error < 0: Marker too near to the beginning of the path. - */ - - if (marker_nr_++ == 0) - { - //First marker - spacing_left_ = spacing_ / 2; - } else - { - spacing_left_ = spacing_; - } - - spacing_left_ -= error_; - error_ = 0; - - //Loop exits when a position is found or when no more segments are available - while (true) - { - //Do not place markers too close to the beginning of a segment - if (spacing_left_ < marker_width_/2) - { - set_spacing_left(marker_width_/2); //Only moves forward - } - //Error for this marker is too large. Skip to the next position. - if (abs(error_) > max_error_ * spacing_) - { - if (error_ > spacing_) - { - error_ = 0; //Avoid moving backwards - - MAPNIK_LOG_WARN(markers_placement) << "Extremely large error in markers_placement. Please file a bug report."; - } - spacing_left_ += spacing_ - error_; - error_ = 0; - } - double dx = next_x - last_x; - double dy = next_y - last_y; - double segment_length = std::sqrt(dx * dx + dy * dy); - if (segment_length <= spacing_left_) - { - //Segment is to short to place marker. Find next segment - spacing_left_ -= segment_length; - last_x = next_x; - last_y = next_y; - while (agg::is_move_to(cmd = locator_.vertex(&next_x, &next_y))) - { - //Skip over "move" commands - last_x = next_x; - last_y = next_y; - } - if (agg::is_stop(cmd)) - { - done_ = true; - return false; - } - continue; //Try again - } - - /* At this point we know the following things: - - segment_length > spacing_left - - error is small enough - - at least half a marker fits into this segment - */ - - //Check if marker really fits in this segment - if (segment_length < marker_width_) - { - //Segment to short => Skip this segment - set_spacing_left(segment_length + marker_width_/2); //Only moves forward - continue; - } else if (segment_length - spacing_left_ < marker_width_/2) - { - //Segment is long enough, but we are to close to the end - - //Note: This function moves backwards. This could lead to an infinite - // loop when another function adds a positive offset. Therefore we - // only move backwards when there is no offset - if (error_ == 0) - { - set_spacing_left(segment_length - marker_width_/2, true); - } else - { - //Skip this segment - set_spacing_left(segment_length + marker_width_/2); //Only moves forward - } - continue; //Force checking of max_error constraint - } - angle = atan2(dy, dx); - x = last_x + dx * (spacing_left_ / segment_length); - y = last_y + dy * (spacing_left_ / segment_length); - - box2d box = perform_transform(angle, x, y); - - if (!allow_overlap_ && !detector_.has_placement(box)) - { - //10.0 is the approxmiate number of positions tried and choosen arbitrarily - set_spacing_left(spacing_left_ + spacing_ * max_error_ / 10.0); //Only moves forward - continue; - } - if (add_to_detector) detector_.insert(box); - last_x = x; - last_y = y; - return true; - } -} - - -template -box2d markers_placement::perform_transform(double angle, double dx, double dy) -{ - double x1 = size_.minx(); - double x2 = size_.maxx(); - double y1 = size_.miny(); - double y2 = size_.maxy(); - - agg::trans_affine tr = tr_ * agg::trans_affine_rotation(angle).translate(dx, dy); - - double xA = x1, yA = y1, xB = x2, yB = y1, xC = x2, yC = y2, xD = x1, yD = y2; - tr.transform(&xA, &yA); - tr.transform(&xB, &yB); - tr.transform(&xC, &yC); - tr.transform(&xD, &yD); - - box2d result(xA, yA, xC, yC); - result.expand_to_include(xB, yB); - result.expand_to_include(xD, yD); - return result; -} - -template -void markers_placement::set_spacing_left(double sl, bool allow_negative) -{ - double delta_error = sl - spacing_left_; - if (!allow_negative && delta_error < 0) - { - MAPNIK_LOG_WARN(markers_placement) << "Unexpected negative error in markers_placement. Please file a bug report."; - - return; - } -#ifdef MAPNIK_DEBUG - if (delta_error == 0.0) - { - MAPNIK_LOG_WARN(markers_placement) << "Not moving at all in set_spacing_left()! Please file a bug report."; - } -#endif - error_ += delta_error; - spacing_left_ = sl; -} - -typedef agg::conv_clip_polyline clipped_geometry_type; -typedef coord_transform path_type; -typedef coord_transform clipped_path_type; -typedef agg::conv_transform transformed_path_type; - -template class markers_placement; -template class markers_placement; -template class markers_placement; -template class markers_placement; -template class markers_placement; -template class markers_placement, label_collision_detector4>; -template class markers_placement, label_collision_detector4>; -template class markers_placement, label_collision_detector4>; -template class markers_placement, label_collision_detector4>; -template class markers_placement, label_collision_detector4>; -} //ns mapnik diff --git a/src/markers_symbolizer.cpp b/src/markers_symbolizer.cpp index 5ea1c4624..9de55e492 100644 --- a/src/markers_symbolizer.cpp +++ b/src/markers_symbolizer.cpp @@ -30,6 +30,7 @@ namespace mapnik { static const char * marker_placement_strings[] = { "point", + "interior", "line", "" }; @@ -45,10 +46,7 @@ markers_symbolizer::markers_symbolizer() allow_overlap_(false), spacing_(100.0), max_error_(0.2), - marker_p_(MARKER_POINT_PLACEMENT) { - // override the default for clipping in symbolizer base - this->set_clip(false); - } + marker_p_(MARKER_POINT_PLACEMENT) { } markers_symbolizer::markers_symbolizer(path_expression_ptr const& filename) : symbolizer_with_image(filename), @@ -59,10 +57,7 @@ markers_symbolizer::markers_symbolizer(path_expression_ptr const& filename) allow_overlap_(false), spacing_(100.0), max_error_(0.2), - marker_p_(MARKER_POINT_PLACEMENT) { - // override the default for clipping in symbolizer base - this->set_clip(false); - } + marker_p_(MARKER_POINT_PLACEMENT) { } markers_symbolizer::markers_symbolizer(markers_symbolizer const& rhs) : symbolizer_with_image(rhs), diff --git a/tests/data/good_maps/marker_ellipse_transform.xml b/tests/data/good_maps/marker_ellipse_transform.xml new file mode 100644 index 000000000..e96595619 --- /dev/null +++ b/tests/data/good_maps/marker_ellipse_transform.xml @@ -0,0 +1,66 @@ + + + + + + ellipse + + csv + +x,y +2.5,2.5 + + + + + + + + + + frame + + csv + +x,y +0,0 +5,0 +0,5 +5,5 + + + + + \ No newline at end of file diff --git a/tests/data/good_maps/marker_ellipse_transform2.xml b/tests/data/good_maps/marker_ellipse_transform2.xml new file mode 100644 index 000000000..048353adf --- /dev/null +++ b/tests/data/good_maps/marker_ellipse_transform2.xml @@ -0,0 +1,66 @@ + + + + + + ellipse + + csv + +x,y +2.5,2.5 + + + + + + + + + + frame + + csv + +x,y +0,0 +5,0 +0,5 +5,5 + + + + + \ No newline at end of file diff --git a/tests/data/good_maps/markers_symbolizer_lines.xml b/tests/data/good_maps/markers_symbolizer_lines.xml index 74adff287..e1f879ad9 100644 --- a/tests/data/good_maps/markers_symbolizer_lines.xml +++ b/tests/data/good_maps/markers_symbolizer_lines.xml @@ -3,7 +3,14 @@ diff --git a/tests/data/good_maps/markers_symbolizer_lines_file.xml b/tests/data/good_maps/markers_symbolizer_lines_file.xml index 39d2f1dbd..e08621485 100644 --- a/tests/data/good_maps/markers_symbolizer_lines_file.xml +++ b/tests/data/good_maps/markers_symbolizer_lines_file.xml @@ -2,8 +2,15 @@ diff --git a/tests/python_tests/images/support/mapnik-marker-ellipse-render1.png b/tests/python_tests/images/support/mapnik-marker-ellipse-render1.png new file mode 100644 index 000000000..5a74baad0 Binary files /dev/null and b/tests/python_tests/images/support/mapnik-marker-ellipse-render1.png differ diff --git a/tests/python_tests/markers_complex_rendering_test.py b/tests/python_tests/markers_complex_rendering_test.py new file mode 100644 index 000000000..a3173abbd --- /dev/null +++ b/tests/python_tests/markers_complex_rendering_test.py @@ -0,0 +1,39 @@ +#coding=utf8 +import os +import mapnik +from utilities import execution_path +from nose.tools import * + +def setup(): + # All of the paths used are relative, if we run the tests + # from another directory we need to chdir() + os.chdir(execution_path('.')) + +def test_marker_ellipse_render1(): + m = mapnik.Map(256,256) + mapnik.load_map(m,'../data/good_maps/marker_ellipse_transform.xml') + m.zoom_all() + im = mapnik.Image(m.width,m.height) + mapnik.render(m,im) + actual = '/tmp/mapnik-marker-ellipse-render1.png' + expected = 'images/support/mapnik-marker-ellipse-render1.png' + im.save(actual) + expected_im = mapnik.Image.open(expected) + eq_(im.tostring(),expected_im.tostring(), 'failed comparing actual (%s) and expected (%s)' % (actual,'tests/python_tests/'+ expected)) + +#def test_marker_ellipse_render2(): +# # currently crashes https://github.com/mapnik/mapnik/issues/1365 +# m = mapnik.Map(256,256) +# mapnik.load_map(m,'../data/good_maps/marker_ellipse_transform2.xml') +# m.zoom_all() +# im = mapnik.Image(m.width,m.height) +# mapnik.render(m,im) +# actual = '/tmp/mapnik-marker-ellipse-render2.png' +# expected = 'images/support/mapnik-marker-ellipse-render2.png' +# im.save(actual) +# expected_im = mapnik.Image.open(expected) +# eq_(im.tostring(),expected_im.tostring(), 'failed comparing actual (%s) and expected (%s)' % (actual,'tests/python_tests/'+ expected)) + +if __name__ == "__main__": + setup() + [eval(run)() for run in dir() if 'test_' in run] diff --git a/tests/python_tests/object_test.py b/tests/python_tests/object_test.py index 3213ca86e..0197d2e7c 100644 --- a/tests/python_tests/object_test.py +++ b/tests/python_tests/object_test.py @@ -21,6 +21,15 @@ def test_line_symbolizer_init(): s = mapnik.LineSymbolizer() eq_(s.rasterizer, mapnik.line_rasterizer.FULL) +def test_line_symbolizer_stroke_reference(): + l = mapnik.LineSymbolizer(mapnik.Color('green'),0.1) + l.stroke.add_dash(.1,.1) + l.stroke.add_dash(.1,.1) + eq_(l.stroke.get_dashes(), [(.1,.1),(.1,.1)]) + eq_(l.stroke.color,mapnik.Color('green')) + eq_(l.stroke.opacity,1.0) + assert_almost_equal(l.stroke.width,0.1) + # ShieldSymbolizer initialization def test_shieldsymbolizer_init(): s = mapnik.ShieldSymbolizer(mapnik.Expression('[Field Name]'), 'DejaVu Sans Bold', 6, mapnik.Color('#000000'), mapnik.PathExpression('../data/images/dummy.png'))