Compare commits
3 commits
master
...
style-with
Author | SHA1 | Date | |
---|---|---|---|
|
e2171b2b9c | ||
|
e9d6872764 | ||
|
d8fa8adfae |
23 changed files with 237 additions and 480 deletions
|
@ -2011,9 +2011,6 @@ if not HELP_REQUESTED:
|
|||
# build C++ tests
|
||||
SConscript('tests/cpp_tests/build.py')
|
||||
|
||||
if env['CPP_TESTS'] and env['SVG_RENDERER']:
|
||||
SConscript('tests/cpp_tests/svg_renderer_tests/build.py')
|
||||
|
||||
if env['BENCHMARK']:
|
||||
SConscript('benchmark/build.py')
|
||||
|
||||
|
|
|
@ -53,6 +53,11 @@ void insert_style(mapnik::Map & m, std::string const& name, mapnik::feature_type
|
|||
m.insert_style(name,style);
|
||||
}
|
||||
|
||||
void insert_style2(mapnik::Map & m, mapnik::feature_type_style const& style)
|
||||
{
|
||||
m.insert_style(style.name(),style);
|
||||
}
|
||||
|
||||
void insert_fontset(mapnik::Map & m, std::string const& name, mapnik::font_set const& fontset)
|
||||
{
|
||||
m.insert_fontset(name,fontset);
|
||||
|
@ -188,6 +193,16 @@ void export_map()
|
|||
"False # you can only append styles with unique names\n"
|
||||
)
|
||||
|
||||
.def("append_style",insert_style2,
|
||||
(arg("style_object")),
|
||||
"Insert a Mapnik Style onto the map by appending it.\n"
|
||||
"\n"
|
||||
"Usage:\n"
|
||||
">>> sty\n"
|
||||
"<mapnik._mapnik.Style object at 0x6a330>\n"
|
||||
">>> m.append_style(sty)\n"
|
||||
)
|
||||
|
||||
.def("append_fontset",insert_fontset,
|
||||
(arg("fontset")),
|
||||
"Add a FontSet to the map."
|
||||
|
|
|
@ -75,8 +75,19 @@ void export_style()
|
|||
class_<rules>("Rules",init<>("default ctor"))
|
||||
.def(vector_indexing_suite<rules>())
|
||||
;
|
||||
class_<feature_type_style>("Style",init<>("default style constructor"))
|
||||
class_<feature_type_style>("Style",init<std::string const&>(
|
||||
( arg("name")="" ),
|
||||
"Create a Style with a name\n"
|
||||
"\n"
|
||||
"Usage:\n"
|
||||
">>> from mapnik import Style\n"
|
||||
">>> s = Style(\"Roads\")\n"
|
||||
))
|
||||
|
||||
.add_property("name",make_function
|
||||
(&feature_type_style::name,
|
||||
return_value_policy<copy_const_reference>()),
|
||||
&feature_type_style::set_name)
|
||||
.add_property("rules",make_function
|
||||
(&feature_type_style::get_rules,
|
||||
return_value_policy<reference_existing_object>()),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
*
|
||||
* This file is part of Mapnik (c++ mapping toolkit)
|
||||
*
|
||||
* Copyright (C) 2013 Artem Pavlenko
|
||||
* Copyright (C) 2014 Artem Pavlenko
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
|
@ -29,21 +29,28 @@
|
|||
#include <mapnik/text/text_properties.hpp>
|
||||
#include <mapnik/datasource_cache.hpp>
|
||||
#include <mapnik/font_engine_freetype.hpp>
|
||||
#include <mapnik/agg_renderer.hpp>
|
||||
#include <mapnik/expression.hpp>
|
||||
#include <mapnik/color_factory.hpp>
|
||||
#include <mapnik/image_util.hpp>
|
||||
#include <mapnik/unicode.hpp>
|
||||
#include <mapnik/save_map.hpp>
|
||||
|
||||
#include <mapnik/agg_renderer.hpp>
|
||||
|
||||
#if defined(HAVE_CAIRO)
|
||||
#include <mapnik/cairo/cairo_renderer.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(SVG_RENDERER)
|
||||
#include <mapnik/svg/output/svg_renderer.hpp>
|
||||
#endif
|
||||
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
int main ( int argc , char** argv)
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
using namespace mapnik;
|
||||
const std::string srs_lcc="+proj=lcc +ellps=GRS80 +lat_0=49 +lon_0=-95 +lat+1=49 +lat_2=77 \
|
||||
|
@ -62,166 +69,183 @@ int main ( int argc , char** argv)
|
|||
// create styles
|
||||
|
||||
// Provinces (polygon)
|
||||
feature_type_style provpoly_style;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[NAME_EN] = 'Ontario'"));
|
||||
feature_type_style style("provinces");
|
||||
{
|
||||
polygon_symbolizer poly_sym;
|
||||
put(poly_sym, keys::fill, color(250, 190, 183));
|
||||
r.append(std::move(poly_sym));
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[NAME_EN] = 'Ontario'"));
|
||||
{
|
||||
polygon_symbolizer poly_sym;
|
||||
put(poly_sym, keys::fill, color(250, 190, 183));
|
||||
r.append(std::move(poly_sym));
|
||||
}
|
||||
style.add_rule(std::move(r));
|
||||
}
|
||||
provpoly_style.add_rule(std::move(r));
|
||||
}
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[NOM_FR] = 'Québec'"));
|
||||
{
|
||||
polygon_symbolizer poly_sym;
|
||||
put(poly_sym, keys::fill, color(217, 235, 203));
|
||||
r.append(std::move(poly_sym));
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[NOM_FR] = 'Québec'"));
|
||||
{
|
||||
polygon_symbolizer poly_sym;
|
||||
put(poly_sym, keys::fill, color(217, 235, 203));
|
||||
r.append(std::move(poly_sym));
|
||||
}
|
||||
style.add_rule(std::move(r));
|
||||
}
|
||||
provpoly_style.add_rule(std::move(r));
|
||||
m.insert_style(style.name(), style);
|
||||
}
|
||||
m.insert_style("provinces", std::move(provpoly_style));
|
||||
|
||||
// Provinces (polyline)
|
||||
feature_type_style provlines_style;
|
||||
{
|
||||
rule r;
|
||||
feature_type_style style("provlines");
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(0,0,0));
|
||||
put(line_sym,keys::stroke_width,1.0);
|
||||
dash_array dash;
|
||||
dash.emplace_back(8,4);
|
||||
dash.emplace_back(2,2);
|
||||
dash.emplace_back(2,2);
|
||||
put(line_sym,keys::stroke_dasharray,dash);
|
||||
r.append(std::move(line_sym));
|
||||
rule r;
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(0,0,0));
|
||||
put(line_sym,keys::stroke_width,1.0);
|
||||
dash_array dash;
|
||||
dash.emplace_back(8,4);
|
||||
dash.emplace_back(2,2);
|
||||
dash.emplace_back(2,2);
|
||||
put(line_sym,keys::stroke_dasharray,dash);
|
||||
r.append(std::move(line_sym));
|
||||
}
|
||||
style.add_rule(std::move(r));
|
||||
}
|
||||
provlines_style.add_rule(std::move(r));
|
||||
m.insert_style(style.name(), style);
|
||||
}
|
||||
m.insert_style("provlines", std::move(provlines_style));
|
||||
|
||||
// Drainage
|
||||
feature_type_style qcdrain_style;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[HYC] = 8"));
|
||||
feature_type_style style("drainage");
|
||||
{
|
||||
polygon_symbolizer poly_sym;
|
||||
put(poly_sym, keys::fill, color(153, 204, 255));
|
||||
r.append(std::move(poly_sym));
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[HYC] = 8"));
|
||||
{
|
||||
polygon_symbolizer poly_sym;
|
||||
put(poly_sym, keys::fill, color(153, 204, 255));
|
||||
r.append(std::move(poly_sym));
|
||||
}
|
||||
style.add_rule(std::move(r));
|
||||
}
|
||||
qcdrain_style.add_rule(std::move(r));
|
||||
m.insert_style(style.name(), style);
|
||||
}
|
||||
m.insert_style("drainage", std::move(qcdrain_style));
|
||||
|
||||
// Roads 3 and 4 (The "grey" roads)
|
||||
feature_type_style roads34_style;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 3 or [CLASS] = 4"));
|
||||
feature_type_style style("smallroads");
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(171,158,137));
|
||||
put(line_sym,keys::stroke_width,2.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 3 or [CLASS] = 4"));
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(171,158,137));
|
||||
put(line_sym,keys::stroke_width,2.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
}
|
||||
style.add_rule(std::move(r));
|
||||
}
|
||||
roads34_style.add_rule(std::move(r));
|
||||
m.insert_style(style.name(), style);
|
||||
}
|
||||
m.insert_style("smallroads", std::move(roads34_style));
|
||||
|
||||
// Roads 2 (The thin yellow ones)
|
||||
feature_type_style roads2_style_1;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 2"));
|
||||
feature_type_style style("road-border");
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(171,158,137));
|
||||
put(line_sym,keys::stroke_width,4.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 2"));
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(171,158,137));
|
||||
put(line_sym,keys::stroke_width,4.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
}
|
||||
style.add_rule(std::move(r));
|
||||
}
|
||||
roads2_style_1.add_rule(std::move(r));
|
||||
m.insert_style(style.name(), style);
|
||||
}
|
||||
m.insert_style("road-border", std::move(roads2_style_1));
|
||||
|
||||
feature_type_style roads2_style_2;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 2"));
|
||||
feature_type_style style("road-fill");
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(255,250,115));
|
||||
put(line_sym,keys::stroke_width,2.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 2"));
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(255,250,115));
|
||||
put(line_sym,keys::stroke_width,2.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
}
|
||||
style.add_rule(std::move(r));
|
||||
}
|
||||
roads2_style_2.add_rule(std::move(r));
|
||||
m.insert_style(style.name(), style);
|
||||
}
|
||||
m.insert_style("road-fill", std::move(roads2_style_2));
|
||||
|
||||
// Roads 1 (The big orange ones, the highways)
|
||||
feature_type_style roads1_style_1;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 1"));
|
||||
feature_type_style style("highway-border");
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(188,149,28));
|
||||
put(line_sym,keys::stroke_width,7.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 1"));
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(188,149,28));
|
||||
put(line_sym,keys::stroke_width,7.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
}
|
||||
style.add_rule(std::move(r));
|
||||
}
|
||||
roads1_style_1.add_rule(std::move(r));
|
||||
m.insert_style(style.name(), style);
|
||||
}
|
||||
m.insert_style("highway-border", std::move(roads1_style_1));
|
||||
|
||||
feature_type_style roads1_style_2;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 1"));
|
||||
feature_type_style style("highway-fill");
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(242,191,36));
|
||||
put(line_sym,keys::stroke_width,5.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 1"));
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(242,191,36));
|
||||
put(line_sym,keys::stroke_width,5.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
}
|
||||
style.add_rule(std::move(r));
|
||||
}
|
||||
roads1_style_2.add_rule(std::move(r));
|
||||
m.insert_style(style.name(), style);
|
||||
}
|
||||
m.insert_style("highway-fill", std::move(roads1_style_2));
|
||||
|
||||
// Populated Places
|
||||
feature_type_style popplaces_style;
|
||||
{
|
||||
rule r;
|
||||
feature_type_style style("popplaces");
|
||||
{
|
||||
text_symbolizer text_sym;
|
||||
text_placements_ptr placement_finder = std::make_shared<text_placements_dummy>();
|
||||
placement_finder->defaults.format_defaults.face_name = "DejaVu Sans Book";
|
||||
placement_finder->defaults.format_defaults.text_size = 10.0;
|
||||
placement_finder->defaults.format_defaults.fill = color(0,0,0);
|
||||
placement_finder->defaults.format_defaults.halo_fill = color(255,255,200);
|
||||
placement_finder->defaults.format_defaults.halo_radius = 1.0;
|
||||
placement_finder->defaults.set_old_style_expression(parse_expression("[GEONAME]"));
|
||||
put<text_placements_ptr>(text_sym, keys::text_placements_, placement_finder);
|
||||
r.append(std::move(text_sym));
|
||||
rule r;
|
||||
{
|
||||
text_symbolizer text_sym;
|
||||
text_placements_ptr placement_finder = std::make_shared<text_placements_dummy>();
|
||||
placement_finder->defaults.format_defaults.face_name = "DejaVu Sans Book";
|
||||
placement_finder->defaults.format_defaults.text_size = 10.0;
|
||||
placement_finder->defaults.format_defaults.fill = color(0,0,0);
|
||||
placement_finder->defaults.format_defaults.halo_fill = color(255,255,200);
|
||||
placement_finder->defaults.format_defaults.halo_radius = 1.0;
|
||||
placement_finder->defaults.set_old_style_expression(parse_expression("[GEONAME]"));
|
||||
put<text_placements_ptr>(text_sym, keys::text_placements_, placement_finder);
|
||||
r.append(std::move(text_sym));
|
||||
}
|
||||
style.add_rule(std::move(r));
|
||||
}
|
||||
popplaces_style.add_rule(std::move(r));
|
||||
m.insert_style(style.name(), style);
|
||||
}
|
||||
|
||||
m.insert_style("popplaces", std::move(popplaces_style));
|
||||
|
||||
// layers
|
||||
// Provincial polygons
|
||||
{
|
||||
|
@ -229,7 +253,6 @@ int main ( int argc , char** argv)
|
|||
p["type"]="shape";
|
||||
p["file"]="demo/data/boundaries";
|
||||
p["encoding"]="latin1";
|
||||
|
||||
layer lyr("Provinces");
|
||||
lyr.set_datasource(datasource_cache::instance().create(p));
|
||||
lyr.add_style("provinces");
|
||||
|
@ -238,17 +261,6 @@ int main ( int argc , char** argv)
|
|||
}
|
||||
|
||||
// Drainage
|
||||
{
|
||||
parameters p;
|
||||
p["type"]="shape";
|
||||
p["file"]="demo/data/qcdrainage";
|
||||
layer lyr("Quebec Hydrography");
|
||||
lyr.set_datasource(datasource_cache::instance().create(p));
|
||||
lyr.set_srs(srs_lcc);
|
||||
lyr.add_style("drainage");
|
||||
m.add_layer(lyr);
|
||||
}
|
||||
|
||||
{
|
||||
parameters p;
|
||||
p["type"]="shape";
|
||||
|
@ -285,7 +297,6 @@ int main ( int argc , char** argv)
|
|||
lyr.add_style("road-fill");
|
||||
lyr.add_style("highway-border");
|
||||
lyr.add_style("highway-fill");
|
||||
|
||||
m.add_layer(lyr);
|
||||
}
|
||||
// popplaces
|
||||
|
@ -362,6 +373,24 @@ int main ( int argc , char** argv)
|
|||
"- cairo-demo.svg\n"
|
||||
"Have a look!\n";
|
||||
#endif
|
||||
|
||||
#if defined(SVG_RENDERER)
|
||||
std::ofstream output_stream("demo-inkscape.svg",std::ios::out|std::ios::trunc|std::ios::binary);
|
||||
if (output_stream)
|
||||
{
|
||||
using svg_iter_type = std::ostream_iterator<char>;
|
||||
svg_iter_type output_stream_iterator(output_stream);
|
||||
mapnik::svg_renderer<svg_iter_type> ren(m,output_stream_iterator);
|
||||
ren.apply();
|
||||
output_stream.close();
|
||||
std::cout << "One SVG has been rendered by native SVG renderer targeting inkscape/illustrator:\n"
|
||||
"- demo-inkscape.png\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::clog << "could not open for writing 'demo-inkscape.svg'\n";
|
||||
}
|
||||
#endif
|
||||
// save map definition (data + style)
|
||||
save_map(m, "map.xml");
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
PROJCS["Atlas of Canada Lambert Conformal Conic",GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Lambert_Conformal_Conic"],PARAMETER["False_Easting",0.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",-95.0],PARAMETER["Standard_Parallel_1",49.0],PARAMETER["Standard_Parallel_2",77.0],PARAMETER["Latitude_Of_Origin",49.0],UNIT["Meter",1.0]]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -54,6 +54,7 @@ using rules = std::vector<rule>;
|
|||
class MAPNIK_DECL feature_type_style
|
||||
{
|
||||
private:
|
||||
std::string name_;
|
||||
rules rules_;
|
||||
filter_mode_e filter_mode_;
|
||||
// image_filters
|
||||
|
@ -66,10 +67,12 @@ private:
|
|||
friend void swap(feature_type_style& lhs, feature_type_style & rhs);
|
||||
public:
|
||||
// ctor
|
||||
feature_type_style();
|
||||
feature_type_style(std::string const& name);
|
||||
feature_type_style(feature_type_style const& rhs);
|
||||
feature_type_style(feature_type_style &&) = default;
|
||||
feature_type_style& operator=(feature_type_style rhs);
|
||||
std::string const& name() const;
|
||||
void set_name(std::string const& name);
|
||||
|
||||
// comparison
|
||||
bool operator==(feature_type_style const& rhs) const;
|
||||
|
|
|
@ -77,8 +77,8 @@ public:
|
|||
void end_map_processing(Map const& map);
|
||||
void start_layer_processing(layer const& lay, box2d<double> const& query_extent);
|
||||
void end_layer_processing(layer const& lay);
|
||||
void start_style_processing(feature_type_style const& /*st*/) {}
|
||||
void end_style_processing(feature_type_style const& /*st*/) {}
|
||||
void start_style_processing(feature_type_style const& st);
|
||||
void end_style_processing(feature_type_style const& st);
|
||||
|
||||
/*!
|
||||
* @brief Overloads that process each kind of symbolizer individually.
|
||||
|
|
|
@ -39,8 +39,9 @@ static const char * filter_mode_strings[] = {
|
|||
IMPLEMENT_ENUM( filter_mode_e, filter_mode_strings )
|
||||
|
||||
|
||||
feature_type_style::feature_type_style()
|
||||
: rules_(),
|
||||
feature_type_style::feature_type_style(std::string const& name)
|
||||
: name_(name),
|
||||
rules_(),
|
||||
filter_mode_(FILTER_ALL),
|
||||
filters_(),
|
||||
direct_filters_(),
|
||||
|
@ -50,7 +51,8 @@ feature_type_style::feature_type_style()
|
|||
{}
|
||||
|
||||
feature_type_style::feature_type_style(feature_type_style const& rhs)
|
||||
: rules_(rhs.rules_),
|
||||
: name_(rhs.name_),
|
||||
rules_(rhs.rules_),
|
||||
filter_mode_(rhs.filter_mode_),
|
||||
filters_(rhs.filters_),
|
||||
direct_filters_(rhs.direct_filters_),
|
||||
|
@ -69,6 +71,7 @@ feature_type_style& feature_type_style::operator=(feature_type_style rhs)
|
|||
void swap( feature_type_style & lhs, feature_type_style & rhs)
|
||||
{
|
||||
using std::swap;
|
||||
std::swap(lhs.name_, rhs.name_);
|
||||
std::swap(lhs.rules_, rhs.rules_);
|
||||
std::swap(lhs.filter_mode_, rhs.filter_mode_);
|
||||
std::swap(lhs.filters_, rhs.filters_);
|
||||
|
@ -80,7 +83,8 @@ void swap( feature_type_style & lhs, feature_type_style & rhs)
|
|||
|
||||
bool feature_type_style::operator==(feature_type_style const& rhs) const
|
||||
{
|
||||
return (rules_ == rhs.rules_) &&
|
||||
return (name_ == rhs.name_) &&
|
||||
(rules_ == rhs.rules_) &&
|
||||
(filter_mode_ == rhs.filter_mode_) &&
|
||||
(filters_ == rhs.filters_) &&
|
||||
(direct_filters_ == rhs.direct_filters_) &&
|
||||
|
@ -89,6 +93,16 @@ bool feature_type_style::operator==(feature_type_style const& rhs) const
|
|||
(image_filters_inflate_ == rhs.image_filters_inflate_);
|
||||
}
|
||||
|
||||
std::string const& feature_type_style::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
void feature_type_style::set_name(std::string const& name)
|
||||
{
|
||||
name_ = name;
|
||||
}
|
||||
|
||||
void feature_type_style::add_rule(rule && rule)
|
||||
{
|
||||
rules_.push_back(std::move(rule));
|
||||
|
|
|
@ -406,7 +406,7 @@ void map_parser::parse_style(Map & map, xml_node const& node)
|
|||
try
|
||||
{
|
||||
name = node.get_attr<std::string>("name");
|
||||
feature_type_style style;
|
||||
feature_type_style style(name);
|
||||
|
||||
filter_mode_e filter_mode = node.get_attr<filter_mode_e>("filter-mode", FILTER_ALL);
|
||||
style.set_filter_mode(filter_mode);
|
||||
|
|
|
@ -395,7 +395,7 @@ void serialize_style( ptree & map_node, std::string const& name, feature_type_st
|
|||
|
||||
set_attr(style_node, "name", name);
|
||||
|
||||
feature_type_style dfl;
|
||||
feature_type_style dfl(name);
|
||||
filter_mode_e filter_mode = style.get_filter_mode();
|
||||
if (filter_mode != dfl.get_filter_mode() || explicit_defaults)
|
||||
{
|
||||
|
|
|
@ -103,6 +103,18 @@ void svg_renderer<T>::end_layer_processing(layer const& lay)
|
|||
MAPNIK_LOG_DEBUG(svg_renderer) << "svg_renderer: End layer processing=" << lay.name();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void svg_renderer<T>::start_style_processing(feature_type_style const& st)
|
||||
{
|
||||
generator_.generate_opening_group(st.name());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void svg_renderer<T>::end_style_processing(feature_type_style const&)
|
||||
{
|
||||
generator_.generate_closing_group();
|
||||
}
|
||||
|
||||
template class svg_renderer<std::ostream_iterator<char> >;
|
||||
}
|
||||
|
||||
|
|
|
@ -57,9 +57,9 @@ int main(int argc, char** argv)
|
|||
mapnik::rule r;
|
||||
r.set_filter(mapnik::parse_expression("[foo]='bar'"));
|
||||
r.append(std::move(mapnik::markers_symbolizer()));
|
||||
mapnik::feature_type_style style;
|
||||
mapnik::feature_type_style style("style");
|
||||
style.add_rule(std::move(r));
|
||||
map.insert_style("style",style);
|
||||
map.insert_style(style.name(),std::move(style));
|
||||
|
||||
std::string csv_plugin("./plugins/input/csv.input");
|
||||
if (mapnik::util::exists(csv_plugin)) {
|
||||
|
|
|
@ -66,7 +66,7 @@ int main(int argc, char** argv)
|
|||
lyr.set_datasource(ds);
|
||||
lyr.add_style("style");
|
||||
m.add_layer(lyr);
|
||||
mapnik::feature_type_style the_style;
|
||||
mapnik::feature_type_style style("style");
|
||||
mapnik::rule r;
|
||||
mapnik::text_symbolizer text_sym;
|
||||
mapnik::text_placements_ptr placement_finder = std::make_shared<mapnik::text_placements_dummy>();
|
||||
|
@ -77,8 +77,8 @@ int main(int argc, char** argv)
|
|||
placement_finder->defaults.set_old_style_expression(mapnik::parse_expression("[name]"));
|
||||
mapnik::put<mapnik::text_placements_ptr>(text_sym, mapnik::keys::text_placements_, placement_finder);
|
||||
r.append(std::move(text_sym));
|
||||
the_style.add_rule(std::move(r));
|
||||
m.insert_style("style",the_style );
|
||||
style.add_rule(std::move(r));
|
||||
m.insert_style(style.name(),style);
|
||||
m.zoom_to_box(mapnik::box2d<double>(-256,-256,
|
||||
256,256));
|
||||
mapnik::image_32 buf(m.width(),m.height());
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import os
|
||||
import glob
|
||||
from copy import copy
|
||||
|
||||
Import ('env')
|
||||
|
||||
libraries = [env['MAPNIK_NAME']]
|
||||
libraries.extend(copy(env['LIBMAPNIK_LIBS']))
|
||||
|
||||
for cpp_test in glob.glob('*_test.cpp'):
|
||||
test_program = env.Program(cpp_test.replace('.cpp',''), [cpp_test], LIBS=libraries)
|
||||
Depends(test_program, env.subst('../../../src/%s' % env['MAPNIK_LIB_NAME']))
|
|
@ -1,312 +0,0 @@
|
|||
#define BOOST_TEST_MODULE path_element_tests
|
||||
|
||||
// mapnik
|
||||
#include <mapnik/map.hpp>
|
||||
#include <mapnik/layer.hpp>
|
||||
#include <mapnik/rule.hpp>
|
||||
#include <mapnik/feature_type_style.hpp>
|
||||
#include <mapnik/svg/output/svg_renderer.hpp>
|
||||
#include <mapnik/text/placements/dummy.hpp>
|
||||
#include <mapnik/datasource_cache.hpp>
|
||||
#include <mapnik/expression.hpp>
|
||||
#include <mapnik/color_factory.hpp>
|
||||
#include <mapnik/image_util.hpp>
|
||||
#include <mapnik/graphics.hpp>
|
||||
#include <mapnik/agg_renderer.hpp>
|
||||
|
||||
// boost
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
// stl
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
using namespace mapnik;
|
||||
|
||||
const std::string srs_lcc="+proj=lcc +ellps=GRS80 +lat_0=49 +lon_0=-95 +lat+1=49 +lat_2=77 \
|
||||
+datum=NAD83 +units=m +no_defs";
|
||||
const std::string srs_merc="+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 \
|
||||
+y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over";
|
||||
|
||||
void prepare_map(Map & m)
|
||||
{
|
||||
// Provinces (polygon)
|
||||
feature_type_style provpoly_style;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[NAME_EN] = 'Ontario'"));
|
||||
{
|
||||
polygon_symbolizer poly_sym;
|
||||
put(poly_sym, keys::fill, color(250, 190, 183));
|
||||
r.append(std::move(poly_sym));
|
||||
}
|
||||
provpoly_style.add_rule(std::move(r));
|
||||
}
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[NOM_FR] = 'Québec'"));
|
||||
{
|
||||
polygon_symbolizer poly_sym;
|
||||
put(poly_sym, keys::fill, color(217, 235, 203));
|
||||
r.append(std::move(poly_sym));
|
||||
}
|
||||
provpoly_style.add_rule(std::move(r));
|
||||
}
|
||||
m.insert_style("provinces",provpoly_style);
|
||||
|
||||
// Provinces (polyline)
|
||||
feature_type_style provlines_style;
|
||||
{
|
||||
rule r;
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(0,0,0));
|
||||
put(line_sym,keys::stroke_width,1.0);
|
||||
dash_array dash;
|
||||
dash.emplace_back(8,4);
|
||||
dash.emplace_back(2,2);
|
||||
dash.emplace_back(2,2);
|
||||
put(line_sym,keys::stroke_dasharray,dash);
|
||||
r.append(std::move(line_sym));
|
||||
}
|
||||
provlines_style.add_rule(std::move(r));
|
||||
}
|
||||
m.insert_style("provlines",provlines_style);
|
||||
|
||||
// Drainage
|
||||
feature_type_style qcdrain_style;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[HYC] = 8"));
|
||||
{
|
||||
polygon_symbolizer poly_sym;
|
||||
put(poly_sym, keys::fill, color(153, 204, 255));
|
||||
r.append(std::move(poly_sym));
|
||||
}
|
||||
qcdrain_style.add_rule(std::move(r));
|
||||
}
|
||||
m.insert_style("drainage",qcdrain_style);
|
||||
|
||||
// Roads 3 and 4 (The "grey" roads)
|
||||
feature_type_style roads34_style;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 3 or [CLASS] = 4"));
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(171,158,137));
|
||||
put(line_sym,keys::stroke_width,2.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
}
|
||||
roads34_style.add_rule(std::move(r));
|
||||
}
|
||||
m.insert_style("smallroads",roads34_style);
|
||||
|
||||
// Roads 2 (The thin yellow ones)
|
||||
feature_type_style roads2_style_1;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 2"));
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(171,158,137));
|
||||
put(line_sym,keys::stroke_width,4.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
}
|
||||
roads2_style_1.add_rule(std::move(r));
|
||||
}
|
||||
m.insert_style("road-border", roads2_style_1);
|
||||
|
||||
feature_type_style roads2_style_2;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 2"));
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(255,250,115));
|
||||
put(line_sym,keys::stroke_width,2.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
}
|
||||
roads2_style_2.add_rule(std::move(r));
|
||||
}
|
||||
m.insert_style("road-fill", roads2_style_2);
|
||||
|
||||
// Roads 1 (The big orange ones, the highways)
|
||||
feature_type_style roads1_style_1;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 1"));
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(188,149,28));
|
||||
put(line_sym,keys::stroke_width,7.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
}
|
||||
roads1_style_1.add_rule(std::move(r));
|
||||
}
|
||||
m.insert_style("highway-border", roads1_style_1);
|
||||
|
||||
feature_type_style roads1_style_2;
|
||||
{
|
||||
rule r;
|
||||
r.set_filter(parse_expression("[CLASS] = 1"));
|
||||
{
|
||||
line_symbolizer line_sym;
|
||||
put(line_sym,keys::stroke,color(242,191,36));
|
||||
put(line_sym,keys::stroke_width,5.0);
|
||||
put(line_sym,keys::stroke_linecap,ROUND_CAP);
|
||||
put(line_sym,keys::stroke_linejoin,ROUND_JOIN);
|
||||
r.append(std::move(line_sym));
|
||||
}
|
||||
roads1_style_2.add_rule(std::move(r));
|
||||
}
|
||||
m.insert_style("highway-fill", roads1_style_2);
|
||||
|
||||
// Populated Places
|
||||
feature_type_style popplaces_style;
|
||||
{
|
||||
rule r;
|
||||
{
|
||||
text_symbolizer text_sym;
|
||||
text_placements_ptr placement_finder = std::make_shared<text_placements_dummy>();
|
||||
placement_finder->defaults.format_defaults.face_name = "DejaVu Sans Book";
|
||||
placement_finder->defaults.format_defaults.text_size = mapnik::value_integer(10);
|
||||
placement_finder->defaults.format_defaults.fill = color(0,0,0);
|
||||
placement_finder->defaults.format_defaults.halo_fill = color(255,255,200);
|
||||
placement_finder->defaults.format_defaults.halo_radius = mapnik::value_integer(1);
|
||||
placement_finder->defaults.set_old_style_expression(parse_expression("[GEONAME]"));
|
||||
put<text_placements_ptr>(text_sym, keys::text_placements_, placement_finder);
|
||||
r.append(std::move(text_sym));
|
||||
}
|
||||
popplaces_style.add_rule(std::move(r));
|
||||
}
|
||||
|
||||
m.insert_style("popplaces",popplaces_style );
|
||||
|
||||
// layers
|
||||
// Provincial polygons
|
||||
{
|
||||
parameters p;
|
||||
p["type"]="shape";
|
||||
p["file"]="demo/data/boundaries";
|
||||
p["encoding"]="latin1";
|
||||
layer lyr("Provinces");
|
||||
lyr.set_datasource(datasource_cache::instance().create(p));
|
||||
lyr.set_srs(srs_lcc);
|
||||
lyr.add_style("provinces");
|
||||
m.add_layer(lyr);
|
||||
}
|
||||
|
||||
// Drainage
|
||||
{
|
||||
parameters p;
|
||||
p["type"]="shape";
|
||||
p["file"]="demo/data/qcdrainage";
|
||||
layer lyr("Quebec Hydrography");
|
||||
lyr.set_datasource(datasource_cache::instance().create(p));
|
||||
lyr.set_srs(srs_lcc);
|
||||
lyr.add_style("drainage");
|
||||
m.add_layer(lyr);
|
||||
}
|
||||
|
||||
{
|
||||
parameters p;
|
||||
p["type"]="shape";
|
||||
p["file"]="demo/data/ontdrainage";
|
||||
layer lyr("Ontario Hydrography");
|
||||
lyr.set_datasource(datasource_cache::instance().create(p));
|
||||
lyr.set_srs(srs_lcc);
|
||||
lyr.add_style("drainage");
|
||||
m.add_layer(lyr);
|
||||
}
|
||||
|
||||
// Provincial boundaries
|
||||
{
|
||||
parameters p;
|
||||
p["type"]="shape";
|
||||
p["file"]="demo/data/boundaries_l";
|
||||
layer lyr("Provincial borders");
|
||||
lyr.set_srs(srs_lcc);
|
||||
lyr.set_datasource(datasource_cache::instance().create(p));
|
||||
lyr.add_style("provlines");
|
||||
m.add_layer(lyr);
|
||||
}
|
||||
|
||||
// Roads
|
||||
{
|
||||
parameters p;
|
||||
p["type"]="shape";
|
||||
p["file"]="demo/data/roads";
|
||||
layer lyr("Roads");
|
||||
lyr.set_srs(srs_lcc);
|
||||
lyr.set_datasource(datasource_cache::instance().create(p));
|
||||
lyr.add_style("smallroads");
|
||||
lyr.add_style("road-border");
|
||||
lyr.add_style("road-fill");
|
||||
lyr.add_style("highway-border");
|
||||
lyr.add_style("highway-fill");
|
||||
|
||||
m.add_layer(lyr);
|
||||
}
|
||||
|
||||
// popplaces
|
||||
{
|
||||
parameters p;
|
||||
p["type"]="shape";
|
||||
p["file"]="demo/data/popplaces";
|
||||
p["encoding"] = "latin1";
|
||||
layer lyr("Populated Places");
|
||||
lyr.set_srs(srs_lcc);
|
||||
lyr.set_datasource(datasource_cache::instance().create(p));
|
||||
lyr.add_style("popplaces");
|
||||
m.add_layer(lyr);
|
||||
}
|
||||
}
|
||||
|
||||
void render_to_file(Map const& m, const std::string output_filename)
|
||||
{
|
||||
std::ofstream output_stream(output_filename.c_str());
|
||||
|
||||
if(output_stream)
|
||||
{
|
||||
using svg_ren = svg_renderer<std::ostream_iterator<char> >;
|
||||
std::ostream_iterator<char> output_stream_iterator(output_stream);
|
||||
svg_ren renderer(m, output_stream_iterator);
|
||||
renderer.apply();
|
||||
output_stream.close();
|
||||
fs::path output_filename_path =
|
||||
fs::system_complete(fs::path(".")) / fs::path(output_filename);
|
||||
BOOST_CHECK_MESSAGE(fs::exists(output_filename_path),
|
||||
"File '"+output_filename_path.string()+"' was created.");
|
||||
}
|
||||
else
|
||||
{
|
||||
BOOST_FAIL("Could not create create/open file '"+output_filename+"'.");
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(path_element_test_case_1)
|
||||
{
|
||||
std::cout << " looking for 'shape.input' plugin in ./plugins/input/" << "\n";
|
||||
datasource_cache::instance().register_datasources("plugins/input/");
|
||||
Map m(800,600);
|
||||
m.set_background(parse_color("white"));
|
||||
m.set_srs(srs_merc);
|
||||
prepare_map(m);
|
||||
m.zoom_to_box(box2d<double>(-8024477.28459,5445190.38849,-7381388.20071,5662941.44855));
|
||||
render_to_file(m, "path_element_test_case_1.svg");
|
||||
mapnik::image_32 buf(m.width(),m.height());
|
||||
mapnik::agg_renderer<mapnik::image_32> ren(m,buf);
|
||||
ren.apply();
|
||||
mapnik::save_to_file(buf,"path_element_test_case_1.png");
|
||||
}
|
|
@ -14,7 +14,7 @@ def test_that_coordinates_do_not_overflow_and_polygon_is_rendered():
|
|||
ds = mapnik.MemoryDatasource()
|
||||
context = mapnik.Context()
|
||||
ds.add_feature(mapnik.Feature.from_geojson(json.dumps(geojson),context))
|
||||
s = mapnik.Style()
|
||||
s = mapnik.Style('style')
|
||||
r = mapnik.Rule()
|
||||
sym = mapnik.PolygonSymbolizer()
|
||||
sym.fill = expected_color
|
||||
|
@ -23,10 +23,10 @@ def test_that_coordinates_do_not_overflow_and_polygon_is_rendered():
|
|||
s.rules.append(r)
|
||||
lyr = mapnik.Layer('Layer')
|
||||
lyr.datasource = ds
|
||||
lyr.styles.append('style')
|
||||
lyr.styles.append(s.name)
|
||||
m = mapnik.Map(256,256)
|
||||
m.background_color = mapnik.Color('black')
|
||||
m.append_style('style',s)
|
||||
m.append_style(s)
|
||||
m.layers.append(lyr)
|
||||
# 17/20864/45265.png
|
||||
m.zoom_to_box(mapnik.Box2d(-13658379.710221574,6197514.253362091,-13657768.213995293,6198125.749588372))
|
||||
|
|
|
@ -30,17 +30,17 @@ def make_map():
|
|||
f['Name'] = str(pixel_key)
|
||||
f.add_geometries_from_wkt('POLYGON ((0 0, 0 256, 256 256, 256 0, 0 0))')
|
||||
ds.add_feature(f)
|
||||
s = mapnik.Style()
|
||||
s = mapnik.Style('places_labels')
|
||||
r = mapnik.Rule()
|
||||
symb = mapnik.PolygonSymbolizer()
|
||||
r.symbols.append(symb)
|
||||
s.rules.append(r)
|
||||
lyr = mapnik.Layer('Places')
|
||||
lyr.datasource = ds
|
||||
lyr.styles.append('places_labels')
|
||||
lyr.styles.append(s.name)
|
||||
width,height = 256,256
|
||||
m = mapnik.Map(width,height)
|
||||
m.append_style('places_labels',s)
|
||||
m.append_style(s)
|
||||
m.layers.append(lyr)
|
||||
m.zoom_all()
|
||||
return m
|
||||
|
|
|
@ -133,7 +133,7 @@ def test_render_points():
|
|||
ds.add_feature(f)
|
||||
|
||||
# create layer/rule/style
|
||||
s = mapnik.Style()
|
||||
s = mapnik.Style('places_labels')
|
||||
r = mapnik.Rule()
|
||||
symb = mapnik.PointSymbolizer()
|
||||
symb.allow_overlap = True
|
||||
|
@ -141,7 +141,7 @@ def test_render_points():
|
|||
s.rules.append(r)
|
||||
lyr = mapnik.Layer('Places','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')
|
||||
lyr.datasource = ds
|
||||
lyr.styles.append('places_labels')
|
||||
lyr.styles.append(s.name)
|
||||
# latlon bounding box corners
|
||||
ul_lonlat = mapnik.Coord(142.30,-38.20)
|
||||
lr_lonlat = mapnik.Coord(143.40,-38.80)
|
||||
|
@ -154,7 +154,7 @@ def test_render_points():
|
|||
}
|
||||
for projdescr in projs.iterkeys():
|
||||
m = mapnik.Map(1000, 500, projs[projdescr])
|
||||
m.append_style('places_labels',s)
|
||||
m.append_style(s)
|
||||
m.layers.append(lyr)
|
||||
dest_proj = mapnik.Projection(projs[projdescr])
|
||||
src_proj = mapnik.Projection('+init=epsg:4326')
|
||||
|
@ -179,17 +179,17 @@ def test_render_with_detector():
|
|||
context = mapnik.Context()
|
||||
geojson = '{ "type": "Feature", "geometry": { "type": "Point", "coordinates": [ 0, 0 ] } }'
|
||||
ds.add_feature(mapnik.Feature.from_geojson(geojson,context))
|
||||
s = mapnik.Style()
|
||||
s = mapnik.Style('point')
|
||||
r = mapnik.Rule()
|
||||
lyr = mapnik.Layer('point')
|
||||
lyr.datasource = ds
|
||||
lyr.styles.append('point')
|
||||
lyr.styles.append(s.name)
|
||||
symb = mapnik.MarkersSymbolizer()
|
||||
symb.allow_overlap = False
|
||||
r.symbols.append(symb)
|
||||
s.rules.append(r)
|
||||
m = mapnik.Map(256,256)
|
||||
m.append_style('point',s)
|
||||
m.append_style(s)
|
||||
m.layers.append(lyr)
|
||||
m.zoom_to_box(mapnik.Box2d(-180,-85,180,85))
|
||||
im = mapnik.Image(256, 256)
|
||||
|
|
|
@ -7,7 +7,8 @@ from utilities import execution_path, run_all
|
|||
import mapnik
|
||||
|
||||
def test_style_init():
|
||||
s = mapnik.Style()
|
||||
s = mapnik.Style("style")
|
||||
eq_(s.name,"style")
|
||||
eq_(s.filter_mode,mapnik.filter_mode.ALL)
|
||||
eq_(len(s.rules),0)
|
||||
eq_(s.opacity,1)
|
||||
|
|
Loading…
Reference in a new issue