Merge branch 'master' into rotated_rectangles
This commit is contained in:
commit
ebbaf49896
52 changed files with 1330 additions and 850 deletions
107
CHANGELOG.md
107
CHANGELOG.md
|
@ -8,42 +8,112 @@ For a complete change history, see the git log.
|
||||||
|
|
||||||
## 3.0.0
|
## 3.0.0
|
||||||
|
|
||||||
- Added new and experimental `dot` symbolizer for fast rendering of points
|
Released: July 7th, 2015
|
||||||
|
|
||||||
|
(Packaged from e6891a0)
|
||||||
|
|
||||||
|
#### Summary
|
||||||
|
|
||||||
|
The 3.0 release is a major milestone for Mapnik and includes many performance and design improvements. The is the first release to provide text shaping using the harfbuzz library. This harfbuzz support unlocks improved rendering and layer for many new languages, particularly SE Asian scripts. The internal storage for working with images and geometries has been made more flexible, faster, and strongly typed. The python bindings that were previously bundled with Mapnik have now been moved to <https://github.com/mapnik/python-mapnik> and are versioned independently.
|
||||||
|
|
||||||
|
#### Notice
|
||||||
|
|
||||||
|
- Mapnik 3.0.0 requires a compiler capable of `std=c++11`.
|
||||||
|
- It is highly recommended you use the `clang++` compiler on both OS X and Linux since it has robust c++11 support lower memory requirements.
|
||||||
|
|
||||||
|
##### Major Changes
|
||||||
|
|
||||||
- Improved support for International Text (now uses harfbuzz library for text shaping)
|
- Improved support for International Text (now uses harfbuzz library for text shaping)
|
||||||
- Uses latest c++11 features for better performance (especially map loading)
|
|
||||||
|
- Uses latest C++11 features for better performance (especially map loading)
|
||||||
|
|
||||||
- Expressions everywhere: all symbolizer properties can now be data driven expressions (with the exception of `face-name` and `fontset-name` on the `TextSymbolizer`).
|
- Expressions everywhere: all symbolizer properties can now be data driven expressions (with the exception of `face-name` and `fontset-name` on the `TextSymbolizer`).
|
||||||
|
|
||||||
|
- Rewritten geometry storage based on `std::vector` (#2739)
|
||||||
|
- Separate storage of polygon exterior rings and interior rings to allow for more robust clipping of parts.
|
||||||
|
- Enforces consistent winding order per OGC spec (exterior rings are CCW, interior CW)
|
||||||
|
- Reduced memory consumption for layers with many points
|
||||||
|
- Ability to adapt Mapnik geometries to boost::geometry operations (in a zero-copy way)
|
||||||
|
- Ability to have i/o grammars for json/wkt work on geometries rather than paths for better efficiency and simpler code
|
||||||
|
|
||||||
|
- Added new and experimental `dot` symbolizer for fast rendering of points
|
||||||
|
|
||||||
- New functions supported in expressions: `exp`, `sin`, `cos`, `tan`, `atan`, `abs`.
|
- New functions supported in expressions: `exp`, `sin`, `cos`, `tan`, `atan`, `abs`.
|
||||||
|
|
||||||
- New constants supported in expressions: `PI`, `DEG_TO_RAD`, `RAD_TO_DEG`
|
- New constants supported in expressions: `PI`, `DEG_TO_RAD`, `RAD_TO_DEG`
|
||||||
|
|
||||||
|
- Added support for a variety of different grayscale images:
|
||||||
|
- `mapnik.imageType.null`
|
||||||
|
- `mapnik.imageType.rgba8`
|
||||||
|
- `mapnik.imageType.gray8`
|
||||||
|
- `mapnik.imageType.gray8s`
|
||||||
|
- `mapnik.imageType.gray16`
|
||||||
|
- `mapnik.imageType.gray16s`
|
||||||
|
- `mapnik.imageType.gray32`
|
||||||
|
- `mapnik.imageType.gray32s`
|
||||||
|
- `mapnik.imageType.gray32f`
|
||||||
|
- `mapnik.imageType.gray64`
|
||||||
|
- `mapnik.imageType.gray64s`
|
||||||
|
- `mapnik.imageType.gray64f`
|
||||||
|
|
||||||
- Pattern symbolizers now support SVG input and applying transformations on them dynamically
|
- Pattern symbolizers now support SVG input and applying transformations on them dynamically
|
||||||
|
|
||||||
- Experimental / interface may change: `@variables` can be passed to renderer and evaluated in expressions
|
- Experimental / interface may change: `@variables` can be passed to renderer and evaluated in expressions
|
||||||
|
|
||||||
- Supports being built with clang++ using `-fvisibility=hidden -flto` for smaller binaries
|
- Supports being built with clang++ using `-fvisibility=hidden -flto` for smaller binaries
|
||||||
|
|
||||||
- Supports being built with Visual Studio 2014 CTP #3
|
- Supports being built with Visual Studio 2014 CTP #3
|
||||||
|
|
||||||
- Shield icons are now pixel snapped for crisp rendering
|
- Shield icons are now pixel snapped for crisp rendering
|
||||||
|
|
||||||
- `MarkersSymbolizer` now supports `avoid-edges`, `offset`, `geometry-transform`, `simplify` for `line` placement and two new `placement` options called `vertex-last` and `vertex-first` to place a single marker at the end or beginning of a path. Also `clip` is now respected when rendering markers on a LineString
|
- `MarkersSymbolizer` now supports `avoid-edges`, `offset`, `geometry-transform`, `simplify` for `line` placement and two new `placement` options called `vertex-last` and `vertex-first` to place a single marker at the end or beginning of a path. Also `clip` is now respected when rendering markers on a LineString
|
||||||
geometry.
|
geometry.
|
||||||
|
|
||||||
- `TextSymbolizer` now supports `smooth`, `simplify`, `halo-opacity`, `halo-comp-op`, and `halo-transform`
|
- `TextSymbolizer` now supports `smooth`, `simplify`, `halo-opacity`, `halo-comp-op`, and `halo-transform`
|
||||||
|
|
||||||
- `ShieldSymbolizer` now supports `smooth`, `simplify`, `halo-opacity`, `halo-comp-op`, and `halo-transform`
|
- `ShieldSymbolizer` now supports `smooth`, `simplify`, `halo-opacity`, `halo-comp-op`, and `halo-transform`
|
||||||
|
|
||||||
|
- The `text-transform` property of `TextSymbolizer` now supports `reverse` value to flip direction of text.
|
||||||
|
|
||||||
|
- The `TextSymbolizer` now supports `font-feature-settings` for advanced control over Opentype font rendering (https://developer.mozilla.org/en-US/docs/Web/CSS/font-feature-settings)
|
||||||
|
|
||||||
- New GroupSymbolizer for applying multiple symbolizers in a single layout
|
- New GroupSymbolizer for applying multiple symbolizers in a single layout
|
||||||
|
|
||||||
Released ...
|
|
||||||
|
|
||||||
(Packaged from ...)
|
|
||||||
|
|
||||||
Summary: TODO
|
|
||||||
|
|
||||||
- PostGIS: Added support for rendering 3D and 4D geometries (previously silently skipped) (#44)
|
|
||||||
|
|
||||||
- AGG renderer: fixed geometry offsetting to work after smoothing to produce more consistent results (#2202)
|
- AGG renderer: fixed geometry offsetting to work after smoothing to produce more consistent results (#2202)
|
||||||
|
|
||||||
- AGG renderer: increased `vertex_dist_epsilon` to ensure nearly coincident points are discarded more readily (#2196)
|
- AGG renderer: increased `vertex_dist_epsilon` to ensure nearly coincident points are discarded more readily (#2196)
|
||||||
|
|
||||||
- GDAL plugin: Added back support for user driven `nodata` on rgb(a) images (#2023)
|
- GDAL plugin
|
||||||
|
- Now keeps datasets open for the lifetime of the datasource (rather than per featureset)
|
||||||
|
- Added back support for user driven `nodata` on rgb(a) images (#2023)
|
||||||
|
- Allowed nodata to override alpha band if set on rgba images (#2023)
|
||||||
|
- Added `nodata_tolerance` option to set nearby pixels transparent (has similar effect to the `nearblack` program) (#2023)
|
||||||
|
- At process exit Mapnik core no longer calls `dlclose` on gdal.input (#2716)
|
||||||
|
|
||||||
- GDAL plugin: Allowed nodata to override alpha band if set on rgba images (#2023)
|
- TopoJSON plugin
|
||||||
|
- Now supporting optional `bbox` property on layer
|
||||||
|
- Fixed support for reporting correct `feature.id()`
|
||||||
|
- Now supports `inline` string for passing data from memory
|
||||||
|
- Faster parsing via static initialization of grammars
|
||||||
|
- Fix crash on invalid arc index
|
||||||
|
|
||||||
- GDAL plugin: Added `nodata_tolerance` option to set nearby pixels transparent (has similar effect to the `nearblack` program) (#2023)
|
- GeoJSON plugin
|
||||||
|
- Now supporting optional `bbox` property on layer
|
||||||
|
- Fixed support for reporting correct `feature.id()`
|
||||||
|
- Now supports `inline` string for passing data from memory
|
||||||
|
- Faster parsing via static initialization of grammars
|
||||||
|
|
||||||
- Added support for web fonts: .woff format (#2113)
|
- SQLite plugin
|
||||||
|
- Fixed support for handling all column types
|
||||||
|
|
||||||
|
- CSV Plugin
|
||||||
|
- Added the ability to pass an `extent` in options
|
||||||
|
|
||||||
|
- PostGIS plugin
|
||||||
|
- Added Async support to - https://github.com/mapnik/mapnik/wiki/PostGIS-Async
|
||||||
|
- Added support for rendering 3D and 4D geometries (previously silently skipped) (#44)
|
||||||
|
|
||||||
|
- Added support for web fonts: `.woff` format (#2113)
|
||||||
|
|
||||||
- Added missing support for `geometry-transform` in `line-pattern` and `polygon-pattern` symbolizers (#2065)
|
- Added missing support for `geometry-transform` in `line-pattern` and `polygon-pattern` symbolizers (#2065)
|
||||||
|
|
||||||
|
@ -51,11 +121,9 @@ Summary: TODO
|
||||||
|
|
||||||
- Upgraded unifont to `unifont-6.3.20131020`
|
- Upgraded unifont to `unifont-6.3.20131020`
|
||||||
|
|
||||||
- CSV Plugin: added the ability to pass an `extent` in options
|
|
||||||
|
|
||||||
- Fixed crash when rendering to cairo context from python (#2031)
|
- Fixed crash when rendering to cairo context from python (#2031)
|
||||||
|
|
||||||
- Moved `label-position-tolerance` from unsigned type to double
|
- Moved `label-position-tolerance` from `unsigned` type to `double`
|
||||||
|
|
||||||
- Added support for more seamless blurring by rendering to a larger internal image to avoid edge effects (#1478)
|
- Added support for more seamless blurring by rendering to a larger internal image to avoid edge effects (#1478)
|
||||||
|
|
||||||
|
@ -67,8 +135,6 @@ Summary: TODO
|
||||||
|
|
||||||
- Added `color-to-alpha` `image-filter` to allow for applying alpha in proportion to color similiarity (#2023)
|
- Added `color-to-alpha` `image-filter` to allow for applying alpha in proportion to color similiarity (#2023)
|
||||||
|
|
||||||
- Added Async support to PostGIS plugin - https://github.com/mapnik/mapnik/wiki/PostGIS-Async
|
|
||||||
|
|
||||||
- Fixed alpha handling bug with `comp-op:dst-over` (#1995)
|
- Fixed alpha handling bug with `comp-op:dst-over` (#1995)
|
||||||
|
|
||||||
- Fixed alpha handling bug with building-fill-opacity (#2011)
|
- Fixed alpha handling bug with building-fill-opacity (#2011)
|
||||||
|
@ -133,6 +199,9 @@ are not set. (#1966)
|
||||||
|
|
||||||
- Added `scale-hsla` image-filter that allows scaling colors in HSL color space. RGB is converted to HSL (hue-saturation-lightness) and then each value (and the original alpha value) is stretched based on the specified scaling values. An example syntax is `scale-hsla(0,1,0,1,0,1,0,1)` which means no change because the full range will be kept (0 for lowest, 1 for highest). Other examples are: 1) `scale-hsla(0,0,0,1,0,1,0,1)` which would force all colors to be red in hue in the same way `scale-hsla(1,1,0,1,0,1,0,1)` would, 2) `scale-hsla(0,1,1,1,0,1,0,1)` which would cause all colors to become fully saturated, 3) `scale-hsla(0,1,1,1,0,1,.5,1)` which would force no colors to be any more transparent than half, and 4) `scale-hsla(0,1,1,1,0,1,0,.5)` which would force all colors to be at least half transparent. (#1954)
|
- Added `scale-hsla` image-filter that allows scaling colors in HSL color space. RGB is converted to HSL (hue-saturation-lightness) and then each value (and the original alpha value) is stretched based on the specified scaling values. An example syntax is `scale-hsla(0,1,0,1,0,1,0,1)` which means no change because the full range will be kept (0 for lowest, 1 for highest). Other examples are: 1) `scale-hsla(0,0,0,1,0,1,0,1)` which would force all colors to be red in hue in the same way `scale-hsla(1,1,0,1,0,1,0,1)` would, 2) `scale-hsla(0,1,1,1,0,1,0,1)` which would cause all colors to become fully saturated, 3) `scale-hsla(0,1,1,1,0,1,.5,1)` which would force no colors to be any more transparent than half, and 4) `scale-hsla(0,1,1,1,0,1,0,.5)` which would force all colors to be at least half transparent. (#1954)
|
||||||
|
|
||||||
|
- The `shapeindex` tool now works correctly with point 3d geometry types
|
||||||
|
|
||||||
|
|
||||||
## 2.2.0
|
## 2.2.0
|
||||||
|
|
||||||
Released June 3rd, 2013
|
Released June 3rd, 2013
|
||||||
|
|
16
INSTALL.md
16
INSTALL.md
|
@ -5,17 +5,23 @@ Mapnik runs on Linux, OS X, Windows, and BSD systems.
|
||||||
To configure and build Mapnik do:
|
To configure and build Mapnik do:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ ./configure
|
./configure
|
||||||
$ make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
To trigger parallel compilation you can pass a JOBS value to make:
|
To trigger parallel compilation you can pass a JOBS value to make:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ JOBS=4 make
|
JOBS=4 make
|
||||||
```
|
```
|
||||||
|
|
||||||
(Note that compiling Mapnik needs several GBytes of RAM. If you use parallel compilation it needs more.)
|
Mapnik needs > 2 GB of RAM to build. If you use parallel compilation it needs more.
|
||||||
|
|
||||||
|
If you are on a system with less memory make sure you only build with one JOB:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
JOBS=1 make
|
||||||
|
```
|
||||||
|
|
||||||
To use a Python interpreter that is not named `python` for your build, do
|
To use a Python interpreter that is not named `python` for your build, do
|
||||||
something like the following instead:
|
something like the following instead:
|
||||||
|
@ -178,4 +184,4 @@ tutorials on how to programmatically use Mapnik.
|
||||||
|
|
||||||
### Contributers
|
### Contributers
|
||||||
|
|
||||||
Read docs/contributing.markdown for resources for getting involved with Mapnik development.
|
Read docs/contributing.md for resources for getting involved with Mapnik development.
|
||||||
|
|
|
@ -29,19 +29,7 @@ namespace benchmark {
|
||||||
image_rgba8 const& dest = util::get<image_rgba8>(desc_any);
|
image_rgba8 const& dest = util::get<image_rgba8>(desc_any);
|
||||||
image_rgba8 const& src = util::get<image_rgba8>(src_any);
|
image_rgba8 const& src = util::get<image_rgba8>(src_any);
|
||||||
|
|
||||||
unsigned int width = src.width();
|
return compare(dest, src, 0, true) == 0;
|
||||||
unsigned int height = src.height();
|
|
||||||
if ((width != dest.width()) || height != dest.height()) return false;
|
|
||||||
for (unsigned int y = 0; y < height; ++y)
|
|
||||||
{
|
|
||||||
const unsigned int* row_from = src.get_row(y);
|
|
||||||
const unsigned int* row_to = dest.get_row(y);
|
|
||||||
for (unsigned int x = 0; x < width; ++x)
|
|
||||||
{
|
|
||||||
if (row_from[x] != row_to[x]) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ function install_mason_deps() {
|
||||||
install harfbuzz 0.9.40 libharfbuzz &
|
install harfbuzz 0.9.40 libharfbuzz &
|
||||||
install jpeg_turbo 1.4.0 libjpeg &
|
install jpeg_turbo 1.4.0 libjpeg &
|
||||||
install libxml2 2.9.2 libxml2 &
|
install libxml2 2.9.2 libxml2 &
|
||||||
install libpng 1.6.16 libpng &
|
install libpng 1.6.17 libpng &
|
||||||
install webp 0.4.2 libwebp &
|
install webp 0.4.2 libwebp &
|
||||||
install icu 54.1 &
|
install icu 54.1 &
|
||||||
install proj 4.8.0 libproj &
|
install proj 4.8.0 libproj &
|
||||||
|
|
4
deps/agg/include/agg_color_gray.h
vendored
4
deps/agg/include/agg_color_gray.h
vendored
|
@ -652,8 +652,8 @@ struct gray16
|
||||||
//--------------------------------------------------------------------
|
//--------------------------------------------------------------------
|
||||||
self_type& opacity(double a_)
|
self_type& opacity(double a_)
|
||||||
{
|
{
|
||||||
if (a_ < 0) a_ = 0;
|
if (a_ < 0) a = 0;
|
||||||
else if(a_ > 1) a_ = 1;
|
else if(a_ > 1) a = 1;
|
||||||
else a = (value_type)uround(a_ * double(base_mask));
|
else a = (value_type)uround(a_ * double(base_mask));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ struct offset_converter
|
||||||
, pre_first_(vertex2d::no_init)
|
, pre_first_(vertex2d::no_init)
|
||||||
, pre_(vertex2d::no_init)
|
, pre_(vertex2d::no_init)
|
||||||
, cur_(vertex2d::no_init)
|
, cur_(vertex2d::no_init)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
enum status
|
enum status
|
||||||
{
|
{
|
||||||
|
@ -238,8 +238,8 @@ private:
|
||||||
*/
|
*/
|
||||||
static void displace(vertex2d & v, double dx, double dy, double a)
|
static void displace(vertex2d & v, double dx, double dy, double a)
|
||||||
{
|
{
|
||||||
v.x += dx * std::cos(a) + dy * std::sin(a);
|
v.x += dx * std::cos(a) - dy * std::sin(a);
|
||||||
v.y += dx * std::sin(a) - dy * std::cos(a);
|
v.y += dx * std::sin(a) + dy * std::cos(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -247,8 +247,8 @@ private:
|
||||||
*/
|
*/
|
||||||
void displace(vertex2d & v, double a) const
|
void displace(vertex2d & v, double a) const
|
||||||
{
|
{
|
||||||
v.x += offset_ * std::sin(a);
|
v.x -= offset_ * std::sin(a);
|
||||||
v.y -= offset_ * std::cos(a);
|
v.y += offset_ * std::cos(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -256,8 +256,8 @@ private:
|
||||||
*/
|
*/
|
||||||
void displace(vertex2d & v, vertex2d const& u, double a) const
|
void displace(vertex2d & v, vertex2d const& u, double a) const
|
||||||
{
|
{
|
||||||
v.x = u.x + offset_ * std::sin(a);
|
v.x = u.x - offset_ * std::sin(a);
|
||||||
v.y = u.y - offset_ * std::cos(a);
|
v.y = u.y + offset_ * std::cos(a);
|
||||||
v.cmd = u.cmd;
|
v.cmd = u.cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,16 +266,27 @@ private:
|
||||||
double sa = offset_ * std::sin(a);
|
double sa = offset_ * std::sin(a);
|
||||||
double ca = offset_ * std::cos(a);
|
double ca = offset_ * std::cos(a);
|
||||||
double h = std::tan(0.5 * (b - a));
|
double h = std::tan(0.5 * (b - a));
|
||||||
if (h > 1.5)
|
double hsa = h * sa;
|
||||||
|
double hca = h * ca;
|
||||||
|
double abs_offset = std::abs(offset_);
|
||||||
|
if (hsa > 1.0 * abs_offset)
|
||||||
{
|
{
|
||||||
h = 1.5;
|
hsa = 1.0 * abs_offset;
|
||||||
}
|
}
|
||||||
else if (h < -1.5)
|
else if (hsa < -1.0 * abs_offset)
|
||||||
{
|
{
|
||||||
h = -1.5;
|
hsa = -1.0 * abs_offset;
|
||||||
}
|
}
|
||||||
v.x = v.x + sa + h * ca;
|
if (hca > 1.0 * abs_offset)
|
||||||
v.y = v.y - ca + h * sa;
|
{
|
||||||
|
hca = 1.0 * abs_offset;
|
||||||
|
}
|
||||||
|
else if (hca < -1.0 * abs_offset)
|
||||||
|
{
|
||||||
|
hca = -1.0 * abs_offset;
|
||||||
|
}
|
||||||
|
v.x = v.x - sa - hca;
|
||||||
|
v.y = v.y + ca - hsa;
|
||||||
}
|
}
|
||||||
|
|
||||||
status init_vertices()
|
status init_vertices()
|
||||||
|
@ -288,31 +299,51 @@ private:
|
||||||
vertex2d v1(vertex2d::no_init);
|
vertex2d v1(vertex2d::no_init);
|
||||||
vertex2d v2(vertex2d::no_init);
|
vertex2d v2(vertex2d::no_init);
|
||||||
vertex2d w(vertex2d::no_init);
|
vertex2d w(vertex2d::no_init);
|
||||||
|
vertex2d start(vertex2d::no_init);
|
||||||
vertex2d start_v2(vertex2d::no_init);
|
vertex2d start_v2(vertex2d::no_init);
|
||||||
std::vector<vertex2d> points;
|
std::vector<vertex2d> points;
|
||||||
std::vector<vertex2d> close_points;
|
std::vector<vertex2d> close_points;
|
||||||
bool is_polygon = false;
|
bool is_polygon = false;
|
||||||
std::size_t cpt = 0;
|
std::size_t cpt = 0;
|
||||||
v0.cmd = geom_.vertex(&v0.x, &v0.y);
|
v0.cmd = geom_.vertex(&v0.x, &v0.y);
|
||||||
v1.x = v0.x;
|
v1 = v0;
|
||||||
v1.y = v0.y;
|
|
||||||
v1.cmd = v0.cmd;
|
|
||||||
// PUSH INITIAL
|
// PUSH INITIAL
|
||||||
points.push_back(vertex2d(v0.x, v0.y, v0.cmd));
|
points.push_back(v0);
|
||||||
|
if (v0.cmd == SEG_END) // not enough vertices in source
|
||||||
|
{
|
||||||
|
return status_ = process;
|
||||||
|
}
|
||||||
|
start = v0;
|
||||||
while ((v0.cmd = geom_.vertex(&v0.x, &v0.y)) != SEG_END)
|
while ((v0.cmd = geom_.vertex(&v0.x, &v0.y)) != SEG_END)
|
||||||
{
|
{
|
||||||
points.push_back(vertex2d(v0.x, v0.y, v0.cmd));
|
|
||||||
if (v0.cmd == SEG_CLOSE)
|
if (v0.cmd == SEG_CLOSE)
|
||||||
{
|
{
|
||||||
is_polygon = true;
|
is_polygon = true;
|
||||||
close_points.push_back(vertex2d(v1.x, v1.y, v1.cmd));
|
auto & prev = points.back();
|
||||||
|
if (prev.x == start.x && prev.y == start.y)
|
||||||
|
{
|
||||||
|
prev.x = v0.x; // hack
|
||||||
|
prev.y = v0.y;
|
||||||
|
prev.cmd = SEG_CLOSE; // account for dupes (line_to(move_to) + close_path) in agg poly clipper
|
||||||
|
std::size_t size = points.size();
|
||||||
|
if (size > 1) close_points.push_back(points[size - 2]);
|
||||||
|
else close_points.push_back(prev);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
close_points.push_back(v1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
v1.x = v0.x;
|
else if (v0.cmd == SEG_MOVETO)
|
||||||
v1.y = v0.y;
|
{
|
||||||
v1.cmd = v0.cmd;
|
start = v0;
|
||||||
|
}
|
||||||
|
v1 = v0;
|
||||||
|
points.push_back(v0);
|
||||||
}
|
}
|
||||||
// Push SEG_END
|
// Push SEG_END
|
||||||
points.push_back(vertex2d(v0.x, v0.y, v0.cmd));
|
points.push_back(vertex2d(v0.x,v0.y,SEG_END));
|
||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
v1 = points[i++];
|
v1 = points[i++];
|
||||||
v2 = points[i++];
|
v2 = points[i++];
|
||||||
|
@ -326,17 +357,28 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
double angle_a = 0;
|
double angle_a = 0;
|
||||||
|
// The vector parts from v1 to v0.
|
||||||
|
double v_x1x0 = 0;
|
||||||
|
double v_y1y0 = 0;
|
||||||
|
// The vector parts from v1 to v2;
|
||||||
|
double v_x1x2 = v2.x - v1.x;
|
||||||
|
double v_y1y2 = v2.y - v1.y;
|
||||||
|
|
||||||
if (is_polygon)
|
if (is_polygon)
|
||||||
{
|
{
|
||||||
double x = v1.x - close_points[cpt].x;
|
v_x1x0 = close_points[cpt].x - v1.x;
|
||||||
double y = v1.y - close_points[cpt].y;
|
v_y1y0 = close_points[cpt].y - v1.y;
|
||||||
cpt++;
|
cpt++;
|
||||||
x = std::abs(x) < std::numeric_limits<double>::epsilon() ? 0 : x;
|
angle_a = std::atan2(-v_y1y0, -v_x1x0);
|
||||||
y = std::abs(y) < std::numeric_limits<double>::epsilon() ? 0 : y;
|
|
||||||
angle_a = std::atan2(y, x);
|
|
||||||
}
|
}
|
||||||
double angle_b = std::atan2((v2.y - v1.y), (v2.x - v1.x));
|
// dot product
|
||||||
|
double dot;
|
||||||
|
// determinate
|
||||||
|
double det;
|
||||||
|
double angle_b = std::atan2(v_y1y2, v_x1x2);
|
||||||
|
// Angle between the two vectors
|
||||||
double joint_angle;
|
double joint_angle;
|
||||||
|
double curve_angle;
|
||||||
|
|
||||||
if (!is_polygon)
|
if (!is_polygon)
|
||||||
{
|
{
|
||||||
|
@ -346,33 +388,28 @@ private:
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
joint_angle = explement_reflex_angle(angle_b - angle_a);
|
dot = v_x1x0 * v_x1x2 + v_y1y0 * v_y1y2; // dot product
|
||||||
|
det = v_x1x0 * v_y1y2 - v_y1y0 * v_x1x2; // determinant
|
||||||
|
|
||||||
|
joint_angle = std::atan2(det, dot); // atan2(y, x) or atan2(sin, cos)
|
||||||
|
if (joint_angle < 0) joint_angle = joint_angle + 2 * M_PI;
|
||||||
|
joint_angle = std::fmod(joint_angle, 2 * M_PI);
|
||||||
|
|
||||||
|
if (offset_ > 0.0)
|
||||||
|
{
|
||||||
|
joint_angle = 2 * M_PI - joint_angle;
|
||||||
|
}
|
||||||
|
|
||||||
double half_turns = half_turn_segments_ * std::fabs(joint_angle);
|
|
||||||
int bulge_steps = 0;
|
int bulge_steps = 0;
|
||||||
|
|
||||||
if (offset_ < 0.0)
|
if (std::abs(joint_angle) > M_PI)
|
||||||
{
|
{
|
||||||
if (joint_angle > 0.0)
|
curve_angle = explement_reflex_angle(angle_b - angle_a);
|
||||||
{
|
// Bulge steps should be determined by the inverse of the joint angle.
|
||||||
joint_angle = joint_angle - 2 * M_PI;
|
double half_turns = half_turn_segments_ * std::fabs(curve_angle);
|
||||||
}
|
bulge_steps = 1 + static_cast<int>(std::floor(half_turns / M_PI));
|
||||||
else
|
|
||||||
{
|
|
||||||
bulge_steps = 1 + static_cast<int>(std::floor(half_turns / M_PI));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (joint_angle < 0.0)
|
|
||||||
{
|
|
||||||
joint_angle = joint_angle + 2 * M_PI;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bulge_steps = 1 + static_cast<int>(std::floor(half_turns / M_PI));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bulge_steps == 0)
|
if (bulge_steps == 0)
|
||||||
{
|
{
|
||||||
displace2(v1, angle_a, angle_b);
|
displace2(v1, angle_a, angle_b);
|
||||||
|
@ -415,18 +452,15 @@ private:
|
||||||
v1.y = start_.y;
|
v1.y = start_.y;
|
||||||
if (cpt < close_points.size())
|
if (cpt < close_points.size())
|
||||||
{
|
{
|
||||||
double x = v1.x - close_points[cpt].x;
|
v_x1x2 = v1.x - close_points[cpt].x;
|
||||||
double y = v1.y - close_points[cpt].y;
|
v_y1y2 = v1.y - close_points[cpt].y;
|
||||||
x = std::abs(x) < std::numeric_limits<double>::epsilon() ? 0.0 : x;
|
|
||||||
y = std::abs(y) < std::numeric_limits<double>::epsilon() ? 0.0 : y;
|
|
||||||
angle_b = std::atan2(y,x);
|
|
||||||
cpt++;
|
cpt++;
|
||||||
}
|
}
|
||||||
|
start_v2.x = v2.x;
|
||||||
|
start_v2.y = v2.y;
|
||||||
}
|
}
|
||||||
start_v2.x = v2.x;
|
|
||||||
start_v2.y = v2.y;
|
|
||||||
}
|
}
|
||||||
if (v2.cmd == SEG_MOVETO)
|
if (is_polygon && v2.cmd == SEG_MOVETO)
|
||||||
{
|
{
|
||||||
start_.x = v2.x;
|
start_.x = v2.x;
|
||||||
start_.y = v2.y;
|
start_.y = v2.y;
|
||||||
|
@ -445,34 +479,39 @@ private:
|
||||||
v2.x = start_.x;
|
v2.x = start_.x;
|
||||||
v2.y = start_.y;
|
v2.y = start_.y;
|
||||||
}
|
}
|
||||||
angle_a = angle_b;
|
|
||||||
angle_b = std::atan2((v2.y - v1.y), (v2.x - v1.x));
|
|
||||||
joint_angle = explement_reflex_angle(angle_b - angle_a);
|
|
||||||
|
|
||||||
double half_turns = half_turn_segments_ * std::fabs(joint_angle);
|
// Switch the previous vector's direction as the origin has changed
|
||||||
|
v_x1x0 = -v_x1x2;
|
||||||
|
v_y1y0 = -v_y1y2;
|
||||||
|
// Calculate new angle_a
|
||||||
|
angle_a = std::atan2(v_y1y2, v_x1x2);
|
||||||
|
|
||||||
|
// Calculate the new vector
|
||||||
|
v_x1x2 = v2.x - v1.x;
|
||||||
|
v_y1y2 = v2.y - v1.y;
|
||||||
|
// Calculate the new angle_b
|
||||||
|
angle_b = std::atan2(v_y1y2, v_x1x2);
|
||||||
|
|
||||||
|
dot = v_x1x0 * v_x1x2 + v_y1y0 * v_y1y2; // dot product
|
||||||
|
det = v_x1x0 * v_y1y2 - v_y1y0 * v_x1x2; // determinant
|
||||||
|
|
||||||
|
joint_angle = std::atan2(det, dot); // atan2(y, x) or atan2(sin, cos)
|
||||||
|
if (joint_angle < 0) joint_angle = joint_angle + 2 * M_PI;
|
||||||
|
joint_angle = std::fmod(joint_angle, 2 * M_PI);
|
||||||
|
|
||||||
|
if (offset_ > 0.0)
|
||||||
|
{
|
||||||
|
joint_angle = 2 * M_PI - joint_angle;
|
||||||
|
}
|
||||||
|
|
||||||
int bulge_steps = 0;
|
int bulge_steps = 0;
|
||||||
|
|
||||||
if (offset_ < 0.0)
|
if (std::abs(joint_angle) > M_PI)
|
||||||
{
|
{
|
||||||
if (joint_angle > 0.0)
|
curve_angle = explement_reflex_angle(angle_b - angle_a);
|
||||||
{
|
// Bulge steps should be determined by the inverse of the joint angle.
|
||||||
joint_angle = joint_angle - 2 * M_PI;
|
double half_turns = half_turn_segments_ * std::fabs(curve_angle);
|
||||||
}
|
bulge_steps = 1 + static_cast<int>(std::floor(half_turns / M_PI));
|
||||||
else
|
|
||||||
{
|
|
||||||
bulge_steps = 1 + static_cast<int>(std::floor(half_turns / M_PI));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (joint_angle < 0.0)
|
|
||||||
{
|
|
||||||
joint_angle = joint_angle + 2 * M_PI;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bulge_steps = 1 + static_cast<int>(std::floor(half_turns / M_PI));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef MAPNIK_LOG
|
#ifdef MAPNIK_LOG
|
||||||
|
@ -516,10 +555,9 @@ private:
|
||||||
displace(w, v1, angle_a);
|
displace(w, v1, angle_a);
|
||||||
w.cmd = SEG_LINETO;
|
w.cmd = SEG_LINETO;
|
||||||
push_vertex(w);
|
push_vertex(w);
|
||||||
|
|
||||||
for (int s = 0; ++s < bulge_steps;)
|
for (int s = 0; ++s < bulge_steps;)
|
||||||
{
|
{
|
||||||
displace(w, v1, angle_a + (joint_angle * s) / bulge_steps);
|
displace(w, v1, angle_a + (curve_angle * s) / bulge_steps);
|
||||||
w.cmd = SEG_LINETO;
|
w.cmd = SEG_LINETO;
|
||||||
push_vertex(w);
|
push_vertex(w);
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,7 +211,7 @@ void render_offset_placements(placements_list const& placements,
|
||||||
pixel_position const& offset,
|
pixel_position const& offset,
|
||||||
F render_text) {
|
F render_text) {
|
||||||
|
|
||||||
for (glyph_positions_ptr glyphs : placements)
|
for (auto const& glyphs : placements)
|
||||||
{
|
{
|
||||||
// move the glyphs to the correct offset
|
// move the glyphs to the correct offset
|
||||||
pixel_position base_point = glyphs->get_base_point();
|
pixel_position base_point = glyphs->get_base_point();
|
||||||
|
|
|
@ -118,9 +118,9 @@ struct render_marker_symbolizer_visitor
|
||||||
if (clip) // optional clip (default: true)
|
if (clip) // optional clip (default: true)
|
||||||
{
|
{
|
||||||
geometry::geometry_types type = geometry::geometry_type(feature_.get_geometry());
|
geometry::geometry_types type = geometry::geometry_type(feature_.get_geometry());
|
||||||
if (type == geometry::geometry_types::Polygon)
|
if (type == geometry::geometry_types::Polygon || type == geometry::geometry_types::MultiPolygon)
|
||||||
converter.template set<clip_poly_tag>();
|
converter.template set<clip_poly_tag>();
|
||||||
else if (type == geometry::geometry_types::LineString)
|
else if (type == geometry::geometry_types::LineString || type == geometry::geometry_types::MultiLineString)
|
||||||
converter.template set<clip_line_tag>();
|
converter.template set<clip_line_tag>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,9 +223,9 @@ struct render_marker_symbolizer_visitor
|
||||||
if (clip) // optional clip (default: true)
|
if (clip) // optional clip (default: true)
|
||||||
{
|
{
|
||||||
geometry::geometry_types type = geometry::geometry_type(feature_.get_geometry());
|
geometry::geometry_types type = geometry::geometry_type(feature_.get_geometry());
|
||||||
if (type == geometry::geometry_types::Polygon)
|
if (type == geometry::geometry_types::Polygon || type == geometry::geometry_types::MultiPolygon)
|
||||||
converter.template set<clip_poly_tag>();
|
converter.template set<clip_poly_tag>();
|
||||||
else if (type == geometry::geometry_types::LineString)
|
else if (type == geometry::geometry_types::LineString || type == geometry::geometry_types::MultiLineString)
|
||||||
converter.template set<clip_line_tag>();
|
converter.template set<clip_line_tag>();
|
||||||
}
|
}
|
||||||
converter.template set<transform_tag>(); //always transform
|
converter.template set<transform_tag>(); //always transform
|
||||||
|
|
|
@ -44,7 +44,7 @@ extern "C"
|
||||||
namespace mapnik
|
namespace mapnik
|
||||||
{
|
{
|
||||||
|
|
||||||
class font_face : util::noncopyable
|
class MAPNIK_DECL font_face : util::noncopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
font_face(FT_Face face);
|
font_face(FT_Face face);
|
||||||
|
|
|
@ -82,7 +82,6 @@ public:
|
||||||
void set_marker(marker_info_ptr marker, pixel_position const& marker_pos);
|
void set_marker(marker_info_ptr marker, pixel_position const& marker_pos);
|
||||||
marker_info_ptr get_marker() const;
|
marker_info_ptr get_marker() const;
|
||||||
pixel_position const& marker_pos() const;
|
pixel_position const& marker_pos() const;
|
||||||
box2d<double> const & bbox() const;
|
|
||||||
private:
|
private:
|
||||||
std::vector<glyph_position> data_;
|
std::vector<glyph_position> data_;
|
||||||
pixel_position base_point_;
|
pixel_position base_point_;
|
||||||
|
@ -90,7 +89,7 @@ private:
|
||||||
pixel_position marker_pos_;
|
pixel_position marker_pos_;
|
||||||
box2d<double> bbox_;
|
box2d<double> bbox_;
|
||||||
};
|
};
|
||||||
using glyph_positions_ptr = std::shared_ptr<glyph_positions>;
|
using glyph_positions_ptr = std::unique_ptr<glyph_positions>;
|
||||||
|
|
||||||
using placements_list = std::list<glyph_positions_ptr>;
|
using placements_list = std::list<glyph_positions_ptr>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,9 @@
|
||||||
#include <mapnik/text/text_line.hpp>
|
#include <mapnik/text/text_line.hpp>
|
||||||
#include <mapnik/text/face.hpp>
|
#include <mapnik/text/face.hpp>
|
||||||
#include <mapnik/text/font_feature_settings.hpp>
|
#include <mapnik/text/font_feature_settings.hpp>
|
||||||
|
#include <mapnik/text/itemizer.hpp>
|
||||||
#include <mapnik/safe_cast.hpp>
|
#include <mapnik/safe_cast.hpp>
|
||||||
|
#include <mapnik/font_engine_freetype.hpp>
|
||||||
|
|
||||||
// stl
|
// stl
|
||||||
#include <list>
|
#include <list>
|
||||||
|
@ -38,6 +40,9 @@
|
||||||
#include <harfbuzz/hb.h>
|
#include <harfbuzz/hb.h>
|
||||||
#include <harfbuzz/hb-ft.h>
|
#include <harfbuzz/hb-ft.h>
|
||||||
|
|
||||||
|
// icu
|
||||||
|
#include <unicode/uscript.h>
|
||||||
|
|
||||||
namespace mapnik
|
namespace mapnik
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,10 @@
|
||||||
#include <mapnik/text/text_properties.hpp>
|
#include <mapnik/text/text_properties.hpp>
|
||||||
#include <mapnik/text/text_line.hpp>
|
#include <mapnik/text/text_line.hpp>
|
||||||
#include <mapnik/text/face.hpp>
|
#include <mapnik/text/face.hpp>
|
||||||
|
#include <mapnik/text/font_feature_settings.hpp>
|
||||||
|
#include <mapnik/text/itemizer.hpp>
|
||||||
|
#include <mapnik/safe_cast.hpp>
|
||||||
|
#include <mapnik/font_engine_freetype.hpp>
|
||||||
#include <mapnik/debug.hpp>
|
#include <mapnik/debug.hpp>
|
||||||
|
|
||||||
// stl
|
// stl
|
||||||
|
@ -59,12 +63,11 @@ static void shape_text(text_line & line,
|
||||||
UErrorCode err = U_ZERO_ERROR;
|
UErrorCode err = U_ZERO_ERROR;
|
||||||
mapnik::value_unicode_string shaped;
|
mapnik::value_unicode_string shaped;
|
||||||
mapnik::value_unicode_string reordered;
|
mapnik::value_unicode_string reordered;
|
||||||
unsigned char_index = 0;
|
|
||||||
|
|
||||||
for (auto const& text_item : list)
|
for (auto const& text_item : list)
|
||||||
{
|
{
|
||||||
face_set_ptr face_set = font_manager.get_face_set(text_item.format->face_name, text_item.format->fontset);
|
face_set_ptr face_set = font_manager.get_face_set(text_item.format_->face_name, text_item.format_->fontset);
|
||||||
double size = text_item.format->text_size * scale_factor;
|
double size = text_item.format_->text_size * scale_factor;
|
||||||
face_set->set_unscaled_character_sizes();
|
face_set->set_unscaled_character_sizes();
|
||||||
for (auto const& face : *face_set)
|
for (auto const& face : *face_set)
|
||||||
{
|
{
|
||||||
|
@ -87,33 +90,35 @@ static void shape_text(text_line & line,
|
||||||
std::size_t num_chars = static_cast<std::size_t>(num_char);
|
std::size_t num_chars = static_cast<std::size_t>(num_char);
|
||||||
shaped.releaseBuffer(length);
|
shaped.releaseBuffer(length);
|
||||||
bool shaped_status = true;
|
bool shaped_status = true;
|
||||||
|
double max_glyph_height = 0;
|
||||||
if (U_SUCCESS(err) && (num_chars == length))
|
if (U_SUCCESS(err) && (num_chars == length))
|
||||||
{
|
{
|
||||||
|
unsigned char_index = 0;
|
||||||
U_NAMESPACE_QUALIFIER StringCharacterIterator iter(shaped);
|
U_NAMESPACE_QUALIFIER StringCharacterIterator iter(shaped);
|
||||||
for (iter.setToStart(); iter.hasNext();)
|
for (iter.setToStart(); iter.hasNext();)
|
||||||
{
|
{
|
||||||
UChar ch = iter.nextPostInc();
|
UChar ch = iter.nextPostInc();
|
||||||
glyph_info tmp;
|
auto codepoint = FT_Get_Char_Index(face->get_face(), ch);
|
||||||
tmp.glyph_index = FT_Get_Char_Index(face->get_face(), ch);
|
glyph_info g(codepoint,char_index,text_item.format_);
|
||||||
tmp.offset.clear();
|
//g.offset.clear();
|
||||||
if (tmp.glyph_index == 0)
|
if (g.glyph_index == 0)
|
||||||
{
|
{
|
||||||
shaped_status = false;
|
shaped_status = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (face->glyph_dimensions(tmp))
|
if (face->glyph_dimensions(g))
|
||||||
{
|
{
|
||||||
tmp.char_index = char_index;
|
g.face = face;
|
||||||
tmp.face = face;
|
g.scale_multiplier = size / face->get_face()->units_per_EM;
|
||||||
tmp.format = text_item.format;
|
double tmp_height = g.height();
|
||||||
tmp.scale_multiplier = size / face->get_face()->units_per_EM;
|
if (tmp_height > max_glyph_height) max_glyph_height = tmp_height;
|
||||||
width_map[char_index++] += tmp.advance();
|
width_map[char_index++] += g.advance();
|
||||||
line.add_glyph(tmp, scale_factor);
|
line.add_glyph(std::move(g), scale_factor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!shaped_status) continue;
|
if (!shaped_status) continue;
|
||||||
line.update_max_char_height(face->get_char_height(size));
|
line.update_max_char_height(max_glyph_height);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include <mapnik/text/evaluated_format_properties_ptr.hpp>
|
#include <mapnik/text/evaluated_format_properties_ptr.hpp>
|
||||||
#include <mapnik/value_types.hpp>
|
#include <mapnik/value_types.hpp>
|
||||||
#include <mapnik/util/noncopyable.hpp>
|
#include <mapnik/util/noncopyable.hpp>
|
||||||
|
#include <mapnik/config.hpp>
|
||||||
|
|
||||||
// stl
|
// stl
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -41,7 +42,7 @@
|
||||||
namespace mapnik
|
namespace mapnik
|
||||||
{
|
{
|
||||||
|
|
||||||
struct text_item : util::noncopyable
|
struct MAPNIK_DECL text_item : util::noncopyable
|
||||||
{
|
{
|
||||||
text_item(unsigned s,
|
text_item(unsigned s,
|
||||||
unsigned e,
|
unsigned e,
|
||||||
|
@ -71,7 +72,7 @@ struct text_item : util::noncopyable
|
||||||
// - format
|
// - format
|
||||||
// - script (http://en.wikipedia.org/wiki/Scripts_in_Unicode)
|
// - script (http://en.wikipedia.org/wiki/Scripts_in_Unicode)
|
||||||
|
|
||||||
class text_itemizer
|
class MAPNIK_DECL text_itemizer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
text_itemizer();
|
text_itemizer();
|
||||||
|
|
|
@ -75,7 +75,7 @@ private:
|
||||||
// Checks for collision.
|
// Checks for collision.
|
||||||
bool collision(box2d<double> const& box, const value_unicode_string &repeat_key, bool line_placement) const;
|
bool collision(box2d<double> const& box, const value_unicode_string &repeat_key, bool line_placement) const;
|
||||||
// Adds marker to glyph_positions and to collision detector. Returns false if there is a collision.
|
// Adds marker to glyph_positions and to collision detector. Returns false if there is a collision.
|
||||||
bool add_marker(glyph_positions_ptr glyphs, pixel_position const& pos) const;
|
bool add_marker(glyph_positions_ptr & glyphs, pixel_position const& pos) const;
|
||||||
// Maps upright==auto, left-only and right-only to left,right to simplify processing.
|
// Maps upright==auto, left-only and right-only to left,right to simplify processing.
|
||||||
// angle = angle of at start of line (to estimate best option for upright==auto)
|
// angle = angle of at start of line (to estimate best option for upright==auto)
|
||||||
text_upright_e simplify_upright(text_upright_e upright, double angle) const;
|
text_upright_e simplify_upright(text_upright_e upright, double angle) const;
|
||||||
|
|
|
@ -17,9 +17,9 @@
|
||||||
#ifndef __SCRPTRUN_H
|
#ifndef __SCRPTRUN_H
|
||||||
#define __SCRPTRUN_H
|
#define __SCRPTRUN_H
|
||||||
|
|
||||||
#include "unicode/utypes.h"
|
#include <unicode/utypes.h>
|
||||||
#include "unicode/uobject.h"
|
#include <unicode/uobject.h>
|
||||||
#include "unicode/uscript.h"
|
#include <unicode/uscript.h>
|
||||||
|
|
||||||
struct ScriptRecord
|
struct ScriptRecord
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
//stl
|
//stl
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <mapnik/util/noncopyable.hpp>
|
#include <mapnik/util/noncopyable.hpp>
|
||||||
|
#include <mapnik/config.hpp>
|
||||||
|
|
||||||
namespace mapnik
|
namespace mapnik
|
||||||
{
|
{
|
||||||
|
@ -35,7 +36,7 @@ struct glyph_info;
|
||||||
// It can be used for rendering but no text processing (like line breaking)
|
// It can be used for rendering but no text processing (like line breaking)
|
||||||
// should be done!
|
// should be done!
|
||||||
|
|
||||||
class text_line : util::noncopyable
|
class MAPNIK_DECL text_line : util::noncopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using glyph_vector = std::vector<glyph_info>;
|
using glyph_vector = std::vector<glyph_info>;
|
||||||
|
|
|
@ -29,7 +29,6 @@
|
||||||
|
|
||||||
// stl
|
// stl
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
// icu
|
// icu
|
||||||
#include <unicode/unistr.h>
|
#include <unicode/unistr.h>
|
||||||
|
@ -52,7 +51,6 @@ struct value_hasher
|
||||||
|
|
||||||
std::size_t operator() (value_unicode_string const& val) const
|
std::size_t operator() (value_unicode_string const& val) const
|
||||||
{
|
{
|
||||||
assert(val.hashCode() > 0);
|
|
||||||
return static_cast<std::size_t>(val.hashCode());
|
return static_cast<std::size_t>(val.hashCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
#ifndef MAPNIK_VERSION_HPP
|
#ifndef MAPNIK_VERSION_HPP
|
||||||
#define MAPNIK_VERSION_HPP
|
#define MAPNIK_VERSION_HPP
|
||||||
|
|
||||||
#define MAPNIK_VERSION_IS_RELEASE 0
|
#define MAPNIK_VERSION_IS_RELEASE 1
|
||||||
|
|
||||||
#define MAPNIK_MAJOR_VERSION 3
|
#define MAPNIK_MAJOR_VERSION 3
|
||||||
#define MAPNIK_MINOR_VERSION 0
|
#define MAPNIK_MINOR_VERSION 0
|
||||||
|
|
|
@ -218,7 +218,7 @@ void geojson_datasource::initialise_index(Iterator start, Iterator end)
|
||||||
// parse first feature to extract attributes schema.
|
// parse first feature to extract attributes schema.
|
||||||
// NOTE: this doesn't yield correct answer for geoJSON in general, just an indication
|
// NOTE: this doesn't yield correct answer for geoJSON in general, just an indication
|
||||||
Iterator itr2 = start + geometry_index.first;
|
Iterator itr2 = start + geometry_index.first;
|
||||||
Iterator end2 = itr + geometry_index.second;
|
Iterator end2 = itr2 + geometry_index.second;
|
||||||
mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
|
mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
|
||||||
mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
|
mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
|
||||||
if (!boost::spirit::qi::phrase_parse(itr2, end2, (geojson_datasource_static_feature_grammar)(boost::phoenix::ref(*feature)), space))
|
if (!boost::spirit::qi::phrase_parse(itr2, end2, (geojson_datasource_static_feature_grammar)(boost::phoenix::ref(*feature)), space))
|
||||||
|
|
|
@ -120,7 +120,7 @@ struct thunk_renderer<image_rgba8>
|
||||||
render_offset_placements(
|
render_offset_placements(
|
||||||
thunk.placements_,
|
thunk.placements_,
|
||||||
offset_,
|
offset_,
|
||||||
[&] (glyph_positions_ptr glyphs)
|
[&] (glyph_positions_ptr const& glyphs)
|
||||||
{
|
{
|
||||||
marker_info_ptr mark = glyphs->get_marker();
|
marker_info_ptr mark = glyphs->get_marker();
|
||||||
if (mark)
|
if (mark)
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include <mapnik/vertex_processor.hpp>
|
#include <mapnik/vertex_processor.hpp>
|
||||||
#include <mapnik/renderer_common/clipping_extent.hpp>
|
#include <mapnik/renderer_common/clipping_extent.hpp>
|
||||||
#include <mapnik/renderer_common/apply_vertex_converter.hpp>
|
#include <mapnik/renderer_common/apply_vertex_converter.hpp>
|
||||||
|
#include <mapnik/geometry_type.hpp>
|
||||||
// agg
|
// agg
|
||||||
#include "agg_basics.h"
|
#include "agg_basics.h"
|
||||||
#include "agg_rendering_buffer.h"
|
#include "agg_rendering_buffer.h"
|
||||||
|
@ -164,12 +165,19 @@ void agg_renderer<T0,T1>::process(line_symbolizer const& sym,
|
||||||
rasterizer_type ras(ren);
|
rasterizer_type ras(ren);
|
||||||
set_join_caps_aa(sym, ras, feature, common_.vars_);
|
set_join_caps_aa(sym, ras, feature, common_.vars_);
|
||||||
|
|
||||||
using vertex_converter_type = vertex_converter<clip_line_tag, transform_tag,
|
using vertex_converter_type = vertex_converter<clip_line_tag, clip_poly_tag, transform_tag,
|
||||||
affine_transform_tag,
|
affine_transform_tag,
|
||||||
simplify_tag, smooth_tag,
|
simplify_tag, smooth_tag,
|
||||||
offset_transform_tag>;
|
offset_transform_tag>;
|
||||||
vertex_converter_type converter(clip_box,sym,common_.t_,prj_trans,tr,feature,common_.vars_,common_.scale_factor_);
|
vertex_converter_type converter(clip_box,sym,common_.t_,prj_trans,tr,feature,common_.vars_,common_.scale_factor_);
|
||||||
if (clip) converter.set<clip_line_tag>(); // optional clip (default: true)
|
if (clip)
|
||||||
|
{
|
||||||
|
geometry::geometry_types type = geometry::geometry_type(feature.get_geometry());
|
||||||
|
if (type == geometry::geometry_types::Polygon || type == geometry::geometry_types::MultiPolygon)
|
||||||
|
converter.template set<clip_poly_tag>();
|
||||||
|
else if (type == geometry::geometry_types::LineString || type == geometry::geometry_types::MultiLineString)
|
||||||
|
converter.template set<clip_line_tag>();
|
||||||
|
}
|
||||||
converter.set<transform_tag>(); // always transform
|
converter.set<transform_tag>(); // always transform
|
||||||
if (std::fabs(offset) > 0.0) converter.set<offset_transform_tag>(); // parallel offset
|
if (std::fabs(offset) > 0.0) converter.set<offset_transform_tag>(); // parallel offset
|
||||||
converter.set<affine_transform_tag>(); // optional affine transform
|
converter.set<affine_transform_tag>(); // optional affine transform
|
||||||
|
@ -183,14 +191,20 @@ void agg_renderer<T0,T1>::process(line_symbolizer const& sym,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using vertex_converter_type = vertex_converter<clip_line_tag, transform_tag,
|
using vertex_converter_type = vertex_converter<clip_line_tag, clip_poly_tag, transform_tag,
|
||||||
affine_transform_tag,
|
affine_transform_tag,
|
||||||
simplify_tag, smooth_tag,
|
simplify_tag, smooth_tag,
|
||||||
offset_transform_tag,
|
offset_transform_tag,
|
||||||
dash_tag, stroke_tag>;
|
dash_tag, stroke_tag>;
|
||||||
vertex_converter_type converter(clip_box, sym,common_.t_,prj_trans,tr,feature,common_.vars_,common_.scale_factor_);
|
vertex_converter_type converter(clip_box, sym,common_.t_,prj_trans,tr,feature,common_.vars_,common_.scale_factor_);
|
||||||
|
if (clip)
|
||||||
if (clip) converter.set<clip_line_tag>(); // optional clip (default: true)
|
{
|
||||||
|
geometry::geometry_types type = geometry::geometry_type(feature.get_geometry());
|
||||||
|
if (type == geometry::geometry_types::Polygon || type == geometry::geometry_types::MultiPolygon)
|
||||||
|
converter.template set<clip_poly_tag>();
|
||||||
|
else if (type == geometry::geometry_types::LineString || type == geometry::geometry_types::MultiLineString)
|
||||||
|
converter.template set<clip_line_tag>();
|
||||||
|
}
|
||||||
converter.set<transform_tag>(); // always transform
|
converter.set<transform_tag>(); // always transform
|
||||||
if (std::fabs(offset) > 0.0) converter.set<offset_transform_tag>(); // parallel offset
|
if (std::fabs(offset) > 0.0) converter.set<offset_transform_tag>(); // parallel offset
|
||||||
converter.set<affine_transform_tag>(); // optional affine transform
|
converter.set<affine_transform_tag>(); // optional affine transform
|
||||||
|
|
|
@ -60,7 +60,7 @@ void agg_renderer<T0,T1>::process(shield_symbolizer const& sym,
|
||||||
double opacity = get<double>(sym,keys::opacity, feature, common_.vars_, 1.0);
|
double opacity = get<double>(sym,keys::opacity, feature, common_.vars_, 1.0);
|
||||||
|
|
||||||
placements_list const& placements = helper.get();
|
placements_list const& placements = helper.get();
|
||||||
for (glyph_positions_ptr glyphs : placements)
|
for (auto const& glyphs : placements)
|
||||||
{
|
{
|
||||||
marker_info_ptr mark = glyphs->get_marker();
|
marker_info_ptr mark = glyphs->get_marker();
|
||||||
if (mark)
|
if (mark)
|
||||||
|
|
|
@ -67,7 +67,7 @@ void agg_renderer<T0,T1>::process(text_symbolizer const& sym,
|
||||||
}
|
}
|
||||||
|
|
||||||
placements_list const& placements = helper.get();
|
placements_list const& placements = helper.get();
|
||||||
for (glyph_positions_ptr glyphs : placements)
|
for (auto const& glyphs : placements)
|
||||||
{
|
{
|
||||||
ren.render(*glyphs);
|
ren.render(*glyphs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,8 @@
|
||||||
#include <mapnik/text/face.hpp>
|
#include <mapnik/text/face.hpp>
|
||||||
|
|
||||||
#include <cairo-ft.h>
|
#include <cairo-ft.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <valarray>
|
|
||||||
namespace mapnik {
|
namespace mapnik {
|
||||||
|
|
||||||
cairo_face::cairo_face(std::shared_ptr<font_library> const& library, face_ptr const& face)
|
cairo_face::cairo_face(std::shared_ptr<font_library> const& library, face_ptr const& face)
|
||||||
|
@ -227,17 +227,13 @@ void cairo_context::set_line_width(double width)
|
||||||
|
|
||||||
void cairo_context::set_dash(dash_array const& dashes, double scale_factor)
|
void cairo_context::set_dash(dash_array const& dashes, double scale_factor)
|
||||||
{
|
{
|
||||||
std::valarray<double> d(dashes.size() * 2);
|
std::vector<double> d;
|
||||||
dash_array::const_iterator itr = dashes.begin();
|
d.reserve(dashes.size() * 2);
|
||||||
dash_array::const_iterator end = dashes.end();
|
for (auto const& dash : dashes)
|
||||||
std::size_t index = 0;
|
|
||||||
|
|
||||||
for (; itr != end; ++itr)
|
|
||||||
{
|
{
|
||||||
d[index++] = itr->first * scale_factor;
|
d.emplace_back(dash.first * scale_factor);
|
||||||
d[index++] = itr->second * scale_factor;
|
d.emplace_back(dash.second * scale_factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
cairo_set_dash(cairo_.get() , &d[0], static_cast<int>(d.size()), 0/*offset*/);
|
cairo_set_dash(cairo_.get() , &d[0], static_cast<int>(d.size()), 0/*offset*/);
|
||||||
check_object_status_and_throw_exception(*this);
|
check_object_status_and_throw_exception(*this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ struct thunk_renderer
|
||||||
render_offset_placements(
|
render_offset_placements(
|
||||||
thunk.placements_,
|
thunk.placements_,
|
||||||
offset_,
|
offset_,
|
||||||
[&] (glyph_positions_ptr glyphs)
|
[&] (glyph_positions_ptr const& glyphs)
|
||||||
{
|
{
|
||||||
marker_info_ptr mark = glyphs->get_marker();
|
marker_info_ptr mark = glyphs->get_marker();
|
||||||
if (mark)
|
if (mark)
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include <mapnik/vertex_converters.hpp>
|
#include <mapnik/vertex_converters.hpp>
|
||||||
#include <mapnik/vertex_processor.hpp>
|
#include <mapnik/vertex_processor.hpp>
|
||||||
#include <mapnik/renderer_common/apply_vertex_converter.hpp>
|
#include <mapnik/renderer_common/apply_vertex_converter.hpp>
|
||||||
|
#include <mapnik/geometry_type.hpp>
|
||||||
|
|
||||||
namespace mapnik
|
namespace mapnik
|
||||||
{
|
{
|
||||||
|
@ -82,6 +83,7 @@ void cairo_renderer<T>::process(line_symbolizer const& sym,
|
||||||
clipping_extent.pad(padding);
|
clipping_extent.pad(padding);
|
||||||
}
|
}
|
||||||
using vertex_converter_type = vertex_converter<clip_line_tag,
|
using vertex_converter_type = vertex_converter<clip_line_tag,
|
||||||
|
clip_poly_tag,
|
||||||
transform_tag,
|
transform_tag,
|
||||||
affine_transform_tag,
|
affine_transform_tag,
|
||||||
simplify_tag, smooth_tag,
|
simplify_tag, smooth_tag,
|
||||||
|
@ -89,7 +91,14 @@ void cairo_renderer<T>::process(line_symbolizer const& sym,
|
||||||
|
|
||||||
vertex_converter_type converter(clipping_extent,sym,common_.t_,prj_trans,tr,feature,common_.vars_,common_.scale_factor_);
|
vertex_converter_type converter(clipping_extent,sym,common_.t_,prj_trans,tr,feature,common_.vars_,common_.scale_factor_);
|
||||||
|
|
||||||
if (clip) converter.set<clip_line_tag>(); // optional clip (default: true)
|
if (clip)
|
||||||
|
{
|
||||||
|
geometry::geometry_types type = geometry::geometry_type(feature.get_geometry());
|
||||||
|
if (type == geometry::geometry_types::Polygon || type == geometry::geometry_types::MultiPolygon)
|
||||||
|
converter.template set<clip_poly_tag>();
|
||||||
|
else if (type == geometry::geometry_types::LineString || type == geometry::geometry_types::MultiLineString)
|
||||||
|
converter.template set<clip_line_tag>();
|
||||||
|
}
|
||||||
converter.set<transform_tag>(); // always transform
|
converter.set<transform_tag>(); // always transform
|
||||||
if (std::fabs(offset) > 0.0) converter.set<offset_transform_tag>(); // parallel offset
|
if (std::fabs(offset) > 0.0) converter.set<offset_transform_tag>(); // parallel offset
|
||||||
converter.set<affine_transform_tag>(); // optional affine transform
|
converter.set<affine_transform_tag>(); // optional affine transform
|
||||||
|
|
|
@ -55,7 +55,7 @@ void cairo_renderer<T>::process(shield_symbolizer const& sym,
|
||||||
double opacity = get<double>(sym,keys::opacity,feature, common_.vars_, 1.0);
|
double opacity = get<double>(sym,keys::opacity,feature, common_.vars_, 1.0);
|
||||||
|
|
||||||
placements_list const &placements = helper.get();
|
placements_list const &placements = helper.get();
|
||||||
for (glyph_positions_ptr glyphs : placements)
|
for (auto const& glyphs : placements)
|
||||||
{
|
{
|
||||||
marker_info_ptr mark = glyphs->get_marker();
|
marker_info_ptr mark = glyphs->get_marker();
|
||||||
if (mark) {
|
if (mark) {
|
||||||
|
@ -93,7 +93,7 @@ void cairo_renderer<T>::process(text_symbolizer const& sym,
|
||||||
composite_mode_e halo_comp_op = get<composite_mode_e>(sym, keys::halo_comp_op, feature, common_.vars_, src_over);
|
composite_mode_e halo_comp_op = get<composite_mode_e>(sym, keys::halo_comp_op, feature, common_.vars_, src_over);
|
||||||
|
|
||||||
placements_list const& placements = helper.get();
|
placements_list const& placements = helper.get();
|
||||||
for (glyph_positions_ptr glyphs : placements)
|
for (auto const& glyphs : placements)
|
||||||
{
|
{
|
||||||
context_.add_text(*glyphs, face_manager_, comp_op, halo_comp_op, common_.scale_factor_);
|
context_.add_text(*glyphs, face_manager_, comp_op, halo_comp_op, common_.scale_factor_);
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ struct thunk_renderer
|
||||||
render_offset_placements(
|
render_offset_placements(
|
||||||
thunk.placements_,
|
thunk.placements_,
|
||||||
offset_,
|
offset_,
|
||||||
[&] (glyph_positions_ptr glyphs)
|
[&] (glyph_positions_ptr const& glyphs)
|
||||||
{
|
{
|
||||||
marker_info_ptr mark = glyphs->get_marker();
|
marker_info_ptr mark = glyphs->get_marker();
|
||||||
if (mark)
|
if (mark)
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include <mapnik/vertex_converters.hpp>
|
#include <mapnik/vertex_converters.hpp>
|
||||||
#include <mapnik/vertex_processor.hpp>
|
#include <mapnik/vertex_processor.hpp>
|
||||||
#include <mapnik/renderer_common/apply_vertex_converter.hpp>
|
#include <mapnik/renderer_common/apply_vertex_converter.hpp>
|
||||||
|
#include <mapnik/geometry_type.hpp>
|
||||||
// agg
|
// agg
|
||||||
#include "agg_rasterizer_scanline_aa.h"
|
#include "agg_rasterizer_scanline_aa.h"
|
||||||
#include "agg_renderer_scanline.h"
|
#include "agg_renderer_scanline.h"
|
||||||
|
@ -89,14 +90,21 @@ void grid_renderer<T>::process(line_symbolizer const& sym,
|
||||||
padding *= common_.scale_factor_;
|
padding *= common_.scale_factor_;
|
||||||
clipping_extent.pad(padding);
|
clipping_extent.pad(padding);
|
||||||
}
|
}
|
||||||
using vertex_converter_type = vertex_converter<clip_line_tag, transform_tag,
|
using vertex_converter_type = vertex_converter<clip_line_tag, clip_poly_tag, transform_tag,
|
||||||
affine_transform_tag,
|
affine_transform_tag,
|
||||||
simplify_tag, smooth_tag,
|
simplify_tag, smooth_tag,
|
||||||
offset_transform_tag,
|
offset_transform_tag,
|
||||||
dash_tag, stroke_tag>;
|
dash_tag, stroke_tag>;
|
||||||
|
|
||||||
vertex_converter_type converter(clipping_extent,sym,common_.t_,prj_trans,tr,feature,common_.vars_,common_.scale_factor_);
|
vertex_converter_type converter(clipping_extent,sym,common_.t_,prj_trans,tr,feature,common_.vars_,common_.scale_factor_);
|
||||||
if (clip) converter.set<clip_line_tag>(); // optional clip (default: true)
|
if (clip)
|
||||||
|
{
|
||||||
|
geometry::geometry_types type = geometry::geometry_type(feature.get_geometry());
|
||||||
|
if (type == geometry::geometry_types::Polygon || type == geometry::geometry_types::MultiPolygon)
|
||||||
|
converter.template set<clip_poly_tag>();
|
||||||
|
else if (type == geometry::geometry_types::LineString || type == geometry::geometry_types::MultiLineString)
|
||||||
|
converter.template set<clip_line_tag>();
|
||||||
|
}
|
||||||
converter.set<transform_tag>(); // always transform
|
converter.set<transform_tag>(); // always transform
|
||||||
if (std::fabs(offset) > 0.0) converter.set<offset_transform_tag>(); // parallel offset
|
if (std::fabs(offset) > 0.0) converter.set<offset_transform_tag>(); // parallel offset
|
||||||
converter.set<affine_transform_tag>(); // optional affine transform
|
converter.set<affine_transform_tag>(); // optional affine transform
|
||||||
|
|
|
@ -64,7 +64,7 @@ void grid_renderer<T>::process(shield_symbolizer const& sym,
|
||||||
placements_list const& placements = helper.get();
|
placements_list const& placements = helper.get();
|
||||||
value_integer feature_id = feature.id();
|
value_integer feature_id = feature.id();
|
||||||
|
|
||||||
for (glyph_positions_ptr glyphs : placements)
|
for (auto const& glyphs : placements)
|
||||||
{
|
{
|
||||||
marker_info_ptr mark = glyphs->get_marker();
|
marker_info_ptr mark = glyphs->get_marker();
|
||||||
if (mark)
|
if (mark)
|
||||||
|
|
|
@ -66,7 +66,7 @@ void grid_renderer<T>::process(text_symbolizer const& sym,
|
||||||
placements_list const& placements = helper.get();
|
placements_list const& placements = helper.get();
|
||||||
value_integer feature_id = feature.id();
|
value_integer feature_id = feature.id();
|
||||||
|
|
||||||
for (glyph_positions_ptr glyphs : placements)
|
for (auto const& glyphs : placements)
|
||||||
{
|
{
|
||||||
ren.render(*glyphs, feature_id);
|
ren.render(*glyphs, feature_id);
|
||||||
placement_found = true;
|
placement_found = true;
|
||||||
|
|
|
@ -368,6 +368,13 @@ template MAPNIK_DECL void save_to_file<image_view_any> (image_view_any const&,
|
||||||
std::string const&,
|
std::string const&,
|
||||||
rgba_palette const& palette);
|
rgba_palette const& palette);
|
||||||
|
|
||||||
|
template MAPNIK_DECL std::string save_to_string<image_view_rgba8> (image_view_rgba8 const&,
|
||||||
|
std::string const&);
|
||||||
|
|
||||||
|
template MAPNIK_DECL std::string save_to_string<image_view_rgba8> (image_view_rgba8 const&,
|
||||||
|
std::string const&,
|
||||||
|
rgba_palette const& palette);
|
||||||
|
|
||||||
template MAPNIK_DECL std::string save_to_string<image_view_any> (image_view_any const&,
|
template MAPNIK_DECL std::string save_to_string<image_view_any> (image_view_any const&,
|
||||||
std::string const&);
|
std::string const&);
|
||||||
|
|
||||||
|
|
|
@ -183,11 +183,27 @@ bool placement_finder::find_point_placement(pixel_position const& pos)
|
||||||
// add_marker first checks for collision and then updates the detector.
|
// add_marker first checks for collision and then updates the detector.
|
||||||
if (has_marker_ && !add_marker(glyphs, pos)) return false;
|
if (has_marker_ && !add_marker(glyphs, pos)) return false;
|
||||||
|
|
||||||
|
box2d<double> label_box;
|
||||||
|
bool first = true;
|
||||||
for (auto const& label : labels)
|
for (auto const& label : labels)
|
||||||
{
|
{
|
||||||
detector_.insert(std::get<0>(label), layouts_.text(), std::get<1>(label));
|
auto const& box = std::get<0>(label);
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
label_box = box;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
label_box.expand_to_include(box);
|
||||||
|
}
|
||||||
|
detector_.insert(box, layouts_.text(), std::get<1>(label));
|
||||||
|
}
|
||||||
|
// do not render text off the canvas
|
||||||
|
if (extent_.intersects(label_box))
|
||||||
|
{
|
||||||
|
placements_.push_back(std::move(glyphs));
|
||||||
}
|
}
|
||||||
placements_.push_back(glyphs);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -201,7 +217,7 @@ bool placement_finder::single_line_placement(vertex_cache &pp, text_upright_e or
|
||||||
vertex_cache::scoped_state begin(pp);
|
vertex_cache::scoped_state begin(pp);
|
||||||
text_upright_e real_orientation = simplify_upright(orientation, pp.angle());
|
text_upright_e real_orientation = simplify_upright(orientation, pp.angle());
|
||||||
|
|
||||||
glyph_positions_ptr glyphs = std::make_shared<glyph_positions>();
|
glyph_positions_ptr glyphs = std::make_unique<glyph_positions>();
|
||||||
std::vector<box2d<double> > bboxes;
|
std::vector<box2d<double> > bboxes;
|
||||||
glyphs->reserve(layouts_.glyphs_count());
|
glyphs->reserve(layouts_.glyphs_count());
|
||||||
bboxes.reserve(layouts_.glyphs_count());
|
bboxes.reserve(layouts_.glyphs_count());
|
||||||
|
@ -214,7 +230,8 @@ bool placement_finder::single_line_placement(vertex_cache &pp, text_upright_e or
|
||||||
pixel_position align_offset = layout.alignment_offset();
|
pixel_position align_offset = layout.alignment_offset();
|
||||||
pixel_position const& layout_displacement = layout.displacement();
|
pixel_position const& layout_displacement = layout.displacement();
|
||||||
double sign = (real_orientation == UPRIGHT_LEFT) ? -1 : 1;
|
double sign = (real_orientation == UPRIGHT_LEFT) ? -1 : 1;
|
||||||
double offset = layout_displacement.y + 0.5 * sign * layout.height();
|
//double offset = 0 - (layout_displacement.y + 0.5 * sign * layout.height());
|
||||||
|
double offset = layout_displacement.y - 0.5 * sign * layout.height();
|
||||||
double adjust_character_spacing = .0;
|
double adjust_character_spacing = .0;
|
||||||
double layout_width = layout.width();
|
double layout_width = layout.width();
|
||||||
bool adjust = layout.horizontal_alignment() == H_ADJUST;
|
bool adjust = layout.horizontal_alignment() == H_ADJUST;
|
||||||
|
@ -233,7 +250,7 @@ bool placement_finder::single_line_placement(vertex_cache &pp, text_upright_e or
|
||||||
{
|
{
|
||||||
// Only subtract half the line height here and half at the end because text is automatically
|
// Only subtract half the line height here and half at the end because text is automatically
|
||||||
// centered on the line
|
// centered on the line
|
||||||
offset -= sign * line.height()/2;
|
offset += sign * line.height()/2;
|
||||||
vertex_cache & off_pp = pp.get_offseted(offset, sign * layout_width);
|
vertex_cache & off_pp = pp.get_offseted(offset, sign * layout_width);
|
||||||
vertex_cache::scoped_state off_state(off_pp); // TODO: Remove this when a clean implementation in vertex_cache::get_offseted is done
|
vertex_cache::scoped_state off_state(off_pp); // TODO: Remove this when a clean implementation in vertex_cache::get_offseted is done
|
||||||
double line_width = adjust ? (line.glyphs_width() + line.space_count() * adjust_character_spacing) : line.width();
|
double line_width = adjust ? (line.glyphs_width() + line.space_count() * adjust_character_spacing) : line.width();
|
||||||
|
@ -301,7 +318,7 @@ bool placement_finder::single_line_placement(vertex_cache &pp, text_upright_e or
|
||||||
glyphs->emplace_back(glyph, pos, rot);
|
glyphs->emplace_back(glyph, pos, rot);
|
||||||
}
|
}
|
||||||
// See comment above
|
// See comment above
|
||||||
offset -= sign * line.height()/2;
|
offset += sign * line.height()/2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,11 +343,26 @@ bool placement_finder::single_line_placement(vertex_cache &pp, text_upright_e or
|
||||||
return single_line_placement(pp, real_orientation == UPRIGHT_RIGHT ? UPRIGHT_LEFT : UPRIGHT_RIGHT);
|
return single_line_placement(pp, real_orientation == UPRIGHT_RIGHT ? UPRIGHT_LEFT : UPRIGHT_RIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
box2d<double> label_box;
|
||||||
|
bool first = true;
|
||||||
for (box2d<double> const& box : bboxes)
|
for (box2d<double> const& box : bboxes)
|
||||||
{
|
{
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
label_box = box;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
label_box.expand_to_include(box);
|
||||||
|
}
|
||||||
detector_.insert(box, layouts_.text());
|
detector_.insert(box, layouts_.text());
|
||||||
}
|
}
|
||||||
placements_.push_back(glyphs);
|
// do not render text off the canvas
|
||||||
|
if (extent_.intersects(label_box))
|
||||||
|
{
|
||||||
|
placements_.push_back(std::move(glyphs));
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -369,9 +401,7 @@ bool placement_finder::collision(box2d<double> const& box, value_unicode_string
|
||||||
margin = (text_props_->margin != 0 ? text_props_->margin : text_props_->minimum_distance) * scale_factor_;
|
margin = (text_props_->margin != 0 ? text_props_->margin : text_props_->minimum_distance) * scale_factor_;
|
||||||
repeat_distance = text_props_->repeat_distance * scale_factor_;
|
repeat_distance = text_props_->repeat_distance * scale_factor_;
|
||||||
}
|
}
|
||||||
return !detector_.extent().intersects(box)
|
return (text_props_->avoid_edges && !extent_.contains(box))
|
||||||
||
|
|
||||||
(text_props_->avoid_edges && !extent_.contains(box))
|
|
||||||
||
|
||
|
||||||
(text_props_->minimum_padding > 0 &&
|
(text_props_->minimum_padding > 0 &&
|
||||||
!extent_.contains(box + (scale_factor_ * text_props_->minimum_padding)))
|
!extent_.contains(box + (scale_factor_ * text_props_->minimum_padding)))
|
||||||
|
@ -393,7 +423,7 @@ void placement_finder::set_marker(marker_info_ptr m, box2d<double> box, bool mar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool placement_finder::add_marker(glyph_positions_ptr glyphs, pixel_position const& pos) const
|
bool placement_finder::add_marker(glyph_positions_ptr & glyphs, pixel_position const& pos) const
|
||||||
{
|
{
|
||||||
pixel_position real_pos = (marker_unlocked_ ? pos : glyphs->get_base_point()) + marker_displacement_;
|
pixel_position real_pos = (marker_unlocked_ ? pos : glyphs->get_base_point()) + marker_displacement_;
|
||||||
box2d<double> bbox = marker_box_;
|
box2d<double> bbox = marker_box_;
|
||||||
|
|
|
@ -92,13 +92,12 @@ void text_renderer::prepare_glyphs(glyph_positions const& positions)
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void composite_bitmap(T & pixmap, FT_Bitmap *bitmap, unsigned rgba, int x, int y, double opacity, composite_mode_e comp_op)
|
void composite_bitmap(T & pixmap, FT_Bitmap *bitmap, unsigned rgba, int x, int y, double opacity, composite_mode_e comp_op)
|
||||||
{
|
{
|
||||||
int x_max=x+bitmap->width;
|
int x_max = x + bitmap->width;
|
||||||
int y_max=y+bitmap->rows;
|
int y_max = y + bitmap->rows;
|
||||||
int i,p,j,q;
|
|
||||||
|
|
||||||
for (i=x,p=0;i<x_max;++i,++p)
|
for (int i = x, p = 0; i < x_max; ++i, ++p)
|
||||||
{
|
{
|
||||||
for (j=y,q=0;j<y_max;++j,++q)
|
for (int j = y, q = 0; j < y_max; ++j, ++q)
|
||||||
{
|
{
|
||||||
unsigned gray=bitmap->buffer[q*bitmap->width+p];
|
unsigned gray=bitmap->buffer[q*bitmap->width+p];
|
||||||
if (gray)
|
if (gray)
|
||||||
|
|
|
@ -155,8 +155,14 @@ vertex_cache & vertex_cache::get_offseted(double offset, double region_width)
|
||||||
|
|
||||||
// find the point on the offset line closest to the current position,
|
// find the point on the offset line closest to the current position,
|
||||||
// which we'll use to make the offset line aligned to this one.
|
// which we'll use to make the offset line aligned to this one.
|
||||||
double seek = offseted_line->position_closest_to(current_position_);
|
// TODO: `position_closest_to` is disable since it is too expensive:
|
||||||
|
// https://github.com/mapnik/mapnik/issues/2937
|
||||||
|
//double seek = offseted_line->position_closest_to(current_position_);
|
||||||
|
double seek = (position_ + region_width/2.0) * offseted_line->length() / length() - region_width/2.0;
|
||||||
|
if (seek < 0) seek = 0;
|
||||||
|
if (seek > offseted_line->length()) seek = offseted_line->length();
|
||||||
offseted_line->move(seek);
|
offseted_line->move(seek);
|
||||||
|
|
||||||
return *offseted_line;
|
return *offseted_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit a6838f99a4c4be37989cea6718442cf4b53bd616
|
Subproject commit 2823c6a0ba9e642869537d8e5c1a1ca5fd3de18a
|
|
@ -1 +1 @@
|
||||||
Subproject commit 7080866ca1b3523fead1a25c017bd87082806eb4
|
Subproject commit 7de3183129a08c835a3205f5d51c85e59494ee33
|
|
@ -10,7 +10,7 @@
|
||||||
#include <mapnik/expression.hpp>
|
#include <mapnik/expression.hpp>
|
||||||
#include <mapnik/expression_evaluator.hpp>
|
#include <mapnik/expression_evaluator.hpp>
|
||||||
#include <mapnik/debug.hpp>
|
#include <mapnik/debug.hpp>
|
||||||
|
#include <mapnik/util/fs.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/range/iterator_range_core.hpp>
|
#include <boost/range/iterator_range_core.hpp>
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
|
@ -157,519 +157,526 @@ void require_geometry(mapnik::feature_ptr feature,
|
||||||
}
|
}
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
const bool registered = mapnik::datasource_cache::instance().register_datasources("./plugins/input/");
|
static const std::string csv_plugin("./plugins/input/csv.input");
|
||||||
|
|
||||||
|
const bool registered = mapnik::datasource_cache::instance().register_datasources(csv_plugin);
|
||||||
|
|
||||||
TEST_CASE("csv") {
|
TEST_CASE("csv") {
|
||||||
REQUIRE(registered);
|
|
||||||
|
|
||||||
// make the tests silent since we intentially test error conditions that are noisy
|
if (mapnik::util::exists(csv_plugin))
|
||||||
auto const severity = mapnik::logger::instance().get_severity();
|
{
|
||||||
mapnik::logger::instance().set_severity(mapnik::logger::none);
|
|
||||||
|
|
||||||
// check the CSV datasource is loaded
|
REQUIRE(registered);
|
||||||
const std::vector<std::string> plugin_names =
|
|
||||||
mapnik::datasource_cache::instance().plugin_names();
|
|
||||||
const bool have_csv_plugin =
|
|
||||||
std::find(plugin_names.begin(), plugin_names.end(), "csv") != plugin_names.end();
|
|
||||||
|
|
||||||
SECTION("broken files") {
|
// make the tests silent since we intentially test error conditions that are noisy
|
||||||
if (have_csv_plugin) {
|
auto const severity = mapnik::logger::instance().get_severity();
|
||||||
std::vector<bfs::path> broken;
|
mapnik::logger::instance().set_severity(mapnik::logger::none);
|
||||||
add_csv_files("test/data/csv/fails", broken);
|
|
||||||
add_csv_files("test/data/csv/warns", broken);
|
|
||||||
broken.emplace_back("test/data/csv/fails/does_not_exist.csv");
|
|
||||||
|
|
||||||
for (auto const &path : broken) {
|
// check the CSV datasource is loaded
|
||||||
REQUIRE_THROWS(get_csv_ds(path.native()));
|
const std::vector<std::string> plugin_names =
|
||||||
}
|
mapnik::datasource_cache::instance().plugin_names();
|
||||||
}
|
const bool have_csv_plugin =
|
||||||
} // END SECTION
|
std::find(plugin_names.begin(), plugin_names.end(), "csv") != plugin_names.end();
|
||||||
|
|
||||||
SECTION("good files") {
|
SECTION("broken files") {
|
||||||
if (have_csv_plugin) {
|
if (have_csv_plugin) {
|
||||||
std::vector<bfs::path> good;
|
std::vector<bfs::path> broken;
|
||||||
add_csv_files("test/data/csv", good);
|
add_csv_files("test/data/csv/fails", broken);
|
||||||
add_csv_files("test/data/csv/warns", good);
|
add_csv_files("test/data/csv/warns", broken);
|
||||||
|
broken.emplace_back("test/data/csv/fails/does_not_exist.csv");
|
||||||
|
|
||||||
for (auto const &path : good) {
|
for (auto const &path : broken) {
|
||||||
auto ds = get_csv_ds(path.native(), false);
|
REQUIRE_THROWS(get_csv_ds(path.native()));
|
||||||
// require a non-null pointer returned
|
}
|
||||||
REQUIRE(bool(ds));
|
}
|
||||||
}
|
} // END SECTION
|
||||||
}
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("lon/lat detection") {
|
SECTION("good files") {
|
||||||
for (auto const &lon_name : {std::string("lon"), std::string("lng")}) {
|
if (have_csv_plugin) {
|
||||||
auto ds = get_csv_ds((boost::format("test/data/csv/%1%_lat.csv") % lon_name).str());
|
std::vector<bfs::path> good;
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
add_csv_files("test/data/csv", good);
|
||||||
require_field_names(fields, {lon_name, "lat"});
|
add_csv_files("test/data/csv/warns", good);
|
||||||
require_field_types(fields, {mapnik::Integer, mapnik::Integer});
|
|
||||||
|
|
||||||
CHECK(ds->get_geometry_type() == mapnik::datasource_geometry_t::Point);
|
for (auto const &path : good) {
|
||||||
|
auto ds = get_csv_ds(path.native(), false);
|
||||||
|
// require a non-null pointer returned
|
||||||
|
REQUIRE(bool(ds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
mapnik::query query(ds->envelope());
|
SECTION("lon/lat detection") {
|
||||||
for (auto const &field : fields) {
|
for (auto const &lon_name : {std::string("lon"), std::string("lng")}) {
|
||||||
query.add_property_name(field.get_name());
|
auto ds = get_csv_ds((boost::format("test/data/csv/%1%_lat.csv") % lon_name).str());
|
||||||
}
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
auto features = ds->features(query);
|
require_field_names(fields, {lon_name, "lat"});
|
||||||
auto feature = features->next();
|
require_field_types(fields, {mapnik::Integer, mapnik::Integer});
|
||||||
|
|
||||||
require_attributes(feature, {
|
CHECK(ds->get_geometry_type() == mapnik::datasource_geometry_t::Point);
|
||||||
attr { lon_name, mapnik::value_integer(0) },
|
|
||||||
attr { "lat", mapnik::value_integer(0) }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("type detection") {
|
mapnik::query query(ds->envelope());
|
||||||
auto ds = get_csv_ds("test/data/csv/nypd.csv");
|
for (auto const &field : fields) {
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
query.add_property_name(field.get_name());
|
||||||
require_field_names(fields, {"Precinct", "Phone", "Address", "City", "geo_longitude", "geo_latitude", "geo_accuracy"});
|
}
|
||||||
require_field_types(fields, {mapnik::String, mapnik::String, mapnik::String, mapnik::String, mapnik::Double, mapnik::Double, mapnik::String});
|
auto features = ds->features(query);
|
||||||
|
auto feature = features->next();
|
||||||
|
|
||||||
CHECK(ds->get_geometry_type() == mapnik::datasource_geometry_t::Point);
|
require_attributes(feature, {
|
||||||
CHECK(count_features(all_features(ds)) == 2);
|
attr { lon_name, mapnik::value_integer(0) },
|
||||||
|
attr { "lat", mapnik::value_integer(0) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
auto feature = all_features(ds)->next();
|
SECTION("type detection") {
|
||||||
require_attributes(feature, {
|
auto ds = get_csv_ds("test/data/csv/nypd.csv");
|
||||||
attr { "City", mapnik::value_unicode_string("New York, NY") }
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
, attr { "geo_accuracy", mapnik::value_unicode_string("house") }
|
require_field_names(fields, {"Precinct", "Phone", "Address", "City", "geo_longitude", "geo_latitude", "geo_accuracy"});
|
||||||
, attr { "Phone", mapnik::value_unicode_string("(212) 334-0711") }
|
require_field_types(fields, {mapnik::String, mapnik::String, mapnik::String, mapnik::String, mapnik::Double, mapnik::Double, mapnik::String});
|
||||||
, attr { "Address", mapnik::value_unicode_string("19 Elizabeth Street") }
|
|
||||||
, attr { "Precinct", mapnik::value_unicode_string("5th Precinct") }
|
|
||||||
, attr { "geo_longitude", mapnik::value_integer(-70) }
|
|
||||||
, attr { "geo_latitude", mapnik::value_integer(40) }
|
|
||||||
});
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("skipping blank rows") {
|
CHECK(ds->get_geometry_type() == mapnik::datasource_geometry_t::Point);
|
||||||
auto ds = get_csv_ds("test/data/csv/blank_rows.csv");
|
CHECK(count_features(all_features(ds)) == 2);
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
|
||||||
require_field_names(fields, {"x", "y", "name"});
|
|
||||||
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String});
|
|
||||||
|
|
||||||
CHECK(ds->get_geometry_type() == mapnik::datasource_geometry_t::Point);
|
auto feature = all_features(ds)->next();
|
||||||
CHECK(count_features(all_features(ds)) == 2);
|
require_attributes(feature, {
|
||||||
} // END SECTION
|
attr { "City", mapnik::value_unicode_string("New York, NY") }
|
||||||
|
, attr { "geo_accuracy", mapnik::value_unicode_string("house") }
|
||||||
|
, attr { "Phone", mapnik::value_unicode_string("(212) 334-0711") }
|
||||||
|
, attr { "Address", mapnik::value_unicode_string("19 Elizabeth Street") }
|
||||||
|
, attr { "Precinct", mapnik::value_unicode_string("5th Precinct") }
|
||||||
|
, attr { "geo_longitude", mapnik::value_integer(-70) }
|
||||||
|
, attr { "geo_latitude", mapnik::value_integer(40) }
|
||||||
|
});
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
SECTION("empty rows") {
|
SECTION("skipping blank rows") {
|
||||||
auto ds = get_csv_ds("test/data/csv/empty_rows.csv");
|
auto ds = get_csv_ds("test/data/csv/blank_rows.csv");
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
require_field_names(fields, {"x", "y", "text", "date", "integer", "boolean", "float", "time", "datetime", "empty_column"});
|
require_field_names(fields, {"x", "y", "name"});
|
||||||
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String, mapnik::String, mapnik::Integer, mapnik::Boolean, mapnik::Double, mapnik::String, mapnik::String, mapnik::String});
|
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String});
|
||||||
|
|
||||||
CHECK(ds->get_geometry_type() == mapnik::datasource_geometry_t::Point);
|
CHECK(ds->get_geometry_type() == mapnik::datasource_geometry_t::Point);
|
||||||
CHECK(count_features(all_features(ds)) == 4);
|
CHECK(count_features(all_features(ds)) == 2);
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
auto featureset = all_features(ds);
|
SECTION("empty rows") {
|
||||||
auto feature = featureset->next();
|
auto ds = get_csv_ds("test/data/csv/empty_rows.csv");
|
||||||
require_attributes(feature, {
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
attr { "x", mapnik::value_integer(0) }
|
require_field_names(fields, {"x", "y", "text", "date", "integer", "boolean", "float", "time", "datetime", "empty_column"});
|
||||||
, attr { "empty_column", mapnik::value_unicode_string("") }
|
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String, mapnik::String, mapnik::Integer, mapnik::Boolean, mapnik::Double, mapnik::String, mapnik::String, mapnik::String});
|
||||||
, attr { "text", mapnik::value_unicode_string("a b") }
|
|
||||||
, attr { "float", mapnik::value_double(1.0) }
|
|
||||||
, attr { "datetime", mapnik::value_unicode_string("1971-01-01T04:14:00") }
|
|
||||||
, attr { "y", mapnik::value_integer(0) }
|
|
||||||
, attr { "boolean", mapnik::value_bool(true) }
|
|
||||||
, attr { "time", mapnik::value_unicode_string("04:14:00") }
|
|
||||||
, attr { "date", mapnik::value_unicode_string("1971-01-01") }
|
|
||||||
, attr { "integer", mapnik::value_integer(40) }
|
|
||||||
});
|
|
||||||
|
|
||||||
while (bool(feature = featureset->next())) {
|
CHECK(ds->get_geometry_type() == mapnik::datasource_geometry_t::Point);
|
||||||
CHECK(feature->size() == 10);
|
CHECK(count_features(all_features(ds)) == 4);
|
||||||
CHECK(feature->get("empty_column") == mapnik::value_unicode_string(""));
|
|
||||||
}
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("slashes") {
|
auto featureset = all_features(ds);
|
||||||
auto ds = get_csv_ds("test/data/csv/has_attributes_with_slashes.csv");
|
auto feature = featureset->next();
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
require_attributes(feature, {
|
||||||
require_field_names(fields, {"x", "y", "name"});
|
attr { "x", mapnik::value_integer(0) }
|
||||||
// NOTE: y column is integer, even though a double value is used below in the test?
|
, attr { "empty_column", mapnik::value_unicode_string("") }
|
||||||
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String});
|
, attr { "text", mapnik::value_unicode_string("a b") }
|
||||||
|
, attr { "float", mapnik::value_double(1.0) }
|
||||||
|
, attr { "datetime", mapnik::value_unicode_string("1971-01-01T04:14:00") }
|
||||||
|
, attr { "y", mapnik::value_integer(0) }
|
||||||
|
, attr { "boolean", mapnik::value_bool(true) }
|
||||||
|
, attr { "time", mapnik::value_unicode_string("04:14:00") }
|
||||||
|
, attr { "date", mapnik::value_unicode_string("1971-01-01") }
|
||||||
|
, attr { "integer", mapnik::value_integer(40) }
|
||||||
|
});
|
||||||
|
|
||||||
auto featureset = all_features(ds);
|
while (bool(feature = featureset->next())) {
|
||||||
require_attributes(featureset->next(), {
|
CHECK(feature->size() == 10);
|
||||||
attr{"x", 0}
|
CHECK(feature->get("empty_column") == mapnik::value_unicode_string(""));
|
||||||
, attr{"y", 0}
|
}
|
||||||
, attr{"name", mapnik::value_unicode_string("a/a") } });
|
} // END SECTION
|
||||||
require_attributes(featureset->next(), {
|
|
||||||
attr{"x", 1}
|
|
||||||
, attr{"y", 4}
|
|
||||||
, attr{"name", mapnik::value_unicode_string("b/b") } });
|
|
||||||
require_attributes(featureset->next(), {
|
|
||||||
attr{"x", 10}
|
|
||||||
, attr{"y", 2.5}
|
|
||||||
, attr{"name", mapnik::value_unicode_string("c/c") } });
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("wkt field") {
|
SECTION("slashes") {
|
||||||
using mapnik::geometry::geometry_types;
|
auto ds = get_csv_ds("test/data/csv/has_attributes_with_slashes.csv");
|
||||||
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"x", "y", "name"});
|
||||||
|
// NOTE: y column is integer, even though a double value is used below in the test?
|
||||||
|
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String});
|
||||||
|
|
||||||
auto ds = get_csv_ds("test/data/csv/wkt.csv");
|
auto featureset = all_features(ds);
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
require_attributes(featureset->next(), {
|
||||||
require_field_names(fields, {"type"});
|
attr{"x", 0}
|
||||||
require_field_types(fields, {mapnik::String});
|
, attr{"y", 0}
|
||||||
|
, attr{"name", mapnik::value_unicode_string("a/a") } });
|
||||||
|
require_attributes(featureset->next(), {
|
||||||
|
attr{"x", 1}
|
||||||
|
, attr{"y", 4}
|
||||||
|
, attr{"name", mapnik::value_unicode_string("b/b") } });
|
||||||
|
require_attributes(featureset->next(), {
|
||||||
|
attr{"x", 10}
|
||||||
|
, attr{"y", 2.5}
|
||||||
|
, attr{"name", mapnik::value_unicode_string("c/c") } });
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
auto featureset = all_features(ds);
|
SECTION("wkt field") {
|
||||||
require_geometry(featureset->next(), 1, geometry_types::Point);
|
using mapnik::geometry::geometry_types;
|
||||||
require_geometry(featureset->next(), 1, geometry_types::LineString);
|
|
||||||
require_geometry(featureset->next(), 1, geometry_types::Polygon);
|
|
||||||
require_geometry(featureset->next(), 1, geometry_types::Polygon);
|
|
||||||
require_geometry(featureset->next(), 4, geometry_types::MultiPoint);
|
|
||||||
require_geometry(featureset->next(), 2, geometry_types::MultiLineString);
|
|
||||||
require_geometry(featureset->next(), 2, geometry_types::MultiPolygon);
|
|
||||||
require_geometry(featureset->next(), 2, geometry_types::MultiPolygon);
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("handling of missing header") {
|
auto ds = get_csv_ds("test/data/csv/wkt.csv");
|
||||||
// TODO: does this mean 'missing_header.csv' should be in the warnings
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
// subdirectory, since it doesn't work in strict mode?
|
require_field_names(fields, {"type"});
|
||||||
auto ds = get_csv_ds("test/data/csv/missing_header.csv", false);
|
require_field_types(fields, {mapnik::String});
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
|
||||||
require_field_names(fields, {"one", "two", "x", "y", "_4", "aftermissing"});
|
|
||||||
auto feature = all_features(ds)->next();
|
|
||||||
REQUIRE(feature);
|
|
||||||
REQUIRE(feature->has_key("_4"));
|
|
||||||
CHECK(feature->get("_4") == mapnik::value_unicode_string("missing"));
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("handling of headers that are numbers") {
|
auto featureset = all_features(ds);
|
||||||
auto ds = get_csv_ds("test/data/csv/numbers_for_headers.csv");
|
require_geometry(featureset->next(), 1, geometry_types::Point);
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
require_geometry(featureset->next(), 1, geometry_types::LineString);
|
||||||
require_field_names(fields, {"x", "y", "1990", "1991", "1992"});
|
require_geometry(featureset->next(), 1, geometry_types::Polygon);
|
||||||
auto feature = all_features(ds)->next();
|
require_geometry(featureset->next(), 1, geometry_types::Polygon);
|
||||||
require_attributes(feature, {
|
require_geometry(featureset->next(), 4, geometry_types::MultiPoint);
|
||||||
attr{"x", 0}
|
require_geometry(featureset->next(), 2, geometry_types::MultiLineString);
|
||||||
, attr{"y", 0}
|
require_geometry(featureset->next(), 2, geometry_types::MultiPolygon);
|
||||||
, attr{"1990", 1}
|
require_geometry(featureset->next(), 2, geometry_types::MultiPolygon);
|
||||||
, attr{"1991", 2}
|
} // END SECTION
|
||||||
, attr{"1992", 3}
|
|
||||||
});
|
|
||||||
auto expression = mapnik::parse_expression("[1991]=2");
|
|
||||||
REQUIRE(bool(expression));
|
|
||||||
auto value = mapnik::util::apply_visitor(
|
|
||||||
mapnik::evaluate<mapnik::feature_impl, mapnik::value_type, mapnik::attributes>(
|
|
||||||
*feature, mapnik::attributes()), *expression);
|
|
||||||
CHECK(value == true);
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("quoted numbers") {
|
SECTION("handling of missing header") {
|
||||||
using ustring = mapnik::value_unicode_string;
|
// TODO: does this mean 'missing_header.csv' should be in the warnings
|
||||||
|
// subdirectory, since it doesn't work in strict mode?
|
||||||
|
auto ds = get_csv_ds("test/data/csv/missing_header.csv", false);
|
||||||
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"one", "two", "x", "y", "_4", "aftermissing"});
|
||||||
|
auto feature = all_features(ds)->next();
|
||||||
|
REQUIRE(feature);
|
||||||
|
REQUIRE(feature->has_key("_4"));
|
||||||
|
CHECK(feature->get("_4") == mapnik::value_unicode_string("missing"));
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
auto ds = get_csv_ds("test/data/csv/quoted_numbers.csv");
|
SECTION("handling of headers that are numbers") {
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
auto ds = get_csv_ds("test/data/csv/numbers_for_headers.csv");
|
||||||
require_field_names(fields, {"x", "y", "label"});
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
auto featureset = all_features(ds);
|
require_field_names(fields, {"x", "y", "1990", "1991", "1992"});
|
||||||
|
auto feature = all_features(ds)->next();
|
||||||
|
require_attributes(feature, {
|
||||||
|
attr{"x", 0}
|
||||||
|
, attr{"y", 0}
|
||||||
|
, attr{"1990", 1}
|
||||||
|
, attr{"1991", 2}
|
||||||
|
, attr{"1992", 3}
|
||||||
|
});
|
||||||
|
auto expression = mapnik::parse_expression("[1991]=2");
|
||||||
|
REQUIRE(bool(expression));
|
||||||
|
auto value = mapnik::util::apply_visitor(
|
||||||
|
mapnik::evaluate<mapnik::feature_impl, mapnik::value_type, mapnik::attributes>(
|
||||||
|
*feature, mapnik::attributes()), *expression);
|
||||||
|
CHECK(value == true);
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
require_attributes(featureset->next(), {
|
SECTION("quoted numbers") {
|
||||||
attr{"x", 0}, attr{"y", 0}, attr{"label", ustring("0,0") } });
|
using ustring = mapnik::value_unicode_string;
|
||||||
require_attributes(featureset->next(), {
|
|
||||||
attr{"x", 5}, attr{"y", 5}, attr{"label", ustring("5,5") } });
|
|
||||||
require_attributes(featureset->next(), {
|
|
||||||
attr{"x", 0}, attr{"y", 5}, attr{"label", ustring("0,5") } });
|
|
||||||
require_attributes(featureset->next(), {
|
|
||||||
attr{"x", 5}, attr{"y", 0}, attr{"label", ustring("5,0") } });
|
|
||||||
require_attributes(featureset->next(), {
|
|
||||||
attr{"x", 2.5}, attr{"y", 2.5}, attr{"label", ustring("2.5,2.5") } });
|
|
||||||
|
|
||||||
} // END SECTION
|
auto ds = get_csv_ds("test/data/csv/quoted_numbers.csv");
|
||||||
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"x", "y", "label"});
|
||||||
|
auto featureset = all_features(ds);
|
||||||
|
|
||||||
SECTION("reading newlines") {
|
require_attributes(featureset->next(), {
|
||||||
for (auto const &platform : {std::string("windows"), std::string("mac")}) {
|
attr{"x", 0}, attr{"y", 0}, attr{"label", ustring("0,0") } });
|
||||||
std::string file_name = (boost::format("test/data/csv/%1%_newlines.csv") % platform).str();
|
require_attributes(featureset->next(), {
|
||||||
auto ds = get_csv_ds(file_name);
|
attr{"x", 5}, attr{"y", 5}, attr{"label", ustring("5,5") } });
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
require_attributes(featureset->next(), {
|
||||||
require_field_names(fields, {"x", "y", "z"});
|
attr{"x", 0}, attr{"y", 5}, attr{"label", ustring("0,5") } });
|
||||||
require_attributes(all_features(ds)->next(), {
|
require_attributes(featureset->next(), {
|
||||||
attr{"x", 1}, attr{"y", 10}, attr{"z", 9999.9999} });
|
attr{"x", 5}, attr{"y", 0}, attr{"label", ustring("5,0") } });
|
||||||
}
|
require_attributes(featureset->next(), {
|
||||||
} // END SECTION
|
attr{"x", 2.5}, attr{"y", 2.5}, attr{"label", ustring("2.5,2.5") } });
|
||||||
|
|
||||||
SECTION("mixed newlines") {
|
} // END SECTION
|
||||||
using ustring = mapnik::value_unicode_string;
|
|
||||||
|
|
||||||
for (auto const &file : {
|
SECTION("reading newlines") {
|
||||||
std::string("test/data/csv/mac_newlines_with_unix_inline.csv")
|
for (auto const &platform : {std::string("windows"), std::string("mac")}) {
|
||||||
, std::string("test/data/csv/mac_newlines_with_unix_inline_escaped.csv")
|
std::string file_name = (boost::format("test/data/csv/%1%_newlines.csv") % platform).str();
|
||||||
, std::string("test/data/csv/windows_newlines_with_unix_inline.csv")
|
auto ds = get_csv_ds(file_name);
|
||||||
, std::string("test/data/csv/windows_newlines_with_unix_inline_escaped.csv")
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"x", "y", "z"});
|
||||||
|
require_attributes(all_features(ds)->next(), {
|
||||||
|
attr{"x", 1}, attr{"y", 10}, attr{"z", 9999.9999} });
|
||||||
|
}
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
|
SECTION("mixed newlines") {
|
||||||
|
using ustring = mapnik::value_unicode_string;
|
||||||
|
|
||||||
|
for (auto const &file : {
|
||||||
|
std::string("test/data/csv/mac_newlines_with_unix_inline.csv")
|
||||||
|
, std::string("test/data/csv/mac_newlines_with_unix_inline_escaped.csv")
|
||||||
|
, std::string("test/data/csv/windows_newlines_with_unix_inline.csv")
|
||||||
|
, std::string("test/data/csv/windows_newlines_with_unix_inline_escaped.csv")
|
||||||
|
}) {
|
||||||
|
auto ds = get_csv_ds(file);
|
||||||
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"x", "y", "line"});
|
||||||
|
require_attributes(all_features(ds)->next(), {
|
||||||
|
attr{"x", 0}, attr{"y", 0}
|
||||||
|
, attr{"line", ustring("many\n lines\n of text\n with unix newlines")} });
|
||||||
|
}
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
|
SECTION("tabs") {
|
||||||
|
auto ds = get_csv_ds("test/data/csv/tabs_in_csv.csv");
|
||||||
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"x", "y", "z"});
|
||||||
|
require_attributes(all_features(ds)->next(), {
|
||||||
|
attr{"x", -122}, attr{"y", 48}, attr{"z", 0} });
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
|
SECTION("separators") {
|
||||||
|
using ustring = mapnik::value_unicode_string;
|
||||||
|
|
||||||
|
for (auto const &file : {
|
||||||
|
std::string("test/data/csv/pipe_delimiters.csv")
|
||||||
|
, std::string("test/data/csv/semicolon_delimiters.csv")
|
||||||
|
}) {
|
||||||
|
auto ds = get_csv_ds(file);
|
||||||
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"x", "y", "z"});
|
||||||
|
require_attributes(all_features(ds)->next(), {
|
||||||
|
attr{"x", 0}, attr{"y", 0}, attr{"z", ustring("hello")} });
|
||||||
|
}
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
|
SECTION("null and bool keywords are empty strings") {
|
||||||
|
using ustring = mapnik::value_unicode_string;
|
||||||
|
|
||||||
|
auto ds = get_csv_ds("test/data/csv/nulls_and_booleans_as_strings.csv");
|
||||||
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"x", "y", "null", "boolean"});
|
||||||
|
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String, mapnik::Boolean});
|
||||||
|
|
||||||
|
auto featureset = all_features(ds);
|
||||||
|
require_attributes(featureset->next(), {
|
||||||
|
attr{"x", 0}, attr{"y", 0}, attr{"null", ustring("null")}, attr{"boolean", true}});
|
||||||
|
require_attributes(featureset->next(), {
|
||||||
|
attr{"x", 0}, attr{"y", 0}, attr{"null", ustring("")}, attr{"boolean", false}});
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
|
SECTION("nonexistent query fields throw") {
|
||||||
|
auto ds = get_csv_ds("test/data/csv/lon_lat.csv");
|
||||||
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"lon", "lat"});
|
||||||
|
require_field_types(fields, {mapnik::Integer, mapnik::Integer});
|
||||||
|
|
||||||
|
mapnik::query query(ds->envelope());
|
||||||
|
for (auto const &field : fields) {
|
||||||
|
query.add_property_name(field.get_name());
|
||||||
|
}
|
||||||
|
// also add an invalid one, triggering throw
|
||||||
|
query.add_property_name("bogus");
|
||||||
|
|
||||||
|
REQUIRE_THROWS(ds->features(query));
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
|
SECTION("leading zeros mean strings") {
|
||||||
|
using ustring = mapnik::value_unicode_string;
|
||||||
|
|
||||||
|
auto ds = get_csv_ds("test/data/csv/leading_zeros.csv");
|
||||||
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"x", "y", "fips"});
|
||||||
|
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String});
|
||||||
|
|
||||||
|
auto featureset = all_features(ds);
|
||||||
|
require_attributes(featureset->next(), {
|
||||||
|
attr{"x", 0}, attr{"y", 0}, attr{"fips", ustring("001")}});
|
||||||
|
require_attributes(featureset->next(), {
|
||||||
|
attr{"x", 0}, attr{"y", 0}, attr{"fips", ustring("003")}});
|
||||||
|
require_attributes(featureset->next(), {
|
||||||
|
attr{"x", 0}, attr{"y", 0}, attr{"fips", ustring("005")}});
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
|
SECTION("advanced geometry detection") {
|
||||||
|
using row = std::pair<std::string, mapnik::datasource_geometry_t>;
|
||||||
|
|
||||||
|
for (row r : {
|
||||||
|
row{"point", mapnik::datasource_geometry_t::Point}
|
||||||
|
, row{"poly", mapnik::datasource_geometry_t::Polygon}
|
||||||
|
, row{"multi_poly", mapnik::datasource_geometry_t::Polygon}
|
||||||
|
, row{"line", mapnik::datasource_geometry_t::LineString}
|
||||||
}) {
|
}) {
|
||||||
auto ds = get_csv_ds(file);
|
std::string file_name = (boost::format("test/data/csv/%1%_wkt.csv") % r.first).str();
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
auto ds = get_csv_ds(file_name);
|
||||||
require_field_names(fields, {"x", "y", "line"});
|
CHECK(ds->get_geometry_type() == r.second);
|
||||||
require_attributes(all_features(ds)->next(), {
|
}
|
||||||
attr{"x", 0}, attr{"y", 0}
|
} // END SECTION
|
||||||
, attr{"line", ustring("many\n lines\n of text\n with unix newlines")} });
|
|
||||||
}
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("tabs") {
|
SECTION("creation of CSV from in-memory strings") {
|
||||||
auto ds = get_csv_ds("test/data/csv/tabs_in_csv.csv");
|
using ustring = mapnik::value_unicode_string;
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
|
||||||
require_field_names(fields, {"x", "y", "z"});
|
|
||||||
require_attributes(all_features(ds)->next(), {
|
|
||||||
attr{"x", -122}, attr{"y", 48}, attr{"z", 0} });
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("separators") {
|
for (auto const &name : {std::string("Winthrop, WA"), std::string(u8"Qu\u00e9bec")}) {
|
||||||
using ustring = mapnik::value_unicode_string;
|
std::string csv_string =
|
||||||
|
(boost::format(
|
||||||
|
"wkt,Name\n"
|
||||||
|
"\"POINT (120.15 48.47)\",\"%1%\"\n"
|
||||||
|
) % name).str();
|
||||||
|
|
||||||
for (auto const &file : {
|
mapnik::parameters params;
|
||||||
std::string("test/data/csv/pipe_delimiters.csv")
|
params["type"] = std::string("csv");
|
||||||
, std::string("test/data/csv/semicolon_delimiters.csv")
|
params["inline"] = csv_string;
|
||||||
}) {
|
auto ds = mapnik::datasource_cache::instance().create(params);
|
||||||
auto ds = get_csv_ds(file);
|
REQUIRE(bool(ds));
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
|
||||||
require_field_names(fields, {"x", "y", "z"});
|
|
||||||
require_attributes(all_features(ds)->next(), {
|
|
||||||
attr{"x", 0}, attr{"y", 0}, attr{"z", ustring("hello")} });
|
|
||||||
}
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("null and bool keywords are empty strings") {
|
auto feature = all_features(ds)->next();
|
||||||
using ustring = mapnik::value_unicode_string;
|
REQUIRE(bool(feature));
|
||||||
|
REQUIRE(feature->has_key("Name"));
|
||||||
|
CHECK(feature->get("Name") == ustring(name.c_str()));
|
||||||
|
}
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
auto ds = get_csv_ds("test/data/csv/nulls_and_booleans_as_strings.csv");
|
SECTION("geojson quoting") {
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
using mapnik::geometry::geometry_types;
|
||||||
require_field_names(fields, {"x", "y", "null", "boolean"});
|
|
||||||
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String, mapnik::Boolean});
|
|
||||||
|
|
||||||
auto featureset = all_features(ds);
|
for (auto const &file : {
|
||||||
require_attributes(featureset->next(), {
|
std::string("test/data/csv/geojson_double_quote_escape.csv")
|
||||||
attr{"x", 0}, attr{"y", 0}, attr{"null", ustring("null")}, attr{"boolean", true}});
|
, std::string("test/data/csv/geojson_single_quote.csv")
|
||||||
require_attributes(featureset->next(), {
|
, std::string("test/data/csv/geojson_2x_double_quote_filebakery_style.csv")
|
||||||
attr{"x", 0}, attr{"y", 0}, attr{"null", ustring("")}, attr{"boolean", false}});
|
}) {
|
||||||
} // END SECTION
|
auto ds = get_csv_ds(file);
|
||||||
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"type"});
|
||||||
|
require_field_types(fields, {mapnik::String});
|
||||||
|
|
||||||
SECTION("nonexistent query fields throw") {
|
auto featureset = all_features(ds);
|
||||||
auto ds = get_csv_ds("test/data/csv/lon_lat.csv");
|
require_geometry(featureset->next(), 1, geometry_types::Point);
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
require_geometry(featureset->next(), 1, geometry_types::LineString);
|
||||||
require_field_names(fields, {"lon", "lat"});
|
require_geometry(featureset->next(), 1, geometry_types::Polygon);
|
||||||
require_field_types(fields, {mapnik::Integer, mapnik::Integer});
|
require_geometry(featureset->next(), 1, geometry_types::Polygon);
|
||||||
|
require_geometry(featureset->next(), 4, geometry_types::MultiPoint);
|
||||||
|
require_geometry(featureset->next(), 2, geometry_types::MultiLineString);
|
||||||
|
require_geometry(featureset->next(), 2, geometry_types::MultiPolygon);
|
||||||
|
require_geometry(featureset->next(), 2, geometry_types::MultiPolygon);
|
||||||
|
}
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
mapnik::query query(ds->envelope());
|
SECTION("blank undelimited rows are still parsed") {
|
||||||
for (auto const &field : fields) {
|
using ustring = mapnik::value_unicode_string;
|
||||||
query.add_property_name(field.get_name());
|
|
||||||
}
|
|
||||||
// also add an invalid one, triggering throw
|
|
||||||
query.add_property_name("bogus");
|
|
||||||
|
|
||||||
REQUIRE_THROWS(ds->features(query));
|
// TODO: does this mean this CSV file should be in the warnings
|
||||||
} // END SECTION
|
// subdirectory, since it doesn't work in strict mode?
|
||||||
|
auto ds = get_csv_ds("test/data/csv/more_headers_than_column_values.csv", false);
|
||||||
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"x", "y", "one", "two", "three"});
|
||||||
|
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String, mapnik::String, mapnik::String});
|
||||||
|
|
||||||
SECTION("leading zeros mean strings") {
|
require_attributes(all_features(ds)->next(), {
|
||||||
using ustring = mapnik::value_unicode_string;
|
attr{"x", 0}, attr{"y", 0}, attr{"one", ustring("")}, attr{"two", ustring("")}, attr{"three", ustring("")} });
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
auto ds = get_csv_ds("test/data/csv/leading_zeros.csv");
|
SECTION("fewer headers than rows throws") {
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
REQUIRE_THROWS(get_csv_ds("test/data/csv/more_column_values_than_headers.csv"));
|
||||||
require_field_names(fields, {"x", "y", "fips"});
|
} // END SECTION
|
||||||
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String});
|
|
||||||
|
|
||||||
auto featureset = all_features(ds);
|
SECTION("feature ID only incremented for valid rows") {
|
||||||
require_attributes(featureset->next(), {
|
auto ds = get_csv_ds("test/data/csv/warns/feature_id_counting.csv", false);
|
||||||
attr{"x", 0}, attr{"y", 0}, attr{"fips", ustring("001")}});
|
auto fs = all_features(ds);
|
||||||
require_attributes(featureset->next(), {
|
|
||||||
attr{"x", 0}, attr{"y", 0}, attr{"fips", ustring("003")}});
|
|
||||||
require_attributes(featureset->next(), {
|
|
||||||
attr{"x", 0}, attr{"y", 0}, attr{"fips", ustring("005")}});
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("advanced geometry detection") {
|
// first
|
||||||
using row = std::pair<std::string, mapnik::datasource_geometry_t>;
|
auto feature = fs->next();
|
||||||
|
REQUIRE(bool(feature));
|
||||||
|
CHECK(feature->id() == 1);
|
||||||
|
|
||||||
for (row r : {
|
// second, should have skipped bogus one
|
||||||
row{"point", mapnik::datasource_geometry_t::Point}
|
feature = fs->next();
|
||||||
, row{"poly", mapnik::datasource_geometry_t::Polygon}
|
REQUIRE(bool(feature));
|
||||||
, row{"multi_poly", mapnik::datasource_geometry_t::Polygon}
|
CHECK(feature->id() == 2);
|
||||||
, row{"line", mapnik::datasource_geometry_t::LineString}
|
|
||||||
}) {
|
|
||||||
std::string file_name = (boost::format("test/data/csv/%1%_wkt.csv") % r.first).str();
|
|
||||||
auto ds = get_csv_ds(file_name);
|
|
||||||
CHECK(ds->get_geometry_type() == r.second);
|
|
||||||
}
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("creation of CSV from in-memory strings") {
|
feature = fs->next();
|
||||||
using ustring = mapnik::value_unicode_string;
|
CHECK(!feature);
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
for (auto const &name : {std::string("Winthrop, WA"), std::string(u8"Qu\u00e9bec")}) {
|
SECTION("dynamically defining headers") {
|
||||||
std::string csv_string =
|
using ustring = mapnik::value_unicode_string;
|
||||||
(boost::format(
|
using row = std::pair<std::string, std::size_t>;
|
||||||
"wkt,Name\n"
|
|
||||||
"\"POINT (120.15 48.47)\",\"%1%\"\n"
|
|
||||||
) % name).str();
|
|
||||||
|
|
||||||
mapnik::parameters params;
|
for (auto const &r : {
|
||||||
params["type"] = std::string("csv");
|
row{"test/data/csv/fails/needs_headers_two_lines.csv", 2}
|
||||||
params["inline"] = csv_string;
|
, row{"test/data/csv/fails/needs_headers_one_line.csv", 1}
|
||||||
auto ds = mapnik::datasource_cache::instance().create(params);
|
, row{"test/data/csv/fails/needs_headers_one_line_no_newline.csv", 1}
|
||||||
REQUIRE(bool(ds));
|
}) {
|
||||||
|
mapnik::parameters params;
|
||||||
|
params["type"] = std::string("csv");
|
||||||
|
params["file"] = r.first;
|
||||||
|
params["headers"] = "x,y,name";
|
||||||
|
auto ds = mapnik::datasource_cache::instance().create(params);
|
||||||
|
REQUIRE(bool(ds));
|
||||||
|
|
||||||
auto feature = all_features(ds)->next();
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
REQUIRE(bool(feature));
|
require_field_names(fields, {"x", "y", "name"});
|
||||||
REQUIRE(feature->has_key("Name"));
|
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String});
|
||||||
CHECK(feature->get("Name") == ustring(name.c_str()));
|
require_attributes(all_features(ds)->next(), {
|
||||||
}
|
attr{"x", 0}, attr{"y", 0}, attr{"name", ustring("data_name")} });
|
||||||
} // END SECTION
|
REQUIRE(count_features(all_features(ds)) == r.second);
|
||||||
|
}
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
SECTION("geojson quoting") {
|
#pragma GCC diagnostic push
|
||||||
using mapnik::geometry::geometry_types;
|
#pragma GCC diagnostic ignored "-Wlong-long"
|
||||||
|
SECTION("64bit int fields work") {
|
||||||
|
auto ds = get_csv_ds("test/data/csv/64bit_int.csv");
|
||||||
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"x", "y", "bigint"});
|
||||||
|
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::Integer});
|
||||||
|
|
||||||
for (auto const &file : {
|
auto fs = all_features(ds);
|
||||||
std::string("test/data/csv/geojson_double_quote_escape.csv")
|
auto feature = fs->next();
|
||||||
, std::string("test/data/csv/geojson_single_quote.csv")
|
require_attributes(feature, {
|
||||||
, std::string("test/data/csv/geojson_2x_double_quote_filebakery_style.csv")
|
attr{"x", 0}, attr{"y", 0}, attr{"bigint", 2147483648} });
|
||||||
}) {
|
|
||||||
auto ds = get_csv_ds(file);
|
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
|
||||||
require_field_names(fields, {"type"});
|
|
||||||
require_field_types(fields, {mapnik::String});
|
|
||||||
|
|
||||||
auto featureset = all_features(ds);
|
feature = fs->next();
|
||||||
require_geometry(featureset->next(), 1, geometry_types::Point);
|
require_attributes(feature, {
|
||||||
require_geometry(featureset->next(), 1, geometry_types::LineString);
|
attr{"x", 0}, attr{"y", 0}, attr{"bigint", 9223372036854775807ll} });
|
||||||
require_geometry(featureset->next(), 1, geometry_types::Polygon);
|
require_attributes(feature, {
|
||||||
require_geometry(featureset->next(), 1, geometry_types::Polygon);
|
attr{"x", 0}, attr{"y", 0}, attr{"bigint", 0x7FFFFFFFFFFFFFFFll} });
|
||||||
require_geometry(featureset->next(), 4, geometry_types::MultiPoint);
|
} // END SECTION
|
||||||
require_geometry(featureset->next(), 2, geometry_types::MultiLineString);
|
#pragma GCC diagnostic pop
|
||||||
require_geometry(featureset->next(), 2, geometry_types::MultiPolygon);
|
|
||||||
require_geometry(featureset->next(), 2, geometry_types::MultiPolygon);
|
|
||||||
}
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("blank undelimited rows are still parsed") {
|
SECTION("various number types") {
|
||||||
using ustring = mapnik::value_unicode_string;
|
auto ds = get_csv_ds("test/data/csv/number_types.csv");
|
||||||
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
|
require_field_names(fields, {"x", "y", "floats"});
|
||||||
|
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::Double});
|
||||||
|
|
||||||
// TODO: does this mean this CSV file should be in the warnings
|
auto fs = all_features(ds);
|
||||||
// subdirectory, since it doesn't work in strict mode?
|
for (double d : { .0, +.0, 1e-06, -1e-06, 0.000001, 1.234e+16, 1.234e+16 }) {
|
||||||
auto ds = get_csv_ds("test/data/csv/more_headers_than_column_values.csv", false);
|
auto feature = fs->next();
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
REQUIRE(bool(feature));
|
||||||
require_field_names(fields, {"x", "y", "one", "two", "three"});
|
CHECK(feature->get("floats").get<mapnik::value_double>() == Approx(d));
|
||||||
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String, mapnik::String, mapnik::String});
|
}
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
require_attributes(all_features(ds)->next(), {
|
SECTION("manually supplied extent") {
|
||||||
attr{"x", 0}, attr{"y", 0}, attr{"one", ustring("")}, attr{"two", ustring("")}, attr{"three", ustring("")} });
|
std::string csv_string("wkt,Name\n");
|
||||||
} // END SECTION
|
mapnik::parameters params;
|
||||||
|
params["type"] = std::string("csv");
|
||||||
|
params["inline"] = csv_string;
|
||||||
|
params["extent"] = "-180,-90,180,90";
|
||||||
|
auto ds = mapnik::datasource_cache::instance().create(params);
|
||||||
|
REQUIRE(bool(ds));
|
||||||
|
|
||||||
SECTION("fewer headers than rows throws") {
|
auto box = ds->envelope();
|
||||||
REQUIRE_THROWS(get_csv_ds("test/data/csv/more_column_values_than_headers.csv"));
|
CHECK(box.minx() == -180);
|
||||||
} // END SECTION
|
CHECK(box.miny() == -90);
|
||||||
|
CHECK(box.maxx() == 180);
|
||||||
|
CHECK(box.maxy() == 90);
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
SECTION("feature ID only incremented for valid rows") {
|
SECTION("inline geojson") {
|
||||||
auto ds = get_csv_ds("test/data/csv/warns/feature_id_counting.csv", false);
|
std::string csv_string = "geojson\n'{\"coordinates\":[-92.22568,38.59553],\"type\":\"Point\"}'";
|
||||||
auto fs = all_features(ds);
|
mapnik::parameters params;
|
||||||
|
params["type"] = std::string("csv");
|
||||||
|
params["inline"] = csv_string;
|
||||||
|
auto ds = mapnik::datasource_cache::instance().create(params);
|
||||||
|
REQUIRE(bool(ds));
|
||||||
|
|
||||||
// first
|
auto fields = ds->get_descriptor().get_descriptors();
|
||||||
auto feature = fs->next();
|
require_field_names(fields, {});
|
||||||
REQUIRE(bool(feature));
|
|
||||||
CHECK(feature->id() == 1);
|
|
||||||
|
|
||||||
// second, should have skipped bogus one
|
// TODO: this originally had the following comment:
|
||||||
feature = fs->next();
|
// - re-enable after https://github.com/mapnik/mapnik/issues/2319 is fixed
|
||||||
REQUIRE(bool(feature));
|
// but that seems to have been merged and tested separately?
|
||||||
CHECK(feature->id() == 2);
|
auto fs = all_features(ds);
|
||||||
|
auto feat = fs->next();
|
||||||
|
CHECK(feature_count(feat->get_geometry()) == 1);
|
||||||
|
} // END SECTION
|
||||||
|
|
||||||
feature = fs->next();
|
mapnik::logger::instance().set_severity(severity);
|
||||||
CHECK(!feature);
|
}
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("dynamically defining headers") {
|
|
||||||
using ustring = mapnik::value_unicode_string;
|
|
||||||
using row = std::pair<std::string, std::size_t>;
|
|
||||||
|
|
||||||
for (auto const &r : {
|
|
||||||
row{"test/data/csv/fails/needs_headers_two_lines.csv", 2}
|
|
||||||
, row{"test/data/csv/fails/needs_headers_one_line.csv", 1}
|
|
||||||
, row{"test/data/csv/fails/needs_headers_one_line_no_newline.csv", 1}
|
|
||||||
}) {
|
|
||||||
mapnik::parameters params;
|
|
||||||
params["type"] = std::string("csv");
|
|
||||||
params["file"] = r.first;
|
|
||||||
params["headers"] = "x,y,name";
|
|
||||||
auto ds = mapnik::datasource_cache::instance().create(params);
|
|
||||||
REQUIRE(bool(ds));
|
|
||||||
|
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
|
||||||
require_field_names(fields, {"x", "y", "name"});
|
|
||||||
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::String});
|
|
||||||
require_attributes(all_features(ds)->next(), {
|
|
||||||
attr{"x", 0}, attr{"y", 0}, attr{"name", ustring("data_name")} });
|
|
||||||
REQUIRE(count_features(all_features(ds)) == r.second);
|
|
||||||
}
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wlong-long"
|
|
||||||
SECTION("64bit int fields work") {
|
|
||||||
auto ds = get_csv_ds("test/data/csv/64bit_int.csv");
|
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
|
||||||
require_field_names(fields, {"x", "y", "bigint"});
|
|
||||||
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::Integer});
|
|
||||||
|
|
||||||
auto fs = all_features(ds);
|
|
||||||
auto feature = fs->next();
|
|
||||||
require_attributes(feature, {
|
|
||||||
attr{"x", 0}, attr{"y", 0}, attr{"bigint", 2147483648} });
|
|
||||||
|
|
||||||
feature = fs->next();
|
|
||||||
require_attributes(feature, {
|
|
||||||
attr{"x", 0}, attr{"y", 0}, attr{"bigint", 9223372036854775807ll} });
|
|
||||||
require_attributes(feature, {
|
|
||||||
attr{"x", 0}, attr{"y", 0}, attr{"bigint", 0x7FFFFFFFFFFFFFFFll} });
|
|
||||||
} // END SECTION
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
|
|
||||||
SECTION("various number types") {
|
|
||||||
auto ds = get_csv_ds("test/data/csv/number_types.csv");
|
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
|
||||||
require_field_names(fields, {"x", "y", "floats"});
|
|
||||||
require_field_types(fields, {mapnik::Integer, mapnik::Integer, mapnik::Double});
|
|
||||||
|
|
||||||
auto fs = all_features(ds);
|
|
||||||
for (double d : { .0, +.0, 1e-06, -1e-06, 0.000001, 1.234e+16, 1.234e+16 }) {
|
|
||||||
auto feature = fs->next();
|
|
||||||
REQUIRE(bool(feature));
|
|
||||||
CHECK(feature->get("floats").get<mapnik::value_double>() == Approx(d));
|
|
||||||
}
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("manually supplied extent") {
|
|
||||||
std::string csv_string("wkt,Name\n");
|
|
||||||
mapnik::parameters params;
|
|
||||||
params["type"] = std::string("csv");
|
|
||||||
params["inline"] = csv_string;
|
|
||||||
params["extent"] = "-180,-90,180,90";
|
|
||||||
auto ds = mapnik::datasource_cache::instance().create(params);
|
|
||||||
REQUIRE(bool(ds));
|
|
||||||
|
|
||||||
auto box = ds->envelope();
|
|
||||||
CHECK(box.minx() == -180);
|
|
||||||
CHECK(box.miny() == -90);
|
|
||||||
CHECK(box.maxx() == 180);
|
|
||||||
CHECK(box.maxy() == 90);
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
SECTION("inline geojson") {
|
|
||||||
std::string csv_string = "geojson\n'{\"coordinates\":[-92.22568,38.59553],\"type\":\"Point\"}'";
|
|
||||||
mapnik::parameters params;
|
|
||||||
params["type"] = std::string("csv");
|
|
||||||
params["inline"] = csv_string;
|
|
||||||
auto ds = mapnik::datasource_cache::instance().create(params);
|
|
||||||
REQUIRE(bool(ds));
|
|
||||||
|
|
||||||
auto fields = ds->get_descriptor().get_descriptors();
|
|
||||||
require_field_names(fields, {});
|
|
||||||
|
|
||||||
// TODO: this originally had the following comment:
|
|
||||||
// - re-enable after https://github.com/mapnik/mapnik/issues/2319 is fixed
|
|
||||||
// but that seems to have been merged and tested separately?
|
|
||||||
auto fs = all_features(ds);
|
|
||||||
auto feat = fs->next();
|
|
||||||
CHECK(feature_count(feat->get_geometry()) == 1);
|
|
||||||
} // END SECTION
|
|
||||||
|
|
||||||
mapnik::logger::instance().set_severity(severity);
|
|
||||||
} // END TEST CASE
|
} // END TEST CASE
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <mapnik/color.hpp>
|
#include <mapnik/color.hpp>
|
||||||
#include <mapnik/datasource.hpp>
|
#include <mapnik/datasource.hpp>
|
||||||
#include <mapnik/datasource_cache.hpp>
|
#include <mapnik/datasource_cache.hpp>
|
||||||
|
#include <mapnik/util/fs.hpp>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -19,55 +20,59 @@ SECTION("layers") {
|
||||||
{
|
{
|
||||||
mapnik::Map m0(100,100);
|
mapnik::Map m0(100,100);
|
||||||
mapnik::Map m2(200,100);
|
mapnik::Map m2(200,100);
|
||||||
mapnik::datasource_cache::instance().register_datasources("plugins/input/shape.input");
|
std::string shape_plugin("./plugins/input/shape.input");
|
||||||
mapnik::parameters p;
|
if (mapnik::util::exists(shape_plugin))
|
||||||
p["type"]="shape";
|
{
|
||||||
p["file"]="demo/data/boundaries";
|
mapnik::datasource_cache::instance().register_datasources("plugins/input/shape.input");
|
||||||
p["encoding"]="latin1";
|
mapnik::parameters p;
|
||||||
auto ds0 = mapnik::datasource_cache::instance().create(p);
|
p["type"]="shape";
|
||||||
|
p["file"]="demo/data/boundaries";
|
||||||
|
p["encoding"]="latin1";
|
||||||
|
auto ds0 = mapnik::datasource_cache::instance().create(p);
|
||||||
|
|
||||||
auto ds1 = ds0; // shared ptr copy
|
auto ds1 = ds0; // shared ptr copy
|
||||||
REQUIRE( (ds1 == ds0) );
|
REQUIRE( (ds1 == ds0) );
|
||||||
REQUIRE( !(*ds1 != *ds0) );
|
REQUIRE( !(*ds1 != *ds0) );
|
||||||
REQUIRE( (ds1.get() == ds0.get()) );
|
REQUIRE( (ds1.get() == ds0.get()) );
|
||||||
ds1 = mapnik::datasource_cache::instance().create(p); // new with the same parameters
|
ds1 = mapnik::datasource_cache::instance().create(p); // new with the same parameters
|
||||||
REQUIRE( (ds1 != ds0) );
|
REQUIRE( (ds1 != ds0) );
|
||||||
REQUIRE( (*ds1 == *ds0) );
|
REQUIRE( (*ds1 == *ds0) );
|
||||||
auto ds2 = std::move(ds1);
|
auto ds2 = std::move(ds1);
|
||||||
REQUIRE( (ds2 != ds0) );
|
REQUIRE( (ds2 != ds0) );
|
||||||
REQUIRE( (*ds2 == *ds0) );
|
REQUIRE( (*ds2 == *ds0) );
|
||||||
|
|
||||||
// mapnik::layer
|
// mapnik::layer
|
||||||
mapnik::layer l0("test-layer");
|
mapnik::layer l0("test-layer");
|
||||||
l0.set_datasource(ds0);
|
l0.set_datasource(ds0);
|
||||||
|
|
||||||
mapnik::layer l1 = l0; // copy assignment
|
mapnik::layer l1 = l0; // copy assignment
|
||||||
REQUIRE( (l1 == l0) );
|
REQUIRE( (l1 == l0) );
|
||||||
mapnik::layer l2(l0); // copy ctor
|
mapnik::layer l2(l0); // copy ctor
|
||||||
REQUIRE( (l2 == l0) );
|
REQUIRE( (l2 == l0) );
|
||||||
mapnik::layer l3(mapnik::layer("test-layer")); // move ctor
|
mapnik::layer l3(mapnik::layer("test-layer")); // move ctor
|
||||||
l3.set_datasource(ds2);
|
l3.set_datasource(ds2);
|
||||||
|
|
||||||
REQUIRE( (l3 == l0) );
|
REQUIRE( (l3 == l0) );
|
||||||
mapnik::layer l4 = std::move(l3);
|
mapnik::layer l4 = std::move(l3);
|
||||||
REQUIRE( (l4 == l0) ); // move assignment
|
REQUIRE( (l4 == l0) ); // move assignment
|
||||||
|
|
||||||
m0.add_layer(l4);
|
m0.add_layer(l4);
|
||||||
m0.set_background(mapnik::color("skyblue"));
|
m0.set_background(mapnik::color("skyblue"));
|
||||||
m2.set_background(mapnik::color("skyblue"));
|
m2.set_background(mapnik::color("skyblue"));
|
||||||
|
|
||||||
auto m1 = m0; //copy
|
auto m1 = m0; //copy
|
||||||
|
|
||||||
REQUIRE( (m0 == m1) );
|
REQUIRE( (m0 == m1) );
|
||||||
REQUIRE( (m0 != m2) );
|
REQUIRE( (m0 != m2) );
|
||||||
|
|
||||||
m2 = m1; // copy
|
m2 = m1; // copy
|
||||||
REQUIRE( (m2 == m1) );
|
REQUIRE( (m2 == m1) );
|
||||||
m2 = std::move(m1);
|
m2 = std::move(m1);
|
||||||
REQUIRE( (m2 == m0) );
|
REQUIRE( (m2 == m0) );
|
||||||
REQUIRE( (m1 != m0) );
|
REQUIRE( (m1 != m0) );
|
||||||
|
|
||||||
REQUIRE( (m0 == m2) );
|
REQUIRE( (m0 == m2) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception const & ex)
|
catch (std::exception const & ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <mapnik/datasource_cache.hpp>
|
#include <mapnik/datasource_cache.hpp>
|
||||||
#include <mapnik/agg_renderer.hpp>
|
#include <mapnik/agg_renderer.hpp>
|
||||||
#include <mapnik/expression.hpp>
|
#include <mapnik/expression.hpp>
|
||||||
|
#include <mapnik/util/fs.hpp>
|
||||||
|
|
||||||
TEST_CASE("image") {
|
TEST_CASE("image") {
|
||||||
|
|
||||||
|
@ -17,47 +18,51 @@ SECTION("painting") {
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
datasource_cache::instance().register_datasources("plugins/input/csv.input");
|
std::string csv_plugin("./plugins/input/csv.input");
|
||||||
|
if (mapnik::util::exists(csv_plugin))
|
||||||
Map m(256, 256);
|
|
||||||
|
|
||||||
feature_type_style lines_style;
|
|
||||||
{
|
{
|
||||||
rule r;
|
datasource_cache::instance().register_datasources(csv_plugin);
|
||||||
line_symbolizer line_sym;
|
|
||||||
r.append(std::move(line_sym));
|
Map m(256, 256);
|
||||||
lines_style.add_rule(std::move(r));
|
|
||||||
|
feature_type_style lines_style;
|
||||||
|
{
|
||||||
|
rule r;
|
||||||
|
line_symbolizer line_sym;
|
||||||
|
r.append(std::move(line_sym));
|
||||||
|
lines_style.add_rule(std::move(r));
|
||||||
|
}
|
||||||
|
m.insert_style("lines", std::move(lines_style));
|
||||||
|
|
||||||
|
feature_type_style markers_style;
|
||||||
|
{
|
||||||
|
rule r;
|
||||||
|
r.set_filter(parse_expression("False"));
|
||||||
|
markers_symbolizer mark_sym;
|
||||||
|
r.append(std::move(mark_sym));
|
||||||
|
markers_style.add_rule(std::move(r));
|
||||||
|
}
|
||||||
|
m.insert_style("markers", std::move(markers_style));
|
||||||
|
|
||||||
|
parameters p;
|
||||||
|
p["type"] = "csv";
|
||||||
|
p["separator"] = "|";
|
||||||
|
p["inline"] = "wkt\nLINESTRING(-10 0, 0 20, 10 0, 15 5)";
|
||||||
|
|
||||||
|
layer lyr("layer");
|
||||||
|
lyr.set_datasource(datasource_cache::instance().create(p));
|
||||||
|
lyr.add_style("lines");
|
||||||
|
lyr.add_style("markers");
|
||||||
|
m.add_layer(lyr);
|
||||||
|
|
||||||
|
m.zoom_all();
|
||||||
|
|
||||||
|
image_rgba8 image(m.width(), m.height());
|
||||||
|
agg_renderer<image_rgba8> ren(m, image);
|
||||||
|
ren.apply();
|
||||||
|
|
||||||
|
REQUIRE(image.painted() == true);
|
||||||
}
|
}
|
||||||
m.insert_style("lines", std::move(lines_style));
|
|
||||||
|
|
||||||
feature_type_style markers_style;
|
|
||||||
{
|
|
||||||
rule r;
|
|
||||||
r.set_filter(parse_expression("False"));
|
|
||||||
markers_symbolizer mark_sym;
|
|
||||||
r.append(std::move(mark_sym));
|
|
||||||
markers_style.add_rule(std::move(r));
|
|
||||||
}
|
|
||||||
m.insert_style("markers", std::move(markers_style));
|
|
||||||
|
|
||||||
parameters p;
|
|
||||||
p["type"] = "csv";
|
|
||||||
p["separator"] = "|";
|
|
||||||
p["inline"] = "wkt\nLINESTRING(-10 0, 0 20, 10 0, 15 5)";
|
|
||||||
|
|
||||||
layer lyr("layer");
|
|
||||||
lyr.set_datasource(datasource_cache::instance().create(p));
|
|
||||||
lyr.add_style("lines");
|
|
||||||
lyr.add_style("markers");
|
|
||||||
m.add_layer(lyr);
|
|
||||||
|
|
||||||
m.zoom_all();
|
|
||||||
|
|
||||||
image_rgba8 image(m.width(), m.height());
|
|
||||||
agg_renderer<image_rgba8> ren(m, image);
|
|
||||||
ren.apply();
|
|
||||||
|
|
||||||
REQUIRE(image.painted() == true);
|
|
||||||
}
|
}
|
||||||
catch (std::exception const & ex)
|
catch (std::exception const & ex)
|
||||||
{
|
{
|
||||||
|
|
24
test/unit/text/shaping.cpp
Normal file
24
test/unit/text/shaping.cpp
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#include "catch.hpp"
|
||||||
|
#include <mapnik/text/icu_shaper.hpp>
|
||||||
|
#include <mapnik/text/harfbuzz_shaper.hpp>
|
||||||
|
#include <mapnik/text/font_library.hpp>
|
||||||
|
|
||||||
|
TEST_CASE("shapers compile") {
|
||||||
|
|
||||||
|
mapnik::text_line line(0,0);
|
||||||
|
mapnik::text_itemizer itemizer;
|
||||||
|
std::map<unsigned,double> width_map;
|
||||||
|
double scale_factor = 1;
|
||||||
|
mapnik::font_library fl;
|
||||||
|
mapnik::freetype_engine::font_file_mapping_type font_file_mapping;
|
||||||
|
mapnik::freetype_engine::font_memory_cache_type font_memory_cache;
|
||||||
|
mapnik::face_manager fm(fl,font_file_mapping,font_memory_cache);
|
||||||
|
mapnik::harfbuzz_shaper::shape_text(line,itemizer,
|
||||||
|
width_map,
|
||||||
|
fm,
|
||||||
|
scale_factor);
|
||||||
|
mapnik::icu_shaper::shape_text(line,itemizer,
|
||||||
|
width_map,
|
||||||
|
fm,
|
||||||
|
scale_factor);
|
||||||
|
}
|
|
@ -27,14 +27,22 @@ struct fake_path
|
||||||
: fake_path(l.begin(), l.size()) {
|
: fake_path(l.begin(), l.size()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fake_path(std::vector<double> const &v)
|
fake_path(std::vector<double> const &v, bool make_invalid = false)
|
||||||
: fake_path(v.begin(), v.size()) {
|
: fake_path(v.begin(), v.size(), make_invalid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Itr>
|
template <typename Itr>
|
||||||
fake_path(Itr itr, size_t sz) {
|
fake_path(Itr itr, size_t sz, bool make_invalid = false) {
|
||||||
size_t num_coords = sz >> 1;
|
size_t num_coords = sz >> 1;
|
||||||
|
if (make_invalid)
|
||||||
|
{
|
||||||
|
num_coords++;
|
||||||
|
}
|
||||||
vertices_.reserve(num_coords);
|
vertices_.reserve(num_coords);
|
||||||
|
if (make_invalid)
|
||||||
|
{
|
||||||
|
vertices_.push_back(std::make_tuple(0,0,mapnik::SEG_END));
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < num_coords; ++i) {
|
for (size_t i = 0; i < num_coords; ++i) {
|
||||||
double x = *itr++;
|
double x = *itr++;
|
||||||
|
@ -70,11 +78,37 @@ double dist(double x0, double y0, double x1, double y1)
|
||||||
return std::sqrt(dx*dx + dy*dy);
|
return std::sqrt(dx*dx + dy*dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_null_segment(double const &offset)
|
||||||
|
{
|
||||||
|
fake_path path = {};
|
||||||
|
mapnik::offset_converter<fake_path> off_path_new(path);
|
||||||
|
off_path_new.set_offset(offset);
|
||||||
|
double x0 = 0;
|
||||||
|
double y0 = 0;
|
||||||
|
REQUIRE(off_path_new.vertex(&x0, &y0) == mapnik::SEG_END);
|
||||||
|
REQUIRE(off_path_new.vertex(&x0, &y0) == mapnik::SEG_END);
|
||||||
|
REQUIRE(off_path_new.vertex(&x0, &y0) == mapnik::SEG_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_invalid_segment(double const &offset)
|
||||||
|
{
|
||||||
|
std::vector<double> v_path = {1, 1, 1, 2};
|
||||||
|
fake_path path(v_path, true);
|
||||||
|
mapnik::offset_converter<fake_path> off_path_new(path);
|
||||||
|
off_path_new.set_offset(offset);
|
||||||
|
double x0 = 0;
|
||||||
|
double y0 = 0;
|
||||||
|
REQUIRE(off_path_new.vertex(&x0, &y0) == mapnik::SEG_END);
|
||||||
|
REQUIRE(off_path_new.vertex(&x0, &y0) == mapnik::SEG_END);
|
||||||
|
REQUIRE(off_path_new.vertex(&x0, &y0) == mapnik::SEG_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void test_simple_segment(double const &offset)
|
void test_simple_segment(double const &offset)
|
||||||
{
|
{
|
||||||
fake_path path = {0, 0, 1, 0}, off_path = {0, offset, 1, offset};
|
fake_path path = {0, 0, 1, 0}, off_path = {0, offset, 1, offset};
|
||||||
mapnik::offset_converter<fake_path> off_path_new(path);
|
mapnik::offset_converter<fake_path> off_path_new(path);
|
||||||
off_path_new.set_offset(-offset);
|
off_path_new.set_offset(offset);
|
||||||
|
|
||||||
double x0, y0, x1, y1;
|
double x0, y0, x1, y1;
|
||||||
unsigned cmd0 = off_path_new.vertex(&x0, &y0);
|
unsigned cmd0 = off_path_new.vertex(&x0, &y0);
|
||||||
|
@ -116,7 +150,7 @@ void test_straight_line(double const &offset) {
|
||||||
fake_path path = {0, 0, 1, 0, 9, 0, 10, 0},
|
fake_path path = {0, 0, 1, 0, 9, 0, 10, 0},
|
||||||
off_path = {0, offset, 1, offset, 9, offset, 10, offset};
|
off_path = {0, offset, 1, offset, 9, offset, 10, offset};
|
||||||
mapnik::offset_converter<fake_path> off_path_new(path);
|
mapnik::offset_converter<fake_path> off_path_new(path);
|
||||||
off_path_new.set_offset(-offset);
|
off_path_new.set_offset(offset);
|
||||||
|
|
||||||
double x0, y0, x1, y1;
|
double x0, y0, x1, y1;
|
||||||
unsigned cmd0 = off_path_new.vertex(&x0, &y0);
|
unsigned cmd0 = off_path_new.vertex(&x0, &y0);
|
||||||
|
@ -166,7 +200,7 @@ void test_offset_curve(double const &offset) {
|
||||||
|
|
||||||
fake_path path(pos), off_path(off_pos);
|
fake_path path(pos), off_path(off_pos);
|
||||||
mapnik::offset_converter<fake_path> off_path_new(path);
|
mapnik::offset_converter<fake_path> off_path_new(path);
|
||||||
off_path_new.set_offset(-offset);
|
off_path_new.set_offset(offset);
|
||||||
|
|
||||||
double x0, y0, x1, y1;
|
double x0, y0, x1, y1;
|
||||||
unsigned cmd0 = off_path_new.vertex(&x0, &y0);
|
unsigned cmd0 = off_path_new.vertex(&x0, &y0);
|
||||||
|
@ -222,7 +256,7 @@ void test_s_shaped_curve(double const &offset) {
|
||||||
|
|
||||||
fake_path path(pos), off_path(off_pos);
|
fake_path path(pos), off_path(off_pos);
|
||||||
mapnik::offset_converter<fake_path> off_path_new(path);
|
mapnik::offset_converter<fake_path> off_path_new(path);
|
||||||
off_path_new.set_offset(-offset);
|
off_path_new.set_offset(offset);
|
||||||
|
|
||||||
double x0, y0, x1, y1;
|
double x0, y0, x1, y1;
|
||||||
unsigned cmd0 = off_path_new.vertex(&x0, &y0);
|
unsigned cmd0 = off_path_new.vertex(&x0, &y0);
|
||||||
|
@ -263,6 +297,41 @@ void test_s_shaped_curve(double const &offset) {
|
||||||
|
|
||||||
TEST_CASE("offset converter") {
|
TEST_CASE("offset converter") {
|
||||||
|
|
||||||
|
SECTION("null segment") {
|
||||||
|
try {
|
||||||
|
|
||||||
|
std::vector<double> offsets = { 1, -1 };
|
||||||
|
for (double offset : offsets) {
|
||||||
|
// test simple straight line segment - should be easy to
|
||||||
|
// find the correspondance here.
|
||||||
|
offset_test::test_null_segment(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::exception const& ex)
|
||||||
|
{
|
||||||
|
std::cerr << ex.what() << "\n";
|
||||||
|
REQUIRE(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("invalid segment") {
|
||||||
|
try {
|
||||||
|
|
||||||
|
std::vector<double> offsets = { 1, -1 };
|
||||||
|
for (double offset : offsets) {
|
||||||
|
// test simple straight line segment - should be easy to
|
||||||
|
// find the correspondance here.
|
||||||
|
offset_test::test_invalid_segment(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::exception const& ex)
|
||||||
|
{
|
||||||
|
std::cerr << ex.what() << "\n";
|
||||||
|
REQUIRE(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
SECTION("simple segment") {
|
SECTION("simple segment") {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
/*****************************************************************************
|
|
||||||
*
|
|
||||||
* This file is part of Mapnik (c++ mapping toolkit)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2015 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 COMPARE_IMAGES_HPP
|
|
||||||
#define COMPARE_IMAGES_HPP
|
|
||||||
|
|
||||||
// stl
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
// mapnik
|
|
||||||
#include <mapnik/image_util.hpp>
|
|
||||||
#include <mapnik/image_reader.hpp>
|
|
||||||
|
|
||||||
namespace visual_tests
|
|
||||||
{
|
|
||||||
|
|
||||||
template <typename Image>
|
|
||||||
unsigned compare_images(Image const & actual, std::string const & reference)
|
|
||||||
{
|
|
||||||
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(reference, "png"));
|
|
||||||
if (!reader.get())
|
|
||||||
{
|
|
||||||
throw mapnik::image_reader_exception("Failed to load: " + reference);
|
|
||||||
}
|
|
||||||
|
|
||||||
mapnik::image_any ref_image_any = reader->read(0, 0, reader->width(), reader->height());
|
|
||||||
Image const & reference_image = mapnik::util::get<Image>(ref_image_any);
|
|
||||||
|
|
||||||
return mapnik::compare(actual, reference_image, 0, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -26,6 +26,7 @@
|
||||||
// stl
|
// stl
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
// boost
|
// boost
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
@ -35,21 +36,23 @@ namespace visual_tests
|
||||||
|
|
||||||
struct map_size
|
struct map_size
|
||||||
{
|
{
|
||||||
map_size(int _width, int _height) : width(_width), height(_height) { }
|
map_size(std::size_t _width, std::size_t _height) : width(_width), height(_height) { }
|
||||||
map_size() { }
|
map_size() { }
|
||||||
unsigned width = 0;
|
std::size_t width = 0;
|
||||||
unsigned height = 0;
|
std::size_t height = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct config
|
struct config
|
||||||
{
|
{
|
||||||
config() : status(true),
|
config() : status(true),
|
||||||
scales({ 1.0, 2.0 }),
|
scales({ 1.0, 2.0 }),
|
||||||
sizes({ { 500, 100 } }) { }
|
sizes({ { 500, 100 } }),
|
||||||
|
tiles({ { 1, 1 } }) { }
|
||||||
|
|
||||||
bool status;
|
bool status;
|
||||||
std::vector<double> scales;
|
std::vector<double> scales;
|
||||||
std::vector<map_size> sizes;
|
std::vector<map_size> sizes;
|
||||||
|
std::vector<map_size> tiles;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum result_state : std::uint8_t
|
enum result_state : std::uint8_t
|
||||||
|
@ -66,11 +69,13 @@ struct result
|
||||||
result_state state;
|
result_state state;
|
||||||
std::string renderer_name;
|
std::string renderer_name;
|
||||||
map_size size;
|
map_size size;
|
||||||
|
map_size tiles;
|
||||||
double scale_factor;
|
double scale_factor;
|
||||||
boost::filesystem::path actual_image_path;
|
boost::filesystem::path actual_image_path;
|
||||||
boost::filesystem::path reference_image_path;
|
boost::filesystem::path reference_image_path;
|
||||||
std::string error_message;
|
std::string error_message;
|
||||||
unsigned diff;
|
unsigned diff;
|
||||||
|
std::chrono::high_resolution_clock::duration duration;
|
||||||
};
|
};
|
||||||
|
|
||||||
using result_list = std::vector<result>;
|
using result_list = std::vector<result>;
|
||||||
|
|
|
@ -23,15 +23,16 @@
|
||||||
#ifndef RENDERER_HPP
|
#ifndef RENDERER_HPP
|
||||||
#define RENDERER_HPP
|
#define RENDERER_HPP
|
||||||
|
|
||||||
#include "compare_images.hpp"
|
|
||||||
|
|
||||||
// stl
|
// stl
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
// mapnik
|
// mapnik
|
||||||
#include <mapnik/map.hpp>
|
#include <mapnik/map.hpp>
|
||||||
|
#include <mapnik/image_util.hpp>
|
||||||
|
#include <mapnik/image_reader.hpp>
|
||||||
#include <mapnik/agg_renderer.hpp>
|
#include <mapnik/agg_renderer.hpp>
|
||||||
#if defined(GRID_RENDERER)
|
#if defined(GRID_RENDERER)
|
||||||
#include <mapnik/grid/grid_renderer.hpp>
|
#include <mapnik/grid/grid_renderer.hpp>
|
||||||
|
@ -56,10 +57,20 @@ struct renderer_base
|
||||||
using image_type = ImageType;
|
using image_type = ImageType;
|
||||||
|
|
||||||
static constexpr const char * ext = ".png";
|
static constexpr const char * ext = ".png";
|
||||||
|
static constexpr const bool support_tiles = true;
|
||||||
|
|
||||||
unsigned compare(image_type const & actual, boost::filesystem::path const& reference) const
|
unsigned compare(image_type const & actual, boost::filesystem::path const& reference) const
|
||||||
{
|
{
|
||||||
return compare_images(actual, reference.string());
|
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(reference.string(), "png"));
|
||||||
|
if (!reader.get())
|
||||||
|
{
|
||||||
|
throw mapnik::image_reader_exception("Failed to load: " + reference.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
mapnik::image_any ref_image_any = reader->read(0, 0, reader->width(), reader->height());
|
||||||
|
ImageType const & reference_image = mapnik::util::get<ImageType>(ref_image_any);
|
||||||
|
|
||||||
|
return mapnik::compare(actual, reference_image, 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void save(image_type const & image, boost::filesystem::path const& path) const
|
void save(image_type const & image, boost::filesystem::path const& path) const
|
||||||
|
@ -106,6 +117,7 @@ struct svg_renderer : renderer_base<std::string>
|
||||||
{
|
{
|
||||||
static constexpr const char * name = "svg";
|
static constexpr const char * name = "svg";
|
||||||
static constexpr const char * ext = ".svg";
|
static constexpr const char * ext = ".svg";
|
||||||
|
static constexpr const bool support_tiles = false;
|
||||||
|
|
||||||
image_type render(mapnik::Map const & map, double scale_factor) const
|
image_type render(mapnik::Map const & map, double scale_factor) const
|
||||||
{
|
{
|
||||||
|
@ -125,7 +137,7 @@ struct svg_renderer : renderer_base<std::string>
|
||||||
}
|
}
|
||||||
std::string expected(std::istreambuf_iterator<char>(stream.rdbuf()),(std::istreambuf_iterator<char>()));
|
std::string expected(std::istreambuf_iterator<char>(stream.rdbuf()),(std::istreambuf_iterator<char>()));
|
||||||
stream.close();
|
stream.close();
|
||||||
return std::fabs(actual.size() - expected.size());
|
return std::max(actual.size(), expected.size()) - std::min(actual.size(), expected.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void save(image_type const & image, boost::filesystem::path const& path) const
|
void save(image_type const & image, boost::filesystem::path const& path) const
|
||||||
|
@ -191,19 +203,76 @@ struct grid_renderer : renderer_base<mapnik::image_rgba8>
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void set_rectangle(T const & src, T & dst, std::size_t x, std::size_t y)
|
||||||
|
{
|
||||||
|
mapnik::box2d<int> ext0(0, 0, dst.width(), dst.height());
|
||||||
|
mapnik::box2d<int> ext1(x, y, x + src.width(), y + src.height());
|
||||||
|
|
||||||
|
if (ext0.intersects(ext1))
|
||||||
|
{
|
||||||
|
mapnik::box2d<int> box = ext0.intersect(ext1);
|
||||||
|
for (std::size_t pix_y = box.miny(); pix_y < static_cast<std::size_t>(box.maxy()); ++pix_y)
|
||||||
|
{
|
||||||
|
typename T::pixel_type * row_to = dst.get_row(pix_y);
|
||||||
|
typename T::pixel_type const * row_from = src.get_row(pix_y - y);
|
||||||
|
|
||||||
|
for (std::size_t pix_x = box.minx(); pix_x < static_cast<std::size_t>(box.maxx()); ++pix_x)
|
||||||
|
{
|
||||||
|
row_to[pix_x] = row_from[pix_x - x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Renderer>
|
template <typename Renderer>
|
||||||
class renderer
|
class renderer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using renderer_type = Renderer;
|
||||||
|
using image_type = typename Renderer::image_type;
|
||||||
|
|
||||||
renderer(boost::filesystem::path const & _output_dir, boost::filesystem::path const & _reference_dir, bool _overwrite)
|
renderer(boost::filesystem::path const & _output_dir, boost::filesystem::path const & _reference_dir, bool _overwrite)
|
||||||
: ren(), output_dir(_output_dir), reference_dir(_reference_dir), overwrite(_overwrite)
|
: ren(), output_dir(_output_dir), reference_dir(_reference_dir), overwrite(_overwrite)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
result test(std::string const & name, mapnik::Map const & map, double scale_factor) const
|
image_type render(mapnik::Map const & map, double scale_factor) const
|
||||||
{
|
{
|
||||||
typename Renderer::image_type image(ren.render(map, scale_factor));
|
return ren.render(map, scale_factor);
|
||||||
boost::filesystem::path reference = reference_dir / image_file_name(name, map.width(), map.height(), scale_factor, true, Renderer::ext);
|
}
|
||||||
|
|
||||||
|
image_type render(mapnik::Map & map, double scale_factor, map_size const & tiles) const
|
||||||
|
{
|
||||||
|
mapnik::box2d<double> box = map.get_current_extent();
|
||||||
|
image_type image(map.width(), map.height());
|
||||||
|
map.resize(image.width() / tiles.width, image.height() / tiles.height);
|
||||||
|
double tile_box_width = box.width() / tiles.width;
|
||||||
|
double tile_box_height = box.height() / tiles.height;
|
||||||
|
for (std::size_t tile_y = 0; tile_y < tiles.height; tile_y++)
|
||||||
|
{
|
||||||
|
for (std::size_t tile_x = 0; tile_x < tiles.width; tile_x++)
|
||||||
|
{
|
||||||
|
mapnik::box2d<double> tile_box(
|
||||||
|
box.minx() + tile_x * tile_box_width,
|
||||||
|
box.miny() + tile_y * tile_box_height,
|
||||||
|
box.minx() + (tile_x + 1) * tile_box_width,
|
||||||
|
box.miny() + (tile_y + 1) * tile_box_height);
|
||||||
|
map.zoom_to_box(tile_box);
|
||||||
|
image_type tile(ren.render(map, scale_factor));
|
||||||
|
set_rectangle(tile, image, tile_x * tile.width(), (tiles.height - 1 - tile_y) * tile.height());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
result report(image_type const & image,
|
||||||
|
std::string const & name,
|
||||||
|
map_size const & size,
|
||||||
|
map_size const & tiles,
|
||||||
|
double scale_factor) const
|
||||||
|
{
|
||||||
|
boost::filesystem::path reference = reference_dir / image_file_name(name, size, tiles, scale_factor, true);
|
||||||
bool reference_exists = boost::filesystem::exists(reference);
|
bool reference_exists = boost::filesystem::exists(reference);
|
||||||
result res;
|
result res;
|
||||||
|
|
||||||
|
@ -211,14 +280,15 @@ public:
|
||||||
res.name = name;
|
res.name = name;
|
||||||
res.renderer_name = Renderer::name;
|
res.renderer_name = Renderer::name;
|
||||||
res.scale_factor = scale_factor;
|
res.scale_factor = scale_factor;
|
||||||
res.size = map_size(map.width(), map.height());
|
res.size = size;
|
||||||
|
res.tiles = tiles;
|
||||||
res.reference_image_path = reference;
|
res.reference_image_path = reference;
|
||||||
res.diff = reference_exists ? ren.compare(image, reference) : 0;
|
res.diff = reference_exists ? ren.compare(image, reference) : 0;
|
||||||
|
|
||||||
if (res.diff)
|
if (res.diff)
|
||||||
{
|
{
|
||||||
boost::filesystem::create_directories(output_dir);
|
boost::filesystem::create_directories(output_dir);
|
||||||
boost::filesystem::path path = output_dir / image_file_name(name, map.width(), map.height(), scale_factor, false, Renderer::ext);
|
boost::filesystem::path path = output_dir / image_file_name(name, size, tiles, scale_factor, false);
|
||||||
res.actual_image_path = path;
|
res.actual_image_path = path;
|
||||||
res.state = STATE_FAIL;
|
res.state = STATE_FAIL;
|
||||||
ren.save(image, path);
|
ren.save(image, path);
|
||||||
|
@ -235,16 +305,23 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string image_file_name(std::string const & test_name,
|
std::string image_file_name(std::string const & test_name,
|
||||||
double width,
|
map_size const & size,
|
||||||
double height,
|
map_size const & tiles,
|
||||||
double scale_factor,
|
double scale_factor,
|
||||||
bool reference,
|
bool reference) const
|
||||||
std::string const& ext) const
|
|
||||||
{
|
{
|
||||||
std::stringstream s;
|
std::stringstream s;
|
||||||
s << test_name << '-' << width << '-' << height << '-'
|
s << test_name << '-' << size.width << '-' << size.height << '-';
|
||||||
<< std::fixed << std::setprecision(1) << scale_factor
|
if (tiles.width > 1 || tiles.height > 1)
|
||||||
<< '-' << Renderer::name << (reference ? "-reference" : "") << ext;
|
{
|
||||||
|
s << tiles.width << 'x' << tiles.height << '-';
|
||||||
|
}
|
||||||
|
s << std::fixed << std::setprecision(1) << scale_factor << '-' << Renderer::name;
|
||||||
|
if (reference)
|
||||||
|
{
|
||||||
|
s << "-reference";
|
||||||
|
}
|
||||||
|
s << Renderer::ext;
|
||||||
return s.str();
|
return s.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
#include "report.hpp"
|
#include "report.hpp"
|
||||||
|
|
||||||
|
@ -32,8 +33,12 @@ namespace visual_tests
|
||||||
|
|
||||||
void console_report::report(result const & r)
|
void console_report::report(result const & r)
|
||||||
{
|
{
|
||||||
s << '"' << r.name << '-' << r.size.width << '-' << r.size.height << '-' << std::fixed <<
|
s << '"' << r.name << '-' << r.size.width << '-' << r.size.height;
|
||||||
std::setprecision(1) << r.scale_factor << "\" with " << r.renderer_name << "... ";
|
if (r.tiles.width > 1 || r.tiles.height > 1)
|
||||||
|
{
|
||||||
|
s << '-' << r.tiles.width << 'x' << r.tiles.height;
|
||||||
|
}
|
||||||
|
s << '-' << std::fixed << std::setprecision(1) << r.scale_factor << "\" with " << r.renderer_name << "... ";
|
||||||
|
|
||||||
switch (r.state)
|
switch (r.state)
|
||||||
{
|
{
|
||||||
|
@ -51,6 +56,11 @@ void console_report::report(result const & r)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (show_duration)
|
||||||
|
{
|
||||||
|
s << " (" << std::chrono::duration_cast<std::chrono::milliseconds>(r.duration).count() << " milliseconds)";
|
||||||
|
}
|
||||||
|
|
||||||
s << std::endl;
|
s << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +71,10 @@ unsigned console_report::summary(result_list const & results)
|
||||||
unsigned fail = 0;
|
unsigned fail = 0;
|
||||||
unsigned overwrite = 0;
|
unsigned overwrite = 0;
|
||||||
|
|
||||||
|
using namespace std::chrono;
|
||||||
|
using duration_map_type = std::map<std::string, high_resolution_clock::duration>;
|
||||||
|
duration_map_type durations;
|
||||||
|
|
||||||
for (auto const & r : results)
|
for (auto const & r : results)
|
||||||
{
|
{
|
||||||
switch (r.state)
|
switch (r.state)
|
||||||
|
@ -70,12 +84,37 @@ unsigned console_report::summary(result_list const & results)
|
||||||
case STATE_ERROR: error++; break;
|
case STATE_ERROR: error++; break;
|
||||||
case STATE_OVERWRITE: overwrite++; break;
|
case STATE_OVERWRITE: overwrite++; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (show_duration)
|
||||||
|
{
|
||||||
|
duration_map_type::iterator duration = durations.find(r.renderer_name);
|
||||||
|
if (duration == durations.end())
|
||||||
|
{
|
||||||
|
durations.emplace(r.renderer_name, r.duration);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
duration->second += r.duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s << std::endl;
|
s << std::endl;
|
||||||
s << "Visual rendering: " << fail << " failed / " << ok << " passed / "
|
s << "Visual rendering: " << fail << " failed / " << ok << " passed / "
|
||||||
<< overwrite << " overwritten / " << error << " errors" << std::endl;
|
<< overwrite << " overwritten / " << error << " errors" << std::endl;
|
||||||
|
|
||||||
|
if (show_duration)
|
||||||
|
{
|
||||||
|
high_resolution_clock::duration total(0);
|
||||||
|
for (auto const & duration : durations)
|
||||||
|
{
|
||||||
|
s << duration.first << ": \t" << duration_cast<milliseconds>(duration.second).count()
|
||||||
|
<< " milliseconds" << std::endl;
|
||||||
|
total += duration.second;
|
||||||
|
}
|
||||||
|
s << "total: \t" << duration_cast<milliseconds>(total).count() << " milliseconds" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
return fail + error;
|
return fail + error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ namespace visual_tests
|
||||||
class console_report
|
class console_report
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
console_report() : s(std::clog)
|
console_report(bool _show_duration) : s(std::clog), show_duration(_show_duration)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,12 +49,13 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::ostream & s;
|
std::ostream & s;
|
||||||
|
bool show_duration;
|
||||||
};
|
};
|
||||||
|
|
||||||
class console_short_report : public console_report
|
class console_short_report : public console_report
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
console_short_report() : console_report()
|
console_short_report(bool _show_duration) : console_report(_show_duration)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,18 @@
|
||||||
|
|
||||||
#include "cleanup.hpp" // run_cleanup()
|
#include "cleanup.hpp" // run_cleanup()
|
||||||
|
|
||||||
|
#ifdef MAPNIK_LOG
|
||||||
|
using log_levels_map = std::map<std::string, mapnik::logger::severity_type>;
|
||||||
|
|
||||||
|
log_levels_map log_levels
|
||||||
|
{
|
||||||
|
{ "debug", mapnik::logger::severity_type::debug },
|
||||||
|
{ "warn", mapnik::logger::severity_type::warn },
|
||||||
|
{ "error", mapnik::logger::severity_type::error },
|
||||||
|
{ "none", mapnik::logger::severity_type::none }
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
using namespace visual_tests;
|
using namespace visual_tests;
|
||||||
|
@ -41,6 +53,8 @@ int main(int argc, char** argv)
|
||||||
("help,h", "produce usage message")
|
("help,h", "produce usage message")
|
||||||
("verbose,v", "verbose output")
|
("verbose,v", "verbose output")
|
||||||
("overwrite,o", "overwrite reference image")
|
("overwrite,o", "overwrite reference image")
|
||||||
|
("duration,d", "output rendering duration")
|
||||||
|
("iterations,i", po::value<std::size_t>()->default_value(1), "number of iterations for benchmarking")
|
||||||
("jobs,j", po::value<std::size_t>()->default_value(1), "number of parallel threads")
|
("jobs,j", po::value<std::size_t>()->default_value(1), "number of parallel threads")
|
||||||
("styles-dir", po::value<std::string>()->default_value("test/data-visual/styles"), "directory with styles")
|
("styles-dir", po::value<std::string>()->default_value("test/data-visual/styles"), "directory with styles")
|
||||||
("images-dir", po::value<std::string>()->default_value("test/data-visual/images"), "directory with reference images")
|
("images-dir", po::value<std::string>()->default_value("test/data-visual/images"), "directory with reference images")
|
||||||
|
@ -49,6 +63,11 @@ int main(int argc, char** argv)
|
||||||
("styles", po::value<std::vector<std::string>>(), "selected styles to test")
|
("styles", po::value<std::vector<std::string>>(), "selected styles to test")
|
||||||
("fonts", po::value<std::string>()->default_value("fonts"), "font search path")
|
("fonts", po::value<std::string>()->default_value("fonts"), "font search path")
|
||||||
("plugins", po::value<std::string>()->default_value("plugins/input"), "input plugins search path")
|
("plugins", po::value<std::string>()->default_value("plugins/input"), "input plugins search path")
|
||||||
|
#ifdef MAPNIK_LOG
|
||||||
|
("log", po::value<std::string>()->default_value(std::find_if(log_levels.begin(), log_levels.end(),
|
||||||
|
[](log_levels_map::value_type const & level) { return level.second == mapnik::logger::get_severity(); } )->first),
|
||||||
|
"log level (debug, warn, error, none)")
|
||||||
|
#endif
|
||||||
;
|
;
|
||||||
|
|
||||||
po::positional_options_description p;
|
po::positional_options_description p;
|
||||||
|
@ -63,6 +82,20 @@ int main(int argc, char** argv)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MAPNIK_LOG
|
||||||
|
std::string log_level(vm["log"].as<std::string>());
|
||||||
|
log_levels_map::const_iterator level_iter = log_levels.find(log_level);
|
||||||
|
if (level_iter == log_levels.end())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: Unknown log level: " << log_level << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mapnik::logger::set_severity(level_iter->second);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
mapnik::freetype_engine::register_fonts(vm["fonts"].as<std::string>(), true);
|
mapnik::freetype_engine::register_fonts(vm["fonts"].as<std::string>(), true);
|
||||||
mapnik::datasource_cache::instance().register_datasources(vm["plugins"].as<std::string>());
|
mapnik::datasource_cache::instance().register_datasources(vm["plugins"].as<std::string>());
|
||||||
|
|
||||||
|
@ -77,8 +110,10 @@ int main(int argc, char** argv)
|
||||||
output_dir,
|
output_dir,
|
||||||
vm["images-dir"].as<std::string>(),
|
vm["images-dir"].as<std::string>(),
|
||||||
vm.count("overwrite"),
|
vm.count("overwrite"),
|
||||||
|
vm["iterations"].as<std::size_t>(),
|
||||||
vm["jobs"].as<std::size_t>());
|
vm["jobs"].as<std::size_t>());
|
||||||
report_type report = vm.count("verbose") ? report_type((console_report())) : report_type((console_short_report()));
|
bool show_duration = vm.count("duration");
|
||||||
|
report_type report(vm.count("verbose") ? report_type((console_report(show_duration))) : report_type((console_short_report(show_duration))));
|
||||||
result_list results;
|
result_list results;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
|
@ -34,32 +34,97 @@ namespace visual_tests
|
||||||
class renderer_visitor
|
class renderer_visitor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
renderer_visitor(std::string const & name, mapnik::Map const & map, double scale_factor)
|
renderer_visitor(std::string const & name,
|
||||||
: name_(name), map_(map), scale_factor_(scale_factor)
|
mapnik::Map & map,
|
||||||
|
map_size const & tiles,
|
||||||
|
double scale_factor,
|
||||||
|
result_list & results,
|
||||||
|
report_type & report,
|
||||||
|
std::size_t iterations)
|
||||||
|
: name_(name),
|
||||||
|
map_(map),
|
||||||
|
tiles_(tiles),
|
||||||
|
scale_factor_(scale_factor),
|
||||||
|
results_(results),
|
||||||
|
report_(report),
|
||||||
|
iterations_(iterations)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T, typename std::enable_if<T::renderer_type::support_tiles>::type* = nullptr>
|
||||||
result operator()(T const & renderer) const
|
void operator()(T const & renderer)
|
||||||
{
|
{
|
||||||
return renderer.test(name_, map_, scale_factor_);
|
test(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename std::enable_if<!T::renderer_type::support_tiles>::type* = nullptr>
|
||||||
|
void operator()(T const & renderer)
|
||||||
|
{
|
||||||
|
if (tiles_.width == 1 && tiles_.height == 1)
|
||||||
|
{
|
||||||
|
test(renderer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
template <typename T>
|
||||||
|
void test(T const & renderer)
|
||||||
|
{
|
||||||
|
map_size size { map_.width(), map_.height() };
|
||||||
|
std::chrono::high_resolution_clock::time_point start(std::chrono::high_resolution_clock::now());
|
||||||
|
for (std::size_t i = iterations_ ; i > 0; i--)
|
||||||
|
{
|
||||||
|
typename T::image_type image(render(renderer));
|
||||||
|
if (i == 1)
|
||||||
|
{
|
||||||
|
std::chrono::high_resolution_clock::time_point end(std::chrono::high_resolution_clock::now());
|
||||||
|
result r(renderer.report(image, name_, size, tiles_, scale_factor_));
|
||||||
|
r.duration = end - start;
|
||||||
|
mapnik::util::apply_visitor(report_visitor(r), report_);
|
||||||
|
results_.push_back(std::move(r));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename std::enable_if<T::renderer_type::support_tiles>::type* = nullptr>
|
||||||
|
typename T::image_type render(T const & renderer)
|
||||||
|
{
|
||||||
|
if (tiles_.width == 1 && tiles_.height == 1)
|
||||||
|
{
|
||||||
|
return renderer.render(map_, scale_factor_);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return renderer.render(map_, scale_factor_, tiles_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename std::enable_if<!T::renderer_type::support_tiles>::type* = nullptr>
|
||||||
|
typename T::image_type render(T const & renderer)
|
||||||
|
{
|
||||||
|
return renderer.render(map_, scale_factor_);
|
||||||
|
}
|
||||||
|
|
||||||
std::string const & name_;
|
std::string const & name_;
|
||||||
mapnik::Map const & map_;
|
mapnik::Map & map_;
|
||||||
|
map_size const & tiles_;
|
||||||
double scale_factor_;
|
double scale_factor_;
|
||||||
|
result_list & results_;
|
||||||
|
report_type & report_;
|
||||||
|
std::size_t iterations_;
|
||||||
};
|
};
|
||||||
|
|
||||||
runner::runner(runner::path_type const & styles_dir,
|
runner::runner(runner::path_type const & styles_dir,
|
||||||
runner::path_type const & output_dir,
|
runner::path_type const & output_dir,
|
||||||
runner::path_type const & reference_dir,
|
runner::path_type const & reference_dir,
|
||||||
bool overwrite,
|
bool overwrite,
|
||||||
|
std::size_t iterations,
|
||||||
std::size_t jobs)
|
std::size_t jobs)
|
||||||
: styles_dir_(styles_dir),
|
: styles_dir_(styles_dir),
|
||||||
output_dir_(output_dir),
|
output_dir_(output_dir),
|
||||||
reference_dir_(reference_dir),
|
reference_dir_(reference_dir),
|
||||||
jobs_(jobs),
|
jobs_(jobs),
|
||||||
|
iterations_(iterations),
|
||||||
renderers_{ renderer<agg_renderer>(output_dir_, reference_dir_, overwrite)
|
renderers_{ renderer<agg_renderer>(output_dir_, reference_dir_, overwrite)
|
||||||
#if defined(HAVE_CAIRO)
|
#if defined(HAVE_CAIRO)
|
||||||
,renderer<cairo_renderer>(output_dir_, reference_dir_, overwrite)
|
,renderer<cairo_renderer>(output_dir_, reference_dir_, overwrite)
|
||||||
|
@ -173,12 +238,12 @@ result_list runner::test_range(files_iterator begin, files_iterator end, std::re
|
||||||
|
|
||||||
result_list runner::test_one(runner::path_type const& style_path, config cfg, report_type & report) const
|
result_list runner::test_one(runner::path_type const& style_path, config cfg, report_type & report) const
|
||||||
{
|
{
|
||||||
mapnik::Map m(cfg.sizes.front().width, cfg.sizes.front().height);
|
mapnik::Map map(cfg.sizes.front().width, cfg.sizes.front().height);
|
||||||
result_list results;
|
result_list results;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
mapnik::load_map(m, style_path.string(), true);
|
mapnik::load_map(map, style_path.string(), true);
|
||||||
}
|
}
|
||||||
catch (std::exception const& ex)
|
catch (std::exception const& ex)
|
||||||
{
|
{
|
||||||
|
@ -191,7 +256,7 @@ result_list runner::test_one(runner::path_type const& style_path, config cfg, re
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapnik::parameters const & params = m.get_extra_parameters();
|
mapnik::parameters const & params = map.get_extra_parameters();
|
||||||
|
|
||||||
boost::optional<mapnik::value_integer> status = params.get<mapnik::value_integer>("status", cfg.status);
|
boost::optional<mapnik::value_integer> status = params.get<mapnik::value_integer>("status", cfg.status);
|
||||||
|
|
||||||
|
@ -208,32 +273,52 @@ result_list runner::test_one(runner::path_type const& style_path, config cfg, re
|
||||||
parse_map_sizes(*sizes, cfg.sizes);
|
parse_map_sizes(*sizes, cfg.sizes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boost::optional<std::string> tiles = params.get<std::string>("tiles");
|
||||||
|
|
||||||
|
if (tiles)
|
||||||
|
{
|
||||||
|
cfg.tiles.clear();
|
||||||
|
parse_map_sizes(*tiles, cfg.tiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<std::string> bbox_string = params.get<std::string>("bbox");
|
||||||
|
mapnik::box2d<double> box;
|
||||||
|
|
||||||
|
if (bbox_string)
|
||||||
|
{
|
||||||
|
box.from_string(*bbox_string);
|
||||||
|
}
|
||||||
|
|
||||||
std::string name(style_path.stem().string());
|
std::string name(style_path.stem().string());
|
||||||
|
|
||||||
for (map_size const & size : cfg.sizes)
|
for (auto const & size : cfg.sizes)
|
||||||
{
|
{
|
||||||
m.resize(size.width, size.height);
|
for (auto const & scale_factor : cfg.scales)
|
||||||
|
|
||||||
boost::optional<std::string> bbox_string = params.get<std::string>("bbox");
|
|
||||||
|
|
||||||
if (bbox_string)
|
|
||||||
{
|
{
|
||||||
mapnik::box2d<double> bbox;
|
for (auto const & tiles_count : cfg.tiles)
|
||||||
bbox.from_string(*bbox_string);
|
|
||||||
m.zoom_to_box(bbox);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m.zoom_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (double const & scale_factor : cfg.scales)
|
|
||||||
{
|
|
||||||
for(auto const& ren : renderers_)
|
|
||||||
{
|
{
|
||||||
result r = mapnik::util::apply_visitor(renderer_visitor(name, m, scale_factor), ren);
|
if (!tiles_count.width || !tiles_count.height)
|
||||||
results.emplace_back(r);
|
{
|
||||||
mapnik::util::apply_visitor(report_visitor(r), report);
|
throw std::runtime_error("Cannot render zero tiles.");
|
||||||
|
}
|
||||||
|
if (size.width % tiles_count.width || size.height % tiles_count.height)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Tile size is not an integer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const & ren : renderers_)
|
||||||
|
{
|
||||||
|
map.resize(size.width, size.height);
|
||||||
|
if (box.valid())
|
||||||
|
{
|
||||||
|
map.zoom_to_box(box);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
map.zoom_all();
|
||||||
|
}
|
||||||
|
mapnik::util::apply_visitor(renderer_visitor(name, map, tiles_count, scale_factor, results, report, iterations_), ren);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ public:
|
||||||
path_type const & output_dir,
|
path_type const & output_dir,
|
||||||
path_type const & reference_dir,
|
path_type const & reference_dir,
|
||||||
bool overwrite,
|
bool overwrite,
|
||||||
|
std::size_t iterations,
|
||||||
std::size_t jobs);
|
std::size_t jobs);
|
||||||
|
|
||||||
result_list test_all(report_type & report) const;
|
result_list test_all(report_type & report) const;
|
||||||
|
@ -70,6 +71,7 @@ private:
|
||||||
const path_type output_dir_;
|
const path_type output_dir_;
|
||||||
const path_type reference_dir_;
|
const path_type reference_dir_;
|
||||||
const std::size_t jobs_;
|
const std::size_t jobs_;
|
||||||
|
const std::size_t iterations_;
|
||||||
const renderer_type renderers_[boost::mpl::size<renderer_type::types>::value];
|
const renderer_type renderers_[boost::mpl::size<renderer_type::types>::value];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue