diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..49761734c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.svg text eol=lf diff --git a/.gitignore b/.gitignore index 9b10ae824..2ecdd4b32 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ *.a *.swp *.dylib +mapnik-gyp plugins/input/*.input plugins/input/templates/*.input demo/c++/rundemo @@ -29,10 +30,11 @@ tests/python_tests/raster_colorizer_test.png tests/python_tests/raster_colorizer_test_save.xml utils/mapnik-config/mapnik-config utils/shapeindex/shapeindex +utils/mapnik-index/mapnik-index utils/ogrindex/ogrindex utils/pgsql2sqlite/pgsql2sqlite utils/svg2png/svg2png -utils/nik2img/nik2img +utils/mapnik-render/mapnik-render demo/python/demo* demo/python/map.xml tests/data/sqlite/*index diff --git a/.gitmodules b/.gitmodules index 8ca2e9845..4cca9a4a8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,8 @@ [submodule "test/data"] path = test/data url = https://github.com/mapnik/test-data.git + branch = master [submodule "test/data-visual"] path = test/data-visual url = https://github.com/mapnik/test-data-visual.git + branch = master \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index c0e2afb14..656ac257a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,14 +46,18 @@ before_install: install: - if [[ $(uname -s) == 'Linux' ]]; then - psql -U postgres -c 'create database template_postgis;' -U postgres; - psql -U postgres -c 'create extension postgis;' -d template_postgis -U postgres; export CXX="ccache clang++-3.5 -Qunused-arguments"; export CC="ccache clang-3.5 -Qunused-arguments"; export PYTHONPATH=$(pwd)/mason_packages/.link/lib/python2.7/site-packages; else + brew rm postgis --force; + brew install postgis --force; + pg_ctl -w start -l postgres.log --pgdata /usr/local/var/postgres; + createuser -s postgres; export PYTHONPATH=$(pwd)/mason_packages/.link/lib/python/site-packages; fi + - psql -c 'create database template_postgis;' -U postgres; + - psql -c 'create extension postgis;' -d template_postgis -U postgres; - if [[ ${COVERAGE} == true ]]; then PYTHONUSERBASE=$(pwd)/mason_packages/.link pip install --user cpp-coveralls; fi @@ -72,13 +76,14 @@ script: ./configure; fi - make - - make test + - make test || TEST_RESULT=$? - if [[ ${COVERAGE} == true ]]; then ./mason_packages/.link/bin/cpp-coveralls --build-root . --gcov-options '\-lp' --exclude mason_packages --exclude .sconf_temp --exclude benchmark --exclude deps --exclude scons --exclude test --exclude demo --exclude docs --exclude fonts --exclude utils > /dev/null; fi - if [[ ${COVERAGE} != true ]]; then make bench; fi + - if [[ ${TEST_RESULT} != 0 ]]; then exit $TEST_RESULT ; fi; - if [[ ${MASON_PUBLISH} == true ]]; then ./mason_latest.sh build; ./mason_latest.sh link; diff --git a/AUTHORS.md b/AUTHORS.md index 75589bfbc..7c2c94a7f 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -80,3 +80,4 @@ Mapnik is written by Artem Pavlenko with contributions from: * Rich Wareham * Nick Whitelegg * Leslie Wu +* Roman Galacz diff --git a/CHANGELOG.md b/CHANGELOG.md index 4acdcb927..c6114f8e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,175 @@ Developers: Please commit along with changes. For a complete change history, see the git log. +## 3.0.9 + +Released: November 26, 2015 + +(Packaged from 03a0926) + +#### Summary + + - Fixed offsetting of complex paths and sharp angles (https://github.com/mapnik/mapnik/pull/3160) (via @winni159) + - Fixed mapnik.util.variant issue when compiling with gcc-5.x and SSO enabled by default (https://github.com/mapnik/mapnik/issues/3103) (via @nkovacs) + - Fixed issue with complex scripts where some character sequences weren't rendered correctly (https://github.com/mapnik/mapnik/issues/3050) (via @jkroll20) + - Revived postgis.input tests + - JSON: geometry grammar has been refactored and optimized to have expectation points + - Filled missing specializations for value_bool in `mapnik::value` comparison operators + - `mapnik.Image` - fixed copy semantics implementation for internal buffer + - JSON parsing: unified error_handler across all grammars + - Improved unit test coverage + - Raster scaling: fixed nodata handling, acurracy when working with small floats and clipping floats by \[0; 255\] (https://github.com/mapnik/mapnik/pull/3147) + - Added [`code of conduct`](http://contributor-covenant.org) + - GeoJSON plug-in is updated to skip feature with empty geometries + - GeoJSON plug-in : ensure original order of features is preserved (fixed) (https://github.com/mapnik/mapnik/issues/3182) + - Shapeindex utility: fixed `empty` shapes handling and ported tests to c++ + - Centroid algorithm: fixed invalid input handling, particularly empty geometries (https://github.com/mapnik/mapnik/pull/3185) + - Updated SCons build system to the latest version 2.4.1 (http://scons.org/) + +## 3.0.8 + +Released: October 23, 2015 + +(Packaged from 2d15567) + +#### Summary + + - Renamed `SHAPE_MEMORY_MAPPED_FILE` define to `MAPNIK_MEMORY_MAPPED_FILE`. Pass `./configure MEMORY_MAPPED_FILE=True|False` to request + support for memory mapped files across Mapnik plugins (currently shape, csv, and geojson). + - Unified `mapnik-index` utility supporing GeoJSON and CSV formats + - Increased unit test coverage for GeoJSON and CSV plugins + - shape.input - refactor to support *.shx and improve handling various bogus shapefiles + - geojson.input - make JSON parser stricter + support single Feature/Geometry as well as FeatureCollection + - maintain 'FT_LOAD_NO_HINTING' + support >= harfbuzz 1.0.5 + - geojson.input - implement on-disk-index support + +## 3.0.7 + +Released: October 12, 2015 + +(Packaged from e161253) + +#### Summary + + - Removed `MAPNIK_VERSION_IS_RELEASE` define / `mapnik-config --version` not longer reports `-pre` for non-release versions. + Use `mapnik-config --git-revision` instead (https://github.com/mapnik/mapnik/issues/3123) + - Renamed `nik2img` command to `mapnik-render` + - PostGIS: Fixed handling of all attributes when `key_field_as_attribute=false` (https://github.com/mapnik/mapnik/issues/3120) + - PostGIS: Fixed parsing of `key_field_as_attribute` as boolean: now `true/false` can be used in addition to `0/1` + +## 3.0.6 + +Released: October 7, 2015 + +(Packaged from 3cebe97) + +#### Summary + +- PostGIS plugin: added `key_field_as_attribute` option. Defaults to `True` to preserve current behavior of having the `key_field` added both + as an attribute and as the `feature.id` value. If `key_field_as_attribute=false` is passed then the attribute is discarded (https://github.com/mapnik/mapnik/issues/3115) +- CSV plugin has been further optimized and has gained experimental support for on-disk indexes (https://github.com/mapnik/mapnik/issues/3089) +- SVG parser now fallsback to using `viewbox` if explicit dimensions are lacking (https://github.com/mapnik/mapnik/issues/3081) +- Visual tests: new command line arguments `--agg`, `--cairo`, `--svg`, `--grid` for selecting renderers (https://github.com/mapnik/mapnik/pull/3074) +- Visual tests: new command line argument `--scale-factor` or abbreviated `-s` for setting scale factor (https://github.com/mapnik/mapnik/pull/3074) +- Fixed parsing colors in hexadecimal notation (https://github.com/mapnik/mapnik/pull/3075) +- Removed mapnik::Feature type alias of mapnik::feature_impl (https://github.com/mapnik/mapnik/pull/3099) +- Fixed linking order for plugins to avoid possible linking errors on linux systems (https://github.com/mapnik/mapnik/issues/3105) + +## 3.0.5 + +Released: September 16, 2015 + +(Packaged from 165c704) + +#### Summary + +- `scale-hsla` image filter: parameters are no longer limited by interval \[0, 1\] (https://github.com/mapnik/mapnik/pull/3054) +- Windows: Fixed SVG file loading from unicode paths +- `colorize-alpha` image filter: fixed normalization of color components (https://github.com/mapnik/mapnik/pull/3058) +- `colorize-alpha` image filter: added support for transparent colors (https://github.com/mapnik/mapnik/pull/3061) +- Enable reading optional `MAPNIK_LOG_FORMAT` environment variable(https://github.com/mapnik/mapnik/commit/6d1ffc8a93008b8c0a89d87d68b59afb2cb3757f) +- CSV.input uses memory mapped file by default on *nix. +- Updated bundled fonts to the latest version +- Topojson.input - fixed geometry_index logic which was causing missing features +- Fixed SVG file loading from unicode paths (https://github.com/mapnik/node-mapnik/issues/517) +- CSV.input - improved support for LF/CR/CRLF line endings on all platforms (https://github.com/mapnik/mapnik/issues/3065) +- Revive `zero allocation image interface` and add unit tests +- Benchmark: use return values of test runner. + +## 3.0.4 + +Released: August 26, 2015 + +(Packaged from 17bb81c) + +#### Summary + +- CSV.input: plug-in has been refactored to minimise memory usage and to improve handling of larger input. + (NOTE: [large_csv](https://github.com/mapnik/mapnik/tree/large_csv) branch adds experimental trunsduction parser with deferred string initialisation) +- CSV.input: added internal spatial index (boost::geometry::index::tree) for fast `bounding box` queries (https://github.com/mapnik/mapnik/pull/3010) +- Fixed deadlock in recursive datasource registration via @zerebubuth (https://github.com/mapnik/mapnik/pull/3038) +- Introduced new command line argument `--limit` or `-l` to limit number of failed tests via @talaj (https://github.com/mapnik/mapnik/pull/2996) + +## 3.0.3 + +Released: August 12, 2015 + +(Packaged from 3d262c7) + +#### Summary + +- Fixed an issue with fields over size of `int32` in `OGR` plugin (https://github.com/mapnik/node-mapnik/issues/499) +- Added 3 new image-filters to simulate types of colorblindness (`color-blind-protanope`,`color-blind-deuteranope`,`color-blind-tritanope`) +- Fix so that null text boxes have no bounding boxes when attempting placement ( 162f82cba5b0fb984c425586c6a4b354917abc47 ) +- Patch to add legacy method for setting JPEG quality in images ( #3024 ) +- Added `filter_image` method which can modify an image in place or return a new image that is filtered +- Added missing typedef's in `mapnik::geometry` to allow experimenting with different containers + +## 3.0.2 + +Released: July 31, 2015 + +(Packaged from 8305e74) + +#### Summary + +This release is centered around improvements to the SVG parsing within mapnik. Most work was done in pull request #3003. + +- Added container to log SVG parsing errors +- Reimplemented to use rapidxml for parsing XML (DOM) +- Support both xml:id and id attributes ( xml:id takes precedence ) +- Added parse_id_from_url using boost::spirit +- Added error tracking when parsing doubles +- Unit tests for svg_parser to improve coverage +- Fixed rx/ry validation for rounded_rect +- Fixed dimensions parsing +- Remove libxml2 dependency + +## 3.0.1 + +Released: July 27th, 2015 + +(Packaged from 28f6f4d) + +#### Summary + +The 3.0.1 fixes a few bugs in geojson parsing, svg parsing, and rendering. It also avoids a potential hang when using `line-geometry-transform` and includes a speedup for text rendering compared to v3.0.0. It is fully back compatible with v3.0.0 and everyone is encouraged to upgrade. + +- Fixed text placement performance after #2949 (#2963) +- Fixed rendering behavior for `text-minimum-path-length` which regressed in 3.0.0 (#2990) +- Fixed handling of `xml:id` in SVG parsing (#2989) +- Fixed handling of out of range `rx` and `ry` in SVG `rect` (#2991) +- Fixed reporting of envelope from `mapnik::memory_datasource` when new features are added (#2985) +- Fixed parsing of GeoJSON when unknown properties encountered at `FeatureCollection` level (#2983) +- Fixed parsing of GeoJSON when properties contained `{}` (#2964) +- Fixed potential hang due to invalid use of `line-geometry-transform` (6d6cb15) +- Moved unmaintained plugins out of core: `osm`, `occi`, and `rasterlite` (#2980) + ## 3.0.0 -Released: June 30th, 2015 +Released: July 7th, 2015 -(Packaged from ...) +(Packaged from e6891a0) #### Summary @@ -66,7 +230,7 @@ The 3.0 release is a major milestone for Mapnik and includes many performance an - 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. - `TextSymbolizer` now supports `smooth`, `simplify`, `halo-opacity`, `halo-comp-op`, and `halo-transform` @@ -147,7 +311,7 @@ geometry. - Optimized expression evaluation of text by avoiding extra copy (1dd1275) -- Added Map level `background-image-comp-op` to control the compositing operation used to blend the +- Added Map level `background-image-comp-op` to control the compositing operation used to blend the `background-image` onto the `background-color`. Has no meaning if `background-color` or `background-image` are not set. (#1966) @@ -335,8 +499,8 @@ Summary: The 2.2.0 release is primarily a performance and stability release. The - Enabled default input plugin directory and fonts path to be set inherited from environment settings in python bindings to make it easier to run tests locally (#1594). New environment settings are: - - MAPNIK_INPUT_PLUGINS_DIRECTORY - - MAPNIK_FONT_DIRECTORY + - MAPNIK_INPUT_PLUGINS_DIRECTORY + - MAPNIK_FONT_DIRECTORY - Added support for controlling rendering behavior of markers on multi-geometries `marker-multi-policy` (#1555,#1573) @@ -728,7 +892,7 @@ Released January, 19 2010 - Gdal Plugin: Added support for Gdal overviews, enabling fast loading of > 1GB rasters (#54) - * Use the gdaladdo utility to add overviews to existing GDAL datasets + * Use the gdaladdo utility to add overviews to existing GDAL datasets - PostGIS: Added an optional `geometry_table` parameter. The `geometry_table` used by Mapnik to look up metadata in the geometry_columns and calculate extents (when the `geometry_field` and `srid` parameters @@ -753,23 +917,23 @@ Released January, 19 2010 complex queries that may aggregate geometries to be kept fast by allowing proper placement of the bbox query to be used by indexes. (#415) - * Pass the bbox token inside a subquery like: !bbox! + * Pass the bbox token inside a subquery like: !bbox! - * Valid Usages include: + * Valid Usages include: - - (Select ST_Union(geom) as geom from table where ST_Intersects(geometry,!bbox!)) as map - + + (Select ST_Union(geom) as geom from table where ST_Intersects(geometry,!bbox!)) as map + - - (Select * from table where geom && !bbox!) as map - + + (Select * from table where geom && !bbox!) as map + - PostGIS Plugin: Added `scale_denominator` substitution ability in sql query string (#415/#465) - * Pass the scale_denominator token inside a subquery like: !scale_denominator! + * Pass the scale_denominator token inside a subquery like: !scale_denominator! - * e.g. (Select * from table where field_value > !scale_denominator!) as map + * e.g. (Select * from table where field_value > !scale_denominator!) as map - PostGIS Plugin: Added support for quoted table names (r1454) (#393) @@ -801,14 +965,14 @@ Released January, 19 2010 - TextSymbolizer: Large set of new attributes: `text_transform`, `line_spacing`, `character_spacing`, `wrap_character`, `wrap_before`, `horizontal_alignment`, `justify_alignment`, and `opacity`. - * More details at changesets: r1254 and r1341 + * More details at changesets: r1254 and r1341 - SheildSymbolizer: Added special new attributes: `unlock_image`, `VERTEX` placement, `no_text` and many attributes previously only supported in the TextSymbolizer: `allow_overlap`, `vertical_alignment`, `horizontal_alignment`, `justify_alignment`, `wrap_width`, `wrap_character`, `wrap_before`, `text_transform`, `line_spacing`, `character_spacing`, and `opacity`. - * More details at changeset r1341 + * More details at changeset r1341 - XML: Added support for using CDATA with libxml2 parser (r1364) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..65c05c574 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,22 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic addresses, without explicit permission +* Other unethical or unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. + +This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) \ No newline at end of file diff --git a/INSTALL.md b/INSTALL.md index b88abd2a4..0776a6f72 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -94,9 +94,6 @@ Additional optional dependencies: - pg_config - PostgreSQL installation capabilities * libgdal - GDAL/OGR input (For gdal and ogr plugin support) * libsqlite3 - SQLite input (needs RTree support builtin) (sqlite plugin support) - * libocci - Oracle input plugin support - * libcurl - OSM input plugin support - * librasterlite - Rasterlite input plugin support Instructions for installing many of these dependencies on various platforms can be found at the Mapnik Wiki: @@ -159,6 +156,12 @@ You can run the Mapnik tests locally (without installing) like: make test +## Python Bindings + +Python bindings are not included by default. You'll need to add those separately. + + * Build from source: https://github.com/mapnik/python-mapnik + ## Learning Mapnik ### Help diff --git a/Makefile b/Makefile index ef02f71f7..3ff06a59c 100755 --- a/Makefile +++ b/Makefile @@ -12,6 +12,25 @@ all: mapnik install: $(PYTHON) scons/scons.py -j$(JOBS) --config=cache --implicit-cache --max-drift=1 install +release: + export MAPNIK_VERSION=$(shell ./utils/mapnik-config/mapnik-config --version) && \ + export TARBALL_NAME="mapnik-v$${MAPNIK_VERSION}" && \ + cd /tmp/ && \ + rm -rf $${TARBALL_NAME} && \ + git clone --depth 1 --branch v$${MAPNIK_VERSION} git@github.com:mapnik/mapnik.git $${TARBALL_NAME} && \ + cd $${TARBALL_NAME} && \ + git checkout "tags/v$${MAPNIK_VERSION}" && \ + git submodule update --depth 1 --init && \ + rm -rf test/data/.git && \ + rm -rf test/data/.gitignore && \ + rm -rf test/data-visual/.git && \ + rm -rf test/data-visual/.gitignore && \ + rm -rf .git && \ + rm -rf .gitignore && \ + cd ../ && \ + tar cjf $${TARBALL_NAME}.tar.bz2 $${TARBALL_NAME}/ && \ + aws s3 cp --acl public-read $${TARBALL_NAME}.tar.bz2 s3://mapnik/dist/v$${MAPNIK_VERSION}/ + python: if [ ! -d ./bindings/python ]; then git clone git@github.com:mapnik/python-mapnik.git --recursive ./bindings/python; else (cd bindings/python && git pull && git submodule update --init); fi; make diff --git a/README.md b/README.md index dcb049e6f..18268deb6 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ _/ _/ _/_/_/ _/_/_/ _/ _/ _/ _/ _/ _/ ``` -[![Build Status](https://secure.travis-ci.org/mapnik/mapnik.png)](http://travis-ci.org/mapnik/mapnik) +[![Build Status Linux](https://secure.travis-ci.org/mapnik/mapnik.png)](http://travis-ci.org/mapnik/mapnik) +[![Build status Windows](https://ci.appveyor.com/api/projects/status/hc9l7okdjtucfqqn?svg=true)](https://ci.appveyor.com/project/Mapbox/mapnik) +[![Coverage Status](https://coveralls.io/repos/mapnik/mapnik/badge.svg?branch=master&service=github)](https://coveralls.io/github/mapnik/mapnik?branch=master) Mapnik is an open source toolkit for developing mapping applications. At the core is a C++ shared library providing algorithms and patterns for spatial data access and visualization. @@ -20,6 +22,10 @@ For further information see [http://mapnik.org](http://mapnik.org) and also our See [INSTALL.md](https://github.com/mapnik/mapnik/blob/master/INSTALL.md) for installation instructions and the [Install](https://github.com/mapnik/mapnik/wiki/Mapnik-Installation) page on the wiki for guides. +# Code of Conduct + +Please note that this project is released with a [Contributor Code of Conduct](https://github.com/mapnik/mapnik/blob/master/CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. + # License Mapnik software is free and is released under the LGPL ([GNU Lesser General Public License](http://www.gnu.org/licenses/lgpl.html_)). Please see [COPYING](https://github.com/mapnik/mapnik/blob/master/COPYING) for more information. diff --git a/SConstruct b/SConstruct index c72d1223c..b3c1a1903 100644 --- a/SConstruct +++ b/SConstruct @@ -1,6 +1,6 @@ # This file is part of Mapnik (c++ mapping toolkit) # -# Copyright (C) 2013 Artem Pavlenko +# Copyright (C) 2015 Artem Pavlenko # # Mapnik is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -110,14 +110,7 @@ PLUGINS = { # plugins with external dependencies 'pgraster': {'default':True,'path':None,'inc':'libpq-fe.h','lib':'pq','lang':'C'}, 'gdal': {'default':True,'path':None,'inc':'gdal_priv.h','lib':'gdal','lang':'C++'}, 'ogr': {'default':True,'path':None,'inc':'ogrsf_frmts.h','lib':'gdal','lang':'C++'}, - # configured with custom paths, hence 'path': PREFIX/INCLUDES/LIBS - 'occi': {'default':False,'path':'OCCI','inc':'occi.h','lib':'clntsh','lang':'C++'}, 'sqlite': {'default':True,'path':'SQLITE','inc':'sqlite3.h','lib':'sqlite3','lang':'C'}, - 'rasterlite': {'default':False,'path':'RASTERLITE','inc':['sqlite3.h','rasterlite.h'],'lib':'rasterlite','lang':'C'}, - - # todo: osm plugin does also depend on libxml2 (but there is a separate check for that) - 'osm': {'default':False,'path':None,'inc':None,'lib':None,'lang':'C'}, - # plugins without external dependencies requiring CheckLibWithHeader... 'shape': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'}, 'csv': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'}, @@ -309,6 +302,7 @@ opts.AddVariables( ('HOST', 'Set the target host for cross compiling', ''), ('CONFIG', "The path to the python file in which to save user configuration options. Currently : '%s'" % SCONS_LOCAL_CONFIG,SCONS_LOCAL_CONFIG), BoolVariable('USE_CONFIG', "Use SCons user '%s' file (will also write variables after successful configuration)", 'True'), + BoolVariable('NO_ATEXIT', 'Will prevent Singletons from being deleted atexit of main thread', 'False'), # http://www.scons.org/wiki/GoFastButton # http://stackoverflow.com/questions/1318863/how-to-optimize-an-scons-script BoolVariable('FAST', "Make SCons faster at the cost of less precise dependency tracking", 'False'), @@ -396,19 +390,20 @@ opts.AddVariables( EnumVariable('PLUGIN_LINKING', "Set plugin linking with libmapnik", 'shared', ['shared','static']), # Other variables - BoolVariable('SHAPE_MEMORY_MAPPED_FILE', 'Utilize memory-mapped files in Shapefile Plugin (higher memory usage, better performance)', 'True'), + BoolVariable('MEMORY_MAPPED_FILE', 'Utilize memory-mapped files in Shapefile Plugin (higher memory usage, better performance)', 'True'), ('SYSTEM_FONTS','Provide location for python bindings to register fonts (if provided then the bundled DejaVu fonts are not installed)',''), ('LIB_DIR_NAME','Name to use for the subfolder beside libmapnik where fonts and plugins are installed','mapnik'), PathVariable('PYTHON','Full path to Python executable used to build bindings', sys.executable), BoolVariable('FULL_LIB_PATH', 'Embed the full and absolute path to libmapnik when linking ("install_name" on OS X/rpath on Linux)', 'True'), BoolVariable('ENABLE_SONAME', 'Embed a soname in libmapnik on Linux', 'True'), EnumVariable('THREADING','Set threading support','multi', ['multi','single']), - EnumVariable('XMLPARSER','Set xml parser','libxml2', ['libxml2','ptree']), + EnumVariable('XMLPARSER','Set xml parser','ptree', ['libxml2','ptree']), BoolVariable('DEMO', 'Compile demo c++ application', 'True'), BoolVariable('PGSQL2SQLITE', 'Compile and install a utility to convert postgres tables to sqlite', 'False'), BoolVariable('SHAPEINDEX', 'Compile and install a utility to generate shapefile indexes in the custom format (.index) Mapnik supports', 'True'), + BoolVariable('MAPNIK_INDEX', 'Compile and install a utility to generate spatial indexes for CSV and GeoJSON in the custom format (.index) Mapnik supports', 'True'), BoolVariable('SVG2PNG', 'Compile and install a utility to generate render an svg file to a png on the command line', 'False'), - BoolVariable('NIK2IMG', 'Compile and install a utility to generate render a map to an image', 'True'), + BoolVariable('MAPNIK_RENDER', 'Compile and install a utility to render a map to an image', 'True'), BoolVariable('COLOR_PRINT', 'Print build status information in color', 'True'), BoolVariable('BIGINT', 'Compile support for 64-bit integers in mapnik::value', 'True'), ) @@ -946,6 +941,24 @@ int main() color_print(1,'\nHarfbuzz >= %s required but found ... %s' % (HARFBUZZ_MIN_VERSION_STRING,items[1])) return False +def harfbuzz_with_freetype_support(context): + ret = context.TryRun(""" + +#include "harfbuzz/hb-ft.h" +#include + +int main() +{ + return 0; +} + +""", '.cpp') + context.Message('Checking for HarfBuzz with freetype support\n') + context.Result(ret[0]) + if ret[0]: + return True + return False + def boost_regex_has_icu(context): if env['RUNTIME_LINK'] == 'static': # re-order icu libs to ensure linux linker is happy @@ -1067,6 +1080,7 @@ conf_tests = { 'prioritize_paths' : prioritize_paths, 'rollback_option' : rollback_option, 'icu_at_least_four_two' : icu_at_least_four_two, 'harfbuzz_version' : harfbuzz_version, + 'harfbuzz_with_freetype_support': harfbuzz_with_freetype_support, 'boost_regex_has_icu' : boost_regex_has_icu, 'sqlite_has_rtree' : sqlite_has_rtree, 'supports_cxx11' : supports_cxx11, @@ -1075,7 +1089,6 @@ conf_tests = { 'prioritize_paths' : prioritize_paths, def GetMapnikLibVersion(): ver = [] - is_pre = False for line in open('include/mapnik/version.hpp').readlines(): if line.startswith('#define MAPNIK_MAJOR_VERSION'): ver.append(line.split(' ')[2].strip()) @@ -1083,12 +1096,7 @@ def GetMapnikLibVersion(): ver.append(line.split(' ')[2].strip()) if line.startswith('#define MAPNIK_PATCH_VERSION'): ver.append(line.split(' ')[2].strip()) - if line.startswith('#define MAPNIK_VERSION_IS_RELEASE'): - if line.split(' ')[2].strip() == "0": - is_pre = True version_string = ".".join(ver) - if is_pre: - version_string += '-pre' return version_string if not preconfigured: @@ -1221,8 +1229,8 @@ if not preconfigured: thread_suffix = '' env.Append(LIBS = 'pthread') - if env['SHAPE_MEMORY_MAPPED_FILE']: - env.Append(CPPDEFINES = '-DSHAPE_MEMORY_MAPPED_FILE') + if env['MEMORY_MAPPED_FILE']: + env.Append(CPPDEFINES = '-DMAPNIK_MEMORY_MAPPED_FILE') # allow for mac osx /usr/lib/libicucore.dylib compatibility # requires custom supplied headers since Apple does not include them @@ -1274,18 +1282,19 @@ if not preconfigured: # libxml2 should be optional but is currently not # https://github.com/mapnik/mapnik/issues/913 - if env.get('XML2_LIBS') or env.get('XML2_INCLUDES'): - REQUIRED_LIBSHEADERS.insert(0,['libxml2','libxml/parser.h',True,'C']) - if env.get('XML2_INCLUDES'): - inc_path = env['XML2_INCLUDES'] - env.AppendUnique(CPPPATH = fix_path(inc_path)) - if env.get('XML2_LIBS'): - lib_path = env['XML2_LIBS'] - env.AppendUnique(LIBPATH = fix_path(lib_path)) - elif conf.parse_config('XML2_CONFIG',checks='--cflags'): - env['HAS_LIBXML2'] = True - else: - env['MISSING_DEPS'].append('libxml2') + if env.get('XMLPARSER') and env['XMLPARSER'] == 'libxml2': + if env.get('XML2_LIBS') or env.get('XML2_INCLUDES'): + OPTIONAL_LIBSHEADERS.insert(0,['libxml2','libxml/parser.h',True,'C']) + if env.get('XML2_INCLUDES'): + inc_path = env['XML2_INCLUDES'] + env.AppendUnique(CPPPATH = fix_path(inc_path)) + if env.get('XML2_LIBS'): + lib_path = env['XML2_LIBS'] + env.AppendUnique(LIBPATH = fix_path(lib_path)) + elif conf.parse_config('XML2_CONFIG',checks='--cflags'): + env['HAS_LIBXML2'] = True + else: + env['MISSING_DEPS'].append('libxml2') if not env['HOST']: if conf.CheckHasDlfcn(): @@ -1367,6 +1376,8 @@ if not preconfigured: elif libname == 'harfbuzz': if not conf.harfbuzz_version(): env['SKIPPED_DEPS'].append('harfbuzz-min-version') + if not conf.harfbuzz_with_freetype_support(): + env['MISSING_DEPS'].append('harfbuzz-with-freetype-support') if env['BIGINT']: env.Append(CPPDEFINES = '-DBIGINT') @@ -1703,16 +1714,19 @@ if not preconfigured: # fetch the mapnik version header in order to set the # ABI version used to build libmapnik.so on linux in src/build.py abi = GetMapnikLibVersion() - abi_no_pre = abi.replace('-pre','').split('.') - env['ABI_VERSION'] = abi_no_pre + abi_split = abi.split('.') + env['ABI_VERSION'] = abi_split env['MAPNIK_VERSION_STRING'] = abi - env['MAPNIK_VERSION'] = str(int(abi_no_pre[0])*100000+int(abi_no_pre[1])*100+int(abi_no_pre[2])) + env['MAPNIK_VERSION'] = str(int(abi_split[0])*100000+int(abi_split[1])*100+int(abi_split[2])) # Common DEFINES. env.Append(CPPDEFINES = '-D%s' % env['PLATFORM'].upper()) if env['THREADING'] == 'multi': env.Append(CPPDEFINES = '-DMAPNIK_THREADSAFE') + if env['NO_ATEXIT']: + env.Append(CPPDEFINES = '-DMAPNIK_NO_ATEXIT') + # Mac OSX (Darwin) special settings if env['PLATFORM'] == 'Darwin': pthread = '' @@ -1775,9 +1789,7 @@ if not preconfigured: common_cxx_flags = '-Wall %s %s -ftemplate-depth-300 -Wsign-compare -Wshadow ' % (env['WARNING_CXXFLAGS'], pthread) if 'clang++' in env['CXX']: - common_cxx_flags += ' -Wno-unknown-pragmas -Wno-unsequenced ' - elif 'g++' in env['CXX']: - common_cxx_flags += ' -Wno-pragmas ' + common_cxx_flags += ' -Wno-unsequenced ' if env['DEBUG']: env.Append(CXXFLAGS = common_cxx_flags + '-O0') @@ -1905,6 +1917,8 @@ if not HELP_REQUESTED: # Build the requested and able-to-be-compiled input plug-ins GDAL_BUILT = False OGR_BUILT = False + POSTGIS_BUILT = False + PGRASTER_BUILT = False for plugin in env['PLUGINS']: if env['PLUGIN_LINKING'] == 'static' or plugin not in env['REQUESTED_PLUGINS']: if os.path.exists('plugins/input/%s.input' % plugin): @@ -1914,11 +1928,17 @@ if not HELP_REQUESTED: if details['lib'] in env['LIBS']: if env['PLUGIN_LINKING'] == 'shared': SConscript('plugins/input/%s/build.py' % plugin) + # hack to avoid breaking on plugins with the same dep if plugin == 'ogr': OGR_BUILT = True if plugin == 'gdal': GDAL_BUILT = True + if plugin == 'postgis': POSTGIS_BUILT = True + if plugin == 'pgraster': PGRASTER_BUILT = True if plugin == 'ogr' or plugin == 'gdal': if GDAL_BUILT and OGR_BUILT: env['LIBS'].remove(details['lib']) + elif plugin == 'postgis' or plugin == 'pgraster': + if POSTGIS_BUILT and PGRASTER_BUILT: + env['LIBS'].remove(details['lib']) else: env['LIBS'].remove(details['lib']) elif not details['lib']: @@ -1958,13 +1978,15 @@ if not HELP_REQUESTED: if 'boost_program_options%s' % env['BOOST_APPEND'] in env['LIBS']: if env['SHAPEINDEX']: SConscript('utils/shapeindex/build.py') + if env['MAPNIK_INDEX']: + SConscript('utils/mapnik-index/build.py') # Build the pgsql2psqlite app if requested if env['PGSQL2SQLITE']: SConscript('utils/pgsql2sqlite/build.py') if env['SVG2PNG']: SConscript('utils/svg2png/build.py') - if env['NIK2IMG']: - SConscript('utils/nik2img/build.py') + if env['MAPNIK_RENDER']: + SConscript('utils/mapnik-render/build.py') # devtools not ready for public #SConscript('utils/ogrindex/build.py') env['LIBS'].remove('boost_program_options%s' % env['BOOST_APPEND']) diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..6566a9ab5 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,26 @@ +environment: + msvs_toolset: 14 + BOOST_VERSION: 59 + FASTBUILD: 1 + matrix: + - platform: x64 + configuration: Release + +os: Visual Studio 2015 + +shallow_clone: true + +install: + - scripts\build-appveyor.bat + +artifacts: + - path: mapnik-gyp\msbuild-summary.txt + name: msbuild-summary.txt + - path: mapnik-gyp\msbuild-errors.txt + name: msbuild-errors.txt + - path: mapnik-gyp\msbuild-warnings.txt + name: msbuild-warnings.txt + +build: off +test: off +deploy: off diff --git a/benchmark/build.py b/benchmark/build.py index 8b2049e24..37ba86707 100644 --- a/benchmark/build.py +++ b/benchmark/build.py @@ -46,6 +46,8 @@ benchmarks = [ "test_offset_converter.cpp", "test_marker_cache.cpp", "test_quad_tree.cpp", + "test_noop_rendering.cpp", + "test_getline.cpp", # "test_numeric_cast_vs_static_cast.cpp", ] for cpp_test in benchmarks: diff --git a/benchmark/compare_images.hpp b/benchmark/compare_images.hpp index 8fc6b1986..fb0e4ac95 100644 --- a/benchmark/compare_images.hpp +++ b/benchmark/compare_images.hpp @@ -29,19 +29,7 @@ namespace benchmark { image_rgba8 const& dest = util::get(desc_any); image_rgba8 const& src = util::get(src_any); - unsigned int width = src.width(); - 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; + return compare(dest, src, 0, true) == 0; } } diff --git a/benchmark/data/polygon_rendering_clip.xml b/benchmark/data/polygon_rendering_clip.xml index 98f0ec6e9..97847de52 100644 --- a/benchmark/data/polygon_rendering_clip.xml +++ b/benchmark/data/polygon_rendering_clip.xml @@ -7,7 +7,7 @@ style - ../../tests/data/shp/world_merc.shp + ../../test/data/shp/world_merc.shp shape diff --git a/benchmark/data/polygon_rendering_no_clip.xml b/benchmark/data/polygon_rendering_no_clip.xml index 8d478e079..36570f1c4 100644 --- a/benchmark/data/polygon_rendering_no_clip.xml +++ b/benchmark/data/polygon_rendering_no_clip.xml @@ -7,7 +7,7 @@ style - ../../tests/data/shp/world_merc.shp + ../../test/data/shp/world_merc.shp shape diff --git a/benchmark/data/roads.xml b/benchmark/data/roads.xml index bc0ad52a4..0ca938ff6 100644 --- a/benchmark/data/roads.xml +++ b/benchmark/data/roads.xml @@ -2,7 +2,7 @@ - -
-
expected
-
% difference
-
actual
)template"; constexpr const char * html_footer = R"template(
diff --git a/test/visual/report.hpp b/test/visual/report.hpp index 3b6146ec5..bb21bafd0 100644 --- a/test/visual/report.hpp +++ b/test/visual/report.hpp @@ -36,7 +36,7 @@ namespace visual_tests class console_report { public: - console_report() : s(std::clog) + console_report(bool _show_duration) : s(std::clog), show_duration(_show_duration) { } @@ -49,16 +49,17 @@ public: protected: std::ostream & s; + bool show_duration; }; class console_short_report : public console_report { public: - console_short_report() : console_report() + console_short_report(bool _show_duration) : console_report(_show_duration) { } - console_short_report(std::ostream & s) : console_report(s) + console_short_report(std::ostream & _s) : console_report(_s) { } diff --git a/test/visual/run.cpp b/test/visual/run.cpp index d44508188..70284bc3e 100644 --- a/test/visual/run.cpp +++ b/test/visual/run.cpp @@ -43,17 +43,59 @@ log_levels_map log_levels }; #endif +using namespace visual_tests; +namespace po = boost::program_options; + +runner::renderer_container create_renderers(po::variables_map const & args, + boost::filesystem::path const & output_dir, + bool append_all = false) +{ + boost::filesystem::path reference_dir(args["images-dir"].as()); + bool overwrite = args.count("overwrite"); + runner::renderer_container renderers; + + if (append_all || args.count(agg_renderer::name)) + { + renderers.emplace_back(renderer(output_dir, reference_dir, overwrite)); + } +#if defined(HAVE_CAIRO) + if (append_all || args.count(cairo_renderer::name)) + { + renderers.emplace_back(renderer(output_dir, reference_dir, overwrite)); + } +#endif +#if defined(SVG_RENDERER) + if (append_all || args.count(svg_renderer::name)) + { + renderers.emplace_back(renderer(output_dir, reference_dir, overwrite)); + } +#endif +#if defined(GRID_RENDERER) + if (append_all || args.count(grid_renderer::name)) + { + renderers.emplace_back(renderer(output_dir, reference_dir, overwrite)); + } +#endif + + if (renderers.empty()) + { + return create_renderers(args, output_dir, true); + } + + return renderers; +} + int main(int argc, char** argv) { - using namespace visual_tests; - namespace po = boost::program_options; - po::options_description desc("visual test runner"); desc.add_options() ("help,h", "produce usage message") ("verbose,v", "verbose output") ("overwrite,o", "overwrite reference image") + ("duration,d", "output rendering duration") + ("iterations,i", po::value()->default_value(1), "number of iterations for benchmarking") ("jobs,j", po::value()->default_value(1), "number of parallel threads") + ("limit,l", po::value()->default_value(0), "limit number of failures") ("styles-dir", po::value()->default_value("test/data-visual/styles"), "directory with styles") ("images-dir", po::value()->default_value("test/data-visual/images"), "directory with reference images") ("output-dir", po::value()->default_value("/tmp/mapnik-visual-images"), "directory for output files") @@ -65,6 +107,17 @@ int main(int argc, char** argv) ("log", po::value()->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 + ("scale-factor,s", po::value>()->default_value({ 1.0, 2.0 }, "1.0, 2.0"), "scale factor") + (agg_renderer::name, "render with AGG renderer") +#if defined(HAVE_CAIRO) + (cairo_renderer::name, "render with Cairo renderer") +#endif +#if defined(SVG_RENDERER) + (svg_renderer::name, "render with SVG renderer") +#endif +#if defined(GRID_RENDERER) + (grid_renderer::name, "render with Grid renderer") #endif ; @@ -104,12 +157,17 @@ int main(int argc, char** argv) output_dir /= boost::filesystem::unique_path(); } + config defaults; + defaults.scales = vm["scale-factor"].as>(); + runner run(vm["styles-dir"].as(), - output_dir, - vm["images-dir"].as(), - vm.count("overwrite"), - vm["jobs"].as()); - report_type report = vm.count("verbose") ? report_type((console_report())) : report_type((console_short_report())); + defaults, + vm["iterations"].as(), + vm["limit"].as(), + vm["jobs"].as(), + create_renderers(vm, output_dir)); + 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; try diff --git a/test/visual/runner.cpp b/test/visual/runner.cpp index f2562c197..06ade7779 100644 --- a/test/visual/runner.cpp +++ b/test/visual/runner.cpp @@ -23,6 +23,7 @@ // stl #include #include +#include #include @@ -34,43 +35,108 @@ namespace visual_tests class renderer_visitor { public: - renderer_visitor(std::string const & name, mapnik::Map const & map, double scale_factor) - : name_(name), map_(map), scale_factor_(scale_factor) + renderer_visitor(std::string const & name, + mapnik::Map & map, + map_size const & tiles, + double scale_factor, + result_list & results, + report_type & report, + std::size_t iterations, + bool is_fail_limit, + std::atomic & fail_count) + : name_(name), + map_(map), + tiles_(tiles), + scale_factor_(scale_factor), + results_(results), + report_(report), + iterations_(iterations), + is_fail_limit_(is_fail_limit), + fail_count_(fail_count) { } - template - result operator()(T const & renderer) const + template ::type* = nullptr> + void operator()(T const & renderer) { - return renderer.test(name_, map_, scale_factor_); + test(renderer); + } + + template ::type* = nullptr> + void operator()(T const & renderer) + { + if (tiles_.width == 1 && tiles_.height == 1) + { + test(renderer); + } } private: + template + 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)); + if (is_fail_limit_ && r.state == STATE_FAIL) + { + ++fail_count_; + } + } + } + } + + template ::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 ::type* = nullptr> + typename T::image_type render(T const & renderer) + { + return renderer.render(map_, scale_factor_); + } + std::string const & name_; - mapnik::Map const & map_; + mapnik::Map & map_; + map_size const & tiles_; double scale_factor_; + result_list & results_; + report_type & report_; + std::size_t iterations_; + bool is_fail_limit_; + std::atomic & fail_count_; }; runner::runner(runner::path_type const & styles_dir, - runner::path_type const & output_dir, - runner::path_type const & reference_dir, - bool overwrite, - std::size_t jobs) + config const & defaults, + std::size_t iterations, + std::size_t fail_limit, + std::size_t jobs, + runner::renderer_container const & renderers) : styles_dir_(styles_dir), - output_dir_(output_dir), - reference_dir_(reference_dir), + defaults_(defaults), jobs_(jobs), - renderers_{ renderer(output_dir_, reference_dir_, overwrite) -#if defined(HAVE_CAIRO) - ,renderer(output_dir_, reference_dir_, overwrite) -#endif -#if defined(SVG_RENDERER) - ,renderer(output_dir_, reference_dir_, overwrite) -#endif -#if defined(GRID_RENDERER) - ,renderer(output_dir_, reference_dir_, overwrite) -#endif - } + iterations_(iterations), + fail_limit_(fail_limit), + renderers_(renderers) { } @@ -117,6 +183,7 @@ result_list runner::test_parallel(std::vector const & files, std::launch launch(jobs == 1 ? std::launch::deferred : std::launch::async); std::vector> futures(jobs); + std::atomic fail_count(0); for (std::size_t i = 0; i < jobs; i++) { @@ -129,7 +196,7 @@ result_list runner::test_parallel(std::vector const & files, end = files.end(); } - futures[i] = std::async(launch, &runner::test_range, this, begin, end, std::ref(report)); + futures[i] = std::async(launch, &runner::test_range, this, begin, end, std::ref(report), std::ref(fail_count)); } for (auto & f : futures) @@ -141,9 +208,11 @@ result_list runner::test_parallel(std::vector const & files, return results; } -result_list runner::test_range(files_iterator begin, files_iterator end, std::reference_wrapper report) const +result_list runner::test_range(files_iterator begin, + files_iterator end, + std::reference_wrapper report, + std::reference_wrapper> fail_count) const { - config defaults; result_list results; for (runner::files_iterator i = begin; i != end; i++) @@ -153,7 +222,7 @@ result_list runner::test_range(files_iterator begin, files_iterator end, std::re { try { - result_list r = test_one(file, defaults, report); + result_list r = test_one(file, report, fail_count.get()); std::move(r.begin(), r.end(), std::back_inserter(results)); } catch (std::exception const& ex) @@ -162,23 +231,32 @@ result_list runner::test_range(files_iterator begin, files_iterator end, std::re r.state = STATE_ERROR; r.name = file.string(); r.error_message = ex.what(); + r.duration = std::chrono::high_resolution_clock::duration::zero(); results.emplace_back(r); mapnik::util::apply_visitor(report_visitor(r), report.get()); + ++fail_count.get(); } } + if (fail_limit_ && fail_count.get() >= fail_limit_) + { + break; + } } return results; } -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, + report_type & report, + std::atomic & fail_count) const { - mapnik::Map m(cfg.sizes.front().width, cfg.sizes.front().height); + config cfg(defaults_); + mapnik::Map map(cfg.sizes.front().width, cfg.sizes.front().height); result_list results; try { - mapnik::load_map(m, style_path.string(), true); + mapnik::load_map(map, style_path.string(), true); } catch (std::exception const& ex) { @@ -191,7 +269,7 @@ result_list runner::test_one(runner::path_type const& style_path, config cfg, re throw; } - mapnik::parameters const & params = m.get_extra_parameters(); + mapnik::parameters const & params = map.get_extra_parameters(); boost::optional status = params.get("status", cfg.status); @@ -208,32 +286,64 @@ result_list runner::test_one(runner::path_type const& style_path, config cfg, re parse_map_sizes(*sizes, cfg.sizes); } + boost::optional tiles = params.get("tiles"); + + if (tiles) + { + cfg.tiles.clear(); + parse_map_sizes(*tiles, cfg.tiles); + } + + boost::optional bbox_string = params.get("bbox"); + mapnik::box2d box; + + if (bbox_string) + { + box.from_string(*bbox_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); - - boost::optional bbox_string = params.get("bbox"); - - if (bbox_string) + for (auto const & scale_factor : cfg.scales) { - mapnik::box2d bbox; - 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_) + for (auto const & tiles_count : cfg.tiles) { - result r = mapnik::util::apply_visitor(renderer_visitor(name, m, scale_factor), ren); - results.emplace_back(r); - mapnik::util::apply_visitor(report_visitor(r), report); + if (!tiles_count.width || !tiles_count.height) + { + 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_, + fail_limit_, + fail_count), ren); + if (fail_limit_ && fail_count >= fail_limit_) + { + return results; + } + } } } } diff --git a/test/visual/runner.hpp b/test/visual/runner.hpp index 77949e46e..e38e39422 100644 --- a/test/visual/runner.hpp +++ b/test/visual/runner.hpp @@ -23,8 +23,6 @@ #ifndef VISUAL_TEST_RUNNER_HPP #define VISUAL_TEST_RUNNER_HPP -#include - #include "config.hpp" #include "report.hpp" #include "renderer.hpp" @@ -35,42 +33,40 @@ namespace visual_tests class runner { - using renderer_type = mapnik::util::variant -#if defined(HAVE_CAIRO) - ,renderer -#endif -#if defined(SVG_RENDERER) - ,renderer -#endif -#if defined(GRID_RENDERER) - ,renderer -#endif - >; using path_type = boost::filesystem::path; using files_iterator = std::vector::const_iterator; public: + using renderer_container = std::vector; + runner(path_type const & styles_dir, - path_type const & output_dir, - path_type const & reference_dir, - bool overwrite, - std::size_t jobs); + config const & cfg, + std::size_t iterations, + std::size_t fail_limit, + std::size_t jobs, + renderer_container const & renderers); result_list test_all(report_type & report) const; result_list test(std::vector const & style_names, report_type & report) const; private: result_list test_parallel(std::vector const & files, report_type & report, std::size_t jobs) const; - result_list test_range(files_iterator begin, files_iterator end, std::reference_wrapper report) const; - result_list test_one(path_type const & style_path, config cfg, report_type & report) const; + result_list test_range(files_iterator begin, + files_iterator end, + std::reference_wrapper report, + std::reference_wrapper> fail_limit) const; + result_list test_one(path_type const & style_path, + report_type & report, + std::atomic & fail_limit) const; void parse_map_sizes(std::string const & str, std::vector & sizes) const; const map_sizes_grammar map_sizes_parser_; const path_type styles_dir_; - const path_type output_dir_; - const path_type reference_dir_; + const config defaults_; const std::size_t jobs_; - const renderer_type renderers_[boost::mpl::size::value]; + const std::size_t iterations_; + const std::size_t fail_limit_; + const renderer_container renderers_; }; } diff --git a/utils/mapnik-config/build.py b/utils/mapnik-config/build.py index 60974e789..e74684e62 100644 --- a/utils/mapnik-config/build.py +++ b/utils/mapnik-config/build.py @@ -29,6 +29,23 @@ Import('env') config_env = env.Clone() + +def GetMapnikLibVersion(): + ver = [] + for line in open('../../include/mapnik/version.hpp').readlines(): + if line.startswith('#define MAPNIK_MAJOR_VERSION'): + ver.append(line.split(' ')[2].strip()) + if line.startswith('#define MAPNIK_MINOR_VERSION'): + ver.append(line.split(' ')[2].strip()) + if line.startswith('#define MAPNIK_PATCH_VERSION'): + ver.append(line.split(' ')[2].strip()) + version_string = ".".join(ver) + return version_string + +if (GetMapnikLibVersion() != config_env['MAPNIK_VERSION_STRING']): + print 'Error: version.hpp mismatch (%s) to cached value (%s): please reconfigure mapnik' % (GetMapnikLibVersion(),config_env['MAPNIK_VERSION_STRING']) + Exit(1) + config_variables = '''#!/usr/bin/env bash ## variables @@ -153,6 +170,7 @@ target_path = os.path.normpath(os.path.join(config_env['INSTALL_PREFIX'],'bin')) full_target = os.path.join(target_path,config_file) Depends(full_target, env.subst('../../src/%s' % env['MAPNIK_LIB_NAME'])) +Depends(full_target, '../../include/mapnik/version.hpp') if 'install' in COMMAND_LINE_TARGETS: # we must add 'install' catch here because otherwise diff --git a/utils/mapnik-index/build.py b/utils/mapnik-index/build.py new file mode 100644 index 000000000..5bb177046 --- /dev/null +++ b/utils/mapnik-index/build.py @@ -0,0 +1,62 @@ +# +# This file is part of Mapnik (c++ mapping toolkit) +# +# Copyright (C) 2015 Artem Pavlenko +# +# Mapnik 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 +# +# + +import os +import glob +from copy import copy + +Import ('env') + +program_env = env.Clone() + +source = Split( + """ + mapnik-index.cpp + process_csv_file.cpp + process_geojson_file.cpp + """ + ) + +#headers = ['#plugins/input/shape'] + env['CPPPATH'] +headers = env['CPPPATH'] + +boost_program_options = 'boost_program_options%s' % env['BOOST_APPEND'] +boost_system = 'boost_system%s' % env['BOOST_APPEND'] +libraries = [env['MAPNIK_NAME'], boost_program_options, boost_system] +# need on linux: https://github.com/mapnik/mapnik/issues/3145 +libraries.append('mapnik-json') +libraries.append('mapnik-wkt') +libraries.append(env['ICU_LIB_NAME']) + +if env['RUNTIME_LINK'] == 'static': + libraries.extend(copy(env['LIBMAPNIK_LIBS'])) + if env['PLATFORM'] == 'Linux': + libraries.append('dl') + +mapnik_index = program_env.Program('mapnik-index', source, CPPPATH=headers, LIBS=libraries) + +Depends(mapnik_index, env.subst('../../src/%s' % env['MAPNIK_LIB_NAME'])) + +if 'uninstall' not in COMMAND_LINE_TARGETS: + env.Install(os.path.join(env['INSTALL_PREFIX'],'bin'), mapnik_index) + env.Alias('install', os.path.join(env['INSTALL_PREFIX'],'bin')) + +env['create_uninstall_target'](env, os.path.join(env['INSTALL_PREFIX'],'bin','mapnik-index')) diff --git a/utils/mapnik-index/mapnik-index.cpp b/utils/mapnik-index/mapnik-index.cpp new file mode 100644 index 000000000..7361c05de --- /dev/null +++ b/utils/mapnik-index/mapnik-index.cpp @@ -0,0 +1,228 @@ +/***************************************************************************** + * + * 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 + * + *****************************************************************************/ + +#include +#include +#include +#include + +#include +#include + +#include "process_csv_file.hpp" +#include "process_geojson_file.hpp" + +#pragma GCC diagnostic push +#include +#include +#include +#pragma GCC diagnostic pop + +const int DEFAULT_DEPTH = 8; +const double DEFAULT_RATIO = 0.55; + +namespace mapnik { namespace detail { + +bool is_csv(std::string const& filename) +{ + return boost::iends_with(filename,".csv") + || boost::iends_with(filename,".tsv"); +} + +bool is_geojson(std::string const& filename) +{ + return boost::iends_with(filename,".geojson") + || boost::iends_with(filename,".json"); +} + +}} + +int main (int argc, char** argv) +{ + //using namespace mapnik; + namespace po = boost::program_options; + bool verbose = false; + bool validate_features = false; + unsigned int depth = DEFAULT_DEPTH; + double ratio = DEFAULT_RATIO; + std::vector files; + char separator = 0; + char quote = 0; + std::string manual_headers; + try + { + po::options_description desc("Mapnik CSV/GeoJSON index utility"); + desc.add_options() + ("help,h", "produce usage message") + ("version,V","print version string") + ("verbose,v","verbose output") + ("depth,d", po::value(), "max tree depth\n(default 8)") + ("ratio,r",po::value(),"split ratio (default 0.55)") + ("separator,s", po::value(), "CSV columns separator") + ("quote,q", po::value(), "CSV columns quote") + ("manual-headers,H", po::value(), "CSV manual headers string") + ("files",po::value >(),"Files to index: file1 file2 ...fileN") + ("validate-features", "Validate GeoJSON features") + ; + + po::positional_options_description p; + p.add("files",-1); + po::variables_map vm; + po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm); + po::notify(vm); + + if (vm.count("version")) + { + std::clog << "version 1.0.0" << std::endl; + return 1; + } + if (vm.count("help")) + { + std::clog << desc << std::endl; + return 1; + } + if (vm.count("verbose")) + { + verbose = true; + } + if (vm.count("validate-features")) + { + validate_features = true; + } + if (vm.count("depth")) + { + depth = vm["depth"].as(); + } + if (vm.count("ratio")) + { + ratio = vm["ratio"].as(); + } + if (vm.count("separator")) + { + separator = vm["separator"].as(); + } + if (vm.count("quote")) + { + quote = vm["quote"].as(); + } + if (vm.count("manual-headers")) + { + manual_headers = vm["manual-headers"].as(); + } + if (vm.count("files")) + { + files=vm["files"].as >(); + } + } + catch (std::exception const& ex) + { + std::clog << "Error: " << ex.what() << std::endl; + return EXIT_FAILURE; + } + + std::vector files_to_process; + + for (auto const& filename : files) + { + if (!mapnik::util::exists(filename)) + { + continue; + } + + if (mapnik::detail::is_csv(filename) || mapnik::detail::is_geojson(filename)) + { + files_to_process.push_back(filename); + } + } + + if (files_to_process.size() == 0) + { + std::clog << "no files to index" << std::endl; + return EXIT_FAILURE; + } + + std::clog << "max tree depth:" << depth << std::endl; + std::clog << "split ratio:" << ratio << std::endl; + + using box_type = mapnik::box2d; + using item_type = std::pair>; + + for (auto const& filename : files_to_process) + { + if (!mapnik::util::exists(filename)) + { + std::clog << "Error : file " << filename << " does not exist" << std::endl; + continue; + } + + std::vector boxes; + mapnik::box2d extent; + if (mapnik::detail::is_csv(filename)) + { + std::clog << "processing '" << filename << "' as CSV\n"; + auto result = mapnik::detail::process_csv_file(boxes, filename, manual_headers, separator, quote); + if (!result.first) continue; + extent = result.second; + } + else if (mapnik::detail::is_geojson(filename)) + { + std::clog << "processing '" << filename << "' as GeoJSON\n"; + auto result = mapnik::detail::process_geojson_file(boxes, filename, validate_features); + if (!result.first) + { + std::clog << "Error: failed to process " << filename << std::endl; + continue; + } + extent = result.second; + } + + if (extent.valid()) + { + std::clog << extent << std::endl; + mapnik::quad_tree> tree(extent, depth, ratio); + for (auto const& item : boxes) + { + tree.insert(std::get<1>(item), std::get<0>(item)); + } + + std::fstream file((filename + ".index").c_str(), + std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary); + if (!file) + { + std::clog << "cannot open index file for writing file \"" + << (filename + ".index") << "\"" << std::endl; + } + else + { + tree.trim(); + std::clog << "number nodes=" << tree.count() << std::endl; + std::clog << "number element=" << tree.count_items() << std::endl; + file.exceptions(std::ios::failbit | std::ios::badbit); + tree.write(file); + file.flush(); + file.close(); + } + } + } + std::clog << "done!" << std::endl; + return EXIT_SUCCESS; +} diff --git a/utils/mapnik-index/process_csv_file.cpp b/utils/mapnik-index/process_csv_file.cpp new file mode 100644 index 000000000..908ec9a79 --- /dev/null +++ b/utils/mapnik-index/process_csv_file.cpp @@ -0,0 +1,239 @@ +/***************************************************************************** + * + * 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 + * + *****************************************************************************/ + +#include "process_csv_file.hpp" +#include "../../plugins/input/csv/csv_utils.hpp" +#include +#include + +#if defined(MAPNIK_MEMORY_MAPPED_FILE) +#pragma GCC diagnostic push +#include +#include +#include +#pragma GCC diagnostic pop +#include +#endif + +#include + +namespace mapnik { namespace detail { + +template +std::pair> process_csv_file(T & boxes, std::string const& filename, std::string const& manual_headers, char separator, char quote) +{ + mapnik::box2d extent; +#if defined(MAPNIK_MEMORY_MAPPED_FILE) + using file_source_type = boost::interprocess::ibufferstream; + file_source_type csv_file; + mapnik::mapped_region_ptr mapped_region; + boost::optional memory = + mapnik::mapped_memory_cache::instance().find(filename, true); + if (memory) + { + mapped_region = *memory; + csv_file.buffer(static_cast(mapped_region->get_address()),mapped_region->get_size()); + } + else + { + std::clog << "Error : cannot mmap " << filename << std::endl; + return std::make_pair(false, extent); + } +#else + #if defined(_WINDOWS) + std::ifstream csv_file(mapnik::utf8_to_utf16(filename),std::ios_base::in | std::ios_base::binary); + #else + std::ifstream csv_file(filename.c_str(),std::ios_base::in | std::ios_base::binary); + #endif + if (!csv_file.is_open()) + { + std::clog << "Error : cannot open " << filename << std::endl; + return std::make_pair(false, extent); + } +#endif + auto file_length = ::detail::file_length(csv_file); + // set back to start + csv_file.seekg(0, std::ios::beg); + char newline; + bool has_newline; + char detected_quote; + std::tie(newline, has_newline, detected_quote) = ::detail::autodect_newline_and_quote(csv_file, file_length); + if (quote == 0) quote = detected_quote; + // set back to start + csv_file.seekg(0, std::ios::beg); + // get first line + std::string csv_line; + csv_utils::getline_csv(csv_file, csv_line, newline, quote); + if (separator == 0) separator = ::detail::detect_separator(csv_line); + csv_file.seekg(0, std::ios::beg); + int line_number = 0; + ::detail::geometry_column_locator locator; + std::vector headers; + std::clog << "Parsing CSV using SEPARATOR=" << separator << " QUOTE=" << quote << std::endl; + if (!manual_headers.empty()) + { + std::size_t index = 0; + headers = csv_utils::parse_line(manual_headers, separator, quote); + for (auto const& header : headers) + { + ::detail::locate_geometry_column(header, index++, locator); + headers.push_back(header); + } + } + else // parse first line as headers + { + while (csv_utils::getline_csv(csv_file,csv_line,newline, quote)) + { + try + { + headers = csv_utils::parse_line(csv_line, separator, quote); + // skip blank lines + if (headers.size() > 0 && headers[0].empty()) ++line_number; + else + { + std::size_t index = 0; + for (auto & header : headers) + { + mapnik::util::trim(header); + if (header.empty()) + { + // create a placeholder for the empty header + std::ostringstream s; + s << "_" << index; + header = s.str(); + } + else + { + ::detail::locate_geometry_column(header, index, locator); + } + ++index; + } + ++line_number; + break; + } + } + catch (std::exception const& ex) + { + std::string s("CSV index: error parsing headers: "); + s += ex.what(); + std::clog << s << std::endl; + return std::make_pair(false, extent); + } + } + } + + std::size_t num_headers = headers.size(); + if (!::detail::valid(locator, num_headers)) + { + std::clog << "CSV index: could not detect column(s) with the name(s) of wkt, geojson, x/y, or " + << "latitude/longitude in:\n" + << csv_line + << "\n - this is required for reading geometry data" + << std::endl; + return std::make_pair(false, extent); + } + + auto pos = csv_file.tellg(); + + // handle rare case of a single line of data and user-provided headers + // where a lack of a newline will mean that csv_utils::getline_csv returns false + bool is_first_row = false; + if (!has_newline) + { + csv_file.setstate(std::ios::failbit); + pos = 0; + if (!csv_line.empty()) + { + is_first_row = true; + } + } + while (is_first_row || csv_utils::getline_csv(csv_file, csv_line, newline, quote)) + { + ++line_number; + auto record_offset = pos; + auto record_size = csv_line.length(); + pos = csv_file.tellg(); + is_first_row = false; + // skip blank lines + if (record_size <= 10) + { + std::string trimmed = csv_line; + boost::trim_if(trimmed, boost::algorithm::is_any_of("\",'\r\n ")); + if (trimmed.empty()) + { + std::clog << "CSV index: empty row encountered at line: " << line_number << std::endl; + continue; + } + } + try + { + auto values = csv_utils::parse_line(csv_line, separator, quote); + unsigned num_fields = values.size(); + if (num_fields > num_headers || num_fields < num_headers) + { + // skip this row + std::ostringstream s; + s << "CSV Index: # of columns(" + << num_fields << ") > # of headers(" + << num_headers << ") parsed for row " << line_number; + throw mapnik::datasource_exception(s.str()); + } + + auto geom = ::detail::extract_geometry(values, locator); + if (!geom.is()) + { + auto box = mapnik::geometry::envelope(geom); + if (!extent.valid()) extent = box; + else extent.expand_to_include(box); + boxes.emplace_back(std::move(box), make_pair(record_offset, record_size)); + } + else + { + std::ostringstream s; + s << "CSV Index: expected geometry column: could not parse row " + << line_number << " " + << values[locator.index] << "'"; + throw mapnik::datasource_exception(s.str()); + } + } + catch (mapnik::datasource_exception const& ex ) + { + std::clog << ex.what() << " at line: " << line_number << std::endl; + } + catch (std::exception const& ex) + { + std::ostringstream s; + s << "CSV Index: unexpected error parsing line: " << line_number + << " - found " << headers.size() << " with values like: " << csv_line << "\n" + << " and got error like: " << ex.what(); + std::clog << s.str() << std::endl; + } + } + return std::make_pair(true, extent);; +} + +using box_type = mapnik::box2d; +using item_type = std::pair>; +using boxes_type = std::vector; +template std::pair> process_csv_file(boxes_type&, std::string const&, std::string const&, char, char); + +}} diff --git a/plugins/input/osm/basiccurl.h b/utils/mapnik-index/process_csv_file.hpp old mode 100755 new mode 100644 similarity index 69% rename from plugins/input/osm/basiccurl.h rename to utils/mapnik-index/process_csv_file.hpp index 7ab529783..f84393d7c --- a/plugins/input/osm/basiccurl.h +++ b/utils/mapnik-index/process_csv_file.hpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2011 Artem Pavlenko + * 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 @@ -20,21 +20,17 @@ * *****************************************************************************/ -#ifndef BASICCURL_H -#define BASICCURL_H +#ifndef MAPNIK_UTILS_PROCESS_CSV_FILE_HPP +#define MAPNIK_UTILS_PROCESS_CSV_FILE_HPP -#include -#include -#include +#include +#include -typedef struct -{ - char *data; - int nbytes; -} CURL_LOAD_DATA; +namespace mapnik { namespace detail { -CURL_LOAD_DATA *grab_http_response(const char *url); -CURL_LOAD_DATA *do_grab(CURL *curl, const char *url); -size_t response_callback(void *ptr ,size_t size, size_t nmemb, void *data); +template +std::pair> process_csv_file(T & boxes, std::string const& filename, std::string const& manual_headers, char separator, char quote); -#endif // BASICCURL_H +}} + +#endif // MAPNIK_UTILS_PROCESS_CSV_FILE_HPP diff --git a/utils/mapnik-index/process_geojson_file.cpp b/utils/mapnik-index/process_geojson_file.cpp new file mode 100644 index 000000000..af9656bee --- /dev/null +++ b/utils/mapnik-index/process_geojson_file.cpp @@ -0,0 +1,147 @@ +/***************************************************************************** + * + * 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 + * + *****************************************************************************/ + +#include "process_geojson_file.hpp" +#include +#include +#include +#include +#include + +#if defined(MAPNIK_MEMORY_MAPPED_FILE) +#pragma GCC diagnostic push +#include +#include +#include +#include +#pragma GCC diagnostic pop +#include +#endif + +#include +#include +#include + +namespace { +struct feature_validate_callback +{ + feature_validate_callback(mapnik::box2d const& box) + : box_(box) {} + + void operator() (mapnik::feature_ptr const& f) const + { + if (box_ != f->envelope()) + { + throw std::runtime_error("Bounding boxes mismatch validation feature"); + } + } + mapnik::box2d const& box_; +}; + +using base_iterator_type = char const*; +const mapnik::json::extract_bounding_box_grammar geojson_datasource_static_bbox_grammar; +const mapnik::transcoder tr("utf8"); +const mapnik::json::feature_grammar_callback fc_grammar(tr); +} + +namespace mapnik { namespace detail { + +template +std::pair> process_geojson_file(T & boxes, std::string const& filename, bool validate_features) +{ + mapnik::box2d extent; +#if defined(MAPNIK_MEMORY_MAPPED_FILE) + mapnik::mapped_region_ptr mapped_region; + boost::optional memory = + mapnik::mapped_memory_cache::instance().find(filename, true); + if (!memory) + { + std::clog << "Error : cannot memory map " << filename << std::endl; + return std::make_pair(false, extent); + } + else + { + mapped_region = *memory; + } + char const* start = reinterpret_cast(mapped_region->get_address()); + char const* end = start + mapped_region->get_size(); +#else + mapnik::util::file file(filename); + if (!file.open()) + { + std::clog << "Error : cannot open " << filename << std::endl; + return std::make_pair(false, extent); + } + std::string file_buffer; + file_buffer.resize(file.size()); + std::fread(&file_buffer[0], file.size(), 1, file.get()); + char const* start = file_buffer.c_str(); + char const* end = start + file_buffer.length(); +#endif + + boost::spirit::standard::space_type space; + auto const* itr = start; + try + { + if (!boost::spirit::qi::phrase_parse(itr, end, (geojson_datasource_static_bbox_grammar)(boost::phoenix::ref(boxes)) , space)) + { + std::clog << "mapnik-index (GeoJSON) : could not extract bounding boxes from : '" << filename << "'" << std::endl; + return std::make_pair(false, extent); + } + } + catch (std::exception const& ex) + { + std::clog << "mapnik-index (GeoJSON): " << ex.what() << std::endl; + } + mapnik::context_ptr ctx = std::make_shared(); + std::size_t start_id = 1; + for (auto const& item : boxes) + { + if (item.first.valid()) + { + if (!extent.valid()) extent = item.first; + else extent.expand_to_include(item.first); + + if (validate_features) + { + base_iterator_type feat_itr = start + item.second.first; + base_iterator_type feat_end = feat_itr + item.second.second; + feature_validate_callback callback(item.first); + bool result = boost::spirit::qi::phrase_parse(feat_itr, feat_end, (fc_grammar) + (boost::phoenix::ref(ctx), boost::phoenix::ref(start_id), boost::phoenix::ref(callback)), + space); + if (!result || feat_itr != feat_end) + { + return std::make_pair(false, extent); + } + } + } + } + return std::make_pair(true, extent); +} + +using box_type = mapnik::box2d; +using item_type = std::pair>; +using boxes_type = std::vector; +template std::pair> process_geojson_file(boxes_type&, std::string const&, bool); + +}} diff --git a/plugins/input/rasterlite/rasterlite_include.hpp b/utils/mapnik-index/process_geojson_file.hpp similarity index 73% rename from plugins/input/rasterlite/rasterlite_include.hpp rename to utils/mapnik-index/process_geojson_file.hpp index b6a9dd274..a4d468ad2 100644 --- a/plugins/input/rasterlite/rasterlite_include.hpp +++ b/utils/mapnik-index/process_geojson_file.hpp @@ -20,12 +20,17 @@ * *****************************************************************************/ -#ifndef RASTERLITE_INCLUDE_HPP -#define RASTERLITE_INCLUDE_HPP +#ifndef MAPNIK_UTILS_PROCESS_GEOJSON_FILE_HPP +#define MAPNIK_UTILS_PROCESS_GEOJSON_FILE_HPP -extern "C" { -#include -#include -} +#include +#include -#endif // RASTERLITE_INCLUDE_HPP +namespace mapnik { namespace detail { + +template +std::pair> process_geojson_file(T & boxes, std::string const& filename, bool validate_features); + +}} + +#endif // MAPNIK_UTILS_PROCESS_GEOJSON_FILE_HPP diff --git a/utils/nik2img/build.py b/utils/mapnik-render/build.py similarity index 74% rename from utils/nik2img/build.py rename to utils/mapnik-render/build.py index 244edbef3..5cdaa164e 100644 --- a/utils/nik2img/build.py +++ b/utils/mapnik-render/build.py @@ -7,7 +7,7 @@ program_env = env.Clone() source = Split( """ - nik2img.cpp + mapnik-render.cpp """ ) @@ -24,11 +24,11 @@ libraries.extend(copy(env['LIBMAPNIK_LIBS'])) if env['RUNTIME_LINK'] == 'static' and env['PLATFORM'] == 'Linux': libraries.append('dl') -nik2img = program_env.Program('nik2img', source, LIBS=libraries) -Depends(nik2img, env.subst('../../src/%s' % env['MAPNIK_LIB_NAME'])) +mapnik_render = program_env.Program('mapnik-render', source, LIBS=libraries) +Depends(mapnik_render, env.subst('../../src/%s' % env['MAPNIK_LIB_NAME'])) if 'uninstall' not in COMMAND_LINE_TARGETS: - env.Install(os.path.join(env['INSTALL_PREFIX'],'bin'), nik2img) + env.Install(os.path.join(env['INSTALL_PREFIX'],'bin'), mapnik_render) env.Alias('install', os.path.join(env['INSTALL_PREFIX'],'bin')) -env['create_uninstall_target'](env, os.path.join(env['INSTALL_PREFIX'],'bin','nik2img')) +env['create_uninstall_target'](env, os.path.join(env['INSTALL_PREFIX'],'bin','mapnik-render')) diff --git a/utils/nik2img/nik2img.cpp b/utils/mapnik-render/mapnik-render.cpp similarity index 97% rename from utils/nik2img/nik2img.cpp rename to utils/mapnik-render/mapnik-render.cpp index e223cf086..0ed950cca 100644 --- a/utils/nik2img/nik2img.cpp +++ b/utils/mapnik-render/mapnik-render.cpp @@ -9,8 +9,7 @@ #include #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Wunused-local-typedef" +#include #include #include #pragma GCC diagnostic pop diff --git a/utils/pgsql2sqlite/pgsql2sqlite.hpp b/utils/pgsql2sqlite/pgsql2sqlite.hpp index 5c5a640c3..e339a8f58 100644 --- a/utils/pgsql2sqlite/pgsql2sqlite.hpp +++ b/utils/pgsql2sqlite/pgsql2sqlite.hpp @@ -35,10 +35,8 @@ #include "connection_manager.hpp" #include "cursorresultset.hpp" -// boost #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Wunused-local-typedef" +#include #include #pragma GCC diagnostic pop @@ -388,7 +386,7 @@ void pgsql2sqlite(Connection conn, { if (oid == geometry_oid) { - mapnik::Feature feat(ctx,pkid); + mapnik::feature_impl feat(ctx,pkid); mapnik::geometry::geometry geom = geometry_utils::from_wkb(buf, size, wkbGeneric); if (!mapnik::geometry::is_empty(geom)) { diff --git a/utils/shapefile/shapefile_reader.py b/utils/shapefile/shapefile_reader.py index 27f74875b..a57d516f1 100755 --- a/utils/shapefile/shapefile_reader.py +++ b/utils/shapefile/shapefile_reader.py @@ -25,16 +25,37 @@ def test_record(_type, record) : print "NULL shape" elif _type == 11: #PointZ test_pointz(record) + elif _type == 5: + test_polygon(record) def test_pointz(record): + _type, = struct.unpack(">sys.stderr,"BAD SHAPE FILE: expected 36 bytes got",len(record) sys.exit(1) - _type,x,y,z,m = struct.unpack(">sys.stderr,"BAD SHAPE FILE: expected PointZ or NullShape got",_type sys.exit(1) +def test_polygon(record): + _type, = struct.unpack(">sys.stderr, "BAD SHAPE FILE: expected Polygon or NullShape got", _type + sys.exit(1) + length = len(record) + rec_length = 44 + num_parts * 4 + num_points * 16 + if rec_length <> length: + print>>sys.stderr, "BAD SHAPE FILE: expected", rec_length, "got", length + sys.exit(1) + if __name__ == "__main__" : if len(sys.argv) !=2: @@ -52,10 +73,17 @@ if __name__ == "__main__" : _,_,_,_,_,_,shx_file_length = header[0].unpack_from(shx.read(28)) _,_,lox,loy,hix,hiy,_,_,_,_ = header[1].unpack_from(shx.read(72)) + shx_bbox = [lox,loy,hix,hiy] + # SHP header _,_,_,_,_,_,shp_file_length = header[0].unpack_from(shp.read(28)) version,_type,lox,loy,hix,hiy,_,_,_,_ = header[1].unpack_from(shp.read(72)) + shp_bbox = [lox,loy,hix,hiy] + if shx_bbox <> shp_bbox : + print "BAD SHAPE FILE: bounding box mismatch in *.shp and *.shx", shp_bbox, shx_bbox + sys.exit(1) + print "SHX FILE_LENGTH=",shx_file_length,"bytes" print "SHP FILE_LENGTH=",shp_file_length,"bytes" @@ -65,11 +93,10 @@ if __name__ == "__main__" : record = struct.Struct(">II") calc_total_size = 50 count = 0 - while shx.tell() < shx_file_length * 2 : - offset,shx_content_length = record.unpack_from(shx.read(8)) + while shx.tell() <= shx_file_length * 2 - 4 * 2 : + offset,shx_content_length = record.unpack_from(shx.read(8)) shp.seek(offset*2, os.SEEK_SET) record_number,content_length = record_header.unpack_from(shp.read(8)) - if shx_content_length <> content_length: print "BAD SHAPE FILE: content_lenght mismatch in SHP and SHX",shx_content_length,content_length sys.exit(1) diff --git a/utils/shapeindex/quadtree.hpp b/utils/shapeindex/quadtree.hpp deleted file mode 100644 index 440ce5051..000000000 --- a/utils/shapeindex/quadtree.hpp +++ /dev/null @@ -1,297 +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 QUADTREE_HPP -#define QUADTREE_HPP -// stl -#include -#include -#include -#include -// mapnik -#include - -using mapnik::box2d; -using mapnik::coord2d; - -template -struct quadtree_node -{ - box2d ext_; - std::vector data_; - quadtree_node* children_[4]; - quadtree_node(const box2d& ext) - : ext_(ext),data_() - { - std::memset(children_,0,sizeof(quadtree_node*)*4); - } - - ~quadtree_node() - { - for (int i=0;i<4;++i) - { - if (children_[i]) - { - delete children_[i],children_[i]=0; - } - } - } - - int num_subnodes() const - { - int count=0; - for (int i=0;i<4;++i) - { - if (children_[i]) - { - ++count; - } - } - return count; - } -}; - -template -class quadtree -{ -private: - quadtree_node* root_; - const int maxdepth_; - const double ratio_; -public: - quadtree(const box2d& extent,int maxdepth,double ratio) - : root_(new quadtree_node(extent)), - maxdepth_(maxdepth), - ratio_(ratio) {} - - ~quadtree() - { - if (root_) delete root_; - } - - void insert(const T& data,const box2d& item_ext) - { - insert(data,item_ext,root_,maxdepth_); - } - - int count() const - { - return count_nodes(root_); - } - - int count_items() const - { - int count=0; - count_items(root_,count); - return count; - } - - void print() const - { - print(root_); - } - - void trim() - { - trim_tree(root_); - } - - void write(std::ostream& out) - { - char header[16]; - std::memset(header,0,16); - header[0]='m'; - header[1]='a'; - header[2]='p'; - header[3]='n'; - header[4]='i'; - header[5]='k'; - out.write(header,16); - write_node(out,root_); - } - -private: - - void trim_tree(quadtree_node*& node) - { - if (node) - { - for (int i=0;i<4;++i) - { - trim_tree(node->children_[i]); - } - - if (node->num_subnodes()==1 && node->data_.size()==0) - { - for (int i=0;i<4;++i) - { - if (node->children_[i]) - { - node=node->children_[i]; - break; - } - } - } - } - } - - int count_nodes(const quadtree_node* node) const - { - if (!node) - { - return 0; - } - else - { - int count = 1; - for (int i=0;i<4;++i) - { - count += count_nodes(node->children_[i]); - } - return count; - } - } - - void count_items(const quadtree_node* node,int& count) const - { - if (node) - { - count += node->data_.size(); - for (int i=0;i<4;++i) - { - count_items(node->children_[i],count); - } - } - } - - int subnode_offset(const quadtree_node* node) const - { - int offset=0; - for (int i=0;i<4;i++) - { - if (node->children_[i]) - { - offset +=sizeof(box2d)+(node->children_[i]->data_.size()*sizeof(T))+3*sizeof(int); - offset +=subnode_offset(node->children_[i]); - } - } - return offset; - } - - void write_node(std::ostream& out,const quadtree_node* node) const - { - if (node) - { - int offset=subnode_offset(node); - int shape_count=node->data_.size(); - int recsize=sizeof(box2d) + 3 * sizeof(int) + shape_count * sizeof(T); - char* node_record=new char[recsize]; - std::memset(node_record,0,recsize); - std::memcpy(node_record,&offset,4); - std::memcpy(node_record+4,&node->ext_,sizeof(box2d)); - std::memcpy(node_record+36,&shape_count,4); - for (int i=0;idata_[i]),sizeof(T)); - } - int num_subnodes=0; - for (int i=0;i<4;++i) - { - if (node->children_[i]) - { - ++num_subnodes; - } - } - std::memcpy(node_record + 40 + shape_count * sizeof(T),&num_subnodes,4); - out.write(node_record,recsize); - delete [] node_record; - - for (int i=0;i<4;++i) - { - write_node(out,node->children_[i]); - } - } - } - - void print(const quadtree_node* node,int level=0) const - { - if (node) - { - typename std::vector::const_iterator itr=node->data_.begin(); - std::string pad; - for (int i=0;iext_<data_.end()) - { - std::clog<<*itr<<" "; - ++itr; - } - std::clog<children_[i],level+4); - } - } - } - - void insert(const T& data,const box2d& item_ext,quadtree_node* node,int maxdepth) - { - if (node && node->ext_.contains(item_ext)) - { - double width=node->ext_.width(); - double height=node->ext_.height(); - - double lox=node->ext_.minx(); - double loy=node->ext_.miny(); - double hix=node->ext_.maxx(); - double hiy=node->ext_.maxy(); - - box2d ext[4]; - ext[0]=box2d(lox,loy,lox + width * ratio_,loy + height * ratio_); - ext[1]=box2d(hix - width * ratio_,loy,hix,loy + height * ratio_); - ext[2]=box2d(lox,hiy - height*ratio_,lox + width * ratio_,hiy); - ext[3]=box2d(hix - width * ratio_,hiy - height*ratio_,hix,hiy); - - if (maxdepth > 1) - { - for (int i=0;i<4;++i) - { - if (ext[i].contains(item_ext)) - { - if (!node->children_[i]) - { - node->children_[i]=new quadtree_node(ext[i]); - } - insert(data,item_ext,node->children_[i],maxdepth-1); - return; - } - } - } - node->data_.push_back(data); - } - } -}; -#endif //QUADTREE_HPP diff --git a/utils/shapeindex/shapeindex.cpp b/utils/shapeindex/shapeindex.cpp index 435daba71..a9c524249 100644 --- a/utils/shapeindex/shapeindex.cpp +++ b/utils/shapeindex/shapeindex.cpp @@ -25,13 +25,12 @@ #include #include #include -#include "quadtree.hpp" +#include #include "shapefile.hpp" #include "shape_io.hpp" #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Wunused-local-typedef" +#include #include #include #pragma GCC diagnostic pop @@ -43,15 +42,11 @@ int main (int argc,char** argv) { using namespace mapnik; namespace po = boost::program_options; - using std::string; - using std::vector; - using std::clog; - using std::endl; bool verbose=false; unsigned int depth=DEFAULT_DEPTH; double ratio=DEFAULT_RATIO; - vector shape_files; + std::vector shape_files; try { @@ -62,7 +57,7 @@ int main (int argc,char** argv) ("verbose,v","verbose output") ("depth,d", po::value(), "max tree depth\n(default 8)") ("ratio,r",po::value(),"split ratio (default 0.55)") - ("shape_files",po::value >(),"shape files to index: file1 file2 ...fileN") + ("shape_files",po::value >(),"shape files to index: file1 file2 ...fileN") ; po::positional_options_description p; @@ -73,13 +68,13 @@ int main (int argc,char** argv) if (vm.count("version")) { - clog<<"version 0.3.0" < >(); + shape_files=vm["shape_files"].as< std::vector >(); } } catch (std::exception const& ex) { - clog << "Error: " << ex.what() << endl; + std::clog << "Error: " << ex.what() << std::endl; return -1; } - clog << "max tree depth:" << depth << endl; - clog << "split ratio:" << ratio << endl; + std::clog << "max tree depth:" << depth << std::endl; + std::clog << "split ratio:" << ratio << std::endl; - //vector::const_iterator itr = shape_files.begin(); if (shape_files.size() == 0) { - clog << "no shape files to index" << endl; + std::clog << "no shape files to index" << std::endl; return 0; } for (auto const& filename : shape_files) { - clog << "processing " << filename << endl; + std::clog << "processing " << filename << std::endl; std::string shapename (filename); boost::algorithm::ireplace_last(shapename,".shp",""); std::string shapename_full (shapename + ".shp"); - + std::string shxname(shapename + ".shx"); if (! mapnik::util::exists (shapename_full)) { - clog << "Error : file " << shapename_full << " does not exist" << endl; + std::clog << "Error : file " << shapename_full << " does not exist" << std::endl; + continue; + } + if (! mapnik::util::exists(shxname)) + { + std::clog << "Error : shapefile index file (*.shx) " << shxname << " does not exist" << std::endl; continue; } - shape_file shp (shapename_full); - if (! shp.is_open()) { - clog << "Error : cannot open " << shapename_full << endl; + if (! shp.is_open()) + { + std::clog << "Error : cannot open " << shapename_full << std::endl; continue; } - int code = shp.read_xdr_integer(); //file_code == 9994 - clog << code << endl; - shp.skip(5*4); + shape_file shx (shxname); + if (!shx.is_open()) + { + std::clog << "Error : cannot open " << shxname << std::endl; + continue; + } - int file_length=shp.read_xdr_integer(); - int version=shp.read_ndr_integer(); - int shape_type=shp.read_ndr_integer(); + int code = shx.read_xdr_integer(); //file_code == 9994 + std::clog << code << std::endl; + shx.skip(5*4); + + int file_length=shx.read_xdr_integer(); + int version=shx.read_ndr_integer(); + int shape_type=shx.read_ndr_integer(); box2d extent; - shp.read_envelope(extent); + shx.read_envelope(extent); - clog << "length=" << file_length << endl; - clog << "version=" << version << endl; - clog << "type=" << shape_type << endl; - clog << "extent:" << extent << endl; + std::clog << "length=" << file_length << std::endl; + std::clog << "version=" << version << std::endl; + std::clog << "type=" << shape_type << std::endl; + std::clog << "extent:" << extent << std::endl; - int pos=50; - shp.seek(pos*2); - quadtree tree(extent,depth,ratio); - int count=0; - while (true) { + int pos = 50; + shx.seek(pos * 2); + mapnik::quad_tree tree(extent, depth, ratio); + int count = 0; - long offset=shp.pos(); - int record_number=shp.read_xdr_integer(); - int content_length=shp.read_xdr_integer(); - shape_type = shp.read_ndr_integer(); + while (shx.is_good() && pos <= file_length - 4) + { + int offset = shx.read_xdr_integer(); + int content_length = shx.read_xdr_integer(); + pos += 4; box2d item_ext; - if (shape_type==shape_io::shape_null) + shp.seek(offset * 2); + int record_number = shp.read_xdr_integer(); + if (content_length != shp.read_xdr_integer()) { - if (pos >= file_length) - { - break; - } - else - { - // still need to increment pos, or the pos counter - // won't indicate EOF until too late. - pos+=4+content_length; - continue; - } + std::clog << "Content length mismatch for record number " << record_number << std::endl; + continue; } - else if (shape_type==shape_io::shape_point) + shape_type = shp.read_ndr_integer(); + + if (shape_type==shape_io::shape_point + || shape_type==shape_io::shape_pointm + || shape_type == shape_io::shape_pointz) { double x=shp.read_double(); double y=shp.read_double(); item_ext=box2d(x,y,x,y); } - else if (shape_type==shape_io::shape_pointm) - { - double x=shp.read_double(); - double y=shp.read_double(); - // skip m - shp.read_double(); - item_ext=box2d(x,y,x,y); - } - else if (shape_type==shape_io::shape_pointz) - { - double x=shp.read_double(); - double y=shp.read_double(); - // skip z - shp.read_double(); - // According to ESRI shapefile doc - // A PointZ consists of a triplet of double-precision coordinates in the order X, Y, Z plus a - // measure. - // PointZ - // { - // Double X // X coordinate - // Double Y // Y coordinate - // Double Z // Z coordinate - // Double M // Measure - // } - // But OGR creates shapefiles with M missing so we need to skip M only if present - // NOTE: content_length is in 16-bit words - if ( content_length == 18) - { - shp.read_double(); - } - item_ext=box2d(x,y,x,y); - } else { shp.read_envelope(item_ext); - shp.skip(2*content_length-4*8-4); } - tree.insert(offset,item_ext); if (verbose) { - clog << "record number " << record_number << " box=" << item_ext << endl; + std::clog << "record number " << record_number << " box=" << item_ext << std::endl; + } + if (item_ext.valid()) + { + tree.insert(offset * 2,item_ext); + ++count; } - - pos+=4+content_length; - ++count; - - if (pos >= file_length) break; } - clog << " number shapes=" << count << endl; - - std::fstream file((shapename+".index").c_str(), - std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary); - if (!file) { - clog << "cannot open index file for writing file \"" - << (shapename+".index") << "\"" << endl; - } else { - tree.trim(); - std::clog<<" number nodes="< 0) + { + std::clog << " number shapes=" << count << std::endl; + std::fstream file((shapename+".index").c_str(), + std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary); + if (!file) + { + std::clog << "cannot open index file for writing file \"" + << (shapename+".index") << "\"" << std::endl; + } + else + { + tree.trim(); + std::clog << " number nodes=" << tree.count() << std::endl; + file.exceptions(std::ios::failbit | std::ios::badbit); + tree.write(file); + file.flush(); + file.close(); + } + } + else + { + std::clog << "No non-empty geometries in shapefile" << std::endl; } } - clog << "done!" << endl; + std::clog << "done!" << std::endl; return 0; } diff --git a/utils/svg2png/svg2png.cpp b/utils/svg2png/svg2png.cpp index 7e3aca532..8e855fd91 100644 --- a/utils/svg2png/svg2png.cpp +++ b/utils/svg2png/svg2png.cpp @@ -35,8 +35,7 @@ #include #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Wunused-local-typedef" +#include #include #include #pragma GCC diagnostic pop @@ -48,32 +47,16 @@ #include "agg_pixfmt_rgba.h" #include "agg_scanline_u.h" -#include // for xmlInitParser(), xmlCleanupParser() - struct main_marker_visitor { - main_marker_visitor(std::string & svg_name, - int & return_value, + main_marker_visitor(std::string const& svg_name, bool verbose, bool auto_open) : svg_name_(svg_name), - return_value_(return_value), verbose_(verbose), auto_open_(auto_open) {} - void operator() (mapnik::marker_null const&) - { - std::clog << "svg2png error: '" << svg_name_ << "' is not a valid vector!\n"; - return_value_ = -1; - } - - void operator() (mapnik::marker_rgba8 const&) - { - std::clog << "svg2png error: '" << svg_name_ << "' is not a valid vector!\n"; - return_value_ = -1; - } - - void operator() (mapnik::marker_svg const& marker) + int operator() (mapnik::marker_svg const& marker) { using pixfmt = agg::pixfmt_rgba32_pre; using renderer_base = agg::renderer_base; @@ -111,28 +94,37 @@ struct main_marker_visitor svg_renderer_this.render(ras_ptr, sl, renb, mtx, opacity, bbox); - boost::algorithm::ireplace_last(svg_name_,".svg",".png"); + std::string png_name(svg_name_); + boost::algorithm::ireplace_last(png_name,".svg",".png"); demultiply_alpha(im); - mapnik::save_to_file(im,svg_name_,"png"); + mapnik::save_to_file(im,png_name,"png"); + int status = 0; if (auto_open_) { std::ostringstream s; #ifdef DARWIN - s << "open " << svg_name_; + s << "open " << png_name; #else - s << "xdg-open " << svg_name_; + s << "xdg-open " << png_name; #endif - int ret = system(s.str().c_str()); + int ret = std::system(s.str().c_str()); if (ret != 0) - return_value_ = ret; + status = ret; } - std::clog << "rendered to: " << svg_name_ << "\n"; + std::clog << "rendered to: " << png_name << "\n"; + return status; + } + // default + template + int operator() (T const&) + { + std::clog << "svg2png error: '" << svg_name_ << "' is not a valid vector!\n"; + return -1; } private: - std::string & svg_name_; - int & return_value_; + std::string const& svg_name_; bool verbose_; bool auto_open_; }; @@ -143,7 +135,7 @@ int main (int argc,char** argv) bool verbose = false; bool auto_open = false; - int return_value = 0; + int status = 0; std::vector svg_files; mapnik::logger::instance().set_severity(mapnik::logger::error); @@ -203,8 +195,6 @@ int main (int argc,char** argv) return 0; } - xmlInitParser(); - while (itr != svg_files.end()) { std::string svg_name (*itr++); @@ -214,20 +204,19 @@ int main (int argc,char** argv) } std::shared_ptr marker = mapnik::marker_cache::instance().find(svg_name, false); - main_marker_visitor visitor(svg_name, return_value, verbose, auto_open); - mapnik::util::apply_visitor(visitor, *marker); + main_marker_visitor visitor(svg_name, verbose, auto_open); + status = mapnik::util::apply_visitor(visitor, *marker); } } + catch (std::exception const& ex) + { + std::clog << "Exception caught:" << ex.what() << std::endl; + return -1; + } catch (...) { std::clog << "Exception of unknown type!" << std::endl; - xmlCleanupParser(); return -1; } - - // only call this once, on exit - // to make sure valgrind output is clean - // http://xmlsoft.org/xmlmem.html - xmlCleanupParser(); - return return_value; + return status; }