diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6d3648a6c..000000000 --- a/.travis.yml +++ /dev/null @@ -1,93 +0,0 @@ -language: generic - -matrix: - include: - - os: linux - sudo: false - compiler: clang - # note: only using ccache for CC is intentional here to - # workaround an odd bug in distutils that manifests when only `ccache` is used to link - # because distutils also has a bug whereby CC is used to compile instead of CXX, this works :) - env: JOBS=8 CXX="clang++-3.9 -Qunused-arguments" CC="ccache clang-3.9 -Qunused-arguments" - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test'] - packages: [ 'libstdc++-5-dev', 'gdb', 'apport'] - - os: osx - osx_image: xcode8.2 - compiler: clang - env: JOBS=4 - -cache: - directories: - - $HOME/.ccache - -env: - global: - - secure: "CqhZDPctJcpXGPpmIPK5usD/O+2HYawW3434oDufVS9uG/+C7aHzKzi8cuZ7n/REHqJMzy7gJfp6DiyF2QowpnN1L2W0FSJ9VOgj4JQF2Wsupo6gJkq6/CW2Fa35PhQHsv29bfyqtIq+R5SBVAieBe/Lh2P144RwRliGRopGQ68=" - - secure: "idk4fdU49i546Zs6Fxha14H05eRJ1G/D6NPRaie8M8o+xySnEqf+TyA9/HU8QH7cFvroSLuHJ1U7TmwnR+sXy4XBlIfHLi4u2MN+l/q014GG7T2E2xYcTauqjB4ldToRsDQwe5Dq0gZCMsHLPspWPjL9twfp+Ds7qgcFhTsct0s=" - - BOOST_PYTHON_LIB="boost_python" - - BOOST_SYSTEM_LIB="boost_system" - - BOOST_THREAD_LIB="boost_thread" - - CCACHE_TEMPDIR=/tmp/.ccache-temp - - CCACHE_COMPRESS=1 - -before_install: - # workaround travis rvm bug - # http://superuser.com/questions/1044130/why-am-i-having-how-can-i-fix-this-error-shell-session-update-command-not-f - - | - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then - rvm get head || true - fi - - source scripts/setup_mason.sh - - export PYTHONUSERBASE=$(pwd)/mason_packages/.link - - export PYTHONPATH=$(pwd)/mason_packages/.link/lib/python2.7/site-packages - - export PATH=$(pwd)/mason_packages/.link/bin:${PYTHONUSERBASE}/bin:${PATH} - - export MASON_BUILD=true - - export COMMIT_MESSAGE=$(git show -s --format=%B $TRAVIS_COMMIT | tr -d '\n') - - | - if [[ $(uname -s) == 'Linux' ]]; then - export LDSHARED=$(python -c "import os;from distutils import sysconfig;print sysconfig.get_config_var('LDSHARED').replace('cc ','clang++-3.9 ')"); - mason install clang++ 3.9.1 - export PATH=$(mason prefix clang++ 3.9.1)/bin:${PATH} - which clang++ - else - sudo easy_install pip; - export LDSHARED=$(python -c "import os;from distutils import sysconfig;print sysconfig.get_config_var('LDSHARED').replace('cc ','clang++ ')"); - fi - - pip install --upgrade --user nose - - pip install --upgrade --user wheel - - pip install --upgrade --user twine - - pip install --upgrade --user setuptools - - pip install --upgrade --user PyPDF2 - - python --version - -install: - - mkdir -p ${PYTHONPATH} - - python setup.py install --prefix ${PYTHONUSERBASE} - -before_script: - # start postgres/postgis - - source mason-config.env - - ./mason_packages/.link/bin/postgres -k ${PGHOST} > postgres.log & - -script: - - python test/run_tests.py - - python test/visual.py -q - # stop postgres - - ./mason_packages/.link/bin/pg_ctl -w stop - - | - if [[ ${COMMIT_MESSAGE} =~ "[publish]" ]]; then - python setup.py bdist_wheel - if [[ $(uname -s) == 'Linux' ]]; then - export PRE_DISTS='dist/*.whl' - rename 's/linux_x86_64/any/;' $PRE_DISTS - fi - export DISTS='dist/*' - $(pwd)/mason_packages/.link/bin/twine upload -u $PYPI_USER -p $PYPI_PASSWORD $DISTS - fi - - -notifications: - slack: - secure: dZhYCFXTvn6zna7GhagCUcInfhoUf/AMkTpJKPnJgaGnS3DlfbnMsSU73J4hs46wCOFII3AfYUOI/SUEBZ15lkJHfBsCku0a5a2M8g5ddxKFoIM8gosH3dLjeGJ5Ou8zNQGyzokXidKfHC+Gh4UVGyn+aeXxglRmRkUeaP+GD1k= diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..53b088ef0 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include src/*.hpp +exclude packaging/mapnik/bin/* +exclude packaging/mapnik/lib/libmapnik* +exclude packaging/mapnik/lib/mapnik/input/* diff --git a/README.md b/README.md index bdcc1a0a6..1324e021f 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,25 @@ +**New** Python bindings for Mapnik **[WIP]** -[![Build Status](https://travis-ci.org/mapnik/python-mapnik.svg)](https://travis-ci.org/mapnik/python-mapnik) - -Python bindings for Mapnik. +https://github.com/pybind/pybind11 ## Installation -Eventually we hope that many people will simply be able to `pip install mapnik` in order to get prebuilt binaries, -this currently does not work though. So for now here are the instructions - -### Create a virtual environment - -It is highly suggested that you have [a python virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/) when developing -on mapnik. - -### Building from Mason - -If you do not have mapnik built from source and simply wish to develop from the latest version in [mapnik master branch](https://github.com/mapnik/mapnik) you can setup your environment with a mason build. In order to trigger a mason build prior to building you must set the `MASON_BUILD` environment variable. - -```bash -export MASON_BUILD=true -``` - -After this is done simply follow the directions as per a source build. - ### Building from Source -Assuming that you built your own mapnik from source, and you have run `make install`. Set any compiler or linking environment variables as necessary so that your installation of mapnik is found. Next simply run one of the two methods: - -``` -python setup.py develop -``` - -If you are currently developing on mapnik-python and wish to change the code in place and immediately have python changes reflected in your environment. - - -``` -python setup.py install -``` - -If you wish to just install the package. - -``` -python setup.py develop --uninstall -``` - -Will de-activate the development install by removing the `python-mapnik` entry from `site-packages/easy-install.pth`. - - -If you need Pycairo, make sure that PYCAIRO is set to true in your environment or run: +Make sure 'mapnik-config' is present and accessible via $PATH env variable ``` -PYCAIRO=true python setup.py develop +pip install . -v ``` -### Building against Mapnik 3.0.x - -The `master` branch is no longer compatible with `3.0.x` series of Mapnik. To build against Mapnik 3.0.x, use [`v3.0.x`](https://github.com/mapnik/python-mapnik/tree/v3.0.x) branch. - ## Testing Once you have installed you can test the package by running: ``` -git submodule update --init -python setup.py test -``` - -The test data in `./test/data` and `./test/data-visual` are standalone modules. If you need to update them see https://github.com/mapnik/mapnik/blob/master/docs/contributing.md#testing - - -### Troubleshooting - -If you hit an error like: - -``` -Fatal Python error: PyThreadState_Get: no current thread -Abort trap: 6 +pytest test/python_tests/ ``` -That means you likely have built python-mapnik linked against a different python version than what you are running. To solve this try running: - -``` -/usr/bin/python -``` - -If you hit an error like the following when building with mason: - -``` -EnvironmentError: -Missing boost_python boost library, try to add its name with BOOST_PYTHON_LIB environment var. -``` - -Try to set `export BOOST_PYTHON_LIB=boost_python` before build. -Also, if `boost_thread` or `boost_system` is missing, do likewise: - -``` -export BOOST_SYSTEM_LIB=boost_system -export BOOST_THREAD_LIB=boost_thread -``` -If you still hit a problem create an issue and we'll try to help. -## Tutorials -- [Getting started with Python bindings](docs/getting-started.md) diff --git a/bootstrap.sh b/bootstrap.sh deleted file mode 100755 index 251f9754e..000000000 --- a/bootstrap.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env bash - -set -eu -set -o pipefail - -function install() { - MASON_PLATFORM_ID=$(mason env MASON_PLATFORM_ID) - if [[ ! -d ./mason_packages/${MASON_PLATFORM_ID}/${1}/ ]]; then - mason install $1 $2 - mason link $1 $2 - fi -} - -ICU_VERSION="57.1" - -function install_mason_deps() { - install mapnik 3be9ce8fa - install jpeg_turbo 1.5.1 - install libpng 1.6.28 - install libtiff 4.0.7 - install libpq 9.6.2 - install sqlite 3.17.0 - install expat 2.2.0 - install icu ${ICU_VERSION} - install proj 4.9.3 - install pixman 0.34.0 - install cairo 1.14.8 - install webp 0.6.0 - install libgdal 2.1.3 - install boost 1.66.0 - install boost_libsystem 1.66.0 - install boost_libfilesystem 1.66.0 - install boost_libprogram_options 1.66.0 - install boost_libregex_icu57 1.66.0 - install freetype 2.7.1 - install harfbuzz 1.4.2-ft - # deps needed by python-mapnik (not mapnik core) - install boost_libthread 1.66.0 - install boost_libpython 1.66.0 - install postgis 2.3.2-1 -} - -function setup_runtime_settings() { - local MASON_LINKED_ABS=$(pwd)/mason_packages/.link - echo "export PROJ_LIB=${MASON_LINKED_ABS}/share/proj" > mason-config.env - echo "export ICU_DATA=${MASON_LINKED_ABS}/share/icu/${ICU_VERSION}" >> mason-config.env - echo "export GDAL_DATA=${MASON_LINKED_ABS}/share/gdal" >> mason-config.env - echo "export PATH=$(pwd)/mason_packages/.link/bin:${PATH}" >> mason-config.env - echo "export PGTEMP_DIR=$(pwd)/local-tmp" >> mason-config.env - echo "export PGDATA=$(pwd)/local-postgres" >> mason-config.env - echo "export PGHOST=$(pwd)/local-unix-socket" >> mason-config.env - echo "export PGPORT=1111" >> mason-config.env - - source mason-config.env - rm -rf ${PGHOST} - mkdir -p ${PGHOST} - rm -rf ${PGDATA} - mkdir -p ${PGDATA} - rm -rf ${PGTEMP_DIR} - mkdir -p ${PGTEMP_DIR} - ./mason_packages/.link/bin/initdb - sleep 2 - ./mason_packages/.link/bin/postgres -k ${PGHOST} > postgres.log & - sleep 2 - ./mason_packages/.link/bin/createdb template_postgis -T postgres - ./mason_packages/.link/bin/psql template_postgis -c "CREATE TABLESPACE temp_disk LOCATION '${PGTEMP_DIR}';" - ./mason_packages/.link/bin/psql template_postgis -c "SET temp_tablespaces TO 'temp_disk';" - ./mason_packages/.link/bin/psql template_postgis -c "CREATE PROCEDURAL LANGUAGE 'plpythonu' HANDLER plpython_call_handler;" - ./mason_packages/.link/bin/psql template_postgis -c "CREATE EXTENSION postgis;" - ./mason_packages/.link/bin/psql template_postgis -c "SELECT PostGIS_Full_Version();" - ./mason_packages/.link/bin/pg_ctl -w stop -} - -function main() { - source scripts/setup_mason.sh - setup_mason - install_mason_deps - setup_runtime_settings - echo "Ready, now run:" - echo "" - echo " make test" -} - -main - -set +eu -set +o pipefail diff --git a/build.py b/build.py deleted file mode 100644 index 0f94826b6..000000000 --- a/build.py +++ /dev/null @@ -1,120 +0,0 @@ -import glob -import os -from subprocess import Popen, PIPE -from distutils import sysconfig - -Import('env') - -def call(cmd, silent=True): - stdin, stderr = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE).communicate() - if not stderr: - return stdin.strip() - elif not silent: - print stderr - - -prefix = env['PREFIX'] -target_path = os.path.normpath(sysconfig.get_python_lib() + os.path.sep + env['MAPNIK_NAME']) - -py_env = env.Clone() - -py_env.Append(CPPPATH = sysconfig.get_python_inc()) - -py_env.Append(CPPDEFINES = env['LIBMAPNIK_DEFINES']) - -py_env['LIBS'] = [env['MAPNIK_NAME'],'libboost_python'] - -link_all_libs = env['LINKING'] == 'static' or env['RUNTIME_LINK'] == 'static' - -# even though boost_thread is no longer used in mapnik core -# we need to link in for boost_python to avoid missing symbol: _ZN5boost6detail12get_tss_dataEPKv / boost::detail::get_tss_data -py_env.AppendUnique(LIBS = 'boost_thread%s' % env['BOOST_APPEND']) - -if link_all_libs: - py_env.AppendUnique(LIBS=env['LIBMAPNIK_LIBS']) - -# note: on linux -lrt must be linked after thread to avoid: undefined symbol: clock_gettime -if env['RUNTIME_LINK'] == 'static' and env['PLATFORM'] == 'Linux': - py_env.AppendUnique(LIBS='rt') - -# TODO - do solaris/fedora need direct linking too? -python_link_flag = '' -if env['PLATFORM'] == 'Darwin': - python_link_flag = '-undefined dynamic_lookup' - -paths = ''' -"""Configuration paths of Mapnik fonts and input plugins (auto-generated by SCons).""" - -from os.path import normpath,join,dirname - -mapniklibpath = '%s' -mapniklibpath = normpath(join(dirname(__file__),mapniklibpath)) -''' - -paths += "inputpluginspath = join(mapniklibpath,'input')\n" - -if env['SYSTEM_FONTS']: - paths += "fontscollectionpath = normpath('%s')\n" % env['SYSTEM_FONTS'] -else: - paths += "fontscollectionpath = join(mapniklibpath,'fonts')\n" - -paths += "__all__ = [mapniklibpath,inputpluginspath,fontscollectionpath]\n" - -if not os.path.exists(env['MAPNIK_NAME']): - os.mkdir(env['MAPNIK_NAME']) - -file('mapnik/paths.py','w').write(paths % (env['MAPNIK_LIB_DIR'])) - -# force open perms temporarily so that `sudo scons install` -# does not later break simple non-install non-sudo rebuild -try: - os.chmod('mapnik/paths.py',0666) -except: pass - -# install the shared object beside the module directory -sources = glob.glob('src/*.cpp') - -if 'install' in COMMAND_LINE_TARGETS: - # install the core mapnik python files, including '__init__.py' - init_files = glob.glob('mapnik/*.py') - if 'mapnik/paths.py' in init_files: - init_files.remove('mapnik/paths.py') - init_module = env.Install(target_path, init_files) - env.Alias(target='install', source=init_module) - # fix perms and install the custom generated 'paths.py' - targetp = os.path.join(target_path,'paths.py') - env.Alias("install", targetp) - # use env.Command rather than env.Install - # to enable setting proper perms on `paths.py` - env.Command( targetp, 'mapnik/paths.py', - [ - Copy("$TARGET","$SOURCE"), - Chmod("$TARGET", 0644), - ]) - -if 'uninstall' not in COMMAND_LINE_TARGETS: - if env['HAS_CAIRO']: - py_env.Append(CPPPATH = env['CAIRO_CPPPATHS']) - py_env.Append(CPPDEFINES = '-DHAVE_CAIRO') - if link_all_libs: - py_env.Append(LIBS=env['CAIRO_ALL_LIBS']) - - if env['HAS_PYCAIRO']: - py_env.Append(CPPDEFINES = '-DHAVE_PYCAIRO') - py_env.Append(CPPPATH = env['PYCAIRO_PATHS']) - -py_env.Append(LINKFLAGS=python_link_flag) -py_env.AppendUnique(LIBS='mapnik-json') -py_env.AppendUnique(LIBS='mapnik-wkt') - -_mapnik = py_env.LoadableModule('mapnik/_mapnik', sources, LDMODULEPREFIX='', LDMODULESUFFIX='.so') - -Depends(_mapnik, env.subst('../../src/%s' % env['MAPNIK_LIB_NAME'])) -Depends(_mapnik, env.subst('../../src/json/libmapnik-json${LIBSUFFIX}')) -Depends(_mapnik, env.subst('../../src/wkt/libmapnik-wkt${LIBSUFFIX}')) - -if 'uninstall' not in COMMAND_LINE_TARGETS: - pymapniklib = env.Install(target_path,_mapnik) - py_env.Alias(target='install',source=pymapniklib) - -env['create_uninstall_target'](env, target_path) diff --git a/demo/python/rundemo.py b/demo/python/rundemo.py index 01d5dce0b..057dc0ecd 100755 --- a/demo/python/rundemo.py +++ b/demo/python/rundemo.py @@ -2,10 +2,10 @@ # -*- coding: utf-8 -*- # # -# # This file is part of Mapnik (c++ mapping toolkit) # Copyright (C) 2005 Jean-Francois Doyon -# +# Copyright (C) 2024 Artem Pavlenko + # Mapnik is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 @@ -19,7 +19,7 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA -from __future__ import print_function + import sys from os import path import mapnik @@ -32,7 +32,7 @@ # Set its background colour. More on colours later ... -m.background = mapnik.Color('white') +m.background = 'white' #Color(R=255,G=255,B=255,A=255) # Now we can start adding layers, in stacking order (i.e. bottom layer first) @@ -92,14 +92,14 @@ sym = mapnik.PolygonSymbolizer() sym.fill = mapnik.Color(250, 190, 183); -provpoly_rule_on.symbols.append(sym) +provpoly_rule_on.symbolizers.append(sym) provpoly_style.rules.append(provpoly_rule_on) provpoly_rule_qc = mapnik.Rule() provpoly_rule_qc.filter = mapnik.Expression("[NOM_FR] = 'Québec'") sym = mapnik.PolygonSymbolizer() -sym.fill = mapnik.Color(217, 235, 203) -provpoly_rule_qc.symbols.append(sym) +sym.fill = 'rgb(217, 235, 203)' +provpoly_rule_qc.symbolizers.append(sym) provpoly_style.rules.append(provpoly_rule_qc) # Add the style to the map, giving it a name. This is the name that will be @@ -130,9 +130,9 @@ qcdrain_rule = mapnik.Rule() qcdrain_rule.filter = mapnik.Expression('[HYC] = 8') sym = mapnik.PolygonSymbolizer() -sym.fill = mapnik.Color(153, 204, 255) +sym.fill = 'rgba(153, 204, 255, 255)' sym.smooth = 1.0 # very smooth -qcdrain_rule.symbols.append(sym) +qcdrain_rule.symbolizers.append(sym) qcdrain_style.rules.append(qcdrain_rule) m.append_style('drainage', qcdrain_style) @@ -163,9 +163,10 @@ sym = mapnik.LineSymbolizer() # FIXME - currently adding dash arrays is broken # https://github.com/mapnik/mapnik/issues/2324 -sym.stroke = mapnik.Color('black') +sym.stroke = 'black' sym.stroke_width = 1 -provlines_rule.symbols.append(sym) +sym.stroke_dasharray="8 4 2 2 2 2" +provlines_rule.symbolizers.append(sym) provlines_style.rules.append(provlines_rule) m.append_style('provlines', provlines_style) @@ -199,7 +200,7 @@ sym.stroke_width = 2 sym.stroke_linecap = mapnik.stroke_linecap.ROUND_CAP -roads34_rule.symbols.append(sym) +roads34_rule.symbolizers.append(sym) roads34_style.rules.append(roads34_rule) m.append_style('smallroads', roads34_style) @@ -218,10 +219,10 @@ roads2_rule_1.filter = mapnik.Expression('[CLASS] = 2') sym = mapnik.LineSymbolizer() -sym.stroke = mapnik.Color(171,158,137) +sym.stroke = 'rgb(171,158,137)' #mapnik.Color(R=171,G=158,B=137,A=255) sym.stroke_width = 4 sym.stroke_linecap = mapnik.stroke_linecap.ROUND_CAP -roads2_rule_1.symbols.append(sym) +roads2_rule_1.symbolizers.append(sym) roads2_style_1.rules.append(roads2_rule_1) m.append_style('road-border', roads2_style_1) @@ -230,10 +231,10 @@ roads2_rule_2 = mapnik.Rule() roads2_rule_2.filter = mapnik.Expression('[CLASS] = 2') sym = mapnik.LineSymbolizer() -sym.stroke = mapnik.Color(255,250,115) +sym.stroke = 'rgb(100%,98%,45%)' #mapnik.Color(R=255,G=250,B=115,A=255) sym.stroke_linecap = mapnik.stroke_linecap.ROUND_CAP sym.stroke_width = 2 -roads2_rule_2.symbols.append(sym) +roads2_rule_2.symbolizers.append(sym) roads2_style_2.rules.append(roads2_rule_2) m.append_style('road-fill', roads2_style_2) @@ -256,7 +257,7 @@ sym.stroke = mapnik.Color(188,149,28) sym.stroke_linecap = mapnik.stroke_linecap.ROUND_CAP sym.stroke_width = 7 -roads1_rule_1.symbols.append(sym) +roads1_rule_1.symbolizers.append(sym) roads1_style_1.rules.append(roads1_rule_1) m.append_style('highway-border', roads1_style_1) @@ -266,7 +267,7 @@ sym.stroke = mapnik.Color(242,191,36) sym.stroke_linecap = mapnik.stroke_linecap.ROUND_CAP sym.stroke_width = 5 -roads1_rule_2.symbols.append(sym) +roads1_rule_2.symbolizers.append(sym) roads1_style_2.rules.append(roads1_rule_2) m.append_style('highway-fill', roads1_style_2) @@ -290,18 +291,27 @@ # text to label with. Then there is font size in points (I think?), and colour. # TODO - currently broken: https://github.com/mapnik/mapnik/issues/2324 -#popplaces_text_symbolizer = mapnik.TextSymbolizer(mapnik.Expression("[GEONAME]"), -# 'DejaVu Sans Book', -# 10, mapnik.Color('black')) + +popplaces_text_sym = mapnik.TextSymbolizer() + +popplaces_text_sym.placement_finder = mapnik.PlacementFinder() +popplaces_text_sym.placement_finder.face_name = 'DejaVu Sans Book' +popplaces_text_sym.placement_finder.text_size = 10 +popplaces_text_sym.placement_finder.halo_fill = 'rgba(100%,100%,78.5%,1.0)' #mapnik.Color(R=255,G=255,B=200,A=255) +popplaces_text_sym.placement_finder.halo_radius = 1.0 +popplaces_text_sym.placement_finder.fill = "black" +popplaces_text_sym.placement_finder.format_expression = "[GEONAME]" + # We set a "halo" around the text, which looks like an outline if thin enough, # or an outright background if large enough. -#popplaces_text_symbolizer.label_placement= mapnik.label_placement.POINT_PLACEMENT -#popplaces_text_symbolizer.halo_fill = mapnik.Color(255,255,200) -#popplaces_text_symbolizer.halo_radius = 1 -#popplaces_text_symbolizer.avoid_edges = True -#popplaces_text_symbolizer.minimum_padding = 30 -#popplaces_rule.symbols.append(popplaces_text_symbolizer) +#popplaces_text_sym.label_placement= mapnik.label_placement.POINT_PLACEMENT +#popplaces_text_sym.halo_fill = mapnik.Color(255,255,200) +#popplaces_text_sym.halo_radius = 1 +#popplaces_text_sym.avoid_edges = True +#popplaces_text_sym.minimum_padding = 30 + +popplaces_rule.symbolizers.append(popplaces_text_sym) popplaces_style.rules.append(popplaces_rule) @@ -365,9 +375,9 @@ mapnik.render_to_file(m,'demo.svg') images_.append('demo.svg') mapnik.render_to_file(m,'demo_cairo_rgb24.png','RGB24') - images_.append('demo_cairo_rgb.png') + images_.append('demo_cairo_rgb24.png') mapnik.render_to_file(m,'demo_cairo_argb32.png','ARGB32') - images_.append('demo_cairo_argb.png') + images_.append('demo_cairo_argb32.png') print ("\n\n", len(images_), "maps have been rendered in the current directory:") diff --git a/mapnik/__init__.py b/mapnik/__init__.py deleted file mode 100644 index 4d99ad14b..000000000 --- a/mapnik/__init__.py +++ /dev/null @@ -1,1072 +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 -# - -"""Mapnik Python module. - -Boost Python bindings to the Mapnik C++ shared library. - -Several things happen when you do: - - >>> import mapnik - - 1) Mapnik C++ objects are imported via the '__init__.py' from the '_mapnik.so' shared object - (_mapnik.pyd on win) which references libmapnik.so (linux), libmapnik.dylib (mac), or - mapnik.dll (win32). - - 2) The paths to the input plugins and font directories are imported from the 'paths.py' - file which was constructed and installed during SCons installation. - - 3) All available input plugins and TrueType fonts are automatically registered. - - 4) Boost Python metaclass injectors are used in the '__init__.py' to extend several - objects adding extra convenience when accessed via Python. - -""" - -import itertools -import os -import warnings -try: - import json -except ImportError: - import simplejson as json - - -def bootstrap_env(): - """ - If an optional settings file exists, inherit its - environment settings before loading the mapnik library. - - This feature is intended for customized packages of mapnik. - - The settings file should be a python file with an 'env' variable - that declares a dictionary of key:value pairs to push into the - global process environment, if not already set, like: - - env = {'ICU_DATA':'/usr/local/share/icu/'} - """ - if os.path.exists(os.path.join( - os.path.dirname(__file__), 'mapnik_settings.py')): - from .mapnik_settings import env - process_keys = os.environ.keys() - for key, value in env.items(): - if key not in process_keys: - os.environ[key] = value - -bootstrap_env() - -from ._mapnik import * - -# The base Boost.Python class -BoostPythonMetaclass = Coord.__class__ - - -class _MapnikMetaclass(BoostPythonMetaclass): - - def __init__(self, name, bases, dict): - for b in bases: - if type(b) not in (self, type): - for k, v in list(dict.items()): - if hasattr(b, k): - setattr(b, '_c_' + k, getattr(b, k)) - setattr(b, k, v) - return type.__init__(self, name, bases, dict) - -# metaclass injector compatible with both python 2 and 3 -# http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/ -def _injector() : - return _MapnikMetaclass('_injector', (object, ), {}) - - -def Filter(*args, **kwargs): - warnings.warn("'Filter' is deprecated and will be removed in Mapnik 3.x, use 'Expression' instead", - DeprecationWarning, 2) - return Expression(*args, **kwargs) - - -class Envelope(Box2d): - - def __init__(self, *args, **kwargs): - warnings.warn("'Envelope' is deprecated and will be removed in Mapnik 3.x, use 'Box2d' instead", - DeprecationWarning, 2) - Box2d.__init__(self, *args, **kwargs) - - -class _Coord(Coord, _injector()): - """ - Represents a point with two coordinates (either lon/lat or x/y). - - Following operators are defined for Coord: - - Addition and subtraction of Coord objects: - - >>> Coord(10, 10) + Coord(20, 20) - Coord(30.0, 30.0) - >>> Coord(10, 10) - Coord(20, 20) - Coord(-10.0, -10.0) - - Addition, subtraction, multiplication and division between - a Coord and a float: - - >>> Coord(10, 10) + 1 - Coord(11.0, 11.0) - >>> Coord(10, 10) - 1 - Coord(-9.0, -9.0) - >>> Coord(10, 10) * 2 - Coord(20.0, 20.0) - >>> Coord(10, 10) / 2 - Coord(5.0, 5.0) - - Equality of coords (as pairwise equality of components): - >>> Coord(10, 10) is Coord(10, 10) - False - >>> Coord(10, 10) == Coord(10, 10) - True - """ - - def __repr__(self): - return 'Coord(%s,%s)' % (self.x, self.y) - - def forward(self, projection): - """ - Projects the point from the geographic coordinate - space into the cartesian space. The x component is - considered to be longitude, the y component the - latitude. - - Returns the easting (x) and northing (y) as a - coordinate pair. - - Example: Project the geographic coordinates of the - city center of Stuttgart into the local - map projection (GK Zone 3/DHDN, EPSG 31467) - >>> p = Projection('+init=epsg:31467') - >>> Coord(9.1, 48.7).forward(p) - Coord(3507360.12813,5395719.2749) - """ - return forward_(self, projection) - - def inverse(self, projection): - """ - Projects the point from the cartesian space - into the geographic space. The x component is - considered to be the easting, the y component - to be the northing. - - Returns the longitude (x) and latitude (y) as a - coordinate pair. - - Example: Project the cartesian coordinates of the - city center of Stuttgart in the local - map projection (GK Zone 3/DHDN, EPSG 31467) - into geographic coordinates: - >>> p = Projection('+init=epsg:31467') - >>> Coord(3507360.12813,5395719.2749).inverse(p) - Coord(9.1, 48.7) - """ - return inverse_(self, projection) - - -class _Box2d(Box2d, _injector()): - """ - Represents a spatial envelope (i.e. bounding box). - - - Following operators are defined for Box2d: - - Addition: - e1 + e2 is equivalent to e1.expand_to_include(e2) but yields - a new envelope instead of modifying e1 - - Subtraction: - Currently e1 - e2 returns e1. - - Multiplication and division with floats: - Multiplication and division change the width and height of the envelope - by the given factor without modifying its center.. - - That is, e1 * x is equivalent to: - e1.width(x * e1.width()) - e1.height(x * e1.height()), - except that a new envelope is created instead of modifying e1. - - e1 / x is equivalent to e1 * (1.0/x). - - Equality: two envelopes are equal if their corner points are equal. - """ - - def __repr__(self): - return 'Box2d(%s,%s,%s,%s)' % \ - (self.minx, self.miny, self.maxx, self.maxy) - - def forward(self, projection): - """ - Projects the envelope from the geographic space - into the cartesian space by projecting its corner - points. - - See also: - Coord.forward(self, projection) - """ - return forward_(self, projection) - - def inverse(self, projection): - """ - Projects the envelope from the cartesian space - into the geographic space by projecting its corner - points. - - See also: - Coord.inverse(self, projection). - """ - return inverse_(self, projection) - - -class _Projection(Projection, _injector()): - - def __repr__(self): - return "Projection('%s')" % self.params() - - def forward(self, obj): - """ - Projects the given object (Box2d or Coord) - from the geographic space into the cartesian space. - - See also: - Box2d.forward(self, projection), - Coord.forward(self, projection). - """ - return forward_(obj, self) - - def inverse(self, obj): - """ - Projects the given object (Box2d or Coord) - from the cartesian space into the geographic space. - - See also: - Box2d.inverse(self, projection), - Coord.inverse(self, projection). - """ - return inverse_(obj, self) - - -class _Feature(Feature, _injector()): - __geo_interface__ = property(lambda self: json.loads(self.to_geojson())) - - -class _Geometry(Geometry, _injector()): - __geo_interface__ = property(lambda self: json.loads(self.to_geojson())) - - -class _Datasource(Datasource, _injector()): - - def featureset(self, fields = None, variables = {}): - query = Query(self.envelope()) - query.set_variables(variables) - attributes = fields or self.fields() - for fld in attributes: - query.add_property_name(fld) - return self.features(query) - - def __iter__(self, fields = None, variables = {}): - return self.featureset(fields, variables) - # backward caps helper - def all_features(self, fields=None, variables={}): - return self.__iter__(fields, variables) - - -class _Color(Color, _injector()): - - def __repr__(self): - return "Color(R=%d,G=%d,B=%d,A=%d)" % (self.r, self.g, self.b, self.a) - - -class _SymbolizerBase(SymbolizerBase, _injector()): - # back compatibility - - @property - def filename(self): - return self['file'] - - @filename.setter - def filename(self, val): - self['file'] = val - - -def _add_symbol_method_to_symbolizers(vars=globals()): - - def symbol_for_subcls(self): - return self - - def symbol_for_cls(self): - return getattr(self, self.type())() - - for name, obj in vars.items(): - if name.endswith('Symbolizer') and not name.startswith('_'): - if name == 'Symbolizer': - symbol = symbol_for_cls - else: - symbol = symbol_for_subcls - type('dummy', (obj, _injector()), {'symbol': symbol}) -_add_symbol_method_to_symbolizers() - - -def Datasource(**keywords): - """Wrapper around CreateDatasource. - - Create a Mapnik Datasource using a dictionary of parameters. - - Keywords must include: - - type='plugin_name' # e.g. type='gdal' - - See the convenience factory methods of each input plugin for - details on additional required keyword arguments. - - """ - - return CreateDatasource(keywords) - -# convenience factory methods - - -def Shapefile(**keywords): - """Create a Shapefile Datasource. - - Required keyword arguments: - file -- path to shapefile without extension - - Optional keyword arguments: - base -- path prefix (default None) - encoding -- file encoding (default 'utf-8') - - >>> from mapnik import Shapefile, Layer - >>> shp = Shapefile(base='/home/mapnik/data',file='world_borders') - >>> lyr = Layer('Shapefile Layer') - >>> lyr.datasource = shp - - """ - keywords['type'] = 'shape' - return CreateDatasource(keywords) - - -def CSV(**keywords): - """Create a CSV Datasource. - - Required keyword arguments: - file -- path to csv - - Optional keyword arguments: - inline -- inline CSV string (if provided 'file' argument will be ignored and non-needed) - base -- path prefix (default None) - encoding -- file encoding (default 'utf-8') - row_limit -- integer limit of rows to return (default: 0) - strict -- throw an error if an invalid row is encountered - escape -- The escape character to use for parsing data - quote -- The quote character to use for parsing data - separator -- The separator character to use for parsing data - headers -- A comma separated list of header names that can be set to add headers to data that lacks them - filesize_max -- The maximum filesize in MB that will be accepted - - >>> from mapnik import CSV - >>> csv = CSV(file='test.csv') - - >>> from mapnik import CSV - >>> csv = CSV(inline='''wkt,Name\n"POINT (120.15 48.47)","Winthrop, WA"''') - - For more information see https://github.com/mapnik/mapnik/wiki/CSV-Plugin - - """ - keywords['type'] = 'csv' - return CreateDatasource(keywords) - - -def GeoJSON(**keywords): - """Create a GeoJSON Datasource. - - Required keyword arguments: - file -- path to json - - Optional keyword arguments: - encoding -- file encoding (default 'utf-8') - base -- path prefix (default None) - - >>> from mapnik import GeoJSON - >>> geojson = GeoJSON(file='test.json') - - """ - keywords['type'] = 'geojson' - return CreateDatasource(keywords) - - -def PostGIS(**keywords): - """Create a PostGIS Datasource. - - Required keyword arguments: - dbname -- database name to connect to - table -- table name or subselect query - - *Note: if using subselects for the 'table' value consider also - passing the 'geometry_field' and 'srid' and 'extent_from_subquery' - options and/or specifying the 'geometry_table' option. - - Optional db connection keyword arguments: - user -- database user to connect as (default: see postgres docs) - password -- password for database user (default: see postgres docs) - host -- postgres hostname (default: see postgres docs) - port -- postgres port (default: see postgres docs) - initial_size -- integer size of connection pool (default: 1) - max_size -- integer max of connection pool (default: 10) - persist_connection -- keep connection open (default: True) - - Optional table-level keyword arguments: - extent -- manually specified data extent (comma delimited string, default: None) - estimate_extent -- boolean, direct PostGIS to use the faster, less accurate `estimate_extent` over `extent` (default: False) - extent_from_subquery -- boolean, direct Mapnik to query Postgis for the extent of the raw 'table' value (default: uses 'geometry_table') - geometry_table -- specify geometry table to use to look up metadata (default: automatically parsed from 'table' value) - geometry_field -- specify geometry field to use (default: first entry in geometry_columns) - srid -- specify srid to use (default: auto-detected from geometry_field) - row_limit -- integer limit of rows to return (default: 0) - cursor_size -- integer size of binary cursor to use (default: 0, no binary cursor is used) - - >>> from mapnik import PostGIS, Layer - >>> params = dict(dbname=env['MAPNIK_NAME'],table='osm',user='postgres',password='gis') - >>> params['estimate_extent'] = False - >>> params['extent'] = '-20037508,-19929239,20037508,19929239' - >>> postgis = PostGIS(**params) - >>> lyr = Layer('PostGIS Layer') - >>> lyr.datasource = postgis - - """ - keywords['type'] = 'postgis' - return CreateDatasource(keywords) - - -def PgRaster(**keywords): - """Create a PgRaster Datasource. - - Required keyword arguments: - dbname -- database name to connect to - table -- table name or subselect query - - *Note: if using subselects for the 'table' value consider also - passing the 'raster_field' and 'srid' and 'extent_from_subquery' - options and/or specifying the 'raster_table' option. - - Optional db connection keyword arguments: - user -- database user to connect as (default: see postgres docs) - password -- password for database user (default: see postgres docs) - host -- postgres hostname (default: see postgres docs) - port -- postgres port (default: see postgres docs) - initial_size -- integer size of connection pool (default: 1) - max_size -- integer max of connection pool (default: 10) - persist_connection -- keep connection open (default: True) - - Optional table-level keyword arguments: - extent -- manually specified data extent (comma delimited string, default: None) - estimate_extent -- boolean, direct PostGIS to use the faster, less accurate `estimate_extent` over `extent` (default: False) - extent_from_subquery -- boolean, direct Mapnik to query Postgis for the extent of the raw 'table' value (default: uses 'geometry_table') - raster_table -- specify geometry table to use to look up metadata (default: automatically parsed from 'table' value) - raster_field -- specify geometry field to use (default: first entry in raster_columns) - srid -- specify srid to use (default: auto-detected from geometry_field) - row_limit -- integer limit of rows to return (default: 0) - cursor_size -- integer size of binary cursor to use (default: 0, no binary cursor is used) - use_overviews -- boolean, use overviews when available (default: false) - prescale_rasters -- boolean, scale rasters on the db side (default: false) - clip_rasters -- boolean, clip rasters on the db side (default: false) - band -- integer, if non-zero interprets the given band (1-based offset) as a data raster (default: 0) - - >>> from mapnik import PgRaster, Layer - >>> params = dict(dbname='mapnik',table='osm',user='postgres',password='gis') - >>> params['estimate_extent'] = False - >>> params['extent'] = '-20037508,-19929239,20037508,19929239' - >>> pgraster = PgRaster(**params) - >>> lyr = Layer('PgRaster Layer') - >>> lyr.datasource = pgraster - - """ - keywords['type'] = 'pgraster' - return CreateDatasource(keywords) - - -def Raster(**keywords): - """Create a Raster (Tiff) Datasource. - - Required keyword arguments: - file -- path to stripped or tiled tiff - lox -- lowest (min) x/longitude of tiff extent - loy -- lowest (min) y/latitude of tiff extent - hix -- highest (max) x/longitude of tiff extent - hiy -- highest (max) y/latitude of tiff extent - - Hint: lox,loy,hix,hiy make a Mapnik Box2d - - Optional keyword arguments: - base -- path prefix (default None) - multi -- whether the image is in tiles on disk (default False) - - Multi-tiled keyword arguments: - x_width -- virtual image number of tiles in X direction (required) - y_width -- virtual image number of tiles in Y direction (required) - tile_size -- if an image is in tiles, how large are the tiles (default 256) - tile_stride -- if an image is in tiles, what's the increment between rows/cols (default 1) - - >>> from mapnik import Raster, Layer - >>> raster = Raster(base='/home/mapnik/data',file='elevation.tif',lox=-122.8,loy=48.5,hix=-122.7,hiy=48.6) - >>> lyr = Layer('Tiff Layer') - >>> lyr.datasource = raster - - """ - keywords['type'] = 'raster' - return CreateDatasource(keywords) - - -def Gdal(**keywords): - """Create a GDAL Raster Datasource. - - Required keyword arguments: - file -- path to GDAL supported dataset - - Optional keyword arguments: - base -- path prefix (default None) - shared -- boolean, open GdalDataset in shared mode (default: False) - bbox -- tuple (minx, miny, maxx, maxy). If specified, overrides the bbox detected by GDAL. - - >>> from mapnik import Gdal, Layer - >>> dataset = Gdal(base='/home/mapnik/data',file='elevation.tif') - >>> lyr = Layer('GDAL Layer from TIFF file') - >>> lyr.datasource = dataset - - """ - keywords['type'] = 'gdal' - if 'bbox' in keywords: - if isinstance(keywords['bbox'], (tuple, list)): - keywords['bbox'] = ','.join([str(item) - for item in keywords['bbox']]) - return CreateDatasource(keywords) - - -def Occi(**keywords): - """Create a Oracle Spatial (10g) Vector Datasource. - - Required keyword arguments: - user -- database user to connect as - password -- password for database user - host -- oracle host to connect to (does not refer to SID in tsnames.ora) - table -- table name or subselect query - - Optional keyword arguments: - initial_size -- integer size of connection pool (default 1) - max_size -- integer max of connection pool (default 10) - extent -- manually specified data extent (comma delimited string, default None) - estimate_extent -- boolean, direct Oracle to use the faster, less accurate estimate_extent() over extent() (default False) - encoding -- file encoding (default 'utf-8') - geometry_field -- specify geometry field (default 'GEOLOC') - use_spatial_index -- boolean, force the use of the spatial index (default True) - - >>> from mapnik import Occi, Layer - >>> params = dict(host='myoracle',user='scott',password='tiger',table='test') - >>> params['estimate_extent'] = False - >>> params['extent'] = '-20037508,-19929239,20037508,19929239' - >>> oracle = Occi(**params) - >>> lyr = Layer('Oracle Spatial Layer') - >>> lyr.datasource = oracle - """ - keywords['type'] = 'occi' - return CreateDatasource(keywords) - - -def Ogr(**keywords): - """Create a OGR Vector Datasource. - - Required keyword arguments: - file -- path to OGR supported dataset - layer -- name of layer to use within datasource (optional if layer_by_index or layer_by_sql is used) - - Optional keyword arguments: - layer_by_index -- choose layer by index number instead of by layer name or sql. - layer_by_sql -- choose layer by sql query number instead of by layer name or index. - base -- path prefix (default None) - encoding -- file encoding (default 'utf-8') - - >>> from mapnik import Ogr, Layer - >>> datasource = Ogr(base='/home/mapnik/data',file='rivers.geojson',layer='OGRGeoJSON') - >>> lyr = Layer('OGR Layer from GeoJSON file') - >>> lyr.datasource = datasource - - """ - keywords['type'] = 'ogr' - return CreateDatasource(keywords) - - -def SQLite(**keywords): - """Create a SQLite Datasource. - - Required keyword arguments: - file -- path to SQLite database file - table -- table name or subselect query - - Optional keyword arguments: - base -- path prefix (default None) - encoding -- file encoding (default 'utf-8') - extent -- manually specified data extent (comma delimited string, default None) - metadata -- name of auxiliary table containing record for table with xmin, ymin, xmax, ymax, and f_table_name - geometry_field -- name of geometry field (default 'the_geom') - key_field -- name of primary key field (default 'OGC_FID') - row_offset -- specify a custom integer row offset (default 0) - row_limit -- specify a custom integer row limit (default 0) - wkb_format -- specify a wkb type of 'spatialite' (default None) - use_spatial_index -- boolean, instruct sqlite plugin to use Rtree spatial index (default True) - - >>> from mapnik import SQLite, Layer - >>> sqlite = SQLite(base='/home/mapnik/data',file='osm.db',table='osm',extent='-20037508,-19929239,20037508,19929239') - >>> lyr = Layer('SQLite Layer') - >>> lyr.datasource = sqlite - - """ - keywords['type'] = 'sqlite' - return CreateDatasource(keywords) - - -def Rasterlite(**keywords): - """Create a Rasterlite Datasource. - - Required keyword arguments: - file -- path to Rasterlite database file - table -- table name or subselect query - - Optional keyword arguments: - base -- path prefix (default None) - extent -- manually specified data extent (comma delimited string, default None) - - >>> from mapnik import Rasterlite, Layer - >>> rasterlite = Rasterlite(base='/home/mapnik/data',file='osm.db',table='osm',extent='-20037508,-19929239,20037508,19929239') - >>> lyr = Layer('Rasterlite Layer') - >>> lyr.datasource = rasterlite - - """ - keywords['type'] = 'rasterlite' - return CreateDatasource(keywords) - - -def Osm(**keywords): - """Create a Osm Datasource. - - Required keyword arguments: - file -- path to OSM file - - Optional keyword arguments: - encoding -- file encoding (default 'utf-8') - url -- url to fetch data (default None) - bbox -- data bounding box for fetching data (default None) - - >>> from mapnik import Osm, Layer - >>> datasource = Osm(file='test.osm') - >>> lyr = Layer('Osm Layer') - >>> lyr.datasource = datasource - - """ - # note: parser only supports libxml2 so not exposing option - # parser -- xml parser to use (default libxml2) - keywords['type'] = 'osm' - return CreateDatasource(keywords) - - -def Python(**keywords): - """Create a Python Datasource. - - >>> from mapnik import Python, PythonDatasource - >>> datasource = Python('PythonDataSource') - >>> lyr = Layer('Python datasource') - >>> lyr.datasource = datasource - """ - keywords['type'] = 'python' - return CreateDatasource(keywords) - - -def MemoryDatasource(**keywords): - """Create a Memory Datasource. - - Optional keyword arguments: - (TODO) - """ - params = Parameters() - params.append(Parameter('type', 'memory')) - return MemoryDatasourceBase(params) - - -class PythonDatasource(object): - """A base class for a Python data source. - - Optional arguments: - envelope -- a mapnik.Box2d (minx, miny, maxx, maxy) envelope of the data source, default (-180,-90,180,90) - geometry_type -- one of the DataGeometryType enumeration values, default Point - data_type -- one of the DataType enumerations, default Vector - """ - - def __init__(self, envelope=None, geometry_type=None, data_type=None): - self.envelope = envelope or Box2d(-180, -90, 180, 90) - self.geometry_type = geometry_type or DataGeometryType.Point - self.data_type = data_type or DataType.Vector - - def features(self, query): - """Return an iterable which yields instances of Feature for features within the passed query. - - Required arguments: - query -- a Query instance specifying the region for which features should be returned - """ - return None - - def features_at_point(self, point): - """Rarely used. Return an iterable which yields instances of Feature for the specified point.""" - return None - - @classmethod - def wkb_features(cls, keys, features): - """A convenience function to wrap an iterator yielding pairs of WKB format geometry and dictionaries of - key-value pairs into mapnik features. Return this from PythonDatasource.features() passing it a sequence of keys - to appear in the output and an iterator yielding features. - - For example. One might have a features() method in a derived class like the following: - - def features(self, query): - # ... create WKB features feat1 and feat2 - - return mapnik.PythonDatasource.wkb_features( - keys = ( 'name', 'author' ), - features = [ - (feat1, { 'name': 'feat1', 'author': 'alice' }), - (feat2, { 'name': 'feat2', 'author': 'bob' }), - ] - ) - - """ - ctx = Context() - [ctx.push(x) for x in keys] - - def make_it(feat, idx): - f = Feature(ctx, idx) - geom, attrs = feat - f.add_geometries_from_wkb(geom) - for k, v in attrs.iteritems(): - f[k] = v - return f - - return itertools.imap(make_it, features, itertools.count(1)) - - @classmethod - def wkt_features(cls, keys, features): - """A convenience function to wrap an iterator yielding pairs of WKT format geometry and dictionaries of - key-value pairs into mapnik features. Return this from PythonDatasource.features() passing it a sequence of keys - to appear in the output and an iterator yielding features. - - For example. One might have a features() method in a derived class like the following: - - def features(self, query): - # ... create WKT features feat1 and feat2 - - return mapnik.PythonDatasource.wkt_features( - keys = ( 'name', 'author' ), - features = [ - (feat1, { 'name': 'feat1', 'author': 'alice' }), - (feat2, { 'name': 'feat2', 'author': 'bob' }), - ] - ) - - """ - ctx = Context() - [ctx.push(x) for x in keys] - - def make_it(feat, idx): - f = Feature(ctx, idx) - geom, attrs = feat - f.add_geometries_from_wkt(geom) - for k, v in attrs.iteritems(): - f[k] = v - return f - - return itertools.imap(make_it, features, itertools.count(1)) - - -class _TextSymbolizer(TextSymbolizer, _injector()): - - @property - def name(self): - if isinstance(self.properties.format_tree, FormattingText): - return self.properties.format_tree.text - else: - # There is no single expression which could be returned as name - raise RuntimeError( - "TextSymbolizer uses complex formatting features, but old compatibility interface is used to access it. Use self.properties.format_tree instead.") - - @name.setter - def name(self, name): - self.properties.format_tree = FormattingText(name) - - @property - def text_size(self): - return self.format.text_size - - @text_size.setter - def text_size(self, text_size): - self.format.text_size = text_size - - @property - def face_name(self): - return self.format.face_name - - @face_name.setter - def face_name(self, face_name): - self.format.face_name = face_name - - @property - def fontset(self): - return self.format.fontset - - @fontset.setter - def fontset(self, fontset): - self.format.fontset = fontset - - @property - def character_spacing(self): - return self.format.character_spacing - - @character_spacing.setter - def character_spacing(self, character_spacing): - self.format.character_spacing = character_spacing - - @property - def line_spacing(self): - return self.format.line_spacing - - @line_spacing.setter - def line_spacing(self, line_spacing): - self.format.line_spacing = line_spacing - - @property - def text_opacity(self): - return self.format.text_opacity - - @text_opacity.setter - def text_opacity(self, text_opacity): - self.format.text_opacity = text_opacity - - @property - def wrap_before(self): - return self.format.wrap_before - - @wrap_before.setter - def wrap_before(self, wrap_before): - self.format.wrap_before = wrap_before - - @property - def text_transform(self): - return self.format.text_transform - - @text_transform.setter - def text_transform(self, text_transform): - self.format.text_transform = text_transform - - @property - def fill(self): - return self.format.fill - - @fill.setter - def fill(self, fill): - self.format.fill = fill - - @property - def halo_fill(self): - return self.format.halo_fill - - @halo_fill.setter - def halo_fill(self, halo_fill): - self.format.halo_fill = halo_fill - - @property - def halo_radius(self): - return self.format.halo_radius - - @halo_radius.setter - def halo_radius(self, halo_radius): - self.format.halo_radius = halo_radius - - @property - def label_placement(self): - return self.properties.label_placement - - @label_placement.setter - def label_placement(self, label_placement): - self.properties.label_placement = label_placement - - @property - def horizontal_alignment(self): - return self.properties.horizontal_alignment - - @horizontal_alignment.setter - def horizontal_alignment(self, horizontal_alignment): - self.properties.horizontal_alignment = horizontal_alignment - - @property - def justify_alignment(self): - return self.properties.justify_alignment - - @justify_alignment.setter - def justify_alignment(self, justify_alignment): - self.properties.justify_alignment = justify_alignment - - @property - def vertical_alignment(self): - return self.properties.vertical_alignment - - @vertical_alignment.setter - def vertical_alignment(self, vertical_alignment): - self.properties.vertical_alignment = vertical_alignment - - @property - def orientation(self): - return self.properties.orientation - - @orientation.setter - def orientation(self, orientation): - self.properties.orientation = orientation - - @property - def displacement(self): - return self.properties.displacement - - @displacement.setter - def displacement(self, displacement): - self.properties.displacement = displacement - - @property - def label_spacing(self): - return self.properties.label_spacing - - @label_spacing.setter - def label_spacing(self, label_spacing): - self.properties.label_spacing = label_spacing - - @property - def label_position_tolerance(self): - return self.properties.label_position_tolerance - - @label_position_tolerance.setter - def label_position_tolerance(self, label_position_tolerance): - self.properties.label_position_tolerance = label_position_tolerance - - @property - def avoid_edges(self): - return self.properties.avoid_edges - - @avoid_edges.setter - def avoid_edges(self, avoid_edges): - self.properties.avoid_edges = avoid_edges - - @property - def minimum_distance(self): - return self.properties.minimum_distance - - @minimum_distance.setter - def minimum_distance(self, minimum_distance): - self.properties.minimum_distance = minimum_distance - - @property - def minimum_padding(self): - return self.properties.minimum_padding - - @minimum_padding.setter - def minimum_padding(self, minimum_padding): - self.properties.minimum_padding = minimum_padding - - @property - def minimum_path_length(self): - return self.properties.minimum_path_length - - @minimum_path_length.setter - def minimum_path_length(self, minimum_path_length): - self.properties.minimum_path_length = minimum_path_length - - @property - def maximum_angle_char_delta(self): - return self.properties.maximum_angle_char_delta - - @maximum_angle_char_delta.setter - def maximum_angle_char_delta(self, maximum_angle_char_delta): - self.properties.maximum_angle_char_delta = maximum_angle_char_delta - - @property - def allow_overlap(self): - return self.properties.allow_overlap - - @allow_overlap.setter - def allow_overlap(self, allow_overlap): - self.properties.allow_overlap = allow_overlap - - @property - def text_ratio(self): - return self.properties.text_ratio - - @text_ratio.setter - def text_ratio(self, text_ratio): - self.properties.text_ratio = text_ratio - - @property - def wrap_width(self): - return self.properties.wrap_width - - @wrap_width.setter - def wrap_width(self, wrap_width): - self.properties.wrap_width = wrap_width - - -def mapnik_version_from_string(version_string): - """Return the Mapnik version from a string.""" - n = version_string.split('.') - return (int(n[0]) * 100000) + (int(n[1]) * 100) + (int(n[2])) - - -def register_plugins(path=None): - """Register plugins located by specified path""" - if not path: - if 'MAPNIK_INPUT_PLUGINS_DIRECTORY' in os.environ: - path = os.environ.get('MAPNIK_INPUT_PLUGINS_DIRECTORY') - else: - from .paths import inputpluginspath - path = inputpluginspath - DatasourceCache.register_datasources(path) - - -def register_fonts(path=None, valid_extensions=[ - '.ttf', '.otf', '.ttc', '.pfa', '.pfb', '.ttc', '.dfont', '.woff']): - """Recursively register fonts using path argument as base directory""" - if not path: - if 'MAPNIK_FONT_DIRECTORY' in os.environ: - path = os.environ.get('MAPNIK_FONT_DIRECTORY') - else: - from .paths import fontscollectionpath - path = fontscollectionpath - for dirpath, _, filenames in os.walk(path): - for filename in filenames: - if os.path.splitext(filename.lower())[1] in valid_extensions: - FontEngine.register_font(os.path.join(dirpath, filename)) - -# auto-register known plugins and fonts -register_plugins() -register_fonts() diff --git a/packaging/mapnik/__init__.py b/packaging/mapnik/__init__.py new file mode 100644 index 000000000..1fb21c7ea --- /dev/null +++ b/packaging/mapnik/__init__.py @@ -0,0 +1,406 @@ +# +# This file is part of Mapnik (c++ mapping toolkit) +# Copyright (C) 2024 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 +# + +"""Mapnik Python module. + +Python bindings to the Mapnik C++ shared library. + +Several things happen when you do: + + >>> import mapnik + + 1) Mapnik C++ objects are imported via the '__init__.py' from the '_mapnik.so' shared object + (_mapnik.pyd on win) which references libmapnik.so (linux), libmapnik.dylib (mac), or + mapnik.dll (win32). + + 2) The paths to the input plugins and font directories are imported from the 'paths.py' + file which was constructed and installed during SCons installation. + + 3) All available input plugins and TrueType fonts are automatically registered. + +""" + +import itertools +import os +import warnings + +def bootstrap_env(): + """ + If an optional settings file exists, inherit its + environment settings before loading the mapnik library. + + This feature is intended for customized packages of mapnik. + + The settings file should be a python file with an 'env' variable + that declares a dictionary of key:value pairs to push into the + global process environment, if not already set, like: + + env = {'ICU_DATA':'/usr/local/share/icu/'} + """ + if os.path.exists(os.path.join( + os.path.dirname(__file__), 'mapnik_settings.py')): + from .mapnik_settings import env + process_keys = os.environ.keys() + for key, value in env.items(): + if key not in process_keys: + os.environ[key] = value + +bootstrap_env() + +from ._mapnik import * + +def Shapefile(**keywords): + """Create a Shapefile Datasource. + + Required keyword arguments: + file -- path to shapefile without extension + + Optional keyword arguments: + base -- path prefix (default None) + encoding -- file encoding (default 'utf-8') + + >>> from mapnik import Shapefile, Layer + >>> shp = Shapefile(base='/home/mapnik/data',file='world_borders') + >>> lyr = Layer('Shapefile Layer') + >>> lyr.datasource = shp + + """ + return CreateDatasource(type='shape', **keywords) + +def CSV(**keywords): + """Create a CSV Datasource. + + Required keyword arguments: + file -- path to csv + + Optional keyword arguments: + inline -- inline CSV string (if provided 'file' argument will be ignored and non-needed) + base -- path prefix (default None) + encoding -- file encoding (default 'utf-8') + row_limit -- integer limit of rows to return (default: 0) + strict -- throw an error if an invalid row is encountered + escape -- The escape character to use for parsing data + quote -- The quote character to use for parsing data + separator -- The separator character to use for parsing data + headers -- A comma separated list of header names that can be set to add headers to data that lacks them + filesize_max -- The maximum filesize in MB that will be accepted + + >>> from mapnik import CSV + >>> csv = CSV(file='test.csv') + + >>> from mapnik import CSV + >>> csv = CSV(inline='''wkt,Name\n"POINT (120.15 48.47)","Winthrop, WA"''') + + For more information see https://github.com/mapnik/mapnik/wiki/CSV-Plugin + + """ + return CreateDatasource(type='csv', **keywords) + + +def GeoJSON(**keywords): + """Create a GeoJSON Datasource. + + Required keyword arguments: + file -- path to json + + Optional keyword arguments: + encoding -- file encoding (default 'utf-8') + base -- path prefix (default None) + + >>> from mapnik import GeoJSON + >>> geojson = GeoJSON(file='test.json') + + """ + return CreateDatasource(type='geojson', **keywords) + + +def PostGIS(**keywords): + """Create a PostGIS Datasource. + + Required keyword arguments: + dbname -- database name to connect to + table -- table name or subselect query + + *Note: if using subselects for the 'table' value consider also + passing the 'geometry_field' and 'srid' and 'extent_from_subquery' + options and/or specifying the 'geometry_table' option. + + Optional db connection keyword arguments: + user -- database user to connect as (default: see postgres docs) + password -- password for database user (default: see postgres docs) + host -- postgres hostname (default: see postgres docs) + port -- postgres port (default: see postgres docs) + initial_size -- integer size of connection pool (default: 1) + max_size -- integer max of connection pool (default: 10) + persist_connection -- keep connection open (default: True) + + Optional table-level keyword arguments: + extent -- manually specified data extent (comma delimited string, default: None) + estimate_extent -- boolean, direct PostGIS to use the faster, less accurate `estimate_extent` over `extent` (default: False) + extent_from_subquery -- boolean, direct Mapnik to query Postgis for the extent of the raw 'table' value (default: uses 'geometry_table') + geometry_table -- specify geometry table to use to look up metadata (default: automatically parsed from 'table' value) + geometry_field -- specify geometry field to use (default: first entry in geometry_columns) + srid -- specify srid to use (default: auto-detected from geometry_field) + row_limit -- integer limit of rows to return (default: 0) + cursor_size -- integer size of binary cursor to use (default: 0, no binary cursor is used) + + >>> from mapnik import PostGIS, Layer + >>> params = dict(dbname=env['MAPNIK_NAME'],table='osm',user='postgres',password='gis') + >>> params['estimate_extent'] = False + >>> params['extent'] = '-20037508,-19929239,20037508,19929239' + >>> postgis = PostGIS(**params) + >>> lyr = Layer('PostGIS Layer') + >>> lyr.datasource = postgis + + """ + return CreateDatasource(type='postgis', **keywords) + + +def PgRaster(**keywords): + """Create a PgRaster Datasource. + + Required keyword arguments: + dbname -- database name to connect to + table -- table name or subselect query + + *Note: if using subselects for the 'table' value consider also + passing the 'raster_field' and 'srid' and 'extent_from_subquery' + options and/or specifying the 'raster_table' option. + + Optional db connection keyword arguments: + user -- database user to connect as (default: see postgres docs) + password -- password for database user (default: see postgres docs) + host -- postgres hostname (default: see postgres docs) + port -- postgres port (default: see postgres docs) + initial_size -- integer size of connection pool (default: 1) + max_size -- integer max of connection pool (default: 10) + persist_connection -- keep connection open (default: True) + + Optional table-level keyword arguments: + extent -- manually specified data extent (comma delimited string, default: None) + estimate_extent -- boolean, direct PostGIS to use the faster, less accurate `estimate_extent` over `extent` (default: False) + extent_from_subquery -- boolean, direct Mapnik to query Postgis for the extent of the raw 'table' value (default: uses 'geometry_table') + raster_table -- specify geometry table to use to look up metadata (default: automatically parsed from 'table' value) + raster_field -- specify geometry field to use (default: first entry in raster_columns) + srid -- specify srid to use (default: auto-detected from geometry_field) + row_limit -- integer limit of rows to return (default: 0) + cursor_size -- integer size of binary cursor to use (default: 0, no binary cursor is used) + use_overviews -- boolean, use overviews when available (default: false) + prescale_rasters -- boolean, scale rasters on the db side (default: false) + clip_rasters -- boolean, clip rasters on the db side (default: false) + band -- integer, if non-zero interprets the given band (1-based offset) as a data raster (default: 0) + + >>> from mapnik import PgRaster, Layer + >>> params = dict(dbname='mapnik',table='osm',user='postgres',password='gis') + >>> params['estimate_extent'] = False + >>> params['extent'] = '-20037508,-19929239,20037508,19929239' + >>> pgraster = PgRaster(**params) + >>> lyr = Layer('PgRaster Layer') + >>> lyr.datasource = pgraster + + """ + return CreateDatasource(type = 'pgraster', **keywords) + + +def Raster(**keywords): + """Create a Raster (Tiff) Datasource. + + Required keyword arguments: + file -- path to stripped or tiled tiff + lox -- lowest (min) x/longitude of tiff extent + loy -- lowest (min) y/latitude of tiff extent + hix -- highest (max) x/longitude of tiff extent + hiy -- highest (max) y/latitude of tiff extent + + Hint: lox,loy,hix,hiy make a Mapnik Box2d + + Optional keyword arguments: + base -- path prefix (default None) + multi -- whether the image is in tiles on disk (default False) + + Multi-tiled keyword arguments: + x_width -- virtual image number of tiles in X direction (required) + y_width -- virtual image number of tiles in Y direction (required) + tile_size -- if an image is in tiles, how large are the tiles (default 256) + tile_stride -- if an image is in tiles, what's the increment between rows/cols (default 1) + + >>> from mapnik import Raster, Layer + >>> raster = Raster(base='/home/mapnik/data',file='elevation.tif',lox=-122.8,loy=48.5,hix=-122.7,hiy=48.6) + >>> lyr = Layer('Tiff Layer') + >>> lyr.datasource = raster + + """ + return CreateDatasource(type='raster', **keywords) + + +def Gdal(**keywords): + """Create a GDAL Raster Datasource. + + Required keyword arguments: + file -- path to GDAL supported dataset + + Optional keyword arguments: + base -- path prefix (default None) + shared -- boolean, open GdalDataset in shared mode (default: False) + bbox -- tuple (minx, miny, maxx, maxy). If specified, overrides the bbox detected by GDAL. + + >>> from mapnik import Gdal, Layer + >>> dataset = Gdal(base='/home/mapnik/data',file='elevation.tif') + >>> lyr = Layer('GDAL Layer from TIFF file') + >>> lyr.datasource = dataset + + """ + keywords['type'] = 'gdal' + if 'bbox' in keywords: + if isinstance(keywords['bbox'], (tuple, list)): + keywords['bbox'] = ','.join([str(item) + for item in keywords['bbox']]) + return CreateDatasource(**keywords) + + +def Occi(**keywords): + """Create a Oracle Spatial (10g) Vector Datasource. + + Required keyword arguments: + user -- database user to connect as + password -- password for database user + host -- oracle host to connect to (does not refer to SID in tsnames.ora) + table -- table name or subselect query + + Optional keyword arguments: + initial_size -- integer size of connection pool (default 1) + max_size -- integer max of connection pool (default 10) + extent -- manually specified data extent (comma delimited string, default None) + estimate_extent -- boolean, direct Oracle to use the faster, less accurate estimate_extent() over extent() (default False) + encoding -- file encoding (default 'utf-8') + geometry_field -- specify geometry field (default 'GEOLOC') + use_spatial_index -- boolean, force the use of the spatial index (default True) + + >>> from mapnik import Occi, Layer + >>> params = dict(host='myoracle',user='scott',password='tiger',table='test') + >>> params['estimate_extent'] = False + >>> params['extent'] = '-20037508,-19929239,20037508,19929239' + >>> oracle = Occi(**params) + >>> lyr = Layer('Oracle Spatial Layer') + >>> lyr.datasource = oracle + """ + keywords['type'] = 'occi' + return CreateDatasource(**keywords) + + +def Ogr(**keywords): + """Create a OGR Vector Datasource. + + Required keyword arguments: + file -- path to OGR supported dataset + layer -- name of layer to use within datasource (optional if layer_by_index or layer_by_sql is used) + + Optional keyword arguments: + layer_by_index -- choose layer by index number instead of by layer name or sql. + layer_by_sql -- choose layer by sql query number instead of by layer name or index. + base -- path prefix (default None) + encoding -- file encoding (default 'utf-8') + + >>> from mapnik import Ogr, Layer + >>> datasource = Ogr(base='/home/mapnik/data',file='rivers.geojson',layer='OGRGeoJSON') + >>> lyr = Layer('OGR Layer from GeoJSON file') + >>> lyr.datasource = datasource + + """ + keywords['type'] = 'ogr' + return CreateDatasource(**keywords) + + +def SQLite(**keywords): + """Create a SQLite Datasource. + + Required keyword arguments: + file -- path to SQLite database file + table -- table name or subselect query + + Optional keyword arguments: + base -- path prefix (default None) + encoding -- file encoding (default 'utf-8') + extent -- manually specified data extent (comma delimited string, default None) + metadata -- name of auxiliary table containing record for table with xmin, ymin, xmax, ymax, and f_table_name + geometry_field -- name of geometry field (default 'the_geom') + key_field -- name of primary key field (default 'OGC_FID') + row_offset -- specify a custom integer row offset (default 0) + row_limit -- specify a custom integer row limit (default 0) + wkb_format -- specify a wkb type of 'spatialite' (default None) + use_spatial_index -- boolean, instruct sqlite plugin to use Rtree spatial index (default True) + + >>> from mapnik import SQLite, Layer + >>> sqlite = SQLite(base='/home/mapnik/data',file='osm.db',table='osm',extent='-20037508,-19929239,20037508,19929239') + >>> lyr = Layer('SQLite Layer') + >>> lyr.datasource = sqlite + + """ + keywords['type'] = 'sqlite' + return CreateDatasource(**keywords) + + +def Rasterlite(**keywords): + """Create a Rasterlite Datasource. + + Required keyword arguments: + file -- path to Rasterlite database file + table -- table name or subselect query + + Optional keyword arguments: + base -- path prefix (default None) + extent -- manually specified data extent (comma delimited string, default None) + + >>> from mapnik import Rasterlite, Layer + >>> rasterlite = Rasterlite(base='/home/mapnik/data',file='osm.db',table='osm',extent='-20037508,-19929239,20037508,19929239') + >>> lyr = Layer('Rasterlite Layer') + >>> lyr.datasource = rasterlite + + """ + keywords['type'] = 'rasterlite' + return CreateDatasource(**keywords) + +def register_plugins(path=None): + """Register plugins located by specified path""" + if not path: + if 'MAPNIK_INPUT_PLUGINS_DIRECTORY' in os.environ: + path = os.environ.get('MAPNIK_INPUT_PLUGINS_DIRECTORY') + else: + from .paths import inputpluginspath + path = inputpluginspath + DatasourceCache.register_datasources(path, False) + + +def register_fonts(path=None, valid_extensions=[ + '.ttf', '.otf', '.ttc', '.pfa', '.pfb', '.ttc', '.dfont', '.woff']): + """Recursively register fonts using path argument as base directory""" + if not path: + if 'MAPNIK_FONT_DIRECTORY' in os.environ: + path = os.environ.get('MAPNIK_FONT_DIRECTORY') + else: + from .paths import fontscollectionpath + path = fontscollectionpath + for dirpath, _, filenames in os.walk(path): + for filename in filenames: + if os.path.splitext(filename.lower())[1] in valid_extensions: + FontEngine.register_font(os.path.join(dirpath, filename)) + +# # auto-register known plugins and fonts +register_plugins() +register_fonts() diff --git a/mapnik/mapnik_settings.py b/packaging/mapnik/mapnik_settings.py similarity index 100% rename from mapnik/mapnik_settings.py rename to packaging/mapnik/mapnik_settings.py diff --git a/mapnik/printing/__init__.py b/packaging/mapnik/printing/__init__.py similarity index 96% rename from mapnik/printing/__init__.py rename to packaging/mapnik/printing/__init__.py index 023f1380d..bebbc2de5 100644 --- a/mapnik/printing/__init__.py +++ b/packaging/mapnik/printing/__init__.py @@ -2,12 +2,9 @@ """Mapnik classes to assist in creating printable maps.""" -from __future__ import absolute_import, print_function - import logging import math - -from mapnik import Box2d, Coord, Geometry, Layer, Map, Projection, Style, render +from mapnik import Box2d, Coord, Geometry, Layer, Map, Projection, ProjTransform, Style, render from mapnik.printing.conversions import m2pt, m2px from mapnik.printing.formats import pagesizes from mapnik.printing.scales import any_scale, default_scale, deg_min_sec_scale, sequence_scale @@ -25,12 +22,12 @@ HAS_PANGOCAIRO_MODULE = False try: - from PyPDF2 import PdfFileReader, PdfFileWriter - from PyPDF2.generic import (ArrayObject, DecodedStreamObject, DictionaryObject, FloatObject, NameObject, - NumberObject, TextStringObject) - HAS_PYPDF2 = True + from pypdf import PdfReader, PdfWriter + from pypdf.generic import (ArrayObject, DecodedStreamObject, DictionaryObject, FloatObject, NameObject, + NumberObject, TextStringObject) + HAS_PYPDF = True except ImportError: - HAS_PYPDF2 = False + HAS_PYPDF = False """ Style of centering to use with the map. @@ -90,7 +87,7 @@ def __init__(self, Args: pagesize: tuple of page size in meters, see predefined sizes in mapnik.formats module margin: page margin in meters - box: the box to render the map into. Must be within page area, margin excluded. + box: the box to render the map into. Must be within page area, margin excluded. This should be a Mapnik Box2d object. Default is the full page without margin. percent_box: similar to box argument but specified as a percent (0->1) of the full page size. If both box and percent_box are specified percent_box will be used. @@ -104,7 +101,7 @@ def __init__(self, be a value from the mapnik.utils.centering class. The default is to center on the maps constrained axis. Typically this will be horizontal for portrait pages and vertical for landscape pages. is_latlon: whether the map is in lat lon degrees or not. - use_ocg_layers: create OCG layers in the PDF, requires PyPDF2 + use_ocg_layers: create OCG layers in the PDF, requires pypdf font_name: the font name used each time text is written (e.g., legend titles, representative fraction, etc.) """ self._pagesize = pagesize @@ -563,7 +560,7 @@ def render_scale(self, m, ctx=None, width=0.05, num_divisions=3, bar_size=8.0, w Args: m: the Map object to render the scale for - ctx: A cairo context to render the scale into. If this is None, we create a context and find out + ctx: A cairo context to render the scale into. If this is None, we create a context and find out the best location for the scale bar width: the width of area available for rendering the scale bar (in meters) num_divisions: the number of divisions for the scale bar @@ -737,7 +734,7 @@ def render_graticule_on_map(self, m, dec_degrees=True, grid_layer_name="Graticul # renders the vertical graticule axes self._render_graticule_axes_and_text( - m, + m, p2, latlon_bounds, latlon_buffer, @@ -1119,7 +1116,7 @@ def convert_pdf_pages_to_layers(self, filename, layer_names=None, reverse_all_bu Takes a multi pages PDF as input and converts each page to a layer in a single page PDF. Note: - requires PyPDF2 to be available + requires pypdf to be available Args: layer_names should be a sequence of the user visible names of the layers, if not given @@ -1128,17 +1125,17 @@ def convert_pdf_pages_to_layers(self, filename, layer_names=None, reverse_all_bu if output_name is not provided a temporary file will be used for the conversion which will then be copied back over the source file. """ - if not HAS_PYPDF2: - raise RuntimeError("PyPDF2 not available; PyPDF2 required to convert pdf pages to layers") + if not HAS_PYPDF: + raise RuntimeError("pypdf not available; pypdf required to convert pdf pages to layers") with open(filename, "rb+") as f: - file_reader = PdfFileReader(f) - file_writer = PdfFileWriter() + file_reader = PdfReader(f) + file_writer = PdfWriter() - template_page_size = file_reader.pages[0].mediaBox - output_pdf = file_writer.addBlankPage( - width=template_page_size.getWidth(), - height=template_page_size.getHeight()) + template_page_size = file_reader.pages[0].mediabox + output_pdf = file_writer.add_blank_page( + width=template_page_size.width, + height=template_page_size.height) content_key = NameObject('/Contents') output_pdf[content_key] = ArrayObject() @@ -1149,15 +1146,15 @@ def convert_pdf_pages_to_layers(self, filename, layer_names=None, reverse_all_bu (properties, ocgs) = self._make_ocg_layers(file_reader, file_writer, output_pdf, layer_names) properties_key = NameObject('/Properties') - output_pdf[resource_key][properties_key] = file_writer._addObject(properties) + output_pdf[resource_key][properties_key] = file_writer._add_object(properties) ocproperties = DictionaryObject() ocproperties[NameObject('/OCGs')] = ocgs default_view = self._get_pdf_default_view(ocgs, reverse_all_but_last) - ocproperties[NameObject('/D')] = file_writer._addObject(default_view) + ocproperties[NameObject('/D')] = file_writer._add_object(default_view) - file_writer._root_object[NameObject('/OCProperties')] = file_writer._addObject(ocproperties) + file_writer._root_object[NameObject('/OCProperties')] = file_writer._add_object(ocproperties) f.seek(0) file_writer.write(f) @@ -1189,7 +1186,7 @@ def _make_ocg_layers(self, file_reader, file_writer, output_pdf, layer_names=Non page[NameObject( '/Contents')] = ArrayObject((ocgs_start, page['/Contents'], ocg_end)) - output_pdf.mergePage(page) + output_pdf.merge_page(page) ocg = DictionaryObject() ocg[NameObject('/Type')] = NameObject('/OCG') @@ -1199,7 +1196,7 @@ def _make_ocg_layers(self, file_reader, file_writer, output_pdf, layer_names=Non else: ocg[NameObject('/Name')] = TextStringObject('Layer %d' % (idx + 1)) - indirect_ocg = file_writer._addObject(ocg) + indirect_ocg = file_writer._add_object(ocg) properties[ocg_name] = indirect_ocg ocgs.append(indirect_ocg) @@ -1238,20 +1235,20 @@ def add_geospatial_pdf_header(self, m, filename, epsg=None, wkt=None): The epsg code or the wkt text of the projection must be provided. Must be called *after* the page has had .finish() called. """ - if not HAS_PYPDF2: - raise RuntimeError("PyPDF2 not available; PyPDF2 required to add geospatial header to PDF") + if not HAS_PYPDF: + raise RuntimeError("pypdf not available; pypdf required to add geospatial header to PDF") if not any((epsg,wkt)): raise RuntimeError("EPSG or WKT required to add geospatial header to PDF") with open(filename, "rb+") as f: - file_reader = PdfFileReader(f) - file_writer = PdfFileWriter() + file_reader = PdfReader(f) + file_writer = PdfWriter() # preserve OCProperties at document root if we have one - if file_reader.trailer['/Root'].has_key(NameObject('/OCProperties')): + if NameObject('/OCProperties') in file_reader.trailer['/Root']: file_writer._root_object[NameObject('/OCProperties')] = file_reader.trailer[ - '/Root'].getObject()[NameObject('/OCProperties')] + '/Root'].get_object()[NameObject('/OCProperties')] for page in file_reader.pages: gcs = DictionaryObject() @@ -1265,7 +1262,7 @@ def add_geospatial_pdf_header(self, m, filename, epsg=None, wkt=None): measure = self._get_pdf_measure(m, gcs) page[NameObject('/VP')] = self._get_pdf_vp(measure) - file_writer.addPage(page) + file_writer.add_page(page) f.seek(0) file_writer.write(f) @@ -1318,11 +1315,11 @@ def _get_pdf_gpts(self, m): """ gpts = ArrayObject() - proj = Projection(m.srs) + tr = ProjTransform(Projection(m.srs), Projection("epsg:4326")) env = m.envelope() - for x in ((env.minx, env.miny), (env.minx, env.maxy), + for p in ((env.minx, env.miny), (env.minx, env.maxy), (env.maxx, env.maxy), (env.maxx, env.miny)): - latlon_corner = proj.inverse(Coord(*x)) + latlon_corner = tr.forward(Coord(*p)) # these are in lat,lon order according to the specification gpts.append(FloatObject(str(latlon_corner.y))) gpts.append(FloatObject(str(latlon_corner.x))) diff --git a/mapnik/printing/conversions.py b/packaging/mapnik/printing/conversions.py similarity index 100% rename from mapnik/printing/conversions.py rename to packaging/mapnik/printing/conversions.py diff --git a/mapnik/printing/formats.py b/packaging/mapnik/printing/formats.py similarity index 100% rename from mapnik/printing/formats.py rename to packaging/mapnik/printing/formats.py diff --git a/mapnik/printing/scales.py b/packaging/mapnik/printing/scales.py similarity index 100% rename from mapnik/printing/scales.py rename to packaging/mapnik/printing/scales.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..bfa7d31d1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,39 @@ +[build-system] +requires = [ + "setuptools >= 80.9.0", + "pybind11 >= 3.0.1", +] +build-backend = "setuptools.build_meta" + +[project] +name = "mapnik" +version = "4.2.1.beta" +description = "Python bindings for Mapnik" +license = "LGPL-2.1-or-later" + +keywords = ["mapnik", "beautiful maps", "cartography", "python-mapnik"] + +classifiers = [ + "Development Status :: 4 - Beta", +] +authors = [ +{name= "Artem Pavlenko", email = "artem@mapnik.org"}, +] +maintainers = [ +{name= "Artem Pavlenko", email = "artem@mapnik.org"}, +] + +requires-python = ">= 3.9" + +[project.urls] +Homepage = "https://mapnik.org" +Documentation = "https://github.com/mapnik/python-mapnik/wiki" +Repository = "https://github.com/mapnik/python-mapnik" +"Bug Tracker" = "https://github.com/mapnik/python-mapnik/issues" +Changelog = "https://github.com/mapnik/python-mapnik/blob/master/CHANGELOG.md" + +[tool.pytest.ini_options] +minversion = "8.0" +testpaths = [ + "test/python_tests", +] \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index f4ca59d33..000000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[nosetests] -verbosity=1 diff --git a/setup.py b/setup.py index 9985da5a2..a72565629 100755 --- a/setup.py +++ b/setup.py @@ -1,252 +1,131 @@ -#! /usr/bin/env python +#! /usr/bin/env python3 -import os -import os.path -import re -import shutil -import subprocess +from pybind11.setup_helpers import Pybind11Extension, build_ext +from setuptools import setup, find_namespace_packages import sys -import glob -from distutils import sysconfig -from ctypes.util import find_library - -from setuptools import Command, Extension, setup - -PYTHON3 = sys.version_info.major == 3 +import subprocess +import os +mapnik_config = 'mapnik-config' -# Utils def check_output(args): - output = subprocess.check_output(args) - if PYTHON3: - # check_output returns bytes in PYTHON3. - output = output.decode() - return output.rstrip('\n') - - -def clean_boost_name(name): - name = name.split('.')[0] - if name.startswith('lib'): - name = name[3:] - return name - - -def find_boost_library(_id): - suffixes = [ - "", # standard naming - "-mt" # former naming schema for multithreading build - ] - if "python" in _id: - # Debian naming convention for versions installed in parallel - suffixes.insert(0, "-py%d%d" % (sys.version_info.major, - sys.version_info.minor)) - suffixes.insert(1, "%d%d" % (sys.version_info.major, - sys.version_info.minor)) - # standard suffix for Python3 - suffixes.insert(2, sys.version_info.major) - for suf in suffixes: - name = "%s%s" % (_id, suf) - lib = find_library(name) - if lib is not None: - return name - - -def get_boost_library_names(): - wanted = ['boost_python', 'boost_thread', 'boost_system'] - found = [] - missing = [] - for _id in wanted: - name = os.environ.get("%s_LIB" % _id.upper(), find_boost_library(_id)) - if name: - found.append(name) - else: - missing.append(_id) - if missing: - msg = "" - for name in missing: - msg += ("\nMissing {} boost library, try to add its name with " - "{}_LIB environment var.").format(name, name.upper()) - raise EnvironmentError(msg) - return found - - -class WhichBoostCommand(Command): - description = 'Output found boost names. Useful for debug.' - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - print("\n".join(get_boost_library_names())) - - -cflags = sysconfig.get_config_var('CFLAGS') -sysconfig._config_vars['CFLAGS'] = re.sub( - ' +', ' ', cflags.replace('-g ', '').replace('-Os', '').replace('-arch i386', '')) -opt = sysconfig.get_config_var('OPT') -sysconfig._config_vars['OPT'] = re.sub( - ' +', ' ', opt.replace('-g ', '').replace('-Os', '')) -ldshared = sysconfig.get_config_var('LDSHARED') -sysconfig._config_vars['LDSHARED'] = re.sub( - ' +', ' ', ldshared.replace('-g ', '').replace('-Os', '').replace('-arch i386', '')) -ldflags = sysconfig.get_config_var('LDFLAGS') -sysconfig._config_vars['LDFLAGS'] = re.sub( - ' +', ' ', ldflags.replace('-g ', '').replace('-Os', '').replace('-arch i386', '')) -pycflags = sysconfig.get_config_var('PY_CFLAGS') -sysconfig._config_vars['PY_CFLAGS'] = re.sub( - ' +', ' ', pycflags.replace('-g ', '').replace('-Os', '').replace('-arch i386', '')) -sysconfig._config_vars['CFLAGSFORSHARED'] = '' -os.environ['ARCHFLAGS'] = '' - -if os.environ.get("MASON_BUILD", "false") == "true": - # run bootstrap.sh to get mason builds - subprocess.call(['./bootstrap.sh']) - mapnik_config = 'mason_packages/.link/bin/mapnik-config' - mason_build = True -else: - mapnik_config = 'mapnik-config' - mason_build = False - + output = subprocess.check_output(args).decode() + return output.rstrip('\n') linkflags = [] +bin_path = os.path.join(check_output([mapnik_config, '--prefix']),'bin') lib_path = os.path.join(check_output([mapnik_config, '--prefix']),'lib') linkflags.extend(check_output([mapnik_config, '--libs']).split(' ')) linkflags.extend(check_output([mapnik_config, '--ldflags']).split(' ')) linkflags.extend(check_output([mapnik_config, '--dep-libs']).split(' ')) linkflags.extend([ -'-lmapnik-wkt', -'-lmapnik-json', -] + ['-l%s' % i for i in get_boost_library_names()]) - + '-lmapnik-wkt', + '-lmapnik-json', +]) + +# Remove symlinks +if os.path.islink('packaging/mapnik/bin') : + os.unlink('packaging/mapnik/bin') +if os.path.islink('packaging/mapnik/lib') : + os.unlink('packaging/mapnik/lib') # Dynamically make the mapnik/paths.py file -f_paths = open('mapnik/paths.py', 'w') +f_paths = open('packaging/mapnik/paths.py', 'w') f_paths.write('import os\n') f_paths.write('\n') -input_plugin_path = check_output([mapnik_config, '--input-plugins']) -font_path = check_output([mapnik_config, '--fonts']) - -if mason_build: - try: - if sys.platform == 'darwin': - base_f = 'libmapnik.dylib' - else: - base_f = 'libmapnik.so' - f = os.path.join(lib_path, base_f) - if not os.path.exists(os.path.join('mapnik', 'lib')): - os.makedirs(os.path.join('mapnik', 'lib')) - shutil.copyfile(f, os.path.join('mapnik', 'lib', base_f)) - except shutil.Error: - pass - input_plugin_files = os.listdir(input_plugin_path) - input_plugin_files = [os.path.join( - input_plugin_path, f) for f in input_plugin_files] - if not os.path.exists(os.path.join('mapnik', 'lib', 'mapnik', 'input')): - os.makedirs(os.path.join('mapnik', 'lib', 'mapnik', 'input')) - for f in input_plugin_files: - try: - shutil.copyfile(f, os.path.join( - 'mapnik', 'lib', 'mapnik', 'input', os.path.basename(f))) - except shutil.Error: - pass - font_files = os.listdir(font_path) - font_files = [os.path.join(font_path, f) for f in font_files] - if not os.path.exists(os.path.join('mapnik', 'lib', 'mapnik', 'fonts')): - os.makedirs(os.path.join('mapnik', 'lib', 'mapnik', 'fonts')) - for f in font_files: - try: - shutil.copyfile(f, os.path.join( - 'mapnik', 'lib', 'mapnik', 'fonts', os.path.basename(f))) - except shutil.Error: - pass - f_paths.write( - 'mapniklibpath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "lib")\n') - f_paths.write("inputpluginspath = os.path.join(mapniklibpath, 'mapnik', 'input')\n") - f_paths.write("fontscollectionpath = os.path.join(mapniklibpath, 'mapnik', 'fonts')\n") - f_paths.write( - "__all__ = [mapniklibpath,inputpluginspath,fontscollectionpath]\n") - f_paths.close() +if os.environ.get('SYSTEM_MAPNIK'): + input_plugin_path = check_output([mapnik_config, '--input-plugins']) + font_path = check_output([mapnik_config, '--fonts']) + f_paths.write("mapniklibpath = '{path}'\n".format(path=lib_path)) + f_paths.write("inputpluginspath = '{path}'\n".format(path=input_plugin_path)) + f_paths.write("fontscollectionpath = '{path}'\n".format(path=font_path)) else: - if os.environ.get('LIB_DIR_NAME'): - mapnik_lib_path = lib_path + os.environ.get('LIB_DIR_NAME') - else: - mapnik_lib_path = lib_path + "/mapnik" - f_paths.write("mapniklibpath = '{path}'\n".format(path=mapnik_lib_path)) - f_paths.write('mapniklibpath = os.path.normpath(mapniklibpath)\n') - f_paths.write( - "inputpluginspath = '{path}'\n".format(path=input_plugin_path)) - f_paths.write( - "fontscollectionpath = '{path}'\n".format(path=font_path)) - f_paths.write( - "__all__ = [mapniklibpath,inputpluginspath,fontscollectionpath]\n") - f_paths.close() - - -if mason_build: - - share_dir = 'share' - - for dep in ['icu','gdal','proj']: - share_path = os.path.join('mapnik', share_dir, dep) - if not os.path.exists(share_path): - os.makedirs(share_path) - - icu_path = 'mason_packages/.link/share/icu/*/*.dat' - icu_files = glob.glob(icu_path) - if len(icu_files) != 1: - raise Exception("Failed to find icu dat file at "+ icu_path) - for f in icu_files: - shutil.copyfile(f, os.path.join( - 'mapnik', share_dir, 'icu', os.path.basename(f))) - - gdal_path = 'mason_packages/.link/share/gdal/' - gdal_files = os.listdir(gdal_path) - gdal_files = [os.path.join(gdal_path, f) for f in gdal_files] - for f in gdal_files: - try: - shutil.copyfile(f, os.path.join( - 'mapnik', share_dir, 'gdal', os.path.basename(f))) - except shutil.Error: - pass - - proj_path = 'mason_packages/.link/share/proj/' - proj_files = os.listdir(proj_path) - proj_files = [os.path.join(proj_path, f) for f in proj_files] - for f in proj_files: - try: - shutil.copyfile(f, os.path.join( - 'mapnik', share_dir, 'proj', os.path.basename(f))) - except shutil.Error: - pass + if not os.path.exists('packaging/mapnik/bin'): + os.symlink(bin_path, 'packaging/mapnik/bin') + if not os.path.exists('packaging/mapnik/lib') : + os.symlink(lib_path, 'packaging/mapnik/lib') + else: + names = (name for name in os.listdir(lib_path) if os.path.isfile(os.path.join(lib_path, name))) + for name in names: + if not os.path.exists(os.path.join('packaging/mapnik/lib', name)): + os.symlink(os.path.join(lib_path, name), os.path.join('packaging/mapnik/lib', name)) + input_plugin_path = check_output([mapnik_config, '--input-plugins']) + if not os.path.exists('packaging/mapnik/lib/mapnik/input'): + os.symlink(input_plugin_path, 'packaging/mapnik/lib/mapnik/input') + f_paths.write("mapniklibpath = os.path.join(os.path.dirname(__file__), 'lib')\n") + f_paths.write("inputpluginspath = os.path.join(os.path.dirname(__file__), 'lib/mapnik/input')\n") + f_paths.write("fontscollectionpath = os.path.join(os.path.dirname(__file__), 'lib/mapnik/fonts')\n") + +f_paths.write("__all__ = [mapniklibpath,inputpluginspath,fontscollectionpath]\n") +f_paths.close() extra_comp_args = check_output([mapnik_config, '--cflags']).split(' ') - extra_comp_args = list(filter(lambda arg: arg != "-fvisibility=hidden", extra_comp_args)) -if os.environ.get("PYCAIRO", "false") == "true": - try: - extra_comp_args.append('-DHAVE_PYCAIRO') - print("-I%s/include/pycairo".format(sys.exec_prefix)) - extra_comp_args.append("-I{0}/include/pycairo".format(sys.exec_prefix)) - #extra_comp_args.extend(check_output(["pkg-config", '--cflags', 'pycairo']).strip().split(' ')) - #linkflags.extend(check_output(["pkg-config", '--libs', 'pycairo']).strip().split(' ')) - except: - raise Exception("Failed to find compiler options for pycairo") - if sys.platform == 'darwin': - extra_comp_args.append('-mmacosx-version-min=10.11') - # silence warning coming from boost python macros which - # would is hard to silence via pragma - extra_comp_args.append('-Wno-parentheses-equality') - linkflags.append('-mmacosx-version-min=10.11') + pass else: - linkflags.append('-lrt') - linkflags.append('-Wl,-z,origin') - linkflags.append('-Wl,-rpath=$ORIGIN/lib') + linkflags.append('-lrt') + linkflags.append('-Wl,-z,origin') + linkflags.append('-Wl,-rpath=$ORIGIN/lib') + + +ext_modules = [ + Pybind11Extension( + "mapnik._mapnik", + [ + "src/mapnik_python.cpp", + "src/mapnik_layer.cpp", + "src/mapnik_query.cpp", + "src/mapnik_map.cpp", + "src/mapnik_color.cpp", + "src/mapnik_composite_modes.cpp", + "src/mapnik_coord.cpp", + "src/mapnik_envelope.cpp", + "src/mapnik_expression.cpp", + "src/mapnik_datasource.cpp", + "src/mapnik_datasource_cache.cpp", + "src/mapnik_gamma_method.cpp", + "src/mapnik_geometry.cpp", + "src/mapnik_feature.cpp", + "src/mapnik_featureset.cpp", + "src/mapnik_font_engine.cpp", + "src/mapnik_fontset.cpp", + "src/mapnik_grid.cpp", + "src/mapnik_grid_view.cpp", + "src/mapnik_image.cpp", + "src/mapnik_image_view.cpp", + "src/mapnik_projection.cpp", + "src/mapnik_proj_transform.cpp", + "src/mapnik_rule.cpp", + "src/mapnik_symbolizer.cpp", + "src/mapnik_debug_symbolizer.cpp", + "src/mapnik_markers_symbolizer.cpp", + "src/mapnik_polygon_symbolizer.cpp", + "src/mapnik_polygon_pattern_symbolizer.cpp", + "src/mapnik_line_symbolizer.cpp", + "src/mapnik_line_pattern_symbolizer.cpp", + "src/mapnik_point_symbolizer.cpp", + "src/mapnik_raster_symbolizer.cpp", + "src/mapnik_scaling_method.cpp", + "src/mapnik_style.cpp", + "src/mapnik_logger.cpp", + "src/mapnik_placement_finder.cpp", + "src/mapnik_text_symbolizer.cpp", + "src/mapnik_palette.cpp", + "src/mapnik_parameters.cpp", + "src/python_grid_utils.cpp", + "src/mapnik_raster_colorizer.cpp", + "src/mapnik_label_collision_detector.cpp", + "src/mapnik_dot_symbolizer.cpp", + "src/mapnik_building_symbolizer.cpp", + "src/mapnik_shield_symbolizer.cpp", + "src/mapnik_group_symbolizer.cpp" + ], + extra_compile_args=extra_comp_args, + extra_link_args=linkflags, + ) +] if os.environ.get("CC", False) == False: os.environ["CC"] = check_output([mapnik_config, '--cxx']) @@ -254,64 +133,21 @@ def run(self): os.environ["CXX"] = check_output([mapnik_config, '--cxx']) setup( - name="mapnik", - version="4.0.0", - packages=['mapnik','mapnik.printing'], - author="Blake Thompson", - author_email="flippmoke@gmail.com", - description="Python bindings for Mapnik", - license="GNU LESSER GENERAL PUBLIC LICENSE", - keywords="mapnik mapbox mapping cartography", - url="http://mapnik.org/", - tests_require=[ - 'nose', - ], - package_data={ - 'mapnik': ['lib/*.*', 'lib/*/*/*', 'share/*/*'], - }, - test_suite='nose.collector', - cmdclass={ - 'whichboost': WhichBoostCommand, - }, - ext_modules=[ - Extension('mapnik._mapnik', [ - 'src/mapnik_color.cpp', - 'src/mapnik_coord.cpp', - 'src/mapnik_datasource.cpp', - 'src/mapnik_datasource_cache.cpp', - 'src/mapnik_envelope.cpp', - 'src/mapnik_expression.cpp', - 'src/mapnik_feature.cpp', - 'src/mapnik_featureset.cpp', - 'src/mapnik_font_engine.cpp', - 'src/mapnik_fontset.cpp', - 'src/mapnik_gamma_method.cpp', - 'src/mapnik_geometry.cpp', - 'src/mapnik_grid.cpp', - 'src/mapnik_grid_view.cpp', - 'src/mapnik_image.cpp', - 'src/mapnik_image_view.cpp', - 'src/mapnik_label_collision_detector.cpp', - 'src/mapnik_layer.cpp', - 'src/mapnik_logger.cpp', - 'src/mapnik_map.cpp', - 'src/mapnik_palette.cpp', - 'src/mapnik_parameters.cpp', - 'src/mapnik_proj_transform.cpp', - 'src/mapnik_projection.cpp', - 'src/mapnik_python.cpp', - 'src/mapnik_query.cpp', - 'src/mapnik_raster_colorizer.cpp', - 'src/mapnik_rule.cpp', - 'src/mapnik_scaling_method.cpp', - 'src/mapnik_style.cpp', - 'src/mapnik_symbolizer.cpp', - 'src/mapnik_view_transform.cpp', - 'src/python_grid_utils.cpp', - ], - language='c++', - extra_compile_args=extra_comp_args, - extra_link_args=linkflags, - ) - ] + name="mapnik", + include_package_data=True, + packages=find_namespace_packages(where="packaging"), + package_dir={"": "packaging"}, + package_data={ + "mapnik.include": ["*.hpp"], + "mapnik.bin": ["*"], + "mapnik.lib": ["libmapnik*"], + "mapnik.lib.mapnik.fonts":["*"], + "mapnik.lib.mapnik.input":["*.input"] + }, + exclude_package_data={ + "mapnik.bin": ["mapnik-config"], + "mapnik.lib": ["*.a"] + }, + ext_modules=ext_modules, + cmdclass={"build_ext": build_ext}, ) diff --git a/src/create_datasource.hpp b/src/create_datasource.hpp new file mode 100644 index 000000000..b6b5bfc0d --- /dev/null +++ b/src/create_datasource.hpp @@ -0,0 +1,68 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 MAPNIK_CREATE_DATASOURCE_HPP +#define MAPNIK_CREATE_DATASOURCE_HPP + +// mapnik +#include +#include +//pybind11 +#include +#include +#include + +namespace py = pybind11; + +inline std::shared_ptr create_datasource(py::kwargs const& kwargs) +{ + mapnik::parameters params; + for (auto param : kwargs) + { + std::string key = std::string(py::str(param.first)); + py::handle handle = param.second; + if (py::isinstance(handle)) + { + params[key] = handle.cast(); + } + else if (py::isinstance(handle)) + { + params[key] = handle.cast(); + } + else if (py::isinstance(handle)) + { + params[key] = handle.cast(); + } + else if (py::isinstance(handle)) + { + params[key] = handle.cast(); + } + else + { + params[key] = py::str(handle).cast(); + } + } + return mapnik::datasource_cache::instance().create(params); +} + + +#endif //MAPNIK_CREATE_DATASOURCE_HPP diff --git a/src/mapnik_building_symbolizer.cpp b/src/mapnik_building_symbolizer.cpp new file mode 100644 index 000000000..45c91c42a --- /dev/null +++ b/src/mapnik_building_symbolizer.cpp @@ -0,0 +1,59 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 + * + *****************************************************************************/ + +// mapnik +#include +#include +#include +#include +#include +#include "mapnik_symbolizer.hpp" +//pybind11 +#include + +namespace py = pybind11; + +void export_building_symbolizer(py::module const& m) +{ + using namespace python_mapnik; + using mapnik::building_symbolizer; + + py::class_(m, "BuildingSymbolizer") + .def(py::init<>(), "Default ctor") + .def("__hash__", hash_impl_2) + .def_property("fill", + &get_property, + &set_color_property, + "Fill - mapnik.Color, CSS color string or a valid mapnik.Expression") + + .def_property("fill_opacity", + &get_property, + &set_double_property, + "Fill opacity - [0-1] or a valid mapnik.Expression") + + .def_property("height", + &get_property, + &set_double_property, + "Height - a numeric value or a valid mapnik.Expression") + ; + +} diff --git a/src/mapnik_color.cpp b/src/mapnik_color.cpp index 3799f7734..bf744be36 100644 --- a/src/mapnik_color.cpp +++ b/src/mapnik_color.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,96 +20,78 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#pragma GCC diagnostic pop - //mapnik +#include #include +//pybind11 +#include +#include - +namespace py = pybind11; using mapnik::color; -struct color_pickle_suite : boost::python::pickle_suite +void export_color (py::module const& m) { - static boost::python::tuple - getinitargs(const color& c) - { - using namespace boost::python; - return boost::python::make_tuple(c.red(),c.green(),c.blue(),c.alpha()); - } -}; + py::class_(m, "Color") + .def(py::init(), + "Creates a new color from its RGB components\n" + "and an alpha value.\n" + "All values between 0 and 255.\n", + py::arg("r"), py::arg("g"), py::arg("b"), py::arg("a")) + .def(py::init(), + "Creates a new color from its RGB components\n" + "and an alpha value.\n" + "All values between 0 and 255.\n", + py::arg("r"), py::arg("g"), py::arg("b"), py::arg("a"), py::arg("premultiplied")) + .def(py::init(), + "Creates a new color from its RGB components.\n" + "All values between 0 and 255.\n", + py::arg("r"), py::arg("g"), py::arg("b")) + .def(py::init(), + "Creates a new color from an unsigned integer.\n" + "All values between 0 and 2^32-1\n", + py::arg("val")) + .def(py::init(), + "Creates a new color from an unsigned integer.\n" + "All values between 0 and 2^32-1\n", + py::arg("val"), py::arg("premultiplied")) -void export_color () -{ - using namespace boost::python; - class_("Color", init( - ( arg("r"), arg("g"), arg("b"), arg("a") ), - "Creates a new color from its RGB components\n" - "and an alpha value.\n" - "All values between 0 and 255.\n") - ) - .def(init( - ( arg("r"), arg("g"), arg("b"), arg("a"), arg("premultiplied") ), - "Creates a new color from its RGB components\n" - "and an alpha value.\n" - "All values between 0 and 255.\n") - ) - .def(init( - ( arg("r"), arg("g"), arg("b") ), - "Creates a new color from its RGB components.\n" - "All values between 0 and 255.\n") - ) - .def(init( - ( arg("val") ), - "Creates a new color from an unsigned integer.\n" - "All values between 0 and 2^32-1\n") - ) - .def(init( - ( arg("val"), arg("premultiplied") ), - "Creates a new color from an unsigned integer.\n" - "All values between 0 and 2^32-1\n") - ) - .def(init( - ( arg("color_string") ), - "Creates a new color from its CSS string representation.\n" - "The string may be a CSS color name (e.g. 'blue')\n" - "or a hex color string (e.g. '#0000ff').\n") - ) - .def(init( - ( arg("color_string"), arg("premultiplied") ), - "Creates a new color from its CSS string representation.\n" - "The string may be a CSS color name (e.g. 'blue')\n" - "or a hex color string (e.g. '#0000ff').\n") - ) - .add_property("r", + .def(py::init(), + "Creates a new color from its CSS string representation.\n" + "The string may be a CSS color name (e.g. 'blue')\n" + "or a hex color string (e.g. '#0000ff').\n", + py::arg("color_string")) + + .def(py::init(), + "Creates a new color from its CSS string representation.\n" + "The string may be a CSS color name (e.g. 'blue')\n" + "or a hex color string (e.g. '#0000ff').\n", + py::arg("color_string"), py::arg("premultiplied")) + + .def_property("r", &color::red, &color::set_red, "Gets or sets the red component.\n" "The value is between 0 and 255.\n") - .add_property("g", + .def_property("g", &color::green, &color::set_green, "Gets or sets the green component.\n" "The value is between 0 and 255.\n") - .add_property("b", + .def_property("b", &color::blue, &color::set_blue, "Gets or sets the blue component.\n" "The value is between 0 and 255.\n") - .add_property("a", + .def_property("a", &color::alpha, &color::set_alpha, "Gets or sets the alpha component.\n" "The value is between 0 and 255.\n") - .def(self == self) - .def(self != self) - .def_pickle(color_pickle_suite()) + .def(py::self == py::self) + .def(py::self != py::self) .def("__str__",&color::to_string) + .def("__repr__",&color::to_string) .def("set_premultiplied",&color::set_premultiplied) .def("get_premultiplied",&color::get_premultiplied) .def("premultiply",&color::premultiply) @@ -122,5 +104,18 @@ void export_color () ">>> c = Color('blue')\n" ">>> c.to_hex_string()\n" "'#0000ff'\n") + .def(py::pickle( + [](color & c) { + return py::make_tuple(c.red(), c.green(), c.blue(), c.alpha()); + }, + [](py::tuple t) { + if (t.size() != 4) + throw std::runtime_error("Invalid state"); + color c{t[0].cast(), + t[1].cast(), + t[2].cast(), + t[3].cast()}; + return c; + })) ; } diff --git a/src/mapnik_composite_modes.cpp b/src/mapnik_composite_modes.cpp new file mode 100644 index 000000000..65494ddf1 --- /dev/null +++ b/src/mapnik_composite_modes.cpp @@ -0,0 +1,74 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 + * + *****************************************************************************/ + +// mapnik +#include +#include +//pybind11 +#include +#include + +namespace py = pybind11; + +void export_composite_modes(py::module const& m) +{ + // NOTE: must match list in include/mapnik/image_compositing.hpp + py::native_enum(m, "CompositeOp", "enum.Enum") + .value("clear", mapnik::clear) + .value("src", mapnik::src) + .value("dst", mapnik::dst) + .value("src_over", mapnik::src_over) + .value("dst_over", mapnik::dst_over) + .value("src_in", mapnik::src_in) + .value("dst_in", mapnik::dst_in) + .value("src_out", mapnik::src_out) + .value("dst_out", mapnik::dst_out) + .value("src_atop", mapnik::src_atop) + .value("dst_atop", mapnik::dst_atop) + .value("xor", mapnik::_xor) + .value("plus", mapnik::plus) + .value("minus", mapnik::minus) + .value("multiply", mapnik::multiply) + .value("screen", mapnik::screen) + .value("overlay", mapnik::overlay) + .value("darken", mapnik::darken) + .value("lighten", mapnik::lighten) + .value("color_dodge", mapnik::color_dodge) + .value("color_burn", mapnik::color_burn) + .value("hard_light", mapnik::hard_light) + .value("soft_light", mapnik::soft_light) + .value("difference", mapnik::difference) + .value("exclusion", mapnik::exclusion) + .value("contrast", mapnik::contrast) + .value("invert", mapnik::invert) + .value("grain_merge", mapnik::grain_merge) + .value("grain_extract", mapnik::grain_extract) + .value("hue", mapnik::hue) + .value("saturation", mapnik::saturation) + .value("color", mapnik::_color) + .value("value", mapnik::_value) + .value("linear_dodge", mapnik::linear_dodge) + .value("linear_burn", mapnik::linear_burn) + .value("divide", mapnik::divide) + .finalize() + ; +} diff --git a/src/mapnik_coord.cpp b/src/mapnik_coord.cpp index 633d31ccf..93249c34c 100644 --- a/src/mapnik_coord.cpp +++ b/src/mapnik_coord.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,50 +19,46 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#pragma GCC diagnostic pop // mapnik +#include #include +//pybind11 +#include +#include +namespace py = pybind11; using mapnik::coord; -struct coord_pickle_suite : boost::python::pickle_suite -{ - static boost::python::tuple - getinitargs(const coord& c) - { - using namespace boost::python; - return boost::python::make_tuple(c.x,c.y); - } -}; - -void export_coord() +void export_coord(py::module const& m) { - using namespace boost::python; - class_ >("Coord",init( - // class docstring is in mapnik/__init__.py, class _Coord - (arg("x"), arg("y")), - "Constructs a new point with the given coordinates.\n") - ) - .def_pickle(coord_pickle_suite()) + py::class_ >(m, "Coord") + .def(py::init(), + // class docstring is in mapnik/__init__.py, class _Coord + "Constructs a new object with the given coordinates.\n", + py::arg("x"), py::arg("y")) .def_readwrite("x", &coord::x, "Gets or sets the x/lon coordinate of the point.\n") .def_readwrite("y", &coord::y, "Gets or sets the y/lat coordinate of the point.\n") - .def(self == self) // __eq__ - .def(self + self) // __add__ - .def(self + float()) - .def(float() + self) - .def(self - self) // __sub__ - .def(self - float()) - .def(self * float()) //__mult__ - .def(float() * self) - .def(self / float()) // __div__ + .def(py::self == py::self) // __eq__ + .def(py::self + py::self) //__add__ + .def(py::self + float()) + .def(float() + py::self) + .def(py::self - py::self) //__sub__ + .def(py::self - float()) + .def(py::self * float()) //__mult__ + .def(float() * py::self) + .def(py::self / float()) // __div__ + .def(py::pickle( + [](coord & c) { + return py::make_tuple(c.x, c.y); + }, + [](py::tuple t) { + if (t.size() != 2) + throw std::runtime_error("Invalid state"); + coord c{t[0].cast(),t[1].cast()}; + return c; + })) ; } diff --git a/src/mapnik_datasource.cpp b/src/mapnik_datasource.cpp index f180e7dd5..8d614a28c 100644 --- a/src/mapnik_datasource.cpp +++ b/src/mapnik_datasource.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,26 +20,22 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#include -#pragma GCC diagnostic pop - -// stl -#include - // mapnik +#include #include #include #include #include #include - +#include "mapnik_value_converter.hpp" +#include "create_datasource.hpp" +// stl +#include +//pybind11 +#include +#include +#include +#include using mapnik::datasource; using mapnik::memory_datasource; @@ -47,57 +43,14 @@ using mapnik::layer_descriptor; using mapnik::attribute_descriptor; using mapnik::parameters; +namespace py = pybind11; + namespace { -//user-friendly wrapper that uses Python dictionary -using namespace boost::python; -std::shared_ptr create_datasource(dict const& d) -{ - mapnik::parameters params; - boost::python::list keys=d.keys(); - for (int i=0; i < len(keys); ++i) - { - std::string key = extract(keys[i]); - object obj = d[key]; - if (PyUnicode_Check(obj.ptr())) - { - PyObject* temp = PyUnicode_AsUTF8String(obj.ptr()); - if (temp) - { -#if PY_VERSION_HEX >= 0x03000000 - char* c_str = PyBytes_AsString(temp); -#else - char* c_str = PyString_AsString(temp); -#endif - params[key] = std::string(c_str); - Py_DecRef(temp); - } - continue; - } - - extract ex0(obj); - extract ex1(obj); - extract ex2(obj); - if (ex0.check()) - { - params[key] = ex0(); - } - else if (ex1.check()) - { - params[key] = ex1(); - } - else if (ex2.check()) - { - params[key] = ex2(); - } - } - - return mapnik::datasource_cache::instance().create(params); -} -boost::python::dict describe(std::shared_ptr const& ds) +py::dict describe(std::shared_ptr const& ds) { - boost::python::dict description; + py::dict description; mapnik::layer_descriptor ld = ds->get_descriptor(); description["type"] = ds->type(); description["name"] = ld.get_name(); @@ -105,14 +58,14 @@ boost::python::dict describe(std::shared_ptr const& ds) description["encoding"] = ld.get_encoding(); for (auto const& param : ld.get_extra_parameters()) { - description[param.first] = param.second; + description[py::str(param.first)] = mapnik_param_to_python::convert(param.second); } return description; } -boost::python::list fields(std::shared_ptr const& ds) +py::list fields(std::shared_ptr const& ds) { - boost::python::list flds; + py::list flds; if (ds) { layer_descriptor ld = ds->get_descriptor(); @@ -126,9 +79,9 @@ boost::python::list fields(std::shared_ptr const& ds) } return flds; } -boost::python::list field_types(std::shared_ptr const& ds) +py::list field_types(std::shared_ptr const& ds) { - boost::python::list fld_types; + py::list fld_types; if (ds) { layer_descriptor ld = ds->get_descriptor(); @@ -139,75 +92,99 @@ boost::python::list field_types(std::shared_ptr const& ds) { unsigned type = it->get_type(); if (type == mapnik::Integer) - // this crashes, so send back strings instead - //fld_types.append(boost::python::object(boost::python::handle<>(&PyInt_Type))); - fld_types.append(boost::python::str("int")); + fld_types.append(py::str("int")); else if (type == mapnik::Float) - fld_types.append(boost::python::str("float")); + fld_types.append(py::str("float")); else if (type == mapnik::Double) - fld_types.append(boost::python::str("float")); + fld_types.append(py::str("float")); else if (type == mapnik::String) - fld_types.append(boost::python::str("str")); + fld_types.append(py::str("str")); else if (type == mapnik::Boolean) - fld_types.append(boost::python::str("bool")); + fld_types.append(py::str("bool")); else if (type == mapnik::Geometry) - fld_types.append(boost::python::str("geometry")); + fld_types.append(py::str("geometry")); else if (type == mapnik::Object) - fld_types.append(boost::python::str("object")); + fld_types.append(py::str("object")); else - fld_types.append(boost::python::str("unknown")); + fld_types.append(py::str("unknown")); } } return fld_types; -}} +} + +py::dict parameters_impl(std::shared_ptr const& ds) +{ + auto const params = ds->params(); + py::dict d; + for (auto kv : params) + { + d[py::str(kv.first)] = mapnik_param_to_python::convert(kv.second); + } + return d; +} -mapnik::parameters const& (mapnik::datasource::*params_const)() const = &mapnik::datasource::params; +} // namespace -void export_datasource() +void export_datasource(py::module& m) { - using namespace boost::python; - - enum_("DataType") + py::native_enum(m, "DataType", "enum.Enum") .value("Vector",mapnik::datasource::Vector) .value("Raster",mapnik::datasource::Raster) + .finalize() ; - enum_("DataGeometryType") + py::native_enum(m, "DataGeometryType", "enum.Enum") .value("Point",mapnik::datasource_geometry_t::Point) .value("LineString",mapnik::datasource_geometry_t::LineString) .value("Polygon",mapnik::datasource_geometry_t::Polygon) .value("Collection",mapnik::datasource_geometry_t::Collection) + .finalize() ; - class_, - boost::noncopyable>("Datasource",no_init) - .def("type",&datasource::type) - .def("geometry_type",&datasource::get_geometry_type) - .def("describe",&describe) - .def("envelope",&datasource::envelope) - .def("features",&datasource::features) - .def("fields",&fields) - .def("field_types",&field_types) - .def("features_at_point",&datasource::features_at_point, (arg("coord"),arg("tolerance")=0)) - .def("params",make_function(params_const,return_value_policy()), + py::class_> (m, "Datasource") + .def(py::init([] (py::kwargs const& kwargs) { return create_datasource(kwargs);})) + .def("type", &datasource::type) + .def("geometry_type", &datasource::get_geometry_type) + .def("describe", &describe) + .def("envelope", &datasource::envelope) + .def("features", &datasource::features) + .def("fields" ,&fields) + .def("field_types", &field_types) + .def("features_at_point", &datasource::features_at_point, py::arg("coord"), py::arg("tolerance") = 0) + .def("parameters", ¶meters_impl, "The configuration parameters of the data source. " "These vary depending on the type of data source.") - .def(self == self) + .def(py::self == py::self) + .def("__iter__", + [](datasource const& ds) { + mapnik::query q(ds.envelope()); + layer_descriptor ld = ds.get_descriptor(); + std::vector const& desc_ar = ld.get_descriptors(); + for (auto const& desc : desc_ar) + { + q.add_property_name(desc.get_name()); + } + return ds.features(q); + }, + py::keep_alive<0, 1>()) ; - def("CreateDatasource",&create_datasource); + m.def("CreateDatasource",&create_datasource); - class_, std::shared_ptr, - boost::noncopyable>("MemoryDatasourceBase", init()) - .def("add_feature",&memory_datasource::push, + py::class_> + (m, "MemoryDatasource") + .def(py::init([]() { + mapnik::parameters p; + p.insert(std::make_pair("type","memory")); + return std::make_shared(p);})) + .def("add_feature", &memory_datasource::push, "Adds a Feature:\n" ">>> ms = MemoryDatasource()\n" - ">>> feature = Feature(1)\n" - ">>> ms.add_feature(Feature(1))\n") - .def("num_features",&memory_datasource::size) + ">>> feature = Feature(Context(),1)\n" + ">>> ms.add_feature(f)\n") + .def("num_features", &memory_datasource::size) ; - implicitly_convertible,std::shared_ptr >(); + py::implicitly_convertible(); } diff --git a/src/mapnik_datasource_cache.cpp b/src/mapnik_datasource_cache.cpp index d962b67bf..82fde280f 100644 --- a/src/mapnik_datasource_cache.cpp +++ b/src/mapnik_datasource_cache.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,81 +20,45 @@ * *****************************************************************************/ +// mapnik #include - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - #include #include #include #include +#include "create_datasource.hpp" +//pybind11 +#include +#include -namespace { +namespace py = pybind11; -using namespace boost::python; +namespace { -std::shared_ptr create_datasource(const dict& d) +bool register_datasources(std::string const& plugins_dir, bool recursive = false) { - mapnik::parameters params; - boost::python::list keys=d.keys(); - for (int i=0; i(keys[i]); - object obj = d[key]; - extract ex0(obj); - extract ex1(obj); - extract ex2(obj); - - if (ex0.check()) - { - params[key] = ex0(); - } - else if (ex1.check()) - { - params[key] = ex1(); - } - else if (ex2.check()) - { - params[key] = ex2(); - } - } - - return mapnik::datasource_cache::instance().create(params); + return mapnik::datasource_cache::instance().register_datasources(plugins_dir, recursive); } -void register_datasources(std::string const& path) +std::string plugin_directories() { - mapnik::datasource_cache::instance().register_datasources(path); + return mapnik::datasource_cache::instance().plugin_directories(); } std::vector plugin_names() { - return mapnik::datasource_cache::instance().plugin_names(); + return mapnik::datasource_cache::instance().plugin_names(); } -std::string plugin_directories() -{ - return mapnik::datasource_cache::instance().plugin_directories(); -} +} // namespace -} -void export_datasource_cache() +void export_datasource_cache(py::module const& m) { - using mapnik::datasource_cache; - class_("DatasourceCache",no_init) - .def("create",&create_datasource) - .staticmethod("create") - .def("register_datasources",®ister_datasources) - .staticmethod("register_datasources") - .def("plugin_names",&plugin_names) - .staticmethod("plugin_names") - .def("plugin_directories",&plugin_directories) - .staticmethod("plugin_directories") + py::class_>(m, "DatasourceCache") + .def_static("create",&create_datasource) + .def_static("register_datasources",®ister_datasources) + .def_static("plugin_names",&plugin_names) + .def_static("plugin_directories",&plugin_directories) ; } diff --git a/src/mapnik_debug_symbolizer.cpp b/src/mapnik_debug_symbolizer.cpp new file mode 100644 index 000000000..a2a3f063e --- /dev/null +++ b/src/mapnik_debug_symbolizer.cpp @@ -0,0 +1,56 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 + * + *****************************************************************************/ + +// mapnik +#include +#include +#include +#include +#include +#include "mapnik_symbolizer.hpp" +//pybind11 +#include +#include + +namespace py = pybind11; + +void export_debug_symbolizer(py::module const& m) +{ + using namespace python_mapnik; + using mapnik::debug_symbolizer; + using mapnik::debug_symbolizer_mode_enum; + + py::native_enum(m, "debug_symbolizer_mode", "enum.Enum") + .value("COLLISION", debug_symbolizer_mode_enum::DEBUG_SYM_MODE_COLLISION) + .value("VERTEX", debug_symbolizer_mode_enum::DEBUG_SYM_MODE_VERTEX) + .finalize() + ; + + py::class_(m, "DebugSymbolizer") + .def(py::init<>(), "Default ctor") + .def("__hash__", hash_impl_2) + .def_property("mode", + &get, + &set_enum_property) + ; + +} diff --git a/src/mapnik_dot_symbolizer.cpp b/src/mapnik_dot_symbolizer.cpp new file mode 100644 index 000000000..d2702c792 --- /dev/null +++ b/src/mapnik_dot_symbolizer.cpp @@ -0,0 +1,66 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 + * + *****************************************************************************/ + +// mapnik +#include +#include +#include +#include +#include +#include "mapnik_symbolizer.hpp" +//pybind11 +#include + +namespace py = pybind11; + +void export_dot_symbolizer(py::module const& m) +{ + using namespace python_mapnik; + using mapnik::dot_symbolizer; + + py::class_(m, "DotSymbolizer") + .def(py::init<>(), "Default ctor") + .def("__hash__", hash_impl_2) + .def_property("fill", + &get_property, + &set_color_property, + "Fill - mapnik.Color, CSS color string or a valid mapnik.Expression") + .def_property("opacity", + &get_property, + &set_double_property, + "Opacity - [0-1] or a valid mapnik.Expression") + .def_property("width", + &get_property, + &set_double_property, + "Width - a numeric value or a valid mapnik.Expression") + .def_property("height", + &get_property, + &set_double_property, + "Height - a numeric value or a valid mapnik.Expression") + .def_property("comp_op", + &get, + &set_enum_property, + "Composite mode (comp-op)") + + ; + +} diff --git a/src/mapnik_enumeration.hpp b/src/mapnik_enumeration.hpp deleted file mode 100644 index 6e13abe55..000000000 --- a/src/mapnik_enumeration.hpp +++ /dev/null @@ -1,91 +0,0 @@ -/***************************************************************************** - * - * This file is part of Mapnik (c++ mapping toolkit) - * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon - * - * 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 MAPNIK_PYTHON_BINDING_ENUMERATION_INCLUDED -#define MAPNIK_PYTHON_BINDING_ENUMERATION_INCLUDED - -#pragma GCC diagnostic push -#include -#include // for registered -#include // for enum_ -#include // for implicitly_convertible -#include -#pragma GCC diagnostic pop - -namespace mapnik { - -template -class enumeration_ : - public boost::python::enum_ -{ - // some short cuts - using base_type = boost::python::enum_; - using native_type = typename EnumWrapper::native_type; -public: - enumeration_() : - base_type( EnumWrapper::get_name().c_str() ) - { - init(); - } - enumeration_(const char * python_alias) : - base_type( python_alias ) - { - init(); - } - enumeration_(const char * python_alias, const char * doc) : - base_type( python_alias, doc ) - { - init(); - } - -private: - struct converter - { - static PyObject* convert(EnumWrapper const& v) - { - // Redirect conversion to a static method of our base class's - // base class. A free template converter will not work because - // the base_type::base typedef is protected. - // Lets hope MSVC agrees that this is legal C++ - using namespace boost::python::converter; - return base_type::base::to_python( - registered::converters.m_class_object - , static_cast( v )); - - } - }; - - void init() { - boost::python::implicitly_convertible(); - boost::python::to_python_converter(); - - for (unsigned i = 0; i < EnumWrapper::MAX; ++i) - { - // Register the strings already defined for this enum. - base_type::value( EnumWrapper::get_string( i ), native_type( i ) ); - } - } - -}; - -} // end of namespace mapnik - -#endif // MAPNIK_PYTHON_BINDING_ENUMERATION_INCLUDED diff --git a/src/mapnik_envelope.cpp b/src/mapnik_envelope.cpp index 91e242d0a..4af90c819 100644 --- a/src/mapnik_envelope.cpp +++ b/src/mapnik_envelope.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,32 +20,21 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - // mapnik +#include #include #include +//stl +#include +//pybind11 +#include +#include + +namespace py = pybind11; using mapnik::coord; using mapnik::box2d; -struct envelope_pickle_suite : boost::python::pickle_suite -{ - static boost::python::tuple - getinitargs(const box2d& e) - { - using namespace boost::python; - return boost::python::make_tuple(e.minx(),e.miny(),e.maxx(),e.maxy()); - } -}; - box2d from_string(std::string const& s) { box2d bbox; @@ -95,36 +84,32 @@ void (box2d::*clip)(box2d const&) = &box2d::clip; // pad void (box2d::*pad)(double) = &box2d::pad; -// deepcopy -box2d box2d_deepcopy(box2d & obj, boost::python::dict const&) -{ - // FIXME::ignore memo for now - box2d result(obj); - return result; -} +// to string + -void export_envelope() +void export_envelope(py::module const& m) { - using namespace boost::python; - class_ >("Box2d", - // class docstring is in mapnik/__init__.py, class _Coord - init( - (arg("minx"),arg("miny"),arg("maxx"),arg("maxy")), - "Constructs a new envelope from the coordinates\n" - "of its lower left and upper right corner points.\n")) - .def(init<>("Equivalent to Box2d(0, 0, -1, -1).\n")) - .def(init&, const coord&>( - (arg("ll"),arg("ur")), - "Equivalent to Box2d(ll.x, ll.y, ur.x, ur.y).\n")) - .def("from_string",from_string) - .staticmethod("from_string") - .add_property("minx", &box2d::minx, + py::class_ >(m, "Box2d") + // class docstring is in mapnik/__init__.py, class _Coord + .def(py::init(), + "Constructs a new envelope from the coordinates\n" + "of its lower left and upper right corner points.\n", + py::arg("minx"),py::arg("miny"),py::arg("maxx"),py::arg("maxy")) + + .def(py::init<>(), "Equivalent to Box2d(INVALID).\n") + + .def(py::init const&, coord const&>(), + "Equivalent to Box2d(ll.x, ll.y, ur.x, ur.y).\n", + py::arg("ll"),py::arg("ur")) + + .def_static("from_string",from_string) + .def_property("minx", &box2d::minx, &box2d::set_minx, "X coordinate for the lower left corner") - .add_property("miny", &box2d::miny, + .def_property("miny", &box2d::miny, &box2d::set_miny, "Y coordinate for the lower left corner") - .add_property("maxx", &box2d::maxx, + .def_property("maxx", &box2d::maxx, &box2d::set_maxx, "X coordinate for the upper right corner") - .add_property("maxy", &box2d::maxy, + .def_property("maxy", &box2d::maxy, &box2d::set_maxy, "Y coordinate for the upper right corner") .def("center", &box2d::center, "Returns the coordinates of the center of the bounding box.\n" @@ -134,7 +119,6 @@ void export_envelope() ">>> e.center()\n" "Coord(50, 50)\n") .def("center", re_center_p1, - (arg("x"), arg("y")), "Moves the envelope so that the given coordinates become its new center.\n" "The width and the height are preserved.\n" "\n " @@ -146,10 +130,9 @@ void export_envelope() ">>> (e.width(), e.height())\n" "(100.0, 100.0)\n" ">>> e\n" - "Box2d(10.0, 10.0, 110.0, 110.0)\n" - ) + "Box2d(10.0, 10.0, 110.0, 110.0)\n", + py::arg("x"), py::arg("y")) .def("center", re_center_p2, - (arg("Coord")), "Moves the envelope so that the given coordinates become its new center.\n" "The width and the height are preserved.\n" "\n " @@ -161,10 +144,9 @@ void export_envelope() ">>> (e.width(), e.height())\n" "(100.0, 100.0)\n" ">>> e\n" - "Box2d(10.0, 10.0, 110.0, 110.0)\n" - ) + "Box2d(10.0, 10.0, 110.0, 110.0)\n", + py::arg("Coord")) .def("clip", clip, - (arg("other")), "Clip the envelope based on the bounds of another envelope.\n" "\n " "Example:\n" @@ -172,20 +154,18 @@ void export_envelope() ">>> c = Box2d(-50, -50, 50, 50)\n" ">>> e.clip(c)\n" ">>> e\n" - "Box2d(0.0,0.0,50.0,50.0\n" - ) + "Box2d(0.0,0.0,50.0,50.0\n", + py::arg("other")) .def("pad", pad, - (arg("padding")), "Pad the envelope based on a padding value.\n" "\n " "Example:\n" ">>> e = Box2d(0, 0, 100, 100)\n" ">>> e.pad(10)\n" ">>> e\n" - "Box2d(-10.0,-10.0,110.0,110.0\n" - ) + "Box2d(-10.0,-10.0,110.0,110.0\n", + py::arg("padding")) .def("width", width_p1, - (arg("new_width")), "Sets the width to new_width of the envelope preserving its center.\n" "\n " "Example:\n" @@ -194,13 +174,11 @@ void export_envelope() ">>> e.center()\n" "Coord(50.0,50.0)\n" ">>> e\n" - "Box2d(-10.0, 0.0, 110.0, 100.0)\n" - ) + "Box2d(-10.0, 0.0, 110.0, 100.0)\n", + py::arg("new_width")) .def("width", width_p2, - "Returns the width of this envelope.\n" - ) + "Returns the width of this envelope.\n") .def("height", height_p1, - (arg("new_height")), "Sets the height to new_height of the envelope preserving its center.\n" "\n " "Example:\n" @@ -209,59 +187,52 @@ void export_envelope() ">>> e.center()\n" "Coord(50.0,50.0)\n" ">>> e\n" - "Box2d(0.0, -10.0, 100.0, 110.0)\n" - ) + "Box2d(0.0, -10.0, 100.0, 110.0)\n", + py::arg("new_height")) .def("height", height_p2, - "Returns the height of this envelope.\n" - ) + "Returns the height of this envelope.\n") .def("expand_to_include",expand_to_include_p1, - (arg("x"),arg("y")), "Expands this envelope to include the point given by x and y.\n" "\n" "Example:\n", ">>> e = Box2d(0, 0, 100, 100)\n" ">>> e.expand_to_include(110, 110)\n" ">>> e\n" - "Box2d(0.0, 00.0, 110.0, 110.0)\n" - ) + "Box2d(0.0, 00.0, 110.0, 110.0)\n", + py::arg("x"),py::arg("y")) + .def("expand_to_include",expand_to_include_p2, - (arg("p")), - "Equivalent to expand_to_include(p.x, p.y)\n" - ) + "Equivalent to expand_to_include(p.x, p.y)\n", + py::arg("p")) + .def("expand_to_include",expand_to_include_p3, - (arg("other")), "Equivalent to:\n" " expand_to_include(other.minx, other.miny)\n" - " expand_to_include(other.maxx, other.maxy)\n" - ) + " expand_to_include(other.maxx, other.maxy)\n", + py::arg("other")) .def("contains",contains_p1, - (arg("x"),arg("y")), "Returns True iff this envelope contains the point\n" - "given by x and y.\n" - ) + "given by x and y.\n", + py::arg("x"),py::arg("y")) .def("contains",contains_p2, - (arg("p")), - "Equivalent to contains(p.x, p.y)\n" - ) + "Equivalent to contains(p.x, p.y)\n", + py::arg("p")) .def("contains",contains_p3, - (arg("other")), "Equivalent to:\n" - " contains(other.minx, other.miny) and contains(other.maxx, other.maxy)\n" - ) + " contains(other.minx, other.miny) and contains(other.maxx, other.maxy)\n", + py::arg("other")) .def("intersects",intersects_p1, - (arg("x"),arg("y")), "Returns True iff this envelope intersects the point\n" "given by x and y.\n" "\n" "Note: For points, intersection is equivalent\n" "to containment, i.e. the following holds:\n" - " e.contains(x, y) == e.intersects(x, y)\n" - ) + " e.contains(x, y) == e.intersects(x, y)\n", + py::arg("x"),py::arg("y")) .def("intersects",intersects_p2, - (arg("p")), - "Equivalent to contains(p.x, p.y)\n") + "Equivalent to contains(p.x, p.y)\n", + py::arg("p")) .def("intersects",intersects_p3, - (arg("other")), "Returns True iff this envelope intersects the other envelope,\n" "This relationship is symmetric." "\n" @@ -271,10 +242,9 @@ void export_envelope() ">>> e1.intersects(e2)\n" "True\n" ">>> e1.contains(e2)\n" - "False\n" - ) + "False\n", + py::arg("other")) .def("intersect",intersect, - (arg("other")), "Returns the overlap of this envelope and the other envelope\n" "as a new envelope.\n" "\n" @@ -282,18 +252,33 @@ void export_envelope() ">>> e1 = Box2d(0, 0, 100, 100)\n" ">>> e2 = Box2d(50, 50, 150, 150)\n" ">>> e1.intersect(e2)\n" - "Box2d(50.0, 50.0, 100.0, 100.0)\n" - ) - .def(self == self) // __eq__ - .def(self != self) // __neq__ - .def(self + self) // __add__ - .def(self * float()) // __mult__ - .def(float() * self) - .def(self / float()) // __div__ + "Box2d(50.0, 50.0, 100.0, 100.0)\n", + py::arg("other")) + .def(py::self == py::self) // __eq__ + .def(py::self != py::self) // __neq__ + .def(py::self + py::self) // __add__ + .def(py::self * float()) // __mult__ + .def(float() * py::self) + .def(py::self / float()) // __div__ .def("__getitem__",&box2d::operator[]) .def("valid",&box2d::valid) - .def_pickle(envelope_pickle_suite()) - .def("__deepcopy__", &box2d_deepcopy) + .def(py::pickle( + [](box2d const& box) { + return py::make_tuple(box.minx(), box.miny(), box.maxx(), box.maxy()); + }, + [](py::tuple t) { + if (t.size() != 4) + throw std::runtime_error("Invalid state"); + box2d box{t[0].cast(), + t[1].cast(), + t[2].cast(), + t[3].cast()}; + return box; + })) + .def("__repr__", + [](box2d const& box) { + return box.to_string(); + }) ; } diff --git a/src/mapnik_expression.cpp b/src/mapnik_expression.cpp index 920e1d35a..687326976 100644 --- a/src/mapnik_expression.cpp +++ b/src/mapnik_expression.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,17 +20,10 @@ * *****************************************************************************/ +// mapnik #include #include "python_to_value.hpp" -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - -// mapnik +#include "mapnik_value_converter.hpp" #include #include #include @@ -39,11 +32,19 @@ #include #include +//pybind11 +#include +#include +#include + using mapnik::expression_ptr; using mapnik::parse_expression; using mapnik::to_expression_string; using mapnik::path_expression_ptr; +namespace py = pybind11; + +PYBIND11_MAKE_OPAQUE(mapnik::path_expression); // expression expression_ptr parse_expression_(std::string const& wkt) @@ -56,15 +57,17 @@ std::string expression_to_string_(mapnik::expr_node const& expr) return mapnik::to_expression_string(expr); } -mapnik::value expression_evaluate_(mapnik::expr_node const& expr, mapnik::feature_impl const& f, boost::python::dict const& d) +mapnik::value expression_evaluate_(mapnik::expr_node const& expr, mapnik::feature_impl const& f, py::dict const& d) { // will be auto-converted to proper python type by `mapnik_value_to_python` - return mapnik::util::apply_visitor(mapnik::evaluate(f,mapnik::dict2attr(d)),expr); + return mapnik::util::apply_visitor(mapnik::evaluate(f, mapnik::dict2attr(d)),expr); } -bool expression_evaluate_to_bool_(mapnik::expr_node const& expr, mapnik::feature_impl const& f, boost::python::dict const& d) +bool expression_evaluate_to_bool_(mapnik::expr_node const& expr, mapnik::feature_impl const& f, py::dict const& d) { - return mapnik::util::apply_visitor(mapnik::evaluate(f,mapnik::dict2attr(d)),expr).to_bool(); + return mapnik::util::apply_visitor(mapnik::evaluate(f, mapnik::dict2attr(d)),expr).to_bool(); } // path expression @@ -83,25 +86,19 @@ std::string path_evaluate_(mapnik::path_expression const& expr, mapnik::feature_ return mapnik::path_processor_type::evaluate(expr, f); } -void export_expression() +void export_expression(py::module const& m) { - using namespace boost::python; - class_("Expression", - "TODO" - "",no_init) - .def("evaluate", &expression_evaluate_,(arg("feature"),arg("variables")=boost::python::dict())) - .def("to_bool", &expression_evaluate_to_bool_,(arg("feature"),arg("variables")=boost::python::dict())) - .def("__str__",&expression_to_string_); + py::class_(m, "Expression") + .def(py::init([] (std::string const& wkt) { return parse_expression_(wkt);})) + .def("evaluate", &expression_evaluate_, py::arg("feature"), py::arg("variables") = py::dict()) + .def("to_bool", &expression_evaluate_to_bool_, py::arg("feature"), py::arg("variables") = py::dict()) + .def("__str__", &expression_to_string_); ; - def("Expression",&parse_expression_,(arg("expr")),"Expression string"); - - class_("PathExpression", - "TODO" - "",no_init) - .def("evaluate", &path_evaluate_) // note: "pass" is a reserved word in Python + py::class_(m, "PathExpression") + .def(py::init([] (std::string const& wkt) { return parse_path_(wkt);})) + .def("evaluate", &path_evaluate_) .def("__str__",&path_to_string_); ; - def("PathExpression",&parse_path_,(arg("expr")),"PathExpression string"); } diff --git a/src/mapnik_feature.cpp b/src/mapnik_feature.cpp index e805a4de4..b532546fb 100644 --- a/src/mapnik_feature.cpp +++ b/src/mapnik_feature.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,20 +20,9 @@ * *****************************************************************************/ +//mapnik #include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#include -#include -#include -#include -#pragma GCC diagnostic pop - -// mapnik +#include #include #include #include @@ -43,8 +32,14 @@ #include #include +#include "mapnik_value_converter.hpp" // stl #include +//pybind11 +#include +#include + +namespace py = pybind11; namespace { @@ -52,6 +47,7 @@ using mapnik::geometry_utils; using mapnik::context_type; using mapnik::context_ptr; using mapnik::feature_kv_iterator; +using mapnik::value; mapnik::feature_ptr from_geojson_impl(std::string const& json, mapnik::context_ptr const& ctx) { @@ -73,12 +69,12 @@ std::string feature_to_geojson(mapnik::feature_impl const& feature) return json; } -mapnik::value __getitem__(mapnik::feature_impl const& feature, std::string const& name) +mapnik::value __getitem__(mapnik::feature_impl const& feature, std::string const& name) { return feature.get(name); } -mapnik::value __getitem2__(mapnik::feature_impl const& feature, std::size_t index) +mapnik::value __getitem2__(mapnik::feature_impl const& feature, std::size_t index) { return feature.get(index); } @@ -88,146 +84,49 @@ void __setitem__(mapnik::feature_impl & feature, std::string const& name, mapnik feature.put_new(name,val); } -boost::python::dict attributes(mapnik::feature_impl const& f) +py::dict attributes(mapnik::feature_impl const& feature) { - boost::python::dict attributes; - feature_kv_iterator itr = f.begin(); - feature_kv_iterator end = f.end(); - - for ( ;itr!=end; ++itr) + auto attributes = py::dict(); + for (auto const& kv : feature) { - attributes[std::get<0>(*itr)] = std::get<1>(*itr); + attributes[std::get<0>(kv).c_str()] = std::get<1>(kv); } - return attributes; } } // end anonymous namespace -struct unicode_string_from_python_str +void export_feature(py::module const& m) { - unicode_string_from_python_str() - { - boost::python::converter::registry::push_back( - &convertible, - &construct, - boost::python::type_id()); - } - - static void* convertible(PyObject* obj_ptr) - { - if (!( -#if PY_VERSION_HEX >= 0x03000000 - PyBytes_Check(obj_ptr) -#else - PyString_Check(obj_ptr) -#endif - || PyUnicode_Check(obj_ptr))) - return 0; - return obj_ptr; - } - - static void construct( - PyObject* obj_ptr, - boost::python::converter::rvalue_from_python_stage1_data* data) - { - char * value=0; - if (PyUnicode_Check(obj_ptr)) { - PyObject *encoded = PyUnicode_AsEncodedString(obj_ptr, "utf8", "replace"); - if (encoded) { -#if PY_VERSION_HEX >= 0x03000000 - value = PyBytes_AsString(encoded); -#else - value = PyString_AsString(encoded); -#endif - Py_DecRef(encoded); - } - } else { -#if PY_VERSION_HEX >= 0x03000000 - value = PyBytes_AsString(obj_ptr); -#else - value = PyString_AsString(obj_ptr); -#endif - } - if (value == 0) boost::python::throw_error_already_set(); - void* storage = ( - (boost::python::converter::rvalue_from_python_storage*) - data)->storage.bytes; - new (storage) mapnik::value_unicode_string(value); - data->convertible = storage; - } -}; - - -struct value_null_from_python -{ - value_null_from_python() - { - boost::python::converter::registry::push_back( - &convertible, - &construct, - boost::python::type_id()); - } - - static void* convertible(PyObject* obj_ptr) - { - if (obj_ptr == Py_None) return obj_ptr; - return 0; - } - - static void construct( - PyObject* obj_ptr, - boost::python::converter::rvalue_from_python_stage1_data* data) - { - if (obj_ptr != Py_None) boost::python::throw_error_already_set(); - void* storage = ( - (boost::python::converter::rvalue_from_python_storage*) - data)->storage.bytes; - new (storage) mapnik::value_null(); - data->convertible = storage; - } -}; - -void export_feature() -{ - using namespace boost::python; - - // Python to mapnik::value converters - // NOTE: order matters here. For example value_null must be listed before - // bool otherwise Py_None will be interpreted as bool (false) - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - - // http://misspent.wordpress.com/2009/09/27/how-to-write-boost-python-converters/ - unicode_string_from_python_str(); - value_null_from_python(); - - class_ - ("Context",init<>("Default ctor.")) + py::class_(m, "Context") + .def(py::init<>(), "Default constructor") .def("push", &context_type::push) ; - class_, - boost::noncopyable>("Feature",init("Default ctor.")) + py::class_>(m, "Feature") + .def(py::init(), "Default constructor") .def("id",&mapnik::feature_impl::id) - .add_property("geometry", - make_function((mapnik::geometry::geometry& (mapnik::feature_impl::*)()) - &mapnik::feature_impl::get_geometry, return_value_policy()), - &mapnik::feature_impl::set_geometry_copy) + //.def_property("id",&mapnik::feature_impl::id, &mapnik::feature_impl::set_id) + .def_property("geometry", + py::cpp_function((mapnik::geometry::geometry& (mapnik::feature_impl::*)()) + &mapnik::feature_impl::get_geometry, py::return_value_policy::reference_internal), + py::cpp_function(&mapnik::feature_impl::set_geometry_copy)) .def("envelope", &mapnik::feature_impl::envelope) .def("has_key", &mapnik::feature_impl::has_key) - .add_property("attributes",&attributes) - .def("__setitem__",&__setitem__) - .def("__contains__",&__getitem__) - .def("__getitem__",&__getitem__) - .def("__getitem__",&__getitem2__) + .def_property_readonly("attributes", [] (mapnik::feature_impl const& f) { return attributes(f) ;}) + .def("__setitem__", &__setitem__) + .def("__contains__" ,&__getitem__) + .def("__getitem__", &__getitem__) + .def("__getitem__", &__getitem2__) .def("__len__", &mapnik::feature_impl::size) - .def("context",&mapnik::feature_impl::context) - .def("to_geojson",&feature_to_geojson) - .def("from_geojson",from_geojson_impl) - .staticmethod("from_geojson") + .def("context", &mapnik::feature_impl::context) + .def("to_json", &feature_to_geojson) + .def("to_geojson", &feature_to_geojson) + .def_property_readonly("__geo_interface__", + [] (mapnik::feature_impl const& f) { + py::object json = py::module_::import("json"); + py::object loads = json.attr("loads"); + return loads(feature_to_geojson(f));}) + .def_static("from_geojson", from_geojson_impl) ; } diff --git a/src/mapnik_featureset.cpp b/src/mapnik_featureset.cpp index 521beabc3..b59b89657 100644 --- a/src/mapnik_featureset.cpp +++ b/src/mapnik_featureset.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,47 +20,35 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - // mapnik +#include #include #include -namespace { -using namespace boost::python; +//pybind11 +#include +#include +#include + +namespace py = pybind11; -inline object pass_through(object const& o) { return o; } +namespace { inline mapnik::feature_ptr next(mapnik::featureset_ptr const& itr) { mapnik::feature_ptr f = itr->next(); - if (!f) - { - PyErr_SetString(PyExc_StopIteration, "No more features."); - boost::python::throw_error_already_set(); - } - + if (!f) throw py::stop_iteration(); return f; } } -void export_featureset() +void export_featureset(py::module const& m) { - using namespace boost::python; // Featureset implements Python iterator interface - class_, - boost::noncopyable>("Featureset", no_init) - .def("__iter__", pass_through) + py::class_> + (m, "Featureset") + .def("__iter__", [](mapnik::Featureset& itr) -> mapnik::Featureset& { return itr; }) .def("__next__", next) - // Python2 support - .def("next", next) ; } diff --git a/src/mapnik_font_engine.cpp b/src/mapnik_font_engine.cpp index c53993847..5fdcfedd8 100644 --- a/src/mapnik_font_engine.cpp +++ b/src/mapnik_font_engine.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,25 +20,22 @@ * *****************************************************************************/ +//mapnik #include - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - #include +//pybind11 +#include +#include -void export_font_engine() +namespace py = pybind11; + +void export_font_engine(py::module const& m) { using mapnik::freetype_engine; - using namespace boost::python; - class_("FontEngine", no_init) - .def("register_font", &freetype_engine::register_font) - .def("register_fonts", &freetype_engine::register_fonts) - .def("face_names", &freetype_engine::face_names) - .staticmethod("register_font") - .staticmethod("register_fonts") - .staticmethod("face_names"); + + py::class_(m, "FontEngine") + .def_static("register_font", &freetype_engine::register_font) + .def_static("register_fonts", &freetype_engine::register_fonts) + .def_static("face_names", &freetype_engine::face_names) + ; } diff --git a/src/mapnik_fontset.cpp b/src/mapnik_fontset.cpp index 43b2e0b9e..243f4faf0 100644 --- a/src/mapnik_fontset.cpp +++ b/src/mapnik_fontset.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 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,42 +20,34 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - //mapnik +#include #include +//pybind11 +#include +namespace py = pybind11; using mapnik::font_set; -void export_fontset () +void export_fontset (py::module const& m) { - using namespace boost::python; - class_("FontSet", init("default fontset constructor") - ) - .add_property("name", - make_function(&font_set::get_name,return_value_policy()), + py::class_(m, "FontSet") + .def(py::init(), "default fontset constructor") + .def_property("name", + &font_set::get_name, &font_set::set_name, "Get/Set the name of the FontSet.\n" ) - .def("add_face_name",&font_set::add_face_name, - (arg("name")), + .def("add_face_name", &font_set::add_face_name, "Add a face-name to the fontset.\n" "\n" "Example:\n" ">>> fs = Fontset('book-fonts')\n" - ">>> fs.add_face_name('DejaVu Sans Book')\n") - .add_property("names",make_function - (&font_set::get_face_names, - return_value_policy()), - "List of face names belonging to a FontSet.\n" - ) + ">>> fs.add_face_name('DejaVu Sans Book')\n", + py::arg("name")) + .def_property_readonly("names", + &font_set::get_face_names, + "List of face names belonging to a FontSet.\n") ; } diff --git a/src/mapnik_gamma_method.cpp b/src/mapnik_gamma_method.cpp index d0ba6f725..d4648af55 100644 --- a/src/mapnik_gamma_method.cpp +++ b/src/mapnik_gamma_method.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,27 +20,24 @@ * *****************************************************************************/ +// mapnik #include - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - #include -#include "mapnik_enumeration.hpp" +//pybind11 +#include +#include -void export_gamma_method() -{ - using namespace boost::python; +namespace py = pybind11; - mapnik::enumeration_("gamma_method") - .value("POWER", mapnik::GAMMA_POWER) - .value("LINEAR",mapnik::GAMMA_LINEAR) - .value("NONE", mapnik::GAMMA_NONE) - .value("THRESHOLD", mapnik::GAMMA_THRESHOLD) - .value("MULTIPLY", mapnik::GAMMA_MULTIPLY) +void export_gamma_method(py::module const& m) +{ + py::native_enum(m, "gamma_method", "enum.Enum") + .value("POWER", mapnik::gamma_method_enum::GAMMA_POWER) + .value("LINEAR",mapnik::gamma_method_enum::GAMMA_LINEAR) + .value("NONE", mapnik::gamma_method_enum::GAMMA_NONE) + .value("THRESHOLD", mapnik::gamma_method_enum::GAMMA_THRESHOLD) + .value("MULTIPLY", mapnik::gamma_method_enum::GAMMA_MULTIPLY) + .finalize() ; } diff --git a/src/mapnik_geometry.cpp b/src/mapnik_geometry.cpp index cf84e4dbf..64243636c 100644 --- a/src/mapnik_geometry.cpp +++ b/src/mapnik_geometry.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 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,18 +20,8 @@ * *****************************************************************************/ +// mapnik #include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#include -#include -#include -#include -#pragma GCC diagnostic pop // mapnik #include @@ -42,19 +32,24 @@ #include #include #include - #include // from_wkt #include // from_geojson #include // to_geojson #include // to_wkb #include // to_wkt -//#include #include - +#include "python_variant.hpp" // stl #include +//pybind11 +#include +#include +#include + +namespace py = pybind11; + namespace { std::shared_ptr > from_wkb_impl(std::string const& wkb) @@ -89,33 +84,16 @@ std::shared_ptr > from_geojson_impl(std::stri } -inline std::string boost_version() +template +py::object to_wkb_impl(GeometryType const& geom, mapnik::wkbByteOrder byte_order) { - std::ostringstream s; - s << BOOST_VERSION/100000 << "." << BOOST_VERSION/100 % 1000 << "." << BOOST_VERSION % 100; - return s.str(); + mapnik::util::wkb_buffer_ptr wkb = mapnik::util::to_wkb(geom, byte_order); + if (wkb) return py::bytes(wkb->buffer(), wkb->size()); + return py::none(); } -PyObject* to_wkb_impl(mapnik::geometry::geometry const& geom, mapnik::wkbByteOrder byte_order) -{ - mapnik::util::wkb_buffer_ptr wkb = mapnik::util::to_wkb(geom,byte_order); - if (wkb) - { - return -#if PY_VERSION_HEX >= 0x03000000 - ::PyBytes_FromStringAndSize -#else - ::PyString_FromStringAndSize -#endif - ((const char*)wkb->buffer(),wkb->size()); - } - else - { - Py_RETURN_NONE; - } -} - -std::string to_geojson_impl(mapnik::geometry::geometry const& geom) +template +std::string to_geojson_impl(GeometryType const& geom) { std::string wkt; if (!mapnik::util::to_geojson(wkt, geom)) @@ -125,7 +103,8 @@ std::string to_geojson_impl(mapnik::geometry::geometry const& geom) return wkt; } -std::string to_wkt_impl(mapnik::geometry::geometry const& geom) +template +std::string to_wkt_impl(GeometryType const& geom) { std::string wkt; if (!mapnik::util::to_wkt(wkt,geom)) @@ -140,25 +119,26 @@ mapnik::geometry::geometry_types geometry_type_impl(mapnik::geometry::geometry geometry_envelope_impl(mapnik::geometry::geometry const& geom) +template +mapnik::box2d geometry_envelope_impl(GeometryType const& geom) { return mapnik::geometry::envelope(geom); } -// Mapnik requires Boost >= 1.58 for the is_valid and is_simple functions -#if BOOST_VERSION >= 105800 -bool geometry_is_valid_impl(mapnik::geometry::geometry const& geom) +template +bool geometry_is_valid_impl(GeometryType const& geom) { return mapnik::geometry::is_valid(geom); } -bool geometry_is_simple_impl(mapnik::geometry::geometry const& geom) +template +bool geometry_is_simple_impl(GeometryType const& geom) { return mapnik::geometry::is_simple(geom); } -#endif -bool geometry_is_empty_impl(mapnik::geometry::geometry const& geom) +template +bool geometry_is_empty_impl(GeometryType const& geom) { return mapnik::geometry::is_empty(geom); } @@ -168,29 +148,16 @@ void geometry_correct_impl(mapnik::geometry::geometry & geom) mapnik::geometry::correct(geom); } -void line_string_add_coord_impl1(mapnik::geometry::line_string & l, double x, double y) -{ - l.emplace_back(x, y); -} - -void line_string_add_coord_impl2(mapnik::geometry::line_string & l, mapnik::geometry::point const& p) -{ - l.push_back(p); -} - -void linear_ring_add_coord_impl1(mapnik::geometry::linear_ring & l, double x, double y) -{ - l.emplace_back(x, y); -} - -void linear_ring_add_coord_impl2(mapnik::geometry::linear_ring & l, mapnik::geometry::point const& p) +template +void add_coord(T & geom, double x, double y) { - l.push_back(p); + geom.emplace_back(x, y); } -void polygon_add_ring_impl(mapnik::geometry::polygon & poly, mapnik::geometry::linear_ring const& ring) +template +void add_impl(Dst & geom, Src const& src) { - poly.push_back(ring); // copy + geom.push_back(src); // copy } mapnik::geometry::point geometry_centroid_impl(mapnik::geometry::geometry const& geom) @@ -201,14 +168,20 @@ mapnik::geometry::point geometry_centroid_impl(mapnik::geometry::geometr } -void export_geometry() +void export_geometry(py::module const& m) { - using namespace boost::python; + using mapnik::geometry::geometry; + using mapnik::geometry::point; + using mapnik::geometry::line_string; + using mapnik::geometry::linear_ring; + using mapnik::geometry::polygon; + using mapnik::geometry::multi_point; + using mapnik::geometry::multi_line_string; + using mapnik::geometry::multi_polygon; + using mapnik::geometry::geometry_collection; + - implicitly_convertible, mapnik::geometry::geometry >(); - implicitly_convertible, mapnik::geometry::geometry >(); - implicitly_convertible, mapnik::geometry::geometry >(); - enum_("GeometryType") + py::native_enum(m, "GeometryType", "enum.Enum") .value("Unknown",mapnik::geometry::geometry_types::Unknown) .value("Point",mapnik::geometry::geometry_types::Point) .value("LineString",mapnik::geometry::geometry_types::LineString) @@ -217,85 +190,172 @@ void export_geometry() .value("MultiLineString",mapnik::geometry::geometry_types::MultiLineString) .value("MultiPolygon",mapnik::geometry::geometry_types::MultiPolygon) .value("GeometryCollection",mapnik::geometry::geometry_types::GeometryCollection) + .finalize() ; - enum_("wkbByteOrder") - .value("XDR",mapnik::wkbXDR) - .value("NDR",mapnik::wkbNDR) + py::native_enum(m, "wkbByteOrder", "enum.Enum") + .value("XDR", mapnik::wkbXDR) + .value("NDR", mapnik::wkbNDR) + .finalize() ; - using mapnik::geometry::geometry; - using mapnik::geometry::point; - using mapnik::geometry::line_string; - using mapnik::geometry::linear_ring; - using mapnik::geometry::polygon; - class_ >("Point", init((arg("x"), arg("y")), - "Constructs a new Point object\n")) - .add_property("x", &point::x, "X coordinate") - .add_property("y", &point::y, "Y coordinate") -#if BOOST_VERSION >= 105800 - .def("is_valid", &geometry_is_valid_impl) - .def("is_simple", &geometry_is_simple_impl) -#endif - .def("to_geojson",&to_geojson_impl) - .def("to_wkb",&to_wkb_impl) - .def("to_wkt",&to_wkt_impl) + py::class_ >(m, "Point") + .def(py::init(), + "Constructs a new Point object\n", + py::arg("x"), py::arg("y")) + .def_readwrite("x", &point::x, "X coordinate") + .def_readwrite("y", &point::y, "Y coordinate") + .def("is_valid", &geometry_is_valid_impl>) + .def("is_simple", &geometry_is_simple_impl>) + .def("to_geojson",&to_geojson_impl>) + .def("to_wkb",&to_wkb_impl>) + .def("to_wkt",&to_wkt_impl>) + .def("envelope",&geometry_envelope_impl>) ; - class_ >("LineString", init<>( - "Constructs a new LineString object\n")) - .def("add_coord", &line_string_add_coord_impl1, "Adds coord x,y") - .def("add_point", &line_string_add_coord_impl2, "Adds point") -#if BOOST_VERSION >= 105800 - .def("is_valid", &geometry_is_valid_impl) - .def("is_simple", &geometry_is_simple_impl) -#endif - .def("to_geojson",&to_geojson_impl) - .def("to_wkb",&to_wkb_impl) - .def("to_wkt",&to_wkt_impl) + py::class_>(m, "MultiPoint") + .def(py::init<>(), + "Constructs a new MultiPoint object\n") + .def("add_point", &add_coord>, "Adds coord x,y") + .def("add_point", &add_impl, point>, "Adds mapnik.Point") + .def("is_valid", &geometry_is_valid_impl>) + .def("is_simple", &geometry_is_simple_impl>) + .def("to_geojson",&to_geojson_impl>) + .def("to_wkb",&to_wkb_impl>) + .def("to_wkt",&to_wkt_impl>) + .def("envelope",&geometry_envelope_impl>) + .def("num_points",[](multi_point const& mp) { return mp.size(); },"Number of points in MultiPoint") + .def("__len__", [](multi_pointconst &mp) { return mp.size(); }) + .def("__iter__", [](multi_point const& mp) { + return py::make_iterator(mp.begin(), mp.end()); + }, py::keep_alive<0, 1>()) ; - class_ >("LinearRing", init<>( - "Constructs a new LinearRtring object\n")) - .def("add_coord", &linear_ring_add_coord_impl1, "Adds coord x,y") - .def("add_point", &linear_ring_add_coord_impl2, "Adds point") + py::class_ >(m, "LineString") + .def(py::init<>(), "Constructs a new LineString object\n") + .def("add_point", &add_coord>, "Adds coord x,y") + .def("add_point", &add_impl, point>, "Adds mapnik.Point") + .def("is_valid", &geometry_is_valid_impl>) + .def("is_simple", &geometry_is_simple_impl>) + .def("to_geojson",&to_geojson_impl>) + .def("to_wkb",&to_wkb_impl>) + .def("to_wkt",&to_wkt_impl>) + .def("envelope",&geometry_envelope_impl>) + .def("num_points",[](line_string const& l) { return l.size(); },"Number of points in LineString") + .def("__len__", [](line_stringconst &l) { return l.size(); }) + .def("__iter__", [](line_string const& l) { + return py::make_iterator(l.begin(), l.end()); + }, py::keep_alive<0, 1>()) ; - class_ >("Polygon", init<>( - "Constructs a new Polygon object\n")) - .def("add_ring", &polygon_add_ring_impl, "Add ring") - .def("num_rings", &polygon::size, "Number of rings") -#if BOOST_VERSION >= 105800 - .def("is_valid", &geometry_is_valid_impl) - .def("is_simple", &geometry_is_simple_impl) -#endif - .def("to_geojson",&to_geojson_impl) - .def("to_wkb",&to_wkb_impl) - .def("to_wkt",&to_wkt_impl) + py::class_ >(m, "LinearRing") + .def(py::init<>(), "Constructs a new LinearRtring object\n") + .def("add_point", &add_coord>, "Adds coord x,y") + .def("add_point", &add_impl, point>, "Adds mapnik.Point") + .def("envelope",&geometry_envelope_impl>) + .def("__len__", [](linear_ringconst &r) { return r.size(); }) + .def("__iter__", [](linear_ring const& r) { + return py::make_iterator(r.begin(), r.end()); + }, py::keep_alive<0, 1>()) ; - class_, std::shared_ptr >, boost::noncopyable>("Geometry",no_init) - .def("envelope",&geometry_envelope_impl) - .def("from_geojson", from_geojson_impl) - .def("from_wkt", from_wkt_impl) - .def("from_wkb", from_wkb_impl) - .staticmethod("from_geojson") - .staticmethod("from_wkt") - .staticmethod("from_wkb") - .def("__str__",&to_wkt_impl) + py::class_ >(m, "Polygon") + .def(py::init<>(), "Constructs a new Polygon object\n") + .def("add_ring", &add_impl, linear_ring>, "Add ring") + .def("is_valid", &geometry_is_valid_impl>) + .def("is_simple", &geometry_is_simple_impl>) + .def("to_geojson",&to_geojson_impl>) + .def("to_wkb",&to_wkb_impl>) + .def("to_wkt",&to_wkt_impl>) + .def("envelope",&geometry_envelope_impl>) + .def("num_rings", [](polygonconst &p) { return p.size(); }, "Number of rings") + .def("__len__", [](polygonconst &p) { return p.size(); }) + .def("__iter__", [](polygon const& p) { + return py::make_iterator(p.begin(), p.end()); + }, py::keep_alive<0, 1>()) + ; + + py::class_ >(m, "MultiLineString") + .def(py::init<>(), "Constructs a new MultiLineString object\n") + .def("add_string", &add_impl, line_string>, "Add LineString") + .def("is_valid", &geometry_is_valid_impl>) + .def("is_simple", &geometry_is_simple_impl>) + .def("to_geojson",&to_geojson_impl>) + .def("to_wkb",&to_wkb_impl>) + .def("to_wkt",&to_wkt_impl>) + .def("envelope",&geometry_envelope_impl>) + .def("__len__", [](multi_line_stringconst& mls) { return mls.size(); }) + .def("__iter__", [](multi_line_string const& mls) { + return py::make_iterator(mls.begin(), mls.end()); + }, py::keep_alive<0, 1>()) + ; + + py::class_ >(m, "MultiPolygon") + .def(py::init<>(), "Constructs a new MultiPolygon object\n") + .def("add_polygon", &add_impl, polygon>, "Add Polygon") + .def("is_valid", &geometry_is_valid_impl>) + .def("is_simple", &geometry_is_simple_impl>) + .def("to_geojson",&to_geojson_impl>) + .def("to_wkb",&to_wkb_impl>) + .def("to_wkt",&to_wkt_impl>) + .def("envelope",&geometry_envelope_impl>) + .def("__len__", [](multi_polygonconst& mp) { return mp.size(); }) + .def("__iter__", [](multi_polygon const& mp) { + return py::make_iterator(mp.begin(), mp.end()); + }, py::keep_alive<0, 1>()) + ; + + py::class_ >(m, "GeometryCollection") + .def(py::init<>(), "Constructs a new GeometryCollection object\n") + .def("add_geometry", &add_impl, geometry>, "Add Geometry") + .def("is_valid", &geometry_is_valid_impl>) + .def("is_simple", &geometry_is_simple_impl>) + .def("to_geojson",&to_geojson_impl>) + .def("to_wkb",&to_wkb_impl>) + .def("to_wkt",&to_wkt_impl>) + .def("envelope",&geometry_envelope_impl>) + .def("__len__", [](geometry_collectionconst& gc) { return gc.size(); }) + .def("__iter__", [](geometry_collection const& gc) { + return py::make_iterator(gc.begin(), gc.end()); + }, py::keep_alive<0, 1>()) + ; + + py::class_, std::shared_ptr>>(m, "Geometry") + .def(py::init>()) + .def(py::init>()) + .def(py::init>()) + .def(py::init>()) + .def(py::init>()) + .def(py::init>()) + .def(py::init>()) + .def("envelope",&geometry_envelope_impl>) + .def_static("from_geojson", from_geojson_impl) + .def_static("from_wkt", from_wkt_impl) + .def_static("from_wkb", from_wkb_impl) + .def("__str__",&to_wkt_impl>) .def("type",&geometry_type_impl) -#if BOOST_VERSION >= 105800 - .def("is_valid", &geometry_is_valid_impl) - .def("is_simple", &geometry_is_simple_impl) -#endif - .def("is_empty", &geometry_is_empty_impl) + .def("is_valid", &geometry_is_valid_impl>) + .def("is_simple", &geometry_is_simple_impl>) + .def("is_empty", &geometry_is_empty_impl>) .def("correct", &geometry_correct_impl) .def("centroid",&geometry_centroid_impl) - .def("to_wkb",&to_wkb_impl) - .def("to_wkt",&to_wkt_impl) - .def("to_geojson",&to_geojson_impl) - //.def("to_svg",&to_svg) - // TODO add other geometry_type methods + .def("to_wkb",&to_wkb_impl>) + .def("to_wkt",&to_wkt_impl>) + .def("to_json",&to_geojson_impl>) + .def("to_geojson",&to_geojson_impl>) + .def_property_readonly("__geo_interface__", [](geometry const& g) { + py::object json = py::module_::import("json"); + py::object loads = json.attr("loads"); + return loads(to_geojson_impl>(g));}) ; + + py::implicitly_convertible, mapnik::geometry::geometry>(); + py::implicitly_convertible, mapnik::geometry::geometry>(); + py::implicitly_convertible, mapnik::geometry::geometry>(); + py::implicitly_convertible, mapnik::geometry::geometry>(); + py::implicitly_convertible, mapnik::geometry::geometry>(); + py::implicitly_convertible, mapnik::geometry::geometry>(); + py::implicitly_convertible, mapnik::geometry::geometry>(); + } diff --git a/src/mapnik_grid.cpp b/src/mapnik_grid.cpp index 03a1d0f9b..973159f1d 100644 --- a/src/mapnik_grid.cpp +++ b/src/mapnik_grid.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,25 +21,18 @@ *****************************************************************************/ #if defined(GRID_RENDERER) - -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#include -#pragma GCC diagnostic pop - // mapnik +#include #include #include "python_grid_utils.hpp" -using namespace boost::python; +//pybind11 +#include + +namespace py = pybind11; // help compiler see template definitions -static dict (*encode)( mapnik::grid const&, std::string const& , bool, unsigned int) = mapnik::grid_encode; +static py::dict (*encode)( mapnik::grid const&, std::string const& , bool, unsigned int) = mapnik::grid_encode; bool painted(mapnik::grid const& grid) { @@ -53,32 +46,27 @@ mapnik::grid::value_type get_pixel(mapnik::grid const& grid, int x, int y) mapnik::grid::data_type const & data = grid.data(); return data(x,y); } - PyErr_SetString(PyExc_IndexError, "invalid x,y for grid dimensions"); - boost::python::throw_error_already_set(); - return 0; + throw py::index_error("invalid x,y for grid dimensions"); } -void export_grid() +void export_grid(py::module const& m) { - class_ >( - "Grid", - "This class represents a feature hitgrid.", - init( - ( boost::python::arg("width"), boost::python::arg("height"),boost::python::arg("key")="__id__"), - "Create a mapnik.Grid object\n" - )) + py::class_> + (m, "Grid", "This class represents a feature hitgrid.") + .def(py::init(), + "Create a mapnik.Grid object\n", + py::arg("width"), py::arg("height"), py::arg("key")="__id__") .def("painted",&painted) .def("width",&mapnik::grid::width) .def("height",&mapnik::grid::height) .def("view",&mapnik::grid::get_view) .def("get_pixel",&get_pixel) .def("clear",&mapnik::grid::clear) - .def("encode",encode, - ( boost::python::arg("encoding")="utf", boost::python::arg("features")=true,boost::python::arg("resolution")=4 ), - "Encode the grid as as optimized json\n" - ) - .add_property("key", - make_function(&mapnik::grid::get_key,return_value_policy()), + .def("encode", encode, + "Encode the grid as as optimized json\n", + py::arg("encoding") = "utf", py::arg("features") = true, py::arg("resolution") = 4) + .def_property("key", + &mapnik::grid::get_key, &mapnik::grid::set_key, "Get/Set key to be used as unique indentifier for features\n" "The value should either be __id__ to refer to the feature.id()\n" diff --git a/src/mapnik_grid_view.cpp b/src/mapnik_grid_view.cpp index b0c9c2b52..2b0bb0411 100644 --- a/src/mapnik_grid_view.cpp +++ b/src/mapnik_grid_view.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,39 +21,25 @@ *****************************************************************************/ #if defined(GRID_RENDERER) - -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#include -#pragma GCC diagnostic pop - // mapnik +#include #include #include #include #include "python_grid_utils.hpp" -using namespace boost::python; - // help compiler see template definitions -static dict (*encode)( mapnik::grid_view const&, std::string const& , bool, unsigned int) = mapnik::grid_encode; +static py::dict (*encode)( mapnik::grid_view const&, std::string const& , bool, unsigned int) = mapnik::grid_encode; -void export_grid_view() +void export_grid_view(py::module const& m) { - class_ >("GridView", - "This class represents a feature hitgrid subset.",no_init) + py::class_> + (m, "GridView", "This class represents a feature hitgrid subset.") .def("width",&mapnik::grid_view::width) .def("height",&mapnik::grid_view::height) .def("encode",encode, - ( boost::python::arg("encoding")="utf",boost::python::arg("add_features")=true,boost::python::arg("resolution")=4 ), - "Encode the grid as as optimized json\n" - ) + "Encode the grid as as optimized json\n", + py::arg("encoding")="utf",py::arg("add_features")=true,py::arg("resolution")=4) ; } diff --git a/src/mapnik_enumeration_wrapper_converter.hpp b/src/mapnik_group_symbolizer.cpp similarity index 62% rename from src/mapnik_enumeration_wrapper_converter.hpp rename to src/mapnik_group_symbolizer.cpp index cf6edbfa1..be818a0b7 100644 --- a/src/mapnik_enumeration_wrapper_converter.hpp +++ b/src/mapnik_group_symbolizer.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 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,28 +20,26 @@ * *****************************************************************************/ -#ifndef MAPNIK_BINDINGS_PYTHON_ENUMERATION_WRAPPPER -#define MAPNIK_BINDINGS_PYTHON_ENUMERATION_WRAPPPER - // mapnik +#include #include - -#pragma GCC diagnostic push -#include -#include -#pragma GCC diagnostic pop - - -namespace boost { namespace python { - - struct mapnik_enumeration_wrapper_to_python - { - static PyObject* convert(mapnik::enumeration_wrapper const& v) - { - return ::PyLong_FromLongLong(v.value); // FIXME: this is a temp hack!! - } - }; - -}} - -#endif // MAPNIK_BINDINGS_PYTHON_ENUMERATION_WRAPPPER +#include +#include +#include +#include "mapnik_symbolizer.hpp" +//pybind11 +#include + +namespace py = pybind11; + +void export_group_symbolizer(py::module const& m) +{ + using namespace python_mapnik; + using mapnik::group_symbolizer; + + py::class_(m, "GroupSymbolizer") + .def(py::init<>(), "Default ctor") + .def("__hash__", hash_impl_2) + ; + +} diff --git a/src/mapnik_image.cpp b/src/mapnik_image.cpp index 9add692c9..b66f29f07 100644 --- a/src/mapnik_image.cpp +++ b/src/mapnik_image.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,17 +20,8 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#include -#pragma GCC diagnostic pop - // mapnik +#include #include #include #include @@ -38,19 +29,12 @@ #include #include #include - -// cairo -#if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO) -#include -#include -#if PY_MAJOR_VERSION >= 3 -#define PYCAIRO_NO_IMPORT -#include -#else -#include -#endif -#include -#endif +//stl +#include +//pybind11 +#include +#include +#include using mapnik::image_any; using mapnik::image_reader; @@ -58,46 +42,28 @@ using mapnik::get_image_reader; using mapnik::type_from_filename; using mapnik::save_to_file; -using namespace boost::python; +namespace py = pybind11; +namespace { // output 'raw' pixels -PyObject* tostring1( image_any const& im) +py::object to_string1(image_any const& im) { - return -#if PY_VERSION_HEX >= 0x03000000 - ::PyBytes_FromStringAndSize -#else - ::PyString_FromStringAndSize -#endif - ((const char*)im.bytes(),im.size()); + return py::bytes(reinterpret_cast(im.bytes()), im.size()); } // encode (png,jpeg) -PyObject* tostring2(image_any const & im, std::string const& format) +py::object to_string2(image_any const & im, std::string const& format) { std::string s = mapnik::save_to_string(im, format); - return -#if PY_VERSION_HEX >= 0x03000000 - ::PyBytes_FromStringAndSize -#else - ::PyString_FromStringAndSize -#endif - (s.data(),s.size()); + return py::bytes(s.data(), s.length()); } -PyObject* tostring3(image_any const & im, std::string const& format, mapnik::rgba_palette const& pal) +py::object to_string3(image_any const & im, std::string const& format, mapnik::rgba_palette const& pal) { std::string s = mapnik::save_to_string(im, format, pal); - return -#if PY_VERSION_HEX >= 0x03000000 - ::PyBytes_FromStringAndSize -#else - ::PyString_FromStringAndSize -#endif - (s.data(),s.size()); + return py::bytes(s.data(), s.length()); } - void save_to_file1(mapnik::image_any const& im, std::string const& filename) { save_to_file(im,filename); @@ -153,84 +119,62 @@ struct get_pixel_visitor get_pixel_visitor(unsigned x, unsigned y) : x_(x), y_(y) {} - object operator() (mapnik::image_null const&) + py::object operator() (mapnik::image_null const&) { throw std::runtime_error("Can not return a null image from a pixel (shouldn't have reached here)"); } template - object operator() (T const& im) + py::object operator() (T const& im) { using pixel_type = typename T::pixel_type; - return object(mapnik::get_pixel(im, x_, y_)); + using python_type = typename std::conditional::value, py::int_, py::float_>::type; + return python_type(mapnik::get_pixel(im, x_, y_)); } - private: unsigned x_; unsigned y_; }; -object get_pixel(mapnik::image_any const& im, unsigned x, unsigned y, bool get_color) -{ - if (x < static_cast(im.width()) && y < static_cast(im.height())) - { - if (get_color) - { - return object( - mapnik::get_pixel(im, x, y) - ); - } - else - { - return mapnik::util::apply_visitor(get_pixel_visitor(x, y), im); - } - } - PyErr_SetString(PyExc_IndexError, "invalid x,y for image dimensions"); - boost::python::throw_error_already_set(); - return object(); -} - -void set_pixel_color(mapnik::image_any & im, unsigned x, unsigned y, mapnik::color const& c) +py::object get_pixel(mapnik::image_any const& im, int x, int y) { - if (x >= static_cast(im.width()) && y >= static_cast(im.height())) + if (x < 0 || x >= static_cast(im.width()) || + y < 0 || y >= static_cast(im.height())) { - PyErr_SetString(PyExc_IndexError, "invalid x,y for image dimensions"); - boost::python::throw_error_already_set(); - return; + throw std::out_of_range("invalid x,y for image dimensions"); } - mapnik::set_pixel(im, x, y, c); + return mapnik::util::apply_visitor(get_pixel_visitor(x, y), im); } -void set_pixel_double(mapnik::image_any & im, unsigned x, unsigned y, double val) +mapnik::color get_pixel_color(mapnik::image_any const& im, int x, int y) { - if (x >= static_cast(im.width()) && y >= static_cast(im.height())) + if (x < 0 || x >= static_cast(im.width()) || + y < 0 || y >= static_cast(im.height())) { - PyErr_SetString(PyExc_IndexError, "invalid x,y for image dimensions"); - boost::python::throw_error_already_set(); - return; + throw std::out_of_range("invalid x,y for image dimensions"); } - mapnik::set_pixel(im, x, y, val); + return mapnik::get_pixel(im, x, y); } -void set_pixel_int(mapnik::image_any & im, unsigned x, unsigned y, int val) +template +void set_pixel(mapnik::image_any & im, int x, int y, T c) { - if (x >= static_cast(im.width()) && y >= static_cast(im.height())) + if (x < 0 || x >= static_cast(im.width()) || + y < 0 || y >= static_cast(im.height())) { - PyErr_SetString(PyExc_IndexError, "invalid x,y for image dimensions"); - boost::python::throw_error_already_set(); - return; + throw std::out_of_range("invalid x,y for image dimensions"); } - mapnik::set_pixel(im, x, y, val); + mapnik::set_pixel(im, x, y, c); } -unsigned get_type(mapnik::image_any & im) +mapnik::image_dtype get_type(mapnik::image_any & im) { return im.get_dtype(); } std::shared_ptr open_from_file(std::string const& filename) { - boost::optional type = type_from_filename(filename); + auto type = type_from_filename(filename); if (type) { std::unique_ptr reader(get_image_reader(filename,*type)); @@ -243,7 +187,28 @@ std::shared_ptr open_from_file(std::string const& filename) throw mapnik::image_reader_exception("Unsupported image format:" + filename); } -std::shared_ptr fromstring(std::string const& str) +std::shared_ptr open_from_file2(py::args const& args) +{ + auto filename = args[0].cast(); + std::uint32_t x0 = args[1].cast(); + std::uint32_t y0 = args[2].cast(); + std::uint32_t width = args[3].cast(); + std::uint32_t height = args[4].cast(); + auto type = type_from_filename(filename); + + if (type) + { + std::unique_ptr reader(get_image_reader(filename,*type)); + if (reader.get()) + { + return std::make_shared(reader->read(x0, y0, width, height)); + } + throw mapnik::image_reader_exception("Failed to load: " + filename); + } + throw mapnik::image_reader_exception("Unsupported image format:" + filename); +} + +std::shared_ptr from_string(std::string const& str) { std::unique_ptr reader(get_image_reader(str.c_str(),str.size())); if (reader.get()) @@ -253,31 +218,27 @@ std::shared_ptr fromstring(std::string const& str) throw mapnik::image_reader_exception("Failed to load image from String" ); } -namespace { -struct view_release +std::shared_ptr from_buffer(py::bytes const& obj) { - view_release(Py_buffer & view) - : view_(view) {} - ~view_release() + std::string_view view = std::string_view(obj); + std::unique_ptr reader + (get_image_reader(reinterpret_cast(view.data()), view.length())); + if (reader.get()) { - PyBuffer_Release(&view_); + return std::make_shared(reader->read(0, 0, reader->width(), reader->height())); } - Py_buffer & view_; -}; + throw mapnik::image_reader_exception("Failed to load image from Buffer" ); } -std::shared_ptr frombuffer(PyObject * obj) +std::shared_ptr from_memoryview(py::memoryview const& memview) { - Py_buffer view; - view_release helper(view); - if (obj != nullptr && PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) == 0) + auto buf = py::buffer(memview); + py::buffer_info info = buf.request(); + std::unique_ptr reader + (get_image_reader(reinterpret_cast(info.ptr), info.size)); + if (reader.get()) { - std::unique_ptr reader - (get_image_reader(reinterpret_cast(view.buf), view.len)); - if (reader.get()) - { - return std::make_shared(reader->read(0,0,reader->width(),reader->height())); - } + return std::make_shared(reader->read(0, 0, reader->width(), reader->height())); } throw mapnik::image_reader_exception("Failed to load image from Buffer" ); } @@ -337,60 +298,70 @@ void composite(image_any & dst, image_any & src, mapnik::composite_mode_e mode, } } -#if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO) -std::shared_ptr from_cairo(PycairoSurface* py_surface) -{ - mapnik::cairo_surface_ptr surface(cairo_surface_reference(py_surface->surface), mapnik::cairo_surface_closer()); - mapnik::image_rgba8 image = mapnik::image_rgba8(cairo_image_surface_get_width(&*surface), cairo_image_surface_get_height(&*surface)); - cairo_image_to_rgba8(image, surface); - return std::make_shared(std::move(image)); -} -#endif - -void export_image() -{ - using namespace boost::python; - // NOTE: must match list in include/mapnik/image_compositing.hpp - enum_("CompositeOp") - .value("clear", mapnik::clear) - .value("src", mapnik::src) - .value("dst", mapnik::dst) - .value("src_over", mapnik::src_over) - .value("dst_over", mapnik::dst_over) - .value("src_in", mapnik::src_in) - .value("dst_in", mapnik::dst_in) - .value("src_out", mapnik::src_out) - .value("dst_out", mapnik::dst_out) - .value("src_atop", mapnik::src_atop) - .value("dst_atop", mapnik::dst_atop) - .value("xor", mapnik::_xor) - .value("plus", mapnik::plus) - .value("minus", mapnik::minus) - .value("multiply", mapnik::multiply) - .value("screen", mapnik::screen) - .value("overlay", mapnik::overlay) - .value("darken", mapnik::darken) - .value("lighten", mapnik::lighten) - .value("color_dodge", mapnik::color_dodge) - .value("color_burn", mapnik::color_burn) - .value("hard_light", mapnik::hard_light) - .value("soft_light", mapnik::soft_light) - .value("difference", mapnik::difference) - .value("exclusion", mapnik::exclusion) - .value("contrast", mapnik::contrast) - .value("invert", mapnik::invert) - .value("grain_merge", mapnik::grain_merge) - .value("grain_extract", mapnik::grain_extract) - .value("hue", mapnik::hue) - .value("saturation", mapnik::saturation) - .value("color", mapnik::_color) - .value("value", mapnik::_value) - .value("linear_dodge", mapnik::linear_dodge) - .value("linear_burn", mapnik::linear_burn) - .value("divide", mapnik::divide) - ; +std::shared_ptr from_cairo(py::object const& surface) +{ + py::object ImageSurface = py::module_::import("cairo").attr("ImageSurface"); + py::object get_width = ImageSurface.attr("get_width"); + py::object get_height = ImageSurface.attr("get_height"); + py::object get_format = ImageSurface.attr("get_format"); + py::object get_data = ImageSurface.attr("get_data"); + int format = py::int_(get_format(surface)); + int width = py::int_(get_width(surface)); + int height = py::int_(get_height(surface)); + if (format == 0 ) // cairo.Format.ARGB32 + { + mapnik::image_rgba8 image{width, height}; + py::memoryview view = get_data(surface); + auto buf = py::buffer(view); + py::buffer_info info = buf.request(); + const std::unique_ptr out_row(new unsigned int[width]); + unsigned int const* in_row = reinterpret_cast(info.ptr); + for (int row = 0; row < height; row++, in_row += width) + { + for (int column = 0; column < width; column++) + { + unsigned int in = in_row[column]; + unsigned int a = (in >> 24) & 0xff; + unsigned int r = (in >> 16) & 0xff; + unsigned int g = (in >> 8) & 0xff; + unsigned int b = (in >> 0) & 0xff; + out_row[column] = mapnik::color(r, g, b, a).rgba(); + } + image.set_row(row, out_row.get(), width); + } + return std::make_shared(std::move(image)); + } + else if (format == 1 ) // cairo.Format.RGB24 + { + mapnik::image_rgba8 image{width, height}; + py::memoryview view = get_data(surface); + auto buf = py::buffer(view); + py::buffer_info info = buf.request(); + const std::unique_ptr out_row(new unsigned int[width]); + unsigned int const* in_row = reinterpret_cast(info.ptr); + for (int row = 0; row < height; row++, in_row += width) + { + for (int column = 0; column < width; column++) + { + unsigned int in = in_row[column]; + unsigned int r = (in >> 16) & 0xff; + unsigned int g = (in >> 8) & 0xff; + unsigned int b = (in >> 0) & 0xff; + out_row[column] = mapnik::color(r, g, b, 255).rgba(); + } + image.set_row(row, out_row.get(), width); + } + return std::make_shared(std::move(image)); + } + + throw std::runtime_error("Unable to convert this Cairo format to rgba8 image"); +} - enum_("ImageType") +} // namespace + +void export_image(py::module const& m) +{ + py::native_enum(m, "ImageType", "enum.Enum") .value("rgba8", mapnik::image_dtype_rgba8) .value("gray8", mapnik::image_dtype_gray8) .value("gray8s", mapnik::image_dtype_gray8s) @@ -402,13 +373,15 @@ void export_image() .value("gray64", mapnik::image_dtype_gray64) .value("gray64s", mapnik::image_dtype_gray64s) .value("gray64f", mapnik::image_dtype_gray64f) + .finalize() ; - class_, boost::noncopyable >("Image","This class represents a image.",init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) + py::class_>(m, "Image","This class represents a image.") + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) .def("width",&image_any::width) .def("height",&image_any::height) .def("view",&get_view) @@ -422,65 +395,53 @@ void export_image() .def("set_color_to_alpha",&set_color_to_alpha, "Set a given color to the alpha channel of the Image") .def("apply_opacity",&apply_opacity, "Set the opacity of the Image relative to the current alpha of each pixel.") .def("composite",&composite, - ( arg("self"), - arg("image"), - arg("mode")=mapnik::src_over, - arg("opacity")=1.0f, - arg("dx")=0, - arg("dy")=0 - )) + py::arg("image"), + py::arg("mode") = mapnik::src_over, + py::arg("opacity") = 1.0f, + py::arg("dx") = 0, + py::arg("dy") = 0 + ) .def("compare",&compare, - ( arg("self"), - arg("image"), - arg("threshold")=0.0, - arg("alpha")=true - )) + py::arg("image"), + py::arg("threshold")=0.0, + py::arg("alpha")=true + ) .def("copy",©, - ( arg("self"), - arg("type"), - arg("offset")=0.0, - arg("scaling")=1.0 - )) - .add_property("offset", + py::arg("type"), + py::arg("offset")=0.0, + py::arg("scaling")=1.0 + ) + .def_property("offset", &image_any::get_offset, &image_any::set_offset, "Gets or sets the offset component.\n") - .add_property("scaling", + .def_property("scaling", &image_any::get_scaling, &image_any::set_scaling, "Gets or sets the offset component.\n") .def("premultiplied",&premultiplied) .def("premultiply",&premultiply) .def("demultiply",&demultiply) - .def("set_pixel",&set_pixel_color) - .def("set_pixel",&set_pixel_double) - .def("set_pixel",&set_pixel_int) - .def("get_pixel",&get_pixel, - ( arg("self"), - arg("x"), - arg("y"), - arg("get_color")=false - )) + .def("set_pixel",&set_pixel) + .def("set_pixel",&set_pixel) + .def("set_pixel",&set_pixel) + .def("get_pixel_color",&get_pixel_color, + py::arg("x"), py::arg("y")) + .def("get_pixel", &get_pixel) .def("get_type",&get_type) .def("clear",&clear) - //TODO(haoyu) The method name 'tostring' might be confusing since they actually return bytes in Python 3 - - .def("tostring",&tostring1) - .def("tostring",&tostring2) - .def("tostring",&tostring3) + .def("to_string",&to_string1) + .def("to_string",&to_string2) + .def("to_string",&to_string3) .def("save", &save_to_file1) .def("save", &save_to_file2) .def("save", &save_to_file3) - .def("open",open_from_file) - .staticmethod("open") - .def("frombuffer",&frombuffer) - .staticmethod("frombuffer") - .def("fromstring",&fromstring) - .staticmethod("fromstring") -#if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO) - .def("from_cairo",&from_cairo) - .staticmethod("from_cairo") -#endif + .def_static("open",open_from_file) + .def_static("open",open_from_file2) + .def_static("from_buffer",&from_buffer) + .def_static("from_memoryview",&from_memoryview) + .def_static("from_string",&from_string) + .def_static("from_cairo",&from_cairo) ; } diff --git a/src/mapnik_image_view.cpp b/src/mapnik_image_view.cpp index a6afd5bed..2359efe09 100644 --- a/src/mapnik_image_view.cpp +++ b/src/mapnik_image_view.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,64 +20,44 @@ * *****************************************************************************/ +//mapnik #include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#include -#pragma GCC diagnostic pop - -// mapnik #include #include #include #include #include +#include +//stl #include +//pybind11 +#include +#include using mapnik::image_view_any; using mapnik::save_to_file; +namespace py = pybind11; + // output 'raw' pixels -PyObject* view_tostring1(image_view_any const& view) +py::object view_tostring1(image_view_any const& view) { std::ostringstream ss(std::ios::out|std::ios::binary); mapnik::view_to_stream(view, ss); - return -#if PY_VERSION_HEX >= 0x03000000 - ::PyBytes_FromStringAndSize -#else - ::PyString_FromStringAndSize -#endif - ((const char*)ss.str().c_str(),ss.str().size()); + return py::bytes(ss.str().c_str(), ss.str().size()); } // encode (png,jpeg) -PyObject* view_tostring2(image_view_any const & view, std::string const& format) +py::object view_tostring2(image_view_any const & view, std::string const& format) { std::string s = save_to_string(view, format); - return -#if PY_VERSION_HEX >= 0x03000000 - ::PyBytes_FromStringAndSize -#else - ::PyString_FromStringAndSize -#endif - (s.data(),s.size()); + return py::bytes(s.data(), s.length()); } -PyObject* view_tostring3(image_view_any const & view, std::string const& format, mapnik::rgba_palette const& pal) +py::object view_tostring3(image_view_any const & view, std::string const& format, mapnik::rgba_palette const& pal) { std::string s = save_to_string(view, format, pal); - return -#if PY_VERSION_HEX >= 0x03000000 - ::PyBytes_FromStringAndSize -#else - ::PyString_FromStringAndSize -#endif - (s.data(),s.size()); + return py::bytes(s.data(), s.length()); } bool is_solid(image_view_any const& view) @@ -107,16 +87,15 @@ void save_view3(image_view_any const& view, } -void export_image_view() +void export_image_view(py::module const& m) { - using namespace boost::python; - class_("ImageView","A view into an image.",no_init) + py::class_(m, "ImageView", "A view into an image.") .def("width",&image_view_any::width) .def("height",&image_view_any::height) .def("is_solid",&is_solid) - .def("tostring",&view_tostring1) - .def("tostring",&view_tostring2) - .def("tostring",&view_tostring3) + .def("to_string",&view_tostring1) + .def("to_string",&view_tostring2) + .def("to_string",&view_tostring3) .def("save",&save_view1) .def("save",&save_view2) .def("save",&save_view3) diff --git a/src/mapnik_label_collision_detector.cpp b/src/mapnik_label_collision_detector.cpp index 629fb0f6d..567db60b8 100644 --- a/src/mapnik_label_collision_detector.cpp +++ b/src/mapnik_label_collision_detector.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 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,20 +20,14 @@ * *****************************************************************************/ +//mapnik #include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#include -#pragma GCC diagnostic pop - #include #include +//pybind11 +#include -#include +namespace py = pybind11; using mapnik::label_collision_detector4; using mapnik::box2d; @@ -42,69 +36,64 @@ using mapnik::Map; namespace { -std::shared_ptr -create_label_collision_detector_from_extent(box2d const &extent) +std::shared_ptr create_label_collision_detector_from_extent(box2d const &extent) { return std::make_shared(extent); } -std::shared_ptr -create_label_collision_detector_from_map(Map const &m) +std::shared_ptr create_label_collision_detector_from_map (Map const &m) { double buffer = m.buffer_size(); box2d extent(-buffer, -buffer, m.width() + buffer, m.height() + buffer); return std::make_shared(extent); } -boost::python::list -make_label_boxes(std::shared_ptr det) -{ - boost::python::list boxes; +py::list make_label_boxes(std::shared_ptr det) +{ + py::list boxes; for (label_collision_detector4::query_iterator jtr = det->begin(); jtr != det->end(); ++jtr) { - boxes.append >(jtr->get().box); + boxes.append(jtr->get().box); } - return boxes; } } -void export_label_collision_detector() +void export_label_collision_detector(py::module const& m) { - using namespace boost::python; - // for overload resolution void (label_collision_detector4::*insert_box)(box2d const &) = &label_collision_detector4::insert; - class_, boost::noncopyable> - ("LabelCollisionDetector", - "Object to detect collisions between labels, used in the rendering process.", - no_init) - - .def("__init__", make_constructor(create_label_collision_detector_from_extent), - "Creates an empty collision detection object with a given extent. Note " - "that the constructor from Map objects is a sensible default and usually " - "what you want to do.\n" - "\n" - "Example:\n" - ">>> m = Map(size_x, size_y)\n" - ">>> buf_sz = m.buffer_size\n" - ">>> extent = mapnik.Box2d(-buf_sz, -buf_sz, m.width + buf_sz, m.height + buf_sz)\n" - ">>> detector = mapnik.LabelCollisionDetector(extent)") - - .def("__init__", make_constructor(create_label_collision_detector_from_map), - "Creates an empty collision detection object matching the given Map object. " - "The created detector will have the same size, including the buffer, as the " - "map object. This is usually what you want to do.\n" - "\n" - "Example:\n" - ">>> m = Map(size_x, size_y)\n" - ">>> detector = mapnik.LabelCollisionDetector(m)") - - .def("extent", &label_collision_detector4::extent, return_value_policy(), + py::class_> + (m, "LabelCollisionDetector", + "Object to detect collisions between labels, used in the rendering process.") + + .def(py::init([](box2d const& box) { + return create_label_collision_detector_from_extent(box);}), + "Creates an empty collision detection object with a given extent. Note " + "that the constructor from Map objects is a sensible default and usually " + "what you want to do.\n" + "\n" + "Example:\n" + ">>> m = Map(size_x, size_y)\n" + ">>> buf_sz = m.buffer_size\n" + ">>> extent = mapnik.Box2d(-buf_sz, -buf_sz, m.width + buf_sz, m.height + buf_sz)\n" + ">>> detector = mapnik.LabelCollisionDetector(extent)") + + .def(py::init([](mapnik::Map const& m){ + return create_label_collision_detector_from_map(m);}), + "Creates an empty collision detection object matching the given Map object. " + "The created detector will have the same size, including the buffer, as the " + "map object. This is usually what you want to do.\n" + "\n" + "Example:\n" + ">>> m = Map(size_x, size_y)\n" + ">>> detector = mapnik.LabelCollisionDetector(m)") + + .def("extent", &label_collision_detector4::extent, "Returns the total extent (bounding box) of all labels inside the detector.\n" "\n" "Example:\n" diff --git a/src/mapnik_layer.cpp b/src/mapnik_layer.cpp index a7caf38d3..d8a2a782b 100644 --- a/src/mapnik_layer.cpp +++ b/src/mapnik_layer.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,144 +20,47 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - // mapnik +#include #include #include #include +//pybind11 +#include +#include +#include +#include + +namespace py = pybind11; using mapnik::layer; using mapnik::parameters; using mapnik::datasource_cache; +PYBIND11_MAKE_OPAQUE(std::vector); -struct layer_pickle_suite : boost::python::pickle_suite -{ - static boost::python::tuple - getinitargs(const layer& l) - { - return boost::python::make_tuple(l.name(),l.srs()); - } - - static boost::python::tuple - getstate(const layer& l) - { - boost::python::list s; - std::vector const& style_names = l.styles(); - for (unsigned i = 0; i < style_names.size(); ++i) - { - s.append(style_names[i]); - } - return boost::python::make_tuple(l.clear_label_cache(),l.minimum_scale_denominator(),l.maximum_scale_denominator(),l.queryable(),l.datasource()->params(),l.cache_features(),s); - } - - static void - setstate (layer& l, boost::python::tuple state) - { - using namespace boost::python; - if (len(state) != 9) - { - PyErr_SetObject(PyExc_ValueError, - ("expected 9-item tuple in call to __setstate__; got %s" - % state).ptr() - ); - throw_error_already_set(); - } - - l.set_clear_label_cache(extract(state[0])); - - l.set_minimum_scale_denominator(extract(state[1])); - - l.set_maximum_scale_denominator(extract(state[2])); - - l.set_queryable(extract(state[3])); - - mapnik::parameters params = extract(state[4]); - l.set_datasource(datasource_cache::instance().create(params)); - - boost::python::list s = extract(state[5]); - for (int i=0;i(s[i])); - } - - l.set_cache_features(extract(state[6])); - } -}; - -std::vector & (mapnik::layer::*_styles_)() = &mapnik::layer::styles; - -void set_maximum_extent(mapnik::layer & l, boost::optional > const& box) -{ - if (box) - { - l.set_maximum_extent(*box); - } - else - { - l.reset_maximum_extent(); - } -} - -void set_buffer_size(mapnik::layer & l, boost::optional const& buffer_size) -{ - if (buffer_size) - { - l.set_buffer_size(*buffer_size); - } - else - { - l.reset_buffer_size(); - } -} +std::vector & (mapnik::layer::*set_styles_)() = &mapnik::layer::styles; +std::vector const& (mapnik::layer::*get_styles_)() const = &mapnik::layer::styles; -PyObject * get_buffer_size(mapnik::layer & l) +void export_layer(py::module const& m) { - boost::optional buffer_size = l.buffer_size(); - if (buffer_size) - { -#if PY_VERSION_HEX >= 0x03000000 - return PyLong_FromLong(*buffer_size); -#else - return PyInt_FromLong(*buffer_size); -#endif - } - else - { - Py_RETURN_NONE; - } -} - -void export_layer() -{ - using namespace boost::python; - class_ >("Names") - .def(vector_indexing_suite,true >()) - ; - - class_("Layer", "A Mapnik map layer.", init >( - "Create a Layer with a named string and, optionally, an srs string.\n" - "\n" - "The srs can be either a Proj.4 epsg code ('+init=epsg:') or\n" - "of a Proj.4 literal ('+proj=').\n" - "If no srs is specified it will default to '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs'\n" - "\n" - "Usage:\n" - ">>> from mapnik import Layer\n" - ">>> lyr = Layer('My Layer','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')\n" - ">>> lyr\n" - "\n" - )) + py::bind_vector>(m, "StyleNames", py::module_local()); - .def_pickle(layer_pickle_suite()) + py::class_(m, "Layer", "A Mapnik map layer.") + .def(py::init(), + "Create a Layer with a named string and, optionally, an srs string.\n" + "\n" + "The srs can be either a Proj epsg code ('epsg:') or\n" + "of a Proj literal ('+proj=').\n" + "If no srs is specified it will default to 'epsg:4326'\n" + "\n" + "Usage:\n" + ">>> from mapnik import Layer\n" + ">>> lyr = Layer('My Layer','epsg:4326')\n" + ">>> lyr\n" + "\n", + py::arg("name"), py::arg("srs") = mapnik::MAPNIK_GEOGRAPHIC_PROJ + ) .def("envelope",&layer::envelope, "Return the geographic envelope/bounding box." @@ -166,7 +69,7 @@ void export_layer() "\n" "Usage:\n" ">>> from mapnik import Layer\n" - ">>> lyr = Layer('My Layer','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')\n" + ">>> lyr = Layer('My Layer','epsg:4326')\n" ">>> lyr.envelope()\n" "box2d(-1.0,-1.0,0.0,0.0) # default until a datasource is loaded\n" ) @@ -183,7 +86,7 @@ void export_layer() "\n" "Usage:\n" ">>> from mapnik import Layer\n" - ">>> lyr = Layer('My Layer','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')\n" + ">>> lyr = Layer('My Layer','epsg:4326')\n" ">>> lyr.visible(1.0/1000000)\n" "True\n" ">>> lyr.active = False\n" @@ -191,14 +94,14 @@ void export_layer() "False\n" ) - .add_property("active", + .def_property("active", &layer::active, &layer::set_active, "Get/Set whether this layer is active and will be rendered (same as status property).\n" "\n" "Usage:\n" ">>> from mapnik import Layer\n" - ">>> lyr = Layer('My Layer','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')\n" + ">>> lyr = Layer('My Layer','epsg:4326')\n" ">>> lyr.active\n" "True # Active by default\n" ">>> lyr.active = False # set False to disable layer rendering\n" @@ -206,14 +109,14 @@ void export_layer() "False\n" ) - .add_property("status", + .def_property("status", &layer::active, &layer::set_active, "Get/Set whether this layer is active and will be rendered.\n" "\n" "Usage:\n" ">>> from mapnik import Layer\n" - ">>> lyr = Layer('My Layer','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')\n" + ">>> lyr = Layer('My Layer','epsg:4326')\n" ">>> lyr.status\n" "True # Active by default\n" ">>> lyr.status = False # set False to disable layer rendering\n" @@ -221,7 +124,7 @@ void export_layer() "False\n" ) - .add_property("clear_label_cache", + .def_property("clear_label_cache", &layer::clear_label_cache, &layer::set_clear_label_cache, "Get/Set whether to clear the label collision detector cache for this layer during rendering\n" @@ -232,7 +135,7 @@ void export_layer() ">>> lyr.clear_label_cache = True # set to True to clear the label collision detector cache\n" ) - .add_property("cache_features", + .def_property("cache_features", &layer::cache_features, &layer::set_cache_features, "Get/Set whether features should be cached during rendering if used between multiple styles\n" @@ -243,22 +146,22 @@ void export_layer() ">>> lyr.cache_features = True # set to True to enable feature caching\n" ) - .add_property("datasource", + .def_property("datasource", &layer::datasource, &layer::set_datasource, "The datasource attached to this layer.\n" "\n" "Usage:\n" ">>> from mapnik import Layer, Datasource\n" - ">>> lyr = Layer('My Layer','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')\n" + ">>> lyr = Layer('My Layer','epsg:4326')\n" ">>> lyr.datasource = Datasource(type='shape',file='world_borders')\n" ">>> lyr.datasource\n" "\n" ) - .add_property("buffer_size", - &get_buffer_size, - &set_buffer_size, + .def_property("buffer_size", + &layer::buffer_size, + &layer::set_buffer_size, "Get/Set the size of buffer around layer in pixels.\n" "\n" "Usage:\n" @@ -269,23 +172,23 @@ void export_layer() "2\n" ) - .add_property("maximum_extent",make_function - (&layer::maximum_extent,return_value_policy()), - &set_maximum_extent, + .def_property("maximum_extent", + &layer::maximum_extent, + &layer::set_maximum_extent, "The maximum extent of the map.\n" "\n" "Usage:\n" ">>> m.maximum_extent = Box2d(-180,-90,180,90)\n" ) - .add_property("maximum_scale_denominator", + .def_property("maximum_scale_denominator", &layer::maximum_scale_denominator, &layer::set_maximum_scale_denominator, "Get/Set the maximum scale denominator of the layer.\n" "\n" "Usage:\n" ">>> from mapnik import Layer\n" - ">>> lyr = Layer('My Layer','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')\n" + ">>> lyr = Layer('My Layer','epsg:4326')\n" ">>> lyr.maximum_scale_denominator\n" "1.7976931348623157e+308 # default is the numerical maximum\n" ">>> lyr.maximum_scale_denominator = 1.0/1000000\n" @@ -293,14 +196,14 @@ void export_layer() "9.9999999999999995e-07\n" ) - .add_property("minimum_scale_denominator", + .def_property("minimum_scale_denominator", &layer::minimum_scale_denominator, &layer::set_minimum_scale_denominator, "Get/Set the minimum scale denominator of the layer.\n" "\n" "Usage:\n" ">>> from mapnik import Layer\n" - ">>> lyr = Layer('My Layer','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')\n" + ">>> lyr = Layer('My Layer','epsg:4326')\n" ">>> lyr.minimum_scale_denominator # default is 0\n" "0.0\n" ">>> lyr.minimum_scale_denominator = 1.0/1000000\n" @@ -308,14 +211,14 @@ void export_layer() "9.9999999999999995e-07\n" ) - .add_property("name", - make_function(&layer::name, return_value_policy()), + .def_property("name", + &layer::name, &layer::set_name, "Get/Set the name of the layer.\n" "\n" "Usage:\n" ">>> from mapnik import Layer\n" - ">>> lyr = Layer('My Layer','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')\n" + ">>> lyr = Layer('My Layer','epsg:4326')\n" ">>> lyr.name\n" "'My Layer'\n" ">>> lyr.name = 'New Name'\n" @@ -323,14 +226,14 @@ void export_layer() "'New Name'\n" ) - .add_property("queryable", + .def_property("queryable", &layer::queryable, &layer::set_queryable, "Get/Set whether this layer is queryable.\n" "\n" "Usage:\n" ">>> from mapnik import layer\n" - ">>> lyr = layer('My layer','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')\n" + ">>> lyr = layer('My layer','epsg:4326')\n" ">>> lyr.queryable\n" "False # Not queryable by default\n" ">>> lyr.queryable = True\n" @@ -338,36 +241,37 @@ void export_layer() "True\n" ) - .add_property("srs", - make_function(&layer::srs,return_value_policy()), + .def_property("srs", + &layer::srs, &layer::set_srs, "Get/Set the SRS of the layer.\n" "\n" "Usage:\n" ">>> from mapnik import layer\n" - ">>> lyr = layer('My layer','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')\n" + ">>> lyr = layer('My layer','epsg:4326')\n" ">>> lyr.srs\n" - "'+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs' # The default srs if not initialized with custom srs\n" - ">>> # set to google mercator with Proj.4 literal\n" + "'epsg:4326' # The default srs if not initialized with custom srs\n" + ">>> # set to google mercator with Proj literal\n" "... \n" - ">>> lyr.srs = '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over'\n" + ">>> lyr.srs = 'epsg:3857'\n" ) - .add_property("group_by", - make_function(&layer::group_by,return_value_policy()), + .def_property("group_by", + &layer::group_by, &layer::set_group_by, "Get/Set the optional layer group name.\n" "\n" "More details at https://github.com/mapnik/mapnik/wiki/Grouped-rendering:\n" ) - .add_property("styles", - make_function(_styles_,return_value_policy()), + .def_property("styles", + get_styles_, + set_styles_, "The styles list attached to this layer.\n" "\n" "Usage:\n" ">>> from mapnik import layer\n" - ">>> lyr = layer('My layer','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')\n" + ">>> lyr = layer('My layer','epsg:4326')\n" ">>> lyr.styles\n" "\n" ">>> len(lyr.styles)\n" @@ -379,6 +283,6 @@ void export_layer() "'My Style'\n" ) // comparison - .def(self == self) + .def(py::self == py::self) ; } diff --git a/src/mapnik_line_pattern_symbolizer.cpp b/src/mapnik_line_pattern_symbolizer.cpp new file mode 100644 index 000000000..77268b282 --- /dev/null +++ b/src/mapnik_line_pattern_symbolizer.cpp @@ -0,0 +1,50 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 + * + *****************************************************************************/ + +// mapnik +#include +#include +#include +#include +#include +#include "mapnik_symbolizer.hpp" +//pybind11 +#include + +namespace py = pybind11; + +void export_line_pattern_symbolizer(py::module const& m) +{ + using namespace python_mapnik; + using mapnik::line_pattern_symbolizer; + + py::class_(m, "LinePatternSymbolizer") + .def(py::init<>(), "Default ctor") + .def("__hash__", hash_impl_2) + .def_property("file", + &get_property, + &set_path_property, + "File path or mapnik.PathExpression") + + ; + +} diff --git a/src/mapnik_line_symbolizer.cpp b/src/mapnik_line_symbolizer.cpp new file mode 100644 index 000000000..102698bc3 --- /dev/null +++ b/src/mapnik_line_symbolizer.cpp @@ -0,0 +1,146 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 + * + *****************************************************************************/ + +// mapnik +#include +#include +#include +#include +#include + +#include "mapnik_symbolizer.hpp" +//pybind11 +#include +#include +#include +#include +#include + +namespace py = pybind11; + +namespace { + +std::string get_stroke_dasharray(mapnik::symbolizer_base & sym) +{ + auto dash = mapnik::get(sym, mapnik::keys::stroke_dasharray); + + std::ostringstream os; + for (std::size_t i = 0; i < dash.size(); ++i) + { + os << dash[i].first << "," << dash[i].second; + if (i + 1 < dash.size()) + os << ","; + } + return os.str(); +} + +void set_stroke_dasharray(mapnik::symbolizer_base & sym, std::string str) +{ + mapnik::dash_array dash; + if (mapnik::util::parse_dasharray(str, dash)) + { + mapnik::put(sym, mapnik::keys::stroke_dasharray, dash); + } + else + { + throw std::runtime_error("Can't parse dasharray"); + } +} + +} + +void export_line_symbolizer(py::module const& m) +{ + using namespace python_mapnik; + using mapnik::line_symbolizer; + + py::native_enum(m, "line_rasterizer", "enum.Enum") + .value("FULL",mapnik::line_rasterizer_enum::RASTERIZER_FULL) + .value("FAST",mapnik::line_rasterizer_enum::RASTERIZER_FAST) + .finalize() + ; + + py::native_enum(m, "stroke_linecap", "enum.Enum") + .value("BUTT_CAP",mapnik::line_cap_enum::BUTT_CAP) + .value("SQUARE_CAP",mapnik::line_cap_enum::SQUARE_CAP) + .value("ROUND_CAP",mapnik::line_cap_enum::ROUND_CAP) + .finalize() + ; + + py::native_enum(m, "stroke_linejoin", "enum.Enum") + .value("MITER_JOIN",mapnik::line_join_enum::MITER_JOIN) + .value("MITER_REVERT_JOIN",mapnik::line_join_enum::MITER_REVERT_JOIN) + .value("ROUND_JOIN",mapnik::line_join_enum::ROUND_JOIN) + .value("BEVEL_JOIN",mapnik::line_join_enum::BEVEL_JOIN) + .finalize() + ; + + py::class_(m, "LineSymbolizer") + .def(py::init<>(), "Default LineSymbolizer - 1px solid black") + .def("__hash__",hash_impl_2) + .def_property("stroke", + &get_property, + &set_color_property, + "Stroke color") + .def_property("stroke_width", + &get_property, + &set_double_property, + "Stroke width") + .def_property("stroke_opacity", + &get_property, + &set_double_property, + "Stroke opacity") + .def_property("stroke_gamma", + &get_property, + &set_double_property, + "Stroke gamma") + .def_property("stroke_gamma_method", + &get, + &set_enum_property, + "Stroke gamma method") + .def_property("line_rasterizer", + &get, + &set_enum_property, + "Line rasterizer") + .def_property("stroke_linecap", + &get, + &set_enum_property, + "Stroke linecap") + .def_property("stroke_linejoin", + &get, + &set_enum_property, + "Stroke linejoin") + .def_property("stroke_dasharray", + &get_stroke_dasharray, + &set_stroke_dasharray, + "Stroke dasharray") + .def_property("stroke_dashoffset", + &get_property, + &set_double_property, + "Stroke dashoffset") + .def_property("stroke_miterlimit", + &get_property, + &set_double_property, + "Stroke miterlimit") + + ; +} diff --git a/src/mapnik_logger.cpp b/src/mapnik_logger.cpp index 50103e17e..c7683c7a0 100644 --- a/src/mapnik_logger.cpp +++ b/src/mapnik_logger.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,60 +20,43 @@ * *****************************************************************************/ +//mapnik #include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - #include #include -#include "mapnik_enumeration.hpp" -void export_logger() +//pybind11 +#include +#include +#include + +namespace py = pybind11; + +void export_logger(py::module const& m) { using mapnik::logger; using mapnik::singleton; using mapnik::CreateStatic; - using namespace boost::python; - class_,boost::noncopyable>("Singleton",no_init) - .def("instance",&singleton::instance, - return_value_policy()) - .staticmethod("instance") - ; - enum_("severity_type") + py::native_enum(m, "severity_type", "enum.IntEnum") .value("Debug", logger::debug) .value("Warn", logger::warn) .value("Error", logger::error) .value("None", logger::none) + .finalize() ; - class_ >, - boost::noncopyable>("logger",no_init) - .def("get_severity", &logger::get_severity) - .def("set_severity", &logger::set_severity) - .def("get_object_severity", &logger::get_object_severity) - .def("set_object_severity", &logger::set_object_severity) - .def("clear_object_severity", &logger::clear_object_severity) - .def("get_format", &logger::get_format,return_value_policy()) - .def("set_format", &logger::set_format) - .def("str", &logger::str) - .def("use_file", &logger::use_file) - .def("use_console", &logger::use_console) - .staticmethod("get_severity") - .staticmethod("set_severity") - .staticmethod("get_object_severity") - .staticmethod("set_object_severity") - .staticmethod("clear_object_severity") - .staticmethod("get_format") - .staticmethod("set_format") - .staticmethod("str") - .staticmethod("use_file") - .staticmethod("use_console") + py::class_>(m, "logger") + .def_static("get_severity", &logger::get_severity) + .def_static("set_severity", &logger::set_severity) + .def_static("get_object_severity", &logger::get_object_severity) + .def_static("set_object_severity", &logger::set_object_severity) + .def_static("clear_object_severity", &logger::clear_object_severity) + .def_static("get_format", &logger::get_format) + .def_static("set_format", &logger::set_format) + .def_static("str", &logger::str) + .def_static("use_file", &logger::use_file) + .def_static("use_console", &logger::use_console) ; } diff --git a/src/mapnik_map.cpp b/src/mapnik_map.cpp index 3036cf89b..b2b164339 100644 --- a/src/mapnik_map.cpp +++ b/src/mapnik_map.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,18 +20,8 @@ * *****************************************************************************/ +//mapnik #include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#include -#include -#pragma GCC diagnostic pop - -// mapnik #include #include #include @@ -39,7 +29,16 @@ #include #include #include -#include "mapnik_enumeration.hpp" +#include "mapnik_value_converter.hpp" +#include "python_optional.hpp" +//pybind11 +#include +#include +#include +#include +#include + +namespace py = pybind11; using mapnik::color; using mapnik::coord; @@ -47,8 +46,14 @@ using mapnik::box2d; using mapnik::layer; using mapnik::Map; -std::vector& (Map::*layers_nonconst)() = &Map::layers; -std::vector const& (Map::*layers_const)() const = &Map::layers; +PYBIND11_MAKE_OPAQUE(std::vector); +PYBIND11_MAKE_OPAQUE(std::map); +PYBIND11_MAKE_OPAQUE(mapnik::parameters); + +namespace { +std::vector& (Map::*set_layers)() = &Map::layers; +std::vector const& (Map::*get_layers)() const = &Map::layers; +mapnik::parameters const& (Map::*params_const)() const = &Map::get_extra_parameters; mapnik::parameters& (Map::*params_nonconst)() = &Map::get_extra_parameters; void insert_style(mapnik::Map & m, std::string const& name, mapnik::feature_type_style const& style) @@ -66,8 +71,7 @@ mapnik::feature_type_style find_style(mapnik::Map const& m, std::string const& n boost::optional style = m.find_style(name); if (!style) { - PyErr_SetString(PyExc_KeyError, "Invalid style name"); - boost::python::throw_error_already_set(); + throw std::runtime_error("Invalid style name"); } return *style; } @@ -77,8 +81,7 @@ mapnik::font_set find_fontset(mapnik::Map const& m, std::string const& name) boost::optional fontset = m.find_fontset(name); if (!fontset) { - PyErr_SetString(PyExc_KeyError, "Invalid font_set name"); - boost::python::throw_error_already_set(); + throw std::runtime_error("Invalid font_set name"); } return *fontset; } @@ -88,8 +91,7 @@ mapnik::font_set find_fontset(mapnik::Map const& m, std::string const& name) mapnik::featureset_ptr query_point(mapnik::Map const& m, int index, double x, double y) { if (index < 0){ - PyErr_SetString(PyExc_IndexError, "Please provide a layer index >= 0"); - boost::python::throw_error_already_set(); + throw pybind11::index_error("Please provide a layer index >= 0"); } unsigned idx = index; return m.query_point(idx, x, y); @@ -98,8 +100,7 @@ mapnik::featureset_ptr query_point(mapnik::Map const& m, int index, double x, do mapnik::featureset_ptr query_map_point(mapnik::Map const& m, int index, double x, double y) { if (index < 0){ - PyErr_SetString(PyExc_IndexError, "Please provide a layer index >= 0"); - boost::python::throw_error_already_set(); + throw pybind11::index_error("Please provide a layer index >= 0"); } unsigned idx = index; return m.query_map_point(idx, x, y); @@ -117,31 +118,15 @@ void set_maximum_extent(mapnik::Map & m, boost::optional > } } -struct extract_style -{ - using result_type = boost::python::tuple; - result_type operator() (std::map::value_type const& val) const - { - return boost::python::make_tuple(val.first,val.second); - } -}; -using style_extract_iterator = boost::transform_iterator; -using style_range = std::pair; +} //namespace -style_range _styles_ (mapnik::Map const& m) +void export_map(py::module const& m) { - return style_range( - boost::make_transform_iterator(m.begin_styles(), extract_style()), - boost::make_transform_iterator(m.end_styles(), extract_style())); -} - -void export_map() -{ - using namespace boost::python; - + py::bind_vector>(m, "Layers", py::module_local()); + py::bind_map>(m, "Styles", py::module_local()); // aspect ratio fix modes - mapnik::enumeration_("aspect_fix_mode") + py::native_enum(m, "aspect_fix_mode", "enum.Enum") .value("GROW_BBOX", mapnik::Map::GROW_BBOX) .value("GROW_CANVAS",mapnik::Map::GROW_CANVAS) .value("SHRINK_BBOX",mapnik::Map::SHRINK_BBOX) @@ -151,35 +136,29 @@ void export_map() .value("ADJUST_CANVAS_WIDTH",mapnik::Map::ADJUST_CANVAS_WIDTH) .value("ADJUST_CANVAS_HEIGHT", mapnik::Map::ADJUST_CANVAS_HEIGHT) .value("RESPECT", mapnik::Map::RESPECT) + .finalize() ; - class_ >("Layers") - .def(vector_indexing_suite >()) - ; - - class_("StyleRange") - .def("__iter__", - boost::python::range(&style_range::first, &style_range::second)) - ; + py::class_(m, "Map","The map object.") + .def(py::init(), + "Create a Map with a width and height as integers and, optionally,\n" + "an srs string either with a Proj epsg code ('epsg:')\n" + "or with a Proj literal ('+proj=').\n" + "If no srs is specified the map will default to 'epsg:4326'\n" + "\n" + "Usage:\n" + ">>> from mapnik import Map\n" + ">>> m = Map(600,400)\n" + ">>> m\n" + "\n" + ">>> m.srs\n" + "'epsg:4326'\n", + py::arg("width"), + py::arg("height"), + py::arg("srs") = mapnik::MAPNIK_GEOGRAPHIC_PROJ + ) - class_("Map","The map object.",init >( - ( arg("width"),arg("height"),arg("srs") ), - "Create a Map with a width and height as integers and, optionally,\n" - "an srs string either with a Proj.4 epsg code ('+init=epsg:')\n" - "or with a Proj.4 literal ('+proj=').\n" - "If no srs is specified the map will default to '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs'\n" - "\n" - "Usage:\n" - ">>> from mapnik import Map\n" - ">>> m = Map(600,400)\n" - ">>> m\n" - "\n" - ">>> m.srs\n" - "'+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs'\n" - )) - - .def("append_style",insert_style, - (arg("style_name"),arg("style_object")), + .def("append_style", insert_style, "Insert a Mapnik Style onto the map by appending it.\n" "\n" "Usage:\n" @@ -188,12 +167,13 @@ void export_map() ">>> m.append_style('Style Name', sty)\n" "True # style object added to map by name\n" ">>> m.append_style('Style Name', sty)\n" - "False # you can only append styles with unique names\n" + "False # you can only append styles with unique names\n", + py::arg("style_name"), py::arg("style_object") ) - .def("append_fontset",insert_fontset, - (arg("fontset")), - "Add a FontSet to the map." + .def("append_fontset", insert_fontset, + "Add a FontSet to the map.", + py::arg("name"), py::arg("fontset") ) .def("buffered_envelope", @@ -213,8 +193,7 @@ void export_map() ) .def("envelope", - make_function(&Map::get_current_extent, - return_value_policy()), + &Map::get_current_extent, "Return the Map Box2d object\n" "and print the string representation\n" "of the current extent of the map.\n" @@ -229,26 +208,33 @@ void export_map() ) .def("find_fontset",find_fontset, - (arg("name")), - "Find a fontset by name." + "Find a fontset by name.", + py::arg("name") ) .def("find_style", find_style, - (arg("name")), "Query the Map for a style by name and return\n" "a style object if found or raise KeyError\n" "style if not found.\n" "\n" "Usage:\n" ">>> m.find_style('Style Name')\n" - "\n" - ) - - .add_property("styles", _styles_) + "\n", + py::arg("name") + ) + .def_property("styles", + (std::map const& (mapnik::Map::*)() const) + &mapnik::Map::styles, + (std::map& (mapnik::Map::*)()) + &mapnik::Map::styles, + "Returns list of Styles" + "associated with this Map object") + // .def("styles", [] (mapnik::Map const& m) { + // return py::make_iterator(m.begin_styles(), m.end_styles()); + // }, py::keep_alive<0, 1>()) .def("pan",&Map::pan, - (arg("x"),arg("y")), "Set the Map center at a given x,y location\n" "as integers in the coordinates of the pixmap or map surface.\n" "\n" @@ -258,11 +244,11 @@ void export_map() "Coord(-0.5,-0.5) # default Map center\n" ">>> m.pan(-1,-1)\n" ">>> m.envelope().center()\n" - "Coord(0.00166666666667,-0.835)\n" + "Coord(0.00166666666667,-0.835)\n", + py::arg("x"), py::arg("y") ) .def("pan_and_zoom",&Map::pan_and_zoom, - (arg("x"),arg("y"),arg("factor")), "Set the Map center at a given x,y location\n" "and zoom factor as a float.\n" "\n" @@ -274,11 +260,11 @@ void export_map() "-0.0016666666666666668\n" ">>> m.pan_and_zoom(-1,-1,0.25)\n" ">>> m.scale()\n" - "0.00062500000000000001\n" + "0.00062500000000000001\n", + py::arg("x"), py::arg("y"), py::arg("factor") ) - .def("query_map_point",query_map_point, - (arg("layer_idx"),arg("pixel_x"),arg("pixel_y")), + .def("query_map_point", query_map_point, "Query a Map Layer (by layer index) for features \n" "intersecting the given x,y location in the pixel\n" "coordinates of the rendered map image.\n" @@ -291,11 +277,11 @@ void export_map() ">>> featureset\n" "\n" ">>> featureset.features\n" - ">>> []\n" + ">>> []\n", + py::arg("layer_idx"), py::arg("pixel_x"), py::arg("pixel_y") ) - .def("query_point",query_point, - (arg("layer idx"),arg("x"),arg("y")), + .def("query_point", query_point, "Query a Map Layer (by layer index) for features \n" "intersecting the given x,y location in the coordinates\n" "of map projection.\n" @@ -308,30 +294,31 @@ void export_map() ">>> featureset\n" "\n" ">>> featureset.features\n" - ">>> []\n" + ">>> []\n", + py::arg("layer_idx"), py::arg("x"), py::arg("y") ) - .def("remove_all",&Map::remove_all, + .def("remove_all", &Map::remove_all, "Remove all Mapnik Styles and layers from the Map.\n" "\n" "Usage:\n" ">>> m.remove_all()\n" ) - .def("remove_style",&Map::remove_style, - (arg("style_name")), + .def("remove_style", &Map::remove_style, "Remove a Mapnik Style from the map.\n" "\n" "Usage:\n" - ">>> m.remove_style('Style Name')\n" + ">>> m.remove_style('Style Name')\n", + py::arg("style_name") ) - .def("resize",&Map::resize, - (arg("width"),arg("height")), + .def("resize", &Map::resize, "Resize a Mapnik Map.\n" "\n" "Usage:\n" - ">>> m.resize(64,64)\n" + ">>> m.resize(64,64)\n", + py::arg("width"), py::arg("height") ) .def("scale", &Map::scale, @@ -348,7 +335,7 @@ void export_map() ">>> m.scale_denominator()\n" ) - .def("view_transform",&Map::transform, + .def("view_transform", &Map::transform, "Return the map ViewTransform object\n" "which is used internally to convert between\n" "geographic coordinates and screen coordinates.\n" @@ -357,15 +344,15 @@ void export_map() ">>> m.view_transform()\n" ) - .def("zoom",&Map::zoom, - (arg("factor")), + .def("zoom", &Map::zoom, "Zoom in or out by a given factor.\n" "positive number larger than 1, zooms out\n" "positive number smaller than 1, zooms in\n" "\n" "Usage:\n" "\n" - ">>> m.zoom(0.25)\n" + ">>> m.zoom(0.25)\n", + py::arg("factor") ) .def("zoom_all",&Map::zoom_all, @@ -377,18 +364,20 @@ void export_map() ) .def("zoom_to_box",&Map::zoom_to_box, - (arg("Boxd2")), "Set the geographical extent of the map\n" "by specifying a Mapnik Box2d.\n" "\n" "Usage:\n" ">>> extent = Box2d(-180.0, -90.0, 180.0, 90.0)\n" - ">>> m.zoom_to_box(extent)\n" + ">>> m.zoom_to_box(extent)\n", + py::arg("bounding_box") ) + .def_property("parameters", + params_const, + params_nonconst, + "extra parameters") - .add_property("parameters",make_function(params_nonconst,return_value_policy()),"TODO") - - .add_property("aspect_fix_mode", + .def_property("aspect_fix_mode", &Map::get_aspect_fix_mode, &Map::set_aspect_fix_mode, // TODO - how to add arg info to properties? @@ -399,8 +388,8 @@ void export_map() ">>> m.aspect_fix_mode = aspect_fix_mode.GROW_BBOX\n" ) - .add_property("background",make_function - (&Map::background,return_value_policy()), + .def_property("background", + &Map::background, &Map::set_background, "The background color of the map (same as background_color property).\n" "\n" @@ -408,8 +397,8 @@ void export_map() ">>> m.background = Color('steelblue')\n" ) - .add_property("background_color",make_function - (&Map::background,return_value_policy()), + .def_property("background_color", + &Map::background, &Map::set_background, "The background color of the map.\n" "\n" @@ -417,8 +406,8 @@ void export_map() ">>> m.background_color = Color('steelblue')\n" ) - .add_property("background_image",make_function - (&Map::background_image,return_value_policy()), + .def_property("background_image", + &Map::background_image, &Map::set_background_image, "The optional background image of the map.\n" "\n" @@ -426,7 +415,8 @@ void export_map() ">>> m.background_image = '/path/to/image.png'\n" ) - .add_property("background_image_comp_op",&Map::background_image_comp_op, + .def_property("background_image_comp_op", + &Map::background_image_comp_op, &Map::set_background_image_comp_op, "The background image compositing operation.\n" "\n" @@ -434,7 +424,8 @@ void export_map() ">>> m.background_image_comp_op = mapnik.CompositeOp.src_over\n" ) - .add_property("background_image_opacity",&Map::background_image_opacity, + .def_property("background_image_opacity", + &Map::background_image_opacity, &Map::set_background_image_opacity, "The background image opacity.\n" "\n" @@ -442,8 +433,8 @@ void export_map() ">>> m.background_image_opacity = 1.0\n" ) - .add_property("base", - make_function(&Map::base_path,return_value_policy()), + .def_property("base", + &Map::base_path, &Map::set_base_path, "The base path of the map where any files using relative \n" "paths will be interpreted as relative to.\n" @@ -452,7 +443,7 @@ void export_map() ">>> m.base_path = '.'\n" ) - .add_property("buffer_size", + .def_property("buffer_size", &Map::buffer_size, &Map::set_buffer_size, "Get/Set the size of buffer around map in pixels.\n" @@ -465,7 +456,7 @@ void export_map() "2\n" ) - .add_property("height", + .def_property("height", &Map::height, &Map::set_height, "Get/Set the height of the map in pixels.\n" @@ -479,8 +470,9 @@ void export_map() "600\n" ) - .add_property("layers",make_function - (layers_nonconst,return_value_policy()), + .def_property("layers", + get_layers, + set_layers, "The list of map layers.\n" "\n" "Usage:\n" @@ -490,8 +482,8 @@ void export_map() "\n" ) - .add_property("maximum_extent",make_function - (&Map::maximum_extent,return_value_policy()), + .def_property("maximum_extent", + &Map::maximum_extent, &set_maximum_extent, "The maximum extent of the map.\n" "\n" @@ -499,28 +491,28 @@ void export_map() ">>> m.maximum_extent = Box2d(-180,-90,180,90)\n" ) - .add_property("srs", - make_function(&Map::srs,return_value_policy()), + .def_property("srs", + &Map::srs, &Map::set_srs, - "Spatial reference in Proj.4 format.\n" + "Spatial reference in Proj format.\n" "Either an epsg code or proj literal.\n" "For example, a proj literal:\n" - "\t'+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs'\n" + "\t'epsg:4326'\n" "and a proj epsg code:\n" - "\t'+init=epsg:4326'\n" + "\t'epsg:4326'\n" "\n" "Note: using epsg codes requires the installation of\n" - "the Proj.4 'epsg' data file normally found in '/usr/local/share/proj'\n" + "the Proj 'epsg' data file normally found in '/usr/local/share/proj'\n" "\n" "Usage:\n" ">>> m.srs\n" - "'+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs' # The default srs if not initialized with custom srs\n" + "'epsg:4326' # The default srs if not initialized with custom srs\n" ">>> # set to google mercator with Proj.4 literal\n" "... \n" - ">>> m.srs = '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over'\n" + ">>> m.srs = 'epsg:3857'\n" ) - .add_property("width", + .def_property("width", &Map::width, &Map::set_width, "Get/Set the width of the map in pixels.\n" @@ -534,6 +526,6 @@ void export_map() "800\n" ) // comparison - .def(self == self) + .def(py::self == py::self) ; } diff --git a/src/mapnik_markers_symbolizer.cpp b/src/mapnik_markers_symbolizer.cpp new file mode 100644 index 000000000..d28d5363a --- /dev/null +++ b/src/mapnik_markers_symbolizer.cpp @@ -0,0 +1,62 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 + * + *****************************************************************************/ + +// mapnik +#include +#include +#include +#include +#include +#include "mapnik_symbolizer.hpp" +//pybind11 +#include + +namespace py = pybind11; + +void export_markers_symbolizer(py::module const& m) +{ + using namespace python_mapnik; + using mapnik::markers_symbolizer; + + py::class_(m, "MarkersSymbolizer") + .def(py::init<>(), "Default ctor") + .def("__hash__", hash_impl_2) + .def_property("file", + &get_property, + &set_path_property, + "File path or mapnik.PathExpression") + .def_property("width", + &get_property, + &set_double_property, + "width or mapnik.Expression") + .def_property("height", + &get_property, + &set_double_property, + "height or mapnik.Expression") + .def_property("allow_overlap", + &get_property, + &set_boolean_property, + "Allow overlapping - True/False") + + ; + +} diff --git a/src/mapnik_palette.cpp b/src/mapnik_palette.cpp index baae694a9..a1ca642f2 100644 --- a/src/mapnik_palette.cpp +++ b/src/mapnik_palette.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 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,22 +20,13 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#include -#include -#pragma GCC diagnostic pop - //mapnik +#include #include +//pybind11 +#include -// stl -#include +namespace py = pybind11; static std::shared_ptr make_palette( std::string const& palette, std::string const& format ) { @@ -45,22 +36,18 @@ static std::shared_ptr make_palette( std::string const& pa else if (format == "act") type = mapnik::rgba_palette::PALETTE_ACT; else - throw std::runtime_error("invalid type passed for mapnik.Palette: must be either rgba, rgb, or act"); + throw std::runtime_error("invalid type passed for `mapnik.Palette`: must be either rgba, rgb, or act"); return std::make_shared(palette, type); } -void export_palette () +void export_palette (py::module const& m) { - using namespace boost::python; + py::class_>(m, "Palette") + .def(py::init([](std::string const& palette, std::string const& format) { + return make_palette(palette, format); }), + "Creates a new color palette from a file\n", + py::arg("palette"), py::arg("type")) - class_, - boost::noncopyable >("Palette",no_init) - //, init( - // ( arg("palette"), arg("type")), - // "Creates a new color palette from a file\n" - // ) - .def( "__init__", boost::python::make_constructor(make_palette)) .def("to_string", &mapnik::rgba_palette::to_string, "Returns the palette as a string.\n" ) diff --git a/src/mapnik_parameters.cpp b/src/mapnik_parameters.cpp index 01332ef48..a5aca2633 100644 --- a/src/mapnik_parameters.cpp +++ b/src/mapnik_parameters.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 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,223 +20,25 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#pragma GCC diagnostic pop - // mapnik +#include #include #include #include #include #include -// stl -#include +#include "mapnik_value_converter.hpp" +//pybind11 +#include +#include +#include +namespace py = pybind11; using mapnik::parameter; using mapnik::parameters; -struct parameter_pickle_suite : boost::python::pickle_suite -{ - static boost::python::tuple - getinitargs(const parameter& p) - { - using namespace boost::python; - return boost::python::make_tuple(p.first,p.second); - } -}; - -struct parameters_pickle_suite : boost::python::pickle_suite -{ - static boost::python::tuple - getstate(const parameters& p) - { - using namespace boost::python; - dict d; - parameters::const_iterator pos=p.begin(); - while(pos!=p.end()) - { - d[pos->first] = pos->second; - ++pos; - } - return boost::python::make_tuple(d); - } - - static void setstate(parameters& p, boost::python::tuple state) - { - using namespace boost::python; - if (len(state) != 1) - { - PyErr_SetObject(PyExc_ValueError, - ("expected 1-item tuple in call to __setstate__; got %s" - % state).ptr() - ); - throw_error_already_set(); - } - - dict d = extract(state[0]); - boost::python::list keys = d.keys(); - for (int i=0; i(keys[i]); - object obj = d[key]; - extract ex0(obj); - extract ex1(obj); - extract ex2(obj); - extract ex3(obj); - - // TODO - this is never hit - we need proper python string -> std::string to get invoked here - if (ex0.check()) - { - p[key] = ex0(); - } - else if (ex1.check()) - { - p[key] = ex1(); - } - else if (ex2.check()) - { - p[key] = ex2(); - } - else if (ex3.check()) - { - std::string buffer; - mapnik::to_utf8(ex3(),buffer); - p[key] = buffer; - } - else - { - MAPNIK_LOG_DEBUG(bindings) << "parameters_pickle_suite: Could not unpickle key=" << key; - } - } - } -}; - -mapnik::value_holder get_params_by_key1(mapnik::parameters const& p, std::string const& key) +void export_parameters(py::module const& m) { - parameters::const_iterator pos = p.find(key); - if (pos != p.end()) - { - // will be auto-converted to proper python type by `mapnik_params_to_python` - return pos->second; - } - return mapnik::value_null(); -} - -mapnik::value_holder get_params_by_key2(mapnik::parameters const& p, std::string const& key) -{ - parameters::const_iterator pos = p.find(key); - if (pos == p.end()) - { - PyErr_SetString(PyExc_KeyError, key.c_str()); - boost::python::throw_error_already_set(); - } - // will be auto-converted to proper python type by `mapnik_params_to_python` - return pos->second; -} - -mapnik::parameter get_params_by_index(mapnik::parameters const& p, int index) -{ - if (index < 0 || static_cast(index) > p.size()) - { - PyErr_SetString(PyExc_IndexError, "Index is out of range"); - throw boost::python::error_already_set(); - } - - parameters::const_iterator itr = p.begin(); - std::advance(itr, index); - if (itr != p.end()) - { - return *itr; - } - PyErr_SetString(PyExc_IndexError, "Index is out of range"); - throw boost::python::error_already_set(); -} - -std::size_t get_params_size(mapnik::parameters const& p) -{ - return p.size(); -} - -void add_parameter(mapnik::parameters & p, mapnik::parameter const& param) -{ - p[param.first] = param.second; -} - -mapnik::value_holder get_param(mapnik::parameter const& p, int index) -{ - if (index == 0) - { - return p.first; - } - else if (index == 1) - { - return p.second; - } - else - { - PyErr_SetString(PyExc_IndexError, "Index is out of range"); - throw boost::python::error_already_set(); - } -} - -std::shared_ptr create_parameter(mapnik::value_unicode_string const& key, mapnik::value_holder const& value) -{ - std::string key_utf8; - mapnik::to_utf8(key, key_utf8); - return std::make_shared(key_utf8,value); -} - -bool contains(mapnik::parameters const& p, std::string const& key) -{ - parameters::const_iterator pos = p.find(key); - return pos != p.end(); -} - -// needed for Python_Unicode to std::string (utf8) conversion - -std::shared_ptr create_parameter_from_string(mapnik::value_unicode_string const& key, mapnik::value_unicode_string const& ustr) -{ - std::string key_utf8; - std::string ustr_utf8; - mapnik::to_utf8(key, key_utf8); - mapnik::to_utf8(ustr,ustr_utf8); - return std::make_shared(key_utf8, ustr_utf8); -} - -void export_parameters() -{ - using namespace boost::python; - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - - class_ >("Parameter",no_init) - .def("__init__", make_constructor(create_parameter), - "Create a mapnik.Parameter from a pair of values, the first being a string\n" - "and the second being either a string, and integer, or a float") - .def("__init__", make_constructor(create_parameter_from_string), - "Create a mapnik.Parameter from a pair of values, the first being a string\n" - "and the second being either a string, and integer, or a float") - - .def_pickle(parameter_pickle_suite()) - .def("__getitem__",get_param) - ; - - class_("Parameters",init<>()) - .def_pickle(parameters_pickle_suite()) - .def("get",get_params_by_key1) - .def("__getitem__",get_params_by_key2) - .def("__getitem__",get_params_by_index) - .def("__len__",get_params_size) - .def("__contains__",contains) - .def("append",add_parameter) - .def("iteritems",iterator()) - ; + py::bind_map(m, "Parameters", py::module_local()); } diff --git a/src/mapnik_placement_finder.cpp b/src/mapnik_placement_finder.cpp new file mode 100644 index 000000000..52b68e108 --- /dev/null +++ b/src/mapnik_placement_finder.cpp @@ -0,0 +1,189 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 +#include +#include +#include +#include "mapnik_symbolizer.hpp" +//pybind11 +#include + +namespace py = pybind11; + +namespace +{ + +template +void set_face_name(PlacementFinder & finder, std::string const& face_name) +{ + finder.defaults.format_defaults.face_name = face_name; +} + +template +std::string get_face_name(PlacementFinder const& finder) +{ + return finder.defaults.format_defaults.face_name; +} + +template +void set_text_size(PlacementFinder & finder, double text_size) +{ + finder.defaults.format_defaults.text_size = text_size; +} + +template +py::object get_text_size(PlacementFinder const& finder) +{ + return mapnik::util::apply_visitor(python_mapnik::extract_python_object<>(mapnik::keys::MAX_SYMBOLIZER_KEY), + finder.defaults.format_defaults.text_size); +} + +template +void set_fill(PlacementFinder & finder, mapnik::color const& fill) +{ + finder.defaults.format_defaults.fill = fill; +} + +template +py::object get_fill(PlacementFinder const& finder) +{ + return mapnik::util::apply_visitor(python_mapnik::extract_python_object<>(mapnik::keys::MAX_SYMBOLIZER_KEY), + finder.defaults.format_defaults.fill); +} + +template +void set_halo_fill(PlacementFinder & finder, mapnik::color const& halo_fill ) +{ + finder.defaults.format_defaults.halo_fill = halo_fill; +} + +template +py::object get_halo_fill(PlacementFinder const& finder) +{ + return mapnik::util::apply_visitor(python_mapnik::extract_python_object<>(mapnik::keys::MAX_SYMBOLIZER_KEY), + finder.defaults.format_defaults.halo_fill); +} + +template +void set_halo_radius(PlacementFinder & finder, double halo_radius) +{ + finder.defaults.format_defaults.halo_radius = halo_radius; +} + +template +py::object get_halo_radius(PlacementFinder const& finder) +{ + return mapnik::util::apply_visitor(python_mapnik::extract_python_object<>(mapnik::keys::MAX_SYMBOLIZER_KEY), + finder.defaults.format_defaults.halo_radius); +} + +template +void set_format_expr(PlacementFinder & finder, std::string const& expr) +{ + finder.defaults.set_format_tree( + std::make_shared(mapnik::parse_expression(expr))); +} + +template +std::string get_format_expr(PlacementFinder const& finder) +{ + mapnik::expression_set exprs; + finder.defaults.add_expressions(exprs); + std::string str = ""; + for (auto expr : exprs) + { + if (expr) + str += mapnik::to_expression_string(*expr); + } + return str; +} + +} + +void export_placement_finder(py::module const& m) +{ + py::class_>(m, "PlacementFinder") + .def(py::init<>(), "Default ctor") + .def_property("face_name", + &get_face_name, + &set_face_name, + "Font face name") + .def_property("text_size", + &get_text_size, + &set_text_size, + "Size of text") + .def_property("fill", + &get_fill, + &set_fill, + "Fill") + .def_property("halo_fill", + &get_halo_fill, + &set_halo_fill, + "Halo fill") + .def_property("halo_radius", + &get_halo_radius, + &set_halo_radius, + "Halo radius") + .def_property("format_expression", + &get_format_expr, + &set_format_expr, + "Format expression") + ; + +/* + py::class_>(m, "PlacementFinderSimple") + .def(py::init<>(), "Default ctor") + .def_property("face_name", + &get_face_name, + &set_face_name, + "Font face name") + .def_property("text_size", + &get_text_size, + &set_text_size, + "Size of text") + .def_property("fill", + &get_fill, + &set_fill, + "Fill") + .def_property("halo_fill", + &get_halo_fill, + &set_halo_fill, + "Halo fill") + .def_property("halo_radius", + &get_halo_radius, + &set_halo_radius, + "Halo radius") + .def_property("format_expression", + &get_format_expr, + &set_format_expr, + "Format expression") + ; +*/ +} diff --git a/src/mapnik_point_symbolizer.cpp b/src/mapnik_point_symbolizer.cpp new file mode 100644 index 000000000..cc0a14f42 --- /dev/null +++ b/src/mapnik_point_symbolizer.cpp @@ -0,0 +1,78 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 + * + *****************************************************************************/ + +// mapnik +#include +#include +#include +#include +#include + +#include "mapnik_symbolizer.hpp" +//pybind11 +#include +#include + +namespace py = pybind11; + +void export_point_symbolizer(py::module const& m) +{ + using namespace python_mapnik; + using mapnik::point_symbolizer; + + py::native_enum(m, "point_placement", "enum.Enum") + .value("CENTROID",mapnik::point_placement_enum::CENTROID_POINT_PLACEMENT) + .value("INTERIOR",mapnik::point_placement_enum::INTERIOR_POINT_PLACEMENT) + .finalize() + ; + + py::class_(m, "PointSymbolizer") + .def(py::init<>(), "Default Point Symbolizer - 4x4 black square") + .def("__hash__",hash_impl_2) + + .def_property("file", + &get_property, + &set_path_property, + "File path or mapnik.PathExpression") + + .def_property("opacity", + &get_property, + &set_double_property, + "Opacity - [0..1]") + + .def_property("allow_overlap", + &get_property, + &set_boolean_property, + "Allow overlapping - True/False") + + .def_property("ignore_placement", + &get_property, + &set_boolean_property, + "Ignore placement - True/False") + + .def_property("placement", + &get_property, + &set_enum_property, + "Point placement type CENTROID/INTERIOR") + + ; +} diff --git a/src/mapnik_polygon_pattern_symbolizer.cpp b/src/mapnik_polygon_pattern_symbolizer.cpp new file mode 100644 index 000000000..5f365fc70 --- /dev/null +++ b/src/mapnik_polygon_pattern_symbolizer.cpp @@ -0,0 +1,60 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 + * + *****************************************************************************/ + +// mapnik +#include +#include +#include +#include +#include +#include "mapnik_symbolizer.hpp" +//pybind11 +#include +#include + +namespace py = pybind11; + +void export_polygon_pattern_symbolizer(py::module const& m) +{ + using namespace python_mapnik; + using mapnik::polygon_pattern_symbolizer; + + py::native_enum(m, "pattern_alignment", "enum.Enum") + .value("LOCAL", mapnik::pattern_alignment_enum::LOCAL_ALIGNMENT) + .value("GLOBAL", mapnik::pattern_alignment_enum::GLOBAL_ALIGNMENT) + .finalize() + ; + + py::class_(m, "PolygonPatternSymbolizer") + .def(py::init<>(), "Default ctor") + .def("__hash__", hash_impl_2) + .def_property("file", + &get_property, + &set_path_property, + "File path or mapnik.PathExpression") + .def_property("alignment", + &get_property, + &set_enum_property, + "Pattern alignment LOCAL/GLOBAL") + ; + +} diff --git a/src/mapnik_polygon_symbolizer.cpp b/src/mapnik_polygon_symbolizer.cpp new file mode 100644 index 000000000..856c23640 --- /dev/null +++ b/src/mapnik_polygon_symbolizer.cpp @@ -0,0 +1,68 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 + * + *****************************************************************************/ + +// mapnik +#include +#include +#include +#include +#include +#include "mapnik_symbolizer.hpp" +//pybind11 +#include +#include +#include +#include + +namespace py = pybind11; + +void export_polygon_symbolizer(py::module const& m) +{ + using namespace python_mapnik; + using mapnik::polygon_symbolizer; + + py::class_(m, "PolygonSymbolizer") + .def(py::init<>(), "Default ctor") + .def("__hash__", hash_impl_2) + + .def_property("fill", + &get_property, + &set_color_property, + "Fill - mapnik.Color, CSS color string or a valid mapnik.Expression") + + .def_property("fill_opacity", + &get_property, + &set_double_property, + "Fill opacity - [0-1] or a valid mapnik.Expression") + + .def_property("gamma", + &get_property, + &set_double_property, + "Fill gamma") + + .def_property("gamma_method", + &get_property, + &set_enum_property, + "Fill gamma method") + ; + +} diff --git a/src/mapnik_proj_transform.cpp b/src/mapnik_proj_transform.cpp index fc753564c..6abb21dcf 100644 --- a/src/mapnik_proj_transform.cpp +++ b/src/mapnik_proj_transform.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 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,38 +20,22 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - // mapnik +#include #include #include #include #include - // stl #include +//pybind11 +#include +namespace py = pybind11; using mapnik::proj_transform; using mapnik::projection; -struct proj_transform_pickle_suite : boost::python::pickle_suite -{ - static boost::python::tuple - getinitargs(const proj_transform& p) - { - using namespace boost::python; - return boost::python::make_tuple(p.source(),p.dest()); - } -}; - namespace { mapnik::coord2d forward_transform_c(mapnik::proj_transform& t, mapnik::coord2d const& c) @@ -62,7 +46,7 @@ mapnik::coord2d forward_transform_c(mapnik::proj_transform& t, mapnik::coord2d c if (!t.forward(x,y,z)) { std::ostringstream s; s << "Failed to forward project " - << "from " << t.source().params() << " to: " << t.dest().params(); + << t.definition(); throw std::runtime_error(s.str()); } return mapnik::coord2d(x,y); @@ -76,7 +60,7 @@ mapnik::coord2d backward_transform_c(mapnik::proj_transform& t, mapnik::coord2d if (!t.backward(x,y,z)) { std::ostringstream s; s << "Failed to back project " - << "from " << t.dest().params() << " to: " << t.source().params(); + << t.definition(); throw std::runtime_error(s.str()); } return mapnik::coord2d(x,y); @@ -88,7 +72,7 @@ mapnik::box2d forward_transform_env(mapnik::proj_transform& t, mapnik::b if (!t.forward(new_box)) { std::ostringstream s; s << "Failed to forward project " - << "from " << t.source().params() << " to: " << t.dest().params(); + << t.definition(); throw std::runtime_error(s.str()); } return new_box; @@ -100,7 +84,7 @@ mapnik::box2d backward_transform_env(mapnik::proj_transform& t, mapnik:: if (!t.backward(new_box)){ std::ostringstream s; s << "Failed to back project " - << "from " << t.dest().params() << " to: " << t.source().params(); + << t.definition(); throw std::runtime_error(s.str()); } return new_box; @@ -112,7 +96,7 @@ mapnik::box2d forward_transform_env_p(mapnik::proj_transform& t, mapnik: if (!t.forward(new_box,points)) { std::ostringstream s; s << "Failed to forward project " - << "from " << t.source().params() << " to: " << t.dest().params(); + << t.definition(); throw std::runtime_error(s.str()); } return new_box; @@ -124,7 +108,7 @@ mapnik::box2d backward_transform_env_p(mapnik::proj_transform& t, mapnik if (!t.backward(new_box,points)){ std::ostringstream s; s << "Failed to back project " - << "from " << t.dest().params() << " to: " << t.source().params(); + << t.definition(); throw std::runtime_error(s.str()); } return new_box; @@ -132,18 +116,18 @@ mapnik::box2d backward_transform_env_p(mapnik::proj_transform& t, mapnik } -void export_proj_transform () +void export_proj_transform (py::module const& m) { - using namespace boost::python; - - class_("ProjTransform", init< projection const&, projection const& >()) - .def_pickle(proj_transform_pickle_suite()) + py::class_(m, "ProjTransform") + .def(py::init(), + "Constructs ProjTransform object") .def("forward", forward_transform_c) .def("backward",backward_transform_c) .def("forward", forward_transform_env) .def("backward",backward_transform_env) .def("forward", forward_transform_env_p) .def("backward",backward_transform_env_p) + .def("definition",&proj_transform::definition) ; } diff --git a/src/mapnik_projection.cpp b/src/mapnik_projection.cpp index c2088cd89..c03f1c7b3 100644 --- a/src/mapnik_projection.cpp +++ b/src/mapnik_projection.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,32 +20,21 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#pragma GCC diagnostic pop - // mapnik +#include #include #include #include +//pybind11 +#include +#include using mapnik::projection; -struct projection_pickle_suite : boost::python::pickle_suite -{ - static boost::python::tuple - getinitargs(const projection& p) - { - using namespace boost::python; - return boost::python::make_tuple(p.params()); - } -}; +namespace py = pybind11; namespace { + mapnik::coord2d forward_pt(mapnik::coord2d const& pt, mapnik::projection const& prj) { @@ -90,32 +79,42 @@ mapnik::box2d inverse_env(mapnik::box2d const & box, } -void export_projection () +void export_projection (py::module& m) { - using namespace boost::python; - - class_("Projection", "Represents a map projection.",init( - (arg("proj4_string")), - "Constructs a new projection from its PROJ.4 string representation.\n" - "\n" - "The constructor will throw a RuntimeError in case the projection\n" - "cannot be initialized.\n" - ) - ) - .def_pickle(projection_pickle_suite()) - .def ("params", make_function(&projection::params, - return_value_policy()), - "Returns the PROJ.4 string for this projection.\n") - .def ("expanded",&projection::expanded, - "normalize PROJ.4 definition by expanding +init= syntax\n") - .add_property ("geographic", &projection::is_geographic, - "This property is True if the projection is a geographic projection\n" - "(i.e. it uses lon/lat coordinates)\n") + py::class_(m, "Projection", "Represents a map projection.") + .def(py::init(), + "Constructs a new projection from its PROJ string representation.\n" + "\n" + "The constructor will throw a RuntimeError in case the projection\n" + "cannot be initialized.\n", + py::arg("proj_string") + ) + .def(py::pickle( + [] (projection const& p) { // __getstate__ + return py::make_tuple(p.params()); + }, + [] (py::tuple t) { // __setstate__ + if (t.size() != 1) + throw std::runtime_error("Invalid state!"); + projection p(t[0].cast()); + return p; + })) + .def("params", &projection::params, + "Returns the PROJ string for this projection.\n") + .def("definition",&projection::definition, + "Return projection definition\n") + .def("description", &projection::description, + "Returns projection description") + .def_property_readonly("geographic", &projection::is_geographic, + "This property is True if the projection is a geographic projection\n" + "(i.e. it uses lon/lat coordinates)\n") + .def_property_readonly("area_of_use", &projection::area_of_use, + "This property returns projection area of use in lonlat WGS84\n") ; - def("forward_",&forward_pt); - def("inverse_",&inverse_pt); - def("forward_",&forward_env); - def("inverse_",&inverse_env); + m.def("forward_", &forward_pt); + m.def("inverse_", &inverse_pt); + m.def("forward_", &forward_env); + m.def("inverse_", &inverse_env); } diff --git a/src/mapnik_python.cpp b/src/mapnik_python.cpp index 14523b034..5a9bf1326 100644 --- a/src/mapnik_python.cpp +++ b/src/mapnik_python.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,180 +20,55 @@ * *****************************************************************************/ +//mapnik #include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include "python_to_value.hpp" -#include // for keywords, arg, etc -#include -#include // for def -#include -#include // for none -#include // for dict -#include -#include // for list -#include // for BOOST_PYTHON_MODULE -#include // for get_managed_object -#include -#include -#pragma GCC diagnostic pop - -// stl -#include -#include - -void export_color(); -void export_coord(); -void export_layer(); -void export_parameters(); -void export_envelope(); -void export_query(); -void export_geometry(); -void export_palette(); -void export_image(); -void export_image_view(); -void export_gamma_method(); -void export_scaling_method(); -#if defined(GRID_RENDERER) -void export_grid(); -void export_grid_view(); -#endif -void export_map(); -void export_python(); -void export_expression(); -void export_rule(); -void export_style(); -void export_feature(); -void export_featureset(); -void export_fontset(); -void export_datasource(); -void export_datasource_cache(); -void export_symbolizer(); -void export_markers_symbolizer(); -void export_point_symbolizer(); -void export_line_symbolizer(); -void export_line_pattern_symbolizer(); -void export_polygon_symbolizer(); -void export_building_symbolizer(); -void export_polygon_pattern_symbolizer(); -void export_raster_symbolizer(); -void export_text_symbolizer(); -void export_shield_symbolizer(); -void export_debug_symbolizer(); -void export_group_symbolizer(); -void export_font_engine(); -void export_projection(); -void export_proj_transform(); -void export_view_transform(); -void export_raster_colorizer(); -void export_label_collision_detector(); -void export_logger(); - #include #include +#include +#include #include #include #include -#include -#include #include -#include -#include #include -#include +#include #include +#include +#include "mapnik_value_converter.hpp" +#include "python_to_value.hpp" + #if defined(GRID_RENDERER) #include "python_grid_utils.hpp" #endif -#include "mapnik_value_converter.hpp" -#include "mapnik_enumeration_wrapper_converter.hpp" -#include "mapnik_threads.hpp" -#include "python_optional.hpp" -#include #if defined(SHAPE_MEMORY_MAPPED_FILE) #include #endif - #if defined(SVG_RENDERER) #include #endif - -namespace mapnik { - class font_set; - class layer; - class color; - class label_collision_detector4; -} -void clear_cache() -{ - mapnik::marker_cache::instance().clear(); -#if defined(SHAPE_MEMORY_MAPPED_FILE) - mapnik::mapped_memory_cache::instance().clear(); -#endif -} - #if defined(HAVE_CAIRO) #include #include #include #endif -#if defined(HAVE_PYCAIRO) -#include -#include -#if PY_MAJOR_VERSION >= 3 -#include -#else -#include -static Pycairo_CAPI_t *Pycairo_CAPI; -#endif +//stl +#include +#include -static void *extract_surface(PyObject* op) -{ - if (PyObject_TypeCheck(op, const_cast(Pycairo_CAPI->Surface_Type))) - { - return op; - } - else - { - return 0; - } -} +//pybind11 +#include -static void *extract_context(PyObject* op) -{ - if (PyObject_TypeCheck(op, const_cast(Pycairo_CAPI->Context_Type))) - { - return op; - } - else - { - return 0; - } -} +namespace py = pybind11; -void register_cairo() +namespace { +void clear_cache() { -#if PY_MAJOR_VERSION >= 3 - Pycairo_CAPI = (Pycairo_CAPI_t*) PyCapsule_Import(const_cast("cairo.CAPI"), 0); -#else - Pycairo_CAPI = (Pycairo_CAPI_t*) PyCObject_Import(const_cast("cairo"), const_cast("CAPI")); + mapnik::marker_cache::instance().clear(); +#if defined(SHAPE_MEMORY_MAPPED_FILE) + mapnik::mapped_memory_cache::instance().clear(); #endif - if (Pycairo_CAPI == nullptr) return; - - boost::python::converter::registry::insert(&extract_surface, boost::python::type_id()); - boost::python::converter::registry::insert(&extract_context, boost::python::type_id()); } -#endif - -using mapnik::python_thread; -using mapnik::python_unblock_auto_block; -#ifdef MAPNIK_DEBUG -bool python_thread::thread_support = true; -#endif -boost::thread_specific_ptr python_thread::state; struct agg_renderer_visitor_1 { @@ -312,13 +187,13 @@ void render(mapnik::Map const& map, unsigned offset_x = 0u, unsigned offset_y = 0u) { - python_unblock_auto_block b; + py::gil_scoped_release release; mapnik::util::apply_visitor(agg_renderer_visitor_1(map, scale_factor, offset_x, offset_y), image); } void render_with_vars(mapnik::Map const& map, mapnik::image_any& image, - boost::python::dict const& d, + py::dict const& d, double scale_factor = 1.0, unsigned offset_x = 0u, unsigned offset_y = 0u) @@ -326,7 +201,7 @@ void render_with_vars(mapnik::Map const& map, mapnik::attributes vars = mapnik::dict2attr(d); mapnik::request req(map.width(),map.height(),map.get_current_extent()); req.set_buffer_size(map.buffer_size()); - python_unblock_auto_block b; + py::gil_scoped_release release; mapnik::util::apply_visitor(agg_renderer_visitor_3(map, req, vars, scale_factor, offset_x, offset_y), image); } @@ -338,7 +213,7 @@ void render_with_detector( unsigned offset_x = 0u, unsigned offset_y = 0u) { - python_unblock_auto_block b; + py::gil_scoped_release release; mapnik::util::apply_visitor(agg_renderer_visitor_2(map, detector, scale_factor, offset_x, offset_y), image); } @@ -358,7 +233,7 @@ void render_layer2(mapnik::Map const& map, throw std::runtime_error(s.str()); } - python_unblock_auto_block b; + py::gil_scoped_release release; mapnik::layer const& layer = layers[layer_idx]; std::set names; mapnik::util::apply_visitor(agg_renderer_visitor_4(map, scale_factor, offset_x, offset_y, layer, names), image); @@ -372,7 +247,7 @@ void render3(mapnik::Map const& map, unsigned offset_x = 0, unsigned offset_y = 0) { - python_unblock_auto_block b; + py::gil_scoped_release release; mapnik::cairo_surface_ptr surface(cairo_surface_reference(py_surface->surface), mapnik::cairo_surface_closer()); mapnik::cairo_renderer ren(map,mapnik::create_context(surface),scale_factor,offset_x,offset_y); ren.apply(); @@ -380,7 +255,7 @@ void render3(mapnik::Map const& map, void render4(mapnik::Map const& map, PycairoSurface* py_surface) { - python_unblock_auto_block b; + py::gil_scoped_release release; mapnik::cairo_surface_ptr surface(cairo_surface_reference(py_surface->surface), mapnik::cairo_surface_closer()); mapnik::cairo_renderer ren(map,mapnik::create_context(surface)); ren.apply(); @@ -392,7 +267,7 @@ void render5(mapnik::Map const& map, unsigned offset_x = 0, unsigned offset_y = 0) { - python_unblock_auto_block b; + py::gil_scoped_release release; mapnik::cairo_ptr context(cairo_reference(py_context->ctx), mapnik::cairo_closer()); mapnik::cairo_renderer ren(map,context,scale_factor,offset_x, offset_y); ren.apply(); @@ -400,7 +275,7 @@ void render5(mapnik::Map const& map, void render6(mapnik::Map const& map, PycairoContext* py_context) { - python_unblock_auto_block b; + py::gil_scoped_release release; mapnik::cairo_ptr context(cairo_reference(py_context->ctx), mapnik::cairo_closer()); mapnik::cairo_renderer ren(map,context); ren.apply(); @@ -410,7 +285,7 @@ void render_with_detector2( PycairoContext* py_context, std::shared_ptr detector) { - python_unblock_auto_block b; + py::gil_scoped_release release; mapnik::cairo_ptr context(cairo_reference(py_context->ctx), mapnik::cairo_closer()); mapnik::cairo_renderer ren(map,context,detector); ren.apply(); @@ -424,7 +299,7 @@ void render_with_detector3( unsigned offset_x = 0u, unsigned offset_y = 0u) { - python_unblock_auto_block b; + py::gil_scoped_release release; mapnik::cairo_ptr context(cairo_reference(py_context->ctx), mapnik::cairo_closer()); mapnik::cairo_renderer ren(map,context,detector,scale_factor,offset_x,offset_y); ren.apply(); @@ -435,7 +310,7 @@ void render_with_detector4( PycairoSurface* py_surface, std::shared_ptr detector) { - python_unblock_auto_block b; + py::gil_scoped_release release; mapnik::cairo_surface_ptr surface(cairo_surface_reference(py_surface->surface), mapnik::cairo_surface_closer()); mapnik::cairo_renderer ren(map, mapnik::create_context(surface), detector); ren.apply(); @@ -449,7 +324,7 @@ void render_with_detector5( unsigned offset_x = 0u, unsigned offset_y = 0u) { - python_unblock_auto_block b; + py::gil_scoped_release release; mapnik::cairo_surface_ptr surface(cairo_surface_reference(py_surface->surface), mapnik::cairo_surface_closer()); mapnik::cairo_renderer ren(map, mapnik::create_context(surface), detector, scale_factor, offset_x, offset_y); ren.apply(); @@ -527,8 +402,7 @@ void render_to_file2(mapnik::Map const& map,std::string const& filename) void render_to_file3(mapnik::Map const& map, std::string const& filename, std::string const& format, - double scale_factor = 1.0 - ) + double scale_factor = 1.0) { if (format == "svg-ng") { @@ -588,6 +462,31 @@ void standard_error_translator(std::exception const & ex) PyErr_SetString(PyExc_RuntimeError, ex.what()); } +// indicator for pycairo support in the python bindings +bool has_pycairo() +{ +#if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO) +#if PY_MAJOR_VERSION >= 3 + Pycairo_CAPI = (Pycairo_CAPI_t*) PyCapsule_Import(const_cast("cairo.CAPI"), 0); +#else + Pycairo_CAPI = (Pycairo_CAPI_t*) PyCObject_Import(const_cast("cairo"), const_cast("CAPI")); +#endif + if (Pycairo_CAPI == nullptr){ + /* + Case where pycairo support has been compiled into + mapnik but at runtime the cairo python module + is unable to be imported and therefore Pycairo surfaces + and contexts cannot be passed to mapnik.render() + */ + return false; + } + return true; +#else + return false; +#endif +} + + unsigned mapnik_version() { return MAPNIK_VERSION; @@ -598,9 +497,9 @@ std::string mapnik_version_string() return MAPNIK_VERSION_STRING; } -bool has_proj4() +bool has_proj() { -#if defined(MAPNIK_USE_PROJ4) +#if defined(MAPNIK_USE_PROJ) return true; #else return false; @@ -625,7 +524,7 @@ bool has_grid_renderer() #endif } -bool has_jpeg() +constexpr bool has_jpeg() { #if defined(HAVE_JPEG) return true; @@ -634,7 +533,7 @@ bool has_jpeg() #endif } -bool has_png() +constexpr bool has_png() { #if defined(HAVE_PNG) return true; @@ -657,8 +556,8 @@ bool has_webp() #if defined(HAVE_WEBP) return true; #else - return false; -#endif + return false; + #endif } // indicator for cairo rendering support inside libmapnik @@ -671,226 +570,154 @@ bool has_cairo() #endif } -// indicator for pycairo support in the python bindings -bool has_pycairo() -{ -#if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO) -#if PY_MAJOR_VERSION >= 3 - Pycairo_CAPI = (Pycairo_CAPI_t*) PyCapsule_Import(const_cast("cairo.CAPI"), 0); -#else - Pycairo_CAPI = (Pycairo_CAPI_t*) PyCObject_Import(const_cast("cairo"), const_cast("CAPI")); -#endif - if (Pycairo_CAPI == nullptr){ - /* - Case where pycairo support has been compiled into - mapnik but at runtime the cairo python module - is unable to be imported and therefore Pycairo surfaces - and contexts cannot be passed to mapnik.render() - */ - return false; - } - return true; -#else - return false; -#endif -} - - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-local-typedef" -BOOST_PYTHON_FUNCTION_OVERLOADS(load_map_overloads, load_map, 2, 4) -BOOST_PYTHON_FUNCTION_OVERLOADS(load_map_string_overloads, load_map_string, 2, 4) -BOOST_PYTHON_FUNCTION_OVERLOADS(save_map_overloads, save_map, 2, 3) -BOOST_PYTHON_FUNCTION_OVERLOADS(save_map_to_string_overloads, save_map_to_string, 1, 2) -BOOST_PYTHON_FUNCTION_OVERLOADS(render_overloads, render, 2, 5) -BOOST_PYTHON_FUNCTION_OVERLOADS(render_with_detector_overloads, render_with_detector, 3, 6) -#pragma GCC diagnostic pop - -BOOST_PYTHON_MODULE(_mapnik) -{ - - using namespace boost::python; - - using mapnik::load_map; - using mapnik::load_map_string; - using mapnik::save_map; - using mapnik::save_map_to_string; - - register_exception_translator(&standard_error_translator); - register_exception_translator(&out_of_range_error_translator); - register_exception_translator(&value_error_translator); - register_exception_translator(&runtime_error_translator); -#if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO) - register_cairo(); -#endif - export_query(); - export_geometry(); - export_feature(); - export_featureset(); - export_fontset(); - export_datasource(); - export_parameters(); - export_color(); - export_envelope(); - export_palette(); - export_image(); - export_image_view(); - export_gamma_method(); - export_scaling_method(); +} // namespace + + +void export_color(py::module const&); +void export_composite_modes(py::module const&); +void export_coord(py::module const&); +void export_envelope(py::module const&); +void export_gamma_method(py::module const&); +void export_geometry(py::module const&); +void export_feature(py::module const&); +void export_featureset(py::module const&); +void export_font_engine(py::module const&); +void export_fontset(py::module const&); +void export_expression(py::module const&); +void export_datasource(py::module&); // non-const because of m.def(..) +void export_datasource_cache(py::module const&); #if defined(GRID_RENDERER) - export_grid(); - export_grid_view(); +void export_grid(py::module const&); +void export_grid_view(py::module const&); #endif - export_expression(); - export_rule(); - export_style(); - export_layer(); - export_datasource_cache(); - export_symbolizer(); - export_markers_symbolizer(); - export_point_symbolizer(); - export_line_symbolizer(); - export_line_pattern_symbolizer(); - export_polygon_symbolizer(); - export_building_symbolizer(); - export_polygon_pattern_symbolizer(); - export_raster_symbolizer(); - export_text_symbolizer(); - export_shield_symbolizer(); - export_debug_symbolizer(); - export_group_symbolizer(); - export_font_engine(); - export_projection(); - export_proj_transform(); - export_view_transform(); - export_coord(); - export_map(); - export_raster_colorizer(); - export_label_collision_detector(); - export_logger(); - - def("clear_cache", &clear_cache, - "\n" - "Clear all global caches of markers and mapped memory regions.\n" - "\n" - "Usage:\n" - ">>> from mapnik import clear_cache\n" - ">>> clear_cache()\n" - ); - - def("render_to_file",&render_to_file1, - "\n" - "Render Map to file using explicit image type.\n" - "\n" - "Usage:\n" - ">>> from mapnik import Map, render_to_file, load_map\n" - ">>> m = Map(256,256)\n" - ">>> load_map(m,'mapfile.xml')\n" - ">>> render_to_file(m,'image32bit.png','png')\n" - "\n" - "8 bit (paletted) PNG can be requested with 'png256':\n" - ">>> render_to_file(m,'8bit_image.png','png256')\n" - "\n" - "JPEG quality can be controlled by adding a suffix to\n" - "'jpeg' between 0 and 100 (default is 85):\n" - ">>> render_to_file(m,'top_quality.jpeg','jpeg100')\n" - ">>> render_to_file(m,'medium_quality.jpeg','jpeg50')\n" - ); - - def("render_to_file",&render_to_file2, - "\n" - "Render Map to file (type taken from file extension)\n" - "\n" - "Usage:\n" - ">>> from mapnik import Map, render_to_file, load_map\n" - ">>> m = Map(256,256)\n" - ">>> render_to_file(m,'image.jpeg')\n" - "\n" - ); - - def("render_to_file",&render_to_file3, - "\n" - "Render Map to file using explicit image type and scale factor.\n" - "\n" - "Usage:\n" - ">>> from mapnik import Map, render_to_file, load_map\n" - ">>> m = Map(256,256)\n" - ">>> scale_factor = 4\n" - ">>> render_to_file(m,'image.jpeg',scale_factor)\n" - "\n" - ); - - def("render_tile_to_file",&render_tile_to_file, - "\n" - "TODO\n" - "\n" - ); - - def("render_with_vars",&render_with_vars, - (arg("map"), - arg("image"), - arg("vars"), - arg("scale_factor")=1.0, - arg("offset_x")=0, - arg("offset_y")=0 - ) - ); - - def("render", &render, render_overloads( - "\n" - "Render Map to an AGG image_any using offsets\n" - "\n" - "Usage:\n" - ">>> from mapnik import Map, Image, render, load_map\n" - ">>> m = Map(256,256)\n" - ">>> load_map(m,'mapfile.xml')\n" - ">>> im = Image(m.width,m.height)\n" - ">>> scale_factor=2.0\n" - ">>> offset = [100,50]\n" - ">>> render(m,im)\n" - ">>> render(m,im,scale_factor)\n" - ">>> render(m,im,scale_factor,offset[0],offset[1])\n" - "\n" - )); - - def("render_with_detector", &render_with_detector, render_with_detector_overloads( - "\n" - "Render Map to an AGG image_any using a pre-constructed detector.\n" - "\n" - "Usage:\n" - ">>> from mapnik import Map, Image, LabelCollisionDetector, render_with_detector, load_map\n" - ">>> m = Map(256,256)\n" - ">>> load_map(m,'mapfile.xml')\n" - ">>> im = Image(m.width,m.height)\n" - ">>> detector = LabelCollisionDetector(m)\n" - ">>> render_with_detector(m, im, detector)\n" - )); - - def("render_layer", &render_layer2, - (arg("map"), - arg("image"), - arg("layer"), - arg("scale_factor")=1.0, - arg("offset_x")=0, - arg("offset_y")=0 - ) - ); - +void export_image(py::module const&); +void export_image_view(py::module const&); +void export_layer(py::module const&); +void export_map(py::module const&); +void export_projection(py::module&); // non-const because of m.def(..) +void export_proj_transform(py::module const&); +void export_query(py::module const&); +void export_rule(py::module const&); +void export_symbolizer(py::module const&); +void export_polygon_symbolizer(py::module const&); +void export_line_symbolizer(py::module const&); +void export_point_symbolizer(py::module const&); +void export_style(py::module const&); +void export_logger(py::module const&); +void export_placement_finder(py::module const&); +void export_text_symbolizer(py::module const&); +void export_debug_symbolizer(py::module const&); +void export_markers_symbolizer(py::module const&); +void export_polygon_pattern_symbolizer(py::module const&); +void export_line_pattern_symbolizer(py::module const&); +void export_raster_symbolizer(py::module const&); +void export_palette(py::module const&); +void export_parameters(py::module const&); +void export_raster_colorizer(py::module const&); +void export_scaling_method(py::module const&); +void export_label_collision_detector(py::module const& m); +void export_dot_symbolizer(py::module const&); +void export_shield_symbolizer(py::module const&); +void export_group_symbolizer(py::module const&); +void export_building_symbolizer(py::module const&); + +using mapnik::load_map; +using mapnik::load_map_string; +using mapnik::save_map; +using mapnik::save_map_to_string; + + +PYBIND11_MODULE(_mapnik, m) { + export_color(m); + export_composite_modes(m); + export_coord(m); + export_envelope(m); + export_geometry(m); + export_gamma_method(m); + export_feature(m); + export_featureset(m); + export_font_engine(m); + export_fontset(m); + export_expression(m); + export_datasource(m); + export_datasource_cache(m); #if defined(GRID_RENDERER) - def("render_layer", &mapnik::render_layer_for_grid, - (arg("map"), - arg("grid"), - arg("layer"), - arg("fields")=boost::python::list(), - arg("scale_factor")=1.0, - arg("offset_x")=0, - arg("offset_y")=0 - ) - ); + export_grid(m); + export_grid_view(m); #endif + export_image(m); + export_image_view(m); + export_layer(m); + export_map(m); + export_projection(m); + export_proj_transform(m); + export_query(m); + export_rule(m); + export_symbolizer(m); + export_polygon_symbolizer(m); + export_line_symbolizer(m); + export_point_symbolizer(m); + export_style(m); + export_logger(m); + export_placement_finder(m); + export_text_symbolizer(m); + export_palette(m); + export_parameters(m); + export_debug_symbolizer(m); + export_markers_symbolizer(m); + export_polygon_pattern_symbolizer(m); + export_line_pattern_symbolizer(m); + export_raster_symbolizer(m); + export_raster_colorizer(m); + export_scaling_method(m); + export_label_collision_detector(m); + export_dot_symbolizer(m); + export_shield_symbolizer(m); + export_group_symbolizer(m); + export_building_symbolizer(m); + + // + m.def("version", &mapnik_version,"Get the Mapnik version number"); + m.def("version_string", &mapnik_version_string,"Get the Mapnik version string"); + m.def("has_proj", &has_proj, "Get proj status"); + m.def("has_jpeg", &has_jpeg, "Get jpeg read/write support status"); + m.def("has_png", &has_png, "Get png read/write support status"); + m.def("has_tiff", &has_tiff, "Get tiff read/write support status"); + m.def("has_webp", &has_webp, "Get webp read/write support status"); + m.def("has_svg_renderer", &has_svg_renderer, "Get svg_renderer status"); + m.def("has_grid_renderer", &has_grid_renderer, "Get grid_renderer status"); + m.def("has_cairo", &has_cairo, "Get cairo library status"); + + m.def("load_map", &load_map, + py::arg("Map"), + py::arg("filename"), + py::arg("strict")=false, + py::arg("base_path") = "" ); + + m.def("load_map_from_string", &load_map_string, + py::arg("Map"), + py::arg("str"), + py::arg("strict")=false, + py::arg("base_path") = "" ); + + // render + m.def("render", &render, + py::arg("Map"), + py::arg("image"), + py::arg("scale_factor") = 1.0, + py::arg("offset_x") = 0, + py::arg("offset_y") = 0); + + m.def("render_with_detector", &render_with_detector, + py::arg("Map"), + py::arg("image"), + py::arg("detector"), + py::arg("scale_factor") = 1.0, + py::arg("offset_x") = 0, + py::arg("offset_y") = 0); #if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO) - def("render",&render3, + m.def("render",&render3, "\n" "Render Map to Cairo Surface using offsets\n" "\n" @@ -904,7 +731,7 @@ BOOST_PYTHON_MODULE(_mapnik) "\n" ); - def("render",&render4, + m.def("render",&render4, "\n" "Render Map to Cairo Surface\n" "\n" @@ -918,7 +745,7 @@ BOOST_PYTHON_MODULE(_mapnik) "\n" ); - def("render",&render5, + m.def("render",&render5, "\n" "Render Map to Cairo Context using offsets\n" "\n" @@ -932,7 +759,7 @@ BOOST_PYTHON_MODULE(_mapnik) "\n" ); - def("render",&render6, + m.def("render",&render6, "\n" "Render Map to Cairo Context\n" "\n" @@ -946,7 +773,7 @@ BOOST_PYTHON_MODULE(_mapnik) "\n" ); - def("render_with_detector", &render_with_detector2, + m.def("render_with_detector", &render_with_detector2, "\n" "Render Map to Cairo Context using a pre-constructed detector.\n" "\n" @@ -961,7 +788,7 @@ BOOST_PYTHON_MODULE(_mapnik) ">>> render_with_detector(m, ctx, detector)\n" ); - def("render_with_detector", &render_with_detector3, + m.def("render_with_detector", &render_with_detector3, "\n" "Render Map to Cairo Context using a pre-constructed detector, scale and offsets.\n" "\n" @@ -976,7 +803,7 @@ BOOST_PYTHON_MODULE(_mapnik) ">>> render_with_detector(m, ctx, detector, 1, 1, 1)\n" ); - def("render_with_detector", &render_with_detector4, + m.def("render_with_detector", &render_with_detector4, "\n" "Render Map to Cairo Surface using a pre-constructed detector.\n" "\n" @@ -990,7 +817,7 @@ BOOST_PYTHON_MODULE(_mapnik) ">>> render_with_detector(m, surface, detector)\n" ); - def("render_with_detector", &render_with_detector5, + m.def("render_with_detector", &render_with_detector5, "\n" "Render Map to Cairo Surface using a pre-constructed detector, scale and offsets.\n" "\n" @@ -1003,80 +830,384 @@ BOOST_PYTHON_MODULE(_mapnik) ">>> detector = LabelCollisionDetector(m)\n" ">>> render_with_detector(m, surface, detector, 1, 1, 1)\n" ); - #endif - def("scale_denominator", &scale_denominator, - (arg("map"),arg("is_geographic")), - "\n" - "Return the Map Scale Denominator.\n" - "Also available as Map.scale_denominator()\n" - "\n" - "Usage:\n" - "\n" - ">>> from mapnik import Map, Projection, scale_denominator, load_map\n" - ">>> m = Map(256,256)\n" - ">>> load_map(m,'mapfile.xml')\n" - ">>> scale_denominator(m,Projection(m.srs).geographic)\n" - "\n" + m.def("render_layer", &render_layer2, + py::arg("map"), + py::arg("image"), + py::arg("layer"), + py::arg("scale_factor")=1.0, + py::arg("offset_x")=0, + py::arg("offset_y")=0 ); - def("load_map", &load_map, load_map_overloads()); - - def("load_map_from_string", &load_map_string, load_map_string_overloads()); - - def("save_map", &save_map, save_map_overloads()); -/* - "\n" - "Save Map object to XML file\n" - "\n" - "Usage:\n" - ">>> from mapnik import Map, load_map, save_map\n" - ">>> m = Map(256,256)\n" - ">>> load_map(m,'mapfile_wgs84.xml')\n" - ">>> m.srs\n" - "'+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs'\n" - ">>> m.srs = '+init=espg:3395'\n" - ">>> save_map(m,'mapfile_mercator.xml')\n" - "\n" - ); -*/ - - def("save_map_to_string", &save_map_to_string, save_map_to_string_overloads()); - def("mapnik_version", &mapnik_version,"Get the Mapnik version number"); - def("mapnik_version_string", &mapnik_version_string,"Get the Mapnik version string"); - def("has_proj4", &has_proj4, "Get proj4 status"); - def("has_jpeg", &has_jpeg, "Get jpeg read/write support status"); - def("has_png", &has_png, "Get png read/write support status"); - def("has_tiff", &has_tiff, "Get tiff read/write support status"); - def("has_webp", &has_webp, "Get webp read/write support status"); - def("has_svg_renderer", &has_svg_renderer, "Get svg_renderer status"); - def("has_grid_renderer", &has_grid_renderer, "Get grid_renderer status"); - def("has_cairo", &has_cairo, "Get cairo library status"); - def("has_pycairo", &has_pycairo, "Get pycairo module status"); - - python_optional(); - python_optional(); - python_optional >(); - python_optional(); - python_optional(); - python_optional(); - python_optional(); - python_optional(); - python_optional(); - python_optional(); - python_optional(); - python_optional(); - register_ptr_to_python(); - register_ptr_to_python(); -#if BOOST_VERSION == 106000 // ref #104 - register_ptr_to_python > >(); - register_ptr_to_python >(); - register_ptr_to_python >(); - register_ptr_to_python >(); - register_ptr_to_python >(); +#if defined(GRID_RENDERER) + m.def("render_layer", &mapnik::render_layer_for_grid, + py::arg("map"), + py::arg("grid"), + py::arg("layer"), + py::arg("fields") = py::list(), + py::arg("scale_factor")=1.0, + py::arg("offset_x")=0, + py::arg("offset_y")=0); #endif - to_python_converter(); - to_python_converter(); - to_python_converter(); + + // save + m.def("save_map", &save_map, + py::arg("Map"), + py::arg("filename"), + py::arg("explicit_defaults") = false); + + m.def("save_map_to_string", &save_map_to_string, + py::arg("Map"), + py::arg("explicit_defaults") = false); + + m.def("clear_cache", &clear_cache, + "\n" + "Clear all global caches of markers and mapped memory regions.\n" + "\n" + "Usage:\n" + ">>> from mapnik import clear_cache\n" + ">>> clear_cache()\n"); + + + m.def("render_to_file",&render_to_file1, + "\n" + "Render Map to file using explicit image type.\n" + "\n" + "Usage:\n" + ">>> from mapnik import Map, render_to_file, load_map\n" + ">>> m = Map(256,256)\n" + ">>> load_map(m,'mapfile.xml')\n" + ">>> render_to_file(m,'image32bit.png','png')\n" + "\n" + "8 bit (paletted) PNG can be requested with 'png256':\n" + ">>> render_to_file(m,'8bit_image.png','png256')\n" + "\n" + "JPEG quality can be controlled by adding a suffix to\n" + "'jpeg' between 0 and 100 (default is 85):\n" + ">>> render_to_file(m,'top_quality.jpeg','jpeg100')\n" + ">>> render_to_file(m,'medium_quality.jpeg','jpeg50')\n"); + + m.def("render_to_file",&render_to_file2, + "\n" + "Render Map to file (type taken from file extension)\n" + "\n" + "Usage:\n" + ">>> from mapnik import Map, render_to_file, load_map\n" + ">>> m = Map(256,256)\n" + ">>> render_to_file(m,'image.jpeg')\n" + "\n"); + + m.def("render_to_file",&render_to_file3, + "\n" + "Render Map to file using explicit image type and scale factor.\n" + "\n" + "Usage:\n" + ">>> from mapnik import Map, render_to_file, load_map\n" + ">>> m = Map(256,256)\n" + ">>> scale_factor = 4\n" + ">>> render_to_file(m,'image.jpeg',scale_factor)\n" + "\n"); + + m.def("has_pycairo", &has_pycairo, "Get pycairo module status"); } + +// // stl +// #include +// #include + +// void export_color(); +// void export_composite_modes(); +// void export_coord(); +// void export_layer(); +// void export_parameters(); +// void export_envelope(); +// void export_query(); +// void export_geometry(); +// void export_palette(); +// void export_image(); +// void export_image_view(); +// void export_gamma_method(); +// void export_scaling_method(); +// #if defined(GRID_RENDERER) +// void export_grid(); +// void export_grid_view(); +// #endif +// void export_map(); +// void export_python(); +// void export_expression(); +// void export_rule(); +// void export_style(); +// void export_feature(); +// void export_featureset(); +// void export_fontset(); +// void export_datasource(); +// void export_datasource_cache(); +// void export_symbolizer(); +// void export_markers_symbolizer(); +// void export_point_symbolizer(); +// void export_line_symbolizer(); +// void export_line_pattern_symbolizer(); +// void export_polygon_symbolizer(); +// void export_building_symbolizer(); +// void export_placement_finder(); +// void export_polygon_pattern_symbolizer(); +// void export_raster_symbolizer(); +// void export_text_symbolizer(); +// void export_shield_symbolizer(); +// void export_debug_symbolizer(); +// void export_group_symbolizer(); +// void export_font_engine(); +// void export_projection(); +// void export_proj_transform(); +// void export_view_transform(); +// void export_raster_colorizer(); +// void export_label_collision_detector(); +// void export_logger(); + +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #if defined(GRID_RENDERER) +// #include "python_grid_utils.hpp" +// #endif +//#include "mapnik_value_converter.hpp" +// #include "mapnik_enumeration_wrapper_converter.hpp" +//#include "mapnik_threads.hpp" +// #include "python_optional.hpp" +// #include +// #if defined(SHAPE_MEMORY_MAPPED_FILE) +// #include +// #endif + +// #if defined(SVG_RENDERER) +// #include +// #endif + +// namespace mapnik { +// class font_set; +// class layer; +// class color; +// class label_collision_detector4; +// } + +// #if defined(HAVE_CAIRO) +// #include +// #include +// #include +// #endif + +// #if defined(HAVE_PYCAIRO) +// #include +// #include +// #if PY_MAJOR_VERSION >= 3 +// #include +// #else +// #include +// static Pycairo_CAPI_t *Pycairo_CAPI; +// #endif + +// static void *extract_surface(PyObject* op) +// { +// if (PyObject_TypeCheck(op, const_cast(Pycairo_CAPI->Surface_Type))) +// { +// return op; +// } +// else +// { +// return 0; +// } +// } + +// static void *extract_context(PyObject* op) +// { +// if (PyObject_TypeCheck(op, const_cast(Pycairo_CAPI->Context_Type))) +// { +// return op; +// } +// else +// { +// return 0; +// } +// } + +// void register_cairo() +// { +// #if PY_MAJOR_VERSION >= 3 +// Pycairo_CAPI = (Pycairo_CAPI_t*) PyCapsule_Import(const_cast("cairo.CAPI"), 0); +// #else +// Pycairo_CAPI = (Pycairo_CAPI_t*) PyCObject_Import(const_cast("cairo"), const_cast("CAPI")); +// #endif +// if (Pycairo_CAPI == nullptr) return; + +// boost::python::converter::registry::insert(&extract_surface, boost::python::type_id()); +// boost::python::converter::registry::insert(&extract_context, boost::python::type_id()); +// } +// #endif + + + +// #pragma GCC diagnostic push +// #pragma GCC diagnostic ignored "-Wunused-local-typedef" +// BOOST_PYTHON_FUNCTION_OVERLOADS(load_map_overloads, load_map, 2, 4) +// BOOST_PYTHON_FUNCTION_OVERLOADS(load_map_string_overloads, load_map_string, 2, 4) +// BOOST_PYTHON_FUNCTION_OVERLOADS(save_map_overloads, save_map, 2, 3) +// BOOST_PYTHON_FUNCTION_OVERLOADS(save_map_to_string_overloads, save_map_to_string, 1, 2) +// BOOST_PYTHON_FUNCTION_OVERLOADS(render_overloads, render, 2, 5) +// BOOST_PYTHON_FUNCTION_OVERLOADS(render_with_detector_overloads, render_with_detector, 3, 6) +// #pragma GCC diagnostic pop + +// BOOST_PYTHON_MODULE(_mapnik) +// { + +// using namespace boost::python; + +// using mapnik::load_map; +// using mapnik::load_map_string; +// using mapnik::save_map; +// using mapnik::save_map_to_string; + +// register_exception_translator(&standard_error_translator); +// register_exception_translator(&out_of_range_error_translator); +// register_exception_translator(&value_error_translator); +// register_exception_translator(&runtime_error_translator); +// #if defined(HAVE_CAIRO) && defined(HAVE_PYCAIRO) +// register_cairo(); +// #endif +// export_query(); +// export_geometry(); +// export_feature(); +// export_featureset(); +// export_fontset(); +// export_datasource(); +// export_parameters(); +// export_color(); +// export_composite_modes(); +// export_envelope(); +// export_palette(); +// export_image(); +// export_image_view(); +// export_gamma_method(); +// export_scaling_method(); +// #if defined(GRID_RENDERER) +// export_grid(); +// export_grid_view(); +// #endif +// export_expression(); +// export_rule(); +// export_style(); +// export_layer(); +// export_datasource_cache(); +// export_symbolizer(); +// export_markers_symbolizer(); +// export_point_symbolizer(); +// export_line_symbolizer(); +// export_line_pattern_symbolizer(); +// export_polygon_symbolizer(); +// export_building_symbolizer(); +// export_placement_finder(); +// export_polygon_pattern_symbolizer(); +// export_raster_symbolizer(); +// export_text_symbolizer(); +// export_shield_symbolizer(); +// export_debug_symbolizer(); +// export_group_symbolizer(); +// export_font_engine(); +// export_projection(); +// export_proj_transform(); +// export_view_transform(); +// export_coord(); +// export_map(); +// export_raster_colorizer(); +// export_label_collision_detector(); +// export_logger(); + + +// def("render_tile_to_file",&render_tile_to_file, +// "\n" +// "TODO\n" +// "\n" +// ); + +// def("render_with_vars",&render_with_vars, +// (arg("map"), +// arg("image"), +// arg("vars"), +// arg("scale_factor")=1.0, +// arg("offset_x")=0, +// arg("offset_y")=0 +// ) +// ); + +// def("render", &render, render_overloads( +// "\n" +// "Render Map to an AGG image_any using offsets\n" +// "\n" +// "Usage:\n" +// ">>> from mapnik import Map, Image, render, load_map\n" +// ">>> m = Map(256,256)\n" +// ">>> load_map(m,'mapfile.xml')\n" +// ">>> im = Image(m.width,m.height)\n" +// ">>> scale_factor=2.0\n" +// ">>> offset = [100,50]\n" +// ">>> render(m,im)\n" +// ">>> render(m,im,scale_factor)\n" +// ">>> render(m,im,scale_factor,offset[0],offset[1])\n" +// "\n" +// )); + +// def("render_with_detector", &render_with_detector, render_with_detector_overloads( +// "\n" +// "Render Map to an AGG image_any using a pre-constructed detector.\n" +// "\n" +// "Usage:\n" +// ">>> from mapnik import Map, Image, LabelCollisionDetector, render_with_detector, load_map\n" +// ">>> m = Map(256,256)\n" +// ">>> load_map(m,'mapfile.xml')\n" +// ">>> im = Image(m.width,m.height)\n" +// ">>> detector = LabelCollisionDetector(m)\n" +// ">>> render_with_detector(m, im, detector)\n" +// )); +// def("save_map_to_string", &save_map_to_string, save_map_to_string_overloads()); +// def("mapnik_version", &mapnik_version,"Get the Mapnik version number"); +// def("mapnik_version_string", &mapnik_version_string,"Get the Mapnik version string"); +// def("has_proj", &has_proj, "Get proj status"); +// def("has_jpeg", &has_jpeg, "Get jpeg read/write support status"); +// def("has_png", &has_png, "Get png read/write support status"); +// def("has_tiff", &has_tiff, "Get tiff read/write support status"); +// def("has_webp", &has_webp, "Get webp read/write support status"); +// def("has_svg_renderer", &has_svg_renderer, "Get svg_renderer status"); +// def("has_grid_renderer", &has_grid_renderer, "Get grid_renderer status"); +// def("has_cairo", &has_cairo, "Get cairo library status"); +// def("has_pycairo", &has_pycairo, "Get pycairo module status"); + +// python_optional(); +// python_optional(); +// python_optional >(); +// python_optional(); +// python_optional(); +// python_optional(); +// python_optional(); +// python_optional(); +// python_optional(); +// python_optional(); +// python_optional(); +// python_optional(); +// register_ptr_to_python(); +// register_ptr_to_python(); + +// to_python_converter(); +// to_python_converter(); +// to_python_converter(); +// } diff --git a/src/mapnik_query.cpp b/src/mapnik_query.cpp index f073779f7..cc32e5c6d 100644 --- a/src/mapnik_query.cpp +++ b/src/mapnik_query.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,85 +20,59 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#pragma GCC diagnostic pop - -#include "python_to_value.hpp" - // mapnik +#include #include #include - +#include "python_to_value.hpp" +#include "mapnik_value_converter.hpp" +//stl #include #include +//pybind11 +#include +#include -using mapnik::query; -using mapnik::box2d; - -namespace python = boost::python; - -struct resolution_to_tuple -{ - static PyObject* convert(query::resolution_type const& x) - { - python::object tuple(python::make_tuple(std::get<0>(x), std::get<1>(x))); - return python::incref(tuple.ptr()); - } - - static PyTypeObject const* get_pytype() - { - return &PyTuple_Type; - } -}; - -struct names_to_list -{ - static PyObject* convert(std::set const& names) - { - boost::python::list l; - for ( std::string const& name : names ) - { - l.append(name); - } - return python::incref(l.ptr()); - } - - static PyTypeObject const* get_pytype() - { - return &PyList_Type; - } -}; +namespace py = pybind11; -namespace { - - void set_variables(mapnik::query & q, boost::python::dict const& d) - { - mapnik::attributes vars = mapnik::dict2attr(d); - q.set_variables(vars); - } -} - -void export_query() +void export_query(py::module const& m) { - using namespace boost::python; - - to_python_converter (); - to_python_converter, names_to_list> (); - - class_("Query", "a spatial query data object", - init,query::resolution_type const&,double>() ) - .def(init >()) - .add_property("resolution",make_function(&query::resolution, - return_value_policy())) - .add_property("bbox", make_function(&query::get_bbox, - return_value_policy()) ) - .add_property("property_names", make_function(&query::property_names, - return_value_policy()) ) + using mapnik::query; + using mapnik::box2d; + + py::class_(m, "Query", "a spatial query data object") + .def(py::init,query::resolution_type const&, double>()) + .def(py::init>()) + .def_property_readonly("resolution", [] (query const& q) { + auto resolution = q.resolution(); + return py::make_tuple(std::get<0>(resolution), + std::get<1>(resolution)); + }) + .def_property_readonly("scale_denominator", &query::scale_denominator) + .def_property_readonly("bbox", &query::get_bbox) + .def_property_readonly("unbuffered_bbox", &query::get_unbuffered_bbox) + .def_property_readonly("property_names",[] (query const& q){ + auto names = q.property_names(); + py::list obj; + for (std::string const& name : names) + { + obj.append(name); + } + return obj; + }) .def("add_property_name", &query::add_property_name) - .def("set_variables",&set_variables); + .def_property("variables", + [] (query const& q) { + py::dict d; + for (auto kv : q.variables()) + { + d[kv.first.c_str()] = kv.second; + } + return d; + }, + [] (query& q, py::dict const& d) { + mapnik::attributes vars = mapnik::dict2attr(d); + q.set_variables(vars); + }) + ; } diff --git a/src/mapnik_raster_colorizer.cpp b/src/mapnik_raster_colorizer.cpp index 6a8a709b3..b7b733cd8 100644 --- a/src/mapnik_raster_colorizer.cpp +++ b/src/mapnik_raster_colorizer.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 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,18 +20,16 @@ * *****************************************************************************/ +//mapnik #include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - -// mapnik #include #include +//pybind11 +#include +#include +#include + +namespace py = pybind11; using mapnik::raster_colorizer; using mapnik::raster_colorizer_ptr; @@ -40,11 +38,6 @@ using mapnik::colorizer_stop; using mapnik::colorizer_stops; using mapnik::colorizer_mode_enum; using mapnik::color; -using mapnik::COLORIZER_INHERIT; -using mapnik::COLORIZER_LINEAR; -using mapnik::COLORIZER_DISCRETE; -using mapnik::COLORIZER_EXACT; - namespace { void add_stop(raster_colorizer_ptr & rc, colorizer_stop & stop) @@ -76,7 +69,7 @@ void add_stop5(raster_colorizer_ptr &rc, float v, colorizer_mode_enum m, color c rc->add_stop(stop); } -mapnik::color get_color(raster_colorizer_ptr &rc, float value) +mapnik::color get_color(raster_colorizer_ptr const&rc, float value) { unsigned rgba = rc->get_color(value); unsigned r = (rgba & 0xff); @@ -93,83 +86,80 @@ colorizer_stops const& get_stops(raster_colorizer_ptr & rc) } -void export_raster_colorizer() +void export_raster_colorizer(py::module const& m) { - using namespace boost::python; - - implicitly_convertible(); - - class_("RasterColorizer", - "A Raster Colorizer object.", - init(args("default_mode","default_color")) - ) - .def(init<>()) - .add_property("default_color", - make_function(&raster_colorizer::get_default_color, return_value_policy()), + py::class_(m, "RasterColorizer", + "A Raster Colorizer object.") + + .def(py::init(), + py::arg("default_mode"), py::arg("default_color")) + .def(py::init<>()) + .def_property("default_color", + &raster_colorizer::get_default_color, &raster_colorizer::set_default_color, "The default color for stops added without a color (mapnik.Color).\n") - .add_property("default_mode", + .def_property("default_mode", &raster_colorizer::get_default_mode_enum, &raster_colorizer::set_default_mode_enum, "The default mode (mapnik.ColorizerMode).\n" "\n" "If a stop is added without a mode, then it will inherit this default mode\n") - .add_property("stops", - make_function(get_stops,return_value_policy()), + .def_property_readonly("stops", + get_stops, "The list of stops this RasterColorizer contains\n") - .add_property("epsilon", + .def_property("epsilon", &raster_colorizer::get_epsilon, &raster_colorizer::set_epsilon, "Comparison epsilon value for exact mode\n" "\n" "When comparing values in exact mode, values need only be within epsilon to match.\n") - .def("add_stop", add_stop, - (arg("ColorizerStop")), "Add a colorizer stop to the raster colorizer.\n" "\n" "Usage:\n" ">>> colorizer = mapnik.RasterColorizer()\n" ">>> color = mapnik.Color(\"#0044cc\")\n" ">>> stop = mapnik.ColorizerStop(3, mapnik.COLORIZER_INHERIT, color)\n" - ">>> colorizer.add_stop(stop)\n" + ">>> colorizer.add_stop(stop)\n", + py::arg("ColorizerStop") ) .def("add_stop", add_stop2, - (arg("value")), + "Add a colorizer stop to the raster colorizer, using the default mode and color.\n" "\n" "Usage:\n" ">>> default_color = mapnik.Color(\"#0044cc\")\n" ">>> colorizer = mapnik.RasterColorizer(mapnik.COLORIZER_LINEAR, default_color)\n" - ">>> colorizer.add_stop(100)\n" + ">>> colorizer.add_stop(100)\n", + py::arg("value") ) .def("add_stop", add_stop3, - (arg("value")), "Add a colorizer stop to the raster colorizer, using the default mode.\n" "\n" "Usage:\n" ">>> default_color = mapnik.Color(\"#0044cc\")\n" ">>> colorizer = mapnik.RasterColorizer(mapnik.COLORIZER_LINEAR, default_color)\n" - ">>> colorizer.add_stop(100, mapnik.Color(\"#123456\"))\n" + ">>> colorizer.add_stop(100, mapnik.Color(\"#123456\"))\n", + py::arg("value"), py::arg("color") ) .def("add_stop", add_stop4, - (arg("value")), "Add a colorizer stop to the raster colorizer, using the default color.\n" "\n" "Usage:\n" ">>> default_color = mapnik.Color(\"#0044cc\")\n" ">>> colorizer = mapnik.RasterColorizer(mapnik.COLORIZER_LINEAR, default_color)\n" - ">>> colorizer.add_stop(100, mapnik.COLORIZER_EXACT)\n" + ">>> colorizer.add_stop(100, mapnik.COLORIZER_EXACT)\n", + py::arg("value"), py::arg("ColorizerMode") ) .def("add_stop", add_stop5, - (arg("value")), "Add a colorizer stop to the raster colorizer.\n" "\n" "Usage:\n" ">>> default_color = mapnik.Color(\"#0044cc\")\n" ">>> colorizer = mapnik.RasterColorizer(mapnik.COLORIZER_LINEAR, default_color)\n" - ">>> colorizer.add_stop(100, mapnik.COLORIZER_DISCRETE, mapnik.Color(\"#112233\"))\n" + ">>> colorizer.add_stop(100, mapnik.COLORIZER_DISCRETE, mapnik.Color(\"#112233\"))\n", + py::arg("value"), py::arg("ColorizerMode"), py::arg("color") ) .def("get_color", get_color, "Get the color assigned to a certain value in raster data.\n" @@ -186,52 +176,54 @@ void export_raster_colorizer() - class_("ColorizerStops", + py::class_(m, "ColorizerStops", "A RasterColorizer's collection of ordered color stops.\n" "This class is not meant to be instantiated from python. However, " "it can be accessed at a RasterColorizer's \"stops\" attribute for " - "introspection purposes", - no_init) - .def(vector_indexing_suite()) + "introspection purposes") + .def("__iter__", [] (colorizer_stops const& stops) { + return py::make_iterator(stops.begin(), stops.end()); + }) ; - enum_("ColorizerMode") - .value("COLORIZER_INHERIT", COLORIZER_INHERIT) - .value("COLORIZER_LINEAR", COLORIZER_LINEAR) - .value("COLORIZER_DISCRETE", COLORIZER_DISCRETE) - .value("COLORIZER_EXACT", COLORIZER_EXACT) + py::native_enum(m, "ColorizerMode", "enum.Enum") + .value("COLORIZER_INHERIT", colorizer_mode_enum::COLORIZER_INHERIT) + .value("COLORIZER_LINEAR", colorizer_mode_enum::COLORIZER_LINEAR) + .value("COLORIZER_DISCRETE", colorizer_mode_enum::COLORIZER_DISCRETE) + .value("COLORIZER_EXACT", colorizer_mode_enum::COLORIZER_EXACT) .export_values() + .finalize() ; - class_("ColorizerStop",init( + py::class_(m, "ColorizerStop", "A Colorizer Stop object.\n" "Create with a value, ColorizerMode, and Color\n" "\n" "Usage:" ">>> color = mapnik.Color(\"#fff000\")\n" - ">>> stop= mapnik.ColorizerStop(42.42, mapnik.COLORIZER_LINEAR, color)\n" - )) - .add_property("color", - make_function(&colorizer_stop::get_color, return_value_policy()), + ">>> stop= mapnik.ColorizerStop(42.42, mapnik.COLORIZER_LINEAR, color)\n") + .def(py::init()) + .def_property("color", + &colorizer_stop::get_color, &colorizer_stop::set_color, "The stop color (mapnik.Color).\n") - .add_property("value", + .def_property("value", &colorizer_stop::get_value, &colorizer_stop::set_value, "The stop value.\n") - .add_property("label", - make_function(&colorizer_stop::get_label, return_value_policy()), + .def_property("label", + &colorizer_stop::get_label, &colorizer_stop::set_label, "The stop label.\n") - .add_property("mode", + .def_property("mode", &colorizer_stop::get_mode_enum, &colorizer_stop::set_mode_enum, "The stop mode (mapnik.ColorizerMode).\n" "\n" "If this is COLORIZER_INHERIT then it will inherit the default mode\n" " from the RasterColorizer it is added to.\n") - .def(self == self) - .def("__str__",&colorizer_stop::to_string) + .def(py::self == py::self) + .def("__str__", &colorizer_stop::to_string) ; } diff --git a/src/mapnik_raster_symbolizer.cpp b/src/mapnik_raster_symbolizer.cpp new file mode 100644 index 000000000..a4aaa5f6f --- /dev/null +++ b/src/mapnik_raster_symbolizer.cpp @@ -0,0 +1,65 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 + * + *****************************************************************************/ + +// mapnik +#include +#include +#include +#include +#include +#include "mapnik_symbolizer.hpp" +//pybind11 +#include + +namespace py = pybind11; + +void export_raster_symbolizer(py::module const& m) +{ + using namespace python_mapnik; + using mapnik::raster_symbolizer; + using mapnik::scaling_method_e; + + py::class_(m, "RasterSymbolizer") + .def(py::init<>(), "Default ctor") + .def("__hash__", hash_impl_2) + .def_property("opacity", + &get_property, + &set_double_property, + "Opacity - [0..1]") + .def_property("mesh_size", + &get_property, + &set_integer_property, + "Mesh size") + .def_property("scaling", + &get_property, + &set_enum_property) + .def_property("colorizer", + &get_property, + &set_colorizer_property) + .def_property("premultiplied", + &get_property, + &set_boolean_property, + "Premultiplied - False/True") + + ; + +} diff --git a/src/mapnik_rule.cpp b/src/mapnik_rule.cpp index feb712917..16407b529 100644 --- a/src/mapnik_rule.cpp +++ b/src/mapnik_rule.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,19 +21,17 @@ *****************************************************************************/ #include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#include -#pragma GCC diagnostic pop - // mapnik #include #include #include +//pybind11 +#include +#include +#include +#include + +namespace py = pybind11; using mapnik::rule; using mapnik::expr_node; @@ -52,45 +50,34 @@ using mapnik::group_symbolizer; using mapnik::symbolizer; using mapnik::to_expression_string; -void export_rule() +PYBIND11_MAKE_OPAQUE(std::vector); + +void export_rule(py::module const& m) { - using namespace boost::python; - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); + py::bind_vector>(m, "Symbolizers", py::module_local()); - class_("Symbolizers",init<>("TODO")) - .def(vector_indexing_suite()) - ; + py::class_(m, "Rule") + .def(py::init<>(), "default constructor") + .def(py::init(), + py::arg("name"), + py::arg("min_scale_denominator")=0, + py::arg("max_scale_denominator")=std::numeric_limits::infinity()) - class_("Rule",init<>("default constructor")) - .def(init >()) - .add_property("name",make_function - (&rule::get_name, - return_value_policy()), + .def_property("name", + &rule::get_name, &rule::set_name) - .add_property("filter",make_function - (&rule::get_filter,return_value_policy()), + + .def_property("filter", + &rule::get_filter, &rule::set_filter) - .add_property("min_scale",&rule::get_min_scale,&rule::set_min_scale) - .add_property("max_scale",&rule::get_max_scale,&rule::set_max_scale) - .def("set_else",&rule::set_else) - .def("has_else",&rule::has_else_filter) - .def("set_also",&rule::set_also) - .def("has_also",&rule::has_also_filter) - .def("active",&rule::active) - .add_property("symbols",make_function - (&rule::get_symbolizers,return_value_policy())) - .add_property("copy_symbols",make_function - (&rule::get_symbolizers,return_value_policy())) + + .def_property("min_scale", &rule::get_min_scale, &rule::set_min_scale) + .def_property("max_scale", &rule::get_max_scale, &rule::set_max_scale) + .def("set_else", &rule::set_else) + .def("has_else", &rule::has_else_filter) + .def("set_also", &rule::set_also) + .def("has_also", &rule::has_also_filter) + .def("active", &rule::active) + .def_property_readonly("symbolizers", &rule::get_symbolizers) ; } diff --git a/src/mapnik_scaling_method.cpp b/src/mapnik_scaling_method.cpp index 978cf5b87..9d45f4797 100644 --- a/src/mapnik_scaling_method.cpp +++ b/src/mapnik_scaling_method.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,19 +20,17 @@ * *****************************************************************************/ - +// mapnik #include +//pybind11 +#include +#include -#pragma GCC diagnostic push -#include -#include -#pragma GCC diagnostic pop +namespace py = pybind11; -void export_scaling_method() +void export_scaling_method(py::module const& m) { - using namespace boost::python; - - enum_("scaling_method") + py::native_enum(m, "scaling_method", "enum.IntEnum") .value("NEAR", mapnik::SCALING_NEAR) .value("BILINEAR", mapnik::SCALING_BILINEAR) .value("BICUBIC", mapnik::SCALING_BICUBIC) @@ -50,5 +48,6 @@ void export_scaling_method() .value("SINC", mapnik::SCALING_SINC) .value("LANCZOS", mapnik::SCALING_LANCZOS) .value("BLACKMAN", mapnik::SCALING_BLACKMAN) + .finalize() ; } diff --git a/src/mapnik_shield_symbolizer.cpp b/src/mapnik_shield_symbolizer.cpp new file mode 100644 index 000000000..3df40828b --- /dev/null +++ b/src/mapnik_shield_symbolizer.cpp @@ -0,0 +1,69 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 + * + *****************************************************************************/ + +// mapnik +#include +#include +#include +#include +#include +#include "mapnik_symbolizer.hpp" +//pybind11 +#include + +namespace py = pybind11; + +void export_shield_symbolizer(py::module const& m) +{ + using namespace python_mapnik; + using mapnik::shield_symbolizer; + + py::class_(m, "ShieldSymbolizer") + .def(py::init<>(), "Default ctor") + .def("__hash__", hash_impl_2) + .def_property("file", + &get_property, + &set_path_property, + "Shield image file path or mapnik.PathExpression") + .def_property("shield_dx", + &get_property, + &set_double_property, + "shield_dx displacement") + .def_property("shield_dy", + &get_property, + &set_double_property, + "shield_dy displacement") + .def_property("image_transform", + &get_transform, + &set_transform, + "Shield image transform") + .def_property("unlock_image", + &get_property, + &set_boolean_property, + "Unlock shield image") + .def_property("offset", + &get_property, + &set_double_property, + "Shield offset") + ; + +} diff --git a/src/mapnik_style.cpp b/src/mapnik_style.cpp index 182943669..779c98687 100644 --- a/src/mapnik_style.cpp +++ b/src/mapnik_style.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 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,26 +20,27 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - // mapnik +#include #include #include -#include "mapnik_enumeration.hpp" #include #include // generate_image_filters +//pybind11 +#include +#include +#include +#include + +namespace py = pybind11; using mapnik::feature_type_style; +using mapnik::filter_mode_enum; using mapnik::rules; using mapnik::rule; +PYBIND11_MAKE_OPAQUE(rules); + std::string get_image_filters(feature_type_style & style) { std::string filters_str; @@ -56,59 +57,54 @@ void set_image_filters(feature_type_style & style, std::string const& filters) { throw mapnik::value_error("failed to parse image-filters: '" + filters + "'"); } -#ifdef _WINDOWS - style.image_filters() = new_filters; - // FIXME : https://svn.boost.org/trac/boost/ticket/2839 -#else style.image_filters() = std::move(new_filters); -#endif } -void export_style() +py::object get_filter_mode(feature_type_style const& style) { - using namespace boost::python; + return py::cast(filter_mode_enum(style.get_filter_mode())); +} - mapnik::enumeration_("filter_mode") - .value("ALL",mapnik::FILTER_ALL) - .value("FIRST",mapnik::FILTER_FIRST) - ; +void set_filter_mode(feature_type_style& style, filter_mode_enum mode) +{ + style.set_filter_mode(mapnik::filter_mode_e(mode)); +} - class_("Rules",init<>("default ctor")) - .def(vector_indexing_suite()) +void export_style(py::module const& m) +{ + py::native_enum(m, "filter_mode", "enum.Enum") + .value("ALL",mapnik::filter_mode_enum::FILTER_ALL) + .value("FIRST",mapnik::filter_mode_enum::FILTER_FIRST) + .finalize() ; - class_("Style",init<>("default style constructor")) - .add_property("rules",make_function - (&feature_type_style::get_rules, - return_value_policy()), - "List of rules belonging to a style as rule objects.\n" - "\n" - "Usage:\n" - ">>> for r in m.find_style('style 1').rules:\n" - ">>> print r\n" - "\n" - "\n" - ) - .add_property("filter_mode", - &feature_type_style::get_filter_mode, - &feature_type_style::set_filter_mode, + py::bind_vector(m, "Rules", py::module_local()); + + py::class_(m, "Style") + .def(py::init<>(), "default style constructor") + .def_property_readonly("rules", + &feature_type_style::get_rules, + "Rules assigned to this style.\n") + .def_property("filter_mode", + &get_filter_mode, + &set_filter_mode, "Set/get the filter mode of the style") - .add_property("opacity", + .def_property("opacity", &feature_type_style::get_opacity, &feature_type_style::set_opacity, "Set/get the opacity of the style") - .add_property("comp_op", + .def_property("comp_op", &feature_type_style::comp_op, &feature_type_style::set_comp_op, "Set/get the comp-op (composite operation) of the style") - .add_property("image_filters_inflate", + .def_property("image_filters_inflate", &feature_type_style::image_filters_inflate, &feature_type_style::image_filters_inflate, "Set/get the image_filters_inflate property of the style") - .add_property("image_filters", + .def_property("image_filters", get_image_filters, set_image_filters, - "Set/get the comp-op (composite operation) of the style") + "Set/get image filters for the style") ; } diff --git a/src/mapnik_svg.hpp b/src/mapnik_svg.hpp deleted file mode 100644 index 36763f7c0..000000000 --- a/src/mapnik_svg.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/***************************************************************************** - * - * This file is part of Mapnik (c++ mapping toolkit) - * - * Copyright (C) 2010 Robert Coup - * - * 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 MAPNIK_PYTHON_BINDING_SVG_INCLUDED -#define MAPNIK_PYTHON_BINDING_SVG_INCLUDED - -// mapnik -#include -#include -#include - -#pragma GCC diagnostic push -#include -#include -#pragma GCC diagnostic pop - -namespace mapnik { -using namespace boost::python; - -template -std::string get_svg_transform(T& symbolizer) -{ - return symbolizer.get_image_transform_string(); -} - -template -void set_svg_transform(T& symbolizer, std::string const& transform_wkt) -{ - transform_list_ptr trans_expr = mapnik::parse_transform(transform_wkt); - if (!trans_expr) - { - std::stringstream ss; - ss << "Could not parse transform from '" - << transform_wkt - << "', expected SVG transform attribute"; - throw mapnik::value_error(ss.str()); - } - symbolizer.set_image_transform(trans_expr); -} - -} // end of namespace mapnik - -#endif // MAPNIK_PYTHON_BINDING_SVG_INCLUDED diff --git a/src/mapnik_symbolizer.cpp b/src/mapnik_symbolizer.cpp index ddbedf7e2..7031121ba 100644 --- a/src/mapnik_symbolizer.cpp +++ b/src/mapnik_symbolizer.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 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,513 +20,368 @@ * *****************************************************************************/ -#include -#include "boost_std_shared_shim.hpp" - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - // mapnik +#include #include #include #include #include +#include #include #include #include -#include "mapnik_enumeration.hpp" -#include "mapnik_svg.hpp" #include +#include #include #include // for known_svg_prefix_ #include #include #include #include +#include +#include + +#include "mapnik_symbolizer.hpp" + +//pybind11 +#include +#include +#include +#include + +namespace py = pybind11; using mapnik::symbolizer; +using mapnik::building_symbolizer; +using mapnik::debug_symbolizer; +using mapnik::dot_symbolizer; +using mapnik::group_symbolizer; using mapnik::point_symbolizer; using mapnik::line_symbolizer; using mapnik::line_pattern_symbolizer; +using mapnik::markers_symbolizer; using mapnik::polygon_symbolizer; using mapnik::polygon_pattern_symbolizer; using mapnik::raster_symbolizer; using mapnik::shield_symbolizer; using mapnik::text_symbolizer; -using mapnik::building_symbolizer; -using mapnik::markers_symbolizer; -using mapnik::debug_symbolizer; -using mapnik::group_symbolizer; using mapnik::symbolizer_base; -using mapnik::color; -using mapnik::path_processor_type; -using mapnik::path_expression_ptr; -using mapnik::guess_type; -using mapnik::expression_ptr; -using mapnik::parse_path; +using namespace python_mapnik; namespace { -struct value_to_target -{ - value_to_target(mapnik::symbolizer_base & sym, std::string const& name) - : sym_(sym), name_(name) {} - - void operator() (mapnik::value_integer const& val) - { - auto key = mapnik::get_key(name_); - switch (std::get<2>(get_meta(key))) - { - case mapnik::property_types::target_bool: - put(sym_, key, static_cast(val)); - break; - case mapnik::property_types::target_double: - put(sym_, key, static_cast(val)); - break; - case mapnik::property_types::target_pattern_alignment: - case mapnik::property_types::target_comp_op: - case mapnik::property_types::target_line_rasterizer: - case mapnik::property_types::target_scaling_method: - case mapnik::property_types::target_line_cap: - case mapnik::property_types::target_line_join: - case mapnik::property_types::target_smooth_algorithm: - case mapnik::property_types::target_simplify_algorithm: - case mapnik::property_types::target_halo_rasterizer: - case mapnik::property_types::target_markers_placement: - case mapnik::property_types::target_markers_multipolicy: - case mapnik::property_types::target_halo_comp_op: - case mapnik::property_types::target_text_transform: - case mapnik::property_types::target_horizontal_alignment: - case mapnik::property_types::target_justify_alignment: - case mapnik::property_types::target_vertical_alignment: - case mapnik::property_types::target_upright: - case mapnik::property_types::target_direction: - case mapnik::property_types::target_line_pattern: - { - put(sym_, key, mapnik::enumeration_wrapper(val)); - break; - } - default: - put(sym_, key, val); - break; - } - } - - void operator() (mapnik::value_double const& val) - { - auto key = mapnik::get_key(name_); - switch (std::get<2>(get_meta(key))) - { - case mapnik::property_types::target_bool: - put(sym_, key, static_cast(val)); - break; - case mapnik::property_types::target_integer: - put(sym_, key, static_cast(val)); - break; - default: - put(sym_, key, val); - break; - } - } - - template - void operator() (T const& val) - { - put(sym_, mapnik::get_key(name_), val); - } -private: - mapnik::symbolizer_base & sym_; - std::string const& name_; - -}; - -using namespace boost::python; -void __setitem__(mapnik::symbolizer_base & sym, std::string const& name, mapnik::symbolizer_base::value_type const& val) -{ - mapnik::util::apply_visitor(value_to_target(sym, name), val); -} - -std::shared_ptr numeric_wrapper(const object& arg) -{ - std::shared_ptr result; - if (PyBool_Check(arg.ptr())) - { - mapnik::value_bool val = extract(arg); - result.reset(new mapnik::symbolizer_base::value_type(val)); - } - else if (PyFloat_Check(arg.ptr())) - { - mapnik::value_double val = extract(arg); - result.reset(new mapnik::symbolizer_base::value_type(val)); - } - else - { - mapnik::value_integer val = extract(arg); - result.reset(new mapnik::symbolizer_base::value_type(val)); - } - return result; -} - -struct extract_python_object +struct extract_underlying_type_visitor { - using result_type = boost::python::object; - template - auto operator() (T const& val) const -> result_type + py::object operator() (T const& sym) const { - return result_type(val); // wrap into python object + return py::cast(sym); } }; -boost::python::object __getitem__(mapnik::symbolizer_base const& sym, std::string const& name) +inline py::object extract_underlying_type(symbolizer const& sym) { - using const_iterator = symbolizer_base::cont_type::const_iterator; - mapnik::keys key = mapnik::get_key(name); - const_iterator itr = sym.properties.find(key); - if (itr != sym.properties.end()) - { - return mapnik::util::apply_visitor(extract_python_object(), itr->second); - } - //mapnik::property_meta_type const& meta = mapnik::get_meta(key); - //return mapnik::util::apply_visitor(extract_python_object(), std::get<1>(meta)); - return boost::python::object(); + return mapnik::util::apply_visitor(extract_underlying_type_visitor(), sym); } -/* std::string __str__(mapnik::symbolizer const& sym) { return mapnik::util::apply_visitor(mapnik::symbolizer_to_json(), sym); } -*/ -std::string get_symbolizer_type(symbolizer const& sym) +std::string symbolizer_type_name(symbolizer const& sym) { - return mapnik::symbolizer_name(sym); // FIXME - do we need this ? + return mapnik::symbolizer_name(sym); } -std::size_t hash_impl(symbolizer const& sym) +struct symbolizer_keys_visitor { - return mapnik::util::apply_visitor(mapnik::symbolizer_hash_visitor(), sym); -} - -template -std::size_t hash_impl_2(T const& sym) -{ - return mapnik::symbolizer_hash::value(sym); -} + symbolizer_keys_visitor(py::list & keys) + : keys_(keys) {} -struct extract_underlying_type_visitor -{ - template - boost::python::object operator() (T const& sym) const + template + void operator() (Symbolizer const& sym) const { - return boost::python::object(sym); + for (auto const& kv : sym.properties) + { + std::string name = std::get<0>(mapnik::get_meta(kv.first)); + keys_.append(name); + } } + py::list & keys_; }; -boost::python::object extract_underlying_type(symbolizer const& sym) -{ - return mapnik::util::apply_visitor(extract_underlying_type_visitor(), sym); -} - -} - -void export_symbolizer() -{ - using namespace boost::python; - - //implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible(); - implicitly_convertible, mapnik::symbolizer_base::value_type>(); - - enum_("keys") - .value("gamma", mapnik::keys::gamma) - .value("gamma_method",mapnik::keys::gamma_method) - ; - - class_("Symbolizer",no_init) - .def("type",get_symbolizer_type) - .def("__hash__",hash_impl) - .def("extract", extract_underlying_type) - ; - - class_("NumericWrapper") - .def("__init__", make_constructor(numeric_wrapper)) - ; - - class_("SymbolizerBase",no_init) - .def("__setitem__",&__setitem__) - .def("__setattr__",&__setitem__) - .def("__getitem__",&__getitem__) - .def("__getattr__",&__getitem__) - //.def("__str__", &__str__) - .def(self == self) // __eq__ - ; -} - -void export_text_symbolizer() -{ - using namespace boost::python; - mapnik::enumeration_("label_placement") - .value("LINE_PLACEMENT", mapnik::LINE_PLACEMENT) - .value("POINT_PLACEMENT", mapnik::POINT_PLACEMENT) - .value("VERTEX_PLACEMENT", mapnik::VERTEX_PLACEMENT) - .value("INTERIOR_PLACEMENT", mapnik::INTERIOR_PLACEMENT); - - mapnik::enumeration_("vertical_alignment") - .value("TOP", mapnik::V_TOP) - .value("MIDDLE", mapnik::V_MIDDLE) - .value("BOTTOM", mapnik::V_BOTTOM) - .value("AUTO", mapnik::V_AUTO); - - mapnik::enumeration_("horizontal_alignment") - .value("LEFT", mapnik::H_LEFT) - .value("MIDDLE", mapnik::H_MIDDLE) - .value("RIGHT", mapnik::H_RIGHT) - .value("AUTO", mapnik::H_AUTO); - - mapnik::enumeration_("justify_alignment") - .value("LEFT", mapnik::J_LEFT) - .value("MIDDLE", mapnik::J_MIDDLE) - .value("RIGHT", mapnik::J_RIGHT) - .value("AUTO", mapnik::J_AUTO); - - mapnik::enumeration_("text_transform") - .value("NONE", mapnik::NONE) - .value("UPPERCASE", mapnik::UPPERCASE) - .value("LOWERCASE", mapnik::LOWERCASE) - .value("CAPITALIZE", mapnik::CAPITALIZE); - - mapnik::enumeration_("halo_rasterizer") - .value("FULL", mapnik::HALO_RASTERIZER_FULL) - .value("FAST", mapnik::HALO_RASTERIZER_FAST); - - class_< text_symbolizer, bases >("TextSymbolizer", - init<>("Default ctor")) - .def("__hash__",hash_impl_2) - ; - -} - -void export_shield_symbolizer() -{ - using namespace boost::python; - class_< shield_symbolizer, bases >("ShieldSymbolizer", - init<>("Default ctor")) - .def("__hash__",hash_impl_2) - ; - -} - -void export_polygon_symbolizer() +struct symbolizer_getitem_visitor { - using namespace boost::python; - - class_ >("PolygonSymbolizer", - init<>("Default ctor")) - .def("__hash__",hash_impl_2) - ; - -} - -void export_polygon_pattern_symbolizer() -{ - using namespace boost::python; - - mapnik::enumeration_("pattern_alignment") - .value("LOCAL",mapnik::LOCAL_ALIGNMENT) - .value("GLOBAL",mapnik::GLOBAL_ALIGNMENT) - ; - - class_("PolygonPatternSymbolizer", - init<>("Default ctor")) - .def("__hash__",hash_impl_2) - ; -} - -void export_raster_symbolizer() -{ - using namespace boost::python; + using const_iterator = symbolizer_base::cont_type::const_iterator; + symbolizer_getitem_visitor(std::string const& name) + : name_(name) {} - class_ >("RasterSymbolizer", - init<>("Default ctor")) - ; -} + template + py::object operator() (Symbolizer const& sym) const + { + for (auto const& kv : sym.properties) + { + std::string name = std::get<0>(mapnik::get_meta(kv.first)); + if (name == name_) + { + return mapnik::util::apply_visitor(extract_python_object<>(kv.first), std::get<1>(kv)); + } + } + throw pybind11::key_error("Invalid property name"); + } + std::string const& name_; +}; -void export_point_symbolizer() +py::object symbolizer_keys(mapnik::symbolizer const& sym) { - using namespace boost::python; - - mapnik::enumeration_("point_placement") - .value("CENTROID",mapnik::CENTROID_POINT_PLACEMENT) - .value("INTERIOR",mapnik::INTERIOR_POINT_PLACEMENT) - ; - - class_ >("PointSymbolizer", - init<>("Default Point Symbolizer - 4x4 black square")) - .def("__hash__",hash_impl_2) - ; + py::list keys; + mapnik::util::apply_visitor(symbolizer_keys_visitor(keys), sym); + return keys; } -void export_markers_symbolizer() +py::object getitem_impl(mapnik::symbolizer const& sym, std::string const& name) { - using namespace boost::python; - - mapnik::enumeration_("marker_placement") - .value("POINT_PLACEMENT",mapnik::MARKER_POINT_PLACEMENT) - .value("INTERIOR_PLACEMENT",mapnik::MARKER_INTERIOR_PLACEMENT) - .value("LINE_PLACEMENT",mapnik::MARKER_LINE_PLACEMENT) - ; - - mapnik::enumeration_("marker_multi_policy") - .value("EACH",mapnik::MARKER_EACH_MULTI) - .value("WHOLE",mapnik::MARKER_WHOLE_MULTI) - .value("LARGEST",mapnik::MARKER_LARGEST_MULTI) - ; - - class_ >("MarkersSymbolizer", - init<>("Default Markers Symbolizer - circle")) - .def("__hash__",hash_impl_2) - ; + return mapnik::util::apply_visitor(symbolizer_getitem_visitor(name), sym); } - -void export_line_symbolizer() +py::object symbolizer_base_keys(mapnik::symbolizer_base const& sym) { - using namespace boost::python; - - mapnik::enumeration_("line_rasterizer") - .value("FULL",mapnik::RASTERIZER_FULL) - .value("FAST",mapnik::RASTERIZER_FAST) - ; - - mapnik::enumeration_("stroke_linecap", - "The possible values for a line cap used when drawing\n" - "with a stroke.\n") - .value("BUTT_CAP",mapnik::BUTT_CAP) - .value("SQUARE_CAP",mapnik::SQUARE_CAP) - .value("ROUND_CAP",mapnik::ROUND_CAP) - ; - - mapnik::enumeration_("stroke_linejoin", - "The possible values for the line joining mode\n" - "when drawing with a stroke.\n") - .value("MITER_JOIN",mapnik::MITER_JOIN) - .value("MITER_REVERT_JOIN",mapnik::MITER_REVERT_JOIN) - .value("ROUND_JOIN",mapnik::ROUND_JOIN) - .value("BEVEL_JOIN",mapnik::BEVEL_JOIN) - ; - - - class_ >("LineSymbolizer", - init<>("Default LineSymbolizer - 1px solid black")) - .def("__hash__",hash_impl_2) - ; + py::list keys; + for (auto const& kv : sym.properties) + { + std::string name = std::get<0>(mapnik::get_meta(kv.first)); + keys.append(name); + } + return keys; } -void export_line_pattern_symbolizer() -{ - using namespace boost::python; +} // namespace - class_ >("LinePatternSymbolizer", - init<> ("Default LinePatternSymbolizer")) - .def("__hash__",hash_impl_2) - ; -} - -void export_debug_symbolizer() +void export_symbolizer(py::module const& m) { - using namespace boost::python; - - mapnik::enumeration_("debug_symbolizer_mode") - .value("COLLISION",mapnik::DEBUG_SYM_MODE_COLLISION) - .value("VERTEX",mapnik::DEBUG_SYM_MODE_VERTEX) + py::implicitly_convertible(); + py::class_(m, "Symbolizer") + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + + .def("type_name", symbolizer_type_name) + .def("__hash__", hash_impl) + .def("__getitem__",&getitem_impl) + .def("__getattr__",&getitem_impl) + .def("keys", &symbolizer_keys) + .def("extract", &extract_underlying_type) + .def("__str__", &__str__) + .def("__repr__", &__str__) + .def("to_json", &__str__) ; - class_ >("DebugSymbolizer", - init<>("Default debug Symbolizer")) - .def("__hash__",hash_impl_2) - ; -} - -void export_building_symbolizer() -{ - using namespace boost::python; - - class_ >("BuildingSymbolizer", - init<>("Default BuildingSymbolizer")) - .def("__hash__",hash_impl_2) + py::class_(m, "SymbolizerBase") + //.def("__getitem__",&__getitem__) + //.def("__getattr__",&__getitem__) + .def("keys", &symbolizer_base_keys) + .def(py::self == py::self) // __eq__ + .def_property("smooth", + &get_property, + &set_double_property, + "Smoothing value") + .def_property("simplify_tolerance", + &get_property, + &set_double_property, + "Simplify tolerance") + .def_property("clip", + &get_property, + &set_boolean_property, + "Clip - False/True") + .def_property("comp_op", + &get, + &set_enum_property, + "Composite mode (comp-op)") + .def_property("geometry_transform", + &get_transform, + &set_transform, + "Geometry transform") ; + py::implicitly_convertible(); + py::implicitly_convertible(); + py::implicitly_convertible(); + py::implicitly_convertible(); + py::implicitly_convertible(); + py::implicitly_convertible(); + py::implicitly_convertible(); + py::implicitly_convertible(); + py::implicitly_convertible(); + py::implicitly_convertible(); + py::implicitly_convertible(); + py::implicitly_convertible(); + py::implicitly_convertible(); } -namespace { -void group_symbolizer_properties_set_layout_simple(mapnik::group_symbolizer_properties &p, - mapnik::simple_row_layout &s) -{ - p.set_layout(s); -} - -void group_symbolizer_properties_set_layout_pair(mapnik::group_symbolizer_properties &p, - mapnik::pair_layout &s) -{ - p.set_layout(s); -} - -std::shared_ptr group_rule_construct1(mapnik::expression_ptr p) -{ - return std::make_shared(p, mapnik::expression_ptr()); -} - -} // anonymous namespace - -void export_group_symbolizer() -{ - using namespace boost::python; - using mapnik::group_rule; - using mapnik::simple_row_layout; - using mapnik::pair_layout; - using mapnik::group_symbolizer_properties; - - class_ >("GroupRule", - init()) - .def("__init__", boost::python::make_constructor(group_rule_construct1)) - .def("append", &group_rule::append) - .def("set_filter", &group_rule::set_filter) - .def("set_repeat_key", &group_rule::set_repeat_key) - ; - - class_("SimpleRowLayout") - .def("item_margin", &simple_row_layout::get_item_margin) - .def("set_item_margin", &simple_row_layout::set_item_margin) - ; - - class_("PairLayout") - .def("item_margin", &simple_row_layout::get_item_margin) - .def("set_item_margin", &simple_row_layout::set_item_margin) - .def("max_difference", &pair_layout::get_max_difference) - .def("set_max_difference", &pair_layout::set_max_difference) - ; - - class_ >("GroupSymbolizerProperties") - .def("add_rule", &group_symbolizer_properties::add_rule) - .def("set_layout", &group_symbolizer_properties_set_layout_simple) - .def("set_layout", &group_symbolizer_properties_set_layout_pair) - ; - - class_ >("GroupSymbolizer", - init<>("Default GroupSymbolizer")) - .def("__hash__",hash_impl_2) - ; - -} +// void export_shield_symbolizer() +// { +// using namespace boost::python; +// class_< shield_symbolizer, bases >("ShieldSymbolizer", +// init<>("Default ctor")) +// .def("__hash__",hash_impl_2) +// ; + +// } + + +// void export_polygon_pattern_symbolizer() +// { +// using namespace boost::python; + +// mapnik::enumeration_("pattern_alignment") +// .value("LOCAL",mapnik::pattern_alignment_enum::LOCAL_ALIGNMENT) +// .value("GLOBAL",mapnik::pattern_alignment_enum::GLOBAL_ALIGNMENT) +// ; + +// class_("PolygonPatternSymbolizer", +// init<>("Default ctor")) +// .def("__hash__",hash_impl_2) +// ; +// } + +// void export_raster_symbolizer() +// { +// using namespace boost::python; + +// class_ >("RasterSymbolizer", +// init<>("Default ctor")) +// ; +// } + +// void export_markers_symbolizer() +// { +// using namespace boost::python; + +// mapnik::enumeration_("marker_placement") +// .value("POINT_PLACEMENT",mapnik::marker_placement_enum::MARKER_POINT_PLACEMENT) +// .value("INTERIOR_PLACEMENT",mapnik::marker_placement_enum::MARKER_INTERIOR_PLACEMENT) +// .value("LINE_PLACEMENT",mapnik::marker_placement_enum::MARKER_LINE_PLACEMENT) +// ; + +// mapnik::enumeration_("marker_multi_policy") +// .value("EACH",mapnik::marker_multi_policy_enum::MARKER_EACH_MULTI) +// .value("WHOLE",mapnik::marker_multi_policy_enum::MARKER_WHOLE_MULTI) +// .value("LARGEST",mapnik::marker_multi_policy_enum::MARKER_LARGEST_MULTI) +// ; + +// class_ >("MarkersSymbolizer", +// init<>("Default Markers Symbolizer - circle")) +// .def("__hash__",hash_impl_2) +// ; +// } + +// void export_line_pattern_symbolizer() +// { +// using namespace boost::python; + +// class_ >("LinePatternSymbolizer", +// init<> ("Default LinePatternSymbolizer")) +// .def("__hash__",hash_impl_2) +// ; +// } + +// void export_debug_symbolizer() +// { +// using namespace boost::python; + +// mapnik::enumeration_("debug_symbolizer_mode") +// .value("COLLISION",mapnik::debug_symbolizer_mode_enum::DEBUG_SYM_MODE_COLLISION) +// .value("VERTEX",mapnik::debug_symbolizer_mode_enum::DEBUG_SYM_MODE_VERTEX) +// ; + +// class_ >("DebugSymbolizer", +// init<>("Default debug Symbolizer")) +// .def("__hash__",hash_impl_2) +// ; +// } + +// void export_building_symbolizer() +// { +// using namespace boost::python; + +// class_ >("BuildingSymbolizer", +// init<>("Default BuildingSymbolizer")) +// .def("__hash__",hash_impl_2) +// ; + +// } + +// namespace { + +// void group_symbolizer_properties_set_layout_simple(mapnik::group_symbolizer_properties &p, +// mapnik::simple_row_layout &s) +// { +// p.set_layout(s); +// } + +// void group_symbolizer_properties_set_layout_pair(mapnik::group_symbolizer_properties &p, +// mapnik::pair_layout &s) +// { +// p.set_layout(s); +// } + +// std::shared_ptr group_rule_construct1(mapnik::expression_ptr p) +// { +// return std::make_shared(p, mapnik::expression_ptr()); +// } + +// } // anonymous namespace + +// void export_group_symbolizer() +// { +// using namespace boost::python; +// using mapnik::group_rule; +// using mapnik::simple_row_layout; +// using mapnik::pair_layout; +// using mapnik::group_symbolizer_properties; + +// class_ >("GroupRule", +// init()) +// .def("__init__", boost::python::make_constructor(group_rule_construct1)) +// .def("append", &group_rule::append) +// .def("set_filter", &group_rule::set_filter) +// .def("set_repeat_key", &group_rule::set_repeat_key) +// ; + +// class_("SimpleRowLayout") +// .def("item_margin", &simple_row_layout::get_item_margin) +// .def("set_item_margin", &simple_row_layout::set_item_margin) +// ; + +// class_("PairLayout") +// .def("item_margin", &simple_row_layout::get_item_margin) +// .def("set_item_margin", &simple_row_layout::set_item_margin) +// .def("max_difference", &pair_layout::get_max_difference) +// .def("set_max_difference", &pair_layout::set_max_difference) +// ; + +// class_ >("GroupSymbolizerProperties") +// .def("add_rule", &group_symbolizer_properties::add_rule) +// .def("set_layout", &group_symbolizer_properties_set_layout_simple) +// .def("set_layout", &group_symbolizer_properties_set_layout_pair) +// ; + +// class_ >("GroupSymbolizer", +// init<>("Default GroupSymbolizer")) +// .def("__hash__",hash_impl_2) +// ; + +// } diff --git a/src/mapnik_symbolizer.hpp b/src/mapnik_symbolizer.hpp new file mode 100644 index 000000000..629dc655e --- /dev/null +++ b/src/mapnik_symbolizer.hpp @@ -0,0 +1,297 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 MAPNIK_SYMBOLIZER_INCLUDED +#define MAPNIK_SYMBOLIZER_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +//pybind11 +#include + +PYBIND11_MAKE_OPAQUE(mapnik::symbolizer); +PYBIND11_MAKE_OPAQUE(mapnik::path_expression); + +namespace py = pybind11; + +namespace python_mapnik { + +using mapnik::symbolizer; +using mapnik::symbolizer_base; +using mapnik::parse_path; +using mapnik::path_processor; + + +template +struct enum_converter +{ + static auto apply(mapnik::enumeration_wrapper const& wrapper, mapnik::keys key) -> py::object + { + return py::cast(TargetType(wrapper.value)); + } +}; + +template <> +struct enum_converter +{ + static auto apply(mapnik::enumeration_wrapper const& wrapper, mapnik::keys key) -> py::object + { + auto meta = mapnik::get_meta(key); + auto const& convert_fun_ptr(std::get<1>(meta)); + if (convert_fun_ptr) + { + return py::cast(convert_fun_ptr(wrapper)); + } + throw pybind11::key_error("Invalid property name"); + } +}; + +template +struct extract_python_object +{ + using result_type = py::object; + mapnik::keys key_; + extract_python_object(mapnik::keys key) + : key_(key) {} + + auto operator() (mapnik::value_bool val) const -> result_type + { + return py::bool_(val); + } + + auto operator() (mapnik::value_double val) const -> result_type + { + return py::float_(val); + } + + auto operator() (mapnik::value_integer val) const -> result_type + { + return py::int_(val); + } + + auto operator() (mapnik::color const& col) const -> result_type + { + return py::cast(col); + } + + auto operator() (mapnik::expression_ptr const& expr) const -> result_type + { + return py::cast(expr); + } + + auto operator() (mapnik::path_expression_ptr const& expr) const ->result_type + { + return py::cast(expr); + } + + auto operator() (mapnik::enumeration_wrapper const& wrapper) const ->result_type + { + return enum_converter::apply(wrapper, key_); + } + + auto operator() (mapnik::transform_list_ptr const& expr) const ->result_type + { + if (expr) return py::cast(mapnik::transform_processor_type::to_string(*expr)); + return py::none(); + } + + auto operator() (mapnik::raster_colorizer_ptr const& colorizer) const ->result_type + { + return py::cast(colorizer); + } + + template + auto operator() (T const& val) const -> result_type + { + std::cerr << "Can't convert to Python object [" << typeid(val).name() << "]" << std::endl; + return py::none(); + } +}; + +template +py::object get_property(Symbolizer const& sym) +{ + using const_iterator = symbolizer_base::cont_type::const_iterator; + const_iterator itr = sym.properties.find(Key); + if (itr != sym.properties.end()) + { + return mapnik::util::apply_visitor(extract_python_object(Key), itr->second); + } + //throw pybind11::key_error("Invalid property name"); + return py::none(); +} + +template +void set_color_property(Symbolizer & sym, py::object const& obj) +{ + if (py::isinstance(obj)) + { + mapnik::put(sym, Key, obj.cast()); + } + else if (py::isinstance(obj)) + { + auto expr = obj.cast(); + mapnik::put(sym, Key, expr); + } + else if (py::isinstance(obj)) + { + mapnik::put(sym, Key, mapnik::color(obj.cast())); + } + else throw pybind11::value_error(); +} + +template +void set_boolean_property(Symbolizer & sym, py::object const& obj) +{ + + if (py::isinstance(obj)) + { + mapnik::put(sym, Key, obj.cast()); + } + else if (py::isinstance(obj)) + { + auto expr = obj.cast(); + mapnik::put(sym, Key, expr); + } + else throw pybind11::value_error(); +} + +template +void set_integer_property(Symbolizer & sym, py::object const& obj) +{ + + if (py::isinstance(obj)) + { + mapnik::put(sym, Key, obj.cast()); + } + else if (py::isinstance(obj)) + { + auto expr = obj.cast(); + mapnik::put(sym, Key, expr); + } + else throw pybind11::value_error(); +} + +template +void set_double_property(Symbolizer & sym, py::object const& obj) +{ + + if (py::isinstance(obj) || py::isinstance(obj)) + { + mapnik::put(sym, Key, obj.cast()); + } + else if (py::isinstance(obj)) + { + auto expr = obj.cast(); + mapnik::put(sym, Key, expr); + } + else throw pybind11::value_error(); +} + +template +void set_enum_property(Symbolizer & sym, py::object const& obj) +{ + if (py::isinstance(obj)) + { + mapnik::put(sym, Key, obj.cast()); + } + else if (py::isinstance(obj)) + { + auto expr = obj.cast(); + mapnik::put(sym, Key, expr); + } + else throw pybind11::value_error(); +} + +template +void set_path_property(Symbolizer & sym, py::object const& obj) +{ + if (py::isinstance(obj)) + { + mapnik::put(sym, Key, parse_path(obj.cast())); + } + else if (py::isinstance(obj)) + { + auto expr = obj.cast(); + mapnik::put(sym, Key, expr); + } + else throw pybind11::value_error(); +} + +template +void set_colorizer_property(Symbolizer & sym, py::object const& obj) +{ + if (py::isinstance(obj)) + { + mapnik::put(sym, Key, obj.cast()); + } + else throw pybind11::value_error(); +} + +inline std::size_t hash_impl(symbolizer const& sym) +{ + return mapnik::util::apply_visitor(mapnik::symbolizer_hash_visitor(), sym); +} + +template +std::size_t hash_impl_2(T const& sym) +{ + return mapnik::symbolizer_hash::value(sym); +} + +template +auto get(symbolizer_base const& sym) -> Value +{ + return mapnik::get(sym, Key); +} + +template +void set(symbolizer_base & sym, Value const& val) +{ + mapnik::put(sym, Key, val); +} + +template +std::string get_transform(symbolizer_base const& sym) +{ + auto expr = mapnik::get(sym, Key); + if (expr) + return mapnik::transform_processor_type::to_string(*expr); + return ""; +} + +template +void set_transform(symbolizer_base & sym, std::string const& str) +{ + mapnik::put(sym, Key, mapnik::parse_transform(str)); +} + +} // namespace python_mapnik + +#endif //MAPNIK_SYMBOLIZER_INCLUDED diff --git a/src/mapnik_text_symbolizer.cpp b/src/mapnik_text_symbolizer.cpp new file mode 100644 index 000000000..59651ac76 --- /dev/null +++ b/src/mapnik_text_symbolizer.cpp @@ -0,0 +1,116 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2024 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 + * + *****************************************************************************/ + +// mapnik +#include + +#include +#include +#include +#include +#include +#include +#include "mapnik_symbolizer.hpp" + +//pybind11 +#include +#include +#include +#include +#include + +namespace py = pybind11; + +namespace { + +//text symbolizer +mapnik::text_placements_ptr get_placement_finder(mapnik::text_symbolizer const& sym) +{ + return mapnik::get(sym, mapnik::keys::text_placements_); +} + +void set_placement_finder(mapnik::text_symbolizer & sym, std::shared_ptr const& finder) +{ + mapnik::put(sym, mapnik::keys::text_placements_, finder); +} + +} + +void export_text_symbolizer(py::module const& m) +{ + using namespace python_mapnik; + using mapnik::text_symbolizer; + + py::native_enum(m, "LabelPlacement", "enum.Enum") + .value("LINE_PLACEMENT", mapnik::label_placement_enum::LINE_PLACEMENT) + .value("POINT_PLACEMENT", mapnik::label_placement_enum::POINT_PLACEMENT) + .value("VERTEX_PLACEMENT", mapnik::label_placement_enum::VERTEX_PLACEMENT) + .value("INTERIOR_PLACEMENT", mapnik::label_placement_enum::INTERIOR_PLACEMENT) + .finalize() + ; + +// mapnik::enumeration_("vertical_alignment") +// .value("TOP", mapnik::vertical_alignment_enum::V_TOP) +// .value("MIDDLE", mapnik::vertical_alignment_enum::V_MIDDLE) +// .value("BOTTOM", mapnik::vertical_alignment_enum::V_BOTTOM) +// .value("AUTO", mapnik::vertical_alignment_enum::V_AUTO); + +// mapnik::enumeration_("horizontal_alignment") +// .value("LEFT", mapnik::horizontal_alignment_enum::H_LEFT) +// .value("MIDDLE", mapnik::horizontal_alignment_enum::H_MIDDLE) +// .value("RIGHT", mapnik::horizontal_alignment_enum::H_RIGHT) +// .value("AUTO", mapnik::horizontal_alignment_enum::H_AUTO); + +// mapnik::enumeration_("justify_alignment") +// .value("LEFT", mapnik::justify_alignment_enum::J_LEFT) +// .value("MIDDLE", mapnik::justify_alignment_enum::J_MIDDLE) +// .value("RIGHT", mapnik::justify_alignment_enum::J_RIGHT) +// .value("AUTO", mapnik::justify_alignment_enum::J_AUTO); + +// mapnik::enumeration_("text_transform") +// .value("NONE", mapnik::text_transform_enum::NONE) +// .value("UPPERCASE", mapnik::text_transform_enum::UPPERCASE) +// .value("LOWERCASE", mapnik::text_transform_enum::LOWERCASE) +// .value("CAPITALIZE", mapnik::text_transform_enum::CAPITALIZE); + + py::native_enum(m, "halo_rasterizer", "enum.Enum") + .value("FULL", mapnik::halo_rasterizer_enum::HALO_RASTERIZER_FULL) + .value("FAST", mapnik::halo_rasterizer_enum::HALO_RASTERIZER_FAST) + .finalize(); + + + // set_symbolizer_property(sym, keys::halo_comp_op, node); + // set_symbolizer_property(sym, keys::halo_rasterizer, node); + // set_symbolizer_property(sym, keys::halo_transform, node); + // set_symbolizer_property(sym, keys::offset, node); + + py::class_(m, "TextSymbolizer") + .def(py::init<>(), "Default ctor") + .def("__hash__",hash_impl_2) + .def_property("placement_finder", &get_placement_finder, &set_placement_finder, "Placement finder") + .def_property("halo_comp_op", + &get, + &set_enum_property, + "Composite mode (comp-op)") + ; + +} diff --git a/src/mapnik_threads.hpp b/src/mapnik_threads.hpp deleted file mode 100644 index aa262ea9f..000000000 --- a/src/mapnik_threads.hpp +++ /dev/null @@ -1,113 +0,0 @@ -/***************************************************************************** - * - * This file is part of Mapnik (c++ mapping toolkit) - * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon - * - * 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 MAPNIK_THREADS_HPP -#define MAPNIK_THREADS_HPP - -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop - - -namespace mapnik { -class python_thread -{ - /* Docs: - http://docs.python.org/c-api/init.html#thread-state-and-the-global-interpreter-lock - */ -public: - static void unblock() - { -#ifdef MAPNIK_DEBUG - if (state.get()) - { - std::cerr << "ERROR: Python threads are already unblocked. " - "Unblocking again will loose the current state and " - "might crash later. Aborting!\n"; - abort(); //This is a serious error and can't be handled in any other sane way - } -#endif - PyThreadState *_save = 0; //Name defined by python - Py_UNBLOCK_THREADS; - state.reset(_save); -#ifdef MAPNIK_DEBUG - if (!_save) { - thread_support = false; - } -#endif - } - - static void block() - { -#ifdef MAPNIK_DEBUG - if (thread_support && !state.get()) - { - std::cerr << "ERROR: Trying to restore python thread state, " - "but no state is saved. Can't continue and also " - "can't raise an exception because the python " - "interpreter might be non-function. Aborting!\n"; - abort(); - } -#endif - PyThreadState *_save = state.release(); //Name defined by python - Py_BLOCK_THREADS; - } - -private: - static boost::thread_specific_ptr state; -#ifdef MAPNIK_DEBUG - static bool thread_support; -#endif -}; - -class python_block_auto_unblock -{ -public: - python_block_auto_unblock() - { - python_thread::block(); - } - - ~python_block_auto_unblock() - { - python_thread::unblock(); - } -}; - -class python_unblock_auto_block -{ -public: - python_unblock_auto_block() - { - python_thread::unblock(); - } - - ~python_unblock_auto_block() - { - python_thread::block(); - } -}; - -} //namespace - -#endif // MAPNIK_THREADS_HPP diff --git a/src/mapnik_value_converter.hpp b/src/mapnik_value_converter.hpp index 8c32d08c2..4a04094ae 100644 --- a/src/mapnik_value_converter.hpp +++ b/src/mapnik_value_converter.hpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,75 +19,186 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ + #ifndef MAPNIK_PYTHON_BINDING_VALUE_CONVERTER_INCLUDED #define MAPNIK_PYTHON_BINDING_VALUE_CONVERTER_INCLUDED // mapnik #include -#include +#include +#include +//pybind11 +#include -#pragma GCC diagnostic push -#include -#include -#include -#pragma GCC diagnostic pop +namespace { -namespace boost { namespace python { +struct value_converter +{ + PyObject * operator() (mapnik::value_integer val) const + { + return ::PyLong_FromLongLong(val); + } - struct value_converter + PyObject * operator() (mapnik::value_double val) const { - PyObject * operator() (mapnik::value_integer val) const - { - return ::PyLong_FromLongLong(val); - } + return ::PyFloat_FromDouble(val); + } + + PyObject * operator() (mapnik::value_bool val) const + { + return ::PyBool_FromLong(val); + } + + PyObject * operator() (std::string const& s) const + { + return ::PyUnicode_DecodeUTF8(s.c_str(), static_cast(s.length()),0); + } + + PyObject * operator() (mapnik::value_unicode_string const& s) const + { + const char* data = reinterpret_cast(s.getBuffer()); + Py_ssize_t size = static_cast(s.length() * sizeof(s[0])); + return ::PyUnicode_DecodeUTF16(data, size, nullptr, nullptr); + } + + PyObject * operator() (mapnik::value_null const& /*s*/) const + { + Py_RETURN_NONE; + } +}; + +} // namespace + +struct mapnik_value_to_python +{ + static PyObject* convert(mapnik::value const& v) + { + return mapnik::util::apply_visitor(value_converter(),v); + } +}; + +struct mapnik_param_to_python +{ + static PyObject* convert(mapnik::value_holder const& v) + { + return mapnik::util::apply_visitor(value_converter(),v); + } +}; + + + +namespace PYBIND11_NAMESPACE { namespace detail { + +template <> +struct type_caster +{ + mapnik::transcoder const tr_{"utf8"}; +public: - PyObject * operator() (mapnik::value_double val) const + PYBIND11_TYPE_CASTER(mapnik::value, const_name("Value")); + + bool load(handle src, bool) + { + PyObject *source = src.ptr(); + if (PyUnicode_Check(source)) { - return ::PyFloat_FromDouble(val); + PyObject* tmp = PyUnicode_AsUTF8String(source); + if (!tmp) return false; + char* c_str = PyBytes_AsString(tmp); + value = tr_.transcode(c_str); + Py_DecRef(tmp); + return !PyErr_Occurred(); } - - PyObject * operator() (mapnik::value_bool val) const + else if (PyBool_Check(source)) { - return ::PyBool_FromLong(val); + value = (source == Py_True) ? true : false; + return !PyErr_Occurred(); } - - PyObject * operator() (std::string const& s) const + else if (PyFloat_Check(source)) { - return ::PyUnicode_DecodeUTF8(s.c_str(),implicit_cast(s.length()),0); + PyObject *tmp = PyNumber_Float(source); + if (!tmp) return false; + value = PyFloat_AsDouble(tmp); + Py_DecRef(tmp); + return !PyErr_Occurred(); } - - PyObject * operator() (mapnik::value_unicode_string const& s) const + else if(PyLong_Check(source)) { - const char* data = reinterpret_cast(s.getBuffer()); - Py_ssize_t size = implicit_cast(s.length() * sizeof(s[0])); - return ::PyUnicode_DecodeUTF16(data, size, nullptr, nullptr); + PyObject *tmp = PyNumber_Long(source); + if (!tmp) return false; + value = PyLong_AsLong(tmp); + Py_DecRef(tmp); + return !PyErr_Occurred(); } - - PyObject * operator() (mapnik::value_null const& /*s*/) const + else if (source == Py_None) { - Py_RETURN_NONE; + value = mapnik::value_null{}; + return true; } - }; - + return false; + } - struct mapnik_value_to_python + static handle cast(mapnik::value src, return_value_policy /*policy*/, handle /*parent*/) { - static PyObject* convert(mapnik::value const& v) - { - return mapnik::util::apply_visitor(value_converter(),v); - } + return mapnik_value_to_python::convert(src); + } +}; + +template <> +struct type_caster +{ +public: - }; + PYBIND11_TYPE_CASTER(mapnik::value_holder, const_name("ValueHolder")); - struct mapnik_param_to_python + bool load(handle src, bool) { - static PyObject* convert(mapnik::value_holder const& v) + PyObject *source = src.ptr(); + if (PyUnicode_Check(source)) { - return mapnik::util::apply_visitor(value_converter(),v); + PyObject* tmp = PyUnicode_AsUTF8String(source); + if (!tmp) return false; + char* c_str = PyBytes_AsString(tmp); + value = std::string(c_str); + Py_DecRef(tmp); + return !PyErr_Occurred(); } - }; + else if (PyBool_Check(source)) + { + value = (source == Py_True) ? true : false; + return !PyErr_Occurred(); + } + else if (PyFloat_Check(source)) + { + PyObject *tmp = PyNumber_Float(source); + if (!tmp) return false; + value = PyFloat_AsDouble(tmp); + Py_DecRef(tmp); + return !PyErr_Occurred(); + } + else if(PyLong_Check(source)) + { + PyObject *tmp = PyNumber_Long(source); + if (!tmp) return false; + value = static_cast(PyLong_AsLong(tmp)); + Py_DecRef(tmp); + return !PyErr_Occurred(); + } + else if (source == Py_None) + { + value = mapnik::value_null{}; + return true; + } + return false; + } + + static handle cast(mapnik::value_holder src, return_value_policy /*policy*/, handle /*parent*/) + { + return mapnik_param_to_python::convert(src); + } +}; +}} // namespace PYBIND11_NAMESPACE::detail -}} #endif // MAPNIK_PYTHON_BINDING_VALUE_CONVERTER_INCLUDED diff --git a/src/mapnik_view_transform.cpp b/src/mapnik_view_transform.cpp index 8a1a0d3e1..6cd3057d5 100644 --- a/src/mapnik_view_transform.cpp +++ b/src/mapnik_view_transform.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko, Jean-Francois Doyon + * Copyright (C) 2024 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/src/python_grid_utils.cpp b/src/python_grid_utils.cpp index c2585732d..6ac426cd1 100644 --- a/src/python_grid_utils.cpp +++ b/src/python_grid_utils.cpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,15 +21,8 @@ *****************************************************************************/ #if defined(GRID_RENDERER) - -#include - -#pragma GCC diagnostic push -#include -#include -#pragma GCC diagnostic pop - // mapnik +#include #include #include #include @@ -40,18 +33,21 @@ #include #include #include "python_grid_utils.hpp" - +#include "mapnik_value_converter.hpp" // stl #include +#include namespace mapnik { + template void grid2utf(T const& grid_type, - boost::python::list& l, - std::vector& key_order) + py::list& l, + std::vector& key_order) { + using code_point_t = std::uint32_t; using keys_type = std::map< typename T::lookup_type, typename T::value_type>; using keys_iterator = typename keys_type::iterator; @@ -67,7 +63,7 @@ void grid2utf(T const& grid_type, for (std::size_t y = 0; y < data.height(); ++y) { std::uint16_t idx = 0; - const std::unique_ptr line(new Py_UNICODE[array_size]); + const std::unique_ptr line(new code_point_t[array_size]); typename T::value_type const* row = data.get_row(y); for (std::size_t x = 0; x < data.width(); ++x) { @@ -93,29 +89,28 @@ void grid2utf(T const& grid_type, keys[val] = codepoint; key_order.push_back(val); } - line[idx++] = static_cast(codepoint); + line[idx++] = static_cast(codepoint); ++codepoint; } else { - line[idx++] = static_cast(key_pos->second); + line[idx++] = static_cast(key_pos->second); } } // else, shouldn't get here... } - l.append(boost::python::object( - boost::python::handle<>( - PyUnicode_FromUnicode(line.get(), array_size)))); + l.append(PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, line.get(), array_size)); } } template void grid2utf(T const& grid_type, - boost::python::list& l, + py::list& l, std::vector& key_order, unsigned int resolution) { + using code_point_t = std::uint32_t; using keys_type = std::map< typename T::lookup_type, typename T::value_type>; using keys_iterator = typename keys_type::iterator; @@ -130,7 +125,7 @@ void grid2utf(T const& grid_type, for (unsigned y = 0; y < grid_type.height(); y=y+resolution) { std::uint16_t idx = 0; - const std::unique_ptr line(new Py_UNICODE[array_size]); + const std::unique_ptr line(new code_point_t[array_size]); mapnik::grid::value_type const* row = grid_type.get_row(y); for (unsigned x = 0; x < grid_type.width(); x=x+resolution) { @@ -156,26 +151,24 @@ void grid2utf(T const& grid_type, keys[val] = codepoint; key_order.push_back(val); } - line[idx++] = static_cast(codepoint); + line[idx++] = static_cast(codepoint); ++codepoint; } else { - line[idx++] = static_cast(key_pos->second); + line[idx++] = static_cast(key_pos->second); } } // else, shouldn't get here... } - l.append(boost::python::object( - boost::python::handle<>( - PyUnicode_FromUnicode(line.get(), array_size)))); + l.append(PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, line.get(), array_size)); } } template void write_features(T const& grid_type, - boost::python::dict& feature_data, - std::vector const& key_order) + py::dict& feature_data, + std::vector const& key_order) { typename T::feature_type const& g_features = grid_type.get_grid_features(); if (g_features.size() <= 0) @@ -199,7 +192,7 @@ void write_features(T const& grid_type, } bool found = false; - boost::python::dict feat; + py::dict feat; mapnik::feature_ptr feature = feat_itr->second; for ( std::string const& attr : attributes ) { @@ -216,19 +209,19 @@ void write_features(T const& grid_type, if (found) { - feature_data[feat_itr->first] = feat; + feature_data[feat_itr->first.c_str()] = feat; } } } template void grid_encode_utf(T const& grid_type, - boost::python::dict & json, - bool add_features, - unsigned int resolution) + py::dict & json, + bool add_features, + unsigned int resolution) { // convert buffer to utf and gather key order - boost::python::list l; + py::list l; std::vector key_order; if (resolution != 1) @@ -241,14 +234,14 @@ void grid_encode_utf(T const& grid_type, } // convert key order to proper python list - boost::python::list keys_a; + py::list keys_a; for ( typename T::lookup_type const& key_id : key_order ) { keys_a.append(key_id); } // gather feature data - boost::python::dict feature_data; + py::dict feature_data; if (add_features) { mapnik::write_features(grid_type,feature_data,key_order); } @@ -260,10 +253,10 @@ void grid_encode_utf(T const& grid_type, } template -boost::python::dict grid_encode( T const& grid, std::string const& format, bool add_features, unsigned int resolution) +py::dict grid_encode( T const& grid, std::string const& format, bool add_features, unsigned int resolution) { if (format == "utf") { - boost::python::dict json; + py::dict json; grid_encode_utf(grid,json,add_features,resolution); return json; } @@ -275,13 +268,13 @@ boost::python::dict grid_encode( T const& grid, std::string const& format, bool } } -template boost::python::dict grid_encode( mapnik::grid const& grid, std::string const& format, bool add_features, unsigned int resolution); -template boost::python::dict grid_encode( mapnik::grid_view const& grid, std::string const& format, bool add_features, unsigned int resolution); +template py::dict grid_encode( mapnik::grid const& grid, std::string const& format, bool add_features, unsigned int resolution); +template py::dict grid_encode( mapnik::grid_view const& grid, std::string const& format, bool add_features, unsigned int resolution); void render_layer_for_grid(mapnik::Map const& map, mapnik::grid & grid, unsigned layer_idx, - boost::python::list const& fields, + py::list const& fields, double scale_factor, unsigned offset_x, unsigned offset_y) @@ -296,12 +289,12 @@ void render_layer_for_grid(mapnik::Map const& map, } // convert python list to std::set - boost::python::ssize_t num_fields = boost::python::len(fields); - for(boost::python::ssize_t i=0; i name(fields[i]); - if (name.check()) + std::size_t num_fields = py::len(fields); + for(std::size_t i = 0; i < num_fields; ++i) { + py::handle handle = fields[i]; + if (py::isinstance(handle)) { - grid.add_field(name()); + grid.add_field(handle.cast()); } else { @@ -332,6 +325,6 @@ void render_layer_for_grid(mapnik::Map const& map, ren.apply(layer,attributes); } -} +} // namespace mapnik #endif diff --git a/src/python_grid_utils.hpp b/src/python_grid_utils.hpp index f38ec75bd..84c8bab62 100644 --- a/src/python_grid_utils.hpp +++ b/src/python_grid_utils.hpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -25,46 +25,45 @@ // mapnik #include #include +// pybind11 +#include -#pragma GCC diagnostic push -#include -#include -#pragma GCC diagnostic pop +namespace py = pybind11; namespace mapnik { template void grid2utf(T const& grid_type, - boost::python::list& l, + py::list& l, std::vector& key_order); template void grid2utf(T const& grid_type, - boost::python::list& l, + py::list& l, std::vector& key_order, unsigned int resolution); template void write_features(T const& grid_type, - boost::python::dict& feature_data, + py::dict& feature_data, std::vector const& key_order); template void grid_encode_utf(T const& grid_type, - boost::python::dict & json, + py::dict & json, bool add_features, unsigned int resolution); template -boost::python::dict grid_encode( T const& grid, std::string const& format, bool add_features, unsigned int resolution); +py::dict grid_encode( T const& grid, std::string const& format, bool add_features, unsigned int resolution); void render_layer_for_grid(const mapnik::Map& map, mapnik::grid& grid, unsigned layer_idx, // TODO - layer by name or index - boost::python::list const& fields, + py::list const& fields, double scale_factor, unsigned offset_x, unsigned offset_y); diff --git a/src/python_optional.hpp b/src/python_optional.hpp index d690b7c51..1ffaff1bf 100644 --- a/src/python_optional.hpp +++ b/src/python_optional.hpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 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,182 +20,11 @@ * *****************************************************************************/ -#pragma GCC diagnostic push -#include -#include -#include +//pybind11 +#include +#include -#include -#pragma GCC diagnostic pop - -// boost::optional to/from converter from John Wiegley - -template -struct object_from_python -{ - object_from_python() { - boost::python::converter::registry::push_back - (&TfromPy::convertible, &TfromPy::construct, - boost::python::type_id()); - } -}; - -template -struct register_python_conversion -{ - register_python_conversion() { - boost::python::to_python_converter(); - object_from_python(); - } -}; - -template -struct python_optional : public mapnik::util::noncopyable -{ - struct optional_to_python - { - static PyObject * convert(const boost::optional& value) - { - return (value ? boost::python::to_python_value()(*value) : - boost::python::detail::none()); - } - }; - - struct optional_from_python - { - static void * convertible(PyObject * source) - { - using namespace boost::python::converter; - - if (source == Py_None) - return source; - - const registration& converters(registered::converters); - - if (implicit_rvalue_convertible_from_python(source, - converters)) { - rvalue_from_python_stage1_data data = - rvalue_from_python_stage1(source, converters); - return rvalue_from_python_stage2(source, data, converters); - } - return 0; - } - - static void construct(PyObject * source, - boost::python::converter::rvalue_from_python_stage1_data * data) - { - using namespace boost::python::converter; - - void * const storage = ((rvalue_from_python_storage *) - data)->storage.bytes; - - if (data->convertible == source) // == None - new (storage) boost::optional(); // A Boost uninitialized value - else - new (storage) boost::optional(*static_cast(data->convertible)); - - data->convertible = storage; - } - }; - - explicit python_optional() - { - register_python_conversion, - optional_to_python, optional_from_python>(); - } -}; - -// to/from boost::optional -template <> -struct python_optional : public mapnik::util::noncopyable -{ - struct optional_to_python - { - static PyObject * convert(const boost::optional& value) - { - return (value ? PyFloat_FromDouble(*value) : - boost::python::detail::none()); - } - }; - - struct optional_from_python - { - static void * convertible(PyObject * source) - { - using namespace boost::python::converter; - - if (source == Py_None || PyFloat_Check(source)) - return source; - return 0; - } - - static void construct(PyObject * source, - boost::python::converter::rvalue_from_python_stage1_data * data) - { - using namespace boost::python::converter; - void * const storage = ((rvalue_from_python_storage > *) - data)->storage.bytes; - if (source == Py_None) // == None - new (storage) boost::optional(); // A Boost uninitialized value - else - new (storage) boost::optional(PyFloat_AsDouble(source)); - data->convertible = storage; - } - }; - - explicit python_optional() - { - register_python_conversion, - optional_to_python, optional_from_python>(); - } -}; - -// to/from boost::optional -template <> -struct python_optional : public mapnik::util::noncopyable -{ - struct optional_to_python - { - static PyObject * convert(const boost::optional& value) - { - if (value) - { - if (*value) Py_RETURN_TRUE; - else Py_RETURN_FALSE; - } - else return boost::python::detail::none(); - } - }; - struct optional_from_python - { - static void * convertible(PyObject * source) - { - using namespace boost::python::converter; - - if (source == Py_None || PyBool_Check(source)) - return source; - return 0; - } - - static void construct(PyObject * source, - boost::python::converter::rvalue_from_python_stage1_data * data) - { - using namespace boost::python::converter; - void * const storage = ((rvalue_from_python_storage > *) - data)->storage.bytes; - if (source == Py_None) // == None - new (storage) boost::optional(); // A Boost uninitialized value - else - { - new (storage) boost::optional(source == Py_True ? true : false); - } - data->convertible = storage; - } - }; - - explicit python_optional() - { - register_python_conversion, - optional_to_python, optional_from_python>(); - } -}; +namespace PYBIND11_NAMESPACE { namespace detail { + template + struct type_caster> : optional_caster> {}; +}} diff --git a/src/python_to_value.hpp b/src/python_to_value.hpp index c8f087b49..73702c02d 100644 --- a/src/python_to_value.hpp +++ b/src/python_to_value.hpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 Artem Pavlenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,94 +22,45 @@ #ifndef MAPNIK_PYTHON_BINDING_PYTHON_TO_VALUE #define MAPNIK_PYTHON_BINDING_PYTHON_TO_VALUE -#pragma GCC diagnostic push -#include -#include -#pragma GCC diagnostic pop - // mapnik #include #include #include +//pybind11 +#include + +namespace py = pybind11; + namespace mapnik { - static mapnik::attributes dict2attr(boost::python::dict const& d) + static mapnik::attributes dict2attr(py::dict const& d) { - using namespace boost::python; mapnik::attributes vars; mapnik::transcoder tr_("utf8"); - boost::python::list keys=d.keys(); - for (int i=0; i < len(keys); ++i) + for (auto item : d) { - std::string key; - object obj_key = keys[i]; - if (PyUnicode_Check(obj_key.ptr())) + std::string key = std::string(py::str(item.first)); + py::handle handle = item.second; + if (py::isinstance(handle)) { - PyObject* temp = PyUnicode_AsUTF8String(obj_key.ptr()); - if (temp) - { - #if PY_VERSION_HEX >= 0x03000000 - char* c_str = PyBytes_AsString(temp); - #else - char* c_str = PyString_AsString(temp); - #endif - key = c_str; - Py_DecRef(temp); - } + vars[key] = tr_.transcode(handle.cast().c_str()); } - else + else if (py::isinstance(handle)) { - key = extract(keys[i]); + vars[key] = handle.cast(); } - object obj = d[key]; - if (PyUnicode_Check(obj.ptr())) - { - PyObject* temp = PyUnicode_AsUTF8String(obj.ptr()); - if (temp) - { - #if PY_VERSION_HEX >= 0x03000000 - char* c_str = PyBytes_AsString(temp); - #else - char* c_str = PyString_AsString(temp); - #endif - vars[key] = tr_.transcode(c_str); - Py_DecRef(temp); - } - continue; - } - - if (PyBool_Check(obj.ptr())) + else if (py::isinstance(handle)) { - extract ex(obj); - if (ex.check()) - { - vars[key] = ex(); - } + vars[key] = handle.cast(); } - else if (PyFloat_Check(obj.ptr())) + else if (py::isinstance(handle)) { - extract ex(obj); - if (ex.check()) - { - vars[key] = ex(); - } + vars[key] = handle.cast(); } else { - extract ex(obj); - if (ex.check()) - { - vars[key] = ex(); - } - else - { - extract ex0(obj); - if (ex0.check()) - { - vars[key] = tr_.transcode(ex0().c_str()); - } - } + vars[key] = tr_.transcode(py::str(handle).cast().c_str()); } } return vars; diff --git a/src/boost_std_shared_shim.hpp b/src/python_variant.hpp similarity index 57% rename from src/boost_std_shared_shim.hpp rename to src/python_variant.hpp index 8b603e57e..75631ff71 100644 --- a/src/boost_std_shared_shim.hpp +++ b/src/python_variant.hpp @@ -2,7 +2,7 @@ * * This file is part of Mapnik (c++ mapping toolkit) * - * Copyright (C) 2015 Artem Pavlenko + * Copyright (C) 2024 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,30 +20,21 @@ * *****************************************************************************/ -#ifndef MAPNIK_PYTHON_BOOST_STD_SHARED_SHIM -#define MAPNIK_PYTHON_BOOST_STD_SHARED_SHIM - -// boost -#include -#include - -#if BOOST_VERSION < 105300 || defined BOOST_NO_CXX11_SMART_PTR - -// https://github.com/mapnik/mapnik/issues/2022 -#include - -namespace boost { -template const T* get_pointer(std::shared_ptr const& p) -{ - return p.get(); -} - -template T* get_pointer(std::shared_ptr& p) -{ - return p.get(); -} -} // namespace boost - -#endif - -#endif // MAPNIK_PYTHON_BOOST_STD_SHARED_SHIM +//pybind11 +#include +#include +#include + +namespace PYBIND11_NAMESPACE { namespace detail { +template +struct type_caster> : variant_caster> {}; + +// // Specifies the function used to visit the variant -- `apply_visitor` instead of `visit` +// template <> +// struct visit_helper { +// template +// static auto call(Args &&...args) -> decltype(mapnik::util::apply_visitor(args...)) { +// return mapnik::util::apply_visitor(args...); +// } +// }; +}} // namespace PYBIND11_NAMESPACE::detail diff --git a/test/data b/test/data index 99da07d5e..41c4ceeb0 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 99da07d5e76ccf5978ef0a380bf5f631f9088584 +Subproject commit 41c4ceeb0be4e5e699cdd50bd808054a826c922b diff --git a/test/data-visual b/test/data-visual index e040c3d9c..7dfd4568d 160000 --- a/test/data-visual +++ b/test/data-visual @@ -1 +1 @@ -Subproject commit e040c3d9c8f6bdf3319e25f42b1cf907725285c9 +Subproject commit 7dfd4568d6181da8be3543c8b7522b596a79b774 diff --git a/test/python_tests/agg_rasterizer_integer_overflow_test.py b/test/python_tests/agg_rasterizer_integer_overflow_test.py index af705e3d8..857766192 100644 --- a/test/python_tests/agg_rasterizer_integer_overflow_test.py +++ b/test/python_tests/agg_rasterizer_integer_overflow_test.py @@ -1,14 +1,6 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import json - -from nose.tools import eq_ - import mapnik -from .utilities import run_all - # geojson box of the world geojson = {"type": "Feature", "properties": {}, @@ -24,10 +16,9 @@ [-17963313.143242701888084, -6300857.11560364998877]]]}} - def test_that_coordinates_do_not_overflow_and_polygon_is_rendered_memory(): expected_color = mapnik.Color('white') - projection = '+init=epsg:4326' + projection = 'epsg:4326' ds = mapnik.MemoryDatasource() context = mapnik.Context() feat = mapnik.Feature.from_geojson(json.dumps(geojson), context) @@ -36,7 +27,7 @@ def test_that_coordinates_do_not_overflow_and_polygon_is_rendered_memory(): r = mapnik.Rule() sym = mapnik.PolygonSymbolizer() sym.fill = expected_color - r.symbols.append(sym) + r.symbolizers.append(sym) s.rules.append(r) lyr = mapnik.Layer('Layer', projection) lyr.datasource = ds @@ -52,12 +43,11 @@ def test_that_coordinates_do_not_overflow_and_polygon_is_rendered_memory(): # m.zoom_to_box(mapnik.Box2d(-13658379.710221574,6195679.764683247,-13655933.72531645,6198125.749588372)) im = mapnik.Image(256, 256) mapnik.render(m, im) - eq_(im.get_pixel(128, 128), expected_color.packed()) - + assert im.get_pixel(128, 128) == expected_color.packed() def test_that_coordinates_do_not_overflow_and_polygon_is_rendered_csv(): expected_color = mapnik.Color('white') - projection = '+init=epsg:4326' + projection = 'epsg:4326' ds = mapnik.MemoryDatasource() context = mapnik.Context() feat = mapnik.Feature.from_geojson(json.dumps(geojson), context) @@ -68,7 +58,7 @@ def test_that_coordinates_do_not_overflow_and_polygon_is_rendered_csv(): r = mapnik.Rule() sym = mapnik.PolygonSymbolizer() sym.fill = expected_color - r.symbols.append(sym) + r.symbolizers.append(sym) s.rules.append(r) lyr = mapnik.Layer('Layer', projection) lyr.datasource = ds @@ -84,7 +74,4 @@ def test_that_coordinates_do_not_overflow_and_polygon_is_rendered_csv(): # m.zoom_to_box(mapnik.Box2d(-13658379.710221574,6195679.764683247,-13655933.72531645,6198125.749588372)) im = mapnik.Image(256, 256) mapnik.render(m, im) - eq_(im.get_pixel(128, 128), expected_color.packed()) - -if __name__ == "__main__": - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert im.get_pixel(128, 128) == expected_color.packed() diff --git a/test/python_tests/box2d_test.py b/test/python_tests/box2d_test.py index 7fe0a9f59..e3a477003 100644 --- a/test/python_tests/box2d_test.py +++ b/test/python_tests/box2d_test.py @@ -1,184 +1,155 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from nose.tools import assert_almost_equal, assert_false, assert_true, eq_ - import mapnik - -from .utilities import run_all - +import pytest def test_coord_init(): c = mapnik.Coord(100, 100) - - eq_(c.x, 100) - eq_(c.y, 100) - + assert c.x == 100 + assert c.y == 100 def test_coord_multiplication(): - c = mapnik.Coord(100, 100) - c *= 2 - - eq_(c.x, 200) - eq_(c.y, 200) - + c = mapnik.Coord(100, 100) + c *= 2 + assert c.x == 200 + assert c.y == 200 def test_envelope_init(): - e = mapnik.Box2d(100, 100, 200, 200) - - assert_true(e.contains(100, 100)) - assert_true(e.contains(100, 200)) - assert_true(e.contains(200, 200)) - assert_true(e.contains(200, 100)) - - assert_true(e.contains(e.center())) - - assert_false(e.contains(99.9, 99.9)) - assert_false(e.contains(99.9, 200.1)) - assert_false(e.contains(200.1, 200.1)) - assert_false(e.contains(200.1, 99.9)) - - eq_(e.width(), 100) - eq_(e.height(), 100) - - eq_(e.minx, 100) - eq_(e.miny, 100) - - eq_(e.maxx, 200) - eq_(e.maxy, 200) - - eq_(e[0], 100) - eq_(e[1], 100) - eq_(e[2], 200) - eq_(e[3], 200) - eq_(e[0], e[-4]) - eq_(e[1], e[-3]) - eq_(e[2], e[-2]) - eq_(e[3], e[-1]) - - c = e.center() - - eq_(c.x, 150) - eq_(c.y, 150) + e = mapnik.Box2d(100, 100, 200, 200) + assert e.contains(100, 100) + assert e.contains(100, 200) + assert e.contains(200, 200) + assert e.contains(200, 100) + assert e.contains(e.center()) + assert not e.contains(99.9, 99.9) + assert not e.contains(99.9, 200.1) + assert not e.contains(200.1, 200.1) + assert not e.contains(200.1, 99.9) + assert e.width() == 100 + assert e.height() == 100 + assert e.minx == 100 + assert e.miny == 100 + assert e.maxx == 200 + assert e.maxy == 200 + assert e[0] == 100 + assert e[1] == 100 + assert e[2] == 200 + assert e[3] == 200 + assert e[0] == e[-4] + assert e[1] == e[-3] + assert e[2] == e[-2] + assert e[3] == e[-1] + c = e.center() + assert c.x == 150 + assert c.y == 150 def test_envelope_static_init(): e = mapnik.Box2d.from_string('100 100 200 200') e2 = mapnik.Box2d.from_string('100,100,200,200') e3 = mapnik.Box2d.from_string('100 , 100 , 200 , 200') - eq_(e, e2) - eq_(e, e3) - - assert_true(e.contains(100, 100)) - assert_true(e.contains(100, 200)) - assert_true(e.contains(200, 200)) - assert_true(e.contains(200, 100)) - - assert_true(e.contains(e.center())) - - assert_false(e.contains(99.9, 99.9)) - assert_false(e.contains(99.9, 200.1)) - assert_false(e.contains(200.1, 200.1)) - assert_false(e.contains(200.1, 99.9)) - - eq_(e.width(), 100) - eq_(e.height(), 100) - eq_(e.minx, 100) - eq_(e.miny, 100) - - eq_(e.maxx, 200) - eq_(e.maxy, 200) - - eq_(e[0], 100) - eq_(e[1], 100) - eq_(e[2], 200) - eq_(e[3], 200) - eq_(e[0], e[-4]) - eq_(e[1], e[-3]) - eq_(e[2], e[-2]) - eq_(e[3], e[-1]) + assert e == e2 + assert e == e3 + assert e.contains(100, 100) + assert e.contains(100, 200) + assert e.contains(200, 200) + assert e.contains(200, 100) + + assert e.contains(e.center()) + assert not e.contains(99.9, 99.9) + assert not e.contains(99.9, 200.1) + assert not e.contains(200.1, 200.1) + assert not e.contains(200.1, 99.9) + + assert e.width() == 100 + assert e.height() == 100 + assert e.minx == 100 + assert e.miny == 100 + assert e.maxx == 200 + assert e.maxy == 200 + + assert e[0] == 100 + assert e[1] == 100 + assert e[2] == 200 + assert e[3] == 200 + assert e[0] == e[-4] + assert e[1] == e[-3] + assert e[2] == e[-2] + assert e[3] == e[-1] c = e.center() - - eq_(c.x, 150) - eq_(c.y, 150) - + assert c.x == 150 + assert c.y == 150 def test_envelope_multiplication(): - # no width then no impact of multiplication - a = mapnik.Box2d(100, 100, 100, 100) - a *= 5 - eq_(a.minx, 100) - eq_(a.miny, 100) - eq_(a.maxx, 100) - eq_(a.maxy, 100) - - a = mapnik.Box2d(100.0, 100.0, 100.0, 100.0) - a *= 5 - eq_(a.minx, 100) - eq_(a.miny, 100) - eq_(a.maxx, 100) - eq_(a.maxy, 100) - - a = mapnik.Box2d(100.0, 100.0, 100.001, 100.001) - a *= 5 - assert_almost_equal(a.minx, 99.9979, places=3) - assert_almost_equal(a.miny, 99.9979, places=3) - assert_almost_equal(a.maxx, 100.0030, places=3) - assert_almost_equal(a.maxy, 100.0030, places=3) - - e = mapnik.Box2d(100, 100, 200, 200) - e *= 2 - eq_(e.minx, 50) - eq_(e.miny, 50) - eq_(e.maxx, 250) - eq_(e.maxy, 250) - - assert_true(e.contains(50, 50)) - assert_true(e.contains(50, 250)) - assert_true(e.contains(250, 250)) - assert_true(e.contains(250, 50)) - - assert_false(e.contains(49.9, 49.9)) - assert_false(e.contains(49.9, 250.1)) - assert_false(e.contains(250.1, 250.1)) - assert_false(e.contains(250.1, 49.9)) - - assert_true(e.contains(e.center())) - - eq_(e.width(), 200) - eq_(e.height(), 200) - - eq_(e.minx, 50) - eq_(e.miny, 50) - - eq_(e.maxx, 250) - eq_(e.maxy, 250) - - c = e.center() - - eq_(c.x, 150) - eq_(c.y, 150) + # no width then no impact of multiplication + a = mapnik.Box2d(100, 100, 100, 100) + a *= 5 + assert a.minx == 100 + assert a.miny == 100 + assert a.maxx == 100 + assert a.maxy == 100 + + a = mapnik.Box2d(100.0, 100.0, 100.0, 100.0) + a *= 5 + assert a.minx == 100 + assert a.miny == 100 + assert a.maxx == 100 + assert a.maxy == 100 + + a = mapnik.Box2d(100.0, 100.0, 100.001, 100.001) + a *= 5 + assert a.minx == pytest.approx(99.9979, 1e-3) + assert a.miny == pytest.approx(99.9979, 1e-3) + assert a.maxx == pytest.approx(100.0030,1e-3) + assert a.maxy == pytest.approx(100.0030,1e-3) + + e = mapnik.Box2d(100, 100, 200, 200) + e *= 2 + assert e.minx == 50 + assert e.miny == 50 + assert e.maxx == 250 + assert e.maxy == 250 + + assert e.contains(50, 50) + assert e.contains(50, 250) + assert e.contains(250, 250) + assert e.contains(250, 50) + + assert not e.contains(49.9, 49.9) + assert not e.contains(49.9, 250.1) + assert not e.contains(250.1, 250.1) + assert not e.contains(250.1, 49.9) + + c = e.center() + assert c.x == 150 + assert c.y == 150 + + assert e.contains(c) + + assert e.width() == 200 + assert e.height()== 200 + + assert e.minx == 50 + assert e.miny == 50 + + assert e.maxx == 250 + assert e.maxy == 250 def test_envelope_clipping(): - e1 = mapnik.Box2d(-180, -90, 180, 90) - e2 = mapnik.Box2d(-120, 40, -110, 48) - e1.clip(e2) - eq_(e1, e2) - - # madagascar in merc - e1 = mapnik.Box2d(4772116.5490, -2744395.0631, 5765186.4203, -1609458.0673) - e2 = mapnik.Box2d(5124338.3753, -2240522.1727, 5207501.8621, -2130452.8520) - e1.clip(e2) - eq_(e1, e2) - - # nz in lon/lat - e1 = mapnik.Box2d(163.8062, -47.1897, 179.3628, -33.9069) - e2 = mapnik.Box2d(173.7378, -39.6395, 174.4849, -38.9252) - e1.clip(e2) - eq_(e1, e2) - -if __name__ == "__main__": - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + e1 = mapnik.Box2d(-180, -90, 180, 90) + e2 = mapnik.Box2d(-120, 40, -110, 48) + e1.clip(e2) + assert e1 == e2 + + # madagascar in merc + e1 = mapnik.Box2d(4772116.5490, -2744395.0631, 5765186.4203, -1609458.0673) + e2 = mapnik.Box2d(5124338.3753, -2240522.1727, 5207501.8621, -2130452.8520) + e1.clip(e2) + assert e1 == e2 + +# # nz in lon/lat + e1 = mapnik.Box2d(163.8062, -47.1897, 179.3628, -33.9069) + e2 = mapnik.Box2d(173.7378, -39.6395, 174.4849, -38.9252) + e1.clip(e2) + assert e1 == e2 diff --git a/test/python_tests/buffer_clear_test.py b/test/python_tests/buffer_clear_test.py index b94e9e4c6..c72c0e919 100644 --- a/test/python_tests/buffer_clear_test.py +++ b/test/python_tests/buffer_clear_test.py @@ -1,30 +1,17 @@ import os - -from nose.tools import eq_ - import mapnik -from .utilities import execution_path, run_all - - -def setup(): - # All of the paths used are relative, if we run the tests - # from another directory we need to chdir() - os.chdir(execution_path('.')) - - def test_clearing_image_data(): im = mapnik.Image(256, 256) # make sure it equals itself - bytes = im.tostring() - eq_(im.tostring(), bytes) + bytes = im.to_string() + assert im.to_string() == bytes # set background, then clear im.fill(mapnik.Color('green')) - eq_(im.tostring() != bytes, True) + assert not im.to_string() == bytes # clear image, should now equal original im.clear() - eq_(im.tostring(), bytes) - + assert im.to_string() == bytes def make_map(): ds = mapnik.MemoryDatasource() @@ -39,7 +26,7 @@ def make_map(): s = mapnik.Style() r = mapnik.Rule() symb = mapnik.PolygonSymbolizer() - r.symbols.append(symb) + r.symbolizers.append(symb) s.rules.append(r) lyr = mapnik.Layer('Places') lyr.datasource = ds @@ -56,14 +43,10 @@ def test_clearing_grid_data(): g = mapnik.Grid(256, 256) utf = g.encode() # make sure it equals itself - eq_(g.encode(), utf) + assert g.encode() == utf m = make_map() mapnik.render_layer(m, g, layer=0, fields=['__id__', 'Name']) - eq_(g.encode() != utf, True) + assert g.encode() != utf # clear grid, should now match original g.clear() - eq_(g.encode(), utf) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert g.encode() == utf diff --git a/test/python_tests/cairo_test.py b/test/python_tests/cairo_test.py index c6c25a379..a3d324314 100644 --- a/test/python_tests/cairo_test.py +++ b/test/python_tests/cairo_test.py @@ -1,21 +1,15 @@ -#!/usr/bin/env python - -from __future__ import print_function - import os import shutil - -from nose.tools import eq_ - import mapnik +import pytest +from .utilities import execution_path -from .utilities import execution_path, run_all - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield def make_tmp_map(): @@ -41,13 +35,12 @@ def make_tmp_map(): m.layers.append(lyr) return m - def draw_title(m, ctx, text, size=10, color=mapnik.Color('black')): """ Draw a Map Title near the top of a page.""" middle = m.width / 2.0 ctx.set_source_rgba(*cairo_color(color)) ctx.select_font_face( - "DejaVu Sans Book", + "Helvetica", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_font_size(size) @@ -92,12 +85,12 @@ def cairo_color(c): if mapnik.has_pycairo(): import cairo - def test_passing_pycairo_context_svg(): + def test_passing_pycairo_context_svg(setup): m = make_tmp_map() m.zoom_to_box(mapnik.Box2d(-180, -90, 180, 90)) test_cairo_file = '/tmp/mapnik-cairo-context-test.svg' surface = cairo.SVGSurface(test_cairo_file, m.width, m.height) - expected_cairo_file = './images/pycairo/cairo-cairo-expected.svg' + expected_cairo_file = 'images/pycairo/cairo-cairo-expected.svg' context = cairo.Context(surface) mapnik.render(m, context) draw_title(m, context, "Hello Map", size=20) @@ -111,7 +104,7 @@ def test_passing_pycairo_context_svg(): os.stat(test_cairo_file).st_size) msg = 'diff in size (%s) between actual (%s) and expected(%s)' % ( diff, test_cairo_file, 'tests/python_tests/' + expected_cairo_file) - eq_(diff < 1500, True, msg) + assert diff < 1500, msg os.remove(test_cairo_file) def test_passing_pycairo_context_pdf(): @@ -119,7 +112,7 @@ def test_passing_pycairo_context_pdf(): m.zoom_to_box(mapnik.Box2d(-180, -90, 180, 90)) test_cairo_file = '/tmp/mapnik-cairo-context-test.pdf' surface = cairo.PDFSurface(test_cairo_file, m.width, m.height) - expected_cairo_file = './images/pycairo/cairo-cairo-expected.pdf' + expected_cairo_file = 'images/pycairo/cairo-cairo-expected.pdf' context = cairo.Context(surface) mapnik.render(m, context) draw_title(m, context, "Hello Map", size=20) @@ -133,7 +126,7 @@ def test_passing_pycairo_context_pdf(): os.stat(test_cairo_file).st_size) msg = 'diff in size (%s) between actual (%s) and expected(%s)' % ( diff, test_cairo_file, 'tests/python_tests/' + expected_cairo_file) - eq_(diff < 1500, True, msg) + assert diff < 1500, msg os.remove(test_cairo_file) def test_passing_pycairo_context_png(): @@ -141,8 +134,8 @@ def test_passing_pycairo_context_png(): m.zoom_to_box(mapnik.Box2d(-180, -90, 180, 90)) test_cairo_file = '/tmp/mapnik-cairo-context-test.png' surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, m.width, m.height) - expected_cairo_file = './images/pycairo/cairo-cairo-expected.png' - expected_cairo_file2 = './images/pycairo/cairo-cairo-expected-reduced.png' + expected_cairo_file = 'images/pycairo/cairo-cairo-expected.png' + expected_cairo_file2 = 'images/pycairo/cairo-cairo-expected-reduced.png' context = cairo.Context(surface) mapnik.render(m, context) draw_title(m, context, "Hello Map", size=20) @@ -160,7 +153,7 @@ def test_passing_pycairo_context_png(): os.stat(test_cairo_file).st_size) msg = 'diff in size (%s) between actual (%s) and expected(%s)' % ( diff, test_cairo_file, 'tests/python_tests/' + expected_cairo_file) - eq_(diff < 500, True, msg) + assert diff < 500, msg os.remove(test_cairo_file) if not os.path.exists( expected_cairo_file2) or os.environ.get('UPDATE'): @@ -173,14 +166,14 @@ def test_passing_pycairo_context_png(): os.stat(reduced_color_image).st_size) msg = 'diff in size (%s) between actual (%s) and expected(%s)' % ( diff, reduced_color_image, 'tests/python_tests/' + expected_cairo_file2) - eq_(diff < 500, True, msg) + assert diff < 500, msg os.remove(reduced_color_image) if 'sqlite' in mapnik.DatasourceCache.plugin_names(): def _pycairo_surface(type, sym): test_cairo_file = '/tmp/mapnik-cairo-surface-test.%s.%s' % ( sym, type) - expected_cairo_file = './images/pycairo/cairo-surface-expected.%s.%s' % ( + expected_cairo_file = 'images/pycairo/cairo-surface-expected.%s.%s' % ( sym, type) m = mapnik.Map(256, 256) mapnik.load_map(m, '../data/good_maps/%s_symbolizer.xml' % sym) @@ -207,9 +200,9 @@ def _pycairo_surface(type, sym): msg = 'diff in size (%s) between actual (%s) and expected(%s)' % ( diff, test_cairo_file, 'tests/python_tests/' + expected_cairo_file) if os.uname()[0] == 'Darwin': - eq_(diff < 2100, True, msg) + assert diff < 2100, msg else: - eq_(diff < 23000, True, msg) + assert diff < 23000, msg os.remove(test_cairo_file) return True else: @@ -219,23 +212,19 @@ def _pycairo_surface(type, sym): return True def test_pycairo_svg_surface1(): - eq_(_pycairo_surface('svg', 'point'), True) + assert _pycairo_surface('svg', 'point') def test_pycairo_svg_surface2(): - eq_(_pycairo_surface('svg', 'building'), True) + assert _pycairo_surface('svg', 'building') def test_pycairo_svg_surface3(): - eq_(_pycairo_surface('svg', 'polygon'), True) + assert _pycairo_surface('svg', 'polygon') def test_pycairo_pdf_surface1(): - eq_(_pycairo_surface('pdf', 'point'), True) + assert _pycairo_surface('pdf', 'point') def test_pycairo_pdf_surface2(): - eq_(_pycairo_surface('pdf', 'building'), True) + assert _pycairo_surface('pdf', 'building') def test_pycairo_pdf_surface3(): - eq_(_pycairo_surface('pdf', 'polygon'), True) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert _pycairo_surface('pdf', 'polygon') diff --git a/test/python_tests/color_test.py b/test/python_tests/color_test.py index 428843145..e8fc90fc6 100644 --- a/test/python_tests/color_test.py +++ b/test/python_tests/color_test.py @@ -1,121 +1,102 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os - -from nose.tools import eq_ - import mapnik -from .utilities import execution_path, run_all - - -def setup(): - # All of the paths used are relative, if we run the tests - # from another directory we need to chdir() - os.chdir(execution_path('.')) - - def test_color_init(): c = mapnik.Color(12, 128, 255) - eq_(c.r, 12) - eq_(c.g, 128) - eq_(c.b, 255) - eq_(c.a, 255) - eq_(False, c.get_premultiplied()) + assert c.r == 12 + assert c.g == 128 + assert c.b == 255 + assert c.a == 255 + assert not c.get_premultiplied() c = mapnik.Color(16, 32, 64, 128) - eq_(c.r, 16) - eq_(c.g, 32) - eq_(c.b, 64) - eq_(c.a, 128) - eq_(False, c.get_premultiplied()) + assert c.r == 16 + assert c.g == 32 + assert c.b == 64 + assert c.a == 128 + assert not c.get_premultiplied() c = mapnik.Color(16, 32, 64, 128, True) - eq_(c.r, 16) - eq_(c.g, 32) - eq_(c.b, 64) - eq_(c.a, 128) - eq_(True, c.get_premultiplied()) + assert c.r == 16 + assert c.g == 32 + assert c.b == 64 + assert c.a == 128 + assert c.get_premultiplied() c = mapnik.Color('rgba(16,32,64,0.5)') - eq_(c.r, 16) - eq_(c.g, 32) - eq_(c.b, 64) - eq_(c.a, 128) - eq_(False, c.get_premultiplied()) + assert c.r == 16 + assert c.g == 32 + assert c.b == 64 + assert c.a == 128 + assert not c.get_premultiplied() c = mapnik.Color('rgba(16,32,64,0.5)', True) - eq_(c.r, 16) - eq_(c.g, 32) - eq_(c.b, 64) - eq_(c.a, 128) - eq_(True, c.get_premultiplied()) + assert c.r == 16 + assert c.g == 32 + assert c.b == 64 + assert c.a == 128 + assert c.get_premultiplied() hex_str = '#10204080' c = mapnik.Color(hex_str) - eq_(c.r, 16) - eq_(c.g, 32) - eq_(c.b, 64) - eq_(c.a, 128) - eq_(hex_str, c.to_hex_string()) - eq_(False, c.get_premultiplied()) + assert c.r == 16 + assert c.g == 32 + assert c.b == 64 + assert c.a == 128 + assert hex_str == c.to_hex_string() + assert not c.get_premultiplied() c = mapnik.Color(hex_str, True) - eq_(c.r, 16) - eq_(c.g, 32) - eq_(c.b, 64) - eq_(c.a, 128) - eq_(hex_str, c.to_hex_string()) - eq_(True, c.get_premultiplied()) + assert c.r == 16 + assert c.g == 32 + assert c.b == 64 + assert c.a == 128 + assert hex_str == c.to_hex_string() + assert c.get_premultiplied() rgba_int = 2151686160 c = mapnik.Color(rgba_int) - eq_(c.r, 16) - eq_(c.g, 32) - eq_(c.b, 64) - eq_(c.a, 128) - eq_(rgba_int, c.packed()) - eq_(False, c.get_premultiplied()) + assert c.r == 16 + assert c.g == 32 + assert c.b == 64 + assert c.a == 128 + assert rgba_int == c.packed() + assert not c.get_premultiplied() c = mapnik.Color(rgba_int, True) - eq_(c.r, 16) - eq_(c.g, 32) - eq_(c.b, 64) - eq_(c.a, 128) - eq_(rgba_int, c.packed()) - eq_(True, c.get_premultiplied()) + assert c.r == 16 + assert c.g == 32 + assert c.b == 64 + assert c.a == 128 + assert rgba_int == c.packed() + assert c.get_premultiplied() def test_color_properties(): c = mapnik.Color(16, 32, 64, 128) - eq_(c.r, 16) - eq_(c.g, 32) - eq_(c.b, 64) - eq_(c.a, 128) + assert c.r == 16 + assert c.g == 32 + assert c.b == 64 + assert c.a == 128 c.r = 17 - eq_(c.r, 17) + assert c.r == 17 c.g = 33 - eq_(c.g, 33) + assert c.g == 33 c.b = 65 - eq_(c.b, 65) + assert c.b == 65 c.a = 128 - eq_(c.a, 128) + assert c.a == 128 def test_color_premultiply(): c = mapnik.Color(16, 33, 255, 128) - eq_(c.premultiply(), True) - eq_(c.r, 8) - eq_(c.g, 17) - eq_(c.b, 128) - eq_(c.a, 128) + assert c.premultiply() + assert c.r == 8 + assert c.g == 17 + assert c.b == 128 + assert c.a == 128 # Repeating it again should do nothing - eq_(c.premultiply(), False) - eq_(c.r, 8) - eq_(c.g, 17) - eq_(c.b, 128) - eq_(c.a, 128) + assert not c.premultiply() + assert c.r == 8 + assert c.g == 17 + assert c.b == 128 + assert c.a == 128 c.demultiply() c.demultiply() # This will not return the same values as before but we expect that - eq_(c.r, 15) - eq_(c.g, 33) - eq_(c.b, 255) - eq_(c.a, 128) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert c.r == 15 + assert c.g == 33 + assert c.b == 255 + assert c.a == 128 diff --git a/test/python_tests/compare_test.py b/test/python_tests/compare_test.py index bb8397a2b..b66775262 100644 --- a/test/python_tests/compare_test.py +++ b/test/python_tests/compare_test.py @@ -1,48 +1,32 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os - -from nose.tools import eq_ - import mapnik -from .utilities import execution_path, run_all - - -def setup(): - # All of the paths used are relative, if we run the tests - # from another directory we need to chdir() - os.chdir(execution_path('.')) - - def test_another_compare(): im = mapnik.Image(5, 5) im2 = mapnik.Image(5, 5) im2.fill(mapnik.Color('rgba(255,255,255,0)')) - eq_(im.compare(im2, 16), im.width() * im.height()) - + assert im.compare(im2, 16) == im.width() * im.height() def test_compare_rgba8(): im = mapnik.Image(5, 5, mapnik.ImageType.rgba8) im.fill(mapnik.Color(0, 0, 0, 0)) - eq_(im.compare(im), 0) + assert im.compare(im) == 0 im2 = mapnik.Image(5, 5, mapnik.ImageType.rgba8) im2.fill(mapnik.Color(0, 0, 0, 0)) - eq_(im.compare(im2), 0) - eq_(im2.compare(im), 0) + assert im.compare(im2) == 0 + assert im2.compare(im) == 0 im2.fill(mapnik.Color(0, 0, 0, 12)) - eq_(im.compare(im2), 25) - eq_(im.compare(im2, 0, False), 0) + assert im.compare(im2) == 25 + assert im.compare(im2, 0, False) == 0 im3 = mapnik.Image(5, 5, mapnik.ImageType.rgba8) im3.set_pixel(0, 0, mapnik.Color(0, 0, 0, 0)) im3.set_pixel(0, 1, mapnik.Color(1, 1, 1, 1)) im3.set_pixel(1, 0, mapnik.Color(2, 2, 2, 2)) im3.set_pixel(1, 1, mapnik.Color(3, 3, 3, 3)) - eq_(im.compare(im3), 3) - eq_(im.compare(im3, 1), 2) - eq_(im.compare(im3, 2), 1) - eq_(im.compare(im3, 3), 0) + assert im.compare(im3) == 3 + assert im.compare(im3, 1) == 2 + assert im.compare(im3, 2) == 1 + assert im.compare(im3, 3) == 0 def test_compare_2_image(): @@ -50,75 +34,71 @@ def test_compare_2_image(): im.set_pixel(0, 0, mapnik.Color(254, 254, 254, 254)) im.set_pixel(4, 4, mapnik.Color('white')) im2 = mapnik.Image(5, 5) - eq_(im2.compare(im, 16), 2) + assert im2.compare(im, 16) == 2 def test_compare_dimensions(): im = mapnik.Image(2, 2) im2 = mapnik.Image(3, 3) - eq_(im.compare(im2), 4) - eq_(im2.compare(im), 9) + assert im.compare(im2) == 4 + assert im2.compare(im) == 9 def test_compare_gray8(): im = mapnik.Image(2, 2, mapnik.ImageType.gray8) im.fill(0) - eq_(im.compare(im), 0) + assert im.compare(im) == 0 im2 = mapnik.Image(2, 2, mapnik.ImageType.gray8) im2.fill(0) - eq_(im.compare(im2), 0) - eq_(im2.compare(im), 0) - eq_(im.compare(im2, 0, False), 0) + assert im.compare(im2) == 0 + assert im2.compare(im) == 0 + assert im.compare(im2, 0, False) == 0 im3 = mapnik.Image(2, 2, mapnik.ImageType.gray8) im3.set_pixel(0, 0, 0) im3.set_pixel(0, 1, 1) im3.set_pixel(1, 0, 2) im3.set_pixel(1, 1, 3) - eq_(im.compare(im3), 3) - eq_(im.compare(im3, 1), 2) - eq_(im.compare(im3, 2), 1) - eq_(im.compare(im3, 3), 0) + assert im.compare(im3) == 3 + assert im.compare(im3, 1) == 2 + assert im.compare(im3, 2) == 1 + assert im.compare(im3, 3) == 0 def test_compare_gray16(): im = mapnik.Image(2, 2, mapnik.ImageType.gray16) im.fill(0) - eq_(im.compare(im), 0) + assert im.compare(im) == 0 im2 = mapnik.Image(2, 2, mapnik.ImageType.gray16) im2.fill(0) - eq_(im.compare(im2), 0) - eq_(im2.compare(im), 0) - eq_(im.compare(im2, 0, False), 0) + assert im.compare(im2) == 0 + assert im2.compare(im) == 0 + assert im.compare(im2, 0, False) == 0 im3 = mapnik.Image(2, 2, mapnik.ImageType.gray16) im3.set_pixel(0, 0, 0) im3.set_pixel(0, 1, 1) im3.set_pixel(1, 0, 2) im3.set_pixel(1, 1, 3) - eq_(im.compare(im3), 3) - eq_(im.compare(im3, 1), 2) - eq_(im.compare(im3, 2), 1) - eq_(im.compare(im3, 3), 0) + assert im.compare(im3) == 3 + assert im.compare(im3, 1) == 2 + assert im.compare(im3, 2) == 1 + assert im.compare(im3, 3) == 0 def test_compare_gray32f(): im = mapnik.Image(2, 2, mapnik.ImageType.gray32f) im.fill(0.5) - eq_(im.compare(im), 0) + assert im.compare(im) == 0 im2 = mapnik.Image(2, 2, mapnik.ImageType.gray32f) im2.fill(0.5) - eq_(im.compare(im2), 0) - eq_(im2.compare(im), 0) - eq_(im.compare(im2, 0, False), 0) + assert im.compare(im2) == 0 + assert im2.compare(im) == 0 + assert im.compare(im2, 0, False) == 0 im3 = mapnik.Image(2, 2, mapnik.ImageType.gray32f) im3.set_pixel(0, 0, 0.5) im3.set_pixel(0, 1, 1.5) im3.set_pixel(1, 0, 2.5) im3.set_pixel(1, 1, 3.5) - eq_(im.compare(im3), 3) - eq_(im.compare(im3, 1.0), 2) - eq_(im.compare(im3, 2.0), 1) - eq_(im.compare(im3, 3.0), 0) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert im.compare(im3) == 3 + assert im.compare(im3, 1.0) == 2 + assert im.compare(im3, 2.0) == 1 + assert im.compare(im3, 3.0) == 0 diff --git a/test/python_tests/compositing_test.py b/test/python_tests/compositing_test.py index ac09ef9ad..46ec373a5 100644 --- a/test/python_tests/compositing_test.py +++ b/test/python_tests/compositing_test.py @@ -1,22 +1,14 @@ -# encoding: utf8 - -from __future__ import print_function - import os - -from nose.tools import eq_ - import mapnik +import pytest +from .utilities import (get_unique_colors, pixel2channels, side_by_side_image, execution_path) -from .utilities import (execution_path, get_unique_colors, pixel2channels, - run_all, side_by_side_image) - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) - + yield def is_pre(color, alpha): return (color * 255.0 / alpha) <= 255 @@ -93,14 +85,14 @@ def validate_pixels_are_premultiplied(image): return (num_bad == 0, bad_pixels) -def test_compare_images(): - b = mapnik.Image.open('./images/support/b.png') +def test_compare_images(setup): + b = mapnik.Image.open('images/support/b.png') b.premultiply() - num_ops = len(mapnik.CompositeOp.names) + num_ops = len(mapnik.CompositeOp.__members__) successes = [] fails = [] - for name in mapnik.CompositeOp.names: - a = mapnik.Image.open('./images/support/a.png') + for name in mapnik.CompositeOp.__members__.keys(): + a = mapnik.Image.open('images/support/a.png') a.premultiply() a.composite(b, getattr(mapnik.CompositeOp, name)) actual = '/tmp/mapnik-comp-op-test-' + name + '.png' @@ -120,7 +112,7 @@ def test_compare_images(): a.save(expected, 'png32') expected_im = mapnik.Image.open(expected) # compare them - if a.tostring('png32') == expected_im.tostring('png32'): + if a.to_string('png32') == expected_im.to_string('png32'): successes.append(name) else: fails.append( @@ -132,53 +124,53 @@ def test_compare_images(): name + '.fail.png', 'png32') - eq_(len(successes), num_ops, '\n' + '\n'.join(fails)) + assert len(successes) == num_ops, '\n' + '\n'.join(fails) b.demultiply() # b will be slightly modified by pre and then de multiplication rounding errors # TODO - write test to ensure the image is 99% the same. #expected_b = mapnik.Image.open('./images/support/b.png') # b.save('/tmp/mapnik-comp-op-test-original-mask.png') - #eq_(b.tostring('png32'),expected_b.tostring('png32'), '/tmp/mapnik-comp-op-test-original-mask.png is no longer equivalent to original mask: ./images/support/b.png') + #assert b.to_string('png32') == expected_b.to_string('png32'), '/tmp/mapnik-comp-op-test-original-mask.png is no longer equivalent to original mask: ./images/support/b.png' def test_pre_multiply_status(): - b = mapnik.Image.open('./images/support/b.png') + b = mapnik.Image.open('images/support/b.png') # not premultiplied yet, should appear that way result = validate_pixels_are_not_premultiplied(b) - eq_(result, True) + assert result # not yet premultiplied therefore should return false result = validate_pixels_are_premultiplied(b) - eq_(result[0], False) + assert not result[0] # now actually premultiply the pixels b.premultiply() # now checking if premultiplied should succeed result = validate_pixels_are_premultiplied(b) - eq_(result[0], True) + assert result[0] # should now not appear to look not premultiplied result = validate_pixels_are_not_premultiplied(b) - eq_(result, False) + assert not result # now actually demultiply the pixels b.demultiply() # should now appear demultiplied result = validate_pixels_are_not_premultiplied(b) - eq_(result, True) + assert result def test_pre_multiply_status_of_map1(): m = mapnik.Map(256, 256) im = mapnik.Image(m.width, m.height) - eq_(validate_pixels_are_not_premultiplied(im), True) + assert validate_pixels_are_not_premultiplied(im) mapnik.render(m, im) - eq_(validate_pixels_are_not_premultiplied(im), True) + assert validate_pixels_are_not_premultiplied(im) def test_pre_multiply_status_of_map2(): m = mapnik.Map(256, 256) m.background = mapnik.Color(1, 1, 1, 255) im = mapnik.Image(m.width, m.height) - eq_(validate_pixels_are_not_premultiplied(im), True) + assert validate_pixels_are_not_premultiplied(im) mapnik.render(m, im) - eq_(validate_pixels_are_not_premultiplied(im), True) + assert validate_pixels_are_not_premultiplied(im) if 'shape' in mapnik.DatasourceCache.plugin_names(): def test_style_level_comp_op(): @@ -187,7 +179,8 @@ def test_style_level_comp_op(): m.zoom_all() successes = [] fails = [] - for name in mapnik.CompositeOp.names: + + for name in mapnik.CompositeOp.__members__.keys(): # find_style returns a copy of the style object style_markers = m.find_style("markers") style_markers.comp_op = getattr(mapnik.CompositeOp, name) @@ -203,7 +196,7 @@ def test_style_level_comp_op(): im.save(expected, 'png32') expected_im = mapnik.Image.open(expected) # compare them - if im.tostring('png32') == expected_im.tostring('png32'): + if im.to_string('png32') == expected_im.to_string('png32'): successes.append(name) else: fails.append( @@ -215,7 +208,7 @@ def test_style_level_comp_op(): name + '.fail.png', 'png32') - eq_(len(fails), 0, '\n' + '\n'.join(fails)) + assert len(fails) == 0, '\n' + '\n'.join(fails) def test_style_level_opacity(): m = mapnik.Map(512, 512) @@ -228,10 +221,8 @@ def test_style_level_opacity(): expected = 'images/support/mapnik-style-level-opacity.png' im.save(actual, 'png32') expected_im = mapnik.Image.open(expected) - eq_(im.tostring('png32'), - expected_im.tostring('png32'), - 'failed comparing actual (%s) and expected (%s)' % (actual, - 'tests/python_tests/' + expected)) + assert im.to_string('png32') == expected_im.to_string('png32'), 'failed comparing actual (%s) and expected (%s)' % (actual, + 'tests/python_tests/' + expected) def test_rounding_and_color_expectations(): @@ -239,24 +230,24 @@ def test_rounding_and_color_expectations(): m.background = mapnik.Color('rgba(255,255,255,.4999999)') im = mapnik.Image(m.width, m.height) mapnik.render(m, im) - eq_(get_unique_colors(im), ['rgba(255,255,255,127)']) + assert get_unique_colors(im) == ['rgba(255,255,255,127)'] m = mapnik.Map(1, 1) m.background = mapnik.Color('rgba(255,255,255,.5)') im = mapnik.Image(m.width, m.height) mapnik.render(m, im) - eq_(get_unique_colors(im), ['rgba(255,255,255,128)']) + assert get_unique_colors(im) == ['rgba(255,255,255,128)'] im_file = mapnik.Image.open('../data/images/stripes_pattern.png') - eq_(get_unique_colors(im_file), ['rgba(0,0,0,0)', 'rgba(74,74,74,255)']) + assert get_unique_colors(im_file) == ['rgba(0,0,0,0)', 'rgba(74,74,74,255)'] # should have no effect im_file.premultiply() - eq_(get_unique_colors(im_file), ['rgba(0,0,0,0)', 'rgba(74,74,74,255)']) + assert get_unique_colors(im_file) == ['rgba(0,0,0,0)', 'rgba(74,74,74,255)'] im_file.apply_opacity(.5) # should have effect now that image has transparency im_file.premultiply() - eq_(get_unique_colors(im_file), ['rgba(0,0,0,0)', 'rgba(37,37,37,127)']) + assert get_unique_colors(im_file) == ['rgba(0,0,0,0)', 'rgba(37,37,37,127)'] # should restore to original nonpremultiplied colors im_file.demultiply() - eq_(get_unique_colors(im_file), ['rgba(0,0,0,0)', 'rgba(74,74,74,127)']) + assert get_unique_colors(im_file) == ['rgba(0,0,0,0)', 'rgba(74,74,74,127)'] def test_background_image_and_background_color(): @@ -265,7 +256,7 @@ def test_background_image_and_background_color(): m.background_image = '../data/images/stripes_pattern.png' im = mapnik.Image(m.width, m.height) mapnik.render(m, im) - eq_(get_unique_colors(im), ['rgba(255,255,255,128)', 'rgba(74,74,74,255)']) + assert get_unique_colors(im) == ['rgba(255,255,255,128)', 'rgba(74,74,74,255)'] def test_background_image_with_alpha_and_background_color(): @@ -274,7 +265,7 @@ def test_background_image_with_alpha_and_background_color(): m.background_image = '../data/images/yellow_half_trans.png' im = mapnik.Image(m.width, m.height) mapnik.render(m, im) - eq_(get_unique_colors(im), ['rgba(255,255,85,191)']) + assert get_unique_colors(im) == ['rgba(255,255,85,191)'] def test_background_image_with_alpha_and_background_color_against_composited_control(): @@ -295,8 +286,4 @@ def test_background_image_with_alpha_and_background_color_against_composited_con # compare image rendered (compositing in `agg_renderer::setup`) # vs image composited via python bindings #raise Todo("looks like we need to investigate PNG color rounding when saving") - # eq_(get_unique_colors(im),get_unique_colors(im1)) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + #assert get_unique_colors(im) == get_unique_colors(im1) diff --git a/test/python_tests/copy_test.py b/test/python_tests/copy_test.py index b4aa45db2..d08a21d0a 100644 --- a/test/python_tests/copy_test.py +++ b/test/python_tests/copy_test.py @@ -1,21 +1,5 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os - -from nose.tools import eq_ - import mapnik -from .utilities import execution_path, run_all - - -def setup(): - # All of the paths used are relative, if we run the tests - # from another directory we need to chdir() - os.chdir(execution_path('.')) - - def test_image_16_8_simple(): im = mapnik.Image(2, 2, mapnik.ImageType.gray16) im.set_pixel(0, 0, 256) @@ -23,16 +7,16 @@ def test_image_16_8_simple(): im.set_pixel(1, 0, 5) im.set_pixel(1, 1, 2) im2 = im.copy(mapnik.ImageType.gray8) - eq_(im2.get_pixel(0, 0), 255) - eq_(im2.get_pixel(0, 1), 255) - eq_(im2.get_pixel(1, 0), 5) - eq_(im2.get_pixel(1, 1), 2) + assert im2.get_pixel(0, 0) == 255 + assert im2.get_pixel(0, 1) == 255 + assert im2.get_pixel(1, 0) == 5 + assert im2.get_pixel(1, 1) == 2 # Cast back! im = im2.copy(mapnik.ImageType.gray16) - eq_(im.get_pixel(0, 0), 255) - eq_(im.get_pixel(0, 1), 255) - eq_(im.get_pixel(1, 0), 5) - eq_(im.get_pixel(1, 1), 2) + assert im.get_pixel(0, 0) == 255 + assert im.get_pixel(0, 1) == 255 + assert im.get_pixel(1, 0) == 5 + assert im.get_pixel(1, 1) == 2 def test_image_32f_8_simple(): @@ -42,20 +26,20 @@ def test_image_32f_8_simple(): im.set_pixel(1, 0, 120.6) im.set_pixel(1, 1, 360.2) im2 = im.copy(mapnik.ImageType.gray8) - eq_(im2.get_pixel(0, 0), 120) - eq_(im2.get_pixel(0, 1), 0) - eq_(im2.get_pixel(1, 0), 120) # Notice this is truncated! - eq_(im2.get_pixel(1, 1), 255) + assert im2.get_pixel(0, 0) == 120 + assert im2.get_pixel(0, 1) == 0 + assert im2.get_pixel(1, 0) == 120 # Notice this is truncated! + assert im2.get_pixel(1, 1) == 255 def test_image_offset_and_scale(): im = mapnik.Image(2, 2, mapnik.ImageType.gray16) - eq_(im.offset, 0.0) - eq_(im.scaling, 1.0) + assert im.offset == 0.0 + assert im.scaling == 1.0 im.offset = 1.0 im.scaling = 2.0 - eq_(im.offset, 1.0) - eq_(im.scaling, 2.0) + assert im.offset == 1.0 + assert im.scaling == 2.0 def test_image_16_8_scale_and_offset(): @@ -67,17 +51,17 @@ def test_image_16_8_scale_and_offset(): offset = 255 scaling = 3 im2 = im.copy(mapnik.ImageType.gray8, offset, scaling) - eq_(im2.get_pixel(0, 0), 0) - eq_(im2.get_pixel(0, 1), 1) - eq_(im2.get_pixel(1, 0), 255) - eq_(im2.get_pixel(1, 1), 120) + assert im2.get_pixel(0, 0) == 0 + assert im2.get_pixel(0, 1) == 1 + assert im2.get_pixel(1, 0) == 255 + assert im2.get_pixel(1, 1) == 120 # pixels will be a little off due to offsets in reverting! im3 = im2.copy(mapnik.ImageType.gray16) - eq_(im3.get_pixel(0, 0), 255) # Rounding error with ints - eq_(im3.get_pixel(0, 1), 258) # same + assert im3.get_pixel(0, 0) == 255 # Rounding error with ints + assert im3.get_pixel(0, 1) == 258 # same # The other one was way out of range for our scale/offset - eq_(im3.get_pixel(1, 0), 1020) - eq_(im3.get_pixel(1, 1), 615) # same + assert im3.get_pixel(1, 0) == 1020 + assert im3.get_pixel(1, 1) == 615 # same def test_image_16_32f_scale_and_offset(): @@ -89,16 +73,12 @@ def test_image_16_32f_scale_and_offset(): offset = 255 scaling = 3.2 im2 = im.copy(mapnik.ImageType.gray32f, offset, scaling) - eq_(im2.get_pixel(0, 0), 0.3125) - eq_(im2.get_pixel(0, 1), 0.9375) - eq_(im2.get_pixel(1, 0), -79.6875) - eq_(im2.get_pixel(1, 1), 112.5) + assert im2.get_pixel(0, 0) == 0.3125 + assert im2.get_pixel(0, 1) == 0.9375 + assert im2.get_pixel(1, 0) == -79.6875 + assert im2.get_pixel(1, 1) == 112.5 im3 = im2.copy(mapnik.ImageType.gray16) - eq_(im3.get_pixel(0, 0), 256) - eq_(im3.get_pixel(0, 1), 258) - eq_(im3.get_pixel(1, 0), 0) - eq_(im3.get_pixel(1, 1), 615) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert im3.get_pixel(0, 0) == 256 + assert im3.get_pixel(0, 1) == 258 + assert im3.get_pixel(1, 0) == 0 + assert im3.get_pixel(1, 1) == 615 diff --git a/test/python_tests/csv_test.py b/test/python_tests/csv_test.py index 5f131b3d3..fdbff69ad 100644 --- a/test/python_tests/csv_test.py +++ b/test/python_tests/csv_test.py @@ -1,32 +1,15 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from __future__ import print_function - import glob import os - -from nose.tools import eq_, raises - import mapnik - +import pytest from .utilities import execution_path - -default_logging_severity = mapnik.logger.get_severity() - - +@pytest.fixture(scope="module") def setup(): - # make the tests silent since we intentially test error conditions that - # are noisy - mapnik.logger.set_severity(getattr(mapnik.severity_type, "None")) # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) - - -def teardown(): - mapnik.logger.set_severity(default_logging_severity) + yield if 'csv' in mapnik.DatasourceCache.plugin_names(): @@ -34,7 +17,7 @@ def get_csv_ds(filename): return mapnik.Datasource( type='csv', file=os.path.join('../data/csv/', filename)) - def test_broken_files(visual=False): + def test_broken_files(setup, visual=False): broken = glob.glob("../data/csv/fails/*.*") broken.extend(glob.glob("../data/csv/warns/*.*")) @@ -49,10 +32,11 @@ def test_broken_files(visual=False): except Exception: print('\x1b[1;32m✓ \x1b[0m', csv) - def test_good_files(visual=False): + def test_good_files(setup, visual=False): good_files = glob.glob("../data/csv/*.*") good_files.extend(glob.glob("../data/csv/warns/*.*")) ignorable = os.path.join('..', 'data', 'csv', 'long_lat.vrt') + print("ignorable:", ignorable) good_files.remove(ignorable) for f in good_files: if f.endswith('.index'): @@ -70,47 +54,46 @@ def test_good_files(visual=False): def test_lon_lat_detection(**kwargs): ds = get_csv_ds('lon_lat.csv') - eq_(len(ds.fields()), 2) - eq_(ds.fields(), ['lon', 'lat']) - eq_(ds.field_types(), ['int', 'int']) + assert len(ds.fields()) == 2 + assert ds.fields(), ['lon' == 'lat'] + assert ds.field_types(), ['int' == 'int'] query = mapnik.Query(ds.envelope()) for fld in ds.fields(): query.add_property_name(fld) fs = ds.features(query) desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - feat = fs.next() + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + feat = next(fs) attr = {'lon': 0, 'lat': 0} - eq_(feat.attributes, attr) + assert feat.attributes == attr def test_lng_lat_detection(**kwargs): ds = get_csv_ds('lng_lat.csv') - eq_(len(ds.fields()), 2) - eq_(ds.fields(), ['lng', 'lat']) - eq_(ds.field_types(), ['int', 'int']) + assert len(ds.fields()) == 2 + assert ds.fields(), ['lng' == 'lat'] + assert ds.field_types(), ['int' == 'int'] query = mapnik.Query(ds.envelope()) for fld in ds.fields(): query.add_property_name(fld) fs = ds.features(query) desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - feat = fs.next() + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + feat = next(fs) attr = {'lng': 0, 'lat': 0} - eq_(feat.attributes, attr) + assert feat.attributes == attr def test_type_detection(**kwargs): ds = get_csv_ds('nypd.csv') - eq_(ds.fields(), - ['Precinct', - 'Phone', - 'Address', - 'City', - 'geo_longitude', - 'geo_latitude', - 'geo_accuracy']) - eq_(ds.field_types(), ['str', 'str', - 'str', 'str', 'float', 'float', 'str']) - feat = ds.featureset().next() + assert ds.fields() == ['Precinct', + 'Phone', + 'Address', + 'City', + 'geo_longitude', + 'geo_latitude', + 'geo_accuracy'] + assert ds.field_types() == ['str', 'str', + 'str', 'str', 'float', 'float', 'str'] + feat = next(iter(ds)) attr = { 'City': u'New York, NY', 'geo_accuracy': u'house', @@ -119,34 +102,34 @@ def test_type_detection(**kwargs): 'Precinct': u'5th Precinct', 'geo_longitude': -70, 'geo_latitude': 40} - eq_(feat.attributes, attr) - eq_(len(list(ds.all_features())), 2) + assert feat.attributes == attr + assert len(list(iter(ds))) == 2 desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_skipping_blank_rows(**kwargs): ds = get_csv_ds('blank_rows.csv') - eq_(ds.fields(), ['x', 'y', 'name']) - eq_(ds.field_types(), ['int', 'int', 'str']) - eq_(len(list(ds.all_features())), 2) + assert ds.fields(), ['x', 'y' == 'name'] + assert ds.field_types(), ['int', 'int' == 'str'] + assert len(list(iter(ds))) == 2 desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_empty_rows(**kwargs): ds = get_csv_ds('empty_rows.csv') - eq_(len(ds.fields()), 10) - eq_(len(ds.field_types()), 10) - eq_(ds.fields(), ['x', 'y', 'text', 'date', 'integer', - 'boolean', 'float', 'time', 'datetime', 'empty_column']) - eq_(ds.field_types(), ['int', 'int', 'str', 'str', - 'int', 'bool', 'float', 'str', 'str', 'str']) - fs = ds.featureset() + assert len(ds.fields()) == 10 + assert len(ds.field_types()) == 10 + assert ds.fields() == ['x', 'y', 'text', 'date', 'integer', + 'boolean', 'float', 'time', 'datetime', 'empty_column'] + assert ds.field_types() == ['int', 'int', 'str', 'str', + 'int', 'bool', 'float', 'str', 'str', 'str'] + fs = iter(ds) attr = { 'x': 0, 'empty_column': u'', @@ -162,146 +145,138 @@ def test_empty_rows(**kwargs): for feat in fs: if first: first = False - eq_(feat.attributes, attr) - eq_(len(feat), 10) - eq_(feat['empty_column'], u'') + assert feat.attributes == attr + assert len(feat) == 10 + assert feat['empty_column'] == u'' desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_slashes(**kwargs): ds = get_csv_ds('has_attributes_with_slashes.csv') - eq_(len(ds.fields()), 3) - fs = list(ds.all_features()) - eq_(fs[0].attributes, {'x': 0, 'y': 0, 'name': u'a/a'}) - eq_(fs[1].attributes, {'x': 1, 'y': 4, 'name': u'b/b'}) - eq_(fs[2].attributes, {'x': 10, 'y': 2.5, 'name': u'c/c'}) + assert len(ds.fields()) == 3 + fs = list(iter(ds)) + assert fs[0].attributes == {'x': 0, 'y': 0, 'name': u'a/a'} + assert fs[1].attributes == {'x': 1, 'y': 4, 'name': u'b/b'} + assert fs[2].attributes == {'x': 10, 'y': 2.5, 'name': u'c/c'} desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_wkt_field(**kwargs): ds = get_csv_ds('wkt.csv') - eq_(len(ds.fields()), 1) - eq_(ds.fields(), ['type']) - eq_(ds.field_types(), ['str']) - fs = list(ds.all_features()) - # eq_(len(fs[0].geometries()),1) - eq_(fs[0].geometry.type(), mapnik.GeometryType.Point) - # eq_(len(fs[1].geometries()),1) - eq_(fs[1].geometry.type(), mapnik.GeometryType.LineString) - # eq_(len(fs[2].geometries()),1) - eq_(fs[2].geometry.type(), mapnik.GeometryType.Polygon) - # eq_(len(fs[3].geometries()),1) # one geometry, two parts - eq_(fs[3].geometry.type(), mapnik.GeometryType.Polygon) - # eq_(len(fs[4].geometries()),4) - eq_(fs[4].geometry.type(), mapnik.GeometryType.MultiPoint) - # eq_(len(fs[5].geometries()),2) - eq_(fs[5].geometry.type(), mapnik.GeometryType.MultiLineString) - # eq_(len(fs[6].geometries()),2) - eq_(fs[6].geometry.type(), mapnik.GeometryType.MultiPolygon) - # eq_(len(fs[7].geometries()),2) - eq_(fs[7].geometry.type(), mapnik.GeometryType.MultiPolygon) + assert len(ds.fields()) == 1 + assert ds.fields() == ['type'] + assert ds.field_types() == ['str'] + fs = list(iter(ds)) + assert fs[0].geometry.type() == mapnik.GeometryType.Point + assert fs[1].geometry.type() == mapnik.GeometryType.LineString + assert fs[2].geometry.type() == mapnik.GeometryType.Polygon + assert fs[3].geometry.type() == mapnik.GeometryType.Polygon + assert fs[4].geometry.type() == mapnik.GeometryType.MultiPoint + assert fs[5].geometry.type() == mapnik.GeometryType.MultiLineString + assert fs[6].geometry.type() == mapnik.GeometryType.MultiPolygon + assert fs[7].geometry.type() == mapnik.GeometryType.MultiPolygon desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Collection) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Collection + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_handling_of_missing_header(**kwargs): ds = get_csv_ds('missing_header.csv') - eq_(len(ds.fields()), 6) - eq_(ds.fields(), ['one', 'two', 'x', 'y', '_4', 'aftermissing']) - fs = ds.featureset() - feat = fs.next() - eq_(feat['_4'], 'missing') + assert len(ds.fields()) == 6 + assert ds.fields() == ['one', 'two', 'x', 'y', '_4', 'aftermissing'] + fs = iter(ds) + feat = next(fs) + assert feat['_4'] == 'missing' desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_handling_of_headers_that_are_numbers(**kwargs): ds = get_csv_ds('numbers_for_headers.csv') - eq_(len(ds.fields()), 5) - eq_(ds.fields(), ['x', 'y', '1990', '1991', '1992']) - fs = ds.featureset() - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['1990'], 1) - eq_(feat['1991'], 2) - eq_(feat['1992'], 3) - eq_(mapnik.Expression("[1991]=2").evaluate(feat), True) + assert len(ds.fields()) == 5 + assert ds.fields() == ['x', 'y', '1990', '1991', '1992'] + fs = iter(ds) + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['1990'] == 1 + assert feat['1991'] == 2 + assert feat['1992'] == 3 + assert mapnik.Expression("[1991]=2").evaluate(feat) def test_quoted_numbers(**kwargs): ds = get_csv_ds('points.csv') - eq_(len(ds.fields()), 6) - eq_(ds.fields(), ['lat', 'long', 'name', 'nr', 'color', 'placements']) - fs = list(ds.all_features()) - eq_(fs[0]['placements'], "N,S,E,W,SW,10,5") - eq_(fs[1]['placements'], "N,S,E,W,SW,10,5") - eq_(fs[2]['placements'], "N,S,E,W,SW,10,5") - eq_(fs[3]['placements'], "N,S,E,W,SW,10,5") - eq_(fs[4]['placements'], "N,S,E,W,SW,10,5") + assert len(ds.fields()) == 6 + assert ds.fields(), ['lat', 'long', 'name', 'nr', 'color' == 'placements'] + fs = list(iter(ds)) + assert fs[0]['placements'] == "N,S,E,W,SW,10,5" + assert fs[1]['placements'] == "N,S,E,W,SW,10,5" + assert fs[2]['placements'] == "N,S,E,W,SW,10,5" + assert fs[3]['placements'] == "N,S,E,W,SW,10,5" + assert fs[4]['placements'] == "N,S,E,W,SW,10,5" desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_reading_windows_newlines(**kwargs): ds = get_csv_ds('windows_newlines.csv') - eq_(len(ds.fields()), 3) - feats = list(ds.all_features()) - eq_(len(feats), 1) - fs = ds.featureset() - feat = fs.next() - eq_(feat['x'], 1) - eq_(feat['y'], 10) - eq_(feat['z'], 9999.9999) + assert len(ds.fields()) == 3 + feats = list(iter(ds)) + assert len(feats) == 1 + fs = iter(ds) + feat = next(fs) + assert feat['x'] == 1 + assert feat['y'] == 10 + assert feat['z'] == 9999.9999 desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_reading_mac_newlines(**kwargs): ds = get_csv_ds('mac_newlines.csv') - eq_(len(ds.fields()), 3) - feats = list(ds.all_features()) - eq_(len(feats), 1) - fs = ds.featureset() - feat = fs.next() - eq_(feat['x'], 1) - eq_(feat['y'], 10) - eq_(feat['z'], 9999.9999) + assert len(ds.fields()) == 3 + feats = list(iter(ds)) + assert len(feats) == 1 + fs = iter(ds) + feat = next(fs) + assert feat['x'] == 1 + assert feat['y'] == 10 + assert feat['z'] == 9999.9999 desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def check_newlines(filename): ds = get_csv_ds(filename) - eq_(len(ds.fields()), 3) - feats = list(ds.all_features()) - eq_(len(feats), 1) - fs = ds.featureset() - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['line'], 'many\n lines\n of text\n with unix newlines') + assert len(ds.fields()) == 3 + feats = list(iter(ds)) + assert len(feats) == 1 + fs = iter(ds) + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['line'] == 'many\n lines\n of text\n with unix newlines' desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_mixed_mac_unix_newlines(**kwargs): check_newlines('mac_newlines_with_unix_inline.csv') @@ -325,111 +300,112 @@ def test_mixed_windows_unix_newlines_escaped(**kwargs): def test_tabs(**kwargs): ds = get_csv_ds('tabs_in_csv.csv') - eq_(len(ds.fields()), 3) - eq_(ds.fields(), ['x', 'y', 'z']) - fs = ds.featureset() - feat = fs.next() - eq_(feat['x'], -122) - eq_(feat['y'], 48) - eq_(feat['z'], 0) + assert len(ds.fields()) == 3 + assert ds.fields(), ['x', 'y' == 'z'] + fs = iter(ds) + feat = next(fs) + assert feat['x'] == -122 + assert feat['y'] == 48 + assert feat['z'] == 0 desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_separator_pipes(**kwargs): ds = get_csv_ds('pipe_delimiters.csv') - eq_(len(ds.fields()), 3) - eq_(ds.fields(), ['x', 'y', 'z']) - fs = ds.featureset() - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['z'], 'hello') + assert len(ds.fields()) == 3 + assert ds.fields(), ['x', 'y' == 'z'] + fs = iter(ds) + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['z'] == 'hello' desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_separator_semicolon(**kwargs): ds = get_csv_ds('semicolon_delimiters.csv') - eq_(len(ds.fields()), 3) - eq_(ds.fields(), ['x', 'y', 'z']) - fs = ds.featureset() - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['z'], 'hello') + assert len(ds.fields()) == 3 + assert ds.fields(), ['x', 'y' == 'z'] + fs = iter(ds) + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['z'] == 'hello' desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_that_null_and_bool_keywords_are_empty_strings(**kwargs): ds = get_csv_ds('nulls_and_booleans_as_strings.csv') - eq_(len(ds.fields()), 4) - eq_(ds.fields(), ['x', 'y', 'null', 'boolean']) - eq_(ds.field_types(), ['int', 'int', 'str', 'bool']) - fs = ds.featureset() - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['null'], 'null') - eq_(feat['boolean'], True) - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['null'], '') - eq_(feat['boolean'], False) + assert len(ds.fields()) == 4 + assert ds.fields(), ['x', 'y', 'null' == 'boolean'] + assert ds.field_types(), ['int', 'int', 'str' == 'bool'] + fs = iter(ds) + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['null'] == 'null' + assert feat['boolean'] == True + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['null'] == '' + assert feat['boolean'] == False desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point - @raises(RuntimeError) def test_that_nonexistant_query_field_throws(**kwargs): - ds = get_csv_ds('lon_lat.csv') - eq_(len(ds.fields()), 2) - eq_(ds.fields(), ['lon', 'lat']) - eq_(ds.field_types(), ['int', 'int']) - query = mapnik.Query(ds.envelope()) - for fld in ds.fields(): - query.add_property_name(fld) - # also add an invalid one, triggering throw - query.add_property_name('bogus') - ds.features(query) + with pytest.raises(RuntimeError): + ds = get_csv_ds('lon_lat.csv') + assert len(ds.fields()) == 2 + assert ds.fields(), ['lon' == 'lat'] + assert ds.field_types(), ['int' == 'int'] + query = mapnik.Query(ds.envelope()) + for fld in ds.fields(): + query.add_property_name(fld) + # also add an invalid one, triggering throw + query.add_property_name('bogus') + ds.features(query) + def test_that_leading_zeros_mean_strings(**kwargs): ds = get_csv_ds('leading_zeros.csv') - eq_(len(ds.fields()), 3) - eq_(ds.fields(), ['x', 'y', 'fips']) - eq_(ds.field_types(), ['int', 'int', 'str']) - fs = ds.featureset() - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['fips'], '001') - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['fips'], '003') - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['fips'], '005') + assert len(ds.fields()) == 3 + assert ds.fields(), ['x', 'y' == 'fips'] + assert ds.field_types(), ['int', 'int' == 'str'] + fs = iter(ds) + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['fips'] == '001' + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['fips'] == '003' + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['fips'] == '005' desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point def test_advanced_geometry_detection(**kwargs): ds = get_csv_ds('point_wkt.csv') - eq_(ds.describe()['geometry_type'], mapnik.DataGeometryType.Point) + assert ds.describe()['geometry_type'] == mapnik.DataGeometryType.Point ds = get_csv_ds('poly_wkt.csv') - eq_(ds.describe()['geometry_type'], mapnik.DataGeometryType.Polygon) + assert ds.describe()['geometry_type'] == mapnik.DataGeometryType.Polygon ds = get_csv_ds('multi_poly_wkt.csv') - eq_(ds.describe()['geometry_type'], mapnik.DataGeometryType.Polygon) + assert ds.describe()['geometry_type'] == mapnik.DataGeometryType.Polygon ds = get_csv_ds('line_wkt.csv') - eq_(ds.describe()['geometry_type'], mapnik.DataGeometryType.LineString) + assert ds.describe()['geometry_type'] == mapnik.DataGeometryType.LineString def test_creation_of_csv_from_in_memory_string(**kwargs): csv_string = ''' @@ -437,10 +413,10 @@ def test_creation_of_csv_from_in_memory_string(**kwargs): "POINT (120.15 48.47)","Winthrop, WA" ''' # csv plugin will test lines <= 10 chars for being fully blank ds = mapnik.Datasource(**{"type": "csv", "inline": csv_string}) - eq_(ds.describe()['geometry_type'], mapnik.DataGeometryType.Point) - fs = ds.featureset() - feat = fs.next() - eq_(feat['Name'], u"Winthrop, WA") + assert ds.describe()['geometry_type'] == mapnik.DataGeometryType.Point + fs = iter(ds) + feat = next(fs) + assert feat['Name'], u"Winthrop == WA" def test_creation_of_csv_from_in_memory_string_with_uft8(**kwargs): csv_string = ''' @@ -448,37 +424,29 @@ def test_creation_of_csv_from_in_memory_string_with_uft8(**kwargs): "POINT (120.15 48.47)","Québec" ''' # csv plugin will test lines <= 10 chars for being fully blank ds = mapnik.Datasource(**{"type": "csv", "inline": csv_string}) - eq_(ds.describe()['geometry_type'], mapnik.DataGeometryType.Point) - fs = ds.featureset() - feat = fs.next() - eq_(feat['Name'], u"Québec") + assert ds.describe()['geometry_type'] == mapnik.DataGeometryType.Point + fs = iter(ds) + feat = next(fs) + assert feat['Name'] == u"Québec" def validate_geojson_datasource(ds): - eq_(len(ds.fields()), 1) - eq_(ds.fields(), ['type']) - eq_(ds.field_types(), ['str']) - fs = list(ds.all_features()) - # eq_(len(fs[0].geometries()),1) - eq_(fs[0].geometry.type(), mapnik.GeometryType.Point) - # eq_(len(fs[1].geometries()),1) - eq_(fs[1].geometry.type(), mapnik.GeometryType.LineString) - # eq_(len(fs[2].geometries()),1) - eq_(fs[2].geometry.type(), mapnik.GeometryType.Polygon) - # eq_(len(fs[3].geometries()),1) # one geometry, two parts - eq_(fs[3].geometry.type(), mapnik.GeometryType.Polygon) - # eq_(len(fs[4].geometries()),4) - eq_(fs[4].geometry.type(), mapnik.GeometryType.MultiPoint) - # eq_(len(fs[5].geometries()),2) - eq_(fs[5].geometry.type(), mapnik.GeometryType.MultiLineString) - # eq_(len(fs[6].geometries()),2) - eq_(fs[6].geometry.type(), mapnik.GeometryType.MultiPolygon) - # eq_(len(fs[7].geometries()),2) - eq_(fs[7].geometry.type(), mapnik.GeometryType.MultiPolygon) + assert len(ds.fields()) == 1 + assert ds.fields() == ['type'] + assert ds.field_types() == ['str'] + fs = list(iter(ds)) + assert fs[0].geometry.type() == mapnik.GeometryType.Point + assert fs[1].geometry.type() == mapnik.GeometryType.LineString + assert fs[2].geometry.type() == mapnik.GeometryType.Polygon + assert fs[3].geometry.type() == mapnik.GeometryType.Polygon + assert fs[4].geometry.type() == mapnik.GeometryType.MultiPoint + assert fs[5].geometry.type() == mapnik.GeometryType.MultiLineString + assert fs[6].geometry.type() == mapnik.GeometryType.MultiPolygon + assert fs[7].geometry.type() == mapnik.GeometryType.MultiPolygon desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Collection) - eq_(desc['name'], 'csv') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Collection + assert desc['name'] == 'csv' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_json_field1(**kwargs): ds = get_csv_ds('geojson_double_quote_escape.csv') @@ -494,129 +462,129 @@ def test_json_field3(**kwargs): def test_that_blank_undelimited_rows_are_still_parsed(**kwargs): ds = get_csv_ds('more_headers_than_column_values.csv') - eq_(len(ds.fields()), 0) - eq_(ds.fields(), []) - eq_(ds.field_types(), []) - fs = list(ds.featureset()) - eq_(len(fs), 0) + assert len(ds.fields()) == 0 + assert ds.fields() == [] + assert ds.field_types() == [] + fs = list(iter(ds)) + assert len(fs) == 0 desc = ds.describe() - eq_(desc['geometry_type'], None) + assert desc['geometry_type'] == None - @raises(RuntimeError) def test_that_fewer_headers_than_rows_throws(**kwargs): - # this has invalid header # so throw - get_csv_ds('more_column_values_than_headers.csv') + with pytest.raises(RuntimeError): + # this has invalid header # so throw + get_csv_ds('more_column_values_than_headers.csv') def test_that_feature_id_only_incremented_for_valid_rows(**kwargs): ds = mapnik.Datasource(type='csv', file=os.path.join('../data/csv/warns', 'feature_id_counting.csv')) - eq_(len(ds.fields()), 3) - eq_(ds.fields(), ['x', 'y', 'id']) - eq_(ds.field_types(), ['int', 'int', 'int']) - fs = ds.featureset() + assert len(ds.fields()) == 3 + assert ds.fields(), ['x', 'y' == 'id'] + assert ds.field_types(), ['int', 'int' == 'int'] + fs = iter(ds) # first - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['id'], 1) + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['id'] == 1 # second, should have skipped bogus one - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['id'], 2) + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['id'] == 2 desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(len(list(ds.all_features())), 2) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert len(list(iter(ds))) == 2 def test_dynamically_defining_headers1(**kwargs): ds = mapnik.Datasource(type='csv', file=os.path.join( '../data/csv/fails', 'needs_headers_two_lines.csv'), headers='x,y,name') - eq_(len(ds.fields()), 3) - eq_(ds.fields(), ['x', 'y', 'name']) - eq_(ds.field_types(), ['int', 'int', 'str']) - fs = ds.featureset() - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['name'], 'data_name') + assert len(ds.fields()) == 3 + assert ds.fields(), ['x', 'y' == 'name'] + assert ds.field_types(), ['int', 'int' == 'str'] + fs = iter(ds) + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['name'] == 'data_name' desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(len(list(ds.all_features())), 2) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert len(list(iter(ds))) == 2 def test_dynamically_defining_headers2(**kwargs): ds = mapnik.Datasource(type='csv', file=os.path.join( '../data/csv/fails', 'needs_headers_one_line.csv'), headers='x,y,name') - eq_(len(ds.fields()), 3) - eq_(ds.fields(), ['x', 'y', 'name']) - eq_(ds.field_types(), ['int', 'int', 'str']) - fs = ds.featureset() - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['name'], 'data_name') + assert len(ds.fields()) == 3 + assert ds.fields(), ['x', 'y' == 'name'] + assert ds.field_types(), ['int', 'int' == 'str'] + fs = iter(ds) + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['name'] == 'data_name' desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(len(list(ds.all_features())), 1) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert len(list(iter(ds))) == 1 def test_dynamically_defining_headers3(**kwargs): ds = mapnik.Datasource(type='csv', file=os.path.join( '../data/csv/fails', 'needs_headers_one_line_no_newline.csv'), headers='x,y,name') - eq_(len(ds.fields()), 3) - eq_(ds.fields(), ['x', 'y', 'name']) - eq_(ds.field_types(), ['int', 'int', 'str']) - fs = ds.featureset() - feat = fs.next() - eq_(feat['x'], 0) - eq_(feat['y'], 0) - eq_(feat['name'], 'data_name') + assert len(ds.fields()) == 3 + assert ds.fields(), ['x', 'y' == 'name'] + assert ds.field_types(), ['int', 'int' == 'str'] + fs = iter(ds) + feat = next(fs) + assert feat['x'] == 0 + assert feat['y'] == 0 + assert feat['name'] == 'data_name' desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(len(list(ds.all_features())), 1) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert len(list(iter(ds))) == 1 def test_that_64bit_int_fields_work(**kwargs): ds = get_csv_ds('64bit_int.csv') - eq_(len(ds.fields()), 3) - eq_(ds.fields(), ['x', 'y', 'bigint']) - eq_(ds.field_types(), ['int', 'int', 'int']) - fs = ds.featureset() - feat = fs.next() - eq_(feat['bigint'], 2147483648) - feat = fs.next() - eq_(feat['bigint'], 9223372036854775807) - eq_(feat['bigint'], 0x7FFFFFFFFFFFFFFF) + assert len(ds.fields()) == 3 + assert ds.fields(), ['x', 'y' == 'bigint'] + assert ds.field_types(), ['int', 'int' == 'int'] + fs = iter(ds) + feat = next(fs) + assert feat['bigint'] == 2147483648 + feat = next(fs) + assert feat['bigint'] == 9223372036854775807 + assert feat['bigint'] == 0x7FFFFFFFFFFFFFFF desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(len(list(ds.all_features())), 2) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert len(list(iter(ds))) == 2 def test_various_number_types(**kwargs): ds = get_csv_ds('number_types.csv') - eq_(len(ds.fields()), 3) - eq_(ds.fields(), ['x', 'y', 'floats']) - eq_(ds.field_types(), ['int', 'int', 'float']) - fs = ds.featureset() - feat = fs.next() - eq_(feat['floats'], .0) - feat = fs.next() - eq_(feat['floats'], +.0) - feat = fs.next() - eq_(feat['floats'], 1e-06) - feat = fs.next() - eq_(feat['floats'], -1e-06) - feat = fs.next() - eq_(feat['floats'], 0.000001) - feat = fs.next() - eq_(feat['floats'], 1.234e+16) - feat = fs.next() - eq_(feat['floats'], 1.234e+16) + assert len(ds.fields()) == 3 + assert ds.fields(), ['x', 'y' == 'floats'] + assert ds.field_types(), ['int', 'int' == 'float'] + fs = iter(ds) + feat = next(fs) + assert feat['floats'] == .0 + feat = next(fs) + assert feat['floats'] == +.0 + feat = next(fs) + assert feat['floats'] == 1e-06 + feat = next(fs) + assert feat['floats'] == -1e-06 + feat = next(fs) + assert feat['floats'] == 0.000001 + feat = next(fs) + assert feat['floats'] == 1.234e+16 + feat = next(fs) + assert feat['floats'] == 1.234e+16 desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(len(list(ds.all_features())), 8) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert len(list(iter(ds))) == 8 def test_manually_supplied_extent(**kwargs): csv_string = ''' @@ -625,21 +593,17 @@ def test_manually_supplied_extent(**kwargs): ds = mapnik.Datasource( **{"type": "csv", "extent": "-180,-90,180,90", "inline": csv_string}) b = ds.envelope() - eq_(b.minx, -180) - eq_(b.miny, -90) - eq_(b.maxx, 180) - eq_(b.maxy, 90) + assert b.minx == -180 + assert b.miny == -90 + assert b.maxx == 180 + assert b.maxy == 90 def test_inline_geojson(**kwargs): csv_string = "geojson\n'{\"coordinates\":[-92.22568,38.59553],\"type\":\"Point\"}'" ds = mapnik.Datasource(**{"type": "csv", "inline": csv_string}) - eq_(len(ds.fields()), 0) - eq_(ds.fields(), []) - # FIXME - re-enable after https://github.com/mapnik/mapnik/issues/2319 is fixed - #fs = ds.featureset() - #feat = fs.next() - # eq_(feat.num_geometries(),1) - -if __name__ == "__main__": - setup() - [eval(run)(visual=True) for run in dir() if 'test_' in run] + assert len(ds.fields()) == 0 + assert ds.fields() == [] + fs = iter(ds) + feat = next(fs) + assert feat.geometry.type() == mapnik.GeometryType.Point + assert feat.geometry.to_wkt() == "POINT(-92.22568 38.59553)" diff --git a/test/python_tests/datasource_test.py b/test/python_tests/datasource_test.py index 011b07cbd..13be4c6f0 100644 --- a/test/python_tests/datasource_test.py +++ b/test/python_tests/datasource_test.py @@ -1,103 +1,91 @@ -#!/usr/bin/env python import os import sys -from itertools import groupby - -from nose.tools import eq_, raises - import mapnik +import pytest +from .utilities import execution_path +from itertools import groupby -from .utilities import execution_path, run_all - -PYTHON3 = sys.version_info[0] == 3 -if PYTHON3: - xrange = range - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) - + yield def test_that_datasources_exist(): if len(mapnik.DatasourceCache.plugin_names()) == 0: print('***NOTICE*** - no datasource plugins have been loaded') # adapted from raster_symboliser_test#test_dataraster_query_point - - -@raises(RuntimeError) -def test_vrt_referring_to_missing_files(): - srs = '+init=epsg:32630' - if 'gdal' in mapnik.DatasourceCache.plugin_names(): - lyr = mapnik.Layer('dataraster') - lyr.datasource = mapnik.Gdal( - file='../data/raster/missing_raster.vrt', - band=1, - ) - lyr.srs = srs - _map = mapnik.Map(256, 256, srs) - _map.layers.append(lyr) - - # center of extent of raster - x, y = 556113.0, 4381428.0 # center of extent of raster - - _map.zoom_all() - - # Fancy stuff to supress output of error - # open 2 fds - null_fds = [os.open(os.devnull, os.O_RDWR) for x in xrange(2)] - # save the current file descriptors to a tuple - save = os.dup(1), os.dup(2) - # put /dev/null fds on 1 and 2 - os.dup2(null_fds[0], 1) - os.dup2(null_fds[1], 2) - - # *** run the function *** - try: - # Should RuntimeError here - list(_map.query_point(0, x, y)) - finally: - # restore file descriptors so I can print the results - os.dup2(save[0], 1) - os.dup2(save[1], 2) - # close the temporary fds - os.close(null_fds[0]) - os.close(null_fds[1]) +def test_vrt_referring_to_missing_files(setup): + with pytest.raises(RuntimeError): + srs = 'epsg:32630' + if 'gdal' in mapnik.DatasourceCache.plugin_names(): + lyr = mapnik.Layer('dataraster') + lyr.datasource = mapnik.Gdal( + file='../data/raster/missing_raster.vrt', + band=1, + ) + lyr.srs = srs + _map = mapnik.Map(256, 256, srs) + _map.layers.append(lyr) + + # center of extent of raster + x, y = 556113.0, 4381428.0 # center of extent of raster + _map.zoom_all() + + # Fancy stuff to supress output of error + # open 2 fds + null_fds = [os.open(os.devnull, os.O_RDWR) for x in range(2)] + # save the current file descriptors to a tuple + save = os.dup(1), os.dup(2) + # put /dev/null fds on 1 and 2 + os.dup2(null_fds[0], 1) + os.dup2(null_fds[1], 2) + + # *** run the function *** + try: + # Should RuntimeError here + list(_map.query_point(0, x, y)) + finally: + # restore file descriptors so I can print the results + os.dup2(save[0], 1) + os.dup2(save[1], 2) + # close the temporary fds + os.close(null_fds[0]) + os.close(null_fds[1]) def test_field_listing(): if 'shape' in mapnik.DatasourceCache.plugin_names(): ds = mapnik.Shapefile(file='../data/shp/poly.shp') fields = ds.fields() - eq_(fields, ['AREA', 'EAS_ID', 'PRFEDEA']) + assert fields, ['AREA', 'EAS_ID' == 'PRFEDEA'] desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Polygon) - eq_(desc['name'], 'shape') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') + assert desc['geometry_type'] == mapnik.DataGeometryType.Polygon + assert desc['name'] == 'shape' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' def test_total_feature_count_shp(): if 'shape' in mapnik.DatasourceCache.plugin_names(): ds = mapnik.Shapefile(file='../data/shp/poly.shp') - features = ds.all_features() + features = iter(ds) num_feats = len(list(features)) - eq_(num_feats, 10) - + assert num_feats == 10 def test_total_feature_count_json(): if 'ogr' in mapnik.DatasourceCache.plugin_names(): ds = mapnik.Ogr(file='../data/json/points.geojson', layer_by_index=0) desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(desc['name'], 'ogr') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') - features = ds.all_features() + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert desc['name'] == 'ogr' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' + features = iter(ds) num_feats = len(list(features)) - eq_(num_feats, 5) + assert num_feats == 5 def test_sqlite_reading(): @@ -106,13 +94,13 @@ def test_sqlite_reading(): file='../data/sqlite/world.sqlite', table_by_index=0) desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Polygon) - eq_(desc['name'], 'sqlite') - eq_(desc['type'], mapnik.DataType.Vector) - eq_(desc['encoding'], 'utf-8') - features = ds.all_features() + assert desc['geometry_type'] == mapnik.DataGeometryType.Polygon + assert desc['name'] == 'sqlite' + assert desc['type'] == mapnik.DataType.Vector + assert desc['encoding'] == 'utf-8' + features = iter(ds) num_feats = len(list(features)) - eq_(num_feats, 245) + assert num_feats == 245 def test_reading_json_from_string(): @@ -120,41 +108,40 @@ def test_reading_json_from_string(): json = f.read() if 'ogr' in mapnik.DatasourceCache.plugin_names(): ds = mapnik.Ogr(file=json, layer_by_index=0) - features = ds.all_features() + features = iter(ds) num_feats = len(list(features)) - eq_(num_feats, 5) + assert num_feats == 5 def test_feature_envelope(): if 'shape' in mapnik.DatasourceCache.plugin_names(): ds = mapnik.Shapefile(file='../data/shp/poly.shp') - features = ds.all_features() - for feat in features: + for feat in ds: env = feat.envelope() contains = ds.envelope().contains(env) - eq_(contains, True) + assert contains == True intersects = ds.envelope().contains(env) - eq_(intersects, True) + assert intersects == True def test_feature_attributes(): if 'shape' in mapnik.DatasourceCache.plugin_names(): ds = mapnik.Shapefile(file='../data/shp/poly.shp') - features = list(ds.all_features()) + features = list(iter(ds)) feat = features[0] - attrs = {'PRFEDEA': u'35043411', 'EAS_ID': 168, 'AREA': 215229.266} - eq_(feat.attributes, attrs) - eq_(ds.fields(), ['AREA', 'EAS_ID', 'PRFEDEA']) - eq_(ds.field_types(), ['float', 'int', 'str']) + attrs = {'AREA': 215229.266, 'EAS_ID': 168, 'PRFEDEA': '35043411'} + assert feat.attributes == attrs + assert ds.fields(), ['AREA', 'EAS_ID', 'PRFEDEA'] + assert ds.field_types(), ['float', 'int', 'str'] def test_ogr_layer_by_sql(): if 'ogr' in mapnik.DatasourceCache.plugin_names(): ds = mapnik.Ogr(file='../data/shp/poly.shp', layer_by_sql='SELECT * FROM poly WHERE EAS_ID = 168') - features = ds.all_features() + features = iter(ds) num_feats = len(list(features)) - eq_(num_feats, 1) + assert num_feats == 1 def test_hit_grid(): @@ -170,8 +157,8 @@ def rle_encode(l): m.zoom_all() join_field = 'NAME' fg = [] # feature grid - for y in xrange(0, 256, 4): - for x in xrange(0, 256, 4): + for y in range(0, 256, 4): + for x in range(0, 256, 4): featureset = m.query_map_point(0, x, y) added = False for feature in featureset: @@ -180,14 +167,9 @@ def rle_encode(l): if not added: fg.append('') hit_list = '|'.join(rle_encode(fg)) - eq_(hit_list[:16], '730:|2:Greenland') - eq_(hit_list[-12:], '1:Chile|812:') + assert hit_list[:16] == '730:|2:Greenland' + assert hit_list[-12:] == '1:Chile|812:' except RuntimeError as e: # only test datasources that we have installed if not 'Could not create datasource' in str(e): raise RuntimeError(str(e)) - - -if __name__ == '__main__': - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) diff --git a/test/python_tests/datasource_xml_template_test.py b/test/python_tests/datasource_xml_template_test.py index b561d93e6..6c5de3587 100644 --- a/test/python_tests/datasource_xml_template_test.py +++ b/test/python_tests/datasource_xml_template_test.py @@ -1,27 +1,28 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os - import mapnik +import pytest +from .utilities import execution_path -from .utilities import execution_path, run_all - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield - -def test_datasource_template_is_working(): +def test_datasource_template_is_working(setup): m = mapnik.Map(256, 256) - try: - mapnik.load_map(m, '../data/good_maps/datasource.xml') - except RuntimeError as e: - if "Required parameter 'type'" in str(e): - raise RuntimeError(e) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + mapnik.load_map(m, '../data/good_maps/datasource.xml') + for layer in m.layers: + layer_bbox = layer.envelope() + bbox = None + first = True + for feature in layer.datasource: + assert feature.envelope() == feature.geometry.envelope() + assert layer_bbox.contains(feature.envelope()) + if first: + first = False + bbox = feature.envelope() + else: + bbox += feature.envelope() + assert layer_bbox == bbox diff --git a/test/python_tests/extra_map_props_test.py b/test/python_tests/extra_map_props_test.py index ac9e7482d..213b22b36 100644 --- a/test/python_tests/extra_map_props_test.py +++ b/test/python_tests/extra_map_props_test.py @@ -1,60 +1,51 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os - -from nose.tools import eq_ - import mapnik +import pytest +from .utilities import execution_path -from .utilities import execution_path, run_all - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield -def test_arbitrary_parameters_attached_to_map(): +def test_arbitrary_parameters_attached_to_map(setup): m = mapnik.Map(256, 256) mapnik.load_map(m, '../data/good_maps/extra_arbitary_map_parameters.xml') - eq_(len(m.parameters), 5) - eq_(m.parameters['key'], 'value2') - eq_(m.parameters['key3'], 'value3') - eq_(m.parameters['unicode'], u'iván') - eq_(m.parameters['integer'], 10) - eq_(m.parameters['decimal'], .999) + assert len(m.parameters) == 5 + assert m.parameters['key'] == 'value2' + assert m.parameters['key3'] == 'value3' + assert m.parameters['unicode'] == u'iván' + assert m.parameters['integer'] == 10 + assert m.parameters['decimal'] == .999 m2 = mapnik.Map(256, 256) - for k, v in m.parameters: - m2.parameters.append(mapnik.Parameter(k, v)) - eq_(len(m2.parameters), 5) - eq_(m2.parameters['key'], 'value2') - eq_(m2.parameters['key3'], 'value3') - eq_(m2.parameters['unicode'], u'iván') - eq_(m2.parameters['integer'], 10) - eq_(m2.parameters['decimal'], .999) + for k, v in m.parameters.items(): + m2.parameters[k] = v + assert len(m2.parameters) == 5 + assert m2.parameters['key'] == 'value2' + assert m2.parameters['key3'] == 'value3' + assert m2.parameters['unicode'] == u'iván' + assert m2.parameters['integer'] == 10 + assert m2.parameters['decimal'] == .999 map_string = mapnik.save_map_to_string(m) m3 = mapnik.Map(256, 256) mapnik.load_map_from_string(m3, map_string) - eq_(len(m3.parameters), 5) - eq_(m3.parameters['key'], 'value2') - eq_(m3.parameters['key3'], 'value3') - eq_(m3.parameters['unicode'], u'iván') - eq_(m3.parameters['integer'], 10) - eq_(m3.parameters['decimal'], .999) + assert len(m3.parameters) == 5 + assert m3.parameters['key'] == 'value2' + assert m3.parameters['key3'] == 'value3' + assert m3.parameters['unicode'] == u'iván' + assert m3.parameters['integer'] == 10 + assert m3.parameters['decimal'] == .999 def test_serializing_arbitrary_parameters(): m = mapnik.Map(256, 256) - m.parameters.append(mapnik.Parameter('width', m.width)) - m.parameters.append(mapnik.Parameter('height', m.height)) + m.parameters['width'] = m.width + m.parameters['height'] = m.height m2 = mapnik.Map(1, 1) mapnik.load_map_from_string(m2, mapnik.save_map_to_string(m)) - eq_(m2.parameters['width'], m.width) - eq_(m2.parameters['height'], m.height) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert m2.parameters['width'] == m.width + assert m2.parameters['height'] == m.height diff --git a/test/python_tests/feature_id_test.py b/test/python_tests/feature_id_test.py index e8a5056a5..20e8ad9eb 100644 --- a/test/python_tests/feature_id_test.py +++ b/test/python_tests/feature_id_test.py @@ -1,24 +1,19 @@ -#!/usr/bin/env python - -import os - -from nose.tools import eq_ - import mapnik - -from .utilities import execution_path, run_all - +import os +import pytest try: import itertools.izip as zip except ImportError: pass +from .utilities import execution_path +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) - + yield def compare_shape_between_mapnik_and_ogr(shapefile, query=None): plugins = mapnik.DatasourceCache.plugin_names() @@ -29,18 +24,16 @@ def compare_shape_between_mapnik_and_ogr(shapefile, query=None): fs1 = ds1.features(query) fs2 = ds2.features(query) else: - fs1 = ds1.featureset() - fs2 = ds2.featureset() + fs1 = iter(ds1) + fs2 = iter(ds2) count = 0 for feat1, feat2 in zip(fs1, fs2): count += 1 - eq_(feat1.id(), feat2.id(), - '%s : ogr feature id %s "%s" does not equal shapefile feature id %s "%s"' - % (count, feat1.id(), str(feat1.attributes), feat2.id(), str(feat2.attributes))) + assert feat1.id() == feat2.id(), '%s : ogr feature id %s "%s" does not equal shapefile feature id %s "%s"' % (count, feat1.id(), str(feat1.attributes), feat2.id(), str(feat2.attributes)) return True -def test_shapefile_line_featureset_id(): +def test_shapefile_line_featureset_id(setup): compare_shape_between_mapnik_and_ogr('../data/shp/polylines.shp') @@ -60,21 +53,15 @@ def test_shapefile_polygon_feature_query_id(): def test_feature_hit_count(): - pass - #raise Todo("need to optimize multigeom bbox handling in shapeindex: https://github.com/mapnik/mapnik/issues/783") # results in different results between shp and ogr! #bbox = (-14284551.8434, 2074195.1992, -7474929.8687, 8140237.7628) - #bbox = (1113194.91,4512803.085,2226389.82,6739192.905) - #query = mapnik.Query(mapnik.Box2d(*bbox)) - # if 'ogr' in mapnik.DatasourceCache.plugin_names(): - # ds1 = mapnik.Ogr(file='../data/shp/world_merc.shp',layer_by_index=0) - # for fld in ds1.fields(): - # query.add_property_name(fld) - # ds2 = mapnik.Shapefile(file='../data/shp/world_merc.shp') - # count1 = len(ds1.features(query).features) - # count2 = len(ds2.features(query).features) - # eq_(count1,count2,"Feature count differs between OGR driver (%s features) and Shapefile Driver (%s features) when querying the same bbox" % (count1,count2)) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + bbox = (1113194.91,4512803.085,2226389.82,6739192.905) + query = mapnik.Query(mapnik.Box2d(*bbox)) + if 'ogr' in mapnik.DatasourceCache.plugin_names(): + ds1 = mapnik.Ogr(file='../data/shp/world_merc.shp',layer_by_index=0) + for fld in ds1.fields(): + query.add_property_name(fld) + ds2 = mapnik.Shapefile(file='../data/shp/world_merc.shp') + count1 = len(list(ds1.features(query))) + count2 = len(list(ds2.features(query))) + assert count1 < count2 # expected 17 and 20 diff --git a/test/python_tests/feature_test.py b/test/python_tests/feature_test.py index 7a544af1a..d4f8afc61 100644 --- a/test/python_tests/feature_test.py +++ b/test/python_tests/feature_test.py @@ -1,26 +1,17 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - from binascii import unhexlify - -from nose.tools import eq_, raises - import mapnik - -from .utilities import run_all - +import pytest def test_default_constructor(): f = mapnik.Feature(mapnik.Context(), 1) - eq_(f is not None, True) + assert f is not None def test_feature_geo_interface(): ctx = mapnik.Context() feat = mapnik.Feature(ctx, 1) feat.geometry = mapnik.Geometry.from_wkt('Point (0 0)') - eq_(feat.__geo_interface__['geometry'], { - u'type': u'Point', u'coordinates': [0, 0]}) + assert feat.__geo_interface__['geometry'] == {u'type': u'Point', u'coordinates': [0, 0]} def test_python_extended_constructor(): @@ -31,15 +22,15 @@ def test_python_extended_constructor(): wkt = 'POLYGON ((35 10, 10 20, 15 40, 45 45, 35 10),(20 30, 35 35, 30 20, 20 30))' f.geometry = mapnik.Geometry.from_wkt(wkt) f['foo'] = 'bar' - eq_(f['foo'], 'bar') - eq_(f.envelope(), mapnik.Box2d(10.0, 10.0, 45.0, 45.0)) + assert f['foo'] == 'bar' + assert f.envelope(), mapnik.Box2d(10.0, 10.0, 45.0 == 45.0) # reset f['foo'] = u"avión" - eq_(f['foo'], u"avión") + assert f['foo'] == u"avión" f['foo'] = 1.4 - eq_(f['foo'], 1.4) + assert f['foo'] == 1.4 f['foo'] = True - eq_(f['foo'], True) + assert f['foo'] == True def test_add_geom_wkb(): @@ -49,13 +40,13 @@ def test_add_geom_wkb(): if hasattr(geometry, 'is_valid'): # Those are only available when python-mapnik has been built with # boost >= 1.56. - eq_(geometry.is_valid(), True) - eq_(geometry.is_simple(), True) - eq_(geometry.envelope(), mapnik.Box2d(10.0, 10.0, 40.0, 40.0)) + assert geometry.is_valid() == True + assert geometry.is_simple() == True + assert geometry.envelope(), mapnik.Box2d(10.0, 10.0, 40.0 == 40.0) geometry.correct() if hasattr(geometry, 'is_valid'): # valid after calling correct - eq_(geometry.is_valid(), True) + assert geometry.is_valid() == True def test_feature_expression_evaluation(): @@ -63,13 +54,13 @@ def test_feature_expression_evaluation(): context.push('name') f = mapnik.Feature(context, 1) f['name'] = 'a' - eq_(f['name'], u'a') + assert f['name'] == u'a' expr = mapnik.Expression("[name]='a'") evaluated = expr.evaluate(f) - eq_(evaluated, True) + assert evaluated == True num_attributes = len(f) - eq_(num_attributes, 1) - eq_(f.id(), 1) + assert num_attributes == 1 + assert f.id() == 1 # https://github.com/mapnik/mapnik/issues/933 @@ -79,16 +70,16 @@ def test_feature_expression_evaluation_missing_attr(): context.push('name') f = mapnik.Feature(context, 1) f['name'] = u'a' - eq_(f['name'], u'a') + assert f['name'] == u'a' expr = mapnik.Expression("[fielddoesnotexist]='a'") - eq_('fielddoesnotexist' in f, False) + assert not 'fielddoesnotexist' in f try: expr.evaluate(f) except Exception as e: - eq_("Key does not exist" in str(e), True) + assert "Key does not exist" in str(e) == True num_attributes = len(f) - eq_(num_attributes, 1) - eq_(f.id(), 1) + assert num_attributes == 1 + assert f.id() == 1 # https://github.com/mapnik/mapnik/issues/934 @@ -98,31 +89,27 @@ def test_feature_expression_evaluation_attr_with_spaces(): context.push('name with space') f = mapnik.Feature(context, 1) f['name with space'] = u'a' - eq_(f['name with space'], u'a') + assert f['name with space'] == u'a' expr = mapnik.Expression("[name with space]='a'") - eq_(str(expr), "([name with space]='a')") - eq_(expr.evaluate(f), True) + assert str(expr) == "([name with space]='a')" + assert expr.evaluate(f) == True # https://github.com/mapnik/mapnik/issues/2390 - -@raises(RuntimeError) def test_feature_from_geojson(): - ctx = mapnik.Context() - inline_string = """ - { - "geometry" : { - "coordinates" : [ 0,0 ] - "type" : "Point" - }, - "type" : "Feature", - "properties" : { - "this":"that" - "known":"nope because missing comma" - } - } - """ - mapnik.Feature.from_geojson(inline_string, ctx) - -if __name__ == "__main__": - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + with pytest.raises(RuntimeError): + ctx = mapnik.Context() + inline_string = """ + { + "geometry" : { + "coordinates" : [ 0,0 ] + "type" : "Point" + }, + "type" : "Feature", + "properties" : { + "this":"that" + "known":"nope because missing comma" + } + } + """ + mapnik.Feature.from_geojson(inline_string, ctx) diff --git a/test/python_tests/filter_test.py b/test/python_tests/filter_test.py index 39deb5b2f..641d6950f 100644 --- a/test/python_tests/filter_test.py +++ b/test/python_tests/filter_test.py @@ -1,18 +1,5 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import sys - -from nose.tools import eq_, raises - import mapnik - -from .utilities import run_all - -PYTHON3 = sys.version_info[0] == 3 -if PYTHON3: - long = int - unicode = str - +import pytest if hasattr(mapnik, 'Expression'): mapnik.Filter = mapnik.Expression @@ -96,11 +83,11 @@ def test_filter_init(): first = filters[0] for f in filters: - eq_(str(first), str(f)) + assert str(first) == str(f) s = m.find_style('s2') - eq_(s.filter_mode, mapnik.filter_mode.FIRST) + assert s.filter_mode == mapnik.filter_mode.FIRST def test_geometry_type_eval(): @@ -110,45 +97,42 @@ def test_geometry_type_eval(): f = mapnik.Feature(context2, 0) f["mapnik::geometry_type"] = 'sneaky' expr = mapnik.Expression("[mapnik::geometry_type]") - eq_(expr.evaluate(f), 0) + assert expr.evaluate(f) == 0 expr = mapnik.Expression("[mapnik::geometry_type]") context = mapnik.Context() # no geometry f = mapnik.Feature(context, 0) - eq_(expr.evaluate(f), 0) - eq_(mapnik.Expression("[mapnik::geometry_type]=0").evaluate(f), True) + assert expr.evaluate(f) == 0 + assert mapnik.Expression("[mapnik::geometry_type]=0").evaluate(f) # POINT = 1 f = mapnik.Feature(context, 0) f.geometry = mapnik.Geometry.from_wkt('POINT(10 40)') - eq_(expr.evaluate(f), 1) - eq_(mapnik.Expression("[mapnik::geometry_type]=point").evaluate(f), True) + assert expr.evaluate(f) == 1 + assert mapnik.Expression("[mapnik::geometry_type]=point").evaluate(f) # LINESTRING = 2 f = mapnik.Feature(context, 0) f.geometry = mapnik.Geometry.from_wkt('LINESTRING (30 10, 10 30, 40 40)') - eq_(expr.evaluate(f), 2) - eq_(mapnik.Expression( - "[mapnik::geometry_type] = linestring").evaluate(f), True) + assert expr.evaluate(f) == 2 + assert mapnik.Expression("[mapnik::geometry_type] = linestring").evaluate(f) # POLYGON = 3 f = mapnik.Feature(context, 0) f.geometry = mapnik.Geometry.from_wkt( 'POLYGON ((30 10, 10 20, 20 40, 40 40, 30 10))') - eq_(expr.evaluate(f), 3) - eq_(mapnik.Expression( - "[mapnik::geometry_type] = polygon").evaluate(f), True) + assert expr.evaluate(f) == 3 + assert mapnik.Expression("[mapnik::geometry_type] = polygon").evaluate(f) # COLLECTION = 4 f = mapnik.Feature(context, 0) geom = mapnik.Geometry.from_wkt( 'GEOMETRYCOLLECTION(POLYGON((1 1,2 1,2 2,1 2,1 1)),POINT(2 3),LINESTRING(2 3,3 4))') f.geometry = geom - eq_(expr.evaluate(f), 4) - eq_(mapnik.Expression( - "[mapnik::geometry_type] = collection").evaluate(f), True) + assert expr.evaluate(f) == 4 + assert mapnik.Expression("[mapnik::geometry_type] = collection").evaluate(f) def test_regex_match(): @@ -157,7 +141,7 @@ def test_regex_match(): f = mapnik.Feature(context, 0) f["name"] = 'test' expr = mapnik.Expression("[name].match('test')") - eq_(expr.evaluate(f), True) # 1 == True + assert expr.evaluate(f) # 1 == True def test_unicode_regex_match(): @@ -166,7 +150,7 @@ def test_unicode_regex_match(): f = mapnik.Feature(context, 0) f["name"] = 'Québec' expr = mapnik.Expression("[name].match('Québec')") - eq_(expr.evaluate(f), True) # 1 == True + assert expr.evaluate(f) # 1 == True def test_regex_replace(): @@ -175,12 +159,12 @@ def test_regex_replace(): f = mapnik.Feature(context, 0) f["name"] = 'test' expr = mapnik.Expression("[name].replace('(\\B)|( )','$1 ')") - eq_(expr.evaluate(f), 't e s t') + assert expr.evaluate(f) == 't e s t' def test_unicode_regex_replace_to_str(): expr = mapnik.Expression("[name].replace('(\\B)|( )','$1 ')") - eq_(str(expr), "[name].replace('(\\B)|( )','$1 ')") + assert str(expr), "[name].replace('(\\B)|( )' == '$1 ')" def test_unicode_regex_replace(): @@ -190,7 +174,7 @@ def test_unicode_regex_replace(): f["name"] = 'Québec' expr = mapnik.Expression("[name].replace('(\\B)|( )','$1 ')") # will fail if -DBOOST_REGEX_HAS_ICU is not defined - eq_(expr.evaluate(f), u'Q u é b e c') + assert expr.evaluate(f) == u'Q u é b e c' def test_float_precision(): @@ -199,16 +183,16 @@ def test_float_precision(): f = mapnik.Feature(context, 0) f["num1"] = 1.0000 f["num2"] = 1.0001 - eq_(f["num1"], 1.0000) - eq_(f["num2"], 1.0001) + assert f["num1"] == 1.0000 + assert f["num2"] == 1.0001 expr = mapnik.Expression("[num1] = 1.0000") - eq_(expr.evaluate(f), True) + assert expr.evaluate(f) expr = mapnik.Expression("[num1].match('1')") - eq_(expr.evaluate(f), True) + assert expr.evaluate(f) expr = mapnik.Expression("[num2] = 1.0001") - eq_(expr.evaluate(f), True) + assert expr.evaluate(f) expr = mapnik.Expression("[num2].match('1.0001')") - eq_(expr.evaluate(f), True) + assert expr.evaluate(f) def test_string_matching_on_precision(): @@ -216,9 +200,9 @@ def test_string_matching_on_precision(): context.push('num') f = mapnik.Feature(context, 0) f["num"] = "1.0000" - eq_(f["num"], "1.0000") + assert f["num"] == "1.0000" expr = mapnik.Expression("[num].match('.*(^0|00)$')") - eq_(expr.evaluate(f), True) + assert expr.evaluate(f) def test_creation_of_null_value(): @@ -226,12 +210,12 @@ def test_creation_of_null_value(): context.push('nv') f = mapnik.Feature(context, 0) f["nv"] = None - eq_(f["nv"], None) - eq_(f["nv"] is None, True) + assert f["nv"] == None + assert f["nv"] is None # test boolean f["nv"] = 0 - eq_(f["nv"], 0) - eq_(f["nv"] is not None, True) + assert f["nv"] == 0 + assert f["nv"] is not None def test_creation_of_bool(): @@ -239,39 +223,39 @@ def test_creation_of_bool(): context.push('bool') f = mapnik.Feature(context, 0) f["bool"] = True - eq_(f["bool"], True) + assert f["bool"] # TODO - will become int of 1 do to built in boost python conversion # https://github.com/mapnik/mapnik/issues/1873 - eq_(isinstance(f["bool"], bool) or isinstance(f["bool"], long), True) + assert isinstance(f["bool"], bool) or isinstance(f["bool"], int) f["bool"] = False - eq_(f["bool"], False) - eq_(isinstance(f["bool"], bool) or isinstance(f["bool"], long), True) + assert f["bool"] == False + assert isinstance(f["bool"], bool) or isinstance(f["bool"], int) # test NoneType f["bool"] = None - eq_(f["bool"], None) - eq_(isinstance(f["bool"], bool) or isinstance(f["bool"], long), False) + assert f["bool"] == None + assert not isinstance(f["bool"], bool) or isinstance(f["bool"], int) # test integer f["bool"] = 0 - eq_(f["bool"], 0) + assert f["bool"] == 0 # https://github.com/mapnik/mapnik/issues/1873 # ugh, boost_python's built into converter does not work right - # eq_(isinstance(f["bool"],bool),False) + # assert isinstance(f["bool"],bool) == False null_equality = [ - ['hello', False, unicode], - [u'', False, unicode], - [0, False, long], - [123, False, long], + ['hello', False, str], + [u'', False, str], + [0, False, int], + [123, False, int], [0.0, False, float], [123.123, False, float], [.1, False, float], # TODO - should become bool: https://github.com/mapnik/mapnik/issues/1873 - [False, False, long], + [False, False, int], # TODO - should become bool: https://github.com/mapnik/mapnik/issues/1873 - [True, False, long], + [True, False, int], [None, True, None], - [2147483648, False, long], - [922337203685477580, False, long] + [2147483648, False, int], + [922337203685477580, False, int] ] @@ -280,16 +264,15 @@ def test_expressions_with_null_equality(): context = mapnik.Context() f = mapnik.Feature(context, 0) f["prop"] = eq[0] - eq_(f["prop"], eq[0]) + assert f["prop"] == eq[0] if eq[0] is None: - eq_(f["prop"] is None, True) + assert f["prop"] is None else: - eq_(isinstance(f['prop'], eq[2]), True, - '%s is not an instance of %s' % (f['prop'], eq[2])) + assert isinstance(f['prop'], eq[2]), '%s is not an instance of %s' % (f['prop'], eq[2]) expr = mapnik.Expression("[prop] = null") - eq_(expr.evaluate(f), eq[1]) + assert expr.evaluate(f) == eq[1] expr = mapnik.Expression("[prop] is null") - eq_(expr.evaluate(f), eq[1]) + assert expr.evaluate(f) == eq[1] def test_expressions_with_null_equality2(): @@ -297,35 +280,34 @@ def test_expressions_with_null_equality2(): context = mapnik.Context() f = mapnik.Feature(context, 0) f["prop"] = eq[0] - eq_(f["prop"], eq[0]) + assert f["prop"] == eq[0] if eq[0] is None: - eq_(f["prop"] is None, True) + assert f["prop"] is None else: - eq_(isinstance(f['prop'], eq[2]), True, - '%s is not an instance of %s' % (f['prop'], eq[2])) + assert isinstance(f['prop'], eq[2]), '%s is not an instance of %s' % (f['prop'], eq[2]) # TODO - support `is not` syntax: # https://github.com/mapnik/mapnik/issues/796 expr = mapnik.Expression("not [prop] is null") - eq_(expr.evaluate(f), not eq[1]) + assert not expr.evaluate(f) == eq[1] # https://github.com/mapnik/mapnik/issues/1642 expr = mapnik.Expression("[prop] != null") - eq_(expr.evaluate(f), not eq[1]) + assert not expr.evaluate(f) == eq[1] truthyness = [ - [u'hello', True, unicode], - [u'', False, unicode], - [0, False, long], - [123, True, long], + [u'hello', True, str], + [u'', False, str], + [0, False, int], + [123, True, int], [0.0, False, float], [123.123, True, float], [.1, True, float], # TODO - should become bool: https://github.com/mapnik/mapnik/issues/1873 - [False, False, long], + [False, False, int], # TODO - should become bool: https://github.com/mapnik/mapnik/issues/1873 - [True, True, long], + [True, True, int], [None, False, None], - [2147483648, True, long], - [922337203685477580, True, long] + [2147483648, True, int], + [922337203685477580, True, int] ] @@ -334,26 +316,25 @@ def test_expressions_for_thruthyness(): for eq in truthyness: f = mapnik.Feature(context, 0) f["prop"] = eq[0] - eq_(f["prop"], eq[0]) + assert f["prop"] == eq[0] if eq[0] is None: - eq_(f["prop"] is None, True) + assert f["prop"] is None else: - eq_(isinstance(f['prop'], eq[2]), True, - '%s is not an instance of %s' % (f['prop'], eq[2])) + assert isinstance(f['prop'], eq[2]), '%s is not an instance of %s' % (f['prop'], eq[2]) expr = mapnik.Expression("[prop]") - eq_(expr.to_bool(f), eq[1]) + assert expr.to_bool(f) == eq[1] expr = mapnik.Expression("not [prop]") - eq_(expr.to_bool(f), not eq[1]) + assert not expr.to_bool(f) == eq[1] expr = mapnik.Expression("! [prop]") - eq_(expr.to_bool(f), not eq[1]) + assert not expr.to_bool(f) == eq[1] # also test if feature does not have property at all f2 = mapnik.Feature(context, 1) # no property existing will return value_null since # https://github.com/mapnik/mapnik/commit/562fada9d0f680f59b2d9f396c95320a0d753479#include/mapnik/feature.hpp - eq_(f2["prop"] is None, True) + assert f2["prop"] is None expr = mapnik.Expression("[prop]") - eq_(expr.evaluate(f2), None) - eq_(expr.to_bool(f2), False) + assert expr.evaluate(f2) == None + assert expr.to_bool(f2) == False # https://github.com/mapnik/mapnik/issues/1859 @@ -364,93 +345,86 @@ def test_if_null_and_empty_string_are_equal(): f["empty"] = u"" f["null"] = None # ensure base assumptions are good - eq_(mapnik.Expression("[empty] = ''").to_bool(f), True) - eq_(mapnik.Expression("[null] = null").to_bool(f), True) - eq_(mapnik.Expression("[empty] != ''").to_bool(f), False) - eq_(mapnik.Expression("[null] != null").to_bool(f), False) + assert mapnik.Expression("[empty] = ''").to_bool(f) + assert mapnik.Expression("[null] = null").to_bool(f) + assert not mapnik.Expression("[empty] != ''").to_bool(f) + assert not mapnik.Expression("[null] != null").to_bool(f) # now test expected behavior - eq_(mapnik.Expression("[null] = ''").to_bool(f), False) - eq_(mapnik.Expression("[empty] = null").to_bool(f), False) - eq_(mapnik.Expression("[empty] != null").to_bool(f), True) + assert not mapnik.Expression("[null] = ''").to_bool(f) + assert not mapnik.Expression("[empty] = null").to_bool(f) + assert mapnik.Expression("[empty] != null").to_bool(f) # this one is the back compatibility shim - eq_(mapnik.Expression("[null] != ''").to_bool(f), False) + assert not mapnik.Expression("[null] != ''").to_bool(f) def test_filtering_nulls_and_empty_strings(): context = mapnik.Context() f = mapnik.Feature(context, 0) f["prop"] = u"hello" - eq_(f["prop"], u"hello") - eq_(mapnik.Expression("[prop]").to_bool(f), True) - eq_(mapnik.Expression("! [prop]").to_bool(f), False) - eq_(mapnik.Expression("[prop] != null").to_bool(f), True) - eq_(mapnik.Expression("[prop] != ''").to_bool(f), True) - eq_(mapnik.Expression("[prop] != null and [prop] != ''").to_bool(f), True) - eq_(mapnik.Expression("[prop] != null or [prop] != ''").to_bool(f), True) + assert f["prop"] == u"hello" + assert mapnik.Expression("[prop]").to_bool(f) + assert not mapnik.Expression("! [prop]").to_bool(f) + assert mapnik.Expression("[prop] != null").to_bool(f) + assert mapnik.Expression("[prop] != ''").to_bool(f) + assert mapnik.Expression("[prop] != null and [prop] != ''").to_bool(f) + assert mapnik.Expression("[prop] != null or [prop] != ''").to_bool(f) f["prop2"] = u"" - eq_(f["prop2"], u"") - eq_(mapnik.Expression("[prop2]").to_bool(f), False) - eq_(mapnik.Expression("! [prop2]").to_bool(f), True) - eq_(mapnik.Expression("[prop2] != null").to_bool(f), True) - eq_(mapnik.Expression("[prop2] != ''").to_bool(f), False) - eq_(mapnik.Expression("[prop2] = ''").to_bool(f), True) - eq_(mapnik.Expression("[prop2] != null or [prop2] != ''").to_bool(f), True) - eq_(mapnik.Expression( - "[prop2] != null and [prop2] != ''").to_bool(f), False) + assert f["prop2"] == u"" + assert not mapnik.Expression("[prop2]").to_bool(f) + assert mapnik.Expression("! [prop2]").to_bool(f) + assert mapnik.Expression("[prop2] != null").to_bool(f) + assert not mapnik.Expression("[prop2] != ''").to_bool(f) + assert mapnik.Expression("[prop2] = ''").to_bool(f) + assert mapnik.Expression("[prop2] != null or [prop2] != ''").to_bool(f) + assert not mapnik.Expression("[prop2] != null and [prop2] != ''").to_bool(f) f["prop3"] = None - eq_(f["prop3"], None) - eq_(mapnik.Expression("[prop3]").to_bool(f), False) - eq_(mapnik.Expression("! [prop3]").to_bool(f), True) - eq_(mapnik.Expression("[prop3] != null").to_bool(f), False) - eq_(mapnik.Expression("[prop3] = null").to_bool(f), True) + assert f["prop3"] == None + assert not mapnik.Expression("[prop3]").to_bool(f) + assert mapnik.Expression("! [prop3]").to_bool(f) + assert not mapnik.Expression("[prop3] != null").to_bool(f) + assert mapnik.Expression("[prop3] = null").to_bool(f) # https://github.com/mapnik/mapnik/issues/1859 - #eq_(mapnik.Expression("[prop3] != ''").to_bool(f),True) - eq_(mapnik.Expression("[prop3] != ''").to_bool(f), False) + #assert mapnik.Expression("[prop3] != ''").to_bool(f) == True + assert not mapnik.Expression("[prop3] != ''").to_bool(f) - eq_(mapnik.Expression("[prop3] = ''").to_bool(f), False) + assert not mapnik.Expression("[prop3] = ''").to_bool(f) # https://github.com/mapnik/mapnik/issues/1859 - #eq_(mapnik.Expression("[prop3] != null or [prop3] != ''").to_bool(f),True) - eq_(mapnik.Expression( - "[prop3] != null or [prop3] != ''").to_bool(f), False) + #assert mapnik.Expression("[prop3] != null or [prop3] != ''").to_bool(f) == True + assert not mapnik.Expression("[prop3] != null or [prop3] != ''").to_bool(f) - eq_(mapnik.Expression( - "[prop3] != null and [prop3] != ''").to_bool(f), False) + assert not mapnik.Expression("[prop3] != null and [prop3] != ''").to_bool(f) # attr not existing should behave the same as prop3 - eq_(mapnik.Expression("[prop4]").to_bool(f), False) - eq_(mapnik.Expression("! [prop4]").to_bool(f), True) - eq_(mapnik.Expression("[prop4] != null").to_bool(f), False) - eq_(mapnik.Expression("[prop4] = null").to_bool(f), True) + assert not mapnik.Expression("[prop4]").to_bool(f) + assert mapnik.Expression("! [prop4]").to_bool(f) + assert not mapnik.Expression("[prop4] != null").to_bool(f) + assert mapnik.Expression("[prop4] = null").to_bool(f) # https://github.com/mapnik/mapnik/issues/1859 - ##eq_(mapnik.Expression("[prop4] != ''").to_bool(f),True) - eq_(mapnik.Expression("[prop4] != ''").to_bool(f), False) + ##assert mapnik.Expression("[prop4] != ''").to_bool(f) == True + assert not mapnik.Expression("[prop4] != ''").to_bool(f) - eq_(mapnik.Expression("[prop4] = ''").to_bool(f), False) + assert not mapnik.Expression("[prop4] = ''").to_bool(f) # https://github.com/mapnik/mapnik/issues/1859 - ##eq_(mapnik.Expression("[prop4] != null or [prop4] != ''").to_bool(f),True) - eq_(mapnik.Expression( - "[prop4] != null or [prop4] != ''").to_bool(f), False) + ##assert mapnik.Expression("[prop4] != null or [prop4] != ''").to_bool(f) == True + assert not mapnik.Expression("[prop4] != null or [prop4] != ''").to_bool(f) - eq_(mapnik.Expression( - "[prop4] != null and [prop4] != ''").to_bool(f), False) + assert not mapnik.Expression("[prop4] != null and [prop4] != ''").to_bool(f) f["prop5"] = False - eq_(f["prop5"], False) - eq_(mapnik.Expression("[prop5]").to_bool(f), False) - eq_(mapnik.Expression("! [prop5]").to_bool(f), True) - eq_(mapnik.Expression("[prop5] != null").to_bool(f), True) - eq_(mapnik.Expression("[prop5] = null").to_bool(f), False) - eq_(mapnik.Expression("[prop5] != ''").to_bool(f), True) - eq_(mapnik.Expression("[prop5] = ''").to_bool(f), False) - eq_(mapnik.Expression("[prop5] != null or [prop5] != ''").to_bool(f), True) - eq_(mapnik.Expression( - "[prop5] != null and [prop5] != ''").to_bool(f), True) + assert f["prop5"] == False + assert not mapnik.Expression("[prop5]").to_bool(f) + assert mapnik.Expression("! [prop5]").to_bool(f) + assert mapnik.Expression("[prop5] != null").to_bool(f) + assert not mapnik.Expression("[prop5] = null").to_bool(f) + assert mapnik.Expression("[prop5] != ''").to_bool(f) + assert not mapnik.Expression("[prop5] = ''").to_bool(f) + assert mapnik.Expression("[prop5] != null or [prop5] != ''").to_bool(f) + assert mapnik.Expression("[prop5] != null and [prop5] != ''").to_bool(f) # note, we need to do [prop5] != 0 here instead of false due to this bug: # https://github.com/mapnik/mapnik/issues/1873 - eq_(mapnik.Expression( - "[prop5] != null and [prop5] != '' and [prop5] != 0").to_bool(f), False) + assert not mapnik.Expression("[prop5] != null and [prop5] != '' and [prop5] != 0").to_bool(f) # https://github.com/mapnik/mapnik/issues/1872 @@ -459,12 +433,12 @@ def test_falseyness_comparision(): context = mapnik.Context() f = mapnik.Feature(context, 0) f["prop"] = 0 - eq_(mapnik.Expression("[prop]").to_bool(f), False) - eq_(mapnik.Expression("[prop] = false").to_bool(f), True) - eq_(mapnik.Expression("not [prop] != false").to_bool(f), True) - eq_(mapnik.Expression("not [prop] = true").to_bool(f), True) - eq_(mapnik.Expression("[prop] = true").to_bool(f), False) - eq_(mapnik.Expression("[prop] != true").to_bool(f), True) + assert not mapnik.Expression("[prop]").to_bool(f) + assert mapnik.Expression("[prop] = false").to_bool(f) + assert mapnik.Expression("not [prop] != false").to_bool(f) + assert mapnik.Expression("not [prop] = true").to_bool(f) + assert not mapnik.Expression("[prop] = true").to_bool(f) + assert mapnik.Expression("[prop] != true").to_bool(f) # https://github.com/mapnik/mapnik/issues/1806, fixed by # https://github.com/mapnik/mapnik/issues/1872 @@ -474,12 +448,12 @@ def test_truthyness_comparision(): context = mapnik.Context() f = mapnik.Feature(context, 0) f["prop"] = 1 - eq_(mapnik.Expression("[prop]").to_bool(f), True) - eq_(mapnik.Expression("[prop] = false").to_bool(f), False) - eq_(mapnik.Expression("not [prop] != false").to_bool(f), False) - eq_(mapnik.Expression("not [prop] = true").to_bool(f), False) - eq_(mapnik.Expression("[prop] = true").to_bool(f), True) - eq_(mapnik.Expression("[prop] != true").to_bool(f), False) + assert mapnik.Expression("[prop]").to_bool(f) == True + assert mapnik.Expression("[prop] = false").to_bool(f) == False + assert mapnik.Expression("not [prop] != false").to_bool(f) == False + assert mapnik.Expression("not [prop] = true").to_bool(f) == False + assert mapnik.Expression("[prop] = true").to_bool(f) == True + assert mapnik.Expression("[prop] != true").to_bool(f) == False def test_division_by_zero(): @@ -490,13 +464,9 @@ def test_division_by_zero(): f = mapnik.Feature(c, 0) f['a'] = 1 f['b'] = 0 - eq_(expr.evaluate(f), None) + assert expr.evaluate(f) == None -@raises(RuntimeError) def test_invalid_syntax1(): - mapnik.Expression('abs()') - - -if __name__ == "__main__": - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + with pytest.raises(RuntimeError): + mapnik.Expression('abs()') diff --git a/test/python_tests/fontset_test.py b/test/python_tests/fontset_test.py index 0baed51fd..72915e67d 100644 --- a/test/python_tests/fontset_test.py +++ b/test/python_tests/fontset_test.py @@ -1,47 +1,40 @@ -#!/usr/bin/env python - import os - -from nose.tools import eq_ - import mapnik +import pytest -from .utilities import execution_path, run_all - +from .utilities import execution_path +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield -def test_loading_fontset_from_map(): +def test_loading_fontset_from_map(setup): m = mapnik.Map(256, 256) mapnik.load_map(m, '../data/good_maps/fontset.xml', True) fs = m.find_fontset('book-fonts') - eq_(len(fs.names), 2) - eq_(list(fs.names), ['DejaVu Sans Book', 'DejaVu Sans Oblique']) + assert len(fs.names) == 2 + assert list(fs.names) == ['DejaVu Sans Book', 'DejaVu Sans Oblique'] # def test_loading_fontset_from_python(): # m = mapnik.Map(256,256) # fset = mapnik.FontSet('foo') # fset.add_face_name('Comic Sans') # fset.add_face_name('Papyrus') -# eq_(fset.name,'foo') +# assert fset.name == 'foo' # fset.name = 'my-set' -# eq_(fset.name,'my-set') +# assert fset.name == 'my-set' # m.append_fontset('my-set', fset) # sty = mapnik.Style() # rule = mapnik.Rule() # tsym = mapnik.TextSymbolizer() -# eq_(tsym.fontset,None) +# assert tsym.fontset == None # tsym.fontset = fset # rule.symbols.append(tsym) # sty.rules.append(rule) # m.append_style('Style',sty) # serialized_map = mapnik.save_map_to_string(m) -# eq_('fontset-name="my-set"' in serialized_map,True) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) +# assert 'fontset-name="my-set"' in serialized_map == True diff --git a/test/python_tests/geojson_plugin_test.py b/test/python_tests/geojson_plugin_test.py index dfd40acac..738cd5523 100644 --- a/test/python_tests/geojson_plugin_test.py +++ b/test/python_tests/geojson_plugin_test.py @@ -1,67 +1,63 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os - -from nose.tools import assert_almost_equal, eq_ - import mapnik +import pytest -from .utilities import execution_path, run_all - +from .utilities import execution_path +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield if 'geojson' in mapnik.DatasourceCache.plugin_names(): - def test_geojson_init(): + def test_geojson_init(setup): ds = mapnik.Datasource( type='geojson', file='../data/json/escaped.geojson') e = ds.envelope() - assert_almost_equal(e.minx, -81.705583, places=7) - assert_almost_equal(e.miny, 41.480573, places=6) - assert_almost_equal(e.maxx, -81.705583, places=5) - assert_almost_equal(e.maxy, 41.480573, places=3) + assert e.minx == pytest.approx(-81.705583, abs=1e-7) + assert e.miny == pytest.approx(41.480573, abs=1e-6) + assert e.maxx == pytest.approx(-81.705583, abs=1e-5) + assert e.maxy == pytest.approx(41.480573, abs=1e-3) def test_geojson_properties(): ds = mapnik.Datasource( type='geojson', file='../data/json/escaped.geojson') f = list(ds.features_at_point(ds.envelope().center()))[0] - eq_(len(ds.fields()), 11) + assert len(ds.fields()) == 11 desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point - eq_(f['name'], u'Test') - eq_(f['int'], 1) - eq_(f['description'], u'Test: \u005C') - eq_(f['spaces'], u'this has spaces') - eq_(f['double'], 1.1) - eq_(f['boolean'], True) - eq_(f['NOM_FR'], u'Qu\xe9bec') - eq_(f['NOM_FR'], u'Québec') + assert f['name'] == u'Test' + assert f['int'] == 1 + assert f['description'] == u'Test: \u005C' + assert f['spaces'] == u'this has spaces' + assert f['double'] == 1.1 + assert f['boolean'] == True + assert f['NOM_FR'] == u'Qu\xe9bec' + assert f['NOM_FR'] == u'Québec' ds = mapnik.Datasource( type='geojson', file='../data/json/escaped.geojson') - f = list(ds.all_features())[0] - eq_(len(ds.fields()), 11) + f = list(iter(ds))[0] + assert len(ds.fields()) == 11 desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point - eq_(f['name'], u'Test') - eq_(f['int'], 1) - eq_(f['description'], u'Test: \u005C') - eq_(f['spaces'], u'this has spaces') - eq_(f['double'], 1.1) - eq_(f['boolean'], True) - eq_(f['NOM_FR'], u'Qu\xe9bec') - eq_(f['NOM_FR'], u'Québec') + assert f['name'] == u'Test' + assert f['int'] == 1 + assert f['description'] == u'Test: \u005C' + assert f['spaces'] == u'this has spaces' + assert f['double'] == 1.1 + assert f['boolean'] == True + assert f['NOM_FR'] == u'Qu\xe9bec' + assert f['NOM_FR'] == u'Québec' def test_large_geojson_properties(): ds = mapnik.Datasource( @@ -69,36 +65,36 @@ def test_large_geojson_properties(): file='../data/json/escaped.geojson', cache_features=False) f = list(ds.features_at_point(ds.envelope().center()))[0] - eq_(len(ds.fields()), 11) + assert len(ds.fields()) == 11 desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point - eq_(f['name'], u'Test') - eq_(f['int'], 1) - eq_(f['description'], u'Test: \u005C') - eq_(f['spaces'], u'this has spaces') - eq_(f['double'], 1.1) - eq_(f['boolean'], True) - eq_(f['NOM_FR'], u'Qu\xe9bec') - eq_(f['NOM_FR'], u'Québec') + assert f['name'] == u'Test' + assert f['int'] == 1 + assert f['description'] == u'Test: \u005C' + assert f['spaces'] == u'this has spaces' + assert f['double'] == 1.1 + assert f['boolean'] == True + assert f['NOM_FR'] == u'Qu\xe9bec' + assert f['NOM_FR'] == u'Québec' ds = mapnik.Datasource( type='geojson', file='../data/json/escaped.geojson') - f = list(ds.all_features())[0] - eq_(len(ds.fields()), 11) + f = list(iter(ds))[0] + assert len(ds.fields()) == 11 desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point - eq_(f['name'], u'Test') - eq_(f['int'], 1) - eq_(f['description'], u'Test: \u005C') - eq_(f['spaces'], u'this has spaces') - eq_(f['double'], 1.1) - eq_(f['boolean'], True) - eq_(f['NOM_FR'], u'Qu\xe9bec') - eq_(f['NOM_FR'], u'Québec') + assert f['name'] == u'Test' + assert f['int'] == 1 + assert f['description'] == u'Test: \u005C' + assert f['spaces'] == u'this has spaces' + assert f['double'] == 1.1 + assert f['boolean'] == True + assert f['NOM_FR'] == u'Qu\xe9bec' + assert f['NOM_FR'] == u'Québec' def test_geojson_from_in_memory_string(): # will silently fail since it is a geometry and needs to be a featurecollection. @@ -107,21 +103,21 @@ def test_geojson_from_in_memory_string(): ds = mapnik.Datasource( type='geojson', inline='{ "type":"FeatureCollection", "features": [ { "type":"Feature", "properties":{"name":"test"}, "geometry": { "type":"LineString","coordinates":[[0,0],[10,10]] } } ]}') - eq_(len(ds.fields()), 1) - f = list(ds.all_features())[0] + assert len(ds.fields()) == 1 + f = list(iter(ds))[0] desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.LineString) - eq_(f['name'], u'test') + assert desc['geometry_type'] == mapnik.DataGeometryType.LineString + assert f['name'] == u'test' # @raises(RuntimeError) def test_that_nonexistant_query_field_throws(**kwargs): ds = mapnik.Datasource( type='geojson', file='../data/json/escaped.geojson') - eq_(len(ds.fields()), 11) + assert len(ds.fields()) == 11 # TODO - this sorting is messed up - #eq_(ds.fields(),['name', 'int', 'double', 'description', 'boolean', 'NOM_FR']) - #eq_(ds.field_types(),['str', 'int', 'float', 'str', 'bool', 'str']) + #assert ds.fields(),['name', 'int', 'double', 'description', 'boolean' == 'NOM_FR'] + #assert ds.field_types(),['str', 'int', 'float', 'str', 'bool' == 'str'] # TODO - should geojson plugin throw like others? # query = mapnik.Query(ds.envelope()) # for fld in ds.fields(): @@ -134,12 +130,8 @@ def test_parsing_feature_collection_with_top_level_properties(): ds = mapnik.Datasource( type='geojson', file='../data/json/feature_collection_level_properties.json') - f = list(ds.all_features())[0] + f = list(iter(ds))[0] desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - eq_(f['feat_name'], u'feat_value') - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + assert f['feat_name'] == u'feat_value' diff --git a/test/python_tests/geometry_io_test.py b/test/python_tests/geometry_io_test.py index f1bac0b4e..f24b90198 100644 --- a/test/python_tests/geometry_io_test.py +++ b/test/python_tests/geometry_io_test.py @@ -1,25 +1,12 @@ -# encoding: utf8 - -import os from binascii import unhexlify - -from nose.tools import eq_, assert_raises - import mapnik - -from .utilities import execution_path, run_all - +import pytest try: import json except ImportError: import simplejson as json -def setup(): - # All of the paths used are relative, if we run the tests - # from another directory we need to chdir() - os.chdir(execution_path('.')) - wkts = [ [mapnik.GeometryType.Point, "POINT(30 10)", @@ -183,20 +170,20 @@ def setup(): def test_path_geo_interface(): geom = mapnik.Geometry.from_wkt('POINT(0 0)') - eq_(geom.__geo_interface__, {u'type': u'Point', u'coordinates': [0, 0]}) + assert geom.__geo_interface__, {u'type': u'Point', u'coordinates': [0 == 0]} def test_valid_wkb_parsing(): count = 0 for wkb in empty_wkbs: geom = mapnik.Geometry.from_wkb(unhexlify(wkb[2])) - eq_(geom.is_empty(), True) - eq_(geom.type(), wkb[0]) + assert geom.is_empty() == True + assert geom.type() == wkb[0] for wkb in wkts: geom = mapnik.Geometry.from_wkb(unhexlify(wkb[2])) - eq_(geom.is_empty(), False) - eq_(geom.type(), wkb[0]) + assert geom.is_empty() == False + assert geom.type() == wkb[0] def test_wkb_parsing_error(): @@ -205,7 +192,7 @@ def test_wkb_parsing_error(): try: geom = mapnik.Geometry.from_wkb(unhexlify(wkb)) # should not get here - eq_(True, False) + assert True == False except: pass assert True @@ -218,8 +205,8 @@ def test_empty_wkb_parsing(): count = 0 for wkb in partially_empty_wkb: geom = mapnik.Geometry.from_wkb(unhexlify(wkb[2])) - eq_(geom.type(), wkb[0]) - eq_(geom.is_empty(), False) + assert geom.type() == wkb[0] + assert geom.is_empty() == False def test_geojson_parsing(): @@ -228,14 +215,14 @@ def test_geojson_parsing(): for j in geojson: count += 1 geometries.append(mapnik.Geometry.from_geojson(j[1])) - eq_(count, len(geometries)) + assert count == len(geometries) def test_geojson_parsing_reversed(): for idx, j in enumerate(geojson_reversed): g1 = mapnik.Geometry.from_geojson(j) g2 = mapnik.Geometry.from_geojson(geojson[idx][1]) - eq_(g1.to_geojson(), g2.to_geojson()) + assert g1.to_geojson() == g2.to_geojson() # http://geojson.org/geojson-spec.html#positions @@ -243,44 +230,44 @@ def test_geojson_parsing_reversed(): def test_geojson_point_positions(): input_json = '{"type":"Point","coordinates":[30,10]}' geom = mapnik.Geometry.from_geojson(input_json) - eq_(geom.to_geojson(), input_json) + assert geom.to_geojson() == input_json # should ignore all but the first two geom = mapnik.Geometry.from_geojson( '{"type":"Point","coordinates":[30,10,50,50,50,50]}') - eq_(geom.to_geojson(), input_json) + assert geom.to_geojson() == input_json def test_geojson_point_positions2(): input_json = '{"type":"LineString","coordinates":[[30,10],[10,30],[40,40]]}' geom = mapnik.Geometry.from_geojson(input_json) - eq_(geom.to_geojson(), input_json) + assert geom.to_geojson() == input_json # should ignore all but the first two geom = mapnik.Geometry.from_geojson( '{"type":"LineString","coordinates":[[30.0,10.0,0,0,0],[10.0,30.0,0,0,0],[40.0,40.0,0,0,0]]}') - eq_(geom.to_geojson(), input_json) + assert geom.to_geojson() == input_json def compare_wkb_from_wkt(wkt, type): geom = mapnik.Geometry.from_wkt(wkt) - eq_(geom.type(), type) + assert geom.type() == type def compare_wkt_to_geojson(idx, wkt, num=None): geom = mapnik.Geometry.from_wkt(wkt) # ensure both have same result gj = geom.to_geojson() - eq_(len(gj) > 1, True) + assert len(gj) > 1 == True a = json.loads(gj) e = json.loads(geojson[idx][1]) - eq_(a, e) + assert a == e def test_wkt_simple(): for wkt in wkts: try: geom = mapnik.Geometry.from_wkt(wkt[1]) - eq_(geom.type(), wkt[0]) + assert geom.type() == wkt[0] except RuntimeError as e: raise RuntimeError('%s %s' % (e, wkt)) @@ -308,7 +295,7 @@ def test_wkt_rounding(): # if precision is set to 15 still fails due to very subtle rounding issues wkt = "POLYGON((7.904185 54.180426,7.89918 54.178168,7.897715 54.182318,7.893565 54.183111,7.890391 54.187567,7.885874 54.19068,7.879893 54.193915,7.894541 54.194647,7.900645 54.19068,7.904185 54.180426))" geom = mapnik.Geometry.from_wkt(wkt) - eq_(geom.type(), mapnik.GeometryType.Polygon) + assert geom.type() == mapnik.GeometryType.Polygon def test_wkt_collection_flattening(): @@ -316,7 +303,7 @@ def test_wkt_collection_flattening(): # currently fails as the MULTIPOLYGON inside will be returned as multiple polygons - not a huge deal - should we worry? #wkt = "GEOMETRYCOLLECTION(POLYGON((1 1,2 1,2 2,1 2,1 1)),MULTIPOLYGON(((40 40,20 45,45 30,40 40)),((20 35,45 20,30 5,10 10,10 30,20 35),(30 20,20 25,20 15,30 20))),LINESTRING(2 3,3 4))" geom = mapnik.Geometry.from_wkt(wkt) - eq_(geom.type(), mapnik.GeometryType.GeometryCollection) + assert geom.type() == mapnik.GeometryType.GeometryCollection def test_creating_feature_from_geojson(): @@ -327,8 +314,8 @@ def test_creating_feature_from_geojson(): } ctx = mapnik.Context() feat = mapnik.Feature.from_geojson(json.dumps(json_feat), ctx) - eq_(feat.id(), 1) - eq_(feat['name'], u'value') + assert feat.id() == 1 + assert feat['name'] == u'value' def test_handling_valid_geojson_empty_geometries(): @@ -336,13 +323,10 @@ def test_handling_valid_geojson_empty_geometries(): geom = mapnik.Geometry.from_geojson(json) out_json = geom.to_geojson() # check round trip - eq_(json.replace(" ",""), out_json) + assert json.replace(" ","") == out_json def test_handling_invalid_geojson_empty_geometries(): - for json in invalid_empty_geometries: - assert_raises(RuntimeError, mapnik.Geometry.from_geojson, json) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + with pytest.raises(RuntimeError): + for json in invalid_empty_geometries: + mapnik.Geometry.from_geojson(json) diff --git a/test/python_tests/grayscale_test.py b/test/python_tests/grayscale_test.py index fad019223..96e33bd5e 100644 --- a/test/python_tests/grayscale_test.py +++ b/test/python_tests/grayscale_test.py @@ -1,16 +1,8 @@ -from nose.tools import eq_ - import mapnik -from .utilities import run_all - - def test_grayscale_conversion(): im = mapnik.Image(2, 2) im.fill(mapnik.Color('white')) im.set_grayscale_to_alpha() pixel = im.get_pixel(0, 0) - eq_((pixel >> 24) & 0xff, 255) - -if __name__ == "__main__": - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert (pixel >> 24) & 0xff == 255 diff --git a/test/python_tests/image_encoding_speed_test.py b/test/python_tests/image_encoding_speed_test.py index 4d990465d..507da9107 100644 --- a/test/python_tests/image_encoding_speed_test.py +++ b/test/python_tests/image_encoding_speed_test.py @@ -1,19 +1,6 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os from timeit import Timer, time - import mapnik -from .utilities import execution_path, run_all - - -def setup(): - # All of the paths used are relative, if we run the tests - # from another directory we need to chdir() - os.chdir(execution_path('.')) - combinations = ['png', 'png8', 'png8:m=o', @@ -79,7 +66,7 @@ def run(func, im, format, t): if 'blank' in tiles: def blank(): - return eval('image.tostring("%s")' % c) + return eval('image.to_string("%s")' % c) blank_im = mapnik.Image(512, 512) for c in combinations: t = Timer(blank) @@ -87,7 +74,7 @@ def blank(): if 'solid' in tiles: def solid(): - return eval('image.tostring("%s")' % c) + return eval('image.to_string("%s")' % c) solid_im = mapnik.Image(512, 512) solid_im.fill(mapnik.Color("#f2efe9")) for c in combinations: @@ -96,7 +83,7 @@ def solid(): if 'many_colors' in tiles: def many_colors(): - return eval('image.tostring("%s")' % c) + return eval('image.to_string("%s")' % c) # lots of colors: http://tile.osm.org/13/4194/2747.png many_colors_im = mapnik.Image.open('../data/images/13_4194_2747.png') for c in combinations: @@ -105,7 +92,7 @@ def many_colors(): if 'aerial_24' in tiles: def aerial_24(): - return eval('image.tostring("%s")' % c) + return eval('image.to_string("%s")' % c) aerial_24_im = mapnik.Image.open('../data/images/12_654_1580.png') for c in combinations: t = Timer(aerial_24) @@ -121,9 +108,3 @@ def aerial_24(): print( 'min: %sms | avg: %sms | total: %sms | len: %s <-- %s' % (min_, avg, elapsed, size, name)) - - -if __name__ == "__main__": - setup() - do_encoding() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) diff --git a/test/python_tests/image_filters_test.py b/test/python_tests/image_filters_test.py index 7a06db3fd..93666aa4a 100644 --- a/test/python_tests/image_filters_test.py +++ b/test/python_tests/image_filters_test.py @@ -1,36 +1,29 @@ -#!/usr/bin/env python - -import os -import re - -from nose.tools import eq_ - +import re, os import mapnik +import pytest +from .utilities import side_by_side_image, execution_path -from .utilities import execution_path, run_all, side_by_side_image - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) - + yield def replace_style(m, name, style): m.remove_style(name) m.append_style(name, style) - def test_append(): s = mapnik.Style() - eq_(s.image_filters, '') + assert s.image_filters == '' s.image_filters = 'gray' - eq_(s.image_filters, 'gray') + assert s.image_filters == 'gray' s.image_filters = 'sharpen' - eq_(s.image_filters, 'sharpen') + assert s.image_filters == 'sharpen' if 'shape' in mapnik.DatasourceCache.plugin_names(): - def test_style_level_image_filter(): + def test_style_level_image_filter(setup): m = mapnik.Map(256, 256) mapnik.load_map(m, '../data/good_maps/style_level_image_filter.xml') m.zoom_all() @@ -61,20 +54,16 @@ def test_style_level_image_filter(): im.save(expected, 'png32') expected_im = mapnik.Image.open(expected) # compare them - if im.tostring('png32') == expected_im.tostring('png32'): + if im.to_string('png32') == expected_im.to_string('png32'): successes.append(name) else: fails.append( 'failed comparing actual (%s) and expected(%s)' % - (actual, 'tests/python_tests/' + expected)) + (actual, expected)) fail_im = side_by_side_image(expected_im, im) fail_im.save( '/tmp/mapnik-style-image-filter-' + filename + '.fail.png', 'png32') - eq_(len(fails), 0, '\n' + '\n'.join(fails)) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert len(fails) == 0, '\n' + '\n'.join(fails) diff --git a/test/python_tests/image_test.py b/test/python_tests/image_test.py index f25ff39c8..3a72cceb5 100644 --- a/test/python_tests/image_test.py +++ b/test/python_tests/image_test.py @@ -1,100 +1,90 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os -import sys - -from nose.tools import assert_almost_equal, eq_, raises - import mapnik +import pytest -from .utilities import READ_FLAGS, execution_path, get_unique_colors, run_all - -PYTHON3 = sys.version_info[0] == 3 -if PYTHON3: - buffer = memoryview - +from .utilities import READ_FLAGS, get_unique_colors, execution_path +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield - -def test_type(): +def test_type(setup): im = mapnik.Image(256, 256) - eq_(im.get_type(), mapnik.ImageType.rgba8) + assert im.get_type() == mapnik.ImageType.rgba8 im = mapnik.Image(256, 256, mapnik.ImageType.gray8) - eq_(im.get_type(), mapnik.ImageType.gray8) + assert im.get_type() == mapnik.ImageType.gray8 def test_image_premultiply(): im = mapnik.Image(256, 256) - eq_(im.premultiplied(), False) + assert im.premultiplied() == False # Premultiply should return true that it worked - eq_(im.premultiply(), True) - eq_(im.premultiplied(), True) + assert im.premultiply() == True + assert im.premultiplied() == True # Premultipling again should return false as nothing should happen - eq_(im.premultiply(), False) - eq_(im.premultiplied(), True) + assert im.premultiply() == False + assert im.premultiplied() == True # Demultiply should return true that it worked - eq_(im.demultiply(), True) - eq_(im.premultiplied(), False) + assert im.demultiply() == True + assert im.premultiplied() == False # Demultiply again should not work and return false as it did nothing - eq_(im.demultiply(), False) - eq_(im.premultiplied(), False) + assert im.demultiply() == False + assert im.premultiplied() == False def test_image_premultiply_values(): im = mapnik.Image(256, 256) im.fill(mapnik.Color(16, 33, 255, 128)) im.premultiply() - c = im.get_pixel(0, 0, True) - eq_(c.r, 8) - eq_(c.g, 17) - eq_(c.b, 128) - eq_(c.a, 128) + c = im.get_pixel_color(0, 0) + assert c.r == 8 + assert c.g == 17 + assert c.b == 128 + assert c.a == 128 im.demultiply() # Do to the nature of this operation the result will not be exactly the # same - c = im.get_pixel(0, 0, True) - eq_(c.r, 15) - eq_(c.g, 33) - eq_(c.b, 255) - eq_(c.a, 128) + c = im.get_pixel_color(0, 0) + assert c.r == 15 + assert c.g == 33 + assert c.b == 255 + assert c.a == 128 def test_apply_opacity(): im = mapnik.Image(4, 4) im.fill(mapnik.Color(128, 128, 128, 128)) im.apply_opacity(0.75) - c = im.get_pixel(0, 0, True) - eq_(c.r, 128) - eq_(c.g, 128) - eq_(c.b, 128) - eq_(c.a, 96) + c = im.get_pixel_color(0, 0) + assert c.r == 128 + assert c.g == 128 + assert c.b == 128 + assert c.a == 96 def test_background(): im = mapnik.Image(256, 256) - eq_(im.premultiplied(), False) + assert im.premultiplied() == False im.fill(mapnik.Color(32, 64, 125, 128)) - eq_(im.premultiplied(), False) - c = im.get_pixel(0, 0, True) - eq_(c.get_premultiplied(), False) - eq_(c.r, 32) - eq_(c.g, 64) - eq_(c.b, 125) - eq_(c.a, 128) + assert im.premultiplied() == False + c = im.get_pixel_color(0, 0) + assert c.get_premultiplied() == False + assert c.r == 32 + assert c.g == 64 + assert c.b == 125 + assert c.a == 128 # Now again with a premultiplied alpha im.fill(mapnik.Color(32, 64, 125, 128, True)) - eq_(im.premultiplied(), True) - c = im.get_pixel(0, 0, True) - eq_(c.get_premultiplied(), True) - eq_(c.r, 32) - eq_(c.g, 64) - eq_(c.b, 125) - eq_(c.a, 128) + assert im.premultiplied() == True + c = im.get_pixel_color(0, 0) + assert c.get_premultiplied() == True + assert c.r == 32 + assert c.g == 64 + assert c.b == 125 + assert c.a == 128 def test_set_and_get_pixel(): @@ -106,27 +96,27 @@ def test_set_and_get_pixel(): im.set_pixel(1, 1, c0_pre) # No differences for non premultiplied pixels c1_int = mapnik.Color(im.get_pixel(0, 0)) - eq_(c0.r, c1_int.r) - eq_(c0.g, c1_int.g) - eq_(c0.b, c1_int.b) - eq_(c0.a, c1_int.a) - c1 = im.get_pixel(0, 0, True) - eq_(c0.r, c1.r) - eq_(c0.g, c1.g) - eq_(c0.b, c1.b) - eq_(c0.a, c1.a) + assert c0.r == c1_int.r + assert c0.g == c1_int.g + assert c0.b == c1_int.b + assert c0.a == c1_int.a + c1 = im.get_pixel_color(0, 0) + assert c0.r == c1.r + assert c0.g == c1.g + assert c0.b == c1.b + assert c0.a == c1.a # The premultiplied Color should be demultiplied before being applied. c0_pre.demultiply() c1_int = mapnik.Color(im.get_pixel(1, 1)) - eq_(c0_pre.r, c1_int.r) - eq_(c0_pre.g, c1_int.g) - eq_(c0_pre.b, c1_int.b) - eq_(c0_pre.a, c1_int.a) - c1 = im.get_pixel(1, 1, True) - eq_(c0_pre.r, c1.r) - eq_(c0_pre.g, c1.g) - eq_(c0_pre.b, c1.b) - eq_(c0_pre.a, c1.a) + assert c0_pre.r == c1_int.r + assert c0_pre.g == c1_int.g + assert c0_pre.b == c1_int.b + assert c0_pre.a == c1_int.a + c1 = im.get_pixel_color(1, 1) + assert c0_pre.r == c1.r + assert c0_pre.g == c1.g + assert c0_pre.b == c1.b + assert c0_pre.a == c1.a # Now create a new image that is premultiplied im = mapnik.Image(256, 256, mapnik.ImageType.rgba8, True, True) @@ -138,26 +128,26 @@ def test_set_and_get_pixel(): # premultiply c0 c0.premultiply() c1_int = mapnik.Color(im.get_pixel(0, 0)) - eq_(c0.r, c1_int.r) - eq_(c0.g, c1_int.g) - eq_(c0.b, c1_int.b) - eq_(c0.a, c1_int.a) - c1 = im.get_pixel(0, 0, True) - eq_(c0.r, c1.r) - eq_(c0.g, c1.g) - eq_(c0.b, c1.b) - eq_(c0.a, c1.a) + assert c0.r == c1_int.r + assert c0.g == c1_int.g + assert c0.b == c1_int.b + assert c0.a == c1_int.a + c1 = im.get_pixel_color(0, 0) + assert c0.r == c1.r + assert c0.g == c1.g + assert c0.b == c1.b + assert c0.a == c1.a # The premultiplied Color should be the same though c1_int = mapnik.Color(im.get_pixel(1, 1)) - eq_(c0_pre.r, c1_int.r) - eq_(c0_pre.g, c1_int.g) - eq_(c0_pre.b, c1_int.b) - eq_(c0_pre.a, c1_int.a) - c1 = im.get_pixel(1, 1, True) - eq_(c0_pre.r, c1.r) - eq_(c0_pre.g, c1.g) - eq_(c0_pre.b, c1.b) - eq_(c0_pre.a, c1.a) + assert c0_pre.r == c1_int.r + assert c0_pre.g == c1_int.g + assert c0_pre.b == c1_int.b + assert c0_pre.a == c1_int.a + c1 = im.get_pixel_color(1, 1) + assert c0_pre.r == c1.r + assert c0_pre.g == c1.g + assert c0_pre.b == c1.b + assert c0_pre.a == c1.a def test_pixel_gray8(): @@ -165,9 +155,9 @@ def test_pixel_gray8(): val_list = range(20) for v in val_list: im.set_pixel(0, 0, v) - eq_(im.get_pixel(0, 0), v) + assert im.get_pixel(0, 0) == v im.set_pixel(0, 0, -v) - eq_(im.get_pixel(0, 0), 0) + assert im.get_pixel(0, 0) == 0 def test_pixel_gray8s(): @@ -175,9 +165,9 @@ def test_pixel_gray8s(): val_list = range(20) for v in val_list: im.set_pixel(0, 0, v) - eq_(im.get_pixel(0, 0), v) + assert im.get_pixel(0, 0) == v im.set_pixel(0, 0, -v) - eq_(im.get_pixel(0, 0), -v) + assert im.get_pixel(0, 0) == -v def test_pixel_gray16(): @@ -185,9 +175,9 @@ def test_pixel_gray16(): val_list = range(20) for v in val_list: im.set_pixel(0, 0, v) - eq_(im.get_pixel(0, 0), v) + assert im.get_pixel(0, 0) == v im.set_pixel(0, 0, -v) - eq_(im.get_pixel(0, 0), 0) + assert im.get_pixel(0, 0) == 0 def test_pixel_gray16s(): @@ -195,9 +185,9 @@ def test_pixel_gray16s(): val_list = range(20) for v in val_list: im.set_pixel(0, 0, v) - eq_(im.get_pixel(0, 0), v) + assert im.get_pixel(0, 0) == v im.set_pixel(0, 0, -v) - eq_(im.get_pixel(0, 0), -v) + assert im.get_pixel(0, 0) == -v def test_pixel_gray32(): @@ -205,9 +195,9 @@ def test_pixel_gray32(): val_list = range(20) for v in val_list: im.set_pixel(0, 0, v) - eq_(im.get_pixel(0, 0), v) + assert im.get_pixel(0, 0) == v im.set_pixel(0, 0, -v) - eq_(im.get_pixel(0, 0), 0) + assert im.get_pixel(0, 0) == 0 def test_pixel_gray32s(): @@ -215,9 +205,9 @@ def test_pixel_gray32s(): val_list = range(20) for v in val_list: im.set_pixel(0, 0, v) - eq_(im.get_pixel(0, 0), v) + assert im.get_pixel(0, 0) == v im.set_pixel(0, 0, -v) - eq_(im.get_pixel(0, 0), -v) + assert im.get_pixel(0, 0) == -v def test_pixel_gray64(): @@ -225,9 +215,9 @@ def test_pixel_gray64(): val_list = range(20) for v in val_list: im.set_pixel(0, 0, v) - eq_(im.get_pixel(0, 0), v) + assert im.get_pixel(0, 0) == v im.set_pixel(0, 0, -v) - eq_(im.get_pixel(0, 0), 0) + assert im.get_pixel(0, 0) == 0 def test_pixel_gray64s(): @@ -235,9 +225,9 @@ def test_pixel_gray64s(): val_list = range(20) for v in val_list: im.set_pixel(0, 0, v) - eq_(im.get_pixel(0, 0), v) + assert im.get_pixel(0, 0) == v im.set_pixel(0, 0, -v) - eq_(im.get_pixel(0, 0), -v) + assert im.get_pixel(0, 0) == -v def test_pixel_floats(): @@ -245,9 +235,9 @@ def test_pixel_floats(): val_list = [0.9, 0.99, 0.999, 0.9999, 0.99999, 1, 1.0001, 1.001, 1.01, 1.1] for v in val_list: im.set_pixel(0, 0, v) - assert_almost_equal(im.get_pixel(0, 0), v) + assert im.get_pixel(0, 0) == pytest.approx(v) im.set_pixel(0, 0, -v) - assert_almost_equal(im.get_pixel(0, 0), -v) + assert im.get_pixel(0, 0) == pytest.approx(-v) def test_pixel_doubles(): @@ -255,80 +245,80 @@ def test_pixel_doubles(): val_list = [0.9, 0.99, 0.999, 0.9999, 0.99999, 1, 1.0001, 1.001, 1.01, 1.1] for v in val_list: im.set_pixel(0, 0, v) - assert_almost_equal(im.get_pixel(0, 0), v) + assert im.get_pixel(0, 0) == pytest.approx(v) im.set_pixel(0, 0, -v) - assert_almost_equal(im.get_pixel(0, 0), -v) + assert im.get_pixel(0, 0) == pytest.approx(-v) def test_pixel_overflow(): im = mapnik.Image(4, 4, mapnik.ImageType.gray8) im.set_pixel(0, 0, 256) - eq_(im.get_pixel(0, 0), 255) + assert im.get_pixel(0, 0) == 255 def test_pixel_underflow(): im = mapnik.Image(4, 4, mapnik.ImageType.gray8) im.set_pixel(0, 0, -1) - eq_(im.get_pixel(0, 0), 0) + assert im.get_pixel(0, 0) == 0 im = mapnik.Image(4, 4, mapnik.ImageType.gray16) im.set_pixel(0, 0, -1) - eq_(im.get_pixel(0, 0), 0) + assert im.get_pixel(0, 0) == 0 -@raises(IndexError) def test_set_pixel_out_of_range_1(): - im = mapnik.Image(4, 4) - c = mapnik.Color('blue') - im.set_pixel(5, 5, c) + with pytest.raises(IndexError): + im = mapnik.Image(4, 4) + c = mapnik.Color('blue') + im.set_pixel(5, 5, c) -@raises(OverflowError) def test_set_pixel_out_of_range_2(): - im = mapnik.Image(4, 4) - c = mapnik.Color('blue') - im.set_pixel(-1, 1, c) + with pytest.raises(IndexError): + im = mapnik.Image(4, 4) + c = mapnik.Color('blue') + im.set_pixel(-1, 1, c) -@raises(IndexError) def test_get_pixel_out_of_range_1(): - im = mapnik.Image(4, 4) - c = im.get_pixel(5, 5) + with pytest.raises(IndexError): + im = mapnik.Image(4, 4) + c = im.get_pixel(5, 5) -@raises(OverflowError) def test_get_pixel_out_of_range_2(): - im = mapnik.Image(4, 4) - c = im.get_pixel(-1, 1) + with pytest.raises(IndexError): + im = mapnik.Image(4, 4) + c = im.get_pixel(-1, 1) -@raises(IndexError) def test_get_pixel_color_out_of_range_1(): - im = mapnik.Image(4, 4) - c = im.get_pixel(5, 5, True) + with pytest.raises(IndexError): + im = mapnik.Image(4, 4) + c = im.get_pixel_color(5, 5) -@raises(OverflowError) def test_get_pixel_color_out_of_range_2(): - im = mapnik.Image(4, 4) - c = im.get_pixel(-1, 1, True) + with pytest.raises(IndexError): + im = mapnik.Image(4, 4) + c = im.get_pixel_color(-1, 1) def test_set_color_to_alpha(): im = mapnik.Image(256, 256) im.fill(mapnik.Color('rgba(12,12,12,255)')) - eq_(get_unique_colors(im), ['rgba(12,12,12,255)']) + assert get_unique_colors(im), ['rgba(12,12,12 == 255)'] im.set_color_to_alpha(mapnik.Color('rgba(12,12,12,0)')) - eq_(get_unique_colors(im), ['rgba(0,0,0,0)']) + assert get_unique_colors(im), ['rgba(0,0,0 == 0)'] -@raises(RuntimeError) def test_negative_image_dimensions(): - # TODO - this may have regressed in - # https://github.com/mapnik/mapnik/commit/4f3521ac24b61fc8ae8fd344a16dc3a5fdf15af7 - im = mapnik.Image(-40, 40) - # should not get here - eq_(im.width(), 0) - eq_(im.height(), 0) + with pytest.raises(RuntimeError): + # TODO - this may have regressed in + # https://github.com/mapnik/mapnik/commit/4f3521ac24b61fc8ae8fd344a16dc3a5fdf15af7 + im = mapnik.Image(-40, 40) + # should not get here + assert im.width() == 0 + assert im.height() == 0 def test_jpeg_round_trip(): @@ -338,15 +328,15 @@ def test_jpeg_round_trip(): im.save(filepath, 'jpeg') im2 = mapnik.Image.open(filepath) with open(filepath, READ_FLAGS) as f: - im3 = mapnik.Image.fromstring(f.read()) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(im.width(), im3.width()) - eq_(im.height(), im3.height()) - eq_(len(im.tostring()), len(im2.tostring())) - eq_(len(im.tostring('jpeg')), len(im2.tostring('jpeg'))) - eq_(len(im.tostring()), len(im3.tostring())) - eq_(len(im.tostring('jpeg')), len(im3.tostring('jpeg'))) + im3 = mapnik.Image.from_string(f.read()) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert im.width() == im3.width() + assert im.height() == im3.height() + assert len(im.to_string()) == len(im2.to_string()) + assert len(im.to_string('jpeg')) == len(im2.to_string('jpeg')) + assert len(im.to_string()) == len(im3.to_string()) + assert len(im.to_string('jpeg')) == len(im3.to_string('jpeg')) def test_png_round_trip(): @@ -356,36 +346,32 @@ def test_png_round_trip(): im.save(filepath, 'png') im2 = mapnik.Image.open(filepath) with open(filepath, READ_FLAGS) as f: - im3 = mapnik.Image.fromstring(f.read()) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(im.width(), im3.width()) - eq_(im.height(), im3.height()) - eq_(len(im.tostring()), len(im2.tostring())) - eq_(len(im.tostring('png')), len(im2.tostring('png'))) - eq_(len(im.tostring('png8')), len(im2.tostring('png8'))) - eq_(len(im.tostring()), len(im3.tostring())) - eq_(len(im.tostring('png')), len(im3.tostring('png'))) - eq_(len(im.tostring('png8')), len(im3.tostring('png8'))) + im3 = mapnik.Image.from_string(f.read()) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert im.width() == im3.width() + assert im.height() == im3.height() + assert len(im.to_string()) == len(im2.to_string()) + assert len(im.to_string('png')) == len(im2.to_string('png')) + assert len(im.to_string('png8')) == len(im2.to_string('png8')) + assert len(im.to_string()) == len(im3.to_string()) + assert len(im.to_string('png')) == len(im3.to_string('png')) + assert len(im.to_string('png8')) == len(im3.to_string('png8')) def test_image_open_from_string(): filepath = '../data/images/dummy.png' im1 = mapnik.Image.open(filepath) with open(filepath, READ_FLAGS) as f: - im2 = mapnik.Image.fromstring(f.read()) - eq_(im1.width(), im2.width()) - length = len(im1.tostring()) - eq_(length, len(im2.tostring())) - eq_(len(mapnik.Image.fromstring(im1.tostring('png')).tostring()), length) - eq_(len(mapnik.Image.fromstring(im1.tostring('jpeg')).tostring()), length) - eq_(len(mapnik.Image.frombuffer(buffer(im1.tostring('png'))).tostring()), length) - eq_(len(mapnik.Image.frombuffer(buffer(im1.tostring('jpeg'))).tostring()), length) + im2 = mapnik.Image.from_string(f.read()) + assert im1.width() == im2.width() + length = len(im1.to_string()) + assert length == len(im2.to_string()) + assert len(mapnik.Image.from_string(im1.to_string('png')).to_string()) == length + assert len(mapnik.Image.from_string(im1.to_string('jpeg')).to_string()) == length + assert len(mapnik.Image.from_memoryview(memoryview(im1.to_string('png'))).to_string()) == length + assert len(mapnik.Image.from_memoryview(memoryview(im1.to_string('jpeg'))).to_string()) == length # TODO - https://github.com/mapnik/mapnik/issues/1831 - eq_(len(mapnik.Image.fromstring(im1.tostring('tiff')).tostring()), length) - eq_(len(mapnik.Image.frombuffer(buffer(im1.tostring('tiff'))).tostring()), length) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert len(mapnik.Image.from_string(im1.to_string('tiff')).to_string()) == length + assert len(mapnik.Image.from_memoryview(memoryview(im1.to_string('tiff'))).to_string()) == length diff --git a/test/python_tests/image_tiff_test.py b/test/python_tests/image_tiff_test.py index b1915c111..492238acc 100644 --- a/test/python_tests/image_tiff_test.py +++ b/test/python_tests/image_tiff_test.py @@ -1,88 +1,71 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import hashlib import os - -from nose.tools import assert_not_equal, eq_ - +import hashlib import mapnik +import pytest +from .utilities import READ_FLAGS, execution_path -from .utilities import READ_FLAGS, execution_path, run_all - - -def hashstr(var): - return hashlib.md5(var).hexdigest() - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield +def hashstr(var): + return hashlib.md5(var).hexdigest() -def test_tiff_round_trip_scanline(): +def test_tiff_round_trip_scanline(setup): filepath = '/tmp/mapnik-tiff-io-scanline.tiff' im = mapnik.Image(255, 267) im.fill(mapnik.Color('rgba(12,255,128,.5)')) - org_str = hashstr(im.tostring()) + org_str = hashstr(im.to_string()) im.save(filepath, 'tiff:method=scanline') im2 = mapnik.Image.open(filepath) with open(filepath, READ_FLAGS) as f: - im3 = mapnik.Image.fromstring(f.read()) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(im.width(), im3.width()) - eq_(im.height(), im3.height()) - eq_(hashstr(im.tostring()), org_str) + im3 = mapnik.Image.from_string(f.read()) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert im.width() == im3.width() + assert im.height() == im3.height() + assert hashstr(im.to_string()) == org_str # This won't be the same the first time around because the im is not # premultiplied and im2 is - assert_not_equal(hashstr(im.tostring()), hashstr(im2.tostring())) - assert_not_equal( - hashstr( - im.tostring('tiff:method=scanline')), hashstr( - im2.tostring('tiff:method=scanline'))) + assert not hashstr(im.to_string()) == hashstr(im2.to_string()) + assert not hashstr(im.to_string('tiff:method=scanline')) == hashstr(im2.to_string('tiff:method=scanline')) # Now premultiply im.premultiply() - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=scanline')), - hashstr(im2.tostring('tiff:method=scanline'))) - eq_(hashstr(im2.tostring()), hashstr(im3.tostring())) - eq_(hashstr(im2.tostring('tiff:method=scanline')), - hashstr(im3.tostring('tiff:method=scanline'))) + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=scanline')) == hashstr(im2.to_string('tiff:method=scanline')) + assert hashstr(im2.to_string()) == hashstr(im3.to_string()) + assert hashstr(im2.to_string('tiff:method=scanline')) == hashstr(im3.to_string('tiff:method=scanline')) def test_tiff_round_trip_stripped(): filepath = '/tmp/mapnik-tiff-io-stripped.tiff' im = mapnik.Image(255, 267) im.fill(mapnik.Color('rgba(12,255,128,.5)')) - org_str = hashstr(im.tostring()) + org_str = hashstr(im.to_string()) im.save(filepath, 'tiff:method=stripped') im2 = mapnik.Image.open(filepath) im2.save('/tmp/mapnik-tiff-io-stripped2.tiff', 'tiff:method=stripped') with open(filepath, READ_FLAGS) as f: - im3 = mapnik.Image.fromstring(f.read()) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(im.width(), im3.width()) - eq_(im.height(), im3.height()) + im3 = mapnik.Image.from_string(f.read()) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert im.width() == im3.width() + assert im.height() == im3.height() # Because one will end up with UNASSOC alpha tag which internally the TIFF reader will premultiply, the first to string will not be the same due to the # difference in tags. - assert_not_equal(hashstr(im.tostring()), hashstr(im2.tostring())) - assert_not_equal( - hashstr( - im.tostring('tiff:method=stripped')), hashstr( - im2.tostring('tiff:method=stripped'))) + assert not hashstr(im.to_string()) == hashstr(im2.to_string()) + assert not hashstr(im.to_string('tiff:method=stripped')) == hashstr(im2.to_string('tiff:method=stripped')) # Now if we premultiply they will be exactly the same im.premultiply() - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=stripped')), - hashstr(im2.tostring('tiff:method=stripped'))) - eq_(hashstr(im2.tostring()), hashstr(im3.tostring())) + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=stripped')) == hashstr(im2.to_string('tiff:method=stripped')) + assert hashstr(im2.to_string()) == hashstr(im3.to_string()) # Both of these started out premultiplied, so this round trip should be # exactly the same! - eq_(hashstr(im2.tostring('tiff:method=stripped')), - hashstr(im3.tostring('tiff:method=stripped'))) + assert hashstr(im2.to_string('tiff:method=stripped')) == hashstr(im3.to_string('tiff:method=stripped')) def test_tiff_round_trip_rows_stripped(): @@ -90,43 +73,39 @@ def test_tiff_round_trip_rows_stripped(): filepath2 = '/tmp/mapnik-tiff-io-rows_stripped2.tiff' im = mapnik.Image(255, 267) im.fill(mapnik.Color('rgba(12,255,128,.5)')) - c = im.get_pixel(0, 0, True) - eq_(c.r, 12) - eq_(c.g, 255) - eq_(c.b, 128) - eq_(c.a, 128) - eq_(c.get_premultiplied(), False) + c = im.get_pixel_color(0, 0) + assert c.r == 12 + assert c.g == 255 + assert c.b == 128 + assert c.a == 128 + assert c.get_premultiplied() == False im.save(filepath, 'tiff:method=stripped:rows_per_strip=8') im2 = mapnik.Image.open(filepath) - c2 = im2.get_pixel(0, 0, True) - eq_(c2.r, 6) - eq_(c2.g, 128) - eq_(c2.b, 64) - eq_(c2.a, 128) - eq_(c2.get_premultiplied(), True) + c2 = im2.get_pixel_color(0, 0) + assert c2.r == 6 + assert c2.g == 128 + assert c2.b == 64 + assert c2.a == 128 + assert c2.get_premultiplied() == True im2.save(filepath2, 'tiff:method=stripped:rows_per_strip=8') with open(filepath, READ_FLAGS) as f: - im3 = mapnik.Image.fromstring(f.read()) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(im.width(), im3.width()) - eq_(im.height(), im3.height()) + im3 = mapnik.Image.from_string(f.read()) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert im.width() == im3.width() + assert im.height() == im3.height() # Because one will end up with UNASSOC alpha tag which internally the TIFF reader will premultiply, the first to string will not be the same due to the # difference in tags. - assert_not_equal(hashstr(im.tostring()), hashstr(im2.tostring())) - assert_not_equal( - hashstr( - im.tostring('tiff:method=stripped:rows_per_strip=8')), hashstr( - im2.tostring('tiff:method=stripped:rows_per_strip=8'))) + assert not hashstr(im.to_string()) == hashstr(im2.to_string()) + assert not hashstr(im.to_string('tiff:method=stripped:rows_per_strip=8')) == hashstr( + im2.to_string('tiff:method=stripped:rows_per_strip=8')) # Now premultiply the first image and they will be the same! im.premultiply() - eq_(hashstr(im.tostring('tiff:method=stripped:rows_per_strip=8')), - hashstr(im2.tostring('tiff:method=stripped:rows_per_strip=8'))) - eq_(hashstr(im2.tostring()), hashstr(im3.tostring())) + assert hashstr(im.to_string('tiff:method=stripped:rows_per_strip=8')) == hashstr(im2.to_string('tiff:method=stripped:rows_per_strip=8')) + assert hashstr(im2.to_string()) == hashstr(im3.to_string()) # Both of these started out premultiplied, so this round trip should be # exactly the same! - eq_(hashstr(im2.tostring('tiff:method=stripped:rows_per_strip=8')), - hashstr(im3.tostring('tiff:method=stripped:rows_per_strip=8'))) + assert hashstr(im2.to_string('tiff:method=stripped:rows_per_strip=8')) == hashstr(im3.to_string('tiff:method=stripped:rows_per_strip=8')) def test_tiff_round_trip_buffered_tiled(): @@ -135,45 +114,41 @@ def test_tiff_round_trip_buffered_tiled(): filepath3 = '/tmp/mapnik-tiff-io-buffered-tiled3.tiff' im = mapnik.Image(255, 267) im.fill(mapnik.Color('rgba(33,255,128,.5)')) - c = im.get_pixel(0, 0, True) - eq_(c.r, 33) - eq_(c.g, 255) - eq_(c.b, 128) - eq_(c.a, 128) - eq_(c.get_premultiplied(), False) + c = im.get_pixel_color(0, 0) + assert c.r == 33 + assert c.g == 255 + assert c.b == 128 + assert c.a == 128 + assert not c.get_premultiplied() im.save(filepath, 'tiff:method=tiled:tile_width=32:tile_height=32') im2 = mapnik.Image.open(filepath) - c2 = im2.get_pixel(0, 0, True) - eq_(c2.r, 17) - eq_(c2.g, 128) - eq_(c2.b, 64) - eq_(c2.a, 128) - eq_(c2.get_premultiplied(), True) + c2 = im2.get_pixel_color(0, 0) + assert c2.r == 17 + assert c2.g == 128 + assert c2.b == 64 + assert c2.a == 128 + assert c2.get_premultiplied() with open(filepath, READ_FLAGS) as f: - im3 = mapnik.Image.fromstring(f.read()) + im3 = mapnik.Image.from_string(f.read()) im2.save(filepath2, 'tiff:method=tiled:tile_width=32:tile_height=32') im3.save(filepath3, 'tiff:method=tiled:tile_width=32:tile_height=32') - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(im.width(), im3.width()) - eq_(im.height(), im3.height()) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert im.width() == im3.width() + assert im.height() == im3.height() # Because one will end up with UNASSOC alpha tag which internally the TIFF reader will premultiply, the first to string will not be the same due to the # difference in tags. - assert_not_equal(hashstr(im.tostring()), hashstr(im2.tostring())) - assert_not_equal( - hashstr( - im.tostring('tiff:method=tiled:tile_width=32:tile_height=32')), hashstr( - im2.tostring('tiff:method=tiled:tile_width=32:tile_height=32'))) + assert not hashstr(im.to_string()) == hashstr(im2.to_string()) + assert not hashstr(im.to_string('tiff:method=tiled:tile_width=32:tile_height=32')) == hashstr( + im2.to_string('tiff:method=tiled:tile_width=32:tile_height=32')) # Now premultiply the first image and they should be the same im.premultiply() - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=tiled:tile_width=32:tile_height=32')), - hashstr(im2.tostring('tiff:method=tiled:tile_width=32:tile_height=32'))) - eq_(hashstr(im2.tostring()), hashstr(im3.tostring())) + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=tiled:tile_width=32:tile_height=32')) == hashstr(im2.to_string('tiff:method=tiled:tile_width=32:tile_height=32')) + assert hashstr(im2.to_string()) == hashstr(im3.to_string()) # Both of these started out premultiplied, so this round trip should be # exactly the same! - eq_(hashstr(im2.tostring('tiff:method=tiled:tile_width=32:tile_height=32')), - hashstr(im3.tostring('tiff:method=tiled:tile_width=32:tile_height=32'))) + assert hashstr(im2.to_string('tiff:method=tiled:tile_width=32:tile_height=32')) == hashstr(im3.to_string('tiff:method=tiled:tile_width=32:tile_height=32')) def test_tiff_round_trip_tiled(): @@ -183,28 +158,23 @@ def test_tiff_round_trip_tiled(): im.save(filepath, 'tiff:method=tiled') im2 = mapnik.Image.open(filepath) with open(filepath, READ_FLAGS) as f: - im3 = mapnik.Image.fromstring(f.read()) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(im.width(), im3.width()) - eq_(im.height(), im3.height()) + im3 = mapnik.Image.from_string(f.read()) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert im.width() == im3.width() + assert im.height() == im3.height() # Because one will end up with UNASSOC alpha tag which internally the TIFF reader will premultiply, the first to string will not be the same due to the # difference in tags. - assert_not_equal(hashstr(im.tostring()), hashstr(im2.tostring())) - assert_not_equal( - hashstr( - im.tostring('tiff:method=tiled')), hashstr( - im2.tostring('tiff:method=tiled'))) + assert not hashstr(im.to_string()) == hashstr(im2.to_string()) + assert not hashstr(im.to_string('tiff:method=tiled')) == hashstr(im2.to_string('tiff:method=tiled')) # Now premultiply the first image and they will be exactly the same. im.premultiply() - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=tiled')), - hashstr(im2.tostring('tiff:method=tiled'))) - eq_(hashstr(im2.tostring()), hashstr(im3.tostring())) + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=tiled')) == hashstr(im2.to_string('tiff:method=tiled')) + assert hashstr(im2.to_string()) == hashstr(im3.to_string()) # Both of these started out premultiplied, so this round trip should be # exactly the same! - eq_(hashstr(im2.tostring('tiff:method=tiled')), - hashstr(im3.tostring('tiff:method=tiled'))) + assert hashstr(im2.to_string('tiff:method=tiled')) == hashstr(im3.to_string('tiff:method=tiled')) def test_tiff_rgb8_compare(): @@ -213,13 +183,12 @@ def test_tiff_rgb8_compare(): im = mapnik.Image.open(filepath1) im.save(filepath2, 'tiff') im2 = mapnik.Image.open(filepath2) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff')), hashstr(im2.tostring('tiff'))) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff')) == hashstr(im2.to_string('tiff')) # should not be a blank image - eq_(hashstr(im.tostring("tiff")) != hashstr(mapnik.Image( - im.width(), im.height(), mapnik.ImageType.rgba8).tostring("tiff")), True) + assert hashstr(im.to_string("tiff")) != hashstr(mapnik.Image(im.width(), im.height(), mapnik.ImageType.rgba8).to_string("tiff")) def test_tiff_rgba8_compare_scanline(): @@ -228,14 +197,12 @@ def test_tiff_rgba8_compare_scanline(): im = mapnik.Image.open(filepath1) im.save(filepath2, 'tiff:method=scanline') im2 = mapnik.Image.open(filepath2) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=scanline')), - hashstr(im2.tostring('tiff:method=scanline'))) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=scanline')) == hashstr(im2.to_string('tiff:method=scanline')) # should not be a blank image - eq_(hashstr(im.tostring("tiff")) != hashstr(mapnik.Image( - im.width(), im.height(), mapnik.ImageType.rgba8).tostring("tiff")), True) + assert hashstr(im.to_string("tiff")) != hashstr(mapnik.Image(im.width(), im.height(), mapnik.ImageType.rgba8).to_string("tiff")) def test_tiff_rgba8_compare_stripped(): @@ -244,14 +211,12 @@ def test_tiff_rgba8_compare_stripped(): im = mapnik.Image.open(filepath1) im.save(filepath2, 'tiff:method=stripped') im2 = mapnik.Image.open(filepath2) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=stripped')), - hashstr(im2.tostring('tiff:method=stripped'))) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=stripped')) == hashstr(im2.to_string('tiff:method=stripped')) # should not be a blank image - eq_(hashstr(im.tostring("tiff")) != hashstr(mapnik.Image( - im.width(), im.height(), mapnik.ImageType.rgba8).tostring("tiff")), True) + assert hashstr(im.to_string("tiff")) != hashstr(mapnik.Image(im.width(), im.height(), mapnik.ImageType.rgba8).to_string("tiff")) def test_tiff_rgba8_compare_tiled(): @@ -260,14 +225,12 @@ def test_tiff_rgba8_compare_tiled(): im = mapnik.Image.open(filepath1) im.save(filepath2, 'tiff:method=tiled') im2 = mapnik.Image.open(filepath2) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=tiled')), - hashstr(im2.tostring('tiff:method=tiled'))) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=tiled')) == hashstr(im2.to_string('tiff:method=tiled')) # should not be a blank image - eq_(hashstr(im.tostring("tiff")) != hashstr(mapnik.Image( - im.width(), im.height(), mapnik.ImageType.rgba8).tostring("tiff")), True) + assert hashstr(im.to_string("tiff")) != hashstr(mapnik.Image(im.width(), im.height(), mapnik.ImageType.rgba8).to_string("tiff")) def test_tiff_gray8_compare_scanline(): @@ -276,15 +239,12 @@ def test_tiff_gray8_compare_scanline(): im = mapnik.Image.open(filepath1) im.save(filepath2, 'tiff:method=scanline') im2 = mapnik.Image.open(filepath2) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=scanline')), - hashstr(im2.tostring('tiff:method=scanline'))) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=scanline')) == hashstr(im2.to_string('tiff:method=scanline')) # should not be a blank image - eq_(hashstr(im.tostring("tiff")) != hashstr(mapnik.Image( - im.width(), im.height(), mapnik.ImageType.gray8).tostring("tiff")), True) - + assert hashstr(im.to_string("tiff")) != hashstr(mapnik.Image(im.width(), im.height(), mapnik.ImageType.gray8).to_string("tiff")) def test_tiff_gray8_compare_stripped(): filepath1 = '../data/tiff/ndvi_256x256_gray8_striped.tif' @@ -292,14 +252,12 @@ def test_tiff_gray8_compare_stripped(): im = mapnik.Image.open(filepath1) im.save(filepath2, 'tiff:method=stripped') im2 = mapnik.Image.open(filepath2) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=stripped')), - hashstr(im2.tostring('tiff:method=stripped'))) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=stripped')) == hashstr(im2.to_string('tiff:method=stripped')) # should not be a blank image - eq_(hashstr(im.tostring("tiff")) != hashstr(mapnik.Image( - im.width(), im.height(), mapnik.ImageType.gray8).tostring("tiff")), True) + assert hashstr(im.to_string("tiff")) != hashstr(mapnik.Image(im.width(), im.height(), mapnik.ImageType.gray8).to_string("tiff")) def test_tiff_gray8_compare_tiled(): @@ -308,14 +266,12 @@ def test_tiff_gray8_compare_tiled(): im = mapnik.Image.open(filepath1) im.save(filepath2, 'tiff:method=tiled') im2 = mapnik.Image.open(filepath2) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=tiled')), - hashstr(im2.tostring('tiff:method=tiled'))) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=tiled')) == hashstr(im2.to_string('tiff:method=tiled')) # should not be a blank image - eq_(hashstr(im.tostring("tiff")) != hashstr(mapnik.Image( - im.width(), im.height(), mapnik.ImageType.gray8).tostring("tiff")), True) + assert hashstr(im.to_string("tiff")) != hashstr(mapnik.Image(im.width(), im.height(), mapnik.ImageType.gray8).to_string("tiff")) def test_tiff_gray16_compare_scanline(): @@ -324,15 +280,12 @@ def test_tiff_gray16_compare_scanline(): im = mapnik.Image.open(filepath1) im.save(filepath2, 'tiff:method=scanline') im2 = mapnik.Image.open(filepath2) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=scanline')), - hashstr(im2.tostring('tiff:method=scanline'))) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=scanline')) == hashstr(im2.to_string('tiff:method=scanline')) # should not be a blank image - eq_(hashstr(im.tostring("tiff")) != hashstr(mapnik.Image( - im.width(), im.height(), mapnik.ImageType.gray16).tostring("tiff")), True) - + assert hashstr(im.to_string("tiff")) != hashstr(mapnik.Image(im.width(), im.height(), mapnik.ImageType.gray16).to_string("tiff")) def test_tiff_gray16_compare_stripped(): filepath1 = '../data/tiff/ndvi_256x256_gray16_striped.tif' @@ -340,14 +293,12 @@ def test_tiff_gray16_compare_stripped(): im = mapnik.Image.open(filepath1) im.save(filepath2, 'tiff:method=stripped') im2 = mapnik.Image.open(filepath2) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=stripped')), - hashstr(im2.tostring('tiff:method=stripped'))) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=stripped')) == hashstr(im2.to_string('tiff:method=stripped')) # should not be a blank image - eq_(hashstr(im.tostring("tiff")) != hashstr(mapnik.Image( - im.width(), im.height(), mapnik.ImageType.gray16).tostring("tiff")), True) + assert hashstr(im.to_string("tiff")) != hashstr(mapnik.Image(im.width(), im.height(), mapnik.ImageType.gray16).to_string("tiff")) def test_tiff_gray16_compare_tiled(): @@ -356,14 +307,12 @@ def test_tiff_gray16_compare_tiled(): im = mapnik.Image.open(filepath1) im.save(filepath2, 'tiff:method=tiled') im2 = mapnik.Image.open(filepath2) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=tiled')), - hashstr(im2.tostring('tiff:method=tiled'))) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=tiled')) == hashstr(im2.to_string('tiff:method=tiled')) # should not be a blank image - eq_(hashstr(im.tostring("tiff")) != hashstr(mapnik.Image( - im.width(), im.height(), mapnik.ImageType.gray16).tostring("tiff")), True) + assert hashstr(im.to_string("tiff")) != hashstr(mapnik.Image(im.width(), im.height(), mapnik.ImageType.gray16).to_string("tiff")) def test_tiff_gray32f_compare_scanline(): @@ -372,14 +321,12 @@ def test_tiff_gray32f_compare_scanline(): im = mapnik.Image.open(filepath1) im.save(filepath2, 'tiff:method=scanline') im2 = mapnik.Image.open(filepath2) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=scanline')), - hashstr(im2.tostring('tiff:method=scanline'))) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=scanline')) == hashstr(im2.to_string('tiff:method=scanline')) # should not be a blank image - eq_(hashstr(im.tostring("tiff")) != hashstr(mapnik.Image(im.width(), - im.height(), mapnik.ImageType.gray32f).tostring("tiff")), True) + assert hashstr(im.to_string("tiff")) != hashstr(mapnik.Image(im.width(), im.height(), mapnik.ImageType.gray32f).to_string("tiff")) def test_tiff_gray32f_compare_stripped(): @@ -388,14 +335,12 @@ def test_tiff_gray32f_compare_stripped(): im = mapnik.Image.open(filepath1) im.save(filepath2, 'tiff:method=stripped') im2 = mapnik.Image.open(filepath2) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=stripped')), - hashstr(im2.tostring('tiff:method=stripped'))) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=stripped')) == hashstr(im2.to_string('tiff:method=stripped')) # should not be a blank image - eq_(hashstr(im.tostring("tiff")) != hashstr(mapnik.Image(im.width(), - im.height(), mapnik.ImageType.gray32f).tostring("tiff")), True) + assert hashstr(im.to_string("tiff")) != hashstr(mapnik.Image(im.width(), im.height(), mapnik.ImageType.gray32f).to_string("tiff")) def test_tiff_gray32f_compare_tiled(): @@ -404,15 +349,9 @@ def test_tiff_gray32f_compare_tiled(): im = mapnik.Image.open(filepath1) im.save(filepath2, 'tiff:method=tiled') im2 = mapnik.Image.open(filepath2) - eq_(im.width(), im2.width()) - eq_(im.height(), im2.height()) - eq_(hashstr(im.tostring()), hashstr(im2.tostring())) - eq_(hashstr(im.tostring('tiff:method=tiled')), - hashstr(im2.tostring('tiff:method=tiled'))) + assert im.width() == im2.width() + assert im.height() == im2.height() + assert hashstr(im.to_string()) == hashstr(im2.to_string()) + assert hashstr(im.to_string('tiff:method=tiled')) == hashstr(im2.to_string('tiff:method=tiled')) # should not be a blank image - eq_(hashstr(im.tostring("tiff")) != hashstr(mapnik.Image(im.width(), - im.height(), mapnik.ImageType.gray32f).tostring("tiff")), True) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert hashstr(im.to_string("tiff")) != hashstr(mapnik.Image(im.width(), im.height(), mapnik.ImageType.gray32f).to_string("tiff")) diff --git a/test/python_tests/images/pycairo/cairo-cairo-expected.pdf b/test/python_tests/images/pycairo/cairo-cairo-expected.pdf index 220a9b210..2d2d0dad9 100644 Binary files a/test/python_tests/images/pycairo/cairo-cairo-expected.pdf and b/test/python_tests/images/pycairo/cairo-cairo-expected.pdf differ diff --git a/test/python_tests/images/pycairo/pdf-printing-expected.pdf b/test/python_tests/images/pycairo/pdf-printing-expected.pdf index e0dedea5f..bf300ba05 100644 Binary files a/test/python_tests/images/pycairo/pdf-printing-expected.pdf and b/test/python_tests/images/pycairo/pdf-printing-expected.pdf differ diff --git a/test/python_tests/images/style-comp-op/color.png b/test/python_tests/images/style-comp-op/color.png index 81dae902b..662b4728d 100644 Binary files a/test/python_tests/images/style-comp-op/color.png and b/test/python_tests/images/style-comp-op/color.png differ diff --git a/test/python_tests/images/style-image-filter/agg-stack-blur22.png b/test/python_tests/images/style-image-filter/agg-stack-blur22.png index 54f92eaf0..b8226452e 100644 Binary files a/test/python_tests/images/style-image-filter/agg-stack-blur22.png and b/test/python_tests/images/style-image-filter/agg-stack-blur22.png differ diff --git a/test/python_tests/images/style-image-filter/blur.png b/test/python_tests/images/style-image-filter/blur.png index dfda77eb8..29b72bfc3 100644 Binary files a/test/python_tests/images/style-image-filter/blur.png and b/test/python_tests/images/style-image-filter/blur.png differ diff --git a/test/python_tests/images/style-image-filter/edge-detect.png b/test/python_tests/images/style-image-filter/edge-detect.png index 825b43cdf..2c7cb1466 100644 Binary files a/test/python_tests/images/style-image-filter/edge-detect.png and b/test/python_tests/images/style-image-filter/edge-detect.png differ diff --git a/test/python_tests/images/style-image-filter/emboss.png b/test/python_tests/images/style-image-filter/emboss.png index b9af9784a..d0cb71c39 100644 Binary files a/test/python_tests/images/style-image-filter/emboss.png and b/test/python_tests/images/style-image-filter/emboss.png differ diff --git a/test/python_tests/images/style-image-filter/gray.png b/test/python_tests/images/style-image-filter/gray.png index 1dc357e72..7ed3982ec 100644 Binary files a/test/python_tests/images/style-image-filter/gray.png and b/test/python_tests/images/style-image-filter/gray.png differ diff --git a/test/python_tests/images/style-image-filter/invert.png b/test/python_tests/images/style-image-filter/invert.png index a3a94bc9e..08d3d22f9 100644 Binary files a/test/python_tests/images/style-image-filter/invert.png and b/test/python_tests/images/style-image-filter/invert.png differ diff --git a/test/python_tests/images/style-image-filter/none.png b/test/python_tests/images/style-image-filter/none.png index 2a9dfc2a1..55d3d42cb 100644 Binary files a/test/python_tests/images/style-image-filter/none.png and b/test/python_tests/images/style-image-filter/none.png differ diff --git a/test/python_tests/images/style-image-filter/sharpen.png b/test/python_tests/images/style-image-filter/sharpen.png index ecae501ab..592b9f650 100644 Binary files a/test/python_tests/images/style-image-filter/sharpen.png and b/test/python_tests/images/style-image-filter/sharpen.png differ diff --git a/test/python_tests/images/style-image-filter/sobel.png b/test/python_tests/images/style-image-filter/sobel.png index ba2e564fc..f7061b378 100644 Binary files a/test/python_tests/images/style-image-filter/sobel.png and b/test/python_tests/images/style-image-filter/sobel.png differ diff --git a/test/python_tests/images/style-image-filter/x-gradient.png b/test/python_tests/images/style-image-filter/x-gradient.png index 6548571be..125bb0169 100644 Binary files a/test/python_tests/images/style-image-filter/x-gradient.png and b/test/python_tests/images/style-image-filter/x-gradient.png differ diff --git a/test/python_tests/images/style-image-filter/y-gradient.png b/test/python_tests/images/style-image-filter/y-gradient.png index 683d64277..88b0be877 100644 Binary files a/test/python_tests/images/style-image-filter/y-gradient.png and b/test/python_tests/images/style-image-filter/y-gradient.png differ diff --git a/test/python_tests/images/support/mapnik-marker-ellipse-render1.png b/test/python_tests/images/support/mapnik-marker-ellipse-render1.png index 43ee30412..e7f12b494 100644 Binary files a/test/python_tests/images/support/mapnik-marker-ellipse-render1.png and b/test/python_tests/images/support/mapnik-marker-ellipse-render1.png differ diff --git a/test/python_tests/images/support/mapnik-marker-ellipse-render2.png b/test/python_tests/images/support/mapnik-marker-ellipse-render2.png index ab1c4314b..d1d0d2215 100644 Binary files a/test/python_tests/images/support/mapnik-marker-ellipse-render2.png and b/test/python_tests/images/support/mapnik-marker-ellipse-render2.png differ diff --git a/test/python_tests/images/support/marker-text-line-scale-factor-0.1.png b/test/python_tests/images/support/marker-text-line-scale-factor-0.1.png index c3a06ac4c..286fa9caa 100644 Binary files a/test/python_tests/images/support/marker-text-line-scale-factor-0.1.png and b/test/python_tests/images/support/marker-text-line-scale-factor-0.1.png differ diff --git a/test/python_tests/images/support/marker-text-line-scale-factor-0.899.png b/test/python_tests/images/support/marker-text-line-scale-factor-0.899.png index fdd53da34..aac9cb89d 100644 Binary files a/test/python_tests/images/support/marker-text-line-scale-factor-0.899.png and b/test/python_tests/images/support/marker-text-line-scale-factor-0.899.png differ diff --git a/test/python_tests/images/support/marker-text-line-scale-factor-1.5.png b/test/python_tests/images/support/marker-text-line-scale-factor-1.5.png index b93b2870a..12f283702 100644 Binary files a/test/python_tests/images/support/marker-text-line-scale-factor-1.5.png and b/test/python_tests/images/support/marker-text-line-scale-factor-1.5.png differ diff --git a/test/python_tests/images/support/marker-text-line-scale-factor-1.png b/test/python_tests/images/support/marker-text-line-scale-factor-1.png index bc485f7a4..08bdf04bb 100644 Binary files a/test/python_tests/images/support/marker-text-line-scale-factor-1.png and b/test/python_tests/images/support/marker-text-line-scale-factor-1.png differ diff --git a/test/python_tests/images/support/marker-text-line-scale-factor-10.png b/test/python_tests/images/support/marker-text-line-scale-factor-10.png index 7dc7b5e9d..bd25f5852 100644 Binary files a/test/python_tests/images/support/marker-text-line-scale-factor-10.png and b/test/python_tests/images/support/marker-text-line-scale-factor-10.png differ diff --git a/test/python_tests/images/support/marker-text-line-scale-factor-2.png b/test/python_tests/images/support/marker-text-line-scale-factor-2.png index 2d6703027..058ed4a4f 100644 Binary files a/test/python_tests/images/support/marker-text-line-scale-factor-2.png and b/test/python_tests/images/support/marker-text-line-scale-factor-2.png differ diff --git a/test/python_tests/images/support/marker-text-line-scale-factor-5.png b/test/python_tests/images/support/marker-text-line-scale-factor-5.png index 7a9d49e48..7122e287b 100644 Binary files a/test/python_tests/images/support/marker-text-line-scale-factor-5.png and b/test/python_tests/images/support/marker-text-line-scale-factor-5.png differ diff --git a/test/python_tests/images/support/pgraster/rgba_8bui-nodataedge-rgb_8bui C T_64x64 Cl--1-box1.png b/test/python_tests/images/support/pgraster/rgba_8bui-nodataedge-rgb_8bui C T_64x64 Cl--1-box1.png index cae620513..2d8149d1d 100644 Binary files a/test/python_tests/images/support/pgraster/rgba_8bui-nodataedge-rgb_8bui C T_64x64 Cl--1-box1.png and b/test/python_tests/images/support/pgraster/rgba_8bui-nodataedge-rgb_8bui C T_64x64 Cl--1-box1.png differ diff --git a/test/python_tests/images/support/pgraster/rgba_8bui-nodataedge-rgb_8bui C T_64x64--0-box1.png b/test/python_tests/images/support/pgraster/rgba_8bui-nodataedge-rgb_8bui C T_64x64--0-box1.png index cae620513..2d8149d1d 100644 Binary files a/test/python_tests/images/support/pgraster/rgba_8bui-nodataedge-rgb_8bui C T_64x64--0-box1.png and b/test/python_tests/images/support/pgraster/rgba_8bui-nodataedge-rgb_8bui C T_64x64--0-box1.png differ diff --git a/test/python_tests/images/support/raster_warping.png b/test/python_tests/images/support/raster_warping.png index bee83211f..83f68213c 100644 Binary files a/test/python_tests/images/support/raster_warping.png and b/test/python_tests/images/support/raster_warping.png differ diff --git a/test/python_tests/images/support/transparency/white0.webp b/test/python_tests/images/support/transparency/white0.webp index 27af0fd72..2f0baac52 100644 Binary files a/test/python_tests/images/support/transparency/white0.webp and b/test/python_tests/images/support/transparency/white0.webp differ diff --git a/test/python_tests/introspection_test.py b/test/python_tests/introspection_test.py index 0c1e39dd2..ccd9a5208 100644 --- a/test/python_tests/introspection_test.py +++ b/test/python_tests/introspection_test.py @@ -1,40 +1,36 @@ -#!/usr/bin/env python - import os - -from nose.tools import eq_ - import mapnik +import pytest -from .utilities import execution_path, run_all - +from .utilities import execution_path +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield - -def test_introspect_symbolizers(): +def test_introspect_symbolizers(setup): # create a symbolizer p = mapnik.PointSymbolizer() p.file = "../data/images/dummy.png" p.allow_overlap = True p.opacity = 0.5 - eq_(p.allow_overlap, True) - eq_(p.opacity, 0.5) - eq_(p.filename, '../data/images/dummy.png') + assert p.allow_overlap == True + assert p.opacity == 0.5 + assert str(p.file) == '../data/images/dummy.png' # make sure the defaults # are what we think they are - eq_(p.allow_overlap, True) - eq_(p.opacity, 0.5) - eq_(p.filename, '../data/images/dummy.png') + assert p.allow_overlap == True + assert p.opacity == 0.5 + assert str(p.file) == '../data/images/dummy.png' # contruct objects to hold it r = mapnik.Rule() - r.symbols.append(p) + r.symbolizers.append(p) s = mapnik.Style() s.rules.append(r) m = mapnik.Map(0, 0) @@ -46,20 +42,16 @@ def test_introspect_symbolizers(): s2 = m.find_style('s') rules = s2.rules - eq_(len(rules), 1) + assert len(rules) == 1 r2 = rules[0] - syms = r2.symbols - eq_(len(syms), 1) + syms = r2.symbolizers + assert len(syms) == 1 # TODO here, we can do... sym = syms[0] p2 = sym.extract() assert isinstance(p2, mapnik.PointSymbolizer) - eq_(p2.allow_overlap, True) - eq_(p2.opacity, 0.5) - eq_(p2.filename, '../data/images/dummy.png') - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert p2.allow_overlap == True + assert p2.opacity == 0.5 + assert str(p2.file) == '../data/images/dummy.png' diff --git a/test/python_tests/json_feature_properties_test.py b/test/python_tests/json_feature_properties_test.py index 41557455c..7e7bb9a25 100644 --- a/test/python_tests/json_feature_properties_test.py +++ b/test/python_tests/json_feature_properties_test.py @@ -1,11 +1,4 @@ -# encoding: utf8 - -from nose.tools import eq_ - import mapnik - -from .utilities import run_all - try: import json except ImportError: @@ -32,11 +25,11 @@ "test": "string with \" quote", "json": '{"type":"Feature","id":1,"geometry":null,"properties":{"name":"string with \\" quote"}}' }, - { - "name": "reverse_solidus", # backslash - "test": "string with \\ quote", - "json": '{"type":"Feature","id":1,"geometry":null,"properties":{"name":"string with \\\ quote"}}' - }, + # { + # "name": "reverse_solidus", # backslash + # "test": "string with \\ quote", + # "json": '{"type":"Feature","id":1,"geometry":null,"properties":{"name":"string with \\\ quote"}}' + # }, { "name": "solidus", # forward slash "test": "string with / quote", @@ -83,30 +76,20 @@ ctx = mapnik.Context() ctx.push('name') - def test_char_escaping(): for char in chars: feat = mapnik.Feature(ctx, 1) expected = char['test'] feat["name"] = expected - eq_(feat["name"], expected) + assert feat["name"] == expected # confirm the python json module # is working as we would expect pyjson2 = json.loads(char['json']) - eq_(pyjson2['properties']['name'], expected) + assert pyjson2['properties']['name'] == expected # confirm our behavior is the same as python json module # for the original string geojson_feat_string = feat.to_geojson() - eq_( - geojson_feat_string, - char['json'], - "Mapnik's json escaping is not to spec: actual(%s) and expected(%s) for %s" % - (geojson_feat_string, - char['json'], - char['name'])) + assert geojson_feat_string == char['json'], "Mapnik's json escaping is not to spec: actual(%s) and expected(%s) for %s" % (geojson_feat_string, char['json'], char['name']) # and the round tripped string pyjson = json.loads(geojson_feat_string) - eq_(pyjson['properties']['name'], expected) - -if __name__ == "__main__": - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert pyjson['properties']['name'] == expected diff --git a/test/python_tests/layer_buffer_size_test.py b/test/python_tests/layer_buffer_size_test.py index 30417a367..c56701fa4 100644 --- a/test/python_tests/layer_buffer_size_test.py +++ b/test/python_tests/layer_buffer_size_test.py @@ -1,29 +1,27 @@ -# coding=utf8 import os - -from nose.tools import eq_ - import mapnik +import pytest -from .utilities import execution_path, run_all - +from .utilities import execution_path +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield if 'sqlite' in mapnik.DatasourceCache.plugin_names(): # the negative buffer on the layer should # override the postive map buffer leading # only one point to be rendered in the map - def test_layer_buffer_size_1(): + def test_layer_buffer_size_1(setup): m = mapnik.Map(512, 512) - eq_(m.buffer_size, 0) + assert m.buffer_size == 0 mapnik.load_map(m, '../data/good_maps/layer_buffer_size_reduction.xml') - eq_(m.buffer_size, 256) - eq_(m.layers[0].buffer_size, -150) + assert m.buffer_size == 256 + assert m.layers[0].buffer_size == -150 m.zoom_all() im = mapnik.Image(m.width, m.height) mapnik.render(m, im) @@ -31,12 +29,4 @@ def test_layer_buffer_size_1(): expected = 'images/support/mapnik-layer-buffer-size.png' im.save(actual, "png32") expected_im = mapnik.Image.open(expected) - eq_(im.tostring('png32'), - expected_im.tostring('png32'), - 'failed comparing actual (%s) and expected (%s)' % (actual, - 'tests/python_tests/' + expected)) - - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert im.to_string('png32') == expected_im.to_string('png32'),'failed comparing actual (%s) and expected (%s)' % (actual,'tests/python_tests/' + expected) diff --git a/test/python_tests/layer_modification_test.py b/test/python_tests/layer_modification_test.py index a4af1861f..bf01d634c 100644 --- a/test/python_tests/layer_modification_test.py +++ b/test/python_tests/layer_modification_test.py @@ -1,21 +1,17 @@ -#!/usr/bin/env python - import os - -from nose.tools import eq_ - import mapnik +import pytest -from .utilities import execution_path, run_all - +from .utilities import execution_path +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield - -def test_adding_datasource_to_layer(): +def test_adding_datasource_to_layer(setup): map_string = ''' @@ -39,9 +35,9 @@ def test_adding_datasource_to_layer(): mapnik.load_map_from_string(m, map_string) # validate it loaded fine - eq_(m.layers[0].styles[0], 'world_borders_style') - eq_(m.layers[0].styles[1], 'point_style') - eq_(len(m.layers), 1) + assert m.layers[0].styles[0] == 'world_borders_style' + assert m.layers[0].styles[1] == 'point_style' + assert len(m.layers) == 1 # also assign a variable reference to that layer # below we will test that this variable references @@ -49,35 +45,29 @@ def test_adding_datasource_to_layer(): lyr = m.layers[0] # ensure that there was no datasource for the layer... - eq_(m.layers[0].datasource, None) - eq_(lyr.datasource, None) + assert m.layers[0].datasource == None + assert lyr.datasource == None # also note that since the srs was black it defaulted to wgs84 - eq_(m.layers[0].srs, - '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs') - eq_(lyr.srs, '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs') + assert m.layers[0].srs == 'epsg:4326' + assert lyr.srs == 'epsg:4326' # now add a datasource one... ds = mapnik.Shapefile(file='../data/shp/world_merc.shp') m.layers[0].datasource = ds # now ensure it is attached - eq_(m.layers[0].datasource.describe()['name'], "shape") - eq_(lyr.datasource.describe()['name'], "shape") + assert m.layers[0].datasource.describe()['name'] == "shape" + assert lyr.datasource.describe()['name'] == "shape" # and since we have now added a shapefile in spherical mercator, adjust # the projection lyr.srs = '+proj=merc +lon_0=0 +lat_ts=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs' # test that assignment - eq_(m.layers[ - 0].srs, '+proj=merc +lon_0=0 +lat_ts=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs') - eq_(lyr.srs, '+proj=merc +lon_0=0 +lat_ts=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs') + assert m.layers[0].srs == '+proj=merc +lon_0=0 +lat_ts=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs' + assert lyr.srs == '+proj=merc +lon_0=0 +lat_ts=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs' except RuntimeError as e: # only test datasources that we have installed if not 'Could not create datasource' in str(e): raise RuntimeError(e) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) diff --git a/test/python_tests/layer_test.py b/test/python_tests/layer_test.py index e303c0242..e8652bbc0 100644 --- a/test/python_tests/layer_test.py +++ b/test/python_tests/layer_test.py @@ -1,33 +1,21 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from nose.tools import eq_ - import mapnik -from .utilities import run_all - - # Map initialization - def test_layer_init(): l = mapnik.Layer('test') - eq_(l.name, 'test') - eq_(l.srs, '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs') - eq_(l.envelope(), mapnik.Box2d()) - eq_(l.clear_label_cache, False) - eq_(l.cache_features, False) - eq_(l.visible(1), True) - eq_(l.active, True) - eq_(l.datasource, None) - eq_(l.queryable, False) - eq_(l.minimum_scale_denominator, 0.0) - eq_(l.maximum_scale_denominator > 1e+6, True) - eq_(l.group_by, "") - eq_(l.maximum_extent, None) - eq_(l.buffer_size, None) - eq_(len(l.styles), 0) - -if __name__ == "__main__": - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert l.name == 'test' + assert l.srs == 'epsg:4326' + assert l.envelope() == mapnik.Box2d() + assert not l.clear_label_cache + assert not l.cache_features + assert l.visible(1) + assert l.active + assert l.datasource == None + assert not l.queryable + assert l.minimum_scale_denominator == 0.0 + assert l.maximum_scale_denominator > 1e+6 + assert l.group_by == "" + assert l.maximum_extent == None + assert l.buffer_size == None + assert len(l.styles) == 0 diff --git a/test/python_tests/load_map_test.py b/test/python_tests/load_map_test.py index ea0b5ccd8..d9e0c3345 100644 --- a/test/python_tests/load_map_test.py +++ b/test/python_tests/load_map_test.py @@ -1,32 +1,23 @@ -#!/usr/bin/env python - -import glob -import os - -from nose.tools import eq_ - +import glob,os import mapnik +import pytest -from .utilities import execution_path, run_all - +from .utilities import execution_path default_logging_severity = mapnik.logger.get_severity() - -def setup(): - # make the tests silent to suppress unsupported params from harfbuzz tests - # TODO: remove this after harfbuzz branch merges - mapnik.logger.set_severity(getattr(mapnik.severity_type, "None")) +@pytest.fixture(scope="module") +def setup_and_teardown(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) - - -def teardown(): + # make the tests silent to suppress unsupported params from harfbuzz tests + # TODO: remove this after harfbuzz branch merges + mapnik.logger.set_severity(getattr(mapnik.severity_type, "None")) + yield mapnik.logger.set_severity(default_logging_severity) - -def test_broken_files(): +def test_broken_files(setup_and_teardown): default_logging_severity = mapnik.logger.get_severity() mapnik.logger.set_severity(getattr(mapnik.severity_type, "None")) broken_files = glob.glob("../data/broken_maps/*.xml") @@ -44,7 +35,7 @@ def test_broken_files(): filename) except RuntimeError: pass - eq_(len(failures), 0, '\n' + '\n'.join(failures)) + assert len(failures) == 0, '\n' + '\n'.join(failures) mapnik.logger.set_severity(default_logging_severity) @@ -75,7 +66,7 @@ def test_can_parse_xml_with_deprecated_properties(): failures.append( 'Failed to load valid map %s (%s)' % (filename, e)) - eq_(len(failures), 0, '\n' + '\n'.join(failures)) + assert len(failures) == 0, '\n' + '\n'.join(failures) mapnik.logger.set_severity(default_logging_severity) @@ -100,8 +91,4 @@ def test_good_files(): failures.append( 'Failed to load valid map %s (%s)' % (filename, e)) - eq_(len(failures), 0, '\n' + '\n'.join(failures)) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert len(failures) == 0, '\n' + '\n'.join(failures) diff --git a/test/python_tests/map_query_test.py b/test/python_tests/map_query_test.py index ab8335e14..542c43fd4 100644 --- a/test/python_tests/map_query_test.py +++ b/test/python_tests/map_query_test.py @@ -1,59 +1,49 @@ -#!/usr/bin/env python - import os - -from nose.tools import assert_almost_equal, eq_, raises - import mapnik +import pytest +from .utilities import execution_path -from .utilities import execution_path, run_all - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield # map has no layers - - -@raises(IndexError) -def test_map_query_throw1(): - m = mapnik.Map(256, 256) - m.zoom_to_box(mapnik.Box2d(-1, -1, 0, 0)) - m.query_point(0, 0, 0) +def test_map_query_throw1(setup): + with pytest.raises(IndexError): + m = mapnik.Map(256, 256) + m.zoom_to_box(mapnik.Box2d(-1, -1, 0, 0)) + m.query_point(0, 0, 0) # only positive indexes - - -@raises(IndexError) def test_map_query_throw2(): - m = mapnik.Map(256, 256) - m.query_point(-1, 0, 0) + with pytest.raises(IndexError): + m = mapnik.Map(256, 256) + m.query_point(-1, 0, 0) # map has never been zoomed (nodata) - - -@raises(RuntimeError) def test_map_query_throw3(): - m = mapnik.Map(256, 256) - m.query_point(0, 0, 0) + with pytest.raises(RuntimeError): + m = mapnik.Map(256, 256) + m.query_point(0, 0, 0) if 'shape' in mapnik.DatasourceCache.plugin_names(): # map has never been zoomed (even with data) - @raises(RuntimeError) def test_map_query_throw4(): - m = mapnik.Map(256, 256) - mapnik.load_map(m, '../data/good_maps/agg_poly_gamma_map.xml') - m.query_point(0, 0, 0) + with pytest.raises(RuntimeError): + m = mapnik.Map(256, 256) + mapnik.load_map(m, '../data/good_maps/agg_poly_gamma_map.xml') + m.query_point(0, 0, 0) # invalid coords in general (do not intersect) - @raises(RuntimeError) def test_map_query_throw5(): - m = mapnik.Map(256, 256) - mapnik.load_map(m, '../data/good_maps/agg_poly_gamma_map.xml') - m.zoom_all() - m.query_point(0, 9999999999999999, 9999999999999999) + with pytest.raises(RuntimeError): + m = mapnik.Map(256, 256) + mapnik.load_map(m, '../data/good_maps/agg_poly_gamma_map.xml') + m.zoom_all() + m.query_point(0, 9999999999999999, 9999999999999999) def test_map_query_works1(): m = mapnik.Map(256, 256) @@ -64,8 +54,8 @@ def test_map_query_works1(): m.zoom_all() # somewhere in kansas fs = m.query_point(0, -11012435.5376, 4599674.6134) - feat = fs.next() - eq_(feat.attributes['NAME_FORMA'], u'United States of America') + feat = next(fs) + assert feat.attributes['NAME_FORMA'] == u'United States of America' def test_map_query_works2(): m = mapnik.Map(256, 256) @@ -78,13 +68,13 @@ def test_map_query_works2(): # mapnik.render_to_file(m,'works2.png') # validate that aspect_fix_mode modified the bbox reasonably e = m.envelope() - assert_almost_equal(e.minx, -179.999999975, places=7) - assert_almost_equal(e.miny, -167.951396161, places=7) - assert_almost_equal(e.maxx, 179.999999975, places=7) - assert_almost_equal(e.maxy, 192.048603789, places=7) + assert e.minx == pytest.approx(-179.999999975, abs=1e-7) + assert e.miny == pytest.approx(-167.951396161, abs=1e-7) + assert e.maxx == pytest.approx(179.999999975, abs=1e-7) + assert e.maxy == pytest.approx(192.048603789, abs=1e-7) fs = m.query_point(0, -98.9264, 38.1432) # somewhere in kansas - feat = fs.next() - eq_(feat.attributes['NAME'], u'United States') + feat = next(fs) + assert feat.attributes['NAME'] == u'United States' def test_map_query_in_pixels_works1(): m = mapnik.Map(256, 256) @@ -94,8 +84,8 @@ def test_map_query_in_pixels_works1(): m.maximum_extent = merc_bounds m.zoom_all() fs = m.query_map_point(0, 55, 100) # somewhere in middle of us - feat = fs.next() - eq_(feat.attributes['NAME_FORMA'], u'United States of America') + feat = next(fs) + assert feat.attributes['NAME_FORMA'] == u'United States of America' def test_map_query_in_pixels_works2(): m = mapnik.Map(256, 256) @@ -107,14 +97,10 @@ def test_map_query_in_pixels_works2(): m.zoom_all() # validate that aspect_fix_mode modified the bbox reasonably e = m.envelope() - assert_almost_equal(e.minx, -179.999999975, places=7) - assert_almost_equal(e.miny, -167.951396161, places=7) - assert_almost_equal(e.maxx, 179.999999975, places=7) - assert_almost_equal(e.maxy, 192.048603789, places=7) + assert e.minx == pytest.approx(-179.999999975, abs=1e-7) + assert e.miny == pytest.approx(-167.951396161, abs=1e-7) + assert e.maxx == pytest.approx(179.999999975, abs=1e-7) + assert e.maxy == pytest.approx(192.048603789, abs=1e-7) fs = m.query_map_point(0, 55, 100) # somewhere in Canada - feat = fs.next() - eq_(feat.attributes['NAME'], u'Canada') - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + feat = next(fs) + assert feat.attributes['NAME'] == u'Canada' diff --git a/test/python_tests/mapnik_logger_test.py b/test/python_tests/mapnik_logger_test.py index 8c6c5437a..7d7566890 100644 --- a/test/python_tests/mapnik_logger_test.py +++ b/test/python_tests/mapnik_logger_test.py @@ -1,21 +1,12 @@ -#!/usr/bin/env python -from nose.tools import eq_ - import mapnik -from .utilities import run_all - - def test_logger_init(): - eq_(mapnik.severity_type.Debug, 0) - eq_(mapnik.severity_type.Warn, 1) - eq_(mapnik.severity_type.Error, 2) - eq_(getattr(mapnik.severity_type, "None"), 3) + assert mapnik.severity_type.Debug == 0 + assert mapnik.severity_type.Warn == 1 + assert mapnik.severity_type.Error == 2 + assert getattr(mapnik.severity_type, "None") == 3 default = mapnik.logger.get_severity() mapnik.logger.set_severity(mapnik.severity_type.Debug) - eq_(mapnik.logger.get_severity(), mapnik.severity_type.Debug) + assert mapnik.logger.get_severity() == mapnik.severity_type.Debug mapnik.logger.set_severity(default) - eq_(mapnik.logger.get_severity(), default) - -if __name__ == "__main__": - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert mapnik.logger.get_severity() == default diff --git a/test/python_tests/mapnik_test_data_test.py b/test/python_tests/mapnik_test_data_test.py index c0efff684..a75306e74 100644 --- a/test/python_tests/mapnik_test_data_test.py +++ b/test/python_tests/mapnik_test_data_test.py @@ -1,29 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from __future__ import print_function - -import os +import os from glob import glob - import mapnik -from .utilities import execution_path, run_all - - -default_logging_severity = mapnik.logger.get_severity() - - -def setup(): - mapnik.logger.set_severity(getattr(mapnik.severity_type, "None")) - # All of the paths used are relative, if we run the tests - # from another directory we need to chdir() - os.chdir(execution_path('.')) - - -def teardown(): - mapnik.logger.set_severity(default_logging_severity) - plugin_mapping = { '.csv': ['csv'], '.json': ['geojson', 'ogr'], @@ -55,7 +33,7 @@ def test_opening_data(): else: for plugin in plugin_mapping[ext]: kwargs = {'type': plugin, 'file': filepath} - if plugin is 'ogr': + if plugin == 'ogr': kwargs['layer_by_index'] = 0 try: mapnik.Datasource(**kwargs) @@ -63,7 +41,3 @@ def test_opening_data(): print('could not open, %s: %s' % (kwargs, e)) # else: # print 'skipping opening %s' % filepath - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) diff --git a/test/python_tests/markers_complex_rendering_test.py b/test/python_tests/markers_complex_rendering_test.py index 652c4ac22..8c07f6b67 100644 --- a/test/python_tests/markers_complex_rendering_test.py +++ b/test/python_tests/markers_complex_rendering_test.py @@ -1,35 +1,29 @@ -# coding=utf8 -import os - -from nose.tools import eq_ - +import pytest import mapnik +import os +from .utilities import execution_path -from .utilities import execution_path, run_all - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield if 'csv' in mapnik.DatasourceCache.plugin_names(): - def test_marker_ellipse_render1(): + def test_marker_ellipse_render1(setup): m = mapnik.Map(256, 256) mapnik.load_map(m, '../data/good_maps/marker_ellipse_transform.xml') m.zoom_all() im = mapnik.Image(m.width, m.height) mapnik.render(m, im) actual = '/tmp/mapnik-marker-ellipse-render1.png' - expected = 'images/support/mapnik-marker-ellipse-render1.png' + expected = './images/support/mapnik-marker-ellipse-render1.png' im.save(actual, 'png32') if os.environ.get('UPDATE'): im.save(expected, 'png32') expected_im = mapnik.Image.open(expected) - eq_(im.tostring('png32'), - expected_im.tostring('png32'), - 'failed comparing actual (%s) and expected (%s)' % (actual, - 'test/python_tests/' + expected)) + assert im.to_string('png32') == expected_im.to_string('png32'), 'failed comparing actual (%s) and expected (%s)' % (actual, expected) def test_marker_ellipse_render2(): m = mapnik.Map(256, 256) @@ -38,16 +32,9 @@ def test_marker_ellipse_render2(): im = mapnik.Image(m.width, m.height) mapnik.render(m, im) actual = '/tmp/mapnik-marker-ellipse-render2.png' - expected = 'images/support/mapnik-marker-ellipse-render2.png' + expected = './images/support/mapnik-marker-ellipse-render2.png' im.save(actual, 'png32') if os.environ.get('UPDATE'): im.save(expected, 'png32') expected_im = mapnik.Image.open(expected) - eq_(im.tostring('png32'), - expected_im.tostring('png32'), - 'failed comparing actual (%s) and expected (%s)' % (actual, - 'test/python_tests/' + expected)) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert im.to_string('png32') == expected_im.to_string('png32'), 'failed comparing actual (%s) and expected (%s)' % (actual, expected) diff --git a/test/python_tests/memory_datasource_test.py b/test/python_tests/memory_datasource_test.py index d19dbb593..fbe1d8987 100644 --- a/test/python_tests/memory_datasource_test.py +++ b/test/python_tests/memory_datasource_test.py @@ -1,21 +1,15 @@ -# encoding: utf8 -from nose.tools import eq_ - import mapnik -from .utilities import run_all - - def test_add_feature(): md = mapnik.MemoryDatasource() - eq_(md.num_features(), 0) + assert md.num_features() == 0 context = mapnik.Context() context.push('foo') feature = mapnik.Feature(context, 1) feature['foo'] = 'bar' feature.geometry = mapnik.Geometry.from_wkt('POINT(2 3)') md.add_feature(feature) - eq_(md.num_features(), 1) + assert md.num_features() == 1 featureset = md.features_at_point(mapnik.Coord(2, 3)) retrieved = [] @@ -23,15 +17,12 @@ def test_add_feature(): for feat in featureset: retrieved.append(feat) - eq_(len(retrieved), 1) + assert len(retrieved) == 1 f = retrieved[0] - eq_(f['foo'], 'bar') + assert f['foo'] == 'bar' featureset = md.features_at_point(mapnik.Coord(20, 30)) retrieved = [] for feat in featureset: retrieved.append(feat) - eq_(len(retrieved), 0) - -if __name__ == "__main__": - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert len(retrieved) == 0 diff --git a/test/python_tests/multi_tile_raster_test.py b/test/python_tests/multi_tile_raster_test.py index 6e131d41a..b4b825b01 100644 --- a/test/python_tests/multi_tile_raster_test.py +++ b/test/python_tests/multi_tile_raster_test.py @@ -1,22 +1,18 @@ -#!/usr/bin/env python - import os - -from nose.tools import eq_ - import mapnik +import pytest +from .utilities import execution_path -from .utilities import execution_path, run_all - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield -def test_multi_tile_policy(): - srs = '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs' +def test_multi_tile_policy(setup): + srs = 'epsg:4326' lyr = mapnik.Layer('raster') if 'raster' in mapnik.DatasourceCache.plugin_names(): lyr.datasource = mapnik.Raster( @@ -35,7 +31,7 @@ def test_multi_tile_policy(): style = mapnik.Style() rule = mapnik.Rule() sym = mapnik.RasterSymbolizer() - rule.symbols.append(sym) + rule.symbolizers.append(sym) style.rules.append(rule) _map.append_style('foo', style) lyr.styles.append('foo') @@ -44,31 +40,25 @@ def test_multi_tile_policy(): im = mapnik.Image(_map.width, _map.height) mapnik.render(_map, im) - - # test green chunk - eq_(im.view(0, 64, 1, 1).tostring(), b'\x00\xff\x00\xff') - eq_(im.view(127, 64, 1, 1).tostring(), b'\x00\xff\x00\xff') - eq_(im.view(0, 127, 1, 1).tostring(), b'\x00\xff\x00\xff') - eq_(im.view(127, 127, 1, 1).tostring(), b'\x00\xff\x00\xff') + assert im.view(0, 64, 1, 1).to_string() == b'\x00\xff\x00\xff' + assert im.view(127, 64, 1, 1).to_string() == b'\x00\xff\x00\xff' + assert im.view(0, 127, 1, 1).to_string() == b'\x00\xff\x00\xff' + assert im.view(127, 127, 1, 1).to_string() == b'\x00\xff\x00\xff' # test blue chunk - eq_(im.view(128, 64, 1, 1).tostring(), b'\x00\x00\xff\xff') - eq_(im.view(255, 64, 1, 1).tostring(), b'\x00\x00\xff\xff') - eq_(im.view(128, 127, 1, 1).tostring(), b'\x00\x00\xff\xff') - eq_(im.view(255, 127, 1, 1).tostring(), b'\x00\x00\xff\xff') + assert im.view(128, 64, 1, 1).to_string() == b'\x00\x00\xff\xff' + assert im.view(255, 64, 1, 1).to_string() == b'\x00\x00\xff\xff' + assert im.view(128, 127, 1, 1).to_string() == b'\x00\x00\xff\xff' + assert im.view(255, 127, 1, 1).to_string() == b'\x00\x00\xff\xff' # test red chunk - eq_(im.view(0, 128, 1, 1).tostring(), b'\xff\x00\x00\xff') - eq_(im.view(127, 128, 1, 1).tostring(), b'\xff\x00\x00\xff') - eq_(im.view(0, 191, 1, 1).tostring(), b'\xff\x00\x00\xff') - eq_(im.view(127, 191, 1, 1).tostring(), b'\xff\x00\x00\xff') + assert im.view(0, 128, 1, 1).to_string() == b'\xff\x00\x00\xff' + assert im.view(127, 128, 1, 1).to_string() == b'\xff\x00\x00\xff' + assert im.view(0, 191, 1, 1).to_string() == b'\xff\x00\x00\xff' + assert im.view(127, 191, 1, 1).to_string() == b'\xff\x00\x00\xff' # test magenta chunk - eq_(im.view(128, 128, 1, 1).tostring(), b'\xff\x00\xff\xff') - eq_(im.view(255, 128, 1, 1).tostring(), b'\xff\x00\xff\xff') - eq_(im.view(128, 191, 1, 1).tostring(), b'\xff\x00\xff\xff') - eq_(im.view(255, 191, 1, 1).tostring(), b'\xff\x00\xff\xff') - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert im.view(128, 128, 1, 1).to_string() == b'\xff\x00\xff\xff' + assert im.view(255, 128, 1, 1).to_string() == b'\xff\x00\xff\xff' + assert im.view(128, 191, 1, 1).to_string() == b'\xff\x00\xff\xff' + assert im.view(255, 191, 1, 1).to_string() == b'\xff\x00\xff\xff' diff --git a/test/python_tests/my.pdf b/test/python_tests/my.pdf deleted file mode 100644 index 7d80dfdd8..000000000 Binary files a/test/python_tests/my.pdf and /dev/null differ diff --git a/test/python_tests/object_test.py b/test/python_tests/object_test.py index 583a523dc..e965c37f3 100644 --- a/test/python_tests/object_test.py +++ b/test/python_tests/object_test.py @@ -1,570 +1,275 @@ -# #!/usr/bin/env python -# # -*- coding: utf-8 -*- - -# import os -# from nose.tools import * -# from utilities import execution_path, run_all -# import tempfile - -# import mapnik - -# def setup(): -# # All of the paths used are relative, if we run the tests -# # from another directory we need to chdir() -# os.chdir(execution_path('.')) - -# def test_debug_symbolizer(): -# s = mapnik.DebugSymbolizer() -# eq_(s.mode,mapnik.debug_symbolizer_mode.collision) - -# def test_raster_symbolizer(): -# s = mapnik.RasterSymbolizer() -# eq_(s.comp_op,mapnik.CompositeOp.src_over) # note: mode is deprecated -# eq_(s.scaling,mapnik.scaling_method.NEAR) -# eq_(s.opacity,1.0) -# eq_(s.colorizer,None) -# eq_(s.filter_factor,-1) -# eq_(s.mesh_size,16) -# eq_(s.premultiplied,None) -# s.premultiplied = True -# eq_(s.premultiplied,True) - -# def test_line_pattern(): -# s = mapnik.LinePatternSymbolizer(mapnik.PathExpression('../data/images/dummy.png')) -# eq_(s.filename, '../data/images/dummy.png') -# eq_(s.smooth,0.0) -# eq_(s.transform,'') -# eq_(s.offset,0.0) -# eq_(s.comp_op,mapnik.CompositeOp.src_over) -# eq_(s.clip,True) - -# def test_line_symbolizer(): -# s = mapnik.LineSymbolizer() -# eq_(s.rasterizer, mapnik.line_rasterizer.FULL) -# eq_(s.smooth,0.0) -# eq_(s.comp_op,mapnik.CompositeOp.src_over) -# eq_(s.clip,True) -# eq_(s.stroke.width, 1) -# eq_(s.stroke.opacity, 1) -# eq_(s.stroke.color, mapnik.Color('black')) -# eq_(s.stroke.line_cap, mapnik.line_cap.BUTT_CAP) -# eq_(s.stroke.line_join, mapnik.line_join.MITER_JOIN) - -# l = mapnik.LineSymbolizer(mapnik.Color('blue'), 5.0) - -# eq_(l.stroke.width, 5) -# eq_(l.stroke.opacity, 1) -# eq_(l.stroke.color, mapnik.Color('blue')) -# eq_(l.stroke.line_cap, mapnik.line_cap.BUTT_CAP) -# eq_(l.stroke.line_join, mapnik.line_join.MITER_JOIN) - -# s = mapnik.Stroke(mapnik.Color('blue'), 5.0) -# l = mapnik.LineSymbolizer(s) - -# eq_(l.stroke.width, 5) -# eq_(l.stroke.opacity, 1) -# eq_(l.stroke.color, mapnik.Color('blue')) -# eq_(l.stroke.line_cap, mapnik.line_cap.BUTT_CAP) -# eq_(l.stroke.line_join, mapnik.line_join.MITER_JOIN) - -# def test_line_symbolizer_stroke_reference(): -# l = mapnik.LineSymbolizer(mapnik.Color('green'),0.1) -# l.stroke.add_dash(.1,.1) -# l.stroke.add_dash(.1,.1) -# eq_(l.stroke.get_dashes(), [(.1,.1),(.1,.1)]) -# eq_(l.stroke.color,mapnik.Color('green')) -# eq_(l.stroke.opacity,1.0) -# assert_almost_equal(l.stroke.width,0.1) - -# # https://github.com/mapnik/mapnik/issues/1427 -# def test_stroke_dash_api(): -# stroke = mapnik.Stroke() -# dashes = [(1.0,1.0)] -# stroke.dasharray = dashes -# eq_(stroke.dasharray, dashes) -# stroke.add_dash(.1,.1) -# dashes.append((.1,.1)) -# eq_(stroke.dasharray, dashes) - - -# def test_text_symbolizer(): -# s = mapnik.TextSymbolizer() -# eq_(s.comp_op,mapnik.CompositeOp.src_over) -# eq_(s.clip,True) -# eq_(s.halo_rasterizer,mapnik.halo_rasterizer.FULL) - -# # https://github.com/mapnik/mapnik/issues/1420 -# eq_(s.text_transform, mapnik.text_transform.NONE) - -# # old args required method -# ts = mapnik.TextSymbolizer(mapnik.Expression('[Field_Name]'), 'Font Name', 8, mapnik.Color('black')) -# # eq_(str(ts.name), str(mapnik2.Expression('[Field_Name]'))) name field is no longer supported -# eq_(ts.format.face_name, 'Font Name') -# eq_(ts.format.text_size, 8) -# eq_(ts.format.fill, mapnik.Color('black')) -# eq_(ts.properties.label_placement, mapnik.label_placement.POINT_PLACEMENT) -# eq_(ts.properties.horizontal_alignment, mapnik.horizontal_alignment.AUTO) - -# def test_shield_symbolizer_init(): -# s = mapnik.ShieldSymbolizer(mapnik.Expression('[Field Name]'), 'DejaVu Sans Bold', 6, mapnik.Color('#000000'), mapnik.PathExpression('../data/images/dummy.png')) -# eq_(s.comp_op,mapnik.CompositeOp.src_over) -# eq_(s.clip,True) -# eq_(s.displacement, (0.0,0.0)) -# eq_(s.allow_overlap, False) -# eq_(s.avoid_edges, False) -# eq_(s.character_spacing,0) -# #eq_(str(s.name), str(mapnik2.Expression('[Field Name]'))) name field is no longer supported -# eq_(s.face_name, 'DejaVu Sans Bold') -# eq_(s.allow_overlap, False) -# eq_(s.fill, mapnik.Color('#000000')) -# eq_(s.halo_fill, mapnik.Color('rgb(255,255,255)')) -# eq_(s.halo_radius, 0) -# eq_(s.label_placement, mapnik.label_placement.POINT_PLACEMENT) -# eq_(s.minimum_distance, 0.0) -# eq_(s.text_ratio, 0) -# eq_(s.text_size, 6) -# eq_(s.wrap_width, 0) -# eq_(s.vertical_alignment, mapnik.vertical_alignment.AUTO) -# eq_(s.label_spacing, 0) -# eq_(s.label_position_tolerance, 0) -# # 22.5 * M_PI/180.0 initialized by default -# assert_almost_equal(s.max_char_angle_delta, 0.39269908169872414) - -# eq_(s.text_transform, mapnik.text_transform.NONE) -# eq_(s.line_spacing, 0) -# eq_(s.character_spacing, 0) - -# # r1341 -# eq_(s.wrap_before, False) -# eq_(s.horizontal_alignment, mapnik.horizontal_alignment.AUTO) -# eq_(s.justify_alignment, mapnik.justify_alignment.AUTO) -# eq_(s.opacity, 1.0) - -# # r2300 -# eq_(s.minimum_padding, 0.0) - -# # was mixed with s.opacity -# eq_(s.text_opacity, 1.0) - -# eq_(s.shield_displacement, (0.0,0.0)) -# # TODO - the pattern in bindings seems to be to get/set -# # strings for PathExpressions... should we pass objects? -# eq_(s.filename, '../data/images/dummy.png') - -# # 11c34b1: default transform list is empty, not identity matrix -# eq_(s.transform, '') - -# eq_(s.fontset, None) - -# # ShieldSymbolizer missing image file -# # images paths are now PathExpressions are evaluated at runtime -# # so it does not make sense to throw... -# #@raises(RuntimeError) -# #def test_shieldsymbolizer_missing_image(): -# # s = mapnik.ShieldSymbolizer(mapnik.Expression('[Field Name]'), 'DejaVu Sans Bold', 6, mapnik.Color('#000000'), mapnik.PathExpression('../#data/images/broken.png')) - -# def test_shield_symbolizer_modify(): -# s = mapnik.ShieldSymbolizer(mapnik.Expression('[Field Name]'), 'DejaVu Sans Bold', 6, mapnik.Color('#000000'), mapnik.PathExpression('../data/images/dummy.png')) -# # transform expression -# def check_transform(expr, expect_str=None): -# s.transform = expr -# eq_(s.transform, expr if expect_str is None else expect_str) -# check_transform("matrix(1 2 3 4 5 6)", "matrix(1, 2, 3, 4, 5, 6)") -# check_transform("matrix(1, 2, 3, 4, 5, 6 +7)", "matrix(1, 2, 3, 4, 5, (6+7))") -# check_transform("rotate([a])") -# check_transform("rotate([a] -2)", "rotate(([a]-2))") -# check_transform("rotate([a] -2 -3)", "rotate([a], -2, -3)") -# check_transform("rotate([a] -2 -3 -4)", "rotate(((([a]-2)-3)-4))") -# check_transform("rotate([a] -2, 3, 4)", "rotate(([a]-2), 3, 4)") -# check_transform("translate([tx]) rotate([a])") -# check_transform("scale([sx], [sy]/2)") -# # TODO check expected failures - -# def test_point_symbolizer(): -# p = mapnik.PointSymbolizer() -# eq_(p.filename,'') -# eq_(p.transform,'') -# eq_(p.opacity,1.0) -# eq_(p.allow_overlap,False) -# eq_(p.ignore_placement,False) -# eq_(p.comp_op,mapnik.CompositeOp.src_over) -# eq_(p.placement, mapnik.point_placement.CENTROID) - -# p = mapnik.PointSymbolizer(mapnik.PathExpression("../data/images/dummy.png")) -# p.allow_overlap = True -# p.opacity = 0.5 -# p.ignore_placement = True -# p.placement = mapnik.point_placement.INTERIOR -# eq_(p.allow_overlap, True) -# eq_(p.opacity, 0.5) -# eq_(p.filename,'../data/images/dummy.png') -# eq_(p.ignore_placement,True) -# eq_(p.placement, mapnik.point_placement.INTERIOR) - -# def test_markers_symbolizer(): -# p = mapnik.MarkersSymbolizer() -# eq_(p.allow_overlap, False) -# eq_(p.opacity,1.0) -# eq_(p.fill_opacity,None) -# eq_(p.filename,'shape://ellipse') -# eq_(p.placement,mapnik.marker_placement.POINT_PLACEMENT) -# eq_(p.multi_policy,mapnik.marker_multi_policy.EACH) -# eq_(p.fill,None) -# eq_(p.ignore_placement,False) -# eq_(p.spacing,100) -# eq_(p.max_error,0.2) -# eq_(p.width,None) -# eq_(p.height,None) -# eq_(p.transform,'') -# eq_(p.clip,True) -# eq_(p.comp_op,mapnik.CompositeOp.src_over) - - -# p.width = mapnik.Expression('12') -# p.height = mapnik.Expression('12') -# eq_(str(p.width),'12') -# eq_(str(p.height),'12') - -# p.width = mapnik.Expression('[field] + 2') -# p.height = mapnik.Expression('[field] + 2') -# eq_(str(p.width),'([field]+2)') -# eq_(str(p.height),'([field]+2)') - -# stroke = mapnik.Stroke() -# stroke.color = mapnik.Color('black') -# stroke.width = 1.0 - -# p.stroke = stroke -# p.fill = mapnik.Color('white') -# p.allow_overlap = True -# p.opacity = 0.5 -# p.fill_opacity = 0.5 -# p.placement = mapnik.marker_placement.LINE_PLACEMENT -# p.multi_policy = mapnik.marker_multi_policy.WHOLE - -# eq_(p.allow_overlap, True) -# eq_(p.opacity, 0.5) -# eq_(p.fill_opacity, 0.5) -# eq_(p.multi_policy,mapnik.marker_multi_policy.WHOLE) -# eq_(p.placement,mapnik.marker_placement.LINE_PLACEMENT) - -# #https://github.com/mapnik/mapnik/issues/1285 -# #https://github.com/mapnik/mapnik/issues/1427 -# p.marker_type = 'arrow' -# eq_(p.marker_type,'shape://arrow') -# eq_(p.filename,'shape://arrow') - - -# # PointSymbolizer missing image file -# # images paths are now PathExpressions are evaluated at runtime -# # so it does not make sense to throw... -# #@raises(RuntimeError) -# #def test_pointsymbolizer_missing_image(): -# # p = mapnik.PointSymbolizer(mapnik.PathExpression("../data/images/broken.png")) - -# def test_polygon_symbolizer(): -# p = mapnik.PolygonSymbolizer() -# eq_(p.smooth,0.0) -# eq_(p.comp_op,mapnik.CompositeOp.src_over) -# eq_(p.clip,True) -# eq_(p.fill, mapnik.Color('gray')) -# eq_(p.fill_opacity, 1) - -# p = mapnik.PolygonSymbolizer(mapnik.Color('blue')) - -# eq_(p.fill, mapnik.Color('blue')) -# eq_(p.fill_opacity, 1) - -# def test_building_symbolizer_init(): -# p = mapnik.BuildingSymbolizer() - -# eq_(p.fill, mapnik.Color('gray')) -# eq_(p.fill_opacity, 1) -# eq_(p.height,None) - -# def test_group_symbolizer_init(): -# s = mapnik.GroupSymbolizer() - -# p = mapnik.GroupSymbolizerProperties() - -# l = mapnik.PairLayout() -# l.item_margin = 5.0 -# p.set_layout(l) - -# r = mapnik.GroupRule(mapnik.Expression("[name%1]")) -# r.append(mapnik.PointSymbolizer()) -# p.add_rule(r) -# s.symbolizer_properties = p - -# eq_(s.comp_op,mapnik.CompositeOp.src_over) - -# def test_stroke_init(): -# s = mapnik.Stroke() - -# eq_(s.width, 1) -# eq_(s.opacity, 1) -# eq_(s.color, mapnik.Color('black')) -# eq_(s.line_cap, mapnik.line_cap.BUTT_CAP) -# eq_(s.line_join, mapnik.line_join.MITER_JOIN) -# eq_(s.gamma,1.0) - -# s = mapnik.Stroke(mapnik.Color('blue'), 5.0) -# s.gamma = .5 - -# eq_(s.width, 5) -# eq_(s.opacity, 1) -# eq_(s.color, mapnik.Color('blue')) -# eq_(s.gamma, .5) -# eq_(s.line_cap, mapnik.line_cap.BUTT_CAP) -# eq_(s.line_join, mapnik.line_join.MITER_JOIN) - -# def test_stroke_dash_arrays(): -# s = mapnik.Stroke() -# s.add_dash(1,2) -# s.add_dash(3,4) -# s.add_dash(5,6) - -# eq_(s.get_dashes(), [(1,2),(3,4),(5,6)]) - -# def test_map_init(): -# m = mapnik.Map(256, 256) - -# eq_(m.width, 256) -# eq_(m.height, 256) -# eq_(m.srs, '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs') -# eq_(m.base, '') -# eq_(m.maximum_extent, None) -# eq_(m.background_image, None) -# eq_(m.background_image_comp_op, mapnik.CompositeOp.src_over) -# eq_(m.background_image_opacity, 1.0) - -# m = mapnik.Map(256, 256, '+proj=latlong') -# eq_(m.srs, '+proj=latlong') - -# def test_map_style_access(): -# m = mapnik.Map(256, 256) -# sty = mapnik.Style() -# m.append_style("style",sty) -# styles = list(m.styles) -# eq_(len(styles),1) -# eq_(styles[0][0],'style') -# # returns a copy so let's just check it is the right instance -# eq_(isinstance(styles[0][1],mapnik.Style),True) - -# def test_map_maximum_extent_modification(): -# m = mapnik.Map(256, 256) -# eq_(m.maximum_extent, None) -# m.maximum_extent = mapnik.Box2d() -# eq_(m.maximum_extent, mapnik.Box2d()) -# m.maximum_extent = None -# eq_(m.maximum_extent, None) - -# # Map initialization from string -# def test_map_init_from_string(): -# map_string = ''' -# -# -# My Style -# -# shape -# ../../demo/data/boundaries -# -# -# ''' - -# m = mapnik.Map(600, 300) -# eq_(m.base, '') -# try: -# mapnik.load_map_from_string(m, map_string) -# eq_(m.base, './') -# mapnik.load_map_from_string(m, map_string, False, "") # this "" will have no effect -# eq_(m.base, './') - -# tmp_dir = tempfile.gettempdir() -# try: -# mapnik.load_map_from_string(m, map_string, False, tmp_dir) -# except RuntimeError: -# pass # runtime error expected because shapefile path should be wrong and datasource will throw -# eq_(m.base, tmp_dir) # tmp_dir will be set despite the exception because load_map mostly worked -# m.base = 'foo' -# mapnik.load_map_from_string(m, map_string, True, ".") -# eq_(m.base, '.') -# except RuntimeError, e: -# # only test datasources that we have installed -# if not 'Could not create datasource' in str(e): -# raise RuntimeError(e) +import os +import tempfile +import mapnik +import pytest + +from .utilities import execution_path + +@pytest.fixture(scope="module") +def setup(): + # All of the paths used are relative, if we run the tests + # from another directory we need to chdir() + os.chdir(execution_path('.')) + yield + +def test_debug_symbolizer(setup): + s = mapnik.DebugSymbolizer() + s.mode = mapnik.debug_symbolizer_mode.COLLISION + assert s.mode == mapnik.debug_symbolizer_mode.COLLISION + +def test_raster_symbolizer(): + s = mapnik.RasterSymbolizer() + s.comp_op = mapnik.CompositeOp.src_over + s.scaling = mapnik.scaling_method.NEAR + s.opacity = 1.0 + s.mesh_size = 16 + + assert s.comp_op == mapnik.CompositeOp.src_over # note: mode is deprecated + assert s.scaling == mapnik.scaling_method.NEAR + assert s.opacity == 1.0 + assert s.colorizer == None + assert s.mesh_size == 16 + assert s.premultiplied == None + s.premultiplied = True + assert s.premultiplied == True + +def test_line_pattern(): + s = mapnik.LinePatternSymbolizer() + s.file = mapnik.PathExpression('../data/images/dummy.png') + assert str(s.file) == '../data/images/dummy.png' + +def test_map_init(): + m = mapnik.Map(256, 256) + assert m.width == 256 + assert m.height == 256 + assert m.srs == 'epsg:4326' + assert m.base == '' + assert m.maximum_extent == None + assert m.background_image == None + assert m.background_image_comp_op == mapnik.CompositeOp.src_over + assert m.background_image_opacity == 1.0 + m = mapnik.Map(256, 256, '+proj=latlong') + assert m.srs == '+proj=latlong' + +def test_map_style_access(): + m = mapnik.Map(256, 256) + sty = mapnik.Style() + m.append_style("style",sty) + styles = list(m.styles.items()) + assert len(styles) == 1 + assert styles[0][0] == 'style' + # returns a copy so let's just check it is the right instance + assert isinstance(styles[0][1],mapnik.Style) + +def test_map_maximum_extent_modification(): + m = mapnik.Map(256, 256) + assert m.maximum_extent == None + m.maximum_extent = mapnik.Box2d() + assert m.maximum_extent == mapnik.Box2d() + m.maximum_extent = None + assert m.maximum_extent == None + +# Map initialization from string +def test_map_init_from_string(): + map_string = ''' + + + My Style + + shape + ../../demo/data/boundaries + + + ''' + + m = mapnik.Map(600, 300) + assert m.base == '' + try: + mapnik.load_map_from_string(m, map_string) + assert m.base == './' + mapnik.load_map_from_string(m, map_string, False, "") # this "" will have no effect + assert m.base == './' + + tmp_dir = tempfile.gettempdir() + try: + mapnik.load_map_from_string(m, map_string, False, tmp_dir) + except RuntimeError: + pass # runtime error expected because shapefile path should be wrong and datasource will throw + assert m.base == tmp_dir # tmp_dir will be set despite the exception because load_map mostly worked + m.remove_all() + m.base = 'foo' + mapnik.load_map_from_string(m, map_string, True, ".") + assert m.base == '.' + except RuntimeError as e: + # only test datasources that we have installed + if not 'Could not create datasource' in str(e): + raise RuntimeError(e) # # Color initialization -# @raises(Exception) # Boost.Python.ArgumentError -# def test_color_init_errors(): -# c = mapnik.Color() +def test_color_init_errors(): + with pytest.raises(Exception): # Boost.Python.ArgumentError + c = mapnik.Color() -# @raises(RuntimeError) -# def test_color_init_errors(): -# c = mapnik.Color('foo') # mapnik config +def test_color_init_errors(): + with pytest.raises(RuntimeError): + c = mapnik.Color('foo') # mapnik config -# def test_color_init(): -# c = mapnik.Color('blue') +def test_color_init(): + c = mapnik.Color('blue') + assert c.a == 255 + assert c.r == 0 + assert c.g == 0 + assert c.b == 255 -# eq_(c.a, 255) -# eq_(c.r, 0) -# eq_(c.g, 0) -# eq_(c.b, 255) + assert c.to_hex_string() == '#0000ff' -# eq_(c.to_hex_string(), '#0000ff') + c = mapnik.Color('#f2eff9') -# c = mapnik.Color('#f2eff9') + assert c.a == 255 + assert c.r == 242 + assert c.g == 239 + assert c.b == 249 -# eq_(c.a, 255) -# eq_(c.r, 242) -# eq_(c.g, 239) -# eq_(c.b, 249) + assert c.to_hex_string() == '#f2eff9' -# eq_(c.to_hex_string(), '#f2eff9') + c = mapnik.Color('rgb(50%,50%,50%)') -# c = mapnik.Color('rgb(50%,50%,50%)') + assert c.a == 255 + assert c.r == 128 + assert c.g == 128 + assert c.b == 128 -# eq_(c.a, 255) -# eq_(c.r, 128) -# eq_(c.g, 128) -# eq_(c.b, 128) + assert c.to_hex_string() == '#808080' -# eq_(c.to_hex_string(), '#808080') + c = mapnik.Color(0, 64, 128) -# c = mapnik.Color(0, 64, 128) + assert c.a == 255 + assert c.r == 0 + assert c.g == 64 + assert c.b == 128 -# eq_(c.a, 255) -# eq_(c.r, 0) -# eq_(c.g, 64) -# eq_(c.b, 128) + assert c.to_hex_string() == '#004080' -# eq_(c.to_hex_string(), '#004080') + c = mapnik.Color(0, 64, 128, 192) -# c = mapnik.Color(0, 64, 128, 192) + assert c.a == 192 + assert c.r == 0 + assert c.g == 64 + assert c.b == 128 -# eq_(c.a, 192) -# eq_(c.r, 0) -# eq_(c.g, 64) -# eq_(c.b, 128) + assert c.to_hex_string() == '#004080c0' -# eq_(c.to_hex_string(), '#004080c0') +def test_color_equality(): -# def test_color_equality(): + c1 = mapnik.Color('blue') + c2 = mapnik.Color(0,0,255) + c3 = mapnik.Color('black') -# c1 = mapnik.Color('blue') -# c2 = mapnik.Color(0,0,255) -# c3 = mapnik.Color('black') + c3.r = 0 + c3.g = 0 + c3.b = 255 + c3.a = 255 -# c3.r = 0 -# c3.g = 0 -# c3.b = 255 -# c3.a = 255 + assert c1 == c2 + assert c1 == c3 -# eq_(c1, c2) -# eq_(c1, c3) + c1 = mapnik.Color(0, 64, 128) + c2 = mapnik.Color(0, 64, 128) + c3 = mapnik.Color(0, 0, 0) -# c1 = mapnik.Color(0, 64, 128) -# c2 = mapnik.Color(0, 64, 128) -# c3 = mapnik.Color(0, 0, 0) + c3.r = 0 + c3.g = 64 + c3.b = 128 -# c3.r = 0 -# c3.g = 64 -# c3.b = 128 + assert c1 == c2 + assert c1 == c3 -# eq_(c1, c2) -# eq_(c1, c3) + c1 = mapnik.Color(0, 64, 128, 192) + c2 = mapnik.Color(0, 64, 128, 192) + c3 = mapnik.Color(0, 0, 0, 255) -# c1 = mapnik.Color(0, 64, 128, 192) -# c2 = mapnik.Color(0, 64, 128, 192) -# c3 = mapnik.Color(0, 0, 0, 255) + c3.r = 0 + c3.g = 64 + c3.b = 128 + c3.a = 192 -# c3.r = 0 -# c3.g = 64 -# c3.b = 128 -# c3.a = 192 + assert c1 == c2 + assert c1 == c3 -# eq_(c1, c2) -# eq_(c1, c3) + c1 = mapnik.Color('rgb(50%,50%,50%)') + c2 = mapnik.Color(128, 128, 128, 255) + c3 = mapnik.Color('#808080') + c4 = mapnik.Color('gray') -# c1 = mapnik.Color('rgb(50%,50%,50%)') -# c2 = mapnik.Color(128, 128, 128, 255) -# c3 = mapnik.Color('#808080') -# c4 = mapnik.Color('gray') + assert c1 == c2 + assert c1 == c3 + assert c1 == c4 -# eq_(c1, c2) -# eq_(c1, c3) -# eq_(c1, c4) + c1 = mapnik.Color('hsl(0, 100%, 50%)') # red + c2 = mapnik.Color('hsl(120, 100%, 50%)') # lime + c3 = mapnik.Color('hsla(240, 100%, 50%, 0.5)') # semi-transparent solid blue -# c1 = mapnik.Color('hsl(0, 100%, 50%)') # red -# c2 = mapnik.Color('hsl(120, 100%, 50%)') # lime -# c3 = mapnik.Color('hsla(240, 100%, 50%, 0.5)') # semi-transparent solid -# blue + assert c1 == mapnik.Color('red') + assert c2 == mapnik.Color('lime') + assert c3, mapnik.Color(0,0,255 == 128) -# eq_(c1, mapnik.Color('red')) -# eq_(c2, mapnik.Color('lime')) -# eq_(c3, mapnik.Color(0,0,255,128)) +def test_rule_init(): + min_scale = 5 + max_scale = 10 -# def test_rule_init(): -# min_scale = 5 -# max_scale = 10 + r = mapnik.Rule() -# r = mapnik.Rule() + assert r.name == '' + assert r.min_scale == 0 + assert r.max_scale == float('inf') + assert r.has_else() == False + assert r.has_also() == False -# eq_(r.name, '') -# eq_(r.min_scale, 0) -# eq_(r.max_scale, float('inf')) -# eq_(r.has_else(), False) -# eq_(r.has_also(), False) + r = mapnik.Rule() -# r = mapnik.Rule() + r.set_else(True) + assert r.has_else() == True + assert r.has_also() == False -# r.set_else(True) -# eq_(r.has_else(), True) -# eq_(r.has_also(), False) + r = mapnik.Rule() -# r = mapnik.Rule() + r.set_also(True) + assert r.has_else() == False + assert r.has_also() == True -# r.set_also(True) -# eq_(r.has_else(), False) -# eq_(r.has_also(), True) + r = mapnik.Rule("Name") -# r = mapnik.Rule("Name") + assert r.name == 'Name' + assert r.min_scale == 0 + assert r.max_scale == float('inf') + assert r.has_else() == False + assert r.has_also() == False -# eq_(r.name, 'Name') -# eq_(r.min_scale, 0) -# eq_(r.max_scale, float('inf')) -# eq_(r.has_else(), False) -# eq_(r.has_also(), False) + r = mapnik.Rule("Name") -# r = mapnik.Rule("Name") + assert r.name == 'Name' + assert r.min_scale == 0 + assert r.max_scale == float('inf') + assert r.has_else() == False + assert r.has_also() == False -# eq_(r.name, 'Name') -# eq_(r.min_scale, 0) -# eq_(r.max_scale, float('inf')) -# eq_(r.has_else(), False) -# eq_(r.has_also(), False) + r = mapnik.Rule("Name", min_scale) -# r = mapnik.Rule("Name", min_scale) + assert r.name == 'Name' + assert r.min_scale == min_scale + assert r.max_scale == float('inf') + assert r.has_else() == False + assert r.has_also() == False -# eq_(r.name, 'Name') -# eq_(r.min_scale, min_scale) -# eq_(r.max_scale, float('inf')) -# eq_(r.has_else(), False) -# eq_(r.has_also(), False) + r = mapnik.Rule("Name", min_scale, max_scale) -# r = mapnik.Rule("Name", min_scale, max_scale) - -# eq_(r.name, 'Name') -# eq_(r.min_scale, min_scale) -# eq_(r.max_scale, max_scale) -# eq_(r.has_else(), False) -# eq_(r.has_also(), False) - -# if __name__ == "__main__": -# setup() -# run_all(eval(x) for x in dir() if x.startswith("test_")) + assert r.name == 'Name' + assert r.min_scale == min_scale + assert r.max_scale == max_scale + assert r.has_else() == False + assert r.has_also() == False diff --git a/test/python_tests/ogr_and_shape_geometries_test.py b/test/python_tests/ogr_and_shape_geometries_test.py index 04fd62430..1dd2e9219 100644 --- a/test/python_tests/ogr_and_shape_geometries_test.py +++ b/test/python_tests/ogr_and_shape_geometries_test.py @@ -1,12 +1,14 @@ -#!/usr/bin/env python - import os - -from nose.tools import eq_ - +import pytest import mapnik +from .utilities import execution_path -from .utilities import execution_path, run_all +@pytest.fixture(scope="module") +def setup(): + # All of the paths used are relative, if we run the tests + # from another directory we need to chdir() + os.chdir(execution_path('.')) + yield try: import itertools.izip as zip @@ -14,11 +16,6 @@ pass -def setup(): - # All of the paths used are relative, if we run the tests - # from another directory we need to chdir() - os.chdir(execution_path('.')) - # TODO - fix truncation in shapefile... polys = ["POLYGON ((30 10, 10 20, 20 40, 40 40, 30 10))", "POLYGON ((35 10, 10 20, 15 40, 45 45, 35 10),(20 30, 35 35, 30 20, 20 30))", @@ -32,22 +29,17 @@ def setup(): def ensure_geometries_are_interpreted_equivalently(filename): ds1 = mapnik.Ogr(file=filename, layer_by_index=0) ds2 = mapnik.Shapefile(file=filename) - fs1 = ds1.featureset() - fs2 = ds2.featureset() + fs1 = iter(ds1) + fs2 = iter(ds2) count = 0 for feat1, feat2 in zip(fs1, fs2): count += 1 - eq_(feat1.attributes, feat2.attributes) - # TODO - revisit this: https://github.com/mapnik/mapnik/issues/1093 - # eq_(feat1.to_geojson(),feat2.to_geojson()) - # eq_(feat1.geometries().to_wkt(),feat2.geometries().to_wkt()) - # eq_(feat1.geometries().to_wkb(mapnik.wkbByteOrder.NDR),feat2.geometries().to_wkb(mapnik.wkbByteOrder.NDR)) - # eq_(feat1.geometries().to_wkb(mapnik.wkbByteOrder.XDR),feat2.geometries().to_wkb(mapnik.wkbByteOrder.XDR)) + assert feat1.attributes == feat2.attributes + assert feat1.to_geojson() == feat2.to_geojson() + assert feat1.geometry.to_wkt() == feat2.geometry.to_wkt() + assert feat1.geometry.to_wkb(mapnik.wkbByteOrder.NDR) == feat2.geometry.to_wkb(mapnik.wkbByteOrder.NDR) + assert feat1.geometry.to_wkb(mapnik.wkbByteOrder.XDR) == feat2.geometry.to_wkb(mapnik.wkbByteOrder.XDR) - def test_simple_polys(): + def test_simple_polys(setup): ensure_geometries_are_interpreted_equivalently( '../data/shp/wkt_poly.shp') - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) diff --git a/test/python_tests/ogr_test.py b/test/python_tests/ogr_test.py index c0c81f56d..76548d6c4 100644 --- a/test/python_tests/ogr_test.py +++ b/test/python_tests/ogr_test.py @@ -1,89 +1,71 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os - -from nose.tools import assert_almost_equal, eq_, raises - import mapnik - -from .utilities import execution_path, run_all +import pytest try: import json except ImportError: import simplejson as json +from .utilities import execution_path +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield if 'ogr' in mapnik.DatasourceCache.plugin_names(): # Shapefile initialization - def test_shapefile_init(): + def test_shapefile_init(setup): ds = mapnik.Ogr(file='../data/shp/boundaries.shp', layer_by_index=0) e = ds.envelope() - assert_almost_equal(e.minx, -11121.6896651, places=7) - assert_almost_equal(e.miny, -724724.216526, places=6) - assert_almost_equal(e.maxx, 2463000.67866, places=5) - assert_almost_equal(e.maxy, 1649661.267, places=3) + assert e.minx == pytest.approx(-11121.6896651, abs=1e-7) + assert e.miny == pytest.approx(-724724.216526, abs=1e-6) + assert e.maxx == pytest.approx(2463000.67866, abs=1e-5) + assert e.maxy == pytest.approx(1649661.267, abs=1e-3) meta = ds.describe() - eq_(meta['geometry_type'], mapnik.DataGeometryType.Polygon) - eq_('+proj=lcc' in meta['proj4'], True) + assert meta['geometry_type'] == mapnik.DataGeometryType.Polygon + assert '+proj=lcc' in meta['proj4'] # Shapefile properties def test_shapefile_properties(): ds = mapnik.Ogr(file='../data/shp/boundaries.shp', layer_by_index=0) f = list(ds.features_at_point(ds.envelope().center(), 0.001))[0] - eq_(ds.geometry_type(), mapnik.DataGeometryType.Polygon) - - eq_(f['CGNS_FID'], u'6f733341ba2011d892e2080020a0f4c9') - eq_(f['COUNTRY'], u'CAN') - eq_(f['F_CODE'], u'FA001') - eq_(f['NAME_EN'], u'Quebec') - eq_(f['Shape_Area'], 1512185733150.0) - eq_(f['Shape_Leng'], 19218883.724300001) + assert ds.geometry_type() == mapnik.DataGeometryType.Polygon + + assert f['CGNS_FID'] == u'6f733341ba2011d892e2080020a0f4c9' + assert f['COUNTRY'] == u'CAN' + assert f['F_CODE'] == u'FA001' + assert f['NAME_EN'] == u'Quebec' + assert f['Shape_Area'] == 1512185733150.0 + assert f['Shape_Leng'] == 19218883.724300001 meta = ds.describe() - eq_(meta['geometry_type'], mapnik.DataGeometryType.Polygon) - # NOTE: encoding is latin1 but gdal >= 1.9 should now expose utf8 encoded features - # See SHAPE_ENCODING for overriding: http://gdal.org/ogr/drv_shapefile.html - # Failure for the NOM_FR field is expected for older gdal - #eq_(f['NOM_FR'], u'Qu\xe9bec') - #eq_(f['NOM_FR'], u'Québec') + assert meta['geometry_type'] == mapnik.DataGeometryType.Polygon + assert f['NOM_FR'] == u'Qu\xe9bec' + assert f['NOM_FR'] == u'Québec' - @raises(RuntimeError) def test_that_nonexistant_query_field_throws(**kwargs): - ds = mapnik.Ogr(file='../data/shp/world_merc.shp', layer_by_index=0) - eq_(len(ds.fields()), 11) - eq_(ds.fields(), ['FIPS', 'ISO2', 'ISO3', 'UN', 'NAME', - 'AREA', 'POP2005', 'REGION', 'SUBREGION', 'LON', 'LAT']) - eq_(ds.field_types(), - ['str', - 'str', - 'str', - 'int', - 'str', - 'int', - 'int', - 'int', - 'int', - 'float', - 'float']) - query = mapnik.Query(ds.envelope()) - for fld in ds.fields(): - query.add_property_name(fld) - # also add an invalid one, triggering throw - query.add_property_name('bogus') - ds.features(query) + with pytest.raises(RuntimeError): + ds = mapnik.Ogr(file='../data/shp/world_merc.shp', layer_by_index=0) + assert len(ds.fields()) == 11 + assert ds.fields() == ['FIPS', 'ISO2', 'ISO3', 'UN', 'NAME', + 'AREA', 'POP2005', 'REGION', 'SUBREGION', 'LON', 'LAT'] + assert ds.field_types() == ['str','str','str','int','str','int','int','int','int','float','float'] + query = mapnik.Query(ds.envelope()) + for fld in ds.fields(): + query.add_property_name(fld) + # also add an invalid one, triggering throw + query.add_property_name('bogus') + ds.features(query) # disabled because OGR prints an annoying error: ERROR 1: Invalid Point object. Missing 'coordinates' member. # def test_handling_of_null_features(): - # ds = mapnik.Ogr(file='../data/json/null_feature.geojson',layer_by_index=0) - # fs = ds.all_features() - # eq_(len(fs),1) + # ds = mapnik.Ogr(file='../data/json/null_feature.geojson',layer_by_index=0) + # fs = iter(ds) + # assert len(list(fs)) == 1 # OGR plugin extent parameter def test_ogr_extent_parameter(): @@ -92,24 +74,24 @@ def test_ogr_extent_parameter(): layer_by_index=0, extent='-1,-1,1,1') e = ds.envelope() - eq_(e.minx, -1) - eq_(e.miny, -1) - eq_(e.maxx, 1) - eq_(e.maxy, 1) + assert e.minx == -1 + assert e.miny == -1 + assert e.maxx == 1 + assert e.maxy == 1 meta = ds.describe() - eq_(meta['geometry_type'], mapnik.DataGeometryType.Polygon) - eq_('+proj=merc' in meta['proj4'], True) + assert meta['geometry_type'] == mapnik.DataGeometryType.Polygon + assert '+proj=merc' in meta['proj4'] def test_ogr_reading_gpx_waypoint(): ds = mapnik.Ogr(file='../data/gpx/empty.gpx', layer='waypoints') e = ds.envelope() - eq_(e.minx, -122) - eq_(e.miny, 48) - eq_(e.maxx, -122) - eq_(e.maxy, 48) + assert e.minx == -122 + assert e.miny == 48 + assert e.maxx == -122 + assert e.maxy == 48 meta = ds.describe() - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) - eq_('+proj=longlat' in meta['proj4'], True) + assert meta['geometry_type'] == mapnik.DataGeometryType.Point + assert '+proj=longlat' in meta['proj4'] def test_ogr_empty_data_should_not_throw(): default_logging_severity = mapnik.logger.get_severity() @@ -118,189 +100,102 @@ def test_ogr_empty_data_should_not_throw(): for layer in ['routes', 'tracks', 'route_points', 'track_points']: ds = mapnik.Ogr(file='../data/gpx/empty.gpx', layer=layer) e = ds.envelope() - eq_(e.minx, 0) - eq_(e.miny, 0) - eq_(e.maxx, 0) - eq_(e.maxy, 0) + assert e.minx == 0 + assert e.miny == 0 + assert e.maxx == 0 + assert e.maxy == 0 mapnik.logger.set_severity(default_logging_severity) meta = ds.describe() - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) - eq_('+proj=longlat' in meta['proj4'], True) + assert meta['geometry_type'] == mapnik.DataGeometryType.Point + assert '+proj=longlat' in meta['proj4'] # disabled because OGR prints an annoying error: ERROR 1: Invalid Point object. Missing 'coordinates' member. - # def test_handling_of_null_features(): - # ds = mapnik.Ogr(file='../data/json/null_feature.geojson',layer_by_index=0) - # fs = ds.all_features() - # eq_(len(fs),1) + def test_handling_of_null_features(): + assert True + ds = mapnik.Ogr(file='../data/json/null_feature.geojson',layer_by_index=0) + fs = iter(ds) + assert len(list(fs)) == 1 def test_geometry_type(): ds = mapnik.Ogr(file='../data/csv/wkt.csv', layer_by_index=0) e = ds.envelope() - assert_almost_equal(e.minx, 1.0, places=1) - assert_almost_equal(e.miny, 1.0, places=1) - assert_almost_equal(e.maxx, 45.0, places=1) - assert_almost_equal(e.maxy, 45.0, places=1) + assert e.minx == pytest.approx(1.0, abs=1e-1) + assert e.miny == pytest.approx(1.0, abs=1e-1) + assert e.maxx == pytest.approx(45.0, abs=1e-1) + assert e.maxy == pytest.approx(45.0, abs=1e-1) meta = ds.describe() - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) - #eq_('+proj=longlat' in meta['proj4'],True) - fs = ds.featureset() - feat = fs.next() + assert meta['geometry_type'] == mapnik.DataGeometryType.Point + fs = iter(ds) + feat = next(fs) actual = json.loads(feat.to_geojson()) - eq_(actual, - {u'geometry': {u'type': u'Point', - u'coordinates': [30, - 10]}, - u'type': u'Feature', - u'id': 2, - u'properties': {u'type': u'point', - u'WKT': u' POINT (30 10)'}}) - feat = fs.next() + assert actual == {u'geometry': {u'type': u'Point', + u'coordinates': [30,10]}, + u'type': u'Feature', + u'id': 2, + u'properties': {u'type': u'point', + u'WKT': u'POINT (30 10)'}} + feat = next(fs) actual = json.loads(feat.to_geojson()) - eq_(actual, - {u'geometry': {u'type': u'LineString', - u'coordinates': [[30, - 10], - [10, - 30], - [40, - 40]]}, - u'type': u'Feature', - u'id': 3, - u'properties': {u'type': u'linestring', - u'WKT': u' LINESTRING (30 10, 10 30, 40 40)'}}) - feat = fs.next() + assert actual == {u'geometry': {u'type': u'LineString', + u'coordinates': [[30,10],[10,30],[40,40]]}, + u'type': u'Feature', + u'id': 3, + u'properties': {u'type': u'linestring', + u'WKT': u'LINESTRING (30 10, 10 30, 40 40)'}} + feat = next(fs) actual = json.loads(feat.to_geojson()) - eq_(actual, - {u'geometry': {u'type': u'Polygon', - u'coordinates': [[[30, - 10], - [40, - 40], - [20, - 40], - [10, - 20], - [30, - 10]]]}, - u'type': u'Feature', - u'id': 4, - u'properties': {u'type': u'polygon', - u'WKT': u' POLYGON ((30 10, 10 20, 20 40, 40 40, 30 10))'}}) - feat = fs.next() + assert actual == {u'geometry': {u'type': u'Polygon', u'coordinates': [[[30,10],[40,40],[20,40],[10,20],[30,10]]]}, + u'type': u'Feature', + u'id': 4, + u'properties': {u'type': u'polygon', + u'WKT': u'POLYGON ((30 10, 10 20, 20 40, 40 40, 30 10))'}} + feat = next(fs) actual = json.loads(feat.to_geojson()) - eq_( - actual, { - u'geometry': { - u'type': u'Polygon', u'coordinates': [ - [ - [ - 35, 10], [ - 45, 45], [ - 15, 40], [ - 10, 20], [ - 35, 10]], [ - [ - 20, 30], [ - 35, 35], [ - 30, 20], [ - 20, 30]]]}, u'type': u'Feature', u'id': 5, u'properties': { - u'type': u'polygon', u'WKT': u' POLYGON ((35 10, 10 20, 15 40, 45 45, 35 10),(20 30, 35 35, 30 20, 20 30))'}}) - feat = fs.next() + assert actual == {u'geometry': {u'type': u'Polygon', u'coordinates': [[[35, 10],[45,45],[15,40],[10,20],[35,10]],[[20,30],[35,35],[30,20],[20,30]]]}, + u'type': u'Feature', + u'id': 5, + u'properties': { u'type': u'polygon', u'WKT': u'POLYGON ((35 10, 10 20, 15 40, 45 45, 35 10),(20 30, 35 35, 30 20, 20 30))'}} + feat = next(fs) actual = json.loads(feat.to_geojson()) - eq_(actual, - {u'geometry': {u'type': u'MultiPoint', - u'coordinates': [[10, - 40], - [40, - 30], - [20, - 20], - [30, - 10]]}, - u'type': u'Feature', - u'id': 6, - u'properties': {u'type': u'multipoint', - u'WKT': u' MULTIPOINT ((10 40), (40 30), (20 20), (30 10))'}}) - feat = fs.next() + assert actual == {u'geometry': {u'type': u'MultiPoint', + u'coordinates': [[10,40],[40,30],[20,20],[30,10]]}, + u'type': u'Feature', + u'id': 6, + u'properties': {u'type': u'multipoint', + u'WKT': u'MULTIPOINT ((10 40), (40 30), (20 20), (30 10))'}} + feat = next(fs) actual = json.loads(feat.to_geojson()) - eq_(actual, - {u'geometry': {u'type': u'MultiLineString', - u'coordinates': [[[10, - 10], - [20, - 20], - [10, - 40]], - [[40, - 40], - [30, - 30], - [40, - 20], - [30, - 10]]]}, - u'type': u'Feature', - u'id': 7, - u'properties': {u'type': u'multilinestring', - u'WKT': u' MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))'}}) - feat = fs.next() + assert actual == {u'geometry': {u'type': u'MultiLineString', + u'coordinates': [[[10,10],[20,20],[10,40]],[[40,40],[30,30],[40,20],[30,10]]]}, + u'type': u'Feature', + u'id': 7, + u'properties': {u'type': u'multilinestring', + u'WKT': u'MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))'}} + feat = next(fs) actual = json.loads(feat.to_geojson()) - eq_(actual, - {u'geometry': {u'type': u'MultiPolygon', - u'coordinates': [[[[30, - 20], - [45, - 40], - [10, - 40], - [30, - 20]]], - [[[15, - 5], - [40, - 10], - [10, - 20], - [5, - 10], - [15, - 5]]]]}, - u'type': u'Feature', - u'id': 8, - u'properties': {u'type': u'multipolygon', - u'WKT': u' MULTIPOLYGON (((30 20, 10 40, 45 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5)))'}}) - feat = fs.next() + assert actual == {u'geometry': {u'type': u'MultiPolygon', + u'coordinates': [[[[30,20],[45,40],[10,40],[30,20]]],[[[15,5],[40,10],[10,20],[5,10],[15,5]]]]}, + u'type': u'Feature', + u'id': 8, + u'properties': {u'type': u'multipolygon', + u'WKT': u'MULTIPOLYGON (((30 20, 10 40, 45 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5)))'}} + feat = next(fs) actual = json.loads(feat.to_geojson()) - eq_(actual, {u'geometry': {u'type': u'MultiPolygon', u'coordinates': [[[[40, 40], [20, 45], [45, 30], [40, 40]]], [[[20, 35], [10, 30], [10, 10], [30, 5], [45, 20], [20, 35]], [[30, 20], [20, 15], [20, 25], [ - 30, 20]]]]}, u'type': u'Feature', u'id': 9, u'properties': {u'type': u'multipolygon', u'WKT': u' MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),((20 35, 45 20, 30 5, 10 10, 10 30, 20 35),(30 20, 20 25, 20 15, 30 20)))'}}) - feat = fs.next() + assert actual == {u'geometry': {u'type': u'MultiPolygon', + u'coordinates': [[[[40, 40], [20, 45], [45, 30], [40, 40]]], [[[20, 35], [10, 30], [10, 10], [30, 5], [45, 20], [20, 35]], [[30, 20], [20, 15], [20, 25], [30, 20]]]]}, + u'type': u'Feature', + u'id': 9, + u'properties': {u'type': u'multipolygon', u'WKT': u'MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),((20 35, 45 20, 30 5, 10 10, 10 30, 20 35),(30 20, 20 25, 20 15, 30 20)))'}} + feat = next(fs) actual = json.loads(feat.to_geojson()) - eq_(actual, - {u'geometry': {u'type': u'GeometryCollection', - u'geometries': [{u'type': u'Polygon', - u'coordinates': [[[1, - 1], - [2, - 1], - [2, - 2], - [1, - 2], - [1, - 1]]]}, - {u'type': u'Point', - u'coordinates': [2, - 3]}, - {u'type': u'LineString', - u'coordinates': [[2, - 3], - [3, - 4]]}]}, - u'type': u'Feature', - u'id': 10, - u'properties': {u'type': u'collection', - u'WKT': u' GEOMETRYCOLLECTION(POLYGON((1 1,2 1,2 2,1 2,1 1)),POINT(2 3),LINESTRING(2 3,3 4))'}}) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert actual == {u'geometry': {u'type': u'GeometryCollection', + u'geometries': [{u'type': u'Polygon', + u'coordinates': [[[1, 1],[2,1],[2,2],[1,2],[1,1]]]}, + {u'type': u'Point', + u'coordinates': [2,3]}, + {u'type': u'LineString', + u'coordinates': [[2,3],[3,4]]}]}, + u'type': u'Feature', + u'id': 10, + u'properties': {u'type': u'collection', + u'WKT': u'GEOMETRYCOLLECTION(POLYGON((1 1,2 1,2 2,1 2,1 1)),POINT(2 3),LINESTRING(2 3,3 4))'}} diff --git a/test/python_tests/osm_test.py b/test/python_tests/osm_test.py deleted file mode 100644 index ef39f4ab5..000000000 --- a/test/python_tests/osm_test.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os - -from nose.tools import eq_ - -import mapnik - -from .utilities import execution_path, run_all - - -def setup(): - # All of the paths used are relative, if we run the tests - # from another directory we need to chdir() - os.chdir(execution_path('.')) - -if 'osm' in mapnik.DatasourceCache.plugin_names(): - - # osm initialization - def test_osm_init(): - ds = mapnik.Osm(file='../data/osm/nodes.osm') - - e = ds.envelope() - - # these are hardcoded in the plugin… ugh - eq_(e.minx >= -180.0, True) - eq_(e.miny >= -90.0, True) - eq_(e.maxx <= 180.0, True) - eq_(e.maxy <= 90, True) - - def test_that_nonexistant_query_field_throws(**kwargs): - ds = mapnik.Osm(file='../data/osm/nodes.osm') - eq_(len(ds.fields()), 0) - query = mapnik.Query(ds.envelope()) - for fld in ds.fields(): - query.add_property_name(fld) - # also add an invalid one, triggering throw - query.add_property_name('bogus') - ds.features(query) - - def test_that_64bit_int_fields_work(): - ds = mapnik.Osm(file='../data/osm/64bit.osm') - eq_(len(ds.fields()), 4) - eq_(ds.fields(), ['bigint', 'highway', 'junction', 'note']) - eq_(ds.field_types(), ['str', 'str', 'str', 'str']) - fs = ds.featureset() - feat = fs.next() - eq_(feat.to_geojson( - ), '{"type":"Feature","id":890,"geometry":{"type":"Point","coordinates":[-61.7960248,17.1415874]},"properties":{}}') - eq_(feat.id(), 4294968186) - eq_(feat['bigint'], None) - feat = fs.next() - eq_(feat['bigint'], '9223372036854775807') - - def test_reading_ways(): - ds = mapnik.Osm(file='../data/osm/ways.osm') - eq_(len(ds.fields()), 0) - eq_(ds.fields(), []) - eq_(ds.field_types(), []) - feat = ds.all_features()[4] - eq_(feat.to_geojson( - ), '{"type":"Feature","id":1,"geometry":{"type":"LineString","coordinates":[[0,2],[0,-2]]},"properties":{}}') - eq_(feat.id(), 1) - - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) diff --git a/test/python_tests/palette_test.py b/test/python_tests/palette_test.py index 9913f81f2..98dbb6623 100644 --- a/test/python_tests/palette_test.py +++ b/test/python_tests/palette_test.py @@ -1,22 +1,14 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os -import sys - -from nose.tools import eq_ - +import sys, os import mapnik +import pytest +from .utilities import execution_path -from .utilities import execution_path, run_all - -PYTHON3 = sys.version_info[0] == 3 - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield expected_64 = '[Palette 64 colors #494746 #c37631 #89827c #d1955c #7397b9 #fc9237 #a09f9c #fbc147 #9bb3ce #b7c9a1 #b5d29c #c4b9aa #cdc4a5 #d5c8a3 #c1d7aa #ccc4b6 #dbd19c #b2c4d5 #eae487 #c9c8c6 #e4db99 #c9dcb5 #dfd3ac #cbd2c2 #d6cdbc #dbd2b6 #c0ceda #ece597 #f7ef86 #d7d3c3 #dfcbc3 #d1d0cd #d1e2bf #d3dec1 #dbd3c4 #e6d8b6 #f4ef91 #d3d3cf #cad5de #ded7c9 #dfdbce #fcf993 #ffff8a #dbd9d7 #dbe7cd #d4dce2 #e4ded3 #ebe3c9 #e0e2e2 #f4edc3 #fdfcae #e9e5dc #f4edda #eeebe4 #fefdc5 #e7edf2 #edf4e5 #f2efe9 #f6ede7 #fefedd #f6f4f0 #f1f5f8 #fbfaf8 #ffffff]' @@ -25,18 +17,15 @@ def setup(): expected_rgb = '[Palette 2 colors #ff00ff #ffffff]' -def test_reading_palettes(): +def test_reading_palettes(setup): with open('../data/palettes/palette64.act', 'rb') as act: palette = mapnik.Palette(act.read(), 'act') - eq_(palette.to_string(), expected_64) + assert palette.to_string() == expected_64 with open('../data/palettes/palette256.act', 'rb') as act: palette = mapnik.Palette(act.read(), 'act') - eq_(palette.to_string(), expected_256) - if PYTHON3: - palette = mapnik.Palette(b'\xff\x00\xff\xff\xff\xff', 'rgb') - else: - palette = mapnik.Palette('\xff\x00\xff\xff\xff\xff', 'rgb') - eq_(palette.to_string(), expected_rgb) + assert palette.to_string() == expected_256 + palette = mapnik.Palette(b'\xff\x00\xff\xff\xff\xff', 'rgb') + assert palette.to_string() == expected_rgb if 'shape' in mapnik.DatasourceCache.plugin_names(): @@ -50,25 +39,18 @@ def test_render_with_palette(): palette = mapnik.Palette(act.read(), 'act') # test saving directly to filesystem im.save('/tmp/mapnik-palette-test.png', 'png', palette) - expected = './images/support/mapnik-palette-test.png' + expected = 'images/support/mapnik-palette-test.png' if os.environ.get('UPDATE'): im.save(expected, "png", palette) # test saving to a string with open('/tmp/mapnik-palette-test2.png', 'wb') as f: - f.write(im.tostring('png', palette)) + f.write(im.to_string('png', palette)) # compare the two methods - eq_(mapnik.Image.open('/tmp/mapnik-palette-test.png').tostring('png32'), - mapnik.Image.open( - '/tmp/mapnik-palette-test2.png').tostring('png32'), - '%s not eq to %s' % ('/tmp/mapnik-palette-test.png', - '/tmp/mapnik-palette-test2.png')) + im1 = mapnik.Image.open('/tmp/mapnik-palette-test.png') + im2 = mapnik.Image.open('/tmp/mapnik-palette-test2.png') + assert im1.to_string('png32') == im1.to_string('png32'),'%s not eq to %s' % ('/tmp/mapnik-palette-test.png', + '/tmp/mapnik-palette-test2.png') # compare to expected - eq_(mapnik.Image.open('/tmp/mapnik-palette-test.png').tostring('png32'), - mapnik.Image.open(expected).tostring('png32'), - '%s not eq to %s' % ('/tmp/mapnik-palette-test.png', - expected)) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert im1.to_string('png32') == mapnik.Image.open(expected).to_string('png32'), '%s not eq to %s' % ('/tmp/mapnik-palette-test.png', + expected) diff --git a/test/python_tests/parameters_test.py b/test/python_tests/parameters_test.py index f6e25b848..66507f044 100644 --- a/test/python_tests/parameters_test.py +++ b/test/python_tests/parameters_test.py @@ -1,71 +1,46 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os import sys - -from nose.tools import eq_ - import mapnik -from .utilities import execution_path, run_all - - -def setup(): - os.chdir(execution_path('.')) - - def test_parameter_null(): - p = mapnik.Parameter('key', None) - eq_(p[0], 'key') - eq_(p[1], None) + p = mapnik.Parameters() + p['key'] = None + assert p['key'] is None def test_parameter_string(): - p = mapnik.Parameter('key', 'value') - eq_(p[0], 'key') - eq_(p[1], 'value') + p = mapnik.Parameters() + p['key'] = 'value' + assert p['key'] == 'value' def test_parameter_unicode(): - p = mapnik.Parameter('key', u'value') - eq_(p[0], 'key') - eq_(p[1], u'value') + p = mapnik.Parameters() + p['key'] = u'value' + assert p['key'] == u'value' def test_parameter_integer(): - p = mapnik.Parameter('int', sys.maxsize) - eq_(p[0], 'int') - eq_(p[1], sys.maxsize) + p = mapnik.Parameters() + p['int'] = sys.maxsize + assert p['int'] == sys.maxsize def test_parameter_double(): - p = mapnik.Parameter('double', float(sys.maxsize)) - eq_(p[0], 'double') - eq_(p[1], float(sys.maxsize)) + p = mapnik.Parameters() + p['double'] = float(sys.maxsize) + assert p['double'] == float(sys.maxsize) def test_parameter_boolean(): - p = mapnik.Parameter('boolean', True) - eq_(p[0], 'boolean') - eq_(p[1], True) - eq_(bool(p[1]), True) + p = mapnik.Parameters() + p['boolean'] = True + assert p['boolean'] == True + assert bool(p['boolean']) == True def test_parameters(): - params = mapnik.Parameters() - p = mapnik.Parameter('float', 1.0777) - eq_(p[0], 'float') - eq_(p[1], 1.0777) - - params.append(p) - - eq_(params[0][0], 'float') - eq_(params[0][1], 1.0777) - - eq_(params.get('float'), 1.0777) - - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + p = mapnik.Parameters() + p['float'] = 1.0777 + p['int'] = 123456789 + assert p['float'] == 1.0777 + assert p['int'] == 123456789 diff --git a/test/python_tests/pdf_printing_test.py b/test/python_tests/pdf_printing_test.py index 45862376b..3240231c0 100644 --- a/test/python_tests/pdf_printing_test.py +++ b/test/python_tests/pdf_printing_test.py @@ -1,57 +1,50 @@ -#!/usr/bin/env python - -import os - -from nose.tools import eq_ - import mapnik -from .utilities import execution_path, run_all +import os +import pytest +from .utilities import execution_path +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield def make_map_from_xml(source_xml): - m = mapnik.Map(100, 100) - mapnik.load_map(m, source_xml, True) - m.zoom_all() - - return m + m = mapnik.Map(100, 100) + mapnik.load_map(m, source_xml, True) + m.zoom_all() + return m def make_pdf(m, output_pdf, esri_wkt): - # renders a PDF with a grid and a legend - page = mapnik.printing.PDFPrinter(use_ocg_layers=True) + # renders a PDF with a grid and a legend + page = mapnik.printing.PDFPrinter(use_ocg_layers=True) - page.render_map(m, output_pdf) - page.render_grid_on_map(m) - page.render_legend(m) + page.render_map(m, output_pdf) + page.render_grid_on_map(m) + page.render_legend(m) - page.finish() - page.add_geospatial_pdf_header(m, output_pdf, wkt=esri_wkt) + page.finish() + page.add_geospatial_pdf_header(m, output_pdf, wkt=esri_wkt) if mapnik.has_pycairo(): - import mapnik.printing + import mapnik.printing - def test_pdf_printing(): - source_xml = '../data/good_maps/marker-text-line.xml'.encode('utf-8') - m = make_map_from_xml(source_xml) + def test_pdf_printing(setup): + source_xml = '../data/good_maps/marker-text-line.xml'.encode('utf-8') + m = make_map_from_xml(source_xml) - actual_pdf = "/tmp/pdf-printing-actual.pdf" - esri_wkt = 'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]' - make_pdf(m, actual_pdf, esri_wkt) + actual_pdf = "/tmp/pdf-printing-actual.pdf" + esri_wkt = 'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]' + make_pdf(m, actual_pdf, esri_wkt) - expected_pdf = 'images/pycairo/pdf-printing-expected.pdf' + expected_pdf = 'images/pycairo/pdf-printing-expected.pdf' - diff = abs(os.stat(expected_pdf).st_size - os.stat(actual_pdf).st_size) - msg = 'diff in size (%s) between actual (%s) and expected(%s)' % (diff, actual_pdf, 'tests/python_tests/' + expected_pdf) - eq_(diff < 1500, True, msg) + diff = abs(os.stat(expected_pdf).st_size - os.stat(actual_pdf).st_size) + msg = 'diff in size (%s) between actual (%s) and expected(%s)' % (diff, actual_pdf, 'tests/python_tests/' + expected_pdf) + assert diff < 1500, msg # TODO: ideas for further testing on printing module # - test with and without pangocairo # - test legend with attribution # - test graticule (bug at the moment) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) diff --git a/test/python_tests/pgraster_test.py b/test/python_tests/pgraster_test.py index 2bb438305..e60e71d8e 100644 --- a/test/python_tests/pgraster_test.py +++ b/test/python_tests/pgraster_test.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - import atexit import os import re @@ -7,12 +5,9 @@ import time from binascii import hexlify from subprocess import PIPE, Popen - -from nose.tools import assert_almost_equal, eq_ - import mapnik - -from .utilities import execution_path, run_all, side_by_side_image +import pytest +from .utilities import execution_path, side_by_side_image MAPNIK_TEST_DBNAME = 'mapnik-tmp-pgraster-test-db' POSTGIS_TEMPLATE_DBNAME = 'template_postgis' @@ -23,13 +18,12 @@ def log(msg): if DEBUG_OUTPUT: print(msg) - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) - def call(cmd, silent=False): stdin, stderr = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE).communicate() @@ -134,17 +128,19 @@ def drop_imported(tabname, overview): def compare_images(expected, im): + cur_dir = os.path.dirname(os.path.realpath(__file__)) expected = os.path.join( os.path.dirname(expected), os.path.basename(expected).replace( ':', '_')) + expected = os.path.join(cur_dir, expected) if not os.path.exists(expected) or os.environ.get('UPDATE'): print('generating expected image %s' % expected) im.save(expected, 'png32') expected_im = mapnik.Image.open(expected) diff = expected.replace('.png', '-diff.png') - if len(im.tostring("png32")) != len(expected_im.tostring("png32")): + if len(im.to_string("png32")) != len(expected_im.to_string("png32")): compared = side_by_side_image(expected_im, im) compared.save(diff) assert False, 'images do not match, check diff at %s' % diff @@ -171,9 +167,9 @@ def _test_dataraster_16bsi_rendering(lbl, overview, rescale, clip): ds = mapnik.PgRaster(dbname=MAPNIK_TEST_DBNAME, table='"dataRaster"', band=1, use_overviews=1 if overview else 0, prescale_rasters=rescale, clip_rasters=clip) - fs = ds.featureset() - feature = fs.next() - eq_(feature['rid'], 1) + fs = iter(ds) + feature = next(fs) + assert feature['rid'] == 1 lyr = mapnik.Layer('dataraster_16bsi') lyr.datasource = ds expenv = mapnik.Box2d(-14637, 3903178, 1126863, 4859678) @@ -188,10 +184,10 @@ def _test_dataraster_16bsi_rendering(lbl, overview, rescale, clip): pixsize = 500 # see gdalinfo dataraster.tif pixsize = 2497 # see gdalinfo dataraster-small.tif tol = pixsize * max(overview.split(',')) if overview else 0 - assert_almost_equal(env.minx, expenv.minx) - assert_almost_equal(env.miny, expenv.miny, delta=tol) - assert_almost_equal(env.maxx, expenv.maxx, delta=tol) - assert_almost_equal(env.maxy, expenv.maxy) + assert env.minx == expenv.minx + assert env.miny == pytest.approx(expenv.miny,1.0e-7) + assert env.maxx == pytest.approx(expenv.maxx,1.0e-7) + assert env.maxy == expenv.maxy mm = mapnik.Map(256, 256) style = mapnik.Style() col = mapnik.RasterColorizer() @@ -202,7 +198,7 @@ def _test_dataraster_16bsi_rendering(lbl, overview, rescale, clip): sym = mapnik.RasterSymbolizer() sym.colorizer = col rule = mapnik.Rule() - rule.symbols.append(sym) + rule.symbolizers.append(sym) style.rules.append(rule) mm.append_style('foo', style) lyr.styles.append('foo') @@ -214,18 +210,18 @@ def _test_dataraster_16bsi_rendering(lbl, overview, rescale, clip): lap = time.time() - t0 log('T ' + str(lap) + ' -- ' + lbl + ' E:full') # no data - eq_(im.view(1, 1, 1, 1).tostring(), b'\x00\x00\x00\x00') - eq_(im.view(255, 255, 1, 1).tostring(), b'\x00\x00\x00\x00') - eq_(im.view(195, 116, 1, 1).tostring(), b'\x00\x00\x00\x00') + assert im.view(1, 1, 1, 1).to_string() == b'\x00\x00\x00\x00' + assert im.view(255, 255, 1, 1).to_string() == b'\x00\x00\x00\x00' + assert im.view(195, 116, 1, 1).to_string() == b'\x00\x00\x00\x00' # A0A0A0 - eq_(im.view(100, 120, 1, 1).tostring(), b'\xa0\xa0\xa0\xff') - eq_(im.view(75, 80, 1, 1).tostring(), b'\xa0\xa0\xa0\xff') + assert im.view(100, 120, 1, 1).to_string() == b'\xa0\xa0\xa0\xff' + assert im.view(75, 80, 1, 1).to_string() == b'\xa0\xa0\xa0\xff' # 808080 - eq_(im.view(74, 170, 1, 1).tostring(), b'\x80\x80\x80\xff') - eq_(im.view(30, 50, 1, 1).tostring(), b'\x80\x80\x80\xff') + assert im.view(74, 170, 1, 1).to_string() == b'\x80\x80\x80\xff' + assert im.view(30, 50, 1, 1).to_string() == b'\x80\x80\x80\xff' # 404040 - eq_(im.view(190, 70, 1, 1).tostring(), b'\x40\x40\x40\xff') - eq_(im.view(140, 170, 1, 1).tostring(), b'\x40\x40\x40\xff') + assert im.view(190, 70, 1, 1).to_string() == b'\x40\x40\x40\xff' + assert im.view(140, 170, 1, 1).to_string() == b'\x40\x40\x40\xff' # Now zoom over a portion of the env (1/10) newenv = mapnik.Box2d(273663, 4024478, 330738, 4072303) @@ -235,20 +231,20 @@ def _test_dataraster_16bsi_rendering(lbl, overview, rescale, clip): lap = time.time() - t0 log('T ' + str(lap) + ' -- ' + lbl + ' E:1/10') # nodata - eq_(hexlify(im.view(255, 255, 1, 1).tostring()), b'00000000') - eq_(hexlify(im.view(200, 254, 1, 1).tostring()), b'00000000') + assert hexlify(im.view(255, 255, 1, 1).to_string()) == b'00000000' + assert hexlify(im.view(200, 254, 1, 1).to_string()) == b'00000000' # A0A0A0 - eq_(hexlify(im.view(90, 232, 1, 1).tostring()), b'a0a0a0ff') - eq_(hexlify(im.view(96, 245, 1, 1).tostring()), b'a0a0a0ff') + assert hexlify(im.view(90, 232, 1, 1).to_string()) == b'a0a0a0ff' + assert hexlify(im.view(96, 245, 1, 1).to_string()) == b'a0a0a0ff' # 808080 - eq_(hexlify(im.view(1, 1, 1, 1).tostring()), b'808080ff') - eq_(hexlify(im.view(128, 128, 1, 1).tostring()), b'808080ff') + assert hexlify(im.view(1, 1, 1, 1).to_string()) == b'808080ff' + assert hexlify(im.view(128, 128, 1, 1).to_string()) == b'808080ff' # 404040 - eq_(hexlify(im.view(255, 0, 1, 1).tostring()), b'404040ff') + assert hexlify(im.view(255, 0, 1, 1).to_string()) == b'404040ff' def _test_dataraster_16bsi(lbl, tilesize, constraint, overview): import_raster( - '../data/raster/dataraster-small.tif', + os.path.join(os.path.dirname(os.path.realpath(__file__)),'../../test/data/raster/dataraster-small.tif'), 'dataRaster', tilesize, constraint, @@ -273,18 +269,18 @@ def test_dataraster_16bsi(): _test_dataraster_16bsi( 'data_16bsi', tilesize, constraint, overview) - # river.tiff, RGBA 8BUI + # # river.tiff, RGBA 8BUI def _test_rgba_8bui_rendering(lbl, overview, rescale, clip): if rescale: lbl += ' Sc' if clip: lbl += ' Cl' - ds = mapnik.PgRaster(dbname=MAPNIK_TEST_DBNAME, table='(select * from "River") foo', + ds = mapnik.PgRaster(dbname=MAPNIK_TEST_DBNAME, table='(select * from "River" order by rid) foo', use_overviews=1 if overview else 0, prescale_rasters=rescale, clip_rasters=clip) - fs = ds.featureset() - feature = fs.next() - eq_(feature['rid'], 1) + fs = iter(ds) + feature = next(fs) + assert feature['rid'] == 1 lyr = mapnik.Layer('rgba_8bui') lyr.datasource = ds expenv = mapnik.Box2d(0, -210, 256, 0) @@ -298,15 +294,15 @@ def _test_rgba_8bui_rendering(lbl, overview, rescale, clip): # NOTE: the overview table extent only grows north and east pixsize = 1 # see gdalinfo river.tif tol = pixsize * max(overview.split(',')) if overview else 0 - assert_almost_equal(env.minx, expenv.minx) - assert_almost_equal(env.miny, expenv.miny, delta=tol) - assert_almost_equal(env.maxx, expenv.maxx, delta=tol) - assert_almost_equal(env.maxy, expenv.maxy) + assert env.minx == expenv.minx + assert env.miny == expenv.miny + assert env.maxx == expenv.maxx + assert env.maxy == expenv.maxy mm = mapnik.Map(256, 256) style = mapnik.Style() sym = mapnik.RasterSymbolizer() rule = mapnik.Rule() - rule.symbols.append(sym) + rule.symbolizers.append(sym) style.rules.append(rule) mm.append_style('foo', style) lyr.styles.append('foo') @@ -317,16 +313,16 @@ def _test_rgba_8bui_rendering(lbl, overview, rescale, clip): mapnik.render(mm, im) lap = time.time() - t0 log('T ' + str(lap) + ' -- ' + lbl + ' E:full') - expected = 'images/support/pgraster/%s-%s-%s-%s-box1.png' % ( + expected = './images/support/pgraster/%s-%s-%s-%s-box1.png' % ( lyr.name, lbl, overview, clip) compare_images(expected, im) # no data - eq_(hexlify(im.view(3, 3, 1, 1).tostring()), b'00000000') - eq_(hexlify(im.view(250, 250, 1, 1).tostring()), b'00000000') + assert hexlify(im.view(3, 3, 1, 1).to_string()) == b'00000000' + assert hexlify(im.view(250, 250, 1, 1).to_string()) == b'00000000' # full opaque river color - eq_(hexlify(im.view(175, 118, 1, 1).tostring()), b'b9d8f8ff') + assert hexlify(im.view(175, 118, 1, 1).to_string()) == b'b9d8f8ff' # half-transparent pixel - pxstr = hexlify(im.view(122, 138, 1, 1).tostring()).decode() + pxstr = hexlify(im.view(122, 138, 1, 1).to_string()).decode() apat = ".*(..)$" match = re.match(apat, pxstr) assert match, 'pixel ' + pxstr + ' does not match pattern ' + apat @@ -342,16 +338,16 @@ def _test_rgba_8bui_rendering(lbl, overview, rescale, clip): mapnik.render(mm, im) lap = time.time() - t0 log('T ' + str(lap) + ' -- ' + lbl + ' E:1/10') - expected = 'images/support/pgraster/%s-%s-%s-%s-box2.png' % ( + expected = './images/support/pgraster/%s-%s-%s-%s-box2.png' % ( lyr.name, lbl, overview, clip) compare_images(expected, im) # no data - eq_(hexlify(im.view(255, 255, 1, 1).tostring()), b'00000000') - eq_(hexlify(im.view(200, 40, 1, 1).tostring()), b'00000000') + assert hexlify(im.view(255, 255, 1, 1).to_string()) == b'00000000' + assert hexlify(im.view(200, 40, 1, 1).to_string()) == b'00000000' # full opaque river color - eq_(hexlify(im.view(100, 168, 1, 1).tostring()), b'b9d8f8ff') + assert hexlify(im.view(100, 168, 1, 1).to_string()) == b'b9d8f8ff' # half-transparent pixel - pxstr = hexlify(im.view(122, 138, 1, 1).tostring()).decode() + pxstr = hexlify(im.view(122, 138, 1, 1).to_string()).decode() apat = ".*(..)$" match = re.match(apat, pxstr) assert match, 'pixel ' + pxstr + ' does not match pattern ' + apat @@ -361,7 +357,7 @@ def _test_rgba_8bui_rendering(lbl, overview, rescale, clip): def _test_rgba_8bui(lbl, tilesize, constraint, overview): import_raster( - '../data/raster/river.tiff', + os.path.join(os.path.dirname(os.path.realpath(__file__)),'../../test/data/raster/river.tiff'), 'River', tilesize, constraint, @@ -384,22 +380,22 @@ def test_rgba_8bui(): _test_rgba_8bui( 'rgba_8bui', tilesize, constraint, overview) - # nodata-edge.tif, RGB 8BUI + # # nodata-edge.tif, RGB 8BUI def _test_rgb_8bui_rendering(lbl, tnam, overview, rescale, clip): if rescale: lbl += ' Sc' if clip: lbl += ' Cl' - ds = mapnik.PgRaster(dbname=MAPNIK_TEST_DBNAME, table=tnam, + ds = mapnik.PgRaster(dbname=MAPNIK_TEST_DBNAME, table=f'(select * from "{tnam}" order by rid) foo', use_overviews=1 if overview else 0, prescale_rasters=rescale, clip_rasters=clip) - fs = ds.featureset() - feature = fs.next() - eq_(feature['rid'], 1) + fs = iter(ds) + feature = next(fs) + assert feature['rid'] == 1 lyr = mapnik.Layer('rgba_8bui') lyr.datasource = ds - expenv = mapnik.Box2d(-12329035.7652168, 4508650.39854396, - -12328653.0279471, 4508957.34625536) + expenv = mapnik.Box2d(-12329035.765216826, 4508650.398543958, + -12328653.027947055, 4508957.346255356) env = lyr.envelope() # As the input size is a prime number both horizontally # and vertically, we expect the extent of the overview @@ -410,15 +406,15 @@ def _test_rgb_8bui_rendering(lbl, tnam, overview, rescale, clip): # NOTE: the overview table extent only grows north and east pixsize = 2 # see gdalinfo nodata-edge.tif tol = pixsize * max(overview.split(',')) if overview else 0 - assert_almost_equal(env.minx, expenv.minx, places=0) - assert_almost_equal(env.miny, expenv.miny, delta=tol) - assert_almost_equal(env.maxx, expenv.maxx, delta=tol) - assert_almost_equal(env.maxy, expenv.maxy, places=0) + assert env.minx == expenv.minx + assert env.miny == expenv.miny + assert env.maxx == expenv.maxx + assert env.maxy == expenv.maxy mm = mapnik.Map(256, 256) style = mapnik.Style() sym = mapnik.RasterSymbolizer() rule = mapnik.Rule() - rule.symbols.append(sym) + rule.symbolizers.append(sym) style.rules.append(rule) mm.append_style('foo', style) lyr.styles.append('foo') @@ -429,20 +425,20 @@ def _test_rgb_8bui_rendering(lbl, tnam, overview, rescale, clip): mapnik.render(mm, im) lap = time.time() - t0 log('T ' + str(lap) + ' -- ' + lbl + ' E:full') - expected = 'images/support/pgraster/%s-%s-%s-%s-%s-box1.png' % ( + expected = './images/support/pgraster/%s-%s-%s-%s-%s-box1.png' % ( lyr.name, tnam, lbl, overview, clip) compare_images(expected, im) # no data - eq_(hexlify(im.view(3, 16, 1, 1).tostring()), b'00000000') - eq_(hexlify(im.view(128, 16, 1, 1).tostring()), b'00000000') - eq_(hexlify(im.view(250, 16, 1, 1).tostring()), b'00000000') - eq_(hexlify(im.view(3, 240, 1, 1).tostring()), b'00000000') - eq_(hexlify(im.view(128, 240, 1, 1).tostring()), b'00000000') - eq_(hexlify(im.view(250, 240, 1, 1).tostring()), b'00000000') + assert hexlify(im.view(3, 16, 1, 1).to_string()) == b'00000000' + assert hexlify(im.view(128, 16, 1, 1).to_string()) == b'00000000' + assert hexlify(im.view(250, 16, 1, 1).to_string()) == b'00000000' + assert hexlify(im.view(3, 240, 1, 1).to_string()) == b'00000000' + assert hexlify(im.view(128, 240, 1, 1).to_string()) == b'00000000' + assert hexlify(im.view(250, 240, 1, 1).to_string()) == b'00000000' # dark brown - eq_(hexlify(im.view(174, 39, 1, 1).tostring()), b'c3a698ff') + assert hexlify(im.view(174, 39, 1, 1).to_string()) == b'c3a698ff' # dark gray - eq_(hexlify(im.view(195, 132, 1, 1).tostring()), b'575f62ff') + assert hexlify(im.view(195, 132, 1, 1).to_string()) == b'575f62ff' # Now zoom over a portion of the env (1/10) newenv = mapnik.Box2d(-12329035.7652168, 4508926.651484220, -12328997.49148983, 4508957.34625536) @@ -456,22 +452,22 @@ def _test_rgb_8bui_rendering(lbl, tnam, overview, rescale, clip): lyr.name, tnam, lbl, overview, clip) compare_images(expected, im) # no data - eq_(hexlify(im.view(3, 16, 1, 1).tostring()), b'00000000') - eq_(hexlify(im.view(128, 16, 1, 1).tostring()), b'00000000') - eq_(hexlify(im.view(250, 16, 1, 1).tostring()), b'00000000') + assert hexlify(im.view(3, 16, 1, 1).to_string()) == b'00000000' + assert hexlify(im.view(128, 16, 1, 1).to_string()) == b'00000000' + assert hexlify(im.view(250, 16, 1, 1).to_string()) == b'00000000' # black - eq_(hexlify(im.view(3, 42, 1, 1).tostring()), b'000000ff') - eq_(hexlify(im.view(3, 134, 1, 1).tostring()), b'000000ff') - eq_(hexlify(im.view(3, 244, 1, 1).tostring()), b'000000ff') + assert hexlify(im.view(3, 42, 1, 1).to_string()) == b'000000ff' + assert hexlify(im.view(3, 134, 1, 1).to_string()) == b'000000ff' + assert hexlify(im.view(3, 244, 1, 1).to_string()) == b'000000ff' # gray - eq_(hexlify(im.view(135, 157, 1, 1).tostring()), b'4e555bff') + assert hexlify(im.view(135, 157, 1, 1).to_string()) == b'4e555bff' # brown - eq_(hexlify(im.view(195, 223, 1, 1).tostring()), b'f2cdbaff') + assert hexlify(im.view(195, 223, 1, 1).to_string()) == b'f2cdbaff' def _test_rgb_8bui(lbl, tilesize, constraint, overview): tnam = 'nodataedge' import_raster( - '../data/raster/nodata-edge.tif', + os.path.join(os.path.dirname(os.path.realpath(__file__)),'../../test/data/raster/nodata-edge.tif'), tnam, tilesize, constraint, @@ -485,7 +481,7 @@ def _test_rgb_8bui(lbl, tilesize, constraint, overview): for prescale in [0, 1]: for clip in [0, 1]: _test_rgb_8bui_rendering(lbl, tnam, overview, prescale, clip) - #drop_imported(tnam, overview) + drop_imported(tnam, overview) def test_rgb_8bui(): for tilesize in ['64x64']: @@ -527,22 +523,22 @@ def _test_grayscale_subquery(lbl, pixtype, value): ds = mapnik.PgRaster(dbname=MAPNIK_TEST_DBNAME, table=sql, raster_field='"R"', use_overviews=1, prescale_rasters=rescale, clip_rasters=clip) - fs = ds.featureset() - feature = fs.next() - eq_(feature['i'], 3) + fs = iter(ds) + feature = next(fs) + assert feature['i'] == 3 lyr = mapnik.Layer('grayscale_subquery') lyr.datasource = ds expenv = mapnik.Box2d(0, 0, 14, 14) env = lyr.envelope() - assert_almost_equal(env.minx, expenv.minx, places=0) - assert_almost_equal(env.miny, expenv.miny, places=0) - assert_almost_equal(env.maxx, expenv.maxx, places=0) - assert_almost_equal(env.maxy, expenv.maxy, places=0) + assert env.minx == expenv.minx + assert env.miny == expenv.miny + assert env.maxx == expenv.maxx + assert env.maxy == expenv.maxy mm = mapnik.Map(15, 15) style = mapnik.Style() sym = mapnik.RasterSymbolizer() rule = mapnik.Rule() - rule.symbols.append(sym) + rule.symbolizers.append(sym) style.rules.append(rule) mm.append_style('foo', style) lyr.styles.append('foo') @@ -565,15 +561,15 @@ def _test_grayscale_subquery(lbl, pixtype, value): h = format(val_b, '02x') hex_b = h + h + h + 'ff' hex_b = hex_b.encode() - eq_(hexlify(im.view(3, 3, 1, 1).tostring()), hex_v) - eq_(hexlify(im.view(8, 3, 1, 1).tostring()), hex_v) - eq_(hexlify(im.view(13, 3, 1, 1).tostring()), hex_v) - eq_(hexlify(im.view(3, 8, 1, 1).tostring()), hex_v) - eq_(hexlify(im.view(8, 8, 1, 1).tostring()), hex_v) - eq_(hexlify(im.view(13, 8, 1, 1).tostring()), hex_a) - eq_(hexlify(im.view(3, 13, 1, 1).tostring()), hex_v) - eq_(hexlify(im.view(8, 13, 1, 1).tostring()), hex_b) - eq_(hexlify(im.view(13, 13, 1, 1).tostring()), hex_v) + assert hexlify(im.view(3, 3, 1, 1).to_string()) == hex_v + assert hexlify(im.view(8, 3, 1, 1).to_string()) == hex_v + assert hexlify(im.view(13, 3, 1, 1).to_string()) == hex_v + assert hexlify(im.view(3, 8, 1, 1).to_string()) == hex_v + assert hexlify(im.view(8, 8, 1, 1).to_string()) == hex_v + assert hexlify(im.view(13, 8, 1, 1).to_string()) == hex_a + assert hexlify(im.view(3, 13, 1, 1).to_string()) == hex_v + assert hexlify(im.view(8, 13, 1, 1).to_string()) == hex_b + assert hexlify(im.view(13, 13, 1, 1).to_string()) == hex_v def test_grayscale_2bui_subquery(): _test_grayscale_subquery('grayscale_2bui_subquery', '2BUI', 3) @@ -640,17 +636,17 @@ def _test_data_subquery(lbl, pixtype, value): ds = mapnik.PgRaster(dbname=MAPNIK_TEST_DBNAME, table=sql, raster_field='R', use_overviews=0 if overview else 0, band=1, prescale_rasters=rescale, clip_rasters=clip) - fs = ds.featureset() - feature = fs.next() - eq_(feature['i'], 3) + fs = iter(ds) + feature = next(fs) + assert feature['i'] == 3 lyr = mapnik.Layer('data_subquery') lyr.datasource = ds expenv = mapnik.Box2d(0, 0, 14, 14) env = lyr.envelope() - assert_almost_equal(env.minx, expenv.minx, places=0) - assert_almost_equal(env.miny, expenv.miny, places=0) - assert_almost_equal(env.maxx, expenv.maxx, places=0) - assert_almost_equal(env.maxy, expenv.maxy, places=0) + assert env.minx == expenv.minx#, places=0) + assert env.miny == expenv.miny#, places=0) + assert env.maxx == expenv.maxx#, places=0) + assert env.maxy == expenv.maxy#, places=0) mm = mapnik.Map(15, 15) style = mapnik.Style() col = mapnik.RasterColorizer() @@ -661,7 +657,7 @@ def _test_data_subquery(lbl, pixtype, value): sym = mapnik.RasterSymbolizer() sym.colorizer = col rule = mapnik.Rule() - rule.symbols.append(sym) + rule.symbolizers.append(sym) style.rules.append(rule) mm.append_style('foo', style) lyr.styles.append('foo') @@ -764,22 +760,22 @@ def _test_rgba_subquery(lbl, pixtype, r, g, b, a, g1, b1): ds = mapnik.PgRaster(dbname=MAPNIK_TEST_DBNAME, table=sql, raster_field='r', use_overviews=0 if overview else 0, prescale_rasters=rescale, clip_rasters=clip) - fs = ds.featureset() - feature = fs.next() - eq_(feature['i'], 3) + fs = iter(ds) + feature = next(fs) + assert feature['i'] == 3 lyr = mapnik.Layer('rgba_subquery') lyr.datasource = ds expenv = mapnik.Box2d(0, 0, 14, 14) env = lyr.envelope() - assert_almost_equal(env.minx, expenv.minx, places=0) - assert_almost_equal(env.miny, expenv.miny, places=0) - assert_almost_equal(env.maxx, expenv.maxx, places=0) - assert_almost_equal(env.maxy, expenv.maxy, places=0) + assert env.minx == expenv.minx#, places=0) + assert env.miny == expenv.miny#, places=0) + assert env.maxx == expenv.maxx#, places=0) + assert env.maxy == expenv.maxy#, places=0) mm = mapnik.Map(15, 15) style = mapnik.Style() sym = mapnik.RasterSymbolizer() rule = mapnik.Rule() - rule.symbols.append(sym) + rule.symbolizers.append(sym) style.rules.append(rule) mm.append_style('foo', style) lyr.styles.append('foo') @@ -793,18 +789,18 @@ def _test_rgba_subquery(lbl, pixtype, r, g, b, a, g1, b1): expected = 'images/support/pgraster/%s-%s-%s-%s-%s-%s-%s-%s-%s.png' % ( lyr.name, lbl, pixtype, r, g, b, a, g1, b1) compare_images(expected, im) - hex_v = format(r << 24 | g << 16 | b << 8 | a, '08x').encode() - hex_a = format(r << 24 | g1 << 16 | b << 8 | a, '08x').encode() + hex_v = format(r << 24 | g << 16 | b << 8 | a, '08x').encode() + hex_a = format(r << 24 | g1<< 16 | b << 8 | a, '08x').encode() hex_b = format(r << 24 | g << 16 | b1 << 8 | a, '08x').encode() - eq_(hexlify(im.view(3, 3, 1, 1).tostring()), hex_v) - eq_(hexlify(im.view(8, 3, 1, 1).tostring()), hex_v) - eq_(hexlify(im.view(13, 3, 1, 1).tostring()), hex_v) - eq_(hexlify(im.view(3, 8, 1, 1).tostring()), hex_v) - eq_(hexlify(im.view(8, 8, 1, 1).tostring()), hex_v) - eq_(hexlify(im.view(13, 8, 1, 1).tostring()), hex_a) - eq_(hexlify(im.view(3, 13, 1, 1).tostring()), hex_v) - eq_(hexlify(im.view(8, 13, 1, 1).tostring()), hex_b) - eq_(hexlify(im.view(13, 13, 1, 1).tostring()), hex_v) + assert hexlify(im.view(3, 3, 1, 1).to_string()) == hex_v + assert hexlify(im.view(8, 3, 1, 1).to_string()) == hex_v + assert hexlify(im.view(13, 3, 1, 1).to_string()) == hex_v + assert hexlify(im.view(3, 8, 1, 1).to_string()) == hex_v + assert hexlify(im.view(8, 8, 1, 1).to_string()) == hex_v + assert hexlify(im.view(13, 8, 1, 1).to_string()) == hex_a + assert hexlify(im.view(3, 13, 1, 1).to_string()) == hex_v + assert hexlify(im.view(8, 13, 1, 1).to_string()) == hex_b + assert hexlify(im.view(13, 13, 1, 1).to_string()) == hex_v def test_rgba_8bui_subquery(): _test_rgba_subquery( @@ -817,23 +813,16 @@ def test_rgba_8bui_subquery(): 255, 255) - # def test_rgba_16bui_subquery(): - # _test_rgba_subquery('rgba_16bui_subquery', '16BUI', 65535, 0, 0, 65535, 65535, 65535) + #def test_rgba_16bui_subquery(): + # _test_rgba_subquery('rgba_16bui_subquery', '16BUI', 65535, 0, 0, 65535, 65535, 65535) - # def test_rgba_32bui_subquery(): - # _test_rgba_subquery('rgba_32bui_subquery', '32BUI') + #def test_rgba_32bui_subquery(): + # _test_rgba_subquery('rgba_32bui_subquery', '32BUI') atexit.register(postgis_takedown) - def enabled(tname): enabled = len(sys.argv) < 2 or tname in sys.argv if not enabled: print("Skipping " + tname + " as not explicitly enabled") return enabled - -if __name__ == "__main__": - setup() - fail = run_all(eval(x) - for x in dir() if x.startswith("test_") and enabled(x)) - exit(fail) diff --git a/test/python_tests/pickling_test.py b/test/python_tests/pickling_test.py index 2b21309b0..4430f6cd9 100644 --- a/test/python_tests/pickling_test.py +++ b/test/python_tests/pickling_test.py @@ -1,45 +1,34 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os import pickle - -from nose.tools import eq_ - +import pytest import mapnik +from .utilities import execution_path -from .utilities import execution_path, run_all - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield -#def test_color_pickle(): -# c = mapnik.Color('blue') -# eq_(pickle.loads(pickle.dumps(c)), c) -# c = mapnik.Color(0, 64, 128) -# eq_(pickle.loads(pickle.dumps(c)), c) -# c = mapnik.Color(0, 64, 128, 192) -# eq_(pickle.loads(pickle.dumps(c)), c) - - -#def test_envelope_pickle(): -# e = mapnik.Box2d(100, 100, 200, 200) -# eq_(pickle.loads(pickle.dumps(e)), e) - +def test_color_pickle(): + c = mapnik.Color('blue') + assert pickle.loads(pickle.dumps(c)) == c + c = mapnik.Color(0, 64, 128) + assert pickle.loads(pickle.dumps(c)) == c + c = mapnik.Color(0, 64, 128, 192) + assert pickle.loads(pickle.dumps(c)) == c -def test_parameters_pickle(): - params = mapnik.Parameters() - params.append(mapnik.Parameter('oh', str('yeah'))) - params2 = pickle.loads(pickle.dumps(params, pickle.HIGHEST_PROTOCOL)) +def test_envelope_pickle(): + e = mapnik.Box2d(100, 100, 200, 200) + assert pickle.loads(pickle.dumps(e)) == e - eq_(params[0][0], params2[0][0]) - eq_(params[0][1], params2[0][1]) +def test_projection_pickle(): + p = mapnik.Projection("epsg:4326") + assert pickle.loads(pickle.dumps(p)).definition() == p.definition() -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) +def test_coord_pickle(): + c = mapnik.Coord(-1, 52) + assert pickle.loads(pickle.dumps(c)) == c diff --git a/test/python_tests/png_encoding_test.py b/test/python_tests/png_encoding_test.py index 8916f0f14..ed91fed6c 100644 --- a/test/python_tests/png_encoding_test.py +++ b/test/python_tests/png_encoding_test.py @@ -1,19 +1,14 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os - -from nose.tools import eq_ - import mapnik +import pytest +from .utilities import execution_path -from .utilities import execution_path, run_all - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield if mapnik.has_png(): tmp_dir = '/tmp/mapnik-png/' @@ -47,7 +42,7 @@ def gen_filepath(name, format): generate = os.environ.get('UPDATE') - def test_expected_encodings(): + def test_expected_encodings(setup): # blank image im = mapnik.Image(256, 256) for opt in opts: @@ -58,9 +53,7 @@ def test_expected_encodings(): im.save(expected, opt) else: im.save(actual, opt) - eq_(mapnik.Image.open(actual).tostring('png32'), - mapnik.Image.open(expected).tostring('png32'), - '%s (actual) not == to %s (expected)' % (actual, expected)) + assert mapnik.Image.open(actual).to_string('png32') == mapnik.Image.open(expected).to_string('png32'), '%s (actual) not == to %s (expected)' % (actual, expected) # solid image im.fill(mapnik.Color('green')) @@ -72,9 +65,7 @@ def test_expected_encodings(): im.save(expected, opt) else: im.save(actual, opt) - eq_(mapnik.Image.open(actual).tostring('png32'), - mapnik.Image.open(expected).tostring('png32'), - '%s (actual) not == to %s (expected)' % (actual, expected)) + assert mapnik.Image.open(actual).to_string('png32') == mapnik.Image.open(expected).to_string('png32'), '%s (actual) not == to %s (expected)' % (actual, expected) # aerial im = mapnik.Image.open('./images/support/transparency/aerial_rgba.png') @@ -86,9 +77,7 @@ def test_expected_encodings(): im.save(expected, opt) else: im.save(actual, opt) - eq_(mapnik.Image.open(actual).tostring('png32'), - mapnik.Image.open(expected).tostring('png32'), - '%s (actual) not == to %s (expected)' % (actual, expected)) + assert mapnik.Image.open(actual).to_string('png32') == mapnik.Image.open(expected).to_string('png32'), '%s (actual) not == to %s (expected)' % (actual, expected) def test_transparency_levels(): # create partial transparency image @@ -111,101 +100,84 @@ def test_transparency_levels(): format = 'png8:m=o:t=0' im.save(t0, format) im_in = mapnik.Image.open(t0) - t0_len = len(im_in.tostring(format)) - eq_(t0_len, len(mapnik.Image.open( - 'images/support/transparency/white0.png').tostring(format))) + t0_len = len(im_in.to_string(format)) + assert t0_len == len(mapnik.Image.open('images/support/transparency/white0.png').to_string(format)) format = 'png8:m=o:t=1' im.save(t1, format) im_in = mapnik.Image.open(t1) - t1_len = len(im_in.tostring(format)) - eq_(len(im.tostring(format)), len(mapnik.Image.open( - 'images/support/transparency/white1.png').tostring(format))) + t1_len = len(im_in.to_string(format)) + assert len(im.to_string(format)) == len(mapnik.Image.open('images/support/transparency/white1.png').to_string(format)) format = 'png8:m=o:t=2' im.save(t2, format) im_in = mapnik.Image.open(t2) - t2_len = len(im_in.tostring(format)) - eq_(len(im.tostring(format)), len(mapnik.Image.open( - 'images/support/transparency/white2.png').tostring(format))) - - eq_(t0_len < t1_len < t2_len, True) + t2_len = len(im_in.to_string(format)) + assert len(im.to_string(format)) == len(mapnik.Image.open('images/support/transparency/white2.png').to_string(format)) + assert t0_len < t1_len < t2_len # hextree format = 'png8:m=h:t=0' im.save(t0, format) im_in = mapnik.Image.open(t0) - t0_len = len(im_in.tostring(format)) - eq_(t0_len, len(mapnik.Image.open( - 'images/support/transparency/white0.png').tostring(format))) + t0_len = len(im_in.to_string(format)) + assert t0_len == len(mapnik.Image.open('images/support/transparency/white0.png').to_string(format)) format = 'png8:m=h:t=1' im.save(t1, format) im_in = mapnik.Image.open(t1) - t1_len = len(im_in.tostring(format)) - eq_(len(im.tostring(format)), len(mapnik.Image.open( - 'images/support/transparency/white1.png').tostring(format))) + t1_len = len(im_in.to_string(format)) + assert len(im.to_string(format)) == len(mapnik.Image.open('images/support/transparency/white1.png').to_string(format)) format = 'png8:m=h:t=2' im.save(t2, format) im_in = mapnik.Image.open(t2) - t2_len = len(im_in.tostring(format)) - eq_(len(im.tostring(format)), len(mapnik.Image.open( - 'images/support/transparency/white2.png').tostring(format))) - - eq_(t0_len < t1_len < t2_len, True) + t2_len = len(im_in.to_string(format)) + assert len(im.to_string(format)) == len(mapnik.Image.open('images/support/transparency/white2.png').to_string(format)) + assert t0_len < t1_len < t2_len def test_transparency_levels_aerial(): im = mapnik.Image.open('../data/images/12_654_1580.png') im_in = mapnik.Image.open( './images/support/transparency/aerial_rgba.png') - eq_(len(im.tostring('png8')), len(im_in.tostring('png8'))) - eq_(len(im.tostring('png32')), len(im_in.tostring('png32'))) + assert len(im.to_string('png8')) == len(im_in.to_string('png8')) + assert len(im.to_string('png32')) == len(im_in.to_string('png32')) im_in = mapnik.Image.open( './images/support/transparency/aerial_rgb.png') - eq_(len(im.tostring('png32')), len(im_in.tostring('png32'))) - eq_(len(im.tostring('png32:t=0')), len(im_in.tostring('png32:t=0'))) - eq_(len(im.tostring('png32:t=0')) == len(im_in.tostring('png32')), False) - eq_(len(im.tostring('png8')), len(im_in.tostring('png8'))) - eq_(len(im.tostring('png8:t=0')), len(im_in.tostring('png8:t=0'))) + assert len(im.to_string('png32')) == len(im_in.to_string('png32')) + assert len(im.to_string('png32:t=0')) == len(im_in.to_string('png32:t=0')) + assert not len(im.to_string('png32:t=0')) == len(im_in.to_string('png32')) + assert len(im.to_string('png8')) == len(im_in.to_string('png8')) + assert len(im.to_string('png8:t=0')) == len(im_in.to_string('png8:t=0')) # unlike png32 paletted images without alpha will look the same even if # no alpha is forced - eq_(len(im.tostring('png8:t=0')) == len(im_in.tostring('png8')), True) - eq_(len(im.tostring('png8:t=0:m=o')) == - len(im_in.tostring('png8:m=o')), True) + assert len(im.to_string('png8:t=0')) == len(im_in.to_string('png8')) + assert len(im.to_string('png8:t=0:m=o')) == len(im_in.to_string('png8:m=o')) def test_9_colors_hextree(): expected = './images/support/encoding-opts/png8-9cols.png' im = mapnik.Image.open(expected) t0 = tmp_dir + 'png-encoding-9-colors.result-hextree.png' im.save(t0, 'png8:m=h') - eq_(mapnik.Image.open(t0).tostring(), - mapnik.Image.open(expected).tostring(), - '%s (actual) not == to %s (expected)' % (t0, expected)) + assert mapnik.Image.open(t0).to_string() == mapnik.Image.open(expected).to_string(), '%s (actual) not == to %s (expected)' % (t0, expected) def test_9_colors_octree(): expected = './images/support/encoding-opts/png8-9cols.png' im = mapnik.Image.open(expected) t0 = tmp_dir + 'png-encoding-9-colors.result-octree.png' im.save(t0, 'png8:m=o') - eq_(mapnik.Image.open(t0).tostring(), - mapnik.Image.open(expected).tostring(), - '%s (actual) not == to %s (expected)' % (t0, expected)) + assert mapnik.Image.open(t0).to_string() == mapnik.Image.open(expected).to_string(), '%s (actual) not == to %s (expected)' % (t0, expected) def test_17_colors_hextree(): expected = './images/support/encoding-opts/png8-17cols.png' im = mapnik.Image.open(expected) t0 = tmp_dir + 'png-encoding-17-colors.result-hextree.png' im.save(t0, 'png8:m=h') - eq_(mapnik.Image.open(t0).tostring(), - mapnik.Image.open(expected).tostring(), - '%s (actual) not == to %s (expected)' % (t0, expected)) + assert mapnik.Image.open(t0).to_string() == mapnik.Image.open(expected).to_string(), '%s (actual) not == to %s (expected)' % (t0, expected) def test_17_colors_octree(): expected = './images/support/encoding-opts/png8-17cols.png' im = mapnik.Image.open(expected) t0 = tmp_dir + 'png-encoding-17-colors.result-octree.png' im.save(t0, 'png8:m=o') - eq_(mapnik.Image.open(t0).tostring(), - mapnik.Image.open(expected).tostring(), - '%s (actual) not == to %s (expected)' % (t0, expected)) + assert mapnik.Image.open(t0).to_string() == mapnik.Image.open(expected).to_string(), '%s (actual) not == to %s (expected)' % (t0, expected) def test_2px_regression_hextree(): im = mapnik.Image.open('./images/support/encoding-opts/png8-2px.A.png') @@ -213,20 +185,11 @@ def test_2px_regression_hextree(): t0 = tmp_dir + 'png-encoding-2px.result-hextree.png' im.save(t0, 'png8:m=h') - eq_(mapnik.Image.open(t0).tostring(), - mapnik.Image.open(expected).tostring(), - '%s (actual) not == to %s (expected)' % (t0, expected)) + assert mapnik.Image.open(t0).to_string() == mapnik.Image.open(expected).to_string(), '%s (actual) not == to %s (expected)' % (t0, expected) def test_2px_regression_octree(): im = mapnik.Image.open('./images/support/encoding-opts/png8-2px.A.png') expected = './images/support/encoding-opts/png8-2px.png' t0 = tmp_dir + 'png-encoding-2px.result-octree.png' im.save(t0, 'png8:m=o') - eq_(mapnik.Image.open(t0).tostring(), - mapnik.Image.open(expected).tostring(), - '%s (actual) not == to %s (expected)' % (t0, expected)) - - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert mapnik.Image.open(t0).to_string() == mapnik.Image.open(expected).to_string(), '%s (actual) not == to %s (expected)' % (t0, expected) diff --git a/test/python_tests/pngsuite_test.py b/test/python_tests/pngsuite_test.py index 8ce517f49..8c91e27ce 100644 --- a/test/python_tests/pngsuite_test.py +++ b/test/python_tests/pngsuite_test.py @@ -1,24 +1,20 @@ -#!/usr/bin/env python - import os - -from nose.tools import assert_raises - import mapnik - -from .utilities import execution_path, run_all +import pytest +from .utilities import execution_path datadir = '../data/pngsuite' - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) - + yield def assert_broken_file(fname): - assert_raises(RuntimeError, lambda: mapnik.Image.open(fname)) + with pytest.raises(RuntimeError): + mapnik.Image.open(fname) def assert_good_file(fname): @@ -31,15 +27,11 @@ def get_pngs(good): for x in files if good != x.startswith('x')] -def test_good_pngs(): +def test_good_pngs(setup): for x in get_pngs(True): - yield assert_good_file, x + assert_good_file, x def test_broken_pngs(): for x in get_pngs(False): - yield assert_broken_file, x - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert_broken_file, x diff --git a/test/python_tests/postgis_test.py b/test/python_tests/postgis_test.py index c3d5f8a43..b17f41950 100644 --- a/test/python_tests/postgis_test.py +++ b/test/python_tests/postgis_test.py @@ -1,32 +1,16 @@ -#!/usr/bin/env python import atexit import os import sys import threading from subprocess import PIPE, Popen - -from nose.tools import eq_, raises - import mapnik - -from .utilities import execution_path, run_all - -PYTHON3 = sys.version_info[0] == 3 -if PYTHON3: - long = int - +import pytest +from .utilities import execution_path MAPNIK_TEST_DBNAME = 'mapnik-tmp-postgis-test-db' POSTGIS_TEMPLATE_DBNAME = 'template_postgis' SHAPEFILE = os.path.join(execution_path('.'), '../data/shp/world_merc.shp') - -def setup(): - # All of the paths used are relative, if we run the tests - # from another directory we need to chdir() - os.chdir(execution_path('.')) - - def call(cmd, silent=False): stdin, stderr = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE).communicate() @@ -215,8 +199,9 @@ def postgis_setup(): (POSTGIS_TEMPLATE_DBNAME, MAPNIK_TEST_DBNAME), silent=False) - call('shp2pgsql -s 3857 -g geom -W LATIN1 %s world_merc | psql -q %s' % - (SHAPEFILE, MAPNIK_TEST_DBNAME), silent=True) + + call('''shp2pgsql -s 3857 -g geom -W LATIN1 %s world_merc | psql -q %s''' % (SHAPEFILE, MAPNIK_TEST_DBNAME), silent=False) + call( '''psql -q %s -c "CREATE TABLE \"empty\" (key serial);SELECT AddGeometryColumn('','empty','geom','-1','GEOMETRY',4);"''' % MAPNIK_TEST_DBNAME, @@ -303,63 +288,63 @@ def postgis_takedown(): def test_feature(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='world_merc') - fs = ds.featureset() - feature = fs.next() - eq_(feature['gid'], 1) - eq_(feature['fips'], u'AC') - eq_(feature['iso2'], u'AG') - eq_(feature['iso3'], u'ATG') - eq_(feature['un'], 28) - eq_(feature['name'], u'Antigua and Barbuda') - eq_(feature['area'], 44) - eq_(feature['pop2005'], 83039) - eq_(feature['region'], 19) - eq_(feature['subregion'], 29) - eq_(feature['lon'], -61.783) - eq_(feature['lat'], 17.078) + fs = iter(ds) + feature = next(fs) + assert feature['gid'] == 1 + assert feature['fips'] == u'AC' + assert feature['iso2'] == u'AG' + assert feature['iso3'] == u'ATG' + assert feature['un'] == 28 + assert feature['name'] == u'Antigua and Barbuda' + assert feature['area'] == 44 + assert feature['pop2005'] == 83039 + assert feature['region'] == 19 + assert feature['subregion'] == 29 + assert feature['lon'] == -61.783 + assert feature['lat'] == 17.078 meta = ds.describe() - eq_(meta['srid'], 3857) - eq_(meta.get('key_field'), None) - eq_(meta['encoding'], u'UTF8') - eq_(meta['geometry_type'], mapnik.DataGeometryType.Polygon) + assert meta['srid'] == 3857 + assert meta.get('key_field') == None + assert meta['encoding'] == u'UTF8' + assert meta['geometry_type'] == mapnik.DataGeometryType.Polygon def test_subquery(): ds = mapnik.PostGIS( dbname=MAPNIK_TEST_DBNAME, table='(select * from world_merc) as w') - fs = ds.featureset() - feature = fs.next() - eq_(feature['gid'], 1) - eq_(feature['fips'], u'AC') - eq_(feature['iso2'], u'AG') - eq_(feature['iso3'], u'ATG') - eq_(feature['un'], 28) - eq_(feature['name'], u'Antigua and Barbuda') - eq_(feature['area'], 44) - eq_(feature['pop2005'], 83039) - eq_(feature['region'], 19) - eq_(feature['subregion'], 29) - eq_(feature['lon'], -61.783) - eq_(feature['lat'], 17.078) + fs = iter(ds) + feature = next(fs) + assert feature['gid'] == 1 + assert feature['fips'] == u'AC' + assert feature['iso2'] == u'AG' + assert feature['iso3'] == u'ATG' + assert feature['un'] == 28 + assert feature['name'] == u'Antigua and Barbuda' + assert feature['area'] == 44 + assert feature['pop2005'] == 83039 + assert feature['region'] == 19 + assert feature['subregion'] == 29 + assert feature['lon'] == -61.783 + assert feature['lat'] == 17.078 meta = ds.describe() - eq_(meta['srid'], 3857) - eq_(meta.get('key_field'), None) - eq_(meta['encoding'], u'UTF8') - eq_(meta['geometry_type'], mapnik.DataGeometryType.Polygon) + assert meta['srid'] == 3857 + assert meta.get('key_field') == None + assert meta['encoding'] == u'UTF8' + assert meta['geometry_type'] == mapnik.DataGeometryType.Polygon ds = mapnik.PostGIS( dbname=MAPNIK_TEST_DBNAME, table='(select gid,geom,fips as _fips from world_merc) as w') - fs = ds.featureset() - feature = fs.next() - eq_(feature['gid'], 1) - eq_(feature['_fips'], u'AC') - eq_(len(feature), 2) + fs = iter(ds) + feature = next(fs) + assert feature['gid'] == 1 + assert feature['_fips'] == u'AC' + assert len(feature) == 2 meta = ds.describe() - eq_(meta['srid'], 3857) - eq_(meta.get('key_field'), None) - eq_(meta['encoding'], u'UTF8') - eq_(meta['geometry_type'], mapnik.DataGeometryType.Polygon) + assert meta['srid'] == 3857 + assert meta.get('key_field') == None + assert meta['encoding'] == u'UTF8' + assert meta['geometry_type'] == mapnik.DataGeometryType.Polygon def test_bad_connection(): try: @@ -374,367 +359,373 @@ def test_bad_connection(): def test_empty_db(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='empty') - fs = ds.featureset() + fs = ds.features(mapnik.Query(mapnik.Box2d(-180,-90,180,90))) feature = None try: - feature = fs.next() + feature = next(fs) except StopIteration: pass - eq_(feature, None) + assert feature == None meta = ds.describe() - eq_(meta['srid'], -1) - eq_(meta.get('key_field'), None) - eq_(meta['encoding'], u'UTF8') - eq_(meta['geometry_type'], None) + assert meta['srid'] == -1 + assert meta.get('key_field') == None + assert meta['encoding'] == u'UTF8' + assert meta['geometry_type'] == None def test_manual_srid(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, srid=99, table='empty') - fs = ds.featureset() + fs = ds.features(mapnik.Query(mapnik.Box2d(-180,-90,180,90))) feature = None try: - feature = fs.next() + feature = next(fs) except StopIteration: pass - eq_(feature, None) + assert feature == None meta = ds.describe() - eq_(meta['srid'], 99) - eq_(meta.get('key_field'), None) - eq_(meta['encoding'], u'UTF8') - eq_(meta['geometry_type'], None) + assert meta['srid'] == 99 + assert meta.get('key_field') == None + assert meta['encoding'] == u'UTF8' + assert meta['geometry_type'] == None def test_geometry_detection(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test', geometry_field='geom') meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), None) - eq_(meta['geometry_type'], mapnik.DataGeometryType.Collection) + assert meta['srid'] == 4326 + assert meta.get('key_field') == None + assert meta['geometry_type'] == mapnik.DataGeometryType.Collection # will fail with postgis 2.0 because it automatically adds a geometry_columns entry # ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME,table='test', # geometry_field='geom', # row_limit=1) - # eq_(ds.describe()['geometry_type'],mapnik.DataGeometryType.Point) + # assert ds.describe()['geometry_type'] == mapnik.DataGeometryType.Point + - @raises(RuntimeError) def test_that_nonexistant_query_field_throws(**kwargs): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='empty') - eq_(len(ds.fields()), 1) - eq_(ds.fields(), ['key']) - eq_(ds.field_types(), ['int']) + assert len(ds.fields()) == 1 + assert ds.fields() == ['key'] + assert ds.field_types() == ['int'] query = mapnik.Query(ds.envelope()) + for fld in ds.fields(): query.add_property_name(fld) - # also add an invalid one, triggering throw - query.add_property_name('bogus') - ds.features(query) + # also add an invalid one, triggering throw + query.add_property_name('bogus') + with pytest.raises(RuntimeError): + ds.features(query) def test_auto_detection_of_unique_feature_id_32_bit(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test2', geometry_field='geom', autodetect_key_field=True) - fs = ds.featureset() - f = fs.next() - eq_(len(ds.fields()),len(f.attributes)) - eq_(f['manual_id'], 0) - eq_(fs.next()['manual_id'], 1) - eq_(fs.next()['manual_id'], 1000) - eq_(fs.next()['manual_id'], -1000) - eq_(fs.next()['manual_id'], 2147483647) - eq_(fs.next()['manual_id'], -2147483648) - - fs = ds.featureset() - eq_(fs.next().id(), 0) - eq_(fs.next().id(), 1) - eq_(fs.next().id(), 1000) - eq_(fs.next().id(), -1000) - eq_(fs.next().id(), 2147483647) - eq_(fs.next().id(), -2147483648) + fs = iter(ds) + f = next(fs) + assert len(ds.fields()) == len(f.attributes) + assert f['manual_id'] == 0 + assert next(fs)['manual_id'] == 1 + assert next(fs)['manual_id'] == 1000 + assert next(fs)['manual_id'] == -1000 + assert next(fs)['manual_id'] == 2147483647 + assert next(fs)['manual_id'] == -2147483648 + + fs = iter(ds) + assert next(fs).id() == 0 + assert next(fs).id() == 1 + assert next(fs).id() == 1000 + assert next(fs).id() == -1000 + assert next(fs).id() == 2147483647 + assert next(fs).id() == -2147483648 meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), u'manual_id') - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == 4326 + assert meta.get('key_field') == u'manual_id' + assert meta['geometry_type'] == mapnik.DataGeometryType.Point def test_auto_detection_of_unique_feature_id_32_bit_no_attribute(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test2', geometry_field='geom', autodetect_key_field=True, key_field_as_attribute=False) - fs = ds.featureset() - f = fs.next() - eq_(len(ds.fields()),len(f.attributes)) - eq_(len(ds.fields()),0) - eq_(len(f.attributes),0) - eq_(f.id(), 0) - eq_(fs.next().id(), 1) - eq_(fs.next().id(), 1000) - eq_(fs.next().id(), -1000) - eq_(fs.next().id(), 2147483647) - eq_(fs.next().id(), -2147483648) + fs = iter(ds) + f = next(fs) + assert len(ds.fields()) == len(f.attributes) + assert len(ds.fields()) == 0 + assert len(f.attributes) == 0 + assert f.id() == 0 + assert next(fs).id() == 1 + assert next(fs).id() == 1000 + assert next(fs).id() == -1000 + assert next(fs).id() == 2147483647 + assert next(fs).id() == -2147483648 meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), u'manual_id') - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == 4326 + assert meta.get('key_field') == u'manual_id' + assert meta['geometry_type'] == mapnik.DataGeometryType.Point def test_auto_detection_will_fail_since_no_primary_key(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test3', geometry_field='geom', autodetect_key_field=False) - fs = ds.featureset() - feat = fs.next() - eq_(feat['manual_id'], 0) - eq_(feat['non_id'],9223372036854775807) - eq_(fs.next()['manual_id'], 1) - eq_(fs.next()['manual_id'], 1000) - eq_(fs.next()['manual_id'], -1000) - eq_(fs.next()['manual_id'], 2147483647) - eq_(fs.next()['manual_id'], -2147483648) + fs = iter(ds) + feat = next(fs) + assert feat['manual_id'] == 0 + assert feat['non_id'] == 9223372036854775807 + assert next(fs)['manual_id'] == 1 + assert next(fs)['manual_id'] == 1000 + assert next(fs)['manual_id'] == -1000 + assert next(fs)['manual_id'] == 2147483647 + assert next(fs)['manual_id'] == -2147483648 # since no valid primary key will be detected the fallback # is auto-incrementing counter - fs = ds.featureset() - eq_(fs.next().id(), 1) - eq_(fs.next().id(), 2) - eq_(fs.next().id(), 3) - eq_(fs.next().id(), 4) - eq_(fs.next().id(), 5) - eq_(fs.next().id(), 6) + fs = iter(ds) + assert next(fs).id() == 1 + assert next(fs).id() == 2 + assert next(fs).id() == 3 + assert next(fs).id() == 4 + assert next(fs).id() == 5 + assert next(fs).id() == 6 meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), None) - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == 4326 + assert meta.get('key_field') == None + assert meta['geometry_type'] == mapnik.DataGeometryType.Point + - @raises(RuntimeError) def test_auto_detection_will_fail_and_should_throw(): - ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test3', - geometry_field='geom', - autodetect_key_field=True) - ds.featureset() + with pytest.raises(RuntimeError): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test3', + geometry_field='geom', + autodetect_key_field=True) + iter(ds) def test_auto_detection_of_unique_feature_id_64_bit(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test4', geometry_field='geom', autodetect_key_field=True) - fs = ds.featureset() - f = fs.next() - eq_(len(ds.fields()),len(f.attributes)) - eq_(f['manual_id'], 0) - eq_(fs.next()['manual_id'], 1) - eq_(fs.next()['manual_id'], 1000) - eq_(fs.next()['manual_id'], -1000) - eq_(fs.next()['manual_id'], 2147483647) - eq_(fs.next()['manual_id'], -2147483648) - - fs = ds.featureset() - eq_(fs.next().id(), 0) - eq_(fs.next().id(), 1) - eq_(fs.next().id(), 1000) - eq_(fs.next().id(), -1000) - eq_(fs.next().id(), 2147483647) - eq_(fs.next().id(), -2147483648) + fs = iter(ds) + f = next(fs) + assert len(ds.fields()) == len(f.attributes) + assert f['manual_id'] == 0 + assert next(fs)['manual_id'] == 1 + assert next(fs)['manual_id'] == 1000 + assert next(fs)['manual_id'] == -1000 + assert next(fs)['manual_id'] == 2147483647 + assert next(fs)['manual_id'] == -2147483648 + + fs = iter(ds) + assert next(fs).id() == 0 + assert next(fs).id() == 1 + assert next(fs).id() == 1000 + assert next(fs).id() == -1000 + assert next(fs).id() == 2147483647 + assert next(fs).id() == -2147483648 meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), u'manual_id') - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == 4326 + assert meta.get('key_field') == u'manual_id' + assert meta['geometry_type'] == mapnik.DataGeometryType.Point def test_disabled_auto_detection_and_subquery(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='''(select geom, 'a'::varchar as name from test2) as t''', geometry_field='geom', autodetect_key_field=False) - fs = ds.featureset() - feat = fs.next() - eq_(feat.id(), 1) - eq_(feat['name'], 'a') - feat = fs.next() - eq_(feat.id(), 2) - eq_(feat['name'], 'a') - feat = fs.next() - eq_(feat.id(), 3) - eq_(feat['name'], 'a') - feat = fs.next() - eq_(feat.id(), 4) - eq_(feat['name'], 'a') - feat = fs.next() - eq_(feat.id(), 5) - eq_(feat['name'], 'a') - feat = fs.next() - eq_(feat.id(), 6) - eq_(feat['name'], 'a') + fs = iter(ds) + feat = next(fs) + assert feat.id() == 1 + assert feat['name'] == 'a' + feat = next(fs) + assert feat.id() == 2 + assert feat['name'] == 'a' + feat = next(fs) + assert feat.id() == 3 + assert feat['name'] == 'a' + feat = next(fs) + assert feat.id() == 4 + assert feat['name'] == 'a' + feat = next(fs) + assert feat.id() == 5 + assert feat['name'] == 'a' + feat = next(fs) + assert feat.id() == 6 + assert feat['name'] == 'a' meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), None) - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == 4326 + assert meta.get('key_field') == None + assert meta['geometry_type'] == mapnik.DataGeometryType.Point def test_auto_detection_and_subquery_including_key(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='''(select geom, manual_id from test2) as t''', geometry_field='geom', autodetect_key_field=True) - fs = ds.featureset() - f = fs.next() - eq_(len(ds.fields()),len(f.attributes)) - eq_(f['manual_id'], 0) - eq_(fs.next()['manual_id'], 1) - eq_(fs.next()['manual_id'], 1000) - eq_(fs.next()['manual_id'], -1000) - eq_(fs.next()['manual_id'], 2147483647) - eq_(fs.next()['manual_id'], -2147483648) - - fs = ds.featureset() - eq_(fs.next().id(), 0) - eq_(fs.next().id(), 1) - eq_(fs.next().id(), 1000) - eq_(fs.next().id(), -1000) - eq_(fs.next().id(), 2147483647) - eq_(fs.next().id(), -2147483648) + fs = iter(ds) + f = next(fs) + assert len(ds.fields()) == len(f.attributes) + assert f['manual_id'] == 0 + assert next(fs)['manual_id'] == 1 + assert next(fs)['manual_id'] == 1000 + assert next(fs)['manual_id'] == -1000 + assert next(fs)['manual_id'] == 2147483647 + assert next(fs)['manual_id'] == -2147483648 + + fs = iter(ds) + assert next(fs).id() == 0 + assert next(fs).id() == 1 + assert next(fs).id() == 1000 + assert next(fs).id() == -1000 + assert next(fs).id() == 2147483647 + assert next(fs).id() == -2147483648 meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), u'manual_id') - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == 4326 + assert meta.get('key_field') == u'manual_id' + assert meta['geometry_type'] == mapnik.DataGeometryType.Point + - @raises(RuntimeError) def test_auto_detection_of_invalid_numeric_primary_key(): - mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='''(select geom, manual_id::numeric from test2) as t''', - geometry_field='geom', - autodetect_key_field=True) + with pytest.raises(RuntimeError): + mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='''(select geom, manual_id::numeric from test2) as t''', + geometry_field='geom', + autodetect_key_field=True) + - @raises(RuntimeError) def test_auto_detection_of_invalid_multiple_keys(): - mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='''test6''', - geometry_field='geom', - autodetect_key_field=True) + with pytest.raises(RuntimeError): + mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='''test6''', + geometry_field='geom', + autodetect_key_field=True) + - @raises(RuntimeError) def test_auto_detection_of_invalid_multiple_keys_subquery(): - mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='''(select first_id,second_id,geom from test6) as t''', - geometry_field='geom', - autodetect_key_field=True) + with pytest.raises(RuntimeError): + mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='''(select first_id,second_id,geom from test6) as t''', + geometry_field='geom', + autodetect_key_field=True) def test_manually_specified_feature_id_field(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test4', geometry_field='geom', key_field='manual_id', autodetect_key_field=True) - fs = ds.featureset() - f = fs.next() - eq_(len(ds.fields()),len(f.attributes)) - eq_(f['manual_id'], 0) - eq_(fs.next()['manual_id'], 1) - eq_(fs.next()['manual_id'], 1000) - eq_(fs.next()['manual_id'], -1000) - eq_(fs.next()['manual_id'], 2147483647) - eq_(fs.next()['manual_id'], -2147483648) - - fs = ds.featureset() - eq_(fs.next().id(), 0) - eq_(fs.next().id(), 1) - eq_(fs.next().id(), 1000) - eq_(fs.next().id(), -1000) - eq_(fs.next().id(), 2147483647) - eq_(fs.next().id(), -2147483648) + fs = iter(ds) + f = next(fs) + assert len(ds.fields()) == len(f.attributes) + assert f['manual_id'] == 0 + assert next(fs)['manual_id'] == 1 + assert next(fs)['manual_id'] == 1000 + assert next(fs)['manual_id'] == -1000 + assert next(fs)['manual_id'] == 2147483647 + assert next(fs)['manual_id'] == -2147483648 + + fs = iter(ds) + assert next(fs).id() == 0 + assert next(fs).id() == 1 + assert next(fs).id() == 1000 + assert next(fs).id() == -1000 + assert next(fs).id() == 2147483647 + assert next(fs).id() == -2147483648 meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), u'manual_id') - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == 4326 + assert meta.get('key_field') == u'manual_id' + assert meta['geometry_type'] == mapnik.DataGeometryType.Point def test_numeric_type_feature_id_field(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test5', geometry_field='geom', autodetect_key_field=False) - fs = ds.featureset() - eq_(fs.next()['manual_id'], -1) - eq_(fs.next()['manual_id'], 1) + fs = iter(ds) + assert next(fs)['manual_id'] == -1 + assert next(fs)['manual_id'] == 1 - fs = ds.featureset() - eq_(fs.next().id(), 1) - eq_(fs.next().id(), 2) + fs = iter(ds) + assert next(fs).id() == 1 + assert next(fs).id() == 2 meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), None) - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == 4326 + assert meta.get('key_field') == None + assert meta['geometry_type'] == mapnik.DataGeometryType.Point def test_querying_table_with_mixed_case(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='"tableWithMixedCase"', geometry_field='geom', autodetect_key_field=True) - fs = ds.featureset() + fs = iter(ds) for id in range(1, 5): - eq_(fs.next().id(), id) + assert next(fs).id() == id meta = ds.describe() - eq_(meta['srid'], -1) - eq_(meta.get('key_field'), u'gid') - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == -1 + assert meta.get('key_field') == u'gid' + assert meta['geometry_type'] == mapnik.DataGeometryType.Point def test_querying_subquery_with_mixed_case(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='(SeLeCt * FrOm "tableWithMixedCase") as MixedCaseQuery', geometry_field='geom', autodetect_key_field=True) - fs = ds.featureset() + fs = iter(ds) for id in range(1, 5): - eq_(fs.next().id(), id) + assert next(fs).id() == id meta = ds.describe() - eq_(meta['srid'], -1) - eq_(meta.get('key_field'), u'gid') - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == -1 + assert meta.get('key_field') == u'gid' + assert meta['geometry_type'] == mapnik.DataGeometryType.Point def test_bbox_token_in_subquery1(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table=''' (SeLeCt * FrOm "tableWithMixedCase" where geom && !bbox! ) as MixedCaseQuery''', geometry_field='geom', autodetect_key_field=True) - fs = ds.featureset() + fs = iter(ds) for id in range(1, 5): - eq_(fs.next().id(), id) + assert next(fs).id() == id meta = ds.describe() - eq_(meta['srid'], -1) - eq_(meta.get('key_field'), u'gid') - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == -1 + assert meta.get('key_field') == u'gid' + assert meta['geometry_type'] == mapnik.DataGeometryType.Point def test_bbox_token_in_subquery2(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table=''' (SeLeCt * FrOm "tableWithMixedCase" where ST_Intersects(geom,!bbox!) ) as MixedCaseQuery''', geometry_field='geom', autodetect_key_field=True) - fs = ds.featureset() + fs = iter(ds) for id in range(1, 5): - eq_(fs.next().id(), id) + assert next(fs).id() == id meta = ds.describe() - eq_(meta['srid'], -1) - eq_(meta.get('key_field'), u'gid') - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == -1 + assert meta.get('key_field') == u'gid' + assert meta['geometry_type'] == mapnik.DataGeometryType.Point def test_empty_geom(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test7', geometry_field='geom') - fs = ds.featureset() - eq_(fs.next()['gid'], 1) + fs = iter(ds) + assert next(fs)['gid'] == 1 meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), None) - eq_(meta['geometry_type'], mapnik.DataGeometryType.Collection) + assert meta['srid'] == 4326 + assert meta.get('key_field') == None + assert meta['geometry_type'] == mapnik.DataGeometryType.Collection def create_ds(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test', max_size=20, geometry_field='geom') - fs = list(ds.all_features()) - eq_(len(fs), 8) + fs = list(iter(ds)) + assert len(fs) == 8 meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), None) - eq_(meta['geometry_type'], mapnik.DataGeometryType.Collection) + assert meta['srid'] == 4326 + assert meta.get('key_field') == None + assert meta['geometry_type'] == mapnik.DataGeometryType.Collection def test_threaded_create(NUM_THREADS=100): # run one to start before thread loop @@ -747,16 +738,16 @@ def test_threaded_create(NUM_THREADS=100): t.start() t.join() runs += 1 - eq_(runs, NUM_THREADS) + assert runs == NUM_THREADS def create_ds_and_error(): try: ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='asdfasdfasdfasdfasdf', max_size=20) - ds.all_features() + iter(ds) except Exception as e: - eq_('in executeQuery' in str(e), True) + assert 'in executeQuery' in str(e) def test_threaded_create2(NUM_THREADS=10): for i in range(NUM_THREADS): @@ -768,23 +759,23 @@ def test_that_64bit_int_fields_work(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test8', geometry_field='geom') - eq_(len(ds.fields()), 2) - eq_(ds.fields(), ['gid', 'int_field']) - eq_(ds.field_types(), ['int', 'int']) - fs = ds.featureset() - feat = fs.next() - eq_(feat.id(), 1) - eq_(feat['gid'], 1) - eq_(feat['int_field'], 2147483648) - feat = fs.next() - eq_(feat.id(), 2) - eq_(feat['gid'], 2) - eq_(feat['int_field'], 922337203685477580) + assert len(ds.fields()) == 2 + assert ds.fields(), ['gid' == 'int_field'] + assert ds.field_types(), ['int' == 'int'] + fs = iter(ds) + feat = next(fs) + assert feat.id() == 1 + assert feat['gid'] == 1 + assert feat['int_field'] == 2147483648 + feat = next(fs) + assert feat.id() == 2 + assert feat['gid'] == 2 + assert feat['int_field'] == 922337203685477580 meta = ds.describe() - eq_(meta['srid'], -1) - eq_(meta.get('key_field'), None) - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == -1 + assert meta.get('key_field') == None + assert meta['geometry_type'] == mapnik.DataGeometryType.Point def test_persist_connection_off(): # NOTE: max_size should be equal or greater than @@ -799,131 +790,125 @@ def test_persist_connection_off(): persist_connection=False, table='(select ST_MakePoint(0,0) as g, pg_backend_pid() as p, 1 as v) as w', geometry_field='g') - fs = ds.featureset() - eq_(fs.next()['v'], 1) + fs = iter(ds) + assert next(fs)['v'] == 1 meta = ds.describe() - eq_(meta['srid'], -1) - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == -1 + assert meta['geometry_type'] == mapnik.DataGeometryType.Point def test_null_comparision(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test9', geometry_field='geom') - fs = ds.featureset() - feat = fs.next() + fs = iter(ds) + feat = next(fs) meta = ds.describe() - eq_(meta['srid'], -1) - eq_(meta.get('key_field'), None) - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) - - eq_(feat['gid'], 1) - eq_(feat['name'], 'name') - eq_(mapnik.Expression("[name] = 'name'").evaluate(feat), True) - eq_(mapnik.Expression("[name] = ''").evaluate(feat), False) - eq_(mapnik.Expression("[name] = null").evaluate(feat), False) - eq_(mapnik.Expression("[name] = true").evaluate(feat), False) - eq_(mapnik.Expression("[name] = false").evaluate(feat), False) - eq_(mapnik.Expression("[name] != 'name'").evaluate(feat), False) - eq_(mapnik.Expression("[name] != ''").evaluate(feat), True) - eq_(mapnik.Expression("[name] != null").evaluate(feat), True) - eq_(mapnik.Expression("[name] != true").evaluate(feat), True) - eq_(mapnik.Expression("[name] != false").evaluate(feat), True) - - feat = fs.next() - eq_(feat['gid'], 2) - eq_(feat['name'], '') - eq_(mapnik.Expression("[name] = 'name'").evaluate(feat), False) - eq_(mapnik.Expression("[name] = ''").evaluate(feat), True) - eq_(mapnik.Expression("[name] = null").evaluate(feat), False) - eq_(mapnik.Expression("[name] = true").evaluate(feat), False) - eq_(mapnik.Expression("[name] = false").evaluate(feat), False) - eq_(mapnik.Expression("[name] != 'name'").evaluate(feat), True) - eq_(mapnik.Expression("[name] != ''").evaluate(feat), False) - eq_(mapnik.Expression("[name] != null").evaluate(feat), True) - eq_(mapnik.Expression("[name] != true").evaluate(feat), True) - eq_(mapnik.Expression("[name] != false").evaluate(feat), True) - - feat = fs.next() - eq_(feat['gid'], 3) - eq_(feat['name'], None) # null - eq_(mapnik.Expression("[name] = 'name'").evaluate(feat), False) - eq_(mapnik.Expression("[name] = ''").evaluate(feat), False) - eq_(mapnik.Expression("[name] = null").evaluate(feat), True) - eq_(mapnik.Expression("[name] = true").evaluate(feat), False) - eq_(mapnik.Expression("[name] = false").evaluate(feat), False) - eq_(mapnik.Expression("[name] != 'name'").evaluate(feat), True) + assert meta['srid'] == -1 + assert meta.get('key_field') == None + assert meta['geometry_type'] == mapnik.DataGeometryType.Point + + assert feat['gid'] == 1 + assert feat['name'] == 'name' + assert mapnik.Expression("[name] = 'name'").evaluate(feat) + assert not mapnik.Expression("[name] = ''").evaluate(feat) + assert not mapnik.Expression("[name] = null").evaluate(feat) + assert not mapnik.Expression("[name] = true").evaluate(feat) + assert not mapnik.Expression("[name] = false").evaluate(feat) + assert not mapnik.Expression("[name] != 'name'").evaluate(feat) + assert mapnik.Expression("[name] != ''").evaluate(feat) + assert mapnik.Expression("[name] != null").evaluate(feat) + assert mapnik.Expression("[name] != true").evaluate(feat) + assert mapnik.Expression("[name] != false").evaluate(feat) + + feat = next(fs) + assert feat['gid'] == 2 + assert feat['name'] == '' + assert mapnik.Expression("[name] = 'name'").evaluate(feat) == False + assert mapnik.Expression("[name] = ''").evaluate(feat) == True + assert mapnik.Expression("[name] = null").evaluate(feat) == False + assert mapnik.Expression("[name] = true").evaluate(feat) == False + assert mapnik.Expression("[name] = false").evaluate(feat) == False + assert mapnik.Expression("[name] != 'name'").evaluate(feat) == True + assert mapnik.Expression("[name] != ''").evaluate(feat) == False + assert mapnik.Expression("[name] != null").evaluate(feat) == True + assert mapnik.Expression("[name] != true").evaluate(feat) == True + assert mapnik.Expression("[name] != false").evaluate(feat) == True + + feat = next(fs) + assert feat['gid'] == 3 + assert feat['name'] == None # null + assert mapnik.Expression("[name] = 'name'").evaluate(feat) == False + assert mapnik.Expression("[name] = ''").evaluate(feat) == False + assert mapnik.Expression("[name] = null").evaluate(feat) == True + assert mapnik.Expression("[name] = true").evaluate(feat) == False + assert mapnik.Expression("[name] = false").evaluate(feat) == False + assert mapnik.Expression("[name] != 'name'").evaluate(feat) == True # https://github.com/mapnik/mapnik/issues/1859 - eq_(mapnik.Expression("[name] != ''").evaluate(feat), False) - eq_(mapnik.Expression("[name] != null").evaluate(feat), False) - eq_(mapnik.Expression("[name] != true").evaluate(feat), True) - eq_(mapnik.Expression("[name] != false").evaluate(feat), True) + assert mapnik.Expression("[name] != ''").evaluate(feat) == False + assert mapnik.Expression("[name] != null").evaluate(feat) == False + assert mapnik.Expression("[name] != true").evaluate(feat) == True + assert mapnik.Expression("[name] != false").evaluate(feat) == True def test_null_comparision2(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='test10', geometry_field='geom') - fs = ds.featureset() - feat = fs.next() + fs = iter(ds) + feat = next(fs) meta = ds.describe() - eq_(meta['srid'], -1) - eq_(meta.get('key_field'), None) - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) - - eq_(feat['gid'], 1) - eq_(feat['bool_field'], True) - eq_(mapnik.Expression("[bool_field] = 'name'").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] = ''").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] = null").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] = true").evaluate(feat), True) - eq_(mapnik.Expression("[bool_field] = false").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] != 'name'").evaluate(feat), True) - eq_(mapnik.Expression("[bool_field] != ''").evaluate( - feat), True) # in 2.1.x used to be False - eq_(mapnik.Expression("[bool_field] != null").evaluate( - feat), True) # in 2.1.x used to be False - eq_(mapnik.Expression("[bool_field] != true").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] != false").evaluate(feat), True) - - feat = fs.next() - eq_(feat['gid'], 2) - eq_(feat['bool_field'], False) - eq_(mapnik.Expression("[bool_field] = 'name'").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] = ''").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] = null").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] = true").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] = false").evaluate(feat), True) - eq_(mapnik.Expression("[bool_field] != 'name'").evaluate(feat), True) - eq_(mapnik.Expression("[bool_field] != ''").evaluate(feat), True) - eq_(mapnik.Expression("[bool_field] != null").evaluate( - feat), True) # in 2.1.x used to be False - eq_(mapnik.Expression("[bool_field] != true").evaluate(feat), True) - eq_(mapnik.Expression("[bool_field] != false").evaluate(feat), False) - - feat = fs.next() - eq_(feat['gid'], 3) - eq_(feat['bool_field'], None) # null - eq_(mapnik.Expression("[bool_field] = 'name'").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] = ''").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] = null").evaluate(feat), True) - eq_(mapnik.Expression("[bool_field] = true").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] = false").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] != 'name'").evaluate( - feat), True) # in 2.1.x used to be False + assert meta['srid'] == -1 + assert meta.get('key_field') == None + assert meta['geometry_type'] == mapnik.DataGeometryType.Point + + assert feat['gid'] == 1 + assert feat['bool_field'] + assert not mapnik.Expression("[bool_field] = 'name'").evaluate(feat) + assert not mapnik.Expression("[bool_field] = ''").evaluate(feat) + assert not mapnik.Expression("[bool_field] = null").evaluate(feat) + assert mapnik.Expression("[bool_field] = true").evaluate(feat) + assert not mapnik.Expression("[bool_field] = false").evaluate(feat) + assert mapnik.Expression("[bool_field] != 'name'").evaluate(feat) + assert mapnik.Expression("[bool_field] != ''").evaluate(feat) # in 2.1.x used to be False + assert mapnik.Expression("[bool_field] != null").evaluate(feat) # in 2.1.x used to be False + assert not mapnik.Expression("[bool_field] != true").evaluate(feat) + assert mapnik.Expression("[bool_field] != false").evaluate(feat) + + feat = next(fs) + assert feat['gid'] == 2 + assert not feat['bool_field'] + assert not mapnik.Expression("[bool_field] = 'name'").evaluate(feat) + assert not mapnik.Expression("[bool_field] = ''").evaluate(feat) + assert not mapnik.Expression("[bool_field] = null").evaluate(feat) + assert not mapnik.Expression("[bool_field] = true").evaluate(feat) + assert mapnik.Expression("[bool_field] = false").evaluate(feat) + assert mapnik.Expression("[bool_field] != 'name'").evaluate(feat) + assert mapnik.Expression("[bool_field] != ''").evaluate(feat) + assert mapnik.Expression("[bool_field] != null").evaluate(feat) # in 2.1.x used to be False + assert mapnik.Expression("[bool_field] != true").evaluate(feat) + assert not mapnik.Expression("[bool_field] != false").evaluate(feat) + + feat = next(fs) + assert feat['gid'] == 3 + assert feat['bool_field'] == None # null + assert not mapnik.Expression("[bool_field] = 'name'").evaluate(feat) + assert not mapnik.Expression("[bool_field] = ''").evaluate(feat) + assert mapnik.Expression("[bool_field] = null").evaluate(feat) + assert not mapnik.Expression("[bool_field] = true").evaluate(feat) + assert not mapnik.Expression("[bool_field] = false").evaluate(feat) + assert mapnik.Expression("[bool_field] != 'name'").evaluate(feat) # in 2.1.x used to be False # https://github.com/mapnik/mapnik/issues/1859 - eq_(mapnik.Expression("[bool_field] != ''").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] != null").evaluate(feat), False) - eq_(mapnik.Expression("[bool_field] != true").evaluate( - feat), True) # in 2.1.x used to be False - eq_(mapnik.Expression("[bool_field] != false").evaluate( - feat), True) # in 2.1.x used to be False + assert not mapnik.Expression("[bool_field] != ''").evaluate(feat) + assert not mapnik.Expression("[bool_field] != null").evaluate(feat) + assert mapnik.Expression("[bool_field] != true").evaluate(feat) # in 2.1.x used to be False + assert mapnik.Expression("[bool_field] != false").evaluate(feat) # in 2.1.x used to be False # https://github.com/mapnik/mapnik/issues/1816 def test_exception_message_reporting(): try: mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='doesnotexist') except Exception as e: - eq_(str(e) != 'unidentifiable C++ exception', True) + assert str(e) != 'unidentifiable C++ exception' def test_null_id_field(): opts = {'type': 'postgis', @@ -931,17 +916,17 @@ def test_null_id_field(): 'geometry_field': 'geom', 'table': "(select null::bigint as osm_id, GeomFromEWKT('SRID=4326;POINT(0 0)') as geom) as tmp"} ds = mapnik.Datasource(**opts) - fs = ds.featureset() - feat = fs.next() - eq_(feat.id(), long(1)) - eq_(feat['osm_id'], None) + fs = iter(ds) + feat = next(fs) + assert feat.id() == int(1) + assert feat['osm_id'] == None meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), None) - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['srid'] == 4326 + assert meta.get('key_field') == None + assert meta['geometry_type'] == mapnik.DataGeometryType.Point + - @raises(StopIteration) def test_null_key_field(): opts = {'type': 'postgis', "key_field": 'osm_id', @@ -949,10 +934,11 @@ def test_null_key_field(): 'geometry_field': 'geom', 'table': "(select null::bigint as osm_id, GeomFromEWKT('SRID=4326;POINT(0 0)') as geom) as tmp"} ds = mapnik.Datasource(**opts) - fs = ds.featureset() - # should throw since key_field is null: StopIteration: No more - # features. - fs.next() + fs = iter(ds) + with pytest.raises(StopIteration): + # should throw since key_field is null: StopIteration: No more + # features. + next(fs) def test_psql_error_should_not_break_connection_pool(): # Bad request, will trigger an error when returning result @@ -966,32 +952,32 @@ def test_psql_error_should_not_break_connection_pool(): # This will/should trigger a PSQL error failed = False try: - fs = ds_bad.featureset() + fs = iter(ds_bad) count = sum(1 for f in fs) except RuntimeError as e: - assert 'invalid input syntax for integer' in str(e) + assert 'invalid input syntax for type integer' in str(e) failed = True - eq_(failed, True) + assert failed == True # Should be ok - fs = ds_good.featureset() + fs = iter(ds_good) count = sum(1 for f in fs) - eq_(count, 8) + assert count == 8 def test_psql_error_should_give_back_connections_opened_for_lower_layers_to_the_pool(): map1 = mapnik.Map(600, 300) s = mapnik.Style() r = mapnik.Rule() - r.symbols.append(mapnik.PolygonSymbolizer()) + r.symbolizers.append(mapnik.PolygonSymbolizer()) s.rules.append(r) map1.append_style('style', s) # This layer will fail after a while buggy_s = mapnik.Style() buggy_r = mapnik.Rule() - buggy_r.symbols.append(mapnik.PolygonSymbolizer()) - buggy_r.filter = mapnik.Filter("[fips] = 'FR'") + buggy_r.symbolizers.append(mapnik.PolygonSymbolizer()) + buggy_r.filter = mapnik.Expression("[fips] = 'FR'") buggy_s.rules.append(buggy_r) map1.append_style('style for buggy layer', buggy_s) buggy_layer = mapnik.Layer('this layer is buggy at runtime') @@ -1015,8 +1001,8 @@ def test_psql_error_should_give_back_connections_opened_for_lower_layers_to_the_ map2.background = mapnik.Color('steelblue') s = mapnik.Style() r = mapnik.Rule() - r.symbols.append(mapnik.LineSymbolizer()) - r.symbols.append(mapnik.LineSymbolizer()) + r.symbolizers.append(mapnik.LineSymbolizer()) + r.symbolizers.append(mapnik.LineSymbolizer()) s.rules.append(r) map2.append_style('style', s) layer1 = mapnik.Layer('layer1') @@ -1031,9 +1017,9 @@ def test_psql_error_should_give_back_connections_opened_for_lower_layers_to_the_ mapnik.render_to_file( map1, '/tmp/mapnik-postgis-test-map1.png', 'png') # Test must fail if error was not raised just above - eq_(False, True) + assert False == True except RuntimeError as e: - assert 'invalid input syntax for integer' in str(e) + assert 'invalid input syntax for type integer' in str(e) pass # This used to raise an exception before correction of issue 2042 mapnik.render_to_file(map2, '/tmp/mapnik-postgis-test-map2.png', 'png') @@ -1042,223 +1028,221 @@ def test_handling_of_zm_dimensions(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='(select gid,ST_CoordDim(geom) as dim,name,geom from test12) as tmp', geometry_field='geom') - eq_(len(ds.fields()), 3) - eq_(ds.fields(), ['gid', 'dim', 'name']) - eq_(ds.field_types(), ['int', 'int', 'str']) - fs = ds.featureset() + assert len(ds.fields()) == 3 + assert ds.fields(), ['gid', 'dim' == 'name'] + assert ds.field_types(), ['int', 'int' == 'str'] + fs = iter(ds) meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), None) + assert meta['srid'] == 4326 + assert meta.get('key_field') == None # Note: this is incorrect because we only check first couple geoms - eq_(meta['geometry_type'], mapnik.DataGeometryType.Point) + assert meta['geometry_type'] == mapnik.DataGeometryType.Point # Point (2d) - feat = fs.next() - eq_(feat.id(), 1) - eq_(feat['gid'], 1) - eq_(feat['dim'], 2) - eq_(feat['name'], 'Point') - eq_(feat.geometry.to_wkt(), 'POINT(0 0)') + feat = next(fs) + assert feat.id() == 1 + assert feat['gid'] == 1 + assert feat['dim'] == 2 + assert feat['name'] == 'Point' + assert feat.geometry.to_wkt() == 'POINT(0 0)' # PointZ - feat = fs.next() - eq_(feat.id(), 2) - eq_(feat['gid'], 2) - eq_(feat['dim'], 3) - eq_(feat['name'], 'PointZ') - eq_(feat.geometry.to_wkt(), 'POINT(0 0)') + feat = next(fs) + assert feat.id() == 2 + assert feat['gid'] == 2 + assert feat['dim'] == 3 + assert feat['name'] == 'PointZ' + assert feat.geometry.to_wkt() == 'POINT(0 0)' # PointM - feat = fs.next() - eq_(feat.id(), 3) - eq_(feat['gid'], 3) - eq_(feat['dim'], 3) - eq_(feat['name'], 'PointM') - eq_(feat.geometry.to_wkt(), 'POINT(0 0)') + feat = next(fs) + assert feat.id() == 3 + assert feat['gid'] == 3 + assert feat['dim'] == 3 + assert feat['name'] == 'PointM' + assert feat.geometry.to_wkt() == 'POINT(0 0)' # PointZM - feat = fs.next() - eq_(feat.id(), 4) - eq_(feat['gid'], 4) - eq_(feat['dim'], 4) - eq_(feat['name'], 'PointZM') + feat = next(fs) + assert feat.id() == 4 + assert feat['gid'] == 4 + assert feat['dim'] == 4 + assert feat['name'] == 'PointZM' - eq_(feat.geometry.to_wkt(), 'POINT(0 0)') + assert feat.geometry.to_wkt() == 'POINT(0 0)' # MultiPoint - feat = fs.next() - eq_(feat.id(), 5) - eq_(feat['gid'], 5) - eq_(feat['dim'], 2) - eq_(feat['name'], 'MultiPoint') - eq_(feat.geometry.to_wkt(), 'MULTIPOINT(0 0,1 1)') + feat = next(fs) + assert feat.id() == 5 + assert feat['gid'] == 5 + assert feat['dim'] == 2 + assert feat['name'] == 'MultiPoint' + assert feat.geometry.to_wkt() == 'MULTIPOINT(0 0,1 1)' # MultiPointZ - feat = fs.next() - eq_(feat.id(), 6) - eq_(feat['gid'], 6) - eq_(feat['dim'], 3) - eq_(feat['name'], 'MultiPointZ') - eq_(feat.geometry.to_wkt(), 'MULTIPOINT(0 0,1 1)') + feat = next(fs) + assert feat.id() == 6 + assert feat['gid'] == 6 + assert feat['dim'] == 3 + assert feat['name'] == 'MultiPointZ' + assert feat.geometry.to_wkt() == 'MULTIPOINT(0 0,1 1)' # MultiPointM - feat = fs.next() - eq_(feat.id(), 7) - eq_(feat['gid'], 7) - eq_(feat['dim'], 3) - eq_(feat['name'], 'MultiPointM') - eq_(feat.geometry.to_wkt(), 'MULTIPOINT(0 0,1 1)') + feat = next(fs) + assert feat.id() == 7 + assert feat['gid'] == 7 + assert feat['dim'] == 3 + assert feat['name'] == 'MultiPointM' + assert feat.geometry.to_wkt() == 'MULTIPOINT(0 0,1 1)' # MultiPointZM - feat = fs.next() - eq_(feat.id(), 8) - eq_(feat['gid'], 8) - eq_(feat['dim'], 4) - eq_(feat['name'], 'MultiPointZM') - eq_(feat.geometry.to_wkt(), 'MULTIPOINT(0 0,1 1)') + feat = next(fs) + assert feat.id() == 8 + assert feat['gid'] == 8 + assert feat['dim'] == 4 + assert feat['name'] == 'MultiPointZM' + assert feat.geometry.to_wkt() == 'MULTIPOINT(0 0,1 1)' # LineString - feat = fs.next() - eq_(feat.id(), 9) - eq_(feat['gid'], 9) - eq_(feat['dim'], 2) - eq_(feat['name'], 'LineString') - eq_(feat.geometry.to_wkt(), 'LINESTRING(0 0,1 1)') + feat = next(fs) + assert feat.id() == 9 + assert feat['gid'] == 9 + assert feat['dim'] == 2 + assert feat['name'] == 'LineString' + assert feat.geometry.to_wkt() == 'LINESTRING(0 0,1 1)' # LineStringZ - feat = fs.next() - eq_(feat.id(), 10) - eq_(feat['gid'], 10) - eq_(feat['dim'], 3) - eq_(feat['name'], 'LineStringZ') - eq_(feat.geometry.to_wkt(), 'LINESTRING(0 0,1 1)') + feat = next(fs) + assert feat.id() == 10 + assert feat['gid'] == 10 + assert feat['dim'] == 3 + assert feat['name'] == 'LineStringZ' + assert feat.geometry.to_wkt() == 'LINESTRING(0 0,1 1)' # LineStringM - feat = fs.next() - eq_(feat.id(), 11) - eq_(feat['gid'], 11) - eq_(feat['dim'], 3) - eq_(feat['name'], 'LineStringM') - eq_(feat.geometry.to_wkt(), 'LINESTRING(0 0,1 1)') + feat = next(fs) + assert feat.id() == 11 + assert feat['gid'] == 11 + assert feat['dim'] == 3 + assert feat['name'] == 'LineStringM' + assert feat.geometry.to_wkt() == 'LINESTRING(0 0,1 1)' # LineStringZM - feat = fs.next() - eq_(feat.id(), 12) - eq_(feat['gid'], 12) - eq_(feat['dim'], 4) - eq_(feat['name'], 'LineStringZM') - eq_(feat.geometry.to_wkt(), 'LINESTRING(0 0,1 1)') + feat = next(fs) + assert feat.id() == 12 + assert feat['gid'] == 12 + assert feat['dim'] == 4 + assert feat['name'] == 'LineStringZM' + assert feat.geometry.to_wkt() == 'LINESTRING(0 0,1 1)' # Polygon - feat = fs.next() - eq_(feat.id(), 13) - eq_(feat['gid'], 13) - eq_(feat['name'], 'Polygon') - eq_(feat.geometry.to_wkt(), 'POLYGON((0 0,1 1,2 2,0 0))') + feat = next(fs) + assert feat.id() == 13 + assert feat['gid'] == 13 + assert feat['name'] == 'Polygon' + assert feat.geometry.to_wkt() == 'POLYGON((0 0,1 1,2 2,0 0))' # PolygonZ - feat = fs.next() - eq_(feat.id(), 14) - eq_(feat['gid'], 14) - eq_(feat['name'], 'PolygonZ') - eq_(feat.geometry.to_wkt(), 'POLYGON((0 0,1 1,2 2,0 0))') + feat = next(fs) + assert feat.id() == 14 + assert feat['gid'] == 14 + assert feat['name'] == 'PolygonZ' + assert feat.geometry.to_wkt() == 'POLYGON((0 0,1 1,2 2,0 0))' # PolygonM - feat = fs.next() - eq_(feat.id(), 15) - eq_(feat['gid'], 15) - eq_(feat['name'], 'PolygonM') - eq_(feat.geometry.to_wkt(), 'POLYGON((0 0,1 1,2 2,0 0))') + feat = next(fs) + assert feat.id() == 15 + assert feat['gid'] == 15 + assert feat['name'] == 'PolygonM' + assert feat.geometry.to_wkt() == 'POLYGON((0 0,1 1,2 2,0 0))' # PolygonZM - feat = fs.next() - eq_(feat.id(), 16) - eq_(feat['gid'], 16) - eq_(feat['name'], 'PolygonZM') - eq_(feat.geometry.to_wkt(), 'POLYGON((0 0,1 1,2 2,0 0))') + feat = next(fs) + assert feat.id() == 16 + assert feat['gid'] == 16 + assert feat['name'] == 'PolygonZM' + assert feat.geometry.to_wkt() == 'POLYGON((0 0,1 1,2 2,0 0))' # MultiLineString - feat = fs.next() - eq_(feat.id(), 17) - eq_(feat['gid'], 17) - eq_(feat['name'], 'MultiLineString') - eq_(feat.geometry.to_wkt(), 'MULTILINESTRING((0 0,1 1),(2 2,3 3))') + feat = next(fs) + assert feat.id() == 17 + assert feat['gid'] == 17 + assert feat['name'] == 'MultiLineString' + assert feat.geometry.to_wkt() == 'MULTILINESTRING((0 0,1 1),(2 2,3 3))' # MultiLineStringZ - feat = fs.next() - eq_(feat.id(), 18) - eq_(feat['gid'], 18) - eq_(feat['name'], 'MultiLineStringZ') - eq_(feat.geometry.to_wkt(), 'MULTILINESTRING((0 0,1 1),(2 2,3 3))') + feat = next(fs) + assert feat.id() == 18 + assert feat['gid'] == 18 + assert feat['name'] == 'MultiLineStringZ' + assert feat.geometry.to_wkt() == 'MULTILINESTRING((0 0,1 1),(2 2,3 3))' # MultiLineStringM - feat = fs.next() - eq_(feat.id(), 19) - eq_(feat['gid'], 19) - eq_(feat['name'], 'MultiLineStringM') - eq_(feat.geometry.to_wkt(), 'MULTILINESTRING((0 0,1 1),(2 2,3 3))') + feat = next(fs) + assert feat.id() == 19 + assert feat['gid'] == 19 + assert feat['name'] == 'MultiLineStringM' + assert feat.geometry.to_wkt() == 'MULTILINESTRING((0 0,1 1),(2 2,3 3))' # MultiLineStringZM - feat = fs.next() - eq_(feat.id(), 20) - eq_(feat['gid'], 20) - eq_(feat['name'], 'MultiLineStringZM') - eq_(feat.geometry.to_wkt(), 'MULTILINESTRING((0 0,1 1),(2 2,3 3))') + feat = next(fs) + assert feat.id() == 20 + assert feat['gid'] == 20 + assert feat['name'] == 'MultiLineStringZM' + assert feat.geometry.to_wkt() == 'MULTILINESTRING((0 0,1 1),(2 2,3 3))' # MultiPolygon - feat = fs.next() - eq_(feat.id(), 21) - eq_(feat['gid'], 21) - eq_(feat['name'], 'MultiPolygon') - eq_(feat.geometry.to_wkt(), - 'MULTIPOLYGON(((0 0,1 1,2 2,0 0)),((0 0,1 1,2 2,0 0)))') + feat = next(fs) + assert feat.id() == 21 + assert feat['gid'] == 21 + assert feat['name'] == 'MultiPolygon' + assert feat.geometry.to_wkt() == 'MULTIPOLYGON(((0 0,1 1,2 2,0 0)),((0 0,1 1,2 2,0 0)))' # MultiPolygonZ - feat = fs.next() - eq_(feat.id(), 22) - eq_(feat['gid'], 22) - eq_(feat['name'], 'MultiPolygonZ') - eq_(feat.geometry.to_wkt(), - 'MULTIPOLYGON(((0 0,1 1,2 2,0 0)),((0 0,1 1,2 2,0 0)))') + feat = next(fs) + assert feat.id() == 22 + assert feat['gid'] == 22 + assert feat['name'] == 'MultiPolygonZ' + assert feat.geometry.to_wkt() == 'MULTIPOLYGON(((0 0,1 1,2 2,0 0)),((0 0,1 1,2 2,0 0)))' # MultiPolygonM - feat = fs.next() - eq_(feat.id(), 23) - eq_(feat['gid'], 23) - eq_(feat['name'], 'MultiPolygonM') - eq_(feat.geometry.to_wkt(), - 'MULTIPOLYGON(((0 0,1 1,2 2,0 0)),((0 0,1 1,2 2,0 0)))') + feat = next(fs) + assert feat.id() == 23 + assert feat['gid'] == 23 + assert feat['name'] == 'MultiPolygonM' + assert feat.geometry.to_wkt() == 'MULTIPOLYGON(((0 0,1 1,2 2,0 0)),((0 0,1 1,2 2,0 0)))' # MultiPolygonZM - feat = fs.next() - eq_(feat.id(), 24) - eq_(feat['gid'], 24) - eq_(feat['name'], 'MultiPolygonZM') - eq_(feat.geometry.to_wkt(), - 'MULTIPOLYGON(((0 0,1 1,2 2,0 0)),((0 0,1 1,2 2,0 0)))') + feat = next(fs) + assert feat.id() == 24 + assert feat['gid'] == 24 + assert feat['name'] == 'MultiPolygonZM' + assert feat.geometry.to_wkt() == 'MULTIPOLYGON(((0 0,1 1,2 2,0 0)),((0 0,1 1,2 2,0 0)))' def test_handling_of_discarded_key_field(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table='(select * from test12) as tmp', key_field='gid', key_field_as_attribute=False) - fs = ds.featureset() - feat = fs.next() - eq_(feat['name'],'Point') + fs = iter(ds) + feat = next(fs) + assert feat['name'] == 'Point' def test_variable_in_subquery1(): ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, table=''' (select * from test where !@zoom! = 30 ) as tmp''', geometry_field='geom', srid=4326, autodetect_key_field=True) - fs = ds.featureset(variables={'zoom': 30}) + q = mapnik.Query(ds.envelope()) + q.variables = {'zoom': 30} + fs = ds.features(q) for id in range(1, 5): - eq_(fs.next().id(), id) + assert next(fs).id() == id meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta.get('key_field'), "gid") - eq_(meta['geometry_type'], None) + assert meta['srid'] == 4326 + assert meta.get('key_field') == "gid" + assert meta['geometry_type'] == None # currently needs manual `geometry_table` passed # to avoid misparse of `geometry_table` @@ -1270,13 +1254,13 @@ def test_broken_parsing_of_comments(): (select * FROM test) AS data -- select this from bogus''', geometry_table='test') - fs = ds.featureset() + fs = iter(ds) for id in range(1, 5): - eq_(fs.next().id(), id) + assert next(fs).id() == id meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta['geometry_type'], mapnik.DataGeometryType.Collection) + assert meta['srid'] == 4326 + assert meta['geometry_type'] == mapnik.DataGeometryType.Collection # same # to avoid misparse of `geometry_table` @@ -1288,16 +1272,12 @@ def test_broken_parsing_of_comments(): (select * FROM test) AS data -- select this from bogus.''', geometry_table='test') - fs = ds.featureset() + fs = iter(ds) for id in range(1, 5): - eq_(fs.next().id(), id) + assert next(fs).id() == id meta = ds.describe() - eq_(meta['srid'], 4326) - eq_(meta['geometry_type'], mapnik.DataGeometryType.Collection) + assert meta['srid'] == 4326 + assert meta['geometry_type'] == mapnik.DataGeometryType.Collection atexit.register(postgis_takedown) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) diff --git a/test/python_tests/projection_test.py b/test/python_tests/projection_test.py index 316632928..7fe312a98 100644 --- a/test/python_tests/projection_test.py +++ b/test/python_tests/projection_test.py @@ -1,60 +1,38 @@ -#!/usr/bin/env python import math import sys - -from nose.tools import assert_almost_equal, eq_ - import mapnik +import pytest -from .utilities import assert_box2d_almost_equal, run_all - -PYTHON3 = sys.version_info[0] == 3 -if PYTHON3: - xrange = range +from .utilities import assert_box2d_almost_equal # Tests that exercise map projections. - -def test_normalizing_definition(): - p = mapnik.Projection('+init=epsg:4326') - expanded = p.expanded() - eq_('+proj=longlat' in expanded, True) - +def test_projection_description(): + p = mapnik.Projection('epsg:4326') + assert 'WGS 84' == p.description() # Trac Ticket #128 def test_wgs84_inverse_forward(): - p = mapnik.Projection('+init=epsg:4326') - + p1 = mapnik.Projection('epsg:4326') + p2 = mapnik.Projection('epsg:4326') + tr = mapnik.ProjTransform(p1, p2) c = mapnik.Coord(3.01331418311, 43.3333092669) e = mapnik.Box2d(-122.54345245, 45.12312553, 68.2335581353, 48.231231233) # It appears that the y component changes very slightly, is this OK? # so we test for 'almost equal float values' - assert_almost_equal(p.inverse(c).y, c.y) - assert_almost_equal(p.inverse(c).x, c.x) - - assert_almost_equal(p.forward(c).y, c.y) - assert_almost_equal(p.forward(c).x, c.x) + assert tr.backward(c).y == pytest.approx(c.y) + assert tr.backward(c).x == pytest.approx(c.x) - assert_almost_equal(p.inverse(e).center().y, e.center().y) - assert_almost_equal(p.inverse(e).center().x, e.center().x) + assert tr.forward(c).y == pytest.approx(c.y) + assert tr.forward(c).x == pytest.approx(c.x) - assert_almost_equal(p.forward(e).center().y, e.center().y) - assert_almost_equal(p.forward(e).center().x, e.center().x) - - assert_almost_equal(c.inverse(p).y, c.y) - assert_almost_equal(c.inverse(p).x, c.x) - - assert_almost_equal(c.forward(p).y, c.y) - assert_almost_equal(c.forward(p).x, c.x) - - assert_almost_equal(e.inverse(p).center().y, e.center().y) - assert_almost_equal(e.inverse(p).center().x, e.center().x) - - assert_almost_equal(e.forward(p).center().y, e.center().y) - assert_almost_equal(e.forward(p).center().x, e.center().x) + assert tr.backward(e).center().y == pytest.approx(e.center().y) + assert tr.backward(e).center().x == pytest.approx(e.center().x) + assert tr.forward(e).center().y == pytest.approx(e.center().y) + assert tr.forward(e).center().x == pytest.approx(e.center().x) def wgs2merc(lon, lat): x = lon * 20037508.34 / 180 @@ -78,7 +56,7 @@ def merc2wgs(x, y): y = -85.0511 return [x, y] -# echo -109 37 | cs2cs -f "%.10f" +init=epsg:4326 +to +init=epsg:3857 +# echo -109 37 | cs2cs -f "%.10f" epsg:4326 +to epsg:3857 #-12133824.4964668211 4439106.7872505859 0.0000000000 # todo @@ -89,43 +67,43 @@ def merc2wgs(x, y): def test_proj_transform_between_init_and_literal(): - one = mapnik.Projection('+init=epsg:4326') - two = mapnik.Projection('+init=epsg:3857') + one = mapnik.Projection('epsg:4326') + two = mapnik.Projection('epsg:3857') tr1 = mapnik.ProjTransform(one, two) tr1b = mapnik.ProjTransform(two, one) - wgs84 = '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs' - merc = '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over' + wgs84 = 'epsg:4326' + merc = 'epsg:3857' src = mapnik.Projection(wgs84) dest = mapnik.Projection(merc) tr2 = mapnik.ProjTransform(src, dest) tr2b = mapnik.ProjTransform(dest, src) - for x in xrange(-180, 180, 10): - for y in xrange(-60, 60, 10): + for x in range(-180, 180, 10): + for y in range(-60, 60, 10): coord = mapnik.Coord(x, y) merc_coord1 = tr1.forward(coord) merc_coord2 = tr1b.backward(coord) merc_coord3 = tr2.forward(coord) merc_coord4 = tr2b.backward(coord) - eq_(math.fabs(merc_coord1.x - merc_coord1.x) < 1, True) - eq_(math.fabs(merc_coord1.x - merc_coord2.x) < 1, True) - eq_(math.fabs(merc_coord1.x - merc_coord3.x) < 1, True) - eq_(math.fabs(merc_coord1.x - merc_coord4.x) < 1, True) - eq_(math.fabs(merc_coord1.y - merc_coord1.y) < 1, True) - eq_(math.fabs(merc_coord1.y - merc_coord2.y) < 1, True) - eq_(math.fabs(merc_coord1.y - merc_coord3.y) < 1, True) - eq_(math.fabs(merc_coord1.y - merc_coord4.y) < 1, True) + assert math.fabs(merc_coord1.x - merc_coord1.x) < 1 + assert math.fabs(merc_coord1.x - merc_coord2.x) < 1 + assert math.fabs(merc_coord1.x - merc_coord3.x) < 1 + assert math.fabs(merc_coord1.x - merc_coord4.x) < 1 + assert math.fabs(merc_coord1.y - merc_coord1.y) < 1 + assert math.fabs(merc_coord1.y - merc_coord2.y) < 1 + assert math.fabs(merc_coord1.y - merc_coord3.y) < 1 + assert math.fabs(merc_coord1.y - merc_coord4.y) < 1 lon_lat_coord1 = tr1.backward(merc_coord1) lon_lat_coord2 = tr1b.forward(merc_coord2) lon_lat_coord3 = tr2.backward(merc_coord3) lon_lat_coord4 = tr2b.forward(merc_coord4) - eq_(math.fabs(coord.x - lon_lat_coord1.x) < 1, True) - eq_(math.fabs(coord.x - lon_lat_coord2.x) < 1, True) - eq_(math.fabs(coord.x - lon_lat_coord3.x) < 1, True) - eq_(math.fabs(coord.x - lon_lat_coord4.x) < 1, True) - eq_(math.fabs(coord.y - lon_lat_coord1.y) < 1, True) - eq_(math.fabs(coord.y - lon_lat_coord2.y) < 1, True) - eq_(math.fabs(coord.y - lon_lat_coord3.y) < 1, True) - eq_(math.fabs(coord.y - lon_lat_coord4.y) < 1, True) + assert math.fabs(coord.x - lon_lat_coord1.x) < 1 + assert math.fabs(coord.x - lon_lat_coord2.x) < 1 + assert math.fabs(coord.x - lon_lat_coord3.x) < 1 + assert math.fabs(coord.x - lon_lat_coord4.x) < 1 + assert math.fabs(coord.y - lon_lat_coord1.y) < 1 + assert math.fabs(coord.y - lon_lat_coord2.y) < 1 + assert math.fabs(coord.y - lon_lat_coord3.y) < 1 + assert math.fabs(coord.y - lon_lat_coord4.y) < 1 # Github Issue #2648 @@ -133,8 +111,8 @@ def test_proj_antimeridian_bbox(): # this is logic from feature_style_processor::prepare_layer() PROJ_ENVELOPE_POINTS = 20 # include/mapnik/config.hpp - prjGeog = mapnik.Projection('+init=epsg:4326') - prjProj = mapnik.Projection('+init=epsg:2193') + prjGeog = mapnik.Projection('epsg:4326') + prjProj = mapnik.Projection('epsg:2193') prj_trans_fwd = mapnik.ProjTransform(prjProj, prjGeog) prj_trans_rev = mapnik.ProjTransform(prjGeog, prjProj) @@ -162,7 +140,3 @@ def test_proj_antimeridian_bbox(): ext = mapnik.Box2d(274000, 3087000, 276000, 7173000) rev_ext = prj_trans_rev.backward(ext, PROJ_ENVELOPE_POINTS) assert_box2d_almost_equal(rev_ext, normal) - - -if __name__ == "__main__": - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) diff --git a/test/python_tests/query_test.py b/test/python_tests/query_test.py index d4298b665..8a0d58903 100644 --- a/test/python_tests/query_test.py +++ b/test/python_tests/query_test.py @@ -1,44 +1,33 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os - -from nose.tools import assert_almost_equal, eq_, raises - import mapnik +import pytest +from .utilities import execution_path -from .utilities import execution_path, run_all - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield - -def test_query_init(): +def test_query_init(setup): bbox = (-180, -90, 180, 90) query = mapnik.Query(mapnik.Box2d(*bbox)) r = query.resolution - assert_almost_equal(r[0], 1.0, places=7) - assert_almost_equal(r[1], 1.0, places=7) + assert r[0] == pytest.approx(1.0, abs=1e-7) + assert r[1] == pytest.approx(1.0, abs=1e-7) # https://github.com/mapnik/mapnik/issues/1762 - eq_(query.property_names, []) + assert query.property_names == [] query.add_property_name('migurski') - eq_(query.property_names, ['migurski']) + assert query.property_names == ['migurski'] # Converting *from* tuples *to* resolutions is not yet supported - -@raises(TypeError) def test_query_resolution(): - bbox = (-180, -90, 180, 90) - init_res = (4.5, 6.7) - query = mapnik.Query(mapnik.Box2d(*bbox), init_res) - r = query.resolution - assert_almost_equal(r[0], init_res[0], places=7) - assert_almost_equal(r[1], init_res[1], places=7) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + with pytest.raises(TypeError): + bbox = (-180, -90, 180, 90) + init_res = (4.5, 6.7) + query = mapnik.Query(mapnik.Box2d(*bbox), init_res) + r = query.resolution + assert r[0] == pytest.approx(init_res[0], abs=1e-7) + assert r[1] == pytest.approx(init_res[1], abs=1e-7) diff --git a/test/python_tests/query_tolerance_test.py b/test/python_tests/query_tolerance_test.py index c49bf258e..da2a1cf60 100644 --- a/test/python_tests/query_tolerance_test.py +++ b/test/python_tests/query_tolerance_test.py @@ -1,22 +1,18 @@ -#!/usr/bin/env python - import os - -from nose.tools import eq_ - import mapnik +import pytest +from .utilities import execution_path -from .utilities import execution_path, run_all - - +@pytest.fixture def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield if 'shape' in mapnik.DatasourceCache.plugin_names(): - def test_query_tolerance(): - srs = '+init=epsg:4326' + def test_query_tolerance(setup): + srs = 'epsg:4326' lyr = mapnik.Layer('test') ds = mapnik.Shapefile(file='../data/shp/arrows.shp') lyr.datasource = ds @@ -29,20 +25,16 @@ def test_query_tolerance(): _map_env = _map.envelope() tol = (_map_env.maxx - _map_env.minx) / _width * 3 # 0.046875 for arrows.shp and zoom_all - eq_(tol, 0.046875) + assert tol == 0.046875 # check point really exists x, y = 2.0, 4.0 features = _map.query_point(0, x, y) - eq_(len(list(features)), 1) + assert len(list(features)) == 1 # check inside tolerance limit x = 2.0 + tol * 0.9 features = _map.query_point(0, x, y) - eq_(len(list(features)), 1) + assert len(list(features)) == 1 # check outside tolerance limit x = 2.0 + tol * 1.1 features = _map.query_point(0, x, y) - eq_(len(list(features)), 0) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert len(list(features)) == 0 diff --git a/test/python_tests/raster_colorizer_test.py b/test/python_tests/raster_colorizer_test.py index 8ae69822c..e9995a5fe 100644 --- a/test/python_tests/raster_colorizer_test.py +++ b/test/python_tests/raster_colorizer_test.py @@ -1,25 +1,19 @@ -# coding=utf8 import os import sys - -from nose.tools import eq_ - +import pytest import mapnik -from .utilities import execution_path, run_all - -PYTHON3 = sys.version_info[0] == 3 - +from .utilities import execution_path +@pytest.fixture def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield # test discrete colorizer mode - - -def test_get_color_discrete(): +def test_get_color_discrete(setup): # setup colorizer = mapnik.RasterColorizer() colorizer.default_color = mapnik.Color(0, 0, 0, 0) @@ -29,16 +23,16 @@ def test_get_color_discrete(): colorizer.add_stop(20, mapnik.Color(200, 200, 200, 200)) # should be default colour - eq_(colorizer.get_color(-50), mapnik.Color(0, 0, 0, 0)) - eq_(colorizer.get_color(0), mapnik.Color(0, 0, 0, 0)) + assert colorizer.get_color(-50) == mapnik.Color(0, 0, 0, 0) + assert colorizer.get_color(0) == mapnik.Color(0, 0, 0, 0) # now in stop 1 - eq_(colorizer.get_color(10), mapnik.Color(100, 100, 100, 100)) - eq_(colorizer.get_color(19), mapnik.Color(100, 100, 100, 100)) + assert colorizer.get_color(10) == mapnik.Color(100, 100, 100, 100) + assert colorizer.get_color(19) == mapnik.Color(100, 100, 100, 100) # now in stop 2 - eq_(colorizer.get_color(20), mapnik.Color(200, 200, 200, 200)) - eq_(colorizer.get_color(1000), mapnik.Color(200, 200, 200, 200)) + assert colorizer.get_color(20) == mapnik.Color(200, 200, 200, 200) + assert colorizer.get_color(1000) == mapnik.Color(200, 200, 200, 200) # test exact colorizer mode @@ -53,15 +47,15 @@ def test_get_color_exact(): colorizer.add_stop(20, mapnik.Color(200, 200, 200, 200)) # should be default colour - eq_(colorizer.get_color(-50), mapnik.Color(0, 0, 0, 0)) - eq_(colorizer.get_color(11), mapnik.Color(0, 0, 0, 0)) - eq_(colorizer.get_color(20.001), mapnik.Color(0, 0, 0, 0)) + assert colorizer.get_color(-50) == mapnik.Color(0, 0, 0, 0) + assert colorizer.get_color(11) == mapnik.Color(0, 0, 0, 0) + assert colorizer.get_color(20.001) == mapnik.Color(0, 0, 0, 0) # should be stop 1 - eq_(colorizer.get_color(10), mapnik.Color(100, 100, 100, 100)) + assert colorizer.get_color(10) == mapnik.Color(100, 100, 100, 100) # should be stop 2 - eq_(colorizer.get_color(20), mapnik.Color(200, 200, 200, 200)) + assert colorizer.get_color(20) == mapnik.Color(200, 200, 200, 200) # test linear colorizer mode @@ -76,20 +70,20 @@ def test_get_color_linear(): colorizer.add_stop(20, mapnik.Color(200, 200, 200, 200)) # should be default colour - eq_(colorizer.get_color(-50), mapnik.Color(0, 0, 0, 0)) - eq_(colorizer.get_color(9.9), mapnik.Color(0, 0, 0, 0)) + assert colorizer.get_color(-50) == mapnik.Color(0, 0, 0, 0) + assert colorizer.get_color(9.9) == mapnik.Color(0, 0, 0, 0) # should be stop 1 - eq_(colorizer.get_color(10), mapnik.Color(100, 100, 100, 100)) + assert colorizer.get_color(10) == mapnik.Color(100, 100, 100, 100) # should be stop 2 - eq_(colorizer.get_color(20), mapnik.Color(200, 200, 200, 200)) + assert colorizer.get_color(20) == mapnik.Color(200, 200, 200, 200) # half way between stops 1 and 2 - eq_(colorizer.get_color(15), mapnik.Color(150, 150, 150, 150)) + assert colorizer.get_color(15) == mapnik.Color(150, 150, 150, 150) # after stop 2 - eq_(colorizer.get_color(100), mapnik.Color(200, 200, 200, 200)) + assert colorizer.get_color(100) == mapnik.Color(200, 200, 200, 200) def test_stop_label(): @@ -97,11 +91,5 @@ def test_stop_label(): 1, mapnik.COLORIZER_LINEAR, mapnik.Color('red')) assert not stop.label label = u"32º C" - if not PYTHON3: - label = label.encode('utf8') stop.label = label assert stop.label == label, stop.label - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) diff --git a/test/python_tests/raster_symbolizer_test.py b/test/python_tests/raster_symbolizer_test.py index caebaab23..04ab6e760 100644 --- a/test/python_tests/raster_symbolizer_test.py +++ b/test/python_tests/raster_symbolizer_test.py @@ -1,22 +1,17 @@ -#!/usr/bin/env python - import os - -from nose.tools import eq_ - import mapnik +import pytest +from .utilities import execution_path, get_unique_colors -from .utilities import execution_path, get_unique_colors, run_all - - +@pytest.fixture def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield - -def test_dataraster_coloring(): - srs = '+init=epsg:32630' +def test_dataraster_coloring(setup): + srs = 'epsg:32630' lyr = mapnik.Layer('dataraster') if 'gdal' in mapnik.DatasourceCache.plugin_names(): lyr.datasource = mapnik.Gdal( @@ -49,7 +44,7 @@ def test_dataraster_coloring(): ]: colorizer.add_stop(value, mapnik.Color(color)) sym.colorizer = colorizer - rule.symbols.append(sym) + rule.symbolizers.append(sym) style.rules.append(rule) _map.append_style('foo', style) lyr.styles.append('foo') @@ -65,14 +60,12 @@ def test_dataraster_coloring(): im.save(expected_file, 'png32') actual = mapnik.Image.open(actual_file) expected = mapnik.Image.open(expected_file) - eq_(actual.tostring('png32'), - expected.tostring('png32'), - 'failed comparing actual (%s) and expected (%s)' % (actual_file, - expected_file)) + assert actual.to_string('png32') == expected.to_string('png32'),'failed comparing actual (%s) and expected (%s)' % (actual_file, + expected_file) def test_dataraster_query_point(): - srs = '+init=epsg:32630' + srs = 'epsg:32630' lyr = mapnik.Layer('dataraster') if 'gdal' in mapnik.DatasourceCache.plugin_names(): lyr.datasource = mapnik.Gdal( @@ -134,7 +127,7 @@ def test_raster_with_alpha_blends_correctly_with_background(): symbolizer = mapnik.RasterSymbolizer() symbolizer.scaling = mapnik.scaling_method.BILINEAR - rule.symbols.append(symbolizer) + rule.symbolizers.append(symbolizer) style.rules.append(rule) map.append_style('raster_style', style) @@ -151,14 +144,14 @@ def test_raster_with_alpha_blends_correctly_with_background(): mim = mapnik.Image(WIDTH, HEIGHT) mapnik.render(map, mim) - mim.tostring() + mim.to_string() # All white is expected - eq_(get_unique_colors(mim), ['rgba(254,254,254,255)']) + assert get_unique_colors(mim) == ['rgba(254,254,254,255)'] def test_raster_warping(): - lyrSrs = "+init=epsg:32630" - mapSrs = '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs' + lyrSrs = "epsg:32630" + mapSrs = 'epsg:4326' lyr = mapnik.Layer('dataraster', lyrSrs) if 'gdal' in mapnik.DatasourceCache.plugin_names(): lyr.datasource = mapnik.Gdal( @@ -169,7 +162,7 @@ def test_raster_warping(): sym.colorizer = mapnik.RasterColorizer( mapnik.COLORIZER_DISCRETE, mapnik.Color(255, 255, 0)) rule = mapnik.Rule() - rule.symbols.append(sym) + rule.symbolizers.append(sym) style = mapnik.Style() style.rules.append(rule) _map = mapnik.Map(256, 256, mapSrs) @@ -191,15 +184,13 @@ def test_raster_warping(): im.save(expected_file, 'png32') actual = mapnik.Image.open(actual_file) expected = mapnik.Image.open(expected_file) - eq_(actual.tostring('png32'), - expected.tostring('png32'), - 'failed comparing actual (%s) and expected (%s)' % (actual_file, - expected_file)) + assert actual.to_string('png32') == expected.to_string('png32'), 'failed comparing actual (%s) and expected (%s)' % (actual_file, + expected_file) def test_raster_warping_does_not_overclip_source(): - lyrSrs = "+init=epsg:32630" - mapSrs = '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs' + lyrSrs = "epsg:32630" + mapSrs = 'epsg:4326' lyr = mapnik.Layer('dataraster', lyrSrs) if 'gdal' in mapnik.DatasourceCache.plugin_names(): lyr.datasource = mapnik.Gdal( @@ -210,7 +201,7 @@ def test_raster_warping_does_not_overclip_source(): sym.colorizer = mapnik.RasterColorizer( mapnik.COLORIZER_DISCRETE, mapnik.Color(255, 255, 0)) rule = mapnik.Rule() - rule.symbols.append(sym) + rule.symbolizers.append(sym) style = mapnik.Style() style.rules.append(rule) _map = mapnik.Map(256, 256, mapSrs) @@ -229,11 +220,5 @@ def test_raster_warping_does_not_overclip_source(): im.save(expected_file, 'png32') actual = mapnik.Image.open(actual_file) expected = mapnik.Image.open(expected_file) - eq_(actual.tostring('png32'), - expected.tostring('png32'), - 'failed comparing actual (%s) and expected (%s)' % (actual_file, - expected_file)) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert actual.to_string('png32') == expected.to_string('png32'), 'failed comparing actual (%s) and expected (%s)' % (actual_file, + expected_file) diff --git a/test/python_tests/rasterlite_test.py b/test/python_tests/rasterlite_test.py index 284def855..015df2e71 100644 --- a/test/python_tests/rasterlite_test.py +++ b/test/python_tests/rasterlite_test.py @@ -1,19 +1,15 @@ -#!/usr/bin/env python - import os - -from nose.tools import assert_almost_equal, eq_ - import mapnik +import pytest -from .utilities import execution_path, run_all - +from .utilities import execution_path +@pytest.fixture def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) - + yield if 'rasterlite' in mapnik.DatasourceCache.plugin_names(): @@ -24,19 +20,15 @@ def test_rasterlite(): ) e = ds.envelope() - assert_almost_equal(e.minx, -180, places=5) - assert_almost_equal(e.miny, -90, places=5) - assert_almost_equal(e.maxx, 180, places=5) - assert_almost_equal(e.maxy, 90, places=5) - eq_(len(ds.fields()), 0) + assert e.minx == pytest.approx(-180,abs=1e-5) + assert e.miny == pytest.approx(-90, abs=1e-5) + assert e.maxx == pytest.approx(180, abs=1e-5) + assert e.maxy == pytest.approx( 90, abs=1e-5) + assert len(ds.fields()) == 0 query = mapnik.Query(ds.envelope()) for fld in ds.fields(): query.add_property_name(fld) fs = ds.features(query) feat = fs.next() - eq_(feat.id(), 1) - eq_(feat.attributes, {}) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert feat.id() == 1 + assert feat.attributes == {} diff --git a/test/python_tests/render_grid_test.py b/test/python_tests/render_grid_test.py index 8752fccb0..399c0393c 100644 --- a/test/python_tests/render_grid_test.py +++ b/test/python_tests/render_grid_test.py @@ -1,24 +1,16 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os - -from nose.tools import eq_, raises - import mapnik +import json +import pytest -from .utilities import execution_path, run_all - -try: - import json -except ImportError: - import simplejson as json - +from .utilities import execution_path +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield if mapnik.has_grid_renderer(): def show_grids(name, g1, g2): @@ -359,7 +351,7 @@ def create_grid_map(width, height, sym): s = mapnik.Style() r = mapnik.Rule() sym.allow_overlap = True - r.symbols.append(sym) + r.symbolizers.append(sym) s.rules.append(r) lyr = mapnik.Layer('Places') lyr.datasource = ds @@ -369,7 +361,7 @@ def create_grid_map(width, height, sym): m.layers.append(lyr) return m - def test_render_grid(): + def test_render_grid(setup): """ test render_grid method""" width, height = 256, 256 sym = mapnik.MarkersSymbolizer() @@ -384,27 +376,26 @@ def test_render_grid(): grid = mapnik.Grid(m.width, m.height, key='Name') mapnik.render_layer(m, grid, layer=0, fields=['Name']) utf1 = grid.encode('utf', resolution=4) - eq_(utf1, grid_correct_new3, show_grids( - 'new-markers', utf1, grid_correct_new3)) + assert utf1 == grid_correct_new3, show_grids('new-markers', utf1, grid_correct_new3) # check a full view is the same as a full image grid_view = grid.view(0, 0, width, height) # for kicks check at full res too utf3 = grid.encode('utf', resolution=1) utf4 = grid_view.encode('utf', resolution=1) - eq_(utf3['grid'], utf4['grid']) - eq_(utf3['keys'], utf4['keys']) - eq_(utf3['data'], utf4['data']) + assert utf3['grid'] == utf4['grid'] + assert utf3['keys'] == utf4['keys'] + assert utf3['data'] == utf4['data'] - eq_(resolve(utf4, 0, 0), None) + assert resolve(utf4, 0, 0) == None # resolve some center points in the # resampled view utf5 = grid_view.encode('utf', resolution=4) - eq_(resolve(utf5, 25, 10), {"Name": "North West"}) - eq_(resolve(utf5, 25, 46), {"Name": "North East"}) - eq_(resolve(utf5, 38, 10), {"Name": "South West"}) - eq_(resolve(utf5, 38, 46), {"Name": "South East"}) + assert resolve(utf5, 25, 10) == {"Name": "North West"} + assert resolve(utf5, 25, 46) == {"Name": "North East"} + assert resolve(utf5, 38, 10) == {"Name": "South West"} + assert resolve(utf5, 38, 46) == {"Name": "South East"} grid_feat_id = { 'keys': [ @@ -670,25 +661,25 @@ def test_render_grid3(): grid = mapnik.Grid(m.width, m.height, key='__id__') mapnik.render_layer(m, grid, layer=0, fields=['__id__', 'Name']) utf1 = grid.encode('utf', resolution=4) - eq_(utf1, grid_feat_id3, show_grids('id-markers', utf1, grid_feat_id3)) + assert utf1 == grid_feat_id3, show_grids('id-markers', utf1 == grid_feat_id3) # check a full view is the same as a full image grid_view = grid.view(0, 0, width, height) # for kicks check at full res too utf3 = grid.encode('utf', resolution=1) utf4 = grid_view.encode('utf', resolution=1) - eq_(utf3['grid'], utf4['grid']) - eq_(utf3['keys'], utf4['keys']) - eq_(utf3['data'], utf4['data']) + assert utf3['grid'] == utf4['grid'] + assert utf3['keys'] == utf4['keys'] + assert utf3['data'] == utf4['data'] - eq_(resolve(utf4, 0, 0), None) + assert resolve(utf4, 0, 0) == None # resolve some center points in the # resampled view utf5 = grid_view.encode('utf', resolution=4) - eq_(resolve(utf5, 25, 10), {"Name": "North West", "__id__": 3}) - eq_(resolve(utf5, 25, 46), {"Name": "North East", "__id__": 4}) - eq_(resolve(utf5, 38, 10), {"Name": "South West", "__id__": 2}) - eq_(resolve(utf5, 38, 46), {"Name": "South East", "__id__": 1}) + assert resolve(utf5, 25, 10) == {"Name": "North West", "__id__": 3} + assert resolve(utf5, 25, 46) == {"Name": "North East", "__id__": 4} + assert resolve(utf5, 38, 10) == {"Name": "South West", "__id__": 2} + assert resolve(utf5, 38, 46) == {"Name": "South East", "__id__": 1} def gen_grid_for_id(pixel_key): ds = mapnik.MemoryDatasource() @@ -702,7 +693,7 @@ def gen_grid_for_id(pixel_key): s = mapnik.Style() r = mapnik.Rule() symb = mapnik.PolygonSymbolizer() - r.symbols.append(symb) + r.symbolizers.append(symb) s.rules.append(r) lyr = mapnik.Layer('Places') lyr.datasource = ds @@ -718,39 +709,39 @@ def gen_grid_for_id(pixel_key): def test_negative_id(): grid = gen_grid_for_id(-1) - eq_(grid.get_pixel(128, 128), -1) + assert grid.get_pixel(128, 128) == -1 utf1 = grid.encode('utf', resolution=4) - eq_(utf1['keys'], ['-1']) + assert utf1['keys'] == ['-1'] def test_32bit_int_id(): int32 = 2147483647 grid = gen_grid_for_id(int32) - eq_(grid.get_pixel(128, 128), int32) + assert grid.get_pixel(128, 128) == int32 utf1 = grid.encode('utf', resolution=4) - eq_(utf1['keys'], [str(int32)]) + assert utf1['keys'] == [str(int32)] max_neg = -(int32) grid = gen_grid_for_id(max_neg) - eq_(grid.get_pixel(128, 128), max_neg) + assert grid.get_pixel(128, 128) == max_neg utf1 = grid.encode('utf', resolution=4) - eq_(utf1['keys'], [str(max_neg)]) + assert utf1['keys'] == [str(max_neg)] def test_64bit_int_id(): int64 = 0x7FFFFFFFFFFFFFFF grid = gen_grid_for_id(int64) - eq_(grid.get_pixel(128, 128), int64) + assert grid.get_pixel(128, 128) == int64 utf1 = grid.encode('utf', resolution=4) - eq_(utf1['keys'], [str(int64)]) + assert utf1['keys'] == [str(int64)] max_neg = -(int64) grid = gen_grid_for_id(max_neg) - eq_(grid.get_pixel(128, 128), max_neg) + assert grid.get_pixel(128, 128) == max_neg utf1 = grid.encode('utf', resolution=4) - eq_(utf1['keys'], [str(max_neg)]) + assert utf1['keys'] == [str(max_neg)] def test_id_zero(): grid = gen_grid_for_id(0) - eq_(grid.get_pixel(128, 128), 0) + assert grid.get_pixel(128, 128) == 0 utf1 = grid.encode('utf', resolution=4) - eq_(utf1['keys'], ['0']) + assert utf1['keys'] == ['0'] line_expected = { "keys": [ @@ -838,7 +829,7 @@ def test_line_rendering(): s = mapnik.Style() r = mapnik.Rule() symb = mapnik.LineSymbolizer() - r.symbols.append(symb) + r.symbolizers.append(symb) s.rules.append(r) lyr = mapnik.Layer('Places') lyr.datasource = ds @@ -852,7 +843,7 @@ def test_line_rendering(): grid = mapnik.Grid(m.width, m.height, key='__id__') mapnik.render_layer(m, grid, layer=0, fields=['Name']) utf1 = grid.encode() - eq_(utf1, line_expected, show_grids('line', utf1, line_expected)) + assert utf1 == line_expected, show_grids('line', utf1, line_expected) point_expected = { "data": { @@ -947,53 +938,49 @@ def test_point_symbolizer_grid(): grid = mapnik.Grid(m.width, m.height) mapnik.render_layer(m, grid, layer=0, fields=['Name']) utf1 = grid.encode() - eq_(utf1, point_expected, show_grids('point-sym', utf1, point_expected)) + assert utf1 == point_expected, show_grids('point-sym', utf1, point_expected) test_point_symbolizer_grid.requires_data = True # should throw because this is a mis-usage # https://github.com/mapnik/mapnik/issues/1325 - @raises(RuntimeError) def test_render_to_grid_multiple_times(): - # create map with two layers - m = mapnik.Map(256, 256) - s = mapnik.Style() - r = mapnik.Rule() - sym = mapnik.MarkersSymbolizer() - sym.allow_overlap = True - r.symbols.append(sym) - s.rules.append(r) - m.append_style('points', s) - - # NOTE: we use a csv datasource here - # because the memorydatasource fails silently for - # queries requesting fields that do not exist in the datasource - ds1 = mapnik.Datasource(**{"type": "csv", "inline": ''' - wkt,Name - "POINT (143.10 -38.60)",South East'''}) - lyr1 = mapnik.Layer('One') - lyr1.datasource = ds1 - lyr1.styles.append('points') - m.layers.append(lyr1) - - ds2 = mapnik.Datasource(**{"type": "csv", "inline": ''' - wkt,Value - "POINT (142.48 -38.60)",South West'''}) - lyr2 = mapnik.Layer('Two') - lyr2.datasource = ds2 - lyr2.styles.append('points') - m.layers.append(lyr2) - - ul_lonlat = mapnik.Coord(142.30, -38.20) - lr_lonlat = mapnik.Coord(143.40, -38.80) - m.zoom_to_box(mapnik.Box2d(ul_lonlat, lr_lonlat)) - grid = mapnik.Grid(m.width, m.height) - mapnik.render_layer(m, grid, layer=0, fields=['Name']) - # should throw right here since Name will be a property now on the `grid` object - # and it is not found on the second layer - mapnik.render_layer(m, grid, layer=1, fields=['Value']) - grid.encode() - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + with pytest.raises(RuntimeError): + # create map with two layers + m = mapnik.Map(256, 256) + s = mapnik.Style() + r = mapnik.Rule() + sym = mapnik.MarkersSymbolizer() + sym.allow_overlap = True + r.symbolizers.append(sym) + s.rules.append(r) + m.append_style('points', s) + + # NOTE: we use a csv datasource here + # because the memorydatasource fails silently for + # queries requesting fields that do not exist in the datasource + ds1 = mapnik.Datasource(**{"type": "csv", "inline": ''' + wkt,Name + "POINT (143.10 -38.60)",South East'''}) + lyr1 = mapnik.Layer('One') + lyr1.datasource = ds1 + lyr1.styles.append('points') + m.layers.append(lyr1) + + ds2 = mapnik.Datasource(**{"type": "csv", "inline": ''' + wkt,Value + "POINT (142.48 -38.60)",South West'''}) + lyr2 = mapnik.Layer('Two') + lyr2.datasource = ds2 + lyr2.styles.append('points') + m.layers.append(lyr2) + + ul_lonlat = mapnik.Coord(142.30, -38.20) + lr_lonlat = mapnik.Coord(143.40, -38.80) + m.zoom_to_box(mapnik.Box2d(ul_lonlat, lr_lonlat)) + grid = mapnik.Grid(m.width, m.height) + mapnik.render_layer(m, grid, layer=0, fields=['Name']) + # should throw right here since Name will be a property now on the `grid` object + # and it is not found on the second layer + mapnik.render_layer(m, grid, layer=1, fields=['Value']) + grid.encode() diff --git a/test/python_tests/render_test.py b/test/python_tests/render_test.py index 42f63f260..b10058b5b 100644 --- a/test/python_tests/render_test.py +++ b/test/python_tests/render_test.py @@ -1,77 +1,62 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os -import sys +import sys, os import tempfile - -from nose.tools import eq_, raises - import mapnik +import pytest +from .utilities import execution_path -from .utilities import execution_path, run_all - -PYTHON3 = sys.version_info[0] == 3 - - +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield - -def test_simplest_render(): +def test_simplest_render(setup): m = mapnik.Map(256, 256) im = mapnik.Image(m.width, m.height) - eq_(im.painted(), False) - eq_(im.is_solid(), True) + assert not im.painted() + assert im.is_solid() mapnik.render(m, im) - eq_(im.painted(), False) - eq_(im.is_solid(), True) - s = im.tostring() - if PYTHON3: - eq_(s, 256 * 256 * b'\x00\x00\x00\x00') - else: - eq_(s, 256 * 256 * '\x00\x00\x00\x00') + assert not im.painted() + assert im.is_solid() + s = im.to_string() + assert s == 256 * 256 * b'\x00\x00\x00\x00' def test_render_image_to_string(): im = mapnik.Image(256, 256) im.fill(mapnik.Color('black')) - eq_(im.painted(), False) - eq_(im.is_solid(), True) - s = im.tostring() - if PYTHON3: - eq_(s, 256 * 256 * b'\x00\x00\x00\xff') - else: - eq_(s, 256 * 256 * '\x00\x00\x00\xff') + assert not im.painted() + assert im.is_solid() + s = im.to_string() + assert s == 256 * 256 * b'\x00\x00\x00\xff' def test_non_solid_image(): im = mapnik.Image(256, 256) im.fill(mapnik.Color('black')) - eq_(im.painted(), False) - eq_(im.is_solid(), True) + assert not im.painted() + assert im.is_solid() # set one pixel to a different color im.set_pixel(0, 0, mapnik.Color('white')) - eq_(im.painted(), False) - eq_(im.is_solid(), False) + assert not im.painted() + assert not im.is_solid() def test_non_solid_image_view(): im = mapnik.Image(256, 256) im.fill(mapnik.Color('black')) view = im.view(0, 0, 256, 256) - eq_(view.is_solid(), True) + assert view.is_solid() # set one pixel to a different color im.set_pixel(0, 0, mapnik.Color('white')) - eq_(im.is_solid(), False) + assert not im.is_solid() # view, since it is the exact dimensions of the image # should also be non-solid - eq_(view.is_solid(), False) + assert not view.is_solid() # but not a view that excludes the single diff pixel view2 = im.view(1, 1, 256, 256) - eq_(view2.is_solid(), True) + assert view2.is_solid() def test_setting_alpha(): @@ -80,16 +65,16 @@ def test_setting_alpha(): # white, half transparent c1 = mapnik.Color('rgba(255,255,255,.5)') im1.fill(c1) - eq_(im1.painted(), False) - eq_(im1.is_solid(), True) + assert not im1.painted() + assert im1.is_solid() # pure white im2 = mapnik.Image(w, h) c2 = mapnik.Color('rgba(255,255,255,1)') im2.fill(c2) im2.apply_opacity(c1.a / 255.0) - eq_(im2.painted(), False) - eq_(im2.is_solid(), True) - eq_(len(im1.tostring('png32')), len(im2.tostring('png32'))) + assert not im2.painted() + assert im2.is_solid() + assert len(im1.to_string('png32')) == len(im2.to_string('png32')) def test_render_image_to_file(): @@ -129,11 +114,11 @@ def test_render_from_serialization(): try: im, im2 = get_paired_images( 100, 100, '../data/good_maps/building_symbolizer.xml') - eq_(im.tostring('png32'), im2.tostring('png32')) + assert im.to_string('png32') == im2.to_string('png32') im, im2 = get_paired_images( 100, 100, '../data/good_maps/polygon_symbolizer.xml') - eq_(im.tostring('png32'), im2.tostring('png32')) + assert im.to_string('png32') == im2.to_string('png32') except RuntimeError as e: # only test datasources that we have installed if not 'Could not create datasource' in str(e): @@ -162,11 +147,11 @@ def test_render_points(): r = mapnik.Rule() symb = mapnik.PointSymbolizer() symb.allow_overlap = True - r.symbols.append(symb) + r.symbolizers.append(symb) s.rules.append(r) lyr = mapnik.Layer( 'Places', - '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs') + 'epsg:4326') lyr.datasource = ds lyr.styles.append('places_labels') # latlon bounding box corners @@ -174,8 +159,8 @@ def test_render_points(): lr_lonlat = mapnik.Coord(143.40, -38.80) # render for different projections projs = { - 'google': '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over', - 'latlon': '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs', + 'google': 'epsg:3857', + 'latlon': 'epsg:4326', 'merc': '+proj=merc +datum=WGS84 +k=1.0 +units=m +over +no_defs', 'utm': '+proj=utm +zone=54 +datum=WGS84' } @@ -184,7 +169,7 @@ def test_render_points(): m.append_style('places_labels', s) m.layers.append(lyr) dest_proj = mapnik.Projection(projs[projdescr]) - src_proj = mapnik.Projection('+init=epsg:4326') + src_proj = mapnik.Projection('epsg:4326') tr = mapnik.ProjTransform(src_proj, dest_proj) m.zoom_to_box(tr.forward(mapnik.Box2d(ul_lonlat, lr_lonlat))) # Render to SVG so that it can be checked how many points are there @@ -194,25 +179,18 @@ def test_render_points(): 'mapnik-render-points-%s.svg' % projdescr) mapnik.render_to_file(m, svg_file) - num_points_present = len(list(ds.all_features())) + num_points_present = len(list(iter(ds))) with open(svg_file, 'r') as f: svg = f.read() num_points_rendered = svg.count('=1,True) + # assert len(selected.features)>=1 == True del ds - eq_(os.path.exists(index), True) + assert os.path.exists(index) == True os.unlink(index) test_rtree_creation.requires_data = True @@ -148,17 +144,17 @@ def make_wkb_point(x, y): # confirm the wkb matches a manually formed wkb wkb2 = make_wkb_point(x, y) - eq_(wkb, wkb2) + assert wkb == wkb2 # ensure we can read this data back out properly with mapnik ds = mapnik.Datasource( **{'type': 'sqlite', 'file': test_db, 'table': 'point_table'}) - fs = ds.featureset() - feat = fs.next() - eq_(feat.id(), 1) - eq_(feat['name'], 'test point') + fs = iter(ds) + feat = next(fs) + assert feat.id() == 1 + assert feat['name'] == 'test point' geom = feat.geometry - eq_(geom.to_wkt(), 'POINT(-122 48)') + assert geom.to_wkt() == 'POINT(-122 48)' del ds # ensure it matches data read with just sqlite @@ -169,19 +165,12 @@ def make_wkb_point(x, y): result = cur.fetchone() cur.close() feat_id = result[0] - eq_(feat_id, 1) + assert feat_id == 1 name = result[2] - eq_(name, 'test point') + assert name == 'test point' geom_wkb_blob = result[1] - if not PYTHON3: - geom_wkb_blob = str(geom_wkb_blob) - eq_(geom_wkb_blob, geom.to_wkb(mapnik.wkbByteOrder.NDR)) + assert geom_wkb_blob == geom.to_wkb(mapnik.wkbByteOrder.NDR) new_geom = mapnik.Geometry.from_wkb(geom_wkb_blob) - eq_(new_geom.to_wkt(), geom.to_wkt()) + assert new_geom.to_wkt() == geom.to_wkt() conn.close() os.unlink(test_db) - -if __name__ == "__main__": - setup() - returncode = run_all(eval(x) for x in dir() if x.startswith("test_")) - exit(returncode) diff --git a/test/python_tests/sqlite_test.py b/test/python_tests/sqlite_test.py index 9720e3bf0..b98678c78 100644 --- a/test/python_tests/sqlite_test.py +++ b/test/python_tests/sqlite_test.py @@ -1,37 +1,30 @@ -#!/usr/bin/env python - import os - -from nose.tools import eq_, raises - import mapnik +import pytest +from .utilities import execution_path -from .utilities import execution_path, run_all - - -def setup(): +@pytest.fixture(scope="module") +def setup_and_teardown(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) - - -def teardown(): + yield index = '../data/sqlite/world.sqlite.index' if os.path.exists(index): os.unlink(index) if 'sqlite' in mapnik.DatasourceCache.plugin_names(): - def test_attachdb_with_relative_file(): + def test_attachdb_with_relative_file(setup_and_teardown): # The point table and index is in the qgis_spatiallite.sqlite # database. If either is not found, then this fails ds = mapnik.SQLite(file='../data/sqlite/world.sqlite', table='point', attachdb='scratch@qgis_spatiallite.sqlite' ) - fs = ds.featureset() - feature = fs.next() - eq_(feature['pkuid'], 1) + fs = iter(ds) + feature = next(fs) + assert feature['pkuid'] == 1 test_attachdb_with_relative_file.requires_data = True @@ -45,14 +38,14 @@ def test_attachdb_with_multiple_files(): insert into scratch2.idx_attachedtest_the_geom values (1,-7799225.5,-7778571.0,1393264.125,1417719.375); ''' ) - fs = ds.featureset() + fs = iter(ds) feature = None try: - feature = fs.next() + feature = next(fs) except StopIteration: pass # the above should not throw but will result in no features - eq_(feature, None) + assert feature == None test_attachdb_with_multiple_files.requires_data = True @@ -63,9 +56,9 @@ def test_attachdb_with_absolute_file(): table='point', attachdb='scratch@qgis_spatiallite.sqlite' ) - fs = ds.featureset() - feature = fs.next() - eq_(feature['pkuid'], 1) + fs = iter(ds) + feature = next(fs) + assert feature['pkuid'] == 1 test_attachdb_with_absolute_file.requires_data = True @@ -80,13 +73,13 @@ def test_attachdb_with_index(): ''' ) - fs = ds.featureset() + fs = iter(ds) feature = None try: - feature = fs.next() + feature = next(fs) except StopIteration: pass - eq_(feature, None) + assert feature == None test_attachdb_with_index.requires_data = True @@ -101,13 +94,13 @@ def test_attachdb_with_explicit_index(): insert into scratch.myindex values (1,-7799225.5,-7778571.0,1393264.125,1417719.375); ''' ) - fs = ds.featureset() + fs = iter(ds) feature = None try: - feature = fs.next() + feature = next(fs) except StopIteration: pass - eq_(feature, None) + assert feature == None test_attachdb_with_explicit_index.requires_data = True @@ -116,70 +109,68 @@ def test_attachdb_with_sql_join(): table='(select * from world_merc INNER JOIN business on world_merc.iso3 = business.ISO3 limit 100)', attachdb='busines@business.sqlite' ) - eq_(len(ds.fields()), 29) - eq_(ds.fields(), - ['OGC_FID', - 'fips', - 'iso2', - 'iso3', - 'un', - 'name', - 'area', - 'pop2005', - 'region', - 'subregion', - 'lon', - 'lat', - 'ISO3:1', - '1995', - '1996', - '1997', - '1998', - '1999', - '2000', - '2001', - '2002', - '2003', - '2004', - '2005', - '2006', - '2007', - '2008', - '2009', - '2010']) - eq_(ds.field_types(), - ['int', - 'str', - 'str', - 'str', - 'int', - 'str', - 'int', - 'int', - 'int', - 'int', - 'float', - 'float', - 'str', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int']) - fs = ds.featureset() - feature = fs.next() - eq_(feature.id(), 1) + assert len(ds.fields()) == 29 + assert ds.fields() == ['OGC_FID', + 'fips', + 'iso2', + 'iso3', + 'un', + 'name', + 'area', + 'pop2005', + 'region', + 'subregion', + 'lon', + 'lat', + 'ISO3:1', + '1995', + '1996', + '1997', + '1998', + '1999', + '2000', + '2001', + '2002', + '2003', + '2004', + '2005', + '2006', + '2007', + '2008', + '2009', + '2010'] + assert ds.field_types() == ['int', + 'str', + 'str', + 'str', + 'int', + 'str', + 'int', + 'int', + 'int', + 'int', + 'float', + 'float', + 'str', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int'] + fs = iter(ds) + feature = next(fs) + assert feature.id() == 1 expected = { 1995: 0, 1996: 0, @@ -215,7 +206,7 @@ def test_attachdb_with_sql_join(): } for k, v in expected.items(): try: - eq_(feature[str(k)], v) + assert feature[str(k)] == v except: #import pdb;pdb.set_trace() print('invalid key/v %s/%s for: %s' % (k, v, feature)) @@ -227,68 +218,66 @@ def test_attachdb_with_sql_join_count(): table='(select * from world_merc INNER JOIN business on world_merc.iso3 = business.ISO3 limit 100)', attachdb='busines@business.sqlite' ) - eq_(len(ds.fields()), 29) - eq_(ds.fields(), - ['OGC_FID', - 'fips', - 'iso2', - 'iso3', - 'un', - 'name', - 'area', - 'pop2005', - 'region', - 'subregion', - 'lon', - 'lat', - 'ISO3:1', - '1995', - '1996', - '1997', - '1998', - '1999', - '2000', - '2001', - '2002', - '2003', - '2004', - '2005', - '2006', - '2007', - '2008', - '2009', - '2010']) - eq_(ds.field_types(), - ['int', - 'str', - 'str', - 'str', - 'int', - 'str', - 'int', - 'int', - 'int', - 'int', - 'float', - 'float', - 'str', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int']) - eq_(len(list(ds.all_features())), 100) + assert len(ds.fields()) == 29 + assert ds.fields() == ['OGC_FID', + 'fips', + 'iso2', + 'iso3', + 'un', + 'name', + 'area', + 'pop2005', + 'region', + 'subregion', + 'lon', + 'lat', + 'ISO3:1', + '1995', + '1996', + '1997', + '1998', + '1999', + '2000', + '2001', + '2002', + '2003', + '2004', + '2005', + '2006', + '2007', + '2008', + '2009', + '2010'] + assert ds.field_types() == ['int', + 'str', + 'str', + 'str', + 'int', + 'str', + 'int', + 'int', + 'int', + 'int', + 'float', + 'float', + 'str', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int'] + assert len(list(iter(ds))) == 100 test_attachdb_with_sql_join_count.requires_data = True @@ -302,68 +291,66 @@ def test_attachdb_with_sql_join_count2(): table='(select * from world_merc INNER JOIN business on world_merc.iso3 = business.ISO3)', attachdb='busines@business.sqlite' ) - eq_(len(ds.fields()), 29) - eq_(ds.fields(), - ['OGC_FID', - 'fips', - 'iso2', - 'iso3', - 'un', - 'name', - 'area', - 'pop2005', - 'region', - 'subregion', - 'lon', - 'lat', - 'ISO3:1', - '1995', - '1996', - '1997', - '1998', - '1999', - '2000', - '2001', - '2002', - '2003', - '2004', - '2005', - '2006', - '2007', - '2008', - '2009', - '2010']) - eq_(ds.field_types(), - ['int', - 'str', - 'str', - 'str', - 'int', - 'str', - 'int', - 'int', - 'int', - 'int', - 'float', - 'float', - 'str', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int']) - eq_(len(list(ds.all_features())), 192) + assert len(ds.fields()) == 29 + assert ds.fields() == ['OGC_FID', + 'fips', + 'iso2', + 'iso3', + 'un', + 'name', + 'area', + 'pop2005', + 'region', + 'subregion', + 'lon', + 'lat', + 'ISO3:1', + '1995', + '1996', + '1997', + '1998', + '1999', + '2000', + '2001', + '2002', + '2003', + '2004', + '2005', + '2006', + '2007', + '2008', + '2009', + '2010'] + assert ds.field_types() == ['int', + 'str', + 'str', + 'str', + 'int', + 'str', + 'int', + 'int', + 'int', + 'int', + 'float', + 'float', + 'str', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int'] + assert len(list(iter(ds))) == 192 test_attachdb_with_sql_join_count2.requires_data = True @@ -375,68 +362,66 @@ def test_attachdb_with_sql_join_count3(): table='(select * from (select * from world_merc where !intersects!) as world_merc INNER JOIN business on world_merc.iso3 = business.ISO3)', attachdb='busines@business.sqlite' ) - eq_(len(ds.fields()), 29) - eq_(ds.fields(), - ['OGC_FID', - 'fips', - 'iso2', - 'iso3', - 'un', - 'name', - 'area', - 'pop2005', - 'region', - 'subregion', - 'lon', - 'lat', - 'ISO3:1', - '1995', - '1996', - '1997', - '1998', - '1999', - '2000', - '2001', - '2002', - '2003', - '2004', - '2005', - '2006', - '2007', - '2008', - '2009', - '2010']) - eq_(ds.field_types(), - ['int', - 'str', - 'str', - 'str', - 'int', - 'str', - 'int', - 'int', - 'int', - 'int', - 'float', - 'float', - 'str', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int']) - eq_(len(list(ds.all_features())), 192) + assert len(ds.fields()) == 29 + assert ds.fields() == ['OGC_FID', + 'fips', + 'iso2', + 'iso3', + 'un', + 'name', + 'area', + 'pop2005', + 'region', + 'subregion', + 'lon', + 'lat', + 'ISO3:1', + '1995', + '1996', + '1997', + '1998', + '1999', + '2000', + '2001', + '2002', + '2003', + '2004', + '2005', + '2006', + '2007', + '2008', + '2009', + '2010'] + assert ds.field_types() == ['int', + 'str', + 'str', + 'str', + 'int', + 'str', + 'int', + 'int', + 'int', + 'int', + 'float', + 'float', + 'str', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int'] + assert len(list(iter(ds))) == 192 test_attachdb_with_sql_join_count3.requires_data = True @@ -448,68 +433,66 @@ def test_attachdb_with_sql_join_count4(): table='(select * from (select * from world_merc where !intersects! limit 1) as world_merc INNER JOIN business on world_merc.iso3 = business.ISO3)', attachdb='busines@business.sqlite' ) - eq_(len(ds.fields()), 29) - eq_(ds.fields(), - ['OGC_FID', - 'fips', - 'iso2', - 'iso3', - 'un', - 'name', - 'area', - 'pop2005', - 'region', - 'subregion', - 'lon', - 'lat', - 'ISO3:1', - '1995', - '1996', - '1997', - '1998', - '1999', - '2000', - '2001', - '2002', - '2003', - '2004', - '2005', - '2006', - '2007', - '2008', - '2009', - '2010']) - eq_(ds.field_types(), - ['int', - 'str', - 'str', - 'str', - 'int', - 'str', - 'int', - 'int', - 'int', - 'int', - 'float', - 'float', - 'str', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int', - 'int']) - eq_(len(list(ds.all_features())), 1) + assert len(ds.fields()) == 29 + assert ds.fields() == ['OGC_FID', + 'fips', + 'iso2', + 'iso3', + 'un', + 'name', + 'area', + 'pop2005', + 'region', + 'subregion', + 'lon', + 'lat', + 'ISO3:1', + '1995', + '1996', + '1997', + '1998', + '1999', + '2000', + '2001', + '2002', + '2003', + '2004', + '2005', + '2006', + '2007', + '2008', + '2009', + '2010'] + assert ds.field_types() == ['int', + 'str', + 'str', + 'str', + 'int', + 'str', + 'int', + 'int', + 'int', + 'int', + 'float', + 'float', + 'str', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int', + 'int'] + assert len(list(iter(ds))) == 1 test_attachdb_with_sql_join_count4.requires_data = True @@ -523,34 +506,32 @@ def test_attachdb_with_sql_join_count5(): ) # nothing is able to join to business so we don't pick up business # schema - eq_(len(ds.fields()), 12) - eq_(ds.fields(), - ['OGC_FID', - 'fips', - 'iso2', - 'iso3', - 'un', - 'name', - 'area', - 'pop2005', - 'region', - 'subregion', - 'lon', - 'lat']) - eq_(ds.field_types(), - ['int', - 'str', - 'str', - 'str', - 'int', - 'str', - 'int', - 'int', - 'int', - 'int', - 'float', - 'float']) - eq_(len(list(ds.all_features())), 0) + assert len(ds.fields()) == 12 + assert ds.fields() == ['OGC_FID', + 'fips', + 'iso2', + 'iso3', + 'un', + 'name', + 'area', + 'pop2005', + 'region', + 'subregion', + 'lon', + 'lat'] + assert ds.field_types() == ['int', + 'str', + 'str', + 'str', + 'int', + 'str', + 'int', + 'int', + 'int', + 'int', + 'float', + 'float'] + assert len(list(iter(ds))) == 0 test_attachdb_with_sql_join_count5.requires_data = True @@ -558,54 +539,54 @@ def test_subqueries(): ds = mapnik.SQLite(file='../data/sqlite/world.sqlite', table='world_merc', ) - fs = ds.featureset() - feature = fs.next() - eq_(feature['OGC_FID'], 1) - eq_(feature['fips'], u'AC') - eq_(feature['iso2'], u'AG') - eq_(feature['iso3'], u'ATG') - eq_(feature['un'], 28) - eq_(feature['name'], u'Antigua and Barbuda') - eq_(feature['area'], 44) - eq_(feature['pop2005'], 83039) - eq_(feature['region'], 19) - eq_(feature['subregion'], 29) - eq_(feature['lon'], -61.783) - eq_(feature['lat'], 17.078) + fs = iter(ds) + feature = next(fs) + assert feature['OGC_FID'] == 1 + assert feature['fips'] == u'AC' + assert feature['iso2'] == u'AG' + assert feature['iso3'] == u'ATG' + assert feature['un'] == 28 + assert feature['name'] == u'Antigua and Barbuda' + assert feature['area'] == 44 + assert feature['pop2005'] == 83039 + assert feature['region'] == 19 + assert feature['subregion'] == 29 + assert feature['lon'] == -61.783 + assert feature['lat'] == 17.078 ds = mapnik.SQLite(file='../data/sqlite/world.sqlite', table='(select * from world_merc)', ) - fs = ds.featureset() - feature = fs.next() - eq_(feature['OGC_FID'], 1) - eq_(feature['fips'], u'AC') - eq_(feature['iso2'], u'AG') - eq_(feature['iso3'], u'ATG') - eq_(feature['un'], 28) - eq_(feature['name'], u'Antigua and Barbuda') - eq_(feature['area'], 44) - eq_(feature['pop2005'], 83039) - eq_(feature['region'], 19) - eq_(feature['subregion'], 29) - eq_(feature['lon'], -61.783) - eq_(feature['lat'], 17.078) + fs = iter(ds) + feature = next(fs) + assert feature['OGC_FID'] == 1 + assert feature['fips'] == u'AC' + assert feature['iso2'] == u'AG' + assert feature['iso3'] == u'ATG' + assert feature['un'] == 28 + assert feature['name'] == u'Antigua and Barbuda' + assert feature['area'] == 44 + assert feature['pop2005'] == 83039 + assert feature['region'] == 19 + assert feature['subregion'] == 29 + assert feature['lon'] == -61.783 + assert feature['lat'] == 17.078 ds = mapnik.SQLite(file='../data/sqlite/world.sqlite', table='(select OGC_FID,GEOMETRY from world_merc)', ) - fs = ds.featureset() - feature = fs.next() - eq_(feature['OGC_FID'], 1) - eq_(len(feature), 1) + fs = iter(ds) + feature = next(fs) + assert feature['OGC_FID'] == 1 + assert len(feature) == 1 ds = mapnik.SQLite(file='../data/sqlite/world.sqlite', table='(select GEOMETRY,OGC_FID,fips from world_merc)', ) - fs = ds.featureset() - feature = fs.next() - eq_(feature['OGC_FID'], 1) - eq_(feature['fips'], u'AC') + fs = iter(ds) + feature = next(fs) + assert feature['OGC_FID'] == 1 + assert feature['fips'] == u'AC' # same as above, except with alias like postgres requires # TODO - should we try to make this work? @@ -613,18 +594,18 @@ def test_subqueries(): # table='(select GEOMETRY,rowid as aliased_id,fips from world_merc) as table', # key_field='aliased_id' # ) - #fs = ds.featureset() - #feature = fs.next() - # eq_(feature['aliased_id'],1) - # eq_(feature['fips'],u'AC') + #fs = iter(ds) + #feature = next(fs) + # assert feature['aliased_id'] == 1 + # assert feature['fips'] == u'AC' ds = mapnik.SQLite(file='../data/sqlite/world.sqlite', table='(select GEOMETRY,OGC_FID,OGC_FID as rowid,fips from world_merc)', ) - fs = ds.featureset() - feature = fs.next() - eq_(feature['rowid'], 1) - eq_(feature['fips'], u'AC') + fs = iter(ds) + feature = next(fs) + assert feature['rowid'] == 1 + assert feature['fips'] == u'AC' test_subqueries.requires_data = True @@ -632,80 +613,79 @@ def test_empty_db(): ds = mapnik.SQLite(file='../data/sqlite/empty.db', table='empty', ) - fs = ds.featureset() + fs = iter(ds) feature = None try: - feature = fs.next() + feature = next(fs) except StopIteration: pass - eq_(feature, None) + assert feature == None test_empty_db.requires_data = True - @raises(RuntimeError) + def test_that_nonexistant_query_field_throws(**kwargs): ds = mapnik.SQLite(file='../data/sqlite/empty.db', table='empty', ) - eq_(len(ds.fields()), 25) - eq_(ds.fields(), - ['OGC_FID', - 'scalerank', - 'labelrank', - 'featurecla', - 'sovereignt', - 'sov_a3', - 'adm0_dif', - 'level', - 'type', - 'admin', - 'adm0_a3', - 'geou_dif', - 'name', - 'abbrev', - 'postal', - 'name_forma', - 'terr_', - 'name_sort', - 'map_color', - 'pop_est', - 'gdp_md_est', - 'fips_10_', - 'iso_a2', - 'iso_a3', - 'iso_n3']) - eq_(ds.field_types(), - ['int', - 'int', - 'int', - 'str', - 'str', - 'str', - 'float', - 'float', - 'str', - 'str', - 'str', - 'float', - 'str', - 'str', - 'str', - 'str', - 'str', - 'str', - 'float', - 'float', - 'float', - 'float', - 'str', - 'str', - 'float']) + assert len(ds.fields()) == 25 + assert ds.fields() == ['OGC_FID', + 'scalerank', + 'labelrank', + 'featurecla', + 'sovereignt', + 'sov_a3', + 'adm0_dif', + 'level', + 'type', + 'admin', + 'adm0_a3', + 'geou_dif', + 'name', + 'abbrev', + 'postal', + 'name_forma', + 'terr_', + 'name_sort', + 'map_color', + 'pop_est', + 'gdp_md_est', + 'fips_10_', + 'iso_a2', + 'iso_a3', + 'iso_n3'] + assert ds.field_types() == ['int', + 'int', + 'int', + 'str', + 'str', + 'str', + 'float', + 'float', + 'str', + 'str', + 'str', + 'float', + 'str', + 'str', + 'str', + 'str', + 'str', + 'str', + 'float', + 'float', + 'float', + 'float', + 'str', + 'str', + 'float'] query = mapnik.Query(ds.envelope()) for fld in ds.fields(): query.add_property_name(fld) # also add an invalid one, triggering throw query.add_property_name('bogus') - ds.features(query) + with pytest.raises(RuntimeError): + ds.features(query) test_that_nonexistant_query_field_throws.requires_data = True @@ -713,13 +693,13 @@ def test_intersects_token1(): ds = mapnik.SQLite(file='../data/sqlite/empty.db', table='(select * from empty where !intersects!)', ) - fs = ds.featureset() + fs = iter(ds) feature = None try: - feature = fs.next() + feature = next(fs) except StopIteration: pass - eq_(feature, None) + assert feature == None test_intersects_token1.requires_data = True @@ -727,13 +707,13 @@ def test_intersects_token2(): ds = mapnik.SQLite(file='../data/sqlite/empty.db', table='(select * from empty where "a"!="b" and !intersects!)', ) - fs = ds.featureset() + fs = iter(ds) feature = None try: - feature = fs.next() + feature = next(fs) except StopIteration: pass - eq_(feature, None) + assert feature == None test_intersects_token2.requires_data = True @@ -741,13 +721,13 @@ def test_intersects_token3(): ds = mapnik.SQLite(file='../data/sqlite/empty.db', table='(select * from empty where "a"!="b" and !intersects!)', ) - fs = ds.featureset() + fs = iter(ds) feature = None try: - feature = fs.next() + feature = next(fs) except StopIteration: pass - eq_(feature, None) + assert feature == None test_intersects_token3.requires_data = True @@ -766,15 +746,15 @@ def test_db_with_one_text_column(): use_spatial_index=False, key_field='alias' ) - eq_(len(ds.fields()), 1) - eq_(ds.fields(), ['alias']) - eq_(ds.field_types(), ['str']) - fs = list(ds.all_features()) - eq_(len(fs), 1) + assert len(ds.fields()) == 1 + assert ds.fields() == ['alias'] + assert ds.field_types() == ['str'] + fs = list(iter(ds)) + assert len(fs) == 1 feat = fs[0] - eq_(feat.id(), 0) # should be 1? - eq_(feat['alias'], 'test') - eq_(feat.geometry.to_wkt(), 'POINT(0 0)') + assert feat.id() == 0 # should be 1? + assert feat['alias'] == 'test' + assert feat.geometry.to_wkt() == 'POINT(0 0)' def test_db_with_one_untyped_column(): # form up an in-memory test db @@ -791,9 +771,9 @@ def test_db_with_one_untyped_column(): ) # ensure the untyped column is found - eq_(len(ds.fields()), 2) - eq_(ds.fields(), ['rowid', 'untyped']) - eq_(ds.field_types(), ['int', 'str']) + assert len(ds.fields()) == 2 + assert ds.fields(), ['rowid' == 'untyped'] + assert ds.field_types(), ['int' == 'str'] def test_db_with_one_untyped_column_using_subquery(): # form up an in-memory test db @@ -810,27 +790,27 @@ def test_db_with_one_untyped_column_using_subquery(): ) # ensure the untyped column is found - eq_(len(ds.fields()), 3) - eq_(ds.fields(), ['rowid', 'untyped', 'rowid']) - eq_(ds.field_types(), ['int', 'str', 'int']) + assert len(ds.fields()) == 3 + assert ds.fields(), ['rowid', 'untyped' == 'rowid'] + assert ds.field_types(), ['int', 'str' == 'int'] def test_that_64bit_int_fields_work(): ds = mapnik.SQLite(file='../data/sqlite/64bit_int.sqlite', table='int_table', use_spatial_index=False ) - eq_(len(ds.fields()), 3) - eq_(ds.fields(), ['OGC_FID', 'id', 'bigint']) - eq_(ds.field_types(), ['int', 'int', 'int']) - fs = ds.featureset() - feat = fs.next() - eq_(feat.id(), 1) - eq_(feat['OGC_FID'], 1) - eq_(feat['bigint'], 2147483648) - feat = fs.next() - eq_(feat.id(), 2) - eq_(feat['OGC_FID'], 2) - eq_(feat['bigint'], 922337203685477580) + assert len(ds.fields()) == 3 + assert ds.fields(), ['OGC_FID', 'id' == 'bigint'] + assert ds.field_types(), ['int', 'int' == 'int'] + fs = iter(ds) + feat = next(fs) + assert feat.id() == 1 + assert feat['OGC_FID'] == 1 + assert feat['bigint'] == 2147483648 + feat = next(fs) + assert feat.id() == 2 + assert feat['OGC_FID'] == 2 + assert feat['bigint'] == 922337203685477580 test_that_64bit_int_fields_work.requires_data = True @@ -854,17 +834,11 @@ def test_null_id_field(): use_spatial_index=False, key_field='osm_id' ) - fs = ds.featureset() + fs = iter(ds) feature = None try: - feature = fs.next() + feature = next(fs) except StopIteration: pass - eq_(feature, None) + assert feature == None mapnik.logger.set_severity(default_logging_severity) - -if __name__ == "__main__": - setup() - result = run_all(eval(x) for x in dir() if x.startswith("test_")) - teardown() - exit(result) diff --git a/test/python_tests/style_test.py b/test/python_tests/style_test.py index ff2c05e8f..00dca93da 100644 --- a/test/python_tests/style_test.py +++ b/test/python_tests/style_test.py @@ -1,21 +1,10 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from nose.tools import eq_ - import mapnik -from .utilities import run_all - - def test_style_init(): s = mapnik.Style() - eq_(s.filter_mode, mapnik.filter_mode.ALL) - eq_(len(s.rules), 0) - eq_(s.opacity, 1) - eq_(s.comp_op, None) - eq_(s.image_filters, "") - eq_(s.image_filters_inflate, False) - -if __name__ == "__main__": - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert s.filter_mode == mapnik.filter_mode.ALL + assert len(s.rules) == 0 + assert s.opacity == 1 + assert s.comp_op == None + assert s.image_filters == "" + assert not s.image_filters_inflate diff --git a/test/python_tests/topojson_plugin_test.py b/test/python_tests/topojson_plugin_test.py index 5a11a8343..ec92c696c 100644 --- a/test/python_tests/topojson_plugin_test.py +++ b/test/python_tests/topojson_plugin_test.py @@ -1,70 +1,48 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function - -import os - -from nose.tools import assert_almost_equal, eq_ - import mapnik +import pytest +import os -from .utilities import execution_path, run_all - +from .utilities import execution_path +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield if 'topojson' in mapnik.DatasourceCache.plugin_names(): - def test_topojson_init(): + def test_topojson_init(setup): # topojson tests/data/json/escaped.geojson -o tests/data/topojson/escaped.topojson --properties # topojson version 1.4.2 ds = mapnik.Datasource( type='topojson', file='../data/topojson/escaped.topojson') e = ds.envelope() - assert_almost_equal(e.minx, -81.705583, places=7) - assert_almost_equal(e.miny, 41.480573, places=6) - assert_almost_equal(e.maxx, -81.705583, places=5) - assert_almost_equal(e.maxy, 41.480573, places=3) + assert e.minx == pytest.approx(-81.705583, 1e-7) + assert e.miny == pytest.approx( 41.480573, 1e-6) + assert e.maxx == pytest.approx(-81.705583, 1e-5) + assert e.maxy == pytest.approx(41.480573, 1e-3) def test_topojson_properties(): - ds = mapnik.Datasource( - type='topojson', - file='../data/topojson/escaped.topojson') - f = list(ds.features_at_point(ds.envelope().center()))[0] - eq_(len(ds.fields()), 11) - desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - - eq_(f['name'], u'Test') - eq_(f['int'], 1) - eq_(f['description'], u'Test: \u005C') - eq_(f['spaces'], u'this has spaces') - eq_(f['double'], 1.1) - eq_(f['boolean'], True) - eq_(f['NOM_FR'], u'Qu\xe9bec') - eq_(f['NOM_FR'], u'Québec') - - ds = mapnik.Datasource( - type='topojson', - file='../data/topojson/escaped.topojson') - f = list(ds.all_features())[0] - eq_(len(ds.fields()), 11) - - desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - - eq_(f['name'], u'Test') - eq_(f['int'], 1) - eq_(f['description'], u'Test: \u005C') - eq_(f['spaces'], u'this has spaces') - eq_(f['double'], 1.1) - eq_(f['boolean'], True) - eq_(f['NOM_FR'], u'Qu\xe9bec') - eq_(f['NOM_FR'], u'Québec') + ds = mapnik.Datasource( + type='topojson', + file='../data/topojson/escaped.topojson') + + f = list(ds.features_at_point(ds.envelope().center()))[0] + assert len(ds.fields()) == 11 + desc = ds.describe() + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + + assert f['name'] == u'Test' + assert f['int'] == 1 + assert f['description'] == u'Test: \u005C' + assert f['spaces'] == u'this has spaces' + assert f['double'] == 1.1 + assert f['boolean'] == True + assert f['NOM_FR'] == u'Qu\xe9bec' + assert f['NOM_FR'] == u'Québec' def test_geojson_from_in_memory_string(): ds = mapnik.Datasource( @@ -72,42 +50,43 @@ def test_geojson_from_in_memory_string(): inline=open( '../data/topojson/escaped.topojson', 'r').read()) - f = list(ds.all_features())[0] - eq_(len(ds.fields()), 11) - + f = list(ds.features_at_point(ds.envelope().center()))[0] + assert len(ds.fields()) == 11 desc = ds.describe() - eq_(desc['geometry_type'], mapnik.DataGeometryType.Point) - - eq_(f['name'], u'Test') - eq_(f['int'], 1) - eq_(f['description'], u'Test: \u005C') - eq_(f['spaces'], u'this has spaces') - eq_(f['double'], 1.1) - eq_(f['boolean'], True) - eq_(f['NOM_FR'], u'Qu\xe9bec') - eq_(f['NOM_FR'], u'Québec') - -# @raises(RuntimeError) + assert desc['geometry_type'] == mapnik.DataGeometryType.Point + + assert f['name'] == u'Test' + assert f['int'] == 1 + assert f['description'] == u'Test: \u005C' + assert f['spaces'] == u'this has spaces' + assert f['double'] == 1.1 + assert f['boolean'] == True + assert f['NOM_FR'] == u'Qu\xe9bec' + assert f['NOM_FR'] == u'Québec' + + #@raises(RuntimeError) def test_that_nonexistant_query_field_throws(**kwargs): + #with pytest.raises(RuntimeError): ds = mapnik.Datasource( type='topojson', file='../data/topojson/escaped.topojson') - eq_(len(ds.fields()), 11) + assert len(ds.fields()) == 11 # TODO - this sorting is messed up - eq_(ds.fields(), ['name', 'int', 'description', - 'spaces', 'double', 'boolean', 'NOM_FR', - 'object', 'array', 'empty_array', 'empty_object']) - eq_(ds.field_types(), ['str', 'int', - 'str', 'str', 'float', 'bool', 'str', - 'str', 'str', 'str', 'str']) -# TODO - should topojson plugin throw like others? -# query = mapnik.Query(ds.envelope()) -# for fld in ds.fields(): -# query.add_property_name(fld) -# # also add an invalid one, triggering throw -# query.add_property_name('bogus') -# fs = ds.features(query) - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) + assert ds.fields() == ['name', 'int', 'description', + 'spaces', 'double', 'boolean', 'NOM_FR', + 'object', 'array', 'empty_array', 'empty_object'] + assert ds.field_types() == ['str', 'int', + 'str', 'str', 'float', 'bool', 'str', + 'str', 'str', 'str', 'str'] + # TODO - should topojson plugin throw like others? + query = mapnik.Query(ds.envelope()) + for fld in ds.fields(): + query.add_property_name(fld) + # also add an invalid one, triggering throw + query.add_property_name('bogus') + fs = ds.features(query) + + +#if __name__ == "__main__": + #setup() +# exit(run_all(eval(x) for x in dir() if x.startswith("test_"))) diff --git a/test/python_tests/utilities.py b/test/python_tests/utilities.py index 9bfc9aec6..0aa3cdf92 100644 --- a/test/python_tests/utilities.py +++ b/test/python_tests/utilities.py @@ -4,35 +4,16 @@ import os import sys import traceback - -from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin -from nose.tools import assert_almost_equal - import mapnik +import pytest -PYTHON3 = sys.version_info[0] == 3 -READ_FLAGS = 'rb' if PYTHON3 else 'r' -if PYTHON3: - xrange = range - +READ_FLAGS = 'rb' HERE = os.path.dirname(__file__) - def execution_path(filename): return os.path.join(os.path.dirname( sys._getframe(1).f_code.co_filename), filename) - -class Todo(Exception): - pass - - -class TodoPlugin(ErrorClassPlugin): - name = "todo" - - todo = ErrorClass(Todo, label='TODO', isfailure=False) - - def contains_word(word, bytestring_): """ Checks that a bytestring contains a given word. len(bytestring) should be @@ -51,7 +32,7 @@ def contains_word(word, bytestring_): """ n = len(word) assert len(bytestring_) % n == 0, "len(bytestring_) not multiple of len(word)" - chunks = [bytestring_[i:i + n] for i in xrange(0, len(bytestring_), n)] + chunks = [bytestring_[i:i + n] for i in range(0, len(bytestring_), n)] return word in chunks @@ -77,29 +58,6 @@ def get_unique_colors(im): pixels = sorted(pixels) return list(map(pixel2rgba, pixels)) - -def run_all(iterable): - failed = 0 - for test in iterable: - try: - test() - sys.stderr.write("\x1b[32m✓ \x1b[m" + test.__name__ + "\x1b[m\n") - except: - exc_type, exc_value, exc_tb = sys.exc_info() - failed += 1 - sys.stderr.write("\x1b[31m✘ \x1b[m" + test.__name__ + "\x1b[m\n") - for mline in traceback.format_exception_only(exc_type, exc_value): - for line in mline.rstrip().split("\n"): - sys.stderr.write(" \x1b[31m" + line + "\x1b[m\n") - sys.stderr.write(" Traceback:\n") - for mline in traceback.format_tb(exc_tb): - for line in mline.rstrip().split("\n"): - if not 'utilities.py' in line and not 'trivial.py' in line and not line.strip() == 'test()': - sys.stderr.write(" " + line + "\n") - sys.stderr.flush() - return failed - - def side_by_side_image(left_im, right_im): width = left_im.width() + 1 + right_im.width() height = max(left_im.height(), right_im.height()) @@ -135,7 +93,19 @@ def side_by_side_image(left_im, right_im): def assert_box2d_almost_equal(a, b, msg=None): msg = msg or ("%r != %r" % (a, b)) - assert_almost_equal(a.minx, b.minx, msg=msg) - assert_almost_equal(a.maxx, b.maxx, msg=msg) - assert_almost_equal(a.miny, b.miny, msg=msg) - assert_almost_equal(a.maxy, b.maxy, msg=msg) + assert a.minx == pytest.approx(b.minx, abs=1e-2), msg + assert a.maxx == pytest.approx(b.maxx, abs=1e-2), msg + assert a.miny == pytest.approx(b.miny, abs=1e-2), msg + assert a.maxy == pytest.approx(b.maxy, abs=1e-2), msg + + +def images_almost_equal(image1, image2, tolerance = 1): + def rgba(p): + return p & 0xff,(p >> 8) & 0xff,(p >> 16) & 0xff, p >> 24 + assert image1.width() == image2.width() + assert image1.height() == image2.height() + for x in range(image1.width()): + for y in range(image1.height()): + p1 = image1.get_pixel(x, y) + p2 = image2.get_pixel(x, y) + assert rgba(p1) == pytest.approx(rgba(p2), abs = tolerance) diff --git a/test/python_tests/webp_encoding_test.py b/test/python_tests/webp_encoding_test.py index ccd8f4229..4af0950a9 100644 --- a/test/python_tests/webp_encoding_test.py +++ b/test/python_tests/webp_encoding_test.py @@ -1,20 +1,15 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function - -import os - -from nose.tools import eq_, raises - import mapnik +import os +import pytest -from .utilities import execution_path, run_all - +from .utilities import execution_path +@pytest.fixture(scope="module") def setup(): # All of the paths used are relative, if we run the tests # from another directory we need to chdir() os.chdir(execution_path('.')) + yield if mapnik.has_webp(): tmp_dir = '/tmp/mapnik-webp/' @@ -48,26 +43,28 @@ def gen_filepath(name, format): return os.path.join('images/support/encoding-opts', name + '-' + format.replace(":", "+") + '.webp') - def test_quality_threshold(): + def test_quality_threshold(setup): im = mapnik.Image(256, 256) - im.tostring('webp:quality=99.99000') - im.tostring('webp:quality=0') - im.tostring('webp:quality=0.001') + im.to_string('webp:quality=99.99000') + im.to_string('webp:quality=0') + im.to_string('webp:quality=0.001') + - @raises(RuntimeError) def test_quality_threshold_invalid(): im = mapnik.Image(256, 256) - im.tostring('webp:quality=101') + with pytest.raises(RuntimeError): + im.to_string('webp:quality=101') + - @raises(RuntimeError) def test_quality_threshold_invalid2(): im = mapnik.Image(256, 256) - im.tostring('webp:quality=-1') + with pytest.raises(RuntimeError): + im.to_string('webp:quality=-1') - @raises(RuntimeError) def test_quality_threshold_invalid3(): im = mapnik.Image(256, 256) - im.tostring('webp:quality=101.1') + with pytest.raises(RuntimeError): + im.to_string('webp:quality=101.1') generate = os.environ.get('UPDATE') @@ -83,14 +80,14 @@ def test_expected_encodings(): im.save(expected, opt) im.save(actual, opt) try: - expected_bytes = mapnik.Image.open(expected).tostring() + expected_bytes = mapnik.Image.open(expected).to_string() except RuntimeError: # this will happen if libweb is old, since it cannot open # images created by more recent webp print( 'warning, cannot open webp expected image (your libwebp is likely too old)') continue - if mapnik.Image.open(actual).tostring() != expected_bytes: + if mapnik.Image.open(actual).to_string() != expected_bytes: fails.append( '%s (actual) not == to %s (expected)' % (actual, expected)) @@ -105,14 +102,14 @@ def test_expected_encodings(): im.save(expected, opt) im.save(actual, opt) try: - expected_bytes = mapnik.Image.open(expected).tostring() + expected_bytes = mapnik.Image.open(expected).to_string() except RuntimeError: # this will happen if libweb is old, since it cannot open # images created by more recent webp print( 'warning, cannot open webp expected image (your libwebp is likely too old)') continue - if mapnik.Image.open(actual).tostring() != expected_bytes: + if mapnik.Image.open(actual).to_string() != expected_bytes: fails.append( '%s (actual) not == to %s (expected)' % (actual, expected)) @@ -127,19 +124,19 @@ def test_expected_encodings(): im.save(expected, opt) im.save(actual, opt) try: - expected_bytes = mapnik.Image.open(expected).tostring() + expected_bytes = mapnik.Image.open(expected).to_string() except RuntimeError: # this will happen if libweb is old, since it cannot open # images created by more recent webp print( 'warning, cannot open webp expected image (your libwebp is likely too old)') continue - if mapnik.Image.open(actual).tostring() != expected_bytes: + if mapnik.Image.open(actual).to_string() != expected_bytes: fails.append( '%s (actual) not == to %s (expected)' % (actual, expected)) # disabled to avoid failures on ubuntu when using old webp packages - # eq_(fails,[],'\n'+'\n'.join(fails)) + # assert fails,[] == '\n'+'\n'.join(fails) except RuntimeError as e: print(e) @@ -166,20 +163,15 @@ def test_transparency_levels(): im.save('images/support/transparency/white0.webp') im.save(t0, format) im_in = mapnik.Image.open(t0) - t0_len = len(im_in.tostring(format)) + t0_len = len(im_in.to_string(format)) try: - expected_bytes = mapnik.Image.open(expected).tostring(format) + expected_bytes = mapnik.Image.open(expected).to_string(format) except RuntimeError: # this will happen if libweb is old, since it cannot open # images created by more recent webp print( 'warning, cannot open webp expected image (your libwebp is likely too old)') return - eq_(t0_len, len(expected_bytes)) + assert t0_len == len(expected_bytes) except RuntimeError as e: print(e) - - -if __name__ == "__main__": - setup() - exit(run_all(eval(x) for x in dir() if x.startswith("test_")))