pax_global_header00006660000000000000000000000064147406416340014523gustar00rootroot0000000000000052 comment=13adc2e79d2b1ad1b84eddbb4db4f45cd9c66905 mod_tile-0.8.0/000077500000000000000000000000001474064163400133245ustar00rootroot00000000000000mod_tile-0.8.0/.dockerignore000066400000000000000000000004521474064163400160010ustar00rootroot00000000000000* !cmake !CMakeLists.txt !docs/man !etc/apache2/renderd-example-map.conf !etc/apache2/tile.load.in !etc/renderd/renderd.conf.examples !etc/renderd/renderd.conf.in !includes !src !tests !utils/example-map # AutoTools !autogen.sh !configure.ac !etc/renderd/renderd.conf !m4 !Makefile.am !modules.mk mod_tile-0.8.0/.github/000077500000000000000000000000001474064163400146645ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/000077500000000000000000000000001474064163400163245ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/autotools/000077500000000000000000000000001474064163400203555ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/autotools/build/000077500000000000000000000000001474064163400214545ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/autotools/build/action.yml000066400000000000000000000005431474064163400234560ustar00rootroot00000000000000--- runs: using: composite steps: - name: Run `./autogen.sh` run: ./autogen.sh shell: bash --noprofile --norc -euxo pipefail {0} - name: Run `./configure` run: ./configure shell: bash --noprofile --norc -euxo pipefail {0} - name: Run `make` run: make shell: bash --noprofile --norc -euxo pipefail {0} mod_tile-0.8.0/.github/actions/autotools/install/000077500000000000000000000000001474064163400220235ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/autotools/install/action.yml000066400000000000000000000005471474064163400240310ustar00rootroot00000000000000--- runs: using: composite steps: - name: Run `make install` run: ${{ !matrix.image && 'sudo -E' || '' }} make install shell: bash --noprofile --norc -euxo pipefail {0} - name: Run `make install-mod_tile` run: ${{ !matrix.image && 'sudo -E' || '' }} make install-mod_tile shell: bash --noprofile --norc -euxo pipefail {0} mod_tile-0.8.0/.github/actions/autotools/test/000077500000000000000000000000001474064163400213345ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/autotools/test/action.yml000066400000000000000000000002171474064163400233340ustar00rootroot00000000000000--- runs: using: composite steps: - name: Run `make test` run: make test shell: bash --noprofile --norc -euxo pipefail {0} mod_tile-0.8.0/.github/actions/build/000077500000000000000000000000001474064163400174235ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/build/action.yml000066400000000000000000000004431474064163400214240ustar00rootroot00000000000000--- runs: using: composite steps: - name: Build `mod_tile` (Autotools) uses: ./.github/actions/autotools/build if: matrix.build_system == 'Autotools' - name: Build `mod_tile` (CMake) uses: ./.github/actions/cmake/build if: matrix.build_system == 'CMake' mod_tile-0.8.0/.github/actions/cmake/000077500000000000000000000000001474064163400174045ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/cmake/build/000077500000000000000000000000001474064163400205035ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/cmake/build/action.yml000066400000000000000000000014101474064163400224770ustar00rootroot00000000000000--- runs: using: composite steps: - name: Prepare `build` directory run: | cmake -B build -S . \ -LA \ -DCMAKE_BUILD_TYPE:STRING=${BUILD_TYPE:-Release} \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=${INSTALL_LOCALSTATEDIR:-/var} \ -DCMAKE_INSTALL_PREFIX:PATH=${INSTALL_PREFIX:-/usr} \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=${INSTALL_RUNSTATEDIR:-/run} \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=${INSTALL_SYSCONFDIR:-/etc} \ -DENABLE_TESTS:BOOL=ON shell: bash --noprofile --norc -euxo pipefail {0} - name: Build `mod_tile` run: | export CMAKE_BUILD_PARALLEL_LEVEL=${BUILD_PARALLEL_LEVEL:-$(nproc)} cmake --build build shell: bash --noprofile --norc -euxo pipefail {0} mod_tile-0.8.0/.github/actions/cmake/install/000077500000000000000000000000001474064163400210525ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/cmake/install/action.yml000066400000000000000000000003031474064163400230460ustar00rootroot00000000000000--- runs: using: composite steps: - name: Install `mod_tile` run: ${{ !matrix.image && 'sudo' || '' }} cmake --install build shell: bash --noprofile --norc -euxo pipefail {0} mod_tile-0.8.0/.github/actions/cmake/package/000077500000000000000000000000001474064163400207775ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/cmake/package/action.yml000066400000000000000000000033131474064163400227770ustar00rootroot00000000000000--- runs: using: composite steps: - name: Set `CPACK_PACKAGE_FILE_NAME` run: | echo "CPACK_PACKAGE_FILE_NAME=mod_tile-${GITHUB_SHA}-$(echo ${{ matrix.image || matrix.os || matrix.box_freebsd || github.job }}-${{ matrix.build_system }}-${{ matrix.compiler }} | sed 's/[^0-9a-zA-Z-]/_/g')" >> ${GITHUB_ENV} shell: bash --noprofile --norc -euxo pipefail {0} - name: Set `CPACK_OPTIONS` run: | if command -v dpkg; then echo "CPACK_OPTIONS= -G DEB" >> ${GITHUB_ENV} elif command -v rpm; then echo "CPACK_OPTIONS=-G RPM" >> ${GITHUB_ENV} elif [ -f /etc/os-release ]; then source /etc/os-release if [ "$ID" = "freebsd" ]; then echo "CPACK_OPTIONS=-D CPACK_SET_DESTDIR=1 -G FREEBSD" >> ${GITHUB_ENV} fi elif [[ ${OSTYPE} == 'darwin'* ]]; then echo "CPACK_OPTIONS=-D CPACK_SET_DESTDIR=1 -G DragNDrop" >> ${GITHUB_ENV} else echo "CPACK_OPTIONS=-D CPACK_SET_DESTDIR=1 -G TGZ" >> ${GITHUB_ENV} fi shell: bash --noprofile --norc -euxo pipefail {0} - name: Package `mod_tile` run: | ${{ !matrix.image && 'sudo' || '' }} cpack ${CPACK_OPTIONS} \ -D CPACK_PACKAGE_FILE_NAME="${CPACK_PACKAGE_FILE_NAME}" || true shell: bash --noprofile --norc -euxo pipefail {0} working-directory: build - name: Upload `mod_tile` package artifact uses: actions/upload-artifact@v4 with: if-no-files-found: ignore name: Package Artifacts - ${{ env.CPACK_PACKAGE_FILE_NAME }}${{ matrix.mapnik_latest && ' (Latest Mapnik)' || '' }} path: | build/${{ env.CPACK_PACKAGE_FILE_NAME }}.* retention-days: 14 mod_tile-0.8.0/.github/actions/cmake/test/000077500000000000000000000000001474064163400203635ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/cmake/test/action.yml000066400000000000000000000016271474064163400223710ustar00rootroot00000000000000--- inputs: options: default: --exclude-regex 'clear_dirs_.+|remove_tile_.+' --output-on-failure runs: using: composite steps: - name: Test `mod_tile` run: | export CTEST_PARALLEL_LEVEL=${TEST_PARALLEL_LEVEL:-$(nproc)} ctest ${{ inputs.options }} shell: bash --noprofile --norc -euxo pipefail {0} working-directory: build - name: Set `TEST_ARTIFACT_NAME` run: | echo "TEST_ARTIFACT_NAME=$(echo ${{ matrix.image || matrix.os || matrix.box_freebsd || github.job }}-${{ matrix.build_system }}-${{ matrix.compiler }} | sed 's/[^0-9a-zA-Z-]/_/g')" >> ${GITHUB_ENV} shell: bash --noprofile --norc -euxo pipefail {0} if: failure() - name: Upload `mod_tile` test artifacts on failure uses: actions/upload-artifact@v4 with: name: Test Artifacts - ${{ env.TEST_ARTIFACT_NAME }} path: build/tests if: failure() mod_tile-0.8.0/.github/actions/coverage/000077500000000000000000000000001474064163400201175ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/coverage/action.yml000066400000000000000000000045351474064163400221260ustar00rootroot00000000000000--- inputs: genhtml-extra-options: default: "" lcov-extra-options: default: "" codecov-token: default: "" runs: using: composite steps: - name: Process `mod_tile` coverage results with CTest run: | ctest -T coverage || true shell: bash --noprofile --norc -euxo pipefail {0} working-directory: build if: matrix.build_system == 'CMake' - name: Process `mod_tile` coverage results run: | lcov ${{ inputs.lcov-extra-options }} \ --base-directory . \ --capture \ --directory ${{ matrix.build_system == 'CMake' && 'build' || '.' }} \ --output-file coverage.info \ --rc geninfo_unexecuted_blocks=1 lcov ${{ inputs.lcov-extra-options }} \ --ignore-errors unused \ --output-file coverage.info \ --remove coverage.info \ "${GITHUB_WORKSPACE}/includes/*" \ "${GITHUB_WORKSPACE}/tests/*" \ "/usr/*" shell: bash --noprofile --norc -euxo pipefail {0} - name: Report `mod_tile` coverage results to `codecov.io` uses: codecov/codecov-action@v4 env: CODECOV_TOKEN: ${{ inputs.codecov-token }} with: disable_search: true file: coverage.info - name: Write `mod_tile` coverage summary to `$GITHUB_STEP_SUMMARY` run: | lcov ${{ inputs.lcov-extra-options }} \ --summary \ coverage.info | sed 's/^ /* /g' >> ${GITHUB_STEP_SUMMARY} shell: bash --noprofile --norc -euxo pipefail {0} - name: Generate `mod_tile` coverage artifacts run: | genhtml ${{ inputs.genhtml-extra-options }} \ --output-directory coverage \ coverage.info shell: bash --noprofile --norc -euxo pipefail {0} - name: Set `COVERAGE_ARTIFACT_NAME` run: | echo "COVERAGE_ARTIFACT_NAME=$(echo ${{ matrix.image || matrix.os || matrix.box_freebsd || github.job }}-${{ matrix.build_system }}-${{ matrix.compiler }} | sed 's/[^0-9a-zA-Z-]/_/g')" >> ${GITHUB_ENV} shell: bash --noprofile --norc -euxo pipefail {0} - name: Upload `mod_tile` coverage artifacts uses: actions/upload-artifact@v4 with: name: Coverage Artifacts - ${{ env.COVERAGE_ARTIFACT_NAME }}${{ matrix.mapnik_latest && ' (Latest Mapnik)' || '' }} path: | coverage coverage.info mod_tile-0.8.0/.github/actions/dependencies/000077500000000000000000000000001474064163400207525ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/dependencies/build-and-install/000077500000000000000000000000001474064163400242555ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/dependencies/build-and-install/mapnik/000077500000000000000000000000001474064163400255345ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/dependencies/build-and-install/mapnik/action.yml000066400000000000000000000034531474064163400275410ustar00rootroot00000000000000--- inputs: version: default: latest description: Version of Mapnik to build & install required: true runs: using: composite steps: - name: Cache "Checkout `Mapnik`" & "Build `Mapnik`" id: cache-mapnik uses: actions/cache@v4 with: path: | mapnik-build mapnik-src key: ${{ matrix.image || matrix.os || matrix.box_freebsd || github.job }}-${{ matrix.compiler }}-mapnik-${{ inputs.version }} - name: Checkout `Mapnik` uses: actions/checkout@v4 with: path: mapnik-src ref: ${{ inputs.version != 'latest' && format('v{0}', inputs.version) || '' }} repository: mapnik/mapnik submodules: recursive if: steps.cache-mapnik.outputs.cache-hit != 'true' - name: Build `Mapnik` run: | export CMAKE_BUILD_PARALLEL_LEVEL=${BUILD_PARALLEL_LEVEL:-$(nproc)} cmake -B mapnik-build -S mapnik-src \ -DBUILD_BENCHMARK:BOOL=OFF \ -DBUILD_DEMO_CPP:BOOL=OFF \ -DBUILD_DEMO_VIEWER:BOOL=OFF \ -DBUILD_TESTING:BOOL=OFF \ -DBUILD_UTILITY_GEOMETRY_TO_WKB:BOOL=OFF \ -DBUILD_UTILITY_MAPNIK_INDEX:BOOL=OFF \ -DBUILD_UTILITY_MAPNIK_RENDER:BOOL=OFF \ -DBUILD_UTILITY_OGRINDEX:BOOL=OFF \ -DBUILD_UTILITY_PGSQL2SQLITE:BOOL=OFF \ -DBUILD_UTILITY_SHAPEINDEX:BOOL=OFF \ -DBUILD_UTILITY_SVG2PNG:BOOL=OFF \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_PREFIX:PATH=/usr cmake --build mapnik-build shell: bash --noprofile --norc -euxo pipefail {0} if: steps.cache-mapnik.outputs.cache-hit != 'true' - name: Install `Mapnik` run: ${{ !matrix.image && 'sudo' || '' }} cmake --install mapnik-build shell: bash --noprofile --norc -euxo pipefail {0} mod_tile-0.8.0/.github/actions/dependencies/install/000077500000000000000000000000001474064163400224205ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/dependencies/install/action.yml000066400000000000000000000214341474064163400244240ustar00rootroot00000000000000--- inputs: centos-stream-build-dependencies: default: >- cairo-devel glib2-devel httpd-devel iniparser-devel libcurl-devel libmemcached-devel librados2-devel centos-stream-test-dependencies: default: >- httpd jq lcov memcached procps centos-stream-mapnik-build-dependencies: default: >- boost-devel cmake freetype-devel gdal-devel git harfbuzz-devel libicu-devel libjpeg-devel libpng-devel libtiff-devel libwebp-devel libxml2-devel postgresql-devel proj-devel sqlite-devel zlib-devel debian-build-dependencies: default: >- apache2-dev libcairo2-dev libcurl4-gnutls-dev libglib2.0-dev libiniparser-dev libmemcached-dev librados-dev debian-mapnik-latest-build-dependencies: default: >- cmake git libboost-program-options-dev libboost-regex-dev libfreetype6-dev libgdal-dev libharfbuzz-dev libicu-dev libjpeg-dev libpq-dev libproj-dev libsqlite3-dev libtiff-dev libwebp-dev libxml2-dev postgresql-server-dev-all debian-test-dependencies: default: >- apache2 git jq lcov memcached fedora-build-dependencies: default: >- cairo-devel glib2-devel httpd-devel iniparser-devel libcurl-devel libmemcached-devel librados-devel mapnik-devel mapnik-static sqlite-devel fedora-test-dependencies: default: >- git httpd jq lcov memcached procps freebsd-build-dependencies: default: >- apache24 cairo coreutils curl glib iniparser libmemcached mapnik pkgconf freebsd-test-dependencies: default: >- jq lcov memcached macos-build-dependencies: default: >- apr cairo curl glib httpd icu4c iniparser libmemcached mapnik pkg-config macos-test-dependencies: default: >- coreutils jq lcov memcached opensuse-build-dependencies: default: >- apache2-devel cairo-devel curl glib2-devel libcurl-devel libiniparser-devel libmemcached-devel librados-devel opensuse-test-dependencies: default: >- apache2 apache2-event apache2-prefork jq lcov memcached procps opensuse-mapnik-build-dependencies: default: >- cmake freetype-devel gdal-devel git harfbuzz-devel libboost_regex1_75_0-devel libicu-devel libjpeg8-devel libpng16-devel libtiff-devel libwebp-devel libxml2-devel postgresql-devel proj-devel sqlite3-devel zlib-devel ubuntu-build-dependencies: default: >- apache2-dev libcairo2-dev libcurl4-gnutls-dev libglib2.0-dev libiniparser-dev libmemcached-dev librados-dev ubuntu-mapnik-latest-build-dependencies: default: >- cmake git libboost-program-options-dev libboost-regex-dev libfreetype6-dev libgdal-dev libharfbuzz-dev libicu-dev libjpeg-dev libpq-dev libproj-dev libsqlite3-dev libtiff-dev libwebp-dev libxml2-dev postgresql-server-dev-all ubuntu-test-dependencies: default: >- apache2 git jq lcov memcached mapnik-build-version-centos-stream: default: 4.0.3 mapnik-build-version-opensuse: default: 4.0.3 runs: using: composite steps: - name: Install Dependencies (CentOS Stream) uses: ./.github/actions/dependencies/install/yum with: dependencies: epel-release packages: >- ${{ inputs.centos-stream-build-dependencies }} ${{ inputs.centos-stream-mapnik-build-dependencies }} ${{ inputs.centos-stream-test-dependencies }} ${{ matrix.build_system == 'CMake' && 'cmake' || 'autoconf automake redhat-rpm-config' }} ${{ matrix.compiler == 'LLVM' && 'clang' || 'gcc gcc-c++' }} rpm-build if: startsWith(matrix.image, 'quay.io/centos/centos:stream') - name: Install Dependencies (Debian) uses: ./.github/actions/dependencies/install/apt-get with: packages: >- ${{ inputs.debian-build-dependencies }} ${{ matrix.mapnik_latest && inputs.debian-mapnik-latest-build-dependencies || 'libmapnik-dev' }} ${{ inputs.debian-test-dependencies }} ${{ matrix.build_system == 'CMake' && 'cmake' || '' }} ${{ matrix.compiler == 'LLVM' && 'clang' || 'g++ gcc' }} if: startsWith(matrix.image, 'debian:') - name: Install Dependencies (Fedora) uses: ./.github/actions/dependencies/install/yum with: packages: >- ${{ inputs.fedora-build-dependencies }} ${{ inputs.fedora-test-dependencies }} ${{ matrix.build_system == 'CMake' && 'cmake' || 'autoconf automake redhat-rpm-config' }} ${{ matrix.compiler == 'LLVM' && 'clang' || 'gcc gcc-c++' }} rpm-build if: startsWith(matrix.image, 'fedora:') - name: Install Dependencies (FreeBSD) uses: ./.github/actions/dependencies/install/pkg with: packages: >- ${{ inputs.freebsd-build-dependencies }} ${{ inputs.freebsd-test-dependencies }} ${{ matrix.build_system == 'CMake' && 'cmake' || 'autoconf automake' }} ${{ matrix.compiler == 'GNU' && 'gcc' || 'llvm' }} if: github.job == 'FreeBSD' - name: Install Dependencies (macOS) uses: ./.github/actions/dependencies/install/brew with: packages: >- ${{ inputs.macos-build-dependencies }} ${{ inputs.macos-test-dependencies }} ${{ matrix.build_system == 'CMake' && 'cmake' || 'autoconf automake' }} ${{ matrix.compiler == 'GNU' && 'gcc' || '' }} if: github.job == 'macOS' - name: Install Dependencies (openSUSE) uses: ./.github/actions/dependencies/install/zypper with: packages: >- ${{ inputs.opensuse-build-dependencies }} ${{ inputs.opensuse-mapnik-build-dependencies }} ${{ inputs.opensuse-test-dependencies }} ${{ matrix.build_system == 'CMake' && 'cmake' || 'automake' }} ${{ matrix.compiler == 'LLVM' && 'clang' || 'gcc12 gcc12-c++' }} rpm-build if: startsWith(matrix.image, 'opensuse/') - name: Install Dependencies (Ubuntu) uses: ./.github/actions/dependencies/install/apt-get with: packages: >- ${{ inputs.ubuntu-build-dependencies }} ${{ matrix.mapnik_latest && inputs.ubuntu-mapnik-latest-build-dependencies || 'libmapnik-dev' }} ${{ inputs.ubuntu-test-dependencies }} ${{ matrix.build_system == 'CMake' && 'cmake' || '' }} ${{ matrix.compiler == 'LLVM' && 'clang' || 'g++ gcc' }} if: | startsWith(matrix.image, 'ubuntu:') || (!matrix.image && github.job != 'FreeBSD' && runner.os == 'Linux') - name: Link `gcc`/`g++` (openSUSE) run: | update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 40 update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 40 shell: bash --noprofile --norc -euxo pipefail {0} if: startsWith(matrix.image, 'opensuse/') && matrix.compiler == 'GNU' - name: Add `nobody` user/group (openSUSE) run: | useradd --home-dir / --no-create-home --shell /usr/sbin/nologin --system --user-group nobody shell: bash --noprofile --norc -euxo pipefail {0} if: startsWith(matrix.image, 'opensuse/') - name: Build & Install `mapnik` (CentOS Stream) uses: ./.github/actions/dependencies/build-and-install/mapnik with: version: ${{ inputs.mapnik-build-version-centos-stream }} if: startsWith(matrix.image, 'quay.io/centos/centos:stream') - name: Build & Install `mapnik` (openSUSE) uses: ./.github/actions/dependencies/build-and-install/mapnik with: version: ${{ inputs.mapnik-build-version-opensuse }} if: (!matrix.mapnik_latest && startsWith(matrix.image, 'opensuse/')) - name: Build & Install latest `mapnik` (Debian/Ubuntu) uses: ./.github/actions/dependencies/build-and-install/mapnik with: version: latest if: | matrix.mapnik_latest && (startsWith(matrix.image, 'debian:') || startsWith(matrix.image, 'ubuntu:') || runner.os == 'Linux') - name: Build & Install latest `mapnik` (openSUSE) uses: ./.github/actions/dependencies/build-and-install/mapnik with: version: latest if: (matrix.mapnik_latest && startsWith(matrix.image, 'opensuse/')) mod_tile-0.8.0/.github/actions/dependencies/install/apt-get/000077500000000000000000000000001474064163400237615ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/dependencies/install/apt-get/action.yml000066400000000000000000000010111474064163400257520ustar00rootroot00000000000000--- inputs: packages: description: List of package(s) to install required: true runs: using: composite steps: - name: Update package information run: ${{ !matrix.image && 'sudo' || '' }} apt-get --yes update shell: bash --noprofile --norc -euxo pipefail {0} - name: Install package(s) env: DEBIAN_FRONTEND: noninteractive run: ${{ !matrix.image && 'sudo' || '' }} apt-get --yes install ${{ inputs.packages }} shell: bash --noprofile --norc -euxo pipefail {0} mod_tile-0.8.0/.github/actions/dependencies/install/brew/000077500000000000000000000000001474064163400233575ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/dependencies/install/brew/action.yml000066400000000000000000000004131474064163400253550ustar00rootroot00000000000000--- inputs: packages: description: List of package(s) to install required: true runs: using: composite steps: - name: Install package(s) run: brew install ${{ inputs.packages }} || true shell: bash --noprofile --norc -euxo pipefail {0} mod_tile-0.8.0/.github/actions/dependencies/install/pkg/000077500000000000000000000000001474064163400232015ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/dependencies/install/pkg/action.yml000066400000000000000000000005761474064163400252110ustar00rootroot00000000000000--- inputs: packages: description: List of package(s) to install required: true options: default: --yes description: Option(s) to pass runs: using: composite steps: - name: Install package(s) run: ${{ !matrix.image && 'sudo' || '' }} pkg install ${{ inputs.options }} ${{ inputs.packages }} shell: bash --noprofile --norc -euxo pipefail {0} mod_tile-0.8.0/.github/actions/dependencies/install/yum/000077500000000000000000000000001474064163400232325ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/dependencies/install/yum/action.yml000066400000000000000000000027741474064163400252440ustar00rootroot00000000000000--- inputs: dependencies: description: List of package(s) to pre-install required: false groups: description: List of group(s) to install required: false packages: description: List of package(s) to install required: true runs: using: composite steps: - name: Install "dnf-command(config-manager)" (CentOS Stream) run: dnf --assumeyes --skip-broken install "dnf-command(config-manager)" shell: bash --noprofile --norc -euxo pipefail {0} if: startsWith(matrix.image, 'quay.io/centos/centos:stream') - name: Enable CRB repository (CentOS Stream) run: dnf config-manager --set-enabled crb shell: bash --noprofile --norc -euxo pipefail {0} if: startsWith(matrix.image, 'quay.io/centos/centos:stream') - name: Install dependency package(s) run: | if [ -n "${{ inputs.dependencies }}" ]; then yum --assumeyes install ${{ inputs.dependencies }} fi shell: bash --noprofile --norc -euxo pipefail {0} - name: Install group(s) run: | if [ -n '${{ inputs.groups }}' ]; then yum --assumeyes groups install ${{ inputs.groups }} fi shell: bash --noprofile --norc -euxo pipefail {0} - name: Install package(s) run: | if command -v dnf5 &> /dev/null; then yum --assumeyes install ${{ inputs.packages }} else yum --assumeyes --skip-broken install ${{ inputs.packages }} fi shell: bash --noprofile --norc -euxo pipefail {0} mod_tile-0.8.0/.github/actions/dependencies/install/zypper/000077500000000000000000000000001474064163400237515ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/dependencies/install/zypper/action.yml000066400000000000000000000004741474064163400257560ustar00rootroot00000000000000--- inputs: packages: description: List of package(s) to install required: true runs: using: composite steps: - name: Install package(s) run: ${{ !matrix.image && 'sudo' || '' }} zypper --non-interactive install ${{ inputs.packages }} shell: bash --noprofile --norc -euxo pipefail {0} mod_tile-0.8.0/.github/actions/install/000077500000000000000000000000001474064163400177725ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/install/action.yml000066400000000000000000000004531474064163400217740ustar00rootroot00000000000000--- runs: using: composite steps: - name: Install `mod_tile` (Autotools) uses: ./.github/actions/autotools/install if: matrix.build_system == 'Autotools' - name: Install `mod_tile` (CMake) uses: ./.github/actions/cmake/install if: matrix.build_system == 'CMake' mod_tile-0.8.0/.github/actions/test/000077500000000000000000000000001474064163400173035ustar00rootroot00000000000000mod_tile-0.8.0/.github/actions/test/action.yml000066400000000000000000000004371474064163400213070ustar00rootroot00000000000000--- runs: using: composite steps: - name: Test `mod_tile` (Autotools) uses: ./.github/actions/autotools/test if: matrix.build_system == 'Autotools' - name: Test `mod_tile` (CMake) uses: ./.github/actions/cmake/test if: matrix.build_system == 'CMake' mod_tile-0.8.0/.github/workflows/000077500000000000000000000000001474064163400167215ustar00rootroot00000000000000mod_tile-0.8.0/.github/workflows/build-and-test.yml000066400000000000000000000200551474064163400222620ustar00rootroot00000000000000--- name: Build & Test on: pull_request: push: jobs: Linux: continue-on-error: ${{ matrix.experimental || false }} name: >- ${{ matrix.image }} (${{ matrix.build_system }}) (${{ matrix.compiler }}) runs-on: ubuntu-latest strategy: matrix: build_system: - CMake compiler: - GNU image: - "debian:11" - "debian:12" - "fedora:40" - "fedora:41" - "opensuse/leap:15" - "quay.io/centos/centos:stream9" - "ubuntu:22.04" on_default_branch: - ${{ contains(github.ref, 'master') || contains(github.ref, 'develop') || contains(github.ref, 'CI') }} include: - image: "debian:unstable" build_system: CMake compiler: GNU experimental: true - image: "fedora:rawhide" build_system: CMake compiler: GNU experimental: true - image: "ubuntu:devel" build_system: CMake compiler: GNU experimental: true - image: "ubuntu:24.04" build_system: Autotools compiler: GNU - image: "ubuntu:24.04" build_system: Autotools compiler: LLVM - image: "ubuntu:24.04" build_system: CMake compiler: GNU - image: "ubuntu:24.04" build_system: CMake compiler: LLVM exclude: - on_default_branch: false fail-fast: false container: env: CC: ${{ matrix.compiler == 'LLVM' && 'clang' || 'gcc' }} CFLAGS: --coverage CXX: ${{ matrix.compiler == 'LLVM' && 'clang++' || 'g++' }} CXXFLAGS: --coverage image: ${{ matrix.image }} steps: - name: Install `git` (openSUSE) run: zypper --non-interactive install git if: startsWith(matrix.image, 'opensuse/') - name: Checkout code uses: actions/checkout@v4 - name: Install dependencies uses: ./.github/actions/dependencies/install - name: Build `mod_tile` uses: ./.github/actions/build - name: Test `mod_tile` uses: ./.github/actions/test - name: Process & Report `mod_tile` coverage results uses: ./.github/actions/coverage with: codecov-token: ${{ secrets.CODECOV_TOKEN }} if: | matrix.compiler != 'LLVM' && !startsWith(matrix.image, 'opensuse/') && !matrix.experimental - name: Package `mod_tile` uses: ./.github/actions/cmake/package if: matrix.build_system == 'CMake' - name: Install `mod_tile` uses: ./.github/actions/install Linux-Latest-Mapnik: continue-on-error: true name: >- ${{ matrix.image }} (Latest Mapnik) (${{ matrix.build_system }}) (${{ matrix.compiler }}) runs-on: ubuntu-latest strategy: matrix: image: - "debian:12" - "ubuntu:24.04" build_system: - CMake compiler: - GNU mapnik_latest: - true fail-fast: false container: env: CC: ${{ matrix.compiler == 'LLVM' && 'clang' || 'gcc' }} CFLAGS: --coverage CXX: ${{ matrix.compiler == 'LLVM' && 'clang++' || 'g++' }} CXXFLAGS: --coverage image: ${{ matrix.image }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Install dependencies uses: ./.github/actions/dependencies/install - name: Build `mod_tile` uses: ./.github/actions/build - name: Test `mod_tile` uses: ./.github/actions/test - name: Process & Report `mod_tile` coverage results uses: ./.github/actions/coverage with: codecov-token: ${{ secrets.CODECOV_TOKEN }} - name: Package `mod_tile` uses: ./.github/actions/cmake/package if: matrix.build_system == 'CMake' - name: Install `mod_tile` uses: ./.github/actions/install macOS: env: CFLAGS: --coverage CXXFLAGS: --coverage INSTALL_PREFIX: /usr/local INSTALL_RUNSTATEDIR: /var/run LDFLAGS: -undefined dynamic_lookup name: >- ${{ matrix.os }} (${{ matrix.build_system }}) (${{ matrix.compiler }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: - macos-13 - macos-14 build_system: - CMake compiler: - LLVM on_default_branch: - ${{ contains(github.ref, 'master') || contains(github.ref, 'develop') || contains(github.ref, 'CI') }} include: - os: macos-15 build_system: CMake compiler: LLVM exclude: - on_default_branch: false steps: - name: Checkout code uses: actions/checkout@v4 - name: Install dependencies uses: ./.github/actions/dependencies/install - name: Set CPATH, ICU_ROOT & LIBRARY_PATH run: | echo "CPATH=$(brew --prefix)/include" >> ${GITHUB_ENV} echo "ICU_ROOT=$(brew --prefix icu4c)" >> ${GITHUB_ENV} echo "LIBRARY_PATH=$(brew --prefix)/lib" >> ${GITHUB_ENV} - name: Build `mod_tile` uses: ./.github/actions/build - name: Test `mod_tile` uses: ./.github/actions/test - name: Process & Report `mod_tile` coverage results uses: ./.github/actions/coverage with: codecov-token: ${{ secrets.CODECOV_TOKEN }} genhtml-extra-options: --keep-going --ignore-errors count,inconsistent,range lcov-extra-options: --keep-going --ignore-errors count,inconsistent,range if: matrix.os != 'macos-12' - name: Package `mod_tile` uses: ./.github/actions/cmake/package if: matrix.build_system == 'CMake' - name: Install `mod_tile` uses: ./.github/actions/install FreeBSD: env: CFLAGS: --coverage CTEST_CLIENT_HOST: ::1 CTEST_SERVER_HOST: localhost CXXFLAGS: --coverage INSTALL_PREFIX: /usr/local LIBRARY_PATH: /usr/local/lib TMPDIR: /tmp name: >- ${{ matrix.box_freebsd }} (${{ matrix.build_system }}) (${{ matrix.compiler }}) runs-on: ubuntu-latest strategy: matrix: box_freebsd: - FreeBSD-13.4-STABLE build_system: - CMake compiler: - LLVM on_default_branch: - ${{ contains(github.ref, 'master') || contains(github.ref, 'develop') || contains(github.ref, 'CI') }} include: - box_freebsd: FreeBSD-14.1-STABLE build_system: CMake compiler: LLVM exclude: - on_default_branch: false fail-fast: false steps: - name: Checkout code uses: actions/checkout@v4 - name: Set `BUILD_PARALLEL_LEVEL` & `TEST_PARALLEL_LEVEL` run: | echo "BUILD_PARALLEL_LEVEL=$(nproc)" >> ${GITHUB_ENV} echo "TEST_PARALLEL_LEVEL=$(nproc)" >> ${GITHUB_ENV} - name: Provision VM uses: hummeltech/freebsd-vagrant-action@v3 with: box: freebsd/${{ matrix.box_freebsd }} cpus: ${{ env.BUILD_PARALLEL_LEVEL }} memory: 4096 # Mapnik is not in the `quarterly` repository (2023.10.12) - name: Use "latest" repository run: | sudo mkdir -p /usr/local/etc/pkg/repos sed 's#/quarterly#/latest#g' /etc/pkg/FreeBSD.conf | sudo tee /usr/local/etc/pkg/repos/FreeBSD.conf sudo pkg upgrade - name: Install dependencies uses: ./.github/actions/dependencies/install - name: Build `mod_tile` uses: ./.github/actions/build - name: Test `mod_tile` uses: ./.github/actions/test - name: Process & Report `mod_tile` coverage results uses: ./.github/actions/coverage with: codecov-token: ${{ secrets.CODECOV_TOKEN }} - name: Package `mod_tile` uses: ./.github/actions/cmake/package - name: Install `mod_tile` uses: ./.github/actions/install mod_tile-0.8.0/.github/workflows/docker-image-build.yml000066400000000000000000000061701474064163400230740ustar00rootroot00000000000000--- name: Docker Image Build on: pull_request: push: paths: - "docker/**" - ".github/workflows/docker-image-build.yml" jobs: dockerfile-lint: name: Lint Dockerfiles runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Lint with hadolint uses: hadolint/hadolint-action@v3.1.0 with: dockerfile: Dockerfile* failure-threshold: warning recursive: true docker-image-build: continue-on-error: ${{ matrix.experimental || false }} name: Build & Test (${{ matrix.service-name }}) needs: dockerfile-lint runs-on: ubuntu-latest strategy: matrix: service-name: - archlinux - centos-stream-9 - debian-11 - debian-12 - fedora-40 - fedora-41 - opensuse-leap-15 - ubuntu-22.04 - ubuntu-24.04 include: - service-name: debian-unstable experimental: true - service-name: debian-unstable-autotools experimental: true - service-name: fedora-rawhide experimental: true - service-name: opensuse-tumbleweed experimental: true - service-name: ubuntu-devel experimental: true - service-name: ubuntu-devel-autotools experimental: true fail-fast: false steps: - name: Checkout repository uses: actions/checkout@v4 - name: Build & Start run: docker compose up --build --detach ${{ matrix.service-name }} working-directory: docker - name: Test run: | until $(curl --fail --output tile.jpg.file.0 --silent http://localhost:8081/tiles/renderd-example-jpg/9/297/191.jpg); do echo 'Sleeping 5s'; sleep 5; done sha256sum --check --ignore-missing tests/tiles.sha256sum | grep tile.jpg.file.0 | grep -q OK until $(curl --fail --output tile.png256.file.0 --silent http://localhost:8081/tiles/renderd-example-png256/9/297/191.png); do echo 'Sleeping 5s'; sleep 5; done sha256sum --check --ignore-missing tests/tiles.sha256sum | grep tile.png256.file.0 | grep -q OK until $(curl --fail --output tile.png32.file.0 --silent http://localhost:8081/tiles/renderd-example-png32/9/297/191.png); do echo 'Sleeping 5s'; sleep 5; done sha256sum --check --ignore-missing tests/tiles.sha256sum | grep tile.png32.file.0 | grep -q OK until $(curl --fail --output tile.webp.file.0 --silent http://localhost:8081/tiles/renderd-example-webp/9/297/191.webp); do echo 'Sleeping 5s'; sleep 5; done sha256sum --check --ignore-missing tests/tiles.sha256sum | grep tile.webp.file.0 | grep -q OK timeout-minutes: 1 - name: Show logs run: docker compose logs working-directory: docker if: failure() - name: Stop run: docker compose down --volumes working-directory: docker if: success() || failure() mod_tile-0.8.0/.github/workflows/flawfinder-analysis.yml000066400000000000000000000013541474064163400234110ustar00rootroot00000000000000--- name: Flawfinder Analysis on: pull_request: push: branches: - develop - master schedule: - cron: "44 0 * * 4" jobs: flawfinder: name: Flawfinder runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write steps: - name: Checkout code uses: actions/checkout@v4 - name: flawfinder_scan uses: david-a-wheeler/flawfinder@2.0.19 with: arguments: "--sarif includes src" output: "flawfinder_results.sarif" - name: Upload analysis results to GitHub Security tab uses: github/codeql-action/upload-sarif@v2 with: sarif_file: ${{github.workspace}}/flawfinder_results.sarif mod_tile-0.8.0/.github/workflows/install-package-and-test.yml000066400000000000000000000051641474064163400242260ustar00rootroot00000000000000--- name: Install Package & Test on: pull_request: push: paths: - "etc/**" - "utils/**" - ".github/workflows/install-package-and-test.yml" jobs: install-package-and-test: continue-on-error: ${{ matrix.experimental || false }} name: ${{ matrix.image }} runs-on: ubuntu-latest strategy: matrix: image: - "debian:11" - "debian:12" - "ubuntu:22.04" - "ubuntu:24.04" include: - image: "debian:unstable" experimental: true - image: "ubuntu:devel" experimental: true fail-fast: false container: image: ${{ matrix.image }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Install mod_tile & renderd uses: ./.github/actions/dependencies/install/apt-get with: packages: >- apache2 curl libapache2-mod-tile renderd - name: Prepare run: | mkdir -p /usr/share/renderd cp -av utils/example-map /usr/share/renderd/ cp -av etc/apache2/renderd-example-map.conf /etc/apache2/sites-available/renderd-example-map.conf printf ' [example-map] URI=/tiles/renderd-example XML=/usr/share/renderd/example-map/mapnik.xml [example-map-jpg] TYPE=jpg image/jpeg jpeg URI=/tiles/renderd-example-jpg XML=/usr/share/renderd/example-map/mapnik.xml [example-map-png256] TYPE=png image/png png256 URI=/tiles/renderd-example-png256 XML=/usr/share/renderd/example-map/mapnik.xml [example-map-png32] TYPE=png image/png png32 URI=/tiles/renderd-example-png32 XML=/usr/share/renderd/example-map/mapnik.xml [example-map-webp] TYPE=webp image/webp webp URI=/tiles/renderd-example-webp XML=/usr/share/renderd/example-map/mapnik.xml ' | tee -a /etc/renderd.conf mkdir -p /run/renderd renderd a2enmod tile a2ensite renderd-example-map apache2ctl restart shell: bash --noprofile --norc -euxo pipefail {0} - name: Test run: | until $(curl --fail --output tile.png --silent http://localhost:8081/tiles/renderd-example/9/297/191.png); do echo 'Sleeping 5s'; sleep 5; done echo 'dbf26531286e844a3a9735cdd193598dca78d22f77cafe5824bcaf17f88cbb08 tile.png' | sha256sum --check shell: bash --noprofile --norc -euxo pipefail {0} timeout-minutes: 1 mod_tile-0.8.0/.github/workflows/lint.yml000066400000000000000000000054221474064163400204150ustar00rootroot00000000000000--- name: Lint on: pull_request: push: jobs: astyle: env: ASTYLE_CMD: >- astyle --break-blocks --indent-switches --indent=force-tab=8 --lineend=linux --options=none --pad-header --pad-oper --style=1tbs --suffix=none --unpad-paren includes/*.h includes/*.hpp src/*.c src/*.cpp name: Lint with `Artistic Style` runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Provision environment uses: ./.github/actions/dependencies/install/apt-get with: packages: astyle - name: Check if any modifications were made by `astyle` run: | ASTYLE_OUTPUT=$(${ASTYLE_CMD} --dry-run) if [ -n "$(echo "${ASTYLE_OUTPUT}" | grep -v "Unchanged")" ]; then echo "The following files are in need of formatting:" echo "${ASTYLE_OUTPUT}" | grep -v "Unchanged" | awk '{print "`"$2"`"}' echo "" echo "Run the following command before submitting a pull request:" echo '`'"${ASTYLE_CMD}"'`' exit 1 fi - name: Write `$ASTYLE_OUTPUT` to `$GITHUB_STEP_SUMMARY` run: | ASTYLE_OUTPUT=$(${ASTYLE_CMD} --dry-run) echo "### The following files are in need of formatting:" >> ${GITHUB_STEP_SUMMARY} echo "${ASTYLE_OUTPUT}" | grep -v "Unchanged" | awk '{print "- `"$2"`"}' >> ${GITHUB_STEP_SUMMARY} echo "### Run the following command before submitting a pull request:" >> ${GITHUB_STEP_SUMMARY} echo -e '```shell\n'"${ASTYLE_CMD}"'\n```' >> ${GITHUB_STEP_SUMMARY} if: failure() - name: Generate `ArtisticStyleFormattingFixes.patch` file run: | ${ASTYLE_CMD} git diff --patch > ArtisticStyleFormattingFixes.patch if: failure() - name: Upload `ArtisticStyleFormattingFixes.patch` file uses: actions/upload-artifact@v4 with: name: ArtisticStyleFormattingFixes.patch path: ArtisticStyleFormattingFixes.patch if: failure() cmakelint: name: Lint with `CMakeLint` runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Provision environment run: pip install --user cmakelint - name: Run linter run: | cmakelint --linelength=125 \ CMakeLists.txt \ */CMakeLists.txt \ */*.cmake prettier: name: Lint with `Prettier` runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Provision environment run: npm install prettier - name: Run linter run: | npx prettier --check . mod_tile-0.8.0/.gitignore000066400000000000000000000010351474064163400153130ustar00rootroot00000000000000.deps/ .dirstamp .libs/ *.la *.lo *.o *.slo aclocal.m4 autom4te.cache/ build !docs/build compile config.guess config.guess~ config.h config.h.in config.h.in~ config.log config.status config.sub config.sub~ configure configure~ depcomp gen_tile_test install-sh libtool ltmain.sh m4/libtool.m4 m4/lt~obsolete.m4 m4/ltoptions.m4 m4/ltsugar.m4 m4/ltversion.m4 Makefile* missing render_expired render_list render_old render_speedtest renderd renderd.sock renderd.stats stamp-h1 stderr.out # Do not ignore !Makefile.am !etc/renderd !.github/**/* mod_tile-0.8.0/AUTHORS000066400000000000000000000030221474064163400143710ustar00rootroot00000000000000Ævar Arnfjörð Bjarmason Amanda Andreas Hubel Anton Belichkov Bas Couwenberg Ben Hosmer Ben Kochie Brian Quinion Christoph Brill Dane Springmeyer David Hummel <6109326+hummeltech@users.noreply.github.com> Dirk Stöcker Eric Stadtherr Felix Delattre Frederik Ramm Ircama JaimeLynSchatz Jocelyn Jaubert Jochen Topf Jon Burgess Kai Krueger Lennard voor den Dag Manfred Stock Matt Amos Michael Fazio Michael Schmiedgen Moritz Seemann Paul Norman Peter Körner Rainer Jung Ramunas Robert Buchholz rusvdw Sean Reifschneider SomeoneElseOSM Stephan Austermühle Stephan Knauss Stephan Plepelits Tom Hughes vholten Xin Yu Zverik mod_tile-0.8.0/CMakeLists.txt000066400000000000000000000216171474064163400160730ustar00rootroot00000000000000#----------------------------------------------------------------------------- # # CMake Config # #----------------------------------------------------------------------------- cmake_minimum_required(VERSION 3.13 FATAL_ERROR) list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") #----------------------------------------------------------------------------- # # Project version # #----------------------------------------------------------------------------- project(mod_tile DESCRIPTION "Renders map tiles with Mapnik and serves them using Apache HTTP Server" HOMEPAGE_URL "https://github.com/openstreetmap/mod_tile" VERSION 0.8.0 ) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 11 CACHE STRING "Sets the C++ standard.") set(CMAKE_CXX_STANDARD_REQUIRED ON) set(THREADS_PREFER_PTHREAD_FLAG ON) set(CMAKE_INSTALL_MODULESDIR CACHE PATH "Apache HTTP Server module installation directory") set(ENABLE_MAN ON CACHE BOOL "Build man pages") set(ENABLE_TESTS OFF CACHE BOOL "Build test suite") set(USE_CAIRO ON CACHE BOOL "Add cairo support if available (for `store_ro_composite.c` backend)") set(USE_CURL ON CACHE BOOL "Add curl support if available (for `store_ro_http_proxy.c` backend)") set(USE_MEMCACHED ON CACHE BOOL "Add memcached support if available (for `store_memcached.c` backend)") set(USE_RADOS ON CACHE BOOL "Add rados support if available (for `store_rados.c` backend)") set(MALLOC_LIB "libc" CACHE STRING "Memory Management Library for `renderd`") set_property(CACHE MALLOC_LIB PROPERTY STRINGS "libc" "jemalloc" "mimalloc" "tcmalloc") #----------------------------------------------------------------------------- # # Find external dependencies # #----------------------------------------------------------------------------- include(GNUInstallDirs) # Packages find_package(ICU REQUIRED uc) find_package(Threads REQUIRED) find_package(APR REQUIRED) find_package(GLIB 2.50 REQUIRED) find_package(HTTPD 2.4 REQUIRED) find_package(INIPARSER REQUIRED) find_package(LIBMAPNIK 3 REQUIRED) if(LIBMAPNIK_VERSION VERSION_GREATER_EQUAL 4) set(CMAKE_CXX_STANDARD 17) endif() if(USE_CURL) find_package(CURL) endif() if(USE_CAIRO) find_package(CAIRO) endif() if(USE_MEMCACHED) find_package(LIBMEMCACHED) endif() if(USE_RADOS) find_package(LIBRADOS) endif() # Programs find_program(APXS_EXECUTABLE apxs REQUIRED) # Functions include(CheckFunctionExists) check_function_exists(daemon HAVE_DAEMON) check_function_exists(getloadavg HAVE_GETLOADAVG) check_function_exists(pow HAVE_POW) # Include files include(CheckIncludeFile) check_include_file(paths.h HAVE_PATHS_H) check_include_file(sys/cdefs.h HAVE_SYS_CDEFS_H) check_include_file(sys/loadavg.h HAVE_SYS_LOADAVG_H) # Libraries include(CheckLibraryExists) if(NOT HAVE_POW) check_library_exists(m pow "" HAVE_LIB_M) find_library(MATH_LIBRARY m REQUIRED) endif() if(NOT MALLOC_LIB STREQUAL "libc") message(STATUS "Using '${MALLOC_LIB}' for memory managment") if(MALLOC_LIB STREQUAL "jemalloc") # jemalloc (http://jemalloc.net) find_library(MALLOC_LIBRARY jemalloc REQUIRED) elseif(MALLOC_LIB STREQUAL "mimalloc") # mimalloc (https://github.com/microsoft/mimalloc) find_library(MALLOC_LIBRARY mimalloc REQUIRED) elseif(MALLOC_LIB STREQUAL "tcmalloc") # tcmalloc (https://github.com/google/tcmalloc) find_library(MALLOC_LIBRARY tcmalloc REQUIRED) endif() endif() #----------------------------------------------------------------------------- # # Set variables # #----------------------------------------------------------------------------- execute_process(COMMAND ${APXS_EXECUTABLE} -q libexecdir OUTPUT_VARIABLE HTTPD_LIBEXECDIR OUTPUT_STRIP_TRAILING_WHITESPACE ) execute_process(COMMAND ${APXS_EXECUTABLE} -q sysconfdir OUTPUT_VARIABLE HTTPD_SYSCONFDIR OUTPUT_STRIP_TRAILING_WHITESPACE ) if(LIBMAPNIK_VERSION VERSION_LESS 4) find_program(MAPNIK_CONFIG_EXECUTABLE NAMES mapnik-config REQUIRED) execute_process(COMMAND ${MAPNIK_CONFIG_EXECUTABLE} --fonts OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE MAPNIK_FONTS_DIR ) execute_process(COMMAND ${MAPNIK_CONFIG_EXECUTABLE} --input-plugins OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE MAPNIK_PLUGINS_DIR ) elseif(LIBMAPNIK_VERSION VERSION_GREATER_EQUAL 4) pkg_get_variable(MAPNIK_FONTS_DIR libmapnik fonts_dir) pkg_get_variable(MAPNIK_PLUGINS_DIR libmapnik plugins_dir) endif() if(NOT CMAKE_INSTALL_MODULESDIR) set(CMAKE_INSTALL_MODULESDIR ${HTTPD_LIBEXECDIR}) endif() if(CAIRO_FOUND) set(HAVE_CAIRO 1) endif() if(CURL_FOUND) set(HAVE_LIBCURL 1) endif() if(LIBMEMCACHED_FOUND) set(HAVE_LIBMEMCACHED 1) endif() if(LIBRADOS_FOUND) set(HAVE_LIBRADOS 1) endif() if(CMAKE_HAVE_PTHREAD_H) set(HAVE_PTHREAD 1) endif() set(MAPNIK_FONTS_DIR "${MAPNIK_FONTS_DIR}") set(MAPNIK_FONTS_DIR_RECURSE 0) set(MAPNIK_PLUGINS_DIR "${MAPNIK_PLUGINS_DIR}") set(RENDERD_CONFIG "${CMAKE_INSTALL_SYSCONFDIR}/renderd.conf") set(RENDERD_RUN_DIR "${CMAKE_INSTALL_RUNSTATEDIR}/renderd") set(RENDERD_TILE_DIR "${CMAKE_INSTALL_LOCALSTATEDIR}/cache/renderd/tiles") set(RENDERD_PIDFILE "${RENDERD_RUN_DIR}/renderd.pid") set(RENDERD_SOCKET "${RENDERD_RUN_DIR}/renderd.sock") set(TILE_LOAD_DIRECTORY "${HTTPD_SYSCONFDIR}") set(TILE_LOAD_FILENAME "tile.load") set(VERSION "${PROJECT_VERSION}") #----------------------------------------------------------------------------- # # Configure # #----------------------------------------------------------------------------- # include/config.h.in configure_file( ${PROJECT_SOURCE_DIR}/includes/config.h.in ${PROJECT_SOURCE_DIR}/includes/config.h ) # etc/apache2/tile.load.in configure_file( ${PROJECT_SOURCE_DIR}/etc/apache2/tile.load.in ${PROJECT_BINARY_DIR}/tile.load ) # etc/renderd/renderd.conf.in configure_file( ${PROJECT_SOURCE_DIR}/etc/renderd/renderd.conf.in ${PROJECT_BINARY_DIR}/renderd.conf ) #----------------------------------------------------------------------------- # # Build # #----------------------------------------------------------------------------- add_subdirectory(src) #----------------------------------------------------------------------------- # # Install # #----------------------------------------------------------------------------- # Determine install destination for 'etc/apache2/tile.load.in' if(EXISTS "/etc/os-release") execute_process(COMMAND sh -c ". /etc/os-release && echo $ID" OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE DISTRO_ID ) message(STATUS "Found ID='${DISTRO_ID}' in '/etc/os-release'") if(DISTRO_ID MATCHES "arch") set(TILE_LOAD_DIRECTORY "${HTTPD_SYSCONFDIR}/extra") set(TILE_LOAD_FILENAME "httpd-tile.conf") elseif(DISTRO_ID MATCHES "centos|fedora|rhel") set(TILE_LOAD_DIRECTORY "${HTTPD_SYSCONFDIR}.modules.d") set(TILE_LOAD_FILENAME "11-tile.conf") elseif(DISTRO_ID MATCHES "debian|ubuntu") set(TILE_LOAD_DIRECTORY "${HTTPD_SYSCONFDIR}/mods-available") elseif(DISTRO_ID MATCHES "freebsd") set(TILE_LOAD_DIRECTORY "${HTTPD_SYSCONFDIR}/modules.d") set(TILE_LOAD_FILENAME "080_tile.conf") elseif(DISTRO_ID MATCHES "opensuse-leap|opensuse-tumbleweed") set(TILE_LOAD_DIRECTORY "${HTTPD_SYSCONFDIR}/conf.d") set(TILE_LOAD_FILENAME "mod_tile.conf") endif() elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") set(TILE_LOAD_DIRECTORY "${HTTPD_SYSCONFDIR}/extra") set(TILE_LOAD_FILENAME "httpd-tile.conf") endif() message(STATUS "File 'etc/apache2/tile.load.in' will be installed to '${TILE_LOAD_DIRECTORY}/${TILE_LOAD_FILENAME}'") # Directories install( DIRECTORY DESTINATION ${RENDERD_TILE_DIR} ) install( DIRECTORY DESTINATION ${RENDERD_RUN_DIR} ) # Configuration files install( FILES ${PROJECT_BINARY_DIR}/tile.load DESTINATION ${TILE_LOAD_DIRECTORY} RENAME ${TILE_LOAD_FILENAME} ) install( FILES ${PROJECT_BINARY_DIR}/renderd.conf DESTINATION ${CMAKE_INSTALL_SYSCONFDIR} ) # Targets install( TARGETS mod_tile render_expired render_list render_old render_speedtest renderd LIBRARY DESTINATION ${CMAKE_INSTALL_MODULESDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) # Man files if(ENABLE_MAN) install( FILES docs/man/render_expired.1 docs/man/render_list.1 docs/man/render_old.1 docs/man/render_speedtest.1 docs/man/renderd.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) install( FILES docs/man/renderd.conf.5 DESTINATION ${CMAKE_INSTALL_MANDIR}/man5 ) endif() #----------------------------------------------------------------------------- # # Package # #----------------------------------------------------------------------------- set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_PACKAGE_CONTACT "OpenStreetMap") set(CPACK_PACKAGE_VENDOR "OpenStreetMap") set(CPACK_RPM_PACKAGE_LICENSE "GPLv2") include(CPack) #----------------------------------------------------------------------------- # # Test # #----------------------------------------------------------------------------- if(ENABLE_TESTS) enable_testing() add_subdirectory(tests) endif() mod_tile-0.8.0/COPYING000066400000000000000000000432541474064163400143670ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program 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 of the License, or (at your option) any later version. This program 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 General Public License for more details. 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. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. mod_tile-0.8.0/Makefile.am000066400000000000000000000120471474064163400153640ustar00rootroot00000000000000AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 AM_CPPFLAGS = $(PTHREAD_CFLAGS) $(GLIB_CFLAGS) STORE_SOURCES = \ src/g_logger.c \ src/store.c \ src/store_file.c \ src/store_file_utils.c \ src/store_memcached.c \ src/store_null.c \ src/store_rados.c \ src/store_ro_composite.c \ src/store_ro_http_proxy.c STORE_LDFLAGS = $(LIBMEMCACHED_LDFLAGS) $(LIBRADOS_LDFLAGS) $(LIBCURL) $(GLIB_LIBS) STORE_CPPFLAGS = bin_PROGRAMS = \ renderd \ render_expired \ render_list \ render_old \ render_speedtest noinst_PROGRAMS = \ gen_tile_test \ renderd_config_test \ renderd_test \ render_expired_test \ render_list_test \ render_old_test \ render_speedtest_test man_MANS = \ docs/man/renderd.1 \ docs/man/renderd.conf.5 \ docs/man/render_expired.1 \ docs/man/render_list.1 \ docs/man/render_old.1 \ docs/man/render_speedtest.1 renderddir = $(sysconfdir) renderd_SOURCES = \ src/renderd.c \ src/cache_expire.c \ src/daemon_compat.c \ src/gen_tile.cpp \ src/metatile.cpp \ src/parameterize_style.cpp \ src/protocol_helper.c \ src/renderd_config.c \ src/request_queue.c \ src/sys_utils.c \ $(STORE_SOURCES) renderd_CXXFLAGS = $(MAPNIK_CFLAGS) renderd_DATA = etc/renderd/renderd.conf renderd_LDADD = $(PTHREAD_CFLAGS) $(MAPNIK_LDFLAGS) $(STORE_LDFLAGS) $(INIPARSER_LDFLAGS) render_speedtest_SOURCES = \ src/render_speedtest.cpp \ src/g_logger.c \ src/protocol_helper.c \ src/render_submit_queue.c \ src/renderd_config.c \ src/sys_utils.c render_speedtest_LDADD = $(PTHREAD_CFLAGS) $(GLIB_LIBS) $(INIPARSER_LDFLAGS) render_list_SOURCES = \ src/render_list.c \ src/protocol_helper.c \ src/render_submit_queue.c \ src/renderd_config.c \ src/sys_utils.c \ $(STORE_SOURCES) render_list_LDADD = $(PTHREAD_CFLAGS) $(STORE_LDFLAGS) $(INIPARSER_LDFLAGS) render_expired_SOURCES = \ src/render_expired.c \ src/protocol_helper.c \ src/render_submit_queue.c \ src/renderd_config.c \ src/sys_utils.c \ $(STORE_SOURCES) render_expired_LDADD = $(PTHREAD_CFLAGS) $(STORE_LDFLAGS) $(INIPARSER_LDFLAGS) render_old_SOURCES = \ src/render_old.c \ src/g_logger.c \ src/protocol_helper.c \ src/render_submit_queue.c \ src/renderd_config.c \ src/store_file_utils.c \ src/sys_utils.c render_old_LDADD = $(PTHREAD_CFLAGS) $(GLIB_LIBS) $(INIPARSER_LDFLAGS) #convert_meta_SOURCES = src/dir_utils.c src/store.c src/convert_meta.c noinst_LIBRARIES = catch_main.o catch_test_common.o catch_main_o_SOURCES = tests/catch_main.cpp catch_test_common_o_SOURCES = tests/catch_test_common.cpp gen_tile_test_SOURCES = \ tests/gen_tile_test.cpp \ $(renderd_SOURCES) gen_tile_test_CFLAGS = -DMAIN_ALREADY_DEFINED gen_tile_test_CXXFLAGS = $(renderd_CXXFLAGS) gen_tile_test_LDADD = $(renderd_LDADD) catch_test_common.o renderd_config_test_SOURCES = \ tests/renderd_config_test.cpp renderd_config_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o renderd_test_SOURCES = \ tests/renderd_test.cpp renderd_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o render_expired_test_SOURCES = \ tests/render_expired_test.cpp render_expired_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o render_list_test_SOURCES = \ tests/render_list_test.cpp render_list_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o render_old_test_SOURCES = \ tests/render_old_test.cpp render_old_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o render_speedtest_test_SOURCES = \ tests/render_speedtest_test.cpp render_speedtest_test_LDADD = $(GLIB_LIBS) catch_main.o catch_test_common.o CLEANFILES=*.slo mod_tile.la stderr.out src/*.slo src/*.lo src/.libs/* src/*.la COMMA=, test: gen_tile_test renderd_config_test renderd_test render_expired_test render_list_test render_old_test render_speedtest_test ./gen_tile_test ./renderd_config_test ./renderd_test ./render_expired_test ./render_list_test ./render_old_test ./render_speedtest_test all-local: $(APXS) -c $(DEF_LDLIBS) $(AM_CFLAGS) \ $(subst -pthread,-Wc$(COMMA)-pthread,$(GLIB_CFLAGS)) \ -I@srcdir@/includes $(AM_LDFLAGS) $(STORE_LDFLAGS) $(INIPARSER_LDFLAGS) \ @srcdir@/src/mod_tile.c \ @srcdir@/src/g_logger.c \ @srcdir@/src/renderd_config.c \ @srcdir@/src/store.c \ @srcdir@/src/store_file.c \ @srcdir@/src/store_file_utils.c \ @srcdir@/src/store_memcached.c \ @srcdir@/src/store_null.c \ @srcdir@/src/store_rados.c \ @srcdir@/src/store_ro_composite.c \ @srcdir@/src/store_ro_http_proxy.c \ @srcdir@/src/sys_utils.c install-mod_tile: mkdir -p $(DESTDIR)`$(APXS) -q LIBEXECDIR` $(APXS) -S LIBEXECDIR=$(DESTDIR)`$(APXS) \ -q LIBEXECDIR` -c -i $(DEF_LDLIBS) $(AM_CFLAGS) \ $(subst -pthread,-Wc$(COMMA)-pthread,$(GLIB_CFLAGS)) \ -I@srcdir@/includes $(AM_LDFLAGS) $(STORE_LDFLAGS) $(INIPARSER_LDFLAGS) \ @srcdir@/src/mod_tile.c \ @srcdir@/src/g_logger.c \ @srcdir@/src/renderd_config.c \ @srcdir@/src/store.c \ @srcdir@/src/store_file.c \ @srcdir@/src/store_file_utils.c \ @srcdir@/src/store_memcached.c \ @srcdir@/src/store_null.c \ @srcdir@/src/store_rados.c \ @srcdir@/src/store_ro_composite.c \ @srcdir@/src/store_ro_http_proxy.c \ @srcdir@/src/sys_utils.c mod_tile-0.8.0/README.rst000066400000000000000000000220401474064163400150110ustar00rootroot00000000000000==================== mod_tile and renderd ==================== This software contains two main pieces: 1) ``mod_tile``: An Apache 2 module to deliver map tiles. 2) ``renderd``: A daemon that renders map tiles using mapnik. .. figure:: ./screenshot.jpg :alt: Image showing example slippy map and OSM layer Together they efficiently render and serve raster map tiles for example to use within a slippy map. The two consist of the classic raster tile stack from `OpenStreetMap.org `__. As an alternative to ``renderd`` its drop-in replacement `Tirex `__ can be used in combination with ``mod_tile``. Dependencies ------------ * `Supported Operating Systems` * `FreeBSD` * `GNU/Linux` * `macOS` * `Supported Build Systems` * `CMake `__ * `GNU Autotools `__ * `Runtime/Build Dependencies` * `Apache 2 HTTP webserver `__ * `Cairo 2D graphics library (optional) `__ * `Curl library (optional) `__ * `GLib library `__ * `Iniparser library `__ * `Mapnik library `__ * `Memcached library (optional) `__ * `RADOS library (optional) `__ Installation ------------ Starting from the following operation systems and their versions: * Debian 11 (Bullseye) * Ubuntu 21.04 (Hirsute Hippo) the software and all dependencies can be installed simply with: :: $ apt install libapache2-mod-tile renderd These packages for **Debian** and **Ubuntu** are being maintained by the `Debian GIS Team `__ in the respective `repository `__. Compilation ----------- You may want to compile this software yourself. Either for developing on it or when using it on an operating system this is not being packaged for. We prepared instructions for you on how to build the software on the following distributions: * `Arch Linux `__ * `CentOS Stream `__ * `Debian `__ * `Fedora `__ * `FreeBSD `__ * `macOS `__ * `openSUSE `__ * `Ubuntu `__ Configuration ------------- After you either installed the software packages or compiled the software yourself, you can continue with the configuration. For your convenience example configuration files are distributed with the software packages and located in the ``etc`` directory of this repository. A very basic example-map and data can be found in the ``utils/example-map`` directory. For a simple test copy it over to ``/usr/share/renderd/example-map``: :: $ sudo mkdir -p /usr/share/renderd $ sudo cp -av utils/example-map /usr/share/renderd/ Copy the apache configuration file to its place, too: :: $ sudo cp -av etc/apache2/renderd-example-map.conf /etc/apache2/sites-available/renderd-example-map.conf Add map configurations for example-map to ``/etc/renderd.conf``: :: $ printf ' [example-map] URI=/tiles/renderd-example XML=/usr/share/renderd/example-map/mapnik.xml [example-map-jpg] TYPE=jpg image/jpeg jpeg URI=/tiles/renderd-example-jpg XML=/usr/share/renderd/example-map/mapnik.xml [example-map-png256] TYPE=png image/png png256 URI=/tiles/renderd-example-png256 XML=/usr/share/renderd/example-map/mapnik.xml [example-map-png32] TYPE=png image/png png32 URI=/tiles/renderd-example-png32 XML=/usr/share/renderd/example-map/mapnik.xml [example-map-webp] TYPE=webp image/webp webp URI=/tiles/renderd-example-webp XML=/usr/share/renderd/example-map/mapnik.xml ' | sudo tee -a /etc/renderd.conf Ensure the ``/run/renderd`` directory exists: :: $ sudo mkdir -p /run/renderd Start the rendering daemon: :: $ sudo renderd Enable the apache module and site: :: $ sudo a2enmod tile $ sudo a2ensite renderd-example-map Restart apache: :: $ sudo apache2ctl restart Now visit the renderd example map in your browser, e.g.: :: http://localhost/renderd-example-map Or try loading a single tile, e.g: :: http://localhost:8081/tiles/renderd-example/0/0/0.png *Note: the above commands and paths may differ based on your OS/distribution.* You may edit ``/etc/renderd.conf`` to indicate the location of different mapnik style sheets (up to ten) and the endpoints you wish to use to access it. It is recommended to checkout `switch2osm `__ for nice tutorials on how to set up a full tile server like on `OpenStreetMap.org `__, using this software together with a `PostgreSQL `__ database and data from OpenStreetMap. Details about ``renderd``: Tile rendering ----------------------------------------- The rendering is implemented in a multithreaded process called ``renderd`` which opens either a unix or tcp socket and listens for requests to render tiles. It uses Mapnik to render tiles using the rendering rules defined in the configuration file ``/etc/renderd.conf``. Its configuration also allows to specify the number of rendering threads. The render daemon implements a queuing mechanism with multiple priority levels to provide an as up-to-date viewing experience given the available rendering resources. The highest priority is for on the fly rendering of tiles not yet in the tile cache, two priority levels for re-rendering out of date tiles on the fly and two background batch rendering queues. The on the fly rendering queues are limited to a short 32 metatile size to minimize latency. The size of the main background queue is determined at compile time, see: ``render_config.h`` Details about ``mod_tile``: Tile serving ---------------------------------------- An Apache module called ``mod_tile`` enhances the regular Apache file serving mechanisms to provide: 1) When tiles have expired it requests the rendering daemon to render (or re-render) the tile. 2) Remapping of the file path to the hashed layout. 3) Prioritizes rendering requests depending on the available resources on the server and how out of date they are. 4) Use tile storage other than a plain posix file system. e.g it can store tiles in a ceph object store, or proxy them from another tile server. 5) Tile expiry. It estimates when the tile is next likely to be rendered and adds the appropriate HTTP cache expiry headers. This is a configurable heuristic. To avoid problems with directories becoming too large and to avoid too many tiny files. They store the rendered tiles in "meta tiles" in a special hashed directory structure. These combine 8x8 actual tiles into a single metatile file. This is a more efficient use of disk space and inodes. The metatiles are then stored in the following directory structure: ``/[base_dir]/[TileSetName]/[Z]/[xxxxyyyy]/[xxxxyyyy]/[xxxxyyyy]/[xxxxyyyy]/[xxxxyyyy].meta`` Where ``base_dir`` is a configurable base path for all tiles. ``TileSetName`` is the name of the style sheet rendered. ``Z`` is the zoom level. ``[xxxxyyyy]`` is an 8 bit number, with the first 4 bits taken from the x coordinate and the second 4 bits taken from the y coordinate. This attempts to cluster 16x16 square of tiles together into a single sub directory for more efficient access patterns. Apache serves the files as if they were present under ``/[TileSetName]/Z/X/Y.png`` with the path being converted automatically. Notes about performance ----------------------- ``mod_tile`` is designed for high performance tile serving. If the underlying disk system allows it, it can easily provide > 10k tiles/s on a single serve. Rendering performance is mostly dependent on mapnik and postgis performance, however ``renderd`` tries to make sure it uses underlying hardware as efficiently as possible and scales well on multi core systems. ``renderd`` also provides built-in features to scale to multi server rendering set-ups. Copyright and copyleft ---------------------- Copyright (c) 2007 - 2023 by mod_tile contributors (see `AUTHORS <./AUTHORS>`__) This program 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 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. See the `COPYING <./COPYING>`__ for the full license text. mod_tile-0.8.0/autogen.sh000077500000000000000000000000311474064163400153170ustar00rootroot00000000000000#!/bin/sh autoreconf -vfimod_tile-0.8.0/cmake/000077500000000000000000000000001474064163400144045ustar00rootroot00000000000000mod_tile-0.8.0/cmake/FindAPR.cmake000066400000000000000000000024621474064163400166350ustar00rootroot00000000000000# - Find APR # Find the APR includes and libraries. # This module defines: # APR_FOUND # APR_INCLUDE_DIRS # APR_LIBRARIES find_package(PkgConfig QUIET) pkg_check_modules(APR QUIET apr-1) find_path(APR_INCLUDE_DIR NAMES apr.h PATHS ${APR_INCLUDE_DIRS} PATH_SUFFIXES apr-1 ) if((NOT APR_INCLUDE_DIRS) AND (APR_INCLUDE_DIR)) set(APR_INCLUDE_DIRS ${APR_INCLUDE_DIR}) elseif(APR_INCLUDE_DIRS AND APR_INCLUDE_DIR) list(APPEND APR_INCLUDE_DIRS ${APR_INCLUDE_DIR}) endif() find_library(APR_LIBRARY NAMES ${APR_LIBRARIES} apr-1 ) if((NOT APR_LIBRARIES) AND (APR_LIBRARY)) set(APR_LIBRARIES ${APR_LIBRARY}) elseif(APR_LIBRARIES AND APR_LIBRARY) list(APPEND APR_LIBRARIES ${APR_LIBRARY}) endif() message(VERBOSE "APR_INCLUDE_DIRS=${APR_INCLUDE_DIRS}") message(VERBOSE "APR_INCLUDE_DIR=${APR_INCLUDE_DIR}") message(VERBOSE "APR_LIBRARIES=${APR_LIBRARIES}") message(VERBOSE "APR_LIBRARY=${APR_LIBRARY}") if((NOT APR_FOUND) AND (APR_INCLUDE_DIRS) AND (APR_LIBRARIES)) set(APR_FOUND True) endif() include(FindPackageHandleStandardArgs) if(APR_FOUND) find_package_handle_standard_args(APR REQUIRED_VARS APR_FOUND APR_INCLUDE_DIRS APR_LIBRARIES VERSION_VAR APR_VERSION ) else() find_package_handle_standard_args(APR REQUIRED_VARS APR_FOUND ) endif() mark_as_advanced(APR_INCLUDE_DIR APR_LIBRARY) mod_tile-0.8.0/cmake/FindCAIRO.cmake000066400000000000000000000026241474064163400170500ustar00rootroot00000000000000# - Find CAIRO # Find the CAIRO includes and libraries. # This module defines: # CAIRO_FOUND # CAIRO_INCLUDE_DIRS # CAIRO_LIBRARIES find_package(PkgConfig QUIET) pkg_check_modules(CAIRO QUIET cairo) find_path(CAIRO_INCLUDE_DIR NAMES cairo.h PATHS ${CAIRO_INCLUDE_DIRS} PATH_SUFFIXES cairo ) if((NOT CAIRO_INCLUDE_DIRS) AND (CAIRO_INCLUDE_DIR)) set(CAIRO_INCLUDE_DIRS ${CAIRO_INCLUDE_DIR}) elseif(CAIRO_INCLUDE_DIRS AND CAIRO_INCLUDE_DIR) list(APPEND CAIRO_INCLUDE_DIRS ${CAIRO_INCLUDE_DIR}) endif() find_library(CAIRO_LIBRARY NAMES ${CAIRO_LIBRARIES} cairo ) if((NOT CAIRO_LIBRARIES) AND (CAIRO_LIBRARY)) set(CAIRO_LIBRARIES ${CAIRO_LIBRARY}) elseif(CAIRO_LIBRARIES AND CAIRO_LIBRARY) list(APPEND CAIRO_LIBRARIES ${CAIRO_LIBRARY}) endif() message(VERBOSE "CAIRO_INCLUDE_DIRS=${CAIRO_INCLUDE_DIRS}") message(VERBOSE "CAIRO_INCLUDE_DIR=${CAIRO_INCLUDE_DIR}") message(VERBOSE "CAIRO_LIBRARIES=${CAIRO_LIBRARIES}") message(VERBOSE "CAIRO_LIBRARY=${CAIRO_LIBRARY}") if((NOT CAIRO_FOUND) AND (CAIRO_INCLUDE_DIRS) AND (CAIRO_LIBRARIES)) set(CAIRO_FOUND True) endif() include(FindPackageHandleStandardArgs) if(CAIRO_FOUND) find_package_handle_standard_args(CAIRO REQUIRED_VARS CAIRO_FOUND CAIRO_INCLUDE_DIRS CAIRO_LIBRARIES VERSION_VAR CAIRO_VERSION ) else() find_package_handle_standard_args(CAIRO REQUIRED_VARS CAIRO_FOUND ) endif() mark_as_advanced(CAIRO_INCLUDE_DIR CAIRO_LIBRARY) mod_tile-0.8.0/cmake/FindGLIB.cmake000066400000000000000000000025541474064163400167320ustar00rootroot00000000000000# - Find GLIB # Find the GLIB includes and libraries. # This module defines: # GLIB_FOUND # GLIB_INCLUDE_DIRS # GLIB_LIBRARIES find_package(PkgConfig QUIET) pkg_check_modules(GLIB QUIET glib-2.0) find_path(GLIB_INCLUDE_DIR NAMES glib.h PATHS ${GLIB_INCLUDE_DIRS} PATH_SUFFIXES glib-2.0 ) if((NOT GLIB_INCLUDE_DIRS) AND (GLIB_INCLUDE_DIR)) set(GLIB_INCLUDE_DIRS ${GLIB_INCLUDE_DIR}) elseif(GLIB_INCLUDE_DIRS AND GLIB_INCLUDE_DIR) list(APPEND GLIB_INCLUDE_DIRS ${GLIB_INCLUDE_DIR}) endif() find_library(GLIB_LIBRARY NAMES ${GLIB_LIBRARIES} glib-2.0 ) if((NOT GLIB_LIBRARIES) AND (GLIB_LIBRARY)) set(GLIB_LIBRARIES ${GLIB_LIBRARY}) elseif(GLIB_LIBRARIES AND GLIB_LIBRARY) list(APPEND GLIB_LIBRARIES ${GLIB_LIBRARY}) endif() message(VERBOSE "GLIB_INCLUDE_DIRS=${GLIB_INCLUDE_DIRS}") message(VERBOSE "GLIB_INCLUDE_DIR=${GLIB_INCLUDE_DIR}") message(VERBOSE "GLIB_LIBRARIES=${GLIB_LIBRARIES}") message(VERBOSE "GLIB_LIBRARY=${GLIB_LIBRARY}") if((NOT GLIB_FOUND) AND (GLIB_INCLUDE_DIRS) AND (GLIB_LIBRARIES)) set(GLIB_FOUND True) endif() include(FindPackageHandleStandardArgs) if(GLIB_FOUND) find_package_handle_standard_args(GLIB REQUIRED_VARS GLIB_FOUND GLIB_INCLUDE_DIRS GLIB_LIBRARIES VERSION_VAR GLIB_VERSION ) else() find_package_handle_standard_args(GLIB REQUIRED_VARS GLIB_FOUND ) endif() mark_as_advanced(GLIB_INCLUDE_DIR GLIB_LIBRARY) mod_tile-0.8.0/cmake/FindHTTPD.cmake000066400000000000000000000032111474064163400170670ustar00rootroot00000000000000# - Find HTTPD # Find the HTTPD includes and libraries. # This module defines: # HTTPD_FOUND # HTTPD_INCLUDE_DIRS find_package(PkgConfig QUIET) pkg_check_modules(HTTPD QUIET httpd) find_path(HTTPD_INCLUDE_DIR NAMES httpd.h PATHS ${HTTPD_INCLUDE_DIRS} PATH_SUFFIXES apache2 apache24 httpd ) if((NOT HTTPD_INCLUDE_DIRS) AND (HTTPD_INCLUDE_DIR)) set(HTTPD_INCLUDE_DIRS ${HTTPD_INCLUDE_DIR}) elseif(HTTPD_INCLUDE_DIRS AND HTTPD_INCLUDE_DIR) list(APPEND HTTPD_INCLUDE_DIRS ${HTTPD_INCLUDE_DIR}) endif() message(VERBOSE "HTTPD_INCLUDE_DIRS=${HTTPD_INCLUDE_DIRS}") message(VERBOSE "HTTPD_INCLUDE_DIR=${HTTPD_INCLUDE_DIR}") if((NOT HTTPD_FOUND) AND (HTTPD_INCLUDE_DIRS)) set(HTTPD_FOUND True) endif() if((NOT HTTPD_VERSION) AND (HTTPD_FOUND)) file(STRINGS "${HTTPD_INCLUDE_DIR}/ap_release.h" _contents REGEX "#define AP_SERVER_[A-Z]+_NUMBER[ \t]+") if(_contents) string(REGEX REPLACE ".*#define AP_SERVER_MAJORVERSION_NUMBER[ \t]+([0-9]+).*" "\\1" HTTPD_MAJOR_VERSION "${_contents}") string(REGEX REPLACE ".*#define AP_SERVER_MINORVERSION_NUMBER[ \t]+([0-9]+).*" "\\1" HTTPD_MINOR_VERSION "${_contents}") string(REGEX REPLACE ".*#define AP_SERVER_PATCHLEVEL_NUMBER[ \t]+([0-9]+).*" "\\1" HTTPD_PATCH_VERSION "${_contents}") set(HTTPD_VERSION ${HTTPD_MAJOR_VERSION}.${HTTPD_MINOR_VERSION}.${HTTPD_PATCH_VERSION}) endif() endif() include(FindPackageHandleStandardArgs) if(HTTPD_FOUND) find_package_handle_standard_args(HTTPD REQUIRED_VARS HTTPD_FOUND HTTPD_INCLUDE_DIRS VERSION_VAR HTTPD_VERSION ) else() find_package_handle_standard_args(HTTPD REQUIRED_VARS HTTPD_FOUND ) endif() mark_as_advanced(HTTPD_INCLUDE_DIR) mod_tile-0.8.0/cmake/FindINIPARSER.cmake000066400000000000000000000031021474064163400175370ustar00rootroot00000000000000# - Find INIPARSER # Find the INIPARSER includes and libraries. # This module defines: # INIPARSER_FOUND # INIPARSER_INCLUDE_DIRS # INIPARSER_LIBRARIES find_package(PkgConfig QUIET) pkg_check_modules(INIPARSER QUIET iniparser) find_path(INIPARSER_INCLUDE_DIR NAMES iniparser.h PATHS ${INIPARSER_INCLUDE_DIRS} PATH_SUFFIXES iniparser ) if((NOT INIPARSER_INCLUDE_DIRS) AND (INIPARSER_INCLUDE_DIR)) set(INIPARSER_INCLUDE_DIRS ${INIPARSER_INCLUDE_DIR}) elseif(INIPARSER_INCLUDE_DIRS AND INIPARSER_INCLUDE_DIR) list(APPEND INIPARSER_INCLUDE_DIRS ${INIPARSER_INCLUDE_DIR}) endif() find_library(INIPARSER_LIBRARY NAMES ${INIPARSER_LIBRARIES} iniparser ) if((NOT INIPARSER_LIBRARIES) AND (INIPARSER_LIBRARY)) set(INIPARSER_LIBRARIES ${INIPARSER_LIBRARY}) elseif(INIPARSER_LIBRARIES AND INIPARSER_LIBRARY) list(APPEND INIPARSER_LIBRARIES ${INIPARSER_LIBRARY}) endif() message(VERBOSE "INIPARSER_INCLUDE_DIRS=${INIPARSER_INCLUDE_DIRS}") message(VERBOSE "INIPARSER_INCLUDE_DIR=${INIPARSER_INCLUDE_DIR}") message(VERBOSE "INIPARSER_LIBRARIES=${INIPARSER_LIBRARIES}") message(VERBOSE "INIPARSER_LIBRARY=${INIPARSER_LIBRARY}") if((NOT INIPARSER_FOUND) AND (INIPARSER_INCLUDE_DIRS) AND (INIPARSER_LIBRARIES)) set(INIPARSER_FOUND True) endif() include(FindPackageHandleStandardArgs) if(INIPARSER_FOUND) find_package_handle_standard_args(INIPARSER REQUIRED_VARS INIPARSER_FOUND INIPARSER_INCLUDE_DIRS INIPARSER_LIBRARIES ) else() find_package_handle_standard_args(INIPARSER REQUIRED_VARS INIPARSER_FOUND ) endif() mark_as_advanced(INIPARSER_INCLUDE_DIR INIPARSER_LIBRARY) mod_tile-0.8.0/cmake/FindLIBMAPNIK.cmake000066400000000000000000000043651474064163400175250ustar00rootroot00000000000000# - Find LIBMAPNIK # Find the LIBMAPNIK includes and libraries. # This module defines: # LIBMAPNIK_FOUND # LIBMAPNIK_INCLUDE_DIRS # LIBMAPNIK_LIBRARIES find_package(PkgConfig QUIET) pkg_check_modules(LIBMAPNIK QUIET libmapnik) find_path(LIBMAPNIK_INCLUDE_DIR NAMES version.hpp PATHS ${LIBMAPNIK_INCLUDE_DIRS} PATH_SUFFIXES mapnik ) if((NOT LIBMAPNIK_INCLUDE_DIRS) AND (LIBMAPNIK_INCLUDE_DIR)) set(LIBMAPNIK_INCLUDE_DIRS ${LIBMAPNIK_INCLUDE_DIR}) elseif(LIBMAPNIK_INCLUDE_DIRS AND LIBMAPNIK_INCLUDE_DIR) list(APPEND LIBMAPNIK_INCLUDE_DIRS ${LIBMAPNIK_INCLUDE_DIR}) endif() find_library(LIBMAPNIK_LIBRARY NAMES ${LIBMAPNIK_LIBRARIES} mapnik ) if((NOT LIBMAPNIK_LIBRARIES) AND (LIBMAPNIK_LIBRARY)) set(LIBMAPNIK_LIBRARIES ${LIBMAPNIK_LIBRARY}) elseif(LIBMAPNIK_LIBRARIES AND LIBMAPNIK_LIBRARY) list(APPEND LIBMAPNIK_LIBRARIES ${LIBMAPNIK_LIBRARY}) endif() message(VERBOSE "LIBMAPNIK_INCLUDE_DIRS=${LIBMAPNIK_INCLUDE_DIRS}") message(VERBOSE "LIBMAPNIK_INCLUDE_DIR=${LIBMAPNIK_INCLUDE_DIR}") message(VERBOSE "LIBMAPNIK_LIBRARIES=${LIBMAPNIK_LIBRARIES}") message(VERBOSE "LIBMAPNIK_LIBRARY=${LIBMAPNIK_LIBRARY}") if((NOT LIBMAPNIK_FOUND) AND (LIBMAPNIK_INCLUDE_DIRS) AND (LIBMAPNIK_LIBRARIES)) set(LIBMAPNIK_FOUND True) endif() if((NOT LIBMAPNIK_VERSION) AND (LIBMAPNIK_FOUND)) file(STRINGS "${LIBMAPNIK_INCLUDE_DIR}/version.hpp" _contents REGEX "#define MAPNIK_[A-Z]+_VERSION[ \t]+") if(_contents) string(REGEX REPLACE ".*#define MAPNIK_MAJOR_VERSION[ \t]+([0-9]+).*" "\\1" LIBMAPNIK_MAJOR_VERSION "${_contents}") string(REGEX REPLACE ".*#define MAPNIK_MINOR_VERSION[ \t]+([0-9]+).*" "\\1" LIBMAPNIK_MINOR_VERSION "${_contents}") string(REGEX REPLACE ".*#define MAPNIK_PATCH_VERSION[ \t]+([0-9]+).*" "\\1" LIBMAPNIK_PATCH_VERSION "${_contents}") set(LIBMAPNIK_VERSION ${LIBMAPNIK_MAJOR_VERSION}.${LIBMAPNIK_MINOR_VERSION}.${LIBMAPNIK_PATCH_VERSION}) endif() endif() include(FindPackageHandleStandardArgs) if(LIBMAPNIK_FOUND) find_package_handle_standard_args(LIBMAPNIK REQUIRED_VARS LIBMAPNIK_FOUND LIBMAPNIK_INCLUDE_DIRS LIBMAPNIK_LIBRARIES VERSION_VAR LIBMAPNIK_VERSION ) else() find_package_handle_standard_args(LIBMAPNIK REQUIRED_VARS LIBMAPNIK_FOUND ) endif() mark_as_advanced(LIBMAPNIK_INCLUDE_DIR LIBMAPNIK_LIBRARY) mod_tile-0.8.0/cmake/FindLIBMEMCACHED.cmake000066400000000000000000000033721474064163400200110ustar00rootroot00000000000000# - Find LIBMEMCACHED # Find the LIBMEMCACHED includes and libraries. # This module defines: # LIBMEMCACHED_FOUND # LIBMEMCACHED_INCLUDE_DIRS # LIBMEMCACHED_LIBRARIES find_package(PkgConfig QUIET) pkg_check_modules(LIBMEMCACHED QUIET libmemcached) find_path(LIBMEMCACHED_INCLUDE_DIR NAMES memcached.h PATHS ${LIBMEMCACHED_INCLUDE_DIRS} PATH_SUFFIXES libmemcached ) if((NOT LIBMEMCACHED_INCLUDE_DIRS) AND (LIBMEMCACHED_INCLUDE_DIR)) set(LIBMEMCACHED_INCLUDE_DIRS ${LIBMEMCACHED_INCLUDE_DIR}) elseif(LIBMEMCACHED_INCLUDE_DIRS AND LIBMEMCACHED_INCLUDE_DIR) list(APPEND LIBMEMCACHED_INCLUDE_DIRS ${LIBMEMCACHED_INCLUDE_DIR}) endif() find_library(LIBMEMCACHED_LIBRARY NAMES ${LIBMEMCACHED_LIBRARIES} memcached ) if((NOT LIBMEMCACHED_LIBRARIES) AND (LIBMEMCACHED_LIBRARY)) set(LIBMEMCACHED_LIBRARIES ${LIBMEMCACHED_LIBRARY}) elseif(LIBMEMCACHED_LIBRARIES AND LIBMEMCACHED_LIBRARY) list(APPEND LIBMEMCACHED_LIBRARIES ${LIBMEMCACHED_LIBRARY}) endif() message(VERBOSE "LIBMEMCACHED_INCLUDE_DIRS=${LIBMEMCACHED_INCLUDE_DIRS}") message(VERBOSE "LIBMEMCACHED_INCLUDE_DIR=${LIBMEMCACHED_INCLUDE_DIR}") message(VERBOSE "LIBMEMCACHED_LIBRARIES=${LIBMEMCACHED_LIBRARIES}") message(VERBOSE "LIBMEMCACHED_LIBRARY=${LIBMEMCACHED_LIBRARY}") if((NOT LIBMEMCACHED_FOUND) AND (LIBMEMCACHED_INCLUDE_DIRS) AND (LIBMEMCACHED_LIBRARIES)) set(LIBMEMCACHED_FOUND True) endif() include(FindPackageHandleStandardArgs) if(LIBMEMCACHED_FOUND) find_package_handle_standard_args(LIBMEMCACHED REQUIRED_VARS LIBMEMCACHED_FOUND LIBMEMCACHED_INCLUDE_DIRS LIBMEMCACHED_LIBRARIES VERSION_VAR LIBMEMCACHED_VERSION ) else() find_package_handle_standard_args(LIBMEMCACHED REQUIRED_VARS LIBMEMCACHED_FOUND ) endif() mark_as_advanced(LIBMEMCACHED_INCLUDE_DIR LIBMEMCACHED_LIBRARY) mod_tile-0.8.0/cmake/FindLIBRADOS.cmake000066400000000000000000000042531474064163400174120ustar00rootroot00000000000000# - Find LIBRADOS # Find the LIBRADOS includes and libraries. # This module defines: # LIBRADOS_FOUND # LIBRADOS_INCLUDE_DIRS # LIBRADOS_LIBRARIES find_package(PkgConfig QUIET) pkg_check_modules(LIBRADOS QUIET rados) find_path(LIBRADOS_INCLUDE_DIR NAMES librados.h PATHS ${LIBRADOS_INCLUDE_DIRS} PATH_SUFFIXES rados ) if((NOT LIBRADOS_INCLUDE_DIRS) AND (LIBRADOS_INCLUDE_DIR)) set(LIBRADOS_INCLUDE_DIRS ${LIBRADOS_INCLUDE_DIR}) elseif(LIBRADOS_INCLUDE_DIRS AND LIBRADOS_INCLUDE_DIR) list(APPEND LIBRADOS_INCLUDE_DIRS ${LIBRADOS_INCLUDE_DIR}) endif() find_library(LIBRADOS_LIBRARY NAMES ${LIBRADOS_LIBRARIES} rados ) if((NOT LIBRADOS_LIBRARIES) AND (LIBRADOS_LIBRARY)) set(LIBRADOS_LIBRARIES ${LIBRADOS_LIBRARY}) elseif(LIBRADOS_LIBRARIES AND LIBRADOS_LIBRARY) list(APPEND LIBRADOS_LIBRARIES ${LIBRADOS_LIBRARY}) endif() message(VERBOSE "LIBRADOS_INCLUDE_DIRS=${LIBRADOS_INCLUDE_DIRS}") message(VERBOSE "LIBRADOS_INCLUDE_DIR=${LIBRADOS_INCLUDE_DIR}") message(VERBOSE "LIBRADOS_LIBRARIES=${LIBRADOS_LIBRARIES}") message(VERBOSE "LIBRADOS_LIBRARY=${LIBRADOS_LIBRARY}") if((NOT LIBRADOS_FOUND) AND (LIBRADOS_INCLUDE_DIRS) AND (LIBRADOS_LIBRARIES)) set(LIBRADOS_FOUND True) endif() if((NOT LIBRADOS_VERSION) AND (LIBRADOS_FOUND)) file(STRINGS "${LIBRADOS_INCLUDE_DIR}/librados.h" _contents REGEX "#define LIBRADOS_VER_[A-Z]+[ \t]+") if(_contents) string(REGEX REPLACE ".*#define LIBRADOS_VER_MAJOR[ \t]+([0-9]+).*" "\\1" LIBRADOS_MAJOR_VERSION "${_contents}") string(REGEX REPLACE ".*#define LIBRADOS_VER_MINOR[ \t]+([0-9]+).*" "\\1" LIBRADOS_MINOR_VERSION "${_contents}") string(REGEX REPLACE ".*#define LIBRADOS_VER_EXTRA[ \t]+([0-9]+).*" "\\1" LIBRADOS_EXTRA_VERSION "${_contents}") set(LIBRADOS_VERSION ${LIBRADOS_MAJOR_VERSION}.${LIBRADOS_MINOR_VERSION}.${LIBRADOS_EXTRA_VERSION}) endif() endif() include(FindPackageHandleStandardArgs) if(LIBRADOS_FOUND) find_package_handle_standard_args(LIBRADOS REQUIRED_VARS LIBRADOS_FOUND LIBRADOS_INCLUDE_DIRS LIBRADOS_LIBRARIES VERSION_VAR LIBRADOS_VERSION ) else() find_package_handle_standard_args(LIBRADOS REQUIRED_VARS LIBRADOS_FOUND ) endif() mark_as_advanced(LIBRADOS_INCLUDE_DIR LIBRADOS_LIBRARY) mod_tile-0.8.0/configure.ac000066400000000000000000000052211474064163400156120ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. # Define mod_tile version number m4_define([mod_tile_version], [0.8.0]) AC_PREREQ([2.61]) AX_CONFIG_NICE AC_INIT([mod_tile], [mod_tile_version], [http://trac.openstreetmap.org]) AM_INIT_AUTOMAKE([subdir-objects]) LT_INIT AC_CONFIG_SRCDIR([src/mod_tile.c]) AC_CONFIG_HEADERS([includes/config.h]) AC_CONFIG_MACRO_DIR([m4]) # Checks for programs. AC_PROG_CC AC_PROG_CC_C99 AC_PROG_CXX AC_PROG_RANLIB dnl Find C++ compiler AC_CHECK_PROG(HAVE_CXX, $CXX, yes, no) if test "$HAVE_CXX" = "no" then AC_MSG_ERROR([Could not find a c++ compiler]); fi # Checks for libraries. PKG_CHECK_MODULES([GLIB], [glib-2.0]) # Checks for header files. AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/socket.h sys/time.h syslog.h unistd.h utime.h paths.h sys/cdefs.h sys/loadavg.h iniparser.h iniparser/iniparser.h]) # Checks for typedefs, structures, and compiler characteristics. AC_C_INLINE AC_TYPE_MODE_T AC_TYPE_SIZE_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT64_T # Checks for library functions. AC_FUNC_MALLOC AC_FUNC_MKTIME AX_PTHREAD(,[AC_MSG_ERROR([no])]) AC_SEARCH_LIBS(socket, socket) AC_SEARCH_LIBS(inet_ntoa, nsl) AC_SEARCH_LIBS(gethostbyname, resolv nsl) AC_SEARCH_LIBS(pow,m) AC_SEARCH_LIBS(clock_gettime,[rt posix4]) PKG_CHECK_MODULES([MAPNIK], [libmapnik >= 4], [ AC_DEFINE([HAVE_MAPNIK], [1]) AC_SUBST([MAPNIK_LDFLAGS], [${MAPNIK_LIBS}]) ], [AX_LIB_MAPNIK] ) AX_ENABLE_LIBMEMCACHED LIBCURL_CHECK_CONFIG AC_CHECK_LIB(rados, rados_version, [ AC_DEFINE([HAVE_LIBRADOS], [1], [Have found librados]) LIBRADOS_LDFLAGS='-lrados' AC_SUBST(LIBRADOS_LDFLAGS) ][]) AC_CHECK_LIB(iniparser, iniparser_load, [ INIPARSER_LDFLAGS='-liniparser' AC_SUBST(INIPARSER_LDFLAGS) ], [AC_MSG_ERROR([Unable to find libiniparser])]) AC_CHECK_FUNCS([bzero gethostbyname gettimeofday inet_ntoa memset mkdir pow select socket strchr strdup strerror strrchr strstr strtol strtoul utime],[],[AC_MSG_ERROR([One of the required functions was not found])]) AC_CHECK_FUNCS([daemon getloadavg],[],[]) AC_ARG_WITH(apxs, [ --with-apxs=PATH path to Apache apxs], [ if test "$withval" = "yes"; then AC_CHECK_PROGS(APXS, apxs apxs2 /opt/local/apache2/bin/apxs, reject) else APXS=$withval AC_SUBST(APXS) fi ], [ AC_CHECK_PROGS(APXS, apxs apxs2 /opt/local/apache2/bin/apxs, reject) ]) if test "$APXS" = "reject"; then AC_MSG_ERROR([Could not find apxs on the path.]) fi AC_CONFIG_FILES(Makefile) AC_OUTPUT mod_tile-0.8.0/docker/000077500000000000000000000000001474064163400145735ustar00rootroot00000000000000mod_tile-0.8.0/docker/.env000066400000000000000000000002671474064163400153710ustar00rootroot00000000000000COMPOSE_PROJECT_NAME=mod_tile DOWNLOAD_PBF=https://download.geofabrik.de/europe/germany/nordrhein-westfalen/koeln-regbez-latest.osm.pbf PGDATABASE=gis PGHOST=postgres PGUSER=renderer mod_tile-0.8.0/docker/README.md000066400000000000000000000040161474064163400160530ustar00rootroot00000000000000# Docker-based building & testing For your convenience, we have provided a Docker-based building and testing method to help get started with development & testing. ### The following distributions are currently supported: - archlinux _(Arch Linux)_ [[Dockerfile](/docker/archlinux/Dockerfile)] - centos-stream-9 _(CentOS Stream 9)_ [[Dockerfile](/docker/centos/stream/Dockerfile)] - debian-10 _(Debian 10)_ [[Dockerfile](/docker/debian/Dockerfile)] - debian-11 _(Debian 11)_ [[Dockerfile](/docker/debian/Dockerfile)] - debian-12 _(Debian 12)_ [[Dockerfile](/docker/debian/Dockerfile)] - debian-unstable _(Debian Unstable)_ [[Dockerfile](/docker/debian/Dockerfile)] - fedora-34 _(Fedora 34)_ [[Dockerfile](/docker/fedora/Dockerfile)] - fedora-35 _(Fedora 35)_ [[Dockerfile](/docker/fedora/Dockerfile)] - fedora-36 _(Fedora 36)_ [[Dockerfile](/docker/fedora/Dockerfile)] - fedora-37 _(Fedora 37)_ [[Dockerfile](/docker/fedora/Dockerfile)] - fedora-38 _(Fedora 38)_ [[Dockerfile](/docker/fedora/Dockerfile)] - fedora-39 _(Fedora 39)_ [[Dockerfile](/docker/fedora/Dockerfile)] - fedora-40 _(Fedora 40)_ [[Dockerfile](/docker/fedora/Dockerfile)] - fedora-rawhide _(Fedora Rawhide)_ [[Dockerfile](/docker/fedora/Dockerfile)] - opensuse-leap-15 _(openSUSE Leap 15)_ [[Dockerfile](/docker/opensuse/Dockerfile)] - opensuse-tumbleweed _(openSUSE Tumbleweed)_ [[Dockerfile](/docker/opensuse/Dockerfile)] - ubuntu-20.04 _(Ubuntu 20.04)_ [[Dockerfile](/docker/ubuntu/Dockerfile)] - ubuntu-22.04 _(Ubuntu 22.04)_ [[Dockerfile](/docker/ubuntu/Dockerfile)] - ubuntu-24.04 _(Ubuntu 24.04)_ [[Dockerfile](/docker/ubuntu/Dockerfile)] - ubuntu-devel _(Ubuntu Devel)_ [[Dockerfile](/docker/ubuntu/Dockerfile)] ### Requirements: - [Docker](https://docs.docker.com/get-docker/) - [Docker Compose](https://github.com/docker/compose) ### Usage: ```shell $ # Use any "{distribution}" value from the list above $ # I.E. docker-compose up --build ubuntu-devel $ cd docker $ docker-compose up --build {distribution} ``` Then you can visit: `http://localhost:8081/renderd-example-map` mod_tile-0.8.0/docker/archlinux/000077500000000000000000000000001474064163400165705ustar00rootroot00000000000000mod_tile-0.8.0/docker/archlinux/Dockerfile000066400000000000000000000076241474064163400205730ustar00rootroot00000000000000# hadolint global ignore=DL3025,DL3059 # Arguments ARG archlinux_version=latest ARG runner_additional_packages # Builder FROM archlinux:${archlinux_version} AS builder ## Arguments ARG archlinux_version ## Install builder dependencies RUN --mount=type=cache,sharing=locked,id=archlinux:${archlinux_version}-/var/cache/pacman/pkg,target=/var/cache/pacman/pkg \ --mount=type=cache,sharing=locked,id=archlinux:${archlinux_version}-/var/lib/pacman/sync,target=/var/lib/pacman/sync \ pacman --sync --refresh --sysupgrade --noconfirm \ apache \ apr \ boost \ cairo \ cmake \ curl \ extra-cmake-modules \ gcc \ git \ glib2 \ iniparser \ lcov \ libmemcached \ make \ mapnik \ memcached \ pkgconf ## Build, Test & Install `mod_tile` COPY . /tmp/mod_tile_src WORKDIR /tmp/mod_tile_build RUN CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)" && export CMAKE_BUILD_PARALLEL_LEVEL && \ cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_CXX_STANDARD:STRING=17 \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON && \ cmake --build . RUN CTEST_PARALLEL_LEVEL="$(nproc)" && export CTEST_PARALLEL_LEVEL && \ ctest --output-on-failure RUN export DESTDIR=/tmp/mod_tile && \ cmake --install . --strip # Runner FROM archlinux:${archlinux_version} AS runner ## Arguments ARG archlinux_version ARG runner_additional_packages ## Install runner dependencies RUN --mount=type=cache,sharing=locked,id=archlinux:${archlinux_version}-/var/cache/pacman/pkg,target=/var/cache/pacman/pkg \ --mount=type=cache,sharing=locked,id=archlinux:${archlinux_version}-/var/lib/pacman/sync,target=/var/lib/pacman/sync \ pacman --sync --refresh --sysupgrade --noconfirm ${runner_additional_packages} \ apache \ cairo \ curl \ glib2 \ iniparser \ libmemcached \ mapnik \ memcached \ # GDAL optional dependencies arrow \ cfitsio \ hdf5 \ libheif \ libjxl \ mariadb-libs \ netcdf \ openexr \ openjpeg2 \ podofo \ poppler ## Copy files from builder(s) ### mod_tile COPY --from=builder /tmp/mod_tile / COPY --from=builder \ /tmp/mod_tile_src/utils/example-map \ /usr/share/renderd/example-map COPY --from=builder \ /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf \ /etc/httpd/conf/extra/httpd-tile-renderd-example-map.conf ## Add configuration RUN printf '\n[example-map]\nMAXZOOM=20\nMINZOOM=0\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-jpg]\nMAXZOOM=20\nMINZOOM=0\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png256]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png32]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-webp]\nMAXZOOM=20\nMINZOOM=0\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf ## Enable module & site RUN printf '\nInclude conf/extra/httpd-tile.conf\n' >> /etc/httpd/conf/httpd.conf && \ printf '\nInclude conf/extra/httpd-tile-renderd-example-map.conf\n' >> /etc/httpd/conf/httpd.conf ## Start services CMD httpd -e debug -k start; \ G_MESSAGES_DEBUG=${G_MESSAGES_DEBUG:-info} renderd --foreground mod_tile-0.8.0/docker/centos/000077500000000000000000000000001474064163400160665ustar00rootroot00000000000000mod_tile-0.8.0/docker/centos/stream/000077500000000000000000000000001474064163400173615ustar00rootroot00000000000000mod_tile-0.8.0/docker/centos/stream/Dockerfile000066400000000000000000000153421474064163400213600ustar00rootroot00000000000000# hadolint global ignore=DL3025,DL3040,DL3041,DL3059 # Arguments ARG centos_stream_version=9 ARG extra_repository=crb ARG mapnik_version=4.0.3 # Mapnik Builder FROM quay.io/centos/centos:stream${centos_stream_version} AS mapnik-builder ## Arguments ARG centos_stream_version ARG extra_repository ARG mapnik_version ## Install mapnik-builder dependencies RUN --mount=type=cache,sharing=locked,id=centos:stream${centos_stream_version}-/var/cache/dnf,target=/var/cache/dnf \ echo "install_weak_deps=False" >> /etc/dnf/dnf.conf && \ echo "keepcache=True" >> /etc/dnf/dnf.conf && \ dnf --assumeyes install "dnf-command(config-manager)" && \ dnf config-manager --save --setopt=${extra_repository}.enabled=1 && \ dnf --assumeyes install epel-release && \ dnf --assumeyes upgrade && \ dnf --assumeyes install \ boost-devel \ cairo-devel \ cmake \ freetype-devel \ gcc \ gcc-c++ \ gdal-devel \ git \ harfbuzz-devel \ libicu-devel \ libjpeg-devel \ libpng-devel \ libtiff-devel \ libwebp-devel \ libxml2-devel \ make \ postgresql-devel \ proj-devel \ sqlite-devel \ zlib-devel ## Download, Build & Install `Mapnik` WORKDIR /tmp/mapnik_src RUN --mount=type=cache,id=centos:stream${centos_stream_version}-mapnik-src:${mapnik_version},target=/tmp/mapnik_src \ if [ ! -f CMakeLists.txt ]; then \ git clone --branch v${mapnik_version} --depth 1 --jobs 8 --recurse-submodules https://github.com/mapnik/mapnik.git /tmp/mapnik_src; \ fi WORKDIR /tmp/mapnik_build RUN --mount=type=cache,id=centos:stream${centos_stream_version}-mapnik-src:${mapnik_version},target=/tmp/mapnik_src \ --mount=type=cache,id=centos:stream${centos_stream_version}-mapnik-build:${mapnik_version},target=/tmp/mapnik_build \ CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)" && export CMAKE_BUILD_PARALLEL_LEVEL && \ export DESTDIR=/tmp/mapnik && \ cmake -B . -S /tmp/mapnik_src \ -DBUILD_BENCHMARK:BOOL=OFF \ -DBUILD_DEMO_CPP:BOOL=OFF \ -DBUILD_DEMO_VIEWER:BOOL=OFF \ -DBUILD_TESTING:BOOL=OFF \ -DBUILD_UTILITY_GEOMETRY_TO_WKB:BOOL=OFF \ -DBUILD_UTILITY_MAPNIK_INDEX:BOOL=OFF \ -DBUILD_UTILITY_MAPNIK_RENDER:BOOL=OFF \ -DBUILD_UTILITY_OGRINDEX:BOOL=OFF \ -DBUILD_UTILITY_PGSQL2SQLITE:BOOL=OFF \ -DBUILD_UTILITY_SHAPEINDEX:BOOL=OFF \ -DBUILD_UTILITY_SVG2PNG:BOOL=OFF \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_PREFIX:PATH=/usr && \ cmake --build . && \ cmake --install . --strip # Builder FROM quay.io/centos/centos:stream${centos_stream_version} AS builder ## Arguments ARG centos_stream_version ARG extra_repository ## Install builder dependencies RUN --mount=type=cache,sharing=locked,id=centos:stream${centos_stream_version}-/var/cache/dnf,target=/var/cache/dnf \ echo "install_weak_deps=False" >> /etc/dnf/dnf.conf && \ echo "keepcache=True" >> /etc/dnf/dnf.conf && \ dnf --assumeyes install "dnf-command(config-manager)" && \ dnf config-manager --save --setopt=${extra_repository}.enabled=1 && \ dnf --assumeyes install epel-release && \ dnf --assumeyes upgrade && \ dnf --assumeyes install \ boost-devel \ cairo-devel \ cmake \ freetype-devel \ gcc \ gcc-c++ \ gdal \ glib2-devel \ harfbuzz-devel \ httpd-devel \ iniparser-devel \ libcurl-devel \ libicu-devel \ libjpeg-devel \ libmemcached-devel \ libpng-devel \ librados2-devel \ libtiff-devel \ libwebp-devel \ libxml2-devel \ make \ pkg-config \ procps \ proj-devel ## Copy files from builder(s) ### Mapnik COPY --from=mapnik-builder /tmp/mapnik / ## Build, Test & Install `mod_tile` COPY . /tmp/mod_tile_src WORKDIR /tmp/mod_tile_build RUN CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)" && export CMAKE_BUILD_PARALLEL_LEVEL && \ cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON && \ cmake --build . RUN CTEST_PARALLEL_LEVEL="$(nproc)" && export CTEST_PARALLEL_LEVEL && \ ctest --output-on-failure RUN export DESTDIR=/tmp/mod_tile && \ cmake --install . --strip # Runner FROM quay.io/centos/centos:stream${centos_stream_version} AS runner ## Arguments ARG centos_stream_version ARG extra_repository ## Install runner dependencies RUN --mount=type=cache,sharing=locked,id=centos:stream${centos_stream_version}-/var/cache/dnf,target=/var/cache/dnf \ echo "install_weak_deps=False" >> /etc/dnf/dnf.conf && \ echo "keepcache=True" >> /etc/dnf/dnf.conf && \ dnf --assumeyes install "dnf-command(config-manager)" && \ dnf config-manager --save --setopt=${extra_repository}.enabled=1 && \ dnf --assumeyes install epel-release && \ dnf --assumeyes upgrade && \ dnf --assumeyes install \ boost-regex \ cairo \ gdal \ harfbuzz \ httpd \ iniparser \ libicu \ libmemcached \ librados2 \ libtiff \ libwebp \ proj ## Copy files from builder(s) ### Mapnik COPY --from=mapnik-builder /tmp/mapnik / ### mod_tile COPY --from=builder /tmp/mod_tile / COPY --chown=apache:apache --from=builder \ /tmp/mod_tile_src/utils/example-map \ /usr/share/renderd/example-map COPY --from=builder \ /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf \ /etc/httpd/conf.d/renderd-example-map.conf ## Add configuration RUN printf '\n[example-map]\nMAXZOOM=20\nMINZOOM=0\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-jpg]\nMAXZOOM=20\nMINZOOM=0\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png256]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png32]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-webp]\nMAXZOOM=20\nMINZOOM=0\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf ## Start services CMD httpd -e debug -k start; \ G_MESSAGES_DEBUG=${G_MESSAGES_DEBUG:-info} renderd --foreground mod_tile-0.8.0/docker/centos/stream/Dockerfile.autotools000066400000000000000000000154401474064163400234070ustar00rootroot00000000000000# hadolint global ignore=DL3025,DL3040,DL3041,DL3059 # Arguments ARG centos_stream_version=9 ARG extra_repository=crb ARG mapnik_version=4.0.3 # Mapnik Builder FROM quay.io/centos/centos:stream${centos_stream_version} AS mapnik-builder ## Arguments ARG centos_stream_version ARG extra_repository ARG mapnik_version ## Install mapnik-builder dependencies RUN --mount=type=cache,sharing=locked,id=centos:stream${centos_stream_version}-/var/cache/dnf,target=/var/cache/dnf \ echo "install_weak_deps=False" >> /etc/dnf/dnf.conf && \ echo "keepcache=True" >> /etc/dnf/dnf.conf && \ dnf --assumeyes install "dnf-command(config-manager)" && \ dnf config-manager --save --setopt=${extra_repository}.enabled=1 && \ dnf --assumeyes install epel-release && \ dnf --assumeyes upgrade && \ dnf --assumeyes install \ boost-devel \ cairo-devel \ cmake \ freetype-devel \ gcc \ gcc-c++ \ gdal-devel \ git \ harfbuzz-devel \ libicu-devel \ libjpeg-devel \ libpng-devel \ libtiff-devel \ libwebp-devel \ libxml2-devel \ make \ postgresql-devel \ proj-devel \ sqlite-devel \ zlib-devel ## Download, Build & Install `Mapnik` WORKDIR /tmp/mapnik_src RUN --mount=type=cache,id=centos:stream${centos_stream_version}-mapnik-src:${mapnik_version},target=/tmp/mapnik_src \ if [ ! -f CMakeLists.txt ]; then \ git clone --branch v${mapnik_version} --depth 1 --jobs 8 --recurse-submodules https://github.com/mapnik/mapnik.git /tmp/mapnik_src; \ fi WORKDIR /tmp/mapnik_build RUN --mount=type=cache,id=centos:stream${centos_stream_version}-mapnik-src:${mapnik_version},target=/tmp/mapnik_src \ --mount=type=cache,id=centos:stream${centos_stream_version}-mapnik-build:${mapnik_version},target=/tmp/mapnik_build \ CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)" && export CMAKE_BUILD_PARALLEL_LEVEL && \ export DESTDIR=/tmp/mapnik && \ cmake -B . -S /tmp/mapnik_src \ -DBUILD_BENCHMARK:BOOL=OFF \ -DBUILD_DEMO_CPP:BOOL=OFF \ -DBUILD_DEMO_VIEWER:BOOL=OFF \ -DBUILD_TESTING:BOOL=OFF \ -DBUILD_UTILITY_GEOMETRY_TO_WKB:BOOL=OFF \ -DBUILD_UTILITY_MAPNIK_INDEX:BOOL=OFF \ -DBUILD_UTILITY_MAPNIK_RENDER:BOOL=OFF \ -DBUILD_UTILITY_OGRINDEX:BOOL=OFF \ -DBUILD_UTILITY_PGSQL2SQLITE:BOOL=OFF \ -DBUILD_UTILITY_SHAPEINDEX:BOOL=OFF \ -DBUILD_UTILITY_SVG2PNG:BOOL=OFF \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_PREFIX:PATH=/usr && \ cmake --build . && \ cmake --install . --strip # Builder FROM quay.io/centos/centos:stream${centos_stream_version} AS builder ## Arguments ARG centos_stream_version ARG extra_repository ## Install builder dependencies RUN --mount=type=cache,sharing=locked,id=centos:stream${centos_stream_version}-/var/cache/dnf,target=/var/cache/dnf \ echo "install_weak_deps=False" >> /etc/dnf/dnf.conf && \ echo "keepcache=True" >> /etc/dnf/dnf.conf && \ dnf --assumeyes install "dnf-command(config-manager)" && \ dnf config-manager --save --setopt=${extra_repository}.enabled=1 && \ dnf --assumeyes install epel-release && \ dnf --assumeyes upgrade && \ dnf --assumeyes install \ automake \ boost-devel \ cairo-devel \ gcc \ gcc-c++ \ gdal \ glib2-devel \ harfbuzz-devel \ httpd-devel \ iniparser-devel \ libcurl-devel \ libicu-devel \ libjpeg-devel \ libmemcached-devel \ librados2-devel \ libtiff-devel \ libwebp-devel \ libxml2-devel \ make \ proj-devel \ redhat-rpm-config ## Copy files from builder(s) ### Mapnik COPY --from=mapnik-builder /tmp/mapnik / ## Build, Test & Install `mod_tile` COPY . /tmp/mod_tile_src WORKDIR /tmp/mod_tile_src RUN export DESTDIR=/tmp/mod_tile && \ ./autogen.sh && \ ./configure && \ make DESTDIR=${DESTDIR} install install-mod_tile RUN make test # Runner FROM quay.io/centos/centos:stream${centos_stream_version} AS runner ## Arguments ARG centos_stream_version ARG extra_repository ## Install runner dependencies RUN --mount=type=cache,sharing=locked,id=centos:stream${centos_stream_version}-/var/cache/dnf,target=/var/cache/dnf \ echo "install_weak_deps=False" >> /etc/dnf/dnf.conf && \ echo "keepcache=True" >> /etc/dnf/dnf.conf && \ dnf --assumeyes install "dnf-command(config-manager)" && \ dnf config-manager --save --setopt=${extra_repository}.enabled=1 && \ dnf --assumeyes install epel-release && \ dnf --assumeyes upgrade && \ dnf --assumeyes install \ boost-regex \ cairo \ gdal \ harfbuzz \ httpd \ iniparser \ libicu \ libmemcached \ librados2 \ libtiff \ libwebp \ proj ## Copy files from builder(s) ### Mapnik COPY --from=mapnik-builder /tmp/mapnik / ### mod_tile COPY --from=builder /tmp/mod_tile / COPY --chown=apache:apache --from=builder \ /tmp/mod_tile_src/utils/example-map \ /usr/share/renderd/example-map COPY --from=builder \ /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf \ /etc/httpd/conf.d/renderd-example-map.conf ## Fix mapnik directories SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN sed \ --expression "s#/usr/lib/mapnik/3.1/input#$(find /usr -mindepth 1 -type d -name input | grep mapnik)#g" \ --expression "s#/usr/share/fonts/truetype#/usr/share/fonts#g" \ /usr/local/etc/renderd.conf > /etc/renderd.conf SHELL ["/bin/sh", "-c"] ## Add configuration RUN printf "LoadModule tile_module %s\n" "$(find /usr -name mod_tile.so)" > /etc/httpd/conf.modules.d/11-tile.conf RUN printf '\n[example-map]\nMAXZOOM=20\nMINZOOM=0\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-jpg]\nMAXZOOM=20\nMINZOOM=0\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png256]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png32]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-webp]\nMAXZOOM=20\nMINZOOM=0\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf ## Create missing directories RUN mkdir --parents /run/renderd /var/cache/renderd/tiles ## Start services CMD httpd -e debug -k start; \ G_MESSAGES_DEBUG=${G_MESSAGES_DEBUG:-info} renderd --foreground mod_tile-0.8.0/docker/centos/stream/Dockerfile.mapnik-latest000066400000000000000000000151631474064163400241310ustar00rootroot00000000000000# hadolint global ignore=DL3025,DL3040,DL3041,DL3059 # Arguments ARG centos_stream_version=9 ARG extra_repository=crb # Mapnik Builder FROM quay.io/centos/centos:stream${centos_stream_version} AS mapnik-builder ## Arguments ARG centos_stream_version ARG extra_repository ## Install mapnik-builder dependencies RUN --mount=type=cache,sharing=locked,id=centos:stream${centos_stream_version}-/var/cache/dnf,target=/var/cache/dnf \ echo "install_weak_deps=False" >> /etc/dnf/dnf.conf && \ echo "keepcache=True" >> /etc/dnf/dnf.conf && \ dnf --assumeyes install "dnf-command(config-manager)" && \ dnf config-manager --save --setopt=${extra_repository}.enabled=1 && \ dnf --assumeyes install epel-release && \ dnf --assumeyes upgrade && \ dnf --assumeyes install \ boost-devel \ cairo-devel \ cmake \ freetype-devel \ gcc \ gcc-c++ \ gdal-devel \ git \ harfbuzz-devel \ libicu-devel \ libjpeg-devel \ libpng-devel \ libtiff-devel \ libwebp-devel \ libxml2-devel \ make \ postgresql-devel \ proj-devel \ sqlite-devel \ zlib-devel ## Download, Build & Install `Mapnik` WORKDIR /tmp/mapnik_src RUN --mount=type=cache,id=centos:stream${centos_stream_version}-mapnik-src:latest,target=/tmp/mapnik_src \ if [ ! -f CMakeLists.txt ]; then \ git clone --depth 1 --jobs 8 --recurse-submodules https://github.com/mapnik/mapnik.git /tmp/mapnik_src; \ fi WORKDIR /tmp/mapnik_build RUN --mount=type=cache,id=centos:stream${centos_stream_version}-mapnik-src:latest,target=/tmp/mapnik_src \ --mount=type=cache,id=centos:stream${centos_stream_version}-mapnik-build:latest,target=/tmp/mapnik_build \ CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)" && export CMAKE_BUILD_PARALLEL_LEVEL && \ export DESTDIR=/tmp/mapnik && \ cmake -B . -S /tmp/mapnik_src \ -DBUILD_BENCHMARK:BOOL=OFF \ -DBUILD_DEMO_CPP:BOOL=OFF \ -DBUILD_DEMO_VIEWER:BOOL=OFF \ -DBUILD_TESTING:BOOL=OFF \ -DBUILD_UTILITY_GEOMETRY_TO_WKB:BOOL=OFF \ -DBUILD_UTILITY_MAPNIK_INDEX:BOOL=OFF \ -DBUILD_UTILITY_MAPNIK_RENDER:BOOL=OFF \ -DBUILD_UTILITY_OGRINDEX:BOOL=OFF \ -DBUILD_UTILITY_PGSQL2SQLITE:BOOL=OFF \ -DBUILD_UTILITY_SHAPEINDEX:BOOL=OFF \ -DBUILD_UTILITY_SVG2PNG:BOOL=OFF \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_PREFIX:PATH=/usr && \ cmake --build . && \ cmake --install . --strip # Builder FROM quay.io/centos/centos:stream${centos_stream_version} AS builder ## Arguments ARG centos_stream_version ARG extra_repository ## Install builder dependencies RUN --mount=type=cache,sharing=locked,id=centos:stream${centos_stream_version}-/var/cache/dnf,target=/var/cache/dnf \ echo "install_weak_deps=False" >> /etc/dnf/dnf.conf && \ echo "keepcache=True" >> /etc/dnf/dnf.conf && \ dnf --assumeyes install "dnf-command(config-manager)" && \ dnf config-manager --save --setopt=${extra_repository}.enabled=1 && \ dnf --assumeyes install epel-release && \ dnf --assumeyes upgrade && \ dnf --assumeyes install \ boost-devel \ cairo-devel \ cmake \ freetype-devel \ gcc \ gcc-c++ \ gdal \ glib2-devel \ harfbuzz-devel \ httpd-devel \ iniparser-devel \ libcurl-devel \ libicu-devel \ libjpeg-devel \ libmemcached-devel \ libpng-devel \ librados2-devel \ libtiff-devel \ libwebp-devel \ libxml2-devel \ make \ pkg-config \ procps \ proj-devel ## Copy files from builder(s) ### Mapnik COPY --from=mapnik-builder /tmp/mapnik / ## Build & Install `mod_tile` COPY . /tmp/mod_tile_src WORKDIR /tmp/mod_tile_build RUN CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)" && export CMAKE_BUILD_PARALLEL_LEVEL && \ cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON && \ cmake --build . RUN CTEST_PARALLEL_LEVEL="$(nproc)" && export CTEST_PARALLEL_LEVEL && \ ctest --output-on-failure RUN export DESTDIR=/tmp/mod_tile && \ cmake --install . --strip # Runner FROM quay.io/centos/centos:stream${centos_stream_version} AS runner ## Arguments ARG centos_stream_version ARG extra_repository ## Install runner dependencies RUN --mount=type=cache,sharing=locked,id=centos:stream${centos_stream_version}-/var/cache/dnf,target=/var/cache/dnf \ echo "install_weak_deps=False" >> /etc/dnf/dnf.conf && \ echo "keepcache=True" >> /etc/dnf/dnf.conf && \ dnf --assumeyes install "dnf-command(config-manager)" && \ dnf config-manager --save --setopt=${extra_repository}.enabled=1 && \ dnf --assumeyes install epel-release && \ dnf --assumeyes upgrade && \ dnf --assumeyes install \ boost-regex \ cairo \ gdal \ harfbuzz \ httpd \ iniparser \ libicu \ libmemcached \ librados2 \ libtiff \ libwebp \ proj ## Copy files from builder(s) ### Mapnik COPY --from=mapnik-builder /tmp/mapnik / ### mod_tile COPY --from=builder /tmp/mod_tile / COPY --chown=apache:apache --from=builder \ /tmp/mod_tile_src/utils/example-map \ /usr/share/renderd/example-map COPY --from=builder \ /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf \ /etc/httpd/conf.d/renderd-example-map.conf ## Add configuration RUN printf '\n[example-map]\nMAXZOOM=20\nMINZOOM=0\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-jpg]\nMAXZOOM=20\nMINZOOM=0\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png256]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png32]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-webp]\nMAXZOOM=20\nMINZOOM=0\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf ## Start services CMD httpd -e debug -k start; \ G_MESSAGES_DEBUG=${G_MESSAGES_DEBUG:-info} renderd --foreground mod_tile-0.8.0/docker/debian/000077500000000000000000000000001474064163400160155ustar00rootroot00000000000000mod_tile-0.8.0/docker/debian/Dockerfile000066400000000000000000000074261474064163400200200ustar00rootroot00000000000000# hadolint global ignore=DL3008,DL3025,DL3059 # Arguments ARG debian_version=12 ARG libiniparser_version=1 ARG libmapnik_version=3.1 ARG runner_additional_packages=libcurl4 libglib2.0 # Builder FROM debian:${debian_version} AS builder ## Arguments ARG debian_version ## Install builder dependencies RUN --mount=type=cache,sharing=locked,id=debian:${debian_version}-/var/cache/apt,target=/var/cache/apt \ --mount=type=cache,sharing=locked,id=debian:${debian_version}-/var/lib/apt,target=/var/lib/apt \ export DEBIAN_FRONTEND=noninteractive && \ apt-get --yes update && \ apt-get --yes upgrade && \ apt-get --no-install-recommends --yes install \ apache2 \ apache2-dev \ cmake \ curl \ g++ \ gcc \ libcairo2-dev \ libcurl4-openssl-dev \ libglib2.0-dev \ libiniparser-dev \ libmapnik-dev \ libmemcached-dev \ librados-dev \ netbase ## Build, Test & Install `mod_tile` COPY . /tmp/mod_tile_src WORKDIR /tmp/mod_tile_build RUN CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)" && export CMAKE_BUILD_PARALLEL_LEVEL && \ cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON && \ cmake --build . RUN CTEST_PARALLEL_LEVEL="$(nproc)" && export CTEST_PARALLEL_LEVEL && \ ctest --output-on-failure RUN export DESTDIR=/tmp/mod_tile && \ cmake --install . --strip # Runner FROM debian:${debian_version} AS runner ## Arguments ARG debian_version ARG libiniparser_version ARG libmapnik_version ARG runner_additional_packages ## Install runner dependencies RUN --mount=type=cache,sharing=locked,id=debian:${debian_version}-/var/cache/apt,target=/var/cache/apt \ --mount=type=cache,sharing=locked,id=debian:${debian_version}-/var/lib/apt,target=/var/lib/apt \ export DEBIAN_FRONTEND=noninteractive && \ apt-get --yes update && \ apt-get --yes upgrade && \ apt-get --no-install-recommends --yes install ${runner_additional_packages} \ apache2 \ libcairo2 \ libiniparser${libiniparser_version} \ libmapnik${libmapnik_version} \ libmemcached11 \ libmemcachedutil2 \ librados2 ## Copy files from builder(s) ### mod_tile COPY --from=builder /tmp/mod_tile / COPY --from=builder \ /tmp/mod_tile_src/utils/example-map \ /usr/share/renderd/example-map COPY --from=builder \ /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf \ /etc/apache2/sites-available/renderd-example-map.conf ## Add configuration RUN printf '\n[example-map]\nMAXZOOM=20\nMINZOOM=0\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-jpg]\nMAXZOOM=20\nMINZOOM=0\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png256]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png32]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-webp]\nMAXZOOM=20\nMINZOOM=0\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf ## Enable module & site RUN a2enmod tile && \ a2ensite renderd-example-map ## Start services CMD apachectl -e debug -k start; \ G_MESSAGES_DEBUG=${G_MESSAGES_DEBUG:-info} renderd --foreground mod_tile-0.8.0/docker/debian/Dockerfile.autotools000066400000000000000000000075361474064163400220520ustar00rootroot00000000000000# hadolint global ignore=DL3008,DL3025,DL3059 # Arguments ARG debian_version=12 ARG libiniparser_version=1 ARG libmapnik_version=3.1 ARG runner_additional_packages=libcurl4 libglib2.0 # Builder FROM debian:${debian_version} AS builder ## Arguments ARG debian_version ## Install builder dependencies RUN --mount=type=cache,sharing=locked,id=debian:${debian_version}-/var/cache/apt,target=/var/cache/apt \ --mount=type=cache,sharing=locked,id=debian:${debian_version}-/var/lib/apt,target=/var/lib/apt \ export DEBIAN_FRONTEND=noninteractive && \ apt-get --yes update && \ apt-get --yes upgrade && \ apt-get --no-install-recommends --yes install \ apache2 \ apache2-dev \ curl \ g++ \ gcc \ libcairo2-dev \ libcurl4-openssl-dev \ libglib2.0-dev \ libiniparser-dev \ libmapnik-dev \ libmemcached-dev \ librados-dev \ netbase ## Build, Test & Install `mod_tile` COPY . /tmp/mod_tile_src WORKDIR /tmp/mod_tile_src RUN export DESTDIR=/tmp/mod_tile && \ ./autogen.sh && \ ./configure && \ make DESTDIR=${DESTDIR} install install-mod_tile RUN make test # Runner FROM debian:${debian_version} AS runner ## Arguments ARG debian_version ARG libiniparser_version ARG libmapnik_version ARG runner_additional_packages ## Install runner dependencies RUN --mount=type=cache,sharing=locked,id=debian:${debian_version}-/var/cache/apt,target=/var/cache/apt \ --mount=type=cache,sharing=locked,id=debian:${debian_version}-/var/lib/apt,target=/var/lib/apt \ export DEBIAN_FRONTEND=noninteractive && \ apt-get --yes update && \ apt-get --yes upgrade && \ apt-get --no-install-recommends --yes install ${runner_additional_packages} \ apache2 \ libcairo2 \ libiniparser${libiniparser_version} \ libmapnik${libmapnik_version} \ libmemcached11 \ librados2 ## Copy files from builder(s) ### mod_tile COPY --from=builder /tmp/mod_tile / COPY --from=builder \ /tmp/mod_tile_src/utils/example-map \ /usr/share/renderd/example-map COPY --from=builder \ /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf \ /etc/apache2/sites-available/renderd-example-map.conf ## Fix mapnik directories SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN sed \ --expression "s#/usr/lib/mapnik/3.1/input#$(find /usr -mindepth 1 -type d -name input | grep mapnik)#g" \ --expression "s#/usr/share/fonts/truetype#/usr/share/fonts#g" \ /usr/local/etc/renderd.conf > /etc/renderd.conf SHELL ["/bin/sh", "-c"] ## Add configuration RUN printf "LoadModule tile_module %s\n" "$(find /usr -name mod_tile.so)" > /etc/apache2/mods-available/tile.load RUN printf '\n[example-map]\nMAXZOOM=20\nMINZOOM=0\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-jpg]\nMAXZOOM=20\nMINZOOM=0\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png256]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png32]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-webp]\nMAXZOOM=20\nMINZOOM=0\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf ## Create missing directories RUN mkdir --parents /run/renderd /var/cache/renderd/tiles ## Enable module & site RUN a2enmod tile && \ a2ensite renderd-example-map ## Start services CMD apachectl -e debug -k start; \ G_MESSAGES_DEBUG=${G_MESSAGES_DEBUG:-info} renderd --foreground mod_tile-0.8.0/docker/docker-compose.yml000066400000000000000000000175441474064163400202430ustar00rootroot00000000000000--- x-mod_tile: build_defaults: &build_defaults context: .. build_defaults_centos_stream: &build_defaults_centos_stream <<: *build_defaults dockerfile: docker/centos/stream/Dockerfile build_defaults_debian: &build_defaults_debian <<: *build_defaults dockerfile: docker/debian/Dockerfile build_defaults_fedora: &build_defaults_fedora <<: *build_defaults dockerfile: docker/fedora/Dockerfile build_defaults_opensuse: &build_defaults_opensuse <<: *build_defaults dockerfile: docker/opensuse/Dockerfile build_defaults_ubuntu: &build_defaults_ubuntu <<: *build_defaults dockerfile: docker/ubuntu/Dockerfile service_defaults: &service_defaults env_file: .env ports: - 8081:8081 services: archlinux: <<: *service_defaults build: <<: *build_defaults dockerfile: docker/archlinux/Dockerfile archlinux-full: <<: *service_defaults build: <<: *build_defaults args: runner_additional_packages: >- curl gdal git npm osm2pgsql postgresql python-psycopg2 python-pyaml unzip dockerfile: docker/archlinux/Dockerfile depends_on: postgres: condition: service_healthy entrypoint: /entrypoint.sh volumes: - data:/opt/data - fonts:/opt/fonts - styles:/opt/styles - tiles:/var/cache/renderd/tiles - ./full-entrypoint.sh:/entrypoint.sh:ro centos-stream-9: <<: *service_defaults build: <<: *build_defaults_centos_stream args: centos_stream_version: "9" extra_repository: crb centos-stream-9-autotools: <<: *service_defaults build: <<: *build_defaults_centos_stream args: centos_stream_version: "9" extra_repository: crb dockerfile: docker/centos/stream/Dockerfile.autotools centos-stream-9-mapnik-latest: <<: *service_defaults build: <<: *build_defaults_centos_stream args: centos_stream_version: "9" extra_repository: crb dockerfile: docker/centos/stream/Dockerfile.mapnik-latest centos-stream-10-development: <<: *service_defaults build: <<: *build_defaults_centos_stream args: centos_stream_version: "10-development" extra_repository: crb debian-10: <<: *service_defaults build: <<: *build_defaults_debian args: libmapnik_version: "3.0" debian_version: "10" debian-10-autotools: <<: *service_defaults build: <<: *build_defaults_debian args: libmapnik_version: "3.0" debian_version: "10" dockerfile: docker/debian/Dockerfile.autotools debian-11: <<: *service_defaults build: <<: *build_defaults_debian args: libmapnik_version: "3.1" debian_version: "11" debian-11-autotools: <<: *service_defaults build: <<: *build_defaults_debian args: libmapnik_version: "3.1" debian_version: "11" dockerfile: docker/debian/Dockerfile.autotools debian-12: <<: *service_defaults build: <<: *build_defaults_debian args: libmapnik_version: "3.1" debian_version: "12" debian-12-autotools: <<: *service_defaults build: <<: *build_defaults_debian args: libmapnik_version: "3.1" debian_version: "12" dockerfile: docker/debian/Dockerfile.autotools debian-unstable: <<: *service_defaults build: <<: *build_defaults_debian args: libiniparser_version: 4 libmapnik_version: "4.0" debian_version: unstable runner_additional_packages: >- libcurl4t64 libglib2.0-0t64 debian-unstable-autotools: <<: *service_defaults build: <<: *build_defaults_debian args: libiniparser_version: 4 libmapnik_version: "4.0" debian_version: unstable runner_additional_packages: >- libcurl4t64 libglib2.0-0t64 dockerfile: docker/debian/Dockerfile.autotools fedora-34: <<: *service_defaults build: <<: *build_defaults_fedora args: fedora_version: "34" fedora-35: <<: *service_defaults build: <<: *build_defaults_fedora args: fedora_version: "35" fedora-36: <<: *service_defaults build: <<: *build_defaults_fedora args: fedora_version: "36" fedora-37: <<: *service_defaults build: <<: *build_defaults_fedora args: fedora_version: "37" fedora-38: <<: *service_defaults build: <<: *build_defaults_fedora args: fedora_version: "38" fedora-39: <<: *service_defaults build: <<: *build_defaults_fedora args: fedora_version: "39" fedora-40: <<: *service_defaults build: <<: *build_defaults_fedora args: fedora_version: "40" fedora-41: <<: *service_defaults build: <<: *build_defaults_fedora args: fedora_version: "41" fedora-rawhide: <<: *service_defaults build: <<: *build_defaults_fedora args: fedora_version: "rawhide" opensuse-leap-15: <<: *service_defaults build: <<: *build_defaults_opensuse args: boost_version: "1_75_0" gcc_version: "13" opensuse_release: leap opensuse_version: "15" opensuse-tumbleweed: <<: *service_defaults build: <<: *build_defaults_opensuse args: opensuse_release: tumbleweed opensuse_version: latest ubuntu-20.04: <<: *service_defaults build: <<: *build_defaults_ubuntu args: libmapnik_version: "3.0" ubuntu_version: "20.04" ubuntu-20.04-autotools: <<: *service_defaults build: <<: *build_defaults_ubuntu args: libmapnik_version: "3.0" ubuntu_version: "20.04" dockerfile: docker/ubuntu/Dockerfile.autotools ubuntu-22.04: <<: *service_defaults build: <<: *build_defaults_ubuntu args: libmapnik_version: "3.1" ubuntu_version: "22.04" ubuntu-24.04: <<: *service_defaults build: <<: *build_defaults_ubuntu args: libmapnik_version: "3.1" ubuntu_version: "24.04" ubuntu-24.04-autotools: <<: *service_defaults build: <<: *build_defaults_ubuntu args: libmapnik_version: "3.1" ubuntu_version: "24.04" dockerfile: docker/ubuntu/Dockerfile.autotools ubuntu-devel: <<: *service_defaults build: <<: *build_defaults_ubuntu args: libmapnik_version: "4.0" ubuntu_version: "devel" ubuntu-devel-autotools: <<: *service_defaults build: <<: *build_defaults_ubuntu args: libmapnik_version: "4.0" ubuntu_version: "devel" dockerfile: docker/ubuntu/Dockerfile.autotools ubuntu-devel-full: <<: *service_defaults build: <<: *build_defaults_ubuntu args: libmapnik_version: "4.0" runner_additional_packages: >- curl gdal-bin git npm osm2pgsql postgresql-client python3-psycopg2 python3-yaml unzip ubuntu_version: "devel" depends_on: postgres: condition: service_healthy entrypoint: /entrypoint.sh volumes: - data:/opt/data - fonts:/opt/fonts - styles:/opt/styles - tiles:/var/cache/renderd/tiles - ./full-entrypoint.sh:/entrypoint.sh:ro postgres: env_file: .env environment: POSTGRES_DB: ${PGDATABASE} POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_USER: ${PGUSER} healthcheck: test: ["CMD", "psql", "--quiet", "--list"] image: postgis/postgis ports: - 65432:5432 volumes: - pgdata:/var/lib/postgresql/data shm_size: 1gb volumes: data: fonts: pgdata: styles: tiles: mod_tile-0.8.0/docker/fedora/000077500000000000000000000000001474064163400160335ustar00rootroot00000000000000mod_tile-0.8.0/docker/fedora/Dockerfile000066400000000000000000000062471474064163400200360ustar00rootroot00000000000000# hadolint global ignore=DL3025,DL3040,DL3041,DL3059 # Arguments ARG fedora_version=40 # Builder FROM fedora:${fedora_version} AS builder ## Arguments ARG fedora_version ## Install builder dependencies RUN --mount=type=cache,sharing=locked,id=fedora:${fedora_version}-/var/cache/dnf,target=/var/cache/dnf \ echo "install_weak_deps=False" >> /etc/dnf/dnf.conf && \ echo "keepcache=True" >> /etc/dnf/dnf.conf && \ dnf --assumeyes install \ cairo-devel \ cmake \ gcc \ gcc-c++ \ glib2-devel \ httpd-devel \ iniparser-devel \ libcurl-devel \ libmemcached-devel \ librados-devel \ mapnik-devel \ mapnik-static \ procps ## Build, Test & Install `mod_tile` COPY . /tmp/mod_tile_src WORKDIR /tmp/mod_tile_build RUN CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)" && export CMAKE_BUILD_PARALLEL_LEVEL && \ cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON && \ cmake --build . RUN CTEST_PARALLEL_LEVEL="$(nproc)" && export CTEST_PARALLEL_LEVEL && \ ctest --output-on-failure RUN export DESTDIR=/tmp/mod_tile && \ cmake --install . --strip # Runner FROM fedora:${fedora_version} AS runner ## Arguments ARG fedora_version ## Install runner dependencies RUN --mount=type=cache,sharing=locked,id=fedora:${fedora_version}-/var/cache/dnf,target=/var/cache/dnf \ echo "install_weak_deps=False" >> /etc/dnf/dnf.conf && \ echo "keepcache=True" >> /etc/dnf/dnf.conf && \ dnf --assumeyes install \ cairo \ glib2 \ httpd \ iniparser \ libcurl \ libmemcached \ librados2 \ mapnik ## Copy files from builder(s) ### mod_tile COPY --from=builder /tmp/mod_tile / COPY --chown=apache:apache --from=builder \ /tmp/mod_tile_src/utils/example-map \ /usr/share/renderd/example-map COPY --from=builder \ /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf \ /etc/httpd/conf.d/renderd-example-map.conf ## Add configuration RUN printf '\n[example-map]\nMAXZOOM=20\nMINZOOM=0\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-jpg]\nMAXZOOM=20\nMINZOOM=0\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png256]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png32]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-webp]\nMAXZOOM=20\nMINZOOM=0\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf ## Start services CMD httpd -e debug -k start; \ G_MESSAGES_DEBUG=${G_MESSAGES_DEBUG:-info} renderd --foreground mod_tile-0.8.0/docker/full-entrypoint.sh000077500000000000000000000037641474064163400203170ustar00rootroot00000000000000#!/usr/bin/env sh if [ ! -f /opt/styles/mapnik.xml ] then git clone https://github.com/gravitystorm/openstreetmap-carto.git --depth 1 /opt/openstreetmap-carto cp --archive /opt/openstreetmap-carto/patterns /opt/openstreetmap-carto/symbols /opt/styles/ python3 /opt/openstreetmap-carto/scripts/get-external-data.py --cache --config /opt/openstreetmap-carto/external-data.yml --data /opt/data cd /opt && /opt/openstreetmap-carto/scripts/get-fonts.sh && cd - psql --command "CREATE EXTENSION postgis;" --dbname "${PGDATABASE}" --host "${PGHOST}" --user "${PGUSER}" psql --command "CREATE EXTENSION hstore;" --dbname "${PGDATABASE}" --host "${PGHOST}" --user "${PGUSER}" psql --command "ALTER TABLE geometry_columns OWNER TO ${PGUSER};" --dbname "${PGDATABASE}" --host "${PGHOST}" --user "${PGUSER}" psql --command "ALTER TABLE spatial_ref_sys OWNER TO ${PGUSER};" --dbname "${PGDATABASE}" --host "${PGHOST}" --user "${PGUSER}" if [ ! -f /opt/data/region.osm.pbf ] then curl --location "${DOWNLOAD_PBF:-http://download.geofabrik.de/asia/vietnam-latest.osm.pbf}" --output /opt/data/region.osm.pbf fi osm2pgsql \ --create \ --database "${PGDATABASE}" \ --host "${PGHOST}" \ --hstore \ --number-processes "$(nproc)" \ --slim \ --tag-transform-script /opt/openstreetmap-carto/openstreetmap-carto.lua \ --user "${PGUSER}" \ -G \ -S /opt/openstreetmap-carto/openstreetmap-carto.style \ /opt/data/region.osm.pbf psql --dbname "${PGDATABASE}" --file /opt/openstreetmap-carto/indexes.sql --host "${PGHOST}" --user "${PGUSER}" npm install --global carto carto /opt/openstreetmap-carto/project.mml > /opt/styles/mapnik.xml fi sed -i \ -e 's#/usr/share/renderd/example-map/mapnik.xml#/opt/styles/mapnik.xml#g' \ -e 's/pid_file=/num_threads=-1\npid_file=/g' \ -e 's#font_dir=.*#font_dir=/opt/fonts#g' \ /etc/renderd.conf apachectl -e debug -k start renderd --foreground mod_tile-0.8.0/docker/opensuse/000077500000000000000000000000001474064163400164345ustar00rootroot00000000000000mod_tile-0.8.0/docker/opensuse/Dockerfile000066400000000000000000000160041474064163400204270ustar00rootroot00000000000000# hadolint global ignore=DL3025,DL3036,DL3037,DL3059 # Arguments ARG boost_version ARG gcc_version ARG mapnik_version=4.0.3 ARG opensuse_release=leap ARG opensuse_version=15 # Mapnik Builder FROM opensuse/${opensuse_release}:${opensuse_version} AS mapnik-builder ## Arguments ARG boost_version ARG gcc_version ARG mapnik_version ARG opensuse_release ARG opensuse_version ## Install mapnik-builder dependencies RUN --mount=type=cache,sharing=locked,id=opensuse/${opensuse_release}:${opensuse_version}-/var/cache/zypp,target=/var/cache/zypp \ zypper modifyrepo --all --keep-packages && \ zypper --non-interactive update && \ zypper --non-interactive install \ cairo-devel \ cmake \ freetype-devel \ gcc${gcc_version} \ gcc${gcc_version}-c++ \ gdal-devel \ git \ harfbuzz-devel \ libboost_regex${boost_version}-devel \ libicu-devel \ libjpeg8-devel \ libpng16-devel \ libtiff-devel \ libwebp-devel \ libxml2-devel \ postgresql-devel \ proj-devel \ sqlite3-devel \ zlib-devel && \ if [ -n "${gcc_version}" ]; then \ update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${gcc_version} 10; \ update-alternatives --install /usr/bin/cc gcc /usr/bin/gcc-${gcc_version} 10; \ update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-${gcc_version} 10; \ update-alternatives --install /usr/bin/c++ g++ /usr/bin/g++-${gcc_version} 10; \ fi ## Download, Build & Install `Mapnik` WORKDIR /tmp/mapnik_src RUN --mount=type=cache,id=opensuse/${opensuse_release}:${opensuse_version}-mapnik-src:${mapnik_version},target=/tmp/mapnik_src \ if [ ! -f CMakeLists.txt ]; then \ git clone --branch v${mapnik_version} --depth 1 --jobs 8 --recurse-submodules https://github.com/mapnik/mapnik.git /tmp/mapnik_src; \ fi WORKDIR /tmp/mapnik_build RUN --mount=type=cache,id=opensuse/${opensuse_release}:${opensuse_version}-mapnik-src:${mapnik_version},target=/tmp/mapnik_src \ --mount=type=cache,id=opensuse/${opensuse_release}:${opensuse_version}-mapnik-build:${mapnik_version},target=/tmp/mapnik_build \ CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)" && export CMAKE_BUILD_PARALLEL_LEVEL && \ export DESTDIR=/tmp/mapnik && \ cmake -B . -S /tmp/mapnik_src \ -DBUILD_BENCHMARK:BOOL=OFF \ -DBUILD_DEMO_CPP:BOOL=OFF \ -DBUILD_DEMO_VIEWER:BOOL=OFF \ -DBUILD_TESTING:BOOL=OFF \ -DBUILD_UTILITY_GEOMETRY_TO_WKB:BOOL=OFF \ -DBUILD_UTILITY_MAPNIK_INDEX:BOOL=OFF \ -DBUILD_UTILITY_MAPNIK_RENDER:BOOL=OFF \ -DBUILD_UTILITY_OGRINDEX:BOOL=OFF \ -DBUILD_UTILITY_PGSQL2SQLITE:BOOL=OFF \ -DBUILD_UTILITY_SHAPEINDEX:BOOL=OFF \ -DBUILD_UTILITY_SVG2PNG:BOOL=OFF \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_PREFIX:PATH=/usr && \ cmake --build . && \ cmake --install . --strip # Builder FROM opensuse/${opensuse_release}:${opensuse_version} AS builder ## Arguments ARG boost_version ARG opensuse_release ARG opensuse_version ## Install builder dependencies RUN --mount=type=cache,sharing=locked,id=opensuse/${opensuse_release}:${opensuse_version}-/var/cache/zypp,target=/var/cache/zypp \ zypper modifyrepo --all --keep-packages && \ zypper --non-interactive update && \ zypper --non-interactive install \ apache2-devel \ cairo-devel \ cmake \ curl \ gcc \ gcc-c++ \ gdal \ glib2-devel \ harfbuzz-devel \ libboost_regex${boost_version}-devel \ libcurl-devel \ libicu-devel \ libiniparser-devel \ libjpeg8-devel \ libmemcached-devel \ librados-devel \ libtiff-devel \ libwebp-devel \ libxml2-devel \ procps \ proj-devel \ tar ## Copy files from builder(s) ### Mapnik COPY --from=mapnik-builder /tmp/mapnik / ## Create `nobody` user and group RUN id nobody || useradd --home-dir / --no-create-home --shell /usr/sbin/nologin --system --user-group nobody ## Build, Test & Install `mod_tile` COPY . /tmp/mod_tile_src WORKDIR /tmp/mod_tile_build RUN CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)" && export CMAKE_BUILD_PARALLEL_LEVEL && \ cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON && \ cmake --build . RUN CTEST_PARALLEL_LEVEL="$(nproc)" && export CTEST_PARALLEL_LEVEL && \ ctest --output-on-failure RUN export DESTDIR=/tmp/mod_tile && \ cmake --install . --strip # Runner FROM opensuse/${opensuse_release}:${opensuse_version} AS runner ## Arguments ARG boost_version ARG opensuse_release ARG opensuse_version ## Install runner dependencies RUN --mount=type=cache,sharing=locked,id=opensuse/${opensuse_release}:${opensuse_version}-/var/cache/zypp,target=/var/cache/zypp \ zypper modifyrepo --all --keep-packages && \ zypper --non-interactive update && \ zypper --non-interactive install \ apache2 \ apache2-utils \ cairo-devel \ gdal \ harfbuzz-devel \ libboost_regex${boost_version}-devel \ libicu-devel \ libiniparser-devel \ libjpeg8-devel \ libmemcached-devel \ librados-devel \ libtiff-devel \ libwebp-devel \ proj ## Copy files from builder(s) ### Mapnik COPY --from=mapnik-builder /tmp/mapnik / ### mod_tile COPY --from=builder /tmp/mod_tile / COPY --chown=apache:apache --from=builder \ /tmp/mod_tile_src/utils/example-map \ /usr/share/renderd/example-map COPY --from=builder \ /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf \ /etc/apache2/conf.d/renderd-example-map.conf ## Add configuration RUN printf '\n[example-map]\nMAXZOOM=20\nMINZOOM=0\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-jpg]\nMAXZOOM=20\nMINZOOM=0\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png256]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png32]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-webp]\nMAXZOOM=20\nMINZOOM=0\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf ## Enable `mod_access_compat` RUN sed -i 's/^APACHE_MODULES="actions/APACHE_MODULES="access_compat actions/g' /etc/sysconfig/apache2 ## Start services CMD apachectl -e debug -k start; \ G_MESSAGES_DEBUG=${G_MESSAGES_DEBUG:-info} renderd --foreground mod_tile-0.8.0/docker/ubuntu/000077500000000000000000000000001474064163400161155ustar00rootroot00000000000000mod_tile-0.8.0/docker/ubuntu/Dockerfile000066400000000000000000000073111474064163400201110ustar00rootroot00000000000000# hadolint global ignore=DL3008,DL3025,DL3059 # Arguments ARG libmapnik_version=3.1 ARG runner_additional_packages ARG ubuntu_version=24.04 # Builder FROM ubuntu:${ubuntu_version} AS builder ## Arguments ARG ubuntu_version ## Install builder dependencies RUN --mount=type=cache,sharing=locked,id=ubuntu:${ubuntu_version}-/var/cache/apt,target=/var/cache/apt \ --mount=type=cache,sharing=locked,id=ubuntu:${ubuntu_version}-/var/lib/apt,target=/var/lib/apt \ export DEBIAN_FRONTEND=noninteractive && \ apt-get --yes update && \ apt-get --yes upgrade && \ apt-get --no-install-recommends --yes install \ apache2 \ apache2-dev \ cmake \ curl \ g++ \ gcc \ libcairo2-dev \ libcurl4-openssl-dev \ libglib2.0-dev \ libiniparser-dev \ libmapnik-dev \ libmemcached-dev \ librados-dev \ netbase ## Build, Test & Install `mod_tile` COPY . /tmp/mod_tile_src WORKDIR /tmp/mod_tile_build RUN CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)" && export CMAKE_BUILD_PARALLEL_LEVEL && \ cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON && \ cmake --build . RUN CTEST_PARALLEL_LEVEL="$(nproc)" && export CTEST_PARALLEL_LEVEL && \ ctest --output-on-failure RUN export DESTDIR=/tmp/mod_tile && \ cmake --install . --strip # Runner FROM ubuntu:${ubuntu_version} AS runner ## Arguments ARG libmapnik_version ARG runner_additional_packages ARG ubuntu_version ## Install runner dependencies RUN --mount=type=cache,sharing=locked,id=ubuntu:${ubuntu_version}-/var/cache/apt,target=/var/cache/apt \ --mount=type=cache,sharing=locked,id=ubuntu:${ubuntu_version}-/var/lib/apt,target=/var/lib/apt \ export DEBIAN_FRONTEND=noninteractive && \ apt-get --yes update && \ apt-get --yes upgrade && \ apt-get --no-install-recommends --yes install ${runner_additional_packages} \ apache2 \ libcairo2 \ libcurl4 \ libglib2.0-0 \ libiniparser1 \ libmapnik${libmapnik_version} \ libmemcached11 \ librados2 ## Copy files from builder(s) ### mod_tile COPY --from=builder /tmp/mod_tile / COPY --from=builder \ /tmp/mod_tile_src/utils/example-map \ /usr/share/renderd/example-map COPY --from=builder \ /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf \ /etc/apache2/sites-available/renderd-example-map.conf ## Add configuration RUN printf '\n[example-map]\nMAXZOOM=20\nMINZOOM=0\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-jpg]\nMAXZOOM=20\nMINZOOM=0\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png256]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png32]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-webp]\nMAXZOOM=20\nMINZOOM=0\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf ## Enable module & site RUN a2enmod tile && \ a2ensite renderd-example-map ## Start services CMD apachectl -e debug -k start; \ G_MESSAGES_DEBUG=${G_MESSAGES_DEBUG:-info} renderd --foreground mod_tile-0.8.0/docker/ubuntu/Dockerfile.autotools000066400000000000000000000073211474064163400221420ustar00rootroot00000000000000# hadolint global ignore=DL3008,DL3025,DL3059 # Arguments ARG libmapnik_version=3.1 ARG ubuntu_version=24.04 # Builder FROM ubuntu:${ubuntu_version} AS builder ## Arguments ARG ubuntu_version ## Install builder dependencies RUN --mount=type=cache,sharing=locked,id=ubuntu:${ubuntu_version}-/var/cache/apt,target=/var/cache/apt \ --mount=type=cache,sharing=locked,id=ubuntu:${ubuntu_version}-/var/lib/apt,target=/var/lib/apt \ export DEBIAN_FRONTEND=noninteractive && \ apt-get --yes update && \ apt-get --yes upgrade && \ apt-get --no-install-recommends --yes install \ apache2 \ apache2-dev \ curl \ g++ \ gcc \ libcairo2-dev \ libcurl4-openssl-dev \ libglib2.0-dev \ libiniparser-dev \ libmapnik-dev \ libmemcached-dev \ librados-dev \ netbase ## Build, Test & Install `mod_tile` COPY . /tmp/mod_tile_src WORKDIR /tmp/mod_tile_src RUN export DESTDIR=/tmp/mod_tile && \ ./autogen.sh && \ ./configure && \ make DESTDIR=${DESTDIR} install install-mod_tile RUN make test # Runner FROM ubuntu:${ubuntu_version} AS runner ## Arguments ARG libmapnik_version ARG ubuntu_version ## Install runner dependencies RUN --mount=type=cache,sharing=locked,id=ubuntu:${ubuntu_version}-/var/cache/apt,target=/var/cache/apt \ --mount=type=cache,sharing=locked,id=ubuntu:${ubuntu_version}-/var/lib/apt,target=/var/lib/apt \ export DEBIAN_FRONTEND=noninteractive && \ apt-get --yes update && \ apt-get --yes upgrade && \ apt-get --no-install-recommends --yes install \ apache2 \ libcairo2 \ libcurl4 \ libglib2.0-0 \ libiniparser1 \ libmapnik${libmapnik_version} \ libmemcached11 \ librados2 ## Copy files from builder(s) ### mod_tile COPY --from=builder /tmp/mod_tile / COPY --from=builder \ /tmp/mod_tile_src/utils/example-map \ /usr/share/renderd/example-map COPY --from=builder \ /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf \ /etc/apache2/sites-available/renderd-example-map.conf ## Fix mapnik directories SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN sed \ --expression "s#/usr/lib/mapnik/3.1/input#$(find /usr -mindepth 1 -type d -name input | grep mapnik)#g" \ --expression "s#/usr/share/fonts/truetype#/usr/share/fonts#g" \ /usr/local/etc/renderd.conf > /etc/renderd.conf SHELL ["/bin/sh", "-c"] ## Add configuration RUN printf "LoadModule tile_module %s\n" "$(find /usr -name mod_tile.so)" > /etc/apache2/mods-available/tile.load RUN printf '\n[example-map]\nMAXZOOM=20\nMINZOOM=0\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-jpg]\nMAXZOOM=20\nMINZOOM=0\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png256]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-png32]\nMAXZOOM=20\nMINZOOM=0\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf RUN printf '\n[example-map-webp]\nMAXZOOM=20\nMINZOOM=0\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' >> /etc/renderd.conf ## Create missing directories RUN mkdir --parents /run/renderd /var/cache/renderd/tiles ## Enable module & site RUN a2enmod tile && \ a2ensite renderd-example-map ## Start services CMD apachectl -e debug -k start; \ G_MESSAGES_DEBUG=${G_MESSAGES_DEBUG:-info} renderd --foreground mod_tile-0.8.0/docs/000077500000000000000000000000001474064163400142545ustar00rootroot00000000000000mod_tile-0.8.0/docs/build/000077500000000000000000000000001474064163400153535ustar00rootroot00000000000000mod_tile-0.8.0/docs/build/building_on_arch_linux.md000066400000000000000000000054221474064163400224050ustar00rootroot00000000000000# Building on Arch Linux This document provides users with step-by-step instructions on how to compile and use`mod_tile` and `renderd`. Please see our [Continuous Integration script](/.github/workflows/build-and-test.yml) for more details. A Docker-based building & testing setup pipeline is also available [here](/docker) for your convenience. ```shell #!/usr/bin/env bash # Update installed packages sudo pacman --sync --refresh --sysupgrade --noconfirm # Install build dependencies # (libmemcached is optional) sudo pacman --sync --refresh --noconfirm \ apache \ apr \ boost \ cairo \ cmake \ curl \ extra-cmake-modules \ gcc \ git \ glib2 \ iniparser \ lcov \ libmemcached \ make \ mapnik \ pkgconf # Download, Build, Test & Install `mod_tile` export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) rm -rf /tmp/mod_tile_src /tmp/mod_tile_build mkdir /tmp/mod_tile_src /tmp/mod_tile_build cd /tmp/mod_tile_src git clone --depth 1 https://github.com/openstreetmap/mod_tile.git . cd /tmp/mod_tile_build cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON cmake --build . ctest sudo cmake --install . --strip # Create /usr/share/renderd directory sudo mkdir --parents /usr/share/renderd # Copy files of example map sudo cp -av /tmp/mod_tile_src/utils/example-map /usr/share/renderd/example-map # Add configuration sudo cp -av /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf /etc/httpd/conf/extra/httpd-tile-renderd-example-map.conf printf '\n[example-map]\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-jpg]\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png256]\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png32]\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-webp]\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf # Enable configuration printf '\nInclude conf/extra/httpd-tile.conf\n' | sudo tee -a /etc/httpd/conf/httpd.conf printf '\nInclude conf/extra/httpd-tile-renderd-example-map.conf\n' | sudo tee -a /etc/httpd/conf/httpd.conf # Start services sudo httpd sudo renderd -f ``` Then you can visit: `http://localhost:8081/renderd-example-map` mod_tile-0.8.0/docs/build/building_on_centos_stream.md000066400000000000000000000065041474064163400231210ustar00rootroot00000000000000# Building on CentOS Stream This document provides users with step-by-step instructions on how to compile and use`mod_tile` and `renderd`. Please see our [Continuous Integration script](/.github/workflows/build-and-test.yml) for more details. A Docker-based building & testing setup pipeline is also available [here](/docker) for your convenience. _CentOS Stream does not provide a `mapnik`/`mapnik-devel` package, so it will first need to be built & installed, which is beyond the scope of this document, please visit the project's [installation document on GitHub](https://github.com/mapnik/mapnik/blob/master/INSTALL.md) or our [Continuous Integration script](/.github/actions/dependencies/build-and-install/mapnik/action.yml) for more information._ ## CentOS Stream 9 ```shell #!/usr/bin/env bash # Update config-manager DNF plugin sudo dnf --assumeyes install "dnf-command(config-manager)" # Enable CRB Repository sudo dnf config-manager --save --setopt=crb.enabled=1 #!/usr/bin/env bash # Update installed packages sudo dnf --assumeyes update # Install build dependencies # (libmemcached-devel & librados2-devel are optional) sudo dnf --assumeyes install epel-release sudo dnf --assumeyes --setopt=install_weak_deps=False install \ boost-devel \ cairo-devel \ cmake \ gcc \ gcc-c++ \ gdal \ git \ glib2-devel \ harfbuzz-devel \ httpd-devel \ iniparser-devel \ libcurl-devel \ libicu-devel \ libjpeg \ libmemcached-devel \ librados2-devel \ libtiff \ libwebp \ make \ procps \ proj # Download, Build, Test & Install `mod_tile` export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) rm -rf /tmp/mod_tile_src /tmp/mod_tile_build mkdir /tmp/mod_tile_src /tmp/mod_tile_build cd /tmp/mod_tile_src git clone --depth 1 https://github.com/openstreetmap/mod_tile.git . cd /tmp/mod_tile_build cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON cmake --build . ctest sudo cmake --install . --strip # Create /usr/share/renderd directory sudo mkdir --parents /usr/share/renderd # Copy files of example map sudo cp -av /tmp/mod_tile_src/utils/example-map /usr/share/renderd/example-map # Add configuration sudo cp -av /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf /etc/httpd/conf.d/renderd-example-map.conf printf '\n[example-map]\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-jpg]\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png256]\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png32]\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-webp]\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf # Start services sudo httpd sudo renderd -f ``` Then you can visit: `http://localhost:8081/renderd-example-map` mod_tile-0.8.0/docs/build/building_on_debian.md000066400000000000000000000053541474064163400214770ustar00rootroot00000000000000# Building on Debian This document provides users with step-by-step instructions on how to compile and use`mod_tile` and `renderd`. Please see our [Continuous Integration script](/.github/workflows/build-and-test.yml) for more details. A Docker-based building & testing setup pipeline is also available [here](/docker) for your convenience. ## Debian 10/11/12 ```shell #!/usr/bin/env bash # Update installed packages sudo apt update && sudo apt --yes upgrade sudo apt --yes install --reinstall ca-certificates # Install build dependencies # (libmemcached-dev & librados-dev are optional) sudo apt --no-install-recommends --yes install \ apache2 \ apache2-dev \ cmake \ curl \ g++ \ gcc \ git \ libcairo2-dev \ libcurl4-openssl-dev \ libglib2.0-dev \ libiniparser-dev \ libmapnik-dev \ libmemcached-dev \ librados-dev # Download, Build, Test & Install `mod_tile` export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) rm -rf /tmp/mod_tile_src /tmp/mod_tile_build mkdir /tmp/mod_tile_src /tmp/mod_tile_build cd /tmp/mod_tile_src git clone --depth 1 https://github.com/openstreetmap/mod_tile.git . cd /tmp/mod_tile_build cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON cmake --build . ctest sudo cmake --install . --strip # Create /usr/share/renderd directory sudo mkdir --parents /usr/share/renderd # Copy files of example map sudo cp -av /tmp/mod_tile_src/utils/example-map /usr/share/renderd/example-map # Add configuration sudo cp -av /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf /etc/apache2/sites-available/renderd-example-map.conf printf '\n[example-map]\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-jpg]\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png256]\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png32]\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-webp]\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf # Enable configuration a2enmod tile a2ensite renderd-example-map # Start services sudo apache2ctl start sudo renderd -f ``` Then you can visit: `http://localhost:8081/renderd-example-map` mod_tile-0.8.0/docs/build/building_on_fedora.md000066400000000000000000000051671474064163400215170ustar00rootroot00000000000000# Building on Fedora This document provides users with step-by-step instructions on how to compile and use`mod_tile` and `renderd`. Please see our [Continuous Integration script](/.github/workflows/build-and-test.yml) for more details. A Docker-based building & testing setup pipeline is also available [here](/docker) for your convenience. ## Fedora 34/35/36/37/38/39/40/41 ```shell #!/usr/bin/env bash # Update installed packages sudo dnf --assumeyes update # Install build dependencies # (libmemcached-devel & librados-devel are optional) sudo dnf --assumeyes --setopt=install_weak_deps=False install \ cairo-devel \ cmake \ gcc \ gcc-c++ \ git \ glib2-devel \ httpd-devel \ iniparser-devel \ libcurl-devel \ libmemcached-devel \ librados-devel \ mapnik-devel \ mapnik-static \ procps # Download, Build, Test & Install `mod_tile` export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) rm -rf /tmp/mod_tile_src /tmp/mod_tile_build mkdir /tmp/mod_tile_src /tmp/mod_tile_build cd /tmp/mod_tile_src git clone --depth 1 https://github.com/openstreetmap/mod_tile.git . cd /tmp/mod_tile_build cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON cmake --build . ctest sudo cmake --install . --strip # Create /usr/share/renderd directory sudo mkdir --parents /usr/share/renderd # Copy files of example map sudo cp -av /tmp/mod_tile_src/utils/example-map /usr/share/renderd/example-map # Add configuration sudo cp -av /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf /etc/httpd/conf.d/renderd-example-map.conf printf '\n[example-map]\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-jpg]\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png256]\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png32]\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-webp]\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf # Start services sudo httpd sudo renderd -f ``` Then you can visit: `http://localhost:8081/renderd-example-map` mod_tile-0.8.0/docs/build/building_on_freebsd.md000066400000000000000000000053001474064163400216560ustar00rootroot00000000000000# Building on FreeBSD This document provides users with step-by-step instructions on how to compile and use`mod_tile` and `renderd`. Please see our [Continuous Integration script](/.github/workflows/build-and-test.yml) for more details. ## FreeBSD 12/13/14 ```shell #!/usr/bin/env sh # Mapnik is not in the `quarterly` repository (2023.10.12) sudo mkdir -p /usr/local/etc/pkg/repos sudo sed 's#/quarterly#/latest#g' /etc/pkg/FreeBSD.conf > /usr/local/etc/pkg/repos/FreeBSD.conf # Update installed packages sudo pkg upgrade --yes # Install build dependencies # (libmemcached & ceph14 are optional) sudo pkg install --yes \ apache24 \ cairo \ ceph14 \ cmake \ coreutils \ curl \ git \ glib \ iniparser \ libmemcached \ mapnik \ pkgconf # Download, Build, Test & Install `mod_tile` export CMAKE_BUILD_PARALLEL_LEVEL=$(sysctl -n hw.ncpu) export CTEST_CLIENT_HOST="::1" export CTEST_SERVER_HOST="localhost" export LIBRARY_PATH="/usr/local/lib" rm -rf /tmp/mod_tile_src /tmp/mod_tile_build mkdir /tmp/mod_tile_src /tmp/mod_tile_build cd /tmp/mod_tile_src git clone --depth 1 https://github.com/openstreetmap/mod_tile.git . cd /tmp/mod_tile_build cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr/local \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON cmake --build . ctest sudo cmake --install . --strip # Create /usr/share/renderd directory sudo mkdir -p /usr/share/renderd # Copy files of example map sudo cp -av /tmp/mod_tile_src/utils/example-map /usr/share/renderd/example-map # Add configuration sudo cp -av /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf /usr/local/etc/apache24/Includes/renderd-example-map.conf printf '\n[example-map]\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-jpg]\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png256]\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png32]\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-webp]\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf # Start services sudo httpd sudo renderd -f ``` Then you can visit: `http://localhost:8081/renderd-example-map` mod_tile-0.8.0/docs/build/building_on_macos.md000066400000000000000000000053611474064163400213550ustar00rootroot00000000000000# Building on macOS This document provides users with step-by-step instructions on how to compile and use`mod_tile` and `renderd`. Please see our [Continuous Integration script](/.github/workflows/build-and-test.yml) for more details. ## macOS 11/12/13/14/15 ```shell #!/usr/bin/env bash # Update installed packages brew upgrade # Install build dependencies # (libmemcached is optional) brew install \ apr \ cairo \ cmake \ coreutils \ curl \ glib \ httpd \ icu4c \ iniparser \ libmemcached \ mapnik \ pkg-config # Download, Build, Test & Install `mod_tile` export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) export CPATH=$(brew --prefix)/include export ICU_ROOT=$(brew --prefix icu4c) export LDFLAGS="-undefined dynamic_lookup" export LIBRARY_PATH=$(brew --prefix)/lib rm -rf /tmp/mod_tile_src /tmp/mod_tile_build mkdir /tmp/mod_tile_src /tmp/mod_tile_build cd /tmp/mod_tile_src git clone --depth 1 https://github.com/openstreetmap/mod_tile.git . cd /tmp/mod_tile_build cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr/local \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/var/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON cmake --build . ctest sudo cmake --install . --strip # Create /usr/local/share/renderd directory sudo mkdir -p /usr/local/share/renderd # Copy files of example map sudo cp -av /tmp/mod_tile_src/utils/example-map /usr/local/share/renderd/example-map # Add configuration sed 's#/usr/share/#/usr/local/share/#g' /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf | sudo tee /usr/local/etc/httpd/extra/renderd-example-map.conf printf '\nInclude /usr/local/etc/httpd/extra/httpd-tile.conf\nInclude /usr/local/etc/httpd/extra/renderd-example-map.conf\n' | sudo tee -a /usr/local/etc/httpd/httpd.conf printf '\n[example-map]\nURI=/tiles/renderd-example\nXML=/usr/local/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-jpg]\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png256]\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png32]\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-webp]\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf # Start services httpd sudo renderd -f ``` Then you can visit: `http://localhost:8081/renderd-example-map` mod_tile-0.8.0/docs/build/building_on_opensuse.md000066400000000000000000000066301474064163400221140ustar00rootroot00000000000000# Building on openSUSE This document provides users with step-by-step instructions on how to compile and use`mod_tile` and `renderd`. Please see our [Continuous Integration script](/.github/workflows/build-and-test.yml) for more details. A Docker-based building & testing setup pipeline is also available [here](/docker) for your convenience. _openSUSE does not provide a `mapnik`/`mapnik-devel` package, so it will first need to be built & installed, which is beyond the scope of this document, please visit the project's [installation document on GitHub](https://github.com/mapnik/mapnik/blob/master/INSTALL.md) or our [Continuous Integration script](/.github/actions/dependencies/build-and-install/mapnik/action.yml) for more information._ ## openSUSE Leap 15/Tumbleweed ```shell #!/usr/bin/env bash # Update installed packages sudo zypper --non-interactive update # Install build dependencies # (libmemcached-devel & librados-devel are optional) sudo zypper --non-interactive install \ apache2-devel \ cairo-devel \ cmake \ curl \ gcc \ gcc-c++ \ gdal \ glib2-devel \ harfbuzz-devel \ libboost_regex-devel \ libcurl-devel \ libicu-devel \ libiniparser-devel \ libjpeg8-devel \ libmemcached-devel \ librados-devel \ libtiff-devel \ libwebp-devel \ libxml2-devel \ procps \ proj-devel \ tar # Create `nobody` user and group sudo useradd --home-dir / --no-create-home --shell /usr/sbin/nologin --system --user-group nobody # Download, Build, Test & Install `mod_tile` export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) rm -rf /tmp/mod_tile_src /tmp/mod_tile_build mkdir /tmp/mod_tile_src /tmp/mod_tile_build cd /tmp/mod_tile_src git clone --depth 1 https://github.com/openstreetmap/mod_tile.git . cd /tmp/mod_tile_build cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON cmake --build . ctest sudo cmake --install . --strip # Create /usr/share/renderd directory sudo mkdir --parents /usr/share/renderd # Copy files of example map sudo cp -av /tmp/mod_tile_src/utils/example-map /usr/share/renderd/example-map # Add configuration sudo cp -av /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf /etc/apache2/conf.d/renderd-example-map.conf printf '\n[example-map]\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-jpg]\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png256]\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png32]\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-webp]\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf # Enable `mod_access_compat` sudo sed -i 's/^APACHE_MODULES="actions/APACHE_MODULES="access_compat actions/g' /etc/sysconfig/apache2 # Start services sudo apache2ctl start sudo renderd -f ``` Then you can visit: `http://localhost:8081/renderd-example-map` mod_tile-0.8.0/docs/build/building_on_ubuntu.md000066400000000000000000000053641474064163400216000ustar00rootroot00000000000000# Building on Ubuntu This document provides users with step-by-step instructions on how to compile and use`mod_tile` and `renderd`. Please see our [Continuous Integration script](/.github/workflows/build-and-test.yml) for more details. A Docker-based building & testing setup pipeline is also available [here](/docker) for your convenience. # Ubuntu 20.04/22.04/24.04 ```shell #!/usr/bin/env bash # Update installed packages sudo apt update && sudo apt --yes upgrade sudo apt --yes install --reinstall ca-certificates # Install build dependencies # (libmemcached-dev & librados-dev are optional) sudo apt --no-install-recommends --yes install \ apache2 \ apache2-dev \ cmake \ curl \ g++ \ gcc \ git \ libcairo2-dev \ libcurl4-openssl-dev \ libglib2.0-dev \ libiniparser-dev \ libmapnik-dev \ libmemcached-dev \ librados-dev # Download, Build, Test & Install `mod_tile` export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) rm -rf /tmp/mod_tile_src /tmp/mod_tile_build mkdir /tmp/mod_tile_src /tmp/mod_tile_build cd /tmp/mod_tile_src git clone --depth 1 https://github.com/openstreetmap/mod_tile.git . cd /tmp/mod_tile_build cmake -B . -S /tmp/mod_tile_src \ -DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_INSTALL_LOCALSTATEDIR:PATH=/var \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_RUNSTATEDIR:PATH=/run \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=/etc \ -DENABLE_TESTS:BOOL=ON cmake --build . ctest sudo cmake --install . --strip # Create /usr/share/renderd directory sudo mkdir --parents /usr/share/renderd # Copy files of example map sudo cp -av /tmp/mod_tile_src/utils/example-map /usr/share/renderd/example-map # Add configuration sudo cp -av /tmp/mod_tile_src/etc/apache2/renderd-example-map.conf /etc/apache2/sites-available/renderd-example-map.conf printf '\n[example-map]\nURI=/tiles/renderd-example\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-jpg]\nTYPE=jpg image/jpeg jpeg\nURI=/tiles/renderd-example-jpg\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png256]\nTYPE=png image/png png256\nURI=/tiles/renderd-example-png256\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-png32]\nTYPE=png image/png png32\nURI=/tiles/renderd-example-png32\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf printf '\n[example-map-webp]\nTYPE=webp image/webp webp\nURI=/tiles/renderd-example-webp\nXML=/usr/share/renderd/example-map/mapnik.xml\n' | sudo tee -a /etc/renderd.conf # Enable configuration a2enmod tile a2ensite renderd-example-map # Start services sudo apache2ctl start sudo renderd -f ``` Then you can visit: `http://localhost:8081/renderd-example-map` mod_tile-0.8.0/docs/man/000077500000000000000000000000001474064163400150275ustar00rootroot00000000000000mod_tile-0.8.0/docs/man/convert_meta.1000066400000000000000000000027731474064163400176100ustar00rootroot00000000000000.TH CONVERT_META "1" "2024-03-16" "mod_tile v0.8.0" .\" Please adjust this date whenever revising the manpage. .SH NAME convert_meta \- A conversion program from .png map tiles to a more efficient conglomerate format (.meta). .SH SYNOPSIS .B convert_meta .RI [ options ] .BR .SH DESCRIPTION This manual page documents briefly the .B convert_meta command. .PP .B convert_meta converts individual .png map tiles into the more efficient .meta file format. The .meta format stores a grid of 8x8 tiles in a single meta tile, reducing the number of files by a factor of 64. .PP .SH OPTIONS This program follows the usual GNU command line syntax, with long options starting with two dashes (`-'). A summary of options is included below. .TP \fB\-t\fR|\-\-tile-dir Specify the base directory where the tiles are stored. The default is /var/cache/renderd/tiles .TP \fB\-m\fR|\-\-map Specify the map style to convert. This specifies the full path to the files and is relative to the tile directory. The default is "default" and results in a full path of /var/cache/renderd/tiles/default to search for the tiles to convert .TP \fB\-u\fR|\-\-unpack Unpack the .meta files back to PNGs .TP \fB\-z\fR|\-\-min-zoom Specify the smallest zoom level to process. The default is 0. .TP \fB\-Z\fR|\-\-max-zoom Specify the largest zoom level up to which to process tiles. The default is 18. .PP .SH AUTHOR convert_meta was written by Jon Burgess and other OpenStreetMap project members. .PP This manual page was written by OpenStreetMap authors. mod_tile-0.8.0/docs/man/openstreetmap-tiles-update-expire.1000066400000000000000000000034321474064163400236710ustar00rootroot00000000000000.TH TILES-UPDATE-EXPIRE "1" "2024-03-16" "mod_tile v0.8.0" .\" Please adjust this date whenever revising the manpage. .SH NAME openstreetmap-tiles-update-expire \- updates a map database from the OSM diff stream. .SH SYNOPSIS .B openstreetmap-tiles-update-expire .RI [ YYYY-MM-DD ] .BR .SH DESCRIPTION This manual page documents briefly the .B openstreetmap-tiles-update-expire command. .PP .B openstreetmap-tiles-update-expire is a helper script to keep a mod_tile based openstreetmap tile server up to date. It downloads a diff stream from OpenStreetMap using osmosis, then applies it to your database using osm2pgsql and finally marks changed tiles on disk as "dirty". .BR Each time you call this script, it does one update cycle. If your db is further behind than the maximum duration for which osmosis is configured to fetch diffs, the db will not be fully up-to-date at the end of this script and it will be necessary to call it multiple times for a fully up-to-date database. .BR This script can be used in conjunction with cron to automatically keep your database in sync with the OSM diff stream. .PP The first time this script needs to be called with a date to initialise the process. Thereafter, the script gets called without arguments. .PP .SH OPTIONS .TP \fBYYYY-MM-DD generation date of the planet file used for import. It initialises osmosis diff processing to this date. It is safe to set this date earlier than the actual date, as one can reapply diffs multiple times. This ensures that there is no missing data due to a gap between the generation of the planet file and the start of the diff processing. .PP .SH SEE ALSO .BR renderd(1) .BR .SH AUTHOR openstreetmap-tiles-update-expire was written by OpenStreetMap project members. .PP This manual page was written by OpenStreetMap authors. mod_tile-0.8.0/docs/man/render_expired.1000066400000000000000000000072251474064163400201160ustar00rootroot00000000000000.TH RENDER_EXPIRED "1" "2024-03-16" "mod_tile v0.8.0" .\" Please adjust this date whenever revising the manpage. .SH NAME render_expired \- expires a list of map tiles so that they get re-rendered. .SH SYNOPSIS .B render_expired .RI [ options ]\ <\ "expire.list" .BR .SH DESCRIPTION This manual page documents briefly the .B render_expired command. .PP .B render_expired is a helper utility that takes a list of map tiles from stdin and expires them such that they will get re-rendered. Render_expired has three potential strategies of how to expire map tiles: .BR 1) Render tiles directly: Render_expired can connect to the renderd socket and submit rendering requests for expired tiles directly .BR 2) Delete tiles: Render_expired can delete expired tiles from disk. The next time the tile then gets viewed it will get re-rendered, assuming a dynamic rendering setup like mod_tile is installed .BR 3) Mark tiles as dirty: A dynamic tile rendering system like mod_tile decides if a tile needs re-rendering by comparing the timestamp of the tile with the time of the planet-import-complet timestamp. Render_expired can set the timestamp of a tile back many years, ensuring it is older than the db import time, thus causeing the tile to be considered dirty and in need for re-render. .PP These three strategies can be combined and applied at different zoom levels. E.g. Zoom level 17-18 get deleted, z11 - z16 get marked dirty and z6 - z10 get rendered directly. .PP Render_expired takes a list of tiles from stdin which should be expired. The format of the list is one tile per line specified as z/x/y. .sp 0 1/0/1 .sp 0 1/1/1 .sp 0 1/0/0 .sp 0 1/1/0 .PP render_expired will automatically expand the list to cover the effected tiles at other zoom levels. .PP .SH OPTIONS This program follows the usual GNU command line syntax, with long options starting with two dashes (`-'). A summary of options is included below. .TP \fB\-c\fR|\-\-config=CONFIG Specify a `renderd.conf` file from which to load various option values rather than specifying via command line. .sp 0 The following options will be set (if present); however, they can be overridden if also specified: .sp 0 \fB\-s\fR|\-\-socket .sp 0 \fB\-n\fR|\-\-num-threads .sp 0 \fB\-t\fR|\-\-tile-dir .sp 0 \fB\-z\fR|\-\-min-zoom .sp 0 \fB\-Z\fR|\-\-max-zoom .TP \fB\-m\fR|\-\-map=MAP Specify the style-sheet for which to expire tiles. The default is "default". .TP \fB\-s\fR|\-\-socket=SOCKET|HOSTNAME:PORT Specify the location of the renderd socket or hostname and port to connect to. .TP \fB\-n\fR|\-\-num-threads=N Specify the number of parallel requests to renderd. Should renderd have less threads active, requests will be queued. The default is 1. .TP \fB\-t\fR|\-\-tile-dir=DIR Specify the base directory where the rendered tiles are. The default is '/var/cache/renderd/tiles' .TP \fB\-z\fR|\-\-min-zoom=ZOOM Filter input to only render tiles greater than or equal to this zoom level (default is '0'). .TP \fB\-Z\fR|\-\-max-zoom=ZOOM Filter input to only render tiles less than or equal to this zoom level (default is '20'). .TP \fB\-d\fR|\-\-delete-from=ZOOM When expiring tiles of ZOOM or higher, delete them instead of re-rendering (default is off) .TP \fB\-T\fR|\-\-touch-from=ZOOM When expiring tiles of ZOOM or higher, touch them instead of re-rendering (default is off) .TP \fB\-N\fR|\-\-no-progress Disable display of progress messages (default is off) .TP \fB\-h\fR|\-\-help Print out a help text for render_expired .TP \fB\-V\fR|\-\-version Print out the version number for render_expired .PP .SH SEE ALSO .BR renderd(1) .BR .SH AUTHOR render_expire was written by OpenStreetMap project members. .PP This manual page was written by OpenStreetMap authors. mod_tile-0.8.0/docs/man/render_list.1000066400000000000000000000061261474064163400174300ustar00rootroot00000000000000.TH RENDER_LIST "1" "2024-03-20" "mod_tile v0.8.0" .\" Please adjust this date whenever revising the manpage. .SH NAME render_list \- renders a list of map tiles by sending requests to a rendering daemon. .SH SYNOPSIS .B render_list .RI [ options ]\ <\ "render.list" .BR .SH DESCRIPTION This manual page briefly documents the .B render_list command. .PP .B render_list is a helper utility that takes a list of map tiles from stdin and sends the requests to a rendering daemon .PP .SH OPTIONS This program follows the usual GNU command line syntax, with long options starting with two dashes (`-'). A summary of options is included below. .TP \fB\-a\fR|\-\-all Render all tiles in given zoom level range instead of reading from STDIN. .TP \fB\-c\fR|\-\-config=CONFIG Specify a `renderd.conf` file from which to load various option values rather than specifying via command line. .sp 0 The following options will be set (if present); however, they can be overridden if also specified: .sp 0 \fB\-s\fR|\-\-socket .sp 0 \fB\-n\fR|\-\-num-threads .sp 0 \fB\-t\fR|\-\-tile-dir .sp 0 \fB\-z\fR|\-\-min-zoom .sp 0 \fB\-Z\fR|\-\-max-zoom .TP \fB\-f\fR|\-\-force Render tiles even if they seem current. .TP \fB\-m\fR|\-\-map=MAP Render tiles in this map (default is 'default'). .TP \fB\-l\fR|\-\-max-load=LOAD Sleep if load is this high (default is '16'). .TP \fB\-s\fR|\-\-socket=SOCKET|HOSTNAME:PORT Unix domain socket name or hostname and port for contacting renderd (default is '/run/renderd/renderd.sock'). .TP \fB\-n\fR|\-\-num-threads=N The number of parallel request threads (default is '1'). .TP \fB\-t\fR|\-\-tile-dir=TILE_DIR Tile cache directory (default is '/var/cache/renderd/tiles'). .TP \fB\-z\fR|\-\-min-zoom=ZOOM Filter input to only render tiles greater than or equal to this zoom level (default is '0'). .TP \fB\-Z\fR|\-\-max-zoom=ZOOM Filter input to only render tiles less than or equal to this zoom level (default is '20'). .TP \fB\-h\fR|\-\-help Print out a help text for render_list .TP \fB\-V\fR|\-\-version Print out the version number for render_list .PP If you are using \fB\-a\fR|\-\-all, you can restrict the tile range by adding these options: .sp 0 (please note that tile coordinates must be positive integers and are not latitude and longitude values) .PP \fB\-g\fR|\-\-min-lat=LATITUDE minimum latitude .BR \fB\-G\fR|\-\-max-lat=LATITUDE maximum latitude .BR \fB\-w\fR|\-\-min-lon=LONGITUDE minimum longitude .BR \fB\-W\fR|\-\-max-lon=LONGITUDE maximum longitude .BR \fB\-x\fR|\-\-min-x=X minimum X tile coordinate .BR \fB\-X\fR|\-\-max-x=X maximum X tile coordinate .BR \fB\-y\fR|\-\-min-y=Y minimum Y tile coordinate .BR \fB\-Y\fR|\-\-max-y=Y maximum Y tile coordinate .PP Without \fB\-a\fR|\-\-all, send a list of tiles to be rendered from STDIN in the format: .BR X Y Z .BR e.g. .BR 0 0 1 .BR 0 1 1 .BR 1 0 1 .BR 1 1 1 .PP The above would cause all 4 tiles at zoom 1 to be rendered .PP .SH SEE ALSO .BR renderd(1) .BR .SH AUTHOR render_list was written by OpenStreetMap project members. .PP This manual page was written by OpenStreetMap authors. mod_tile-0.8.0/docs/man/render_old.1000066400000000000000000000012041474064163400172230ustar00rootroot00000000000000.TH RENDER_OLD "1" "2024-03-16" "mod_tile v0.8.0" .\" Please adjust this date whenever revising the manpage. .SH NAME render_old \- renders a list of map tiles by sending requests to a rendering daemon. .SH SYNOPSIS .B render_list .RI [ options ] < "render.list" .BR .SH DESCRIPTION This manual page documents briefly the .B render_old command. .PP .B render_old is a helper utility that pre renders expired map tiles by sending appropriate requests to a rendering daemon .PP .SH SEE ALSO .BR renderd(1) .BR .SH AUTHOR render_expire was written by OpenStreetMap project members. .PP This manual page was written by OpenStreetMap authors. mod_tile-0.8.0/docs/man/render_speedtest.1000066400000000000000000000026331474064163400204540ustar00rootroot00000000000000.TH RENDER_SPEEDTEST "1" "2024-03-16" "mod_tile v0.8.0" .\" Please adjust this date whenever revising the manpage. .SH NAME render_speedtest \- Benchmark tile rendering with renderd. .SH SYNOPSIS .B render_speedtest .RI [ options ] .SH DESCRIPTION This manual page documents briefly the .B render_speedtest command. .PP .B render_speedtest renders a bunch of tiles at various zoom levels to benchmark the speed of the rendering. .PP .SH OPTIONS These programs follow the usual GNU command line syntax, with long options starting with two dashes (`-'). A summary of options is included below. .TP \fB\-s\fR|\-\-socket=SOCKET|HOSTNAME:PORT Specify the location of the renderd socket or hostname and port to connect to. .TP \fB\-m\fR|\-\-map=MAP Specify the rendering style to test. The default is "default" .TP \fB\-n\fR|\-\-num-threads=N The number of parallel request threads (default 1). .TP \fB\-z\fR|\-\-min-zoom=ZOOM Only render tiles greater or equal to this zoom level (default is 0). .TP \fB\-Z\fR|\-\-max-zoom=ZOOM Only render tiles less than or equal to this zoom level (default is 20). .TP \fB\-h\fR|\-\-help Print out a help text for render_speedtest .TP \fB\-V\fR|\-\-version Print out the version number for render_speedtest .PP .SH SEE ALSO .BR renderd(1) .BR .SH AUTHOR render_speedtest was written by Jon Burgess and other OpenStreetMap project members. .PP This manual page was written by OpenStreetMap authors. mod_tile-0.8.0/docs/man/renderd.1000066400000000000000000000032221474064163400165330ustar00rootroot00000000000000.TH RENDERD "1" "2024-03-16" "mod_tile v0.8.0" .\" Please adjust this date whenever revising the manpage. .SH NAME renderd \- Rendering daemon for rendering OpenStreetMap tiles. .SH SYNOPSIS .B renderd .RI [ options ] .BR .SH DESCRIPTION This manual page documents briefly the .B renderd command. .PP .B renderd is a rendering daemon for map tiles with the mapnik library. It receives render requests on a socket. Renderd queues requests in a number of different queues to manage load while rendering the requests with the mapnik library. By default renderd will start as a daemon. It will log information in the syslog. .PP .SH OPTIONS This programs follow the usual GNU command line syntax, with long options starting with two dashes (`-'). A summary of options is included below. .TP \fB\-f\fR|\-\-foreground Run renderd in the foreground for debugging purposes. .TP \fB\-c\fR|\-\-config Set the location of the config file used to configure the various parameters of renderd, like the mapnik style sheet. The default is /etc/renderd.conf .TP \fB\-s\fR|\-\-slave Renderd can be used in a distributed fashion across multiple rendering servers. The master renderd handles queuing and passes requests to the slaves. This parameter specifies which of the slave sections of renderd.conf applies to this instance of renderd. The default is to use the master section .TP \fB\-h\fR|\-\-help Print out a help text for renderd .TP \fB\-V\fR|\-\-version Print out the version number for renderd .PP .SH SEE ALSO .BR renderd.conf(5) .BR .SH AUTHOR renderd was written by Jon Burgess, and other OpenStreetMap project members. .PP This manual page was written by OpenStreetMap authors. mod_tile-0.8.0/docs/man/renderd.conf.5000066400000000000000000000146571474064163400175010ustar00rootroot00000000000000.TH RENDERD.CONF 5 "2024-06-06" "mod_tile v0.8.0" .\" Please adjust this date whenever revising the manpage. .SH NAME renderd.conf \- Configuration file of rendering daemon for rendering OpenStreetMap tiles. .SH SYNOPSIS renderd.conf .SH DESCRIPTION This manual page documents briefly the \fBrenderd.conf\fR configuration file. .PP \fBrenderd.conf\fR is an INI configuration file for use with \fBrenderd(1)\fR. .PP The default location is \fB'/etc/renderd.conf'\fR (macro definition \fB'RENDERD_CONFIG'\fR). .PP All option names are \fBcase-insensitive\fR. .SH RENDERD Options belonging to a \fB[renderd]\fR section. .PP There must be at least one \fB[renderd]\fR section (e.g. \fB[renderd]\fR or \fB[renderd0]\fR). .PP By default, there can be up to \fB5\fR (macro definition \fB'MAX_SLAVES'\fR) \fB[renderd]\fR sections (e.g. \fB[renderd0]\fR - \fB[renderd4]\fR). .PP \fB[renderd]\fR section names must begin with \fB[renderd]\fR. .TP .B iphostname Specify the IP address/hostname to be used for communication with \fBrenderd\fR. This must be specified in combination with \fBipport\fR. This option and \fBsocketname\fR are mutually exclusive. .TP .B ipport Specify the port number to be used for communication with \fBrenderd\fR. This must be specified in combination with \fBiphostname\fR. This option and \fBsocketname\fR are mutually exclusive. .TP .B num_threads Specify the number of threads to be used for \fBrenderd\fR. A value of \fB'-1'\fR will configure \fBnum_threads\fR to the number of cores on the system. The default value is \fB'4'\fR (macro definition \fB'NUM_THREADS'\fR). .TP .B pid_file Specify the file path into which the PID will be written by \fBrenderd\fR. It is only written to when \fBrenderd\fR is not running in \fBforeground\fR mode (e.g. without \fB'--foreground'\fR / \fB'-f')\fR. The default value is \fB'/run/renderd/renderd.pid'\fR (macro definition \fB'RENDERD_PIDFILE'\fR). .TP .B socketname Specify the file path to be used as a unix domain socket for communication with \fBrenderd\fR. This option and \fBiphostname\fR / \fBipport\fR are mutually exclusive. The default value is \fB'/run/renderd/renderd.sock'\fR (macro definition \fB'RENDERD_SOCKET'\fR). .TP .B stats_file Specify the file path into which statistics will be written by \fBrenderd\fR. By default, a stats file will not be created. .TP .B tile_dir Specify the directory path into which tiles will be written by \fBrenderd\fR. The default value is \fB'/var/cache/renderd/tiles'\fR (macro definition \fB'RENDERD_TILE_DIR'\fR). .SH MAPNIK Options belonging to the \fB[mapnik]\fR section. .PP There can be only one \fB[mapnik]\fR section. .PP \fB[mapnik]\fR section name must be equal to \fB[mapnik]\fR. .TP .B font_dir Specify the directory path where fonts are searched for by \fBlibmapnik\fR. The default value is the output of \fB'mapnik-config --fonts'\fR / \fB'pkgconf --variable=fonts_dir libmapnik'\fR (macro definition \fB'MAPNIK_FONTS_DIR'\fR). .TP .B font_dir_recurse Specify whether or not to recurse the \fBfont_dir\fR when searching for fonts by \fBlibmapnik\fR. The default value is \fB'false'\fR / \fB'0'\fR (macro definition \fB'MAPNIK_FONTS_DIR_RECURSE'\fR). .TP .B plugins_dir Specify the directory path where input plugins are searched for by \fBlibmapnik\fR. The default value is the output of \fB'mapnik-config --input-plugins'\fR / \fB'pkgconf --variable=plugins_dir libmapnik'\fR (macro definition \fB'MAPNIK_PLUGINS_DIR'\fR). .SH MAP SECTION Options belonging a \fB[map]\fR section. .PP There must be at least one \fB[map]\fR section. .PP \fB[map]\fR section names can neither begin with \fB[renderd]\fR nor be equal to \fB[mapnik]\fR. .PP \fB[map]\fR section names can be anything else, but they must all be unique. .TP .B aspectx Specify the X aspect to be used by \fBmod_tile\fR. Only used by \fBmod_tile\fR. The default value is \fB'1'\fR. .TP .B aspecty Specify the Y aspect to be used by \fBmod_tile\fR. Only used by \fBmod_tile\fR. The default value is \fB'1'\fR. .TP .B attribution Specify the data attribution to be provided by \fBmod_tile\fR as \fBTileJSON\fR (via \fB{URI}/tile-layer.json\fR). Only used by \fBmod_tile\fR. The default value is \fB'©OpenStreetMap and contributors, (ODbL)'\fR (macro definition \fB'DEFAULT_ATTRIBUTION'\fR). .TP .B cors Specify the CORS configuration for \fBmod_tile\fR. Only used by \fBmod_tile\fR. .TP .B description Specify the description to be provided by \fBmod_tile\fR as \fBTileJSON\fR (via \fB{URI}/tile-layer.json\fR). Only used by \fBmod_tile\fR. .TP .B htcphost Specify the IP address/hostname of the HTCP Host to be used by \fBrenderd\fR for HTCP cache expiry. Only used by \fBrenderd\fR. .TP .B host Specify the IP address/hostname of the Host to be used by \fBrenderd\fR for HTCP cache expiry. Only used by \fBrenderd\fR. .TP .B maxzoom Specify the maximum zoom level for this section. The default value is \fB'20'\fR (macro definition \fB'MAX_ZOOM'\fR). .TP .B minzoom Specify the minimum zoom level for this section. The default value is \fB'0'\fR. .TP .B parameterize_style Specify the parameterization style/function to be used for this section. The value of \fB'language'\fR seems to be the only one supported. .TP .B server_alias Specify a URL alias of this server to be provided by \fBmod_tile\fR as \fBTileJSON\fR (via \fB{URI}/tile-layer.json\fR). Only used by \fBmod_tile\fR. .TP .B scale Specify the scale for this section. Only used by \fBrenderd\fR. The default value is \fB'1.0'\fR. .TP .B tiledir Specify the directory path into which tiles will be written by \fBrenderd\fR. The default value is \fB'/var/cache/renderd/tiles'\fR (macro definition \fB'RENDERD_TILE_DIR'\fR). .TP .B tilesize Specify the tile size for this section. Only used by \fBrenderd\fR. The default value is \fB'256'\fR. .TP .B type Specify the tile configuration (in the format \fB' '\fR) for this section (e.g. \fB'png image/png png256'\fR). .TP .B uri Specify the URI prefix with which tiles can be accessed for this section. .TP .B xml Specify the file path of the Mapnik configuration XML file for this section. Only used by \fBrenderd\fR. .SH SEE ALSO .BR renderd(1) .BR .SH AUTHOR renderd was written by Jon Burgess, and other OpenStreetMap project members. .PP This manual page was written by OpenStreetMap authors. mod_tile-0.8.0/etc/000077500000000000000000000000001474064163400140775ustar00rootroot00000000000000mod_tile-0.8.0/etc/apache2/000077500000000000000000000000001474064163400154025ustar00rootroot00000000000000mod_tile-0.8.0/etc/apache2/renderd-example-map.conf000066400000000000000000000206451474064163400221070ustar00rootroot00000000000000Alias /renderd-example-map /usr/share/renderd/example-map Redirect /renderd-example-map/leaflet/leaflet.css https://unpkg.com/leaflet/dist/leaflet.css Redirect /renderd-example-map/leaflet/leaflet.min.js https://unpkg.com/leaflet/dist/leaflet.js Options +FollowSymLinks AllowOverride All Order Allow,Deny Allow from all Require all granted Listen 8081 # Specify the location under which (meta)tiles are stored. # This can be a directory path, or, when using storage backends other than "file", a URI. # # I.E.: # "memcached://{memcached_host}:{memcached_port}" for MemcacheD ModTileTileDir /var/cache/renderd/tiles # You can manually configure each tile set with AddTileConfig. # The first argument is the URL path relative to this virtual host # under which a tile set is served. The second argument specifies the # name of the tile set. This is used in the communication with renderd # and is the directory under which (meta)tiles are stored on disk. # # By default (AddTileConfig) mod_tile assumes you are serving png files, however, # mod_tile can also serve arbitrary other tile types such as javascript vector tiles, # assuming the backend render daemon can handle the type. # To this purpose AddTileConfig also accepts additional arguments in the form of # key=value pairs, with the following keys currently being supported: # * extension # * maxzoom # * mimetype # * minzoom # * tile_dir # #AddTileConfig /folder/ TileSetName #AddTileConfig /folder2/ TileSetName2 extension=js mimetype=text/javascript # Alternatively (or in addition) you can load all the tile sets defined in the configuration file into this virtual host LoadTileConfigFile /etc/renderd.conf # Specify if mod_tile should keep tile delivery stats, which can be accessed from the URL /mod_tile # The default is On. As keeping stats needs to take a lock, this might have some performance impact, # but for nearly all intents and purposes this should be negligable and so it is safe to keep this turned on. ModTileEnableStats On # Turns on bulk mode. In bulk mode, mod_tile does not request any dirty tiles to be rerendered. Missing tiles # are always requested in the lowest priority. The default is Off. ModTileBulkMode Off # Timeout before giving up for a tile to be rendered ModTileRequestTimeout 3 # Timeout before giving up for a tile to be rendered that is otherwise missing ModTileMissingRequestTimeout 10 # If tile is out of date, don't re-render it if past this load threshold (users gets old tile) ModTileMaxLoadOld 2 # If tile is missing, don't render it if past this load threshold (user gets 404 error) ModTileMaxLoadMissing 5 # Socket where we connect to the rendering daemon ModTileRenderdSocketName /run/renderd/renderd.sock # Options controlling the cache proxy expiry headers. All values are in seconds. # # Caching is both important to reduce the load and bandwidth of the server, as # well as reduce the load time for the user. The site loads fastest if tiles can be # taken from the users browser cache and no round trip through the internet is needed. # With minutely or hourly updates, however there is a trade-off between cacheability # and freshness. As one can't predict the future, these are only heuristics, that # need tuning. # If there is a known update schedule such as only using weekly planet dumps to update the db, # this can also be taken into account through the constant PLANET_INTERVAL in render_config.h # but requires a recompile of mod_tile # The values in this sample configuration are not the same as the defaults # that apply if the config settings are left out. The defaults are more conservative # and disable most of the heuristics. # Caching is always a trade-off between being up to date and reducing server load or # client side latency and bandwidth requirements. Under some conditions, like poor # network conditions it might be more important to have good caching rather than the latest tiles. # Therefor the following config options allow to set a special hostheader for which the caching # behaviour is different to the normal heuristics # # The CacheExtended parameters overwrite all other caching parameters (including CacheDurationMax) # for tiles being requested via the hostname CacheExtendedHostname # #ModTileCacheExtendedHostname cache.tile.openstreetmap.org #ModTileCacheExtendedDuration 2592000 # Upper bound on the length a tile will be set cacheable, which takes # precedence over other settings of cacheing ModTileCacheDurationMax 604800 # Sets the time tiles can be cached for that are known to by outdated and have been # sent to renderd to be rerendered. This should be set to a value corresponding # roughly to how long it will take renderd to get through its queue. There is an additional # fuzz factor on top of this to not have all tiles expire at the same time ModTileCacheDurationDirty 900 # Specify the minimum time mod_tile will set the cache expiry to for fresh tiles. There # is an additional fuzz factor of between 0 and 3 hours on top of this. ModTileCacheDurationMinimum 10800 # Lower zoom levels are less likely to change noticeable, so these could be cached for longer # without users noticing much. # The heuristic offers three levels of zoom, Low, Medium and High, for which different minimum # cacheing times can be specified. #Specify the zoom level below which Medium starts and the time in seconds for which they can be cached ModTileCacheDurationMediumZoom 13 86400 #Specify the zoom level below which Low starts and the time in seconds for which they can be cached ModTileCacheDurationLowZoom 9 518400 # A further heuristic to determine cacheing times is when was the last time a tile has changed. # If it hasn't changed for a while, it is less likely to change in the immediate future, so the # tiles can be cached for longer. # For example, if the factor is 0.20 and the tile hasn't changed in the last 5 days, it can be cached # for up to one day without having to re-validate. ModTileCacheLastModifiedFactor 0.20 # Tile Throttling # Tile scrapers can often download large numbers of tiles and overly strain tileserver resources # mod_tile therefore offers the ability to automatically throttle requests from ip addresses that have # requested a lot of tiles. # The mechanism uses a token bucket approach to shape traffic. I.e. there is an initial pool of n tiles # per ip that can be requested arbitrarily fast. After that this pool gets filled up at a constant rate # The algorithm has two metrics. One based on overall tiles served to an ip address and a second one based on # the number of requests to renderd / tirex to render a new tile. # Overall enable or disable tile throttling ModTileEnableTileThrottling Off # Specify if you want to use the connecting IP for throtteling, or use the X-Forwarded-For header to determin the # 1 - use the client IP address, i.e. the first entry in the X-Forwarded-For list. This works through a cascade of proxies. # However, as the X-Forwarded-For is written by the client this is open to manipulation and can be used to circumvent the throttling # 2 - use the last specified IP in the X-Forwarded-For list. If you know all requests come through a reverse proxy # that adds an X-Forwarded-For header, you can trust this IP to be the IP the reverse proxy saw for the request ModTileEnableTileThrottlingXForward 0 # Parameters (poolsize in tiles and topup rate in tiles per second) for throttling tile serving. ModTileThrottlingTiles 10000 1 # Parameters (poolsize in tiles and topup rate in tiles per second) for throttling render requests. ModTileThrottlingRenders 128 0.2 # Enable the .../Z/X/Y.ext/status URL, which shows details of that tile. # Off = a 404 is returned for that url instead. # Default: On #ModTileEnableStatusURL Off # Enable the .../Z/X/Y.ext/dirty URL, which marks that tile as dirty. # Off = a 404 is returned for that url instead. # Default: On #ModTileEnableDirtyURL Off mod_tile-0.8.0/etc/apache2/tile.load.in000066400000000000000000000000761474064163400176100ustar00rootroot00000000000000LoadModule tile_module @CMAKE_INSTALL_MODULESDIR@/mod_tile.so mod_tile-0.8.0/etc/renderd/000077500000000000000000000000001474064163400155225ustar00rootroot00000000000000mod_tile-0.8.0/etc/renderd/renderd.conf000066400000000000000000000005071474064163400200160ustar00rootroot00000000000000; BASIC AND SIMPLE CONFIGURATION: [renderd] pid_file=/run/renderd/renderd.pid stats_file=/run/renderd/renderd.stats socketname=/run/renderd/renderd.sock num_threads=4 tile_dir=/var/cache/renderd/tiles [mapnik] plugins_dir=/usr/lib/mapnik/3.1/input font_dir=/usr/share/fonts/truetype font_dir_recurse=true ; ADD YOUR LAYERS: mod_tile-0.8.0/etc/renderd/renderd.conf.examples000066400000000000000000000067341474064163400216430ustar00rootroot00000000000000; EXAMPLES FOR BASIC CONFIGURATION OPTIONS [renderd] pid_file=/run/renderd/renderd.pid stats_file=/run/renderd/renderd.stats socketname=/run/renderd/renderd.sock num_threads=4 tile_dir=/var/cache/renderd/tiles ;[renderd] ;iphostname=::1 ;ipport=7654 ;num_threads=4 ;tile_dir=rados://tiles/etc/ceph/ceph.conf ;pid_file=/run/renderd/renderd_rados.pid ;stats_file=/run/renderd/renderd.stats ;[renderd] ;iphostname=::1 ;ipport=7654 ;num_threads=8 ;tile_dir=memcached:// ; Defaults to "localhost:11211" when host:port is not specified ;pid_file=/run/renderd/renderd_memcached.pid ;stats_file=/run/renderd/renderd.stats ;[renderd] ;iphostname=::1 ;ipport=7654 ;num_threads=8 ;tile_dir=memcached://memcached_host:11212 ; You may also specify a custom host:port ;pid_file=/run/renderd/renderd_memcached.pid ;stats_file=/run/renderd/renderd.stats ; EXAMPLE FOR MAPNIK CONFIGURATION OPTION [mapnik] plugins_dir=/usr/lib/mapnik/3.0/input font_dir=/usr/share/fonts/truetype font_dir_recurse=true ; EXAMPLES FOR LAYER CONFIGURATION OPTIONS [example-map] URI=/tiles/renderd-example XML=/var/www/example-map/mapnik.xml ;[style1] ;URI=/osm_tiles/ ;TILEDIR=/var/cache/renderd/tiles ;XML=/usr/share/renderd/openstreetmap/osm-local.xml ;HOST=tile.openstreetmap.org ;TILESIZE=256 ;HTCPHOST=proxy.openstreetmap.org ;** config options used by mod_tile, but not renderd ** ;MINZOOM=0 ;MAXZOOM=18 ;TYPE=png image/png png256 ; Values are: (for more information about output format see https://github.com/mapnik/mapnik/wiki/Image-IO) ;DESCRIPTION=This is a description of the tile layer used in the tile json request ;ATTRIBUTION=©OpenStreetMap and contributors, ODbL ;SERVER_ALIAS=http://localhost/ ;CORS=http://www.openstreetmap.org ;ASPECTX=1 ;ASPECTY=1 ;SCALE=1.0 ;[style2] ;URI=/osm_tiles2/ ;TILEDIR=rados://tiles/etc/ceph/ceph.conf ;TILESIZE=512 ;XML=/usr/share/renderd/openstreetmap/osm-local2.xml ;HOST=tile.openstreetmap.org ;HTCPHOST=proxy.openstreetmap.org ;** config options used by mod_tile, but not renderd ** ;MINZOOM=0 ;MAXZOOM=22 ;TYPE=png image/png png256 ; Values are: (for more information about output format see https://github.com/mapnik/mapnik/wiki/Image-IO) ;DESCRIPTION=This is a description of the tile layer used in the tile json request ;ATTRIBUTION=©OpenStreetMap and contributors, ODbL ;SERVER_ALIAS=http://localhost/ ;CORS=* ;[style3] ;URI=/osm_tiles3/ ;TILEDIR=memcached:// ;TILESIZE=512 ;XML=/usr/share/renderd/openstreetmap/osm-local3.xml ;HOST=tile.openstreetmap.org ;HTCPHOST=proxy.openstreetmap.org ;** config options used by mod_tile, but not renderd ** ;MINZOOM=0 ;MAXZOOM=22 ;TYPE=png image/png png256 ; Values are: (for more information about output format see https://github.com/mapnik/mapnik/wiki/Image-IO) ;DESCRIPTION=This is a description of the tile layer used in the tile json request ;ATTRIBUTION=©OpenStreetMap and contributors, ODbL ;SERVER_ALIAS=http://localhost/ ;CORS=* mod_tile-0.8.0/etc/renderd/renderd.conf.in000066400000000000000000000003731474064163400204240ustar00rootroot00000000000000[renderd] pid_file=@RENDERD_PIDFILE@ socketname=@RENDERD_SOCKET@ stats_file=@RENDERD_RUN_DIR@/renderd.stats tile_dir=@RENDERD_TILE_DIR@ [mapnik] font_dir=@MAPNIK_FONTS_DIR@ font_dir_recurse=@MAPNIK_FONTS_DIR_RECURSE@ plugins_dir=@MAPNIK_PLUGINS_DIR@ mod_tile-0.8.0/includes/000077500000000000000000000000001474064163400151325ustar00rootroot00000000000000mod_tile-0.8.0/includes/cache_expire.h000066400000000000000000000020261474064163400177220ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef CACHEEXPIRE_H #define CACHEEXPIRE_H #ifdef __cplusplus extern "C" { #endif #define HTCP_EXPIRE_CACHE 1 #define HTCP_EXPIRE_CACHE_PORT "4827" void cache_expire(int sock, const char *host, const char *uri, int x, int y, int z); int init_cache_expire(const char *htcphost); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/config.h.in000066400000000000000000000021051474064163400171530ustar00rootroot00000000000000#ifndef CONFIG_H #define CONFIG_H /* Define to 1 if you have the functions. */ #cmakedefine HAVE_DAEMON @HAVE_DAEMON@ #cmakedefine HAVE_GETLOADAVG @HAVE_GETLOADAVG@ /* Define to 1 if you have the header files. */ #cmakedefine HAVE_PATHS_H @HAVE_PATHS_H@ #cmakedefine HAVE_PTHREAD @HAVE_PTHREAD@ #cmakedefine HAVE_SYS_CDEFS_H @HAVE_SYS_CDEFS_H@ #cmakedefine HAVE_SYS_LOADAVG_H @HAVE_SYS_LOADAVG_H@ /* Define to 1 if you have the libraries. */ #cmakedefine HAVE_CAIRO @HAVE_CAIRO@ #cmakedefine HAVE_LIBCURL @HAVE_LIBCURL@ #cmakedefine HAVE_LIBMEMCACHED @HAVE_LIBMEMCACHED@ #cmakedefine HAVE_LIBRADOS @HAVE_LIBRADOS@ /* Define configuration options. */ #cmakedefine MAPNIK_FONTS_DIR "@MAPNIK_FONTS_DIR@" #cmakedefine MAPNIK_FONTS_DIR_RECURSE "@MAPNIK_FONTS_DIR_RECURSE@" #cmakedefine MAPNIK_PLUGINS_DIR "@MAPNIK_PLUGINS_DIR@" #cmakedefine RENDERD_CONFIG "@RENDERD_CONFIG@" #cmakedefine RENDERD_PIDFILE "@RENDERD_PIDFILE@" #cmakedefine RENDERD_SOCKET "@RENDERD_SOCKET@" #cmakedefine RENDERD_TILE_DIR "@RENDERD_TILE_DIR@" /* Version number of project */ #cmakedefine VERSION "@VERSION@" #endif mod_tile-0.8.0/includes/g_logger.h000066400000000000000000000017361474064163400170770ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef G_LOGGER_H #define G_LOGGER_H #include #ifdef __cplusplus extern "C" { #endif extern int foreground; void g_logger(int log_level, const char *format, ...); const char *g_logger_level_name(int log_level); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/gen_tile.h000066400000000000000000000026621474064163400170770ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef GEN_TILE_H #define GEN_TILE_H #include "protocol.h" #ifdef __cplusplus extern "C" { #endif enum queueEnum { queueRequest, queueRequestPrio, queueRequestBulk, queueDirty, queueRender, queueDuplicate, queueRequestLow }; struct item { struct item *next; struct item *prev; struct protocol req; int mx, my; int fd; struct item *duplicates; enum queueEnum inQueue; enum queueEnum originatedQueue; }; // int render(Map &m, int x, int y, int z, const char *filename); void *render_thread(void *); struct item *fetch_request(void); void delete_request(struct item *item); void render_init(const char *plugins_dir, const char *font_dir, int font_recurse); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/metatile.h000066400000000000000000000035561474064163400171200ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef METATILE_H #define METATILE_H #include #include "config.h" #include "render_config.h" #ifdef __cplusplus #include extern "C" { #endif #define META_MAGIC "META" #define META_MAGIC_COMPRESSED "METZ" struct entry { int offset; int size; }; struct meta_layout { char magic[4]; int count; // METATILE ^ 2 int x, y, z; // lowest x,y of this metatile, plus z struct entry index[]; // count entries // Followed by the tile data // The index offsets are measured from the start of the file }; #ifdef __cplusplus } class metaTile { public: metaTile(const std::string &xmlconfig, const std::string &options, int x, int y, int z); void clear(); void set(int x, int y, const std::string &data); const std::string get(int x, int y); int xyz_to_meta_offset(int x, int y, int z); void save(struct storage_backend *store); void expire_tiles(int sock, const char *host, const char *uri); private: int x_, y_, z_; std::string xmlconfig_; std::string options_; std::string tile[METATILE][METATILE]; static const int header_size = sizeof(struct meta_layout) + (sizeof(struct entry) * (METATILE * METATILE)); }; #endif #endif mod_tile-0.8.0/includes/mod_tile.h000066400000000000000000000106071474064163400171030ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef MODTILE_H #define MODTILE_H #include "protocol.h" #include "store.h" #include #include /*Size of the delaypool hashtable*/ #define DELAY_HASHTABLE_SIZE 100057 #define DELAY_HASHTABLE_WHITELIST_SIZE 13 /*Number of tiles in the bucket */ #define AVAILABLE_TILE_BUCKET_SIZE 5000 /*Number of render request in the bucket */ #define AVAILABLE_RENDER_BUCKET_SIZE 65 /*Number of microseconds per render request. Currently set at no more than 1 render request per 5 seconds on average */ #define RENDER_TOPUP_RATE 5000000l /*Number of microseconds per render request. Currently set at no more than 1 request per second on average */ #define TILE_TOPUP_RATE 1000000l #define INILINE_MAX 256 #define MAX_ZOOM_SERVER 30 #define FRESH 1 #define OLD 2 #define FRESH_RENDER 3 #define OLD_RENDER 4 #define VERYOLD_RENDER 5 #define VERYOLD 6 /* Number of microseconds to camp out on the mutex */ #define CAMPOUT 10 /* Maximum number of times we camp out before giving up */ #define MAXCAMP 10 #define DEFAULT_ATTRIBUTION "©OpenStreetMap and contributors, (ODbL)" typedef struct delaypool_entry { struct in6_addr ip_addr; int available_tiles; int available_render_req; } delaypool_entry; typedef struct delaypool { delaypool_entry users[DELAY_HASHTABLE_SIZE]; in_addr_t whitelist[DELAY_HASHTABLE_WHITELIST_SIZE]; apr_time_t last_tile_fillup; apr_time_t last_render_fillup; int locked; } delaypool; typedef struct stats_data { apr_uint64_t noResp200; apr_uint64_t noResp304; apr_uint64_t noResp404; apr_uint64_t noResp503; apr_uint64_t noResp5XX; apr_uint64_t noRespOther; apr_uint64_t noFreshCache; apr_uint64_t noFreshRender; apr_uint64_t noOldCache; apr_uint64_t noOldRender; apr_uint64_t noVeryOldCache; apr_uint64_t noVeryOldRender; apr_uint64_t noRespZoom[MAX_ZOOM_SERVER + 1]; apr_uint64_t totalBufferRetrievalTime; apr_uint64_t noTotalBufferRetrieval; apr_uint64_t zoomBufferRetrievalTime[MAX_ZOOM_SERVER + 1]; apr_uint64_t noZoomBufferRetrieval[MAX_ZOOM_SERVER + 1]; apr_uint64_t *noResp200Layer; apr_uint64_t *noResp404Layer; } stats_data; typedef struct { char **hostnames; const char *attribution; const char *baseuri; const char *cors; const char *description; const char *fileExtension; const char *mimeType; const char *store; const char *xmlname; int aspect_x; int aspect_y; int enableOptions; int maxzoom; int minzoom; int noHostnames; } tile_config_rec; typedef struct { apr_array_header_t *configs; apr_time_t very_old_threshold; const char *cache_extended_hostname; const char *renderd_socket_name; const char *tile_dir; double cache_duration_last_modified_factor; int cache_duration_dirty; int cache_duration_low_zoom; int cache_duration_max; int cache_duration_medium_zoom; int cache_duration_minimum; int cache_extended_duration; int cache_level_low_zoom; int cache_level_medium_zoom; int delaypool_render_size; int delaypool_tile_size; int enable_bulk_mode; int enable_dirty_url; int enable_global_stats; int enable_status_url; int enable_tile_throttling; int enable_tile_throttling_xforward; int max_load_missing; int max_load_old; int mincachetime[MAX_ZOOM_SERVER + 1]; int renderd_socket_port; int request_timeout; int request_timeout_priority; long delaypool_render_rate; long delaypool_tile_rate; } tile_server_conf; typedef struct tile_request_data { struct protocol *cmd; struct storage_backend *store; int layerNumber; } tile_request_data; enum tileState { tileMissing, tileOld, tileVeryOld, tileCurrent }; #endif mod_tile-0.8.0/includes/parameterize_style.hpp000066400000000000000000000017171474064163400215610ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef PARAMETERIZE_HPP #define PARAMETERIZE_HPP #include typedef void (*parameterize_function_ptr)(mapnik::Map &m, char *parameter); parameterize_function_ptr init_parameterization_function(const char *function_name); #endif mod_tile-0.8.0/includes/protocol.h000066400000000000000000000036471474064163400171560ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef PROTOCOL_H #define PROTOCOL_H #include "config.h" #ifdef __cplusplus extern "C" { #endif /* Protocol between client and render daemon * * ver = 2; * * cmdRender(z,x,y,xmlconfig), response: {cmdDone(z,x,y), cmdBusy(z,x,y)} * cmdDirty(z,x,y,xmlconfig), no response * * A client may not bother waiting for a response if the render daemon is too slow * causing responses to get slightly out of step with requests. */ #define TILE_PATH_MAX (256) #define PROTO_VER (3) #ifndef RENDERD_SOCKET #define RENDERD_SOCKET "/run/renderd/renderd.sock" #endif #ifndef RENDERD_HOST #define RENDERD_HOST "localhost" #endif #ifndef RENDERD_PORT #define RENDERD_PORT 7654 #endif #define XMLCONFIG_MAX 41 enum protoCmd { cmdIgnore, cmdRender, cmdDirty, cmdDone, cmdNotDone, cmdRenderPrio, cmdRenderBulk, cmdRenderLow }; struct protocol { int ver; enum protoCmd cmd; int x; int y; int z; char xmlname[XMLCONFIG_MAX]; char mimetype[XMLCONFIG_MAX]; char options[XMLCONFIG_MAX]; }; struct protocol_v1 { int ver; enum protoCmd cmd; int x; int y; int z; }; struct protocol_v2 { int ver; enum protoCmd cmd; int x; int y; int z; char xmlname[XMLCONFIG_MAX]; }; #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/protocol_helper.h000066400000000000000000000017141474064163400205060ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef DAEMONHELPER_H #define DAEMONHELPER_H #ifdef __cplusplus extern "C" { #endif #include "protocol.h" int send_cmd(struct protocol *cmd, int fd); int recv_cmd(struct protocol *cmd, int fd, int block); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/render_config.h000066400000000000000000000102651474064163400201130ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef RENDER_CONFIG_H #define RENDER_CONFIG_H #include "config.h" #define MAX_ZOOM 20 // MAX_SIZE is the biggest file which we will return to the user #define MAX_SIZE (1 * 1024 * 1024) // With directory hashing enabled we rewrite the path so that tiles are really stored here instead #define DIRECTORY_HASH #ifndef RENDERD_TILE_DIR #define RENDERD_TILE_DIR "/var/cache/renderd/tiles" #endif // TILE_PATH is where Openlayers with try to fetch the "z/x/y.png" tiles from // this is now only used if DIRECTORY_HASH is undefined // #define TILE_PATH "/var/www/html/osm_tiles2" // MAX_LOAD_OLD: if tile is out of date, don't re-render it if past this load threshold (users gets old tile) // (This is the default value. Can be overwritten in Apache config with ModTileMaxLoadOld.) #define MAX_LOAD_OLD 16 // MAX_LOAD_MISSING: if tile is missing, don't render it if past this load threshold (user gets 404 error) // (This is the default value. Can be overwritten in Apache config with ModTileMaxLoadMissing.) #define MAX_LOAD_MISSING 50 // MAX_LOAD_ANY: give up serving any data if beyond this load (user gets 404 error) #define MAX_LOAD_ANY 100 // VERYOLD_THRESHOLD: defines how old a tile needs to be (in microseconds) to get rendering priority rather than renderingLow priority // 1000000*3600*24*365 = 31536000000000 #define VERYOLD_THRESHOLD 31536000000000 // Location of osm.xml file #ifndef RENDERD_CONFIG #define RENDERD_CONFIG "/etc/renderd.conf" #endif // The XML configuration used if one is not provided #ifndef XMLCONFIG_DEFAULT #define XMLCONFIG_DEFAULT "default" #endif // Maximum number of configurations that mod tile will allow #ifndef XMLCONFIGS_MAX #define XMLCONFIGS_MAX 100 #endif // Default PID file path #ifndef RENDERD_PIDFILE #define RENDERD_PIDFILE "/run/renderd/renderd.pid" #endif // Mapnik input plugins (will need to adjust for 32 bit libs) #ifndef MAPNIK_PLUGINS_DIR #define MAPNIK_PLUGINS_DIR "/usr/local/lib64/mapnik/input" #endif // Default directory to search for fonts. Recursion can be enabled if desired. #ifndef MAPNIK_FONTS_DIR #define MAPNIK_FONTS_DIR "/usr/local/lib64/mapnik/fonts" #endif #ifndef MAPNIK_FONTS_DIR_RECURSE #define MAPNIK_FONTS_DIR_RECURSE 0 #endif // Typical interval between planet imports, used as basis for tile expiry times #define PLANET_INTERVAL (7 * 24 * 60 * 60) // Planet import should touch this file when complete #define PLANET_TIMESTAMP "/planet-import-complete" // Timeout before giving for a tile to be rendered // (This is the default value. Can be overwritten in Apache config with ModTileRequestTimeout.) #define REQUEST_TIMEOUT (3) #define FD_INVALID (-1) #ifndef MIN #define MIN(x, y) ((x) < (y) ? (x) : (y)) #endif #ifndef MAX #define MAX(x, y) ((x) > (y) ? (x) : (y)) #endif #define MAX_CONNECTIONS (2048) // default for number of rendering threads #define NUM_THREADS (4) // Use this to enable meta-tiles which will render NxN tiles at once // Note: This should be a power of 2 (2, 4, 8, 16 ...) #define METATILE (8) // #undef METATILE // Fallback to standard tiles if meta tile doesn't exist // Legacy - not needed on new installs // #undef METATILEFALLBACK // Metatiles are much larger in size so we don't need big queues to handle large areas #ifdef METATILE #define QUEUE_MAX (64) #define REQ_LIMIT (256) #define DIRTY_LIMIT (8000) #else #define QUEUE_MAX (1024) #define REQ_LIMIT (512) #define DIRTY_LIMIT (10000) #define HASHIDX_SIZE 22123 #endif // Penalty for client making an invalid request (in seconds) #define CLIENT_PENALTY (3) #endif mod_tile-0.8.0/includes/render_submit_queue.h000066400000000000000000000020511474064163400213470ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef RENDERSUBQUEUE_H #define RENDERSUBQUEUE_H #ifdef __cplusplus extern "C" { #endif void enqueue(const char *xmlname, int x, int y, int z); void spawn_workers(int num, const char *socketpath, int maxLoad); void wait_for_empty_queue(void); void finish_workers(void); void print_statistics(void); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/renderd.h000066400000000000000000000040231474064163400167250ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef RENDERD_H #define RENDERD_H #ifdef __cplusplus extern "C" { #endif #ifndef HAVE_DAEMON int daemon(int nochdir, int noclose); #endif #include "gen_tile.h" #include "protocol.h" #include #define INILINE_MAX 256 #define MAX_SLAVES 5 typedef struct { const char *iphostname; const char *mapnik_font_dir; const char *mapnik_plugins_dir; const char *name; const char *pid_filename; const char *socketname; const char *stats_filename; const char *tile_dir; int ipport; int mapnik_font_dir_recurse; int num_threads; } renderd_config; typedef struct { const char *attribution; const char *cors; const char *description; const char *file_extension; const char *host; const char *htcpip; const char *mime_type; const char *output_format; const char *parameterization; const char *server_alias; const char *tile_dir; const char *xmlfile; const char *xmlname; const char *xmluri; double scale_factor; int aspect_x; int aspect_y; int max_zoom; int min_zoom; int num_threads; int tile_px_size; } xmlconfigitem; extern struct request_queue *render_request_queue; void statsRenderFinish(int z, long time); void request_exit(void); void send_response(struct item *item, enum protoCmd rsp, int render_time); enum protoCmd rx_request(struct protocol *req, int fd); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/renderd_config.h000066400000000000000000000035121474064163400202540ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2024 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef RENDERD_CONFIG_H #define RENDERD_CONFIG_H #include "render_config.h" #include "renderd.h" #ifdef __cplusplus extern "C" { #endif int num_slave_threads; renderd_config config; renderd_config config_slaves[MAX_SLAVES]; xmlconfigitem maps[XMLCONFIGS_MAX]; double min_max_double_opt(const char *opt_arg, const char *opt_type_name, double minimum, double maximum); int min_max_int_opt(const char *opt_arg, const char *opt_type_name, int minimum, int maximum); void free_map_section(xmlconfigitem map_section); void free_map_sections(xmlconfigitem *map_sections); void free_renderd_section(renderd_config renderd_section); void free_renderd_sections(renderd_config *renderd_sections); void process_config_file(const char *config_file_name, int active_renderd_section_num, int log_level); void process_map_sections(const char *config_file_name, xmlconfigitem *maps_dest, const char *default_tile_dir, int num_threads); void process_mapnik_section(const char *config_file_name, renderd_config *config_dest); void process_renderd_sections(const char *config_file_name, renderd_config *configs_dest); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/request_queue.h000066400000000000000000000043211474064163400201770ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef REQUEST_QUEUE_H #define REQUEST_QUEUE_H #include "gen_tile.h" #include "render_config.h" #include #ifdef __cplusplus extern "C" { #endif #define HASHIDX_SIZE 2213 typedef struct { long noDirtyRender; long noReqRender; long noReqPrioRender; long noReqLowRender; long noReqBulkRender; long noReqDroped; long noZoomRender[MAX_ZOOM + 1]; long timeReqRender; long timeReqPrioRender; long timeReqLowRender; long timeReqBulkRender; long timeReqDirty; long timeZoomRender[MAX_ZOOM + 1]; } stats_struct; struct item_idx { struct item_idx *next; struct item *item; }; struct request_queue { int hashidxSize; struct item reqHead, reqPrioHead, reqLowHead, reqBulkHead, dirtyHead, renderHead; struct item_idx *item_hashidx; int reqNum, reqPrioNum, reqLowNum, reqBulkNum, dirtyNum; pthread_mutex_t qLock; pthread_cond_t qCond; stats_struct stats; }; struct request_queue *request_queue_init(); void request_queue_close(struct request_queue *queue); struct item *request_queue_fetch_request(struct request_queue *queue); enum protoCmd request_queue_add_request(struct request_queue *queue, struct item *request); void request_queue_remove_request(struct request_queue *queue, struct item *request, int render_time); void request_queue_clear_requests_by_fd(struct request_queue *queue, int fd); int request_queue_no_requests_queued(struct request_queue *queue, enum protoCmd); void request_queue_copy_stats(struct request_queue *queue, stats_struct *stats); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/store.h000066400000000000000000000040631474064163400164420ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef STORE_H #define STORE_H #ifdef __cplusplus extern "C" { #endif #include "render_config.h" #include #include struct stat_info { off_t size; /* total size, in bytes */ time_t atime; /* time of last access */ time_t mtime; /* time of last modification */ time_t ctime; /* time of last status change */ int expired; /* has the tile expired */ }; struct storage_backend { int (*tile_read)(struct storage_backend *store, const char *xmlconfig, const char *options, int x, int y, int z, char *buf, size_t sz, int *compressed, char *err_msg); struct stat_info(*tile_stat)(struct storage_backend *store, const char *xmlconfig, const char *options, int x, int y, int z); int (*metatile_write)(struct storage_backend *store, const char *xmlconfig, const char *options, int x, int y, int z, const char *buf, int sz); int (*metatile_delete)(struct storage_backend *store, const char *xmlconfig, int x, int y, int z); int (*metatile_expire)(struct storage_backend *store, const char *xmlconfig, int x, int y, int z); char *(*tile_storage_id)(struct storage_backend *store, const char *xmlconfig, const char *options, int x, int y, int z, char *string); int (*close_storage)(struct storage_backend *store); void *storage_ctx; }; struct storage_backend *init_storage_backend(const char *options); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/store_file.h000066400000000000000000000016411474064163400174400ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef STOREFILE_H #define STOREFILE_H #ifdef __cplusplus extern "C" { #endif #include "store.h" struct storage_backend *init_storage_file(const char *tile_dir); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/store_file_utils.h000066400000000000000000000034401474064163400206570ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef DIR_UTILS_H #define DIR_UTILS_H #include #ifdef __cplusplus extern "C" { #endif #include "render_config.h" /* Build parent directories for the specified file name * Note: the part following the trailing / is ignored * e.g. mkdirp("/a/b/foo.png") == shell mkdir -p /a/b */ int mkdirp(const char *path); /* File path hashing. Used by both mod_tile and render daemon * The two must both agree on the file layout for meta-tiling * to work */ int path_to_xyz(const char *tilepath, const char *path, char *xmlconfig, int *px, int *py, int *pz); #ifdef METATILE /* New meta-tile storage functions */ /* Returns the path to the meta-tile and the offset within the meta-tile */ int xyzo_to_meta(char *path, size_t len, const char *tile_dir, const char *xmlconfig, const char *options, int x, int y, int z); int xyz_to_meta(char *path, size_t len, const char *tile_dir, const char *xmlconfig, int x, int y, int z); #else void xyz_to_path(char *path, size_t len, const char *tile_dir, const char *xmlconfig, int x, int y, int z); #endif #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/store_memcached.h000066400000000000000000000016711474064163400204320ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef STOREMEMCACHED_H #define STOREMEMCACHED_H #ifdef __cplusplus extern "C" { #endif #include "store.h" struct storage_backend *init_storage_memcached(const char *connection_string); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/store_null.h000066400000000000000000000016171474064163400174760ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef STORE_NULL_H #define STORE_NULL_H #ifdef __cplusplus extern "C" { #endif struct storage_backend *init_storage_null(); #ifdef __cplusplus } #endif #endif /* STORE_NULL_H */ mod_tile-0.8.0/includes/store_rados.h000066400000000000000000000016551474064163400176360ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef STORERADOS_H #define STORERADOS_H #ifdef __cplusplus extern "C" { #endif #include "store.h" struct storage_backend *init_storage_rados(const char *connection_string); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/store_ro_composite.h000066400000000000000000000017001474064163400212170ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef STOREROCOMPOSITE_H #define STOREROCOMPOSITE_H #ifdef __cplusplus extern "C" { #endif #include "store.h" struct storage_backend *init_storage_ro_composite(const char *connection_string); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/store_ro_http_proxy.h000066400000000000000000000017011474064163400214360ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef STOREROHTTPPROXY_H #define STOREROHTTPPROXY_H #ifdef __cplusplus extern "C" { #endif #include "store.h" struct storage_backend *init_storage_ro_http_proxy(const char *connection_string); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/includes/sys_utils.h000066400000000000000000000015501474064163400173420ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #ifndef SYS_UTILS_H #define SYS_UTILS_H #ifdef __cplusplus extern "C" { #endif double get_load_avg(void); #ifdef __cplusplus } #endif #endif mod_tile-0.8.0/m4/000077500000000000000000000000001474064163400136445ustar00rootroot00000000000000mod_tile-0.8.0/m4/ax_lib_mapnik.m4000066400000000000000000000045501474064163400167070ustar00rootroot00000000000000# SYNOPSIS # # AX_LIB_MAPNIK() # # DESCRIPTION # # This macro provides tests of availability of mapnik 'libmapnik' library # of particular version or newer. # # # # The --with-mapnik option takes one of three possible values: # # no - do not check for mapnik library # # yes - do check for mapnik library in standard locations (xml2-config # should be in the PATH) # # path - complete path to mapnik-config utility, use this option if mapnik-config # can't be found in the PATH # # This macro calls: # # AC_SUBST(MAPNIK_CFLAGS) # AC_SUBST(MAPNIK_LDFLAGS) # # And sets: # # HAVE_MAPNIK # # LICENSE # # Copyright (c) 2012 # Copyright (c) 2009 Hartmut Holzgraefe # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. AC_DEFUN([AX_LIB_MAPNIK], [ AC_ARG_WITH([libmapnik], AC_HELP_STRING([--with-libmapnik=@<:@ARG@:>@], [use mapnik library @<:@default=yes@:>@, optionally specify path to mapnik-config] ), [ if test "$withval" = "no"; then want_libmapnik="no" elif test "$withval" = "yes"; then want_libmapnik="yes" else want_libmapnik="yes" MAPNIK_CONFIG="$withval" fi ], [want_libmapnik="yes"] ) MAPNIK_CFLAGS="" MAPNIK_LDFLAGS="" MAPNIK_VERSION="" dnl dnl Check mapnik libraries (libmapnik) dnl if test "$want_libmapnik" = "yes"; then if test -z "$MAPNIK_CONFIG" -o test; then AC_PATH_PROG([MAPNIK_CONFIG], [mapnik-config], []) fi if test "$MAPNIK_CONFIG" != "no"; then AC_MSG_CHECKING([for mapnik libraries]) MAPNIK_CFLAGS="`$MAPNIK_CONFIG --cflags`" MAPNIK_LDFLAGS="`$MAPNIK_CONFIG --libs` `$MAPNIK_CONFIG --ldflags` `$MAPNIK_CONFIG --dep-libs`" MAPNIK_VERSION=`$MAPNIK_CONFIG --version` AC_DEFINE([HAVE_MAPNIK], [1], [Define to 1 if mapnik libraries are available]) found_libmapnik="yes" AC_MSG_RESULT([yes]) else found_libmapnik="no" AC_MSG_RESULT([no]) fi fi AC_SUBST([MAPNIK_VERSION]) AC_SUBST([MAPNIK_CFLAGS]) AC_SUBST([MAPNIK_LDFLAGS]) ]) mod_tile-0.8.0/m4/ax_libmemcached.m4000066400000000000000000000123641474064163400172010ustar00rootroot00000000000000# =========================================================================== # https//libmemcached.org/ # =========================================================================== # # SYNOPSIS # # AX_LIBMEMCACHED, AX_LIBMEMCACHED_UTIL, AX_ENABLE_LIBMEMCACHED # # DESCRIPTION # # Checked for installation of libmemcached # # AC_SUBST(LIBMEMCACHED_CFLAGS) # AC_SUBST(LIBMEMCACHED_LDFLAGS) # AC_SUBST(LIBMEMCACHED_UTIL_LDFLAGS) # # NOTE: Implementation uses AC_CHECK_HEADER. # # LICENSE # # Copyright (C) 2012 Brian Aker # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # # * The names of its contributors may not be used to endorse or # promote products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #serial 1 AC_DEFUN([AX_LIBMEMCACHED], [ AC_CHECK_HEADER([libmemcached-1.0/memcached.h], [ AC_CACHE_CHECK([check for -lmemcached], [ax_cv_libmemcached], [ AC_LANG_PUSH([C]) AX_SAVE_FLAGS LIBS="-lmemcached $LIBS" AC_RUN_IFELSE([ AC_LANG_PROGRAM([#include ], [ memcached_st *memc; memc= memcached(NULL, 0); memcached_free(memc); ])], [ax_cv_libmemcached=yes], [ax_cv_libmemcached=no], [AC_MSG_WARN([test program execution failed])]) AC_LANG_POP AX_RESTORE_FLAGS ]) ]) AS_IF([test "x$ax_cv_libmemcached" = "xyes"], [ AC_DEFINE([HAVE_LIBMEMCACHED_MEMCACHED_H], [1], [Have libmemcached-1.0/memcached.h]) ],[ AC_DEFINE([HAVE_LIBMEMCACHED_MEMCACHED_H], [0], [Have libmemcached-1.0/memcached.h]) ]) ]) AC_DEFUN([AX_LIBMEMCACHED_UTIL], [ AC_REQUIRE([AX_LIBMEMCACHED]) AS_IF([test "$ax_cv_libmemcached" = yes], [ AC_CHECK_HEADER([libmemcachedutil-1.0/util.h], [ AC_CACHE_CHECK([check for -lmemcachedutil], [ax_cv_libmemcached_util], [ AX_SAVE_FLAGS AC_LANG_PUSH([C]) LIBS="-lmemcachedutil -lmemcached $LIBS" AC_RUN_IFELSE([ AC_LANG_PROGRAM([#include ], [ memcached_pool_st *memc_pool= memcached_pool_create(NULL, 0, 3); memcached_pool_destroy(memc_pool); ])], [ax_cv_libmemcached_util=yes], [ax_cv_libmemcached_util=no], [AC_MSG_WARN([test program execution failed])]) AC_LANG_POP AX_RESTORE_FLAGS ]) ]) ]) AS_IF([test "x$ax_cv_libmemcached_util" = "xyes"], [ AC_DEFINE([HAVE_LIBMEMCACHED_UTIL_H], [1], [Have libmemcachedutil-1.0/util.h]) ],[ AC_DEFINE([HAVE_LIBMEMCACHED_UTIL_H], [0], [Have libmemcachedutil-1.0/util.h]) ]) ]) AC_DEFUN([_ENABLE_LIBMEMCACHED], [ AC_REQUIRE([AX_LIBMEMCACHED_UTIL]) AC_ARG_ENABLE([libmemcached], [AS_HELP_STRING([--disable-libmemcached], [Build with libmemcached support @<:@default=on@:>@])], [ax_enable_libmemcached="$enableval"], [ax_enable_libmemcached="yes"]) AS_IF([test "x$ax_cv_libmemcached" != "xyes"], [ ax_enable_libmemcached="not found" ]) AS_IF([test "x$ax_enable_libmemcached" = "xyes"], [ AC_DEFINE([HAVE_LIBMEMCACHED], [1], [Enable libmemcached support]) LIBMEMCACHED_CFLAGS= AC_SUBST([LIBMEMCACHED_CFLAGS]) LIBMEMCACHED_LDFLAGS="-lmemcached" AC_SUBST([LIBMEMCACHED_LDFLAGS]) AS_IF([test "x$ax_cv_libmemcached_util" = "xyes"], [ LIBMEMCACHED_UTIL_LDFLAGS="-lmemcached -lmemcachedutil" AC_SUBST([LIBMEMCACHED_UTIL_LDFLAGS]) ]) ],[]) AM_CONDITIONAL(HAVE_LIBMEMCACHED, test "x${ax_enable_libmemcached}" = "xyes") ]) AC_DEFUN([AX_ENABLE_LIBMEMCACHED], [ AC_REQUIRE([_ENABLE_LIBMEMCACHED]) ]) mod_tile-0.8.0/m4/ax_pthread.m4000066400000000000000000000540341474064163400162330ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_pthread.html # =========================================================================== # # SYNOPSIS # # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) # # DESCRIPTION # # This macro figures out how to build C programs using POSIX threads. It # sets the PTHREAD_LIBS output variable to the threads library and linker # flags, and the PTHREAD_CFLAGS output variable to any special C compiler # flags that are needed. (The user can also force certain compiler # flags/libs to be tested by setting these environment variables.) # # Also sets PTHREAD_CC and PTHREAD_CXX to any special C compiler that is # needed for multi-threaded programs (defaults to the value of CC # respectively CXX otherwise). (This is necessary on e.g. AIX to use the # special cc_r/CC_r compiler alias.) # # NOTE: You are assumed to not only compile your program with these flags, # but also to link with them as well. For example, you might link with # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # $PTHREAD_CXX $CXXFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # # If you are only building threaded programs, you may wish to use these # variables in your default LIBS, CFLAGS, and CC: # # LIBS="$PTHREAD_LIBS $LIBS" # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS" # CC="$PTHREAD_CC" # CXX="$PTHREAD_CXX" # # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant # has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to # that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). # # Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the # PTHREAD_PRIO_INHERIT symbol is defined when compiling with # PTHREAD_CFLAGS. # # ACTION-IF-FOUND is a list of shell commands to run if a threads library # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it # is not found. If ACTION-IF-FOUND is not specified, the default action # will define HAVE_PTHREAD. # # Please let the authors know if this macro fails on any platform, or if # you have any other suggestions or comments. This macro was based on work # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by # Alejandro Forero Cuervo to the autoconf macro repository. We are also # grateful for the helpful feedback of numerous users. # # Updated for Autoconf 2.68 by Daniel Richard G. # # LICENSE # # Copyright (c) 2008 Steven G. Johnson # Copyright (c) 2011 Daniel Richard G. # Copyright (c) 2019 Marc Stevens # # This program 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 3 of the License, or (at your # option) any later version. # # This program 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 General # Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 31 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_REQUIRE([AC_PROG_CC]) AC_REQUIRE([AC_PROG_SED]) AC_LANG_PUSH([C]) ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on Tru64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then ax_pthread_save_CC="$CC" ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) AS_IF([test "x$PTHREAD_CXX" != "x"], [CXX="$PTHREAD_CXX"]) CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) AC_MSG_RESULT([$ax_pthread_ok]) if test "x$ax_pthread_ok" = "xno"; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi CC="$ax_pthread_save_CC" CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items with a "," contain both # C compiler flags (before ",") and linker flags (after ","). Other items # starting with a "-" are C compiler flags, and remaining items are # library names, except for "none" which indicates that we try without # any flags at all, and "pthread-config" which is a program returning # the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 # (Note: HP C rejects this with "bad form for `-t' option") # -pthreads: Solaris/gcc (Note: HP C also rejects) # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads and # -D_REENTRANT too), HP C (must be checked before -lpthread, which # is present but should not be used directly; and before -mthreads, # because the compiler interprets this as "-mt" + "-hreads") # -mthreads: Mingw32/gcc, Lynx/gcc # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case $host_os in freebsd*) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) ax_pthread_flags="-kthread lthread $ax_pthread_flags" ;; hpux*) # From the cc(1) man page: "[-mt] Sets various -D flags to enable # multi-threading and also sets -lpthread." ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" ;; openedition*) # IBM z/OS requires a feature-test macro to be defined in order to # enable POSIX threads at all, so give the user a hint if this is # not set. (We don't define these ourselves, as they can affect # other portions of the system API in unpredictable ways.) AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], [ # if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) AX_PTHREAD_ZOS_MISSING # endif ], [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) ;; solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (N.B.: The stubs are missing # pthread_cleanup_push, or rather a function called by this macro, # so we could check for that, but who knows whether they'll stub # that too in a future libc.) So we'll check first for the # standard Solaris way of linking pthreads (-mt -lpthread). ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" ;; esac # Are we compiling with Clang? AC_CACHE_CHECK([whether $CC is Clang], [ax_cv_PTHREAD_CLANG], [ax_cv_PTHREAD_CLANG=no # Note that Autoconf sets GCC=yes for Clang as well as GCC if test "x$GCC" = "xyes"; then AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ # if defined(__clang__) && defined(__llvm__) AX_PTHREAD_CC_IS_CLANG # endif ], [ax_cv_PTHREAD_CLANG=yes]) fi ]) ax_pthread_clang="$ax_cv_PTHREAD_CLANG" # GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) # Note that for GCC and Clang -pthread generally implies -lpthread, # except when -nostdlib is passed. # This is problematic using libtool to build C++ shared libraries with pthread: # [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 # [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 # [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 # To solve this, first try -pthread together with -lpthread for GCC AS_IF([test "x$GCC" = "xyes"], [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"]) # Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first AS_IF([test "x$ax_pthread_clang" = "xyes"], [ax_pthread_flags="-pthread,-lpthread -pthread"]) # The presence of a feature test macro requesting re-entrant function # definitions is, on some systems, a strong hint that pthreads support is # correctly enabled case $host_os in darwin* | hpux* | linux* | osf* | solaris*) ax_pthread_check_macro="_REENTRANT" ;; aix*) ax_pthread_check_macro="_THREAD_SAFE" ;; *) ax_pthread_check_macro="--" ;; esac AS_IF([test "x$ax_pthread_check_macro" = "x--"], [ax_pthread_check_cond=0], [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) if test "x$ax_pthread_ok" = "xno"; then for ax_pthread_try_flag in $ax_pthread_flags; do case $ax_pthread_try_flag in none) AC_MSG_CHECKING([whether pthreads work without any flags]) ;; *,*) PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"]) ;; -*) AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) PTHREAD_CFLAGS="$ax_pthread_try_flag" ;; pthread-config) AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) PTHREAD_LIBS="-l$ax_pthread_try_flag" ;; esac ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. AC_LINK_IFELSE([AC_LANG_PROGRAM([#include # if $ax_pthread_check_cond # error "$ax_pthread_check_macro must be defined" # endif static void *some_global = NULL; static void routine(void *a) { /* To avoid any unused-parameter or unused-but-set-parameter warning. */ some_global = a; } static void *start_routine(void *a) { return a; }], [pthread_t th; pthread_attr_t attr; pthread_create(&th, 0, start_routine, 0); pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_cleanup_pop(0) /* ; */])], [ax_pthread_ok=yes], []) CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" AC_MSG_RESULT([$ax_pthread_ok]) AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Clang needs special handling, because older versions handle the -pthread # option in a rather... idiosyncratic way if test "x$ax_pthread_clang" = "xyes"; then # Clang takes -pthread; it has never supported any other flag # (Note 1: This will need to be revisited if a system that Clang # supports has POSIX threads in a separate library. This tends not # to be the way of modern systems, but it's conceivable.) # (Note 2: On some systems, notably Darwin, -pthread is not needed # to get POSIX threads support; the API is always present and # active. We could reasonably leave PTHREAD_CFLAGS empty. But # -pthread does define _REENTRANT, and while the Darwin headers # ignore this macro, third-party headers might not.) # However, older versions of Clang make a point of warning the user # that, in an invocation where only linking and no compilation is # taking place, the -pthread option has no effect ("argument unused # during compilation"). They expect -pthread to be passed in only # when source code is being compiled. # # Problem is, this is at odds with the way Automake and most other # C build frameworks function, which is that the same flags used in # compilation (CFLAGS) are also used in linking. Many systems # supported by AX_PTHREAD require exactly this for POSIX threads # support, and in fact it is often not straightforward to specify a # flag that is used only in the compilation phase and not in # linking. Such a scenario is extremely rare in practice. # # Even though use of the -pthread flag in linking would only print # a warning, this can be a nuisance for well-run software projects # that build with -Werror. So if the active version of Clang has # this misfeature, we search for an option to squash it. AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown # Create an alternate version of $ac_link that compiles and # links in two steps (.c -> .o, .o -> exe) instead of one # (.c -> exe), because the warning occurs only in the second # step ax_pthread_save_ac_link="$ac_link" ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' ax_pthread_link_step=`AS_ECHO(["$ac_link"]) | sed "$ax_pthread_sed"` ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" ax_pthread_save_CFLAGS="$CFLAGS" for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" ac_link="$ax_pthread_save_ac_link" AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], [ac_link="$ax_pthread_2step_ac_link" AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], [break]) ]) done ac_link="$ax_pthread_save_ac_link" CFLAGS="$ax_pthread_save_CFLAGS" AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" ]) case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in no | unknown) ;; *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; esac fi # $ax_pthread_clang = yes # Various other checks: if test "x$ax_pthread_ok" = "xyes"; then ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. AC_CACHE_CHECK([for joinable pthread attribute], [ax_cv_PTHREAD_JOINABLE_ATTR], [ax_cv_PTHREAD_JOINABLE_ATTR=unknown for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [int attr = $ax_pthread_attr; return attr /* ; */])], [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], []) done ]) AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ test "x$ax_pthread_joinable_attr_defined" != "xyes"], [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$ax_cv_PTHREAD_JOINABLE_ATTR], [Define to necessary symbol if this constant uses a non-standard name on your system.]) ax_pthread_joinable_attr_defined=yes ]) AC_CACHE_CHECK([whether more special flags are required for pthreads], [ax_cv_PTHREAD_SPECIAL_FLAGS], [ax_cv_PTHREAD_SPECIAL_FLAGS=no case $host_os in solaris*) ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" ;; esac ]) AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ test "x$ax_pthread_special_flags_added" != "xyes"], [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" ax_pthread_special_flags_added=yes]) AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], [ax_cv_PTHREAD_PRIO_INHERIT], [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[int i = PTHREAD_PRIO_INHERIT; return i;]])], [ax_cv_PTHREAD_PRIO_INHERIT=yes], [ax_cv_PTHREAD_PRIO_INHERIT=no]) ]) AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ test "x$ax_pthread_prio_inherit_defined" != "xyes"], [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) ax_pthread_prio_inherit_defined=yes ]) CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" # More AIX lossage: compile with *_r variant if test "x$GCC" != "xyes"; then case $host_os in aix*) AS_CASE(["x/$CC"], [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], [#handle absolute path differently from PATH based program lookup AS_CASE(["x$CC"], [x/*], [ AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"]) AS_IF([test "x${CXX}" != "x"], [AS_IF([AS_EXECUTABLE_P([${CXX}_r])],[PTHREAD_CXX="${CXX}_r"])]) ], [ AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC]) AS_IF([test "x${CXX}" != "x"], [AC_CHECK_PROGS([PTHREAD_CXX],[${CXX}_r],[$CXX])]) ] ) ]) ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" AC_SUBST([PTHREAD_LIBS]) AC_SUBST([PTHREAD_CFLAGS]) AC_SUBST([PTHREAD_CC]) AC_SUBST([PTHREAD_CXX]) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test "x$ax_pthread_ok" = "xyes"; then ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) : else ax_pthread_ok=no $2 fi AC_LANG_POP ])dnl AX_PTHREAD mod_tile-0.8.0/m4/ax_restore_flags.m4000066400000000000000000000015611474064163400174400ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_restore_flags.html # =========================================================================== # # SYNOPSIS # # AX_RESTORE_FLAGS() # # DESCRIPTION # # Restore common compilation flags from temporary variables # # LICENSE # # Copyright (c) 2009 Filippo Giunchedi # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 3 AC_DEFUN([AX_RESTORE_FLAGS], [ CPPFLAGS="${CPPFLAGS_save}" CFLAGS="${CFLAGS_save}" CXXFLAGS="${CXXFLAGS_save}" OBJCFLAGS="${OBJCFLAGS_save}" LDFLAGS="${LDFLAGS_save}" LIBS="${LIBS_save}" ]) mod_tile-0.8.0/m4/ax_save_flags.m4000066400000000000000000000015471474064163400167170ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_save_flags.html # =========================================================================== # # SYNOPSIS # # AX_SAVE_FLAGS() # # DESCRIPTION # # Save common compilation flags into temporary variables # # LICENSE # # Copyright (c) 2009 Filippo Giunchedi # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 3 AC_DEFUN([AX_SAVE_FLAGS], [ CPPFLAGS_save="${CPPFLAGS}" CFLAGS_save="${CFLAGS}" CXXFLAGS_save="${CXXFLAGS}" OBJCFLAGS_save="${OBJCFLAGS}" LDFLAGS_save="${LDFLAGS}" LIBS_save="${LIBS}" ]) mod_tile-0.8.0/m4/libcurl.m4000066400000000000000000000236711474064163400155530ustar00rootroot00000000000000# LIBCURL_CHECK_CONFIG ([DEFAULT-ACTION], [MINIMUM-VERSION], # [ACTION-IF-YES], [ACTION-IF-NO]) # ---------------------------------------------------------- # David Shaw May-09-2006 # # Checks for libcurl. DEFAULT-ACTION is the string yes or no to # specify whether to default to --with-libcurl or --without-libcurl. # If not supplied, DEFAULT-ACTION is yes. MINIMUM-VERSION is the # minimum version of libcurl to accept. Pass the version as a regular # version number like 7.10.1. If not supplied, any version is # accepted. ACTION-IF-YES is a list of shell commands to run if # libcurl was successfully found and passed the various tests. # ACTION-IF-NO is a list of shell commands that are run otherwise. # Note that using --without-libcurl does run ACTION-IF-NO. # # This macro #defines HAVE_LIBCURL if a working libcurl setup is # found, and sets @LIBCURL@ and @LIBCURL_CPPFLAGS@ to the necessary # values. Other useful defines are LIBCURL_FEATURE_xxx where xxx are # the various features supported by libcurl, and LIBCURL_PROTOCOL_yyy # where yyy are the various protocols supported by libcurl. Both xxx # and yyy are capitalized. See the list of AH_TEMPLATEs at the top of # the macro for the complete list of possible defines. Shell # variables $libcurl_feature_xxx and $libcurl_protocol_yyy are also # defined to 'yes' for those features and protocols that were found. # Note that xxx and yyy keep the same capitalization as in the # curl-config list (e.g. it's "HTTP" and not "http"). # # Users may override the detected values by doing something like: # LIBCURL="-lcurl" LIBCURL_CPPFLAGS="-I/usr/myinclude" ./configure # # For the sake of sanity, this macro assumes that any libcurl that is # found is after version 7.7.2, the first version that included the # curl-config script. Note that it is very important for people # packaging binary versions of libcurl to include this script! # Without curl-config, we can only guess what protocols are available, # or use curl_version_info to figure it out at runtime. AC_DEFUN([LIBCURL_CHECK_CONFIG], [ AH_TEMPLATE([LIBCURL_FEATURE_SSL],[Defined if libcurl supports SSL]) AH_TEMPLATE([LIBCURL_FEATURE_KRB4],[Defined if libcurl supports KRB4]) AH_TEMPLATE([LIBCURL_FEATURE_IPV6],[Defined if libcurl supports IPv6]) AH_TEMPLATE([LIBCURL_FEATURE_LIBZ],[Defined if libcurl supports libz]) AH_TEMPLATE([LIBCURL_FEATURE_ASYNCHDNS],[Defined if libcurl supports AsynchDNS]) AH_TEMPLATE([LIBCURL_FEATURE_IDN],[Defined if libcurl supports IDN]) AH_TEMPLATE([LIBCURL_FEATURE_SSPI],[Defined if libcurl supports SSPI]) AH_TEMPLATE([LIBCURL_FEATURE_NTLM],[Defined if libcurl supports NTLM]) AH_TEMPLATE([LIBCURL_PROTOCOL_HTTP],[Defined if libcurl supports HTTP]) AH_TEMPLATE([LIBCURL_PROTOCOL_HTTPS],[Defined if libcurl supports HTTPS]) AH_TEMPLATE([LIBCURL_PROTOCOL_FTP],[Defined if libcurl supports FTP]) AH_TEMPLATE([LIBCURL_PROTOCOL_FTPS],[Defined if libcurl supports FTPS]) AH_TEMPLATE([LIBCURL_PROTOCOL_FILE],[Defined if libcurl supports FILE]) AH_TEMPLATE([LIBCURL_PROTOCOL_TELNET],[Defined if libcurl supports TELNET]) AH_TEMPLATE([LIBCURL_PROTOCOL_LDAP],[Defined if libcurl supports LDAP]) AH_TEMPLATE([LIBCURL_PROTOCOL_DICT],[Defined if libcurl supports DICT]) AH_TEMPLATE([LIBCURL_PROTOCOL_TFTP],[Defined if libcurl supports TFTP]) AH_TEMPLATE([LIBCURL_PROTOCOL_RTSP],[Defined if libcurl supports RTSP]) AH_TEMPLATE([LIBCURL_PROTOCOL_POP3],[Defined if libcurl supports POP3]) AH_TEMPLATE([LIBCURL_PROTOCOL_IMAP],[Defined if libcurl supports IMAP]) AH_TEMPLATE([LIBCURL_PROTOCOL_SMTP],[Defined if libcurl supports SMTP]) AC_ARG_WITH(libcurl, AC_HELP_STRING([--with-libcurl=PREFIX],[look for the curl library in PREFIX/lib and headers in PREFIX/include]), [_libcurl_with=$withval],[_libcurl_with=ifelse([$1],,[yes],[$1])]) if test "$_libcurl_with" != "no" ; then AC_PROG_AWK _libcurl_version_parse="eval $AWK '{split(\$NF,A,\".\"); X=256*256*A[[1]]+256*A[[2]]+A[[3]]; print X;}'" _libcurl_try_link=yes if test -d "$_libcurl_with" ; then LIBCURL_CPPFLAGS="-I$withval/include" _libcurl_ldflags="-L$withval/lib" AC_PATH_PROG([_libcurl_config],[curl-config],[], ["$withval/bin"]) else AC_PATH_PROG([_libcurl_config],[curl-config],[],[$PATH]) fi if test x$_libcurl_config != "x" ; then AC_CACHE_CHECK([for the version of libcurl], [libcurl_cv_lib_curl_version], [libcurl_cv_lib_curl_version=`$_libcurl_config --version | $AWK '{print $[]2}'`]) _libcurl_version=`echo $libcurl_cv_lib_curl_version | $_libcurl_version_parse` _libcurl_wanted=`echo ifelse([$2],,[0],[$2]) | $_libcurl_version_parse` if test $_libcurl_wanted -gt 0 ; then AC_CACHE_CHECK([for libcurl >= version $2], [libcurl_cv_lib_version_ok], [ if test $_libcurl_version -ge $_libcurl_wanted ; then libcurl_cv_lib_version_ok=yes else libcurl_cv_lib_version_ok=no fi ]) fi if test $_libcurl_wanted -eq 0 || test x$libcurl_cv_lib_version_ok = xyes ; then if test x"$LIBCURL_CPPFLAGS" = "x" ; then LIBCURL_CPPFLAGS=`$_libcurl_config --cflags` fi if test x"$LIBCURL" = "x" ; then LIBCURL=`$_libcurl_config --libs` # This is so silly, but Apple actually has a bug in their # curl-config script. Fixed in Tiger, but there are still # lots of Panther installs around. case "${host}" in powerpc-apple-darwin7*) LIBCURL=`echo $LIBCURL | sed -e 's|-arch i386||g'` ;; esac fi # All curl-config scripts support --feature _libcurl_features=`$_libcurl_config --feature` # Is it modern enough to have --protocols? (7.12.4) if test $_libcurl_version -ge 461828 ; then _libcurl_protocols=`$_libcurl_config --protocols` fi else _libcurl_try_link=no fi unset _libcurl_wanted fi if test $_libcurl_try_link = yes ; then # we didn't find curl-config, so let's see if the user-supplied # link line (or failing that, "-lcurl") is enough. LIBCURL=${LIBCURL-"$_libcurl_ldflags -lcurl"} AC_CACHE_CHECK([whether libcurl is usable], [libcurl_cv_lib_curl_usable], [ _libcurl_save_cppflags=$CPPFLAGS CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS" _libcurl_save_libs=$LIBS LIBS="$LIBCURL $LIBS" AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]],[[ /* Try and use a few common options to force a failure if we are missing symbols or can't link. */ int x; curl_easy_setopt(NULL,CURLOPT_URL,NULL); x=CURL_ERROR_SIZE; x=CURLOPT_WRITEFUNCTION; x=CURLOPT_FILE; x=CURLOPT_ERRORBUFFER; x=CURLOPT_STDERR; x=CURLOPT_VERBOSE; if (x) ; ]])],libcurl_cv_lib_curl_usable=yes,libcurl_cv_lib_curl_usable=no) CPPFLAGS=$_libcurl_save_cppflags LIBS=$_libcurl_save_libs unset _libcurl_save_cppflags unset _libcurl_save_libs ]) if test $libcurl_cv_lib_curl_usable = yes ; then # Does curl_free() exist in this version of libcurl? # If not, fake it with free() _libcurl_save_cppflags=$CPPFLAGS CPPFLAGS="$CPPFLAGS $LIBCURL_CPPFLAGS" _libcurl_save_libs=$LIBS LIBS="$LIBS $LIBCURL" AC_CHECK_FUNC(curl_free,, AC_DEFINE(curl_free,free, [Define curl_free() as free() if our version of curl lacks curl_free.])) CPPFLAGS=$_libcurl_save_cppflags LIBS=$_libcurl_save_libs unset _libcurl_save_cppflags unset _libcurl_save_libs AC_DEFINE(HAVE_LIBCURL,1, [Define to 1 if you have a functional curl library.]) AC_SUBST(LIBCURL_CPPFLAGS) AC_SUBST(LIBCURL) for _libcurl_feature in $_libcurl_features ; do AC_DEFINE_UNQUOTED(AS_TR_CPP(libcurl_feature_$_libcurl_feature),[1]) eval AS_TR_SH(libcurl_feature_$_libcurl_feature)=yes done if test "x$_libcurl_protocols" = "x" ; then # We don't have --protocols, so just assume that all # protocols are available _libcurl_protocols="HTTP FTP FILE TELNET LDAP DICT TFTP" if test x$libcurl_feature_SSL = xyes ; then _libcurl_protocols="$_libcurl_protocols HTTPS" # FTPS wasn't standards-compliant until version # 7.11.0 (0x070b00 == 461568) if test $_libcurl_version -ge 461568; then _libcurl_protocols="$_libcurl_protocols FTPS" fi fi # RTSP, IMAP, POP3 and SMTP were added in # 7.20.0 (0x071400 == 463872) if test $_libcurl_version -ge 463872; then _libcurl_protocols="$_libcurl_protocols RTSP IMAP POP3 SMTP" fi fi for _libcurl_protocol in $_libcurl_protocols ; do AC_DEFINE_UNQUOTED(AS_TR_CPP(libcurl_protocol_$_libcurl_protocol),[1]) eval AS_TR_SH(libcurl_protocol_$_libcurl_protocol)=yes done else unset LIBCURL unset LIBCURL_CPPFLAGS fi fi unset _libcurl_try_link unset _libcurl_version_parse unset _libcurl_config unset _libcurl_feature unset _libcurl_features unset _libcurl_protocol unset _libcurl_protocols unset _libcurl_version unset _libcurl_ldflags fi if test x$_libcurl_with = xno || test x$libcurl_cv_lib_curl_usable != xyes ; then # This is the IF-NO path ifelse([$4],,:,[$4]) else # This is the IF-YES path ifelse([$3],,:,[$3]) fi unset _libcurl_with ])dnl mod_tile-0.8.0/modules.mk000066400000000000000000000003741474064163400153310ustar00rootroot00000000000000# # this is used/needed by the APACHE2 build system # MOD_TILE = mod_tile dir_utils store mod_tile.la: ${MOD_TILE:=.slo} $(SH_LINK) -rpath $(libexecdir) -module -avoid-version ${MOD_TILE:=.lo} DISTCLEAN_TARGETS = modules.mk shared = mod_tile.la mod_tile-0.8.0/screenshot.jpg000066400000000000000000007415311474064163400162160ustar00rootroot00000000000000ÿØÿàJFIFHHÿá –ExifII*V^(1 f2ti‡ˆšHHGIMP 2.10.202020:10:08 10:18:39 z‡ ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀz"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?öñޏ4H1Ó4…€8Éü³II qL}ÅlòéN šÑM2\Z‘˜*c…&–±u÷i Q6Õ6;Ö‘‹“± ÙM©Á0d(L`t#–?Ò³%HÀªQÐ ŽŠêŒv1rlRÌÀIÇLÒQEP‚Š( Š(  !‚K‡ÙËc=kBÂÎæ ÅfB©ƒ¸ç­E¤m„–ÇÊkjy–ZF `qžæ±©7~SHÅZãeº†Û$N3ƒP›Ã-»In¹(rU¸$V²¼ï½Ø³TâI>Ðo¹ 1Æp=é{+9»oqÊnõÅ=Ø"3±À&±’í#Ôö*’6¾ßZµö–{æ·‘Ç–ÀЇ JR)RO6VmÅXªOJ¹¦ÝÄëå’D¤çæ9ÝøÕ¸í ˆ±/=x¦¥•ºH$X€`r)¹E­)!šŒÒÁk¾,g8'Ò8/Ùƒ‰ZBܶ㜚àn·lßòŸ—Ö³ôi%ˆŒ`æ’ÖöZ(¢³((¢Š(¢Š(¢Š[ûÄgÚUí®|àUÀ§ óúTßw¦~•ƒV5Nâ÷ä â±îoç3°FÚªHªæærûüÖÏÖ´T[0xˆ§cbêímæsÐVÌÒÊÒ62Ç'fåÖha“vdÆÆõâ«Vô—*1«Q¹yQR 4§¥n¤™*HmQV0¢Š(QE>42:¢‚YŽ %³´{¹v©À’kpØ[—ÊXRH¤²²[D?1gn¦­W4æÛÐÚ1²Ô@ª£=…gêVs\²4{HQМÑ¢¥6ÊjêÇ.°JÓù;q!8Á©&¶¸ƒïÆxþ03ú×FUK* :RÖžÕö#Ùœä&âGXòø”ŽNk£ Ì &U]Ûzj‘d]NU†A¨œ¹µU‡Q€;Ts£É*1V#ƒTàÔ@_.tq2Ž@Ͻ%ÖƒnÆ…ÜBë••ÿxSÕÕÆQƒcšVê(¢Q@Q@ÒLèë"7̽ëhÉ„ó#fz`Ö>9¤„æ7+š©ÓæØç¥[“qgŽD•ƒ) NióÚ›tŒ»üÏÎÜt©F¥>á¸!1Š]Ñ]Ê çv1€úÔ¹¤·)B¿+Ô¬v˜Êäýå=?ÆšT—Â0o~”ÓòÈB“…<ôJÒÝŒ¹º0?/^(¤$Œþ‹ÀÇjd1ÄÖ˜SÒŸERmmt¢¥#4ž•jhµ!µ¥£ª‡$e‚ñYÝ:Ö–·Î“ócŠ'ð³HnmQEÈnQEQE!¡â³ìä[{©m›ïf<úVS½´3”–6Ä‘ò=ê¢ÖÌOº.S|´ [jäõ8ªrê–ñ ]½f]_Ï9#&4é°¨Ó“’C/š¹o!@QÇ ¤³¹6Ó†ËlîW¢º-¥Œo­Î¢Þaq” ž*BB©bp$šÍÑçÝ Bz©ÈúU}ZæC9€6p+Ÿ’ò±·7»rYõ²‰(þ#Þ´á•g‰dC•5ÊÕûÉ"–( =Ø8sZNš¶„Æzên³RÇ 5žÚ® 2ˆ~]ÛW-Ö®Ç4rîØÀ•8#Ò±5H ¡°`0Î=+8E7fT›Jè­EUœAJ¬Qƒ)Á”RÚ’nFl•äõÇó­h¡ à~µ—m Êä¶ Ž£8Ím¤r S\õ^¶GmuÌÈÍ´fRÍfõ5JîÄ&n'”µ’9=ph g?wñQ´Í%ÉYœûÅ$XÞ…sÓ4Êݹ€O 9=TúÇšÚh?Ö!Ôr+¢9·8êRqzlEEV†!Ö…-‡Š°éE'a¦Ñ½i{Êc;\TÔ¦æ 3&_˜W9M)éSÈ™º­Ü衽·žB‘¾[¯J±\ž ŸJè4Á‹%³œž½9¢pQWFŸ1rŠ(¬‹ (¦K"ÃHç )€É6èóQŽIÇZæåË+ÈÝXæ§¼¾’í±÷c…ÿ«]4ámYŒ¥}‚Š(­ %¶­§¨É½i÷2$Á%Ý™[;ǧ¥W¢•µ¸_KKdu˜F̈A8¨ªkiÚ A†‡°"gÙH'„Ÿ.NTÿCU^G‘‹;cÜÖµä^e‚hÉÈ^ëQi–°\i2Χ•=1Y©+]–âïbQRr…¥PÝ5Ð9B¿3t?ÒµÉ ÁéŠæÕŠ8`pGJÝ·•&„ƒê=+š¬u¹Û‡×)g€1HqLR¸ ¸{OSÉÍbt IÔ`þ41 3Èf‘Nrq@îma™r@RÞVjj“°ÛðÉ#¿µe×U$ùu8k´ådQEj`QEsM¸0Γ±øÇ¡ªufÊ:åAè¿1©“´Y¥;ó+Àæ–¢Úwgqv » ÎÜŽ¼kO¹ÜãØ–²u¢Øˆgå9ãÞµà Ž=RÕÀû8ÎáŠÚ›÷‘œÖ† jÒÍî¤èV1ÉnÕY†C]WW±…„¢Š)€QEQEjÚöKudÎcaŒÞâ•=äÇy´ã« T¢§”w%¢Š+#œ(¢Š)Ñ»Æá‘ˆ"›R@¥çGvžÅGucr@vSóvö§)!yïNóëQË8*©Ípž™)ëÁëLyV!ó:+Ï€=ëYG%˜°ÏñWsT¨ ‡ÞL'¹få ‚Š+­++2wwaES$(¢Š*OÝn W£¥&®¬T_+º:>1Áü©¹Nv°>Û©°É+¦;gΕ¾q(¨#ÄzkT$»¶³©è§ŒVÒ¼ïºF$þ‚´o¦’,œÃqþ™]U•ÎLDõ²6DŸgÒFì ΰjÓÜI$)µz`T%A­©»nDª&ô#¢”©•²wîQE1…Q@Q@ÑE€QETÖ„‹”lŒž{T5{LÈäAš‰»EšRWš5”†Pq€GB1QÌëŒ=2jMëÈÈÏÖ³o/b‘Z1[•Ü{W,bÛ;§5v2úðºù*F;sš¡Eר«#‚sswaEUQEQEQEhi·)t’EQÔn5iõp§ \öX´VN’nìÞ5åÙ–FšB팟NÔÊ(­±‹wwaESR–Š.1…H¦Ô´„V§Ü¥.ätSŠ‘Ò›Vše§p¢Š)Œ›gŒ}i*ÌãµV®dîg(Ø(¢Š¢´´Å Ç«6µôÛqžÏý+*¿ ½ïŽÔ¦DƒfAv< þµZZ°£8õ¬Ú)+DUÝæQEjbQEQEQEQEQEQH;ÐÑE€ (¢€ (­Àòcãø­&ìTcs:€kSoâjU=J—;©ù˜…jmm0^;At«äghÏ®+HTl«ÿÙÿâ°ICC_PROFILE lcms0mntrRGB XYZ ä  acspAPPLöÖÓ-lcms desc @cprt`6wtpt˜chad¬,rXYZØbXYZìgXYZrTRC gTRC bTRC chrm4$dmndX$dmdd|$mluc enUS$GIMP built-in sRGBmluc enUSPublic DomainXYZ öÖÓ-sf32 BÞÿÿó%“ýÿÿû¡ÿÿý¢ÜÀnXYZ o 8õXYZ $Ÿ„¶ÄXYZ b—·‡Ùparaffò§ YÐ [chrm£×T|LÍ™š&g\mluc enUSGIMPmluc enUSsRGBÿÛC     ÿÛC   ÿÂÈ üÿÄÿÄÿÚ ý'jà’¶ø©÷›®vïP£zÞ¥€jy^Þj׬Tm ew+ÒÖ€5-!µc¼z..ž~ÙðºùÀŸ>¾§ CIn­hâtラŽ®G.~¹ÜÊù$‰Üi϶ÅÒr®woPæëNV¹ÇxŽb¶•–£ƒ«Çz\`˜bA ëlÞ •ŸSÃÕ;/lùúW©† X’0qz2çk-©s+ú~.™ U´yC’;Fa´Os—nï>À 9ö²sKÀæiNŽ” aÍÚœnœoc§¯ŸÓpõK¹½qzðÒÞÃÏêž$Q Š^;ÐäŽÑw+ÜÊ÷s½œïçúðçtbŠ[ÖùýsÀ+ZyÞÎx¯P¢ãèísêÓŸk'œ!0]-Ì^Ò¸½p€Ç/lú8é’ŒZ+Lsöϛі“zï;®õ,)Þ¼>œ}.óÖ@ïå©­ô}¾}rq¶Ïƒ×Ï,Z)OÅÑÐÎàÎҼݳätᬀ±Ktð×Ðòìsí!l¬Åk×K×i€›‹§¥•ô9åOZ߯ý|t 'ÒãŽcÚ'jºykéx·Lsì2Œé\Þ©€FFsµ§«æ=_UªÛ•®u4§+| Ò 7¬ûo7³p"”±$äûy¹ûç˜]Ã_QÇÑ0*Þ¾S»š Ô;¼ÉՄV“±Í· äßt8Ûåç{9ñ ;|»ÝÊýL®kÇÑ¡²2G)b×;f˜N'F<Íó¯­SŸ_OÇÑRõò]üºZzΖ€o+û  #¾^s³Ÿ«†¾“¢Hn´ó=¼ÑÚ6¬÷¸ú;˜è<÷VN¬@hŸQÃÓÒÊà9xïC’ Ô +>ÇÏëž²c‡¢)™bij†Ó5`$Ö™Û5 Q½|§.–€õžÇ6ü®Œa½@ )>—‹¦¶•ŽÕÚ&Ž´ŽbZÍm+ ÇgŸ_C˸ Ýi{;É ¦†´ó|õt ƒ“£¹Í¨íGÐå†ÕÚ'·Ë¿O Ü­€ O/ÛÏÍß CÔðõtr¸àtãÂëÀÙåÛÑòîkÅÑ §¨ K0-9:Ò]sͪ#ÝËGjèa§ªâéØHÄ1fÑ$ò}ÜÜý³Bîz_Êü~Œc´½fîWõœ] =Õ«´MÌtësëØÊà<ÿN<>¼;<ÛzMöæºðätâž–ô|}¯1ÃṈ̃¬¶€Úf¬I¥s¶kÔ"—ôy"µ@mì<ÞËPïå§­f£‡«‹ÓM)Ûå߹ϮAZÑä=Hí1;C£Žž‹“¢h©ä=ZšP_ÇO[ÃÒ w¯ôy0g—oG˸CÅÓ^õ»œ€Ciš°Ö¹Û5ê-ÛÏC\ô´ö>o]ºØG»–ŽÔ˜{o7·Ìöss·Ìu¹öôÜ{Nõáôáâ–´Îvõœ=SÖ@àtãÂëÀܯ{+Çf!Ééĉöþgd€<ÿV<>¬Ôpôõ1Ð#ãßX´±–`Ciš°1,ÀX Ò,ë“J€Z48Ûå‰swÊÔ«àêèåpËÌvós¶Ì®]¹ih©Ï¯¦ãèØ“¶~k·Ÿz>Žß> C1æ{yèkLL ™_Ññô]¥€m¼9Ûg¬ÀtpÓÕñt€-ÛÍÌß0k—oE˸s°‹¸õjk1„ïËh JXŒÀPLÏX^3¾m(';\ùÛR†ÙÅz€z>.žÎ4<Ço7?jk0 ØéÜæÛ•¶\®œ€Dö9¶ïòí¸+Z8=Xr÷ËôÜ]=l4 t­KÒæw½KÁ5ãtå’þX‰ŽbÅm$-ís­zÜÎõï[Yß§•À«xò‡&“IY÷o`EǶñlÀE3,@6™«©kJ€6µe×6´áëŸ#¯ÚP[çõßÎÀçz¹ø½X€·>Þ›~6ùyÎÞpBÕ/ì|þ¬¦­«OJb^k·›Ôpuu2¸ËŸ¥0yþÎx/YioGÅÑ âµë¬Ç åÚXlø}XWÒ¢æWö@ûDæ)™b+Úw€36ëPWM¶m(˜hny޼9]8€HÑ>ßÌì܆•ò~‡&s+íª)í@¿Žž·‡§‰Ñ—žìç.åQÅÓçú°åte×æÛ“ÑŒô·®àêš$jxßG“zÚ¦¹ÙÊúÌ{??¯`ÔðþŸ³K[uyµíóí<Ç@)™b!´ÍX‰dB L°Êa¬¢™\íF´LS´E1¬¼Ç.³ ¢u˜Þä߽ͰÍvsò:2A.§>±Z(m˜¦úN>;Õ‡'§wù::Øiä{ù`½FÑ=^}}/'A‰¾\>¬cµE¬ïØåß³•À W¯‘ôy0:<úúž>€1ÃІfh€m3V–Ó,BX‰ŽSDm,ëž6¦f¤6&<—.–€m ¢Ø˜CK@=GkŸ\È WÉz–³½](%fÅ-OZ0u¹õèã§œìçŽÐOIÁÕ×Êø9zÓŸ¶|ݳŠõÞ³o;ú΀K»š†Ù€Ñ>‡~Î€Ž€S2Ä*6·BµŠfHˆfa•ªÀÜÁˆE3´Ôo€¯i±XīͬEuÞ™×=æÔ¡jÒÖL´ñ¾—a½f;À’³ëx:­ÒÀ3Çz<^ RÖijZÒÖw¹»<Ûy~þZרjZÎvïò黎Í^³Ÿ¾vò¿G {ÜúìòݼüÍòê¸:ºY\ pô -Öb™– y}´ÐçÚzô¬§&Ö°Š“>*ts¬S;£h4™ŽSÖ)^ÒCtKi³Z)Û·?£ r½mh{/;²Õ$Ipú1áuà˜u¹öô<»ï ž{«7N:Ì·Žž«¢zÏ3Jr:2¥­%­ ½%­»¼»uñÐ|¼÷^ZÓðôõqÐxºcµ6‹m̱Á̱Ñ+ZÚ“©%d HgZgj-P›ëßӈ3‰€‹¢Å-Cjuùõ³Y C¿–ž´7¬âZÌZÎØ$‰©zõ1ӫϯŸìÃIŠ÷¨Þ³ÝåߵϯžëÃÓˆ;Ü›ö¹öóÝXrº2ÚÖc³T^ÇNÿ6÷)$Áç:ðãôâ=þ=û¼ú€Ž€S2Ä­­,@$ˆɪlÌf30Aiž±éb—YŒ§S3ß96ÏkT«hñ¾—»ý]kW‹ÑŒ7ŒÂ¦”­¥@Ì:xk=fÕ/{+Ù­µE iÐÊù ‚ y®Þz牀-ghì†Õ•µÜ­„ó÷ÊZÙ f›;X¥´´z~.‰âÞG»–Hwùw·K^¹ówÍßæÚzÈÒ½)kK™^Õ-“p pô"™– LËS2ÄÄ¢L¨Ì1,ÀVµµ-ÖºZ»ÖÀ HChÖ/4FúS;f½@xþþZzÐvùwæí•m*^Êþ›¦Åd(é_%èr`I[Gj€%&;ÀÌÁ´:XkËèÊz[bµê%¬õ0ÖµëClð]Êþ¿ƒ¨1ÃЊfX€E3,@LËZ±i0mNö¹JŒK0Ô†m,@+´KJç|רùžÎnOF@ÚyœÝóóýXMµf®´³ýoLÑ >«V†$vù7ÒѤÅ-i£zÌ7¨s+Ú¥ª^*kŸG =_Hc‡ ̱ŠfX€"™– 1,Â)™3¤Í[MŠÀŠÑ¼3–†ÐØ­eª¶ ™Ý„± !¶Uu§«_FXÜtõü=$áNZÑf³Ëvsó:2BJÏW`´s¶Ï¬ïê8zyûgÀêÃKE̯ì8:€‹ÇÑ! Ûª)™ḇG3ÎÈ’ÄbY€†fT ÀTµ­ÖºËhE3"&#ZjƳLï ÌCY˜ÉQä»üú=<Ûë:épêø:º9\òýœü¾Œ€ìsmé97 É¿ 9Ç©¬Ä™ḇ ´Ã-‘¼N¦nV»@bU-k”®%˜C32LË1*Ö‹¶ÑÚ²VÜ^œ8ÓÍëš$+pôXŠfX€E3,@¬ÀÄ·¬Åf‰ÞA3rµ)™bPLÏX–aRÖ·ZÇjÃjøÿGÍšÜà13¦ºi¦‰È†$;üÎmr ÖŽN­óØŽÐÔ¶&"´u0×ÔqtÇ@)™ḇĆa²â§k[Šæ™Œ– k(¦f¬d9%Îß›ÎvpMl2:k¦šhÖW­¥1 ¯>¾Ÿ &µ«Ëß.?FP^ ,RÞ££•ðp:qáõàQÁÓÔË@ñtË1(Ò+ÚF¥ªÀcx€k06‰Öf)‰êLÇ3fµLË‚Ó=`8½ùÞÿ.Ã0%¦šk®˜™QÃÓÔÇ@ÉÛ?3ÝÌ×ùÝwi`5)Þµï^7N4´¢@f'Ûù½’@^>¨¥Dbg§5˜¯7Ô¹Z€–`EzW™»±*ÓkU¨LË‚Ó=`‰x¿KÌΜ€%®ši®˜™r¿¯àê §vÙÓÒ¸˜Ôðõtñ¸¤+Ú¼ý©Ëß*·®&:üÛvùõŠb†´âôã=-ÚçÛ¯†€8zL‰b5˜ÊkM¡—B•$3‰GjËKV´Ù¬¬Dë!,!µ!^xŒ§`2`§¥<§¥äYd4ÓM5Ót1ÓÖpô€Ò\}òàuá˜{?®Íd-Ì]¥€;™@ M€Ž€‚Ó=`̽úU¦`"™’# Ì%˜ ·M’¶Ä°ŽgO%[G+£š$ì-•Ì·õ=øE{Öþ;k3¤ÓÄú~TöÀ¦—Ó]qi%gÛy½›&Xj`ÜhC1ÄéÆÅmÚç×`qtmkLñ@ fy—·F•çÞÓDV™½ZÙ¬õNjч;le‰–·µZöù{+éŸ/£“‰¾vتTéŠÙèŸaçú7±ÛKG–íâ¯Ñç€×KÇ®˜µ€‹£µÏ¨Ê­éÐÎâ)K¥å»y¨í˜ìsmé97uùö–a¦zÀV—µX·ZŽuíÑ¥@KE{SËwpǯ,Š]¦±mÓ¤_keaZ7öþg©%mG\ü§£åNÌÖöÓ]u½€ kh­Pag;ÕÖ€w¹7ïsn@Íõáé97ÐçkŸS-›ëÃÓˆÞ'×ùÝv« Ïz8½o¡ÔÃQ Èš#Ym13¦¬ÍKMúWÌ#µ|§ŸKl,2®×Ûù~´•¾³'Óò¥·8·¶šé­îBÎwµKÔÓ:ºVî:T×2nå~Ï>½Lt Å;Ææõ\­€+×Èz‰¿6¾—“¤€>éóö‰ïñ÷÷¹º4H’ K0Žg&ñG.uïn±jµKÍvqs:¸$TAúÞFöÌyžÞ:}>v¤kÌ 5µ´×Mt¸Öt´ 9Ú¶•ÙyÝ–©bÂèË›¾Tô¯¯óú®RÀÎõaÅéÅ,ÂÞwô\³°yE(tùêíé¸}žѽ®V6ˆÖg&b"™ÖSÖ«kIBöêçMfdŠÊ¬q¦ÓI-:k®š\éᯩâè#ÒãÖÑ{-=wHÔóýXñz°JÏs—~ç>¢)sô¤7¯oŸP>=üg¡çç^*ñ·£áôº¸í Ö«X-8NQ¼"²Õ i3§¬rµ¶bzt¯‘ïób߈^5õÞ§w- µ|'©åtck}fµÚíl2bgMtÓM©†¾“¢Hä;ù©k˜ôÜ==ltC¿–¶”Ä€’–¹KC1¬Äv{7¯ f¡¥8]œ+é¸}¯?H©6‘+Ai–+´H¯i±XŽPÍ«L𺹣êñÀ\û=‡ŸßKL¼‡ éçÅ´š×ia˜™Ó]4ÓD€%gÛù½™Ýiå{ùD´ŸgçöI O+ÛÏÛæÛÌwrÇh&ÕŸCÇÑç;y»œ»õqÖZÌ‘9 Ñ€Ì-{¼G§æM~Hô9ú½OtKX¬+ZSDÍ Ó3ÄjbZ[/èù“[0u0è­*[ai•Šk ³ÄÄQ¦f»ªÄ´ÓMuÓ :¼úú~>€C¿––´~m½/&à¥/ç§Œôxà½@’³é¸ºoåéñbChK[OK_ÎòVaµu• )j–‚ÕÖc£–¾ƒ—`~oSÃßæû8ù{ó[œ"^ç7£ê8ºã™³Zë-ˆækÌÜ­`™‚fåb Y¬s÷ÃÊzdìÀªÚ¼¨•ϧKgbÜ÷ù=:ûñÁjìˆÁ5¹ó4bZ馚é‰6‡©áêèåp4¯“ïåÄŬïëx:e‹ mGÐäĆõŸMÅÓÑÎÃËvóó7È3Ôç×Óro ¢zÈ Óåé]}?¥Vôâvùr«Öùûý_t2±Xäio5®—b7FåºÇo*èH¯?l|·¡åΠ Sœ» %¶«ÑoÐò¥P²jóõâcžÑR‚ÝqéS­34¥x­jØœgP´N f+µ²Èi}5׳¿±óú²ÍõáÇéÄ YÛØùýy<}ñó}¼àè8ú;œúÁ)!¸ŠéòF©¥†Y…§s‡Õéá½ÚÍbíkZÓ˜[­a´èk.Füü¾ß'd­"°4ÁažJ¹tOjmR,®¾«º•éG7_›ªÞ[Ci«·§š+WÂE@ÓH-Xžr×KÇ®˜µ€‹£³† LyŽÞ~~Ùë8:º\x=8pzðow:8ß·†Ä`ÈL™Gju²¼°Ó䀭 kÓÇ£ÖpwÕ›G,™5-V0‰SJÓðòþ‡—<ä@ÒE6F‹a:$n¬ªÖkÖÇM -Û=SgŸ¾í-œºîV"¾|ž®6ÙZœdTZ6–Z³³ÖöÓ]u½€:8éêøz@Áä=ZzPz¿?«£À'¾^s·˜“Ôputò¸³êc¦ÀÖµ`­«B óƒJsº1ô<}|tƒS&Á:¢µ£Êïå&œtH¯¶ó=|ÄÓµ¬DY¬i/=1ΘŠ\‚sµÛçc^0ÚXgªsŸWGŸ¯£îe§?HóýœëÅ‚%òm]0ŒMsŸfµÖ²l[mçöèžnøq÷†¸Él¬3½–ümbfqÅöšì‰”ÖöÓ]u½€ß.þ‡—ràôãÁëÀwù7îól;×ÊwòÅh ¡êø:ïç}æ{yýDÐÕ§ §&éóëêxúH×ÎösÁzôqÓÐrtyî¼(ëœ7¬üœÀåÛë¼ÿKTÚ­q*V¶õ¯Ë„F³8•My¨öù’(Yeþ£ÍözyHŠf¶™ù>ÿ>kòF´Q§B“6]=ÎNüDÐèäåí—;~ib³NQ¯‚e5L+Øf5µ´×Mt¸I[zž›ùØf´òÝü¢jÛØyÝSD€S¼yξz[ffu¡®zZ=?WW€æzðäôâ1>ŸÏêéRû qwË׎–¨±½'O”ôy0 ù9€v{?;ÒÌ4´s4ŽžWäZ<ÔÇmn27-™%êã§ÓæäD¶M#Oeåû;¦ÄGïàƒ~UKê™ÙÕk×Ã}²ëÌçµtŽøòú9¥¦ÐÓªºn3™¦öaµ°‚øÍ~P‹Lzë®—vùvô<»€ÍÖžW¿” ë>“£«Ž€3+Óã'Ùù½–k Lqz2áuà in¯&ýütÜ7Jyžîhíu¹¶»êiN?N!ÉŽ¢m²¢Öyýï'dõ¶’»J&t<Š;VµÃ&Ó—ô¼»3€D·K—Ðôü]•í3Ä éyÖ4â+5Ö/<ã„Ê ÑmÕ2`£>‡ÔŽ6Õož»ðVµ%¶+bÁŧMtÒú$踺;Xj;×Çú<€ ¡í|ÞÙ`xŸOˆ=WŸÕÒÎàåmŸšíçÖ`KKzŸ;®ðèrÕÒ€z.>Ž×>£È÷rÑÚƒ<˜Æ¼ŠL ªa½'§×ÇXæ5M+ZÍcYf+ZÖ´RÒøòwÑÕÃ%m=6·—F/Ïê|ÿDE3ƒ™¾>_»Ï»<À`Å:lRõtÇ6ËTÁßçìÒÔ£ŸoB—Êa•Ȧ‰çiI'9­Ï%°¯|«Í÷SI› åPÄΚ馚$[Êþǃ¨ ¬x}X;¼»÷ùv/F\Nœ`½@ÐñôvùõM-)ä»ù5Vzkê97“îæç혓‹§¯†œóó}œÉ ù9€F·1鱟G£äè’Yš–œÄfiˆÒhŠzg½t–&) LÅ3ÉêàçôqÜ`Á½6ô\]ÞéöñbøF¶ùôÅ1<1]ú9¹}¹šãH¦{¼ÛÉ[ó:99vÒå¹¢^ÒìóîÕ1-5Ó]4ÄÈKK{;¬9çæ»y€’³ì<þ»^ÑGJy¾Þ}fvy¶ô|›€åmŸœìçŽÐ‰öÞod°4gfÀÁËÛ?1ÝÌ ·³óºòC1æ;yý&÷)`<½²ó}œñÚOÉÌ­ÜzºXoÓÇ~fØñ:¸±.ï'gc ô•6‘JÌD34öåâtavµÍ³»–ÑJW—¶”qn„ó ȦĘ̂¦–YW±]ä¿,ªš_MuŤ•˜íõþ]Üì—®~{¯oLHvùvô<»€50˜b@“Ôputò¸D¦3Ðä­¥@òs­­w»‡]zôõó´1¤ñ[5$×ÊúDÖÂÉʹ$Tß\Ääšq[ÛMtÖ÷=-ìüî½€©ä{ù{Ü»ô²¸átåG\êÞ°^ 6‰÷gfÀ éñb@µ•à½t´·•ý‡Pòü¾‡“¢þvÇú´ô ~N`§TYwtqÜ™+js3N|n® Úyôã{œªúÓ¢4ÇlöLŠåšå©#Z¤ozyÈÂv@bÓºë¥ÀÖ}·›Ù”5<ï^ÌTÒµ´ ÒÝ5äôã¬ÀÓðôõqÐ/jy~îP>ÜîŒ@dµŽž·‡§cÂú|]\5ž¶ïòíÇ÷sSÛ0üœÀ`“FÎ{ô+KØuÑÓ=èy2)]­–@˜:8ôt¹=/WN[Ytñ¶Ê9™ç)UŠ4–sÒ-¬¤WeqÉ´ÓtbÓ¦ºi¦€t°×©Ï¯S+ìA1æ;yèí˜O v‡+|²%€¬íì<þ½€òݼüÍòKØkK\õ´bmcUÇÓ^ôò^‡(±ÓÕqtÉSÅz<‘^  ù9€¦L;ö‹v9»yúáK³ÇÈ!i&]=Œ7šš[Ç£—ÓÉOn^·7_OµOžíà¥ÑÂ0b/™®ÙôWÓ<«¢Â8Òäód™Ó]4ÓD€c+Ëé8º-Ä€GJy.þLK0Áj–èc¥º^jÍšZÅm纰âu`;¼»÷ùv%ÝÍClÀ†ÉÖ`›;úîŸ?ׇ/£ ô<}|u’$€róýXqúq ù9€[Zb ²ìõ^§„óîà÷øûßœ%ú\~§{Ÿ}â`™Ô[Eå­äD35tΖܞ_½•ëÆëó¢™êò÷[ËxmO?×É×åîäöùVY&t×M4Ñ KK{?®hNùÑÒ®}w‰š³<ïV<^¬Š[ÚyÝ`¦<Ço??lÀ%­¢µ@d·Žž³‹§Äú\zÌ6¬õùöìa¥ºXT½yúÓ‰ÓŒW®Ÿ“˜d@5mówÍM¯ã¾—ËÍwy–mÍ“Të]ô𦳳Á]¯¤àõzì"•ªÇ›ïóQn·'¥4EižrœÉ]„éåÌ{jEY½ÊÖ ž^ÙÐ×gO©ÄĵÓM5Ó Ô·ªáê¹IÄz\‘Z {;®å, ñýü™­«ëœV€\ÇOMÇ¿ô9q1¬€NõŸiçuï/ÙÏÌèÈòsÁÖ¹ý×'|eÉêá†Ùßçí¿I’­ç{|õ²ô|>…ÜzõF²…k5…òåé^¶Ç+•Ž5Õ‘z­ N„ÚÍb™â*ZnÒ¾[Ðó9[ãi1-tÓMtÄÈ 9ßÙùýdR½|‡¡ÈÎ]½.ø8Xò·Ë±Í·K+ùC—Y€18˜;\»ú]¹[SÌöò¤2¿¤ãèóœôµ üœÀÖãÝÞÅæíÙÝXÏ•®{Í.á½”I~N×7X‚fxVÓ¼-Åw†…;ZTo U¨†gYKÌe¨‚ùp7ŽoW¶æÈ¦—Ó]qioì¼Þ¹â@ƒÈ÷òÒÖ€ ¢}7N¨ó=ÜÃÔpuu2¿œëÃÓˆ$­£µ@vywô»lyþZZÐ7‰CKFa‰~N`–Ñnž;tùû7‹yÎï7O—¾Æ¶¢–ªÈ4N’‚fxˆÓrµÖY†%„áÈj‰!f®WWW—²½¦…­Ð­ušùCÍßN@4Òúk®- vywô<»lœx]xcæö[¬Á1ã=.Mf ë:Ú0´›Ùkêx÷áôãçú📘`®ÖJÉ;Û‘V7ϥͦèqKRÁô<æ­Vgp\­lV3.&·­+´en"Ô³¡<—°éŠg‹¥úô®§3~~g5°k¥ã×LZÂæWô›ô3¸H¡â½.8íÕput²¸ñ~%{ÔÌ'¥ëé@½ÉÑÚç×ÏuaÍß(­üœÀÁ7?£èø»«‘Ñ™–#dÔµ¬V2Œ”mi"69—¶È»X¯i’"C6ô¹gˆrt¿[:ÎÒÐÚž·Ï‹Nm¡ªs1*› ÞÚk®·¶aè9:;<úìmý³¯jÇhÌOkŸ[t°/F^w³œ¿>¾‡“£‹Ñ¯fRÞ›¢þw¥¥|Ïg5m*~N`+´Ù(cÝí<ïBí¡ƒI›ŒJå*!´í "7KÚ­F ¡¼@­23ævF¥KNóTZ$æsçí‡;£“]9¤TåsöRz}yXÞ—±ÓÕqtÉ@’Iq÷ʆ¹ÐÛ=$š¶õþTõrñ¾$6¨18˜0ëómÏß8/@ÑñtöpÐV´y^îZÚT'äæÎ=¾ÏÎôyš_bÕj+Ìݬ`†fx¢¡{_¥F&kÌÙ­@ÌùMôÝBšoÖ=9Õµ¹Ûáoú¬7¯™ìãçôqØfZj‹lŒiï6úNŽ–ZÎÖœò­zÁhÉs;t2Ò W‘Ñ”v€vy¶ô|›„M{DÐØ<×fŽŒ@¡ãèísë©Zõò‡&$:œúúŽ>€9ºÓÊ÷ò€É€üœÀVk%ãÓRk~ºS¾sçÑêyºîÄÌ-µèZmÂÄEI›1«‡fiÞÝ*QÌÒû@nˆÓ‚ôWŸk&-UÍÚWxJóÝPb^{§7o—;0‹Nšé,í˜VÒ ‡S t˜¯x‚ÕëóíÞåÚ@9åæ»yÀÌ=?WSŽF\Îq=-í<­ëã}@2½=h~N`#ZZÍ<÷–&YŠ4ÝM=O7Mµ¬" MúTÎЫiÚÄRµº®Ñ<ëZåk^Óz•—¥úT®}ínµ±X›{ôk\Äso~kâ}O{`ÄΚ馚$–¶ž“w;ú>MÀ­©æ{¹p3Ç6Þ—qÎÖžs³ž½ëè9:;œÚŠÖéqµg[@’–Žõ'åæÄãôôµ.Åù¸ô{Ú^üZDh›U®&k̑ΠԨ}/×¥s/Kߥeˆsïn…*!´Ñ›u)Jö˜fb™éçHæjZoÒ¾SÐóêïÃ13¦ºi¦‰-çcçõÍÛ?-Û͉6‰³ªkOAÇÑÜçÔE/èr]ÁÕb³¤¼w%}*%¥¢½Fa˜mv9¶èe=4¢rl€IZ½^ßÌôëÚÓV"™Ù$…{MÊTG3Böž±¹*Þ´4¯'£žvùö¡¥<Ÿ¡Ê]ÇJ[gc;{/?¯cÅtù `‘Y©k—Óô<½ÄRµ¶EêTAiÈ„„6k^6š]­dEŠÇžÛOCŽc—¥÷‡F”‚Ó=` fy·´S3Ö:4¦@ ´Æ›u¨•-yüw£çÛb© K]4Ó]12Dë0=_WG+€–•òݼÐÞ Dõyõäôcëx:ïg`*Z8=XÇjõ¹¶àvsÃz€¼¯ìx:£)é^†v+§É@Òvq/ªeSd+ÇE¯?Ó¯[s¥Ô‰õõšó3Âx¢#™Ô‚Óv•/±ÕΉi1Bt­iëçI"6™«æv6ˆ¤Î¤±+G·ƒ›ÑÅ]¥–@%‰•@k¦ši‹HÌ1 =oŸ×;'™®^c»›YÒÃ^nùw9vô»€ 9ZÓ“ÓŽ Ûfbq1׿ÛÒòn+§Éh›Yt-”7Ϋ[,€kXôõø½Z‘âa™±RÖ’#£JË*ZÚÊÕb•§¡JâCYŒÃÞ³¥£hS‚HfkÌì‰a¼@^Ób±œ~®+4š{aGLèéITÝM/¦ºâÒfÖÐÞ£jϺó{v´òÝܺÈ’ZXwùwëá yþ\RÔº2 ·´óºÀÅtù &]] íS\+Ù ©* ÛÖyþž´èÕÄM3_K±ÏÓ¼EY·J´ž±‰fbX@ÊyW½ˆ‹‘D2œÀoMâÙ0fÌCzKK¦5Dµ¼S3@EjMKŒJç%o½gÂ6‰í;BhŒHf­­º%†Ñ—žëãäupË9€+̤ 'fbÓºë¥Àž–õ¼V+ âôeç{9À18˜è㧨áéÜyî¬8XäÌN³ 4´ùÚ¶|@ 'fípúµiÝÖ¥4M¨®a4@k1´MKZÕæÖë^+˜6Æ|ôÄ¥¥†$3 ¦Xf7¬âZÍq))`#µhíÏÒçê¯k´¨I¦zÀ¯kX­@äôsù®ï:yÈ ó) ó(‹Nšé¥ôJz[×puOYƒÅú<^ ˜ H©†¾“¢H’—ìçåôd?'0IÙ€=ŸëôðÞ•íNÖìå{MŠÀÌ9^íbÕjZ™3°f9”ÆQ½dihÞ³«%lŠõÖ/ÍÚW Ciš°+ZÖkP0sv§#§KR¦˜Él@ L鮚i¥ìïë¸:€yÞ¬9{ç êDë0ô·¨áé»K/ÙÏËèÈ~N`“³ty»»Ü~…;^Ý+!RÓb±"3(ÞÓDX¬H‚Óº’VÀbY†–âP‚QÌË ë¦zÀ†fh¬Ä3ië fnž~w•º‰C®ÞۃИèrÃjéhõ-V}'LV¯£*šS&$a‰?'0IÙ€,å¿kÓ³ž»ÃYC3f±¤µMšÔq´Ó³ž`¥‰‰« K0ĵ˜Þ²‚Ó,D6k¦zÀ‚Ó=`Ad3ab±¤´šøïSǰÈDõ¸ý_Cž¹Û4ÀÁå»9ù½Ñ>¯ƒªÅ-‰ŽnÙùî¾|H^ÇJZÓŸ—˜¤ÌòoƒÙêa¶²6ëZÖ´±9öµ˜‹U¨ K0,–@1!„mS90ˆ­6©W´á,@ÄÍY›1LØŠÁ3ªmV£Æú~^ºr€ë¿·òýxfgÓ<íšÐRõñÞ KKZ¥½?LÑ$U´i1 £§–‘ÌPÖžvþ`+´°Ì,eÑè8=¡¤®V ”Ђf§§Jê77ˆS9$ˆ Ö¶ U¨ f6‰Aiž±‰sík1B O ¢ÌàÁ Îärž±‰`ò¾‡™®¼`j˜ZXd­ßàõºônŒ™Ö’ëšÕ9úSË÷sGh%fÕ/ÒÇM&'¤õ1ÖJ’+§Év–€êr÷õù=LJ ›UÑ-c™¥ú™ÐÎ bAi’šÊhf ¶¶ u¨ e“0LÕŒK ©iš"zÀbf­¦Õb)I ¢5™Œš#YêøóN@`yT7¡îx»ëZe¬i3f+¦µ“\רsô§“ô9@BöZOKlskÀ ÚÙd¿¬òý¹âkÌï¤Î U¯.÷µjµLï¤Î’ÞÄE2"™–!lå¥ã™§kYŠ’HU´ï "%ˆk1´HŠgsh Γ;"zÀŽgHå «-o4D6ˆfg¬‘„ñúyy=ž^ê€mŠbŸ¯î²ÞZÆ“2Cxˆ-3Ö×;f½@Áâ=.8íÚ'Y€òsj˜W™Mìqú}n^Ê·ž+^Ó nV©6­iÞÌö3ÌæiÚa™­3ßË0©k[­@ij#™ŽCMMÍL&Õk¬ÆS˜Tµ¶,Ö¢½¦XC|ô¶v3Ûx¶bk[dI Zãå½*YÌVåîçS]«µÌô },…ˆF b!´ïzÉ®M*<Ÿw7?lÀ˜œLŸ“˜ í,3IMoðû‘LÅ-G…#™³ZäZkÌߥD34/k‹‘›Û¯µ­fµÌ e´3;›ÄEzËK &y÷µÊÔf f^Öš#™¶^o»Ì–ؼNJñ¯ªóý[TÒJÆ&jÚmV4 ™µÌšD[ÒÍó‡˜íçæï Ãm„b@òsÂE@®/S«Í×^ËUji(æPØž"¥¦ ˜fmÖ½ WYšÓ0Ë¡XÌ@µ¤KuªH¥£zÈĆ`+ZÖkP©k[­t™Ù†&3­­­²ò^—“,äc‹Öîòôbf•¦õ+ZÖ–"½¦HŒ€Qµ¬Í-ë–ÚPòݼüÍò0Ä€üœÀNÌVš÷¼¿g¯kK͈ˆ%±$6ˆŸ{\¬i(ÓnµŠg›{u³¦@¥k]­@$%˜ZÖ³Z€+ÚÅbX€1,Àò‡Ÿü kw¸=nž;ê›KÎkyLDYˆìeP>öèE3¾mj@Ìvsòº2Îw†õÒ`•˜í~N` 'ft×ji®~¯Ÿ«iâ+LܬIVÖž#xRÖÁn"¥¦å+¬Æ“4fù,ÄY­@«kZ­@1,ÀQ½µ.VÊíRÖ³´@–`_:ZÓÍwùr¨DÝçíõ>†ƒcR+4™åZztŒéP;ÚÕch…£;ÓS3ÍõáÇéÄ ¢FÐÒX˜0ôÜ=\nœhí˜üœÀ í,3¾zéŸE”mÏÝêrÛ&ȆfÍbXŠö˜Ó29Ö·_:G3¥ŠâbJØ 7µi™¡",V%ˆAiÌ#™ŠZJHF›1kQ‰f<ÿg«ŠYÌ×>¿cçzRÖc™ÒD=b Lõ€s<ëÚx=¥ó)¢)ZmD[ˆô†u¢`«V˜œ! …¼ïê8ºt˜ñþ üœÀ 4°Ì;¼½ÜzvDÈ4–ðŽSÖ9:_­+ZgŠíÒgI !ÆÒý¼³-3Ögµm-m β±XÒg(Ú<§¡çV߈Á×âõ{<ÝQLÈ¡±ZfTjm LÄæiZÓÄH&gˆÞ#IA2†Ó4͵8]9p:ùÀ&ÎÞ˃®H`ðþ—–€OÉÌ-¢fPg.GçúŬV´ol›Ã%šÔs4¿O:Vµ¬Ö æD‘Sµ¦ˆš 5–ÐFö½Zà†Ñ5Y‰¯iŠdo òžb6ˆyKÌ‹^@ýgêÛË}Ib†Ó$6ˆÒf)K â#™ŠfÍj[[tpf2Ž$¹RëÃÑ-J]Ó% ÎÒžSÐåîroè9¶ÉwsPÛ0OÉÌ»K ÀÆ]=ÞVJÛbÍkÍKOB•áôã>]\ë¬Ì6kP"™É$@oi¢,VYm«ky ïÜ­yV¶Ç­Ã=&t/Ö…k[RÝj”ô<êÛñ×oåúðÌÍ Äi3¡͈¢Òf9`ÂlÖ W´ìdÁÅ7;±»ß£Jì7ÊM+›@átåÀëç7‰îòoÛç× Š\>œxX~N`'E¦P6‹ö8ý¿7VðZG£Ÿ›¶÷hšÖ´±—K\¬HYˆÄ'¬^—¹Zí ¢ f`”ðÙ& –´)¹Zˉ À fkÌÅ+•5ÛÁO§ÏF:®O6È/òú>—‹·I˜¥±‚zÀ†Ó5`+ZÚ–ëP+ÚdRJÛIV™¯3)γX­ŸF‘fš4£zfÕ+Z<‡¡É M¼íÖçסíÖäçú±åï•{Ôòs/"» õÓ­ËÝÙåì­6É,G'jöqm^Ó,6<ŽúKv#Rhvr ŽgR…íÕ΀KZÝj¥k^¥F%˜ Hf{ôiOêyKóOKÑñöS½­V6ˆCiš°—¥úyÓ ¯i±X™µ)i— åèŠdIËÒù-Ö4—7wvyö½@B½ãË÷sSÒ€5-í¼îÂq7ËÏvó€OÉÌ í&Sd翃՞·çÚÝ*WsRµ¦í*"™ŽfÍjQ½µEÊ¢™Á²0lj›5¨νú4 Ä³’Yšó6b¾Öñ§dYäõû\ÝRÄAi–# ʉÊ-V&ˆbg›{MXÒ[—iQCZW›_ÎÛV+ZØ'ˆåinÆT¯i£kté]ïLïŸ?|øXPÖšÌôœ]=Œ4<_¡É_J€OÉÌ]¤ŠÈ¨.}ƒÔ‚5Ò] WŽ[Dɤ DÐܜ楮V'¬ Là¯2-V ¥£14­kô¨Öch©kk*šóyžï:Ì唹ÍézŽÁ¤ÈŠZ&XŒšÊÁi’#DÅ(¦EÊÄ2†fÕctn‰ab±^Ób±Nöš±¤ÍYžk^fÄD³XòÝÜ\þηÖ÷=WWK+€'Ñä†õŸ“˜D¶‹NÏ &öDøöû/?¾•¦ý**éY)zö™¢<½«Ç/›ÃÛR@…í~µ@+ÚrOXÖTï‹î®…ʳ_Lìg §k]¥|£æÇ·"½gêÝÊ⑉OX­2B9›5¯>ö¹X’ s¯~)‰ž}­Ñ¥s1KsS æ^kÚb:5ŠÒ¢9zÓŸÑĶ3ß—[Nšë®—ï.ýþ]€U¼xßGŒOÉÌ®ÐNÏE´Zvb*uû?7Ô5/éXlÞ¶’©,’C›1ÔZt$!—B•5™†UæoÖ¹€Ä±1µdbY€–JÓ6"<—¡æC· rìö~o¤*ÚÃ&ÈÕ9EŠÅkL©kt)@5™ÐŠf[¬K5™‚VkmhlÐ¥kH‹‰Qæû8¢µ14žqÖø‹Nšë¦—z[ÚùÝ`8]8ð:ðòs¦ÚvrªVÞóÉöu(ZÖ¢'ˆÚ"9žU–b8vv*áÚ}W«TµªZzÙÐÊ•æxS˜Œ&BjÀ §hPÛŸÌú\ª VÝPþ“Ïõº¸j&a”õ€"™Ü§i½J¬Î ¢(^ÑÌÛ­kM¬ÅplOX¯i¥kZ¬[­|¡Áü; ™Ó]4ÓI"}¿›ÙOïä§­·•êk@18˜?'0ãwåzøO:ÖéÒ€Aiò“Y̘®uÓzè‘5`rô¿S: Q¼N¬ùßÐ’­3Å`Ó;ëDºlDC6°§‘ô<Ý5ä õ[^sì€.=Þ»ÏôpA3¤®R W´Í JV·F””IȘÙDóíi •ˆ™‰j§k b±ã½(÷ó²'OAÏßܦ‰€{G‹ô¸À6‰Ö`YÎÕ´¨ ù9€®××ù~Ôð§6éR€VµˆÜÌF’ŽfÍcxCiš°9׿F”+]%fDk1¬ÖZ\Ai e-4ñVŒÊÔ<ô½¬Z·O¯‚e…Gçúý,u–#I%5bµ­¼ÝóÖ`òs^7÷~W±˜ŠÖœ’Ù{ ¢%„r«6ìç˜Zplr¯{µ®‰½Zï²çkŸOÖY3ZÖ³ZŽ-ÈbZ•áöñW߀1Ÿ_²ó}=Ⴝ¤a6«QϽ‡B•æin+È’@CYšvš³kõ®¦L¢h`’#Yêy3[¤kåª!_µÅéö9údˆ«kLŒëIµËÊwòÐÛ0'äæ\û=·™éÆÓIÔ¥!™Þ}/ÜË:öšv·S:-3@R´Ø¬R½éÒ‚)šEªN%”OYÞ#Á´*ZØ$G‰¼vkùŽ‘Åíà›n=ЭÝáõz˜tk-á*)M¯Ö¸…Úõ*<†úÚDr#%˜MŽufÐLô+XeÌS=<饢5§¬èùñí­ªÓ3­Üáõ»œÛAiÕ;Äk(oO%ëyYÖqiòs SÑãõ}/'@ÌÒ´Û¬lŠÓkÄuí¹^QÌÞ­a™ŽfXˆ¦e¬ti]f`•šÀYÑÛ;Uo[ òœ¯zyÞÎ še wµYTþƒƒÕ¡T-7¢'G×êÖ¾§ ùÖ½ºÔ e´²É=b…í’õ*¡{_¥D32™ˆñÞ—›¦¼`<û}¿›èèšöá©ÌߟÝäl4¾šë‹H'äæ/rz¾Ÿ¤ ¶´RL[¢HŠö›^Ò9wµOf•æ^s&l"ÕB9޶uJö«ŽWªó×¥­¹èß:[óÛ¾Töx}~¾Ö™³¼55‚fõ*{ZXâ˜Ó¯Ò  fhÚÖ"¶+ÌèKâ½?/:r€i×OI𶙢+ÌùîÞ÷ó€éxõÓ° ù9€êqzþƒ—q̽îÖ³DbQLêo b#™ŽXMš×I𵍭ŠÅ[ZXŠöœ•­\ Û1F+o£“¥5‰ÙïŒÛso—Óõ<]`Úõ*6¡¬…;[ŠIJÂNi)¡ÚHŠ7µÊÖXŠ–µ=¹¼ïw™30^7ö^g«$^XA-Oêxö'ÞÚk®·°~N`‚ÿ'©é¸ú£™æ^ÝÔo ‘˜ ft%ˆÖf´Í¨ŒÄSµ¼‚=ÏF)Fóׯ€ wpúøù]ªV¶ä±Kchnˆ¦f¬ §h^ËuŒÃ-/Ýιˆ öf#ƒ×ÃÈëó÷@kt9»ú<¾„‡O: Ö¶†Óë#™’"­*-V ™ÒfÕjÏÅú~Tó‰i®šé¦&@ ù9€+´õ^o±w;Òµ­ElV¯išJzÀÌÍk,Ô/Kô©]¢Öch(ÞÔöäàvùÒÎ`F·cÓêóuÇ!±v•áízVšó6!Ñ¥x÷µ¸‹h¢ŸK97¼ÑÕ¹¤Ç«‘ÓÁ¼Ð×M4×LL€üœÀšz^S§†ôí6b6„ÑCiÐØCBhÎQ$>ö·X– Íòòýþ\ók]½¯—ìfZg¬ÈÒý|è<æ“V3ÏôòÛ¶SmÍ5ò-4ÓMtÄÈòsWýwê\ÏNeíÕΚÌÅ)ë LÞ#YŽ~ÙÉ[ÞÊ #[dA3JÖèR·kP^Úœ®j!_ÖùÞ­ÜvÄÎb1!˜4™ÒSV+KÝ­kM¤EÚW‡ÑÏÁïó&Pi}5× ù9€õûO3ÓçÞ×ëˆÌèK ¦jÀÒÑçû¼Î‡7wO …{,T8›:¹M+Ú3­Jæ ÍèåæôròvÊiȳÏÝèx½!jµ6šö‰«"¥­ÓΕíhæ¾SÒòw·8i}5Ó°?'05é÷^W«Ì½út  Lõˆ¦eˆCi𱤣”õ6Ó×É¡™¯3zµž° ½<ï•U®5ä =WêZÏyë fvˆÄÌf’±X f<§mø¶@k{鮚ÞÀ?'0vž¿ËöèÚýŒé¤Šgdlj›®@!´ÍX«uªs5¬ž&xˆfkZg¬ZŠó:xüß_&öÇdOg©óý=%,6ˆÒf¼ÎäRÞ«PļŸ¡çUÛt[ÛMuÖö~N` í=—ìâ/‰[¬ ÊzÆ ö›5€ ´ÏX­tÑÖb™æékµ-B7®“Ds¯j=}=2‹n0×ðêõ>¥VÖ£hK^ÖÞ"X€Ö^g»ƒŸÑÅ" kki¶šÞàòs'tEN¯UæúÜÔZµºùÐm3V6™«Aiž±§dKW?I¡kP´ˆ&mDW™ŽÙw\~{³†v`%½¥×æêLâ;Úå*Z¾W¿Ï¯·.ê€Å¦=u×K€òs*FÒMggÏŽ«³Íº«¤ÙõúžC—~ÖèV¸€­i³XÚf¬ &på´4•#¡œÅ3©4FÄ5tËÎwy“[œC.¿_æú}@–NË´f9½´ï~žMí†@ Zt×M4Ð'äæ`ÈN~ȹ»¤­ó*åH}J’…{MŠÀŠfXˆ¦b’k$LÕ¼†¹ßÓ;Ùé56C_¿Ì×^<€ÓY¢s5©]íóuz¬:2šºaéÀëãLCjÛËZúe¬ÄŠn€ÄΚ馚$'äæv¾¯ÍõìÒô­kT¥“h &u%ˆ­i’JÅ`bY‡"ÎÌ8$œœîŽ9óÛtZË^>ø´æ‘PW.ŠYn­õÓ+e l:›s˯4Š€ÄΚ馗L€~N`OÏèúÞÑËÒýLéé-.™ëLË̱x». Ú8záÛìóà´e€Þ'I€–ši®ºbdüœÀ ÷ø}^®ÏXãé§c<õ˜l˜– ¡±¼DS;«”æyëÄdpÐòéëŽ ”%®ši®˜™OÉÌTÃNŸaçz¶ªäi~…+$D±LË)™b1,À%–P•øŸ+éy[ߘ©Ù¦—Ó]qiòs®×¿ÅßÖçëÕ4/=Œ©Î’Øš±‰f¦s4Þ-˜§®~CÑò§œ€«lDÛœ 4¾šé‹Xòs®×ÖùžÌÕ™¡FÖèÒ€Uµ·Dõ€ŠfX€YxÏKÌΜ€"[$Š€ ˜·¼{i‹XüœÀ)GG§áô»\úVµ©Úz”®b#µu^9b±´HÚf¬ŠÑçúùy}^tª€‰a* k{i®ºÞÀ ù9€Wièø}.Ï7@äi~­+´F–г6êÌ+ÚÙFA‘ËèÂîwŽÔçiZzãKni'-Ð@Ó†`­í¦ºk{€ ù9€IÇ/µŒ÷¸¯K8êgQClîe¦ð LÕ™·ZùNþoG&È™L€kiÓ]uÒàŸ“˜x×ÓùþŪM _«JKT™a´@“CÆz>v5âÈW™@´é®ºipòsŠ^ËÍõkß:ñ·N”†Ñ,OžÓKñ^}­¸'Dpìg\¢HxßOÉ_ q½‰ÊE@ L鮚i¢@OÉÌl=[ÁÝÏß– ôܪær5™Ò\›Úýk!¼<—¡æõpÖþ=4vÃ×Å<àÄΚ馚$?'0 sêö£^ùiN¾(å=/*¶¼Â³[ ²Ä´ÓMuÓ üœÀ ý®Nøôæ¿L9zlèÀîójé…;L¶Ã %®ši®˜™ ù9€ž›ƒÒåõùý¾/B*õtó¨3§q:0‡n0Ó]4ÓLZ@Ÿ“˜+5ög­Àô'm1kMKæÔôž«wãµy·¿[(O\ù}|^Œ&¶/µt[Ð4[»Éé <\Ý1êåÑ©$MkRzÞµ³Öc§—D¤±j¶Ïš¶‚Ôž·›Ÿ®LÙÊ”ó¤Ö›ºÞÆ!™VλÊKMíõó½¾}Ìõ¯jk1f·Ñ•ºiÍÓ¦]¥óš/·ætSÿÄ5 !0`"123@P#p$4A%5BCÿÚÊ£²Çᵸ­Zßþ<«ù•Ji¡Z?ï~Ö+tü­«¤B©•ò‰˜äº¤iíÜÁ:[TêŽç¥½fñ2öE˜oì·w²ÂÕ?Éžèu‹%Y-õÍ‚¸þEX/Ya¸àãÚ«_VAR¼B° çïn¦ Jl1eIM¥†2ºeìqB+öëØ5‹ZN/cÈn-¿®¹nQ‹ºdUº‘–8†ÙmÖ0H¤Ê¬(¥jþß Y°™<·Îp×y{V®"æ¸t8t¶^»ûÒÓk©®Ÿ.†Ê !\K¬Ë¢±i/Ì‘:«õlh¨ZΫ ¤qW R|Jg*ÛëË:ƒ/aö!2óRúCëZ²0Ògo VØö­\¹U®&pÖõ¯Ñ´øm9fVß'û¦0T>&m`¹xPÇUÁS TÔÓ)2öv|?„û@Œ+ì9H/ñ¬¸šÎuiˆæšr¼Ø+#™ªÁQ†RL-^­‹P¹;æ'3¬óŠì(§Jw{Y çˆVx…gˆVx…gˆVx…d½%„JȘ/Û6Ü¢[Äq2v |†7IŽÜ ‰&Š!y×[Ä"!–I¸¥KIÅŽ»Ó;ðÜlö¸Æ¤1¶=X`ö_,òên•²?…að…›Ìû¸{"Õí0šgÏå‹â'¼N=> ãÚô…ª®–V/léëèœéûhŽY¸,8™Ï‡ýü³brsÛ·ÚuuVí½±ˆ¾KŲ­Tëᄬ°|¦™ŒGáq3ø½Q)©o¯‘„0MRÌK”3ª¯l3éçòîgì.7¦™9˜ì‘˜»¥f4š^Ï’”ÓNÆðÝOø¹Â០QpäTtç€n vÈÏAu;¦tÉâ ƒ†„Ƽ¬[c,±½œ1ž\Üx¹}&dN™Zî»B€Eæ Ǭ%#5­Ã‡ÒâeçÙÚ]Ol˜ú-ùn,ÜY¸³qfâÍåú«Œê?ÚÔÔ d G£¶'зbZÞI¶ÄAq9ÚÖŸ2³H¡nìâkйF˜'¼*Úóõ¸œÌ~ø‘ A¡Ä@ºÝŠl¤ª[ê®'_k³éôYòýsY [ ½¬3¤×gQiR§wGœªŠÄoÛ2e#JÔçÏ„õÖa+.ZéSZìªVÑlze¬ ‚d³ð©¶AÞ…´õÕòîá°RÏjêxRÍÞ‹>_®ùåÕtœC¶}­V"øg[Ñ:J‹rï–ë<©Z…vY­"x{bYQŠuÝÐbœO‰+½TØÜþ0²xk1”zHôV[×è_­¤öðèˆGµD·C>ŸEŸ.Zéè¦|¿A/\gŠVMí’vE¤F~Ö [«zÆp±}Ów£Zì)fREέÙNDÁG;ÉcbcNÎ1·Ójú«m&+±Mòš žÑ¬¡›È\C!=µjËOÒ»[ }œ9ÚµiÉJÎ7F‡“1;ât<ÐóCÍ4<ÐòDç4<ÐóRƒôîþq2]σBëÉkížö½C-‚Ûì9’’ôUTšªÔá1}=3ìÖr˜'<›\áæ¾|44OnºaÜaq1‚Uźqî„,øœÌ)ä–©ÁÐQg„NÅcÞÔ‹¡œ4dc†£Ã•Ή  õ,£®£ Ys `ýªGØW£?sÐtyôóftòO˸f‚+ ?nQµ½KÖ¦OÒ ùg8…÷†GË,? ´ñ8¶ˆ°¼áåª9™l ¶Å‘k¤àøyò• qÕXT  h6%a°2׆ÜážTã[>Õ‘‰žgõz'³dæÉÍ“›'6NlœÙ9²sd沞ӿ»âÎ {›í¸d`úwÑ_4U7͚҉ܮm/û9Äçáï]¶¨PΪ¸ƒ7;*Ý„®gYNýé>¢ùî ®Ò~³Óع’˜±ùó;`§qsS:LŽ6.OÛõz%÷=F“ôÈþ¹üETÍþ¶,.kÌîŸÞLù~w®2>§‰ës¯t—6ìõÏ»‡Çüƒ`¯8‹ ÏÐá¬Æ ›y HÍ[p¶s»µ³Óžu–fÉêCÿ>ËDCµ/ˆé·Úçõz%÷=%ý=… uƒ™ÓœN±êü°ç@­æ‹(‡®KXöDÌŒN±ŽÒüН­/NÚ‹Ä3§>ˆ0—/´oô¨(ú–®Ê ÎX}•Æ$¹K(–i·”Dä\C®ŸvflvÌëœ7vÏk—ÕèÍ…Lmœƒ‚3ª=a´³xL0uÚO5ý3®L騡çÏ3¬F˜GPQé”nÆ£­n–Ñ_°~YÔ-¿”, ÊŸcÒaÂÁ®'à©Dé M1˜˜•Vc'ÀLUKÝ×gb¶õ$úWטoˆ Cúðc:Y^îIwJÄôGåùÜB4±ÝÃÿë{W]yÕèøgí_ }~K±[„½iáõ:6úL"º W©ÃØ£¥Âeu8zÍU1N¾}„0p0Y# B aC§¬ÀÝrÛw±º³ñêÎêþ‰œ..Ù¯ð¨X`°Yšrâ.Ô·NÐÑK…ã–GÄ,^UÙòÆiî!…ÄðøˆÎ¿X›’2=Ü8µ¯í>¤g[Î3©Lz‘HΤgR3©ÔŒêFu#:£’R3©ÔŒêFu#:‘HΤgR3©¯§¹“°‹y.&=žhAŒ‰[=‘1¤þ0_RâoªùIÕ6ñÛò‹/볿adÄÇ%W7a×06Ôbbg+Q–gV–UÒsjBRž% A†z3H€1d[Ý^Á”ǘ„oŠ2¾«?¹ŸKåø“åÚ²%ÙÃfcÛõzS›ç7ÎoœƒÖ{g'\˜ó9¾s|æùÍó›þ&‘îœø¥Úhy¡æ‡ŸOS6èß›£^÷¦ >â¨Î›ñ¶œw-ßÈ;?‘nM†dF¸¾Ãã ÃÄ'Ã+\!ƒ ¡=4çòæp7,uÎ'NÅYb±­–Ÿ  ÊözJcºµòA<ÖvΙ¿v)ó»Œ\$úŠË.’zï°2½‘±®[èÂx†>Á\$­_ˆÚ‹nG F’C:c#íƒú½ý<çî÷‡Ï¶` ëùŽOÕÙ¯)8\D0°Glz×Ó¸ª½ò67Ë{†›dÔ€Hú·MLƤ:z›¦G·å…db®-Æ™+ 2ä¶Šx™m;­`ÌëÊœ”X÷‰ý^‚þžs÷;Jvæ¹sµÚ™wù F¬Á “Íã9ÿ¾þ%?ßî$/¤›´å“¶uðìÚUÌŠAºA@¸ü&ƒY bg‡L™ðæ F@Œ¢G¾½b±‘ÃY«6*²Ç¼OêôôóŸ¹Û¦¼£îvlŒ³6Nlœˆ.¤ÄÆmœÛ8$½sdäjF+Éœ•DNâÍþ|µÂ²¸Æßaf¾â¦®«û&5Ï ¬ˆÛƒm½sü 9Y&yãÛ“:Ï}c€p¹0/âe¼²œégÝ¥Æ*+ŠVm”qŠÍVÕè/éæA»40ž¬`”f¼ãîv0¤Ï"4ä?W/”+éÈÆ*[Çå‘:ÆLnˆ³–®ƒééš{ŽŠ:Jü{W ³ú>dûµ| ¯\8h†#ByÕè/éî$‰I#YgK:ZgR@ãåί˜s_`ý|ÌJ tføÝ—ît`­YœXíŽZfßo­&Ù¯DUù/áûÙl gô1ÊU _»AÌþ¯AO|æ¾zÄò8ÔD§nùÍó›§*ýžjú9ü›Ùö' wì #Yýû}¹RŒmˆÓòÞx»‡擯L½@I³ d¿ÂáêÞïyÕè/éôËè§}cœùbþß-<Ûå†=@×-ñD–ëòô¶û8l/Òáê7ô ®¶ã8f5$‰îJIäª+^|²c\¿\ÃÃj=Ü Ç-<Ïêc!Aâ£|Œà2ÍΦt·~>"Pì^Ln×ÅþN™§ïµ™…¬a—6+¹šò¹ ŠgYˆ™ÅÑiåj0™ýó• îoLøzò°_á[O]<øs6·ÚûÌb†8[%¼Œwˆáí`ïXN¡ÌŽ Ä7¬¸“ädd~'iUÔ£#M8ªúlɘˆ†‰Oäiš~ð´’ä κa*ö Õ–˜a¯WuÕøW*–þQ:HÛhÍ[1`}«Ä)é‚ÍØ²eb¥oÅFo (Ñ„P1¬d°bQö¹”L1¿n¹@?&5‡N¶ † 8mƒqiÒ´¾µX=ˆ…äŒtÈ0òtÍ=A=Fû‡x”m,¨þƒ‡xܨCØK$ñ y›ybÕ¯Ëìù,´¾…ˆ`âW”˜Ó@.ÅQ…Ù)ø´fùlæs0 ɢ˜½Ÿ<â#ÓoâOõ¶›üB~ybtDÿÖÓ%ç2ñYˆ~^ßÞÖ¤MÀX¨}‡m=r þ¢ÿãp±3 ìß:Ö¬|K#"ÁÄôÎ'XöwgR¿\õðK¾Š–FWʦJÖc›3O>Q;`ó`ÁÏDÇIþ-{EKÈh_fÄGľ@[ ðÏæmýÒèqG±/'ªžK-† Á(Ü-A×@øfi¿°FL’©©_ç•ïˆZQˆ°O>~Ëàý…@v×`uã*ft¶å[Æ¢‚Ü#ð³±±Œ1ŒÞQ:áÇP‚$E‡¶ wM‚‘L”¹ß‡®œšq¥K]]µ/ƒˆâ•Ôˆc\`LÀ–øüÝ¿¸áëým=rá̘oá^^䘺 [Ì™5*ô ÇxÌi<¢f0kÏ݉çòq¸¸™Hÿ&xVXyA»|c³Å78{ Ýì·%R§£ˆ„“µ%ò1×*ñV|-o–i¯"-'o”yçHqL™)×EŒLeéÒº~Ã`Îù\Ä@r74‘³Ä×5Ü!`Õ¾°6º«b¬õ’ôM† äHþnßÚÄIMJ¾=r¿]sNVwE ÐgáLk– A$23¦èTG4ÑÒW‘+g«1”ì[%Áûâ<ø“5¬®Ðñ*»g¾0‚ (Xè6ct ·+8·ÿ¬±Ï©m®¸Ž Û5fó&²&Ò-¸¸˜×/ÿ×GÙüiùVƒšc5T¸P®œ¾ƒ Ò©M]1i€$Ö8MAùûgA0!ì‹óýÜ„¤f¥¯?„ê ù VYà¬C°Â YÃ'«Wê‰HÌ0¢WIûó“F3¹¼Œ7G»]‚‚Âó,ÛƤÈ þµî–òâ?õ•öýAú‘bJ•+^&)q'4YÅK+›bx´•fÚÓˆ[m„ºxs79àóV«ÑglOœÆ™¦¸3²A·4ý}dõÛ¤{#‰ç]½üÿ2ý}ãêгqŽàáýìÆèékŸ.ÃÊvg ÷Lšìh½ã·~ù‚.¦jQ¸$¬çvêñåäb›:|'Ÿ,ð•ñ@ˆðµAI¦»2°¥¨«R×ò½2æ H ËuÇaU“"QªàûgI›óâQ~‡LÓõ¼5[GÙ,X´lÓ$vQ9:ÿ™jˆˆú‚2d™xÍzEÕå3§ämˆ˜žO¹ñ)Õþ‘…#„ÉáÒ¾¦õ‘.:šBäË«›Š[Ô‹rÛãwÖ™\÷Îà£óÁ &çW:Ñ‚È)ÇbX¥IÞë’W6šÕtÈM‹¸Nb_¸)Y­}UܷɇUö©Nèq‹»J%­èyo‘ŸÐéš~ªŠºö_Ï-®þT$z™Ä_´}H2®°óâ(ù~#ž)±äLˆÈ¶+7ÛêažpãþŸÆ×L‚‰Íѯ"(ñjôwAë¡j,QmSÍBUÙñ:L¶ ƒ`ñ¥Žî óÒÕæ¹þ!è¥q¯´«£ÿ/»­¦vÂPë8<4WR³¢½"‚Lì/¡g—H5%‰bØäI1ÆQ¿Zê$¶² GYMJÖ³I.bAŒÆÒYðÜ!miëyGÅž‚²Ó‚ YØ™ý™§ê8o”û3ˆ§Ï— Ÿ/˳dk‹-?TJFjÚ‡ŽˆEÛ0òN/Ãâ çšÏmÀ7ñ5Óg§ °l‘)‚[Ùeå2Ûì’'zulT@­=6f¸lk­¦°¬ê·ø’øaPß$»Š. ¡$êÍ‘-{ŒväL7åPöWäE¦9ÑUm×Äz$y¸‚QnUxâ6Id>g DÚR9ñHgË1°ÛȆ "esú-3OÓ ÈåkQcÙœB?ãòSeF¦‹GòfÐ lŸT½x2/¶ŠNs‡LøÃâ >úÖà‚'Xü+övÎéÓ’Òm±ƒG©UžÌN¼ˆ`à*ˆ<Â.R3gÿl¾K'ÿkî âªÿ×›¸[¬GHûLd°lN¦Ô¯eq³×È â ëXÛè‘i0E…2Cë¤g‹ÄàñPþU;Ë‹LËZL0®×•2•X±""Ã,C!Åãšü›’‚Å;­ú=3OÒðÕ|^Ëâõ¹‰HMK½oÈqôÕšëø•‡b?Ú ËB}œ¬‚æÉÞ?fä# å…Í\<"„ݪ)BÝÒâÚ óžãñR÷†Å;Åf½#?îžDP¡Új£n²Š;Œþ%¶ÊzêT]¤ÈáõŽ[%dưÁÑýîχyìÀÏ-ƒ³Á|9^Wiv%¸J¢¦]Q„Q$P0ÁÓmý 1Ûì»aÔ¯Ø3¶TÈjÿý_‹I›“øîtpŠN}%é½0_eGó£X:|­¶XîB¨hΫBb 4$å' œˆÒ[yc'yõ ±»£)> 5Xß–(®Ì·…tò·XÉŠ¶?ÅÆª `ÎMûLl(ˆ µ†,õT§X‰£hp᪑(82ÛB3m"BD$N›õÐçRéÑ>²IN%äuÉl3R«Ö¸êž ÜaC–RDs´AÒE¸OYýßÐÕ>›ý—óËT6Gg wã\¸J3q³ñ¸dFß^Õ2Sél*Öfø+ô΃å3¤Z½Óü \ꞬþLL€áƒÈ† &X¼%›g£®bq àñ•°µœ”ºqdMŸ0^+gúv5³ÄÊdÑ~œùÅ[ «,+>!g`¸†:C ›’ 1¹Ó‹aø‹ òë™f®ŒÜݪÜ=å+â  þexÛLqBØÁN›]»z§R’À[ãô=_wÙ·«A5œ¬Òñxþ^ÂâKŒ"“/Æá…ýžµ›Â¬3–«¤©Uúä0pð黟Tä† æT”ñ6"`£;À|r5 2FBwÎLuškÝ"ýÉÊšŸ+?õ¦&ÓD^3îÀTÄ÷\±àéÓ·Zøš‰ â D6à‰ÍÁ\©°åó1Э}ŒdD k:bdœäù~oàå„C°½:*ê?ÙÖ¦îj9S"wG¯®™º2ñÁ¿ÿ_P¶Xõ bÍùgàÓ ˆäm…—«v¯Z>_KÀ&×1âó» ÑXÈ2(#ÑcG›“(ĸl.äéT·cÿæ·Èc|žhÌk>€5p° ^µV+•qB„ Dcð¸`ÏÌw*·GèöúÚé’[°àc‘N³èpÐÑ^ÎâC£´ç,ƒÉõhâFXFG?•¤‰nK]2å…þ¨ï—&4ìõΣzÉõlÒ‡a¬–_“ÓóWe|W/­ÅVnu‚ê¸"§†äŒkÓ°·‚BŸ `XU«R´°—üaáVpˆpÏ"á³´¸kƒe#¦u¦g˜2fXS¢Þ†E´A»‹vÙ•ŽÐý.ÜÓ×"Ý…1Û[ÙÜA{Ըϗ>Õõ'é"“/͠Îl¹Ÿ‚¦t˜<@gxEDRSœ9›]ëYG]sgòˆ 󥂮<ªÑ *x?ùB›nX]ÊübŹámmŽm0ë·|5et Rœ´äÕOHcp96 ±PY…ÒIGôÇ="2c_Åùà(ByÛƒÓéšzzhLOIž…Jýv{>cX:[pQ<¸{ Ò#ÆqŽ:ãùõÿBó:hüŠö}~!^$?*t ‘ÇXMlÙ9Xš‹¯I`H¬FWg\… ŠïdtΰްºT+p̳ÃÚUßYÎkRhá–´RßCE[ä&XDSƒ:äîé4'ûS¦ièDé„RSèFQt0=¢ä ÆÕOɵޅ‹#\NóO&d§ô4,KG¾øî­ù[ s¸=fõþW¬Ë”YþTɵ\Ó»ÓrmÛ¼ê¶Imáím«É»ü{ÆÊÎ¥Ë á³Ä:íéÈF„9¹ªZš #r'–)BœÚ9'â¦@£vŒ”~K~iõzfŸ‚–ôX²ÞÑ0† Šdˆÿù®Î²{lÞçò Ã9a~މì±ÞáÞŸÉ¢qÑõìÄCÿFAN /°Xþ ¾š,Ív‹J-WÅ‘SÂòÚdíE;e8ÈÀ×S d`þH£í2"KH~¯LÓ×@Á´F=¤C¸lV$‰¯ÉüGÎo» æÏÓŒí‘ÃÜE´´D¸§RîAôçÈì7})"¯éÛ‰¦•ƒ2°Å8ðíG­K§ž­â4ÐɰTÞ„+mxÈ8QnL™# qX"O|˜o‚=B˜ëä8'5׸Ïdha‘DƱúí3OUE±±;£Úr0Xù*M> ‰°ÂM0d M3‡·z»¸ƒ6'ðiú¨Ñq ÓоJñêDë¦uúŠ®ð¬±›û¡{àø¯ý2\LkO«°‹HŠ Êiå§Ü½N! " 4Ì0Çs°µÎ”gD0’:NÝN0Y…ˆäÆ'ö:fßR­¢L&ܶF&#Ú,t/-¸šÏFµ"v[¨(Œ™×ôÝÐh”öñoàU)Ü]À8‰ÕWÑO5Ø0€8`wÌí†ãýDÄWc# »DâÎÄä§>~™3ÏÁ–ÕP‰MjËÆ¾`ìÔR«±Ä5q› È·WÃvLäN°°–àÄôÇMļø™Œ—ìöúJ(ÂÛ@£ÚV«õרšæéþ0°xjã<qhZ¹qO¯ô\5¾}·£K?ƒ¯—~ÙÊútx‰jþÊ JΦÁ^Z½ÕD[3úÊêÝfÈÅ—Yj¨©) ®ÏDËnpôA,?³æ'§ ºqªî:žºÄÉÑÒŒþÖÖêÈÓÔ­Ö³â‡FÆ ¯ ·x~ðŠõc-€W|e[èú™ûm¾’Ïa-‚qí+4!²<4õM`F@óåÝ} ÉýÒwa´—LXÿÁ3ßèW˜ë0¶L”öDé‘q›d\¬XŠâÞ%i8½-ƒúˆNnÁÍKRÜPˆeÅTyr™¶6´fçÈ>2©ýk\ÆéŸí¸z%c°.ëªÖ-L®Ðšç©¾Ÿ^í†qÅÃmq?i»&ˆãc²!ŽPi’µ†O [Uc‡5x¯‡÷;}4’UŸ×m·ÍŸ£¨Î¢9ÚwYÉ@²X²Q~B#¨Ë:Ê$¦|§´y€ì ±}ðÖ~¼–'•^À±a}$YwQeE«XÃQc’bœWE¸Ú€ÌdMRéîÊucf€Ð‹¨,}s{k°Ë-§¬šôÏt4ç+l!‚ŠÔ•,G¦(±Â«ZypŠåÁ«zèß+(˜(LÈqA6C÷[}2Tßm[A_¤áŒÒr|±œNu6õ3‡+I°ˆzæ6ÏãðÑøçÎQ`ŽêŸö,ÙŠâëlwì‚e.eíÃâÁƒÃ«\8…†W±l©Â_fÈÖs–<Ì|Û‘¦06•Œ‰Á9a·éNè+1).ˆ‹˜[DG`š™'ÑÀ­ ±É¬éŠS ãx,n› b?#®7*C¬_Ýíî©%½~Øùe·õÛúH™¯x퀇.½ŠÎ"¥ø}Ô]Ò-ã1º-# ÎÑ) 3–OìÌ!‘ Û‰a¹iµ{”·AnÊ– Ñw° È`m“–Æ‘`wŒoƒ·"XÇ«J„&&{sd–J†s¥Ð˜l@&%ä¤1\4Å<ÉÙÑÓË H$^`Ä•3ýÞ™§eHαí~$ï?ÕUÿ¯ŽT9eKׂ;…’#RÏG—‡÷<…L@A ¤Lò"1äq+6 ³xɱ»u^)zogòâ6WZ~~aŠ[–&ŒKz£É|BO#ŠÖp"¥¬¸¢â©\X`q¸â{xb@xl Æd,´ÐW‚mÉ,¿y¦iÍV˜¬­l_íb-‚Öꨵ¹X('zñ:KäIô,ÃYÅ’‹Ø:·¢+ ¥S¤šê¶·ˆ •!Ö F¶Žæfæc iø¹,ñ8IŠÖ+ð¥K<=V§ÁC!Ü6ÔÐ[oKS>s—'s9˜ÈK’.C”u]ÖŒêLÇRf:±‘0Qû3NQ:J8Œh%»l­8|Lsù2ÉâLÁâLŒ¯`l︓´Õð÷BȘ /â1#ùå‚C²›º©ËFÄ=$ƒý‹KLTîRŒon0ö˜±“uA Pc<·ÌÈØäSÖ· !ͧ„¹ŒÞ=ŽTê¦C„™“&8Fõ°JÐ’ÌÔ{R±Û IÂ6ãêò'û‹v™^ê.±¬[w³+Zšò LÌBÄ¢3ùa™v)’£KaËýã d¸ý‚º=u5™íá£"žx½$‚ý÷-­Ë2!ý¥zDR°Ú2P9%'1G:¿>SÀ²{&b1ò«Wêq‰$ùq=ÜJ·t/…<šÜ$ùÉš>+#ÔËT d,óý±‚Ú& `ª´ 4ÄØ4J/y¶ò×…ÄY379-2Áq† öŽ+‰Dãx”ÎÉÏ¡Ã[µœØØQAnýÅûBötm˜×$¡µA1¤ò þ›9ßWQ¸6Hãeˆ¶±¦\Q}µ0ãâuÖÔÝðͯv.!ƒ¶º£Lá½zì:Å[¬o‰ë°Š w#¬1ÅÈÚ!ˆ(‹<î‘.<_Nbú g¦}K%ƒJ %`Am)Á3M§VwHˆ„rœ‚ç1¦zÄþëLÓì^2Ãø_,"ȾéÉ´ÙÍå9BÄ ‰‰þÚåΟ±"tš–úÜø‚&y¤úmçh¶×î]ib¿c %?ÿ> ::ÞARááÕ‡*ðè’] ëg\I˜S.-s\×5œ)LöŒu Šòì@¬gQå1¬$v<”ˆ«Ñ.vÿ±‘0Cºtb]Žw´èÇË=sˆÒås÷»qv¤z¼9ûKö—-ô£Ø±:M~ P\¬RcPiœ£o^vB Æ7O(9ý¡·ˆð«EC{ÅX¸š.Sü£í,bE™òήìѸpÈ€Üøæ{Òøc‚„™¥àèÃàÕJYÈRç.jáÒy9Z~aDîÀêi#‰‚–0¤DWƒñÉü&EðeéÈÎ gØulS†ö¸8s5_; ÅV>¬Ÿ;¿a–ÑòXµýuŹbi¡lM¥!uÈ3ÀFØ©%ÎÊzÀÅÍbùûoâpïû±3‡ñœ™Ö}¦yk 鲿‚ÏŸ'×Ãè‡IÉ*…_ˆk‘:ÅÔõßᣕÇsÄvÇæŸÖA1"p^¼Î؈cr’¬ PñxÁÀj8NR¡œ\à¸JvŠ"6at"Ø&¹ÄWPÎDô/rñ½\JÔÃøKœÄmeL™eÂ!Új‚È3ì·ö5éåÌÀÄL~“o¯3¯:*…«öm xcI¥ì‘ÓWÒÑq:e"‚­Í•V̰ˆˆ®ó®swX5wU_QÙÓ] [?9ßF÷N¦83§‘±É¯<_‡ñbRüsÜ!ÅŸãU¡%~¹;—‘:ÆAá.#âg.±ØŠ‹0g-7DÖ8‚áú8<öûôG˸‡t,·&2‰…yõÏå§ÛøÕ*_ëíÛèa”û.½ÉPç gŸgJ5r ] Û†É!ç¯.Öý Žá¤0øè–ºëŸlý3-£R¬³-RSÒªU©Ü" *"Lê À[võ?SAól‰çö$D ²‘GGžºf±º'””FDë…„ päξ #´'PïëVÏjGQg äÕfó‡ û LÓÒ§PYúûv|8‘IO³’#123RLj_3‰bÛÖË;¢tšöaƒú gû%¾sý¥éÔ) uÉ›2ÄÆNÍÑa4ܪi6Tœ1ÊÓÕ]’ ˜Œ¶…Hh[¹|óLÛ·>}ºÆF©> äN¸e´DrF5Tr\j舎f;£çÌ{û-rÙ$àX„LAAðõ”— Ááfq<#¦ÕÁ((ýö™§j\H4¸^©2Ø,q¶}§Rጫg q:Çh—ƒ›ò%&ë•Ô åRÈ×¼˜€º¶êY>™gL³¦YÓ,`Ò”“HfCy„­=~]Óž|úÀËQñ\Í;t’-tÍsÊ2&'5ÈWœFHFt£Yß(2gLPlS:f’X1¤dΙsyÈ)K…(9é-†õ î˜×,ðß8™ÝÚLœ–ÄOíôÍ;âI¥âðýG|Œ{W_,­p‘€pcÙÄŒ·f¹¬éÙ]½•çþž>÷eJÿ Ixк£&3eD¯¤¨û½¿ûÆÚR&oAÃQbF˜2И’8øJ{‡â9òÍ5箸^QÌãQ¨s§ ³;[Í1 ó»KÄFý“ØJƒœ• Ïî¶óK‰&— ÃôÓ00öu[íŠ×e8,|C_ûû½”IYm{ÔÅ›§ôwu™o\S¤Îç:G 8ŽSæ]›±®Àíw{9«áíu³áŽ ðpºŠ?`måQÝþ›‰7S0Ú?‹»Hö\I*Ö†Ärâ3­ØO“»)oê‘í‚dÀ2tXÆÑ,‚ø»-·¦”/¤ŸÄ×SùÏl÷J‰cÕZ¦t퟉œ§îòt?ŠyñEu+ n`ŸÁ 8bÿJÝ\dR^Ù"ß8³•—Ž™qêZ.£òcO×Ÿ×Ø—’f½d¿íˆ@¬ga6glt£g“Ú3kñ ö@ޑ߭™Ðcl+Ì»Yå<çÉ’B°Ô"Cq0úaQ†[Á?/`˜îÓ!ý$ƱñgÛ»g*×…&RŽ@>†ŽLi<¦›!Z~¥ßGϹVIŠXÀƒbd¤ÆÆb0Š/7meB׈Ȝݛ>‘aô´êžd[³  ˆžSâHb  ±Ü™Ó΄Dò²=;Þšî‰Ö'Ygèí}º ȼ蕚ùe2bšb?V¸@'ǧõ+øgµ é6›2"q:ÁOLævÄ”Ý^Ì=3igǸ°Kw£,‘8.ëýkþ¶wOiù(ÿŠ`á9)Ú"C¤v27Ày”1™«1ŸÔI ǘœ³ó¿,§hÁOéLw°NFÃÝ ó– NÿÐÞ*ä:G·¡‡N4þS‰à0Y«gÁ=Ô]´Êz¤CÒÀ“7n( #ßY\¼9vë‚]ì-0Glë€Øùö>qþ]óò쟈ùOÉZ·ç=³:D_>XMÈ((ÂÑ ˆœ)Û;Ùù‚±ý5kë«r't|lȘ òÂ-¿Ÿz&kû’&FFó‡'‰3HâeúŸž ôç·YŒC!ª˜Ö¦ ÇX–o°'œ@ÄP§G(1’ÂÙ© Ar– I2 º³¡DÎH Ê ‚ó ¼è/!@31¬i(˜°¹È` s01É£&´à'r#Á`–IÄN’rÃ=ÒÉ€†×àoŒÐ—hœÙ'œE‚Ø>Á‘Ò]¡Âä0tZäIrs21´püËó§äq~„ ÏøDÄ|KÁ-ÑÙÙæçtÔx<²Nz„3ËMqÕVáE©éXü¤˜[ùÈAcÛ5â{üó©®±—urô5ÿ ûlìYÊŽÔ2ç½ÃJk¯„ÆÙ[eÐÑ(Yl_2™"¯7“Y \¹–15ES3¦YgP;Så>³J@vŽ3Í|M£\XJ/.¦¸>ƒ¤ mv_$ŠÃ=‡¼£:…Ž3ÊåÖPŒ‰dü–ȉ×w`GS°‹nÄÙùȼ‘K=·kûSÀ²Þ”,À´‰fØÔ–'/ˆJF]íø‹µ³¹˜@%é2zEbÈW\jgì~ñ²gLÜ9ô4N 5ÔÅ»£ygXr ¤å ëŠÝ%y²nç§—ørûl6R®}¶óoL÷ć[Ëå@Ktsgô8‹nAÌccyã´»Ãw«úÝê3îvÛÈXlqè^!ec™l£UMò9÷cXWœ'ˆøŽ.Ž!ÄÊÅ×Ïÿì;/ÆêŸÄGGó&Á BN{€%ŸàñäÞlh¡ob6í*® Š;1ÂjúÒé³#úÌJ9Z1 õ—"¿žDic7F¦0ÈŠï™°œCÆÂùé,w©hºqU†Ì3ˆ˜žDijb:éè±¢¬½n+¬l{‹hÖ nÈ ‹líù+èŠÊtR¬©Âa&P4Pì·IÛ¿õóü>$:7˜ÎÙ×I–Æ“ÙEKéÜWMßà­ø{,'ÄWu? ®¶ á’6«ù‘2KúÀa ¹þ)ß Ôã.ÄíÃ8X($p‡pÀiØÚ`†2¡Ï¯Û Zúaê3Ï ByÒ-íQÄÉÑnquØ)p=Dò¯j_b27u"Fº¢á"J#Hç(gr{8•ßcŒulã"wDëE¶7HLNèç %’¨Œ’ܺßõù»Ì~<Ôã:úgâL­2Ón#vظ`ĄĎK'çéñ3ôdå4ze§9hAñ8øÿÁˆwŠËX푚÷°J 9;íA–¶8~…J׆‘((ÆÆâÝæI¿aZT-rn¨KõXU\°GˆVuCЙ‰™¶9kšâ÷ˆjɰ°ØÓ¢k.p€Ó;‡ŠNçþ¸´Á×oãð‘äe´  #Cœ™žQäÎÖŒéRbkswÊGRÍ‘¹•БðJƒ8äÌ …q ¼¯ž¾ƒÞ(9av…V«‡hJª´öñ k›.gø;"pJ ;€ÉrGL‰‚ˆŽ´¼ ae$ W|D c'$`°")ðéd8[ü;xy–r†…F¡Ü¶ çD1É¿{ug¡ªÇ£¬Yv<·Ýö?ú0Œ%Ð0=£ñwý3çô·›ï ÊÛ px¿£:§0[Ô†F VΦDëŽx lY+Ú‘“oßáD23º;ÔòFVfä„LåÉ”¥C½Ñ©œoÕ¼˜"@v÷ÙL³r †{F–jÅç]yâ!dS(û]ëþÇúò­1ÒÆ|'ì~³`øvÎMDÈI¶´u]’æÄuWd¢zíÅ4\“ƳOVßÒÕŠÉЧv‚ήøˆ€Î÷dFæÛ®, ú9:êÕŒ¼Ó™))îáèÚï™ë…HÈHœ  •âí-‚_iÔp¨ÁT ¾t‰%æÌ]ä6ÅŠÒñ„×£hG=<ô@Mp!탂Éù&zŽå®³Úm-Ò$ ·~&Íã/cp³€o"¸8êì×:q‹$ê‰E°]'ÆØŒ+E$lÉI x´Š$­ž:¬§:oÿ’”H›>¼»Ž¡(:`?Ý–ìLÀÂËs& ¢&U~Ü´à4=?ñ»Ü[ÛþK‚Íò¤PÓêOEyIAã|£ðÙå;™zËW]§›-׺Ž`°xGJï«lx—mŸˆ»ˆ'WND[F ÌΉxTáTYg˘ü'Ú’ÔòçÿdNàe[1e|•öÛôGÝå»aïœ2äñƒ° !Î*‚ŸŸ"K?äNÈŒSá’¿î|ÄLt¶äOˆ.Í=ûÙvbo{&³<5žP'¸•%‘%ÔczåçG6NigœÄtƒaºÊ¢l)ГÝ12ÓToG­XB¼8)háèðÖj!ñ&UÖ¼…ÄNÐç¦mìK:Mîâlÿ=ÉÁ ½ŽùÒe°(†•úº""1“´,pò²I§±êà ¨ 8}®:¢5ÓÎÊáŠáÛú^„ÌF#VÉ#%ä> !‡8':÷“ phñô+€éë|³xÇîØ;‡‡Ø‡'œÎ:‘ó–dL7þÏ9 J#ɘuæ r{úºg‰–KLêNМ´éËâÜîae·±®€Go%„°û­³¨ÿM5ÉÃß§ø>¼(Ü4Êc±¿E»‘S‰Õ´\O‚×¶**üZû¨ø¶ÕºË±ÓˆÈ™åT¶;Бë—/žBã?·M3yi¿V«  §! D‰ÖH "ë¼Kýfèé–£î©FÞºdÌD|g€0QÏîdDDJÆpÆÏ/–k2±™Û“£å_Q´ÊMcfEzЙǔŒW^‚åêÞþ~›'8j´Ž×³¤¯GlÏ1°b¿ð–üÖïëÔ‹#æùÛ\6B‡@ ¥ aÿŽ"!~sÉÞû é‚Ã`i®i¦kͱ;E“%ñi¦qšVìÙh¿«¶ßð Câh!Kˆ¸— #Ùlr‹FÊúAˆ ñZíÇD–ÀÇ'DMŽgH3¤*Ö9³ЮÁ(,!‚Í îW:°9ÔÝ~¾êË)íal"ƒì_ö1AÒ^§%¿ãçÄÙ z4íB"~ágñ7•…õRƒê(Î2l°Ó°D4"^þŽC_Žq2y–°pèœêŽCáFê mû|=ZL°Cn$!‚C!pÈ(œºÎ¥ñjÿÎu\ŸÉqßhÂw´ñÉ7ó?Š~YóÀ¶‰+,T¬•²o‘²„–5ŸÝ2L`àXÑÊr93úˆŸç¡ÌË <F­~†Xž¸®Á>¸Œ{4ôáG%^¹nùn qŒŸ)ƒ^œª}¾Wx{›i´L®¶³øSíaÒ²UY խԱcˆcÝÒ‚K]C ÏŒB…60¦cåÎ÷ýþÓÂ#;¹ïÍÉÆ–ƒ¾÷crÔô«‡Æ?â³:G éÏHÕ?Èà3çpœ‚‰æHê©vu+· ³ˆ/ÃE¥•ä³Å*ÆMõBqR6þɰó•‰aW\âF˜™æ?–5°µ•¨^E–ćT3s°COg·Í|0·&ÕuìW´Jw7X­YÊmz.W´«-»•¦vn,ÜY¸³¨[·n,ǃ;gnP‚‚ìoÃk“~ŸŸ+gÔ½é ü;õÂ8­ä-øf$Î,Ý©Ú9¦iþ)Â'±–XÉ\ÇWµŒˆg$Ü‘zÛT‰·ê/ˆ±´¡ü\VMZ.•O¬©Ïš¤uz–7°‹¹:•òþA|Lfí[‰»•8’ÊÐñXqY¸©z oöœùG \xnäà ‚ !b3ÒÈŒ©öù”íl6Fìd’‚šºHì™ÛžªÞ²ð¾•ý¼±nzQçg vŒnT´$Æ!›±Ù¦iþ%ÁþÏ-JËV Tr·6ò"Ó%1¾C‘ D2®.ÂÝÈgûÙ¶DEÆmêÞî±®þ!\﹟øñÙ0#U¤U]u ™ábÅ[Ú|)°>…IœçîL fÉŒÜC±ÛÜI þ²†|uh`½e‘iE€ÁhNã%ŒˆWXÎ èY¬¨­_ˆDúgðguÔߦcXF½'H¨Wæ]ÚfŸáÿ,àúò#Äjv5<ÜX%¼y‘mS£âNDëꋱœFËø}Š1×H¬dŠvr(š^eß` ¢Å©\hLÏ—µÕòúxä r'^P½«[!¡É–#zZ§J¾YXwºÇœJ×cPŽuleµ-Ôw[…QetÿäeÕ‰õø‚øË‡*ñ™ ãv*Ë-²êLnŠ–æ±¢À8Œábwà ÷ov¡¦iþ3ê¿íQ(„õ'"&M?ŒlébLG”n<•k;0ÆD9i)ʼm6RygŠUk‹ˆW.â|zïVâ1Ô‚UzÊôNI|µó3€­M‚¼µbQñ³ÛKâu†ÎB`c¥æ # œéDeO·SUÎ@ S!µÅørrÌâ• 2Ûf•‹™^´@Ã>˜u^1e£áÄœÓ[™´ŽKÖ˜‚ŽœÆTò>®™§¶›:bµýŒïPè^›¾ÝÒ¿*ÿ{‘/fF†¸Ð97ír²©±[ÿ¢¼ÿuÑ„ðæX +m>á‚ÒÖµ,:k\u-í<ÔÇï!T éAyÄKj“+ÛòŠmꧺ¯Û§­Œ¯ÆL'E‰nŽÃ(ԙÊÄK[#2P16x…DÕOètÍ=¤ÙÐ#Èÿk1&åŽÅå‡tÖXäÂØ";¾#æßµØòÛñIfN°jN6Ö¢ªý2sňhŸZuEÙÄT«åí‚ÃÂY$¾êŸlh$pi¨ “F9ÌÉG\$ )ÙÖLG-ôÜ0äŽVHXÎ&¡ý&ßhÄD~Ú²¦ÅžLþë`{¹”n~+Îy·íîŒÝº3XÄ}“X°[ÃÔÁ_ ZÚ¥­e.+„šJAdá„0vð9\-«Wœ{e,ðÖbwGm_·ØQ¸I¦8-™­œìtGMj^C «wôÛÁ>YÂyYiW%Áäoøç·+èæß·ÓéŽtDZœˆÒ9ÀÍ÷Di¤2'66äN±Å´@v¶LwÅ+²¹lŶ×Õ/äÛœ« ñIëé“åAêäÙ®UÃ@V™§8rÊfzÝŽ˜…ï/ÔmÿožSØÅ›§^­ÒøYÏI ‰Ý›;5ÞÞf^)±1ÌuÓ‰;·|>m™/mAç õº°:zN\ì°9]¸Â/ˆU¬ø‡³j©pø°VÃxYðçEgQ=€©Ž'Ãb-@­·a´V@Þ ä¶ŸUÌ>-V"¯­nµ¾$»"$¿Õmÿo”Ð5nÊŸ ³]ÝO>cðœë-ñ\‚‚Âóg&NÕ€ìN~²¥ÂƒÐ¸@¡\{x£gÒù·ž…tù=.¹m|%µÙ[…ÇG†c—NˆÔjSáÑú;þ8–DôS3¶)ŸQ¹123Èþ¡¼¦b1ïwE‰äéÑ]o-ç–,ìZ!ó±ÔžæLˆxœâE½¾ÞoÑQTgϔƱÚC‹.vÃ=®…*"Ë"¨ÂÇ8£v)Kéì6æžû¯åw.ý¯—9,Qñ"8e#ƒ®²Hrw˜rƒˆõG,·ˆŽÈc²ó6ª¼tDfZ~ßá­èºf[=ô:ŠÓº:s(ϺSÕ•ã6_û=3O|îØü…‹/v èÌ_ÓÙón@@F³Ê±G[Êq“Š …º¶)°Øäæt–Dv ÓüYûƒ9Eâä7Ê;7F¼ˆ´Îœ—tÌ ]·âf#Hý®™§½Ù:›ýVNEÄ["Ì™„ñ‘•”çögÄ¡ì«4ÜþLò]8Ò¿HrCFAIê¡èåwK2gL»¦K ãá¡=;lâ:îrLb@zÓ,bÓˆ ºí©°ÁýÆ™§½[•uðö× Bw6`²`á?k’âœÙð–GÜ‚‚äÍeh=•†Î'§#×v-dC'ŽÔOÇø¬¼}{^â2Ú4k3&XÉ9Ö1Éå´;¦`bíÙd€lÝéš{§tkè¬`í=¢ ©|m6íjúy+Î9ŸÇ8χ÷ÆZq+Y¾é¾àÜC$xm·ÓxÙE6X·[¯plWZþ)÷ppûUr!ƒä&]¬`¨lÝ+X!¾Ó4÷#>âgPäÀw±«@¥3-”uF’+XTkoNsýEÉ_G"-°¶´Î–õL1ç'«¦CY,t©Bj\W§¥’Sh½Éâæ–pûÞ9=ºb>yº5uÞ‘·ŠÄC%–gåì-3o¸¾~¥Àþ½/¾Øð—øh[ÑV¥[")]«/“¼Ã’þœ"Ò~-ÀlÒ"byñ~  š¼Gà]{ŽK®q1†]X\ˆ Çx†{‰­ÄÈÄyu jc¸eŦÊlªÀÍ…@xôÌPaÙc¦ñÍY¼&1…²K`cØÛ}Øß‹¢Z`Y–d,TEÁˆŽD;Åe¸0 b7Îk«{,qjµ¶rÏM·XáS¨-±=v ¦œG‘s€ãz̛Īڼ¥ Ùw…pâ iMŒ³BÃä8LäU˜X´6û«†«ªÎF/æÿìæŸ·‹úr~ïgá¶lÛe[^ 8 kƒÊÝg†—Eãý½hÈ^ïs‘É{co¹Ý:.˜ôœ3ëÜÍ<Îf'CÍ LcCÁ±“÷K];Åwñ¤ñĘþEX–à3=¸§úßrLÀÀ X4GJ`¢ÈL²+€¨ ^GõwOÝÍ{‚ÕO®”¯…ôëW¨ºÕYmþú&'üo¸›ó«LÀÍpy£4Û>?gwSd”ë9¦³÷dwzŸÐRõ/߬)êƒéš{}žE­m$J þ÷3\n\ν³÷{ȶ‡¶~øƒvm÷û&p|ÑÓ4öîÞ®Søƒ­rÒöϑȌœÊÊ §ÙÿúwqoˆA@Øÿ>fÝ«ˆôôÍ=¶ÅnÎíêåý—"#la 3hÀý9 (íÏìΦ™?u°Dîé1XWÕ\\p¸ƒÙ’&r#þ€ÈW¯«¦ií•Ææp‰k”®#a|.Ç*á¦L­ÉŽ"!£Û1¬@ln\ªP½ùÑ ùªiš{]“;ªÐZ«§§Ä]5(RæÌÛóÛ¦\i:=“ÕŸðí3oµCâ³Ó˜Ã%œ;ûìÉÄGPç0㉠²ûaÅš+Ž#f+²å­Në!³ÄÂÖwˆ¬SýÇ‚Ïõˆü½‘¯v‡ø†ßið艳3¤-ÐüdO‹Æ\ b JZf¯ öWM WSc¤c `"l °¯lÍzÍÿ]ÛígµÆ{A†,ô‹Þ˜Ûc—ÞÿaÛìçOõðôë–†Vêl½i´Ý4<ÐóCÍ4<ÐóCÍ,ÖoQH7KÂEßì{}˜Ï9£ÿ]³2uƒEQû~«]<¥CÄ?íÿ²íöV»›Y} î.£=% ÍGwLkÎÓÔ¸—ÉȇŸû>™§±›:,v@C¢™±ž$vÙŽ“}L¦Ã¿ÚôÍ=ˆÿ³ !Û:ÙaC×/AŸl~ïûn™§°Š%²˜ÓuW«Ï,:£ ´Šç®LkÎí¸H¯ýÃLÓÔ²ùIXŠÕuMF+¢ºˆ¼ gÿƒCõ±;¸’=‘!Lb>L¤UY&®[Ç^FÐV2ùn¸ùr¿Ü´Í=+ÿsІ¥#iª¨£ñ&C‹-‚ò?ÿMúÓúê„K4ˆÅíˆÒ-‘âW"F¸¸˜ °ÖKOô’uG_géšzN³Àñ ð'6þ (0i¯Iz}–B²¬§ c¯8S‹¸΂DAů‘©¿îšéáÔÓLôñ‘~@´"#Ú^dø»&ü“Ý'ò,½DL™Ðl™c¦ ë @ôãÔéÉ øOÒ &ºG•¢#Ù‘3ãü‹„` û©è˜îú”Dh˜M|n§Šé›òl7à¸H×OÅÇÔyBÔD{+Û.ݹï¬Yýñcºhss,…‘¡§*%A×Xa+Ó^™E97·®¦}>, ‰åËAE±ì`‹ãd]ø»6äùàµð.ކ>-¥à´Óâ¢Â45¹h`'2öÖÖÏ í4ÏO ]–ûbyb%󥤬­mÑ=¼jžÜbó šØU¦;¾‚ÐQglX-¯0˜^ AÀàN]ר½O²9@YBûp”i…é¬c¢ ˆá!þ,\Í‘(·šy…˜øã6â½Ý¸l7Œ*kÌqq€ƒå9³|¶‚šé ÐBúpû„%•’VB‡:ì½Q±gÕâÐ1¾˜”[ÌTíã›ñ\èÐ)Í…"53|*mÀ!!T7Œø 趈ÊQv:¡ÛØï Y‰>i‹y1tLH…3ãŒo^-M |'ºuÓÝL*œg¢ DvMuô: ”bÐe^n}€‘©®7-äFÈŒÂøœ ¹–›[ŠàÁ§„ '8»…L”çåDÉÒ1ýÑaè»ÎW>ÀýõÓÛÉãY”x9;"#‹(8l‰žlMÛˆ\O$ ÂT Ò²u+`œdén÷S²Õ_²xëƒL DG°Tß[6ñmø’7Y‘;¢à‰²è¦ó(”]twáôR§ÕÄ#æ„óÓÁävæ· d÷96:,¦vÆ¡è¤ëÌ3ƒ®,³AÀÞè]@ê™Ï½² êfÞ+yQì:‹TG Ö'È3–p½T:ÜìÜ*0 %e(°…5“ºÊ„Db,ƒûáPpiíƒþS!L¯²î£ÙfÀöõÓOÊÊ?l7ÛlÚ¬¬‹B Ž#ÈØ`àH¿²õ õ ÌtŠr½4ŽA®É8„Lß‚ &º&[|&чL&nƒ¾È<„.0qº!fÅ΄Ýf°BÕ-zaelp;ëhĵÞ ù\äíï¯!@GÂF¢8›ëÌ"Ä,Çaz…z…oƒ7ü L)Ö8娶4ÔßÈÅ„'¶n¡e+)‰À6Pr5:rM‹¯NèÓYJÙÀ ̽2Œ ¿áÔm¬‰DB‹Ï‘°IÕ“qžL¨W¨x-0T„çÆÈß}_ƒ>úöÖ é©Reê áäl9‡>mìŒJ”ñ:ã˜LaR¦[jÏTó&؃;ÇÀ%5‘Ì–J±€Êê¤k<]ðssNxfê¡þuÌ þþ8Æu<áº,ì¡Aâ’ˆ#’`“ø4ë„5ú°`'?þxS?¿‡@Ž0° 4û"#XƒÅâÖäY·šÂ„Gý”k7ãT||¡9Ý?¿‹°@ö:‚ÚÚ iÊ@œÜ¾{*ÊÊÊÊÃAv]×\zqêºdò!ýÔω²çÙM>Ȱ,2Ýq*Ë(FŸdZF!ÖP²ævPVR˜Øóa}ÇóöÕ8 €vTL Y”Ê5>’ŸÓ“@ψ²ÇÙá}°v@@â ð {ra´e­šeTªè˜MsŽÅMç2}@íÊÌÔÚ„•1ÁâB{ÃÓj‡ÄîªåC øxí$Ji”_zž>uo€°•˜©ÔG´åùW¬a¿tÚßt]#(ÿºe\†ÈTl/U gU&¡‘p¾–è  èúJ.îS^]b©¿8ƒ¾VeùpâÃXØ÷˜yRœgg§ŒÓªHâOtLâ;jÌuF·uQòd¢IÑœõ_)¼¬Íî pÍ~©ŽÌÐTª¢Zž:óÄ à|ˆÔOM gSïÇåtâͧ“¨/8Ê|`< ›ó¾¢'„{è. Ý9Ù÷EÿÛÉ2À¹0eh„ù‹óAÄ à}úPº| –DàØÄ0”ÖG¿‘6@ô)Û!Ü›„ÏOÊÙ²ÏyTœnxßdS}ÓŽcɳbŒØ²¬ÛþøD¢Çdw¾¶!Ð#@àOÁ¦G&öôf)®Ì×ÞS\$* ú«.n˜4ÆêAtG ±ù÷ŽQ¤D,À›"×¹ ¡9„æU(¹Ÿ(N¦æ}J›]öO æ˜^M¡u.,žÇ2Îçƒû©Ÿukrø;†a‹NR¦väÊ"1”ÖåÅâóÇa¾=ü8·dÚ—Lx~ûª­¶`œØÀ:*½:`pnøe£mU §ýG—Ý: 7²ýCA'î«Tú†B5éÅ—¬Î¨ÖoEë·ø^»Ê©mO§Øû gܘ xMMñMtòe¹”OÙ}‘i]”•›ø vYÆ\ÅT8´Â§Sú\·áÖúþ£Å Ì—"À×YÓi†Ü/I»¯A«Ñl&QiÑxd«²9…œ¡Žû'0·}!›æÂŸoh“áU44Áç^Þ¼f:ØmïàÅשØik¢ÅRq;àŽÚáVØ#¿4°fèÜfÇ×r5 ºõ\.½g ÷•» Þê¥@ÏÝ õ ÏÕJßJ {ÊœÒÝô›‰„&lˆ›ûrm`ð¨›'242ãs:ñbPÌšËÉç‹¿²´Në%Õ+»æáPKSÛ×€ˆ(™3qvè4•“´ŒÖ7TËXUZ'²c}CœÓMЃÎÈgh’äǺN`Œ„S˜ ú§1ÍØYz`‰iE¤im›û¬Ãª-þßb!íL|5ö8³nv¡‹q˜éÐþœ± ¤^z&S6çdp‚꛲`n¶àUIד&É”§d(ýФÑu #FÁUhÆ9ÝÝšð.,‰¹+3GDjSxîzhÔfý Uj‚^‰§0•èæ2Û"Ç´ØJ-i1Y;(^‘1Dò”Xz&4ƒ%C;¢Ó¿±ƒöŠ~PuÆŸ8ã™3Æû¦»6'ºyW¸í­†ñˤ KŽæTžüß/L'L⪭ÍpœÜºšg¥ÑºgtÖå¡ï U@o›„Z¬ádǾ™…ú“7@ÈPÃ4õU)dº&ÝÂÎ: 'dçN‘pˆÍqì`ƒ½ša5Ù¼1ûb ]<Ô‰„ëß‘ÎTÎ Û”yà5Ò9Gº,§˜)Ûqi>.q :Å5·hÀàpŒjÓþu ÆU6“·\v³wt]<Ù¹YDHÀã*¥•—"èÖÿù^¹íü¯ZßJ5Ÿû'æ;ªts Qù„§P€¿HÞëÐô¾di62žèS€îE²jB˜{$ ïe¦:øcþÂk¦ÜÁ1Ë ¹)ÓŸÛ‚ Yù':3 SY;”öÆØ–B¦$§ý÷ÖUN¨: ZDŒ6ÓÜâFîuÍÕq|5¬OŸdí¾d¸œsúvM“Ñ30h%*S³n {Þߥ ïËû'ÔuKew±°Ú<1×…ùsË3nE΄oÃß’q“¡8¸ßòSéÇ€ l›R÷TߘjÝa:˜uÓ¨ž—ýÑgP…9B‰ì½ëÓ¨]§7Í}Í9ŽÇŠ)vý¨²>È‚7M›L¯Ó¯N£~‘d3Lôÿâé…¥fjb ph2²ÕZRéh^Ùº ˜]I-º@E±`˜Aýý…¦†¹´Ó=9gº$òÔöäèQd×_‘{cC £>6äøM®{¯_ì…ïªT_0ÇÓdÉ 0 e  ÝGüùUCÓˆwSuø†jSp’ŽWHj‹…œ¦×uú‡M‘ø‚U*ÿܾ"¯ôµæ •-û©hدPL¦¼ JmG5± Õwö§ã.*Á?uN#æÙTëk ÙQì?¿>7ðç¶o @Ï&L,áoËÓã¹ð‰ž8¿"ëväÁ‹„ʱ²[ÖÜ)ŒEìlKÊ2Kœ ß] ë¢Æ™^‡b½P¢/DªŒÈt" /íÁõ &nPtX«ýÿêË”BCìaýÔÏ FÜF ø{¬ty)O¹æYõqœùÛ‘nØÌqžÙæCˆL«—k!Y1Áûi$ оfÅn¤çå9•ÉY#ê_"-‹¸,vC(WtÚ÷N¬Ë×=—¯öO~~EÊP¾éÍÍÙ8G²÷S¤î›Sî™RläD  ²²™BV’muè’ë„iÕ ãчwR™ðï›”h»² ½‘=²¡Í~É¢Òœ Ç(˜Nd ™„üÏ”ó'Ùƒ3Ç&x-ÛÃÞ,†Š}xÛßža‘ÂÌ2y&˜+8Nx…¾øS7Ž;†np6YþÈ’wÂÌë¦:LBiéˆÁÍP‚Ø] ©ÅÔ 2ª1à‹l¾³_D©ŽX¼»él}ÑoíÈx„Aà±¹¼G-‘ž¸°ôáì‹ÀEäóì0x/09–}\ƒÛ×›kK¶D›MÎÙ‘dÜÓeêTB«û*oue¶me2‰@ầ²¬Ù6)ÅÍ´àZ.D´¢ Ç5Hí*¨m½¨8 àx&ü&:|H‰Nl\`Ýø.t"òTû<íÌ‹k¦Dò ¦Z”*Ë25ÃJcÚFb¬¡D«-ñ¬ò\·@²yòšIˆ·5MV ííÄ ày˜3âq)ÌŒ‘©Ï…êoìŒß€oÍ3nAö>È[²Üø€Bkó‚Z„¢®£ª„áeUŽc¤àÞ©ÄX X§<Ôå §ÔU>©ûO¶ƒ‡¢Oй°™ôâꑲÎQ$ø 7G}mµñ.)»q¿³Ò«é´¦üC]²/oÔƒÃĵM¡ç«VæåùBxƒÄ &é”§`½0Óp›””ö˜—"Á²ôþëÓrÈîÚÚ%|§æDÊÙNþÜ ;Š7ñcò/P¬Çˆ"nˆh_·:Ã#[ÌIxwÐ*B/Ãy“í4jÚê©-·eJ®c•ØTÙ!l³öáäþë)d(=÷r³ÕZÍÙª”fºªA½53õY[º.e6}î½GwM¨få9ÂT4ìQi ¤ çJÊO¹;ˆ×B›xœÂy“Âk%= {N]u ã’‰“¬ `hÌaoÁ;ûH1p©œâðÇúJh1uY¤·åâõ)ÔÞÁ! sDî›A¬2¡|Cs Lørë“h5«(ì *Ô€¸ÐÖ—#d)¥8€l³Ôl¾VýÊk¦=Òa÷á4¬ÐTø››:CI^šôÂÈaS§±Ó=5?êæ ¡²©¾–k˜NAÂ,öpÙ:âVg„Ñ;ªM¶cº"Ðq=•Bn˜d-”àö‡µ‡dÄ¡E Ô*,sJÜSi8)Éã.Áz–%R'̶ßݦ~0Vþ&æJôÏT­äciƒ¦@O¹ä‰žwÃ}YÌ gr£S²&xrb=†›ò* Tß-¹E9™ÕJaÄ Ù6œì%ÖîoVâ~Q•QLb0ø™•ðÎ;+(Çe*T¨7²,Úäðw÷þüâ6@ÏŽý‘¦F‡(6Q̶ävCX½±y“îÅ»*5IÝ ÕJyÅ“©Ûæ ´ (P„j6‹a6°©²u&ºûêwƒºÍb£O ¾øJ?d "žÜâ:M¥À‹£nï…c•è´:í÷ þêg[Lqì´ÏLMNÈ™L ÂDs4ð-­›§;*/&ÞåN¦]Õ‰0‡Ä0¢àlÙ86Jslè‹CÁR§ÁJ²©GA8È*0u&»thˆ–§Ó3ïaÈÔ–[”D{Å6‚ÅB-"ø5ÙVüM·F§eœ£{é.Ÿ}6DÉŸÉ™‘©‚Ø‘›táÿHÿÕÿ!ál ‹J tNÂP¨}BÄvÃ{ú9GËpœÈ¸÷vÔ-°^³—¬äjƒ“]•Î%à/P¬ÅIRVr…DjvD“Á¦z{ëÌÛÀ鞘–ƒm =4ëíÑuÓEJ}·Nïï`ÂYŠ.'“õ ÎVb¤¦;¿»¹Ñ·‚µÓ‹Æ ÁÐl5†È÷ â6MvS+×=¨çl®{&<‘0˜sm£m{)Ài;`Lèñû( ÄZøTÿÉ÷Ða÷å…Ê~ê™éî®tx6ɯï‰h(´Œî‡$hq˜÷Vý“ÚE®$e@B-ôÛ ´oa¤\jé£q„(+a‰Â³~hïïó?¿,ÛŸts£ÁÜÞØStÛìˆÊš` AÆ ßàz}ÖA΀NȈ±äd¦t:À•n )Ò4D(D¡ºžë2žØÕfpœÙ·_ïÊSßܶNe÷ð‹)ƒd×Îø‘›tæFD×÷ÁâG)‰Á»óíØ„6r"8ñ+(uG¹²"5NÂñeu8n1ŽèžÚ÷[,ݸu¾‚£8ûó€M‚",}”?º™äX {‹•› s-#›h"wNodÒA…˜"#}Mq Ÿfñ€tX¨iÙD¹z”§0©¨µ½Ñc¼9 û{”/¿‡ƒ 0k³ $Dέ“Lb‹/ý‘dìWÐ'ˆÂ}Q :«áu_Ûa¤#Á ÐiTÎ{Ùd.0ß^`³»`£û±sÚÝÖSQÙŽÚ*Sk÷^ƒ²0L9–ïàAÄ àx 1íÆÉÆLøÔ0c ê~è E¤h…™ ph—‹öS8¥+m²‚£ºÊ¢Ó#[­ó`@;¦´7e8J™$' úW¨æ›£[ A“õ(…³ÿt[;¨YˆÌïÛ@Çâ[Õ6Qb,péàAÄ àu´Èö×:|N-86ãKš iÊ6Gæn¦àÿ©S©òʧ!—Ãue<JïŽê¤+ÓwÙgjÝ8À@" #ÝîrŒ\'@ÑÓ¤¾P`j ×éÛ2¨·ýРáÙz¿ðŸN7² ýüva>ÖI>(‡T×e×ô§¦)í8µÐ`ƒö–ÄIY‡e˜vY‡e˜vSœý)”¹Ý 6áìMq%mºžËk©•=bÊhYø 6LnQŽÊ º!ˆÑ¶½ÓèöÙ9±¶ ÉÀ0Ÿx„ ¨öšŽ‹xÃ_ªp`¬çÚÐ4ЦÙ]Õ¬×ÊöàI„±àþøÌ£¶‡\*ݧˆtT§7³ût‡‘€y=èA؃•>Ðã'ÆZøßMMýÄý#M\ËàÛ`uÄn‰ž)à£Àïcæ•¶á/¦û§Q=§ø^‰þßå9‘à?¾ 0}ž¡èˆŽ[íà€–¦™Æ¦þãý~<ý¿#Êì·¾£ßVÜcöCEa³‘cÀšsSìÆ÷DÏŒ“8Y­(½:çÜ[ô4êš²eV¿d4Ê8n[~íɾ—70…Syð `ʤéJ-öQòØøô&¶(‰^˜ÑÄûS7tjzƒŽ–‹ûÀc8Õ>MÄ…¾ª#Ø_Ç!g(mŒ²…c{K¯ój¢üŽS†ú[Û–ÀÝGØo„ªçßœL"Ñöf˜2¨‘{cµD{öQ?%g+Ôî½D*)ŸlmÆ] SúˆÐo~T!¬ð~Ü ¸À”ó·:^O³Ñ±Õ•óöòlåz…zžÔ~o˜jc²8½õ·uW6o‰RT•%N¨,Œø8a2–amm3r•'HÆ%eå*iû­õ2:'ˆ?™—E7›¡Uƒd+MùÄ„x#A!Ɖ[첕î¬U(ð«:N^‰ÏŸ¥Nv@e8¤©)³ºwV]ÔtOòU8a©¢vßÝó|ÃUÉnÀvÀ+Ï_~D¿²iÌ' ¶çÂÕ¹àN£¾6Ö[Û©Ânëï‰ßð09S€Üi£SÓ7Ù Æ¡‰µ°!msÂЀŒJýÔ­Ôj­¹ð† r¢>N1ßQÓ¸EŠ#Ší¸Ll±ü.ØÕF®CdÊ5: ¦aÈñ:£[z.à ‰S¦Vú+‹Ÿñá oóÇ;è ØF‰Ê:ñ*ºk#}5?ƒS„\jk‹ …N¸u aR–~¨giÌãL(ÇlHPTr82·Â¹}½»F&ÜÅ §Œß@DIOd -k—¢Ä)°tÀµDp „LßVRƒ!—÷N24ô(ˆ±ÖÊŽfÉŸ †È`æJ,4—JÌ©Dè•<‡NØU3ÿ>Gë'¿»²ÍS Ó*:ã9ItçfÔÛžû~ 9à2³˜šìà€¿ð:!FÓ„pmÁo„Q*T•b ( ²²€¶Õº‚¡bé‹&´ cp-P‹€Eäð-<¿à°A³‘i uNÝ\$-‚=ñQ¬êœ6ŒêŒ*¼¶ÁÁ™GÁè;ùáNVQ„¨*Ȳ²¶¡ˆØê«S§Eœ àx úxÁÄ,¡ßN'e›-š³»º ã"øtŤÍÇqô Ôp¬Mü&›¡1Ù‡m¢qÞü‰Qe ¬¨  ±º43[ü*´}18BÕLôÖl?柩eŸ§ÒQ= •)àÇÊšs YDæë¢aN J…ee nœ ïŒbN#lj¿yDÍÏAýðN·™4Ó”ªubݵѽñ‰EG]7ÛT-´T³L§ôÒßð:”Öå0éÉí¤O]U©¾lØ‘‹ªº5šª88ÈöÐþêgÏiŽÛ¦7#cÁ˜N¬Æ¦;;gDcdF·’…ë4ì«: ºxû7…HË4oÂP¬Ž_ ÎÌgÜC3çtwn„@uŠuOʨ´µíS€Yu|C¡°© ¿õ yéäŸyï†ü-°•›Ñ*£§üû r9¤cþpéÉe±û«vFæDº©’ŸS¯üy XªoÌ ïÈ*¥\×éÿtLß݃ˆAÀù»v8 Æ:e¦~’‰ ²&n|Š‹¾fð.Žº•3_¢&n}ä8„š³ª# ðd é&Lãö*³ÎpЪ¾åÞFÑ%SkœðbÚÆ²cuR¦oÛþèœÞøBò˜;ð©`a)Ýù)…ÚPÙYL *›ÀU*o#iƒuAßÐzq\àÁ%T©›êÿ„Ivþþ ;ɵ“÷Â{#½¸ avÊ•3 vÄíÈEõ¹íh’žè'¿’°<8p¤lª|FGD/\‘`œûÎçÀÁ„åÔ6MÚÚÆ¶ÏTN‡æ• ͉reV>Átħ¿1ÍÛþþFÊyѤFËÓwdÀæÂffÝÅ8:³XazãúBqŸ¬ ð6%|©µ vr9fIYûI;ø4Âï匷̘ÐÖ€ïÉ:¾TXEÚ™ñmž¿Rù²=J“õ"éò:nZS\ÆÚSœrª=¥¶L¨Ð…VލÕouR¦ÙVg¾0ƒûùU&|ß¶=0ÍóeÐx®pp'æYøY¢ÍòrIñ€þêgÉ™õ*-g©Q†Ã¼ðLôàWa”)Λ ¿!÷S>FØ&2~P¿lžR¨ß÷÷òßð0r|…ƒtÊ9(a6âDïÀ{² O==ø|¢Tü·üÇÙ± ¤8ôå*TŸ›þ=ü:3çíêS¶žq@ƒã³“eDE1ˆÅÄ ””‘á’éÕ§éÿ”ã;~?lÏÊœI?78„µñû*&Ù{c¶$X ÐÜY³^£;£]ƒª5»5:³»¢ôI;þ@iÜ'DÆ!ã-P³‹a@› &·Ä¸Ì7…]‚DuEÑf¬îüª ;ÅÙÝ6@‚TÂoeQùêG…dwàðax­!vàö5Û¡`·Å£º‹,ª€¡EѶ%…µ!ü&C®œdÏáa÷ñ:?Wø[&¼Tº#3g®’éRqwÄTkŒ&|HwÔ½FwOø–®žã×sùva÷ñ»ÓpNt6U:ͲéÂøÇèýÿ0‡÷ðæ}Jƒs6\«×ÙPy€!Dp«±ÅÓ ”ó›(É=Çæ@þêgÂÛÔª?þBÉ%Ql0'o­[/ʨ‘˜Wêwæ`ô øL|¿º1°žs:Ƀ#@[ßYW³é"Qùw·ÙZæ€ä ø3¸#q÷@‘Õ2Î-ÓvŽ ŽQ)Î;õ?› ø#>¡€iÊŽ\øtà»é2ôÍÁÄ à| ¢>dË•uE‚å1z#µôÄãV¬|Ý; üàB‰MäÊô‚ô‚ô‚ô‚ôÂm&“Ü>ŸÙ4IYAmÕ"øOuŽa‹žÖ}IßgåYÍG‰üæ ;…Csû!Lõ{sv*‹At« ”À"Ál‚&q4æSA &§¸¼ÉÁ›þ%Èï„À¡õ-쥽”·²–öRÞÈ8‹-?íYiÿjËOûVFA·µ:ÐÔÑk„îé›`-}_aŠT¦ÿ3tÍó $ÂÊ;¢Ò<<AÚ¨nd4=íí,Ý05Ÿ;‘9›2ˆ˜nþl¡zE×%SkÛõ/nvÂs Ò n‰ °Nye-0M0T}ÂtÌߨb+˜-ùOþPøŠgªõÙþÊ?xgï¼ÂýC ¯ZŸuú†H×êÿèú‘~ßïÚëÖ`0™Uµ>•ëÓî¿QOý~¦ÿÝ×êš ö #YƒªoÄÓpºýE?ô/²qÊ%gÛî³…œ"aZBÏrÎ`ƒÁYÚŽÖMøâÑÜOûü¦|C\õÿÊgÅ1íÌmþÿ)ÿÖ‰ÿßÛÿhUa9Sªµ¦ õé÷ÿB§TTq¢ýWÊóOóþÂuw2CÛüÿñT¨Ö¸5ÈÔg} yfËÔ+Ô+Ô+Ô+Ô+Ô+Ô+Ô+Ô+Õ>ÒÓ9ÄM¨ˆnj™lë©LTWé~è·(-?„š`®™L"dϽ7ꊀÂï”!ð®{¨She öì#¢ý;²d›}Â{éqÞz^ÿt> ˆ±ì¿H"'¤õ;áL—ßùTi9¿3H^ƒ²äÍoþÊm¨Mÿÿ?ô™ðÙ Ooá~˜Þ¼ÿ&WéïciŸâøb—7oᇖ垧§r›ò€†a %åzKÓNl¬Ÿ.U’nVN«Ó^šÈ…‘øIk›;ŸôÝ~˜z™íÿ ¿ @™/Ò˜ú»ÿ1ÿ¤ß‡Êüöÿüª¿ ê;4öéÙ†ÌܳßùB“Æcšå;àÛ—+ Z;§PfBÆ•Z«×¿ò›ðñÛüBŒuïü™Y (_ÿÄ3! 10@APQ`2"apBq#R¡ÁbðñÿÚ?3¡»wlÖŽ7J.ƒ—ÀÔ‡F¢`„÷;`&À•Pþ)Í˺£ÔúEP"{Ài;/†È±ŒÝ=ÀíÈNËâr‚¶õ¦íß©˜!=¹N`…PL#U¡)îÊü°;tÖ†íéH„æ8vêlͺ4€Ù;/M,¥7)´€(ªf"g~A£1@íŒX_7­ßÅGùYM—Äsm…Rs#eHŸSw”w·lS[”B"Q¤ |§Ó˲²`Úì‚Q½øìi”Újº}naw° Ùdßu”«„׬ç6e¡•ñNè·ª8AäÚÂí—ÄЂm˱¡£EJ‡¦4ݾFÌ àvUIã5„¦Ò] ѾUJ¯L îîÖgM¢êÀWÂÎb1œP¤Þ¾¬O'DÚ0,9¡<° ´*ŒË€¾ÈÑre27O¦[~;FQ“U_hö0îèÚDî…06OvPœâíðm<ÂS)äšÀßW;qàé¥õÃö²ÀºÛ“ksª­2™NnPhDE–Ûñ)4ô¾¡i„÷_¯¬J•u*T©R¥J•*J< Aíì¦nšÀÜk}g·1@íª}N¥šxÔéÍΤȈŕ2 AÛ%Ty:#sÆ öe<0bè>cAÛ§ ¦=dp†SÊÖÆËhtº4‰é5¡»zt#QÅ Ð.¾Ò¯t*µ|Œò¾V¢ö„*«†ÜÑ*,az ktVÍ¡®-Ù4æ§Óð‹c‘£õãÄØ§°·‡DntÖhßÖvá AþP öšbêõ\@·%M€ ÅÔî¾Ý”@Ð ‰U.-¦‰é‰DAOo^=§’4GE·‰¥ÍÝ=™M½`p‡<„fL&Œ¢=\Ý8e<2Ý{'TqT¾ªʨΣC]”Ê6Ú”ú@숎+@·'Q²ÓÁc²™×Z=^Ñ òp=“eIÒ2=]ûò4¾ºÍÑÝRúãQ“q¡®-B³P¨ n`‹H߉Dÿf£Bù¿KæU—@äé?üU~Þ¬D!ÂLÄ ð{R²»ÂgtÖJlE½^¯ÛŽì›L7‚úrd!am)ÍÆšE£}5ø€å2›U§Cžœòí9”ʃ-Ð íª£ÀáÒ~a}5›×ÕªÄâ#”é€q<t=Ø,Î}‚Ï­VûqEФêۂ熺êfT]"4ÀUYÔ-¬q-Ù6¬ØãT˵ä1+á(°·ŒÆ|¯ƒöœà 1ò³»Ê&Lž$l…b7_25\P¬z§Ts¸vS( íVxÝ8!J•(8„5M¡Ö!|m¹Sîx´™Ôðê¶Ù°¢?)Öiƒ‹[˜§R#eMùL*Ë@Ý=…©™ÙYã ÄKNɯ!V,Qª &yÖgèšO\j}}Zyô´…Aט¢:úá²ß‰M¹Ž‡T L©™Cƒ§U_®wàmrpÊaQœ*S“#Ä~Hˆ8ìÎÕ“ÉÁÙ¦ÉÖ¿b¡Ã0„XÐ%×±tä7@…O<™ƒdY5¤ºßzóõþx´v:H•6eëÅNÊˆŽ aÕìšdN$H‚ŸNÃE9mÖljÕ‹dÀÐI¶§4¸“>ÜxÂ<ò;tÇd2zÈ£´„ Žf£)ÇPÃufwžMƒ'&[!|#Êø[Áx%¶D96‘?daSê}º(áQÃýëÝA„pÛM:a¨|ûGf<Å:`\öJ®‹{t©SÃ:剕*u;Aàt¾1× tó\¯£¦€HAþ¾\º}Bí¹–ÕuHï=‹dNc{<„—o èé§|„Öì€Á„ç×*T¼q®-Ù6¬î¤,È\nJ³ G¾Dè:¶RÍr€ŽÂóéÆ˜&xu‰;qV=S\¶·87têŽv4œf#T˽¾#Hº…ˆäŽ‚g¤j¤ÌÛñ¶AþTÏ«UvcØé:ÞeÇNw/•Ê›óñI„oíò¬¬¬¬¬1È+üãÓ@Ò51¹[ˆ” úC•½”Vò…F•3¢¨‡k…%¸&Ö=P¨Ò·ÛU‘{T*8&UèV`¾FyU*f°÷@Ž€aÔq­tÑ*Qß »•s²ºº›¹@ÿ(}F­ÛÙä¦Uò‘1ºs³â²sÜïà-ñ¢8‘e”ÌhÍNXÙYáe07e ÔÁºsr˜MivÉÔËt"yPô>žLÒFÊIß´Á(S–¡GÖ¶Òu}¶ÀXJÌVi³‘À§®…Õdo„i´¡N §R”XWÆ~©Íõn¢0¨Ì‡¨º[¸y8Mªé°ïƒuP_䂦ÐݱªÑ½f£¾“®|¢gq ¦±ÎÙ1™¸êˆž ÂRt;™!èÒcNÉ­¾c¢¥Nƒ¿6ÈÆ®ñÉÑ6ŒHÓ›”Áõ‚%TfMµm¤é>t)ŒËÊ8Éœ6)»[š!ßÉ1eNMÊ/Æ—~! )„j4'Ô.ïí9n‡ú ŸÙ:IžMŽÊtVŸZ¬ˆãD¡ˆÆ“m<­K~8EÒ#œ!ƒßõİ:ìÇЩÂÀ2¿IíÊy:uF;£M¥=™«R|ˆ89¡Â ¨Ìœ¤#¶É»rµYšã m7L9_ÏBô ®‡µÐÛ Ì9JoètbJ6Æ LkNéô²‰õaSvfãTäL­ñ•lõ—ÆŒ·–¨Ü§}†dì„Åù°aùﯫ’wôJn–ãUdrwâ'NP&›¯¨Å0¾ÃÔ*ˆr¢zcR™™Çtt=5ç*ùg0?tæå0© tè"DsÀÂóÞœ`z57e8‘"·&pÍá9ß”¦Ì_I9nSò;Ò;„Xàˆ#Mª$ Mpv/¦ Ñu'´0K®€Ž]ôó¦3&.vXì3?Êß»Öúú=7fÖ'“¦`Æ »téïζ[ãî‹A_Q£á|% >WÀÆÕðµ|Lð¾6xUZô'°99¥»ªDdÐúSu²Œ£Â—Û™ÌVo*SÁuÐu–o+0Y–d óÁþTÏt&ßŸÑØì…o|ÜÁFü›nP ì§Êê‹€OqvøÒtˆã@;ªŒ# ¦'ÐÅ=…›*/¼Y"F|¾PʈÀáGïÌ‚€PVR²•APSDvÿ(îU]&=&ˆ´âD§³!äÚòÝ‘$Ý·Ð ] ÞP{O‰ÝeªoBÈs@Cmiõ }¥J”q£öã‡@AÄ‚³"âVr³•ò]:©Íeó5|­AÀö°ô=½îÊßJ¢mÜÍŽv“àÁãUeäaèÕ€Î3„*"üŒ À@¾0‹ZÑ%øMizøZœ>¸Ó¨v+8AÚºö@òƒíµ&=(6L©›};eBlx¤À’‰)Ï´y¬”Dìõf' ðÝmºè£®©º&ðçM¦&»0”Z赩Ì„ADƒ ]2 ;ö°âp=ª©é´Ì·Ÿkó´›&xÏfS¢—^X4¹\›IÅ6™ê€…WíÎå<3½°7[`PÓLËxNxnè×ðªâ³j9SvfÎ’ÐíÐm„AP€NPÂmR,…Võ@ÍÆ9Á·g!Ú+zmtÆ·Nq¬Ì€Ê#ŒDÙ=™N²¤Ì·(ò”˜ Î4ÖžY­”Ú`"ÉÌ n ¤"è4ÜÐ ¢TèŒ7ÑIÙlxO»Ž†´¼¦Ä[ˆæî¾¶²„*™Rl{Xq8ÌZ {2ze/¶.„" wæ²:%2Öä~&Ì #l*ý¹J-"ç€öe<¥6MÖQ3‹ÆfÂaü‡£s A‘y#|© Tã*“æÜbåyÃl)oáî°_•ð~×ÂP : 2ˆ Õl³Í–eò"éÇ1@ö0HAã²Ö?çÓ)}´éôòÜsæ9g<ì™K©à¹¹„/ˆòLf{ #m°è¾_Ry&¥,×·&ëu¶‚¡tŒ7 ŽÊe:ê¼F\,Û•}-AJ’šä+(@Ge„ç±ÕgÓaÚMÄ"#— òÕ;‘e<×(á¾bÜ•1 ÑQæcmÆ"wU“lDn·ÙHM¨[dÚÞp$ ѨÕó#Q±ˆÝD¯ŽÃŒ^ºù[娓Uò‹šäaZ0´¨P†#[eò³Ê”ö0aùì5·ÓYW¡ÓY¿ë–§L8IA rÕ·äÜÅ [ˆá!=ŸŒò4ߘ_EVœÓƒ)æß‘4šWÃà¢"ÇEŠ îˆà*8ZQœÙB„Õ†ÊüxÎ`r4\cÁ²lÇå‰`+(YžËYRoú*Â(N£&e|´Öå;4Âò·çõ>MðcC†a´·~L4»eñ9½a`xì¦]º ¸Û¢ Ç!0˜e£FQ3Ê9Û£IâqØ)À^ÈøÀo…?¨â¼ÀYŠùÈWÈWÈS]›’?¾ÈåLñÉ%"x•L7ÓØe£C€pŽNÌÔ‡”¢ç‘y—@ž57å·4æ‡n:™nèè”_H3bˆ„ÝÐðš GÃ2øÑ¦ƒ ø×ƚܼœöPÿ(â'®ÜÇòôú&ÚH6U‡^3X\›Du@ÍïŦÇO"ó•¤â;*lʞܮã2¡j;nmÔƒ‘¢z,®ð…wM`nȰ*”¢á }••”ʺ¦XÔj¾V¯•¾Q®:/œtB³{š2öpÿ(xàGÿcéôŒâ:­ñªlÏUlá|nMhhŽIÃ0„i›L“tm…aiãµÙJçÞ`"!8uAFL/Ú…t*êR›W(ˆM29ø¼öô<9µva#ƒQÙG¨Š’nšAÛ­;ðâP¤ãºm6·Ÿ¨%¼b]ÌÔúò/6] Qx B0²µdjs¸Àà<©Ê!L¢å!H[¬Áfð©ÇŸp‡ïµƒàBÚÜ*Ê}I®-¸L©šÇýO­Ì…&…±UfSn/·2Dˆäƒ<Û·Be:ÇT!Jn¾2Ü?JȉYfêBß·(Ž|¡ÛˆAÀò.„#oR.™W5Šêœ2˜ÔÊeÛ¯…¨°ì•D·€ æª}¹ ^Ë”#GÂøà§7Â!AYVTÐ3Ï™À¡ÛˆAÀñÞa¥oꌩ˜*¿l[Fw_Pc[·g7àoÉT0Ô o#ÆÂ}‰OëÙÈ’œÂVå;ÂÌ"=P!ÆÉ¼ƒœºÛïÄ ñÅvÞ¬ÁòÉ ,™â<˜²¨PýóµDt„»’«ÓYEǦƒH(ѵ¸t„ÓQ™®/º}(¸EQûrªÖ¬®}Õ:yS¦,©feÃ;˜$ þ%FfE™oê`J¦Ð õceN¡wb{s tGã<‘ôÖIé…6æ:LDà·nÔðä…V¢ÙQ€x¢£I„kë'Ôq²©TŸxNªg””B¥S5ް.Žò ÿ<'ƒÑeÌØD¿©±ÙN;`熯˜xF³Šù_å…½Ž°ëª—×’ýð$'n¨í:j·ò²" jS)EÏk¨rµ FË1 ®Ì'‡U×…¶ØJw…M¥ÎNüIl§¿-Ѫba:£¦P¬fè8‘0TJÎôÂ]bPªoÝæž›™¦Ê™laÑ9åÛ¬Äk¤;Ä·Hª@~I¢'€ñc€m$JøÄÁNaiÅ­/M¢Né­ °í„H‚¾2ߪ¨ÈÙÇë AÐ\êgm5.âŠè›¾c*¬-(²ë$ p’¾R eFûÎÈ?Ê™à9¡Û§7)õÆì;#ÆWÛ•©ïËÑ5ÁÛs/°%3ì0†¢`J' MÜj0D d¦~% €˜Eà#T•”Ô¸Nk›º PtŒ*Ô“o€„ #úLvRŸS5”'`L)º•*Qó†ø8Rú÷°ÿ(ÖöË}mÌ{-a×GÊ …YÝ]”Ï3[Ôq"uÔú”Æç)´ÚÞäææ5ñ8'xPlÝmƒÀ „ƒeQÙ›!È"…ì¦Èa%n¥8‹¢gò­ÕLàFÉ•H’kƒ¶ïaþP é;"/ëTÛ”vS{'Ó!Sa&q¨dáEßç‘']Q$-°¦ìÃQ€»³€wâœÌ¡1×PQ½WÕL¨+d¶ ê€)ØÊ•*ÊÊTÖcƒ˜sa+¤¢e4d&eg²ù1n–¸´Ù1áÂ{ðq8 Ó©EÂÛ .Ù $î¾ÚøBøs2žÿE·žÙU³p ”Ú]O4A›'·+°kË]œzS Àm%m¯`§ùÔB¡B¶=¸lÍÑM$+ùMxvÝ寛UÁ6«]ƒÙ™<@ Ù $î¾  @Òæ‡(Œ¦}lš2ˆô#S)ˆMxvªÆN-qjcƒ»ý_¦lº(Æi=4F¡`‹VP²Ù‚~à Y@l£¶\&U±ï ¬d ¿Š9éÔËqÜP¢ÆÏ (ð‹Aè”h‘²m(6àÖm§@Þi2?/C¬ÛÊýàZe ãU²3h¦aÝéÓAä/¬å|‰î$B…×*²¶ž1„vÐÕ—ÂÊP ÍáIM7º'¢”TâJé T-ß!ùYPcG'ðµ|M #VPª³Àîôéæ¹ôW³.ØÒwC‹„ˆÐÁ.ËàÇs"WÆ‹Ý|h´D ­ÖÊÚ·V TÊo'iR‰ µñý"#CwÄZø7aßA„ç–qeNrÝVoúî´Ù˜Éôcá>—Q‹j¦¸;l*2.1iƒmÆ% ™î¥0™R:£tP[è*ÊËmˆEÄ(Óþq+q„”¡a‹°¢éožZ¡†èÆf>ŒI¯›; ¬ãa5ÁÛ'¶B--ßaf1Ö_+»|E­À“£þQPƒe@§è FÚ[ãТöN% ÔùY–oÓvRŸ@˜AþVüo¯r‰M¥ÿ’ˆÛÑ÷W… §R‹ŒZâÝ“jnšï\'RêÜ)»)à|ƒ4`û4÷*¥¦ÈˆÕ8F Å”•' ÛŽ_(‘Ó]œ¬Õ›Æ™_ñ¦—Øz” ò5.î-arkCvô£=*Þ…K;@qnÉ7ÌžÐá+ã(8µTvVâj=Àì‹‹¯Œ#º¸[è•ÿÄuGDùY­}Hàœ@“ ˜=?ÊxïvcÜÌ×@E½1ôó_ ñÊ‹gòMmçUm‡c{‹E–aǨðЧp’³,£tBŒB±ÁÛé…:¥O|”!ªZz¡P7u§¯¢‡ A൹Dbvî fd[ÓœèYdAº}<·©Y«4:5>2™ìo†þP¨Ï„Ðá¹Sx⪧3¬‚”P[)Bà a U±x:ä©*|¨El­¾;ðJ€±¤%ÉÕ¯ þP¨Óèå©PÇ·±™Êˆ°ôòÐíÔ„D§·!6Z¢%9¥§¸¿êƒz•(ƒ²ü°6¶ÂpÒ ”wàÌ,† k\Žøo®ÕOŒCK¶RØ3–•ò„ úq8VÈžÜÙ5¹Dzˆšo£3 ÕKêœàÝÓ^¶‰¼,‚e:$Çpq€´)RpY‚.•õÐß )Y|¬¾Q›¾˜Ö/l„Ivê0Ù@” XwY_R‹¼a»P1†b¦ÑH@ƒ·¡‡ƒÖöåwll™O/©B“š0x‡ic‹Jx;¡ø?UVíÜ^ËÈO±AnTºÍãTâÒWœvÂJPWÜ~ÖS€Q(pwÔ Æƒ£¦3 „\J˜Ù ÎB·”k…óþ“^· ƒÒææNiiƒÚÃCvõB˜OÔªŒÌ5Æ{…JAU@TÞéƒFñ¹n¸Ù:¬ýPý«l„#‹.šæ·P¡,ȳiq“£k#‰Ä#}[!¾†VÿËÐA!èsC…ÓšZ`öšM“>­>˜r6¶š#®1¥ã0„)7¶Õ|œ«¢ÊWTnx £ÊŒpA€‰WHC@ÝUècµµ NŠu2Øú$ ÿ8¹¡ÂéÍ-ß´5¹[²úY®m~½Ú¨Ø7u¶Øpº&y]Ôéé¦m [k-~D+4î¾V AÛЄç Ì;=þ“]$òß¿DsC·OfLhý{µl±ûP¿aD”nyÁ«1FÈðzhýè¢o…0ƒü­»3€Y0 :Å|fa|F ªvhœ7î`z}2Ô1J8cÊŽ0ÀêŒmˆ¾ ±ŸD­ Ê< g²Îãפ]=ÅÆT”[²Ž•³ ~àöe( Ž–‹£<¨â…( NßL•:*q¦e£ÐÜ„®…O({ Þ¼Zºø˜Ž!Ä,î_+¶ÂH_+û}Fæj’–øo‹OE²œ-Ãao}cöQ‡üjØ+-ÖêÊU?¨ôJ­‡N®ƒÏT=‚™ü2}(ð&”hø_í$"ß¹Ögú[-ð…ÿ›ßŠ1!@ÚÈkÛÑ86BÞ‰T¶# °85窞§öËî&•ð…ðþû!8Aƒ‡ïÅ»¢80¡m€=0”J’¤©*øn ¨âWMŽS lˆF7Â0¤Ùü¡È¨ü¥ aä àyÖÜ ÇðúíꀔB… õˆ$"<(tãa¤ VV:J•8HVVGEÓ˜67rÌß™Ð1¦Ìåmès©NÆÊÚaƒÁæéo?‡½¹„/©AË6øB:OL@•n‰œÔxãï¢a0c “FQ‰ €ž2˜S‰í¡®!dYRva\3Ó¾Vi™AÓ S„)ÂVdJŸ웾©RCXÔ<ñÓ˜ÂôŠûééŒaH„<b“m?Æa¸o„`PÒ.#†È‰¸Öx£SwG‰I°/é5H.Ãl Ë E¾_4,¶-݃Ç^Z‰üte‡fDÆú˲ÿ q„_TÊE”-0§ô Ñ8tÂíHð¬Q£§¨á²8 ømivÊ›3>’ú¡¶[è+1RQ…˜¬ÇKqžRŽƒ{(+4j¨LÁTÝ-þñ+#ŠøÊpÈa7Â0œ,›‰@Á”N€å¶YJèߊ©@ùP7G~ÔG¤=ÙB2np #À.2³ÌÛ 'uFð²ôNüvB§•¿#G¯ ¾7N9Œ¦pþZs-ôœ ØŒplxÝ1 òœ ÆtxT~¾“]Ý13¤#ºY_R§Â< gG~þªzb.?†Tfp ´Ý!‰µ° `£ª5ÁÇ|@”TFêVê5RúIªeÜ)Ä#¾›!§þqÿHx´ç7 £Ã„h¤áµœ"2ب[c¾ §™²±>t -0ŽüM“v ÖVB%F¬Úeo¢×Ò`Op;èÆƒfqká<:"óÀ&7N©;h‹J¡ü8€wO£]|V,žÐäD`1Ê£ôˆ²Êˆ#|\. ¨<µhG%o…øúErcÓPG}M£B Íá¤yYœ~¥»Î窀֗@e5Q¬NÉÎ.ßM+‚ÔÖåüAÍÝ:‡Œ6A×d |%Ò³˜,Á8‚4I@Øð¯ÀÜp¶0CG¤Wé‡N/ÛGM §7;/ûc„ùD ÂÙÒ¥­.Ù1™58ÀàRóüMÔ÷OcÜ£M€ ,¶œ,¬ ¨EÛ„}*±ÙHð³b yP<¨T*ÞT(ˆÕöYJʈŒX$Ý—ÓptM8ƒM¦ç!I¡ÚêºLp_âŽ`rs VÁÝJ”ØøÕº:£Á+ô©ÓÍ¿¤×!N‰R<)”a?µWâ›)Wâ‰èæÈÝl†Ç†øR§(8„¯Û€Ñø­_®ll:à ¶ƒ¨yàF6 VbƒˆÐn'Q—ÐzIdöd8”Mcc¢dbo}{&wYl²ø@]dA¡eA°ef@àBUa×[D¸âÏ»J•%@ÝtË "(YJ…ø«"°Ž ­´Ï¦¾¾•Q¹›”«bpœ:2T VÚ>r³•›ÂÌe‰´ü¢Õ•Æ€HAú$k¢:ÿvêt΂:wÙYDì¡@àÆ›&þ™U¹Lðc±àO”T(R¡o†èl¶Â›ÂœrtÄ4qP4ÇàGyÐ5:ÓÁDuÄ™Àa”j( °TÙzeQ-àm„®‡L­Ð¶;|€Œi¿5¸“ ­i9VúZ$Ç F%€™þEðЩR®ŽøŒzp%%ã„Û]ÿ¯N¨Ü¥N' lT©R§HDMñÿŒ[k¨P¤*Nü¸ŽvTL™@Âò¦q¢/<*ŒÌd!ü0툱GÆ T¨PtÂ…ÊVS§tã”e˜Â…ÉTšZ/éÏnaˆÄáÓ цS„a 4f(µA t¢g@²váÕ?”)R¬AǪU! þ/Sêt+ë€Ýlm°‡Q«¢’Œ­Æ1+4V@‹HÔ.£Ê¶†ê›cÔ+ÉAP¤¨QÓb!f²Ì³¤JÌ • m„ÂÌQ20Õi}8u‡å8 B§w׋V'm'eo¢`¢<&‰Yz,«!YadA¨uÄøÑ%IDQ-iq„k¬  Àzo²À+)D©º¨OA¤mˆÆ†ðÞÜÍŒJ UyÄ=ñJça  Fëdpé‰@Âl0³,Ò‹‚ÏÕM”ÝY[}eBŒ2 ÆEÓ]–È8T¬%*Èá*pvŽšÝ8ßP²"182íá¿êqÝ “aºCÊø•}Æ;)Ñ:,QaÑ "¬a)´‰Ýd²øÊøÊkH2}R³dOܲ…F%Žé”§~!žÜ¦Àî…솰âp?Ä+ôÄí Ü-ñ!*J”‡Tþƒ€ÕMŸëÖ_õ*QÕú[b2ÁÛB•:% ©ˆ÷Sêpn“¤6MªØº¨àãlGœ„,²ê¢Ùt§þ_‚=‚«dJÙoÁŸ T¬ØJvMnQÐ< A÷— lÝgOLOŒ]°|«DbјÂS˜Ì¾ÅQ¹JÕº§O&ýÜ8„¼t6Æ)V[ëéˆNÝN;©èˆŒ(þ”{Q-Õ²”%tÙ”w âp>ìÿ±Ctmm aÑEð‰D£úP‡‚›O($ªBì•2‘L'¯uNž[žúBÜÞ`Jý©òçAÄê`Ñ*D©åe>›?¥LöZ̓:ï©­.°L¦èÄ ñîÀ'ºÈ¨Ga£|N©S€ú¡Bè¤ìŠÙ0Y=–¡·à!2Ža2…(47oC„íÕ7NßXÄè9zi¦à Ó¨™üS©¹¢ë¦-Õ6åÆçeAáf Ðá À¡6“œ…Õ5¡», ôÀÓi耋z@0ƒüûkÝ™×VCΣ£¦˜Åµ¿òR¦Ó²šÁûM`nÞÈàfB!ÅJkH)Í$¢ÂVB˜#Ô&Ÿj¬èùÂ-:œ !·´Ç«ìƒü©Ÿgªs:a°Âzael-D?zaB…LË¢ù@ϲ8åï€ò§…ÓT©R¥3ê?¢‡ù@ƒìOª# /óŒxàtS71þ؇_ó¢uôృúPq8^«ö8J‰;a#ª7Ûˆ;&Q‹»úhq8\¬/8Ù¸‚FÊçtp•ee ¢´ê]Z¾7øB‹¡ä¡E¡DP!ë'dõ”JË)ûâ"]Àé…'êáÄ ð}aÏ%4”Lá·¦Û”Y„ê®Ø©MÌ>¨X(P2˜<¬« YBÊ”J" .ˆY4È‘ýlAþ}N·×4²ÈY錩EöYŠÌT”!>P²?ÂIû "ß×A„çÔ^36SÙ”¯ó¤e¿Øæ•3éÕÊ™jªŲ̂Ž7¶!9á» s þÈå>™W¨"§O5ÕY T¾ŸÙƒü gÒœs94@ºqÌåö¦×…´‡ Aô{Ê€œ$BÊ…íÁh“?¶”£H&ê¤åÀO»ÿo!ènÛ ŽØ' ѯ§liSŸÈÿq!Äyʳ¬ë:ÎVr‹ÈíæÁMÕRgâ·Æ!¥Û!DEÓY“û˜q8M†§mÛê ý‰œå8‡ìsd.šÜ¢?»‚BàTÙAPTAPT»Ê—yRï+3§~Öw²o„íðoTn½Bóª¦ÁOQÚžK¿¶(“†[ILtlËNÃK”Ê8HþSœí‰¤$¶nÄñÑ|NB¥ËáqÆåñ: _ôÿ÷2JøÝœÇ3uñ=|O_ ×Àè²øÜBøž“•¥ËåúþѪ:/•¨¸59ðÜÁ|aëåjù0…f‘+åbF‰þ“©L'QsLÚ%Ö_¢Pcœ$/‰þ©ä•ð]¢wBte*˜t54»b4™d YÈ@²,d íYDæEU¹ÂîD@ÖÇ–/œx@ÍÇñ8¼÷³¶&¨’è¹F¸k¥©ÕfBùFlÑùF´ˆ…ÿPlQ«2?û_õ®©µÆÑáTxuš¾Q9¢èÕ–å:¶i·Ÿý¯˜ZGýòÛk£X0…[Ïü'\§73KWÅy•ÿO䯅=™¡|öòJ4ɹ7B•æ|ÿí|´(ÇUñu”Cÿ‘ù æ9r£XL€¾×ÿ·N­-ËÿÚel‚! Ðf<é¶Â,ÿä—²…WfuÕ:Ÿ5§ÿêùúÿÒÇTËÿÄN!1"2AQa q0@B`‘¡#3PRb±Ápr’Ñð‚¢€á$5CS¤²4ct£ÒñDÂsÿÚ?Uœr|Bwgít[^ƒ®Xü—k©WþKÿág•Äk6xLóTû?g¦)Q¦!¬nAvŸôþŸ{Ž2xwíšüo)ÏÈ 9è5?ÃÞyäš÷ÔòVt´&ÎY§aÜjcxgî@ý>ø‡:ë&ÂØEÕ3êF¬àŒ˜…-2=ÖvÑl²ûG†4}£?©v’ ›¿‹½ °TÂÑe‹>‹(ñYƒà°üQŽP= 0¥Æ}ÈfpîGîඈoXWÍ7‘ÝÃNýTLx"NeWâ¶l¨OKŒéÍ~cÝjŸÍûXÑì­:N,?Y1ÈBÂ÷ÁÌì’âx-[ŸÉ9Ià»WÖ\F;›q?ÉKiIèV ·U†vü#F.%g páÅó÷O ¯Í6D[îÌN)Î磘ä¶Dx¨" dr@¸“<û˜BÃ2=;›¸-£=×8ˆ>ëTþoØ*¿öûEðvšU|'ŠíÊ5;V¸6ì®i·tˆONEv– uqª"´3‹Dçâ¿â]Uì{ŽpÖÛäšu‘¬l‚à]‡ÚUh;&w¾û—NkF |^¬öüTrü> V,9­X—sD1 ÍI2}Ôh-‰#ÔïŸ%hh(œGÕÎ+Gæ7Á<´Èt@Fkm•ÚB6Ž~›"U „OrÌ+AaÀû®ðçÞoà¾ÕŸÔ¾ÕŸÔ¾ÕŸÔ¾ÕŸÔ¾ÕŸÔ¾ÕŸÔ¯QŸÔ­UŸÔ¬gïg{C‚³à¸¹Ëë þ^¸€ïÙHUÔ ¯œx{¬Èáêxùèqè…Èq¼>ªãÈ"{Í0S± ŽZ vHËpމ¸E‡4[„úiNw=1£#ø+}75µ{( Ùóц8梙Ï%w{®Løúh÷]¤FX`¬ýNxð î=â  žh²Ÿ›–Ó‰ñ=ÁuKLøz@ãÝÆdòE£nŸ»>až„xýãšhlóFNz|ŽŽ¼žö~é³ÇÓ`a¿¢´ÔÜ´âr‡:9¬?êmoé¤*øôe®¸NžÉi„×{³æ?_D p£d@i²‚ ¬d@áߺŽÐ8…ôÆn[Nî9žc¹ø¢ÓÃL>IิþoP’cÅZöôò,B½ŸèØß>îgÝ‘â=ñ sæ·>ksæ·ÅnŠÜ?u;·ºûgËš°ô9z'dZ4ÀÝä¬Ð‹Žg¹bq†óîµüí¦ê ÌVß‘ôìoRÍ2>‡³ÝÄÜÑÆ@p÷cÌ~¾ˆx»Ë a÷\=DÏA´1©Ë Ù`yÀžánK ¬{°ëµK}%„žKë3¦Ñ;&Þ†8æ;ÿ”{­ºßŠ»@l·_Dhl†øzîÞ÷E°éo],÷X]ÆxþÞ‰·ŒÖùù-÷|–ùù-÷|–û¾K|ü–û¾K|ü–ùù&·xd9÷Aá7õxѽÜËÒ[xåܵ‡5ͼÔaŠŸ‹¼ÛN†AÖêšå‡ƒt8xh³´šxé#šƒ˜D††âÌhÚœ|f+NJßp‰î5Üh"c݆xþÞ‰¾~ˆáÄÛÖÎdÒs>Œ¸ðRãåêPÑ(˜Ål·Ì§>£°t]ewF=Õ4àò@:U-Š]„x©¦ðÞµÀŒ.ÐûæÔÖsV¿¯ùwÇ» ñý½;6«dvn»ÇYÆxd›[VÑ[ény}§Ÿòa©S³Š†˜ªÒve’ðZ_ŠíëÚ]Aôõ²ÀǸñ¶×ÅvŠìŸC¤êtÚ³˜.œ¼BÅJ•JTÜï®.sM:‚9LÎ\•Íô&á:À3<œ&ߺìN©H­ì¢‘«²M7œ•VU¥_©84ÓÛ<Ç?õ&±ôÛH‚vZ´ô´èó?¯z -7ë£{td €H<Ô‹z|#büd{ö{\çÖáè¥ÆÂÇz™k­ÈQ1Ô­«·ò§OŸ¯‡¶ñÁ\ÞŽGÝN?ÒTawŒ.?ÒWé)™çȯkúJö¿¤¯kúJãý%qþ’½¯é+Úþ’½¯é+ô”Û;úJö¿¤¯kúJö¿¤¯kúJãý%qþ’½¯é+Úþ’½¯é+Úþ’¼ÏëßÇÖˆä類1É<Ä‚"}àk@0jfz-Ëx­Ÿ‡|Ÿg‡ Ý*ãFÈóXKT‘naXJ—ìµD"Á=\á¦Z`¯¬æ#"šî'?CPñRÓ!6Ø”œÔ+ÔÃ6F.ò_h5â=¯Þè½£g§uíË» ñý½ ¶IºÜwÉn’ÜwÉDß²Ý&åY‡ä¾Íß%¸ï’ÜwÉn’´‰N¦ÖÍ®en;ä­³n+x|ó~ y¿›~ Ù^ÈRaǪà¢oËÐ=±ýãËFÓ—ÕÙ½Vcà¸|¥Çk>唾¿mN­º ‰ cË¿¬t[жèîÃ]nKÏИãeQ³àš¾8óÐD~+¢†Pßg‚%­¼pÉ3<Ó]ÓCàœÕöÇUkZp·ôPý°ƒ)‚šÓsê»°y…w•»>+t+=Øgíè|ÏëÜoôñïét_ˆÎßëÉ\àþ_DáçïãäJv³{¾ž*<ý1 Ͻ3Ÿ¤ ›w¥jØ6ŽñѲab.3§sVhH¿%} Ãï“<oCæ^ã|$ïÞm<¹>†g x/Ý]À-à‡ §¼moE—<”EÔàtx!P‹h°·4h·¨µÑêCƒâŽ7 ´9‘´x(6)­=‹G5rÂËþc ™÷Éž?·¡ó?¯q¾Ð;ÀwKŽBëXN–û¾K}ß$F3Kyß%¾~K|ü“N3—E¾~K}ß$í³„Z8«Y,Lå¹óPFæô¢‡?xÛÈ\÷ouömP2õ#˯©È+u«Ùœz—F¨æÂŠW>»)ÐÉ÷¹‡[‰¯k_‰¬$œ‰<<ТìEî¥:·â"ràWg& +2›ÚÐÖ<¢×ÿ'C<oCæ^çQ‘S%ýÁJGuÞ»ªµÏqþ:y¨ätòw0§DVyŒ³œg¯¼’wë£e¿r»Ù÷ºž URÚb›¾•Ùµ˜°œÚmê—Ö«í/í9g‹¿Ü» úCNÅM”©6€!ÖüßÛC<oCæ^ýø¯´v"QÂùœñ«½ÇÍK\qu¼£†ÿ†ãº_m£6î8õî?ãÜÄÏ1ÍHÑZ0 ¸£$Ç Uó&}ãÙl _´ïY–4Øf qB ÷ã<oCæ_C = ƒò[Žù-Ç|–áù!{ž7O°Múh…µä ûÉŽ ð¹K6‚ˆºÝ?I²ÒVÓHõ,\ï›<oCæ_HPî=‡Ù?.ã|4Êü7î¹¼Ä'fÜÔ •Ц\½Ù,¶£Ñ’ïg‡Ü;M+aÞEC‡~ñW]ºkbþ£?‹Þùkƒ¼4Êgì‹_gWúÙÔþ…þ--+y¿¼> x|ðøz+(Ó6ØéÅô 8“'Kü;­oâ=ã„÷rXŸéï.#¼ï¸ñ~û~=È7 qX¸(Ì>–[È{ß1üBÅl¼<~u•/‰YSø•‹ 3†ö%AÕ€bàèÉ5Ív·"°?f¨Ìz…÷y©ö^>z[É¢{Žðî·ù»Ó2þyZ_rAÉl;È­ÙðîÇá·hJŒ#à·#ÁM2g‘WoÁ_@.Ù•˜á4ÿ¥d¤0±?{‡O} f†]ÁÆNª¨¿²áízoË¡£ðíi’ÐVã~ÃO‚g"©Ž. Áá¶¼ø©Âպ߂0<ánŠÜ+qß%Äy)Ù2'’íï(“sí|TÒ·åPD3(7?I´'™î7ÇÖ3÷‚§íÝFõ3#¸aàÇ_IPðƒ¸äâ²%» žKx™ä›²ã‡/òQ$ ó+íÁa}‡0±'à¬o¢ hàª3ÞPÑ™@}Ñ´Ù[-ÔÁõS*îˆm·ª[%=Ù\w\îgºAȦ±ûT²½ÞƒgtŸ‚k´Qi;ì´þ.ì Ö³^'¹Ž ðoߨÆN: 䋸¹Þ¦ø´È0Tv VØÌÁWÞÏÇK`Ló(“à4Aîââîãšr6B“íQ£ã×¹u†%ÝÞ×N¤Æð7Mo-‹8÷gÁ3YºîESª5Ù1ºñR¬·pŽ«¶ßå¥jªoš1Å5{OŸ©ÀÌ\w »Ahã9­—OŠqs±3¢^=ç7˜„NåÓ#6?²$· ‚4ág‚ëÄú›È¢óípÑdÚ¿l¢oî¶È¤°ãG>á1ü9 Ârâ§ŠˆÏÔßPF2,·Éñ\œ8{¬pÉÜ–&\râŒØ†ã9©h’a[DPîb2 Ö›pOl@©¶Þ¼ôBýR }7›·FÈóFEâQ9áRí¢ ©i‘È®¼½Úž ÷ ƒÅ£òœýLƒ’mÚrZ`¡¬êvŒ F5…Ã<½ÑâZ„ç5Ñâ%D_ñõXà#ž†Ãu‰»[Í>JûÅÑÆñ¤‘r±oz†Å¦[9uQY†‰ùhÅÆgüùú¨|HÈÂÅ¢¡ü¥UÿRÊVâ±³!ú{²ë3õPÑÜCÈÜiÀw›êd'p8÷sQŠ|VwçÅq%O"§ÜñÒɵ3nKWÀ /0ƒ2#Inc É8‘{eO%¦Òm¢ duA¼6§‹‹n‚xOr$©vú#š60¶\BŒKmŸvvx!…°VëUÝ#‘Qo‚ß[å:LÛ¸G?å+dù"#Å–ÑÄ<4[1’À[#—³‘Ì-¬ø÷ 4¸ò ÑF$tÑÈ©9;¤# óws:U<Ìú 8qŽHH–Ì–…²> Æó)Ô­-º£‹2¼qè…ZUí€ì„³Åb§Q•”°ÊÕꪀjZÓK¾2ªá{N\X×\"êUà'm Ÿ]KV cÇi侪µ3Pð¦û§ýe*›å¯>(9®kÚx´û§åIß>ãÛx(:¸q[.Ôö>JêÚ,҉礻Ùw¦±„œâŒ†/¿î±S'Á7bEíÏŠÊÌhëÍjÝ‘ÿ%Bl™:;O Ž ±°—OfªÖU?ùö‰ÿR­QÆ/´¦âì3L4˜êƒêµ¬~¾“uq‘µæ»e\mséë#³ÀÙÂëǯš«LÕoie=[…V™›[Ëâ‡P|‘•æ™áêæ3F®-úllq‹û¦Q5ŽÈ{1 q8oòN$QÑ$UuIæZߪo׺ÖÎ2?KYV³\uu8œgVÈ/ªªÖéã‹ó÷$Œ0G2,U÷Ç©‚éòP•؇rKaÓâ®ß‡¥`©ÄV.|Ðo/p1¶×ºkÜsOpÝËM¬á‘X`Ž¢.’¬AMn‚p‰6&äñ @ÏŠÂÐGÚ†Z|Ó|=(UkÖ3‚­vÉéUÍh·UŽ ¢óL؉Ù˜7ö—ü+]J­VlÕ«P³ þ¨žÍTm&†Tkû3šá‰¡Áõb.:gÕUc˜ÁÚYWW„bpvÈt€ûAôû3M&vZ}­øžf6ÎÍMí{Í ðggÃ$ö–Ôö)Øbµ­™“ke DÚw‘–ŸHáÄÎKki[>^ƒ ¼º!¼8û”ÓÌw¾>»¢ã?MîðÏ’#ïè9(.%¼»¸›gÞ1Í‹Û%²kØd¦­º¬SnjY¿ÈñBxû<´b‚¶Ž!Ð/jùl­Ÿ1ËÐí4r½ÑlÈÌNšŸP߬’á& ™e}#RÓWx<œs*vrAÏhhŠˆÅŠÐš ™+EÔ:™ ZíkyqVøwg rQú ×\ºç¹Pá!Nó9÷ð·®¹ì´pô¡£2‹iÊÇPÏ­ÉÒÜÂ3AÃÜ¢3&{\ÂÂ>zâ%‡;d€h¾wÈ&4Ì å™CVeÂóÀ+‹2ÒÔ\\FdÚÁQ›Œ.™ÐÆ5Í{dâ‹ä‹œZØâã óž~ƒbâ.€å¢ÁGAn:ƒä£#Èèh&MÖÜWwÅ• qð”êa~ÖP@h?@=Ítîìá ­ªÇ’ÑQÌŹn*£éTeVÌK+dnû]y,Mikº,/mú+ò‡ åZ¡ê¡ö<ø{š'!s\Z@oŸ®Šc3Ÿ¦Âwÿ^å?æÉ[/U¹¿$8)rngœ#«¬=}f8é½–øô¡ÀâàZ›ç0©â7#ÚXq±LBP'a ù•*ù“+±T«LÔìÌ­5€n/dá$r`Õý«†©‘|lÿª¬i>°ìzÊ@ÔkM™˜ó)̦;F'Õ®öUÚi7ÙÈ\ß䈮êâ©=œÒ¤Æ.nÁqËž/XM›3ßÈáË‚NJà VÛäüVR£cÁI§%„e6#Nàø.¼ÖÁñRpüTãø›]GX_8¡ÂL‘{õT1T§ª£Ž&LùYS§¬¥NµÌhSs$ɶѵ·UW9ãBÓåÑ™M!aqÏ‚Äó"ò†#„¨wkhaëÁZ눽§3䯲yZ6œy-Ñá*Éä}Ëqçaîh~šƒ×9»€EÎÌú`Eˆ_ŸD¸ÂwBle©«#õïÁõm˜%\› x¦ QÔ&ÞëdÀË£.`—Nkhãšq˜°å¦œ8ä±UªÖ ÃsÇ’£]Õ™E¯s æ¢|fk޾¨¥#ÙêU~ÊòZê4õµ*¸ì7§À…MÚæÑ’én²öýâ 5 qN•<0qïH©Gh¦Ú®´œä¯g Ç{zNJB™ ˜™Óy'‚i;Ü}þ+oh~ ¥¤•¸9¹5اŒ8-Ç[2U'œ"ú2B>K-]NœVÕC¢H•'xè‚$,'.Ü¡ £h#ÜÏ=!ÂZ,&#Š5'3tõ›þ%$ÏŽ?T¹· oÀú¦¬ª8ik‘3}-÷]¦®&d…%Ù«vrÁVƒ‰Š™8 “Q•NÓ¨Üo¦Ò ˽ŸŠk ¾¦™pø³îª2¯hÅ]ôˆ}P Þ\>„ê5Khâvº£ƒT’ »ÿȲ©•©£*á¾Õ–þåÚ¬¦Ú0k³%¤Fê×7Ìw„!s)¬h“še7pn‹IË)ä¿ùª° .´Ä…Ìóô ˆ Ð'‘ч‰Ç–Ì|%êÝ¡þȺsŽ­’<­PD ‘%›Wj«M×e8ïWh©PTª)½”Ø*6ˆð°Êイ>†¥J¦˜ÖÁfÌÜJ§O ©: ©%®Ý,xiû¦Te0æ?²6¹¦_òU34糆ær®ç5­¡L¼csï²c–J¤³Væ87‘ÔrKøeîaîKL…öëëw!êÌ—©m:m?êô2QÃ副OrI°¶ê’šæø"ÆEg£ƒ¯~‰ÿ8+ g«¾Ä{Ha§zcLœ•ó7)ŽÕ}"ž³k\ÎyM®.!RÆuµ[M˜â|aSi¢Ö0Q̶Ü×Á;6æÒxnä4íXñ&–H†² ®I$“ñ÷$·Ú÷1ÿè#‚çêïo1êÌ£…»ÿ¢“sèÆ,–CÔ\{‚¡Ú:]{Nž=:)Í'/AJÅL›pD›¿ª²•zgȯl:6GU° ~"¬Ÿæ…QØKdñÐLcž\Äl¥Ù©¤~ ¼9qPÆ|P„x(q“ÖêÍKü"ž.Ü-Ýp…Íø½.&oUìŸ%·NÝŒ¬¤¡OqÜJšuCËšÄè8ŽVB¤ö×oksñd q3‹ùTÐu¸õä}^ªñÓ—šì¢­^ÒAS[¶áÇgÁLÕOb¦düN>jµ6¦ž¶K½½»®Øç6µ^Ð5ŸWµ„¶vpðÊ:ªiª¯§„íNjîºs0Õ{uØZüOØœÅÍ×g{·Ë6±‹¬›þy¨u?šÂa€ mxN‚dÞŠ._p˜|½Í/§ý=ÓLøVÀÏ5´â}Yç¨~c’“Ÿ¤˜¡BL4ú·N]ÀÂèr“`°²yú„µÑЫíŠÛäV&Üiƒp¢Ï½¹¡‹ mÅߦ‡7wб”ó‡hõN-pܶ.B¿i˜8H´'4Óq#ð _cWúSþª ·šê¸@nèi̦ÎðÙ–¢dÓÒ°ƒÃ$8)v°ýf[Xpâþ˜Ãç+YNÍȨ9(ùÊsXí—uÉ­uøp_láàšQÎ`2e`,\×Õ’zÍ–ð\>+hå–ÒÇd ͲŸ‰ÕøIDÈ e~(`Þý“]Š^s’ÂÖâ9©÷ž>çchÚÀá˜R߇©ŒF%s>®áÓÓánÓ¿E.2}4 î~¡HOo îaÄpòõ89)Z³… ÈÐG?ËNЕ,!n;䃰ÃE¼T‡aw5dj°c<[Ñ>©ˆ|DÑWùJc‹ŽbÀ'bÙñ [q#ºÛA»Ÿ~­`,ŒÄûAvWá §Úk1·h yÚë³å’mgQ­I¡ôŠs¼ZÞ;CâªkhÔ¤únk]‰íÂ$8¹Yv–>ƒ…*TYSp$—M¿e«u ºüm§«–û@ÁžV+ias ]ÀƒÀæÄåu·~œ=Š’p¹—ŒÏ$ 6HNzo÷žÜ¢~r÷=ÍëÜk…”QÍ22õ–|=,œ–l·Ÿ¨·Kö½6&å~kIp[tálçÀ+ïf{—0‹…ÐÍmfv˜=¤Ü•O†ßdª†àd¹¶‹¯¼ßXN˹z2 –Èõ•­ï¬×o[Ù›K!N ÀçŶxÿ¤]v‡T¬æÔ¬æýLFâpõä°8S4EÌv-y‰Íb8êTÆ*c{¯ ?TCK‹ÍøŸQüÜ `k ]ÄÎkƒ‡ËïA…Ó壗¡.æ}ϘîAn×4æpš ¿%°0©qŸ\Ÿ¤s&\yz‹[¦\a[t x‹M‰¶z‡>µ-8T?hxJ‚!}£~*1$áVx‡åXgh%ÎÙ ªiÍðÀ²g×–âü¡1ÀÇâ Ôླྀ Rg e>á´éÛâ¬1GeI`´ë­¢è¶_#ªs\0?‚”u 83fsî2àâáÉÎâõBNAA¡µ9"ï1oÑ_>?{7dùú{Ÿ‹ð£ŽãÈG¥(“™õì[裚.>¤œ+ìÇÍlN*I“£éãG­ÜJÞwÅ[Gi¬ØÇN™pÅê©ÍJõ±×¦ÌU×Ãä©U})•Z¥ ß`M•vö·SQHµŒ.sq»†ÝS£N=y5qËD0´eñÿ{Š£é0¹Ï3&³I‰±O|·<Õ*TÃÚçÅÈ?û‚$86€yiÇm×GÄ)5…&žo„ú•004Û¡CŸˆ6ÛG$Y¬i‹™p–‰B©­LR>Þ!\ç ЍYk¹!2îßÕ¤|ô–ûq Æ~ôƒdZì¸C}ÑŸº/¶Ð6=ÔùéÓ£¹[mg„r¾Ò} æëzË=CX3¶^óƒ7p†º½:Dܺåe> vÆ×QK³´6C­$[(F£;+ó;XyçñZ“ٌؘF ®ŸE´¨a¢û¶7\@9õ*tèQÁM†«ÍÄEâc$æÔÀã,HÎñ3š©K/üGÙT+¼ë(Ò¤ñ²KL’ÏìWf†²¶®¥b)¹ÜâAžqúªRi¿fu/2EZ”R¨jvvQÆ÷F éÕvV­Ô«W¥W~XpÈô®ÒÊ8zÕMIü²>q(ĺãUŽ.Ùo„Ã]ÚÇ${3à‰Î[e‹ Ɔ¸Ä;‚vo4^µˆ€AWˆûÒMÏ¢-ÂG/t¡ß$2Ý =}7p <> IŸ¸‹mô§¬‡ ¦fÞÍæ=n¸ÕתH§¨Õ“„k§<ÕWÖ¨ð5µÙ» éßÅ‹.…ÛÆUÅ]¬ÕÕ¥O@Ý#üÞUZêujÚ‡  Ü~Uoª}6¼V¦ñ´AÁyƒ6ÈYvfÓ5û=£†·êžç2¬™1ˆtÎȽÚëöÃ1QÀj°øå+èì¥T¸kõOÚqß8ˆ‹EÊíCPç2·i.‡ü šG ²R¯Eî¬)7XI¼¦€u^"á[f¦eÇŒäJfDbœN­Â²v3Ñ` uG722Mv6;XoùQÃ5*qy@rÐê®ã謤ä›{3¥×µ{á G­4pN‹[ïàáÁs÷H´äTï7C]Þ†m9pø,N2~ämè:zÓ[7åêŽrAD ®ŽEb©O½•M•ᬚañrÍ`¨6µš˜wòʬ÷T ìÍþéµu­’ì8Ý;ßÝ 2QÙ ‰´¢Æ×§ŠöÏ,ÔkìTñˆåÍ .‹àt4Æ(9'8ˆžaÁF~µWž50?rnœÆœLý>ücND¨ºdsCŒñC¦˜§ñ[Ñä¶œOÜàò@÷É>¤Ü[¼Q=ö¾r:NÙžˆ'Ÿ¤|ýÎÝfÓ@p,#zU-esP3´kqà Զ»b›يHœ•¬¨“X5Ý^`þØ¿Š§\×.k*ëv,°‘—ÙèÜСJ£1šNnp8çä¥ý¥”ðv}C^Æ›e´žKñš¤:ÓhÇF2KLØ©ô‘™ä4GAtþ<ÍÀôºh¦LÚx-dK:f±Îk5¾>=þg€A–—fáš…{«ûñ‡‘R/î¥Ä¢)˜º ê’= ÖŽÑw‚¶]}w÷ðþ/R©ü¾€‡w²€G@nm‡¢‘迺pӹȱs1- —ðŽ(‡G;'Þ*“ˆ ô{;g¢ÚtM[- o4ªDX! 1ƒ¥Ó1îbºiÙ}S±´û)á­ ;¡Mƒ‡g%Ó¢x•’Ý Ä·Å]³ü«¯"šáxàœ÷ï€~ÿŒÚƒ=®$«™÷K™äÛ†8z)vË?U!ÞGî x F]èü>¤r'¿sÊmå~caÜl7‡48ú 6N=~éƒpÙÚÓU¼?*’1aÍÌMuF¾™üqo5IÎnÀ< úL-»¿E5wÿe$¸;’ÅܧL<Þç$2ñ½ÕD`ã.[D½n…´À%†ÐxweYÌhâIÉ\æ€ä¶¶›ø—àoÍ¿?¿ïdÂiÝ3ø†]Ý‘nk|+’å—Íl´ ðûÔüÇyÞ¥=à›ŽCºìFAÃ#ÞÚp±ƒgŸ¢i´p¶øpŒV⤜š¿²#uNáºyD#3’/nÓšî)Ä‹e ÜESŒÚµ§z¥Ê<\ë©ÔdbÌ+l%fãþ¤ ²“´V«µtæk6ÇEQ¬m\Sãel­œÎ.#k‚‡Œ>+ F'r ¢OR®™²k™F“˜s² ÂÖÍöFA'™9 86þà‚¶Lû§‰».Wp d_š»ïÄÝ¿qµÝݧ¥¦Dz“m#Ð3ðÊ'Rn{ÅÍÂÐßeL€yi“uõbz•.2}(ÏÕÿhh,sGˆvXDJ$âŽT0Eø…¶#¯ 2l&ÃÕKmÑC¶]¤¿È&¸èO³s“‘hÞ~ËBkyG-”YrÇ À[.ÊåˆÛ¦i®ÜÄaU«¯ÕÒ¥XÒm0Àqa±“ñ] áǪa¨ÓÙêN11û…Eµé´Tyh{[_m²ì"ÑuV¼jû;1V¥hÆAÔXJmSH¬u"ú•b“"=¨ã<•š‚Å„X΂ì sLöµM7¹ã‘XMŸ×¸RÓ xò÷qÞ?r4ùw áÁAtõ–3¬§Æp qQÞ æP0¤ä†-ï 4)ãÅ‹žh8Ø0r*žÁ›JÄÏ6r[4Ë3² ®8«™òAµaÖÝC-ÑȆ홇J.æ›·ËÁb{Až5­Ý>Ï%·³‚fYÁwD\Ú¸ vE“›R1°ðDHd¬x¼ t2ãªm†6ØóPWÒZê{/ÁP†¸Ž$d Syn¯VÐê¤Ã$À#Z ~'a$5ä„Ȫ7ëìxC v,R9º}® ¨I|Vv܈3ðZ³ Td¡ñ‡ƒ—0µNà,tÕCòü^á4û·Ôå÷+™ÎãNÃ|Ê2ôEùˆQÇPsõ‡:<ô8µ»C~ŸŠæy(&!÷ªÀ â u70±¯çxðXd´å%Ñ«¦Ü=¢¹– º›O±éý‚íŽeRF£Xý…ܲϢíluWÔhÕá¨èg;ÆVT&£›Š­F¹íÌ´L^µ_R´Ð©QÍÂ3lD|×i§ŽiÃ\×4ÍÌÍà&l¹‡i£õO¦ð1µ:7fˉÐ8LÊóªx1š×4ÄÃ_áÍ73Ä—VÞ6 ÓAñNÿĦwxß’‰# 6S6 |tÌIÈi¢ųè¶‚ÂØ`ÓâIÏ@$J §Ëh ‚ÃQ¶çö>¾ít}Ë"Åí—'0qå¤õ:ÿ~£Ï~¬ÝŠÂ2ïH±Rã'ïK¨}>6»¶¸)¼6ð¥Ï‡ç¼œ Ƀ«=Àÿ1WÞÎKÆMßøš‰ úØ€îjΰ ^ piM<Žh7‰(¸²LE•;sgÁ ‡ƒæ&jeubw%¶dr (<ÂÍßÔ³tóĬ柲òAÅî‘d¶µÅ„[F 8G†ŒÏNˆRËb$~Ê<ù¨p™É»pü½Áp|Ôû°)ƒã÷[<4”G¨G>ùËCå÷îG ¦‘ÁÒÖT.€²a ½¤ƒÆ9.Â;3ÜÚÍí…¦5´žì'Æhíô\jöO¢T4iÎÉÂæm|K¼‚£J­*tjU­•ª0D`.Ý7·5Nf¦ÞÑYÇ´ÕÅœ K…¾<CvŸ¦;frÿ±ƒ 1A´;=:i%Ø€›ÎŒCÚrE´ÜÌ ñ  ø‚¶uu†”8o7–š,-hªj9•á ÌþŸÔªÔq~­Œ"7›ÍT/kéd7…ï’5Ã*= –¸6%§–¢«8¾®£iºÜ]†?÷(^qbà\3 ³öªÍ#YL9Î`³~iÎ}Ý¢\p„ xÀÌX+Ô2"@ü+`X •† Ž¾àÙÖäTdî^ëx"ã™û¬tÒò2ŸQ–d}ºÜiÉX‚°ºÇÜm8v ‰øY7&á ,†´dÙÇd¢Êdâ4µmƒÖ¯´vJ.¦Ý ÇP³\sÏõ_fÌNvxzDü lÂ`FY)Ä<Óª_;¶Xç-ÁýKpRÖ6Õ2s´Í„0¶h?Îʧku95€c›ŠÃÿÜ7àŸC‹Ü­|•Râf¦ÂŹ*!µÜÝQÇjmŸNvº£Z÷²£˜"îlGþÐY§yÅå˜Fg¬Je-cÃ[GQ6ø[M6ï˸Cd¶ÖXw?/ìµw©"l®ß%"›Ü:·G’¸s|B‘¿,¢¥5 Èõ §_’Ùi>+p,šÀ+‘â>ÿ ç÷cšã©."ÚcÏÖ9ðBsÑÈóP~?y ¬‹—>¾•¢clŸž‰p‹©³Ë¢[ŠÜðî1Ǫmì‹Y´M;"z•Ìñ:ZÓY3Èg[ÁA6çÅl6„>è©Oíûº)c’æz,FäwÞrª»»* #ù"ÇZpˆU{$Ýd WX«êkkh}óØáÑÙ‹]®oúšÐ|‹—htc_Lvvn€×bƒ> ”êdE@ç6ò‰ÿÜ4so$Ó#ÒKŒ/«Ô¬ÇÁnOt9¹ á÷éqà‹pƒƒü-¡çÞtñ¾˜>EC¼ßì?œpÝÈ ÃvÇ"Ô|pÈ7‰Nòw  È€xñP;•ÿÿ¦˜P÷AûÀUûG6=ØQóÛâ©nÑ êt9Óv¸ ×enηµR¦êf8ûtÊL3EÅì‚°ò¼ðâ¨Öí5…ýŒö“…›±‡ÿËä»S_w4·jÓqÐÆ‚âÔÈB¬ã¼¡Íp–¬m&?X]g}î[R“jÃLâü'0œÓEì ùeðLÁI­Á8OŽi­kDѳðPv]§°l±§‰ZÃUµ_¬Áõ±@ƒµÌ¡Qô›Ù™ÚÞì@ac§ç²Uv:ciö¦vv¹¦qNþ*»Ö­ŽxÕTÅèd„æÍSé {ZÚxÁ.“Ÿ‘O8Míq¦æçQ@Rs&TU¾¬qv±¿XLœ(U¨ê®ey®Â>Íð-úÙŠ®cÚÊzŠ8GÖóÿ8*˜{EF0v¦ÒkZÑ…£§UÙuÕßL2½VÖ˜¼J¡Z {ã³V}™´èpˆ] =úÖ4·±báΛ•Xs‡ã\,ö×3VÓ“¦PÛÏ¢Õµ…õ á@¢ù‹–*ßZÿX æ~,±±ƒ6‡OO´]Á­†7ƒgŒBÂÖɽ®ª8pþ¡­ €,ÙˆÍToãù]Ñg:ZJs¿lòÓ½N,z•˜K|­âœìò”`Ì}í›Üù{‰!avÿë¤9­·Ò×gÇž÷<ny\œ%ÂDKLõºÌXµºÓŒÚ3ð ô<4êQoe ¿6ŒPõ ×Ô«¬omûÙïþ¨»µ<jþ²Œ™mI”GUµØß¥»´T§%ÍŒ'Âq†¡¥R“j`t» µ¶èƒ%Ú5ÌþÑV17†%Œ; =3‹NÃIêló?¥bÄ ^0 ]„6da¿s[Pc§Â=…#.å*’#…Þ eÀøéœõvøéwŠ3»[ätÒUÁû*Mo±´N‚ãZýSkF°dsë0uŸ½0ÿÑI÷NK6~š5n3ÚH"T JÙ>Z y¬.Ó aðîDw$ºâ|ývêG¨Æ#(’i·„æ­ºP8æ8*V'~+ ®˜àQ#’'ÄΛ”M1Ts´šn²£|I[Ãâ›íɈjv¥¶üG’s? î5¦ž°»&¡S:1õPDå:™¸_¦‚x«œ¹¯²¨n*GÍR•Jr#j©4–ìÎÔŒ"<‘ú=fà=& ÕDç|ÐÖÕ5Z=’4ø#Æ”ü=ÕòûÊ\`(§aÍI÷#¢ÄËG5-×Mþ*YgšnÕú(©ýJB&6‡ Öp塃ªëÔÏXX™ŸÍ~ÞžM‚Ø<!¡ôÀÈ‹•-x…$\Ñ ²pà='„ò(´æŠ'’‹ºçEMn „bRÖSpè‘M€øhsˆ-i0O 1A†§æÈ,uŸŒØÐÃ#Ü‚$#«n²–xfíL€ú4³ÅÅ}g×;›»²6]ø‚àAÈ·ºåùË×$Ø){Îdçxów%.3îUòAô‰pÐÞ–în€S 6›®8x…-ÌB„Oy å£t,Cà}~y_Dƒ…ÜÕÛ‹ùTŒ½-÷µ¥øhÎI¦¦çb8ÏÁñTÅZ ÐÕW}:µMÞÖÔ†㲨=µGfsÏeÄöÄÒš JxÚ]CQ@ ÚŸ)ð)¼A†“Ó53¾R4]a<£‚ÃV'1ÈéštŠ¢ªç0StÞë :ÅŒåBi¦p2!×¹A­°OuÞz é.9´CgÙDY`9{'Öú‹£Ìßïx&fÿxamßú)7>æa7n‡3Ï»?%‹ ÁòÍEúñçûˆŽi¸¸Í8»£ò»ä}$ 8{EjŸM¯ áŽl…M¯£­,†ÓÄqfÝ>ž¬] ánÓÛ˜ŠÆh0º0—ˆPi`9‹¦àú··uÉÔõXÞ8ä¡pçLÙr<Žœ9»ôS‹Xr¬AðXdbnc—w5žŒÂ²#šëÇD•‰ù›ø#dOAÉËDü“K¶¤Ú˜Fi5ÝVÈq+m¾mV3în#¤}á}IÏÜãÏ’ÄÍ•Ùóï’ߊعqï7÷èãÂÙMÛÄüŠ…8¸ù&âÏÑÀ¾Õ^Ä™„8@ÍÈŠ·.±‘ÁT,’I‚ç8¸Ÿ2¦ ­#’0BãæU .8˲è¿8Ýw„ÝüA[.!oü²‚2L­OyŽÚ#<(p{ù-8QˆÇÉu≘hFœOæá§&Çöô„çÅ=îɲSÓKºÙKþ F÷5g;¢ &? Î#osCɰá÷æ9)7>çº8ÙEŠ‘b¯˜Ï¸CLhâžhÃhïOr'á÷ÜapxøönjhÎ} ÁÄT;=:f¡,.'û.ÑOXÑU Èü#üºÞ1\ræµí§H; ‹IÏ”#XÀn±Ìi'trL—Óšãb=¯jŸÔêž6®Çþ&X¨qÛi-ñî½ØwA)äÑènƒšÿ"ˆp‚ Ñ48¨(Ø–\Ëðïsð[|P›ZnV2hÈ7ÉEÏ} ¡·…[ܼ'ußw¸û£À8pÑùN}ç¬-P'#Ü•„ …Ùýá„b7(WkhUúÖΓ#â»H5ìõ‹Œß.ýSÉí,§PÑm`‡4j ­55⸒70Á›¦=µ)º«j=ù;JM`v oi´]Î?¢ÂÛQâïÅÜePÑ!à¹ÑÁ}«V,×~"Õ±ïwÆJÇk‹NaaüD7»ŸóÑê!^ÚƒÌ-^Š39-‹­©žH¶\~Kn窲œ! Ñ’›~æ’ý€å ;ÉHáÅ}c<ÂÞ~åÄ3û³Q;>éÌíNZÇ»7ÅÑ©tóD´uï_6ýáã%¸ìg4ѵÕŽB¹ÙÖÅ<-æõ5_Ó!Þ‚ÆÇ‚€ r Ëvøç+³1ç³?æzz÷¹Ó?%žŽ}Lá»žŠŽòYið3èMÃ2ñÒ]9Yeæ ‰_²s¨» Ö0Uª»âVØÆÞaH÷V&ü>ê'’Útû«¬h†—å9©âödØ¡‡ g調¹BZ8y­—9(ã§[?º§•ûîhÌñ@ •³YAÎ4&ÙÂâz•sàºÖ(5£OUŽÐ##é3‰0¬<Ñt<ä÷!ŸÕÉeÅÅO§¸n3˜V2š}o£jV0ÞZ^>áµíâ`èÃ0#‡ÜŽÇÃÝë½>)®97`•7ðÑGã÷Ig,¼;Àðâ§6œ‡$aÖæsBðÂ#ÍZë²sRS0Ý á¸‹=žhqX·¾!{'ä·~uåèbUz?Ï‚s8oH×yiü´«OÁx-³'—kwHM8mšÄ 9d5GŠÎ^m(<€ p¶áÇš}æ\'’n !ÙGÜÄ!a-ÎJňO‚‚ ‚SbàýÂc…ÐëîýœGšÞŸ¶ß‚û?šÚijÙ3÷`‘ïà9–»Æ&GîœM3~P€"0›ŒÖ'ÐrQ£èïËØw>ï#ÍA³»à âT A‚˜Î{¬o7Çrw¤ŽçMòMªï& Îyøw®­ö3 æc8R Š›¸ó: <Gž'×l>çp$á¶"Ho‰à¤ éöDï‹ô_1ï-¬·§Åd%nº°½“Þ¶i®m‡% ÷Tc{5 Ìü“Ž‹ï³xºhÖµÎèt–ÈÄ3n1u ÇD|W^AK] æ§>(Z&.œâÁwÂû6üÙ·à¾Í¿!Á^èa³ˆä·ÛñVp>~ƒÌ«isZì$ñ@XbÑÁ;¨Ó×’€á<”fy&Ãð©lcåÍÂ#š€¯cÌ#JoÏ¢±/г\|”¾|É`§öŽê€åîŒ×Õ¿tãmÄŒ&P:­“šææ ·"kDžhZG‰õû§”ÛÐØ Ü/Æßš‘Ýs<Â}Z‡ 6 N=PÂIHs \<Ž€ÀÓ0LÅ“žw†QËIÙn(Þ„Ýe:˜øì†“M>oxUã)‰9÷:óVv"ro³MŒç¬3?ˆêÀvx÷ªuyôh>!n7à­#ÁÅo<©}£¾Kí>!o7ú꽂‹Ï€¥®àá…6 Ò:…uÄÅ}­¼gãÜià á~1óRæî¿|û‡YÄÝf«Áƒnk³K;EZ”» õíÀDÕØãï&ÑÖWmv°1íÍSŽñ¼bL§Ú*v¦ÑHk\ÀKœáR?éø¯øel­MôÙÙZw¶špãÙˆÊsF LœÊ™  TŒÐA:6v¼óõdžœ¿‚•߯t8p]®‹vªU¦àÏ0ªT}Z•ÌeäKÀ9r]•®c=ïu:Ø-#“l‹[O>–+k1ÃúÊ&rÍ["s=Ì"ÜÊ®?0ý’÷dmun{V)sß–'™WLÿÊ/yŽõAÀ;þ¾œÆy-ÒqF¨;gÔ´ËK‡5.Þ>ÀoG$%8¢¢Ö>܇¸›ÅoŠÄíž¼p{€ä·Ž˜&Ã'sÑÈ„q™ƒ;ƒŒ£OÛY‡ aÈð÷rù}ìBºXMæÈÆ|3·<ÔÀ•|ù¦œD󔿵…Ðl·_<°¡ˆº|U“žÚަ]œ#Øãx¹l:¥‘ w Z†¤d8h“`š/€\Ù;•§¼ná7³•žà|QcŠÓ1éi·¬÷›LÙ¦þ=4\èƒýœœ‹‰Ô~ñ÷*èâ”蔂‹›”y C Üù®g—ˆÀŽ ¦1ÐgŠ[å…I0=`·€îOð=ã“»¸›Ò'!&%TefÃØÜÁœØ‰0móÙ8X¸)Âèã ·‹U¾ÔŽà©ìºÏýŠemŒ'ä˜ßg3×C˜r"ª®,ö^r#¿Q§ÚÛÒÒ=nñ2@@DyϤÚcncÜ‚S/䶇Á4ÑÅBçx”ê&§Ô’i°í7;õÚþ•Mõ{3iÒ{öìGgÉ^†ê>‘±RdrÉT¦æŠ5˜úaøkªG!Ȧö|!Ìs‹CÚMˆ{G}ÚƒÖ'˜î5‘vœÔ4Iï˜þž£¹Í.Û§Nç†þé³ÚÖ¸ºr˜Ê:­M7jÁvÍK˜ã”ÚÓÕ;³‚áTaÔÜÙ8$_1’{rd¨M{YÖÖ‡X™Ê//»c$Òó/pâk›-+ +ìõjË^>7ÌrîNA‡öô¬qÝOÆDÌa䥯0f,t¾LrñBLŸE´­wœ‚½Üs÷P©PÀü*JP¦ôTé†lS ´r!Rki‰ §4÷TÀ溞«V1‡ÏôE¸&K\Kœ\ld\­hfÞ"á´`œ>î7Î-3ê€ôîHSÅ@¹*;¸À’y£hiËø Ë?á¦*j-Ä%¯ÊG$u½¡¸Í=\Ó£x;ñJ±Ö­¯J¾îX mç‡æ¾”êúç1fáv" ÌôXo#9E­Ùæä<.ˆÂëß%GD·dñ(4>yÊÈ5LmˆÐ瀔\ë½Ù¢9¡Ó¹Žìâi…õïÖSvOŒúÁu‰Ž»wªçÏÒ†ó*âüø¡ˆâhL4p´Œç(O׿ÙA{@çNÙ“”ÝXà=h…îÆïr!ÎÂÆ¬‹pÐîYBŒ$ø'p¾‡Vî»ÁS$-´Véþ¢¨öjº–>¬ÅJÄl7‰º£K³T†³U –ásI‹q6Ví=¨ÐÂÛ6'–k±Ý«QíÍm‘ºèãn •ëÿ¨^KZ'<×hkž* o†¸}‘ÊÞ¦ÃèÀfÿšÞ)Ö¸¸þà>]çsÅtçF oà‹ 4B]0bSEÔÜèìõOÞÝtîÜ«êVÖÖŠu)l¸¼ ##éÐF˜mÜÐð3ƒ’lp´rÑ´@ñV¿}ƒÏÜaÍÈFÌe%& ¹«¨GfD•³´ºóE¦ý{ŽðTË];"Î@¼gË‚ÙgõYN»À«;A¿Í7hƒÅl˜o[¢úhh䨰z&ø§:.sÒG/à4AÌwg‡pœì‹/Â4ƒi¹BZZÞ1šÁ@É4p‘:Mgn‹0~úïÂg»ò]xžã†G0z¦;˜ŸJê¬kßpÒá'IÆö· q<9¨Ð&cŸ%Yà‚'?$Eþ~_mÜ\åQÂŽÉÿË _NÌ… Èæ;¦ ¾^äÏ ˜4_>ã›ç¦è`GÍ~Úà©(î=öñY¶yBÿˆgö†Ùj2¸ú!/mG ¦,¿áÏ«YîÖ†ÙÆËä¸íD\e<¡vÀÑQÔªvjÕ8Ékƒ› ÝÙ76ºm:Æ£;[ª¿YÀÖ“AÂfdG?’ìu»EªT ÂîXd¨`uN­ôÆNYz)<qÝoüàl{Ò2â6ÔÚåÉ@aÅÄ,8p·ŽŽ>I¸w|r¾‚Ì!ÔÛ™âª9§êK¶'D„XgòÏ.á> GsaÄÀ”Ì;±iïOx—\M—lhŠ mü{öž ·Wp¬ý[F®‹\`ì^9®Ô5•YO  ó¼«J«C»M‡KŽ$sø'_$ÇEðóD¹Ä‚g°VîKIaüªd½œg‡qGùú{‘MžgC\/éÜ>kÛæŽåÀ>+cdôOàB¥ü£¹„fë,ÛðYŠÚÙ)º°^`øN_(ðOÖ¸âc°˜6Ë8ø¬à)Æ×íDæ„rÑÂ}#[>^‚’˜_´yrîâÚ<þ ï ûÒ ©H øi|ò@9±<Š}Vºs8c5«&ÆÒdh`Ëû®¼B  Tš^˜¨HcŸ†ç]íí®¥¥ÏkÒD-èq26¦ÿ² 6p´G÷X©UeV‹K:Z×Th#+íñ[íøú 6 Ç~jt 2yÊt,'šŽù%vÏRV&T3mOîæ©4o ÏÝïÅ‹²Ÿ¬<ÆÐ9è'’ »ŒpS1ÑK¸i#ûÄ·”©EöGps‘壿9²×fÞÆQ œ-lD)«õ¯üÈ3 #8M“,冽<'ñæ ÄÖG'È"çf{Øp‘â¶Î&ò["üû­ªqþ71óR;òÓco˜R …2pƒd$ÁÌHÅ7ÿ&ž˜¨hŒƒ¢Ë³—6•<.|Ô¦òMI›D[Ÿ’m'MÕvWöf9¤íÌ\ÚÙuUË\ÌdS4Ï'3š¤iÕ`¬ÎÏU˜ÏþcËN/ˆ*½J¤M@Ûc/ÊxÀÓºã~ ‹CÙ3 rôæyz ÅK^‹ ì6þ:*^ä–:.áÕXGuÇ˾fô\f îsÅ—Næ­ŸY[ðÇT á2¡Â- ŠzÁàë)TÇEˆ“| ¨¨ÜMæ;ë컚žì»à¯a˼Ð=["×?À¬Móo5#ÐlåÉc6âV'gË’1T4rãàCwaDß’’š"ÄtÒqîõF䉱>gÚS2¦æ>›Ïâ sä9«–³Âëklsüððâ¯n‡4qYqvÑôÏÞnëyuô6A¼[²t5þG܇ýfO².¡Ý –ôVXÊ2Wn¹¿‹"¾Ãýêuî_aþõöïGê3üèýF_™7»áeÔ8-[òy6êôêô'™ÃhÓ Ú¨íÖ„]âîe\àðXNyÈÑS‚†o~‹ THÈ» ós….äcÅ ?ˆò Ç褙ïë8Ÿ@ùÎx™æÕoAÌr9)ÄТöÚ‡T{= m4ôFv§šÌ˜ÊNKdsAÁÀ ³1ˆÐc˪ aÃ<±dš-c0öâÉR¦]xÂ1fîûjÿå™·$%ÝOrx¨†ž¹,<‰´÷­}¨Û°´ãèp±¸¹•zÐp„ÜNw$FIè¯î=Fi#šiæÉÍlìÛ‚»‰ù/kúŠ싃MÇ<5…µÜw«WŸ­£FŸR¯R‹|.¬êÕ?•¸B/ú;\Gã~"S^ÜŽMhº³L‹V¸T¨\7_WT=§ñ¨Ø«ù²O¨òQܸLuÐéχ††ˆ/tÄwxÙ§søðRrN‰ë<Ô!AÝç£ULÛ‰\|ýñô=“‘æÖ_ˆwd©xžM[ø(Â#—ÅB¢îrÝ.c‘”Ašrs¿ÉSŠü¸*¿B¡Q —º£+9º§œ& o"]5Q§³j¨=ýÆœ5‚Î8ì(ñT{jP¥Ú^XаÒÑtÄh­Ù‡fÇEÚÐÐÐÒüC‘¿žõ‰ß§¨vÉÐIÈ àÁÖêSy /²gÁ ðþmðîF}ç@1מƒáý½É˜R3Ò:Yy„ãÃ-$DÍì·òLx碀 |ÖË@ðÓ¬.téÔ“²w'ôÒ0Tâ$ŒY­¦Ïò¢á–J›¨€\j\Fb #ä°4·èM çuOèà±Ö¥R˜¦Ípdƒ‹þ·ù ê¬m7“ `>kZÊ/©LvqÚ\daŸ•H¤iÖ§VˆÃ ÈsÀþé¬s 7ëõ/m³‰S¦)¹´ê‡`|‹€…SŒP=¤ž‚-óOÃbȬBý} ©ù÷Üû:p[c^:òXŸËK ÄÅüÿê·AñPGÏ$ö‰m…Nh³<Ö°Sé2-Ã"Î ‡g¤Ækëb ¼ìµ£3ó —f¬ÓS´áÖ;RÝÙ‰ºìú¶¾¡«C^ÆrÛùîØCéÑlï ÚÖŒ×i©>¦iµ”ÈÛ.tõŽ µãDEÁ[^|l¿ðÛÒåáž—Xw_øJuN Ùoïè Ü-—ÐÜ'4Ù­±Ž'Ó嵢ܿ·¹Bû.Òá`ÞoÈäQ6tù(Âgª¹ÆÔçÄpÐ6²[îù*7'<û—·ðOÙòè±µ»L8¬&™HQÄh8NS]Tc v0g#—šciÓ†±®h6v¢«EÁΦ¶òNŠ—Òª¹Õ™‹mµG„ùªÀp:€ìäO±þTÔo"¦B†5ÎÃS[ιtGè©UaOչċòQνT=䌖{‡çv/B×wØÏ?à‰À$”æy÷æ>H';8…¶gG “n #€TªkF­2KÈ´æ/ÁkŸQÕjjõdÜž(3é} a {3ÎÃ$tÏg4{%G’ÂÜ!͸yG‚"¥jµëÔs_Œµ»8g„GÊX²ü¡¿!Üu¶€±ä¯¹ìú«f#—«?ú‚ÈÃÍl‚ï¾Î€æO@º¢FjI—ϧÌ}÷lňîâˆåÜ>*ÆUõw/edG;è.¢í[Ž|ŠÀùkâA™]†Ù”XÑ~a0E“mraKŒ»Ž†ºN\•&†í·h»¢-°ùè4ñl…ÒŒÏ}ÇËÒ=ÃÙþ7ÏAÕJr¦`sîG;.ØüE¥ÝŸ³±¥°njUçeÚ1ö‘B¡ÖÒä‚@u¿eB.ÎÌlíE¯jckŽ¡îÙ>AkƒéÍMF' }@Ó`r¿®Þúµþ§²±²E1‰äƒšílªqêjàfc7Ãn*Òß3:kQäqBéµ¶ªƒc¦a¡¼Í†‰ enÓF‹Ý“j<4”à%¦ûè{Cs,áÉ?…åI°MoN:„ã»V„ßg“´O-Ù'ЇÈžçåä­evƒä¨ÀçÜg줅-eúimÜÒÑì I.¹¹Ðâ3X9&Ï+è‘›ArÆN'¾ä¬c.#F6Üz@ѼQyyÎôYiÀÏð)ž)˜Á7аN ÞÅ„žsÜåý–1®´¢"AàE cZ@ “Ý«`ö Í Y¸]CZ9¡£ŒÎš§ð´7Ð~cfͦö:e·so KQ1žŽÒhN­Ý“†´Ú2ÐxU1·µ}é#e˜ñ`Ô62¼båÇÍõÚì\þ·U¬ÿÝ«óUÏg¦Ÿejaq{*9åš¾R£ÿÒ£Š»ê>7ÞÂÒD˜ë ÒA“¼s÷2FðRvœ9­ÑðÑq+Ÿ#¡­àJ–š|Öã~ q¿¸ß‚° <À_˜pî4ò(ÒÆ5gi¤ðè¬AðW°¼öIÒ)·|þVÐ-VapQ!£¢vÖ Çì¬.ƒ"mݶoÝžI­ä¢-eƒŒOq­çè‹\,QŒ¿Œnt½¼H²ˆ”\ãqSIŬ2,JƒEóíZÊ×RêádÈœã/°ýkPæa/Ž3#rbDpFÇà¸%Ú ˜î}«|ÊÊ?1ÉKH>É&×ѹýýú¦­J{N«©ì1Õ7xØ,dzv±Å£위ª”ñEFâ¹Á-¢å8º±ÇM˜ÞC]…€‰ÎÍ2e†Ú,#Ȫa†Ks÷;mrdt‰1â¡™~$nL:N@€HÞTÜæϵ:MÅÀ¶1œ¤¨ãÉ×´€`™È¬C?ÕOÉGZ.%­ß‰¶A®Ä^:L¨¿"¿!ù 1TvMD“‰çyÈŽzH[CÙîÁÉ—Uož—'ºî‰_hcÁsñXÝwJZOQšÍXÊ<…¿‚íŽú÷*1ïkp¼Àžßê˜E¹E§"‰«-¢äy#N¡ÛFœ'†ÏÅ]>!`ÄÞ6´¦á€* ÓMþ ƒO&;‚öó_e>Qºy:È\_º2'i5´áäõW·Fqø¯¬{h—ÔªãÙê ežm‘ŒB:æ«Q×}£)6på€ì»I¦iÔÖTwgúÆë&áÓ•ÏÚißm‚¥ ú*†¥aR½R19ŒÂ[À <ÔçÉAÌ*”ÅFã¦{yÝ]Úá8ÖâØž‰¥Ž&“ÛŽc1]üîýtö—2›¦¡acõ°ÁÚlßàœíSKhmQÚdKZ#gŸ<ÕjF3ô‡Uk©`¼’F|§Š¤Çjé5ô@­«°k›8cÌÿµ1µ)°º«žúÑ„áqÝÞáUuŒcë}”XIÊ ÅýÅÓj`YV›šñ‡tgÔö½Œ¨Àë7o‰óUhŒn}-øaiNqÆ \XXqb9L4ÜELf›q4‹$‚×m¹—œ à“˜ ÅAã0ü?œ‚/ˆà'¹vƒâèiæÕ>Ø8L«%€ßÃDÌ CŽÁKC•­hÞ&Ëx"I—>ç¿Ár»Wi¢ß®€æ7ÄßóˆTÊTéÓÂñ˜pÞ¼Û%V¦gµ âÿÃÂ'Ë5A¸H¦Ê•¸äÓºµm ú‰ÔêbvýHbþ?IÍ£J*ìÈÅyñ´ht6vÝǪÜù­ÏšÜù¨Á¹ó[Ÿ5@â°Ÿ# ±€Ô«ø[ÃÅk*œUzpîÑ<äiÄ3môžMÿ?¿£œ€¿—Q¹™¬Ó]Æa8‘yÍŒó@rþ —qm‡r3wá¦ããryS ü(ᢇK`äk•*•Z;Fª`@⎵ΤÌYL6ò+¶ noÒL’=?d÷S“WZÚ£UM e9&½ï4«Šî¬$® j— xÁÆÀéÄf|UæÑìô ¸ÄT>ÏÂ\¢N>6§ÕæQ ñ.±ÄÜ£¾ö$¸x/¢S£¬sZ×¼—á€IËžJ·g¨ÑHSkÎÌ$u51aÌArÍ6{;© ®ªÊoÖb$â· —ÕÔnö .áškDEBçâ€Âȟյ⣛ˆ#î™B`«lø'ZIê ÄôWø«¼àŽœÿ— ä¥Û@ÄžJÏŠ€ÃŠcMüàÄô\ÐpÒaƒ¸¨cCtË]KÙRLžz o9kЇßóh‡ ÀÕ¥þà¶\ åÇEO<Ô;#eiÜO×aÆáR$ag.ý'a.ƒìª à¤Æ¹¥¯4ޱ¦}“Á‡rÿ­´]V¨Ðwc%»ó[Ÿ4rT㢠ÂüTþcNÓoÌ&ÓúQ§D€C‡¬çœZÂ÷TßY¸kÆ'°ð)Ö¶CF¬µØbÒ2è«Tö^í“è/-Ã{#IÎ&=®kke¿‡Ý†pÿö·ñDr:n@VÐêÔÇÖ5îóƒšd8'U#—F/µÂèøè©S€Ù ­jòê Êz°ò1CžN 3 ¶àÀÚµ{H¯üÀÚc¢ì•ËÍ\muIpŇhZ8|×ÑË(Ó4û»6&»íI‹ŸéùªìººsJ Ø'’bÑ‘Omx3UÕ#<ü—g£EÚ§»´7 ‡AƒñTÿâE½š¨Æciu¹â‘þ•U¿Kkiý"“uç 8¸x*HíÏešì¡ÐL%¸oýYrNí?Hsª»þN³(0¹ûxŒDò6]¢ŸÒþ•E…µ†ÌËmnù©Ìó>’K øs_üÎEbq€‰`ÙüÆ·~_õ@®cÒ•²ÒLÞÙ»ä±:•fp0ñ _›•]Àh;P%oºË}ÈœnËH‹³/Ú*½¦‹)8Gµ¤Ã#š\ט_MÍ8Aƒ˜TÆ¹Æ s°ÓtmnÉ‹*¢KŸCyåŽÀÇnìØø¹˜¡ñðƒ»’š¯s\Íc[«xÄÙDçšÆÌfšjb$‰Ï$Zó/aƒÜ—[á_ŠÈß›Qwaî˘RŽÈ…²HóRI*Âèø+Hð)ßÎïÕT£ø ¼‚ã`'˜söÊÈpÉͱG\c•EzŒɹ,"ýO4ȦÖ‚0Y×µáÐGâ™Mó¬È3ÙÑŒ†‚㺧؅ÍÛ¥Y¯ ; I=.œ+làh;˜#<Û& ¿E%ÆMüϦ‚%l»ÈÝ^§î¯´yŸzá¼HWùýÆ¢pà½!ëd:é«?iÖ$ã‰ßÔ›á¥þjÒmMSžÒÐð'T~ˆ)Òi¥ªu*¬Ö1ÂÑ"x_⥽¯ ÙN¥7ã§;.3³µháš©HU?YN•9ÃøD.ÙDUŽÍUÎxa¥¶ ˆÞr™àƒþ>Œ+žÐ)jöñÞÓ9Iä»Öð½œö}ÝéÁö|ÐhœFÌŠky]£1E–ÿÉ]¸¿•D88åe2O‰P¶F†ø{²×~+ÂÝ÷;¿U­;õ.T¸ÀXêήvX¢ÄÛ½.±…u-&çÒÊ\#ꙋz~ ³\$9¡EM¤Á‚¾¶·TIê Ÿ½šÐšÞB4b‰áT{E0ÛDÌ÷äšß>ãü;±RÔÙÃñYŽÑö Ìßr§0¢‡ŠàÕ::wð[£à·GÁn‚`‹Á@˹ˆ‚; ìxq×øV)4Þ7(“UÜ6˜H‡hƒp°›ò:[¯ßEÐpuø€‹… i‚ZçùB“Oàn†®÷n… š|I¨Ýâ‹0Õnmï’ä¦Qà=ßž"é§Ñ]avðÒʞ˶_ýѨn$%̦!%`F¡ N9´!ÿ G–€ÏÆàÞåÑ…´ÊÚ`‰áfÎ'N.¶—àìmOÿµs ÄÓnO "]Ïz?5º2Nª}¬½à4Žéɳ‘)»¦Ñèg"2*^÷<‘[/#æ±™( nÕƒ8Z‘&TUæQyÈeü.c´9ÎXëÇôyŸ×»à4IÉŸ!ÓMj޲èð¢Ä£Køo u´¿ Ì#ÀƒK¹,G!ð µ&ûÂ7š„pAÜv&ü´€.JÚw˽&Ájéä3<” ©øþÉ–/t^70a¨v ä¥]¸g%Iä³ òâ†ßÄ/eg û¯t]0˜x»hø«Hð*žÑ7@žŠÅˆž‰Õí]G<‘kÆÞ]»â lSýT ½â#Q’-vcˆQ7щN'Ú*1Kxʃ ð²pÛæsïàa†ŒÊü/gЧ<“ù!1+«(!7¥´¸ ­Ük¼Ž‡ó²=4:3…@bkà4çtöëç²1tFAq&51ªo„”×Ô3i…Çà…jyÄ8šf„OƒÙñÎi®#©v‹XÈNù"/ÍsñÑœ)é6 UK̨þDß—¢ƒ<2p²˜Ä÷r Ì§Ž›¿ X>9¢ËRæ–Ô3Ǹ|Oë§â¿sÇF1Ã5:)µóDª€¼Ò¨‚À‘î«S T­JX\ÂØ™ƒâ»e>ÍÙñ6¯gm6´0Œ_Ü'¹Ôµ•Úhjë –Ë6sži;{Q«¬Ä0á¿^ª£«RRFÕ,Â.Hƒc'Êcê» íV%¿…£÷¹óA‚hNyÍÞñÛ<Ð-¦êÚK±C½.0°Ó³9¨ÂHq†Å¯ 9ätZIâ#?4'8ôÕj¶6ŽkYìZ r!TmP jN!Â-¨úµ f]yŒú.ÑÏg¸]ìœúwúñVÌä®âT—sW‡0»3ÅÆÐ[M#æì‚&öN–f= µº¶chßÃp„ïÓÁhu=Ü$XóTª6%™5kºnï-„¸:Óa–/ÒQv%¦™úßxÈò"n°á˜PÖ˹)ªmËøeN«5â»1jßA˜£+†ßÙžhTcèâ4äŒ6Ç|Qpˆà©ìÕÖÕevVÄóyœ3ò_UÙªê¾N™¦üV8öÌMíuGéT«¿²Š•v)È>Î1½á*‹ðöšÏbgÑžÇîÕÚ¹¿òÜ«ç£Ì~º|Ïë %N ²ºÚ§óRnO˸êXI-»Þ#g‰µìœÚÛí<8¦h5²Ù⛈Ȝ€X[ps<Îè•éï1bsžp±ŒçOÕ:¦ë*×-^6kòñ _´ZàÒð>ì<2ÉT쬣Ú$CÜçD^v‰ž…SªÇƒ–ƒµ´ð‘ˆC.i®cµŒvOeÂ?º$ L+:<ʘoÅdOœ©ÃÌ­·“áeGðÌ3ž~ ¸Nw8•ÜHL¢Fó³6½u€ÈTl €,ÒABsã ì“sú­Ç|±=×Sª÷5ÍÎ:·´nˆ $j;žÑëGˆµ¬±R'Áæ™VdðÎ[Án —&çuן¼t+RÂçÒÄÒÇ»&º8ó°F› :lu(ìÝtœÎû*±ÁM”ÛÛÍmlí[„!­8ßN–¨TÖN+ŽÑvÈ{»³Ó¤Ùæ×<™épªÕa¥Ùë=Í€×dÜ.cŒ;ý¡=³LÑiêZxRkËãçAv‰ÂáR±¨Øå þÅY yµ‡ÇM'þ0AÐ['¸;èó?®†øïl4èÖ~ºƒMͪÑOž ¸¸àSéŠÌwm§Ú5Ø›‡Ìñ™²ís*ý%ôj3Y4Ã*’y¯ŽWUµ™‡±—ÒyììÂXÅŠÙ~ù&àì ÔeJŽ¥AÚ·S¤ l¸ÓÙÉ=ÔÎg.eµøT¾ç—/yîgø~~ „ñdy¢pÓ&O Å4+y¿¼> Mø-æü4·ÀþÊÆtö<-ÕnÍõ‘ˆé?"©â?öqÚ*Tk!¬a››þ\®œpÔƒW³'ÅñG™Ræ–cÚÂìµÜr MÝÿ$ÒrViü þ¨R7cÍ^ê0çK<š° ”ឆxþÝöøó®m]v¾°»›ü¼ém½ìíìÎ9°bÿò)ôGj¨C¢æ<¹nÝRìíÚ§M¸F4ÿóßö3ühhóAÅÀŒì²¸sTî]…†ç@pϪÛÕ3Çöï·À«›tôñà‹Ï¾þsf,š.3ðþ40¦`¶@)Ä\Þ"ƒ*œå„áñî66oÃÁ8N 8÷›ào@IÈ"èÏû}ÿ9ag$ñ÷ý ž)ÍÅŠ8ûÐé'8²kn#jtK`?š¦òNËÁ&tËŒÏÙM29ÈQPßñsV1ÜoïíF›Ïjù›ŸáþÜGU³—½‹9aóÒèvª–]J¢Ž á}‡ï¢Cð 6Ÿ(^Éù-±‡ôMð(†;¹ÂŠÙþ ·þK‰[ OŒ«>KiÿoâH£‚qŒ3ÃÞ†ƒÍ7´c~Ûü ÃÒÖh.ú ®×µ×°üWk;OúÊlcL˜±¾™¤Kbýtêé wp ;Ðn`ìÁ·-û9©|Ï VìøßþKCf-6F¦k™jÀi· T~^H°±®©T‹½1‚4á(¾F7E¸r¢Œ±˜°V{‡ÍWeÃTšÇF¢à¥,óuj¤ÊÍ`š¤Sat“mÌþˆ6aìú–VÇă‹/‚mZ´Øi¿²?µLÞØmþä÷»³ŒX©5„‚ÖœnÃòZ¶šMªÞÔ(¹ØdƒîN›Z\îÕ©Û&Õâ”Àíª 8\àp'ôøªUc±ñÊBgNÒƒ¶JäáÂ]àš9ã$œä©EÛ@·’Ùs9òLÄØ-㤾2%lSk-‡dpÿ F±ÕÎÀ)Ò LÜòXYElƒË$ðúmp{±:Ùž%‹g9j"~Q¦è$ݼNi¬, `0ðWp>HŠw ZÃqÃþKÅNfa~ à ʦy°Dñ×Oäýä½ßé6„cŠ ÃŸ5D²›¶Nó…–ðø-æüó~ y¿¼> x|ó~ y¿¼ß‚.ÞžA@…«~_¯ü˜0uF3•S/îwÚIÅûYvôÿîwSç(Ë‚p3¸¨ ±ã¢6œ8Ì ÁÃKÌÝÈš%cøKÓÝžÈì=¡°è˜_÷Ÿþ«þóÿÓµÞúv¯ûÏÿNÕÿyÿéÚJ¯üG7f5Þ?ý€¿ïþÀ_÷ÿ`.ÎçvÍs_U¬-Õ÷QWn§„!ËC)Ç’ßwÉoŸ’ßwÉo»ä·ÝòMŒšÍ3ù¿„ƒðÍÐhÉÖ•%^›—#Èû­Øú–.ÊkT¡My?ö–baÙ(¹ôÙGµRú#ˆ¿±þd¨a¦ë«}#ñ`Ú‰é¹ ¨oh,c{E:A¡­È´Oª°¨àòÖâÂ$ÅZý»½ÿ©gï÷M¸Ù5îk2šÜÁÌè-mÿdÙ©õ¸†„q».ìqà¡Â†yŸà”¢çÞB}H0õJ¶i¿TmÑcÄ}ÌÏæ °ÃQî³Z<@ýHø§ ” ¹i3þd©Wu =–¡¯Çµ|‰û©-Éi¹¡´é.DZLNQÉ5”E7â`¨ÝeLº '±áìsHná2H˜²p5# ܃Îê“2ç¿` ‹û'v­NØvV/<ü.œÂâœÚbÙÝ[‰°uÚE§}fï&÷‹s¿$67ÙÕºm£ª}Ý„€àÂqH‘‡Ù 8âh''òè‰Ç“°XâU'æãk]§fržHí8Ëvi¸ÜgÁ2¿iª)R{ƒCˆâS»gevº8L.Ì~Ûý«fI–ˆ ŒÌJœVÎ@? ›ò…VRb/”[Yº·# Å(mͰ™¶h6sècâ±nˆàÎkxñ;§†h–ŒF,9®ÎÜ0Ú´µ˜§w§ÉßHºi½á¶‚Cg O4ÚŽ&™,Ç„ƒòçä±CˆÂçFX”~d)‡DÆé‰å<ÐcÉDzbù]¬€Œ–6sU˜Ö)á»ý í¯Õðí/n×Ú7ø‚ª6µ‡µ˜Æ ’˜ä©Ò¬ÝðãŠ,#ªÄÊŽÌ a¾\ËM"gêÞ*æAdAdAdAdAS¶­â ÃÏîž©£ÙÍgEüô¼Á{eßäáÅo5z·ø%o)Üt@r}3ù‚Âá‘ÿ<Á:]α?ç‰T¨:¾.ËL‚Ö`Ú¶@™ý“Ú¸5¸r“7ç’¤é¦ÒÇ—Å*XÙ„(k©º–%µhâóLp«,kšè"önÕf^ ˜î) {_›ÍSpu=•þ®ŽlG>¥®ÿÂÕîñü_ 'ÕmF¹ò÷ £ˆ=P©UáÏÕ6œÄBÔëþ¨L28± æ¾ú¸ªX¡°.?ÿ*›uÒÖjÎçˆæª`íºÁQ¦Y6s‹¹õV«µšÌ8o»­V¼1ØÏ+ÙjÃÙö|½“œM¯lÓ»rj4¶1œçñ!ب ] Ìû¢ü~Þ 9gû’€sÚD4:½i»Mt7Ö3¸&A7ƒ› ¦Ð»b‡‘•¬}@jˆÂCl<–2ùq›sì˜u€á- –^Â!­»cÏ"ºy/—=®ÜÑÁͶD*ôÅb J˜šì;ü?7|Q¬5Pç5Ç)p<2 ŒufTéMšV¼LÞù" aí6[•ÿ/Ík~¨Ë‹þËjOæN©­hœ1Šž"Ø3k¡HÕÈTâ]¥æ¸ÖÖhÍdÇšÕѨúTê'/Ù#©U(Òc;><Ë›/ÂÜ/c„foI¤=®úª8&~²@uWeøÝ?%gã> ´‚¿ÿÄ,!1AQaq 0‘¡ð@P`±ÁÑñápÿÚ?!šVáèCI›%#%F÷j×¥¤w¤V˜ q·Íüy%fݲªQº¯Šé)èù³ÄD9á £v‡¨«ñ3ᩱi¡*Êk,b±e§™¢(âz¿0Å ¹)Ûe7AO©Ûä,yÔëg?I2ÚôŽ<"½ª+E̵›‡ÒÎÔ‘ì:œéÜ>ƒíQH0“ÑDÙìI>|öl×…àY£@¼]«Í­ÕH…ÜÓ@F5lTÄ´#^Äik,UÑ‘DX]dµ nG˯ òºT¦Å‚•áHho˰ JÁSB#.ó‹dÓ9¶Æ¤¢¤ã°1ð ¦>b¦Asà)Šn¹ œßécÍpÆK,/4š•ã q*rô GÀK›S±ˆÏMwÛçÝ#¿JIÒ*JÈ!rŠ’!¦O º/7Ëú£i ·æ¨ú ßé1b_mQòao–3€Îê¬C^~ÕÄù&°ð4Öµ.8E9…–¯c -3LRç~Tn…×É=}”HK\ú[ÇHΧZ’ÛZ×&˜ 6ÊÙ"§<¤2Hp‹æ£x[ª&.ÊDã Ré0¤b/4”…˜AxÒòxR# ¾u°€ÌéKTH/CŒÍNjç3C+G¼$ÛŸùB_R¶*XäÊý)>qŒƒé9¦£±DTwS5? °(KZ˜ ÛØ ]a@ ¤’÷  ×#zYaiEÒz¸LYøå=ö$ß&ÑNVÐb·±g±4YÒ°·Òè´±N$ø¯æ+ùŠþb¿˜¯æ+ùŠ*ïÁBÎäŠ2@n3óc$&„Zô.ª»U`¢¼ÊèhRã‰öš!BŒ•õ4ФæÞÔËô¬”OÁ±ÈA¯œŽu’B<û|-éÈúTÙ•^ÄX êŽ@ë@’Ϊ‹¬³ÍWÌ,=òȰk\À-),Dí b¥‚ËåB¶ÔêýG?2gv&‘Œê)«Ò褘CµÎ Ç›ubŽûê5ŠdÀ»’³„mô¼_t)E>~øà1ˆÊÞY¹ÂmmZè8R…I1?Éß Æ“gΓnÔóÀZž›û"€õsˆªFbàÁëQby»ÀGõŽÃ 3®Xcúe 6=hk—r†ƒ„ü½ÐDÓ± £JA#«Ÿh¶– qS¾Ã#º½¬fÖÿIÁú»Ý&†òx4åSQÿž) •Äïa"øicG‚ HmGÕ¯?ƒÄÏ}&[…<¯ÏŸvtIDð'×± /•oz_¯Ó8½ÖvØícï_±?, ¸š’vdNQ3s“*\[é`nÝŒ Ê,Ô"+¨˜®J–7uh£fš±uµ#…&™ʤKa©‡–½°2 çWui¡C^€á¿àÒ¦e‹‰ZÜìÂúj)/ég‚âë5?{x ƒXOO€†#½'¹ßƒvàj,ÂÉ¿wç®ÈŠw LߣXó; 'i£¸¼kŸòW?ä®wÉ_ÊWò•ý†M¾S>‡Òñ=TM€Å¨#·Ì^”Jxî&hqN1”Ȥñ7¨Áäv,% °’X¡’tìr .9$ò‚¹M„=*YFgÏ¿BT®Ÿ!”³LÜä=›|²/A1 é%‘“sé|^ë;K lö=Û~â~OŠqŠWzпÒù2Ι£d‹±§/Á)¿l@mGîEߺ†Jšn Šm˜7¡Œœ¸„¬²<éøAÄX<¨Øm¥ÇN®âC´QÑÓmðr¹$‰·s*p¡“´‰–ßéoèT¤?GuîÛü¾Â”rk U¯rO¥ÖŒ? ÙíÅlf°Mɱj:'U¹m ’_ˆùÙ yÞil”{&4nT ¹;ÈÐvÈ(ç­§KÆ(tˆ˜ w# ízä÷/v&Žý¨H½Çéh»…1{¬áŽãÝ·â+{—¼ÖÖùbt¬½DIªÎÉ!qGúä®ÿ)è_KÄw'~ù€itZõîZâ33ʲL{>nëÑþµ‚^) [&uTÀ“°@uëRÌ4ÚpŸƒSg™ú¨¡£Îjae` æ~z L»ü~5¬ »Ó)ÐpËÂX€¤Ì1;s ”H™lMAé2æÚée@ ç­wQ¨¸5‚/&Yã/æÚj NmŠYTĆVŠK0äO*ˆøô¬.&S•kÙ ·JœTƒ-L{nnëÑwlÞë»6é†djnRžïy­„ßN@J‘Ôï” s¥-!!KpIzÔ¨ÅÅMˆÐ"M ÖE‰«5Êë¥'ÄÆ=©mX§«¼•vÍ4±E¬Ýîo¨dÀKn Œ š@≕¬é’ö2”•ß‹ ¥ yÞ£XPùq¬Mê àØäϲ$m¥I˜xPÆB>‹H ¥©IÐ&A&€–#HÖ†ñ¨Þ¹T‰ž½×S&b{h$œÎ\ŠvR—SU€ÀTá½â ­Kˆr<7æÔ²ó+… ˜>‚HÅn.jÏÅ^LRªN—»À š¿–Ú‚[(Õ¶Æ šVdhòÐ)a šÌà'q¦VÛ’§D¯HÍÉa('Ý¥„œ¡¬£ë TÜ#a)dŒ Mdžczyk”ï@ ¡¼ÏÇÌ»‡¶<ÇÒ×-£~Ï›¹3A%ŒÎ°âz5ïêÝ®RêH/+€0¹¢šnÌ 'y›ˆ´µ’ítÊbˆÅŸ’a-˜34!»’8°V;¾ Ÿ¯ÜI•Yu¼¨4T&l7fù ê$}íñF…¬<3{®¬„xöc “4‰XH²œ'r,j(ÜžÚVh6ëÐ@—-Oyu XÍ££+‡L¾~‡y˜òø‡[ËÝCfóWifR3ðC¥-XVõЄÌ_~˜®ê¥ço2Ý´¶8Ñ9ºšœ5çÞòJ—ÅY¥ îŽ ¼C ìiQPyBÍÅ4y B GÇÄYhü;Ìíuô§#ÜåZ(·?Õ §#ÜåX—êšå{«•ìr®W±Ê¹ç*ä{œ«•ìr®W±Ê¹^Ç*bqoaD¥‚gøW+Øå\¯c•r½ŽUÊö9W#Üå\s•r½ŽUÊö9W+Øå\¯c•)Ÿ²]¶ yl(_º¶–Èì,¡šžå!jMž Š›H}Ì9>"bYQLÜ—« }J›+™Y;K"Ø5¦`Àî$C¥eC©ÁýؠʳW²ê+"tRñ» –±Ùå›Ô0FëÍ@Eì ªÂ‡˜¦BF† ̬ÙU×r+6Ç4%kZæM«EH]8£¥¬i½0ô«¤g7dòP"ùƒÝ¨%±»Y> s©;¦ŽÁz˜ ¾˜ö|ÝÈ‘w#¦õý/Ú¿§ûWô¿j¸›xì$‘Á$Fõkw:@o¢7yÔo¼ýª3ì?µ ÞÚ×ô¿jþŸíEäpXŠƒ  ÍûR‹šb3ë_ÑþëúÝCû©ÛÅ×û¯Ì¥ ¼­ØÙéJW#ÉhwIºýÀ Æ¿Qó+SÁH‰Ú&‘é ñ“tò;À+±VbWòšŸÂ-Ad:V*¶’’Lš< ÛJ8Lj[\ãçΑ[°,ƒsš9f;››•%$­œ{éSÅ–Û2,øê*]Õ %umyÞ–Sa•¨¼öh@ò«J©áðna Z×"¬[ ®"*¡ÍÖ€LÚsHLß…–_Pn&Å«|¹¨t¦%ND}1ìù»œžë»ŹÛÕ¡ø  †¨ÁåP¾ À4{0˜¨ pà·jB-™}è£/ªº½ÌYaõ–˜/c´^•k4´(€'Ì÷×”ÆôŠG°fô.ÌÇy§‹!ÚÔp'É£D ˜‡ß7àm"&í£(2Eé©wxjaé¯Ö^Ï›¹Íî»±í[«'JT™Ò¬ëzloC%#$1Ä–ËÔZ-Ç^ÂÅ*ÖÁvœáÍ.ó½LE×+-: :,QˆEA·q.ÀŸQïwZWˆ¾ú‚ã’/HËQ$ã±Ù^‚Hr_à`=¬‹»ÂÑð‡.®GK7£¥òLÕ†¤Âí882QÙ®]û‹¦nQ± yšo Ë?ŒÓ‡$ íõ—³æîr{®ì{Vçi±Êü=«v– Ú†q~%†*Їr/æWð¿JþéWk€ã›—*Ù-ãô©½ïµ?çþ”¹•?JC 9öŠþéWâ.ÏUªf9eÕ¬€z•!°•üT-.X\GK«ô푵Iv€™Ø¾ä5R.õ¶Š2 «çŸq$¡Ñ²Ì'‰^ÚüW³¿¬A ‹t¨E6ë;p–ÓífÛ4©¼Î]ÂIÄÉ‚¢06KÉþ}¸$Ô^ nãíØõ·gGH_nÊÁS0]càNÊHúOžª>HH¬•q|²ìou"„‡g°’µvÑñÇ@`ö…i¯"‚hR43=TŽ;5š)ò·Ô•uÎõ%I¬³W8§;âW5Ojt>´@µ«A†ÖDp°NtL^Õ"TÎu!Šdá=åì4f:Ï?ž¡ùòã+„EÊþ„‘›»@Ø/P ʺfàò¦0‘)™9¤dx()ܰÁ¯kýV÷„•ì¿jÅ$Ì]Ô.áÃï5ì=ì¤O¤WÈ2K¿ÉÍŽŠš)Þe¢73b€ÖY^òo=jÆ/3±ö /‡Pý@"ç¸ì‰Â°úPÉ%Î.”0,ö¢üRsÄI d—òQ)b+¼Qx4 m+§JçÛ¦/áW†pÕ/­?rÈÅ '^… ºrœ}ê Ém¥ÁH–újiX‹c¯a#V©iRVÚß…gJ>¼ Er ”6% ô>=m½]É“áH‹%1 ž(mËÍ(W×2آĒ~™B丒@¼ìÏ8¢HÛ° ÈBr¬3HkòP@×ÈÁÅ%p~ivAajŠ›f³c•Ieì÷ßËP:«šXÂ.‰òUÍÉÌýÿtråÜÏëH¯ð©zNF¤/%“qéK˜t4å0–¶€Ä„5q)ȽO(º.ñkrBj*l¿>|•–ÝiHç}¿^oÃ¥åJ>™ móÉ%š1Mî"ùø.¬´"oâ¼¾OÓ3zž‚f:ߊO Ì&ý=#¶ÊänÑFìåׂ\æÑÃáÚÉ­JsÙ¸±ZÚˆ ån½‡âÚB†Rÿ·¥!A,‰›ÑÆm·ž«<-î Ê3ÆR!”½BÇ:MÐ1ö§èëê|BZYô I”!uÒ† 3¨ìEQ÷~}&¢dýÊÍøA]ü.X_¯n 4i,ÏÓ„—.—ëoZµÛi'”`Ó¥yC‘À$tì(°U…sÃC°-Èʶ úq ûpg*šxrTXžè{OÁä—…rqœ¥*ijVÒ›ÆÿÏ?ŠCK>|L˜P´5JD²³Ð¯<&ü…»Þ¦73JÀW`¨?mICNÖ>‡¶ã³Fv­ŽÚ!Ù ¨H°ÏÁÊU ‡—Åë%1ÁÀŠ­¯åéöà©jÔQ{iø”4³ç}i±MgŒ =ïí¯Q²ï  ‹)a­Nê¡Wh|ÈgŠ(ÂQæÃKŠUmGÒƾHvéDØ%—@lÚ0ý•e‡yNWÒ#ÙF)+´¡N¼ JaƆl¤¹Ò´éhʵ‚––nÊå%“€>ïÂ#)r'zP;ÐmW ¬TyíM–ër/_B¢DÔizÏ>µ)Y>% 1úç&ÿÐeq*TÑŽ è|®%j„,C˰iûÔÐF,QžÆ9ÔqÅNïê‚2} I:^´éÂ’7”X-¡-Éέ%\–¦!lKM*f­$—zéù¢’Ë”ÃÔ¨åä gÖ¡‘޾n5N‡¦¼Aˆ4« ËçàèuýdYÊÓL ˜[¿HÈëQ³Ê¿ i* )yP‘Þ’W#>ÅdoûU¼ŽV¤tZì¡)5Çó É?ƒNÏžþÔT!ô$|Bq?!ðb¥„‰©K(ڢܻÂS9–¡1µ¹(d™˜>jŠ.©o0ŒÔf0“ôzJÕû~kl‚Íÿjwps”¬NÍêSJßÁçC5C•æn†¦JN¦ž‡qº ÐÍ'¦ŒÛ‰°Êž»Ÿw…‹f²Jw!toGT•……ÏÃL¢ûÃñZÀQ+›|îËR&8©ÞMê&¼Ò÷mñ‰4ì¬|åFÕ$`#èY Èâ€-Óg<©øŸ‚!«¸A ®jæ@­Q+tìÝQt(Õ˜LmJ¥rлaÕ‰²†§ì;4& ÷+ªÝjj(f>‚Žç*°"¬ Ê=5¤’•vðrûðÒtÜh‹R´2Á͈~Çfüh¹zmEÏúV$9"€Êr’lnOèå!]PJ)B91§ýR'3MYæný„@•ƒ ’\­ Q™©ÒSFj6eYß‚¸o Í  hF´ê18yQÈidÙøÔšvRGÍæ÷?CÀ·ØÉoƒµ ñ¥3D§nJŽ:Ԧ팔i¥HøHË%¸«s¡¯FJVI“8ŠrÒsuÉM×j ufåŸ j@dg!BY4tRúü—®`‹ÐB& U(‘Õd¤´L S•K!8'>G.eXí¼‡ù*Ýš}š†”[¯Bt½âŸZ}Ž´«ãbGö Œ_X¦òƒ³Ê‰u#jÒ–ZqW̉ØéÁ† y^®æÞ«|$WŽNuw5`M¿>t|ì'ÒµRn_ÝaÏRJ䓼ðTÛ+„)õʯ`¤P;Z›ÇöÄM ØÚ k•‰íÖÔEK’)=ùQÊtæo@äTrÖ‚uNÈï}‹f­7bqñ±4줚0}d/Ëèe#%“‡^’?FÔÑ –50ó¦É¸±Q{ÎÔXb“*.œsž"ÒäÉ£ßeNŠÄ`DÍI½@´ŸŸ”FcPÑ{þÕt“>Ô²"Éñ¬†aá6)Zô–lÐ*¸‘L¡"áyK(J‡&´xÓÑ$T}ˆÑy.Kˆ„õ3kÍeÙ>M®¾Rmc#J»¼Ei¨ØžJ‡É»-+Bé[$+¨™†¡BR 6éYú+ÐþßÚŸ(µAH†KËå¡{µX9]Nfí©(¾Å#š³"ÆZ’äKÊVYŒÖ±U¤Ø#8±N“J&…´ JÚ­~Ž À‡_±N‘lDGFPØ[ûIÊ‹z“_;)#æSôžܾ‰6#Erqða*`“Ÿ?ƒ‰‚Zêƒsî¬Ô¨ËNù{ ŽUãÉ•fxÞîôZƒR¡‰&s­aÒ]O:Ž™K¯Ð7µÉWµ jºÑgèûãzYi œÕ$¤ÉjŸ¶lÍ=•'´¯3w©Z¥ JlÐUí"OY¨­FÜu£Ô𠙬ÁޝçÞØkÑþÝéž½0E³J“¥iÂBQ ƒ íX!6ê\È),”†Ù¢c`„¢sÿ¨±“o)Þ!FÄDƒ»R9}3”ÜæAŸZ'ï–Á «{Q£%ª€OÝ"à5ªY%¨¥¿>tžÆ<³åYW2ÉØd­mŒ1NáëH0á§,^'ŸÈZ(ù~€eÑ@@`ú7So°9PÀM~3¦ÌjwÁa²“j&ã©NíýŸ„«%Lsi¯Z@A±Ør&‹Ÿ*%ØEe}J6CmÑ<½ïDX ¦dÚ¯E€ëÌÆ °„zšJ, GÞµQl³yÞx,Š Ü}Í,ê›áåj^8zÖ„L…Ô|¨‚ w—]Þ wDhT™H\KÏ€ÈL•vøiNHEƒ”šÓv8È–Võ$ Âp/…F– lVÀ7%š°°1òQ ,“‚Ÿ8 dÖU}Šs»4°M6*ó ¬<ÌÖ‚rNb¶7«Ÿa;¯­ZM_3‰YäW&cLýê<±l:§ªI` ,¤u) ƒz‡€‹±ÐÆ/VU" »<æ+š ÚýĽÄ~µsæ4€D‘Ò§­R²Wa-HõQøÍIéûÔy;pFôŸÅ;‘ &TÌF4ÜØr7M1±¢`Nu ?˜’£ ùÔœîFñgEK€›Eê.5|nu¨´­¹þ”¨¡­Óû9T%sk¤RÃ:Ž+ÔrZ?"KJ>Ub$eôbMš&ã ðâ¼Á‡?‘½ê)L4s0ë±³“-Ô@Øl1ð­tëZ]®´Ò„ïKJxSÍ(›Š_‡@•ƒvA¹j€Ì•ãˆ2á1Béuî u¶M)Oµ‹¹µHqÛkʈq“+­ .òçL%Gž9å@lIÿ bƆ´‰+…Êjâ,¬ÄÊz,éH‘,Ž.ÇŽ¢>–Fâb,1}ªà— (…Õ.&Y©ôçD’NiÐÆ¬8¼É.»›îDð5 iy÷Z¿ªüÐ}Í?>å^—aR+£+=i…­Ž×&³®Ò%K0,•ÐDØý©ô§uþ¨F€—þ• õ§?¼KŽŠ$‡8“Bwj_` eƒ«-<¤ðá@™·$ÁV->#IÄáwÚ1çsΚ¿€§ìõ @õy©TE(¼ë}©ÖÀ—¿Zm¢9J|K~´E6¼J èÇÈ’ÒÏ”Eä,ç?FÚíÒâ7³u\¥"žøFÛ¨3ÉÂO:së´®ão„¨]J*¥g­Z‰b•s~Á<)gf‹ü"¬êj0§4É¡£!Ö’2ìàöQ ‘i’²2Z Ò)rÊMª;§ |¨Ä±š´@½2Ô $û9p„d ØÕ2“öŠ+‘‘ʬ áX©}O9xÅÊ `ÂqȨ]‚† ›²|jÐ' DÌ.’!¶åéÌAØ`ŒÊyм2í ïd'4Ó&ËÚË”‰*ò©Á%_ Ö ""Ô—¿‡Åõƒnme˜nê´Ú2ç)¶¾±ÝDBæJÁ ¹[(£Š&ž´œœƒñL#—@òޡ샩ú®³)ös£Q,Û?j.jP \ã¡Z€Á©9rCD^³ò44³ä×rj5†·Ñ€·PqÉÞè[_M~(àÁ*uÚ¤@Úü!MJ²oR§‰ð2 aCð—ú°þ±DL”Ù< ª9ßàशKŽ•{(LÁÇgÆý( H­K,ú÷‡>¡??º##‡‚s(|&"lÓÆ ìåAaÓ5rQ¼ÍõÞ’y%v¢\ã ïO7 ÂÀwxP ´H/„a3]ô6#œš H`Tc2rY&ÐW+ÛÉ—4Ë|UŸÌó5JÏfPJ’I)ÚYXŠ0 eø¨Ø0†˜ŠçÜKî© )…½íD0:Á¢pÅ—ïFÖWÊîUt¡„cs„˜/Çnu yy»(JA/¥ ha†Y÷\”+¾ø¨$ZhÂ,ç”æœvÀWŠ¿32 'ÉÕ˜›P†×é(ClÖoÕ*EÅ]ÑZ­£b« ¥Ò£`¾vèïÆhi4J%,$:éQL¤˜‰ó}>F†–|•àCèÏPv'ã‘AÄ|CÚCJ¬¬´¬¾n §àŒ’ë Ws xó) 0K­6Ÿf 6§Ò {é’–X6(C‹ÁÄ¢Ja«a€#.ñNKVEù¼çö2º‰‘¥†Rc«»¥<£n§ÜU‰2UZ‘û«bø3xß‹‡H‘²æ‚H!#™¨™3°KÓh–„r ^tL͹6ŠÀ,Ç??:‰#Q4}0hö˜±ÓÉBÆšÃ2Á¾Ÿ¤ÝKD/&¯w>¡i¤)=>åÚ`éØP2bpqŒÔ`C&-©bÖÒÄt¤–K±ÜFÙ¬fw~Δ|8ôiÈÓS&é¥dr“Ö)žÀšb<¨ÊM±ùÑ<”ÄcÇûDš%qG÷ƒwÄcW%ü|êÍ…7å4À>âY:Â-hdÝ&•#?w‘NרˆûÑwWCmÚ 9pñõµ^ŒX*Öe"N1šu {/3~ñ`š,7r%k¶ô nl¼ªYžrCE”›$‘m|k‘@gÁ+&äiŠ<À§¤ÌD2 oEqÚŽPéÌ¿š–QCHy<±:Ífï‰NÉjŠüËP‹Ú íz9Y@HíY~¡2š ˜8e`f¥»¹¤8™JIŸ² #ñ{Ó¨|)è ¡Rî·Äû͈ ôÔÒ´Ï´ÐÛ@˜|&Ÿ!뤼~ŒHCŠŒ{(–gý>p‚Ey2M¾ž˜ðø”ŒBœ¤«¯ygDÒ¸_Ò~NÁÙ dƒV…]&¹ nÎ{ð9ŒÁ%Aö5½Jå,^/Ñ ©'f&F ÄL$ÇGûQXsl§ûΈn:Þ<”Ø€`¶Åh øÍMG¢†DÅÙ«FÍàxT)pÀÈñ­pTÏ*ئ¬“,ÉV´y`c€*%Öð˜¨/"L>z¡—v›¯¥B%õyziÞ Êu1m©p«w-Rqò?wÒ˜XÞô„f˜¡/7'ÎzZܨz C*A~RÒ„qY’ÔxHªS¸¿LnYœÿtk3¢~i0ñŸŠ‹0»†oz Œ»»•2JeŸÕ Zn§â®=¥”UÇmÿ+4„’é”¶j+¹Å.©ÃJǤ•â BhŽÈiÙQé?£rR°XÔìeXš›—×QðyÄpEåj`¥%øp¬»ùH},boß! — ~:¶š » 9ŸŒ%UØ&ö¿‰­jǫÀÜ8c+-::Q[C„ÊÛŠB*#ÃjDRy×°ýªTR›Ô[¤Ó¶šœÊ"ή­Jm å'tœ…PÒÁ^qÒ•Ú!°Û­9Ø[X ÎI0‘ùìÚÚl»{jIJÓrEâ(%´® ›šš10ÒËAA°!``—T+²ÈÔf€« Q¬²/£ ڙ̘YåË'bä kTå6Êër7‚` hSIÀ‰ÑëEÍÊÙiÔ­è7iåïz¿Œ3‘DÓ²’;òá&”Ð(1k÷’~kèñ‡»ÎŒRGŒ˜®KΗ¢5N|Dêb_Wzàe©)u5?Âo7ãj@÷Û'ce$‘²|B2ô|èÀÖ“÷ƽàO[ÔúùĹ£cƒØJL^­”``;6IÁ{oI ½ÊêW† ]ÝÌb¢º¨·>Õ»­²X¤âÀéAÆó_ŽåRÞˆêU˜l‚~Æ«7È4°´Y;H$g–£R‚H\åcA­&,%\fëq=(f®ä D±½&“>•Ù~E‹0bŒC'Üò«½ÍÔÇŒ­KYCü‘ÙI蔌Sd PR¦\a`»C¹=Z/£ý»Ew€Ã:Ö;zÒÐùë¨Ì*À|µ2/›ñpI’‹oOvŒëChÈxZƒ„)òÕÎf ÿVŽù·ðÍ2`oñLRÛv0ÑQ:H‡ùC®úŸªÈƒ­ÔĈ&>Y¦@—ÚmDdà‹‘<¢¡+LG¦•˜¦ëU1PzÚšd¨FKáòÖ‹Û£'–=jE0heŽxåD&ÒyÔy,ZŽT”!¥rçv›B¶Þº$„kÍ4Úº¿ÚU"ÑyT *\sþÕÛ!ââØ©êP{r¡1eö¥R&÷0óø@ĉi¯]âá¥Ø®Üü™ݸJ9n¹ü™ÙJ;ó g«¹ë~…·Ç*•AÌ4Š„‡Ú·z!špæãñÆ ™Ü¬«Ý-–­s°l|VJ諚ngÉGQôT‰I«Á“Kêwä°yoN ˆÅþ,(.ªˆ@Ó¢ Æî¯ É›‰Ÿ Yo1R|¥ãBQGã¿(Lm FTÔB ḾjW¥»ÍÖ­eØJnØMÆ:QsJ—9“¥èI ‚M¿ Gí ƒÒí›< }Þ®ÄÂ! ˆw3½aä ¹ys«,s|ÑV_«Ú˜L7jåpźv(å 0çð¨I)“ºž $R™1eÐzkSCŽcå iGušœ<†ãSÆ! LíÜ·Q-ùPAú<n9¦pîEÍ,´uqvG»MæÖ¿\±R§Ç‘M‚c=ˈ·Ä‡ÇÀ"µ›™ñl»†/š—’&-V(¶jÆÜé4ˆFá7)ÛÁEá¨Î•kÜÂçt¤Zµ y`Ú5Õ¨1g$ȆæÕ$ÝŠ Êò[Í*DàPÔ¢b—NIœ‚zc(‰¢Æ„˜7% oHKG Fž•6É^zDªû„ì— P[Þ2S!â# ¬EÝ7ÖšlXÎh¨Ä’*A Wd&]ä‘D±wM”!Ñ8m{ž¥bÑ#Äiµ&d½76¥µ‰–¬EÄÆßwѼÓ×Ò˜Î_ßwåIiGp¸1HÖK«YÏp¡Ñ£†\['ÒWÜFò&´¦–=ÌŹLôEïšœ»ò+ÄênwGâWñIyÑ9k’¸Yî˜ õ(MŒ¶ ¶)¬¼êgãß2¬œ©ƒ”IÚäÁšA1-¹\ð{DC½\–‘1D‹f§¡ì"•ÀšY;ª@Õ¢7 ùK€F¬À&è–—¤7äHof’ ÁãGtïV,ЀKw‚nmÕM” Z!¡amWp‚o ¢òÒ°Ú‰“稗¶9Fiåë¥Eäb ÅDP )Jä´¾Üf*"W˜}èa¨&¾£)ÌïÙK ¾¥?¢IÖ§[Wê 0ÑÜM~f“NÎéDáãQ<ç:óë\À YúH¡Ê‘ð`%¬UÜó1WÌ:±Dþ8®wÍP’›ëÂ\™|¦6Á·k«áôø)uÜ”ºb}hh5¬ÊÅnʘ#ʰ Ohiê-YAYZ÷I!-'¸ùpeÉEèÎ(Ú3a>**Å]a~Õ–û» p´ #yµßZˆrϯڱÎ`:ÓlìŵûRµß4<ªR";¶*Ç߬J* ŒáÝo£yQ¢D®sëNpŽ0›ËRu‚€/ëS@ 6¼¿L2¬¦ß˜û´Œ¿F ”°kËbzE]ÕÍÊqáKrž“=*Æ—–‰Î¬è‡êGUX¬6sqódšvV;•1`nP@`I£y38iöN±z+&§š~ZÝ(Ú…rË#Ú!¿f6û‰kÒçÁFÒ}}²€ Èæ¹Ž4­¤»Ù„3XBE´H)׉0’ÀPÑèÊý3ØÀ'˜_ä3(e§&Ÿ¬¾±¶)öÛöO û Ø4bå+ þUµCqӻƳÁÂA«QbD×ΙP(…Ÿš0~1èðX%«&çõ}íJàÃ9÷µ2tŠY̨ ÖHÒ¤÷ÕÉÊÊ,2ãÁ·ŸâáZR3]*ÒëQ‚‘¨¯…êÖ‰Lfx³Ve.`íÒ…{9~é*3°M¨Ê6Y vO[{§Þ§0¼SðiTΜùyV#`¸¤ Õù" $48 h\À+<œÝ䦸À ¶žú¹š›ÌÌÑa±·øùã:QØH‘pÔŸ¦‰ùYŠœaÙ©o#t%$0Ú;I„J>Tnú£\^†A0ÔÛç„$•Ð kÂiÁäD¤Q$ 0hpHBÕª¢9%š(2"EO‘Ù˜œí‹Óç³BèÝ 6³¥/J(KìÌÊÓ$ælNu$ëdÔ&·gŸ1¤¾båãzž¥DÞUz¢*)ÎQð© —‰2àdœUâO,MðôôQ…fí  ½nZ•FqFéÃëmbƒ-ØÂÄŒ•HºjôvGúPµœ°",àt‹f¢Â±’Ã-pKJlŒ iÂ!÷%…¨w^•0™<©Å¼î¾ÿz<$‰5çéYÙãöùê^T£žn  ÖúX°a5•b~W™RqÈÙüC³4–ÑL:w &Ã/µ r«×>Ô„ ú5¸S,‚:¡}i[fH„Œt¶:Qóš:äYëBâÈ‚a!’lÌÔ°iÔouF:Z“Ãè %#OJbQ•“uH˜&*À«i|b†&.Mßj“G¯ø¨ÀØÚ„8y€g£Z×ázSêL ƒÑDbL&s$àͪHJd HyJlæcXpZñü¤‘kcyLHd9SïÞŒI¾}-Hƒ\’fâܽã6Ú”0퉩{r‚ÞÁ-.üإŽ&AQzä¾Ôs$>ô8×ú¨£ÞÑ„©ó”´³‚‘5)£¦pÐ!&§À!l]¡O\QN—ŽÒñà+ôžÓ´³àüþ ÍïO–g"EÞ¹“¬ÊŸÄ©IDëc†¦Êo¸–< ʽMß2Hî^…,«“‰7ïg¦18LzpsiXšN>Õ’vy'–j '6£´“Ä£¨d[DF*ãO8ê}ߌ‘me ¬³#;-†:¹©’é[Ýz˜½çÚ¦’ÑFØ¡{-´hè7UóÎ+f¤"b˜ CA¨ÒçJd$NtWs·Oe/Àp»÷<¨#ÂFkn•t¢¹*êD,GWZ‰†­îš‘F°óuò¨°ç6Ÿ}¨–20ŽOœÊ Œ‡HI¦hF¢ì[–»±WTvÑKC´p7«šOÁ×¼“Ί‰~)¡GPöWµ¯–MŸžª°S=þ–Ë6>²MÍ·¨„-sî¨ó†ÌTZ{*DzñÕiàT6èkóù 5?< ˜·&ö÷–’† ±#i(Û±ˆ×]+Ÿ Ðsò¥F…à¿Ú0QÒŒÀ·zQM2é„þÐ@hv<¾Çeq³Pbª'g³ (Nõ’É &`çÉŠ³â+°óe¤˜…VŠ/9¦´ƒl­ÃQTù8ó`^ãDFb‚¤a›úá)&jìÒ ç¥lª"}g„`“°ƒ©Ñ5£dÄoóx J3€ÁP“ÀéÞÛ¯ÊÕ˜¹›¬´ô)ØdäH¸1IQ¸~„“ÒJB!‘“ 닉¥ê_eÒC‚Æ9Ñß<¬Ib[¦ŒL¤CÉ•½ñFŠ!Ê!€FÚk©˜r<0Y0¬éAC„¿Þ˜n¡·*8Å])É„´Z†i1 A‚K¦S1=\YaA8\–öc Û4ÝßJAs¹^œ^ÒA$A¡1R#b"ùâÍÈÇ*ŸhTÁšæÖ¿âôLÛ„8ÄG'×±=5Ìuҕȉڞ•Õ,ÀLJÒÉ?6y› 3yèÀß%0•†¤ÛÂÄp%0ºÅ,Æ3´ ÕŠ³‚Ñщ£fú!Å—-Ô)NX¾5,þ8°—Ç:;N>DØôš’».cçhi¯’TÖfM´ø!P–w©8z>Ë)“ðØ¥¢jµæßJÔ›E[œS?A¡$&´câ‡xd/ÄY ¦(dž;µg»zÚ®üÈ¡ªTà›Æ},ˆs²Å\*( ­]É­°è^,”L-à›AH©K3ŒIq„DFòÐ#Á@Ý ÊØrŠ[˜ ´17Ò”F4¤AK)ää<ÿHqRÝ©n×9YÓûQÛY«¶E´¯ÁF‰4ÃCÐ瘫TñÅÏTR1*J´â 6FˆL_ qåW_L(»A2ÚOfB#”(ÊiS2šžËóþ…™¬#íìs—²êð)»e<,m¬}þzA§gÃNÿnÖÖ3Τ×W«æ·<–´Ueeúi"‰ä©žLÃÖ­7ÏG…ÚÙø¸àB^NÂÀÏ6&ËËoš¨a£ÂCvTåGDMÛH"Y·Tƒs°"CVlÚ£¦s‚¸V#Ì€jGöŒPèô«M ì·VƒÜ…mˆü_‚VZšR`|Ê”,:Üþjþ{RmÖ°äIŽ}{â§#x $•q; m7_–;0äŒt”þ8â°. ´¬rOÏÆº*%ê6ób¡mÚZǃ-4ì¬Í§¡cmh~|“NÊÇÂìõÏšì¯E#$®_¡z•x£÷LõpŠAüD`ŒQHoˆ¡VÅHäwdá=0¨[mÍN6!Ë»ŠËWA&sذ™bÅÚ%Óoñ¢…gKF #ð À;) ¥ñsåWÆ›S5Ú Œ¾ôä]¶t½".:Pãæ¢ìØŠ¹ór%Hưõâ>)Á«Ð©FÀ$è'ȤéI1 œ›tµƒÖ™­ç¦Ð굘èßï'‡bû´hs¬;ÙòS·øœÉØŒŠI:&óʆI.TIÑ×J#€+Í£‰µ”¡ÒUåÐbu©ļ&·©N5‘„Þ6´Ôn[)â3Q$ Û´Ä®üH TÛmk7LÓÍÒ„\~~“NÊHø9àkðù”.ï52|Î|)™u~N‘òR0ò¢ÜÍ"yfU tᡌœ¸-L œ³½Ëä”0¶û^€’GR¢InâÈÈÕ$­E¾:ß`JG‘—ꉵ“+'~h€ej÷ÔZ¿ª5ˆ-!Û¥tp¢¤#4Pë¶9MéV"Y>±Ç u$'ʲ¯ÎoQ)ß)é½ 8Ônš7¿’Üo9ÍsK®(EÄ3ÁÚJ±±g¯ßŠ’§²Ñ²ì6¿^5§·°ìI‘$j>Ò(ô7*„«òÚ¯ÛÖαØÓFæJ^Õ‰Yæv´4ÄÓ¥L¶wþ¾1•£&§É3NÊHï’ˆ·¡/ÒôùÃw¿³Ðú*ÆóTPBŽQÏZE%B¶ÉØ&àa "fôlr_ÒŽaC›SåÓМšÁSΧ*=±r>?ÏO#À,C^Téæùy4-%w¼ß,oþz™W¢lRPèê¦+5a ÜÖÍrUÃPŒ±dÙ:6%ÉÑŒy”b2—ÖsR ­×eYI%˽ª"¸78GÆæ†´ÌËȤ܀„<_ó‹,³VÑBQ%ž1†Ã‘Ö—ô¤ jfüÁAd(=Ùv5ìú¥znØDõ“Ji`²’k qÄ{40.éKé70bš)+'ÛâÜ¡„âœÉ””cäîÊHîÑ2Gaˆ Š|ÀE…¢7S+ôc|c¸@Ç6vY¢ÒÊ‹M4µ"ÒÚ@ NjQ,™ìG<®?"¹Jxƒ‰jW+•)J˽=ìëÞOÂ] ÚÔ‘»V›Æ¦ÀÇ‹r›rÎh‹ËÍ˪ÿ»w ‰t Bim*äè¼"@11Wvl ð×/…%:ïRiN c}hA Beá6Œâ¤‚C% ÆÆ¶Ú2·*&…ÀÅ&/$ž÷9âÜ­ÃÆœÀuhÓ'nrêÐBnTózpbðHV­$ãlФ7k™AîŠË+Arpè ­K|Œc«DÑ9åêYF(‹s (-Æ9Ç•Krú ¢ŽàaÆôÎ`Ðãê+/È׎\éÑe~3µKËZʉ³Q#a¯f`£A¢A­1eÔáãÚ“ W_‘@ÎÙ>õ7hV=ÂòæŽ`ÿ*]¦ý؈¤&½X Të¥`BB 0rEi±è,J-M8à }qRè5çÊ¡f[ÈÏF$OóÖ“Z FÉ¥éB1ßÊ^1¥ÀûLýëHñ·Ð“€Ž5÷J53R„ø‚ÚéJÔ ûi ':Ó¼m4c…]H9‚ÍX(u9¦k™Îù¡(ÍÆ-z šm8ÏŒ²Œ–7î²\d¤u˜õ fWòu²{ý¸•ä6(A®“ OKÖ›¸Ø¬P«ðÒQæ—¯ÐIiGuyD>^KâmΑº—WèñÍRð0.´e ¸Ô‚"×3Ø›B!²¥`] Qrˆ;LChž‰¦_"8Žì«—9áFÊÑ4 /¨"]»ÈŽœeAœb -=TÈdÆabÖÃ7§±»¶iN(¢ˆÚÄÍÍ)¢;Y ¾©²Å^âázw‡Rõr–6£Ò)‰@ ¸59KÂB D‰V—ѯa‰#£E`!"ÚS-zv3Ù"Ô$"ÐH”›·ÐÌë>µ*}›e% Ô!’¤®¤Ì–ͪnÙLv’/4˜ê"ŒP$OÝèòg*Œ„ÿ•Å’€ek-¡Ýv™GZibßö{,dr}–”wsCÇåʬÔ ÇÒ6‚ò<Þõ®Taôá0G¾èö­'­(ŸÝ iq–gæЦÔ^§ 8ó 'í­“e‰ »Þ´™¾”Ý–Å=#|¬¥i¾úP1‡ÒFfòZÿº¾1üÉÄ«S—$Úè9s ‡Mn5SÐxŒTb!t@hg—õÕŠ!°êkþñì€øÒñCÙË¡†å Ô@:³ r°P¸&ÇÚ¥à–ž¨­M&=)eˆ[M ²¡ .{ñ@l”¥3`êR0›4-ŒTÙ›»š1'Ü=‹Á„4˜ËF&p½f§0$3҃Ü•ô¤âkÏÐ)igk]–4 Š».I ¤c¥K§C¦3Â|²{*#7R1çpQfÂ×í=¤O/˜ ]Þc4)'T]ª 5‰iÒ)‰]¾Õ 89¹¨D“_ðPRQtÝð¨,v95# ˆŠbÀAöOEG %ùŸ³Æü¥ K(Û󤧭` »%„q¬–¨X_:·1ÌP 9Q"Ÿ’%²rML®ef(Ö=‡C†×5)¸Är<” “ˆ‚usØÈ`4N;BKÿjIʲµCd©5ÃÄW0. H¨ "ߊ´}šÓкªDÉóô4³²:›Ôàë±ùP/¢j@ܺ}(ábAšdJ:²ÚšöÐÚ€’GhdĽ¾U+<áʤŠȲEe1H•¸Æœ ³]ÓróO”·}Šù¯h~*ϸò¯l~(—7³j háHædcÅZÞÀà:b‹•)„o¯:h YÈA'­{«Ÿ²¨Õ˾”#­jÞ"ú¬zUËkħñD¤Úº•ŽÄÊ©‚5¯ôSÊ]ªìžkÖš›¨o4¸.S)Öºž–ðT… ÑjEËò©„‚ø7 P iŽæKŸ±MŽ] Î%±MIË‹&A×X>ô ®§"Õx,Ð,qÉÀaÖjr#}¨o “€I6içηQõsŒ=¥B€Çlƒ8!¥€IÔÞ¦WU§ÊQ‡“éiY¦Üü€éX$ìãv¦üL)4°’4ìƒ6fÓNP”MÙ ïÙprf "‚.Y.Y1¦þ5p7%ŒÒ]½e·ÞˆTZ¯‡@ûö²8lh+éKe\/6¬Ì@"ê&8,ØAAwË‘4Q ëk—+qX¨¼ñä?õ{åJ‚C£ÁC5¤´e€–ÓØ‹D¶¢X¤g3ÇP{@¸k@ý8ÀJ%·`ÃV ­MÆ&,ö^2½€Û«„Í7É6züéœA'Sr§]ÉÜ$V¶e[§ÃF'Ùßè/ß †‰¸v—ù‰Zä¾ý•Ár¸I² ’Méls‰^ÿŽÏRø»1<å‚Z>»8OS½»¡AçÆ//ÿtU˜` ¨.Êóx†¤¼=Ù=˜©!3–§Š9Gî¢;V› Ÿž.1F·ÝØÅ bß´–ÎvˆVá¥[è&œ#×äà¶%(n뉷à DËèK:nhÖÁ3ÅhÐ1¹îžOúöbi›=1B:¥µ­åµ–žuŠc Úõt&–¦@Y9ÖÞ‘Ná‰!‘¡’KˆãBfªÇÒcëð°C`ëïó@’°`ìkÀøŽÊoGl„‰Bu“ ^5Í1•9>ž‘Ú7¸ÈîÏùÇÛ·âÚ'JO fßíDPÌðŒZ‹ß8¢'~I¤JÔ¼J>J±H Çø¥IÒÇ/¦c,Ž Ì b¦ ¯K99ÕZ³H|8#¹{?+±ù§§f~åeá¤S#@R `ÉÖxšÒ\•šþ I2 ¨}þ©šI)¼iPš° ÝÁ~'à Svy|/8vÜiTÁwuì¼Ó‹²zöS¢X](m ‚”£cÑ=¡º§Q7ãnÖ ÉöqwC…šéJ÷yf'y§F€:ñSÚ3Ö¬ŒÝ}h£-þÔ¢Ylþ~‚¹v"h_1åïJŸ’s°Šœ’1ÇÓÐ œèÀœWóŒZ¼J[0 ¯zFD '‰lÖ”doòØË<´ ãÚž’6ü*À¹^œëàÑr¢=kÛÁà!@$t¢ ÑÊJÔ¸†í nDÞ<(›sˆ_ß——jV¯èjzPßA'h¥‚¡s» Hg5.Ür®¾5?„LÕË$i*Çag›ŠŸº³c¹ !$thŠBX(‰!¸ÔÂåÌvÞ™ÒôdO@Ì>t¤)Õâ6‘3ßÐk|Xw ÃNøº²þ$F;}<.+Î ™x@£&SŠ…‹Z²W­B@C7Z¢£wi>U9z©§—¢€B Ö9{Ô²3¾QIàKÕöš 7(…Ho§æ DSMˆ£‡e²Á·$ÆJØý†~õ ã}¢½ËûT9Ÿ÷ŠdY;•ˆÉI )§i&rîæ´Ð³!ÛsÞý»àçÙÞÙÕ/Ç)f³ì³C‡vÒhq!- Y â9sñ ˆc²§e-@Á b¥üTõ !hHº_B§X=vò¬¹d¾ìR€°+Ò¥ „¡z¶ÕJW÷oŒp&¹°Vc¯—É·“NMc"–ÀHŒ*D+rÞue‡ž<ê3 ˜“ß/ÎèÉÌ©|‹u´úÑYRÓ1µÕ˜Fªœ"ŽøÎJ’OËÌy=»ÕöM”Ý?²h•ËÈ9Rð1cïI';¦éöõ§H‰š[ãv-5šCBì%·S³fŒ8¤¬|úvÜ¢óô¢9Ï7Z]¡˜šE}f(h@##©Ø Èk¹_ñQ•`åÄ ¿=±4ì[~é¡ùâXêšÈ!bÀïáOÝ^Öñí(V“0J… °R6* 7€áD4A!ÂðhKK·ÃýŸŒÍ nÌ|œ®óa;hÍ$äHŒ¿y¡ ©uKÑEå" á ÈÁøý¿Ý O©e \šüjšTCÝA.¯—ÊPÜj)yÏ·k%@ÅT ‰%HêmÇ&§D¸êJ‰FÄ¡âñ¨²RëÝ›_„m éV ðÃwˆ° ÎaðL•µž/• ð\‘¢`²ñq¦eˆ@2ëø«õ1Z‘yrpìkWâáJþ.¿‹¯âè¨M@4°‹åžHš|èS·.ɲq[YÖØÕ¨€Ž1ðÐl¢„B&ÒOùçÆn°2ÒòËÖtØ&‚Å6Ê/Í­†#äE[G/ ½+ó'•iŒcžµHmOƒû ©x ÃÏFjã¤Ä½hE¼"~‚I-¦·'Û:Ó$…4²Ý @2a$RÇš=huÙéEÄu¨LrÍqQ6ãà£4̈B:÷ ª!›Âš5Ê}v`[l^â¤pVÛ4e.ÜHŒZÍC¦õ;t…d9îÑÁ„ ˜}èd’å#"h…%!A0‚ •-½œŽ†´›x‰‰Yñì=)Ða£&ľ”ó‹r {M[‹âݤ¢ÆNÇÞì¥Ï´(û$?4éxÃ÷­Ëý*àO‘¿h¯ë¨%¸S÷B o$C_{p›ÙF8˜òíK@„¾—8®ó7(þÔbV¸Œ%%ù.4ç[¯ƒS®wPAŽ&‹7Z‘ Z³ÈÇš¼0^‰…Ú©æÌ}>"ž[³2Òƒ3“èS®k59l,3¾4«©‹ð¥„»&Ú¦‹Ž“j‹" ¤-&SÎPïk>´.7)ðm|%Ehj<#`_“ΈÅ:¾2ë&v=È‹Kaÿ…{\®Îs–7«È.:_zr\_&Œ‘ÖÉ ß4•aŽÀh&ðQ²xÂÑ94*DÔ´Q"ÅE‰ý©ž:©¼¨ 8– 2ò©~^hèT¯”E”í4" k=¢²˜6^«Ø{ƒ/,þcbˆü¿sˆqT%Ñ[[‰ì´žu&Ø#š3ìCÒ‰>§*ÅöŽe¥ ¾”$àE+’pkԥ承§?¡@ÓR‘nÅ›“^ªD‰™~sHˆ€^PãZš°(ˆ›•lމ¥¸HeA8èŠP ¤e_¤¼êPˆáß/¡mnÝı>kzáK;Vˆ®M{1ƸiÇÌêÒ¦W^ Œi½ªÚÉa‹Ó‚,ÀÉR´ƒ"éÛ¥L Œx¨¦”&jyò¥\©)2¥n¾UšÝÊ»> sá‘W#ŒËØZAµðÄ·^ ZB ¡„¦ÍH" hID!;7JR«<¬­ôhæDëÚeÌøqqGâŸCýŽÕ€Rû´áê8PA8eíé:ð6}ïS—E‰Íˆúè}šblÂØtàPµ6˜1Áûá [WÜÖ 1W" e!{Þ‘É$ºGžkUqÈM8 ÂGì¡|Çê†Èê/(ÄS¶ù 4èE«ñb,‰uÿ‡»î·üögÔŽáBGbeéH^c’„$êá5®IŠÞ† Àt¤’@%F{Pˆ##VµÈS^tï”CÈÚƒ@é‹FÑò½‘Zr”Q„†¦T®°ä ‘nÐJ)¡Ò‹ÔÏh ²ætO y÷/`øéêýv³€Õ #ì”<^ÆÆ$²ûû}ià<꛿Z•ˆI ±É©…L—áé@ÊãjJ“ Õ§c¥•œœèÞ¬=ÜnBC&/Cä&Ì‘ úS§`°nÂÚ‚–éTuBþ;ƒÏ3„ü1Ð^Ä#ˆnÞ¦óh¦ÌkÚxFIJý AØöŸ/g`™œ ð¿Â¦9‘ ¤uxU°´‹)‘dÍCÆÎ€ŽC’¹£q­‰=ïSXêmãQˆ©àºP;É3˜mBVN%¦7*IÑÅLr¤7‚U ‡Kœ¹}ª0“ZX B¥`ÅfI¹×æ[v.†*ý—ïD;t¦pÔì†Õ* §«Q³šÆZ8\'¦7‚5‰ì£@±P3N’È\þ‡šBÆ…gl“E bÒØ g¨)±iÅ8ƒD_•’S;sû €taêh¼¹eÒåí°Åè ‹‡º-“©¯ ÓÉJH&Sû2耘µ›l‹ç‹d°P€FGàîΎ†C8Le%¸üÚFY;-©:PrŠçx‡üæ÷ÌϾ]‡*²ô7nje2$kèyëH»&¾ë®ûºSE´®Rw)it¤HÎêûhŽ"~/R¾4µÞ ðvÝúPE˜ mX]y |àppÌTb26œŽTLȵò yvާ*˜g²pm,Ôh5¥åÒˆ›ºê[÷¶ó!ä7÷Γ“Ø,ˆ\=¹¥“*Ç®M Ç*l¨‡2Q€@ФsX òÓ,DÌÚŠæÛ•ˆ…„›¨A#'.>©M±;V4Ú„ ×H™j&Tmcʤ “&§R¦ëIu™³Ö¬2¿«RX-(S½¼c-aøß÷R˜È*C†H#†¹žø7–ÉJŸ+¿>ËúA©D@Ëj† …“†©¬hY©¹Ì#•4Âvè°—Ìñâ$Þ[^ JA_oÏe¦ d,Q—U“/b!aè†+JSó¤×¶Õê>%´¨§êwr ç£@Ž\†AQlùÓùƒhûQXe¥›óW³>¯ê®DA¦çU§­š gÂHöS²/¢Öžcb¬„Zha7\ZS2]êß°ä ÛŠ´1@1¯¤ÙJ‚ƒÑ6hͬ™Y8z¥{¶ÝŒÆW@Ò<êÅl5?u&FîhÖ„BÛ%c¬RÝvI„§ŒÁ$­OØ#-%–&ލQÒÉ€–ÍÓ¥ìá8!)§ùA> OÝòq —ºlp\¾ø?ám=±´í ¼üb¶9L#“#f–‚³‰2RSn2„¢*+!¡ Šï…†‚ö²zRœ¦CÔœ¸#9,I‹«§«—aX‹^¢å µ¸äu;Ô¹ò NÆçj/¿j^Àç0cd›Ëé3¥ Æá §¬8½ Šš-+2bŠåƒi)*˜›ò­h‡FÓ ‚okžjq/FÁiFƒ¼P0¼_9®Ê'ÃróÜÇ.]€³G>ˆ|›h}Ú‚Yäh\np Ö–,1z(Šˆ°IÊŒRG±é:š¸õzÕÐ@Dð¯jÛ±0hO&¾•fKÅû£Tó‘ø¢ðÑצô=1tÆü +²Vêl”È^Y a.®Is‚š1a·JU°Bªhp ЂF{°‚¿//puÉ¡QÊfãkA8´Ý≠á(Gü‘ÃJÛíÚàÂP7äûÔ9»©ã &ˆkE’õ-ù7U›%<ÐH#OÕjpº¼=üÖRÇÀ¤ë0v(©JêB1 §tD0LkR­¥‚ÈBÃ2 &ô29B&|–Ö$•JÉŽÖã:O‰v„ÃxhxÔ¶Ä+B2‰‹]Ï•MMKza­5¨.ëF©½u¶G9¨)ÌN G`ÄJ!*^åÕ/OMú$MwµoÒ êo¯ËågçÜr®ß¯ÄLWœ¹¼9›ÑžÃÜQ¨zFOƒ±€ ºñ塞¿ŽÖa=@¥d$éØ3¦ñ~~“RÒì8(Q ƒ•¢ïª·¥<'v4žZT?6p:{HrDµ J’Ú¥¡;A„fà¿—,N;„OŠÓñ=%Š;3ª (]((Ìõ ögiÙ*ÚS`Øÿ„JºÓÙµIÜ¥¾UM¿ë£fK‰†¤X@fÓò‡‚}h†ëPÊ%X±*04á2rÚ ¦õ%ÍK4±¼µ-ìÁ#&ª™@i ãSÊðjg,]hÂÛ¢Ó²DK6š1+Ê“tP0nJÜ0qY*u)\·†¯X 2ØÇp—œzï>Öh"¦A-5}“ÅââŒqXg|¼$ìˆ'«ú>ˆ:WnÏ÷‚(è• ‚vzUç¶KTŒßŠÏÌÆ Ý‚Pt?.Ô:rÚMÞ:"bœ„l&qjI!«ÆCÛÇõG]"dOQV€È¡•0fƒWʲֳ£eߣð’oÙH—ÓSR v†4³ÜF`¾?áL¯û4cî&¦ •¢H’°àiËÓÂÑî(•çdnPsÂ1Kuêë%hke’N«ùž¼CäìçJ㙢wA™áÑêNµh§¨ 3Vtr;=`ÈЛaJlò¦HЯJƒ¸bëׇt“ß㊑¶ykÔSÔ?}a å©¸ÐpaYYõ¤Í2¶<í ´q#Â΀Y Ñ÷zbˆkÔûT,BÜ&¤A¤ðSRd–ëÛ)\žGqvòü- Lßs§:™•̚ÄDÓS¬ab*KÐ’í* b(O )Sš4ì‰(‡$ŽšÐ¼•ÁyêÒ±u9=(³­ÉÏ&•‚2P l\m3Q§”—‘9ž¡SŠVÖÄÒŒ‰Ú)Ty„¬ûåL»ROѧ ‚]kH,‰Ö‚ Ü”óÃNù$È'´ÅÇ0Ö{ųDY‘¼Ç+œ@€ã=¨# ‚‡0Úý¢íÉ<*÷aæ©CV¿C¹X•óü¼fœ¥G ÔÕ†hE")úëY®L=+Ÿìs¨!=l謡Š˜ ,Ri`8zÅ`Syeô áîeùT6SCÎhèa¨¥ÅÀíÖάóéC4ð?TDDåoNdT©z§R­v ,¥¤Š|~œa89¿©àÄäg’'Ïʰ¼…â„ çµçSyáàÿTå TˆŠ8h”JØž6ž}‰ÕRÔÒÎâ@ÙÇ0Wü1œ‡©ÃϹ;P‰%ÎÄäµj\?ÚY•Þš|ЃjDŸ…Ï9¹öã9ï´…¦œ`13¿ê–u¡*ÖzSnûN!.pC j~ƒ¯sí–UÒ<¾Å¼À,Ú…I¤¤HÛo¥ŽÓÄÏ2èûváaZ,_zä÷ÃÑà  %hÔ‰%ë”–Š]tÝ ŸÕ`û~À2Áõ€q¾›+Ï>Ð ™ m.{ÏÙDyþ‰+¯”5Ö±èdãƒì-V{*i0×?ž2–Ë {/Ú‚5€¾Ï J'JôAG’˜¥±#1ÁƯA)cÎ\¸@äûÅHÜí‡ÞAiÒ#UO¥8D-¡Lä™Îm…jd%g!Øõ”z!^` b@Œn­$ &ÈÏ*‹šj6 a…½,OÉHrßZŽæ$ؾÉêB!çM'NY©_:B—½_;™ D lÉÁ ,íDŒ6v÷® ëÿRž·íkÉ/桌”!7Xi2þŠú4Män%¦ˆve=T + ló®Hè¶õ †Ð‚|ªÅËï•:@èòiÝå˜.²læ‡Qž$U‹\™ÚhU¬<½ÐëWKnq¡Át%3åµLT˸ Â5®>XúÖ´sŸáZÓË}ibÎöˆÝßÅÕøîš5*h4ÿ“S´iαŢ:¾|cµ[ÌÓÒjk]‘Ëíî>ŠÓ/¿ïωH˘%fíGŠØ-édä,Q²“$‘‘иþ¨JHØ$0pû}kzWñ¿Jyý-·`ȲWó¶?´¡f! ‘“ÊhX÷Î&¦—”¬_^| ¶A3šbˆ7d‹¶B©Æ'¡ —|<*ñÒ€Ú´;fÕ;[œ­Â¨4Ñ¡’q¥îmØP Z–91Shµ· 4â| ùÒÓ[…TåAƒ÷DÞ)Ä9jÛŒ¢Iµ~‹©Û[ûÞ”M`žÂ` ì­ÇUˆÆŠˆ>ÇR„ ]—-+avÒíNÙYý>Œ`o§>U÷U!¦°à¨c•$¥DÜÔà@± }  X8›iNUü_ÀT.XÀ¦i"ÃÍØo1˜ˆüÓðF2½=_SCÁ Þ·©²ô^>2û_~ŠÆGdûEj‹­‰ó©nE]} ° š¬±Ö‹¦{* –@žlPyì´Ø^ ×óB-íNõqYÝD9\=ˆT½ÒéRd¦1!6?á‚›·æ €à†‚‹·Òˆ© ç3Š ʰ<µ*ŠèÑsrv¨9V.ž5CecÆ“ž«üÕÓð"àõì@†P±Õö¤ `ÝeB²–eó¬9øz söš†u=†b ­Må`Ç`9“Gƒ€ŽÔEå:S!'‚[ –[ iR¶é¹Ð¢—ϳŒöv¬fÚƒX¥'ÃS’.º Jˆñ›©Þ8Öø”ãn'x¨!„¬S„¢m X%\ €ÊX>Y'éVóêùE8™6m>U"&-Dçú¨¸AùžU­ÜU§ðŠY2Œrc‡3¬h˜“çdØ%dhñ$™©¢Bh!âã¥"¨éh ë4»&ULÒ+Ï6DZ ÐeȤ£Sáãz]aÅZ.‹þ.£Ü0í?Çb#[®µN†@j+ß–(8¯ é­L<0Å@ë\‰Õûp†xdVåL¢r:øxÊ#•Ëý … ¸ÐëW*•¾ì´úi&_º u¥‘:¸”Jî+У ™nf2Ôà@nÉôŠ[\ï*T”´eëC › ç„¢ÙçIK‘Áà'§íFÌW@u*YV…ñ{s¥Xƒm†IdXÈ%S¶ô&'Z(PCšêLÊ=–šDdm"œÍîXð¬. XA •yÖ­Äú8‚âv¬yM¦Ÿ"b]°µ±{8ôj$„ M"}&.К¶¸ª»!ŒíRÙŽÇ¿ŠâÜ]Š˜•µ Âóh­ë ÚÈp à£î ;>‹à¢v)qpH\AlE¨+('—¤f¶…Å™›H#É«¡(Üÿ)ò-›¡Õ °X¨9S “Ƭ&Mbõ‰Yèóà§ÍŠqmzß<\_bëý;V£ ˆó*M,@yÆÔžsö<76Ó­˜ZžŽB¨E…&ÏÞ¬á% i¤©i#þ+2ØšHv–^<{XsEòÒqŸ½< K”ÂÐÞôFÜ¢—E ƒÇñ .Ta|À±Îµ«òq€æéÏN(ÀÆïÝ ’ÈªŽ ØÄ ‚¤GùCß‚`#8µ# éWU%{ ÿ%:´¾Ô'^¢Çª€•¢¾‚!nÃÒþòP!— Nšn£Åy&rÁ†pÃkSf<©â(Q@ƒ5u"T+U Ä:]µÎî¦ê“ð>œ/íF& Ñ­ŠfåX½ r+æb 6ÃFf‘ŽY9‚3ªãZ•!ƒ^KÛj  óБ~:?'K™Ç“©ö£Ù:½…Dè$ñ"½÷iµ!J˯šŒÉÉHŠ„Yc±ÙF ïiŽ"ÄýP€K,ÉcÓ»eê¯76~h šñ°C @²(E-v"Ç"Ðù@OkSgØõJp#ƒ:QÿÍ,ÅŽˆ¯èì1&|ª_`»<ª#ZYå>óAhp+NÌ^0ÐÀ&ÔIYèïL¼²^xuµXãr‘u¡Ü ·V´=ÿz¸Ô%– &o7¤,Km AŒ3fÖ½K‚ÑP`:<ñDœH²g/ªf¦Kq-¦_ÇO©}!3;ϯoÝ–¯ö˜á*ðdˆ˜JqCå a ·¦XX¶ö³G ‚9@o§{×Ü7æÛ1zi‰“¤ˆpÎ*D·x¢gOƬ£‚á)pÓœÒjRš¾q\­wü‚ˆ;ÒÄZxƒªaXmÀÐ$²4昷÷?zFFâpŠÄä5Σ”9K‹ cŠ$…+YM¹LSSaÇ?|ªyÀm¥CZûûeìÄä‹sÃÑ '±Ê³\1¬Ò¢¼ƒ¥ L'›öXÍ*äB } @&¶ß„´Âš·gŠ=á¨J!¦×å¾Þç:;*,Ú’òµÍZ°êÀ`\ :×ø¡ñ®ýÊZYÿ2ÇSC$—ï<½5ô¨t½kóÒ*ÌžÇZŽ@h^çgíÁsd6mü£ä è¼$"ì@^ÖûÒ¦Œ~ªÃõA)„éú£N×üÑ€©²£ˆMòñHØ¢F@€çΣ݋BL qéQ“ 2eÁ>uÀBá €Íæ|hÀ-€1@²Ä•?– *0ùj[£6ySr‚5ššÕޤ!ãó£ å ¾7I,#̯÷ˆêûçôÌÉñÅ Ê)0˜–*¾÷P¶ Ò<«ô¡YÚk½r<¨Á{'EËùæØR´JAmnÿ+zÈ ¥c;03ç@2úð^‰#©Èµx]ÂUš°îaŒ€…ß’“¹î4©XiByP»m*4ö ÓˆujaÙe26bMèÕÜ‘÷.±5p­j XШ2@Ó¾Œ³ZØ:/ÝJ4µez´D×½CK>šE5þB$¹‡$Óäp!üR•$4ç{w˜ÜƒÅ©^åõâfˆþ.©I2!©êÆåböж$$ <}GíGµ6"D@Þ‡2ª3êaÑ«]&Oô¦+$GJ\!«)³¯¥N«åÉduójc%’Sª”ÅØ&)¤¶Ks,r^€Ò¤âc”ÔWp©›Ë‰µE§Çï­07Wòj(’‹¼xPÙ3±ZAS³ ¾c‘±Ê‡yÖ×é™MÑÊ#óP*Bôæ;~ù¾¤–pZlxVQˆÅ*ª´›´qy&#õWèKÂ=8È7ûF‹ ÒšG$liÄ„ÓlH=jvÊœ ÕùÑk Ĺ C'"_ ëò$4³é&¸’‡%é)tÑå›C¶àê¶*ýJRà/{ ”J´[QŠCÏ—”Ëapu¨žà Þ†=~ÝQûvqŒÔÓNU&àí|“Iq¨åeñËËÜÐ1 KF±¢I Š–3c)P;*Ä×4 »,?J‚¹E^½9ÒŽ|ÎY=¦e›I©\†9ãÔvý}HBoqùSÈKAéÅL±ƒ«Ø‡ N¥ñh Ñg®õe’à¢ÁMÑà•Íìþš5DŽtÕoXT&²/ãáZЖð¤7clö|‘ŸH$Ü¡`—͈‡q°]|àâá äFxŸ FW@í¿ÿI HÂIzÛJ°•- ìz¯Úˆñó®Kι:bÄñ ímLžt|¬ˆ¶œ±M7_ þjÛ. žüªs–å åI—‚Ë„­ʬÍp“ä×€Är;q"Päx®±?_¦œ;,øêxçÂŒRQ#ÚÞOìvy"а Ü]Š=nm´©¶\9s£`z5ì!ŒƒÑV¨KñU.ãZì xÛäÄšvÁ à©À¦û©ŽX¥Ç»Mòäæ§¤AŤ¢l²¹*/2Z /Þ¢Õœ=j iŒ:M»[ñ·aJU©Ù¸-š‚I‚®]Rí`;R +Ožuˆ±–ÿµ$¢F¼`î•覆UµFâÑüŸªr—(.ŒBB݃~,‚Nã“KçRlšKl–tÞ¥³\Ð €«+î7œLøÔK^æg, .«hÞˆ€ PL²¿…Of”dŽ7Ê©‡ É=?}†| TÈrºß”$Ó²¢?à7S-ùÆ”™ Cv_qE8 ÙR$¦Q7E¼ªé­Ë¿Gb?Œ—]$L ä6ßñØ»ú_ñØAn•]<—°ÁØl,TÀ`;C°+¶X?˜¢—`4¢lúÓ„’ã³Ei±÷çXS¤¡€A#Æ’Z’xÓ-âO< ƒl@–d€-˜¹­; ±fÛÞò-`–¬ …‚"¾eÂfÇ‹r•,—Z¾„ºÔ§Å M¤^­jòÚY´ÏÊ“ÈÛ²(äºæ/&Ôõ- ¤º&£D¥Z‘ÄÂQJ£eu“ã½ ÁœU 9 “P1%›ƒxUž°‰cAe+µÅÄ€ÉkfJ€¡§#¦(!e™Ê¢iÙI_Þvž¶³Sè*PƒYýÑÖz¨OM£ó@1bËö6œÛù÷ίؕÛÇê¡Ï†(̺cΧ­›3V§ðüñq²xÅ'D¶Ý¬ šº»÷&ÝÆ4Í;2sR†ßO9\Ï ý¦³Ý))Ö ç_{ös.LžXãfî0¬¶¢otó¶¶3­>YÊ[[è mª z´ìKVÍL¥¶ª+ò S)üüµÙI^á„ob˜Œ#rhì –¤|‚›cÀF¼âñ+>/¼M½Åu[«/YD°Tà'²P´ØCï‘ÄœÄK4ÛŒèo,PfÓŠšï‡<é)¯•½¦Å{ª”œ»f”¥ã–´1Š F(Æu߇ӿPÙøQŒÌÀA#' !˜vb‚;"ÀÅÇjFyŸ>|b6-ŸjZñO53^F`åµ'„“,ÌáàZ€6F,˜ÏÞ€M>bì¥]ÞÒÌO7÷ÁN$ÇE½ 4âDuYŠl*Wvü9^Ïçß.0հж8¨E`MÌþøÜ§ï‚VPc"%‡¢ÔVC4œŒiITêx1-##q;1Eºô««WA}¨$¦§áúò¢o6cçoJ ¡xƒ=Ⱦ Aˆ¤;@4ï­!ìþÕ"`¢íi³Æ$Mšp2lAšš€²˜|5•î:½þ~h–”}s#4'ÄG†Ei3~È#dƒ™øá›ÝwgÝjÿ=x!xZc(¶M=¿DéÍDZšˆXtМ145¥ÐÄ’,Ï:x ±h4½;­rãaçe&±™¡båCÏ—Ô,7!š•"î!¥Ê·ÃžÎ†×uøI17§d7 T2n~›´á ÊÔ|ÂO±š">l–”}oÊRûªÚ@y6Ý¥¨© Âϸ£‘;@]¥ˆÃ&iD º 1ºÂ÷·ZŒÈZ¯ÛæÕÑTïC'b&ªÿC„bLé^.ßÓŠR PÄÅÑ)VU]¯ÀP)®< bÄ5>‚‡Ú¬ }æø©’É0‘Ñ'JÒÀÇ$¡Í*),FBC/6Í  Q8¶¿ç?ª—ÚÔÖÐIb„pC$ðP´s›‰$!¥)¦…èg¼d¬ì ö‚Á-Š“ì'ÞhAó”´³ëTH×V¢/&9©™‘/M;Xê #îŠ"òQXDº–à’P‹Ã{ÿ…±LœŸö8rñÉÓÜӀʇ ÃP›ÓÜ’”LFöo4 ‚¥Ä˜‚¥›œa·Þ¯94É¿¥ H€k@\ÆžtÖ’åÀu‹5%`]µ3áS “¸9÷¨æ,:WŒÞx_„Ä‹ÚhËVòt×ßJbD@Ù‰~[瀾N°»Õ¥Î«=§­NfZWÕ\¯ÏÒϪS, uû¡Ï¢MŠÔñ3Ï C1dB\P’Iz²‰bÚìŒ ¾óÊÊì0Ëå·«2,X‚Ž£‘Û„äÐ !f<¼è€?é1‰br»¤SžýBÅÈ.N<ªzÌÀ!HЂŠH+N‚ãˆ†î„Æ bbæ›4Ò¡5-l¥‰}‰§>ôža‘ÉGÊ¥t ]¶©dL“¶žùýFÌ`ÔfŠbàz8 •ëÅÎ+‘¼«>õ'ja÷ËQ???CK9ýH„£¨´™Y5&Ü"<ýÊ€$Kϸx»5ƒZvÖBºÈ/Îh ØIs"<ªÎ v@Ê•„µ‰Z©lñêvüq›)ëÆ÷—»YU×:ŠbT؆‘G A5ÈþhdyóåR ´“OòŒ ôƒåS“g-sáWáö{µH‚.¹¤fâ{ÑH˜7ñRÀÝÖOLQëÐ}J°IÀ«JÃ``-¨­lAQ‚EW ã´„DÍ4h:G É4jwŠÅLŸŠ«$l¹ëþPCè$4ìú‰ ‰ï ²t+ĵŒ£˜«L£› ¸ˤ6L ‚90^Ê0eˆB°na¥Ø/(Œ 3pÄYÜ c|äîáãx$:“~Džã›Ýwah¶÷ΕÎY Òp° ¤Ça*äA, M„(07ŠÊ I8oR KîhC{¦‘Ç,^±A Øoú§lKð‹~*óDbÚjh ƒê(q‰ž%ƒ`Ê¡Jçh¸f"&þ» °k3”Ô @Û•KÍè|*ŠZÎlÚ›×¢”ŒÄ:Rä±Lì_9À_:øŒ0 ›©¬гÊP/)vg‛C5+{ÈýÒùä?uC`ú&ŸVnþÊ‹i K<™"‘ŠJå»o `o ÇÎŒ±  Ž%$S;‘ÔYàA¾ˆÝο­ûPæïhÜⓞ\m9!$réP9Sà+¥À)ÐÜÛ™_ôÖjˆ$OûWÛ|³¼u¤wâL2 »™óÞÇî„bë+/Ôc-ª@%`9èèÒÏ,·,ˆ„y54÷3ÂY ¸™ÂЈ¢hŠ“në¬+™š@Ѹ‚Ü‚ñh±.M HW!ïœC$#3kj¢A¡Í£Ó»èòM;*#ê“uÙö8—º´‚mÀ•Ч=0yÙá›ÝwbÜìÂ,‡WÜpš³JÑC¤Ø*Ñ㥥¨:Ñ’M¤zÍÄñƒÈ $®ðÔÖtM¸Y¦w%ðÂZã/aDH Û}J·¥£êtÐÄ¿KÄÓ²’>¦°T³›’Î¥ñ« iŒ/>C L´|#š†‚Q)5ýî—cýÑ’Œÿuýîž–VcŸbÞ3hRhÅóØÂLrR íêTä¼0ׯVK­âìt²àÎÉ)3Ah—/ )'&¬¤*ZóåÛ‘ÿBvRGÔnR]“eê¤e•²¨:Aõ+”ûÓ¾YYg{ÕýœOÏgÍÛö.\!17Û‰d¡ÇÌ(a–¢­a«x]½áIAV@’-”Þvë'òU’ÁéåPžQä¾+6àÎÊDú…rS8Ö*óÊ3%«’áôddŸµZ™ƒ‚ÄH©@Dpt HÈæup¹ß³ì[”²9 +²‡oÚ¤äbvùòÔ ¶Ÿ*ÈSpDCð†t£éûÇ´ãß:”9°?¸>VRR‰xßsÓ°'š~ûJTÆ<Î×±oÜ%£b-o?Ÿ›ž3TæJµúþeÓ‡ô©»g_×t–”};Œ{ °U•pØçpyÃ"‰G?«{P€FG^ Nf) ?em«2O<”—xr  XÔ^݆oçõvä‚yÕ¶ :‡à¦FØÿŸ†¬s¢¥±–£3ãÞ%¥ŸM‘‘ ‘N“])e÷ÓÏŒž¸Î“ÒˆlbØi),Füö®a·Uf ú(|[òh†Oý¨˜%¬W•lÑ/RËY¡šö•`^‰û«ñ#Ȭf<ƒû¤ ¦~t<ÿ d5¤Ï‘g¯|†–}2xƒu=® $Î#'Y’§É çu'[9Ò€Îl_v Ö§Bù Íø­„döMFl홎&èàs´ÔåJ&/R+Và÷µ õD4³éu =V‹zÓ¦J‚¹¶'œSÀ³”Zq=6)— ý"zV†Jýø B*Ùšâõ.ò SÂ Ï ŸD®¬2ÃŽ?áèiúT[M «J¸éíM)¨"ü·§­D†-b¤k/H¤òü Õð˜Ë»R–Ÿ4ƒ·5Mˆ+0LéÁÈ“Nsl½*E¾S:ÉPÅ„ÂdÀΔWƒÃ\’QH Émº€¸®Y°cÖ¡³rzS²’µ|"$ÍôŠNcèL%âÈeÖ™ÖP'þ ƒNϤçDKëh=(`½Œ²TuË‘„*Ënu*Y²âE¸‡ Ý ƒÂ_:Ú*'Ì©–õ¶€lfצŒ0öKŸ*ICÖ`€u@¿*ö—Vb‘æŸ;šÑ*ÁDÎTQ^Y¡¶¡-×/ýu&•¤-B‘†Ñé¶NjÂA¥7Y.uÃÝSªüïùãËïüV?ëé4줣`‰…!Õµ suþSR`IR¶…!+ BožUýî¿¡ý×ô?ºþ‡÷_Ñþëú?ÝSû¯êuýOîˆb7MÔshck˜ôV?ìY§e$}‰LÏÊô›+Ž)RRXiçA„éQ¼›mç=ÑDš^qd¦ë­EÇåÿ³;)#è” ]SÕŠÑcwOŧ,nÔˆ¶5©U%ºëxéžØÆf׳Z€‹ÆÞ:Pð›YªSmMþÐΔ} ÎPÆv¨hal ¶íZ…Gaj,ŽsFôøÜg[·îl4Âb¯¸±¿öÄ´£èO"Ú UæÃ õ¨¤±ÅY7+DDTd¶Ô oÜE…q!^G?qÿnKJ>‚&ópÍô)ÐÃ}‹4æ©Ô7Ê´ctjãÅ4…$ƒ|b6ì@’€À“gˆªÍÛ*+NØÓþà––w…j6ƒ$s¨8 ŠízÑá7³÷xQ‘©t\ò)³:U}¬•ý’¹sÅ×óòé–/Gý¨ìÎ&˜:ÂHI Ìjð©Blò­D™jwŠÄ…É75â4!3c#•a`ºëL—éc4Xÿ¹!¥×µîÐ#'Fóû¨Â¼‰‘e¢ïjÅZñ‘ày±Qç<ˆQR3b[dâ„ |«Òs°åòð/‹øGò—‘¤W®RÅsÑC­ªÄO‹R.YÁšÅ¾¨Ï¢‡+E#²hÔÀMj±ƒn ËÔÿ’Î] NÓô‚YÜ[W9Íákßý•ïþÊ÷ÿe{ÿ²½ÿÙV£Ë,Ã:<«ßýµïþÚ÷ÿmh4,ëáò¢îO€T®.•æ+˜RáiCÕ±ÎÕü/Ò¿‡úWð¿Jþé_Âý)e¼‡ê¥º¥º…¹™üþH¢u·ö*lðhG½)Ž"Œà4ÃN¥Ã!£ÐiÙÚö½Ú'sÐp×T­û! ÃBnÆ¢m^¯æ7dÒ"¥û"‰Ë™LÈüâ¥ì&b¡±¶&m~Ͻmò“ ±Dö–*\ 2Å “íK{¸È&Ü!ìÎU¨Á Ïn“z óvg²ê§Ñ 8^<¿çþ$ ¬hD-Aïz@VYIÞŸ€mPgHÙM ‰;• öÒr²@Þ~Mï›ÑÎ\J&n,?I@ä@è Ã'W–ÐÐÕ¹À 1!“TM4@ (n0PµDdCËmØ –ÜXÕ.©myˆ’¡?D„cju¥rІ!˜thà™ÊEcw]šBr~ùèzthý(Ô&… |0D™À HÃr“f‚r „’@Êí# ÔF W ƱP€– ^£T¯œ’â2pnæ—É%ÊÚX½©’8“[$DóLà\¤"èK§æ“G:Tëb¡›"ç*‘Êå}šøÛ΋=Šâ’åè ëF9â¥$(oB 3ˆÀŽtCà$Xµ†gHÖ€X ‚â$‰+<¬!&I&Š"åHdPZ/(ip@2H‹G:…kÉ Ùz˜RÚÈŠ? ¦*%à¾%Ê’ƒ ÚA”kC¨«rS;É9å,à ‰ÁišÏV“ D»µ7¸˜ÁDT†(›B®Ð´v+¡!kYk£­>L ¢7)†ûR8ÕhD«£4§pJÂæaŽ•9䫨ÂyñjC¾'•%¯äµü–¿’×òZþK_Ékù-%¬Mn€œò¿ÊYŒ‡j Úž Œë@Ò!Âñm,Îõ Œ 79ǯlÝô§M:ͰÇAÏãþ$ˆ dƒ¬3ˆ$´Åñv­[€ù×¾o\Ë“#¢9Iz´Ð.".ÅÕÊJ¬­èúØàÆ9A¢b‹øBY ”¦ [4S¨€”£ï2ÍC6 ]gYÅ.U+µî–¢8¥…°F鿉iKgæ"¡)‹ÓáREw 3-œÉ “˜£•@6 jl[2ovYslb¯¡!ûÀ¢œ-§€j¹ "ì@wÍBë ±}][U¦FâÏÅ@#Ÿ"ÍØ×FF)Ø sá@’IfL(²FPóÈ-Õ7©"™ÎVî´"l˜DƒÅP0(!‹©†^u-/ˆŒ'¹É¾»Tb³:M’€öY»$ò©© 6uk­él‘F"»ËéY§BÂäâ͹і¢+D‚o}SPäIËé à˜2ÍïÒ”ê rM—ئ¾_²47Š-@8‰CÈškWpÁý ¦ 3«¡³«KĬL»é5œ…º%OZSA’7®úO0Îb$ „§3š´Šæ,B ¹Ñ›£aœ4jTYfÏ;ÐI|ÉÍóò‚À5ÈuÝãÊ‚Æ!3q‹×‡P¯ÿÚ ´Im¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶»m¶öi¶ÛhÎÙm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶×m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶×í·Ô)¶Ûm¶ÛH¾ëm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm€ m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶–´6[kÝm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶× ¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ì´ðE¶Ûm¶Ûuëm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm‚M¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶µiÜÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Û ¶Ûm¶Ûm¦{m¶Ûm¶Ûm¤)UÚe¶Ûm¶Úu¶»m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm‚HÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm2°œ[m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛM¶Ûm¶Ûm½m¶Ûm¶Ûm·[)Ìí¶Ûm¶Šm·m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm+[m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûl«ÞþÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûe¶Ûm¶ÛmªYm¶Ûm¶ÛmŒ[j»ƒí¶Ûm¶×c[m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm’I$‘•m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Úú¯Jù¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Úí¶Ûm¶Ý-ºý­¶Ûm¶Ûm¶ÛlG’0¶Ûmµ»IVÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’W-¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Óô¶ß-¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛI¶Ûí¶Ûm ¾…©Åm¶Ûm¶Ûm¶Ûl6Ûæ¶Ûm¶o6Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm·[mÖÎvÛm¶Ûm¶Ûm¶Ûm¶Ûm¶»­¬+m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶>‹µÜm¶Þ•€EµK!¶Ûm¶ÛkVÛm¶Ûm¶Ûm¶ëmÖÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm©·[mÒI[m¶Ûm¶Ûm¶Ûm¶Ûm´»m¶[m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmµóm²ú­¶Ó|Û™¶ëaÛm¶Ûm6Ûm¶Ûm¥Ûm¶«mJÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm¶Þm–Ûlåm¶Ûm¶Ûm¶Ûm¶Ûm´ m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm´+m¶Ûm¶Õm¶ýž¶ïnx[m¶ÛH¶Ûi¶Ûm·m¶³mµÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûme¶ÛmÖÛm·M¶Ûm¶Ûm¶Ûm¶Ûm· m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm´Ÿm¶ðí†ÖݽËm¶ÛnÙkŠ[mµkm¶ÕmµÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm ÖÛm–Ãm¶kvÛm¶Ûm¶Ûm¶Ûi+m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ëm¶Ûm´m¶—­¿ZV–Ëm´óo*eí¯ -—[m¶Òí·Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmAúÛm–Ím¶Éx[m¶Ûm¶Ûm¶Û{:m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ëm¶Ûm~[m¶„í°EnN‹i¬}\Ym¶Ú߸Ûm¶Ú!´m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmU¶Ûm¶ëm¶Þ´×m¶Ûm¶Ûm¶Ûy–Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ëm¶ÛlvË-¶Ãm¶øfòknFÔð&Ûm¶Ûm¶Ûm¶ÚW·Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm•¶Ûm¶ëm¶ÛM¶†¶Ûm¶Ûm¶Û}¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ûm¶Ûm¶Ûm¶Ûmµkm¶Ûn6ýí¶ªí¶ÚI›÷k2þI6Ûm¶Ûm¶Ûm¶Ûá¶im¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm¶Ûm¶Ëm·K"׉ZÛm¶Ûm¶Î¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛlòÛm¶Ûm¶Ûm·Ûm¶Ûi¼µm¶…­¶Û`RÂ-ùÿ¶Ûm¶Ûm¶Ûm¶ÚM¶³m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm¾Ûm¶Ëm¶Ù­›uÁ«m¶Ûm´š­¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛXžÛm¶Ûm¶Ûm´»m¶Ûn¢Ûm¶×m¶Ó[öÓ ¥V]¶Ûm¶Ûm¶Ûm¶Û-¶¯m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm¶Ûm·WÉnRÝb1í¶Ûm§ëm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Û_¶ëm¶Ûm¶Ûm·m¶Ûu¦Ûm¶Ûm¹$q¶ÐÌÐí¶Ûm¶Ûm¶Ûm¶Û-¶Õm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmn6Ûm·VÖìo6ج¢ó¶Ûm¶Çm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶«m¶Ûm¶Ûm¶‹M¶Ú¶Ûm¶Ð rÛ¥¶Û•d¶Ûm¶Ûm¶Ûm¶Û]¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmo¸Ûm¶óm¶Û}¶ÛJ¶î¢Ûmµkm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ým¶Ûm¶Ûm¶ÿU´Ú¥·ÙÅmãÈ3fm¶ÛL°°Ê2Ûmµ›m¶Ûm¶Ûz§Im¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm¶Ûm·›i·Onrß?¶é;›m¶›m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Óm¶Ûm¶Ûm¶ß\\/íúÄ´ûtÖÒ-¶ÛaÔ&Þ&ígm¶Ûm¶ÛlõGm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm¶Ûm¶Ûm¶ÛºÚͶÚhí¶×m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Û`rË?–Ûm¶Ûe¶Ñm¶Ûmö±[¬Ûa´Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm¶Ûm¶Ûm¶Ú®ÜØm6çÚnù¶Ãm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûo¨ü­¶Ûm¶Û@®Ûm¶Ûm R$¶Ú[Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm¶Ûm¶Ûm¶ÚnÖálí/}[÷m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛuœXm¶Ûm¶ÛK¶Ûm¶Ûm¿¹­¶ÚÖÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm™vÛm¶ÛzÃ~ÖóÍÔCuŒãŽém¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛM¶Ûm¶Ûm¶Û|¶Ûm¶Ûm¶Ûm¶Û.¶[m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¦ÖÛm¶ÛmºÕ¦7K–Ûu†œå"e¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÄŶÛm¶Ûm¶Ûa¶Ûm¶Ûm¶Ûm¶Ûm©Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm¶Ûm¶Ûm¶Úr¶óm¶cuçzI$“ÖÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛivÛm¶‹m¶Ûm¶Ûm¶ÛmêËm¶Ûq¶Ûm¶Ûm²Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm¶Ûm¶Ûm¶À|6ðc·mÈI$’Ó[m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛjöÛmµm¶Ûm¶Ûm¶Ûm¬c-¶ÕIÚ[m¶Ûm¾Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm‡€-¶m¶ÿq·£m¶Ëm·RI$’Jóm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Úí¶ßmÛ]­¶Í-‚Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm™¶Ûu¶Ûg°›m¶ëmÐ’I$’I* ¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm®Ûm¶Ûm¶Ûm¶Ûm¶Ú’Ñm¶ìàÕJÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm½Ÿ’¶ÛmŠÝŽ·Y äïò’I$’I$žVÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¢Ûm¶Ûm¶Ûm¶Ûm¶Ûm}m¶ÛM(Ú!6Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmm·[oVßmºÝl§XÅkŒ~’I$’I$’W[m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm®Ûm¶Ûm¶Ûm¶Ûm¶Ûmµ‹m¶Ûm¶Ú}¶Ûm¶Ûm¶žó¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmM·slJm¶Ûϑؕ$€ÕŒ’I$’I$’I m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm6Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Û¢Ûm¶Ûm¶Ëâ¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm †[m¹›m¶Çq%8$’E4RI$’I$’I$Õ¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûe–÷mÂÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛAþÛm¶Ûm¶ÕiVÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm±¾ÞmÊÛE†ÐI$’I$’@‰I$’I$’I$”6Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛJö3¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Û^Ûm¶Ûm¶ßírÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¹›a ÛÚ3ŠI$’I$’L6W‰$’I$’I$’[Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛY6Ú%¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûn»Ûm¶Ûm¶Úýÿ[m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm€câÛ\?ÒI$’I$’A í¨$’I$’I$’I-¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶™m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûnöm¶Ûm¶Ú…¶Â`Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¹¶7²°Ø¬IÒI$’I$’K©ìt’I$’I$’I$ž¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Qm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm–{m¶Ûm¶ÛrܵÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm(Kn­ºÛ/¤I$’I$’IKÚR”’I$’I$’I$–šÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶»m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm’¹m¶Ûm¶ÛxÆÚr|H/´Ú¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm p£m»AœÒI$’I$’O”êà’I$’I$’I$’W»m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm·[m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm6Ým¶Ûm¶Ûo Jïò‘¥c.Á¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmŠä*OP2²I$’I$’CAĪI$’I$’I$’ITm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¦Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm£\ŶÛm¶ÛmœÌm–Ûl¶Ûv¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmŒ…ܽ+¹’I$’I$¢I(¤—ÝbI$’I$’I$’I$¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm†Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm´Û=¶Ûm¶Ûmœm`[m¶Ûl…[m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmIÊ^ˆ1½I$’JÛˆÓuI_¬I$’I$’I$’I$’úÛm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûmµ M¶Ûm¶Ûm¶Ûm¶{m¶Ûm¨‡m¶Ûm¶Ûm¶Ûm¦Ûm¶Ûm¶Ûm¶ÛmK^’ †fÕI%”¨¨·Î>6%6É$’I$’I$’I$’H[m¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmvÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ûi¶Ûm¶Ûm¶Ûm¶Ãm¶Ûm¢9Y¶Ûm¶Ûm¶Û1[m¶Ûm¶Ûm¶ÛmI$•`&ëÌÒI$¢Ò¡`Ô“ªN$’I$’I$’I$’I&­¶Ûm¶Ûm¶Ûm¶Ûm¶ÛivÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûk¶Ûm¶Ûm¶Ûm¶Åm¶Ûm§[M¶Ûm¶Ûm¶Í,GÛm¶Ûm¶Ûm´› I$‘øPAŒ¸ÒI ±Ãæ´‡:½Ý=$Ð’I$’I$’I$’I$¨¶Ûm¶Ûm¶Ûm¶Ûm¶Ûn–Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Óm6Ûm¶Ûm¶Ûm´·m¶Ûm¶è”¶Ûm¶Ûm¶†”ƒm¶Ûm¶Ûm¤’0I$’Næ/áªbI*§m€’I$¢¦Û`N’I$’I$’I$’I$’I$’I$’I$’I$’ïðÀVò¶Ûm¶Ûm¶ÛmRÛm¶Ûm¶ÛmvÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ú¶ÛmI$’I$’I$’I$’I$’IB’[¸ÈZS+‰†Û©2ÛÚØm¶š­¶Ûi¤’I$’I$’I$’I$’I$’I$’I$’I$’KF+ši$Ûm¶Ûm¶Ûm²Ûí¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I ’QE!·uÏ~Ûo¶Û-†Øm·m6Ûo¤’I$’I$’I$’I$’I$’I$’I$’I$’J2…`>Ûu™m¶Ûm¶Ûm¾Úe¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I(’* zj6”„£m6ÛmºÝ¹7ƒm6Ûâ$’I$’I$’I$’I$’I$’I$’I$’I$’Uà‚"¬Ûr>5¶Ûm¶Ûm¦ÚÖÖµ¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¦Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I(’ðÍ­lâܽ¶Í¶Ûm¶ÝêÒƒa¶Ûù$’I$’I$’I$’I$’I$’I$’I$’I$’T´Yu¶ô¨jÅÖÛm¶Ûm¾Ûjöò¬¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I.RI\P…V]m¶­‘¶Ûm¶Ýlæë ¶Ûi$’I$’I$’I$’I$’I$’I$’I$’I$’kã¢7†ÒMo,[m¶Ûm²Ûm¶ñk¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$*I,ª6mµ›{¶ÛͶڶ·DÍÛ)$’I$’I$’I$’I$’I$’I$’I$’I$’ fÙŒÐm£ú7m¶Ûm¾Ûm¶ën¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$ªI$r¸ØM¶›v¶ÛͶђBx•¤Û©$’I$’I$’I$’I$’I$’I$’I$’I$’”‡: °xm?Œ-=¶Ûm²Ûi @¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$¹I$7nšÛJÖálÛm¹å¥Åœ­¸ŒÉ$’I$’I$’I$’I$’I$)$’I$’I$’¬[-¶ðmžÙ¦ –ÛmªÛd<[a¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$•É$•7,{©Øo0Ûm¼ÛnÚØnÖó $’I$’I$’I$’I$’I$’I$’I$’I$’ÎÛ-¶Üí–Üí¿fÛ|Ûd¶Û]¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’¹$’WÖÂäÛm¹ÒU˜Ûm¼Ýl· $’I$’I$’I$’I$’I$’I$’I$’I$’iC ]¶ØÖÊ¥¬ ½sжÛU¶Û}¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’V$’HÕy‰ÐÝm· b,Ûlâßm¶‹i$’I$’I$’I$’I$’I$’I$’I$’I$’ ÁWwϾzüÅuްQ»m¶Û]¶Úu¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’Jž’÷[Ÿ–³m¶ m³Yb²Úm»_I$’I$’I$’I$’I$’I$’I$’I$’I$’!¾Ëu&óiÌÚæ¨½Ï—ÒÛE¶Ú%¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I+9‰¦=ºÅ¶ÙÛm·.­ÒÝmÒïI$’I$’I$’I$’I$’I$’I$’I$’I$’Iµ±UÔ›q§Û•£PlÒDKE¶Úm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$­I´lM¼[m¶ððÛm²¼I$’I$ÂI$’I$’I$’I$’I$’I$’I$’C6Ámé[p7aÅÆfk¤’I'­¶Ù­¶Ûm¾ÛmºÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’I3n½¶¶m¶Lg·íîµ2I$’I$`I$’I$’I$’I$’I$’I$’I$’H ƒnÅ›‹y—•w¤’I%>¶Ûm¶Ûe¾Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’ILsm!ͬ¶›m¶:»nêáJI$’I$’I$’I$’I$’I$’I$’I$’I$’HÞÁ`7ÒO6Ï’·-$•‰%LRÛm¶Ûx¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’IPê½SnÛ.µÛx6›ˆðI$’I$’I$’I$’I$’I$’I$’I$’I$’Iÿé¾Üó®Ü*,fm¤—9+¯¥m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’IFÞ‚àìnˆØÍ¢ÄŒg³ÒI$’I$’I$’I$’I$’I$’I$’I$’I$’IJï;ºÚ)Œ@ØÁn$’Q-]Æ­¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’J&ܪhß?qîvÝ ¶DüÐ’I$’I$’I$’I$’I$’I$’I$’I$’I$’I é°Ôÿ?ÚÉmºÛŒ¤’I$‹v¾¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’KfÜw2ݯâÝû¶ÛaLÓi$’I$’I$’I$’I$’I$’I$’I$’I$’I$’ILyÚÛ »«mÄ˜í¤’I%6„’Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’I÷tžÑŽ"ã 6Ûu<ÄÉ$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I,Ûm²Ûª™šà¬ €¤’I%™$’I«m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’K¶½°6;˜5ú¡¶ÛoÒI$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I.cp;›D²¾KUî$’I%d’I*m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$ºI$’H;k£3n¯!÷ª>Y„’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I,k)Ó˜ªföáì)i$’I$“$’I$·¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’V$’I$’I$’I$’I%I$’HÖÛììÛ­’ûoRe–’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I(m»]‰¶Ô ãRI$’I$’I$’I$’2[m¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI’I$’I$’I$’I$’I$’I$’Kö3lVÛm³ªè¯[lÄ’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I+[m»‹y¶Bx"I$’I$’I$’I$’Hm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmIx’I$’I$’I$’I$’I$’I$’H6[q&Û@f“ª<o„’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$«mºÛ`‰›v·úI$’I$’I$’I$’I-µ¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’^¶™:ÖÝÉ"íá¶ÝÑ’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I% ŒK•:[ù·òI$’I$’I$’I$’I$­vÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’F¼[vßmöضÛ)$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$¤L‡ˆƒ(bÎ ¢I$’I$’I$’I$’I$’¬Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’W7{lºgovòm¶Û $’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$‘­¶Ûn¶Ûb¼¢I$’I$’I$’I$’I$’Hƒm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’Rål‡Ànë¶m¶Ê $’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$‘®1[lÍEø’I$’I$’I$’I$’I$’I%õ¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’QØØn·%o¶øa¶Ò©$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’é6Ûm‘hc’I$’I$’I$’I$’I$’I$”¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’A¦Û­6Ý}·e¤·¿I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’ž¶ØsÛ~ä’I$’I$’I$’I$’I$’I$’IÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’DÀzõvÙi´JJ,^I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’AÂ6Û£$’I$’I$’I$’I$’I$’I$’IQm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’W"¯öÙr9RI$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’=¸¶õI$’I$’I$’I$’I$’I$’I$’I$“ÒÛm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I&i$’„n[­–ÆÏàCä’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’A¶Ù¼I$’I$’I$’I$’I$’I$’I$’I$’DËm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I.’I$’ý ‹­§¾m‘˜ˆ^’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$¡I1šÛÒI$’I$’I$’I$’I$’I$’I$’I$’Ie­¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’—[´[ŽI „’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$qI I$’I$’I$’I$’I$’I$’I$’I$’I$’I%§¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’–[u¦Ûm …$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$•æÛm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$•%¦û5&mÛЩ$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’Tm¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$’€Ûm¶Øn§BI$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I>­¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$ƒ’7K ¶•m¶ÎI$’I$’I$’I$’I$’I$’I$’I$I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$¯¶Ûm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$„ñ6Õm¶›m¶JI$’I$’I$’I$’I$’I$’I$’I$JI$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I%*I$’VÛm¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$”pƤ-¶mº*I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I%jI$’I«m¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$¢nÇúÛ=§-ÒI$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I%*I$’I)m¶Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$¢P¶;m±ž7¤’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$¾–Ûm¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$˜x²m¶Âé$’I$’I$’I$’I$’I$’I$’I$’I$’E$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’"[m¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$o;8º÷ $’I$’I$’I$’I$’I$’I$’I$’I$’_$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I=m¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$€t¶”§G I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I-5¶Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$®é¶Ûm„†I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$¯vÛm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$g¶ÛI\òI$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’ Ûm¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I$0õ¶Ûm“I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I…m¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I%yM¶Ûí¢RI$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I%õ¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I*«¶Ûot’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$¶Ûm¶ÛmI$’I$’I$’I$’I$’I$’I#S 6ÛoT’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’M[m¶ÛmI$’I$’I$’I$’I$’I$’I'¸_¶ÆoD’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’ICm¶Ûm$²I$’I$’I$’I$’I$’I.Ï·Û¹ä’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I%½¶Ûm”;BI$’I$’I$’I$’I$’I •ض—*$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$I$’I$’I$’I$’I$’I$’I$’I$’I$’I$‘’Ûm@ÿ$’I$’I$’I$’I$’I"æi¶Ú$$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$Žö¤’I$’I$’I$’I$’I$’I$’I$’I$’I$’@ëm‰N­¶$’I$’I$’I$’I$’IUë ¶ÜH$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I'bI$’I$)$’I$’I$’I$’I$’´RÄ[ï©Tg Ϧq û\­ä’I'$’I$’I$’I$’I$’I_E¶Û$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I ZI$’I$’I$’I$’I$’I$’I$’¤!›µGÒPö«µe\¬ ÿÄ- !1 AQaq0@P`‘¡±ÁÑðápñ€ÿÚ?C(äßÚÀÉè- Û ¥®"ƒÁ,s –L›–ñqþ1‡Ú+9ÝÆ7k€mê ‰Á:Ä‚%žíoíáh+¤dw ¾aN@Q—î@“Kìç´Þ­òÚ¨3ZÐb€P¶l8kRÙëîÜG'·«w)]J”Ãu™«…Š!¼À 7m½˜ØÉp‹C˜I@ß&:bJyi÷mq}=µå´«Dé\Ch{Å¡D©¤U[Ý>Ñ‘~¦[ÅÔ@…>®õ<…¶åQé.¦‘ ÕGXŠÎ@½&Kûºƒ¸öÍ{[®š EH#/ê)‡RŠpÇ>ꀩõŽÞ:=UÄ[UäBÆPÏ‚ÙiÄ€ÃS—EÅeŽøµTe±xwØ•¿i4nCr*®2¹LR6ºùzuùz/ š>G'©ÑKÏ1*Ä)¶·jë7®Œêœ˜“k.Ž’â =@¡¸‡N(ÓpN}ÑÊ–û²êr%óŽòŽò¼eâZbmìœã݈s_G<éGILÔ'²8ÐD¹Ç[‚ÔÇ‘×.Ž"Ä~±ÖÞ¸z’ôAž„¶/+»&ÒXëÝs™i9@¢íìeK—«Ýq¦âEú‹%ó™‚S˜iÅ'dDÊ_´Ätñ¸, |"a¤¯J’:=MBzb8Ç5•÷[(:¬}}è‹ìFàYÒX÷\¯QW¿>Bið¹”å7® 3Ã^EWÒiI«™åÀ¼ñéL£¿CtŒ/Ðæ¯1(û­®Wœ8kÐT2úÇxžÀU¶x°p„†5séÔ7¡*6‰Ux±2jñ ±7È } My)â‘©ž\:ˆ`ˆ›åóŽ­ô`ÄåÉou™òàÔÄ$*bbbbbbXLLL$Áè:pC×vmJ¶náî×wÒ­ ¨«·Ð­¤ܲ–Î[C Åeœq,s1U¾y¹”7‚ À¥K‡ ]F€‹tž )èhƒï`{RnsGlDTò&•îµ@õľ'_EÓÐ¥Ëð—h†½i ‘~³y÷lW¥Ò‡+ÏÀê9ª㖇“†¦ø"¼Š‹›éM̬BŒÊ¦@¹ŽÍÜv…ù¨(Õzê‘ ñŒ/Ýe‚š”ñ°Ô·‰ÍW¨+UHúVP‰O-= ¢¹³¸ájb®æîø3²5`Š‘Už {äuô/'NmȪ#â=8ǯ"Ô Á0E¹K©LEDà NRÃÞ3TZ®å®ªaº‰p õ·/SÓt¦Æ¢ˆ5Q-LW~wÒh»U÷Èëè^NœÉ|:E °G'¶¢† ~ü&³Q§Ic,½F¯Rü%øEK¨«¾Ñ”w•Û€Âlj/Q÷ï ˜áá<(c^¤Ï=HÄC ð¢‘oyô€–0EB²ë†{®\¾_Bò L2¢Wµ8ôˆ;šâpptK©XÄ c1ïÀUn9*yE °œ5*òE¢æúøðË&"·ïÛ2F¥úõrßkÞê•+×мâ"+0 Ëð—ÞP˜aužLZà׃Èèãs:\ ŽúÿCÎ-‡éÇçí: q] »›÷{¢@ÍËë&„„a´@ï‘×о€^¥é,Íð«FâxÊñ•ã+ÆoÈï“§8,JëËEv®¼­­Ë÷}éË“âä§šºñAßÔ*§çŠ5DJŒ?ºw‰ÌþT°XóÈÜ<õ0,ù¸¶ùÿ’¨ì:QïÞ •¹|%û‡³Àjf.8<üþ}UMÍ'¹è€öHmh¯S3êÈ0³ dˆ g'»"%œ½mNA¬Ä370Ï0ž$¸¨VÞªe]'‹ýkÛ|±×Íû"¸|¾ÌÜDèîL‚èþÜÆ.œ1Â&S®<¿÷íÈ„:L¬wšqK<ÇðD-ñÌíƒ| ÌÀë“Ìýž®-Í'¹<µöå‘©aNÎ"-z’¨è¾Þ(ßÝÇO©Å/ƒßœÃ¨–8ä<ÇfÁƒÈýöòë·=¬ñŽõ>×0FÎÚŠéëã×ãªïè)Ñ@|ê3½e.™6jVÓO߯÷ov@~Z¼Ò‡Uíàµzðp\Îξ§Ä"’³Ý„ɦºþ¥ é‡ˆ^âôàžW· Ódà–W ˆ`5Ç_í¾—£Ç¯ùðõ/™àŒ{Á‰ë3NïÇþü½ÙRT wE‚¡®o°\õöøMû%ÇR S¾JLû¯`>Z|àL÷Çâ7(Œ¬ÜŽ9¸ 7<—“º#'IcŠ,Ó~_ûÓÎ-:xz›¿)f}zwàìþº}~ü¨ 6ž³¤‚ïÛˆ¾$C®C'¸E A)–ÜËzš”jWFÈ_ušöï_3Æ>fNókèQl OëáCS\øÒ͹ T72)ŠÐvû¿çª!l%õí“òA²æßX·8wå úŪhþÛµ™Gñ?$môõõ•Ò ¿p3޽ÃAÜœ0]=N®î>ÈŠÉÛqZšÊ‹B{ ¾’Ÿì~ñLE*Þ°Ö"õOV]Ñ'ìf"ö–ØWùÄŠë…é1Z¾w÷õÉõ=U™y?1 iˆ8b«;EÆôý%Œ—nâÀï3^Zóéú‰X}i4€î ëÛ‹“SÜK'd}O¦â[²S¾6ËÖà3 #Ý>£)ò“á¿Æàzïä?û7Çí öÞ¸U—B"û¸ì¾%©sÇh:üV;x®Ï´=awâPšûqpèLîc]ùÿ¿¿\ G^Ù8½ÉeC–z’ ñÁ.•P•ÁàÐ0BîFo^ƒ=¥>ኸ€§Píƒ30Q²îñëþÀ/«Ëõò#(¾S´Cd£§„ —X#ñ-¯Øý¾¨ ¢"4ËÕ‚//\f·íÕF²é»à¨:аSPhÿ\wO®Šk€×µÀû¶®)}¾§agIã:ä„!DŽâ)¬M#Ãì¥/QU‚_´V`ªž$ñ£ ÷ úØú?œ·1#.±çÛû¼EªäÙááÀRhî|'£ú{ÍDíÁ _NÖ|x[‚ôà£ûs T3M0bÙ ·ë²üõú–òß—ïõ(4= ýxÌ Òï¿ù*éW”¿ìA­ReaÔQ¾SäÄÕ½|â´W®Šdà§µZ!Ü|gX•¾H —ê]ã¢"FV,'D§×=sŠi‹‚ðI«öþá%§¿ï¹¬Ip DzæYÃc® ®ž“BrÆz=¼G¤ÔÛ†˜€/sßÚ€zúĪ;06<³çª»•qÜoáü)õÏ«ÒÀìü±Pð@ýããaàh…mqUÓÎR ÷¾n,¥¡5ãØ–ÞqCå_¨Šfôc÷ñ†áå}ñëãZˆ@ií#³Ü”4N)Ó«êe´DXª”äA-©ÝN5¿H*Ö€£Üxx&`%¨—|U®×®6éÈîY“Osûp)R’ø[© —pslj±Çg™÷'Õ>—b•Š:2Ô÷–P¿Á)Ã;EaÖ—Æ ~C÷h}Ö‹®çÚk™wýü&½ŽA¬‘g1AFÈAø§äðû{MÁÓÙøæPkÜ“MòS&½s2›ôÁ‚Ï(¦ÐoÛȸá¡ûy"«o'…>Þ$L¿¡ULÁ¢J­Ê+…â¦A2Þ Ÿß8•.ý|ém5*`§O¬À{EÅÒGX#D›è‚Ì ¿dAToû¬1©^ûƒ’c2Ø]|þ‘2:šøŽ~Q å"Õ?»J‚©„PRlüžÂSsIìÚÅ}Ê@¶¢fr*o×J©éK(”¨J,_[!¢*YìQ³¡˜3G¿OƒÞK“¦ºE¢á’ºÁÆfí[”Tq/†‹ÌKGÄíþv}ùÃx—]ÐQ²nk5ã‰MRÿºêU°|W ·^Ú5ûøKv}|*Xr‘mäå¿·÷â8[!Ê)åúŠ·5_ßYVJ¨„‚$Œm‹¦þ'õÇM¼¿Ï}¦>œnøQ”ìùCÊ«äþࢫ®ãH5ñEk×T#ÒÝUJeï“Vm&Yc²Ã¤$ÄjÜv}wÇôq@ãÆ®»=ÊÔV±­=mÑãKÖPbcãÂø9ܰWLy;~xç¿r÷]x"Ù¶oÅ×Àë(3O ?‘eñÌBRb BÞËøß熦·ÎkÜvŠä^C÷ÔZÒù¸"Ëy¿ªšãÓãøˆÛ_Û]ì>Ç×Ãó~ Ê^øÞî-á+yL|ú}ãNÏì?Hµ§~~_¨¡¤ÌaGÇ=¥Aëæ.fÆGr* Ül¯š±û‚)“ÃܰVû «O® #\ôÂ7pSf¡" ã¨F‘V[[—{Ï%^¬eî1wôᨋu”z,+»ºgÿe±±ÀGPcˆ±b—ˆƒdn(6ñìK9—[¸Ž åì:C«>X 3[‡ú/DÆ?È‚1{?‡÷àÎÑ^ÊÜ|S:åÜSã ù›?_iCy<Ö*¶Â”«¼Õ4_l¦ëu?'ö=‡¤‚ïØÂï{Ÿ³g­Ã³êL›˜ê+lðnþ¨jùÆ‘8zªt‡&÷‰¿HhòŸÃøâeMxH5©gR Ô©hˆæSl×Ðôq+§a,e²Œ€½XwåõP Ùöá›sƒ ÷Eö’ ¿bî÷3»‘“}¿X¡q¶Ù~¨@¤•«ÐžJ—ŸR‘¼™s RÈ"ø-˜ÜÛ˜>_h±¯‡õsº²\žGuàõĽ6M˜!ÝÄ/m–u4S0Í–ËÔKB‘ÏlõæcÐwˆŽ’êöß_ ž+äO‹Ÿ· t½Uï(›‹¦.®þUž0ÚÑáŽ*”®QU‘Ðк|Bÿ0/¿ÇI½¥‹®¾RÙnñ¿‰Ú»ÄBÈ^S¾ã¢x‘;fW¢­ èzËÉâ.Ó§iøøÂ*›¼éó„ 7G Ej£eƒÜüôbÖá7ûøðEÔ¯`”•Ñ4H¡ÌFÊ=ÈÃ*vü:ÓQ—¨Ï—íÄ3ž¨ùnh>@% êñ6@V‰aÛ x»`» ‹Û>-¢]„GÀ‚êø'ê¥W‰_Xí n–ƒ1ÃaÁþÿîbT1^Oî+ú¯çP‹“?ê47Ããx¡t*Š«f‹K¦i Çh6…ÓÛáGC]Î<`½“Á?1fß̉RGćPÒû¢ç’¿2˜˜Möcœ„ðö´9=Sîh…Ãòl^¬ª‰´}[­×ÔIo~‘Xjâïê(”äð1»¿N»|Ƙï<}LB€¾zF O©†jx#㟈êâ&å•‹1hˆt‚5„Ì»žrd3,b@°:÷þe€„Nÿ¾¾‘R2äX%Hµ/(ºü¢QãgŸSã³3=¿çÊ\[ûñý¦9šy³ÿKõQƒ±k¹U6~ßû+ÿŸ”¿Wù cË÷*Ì·¿—L½s(s_Ë^>0Ötñ!ViñŠƒ~×—Hv¯øŸÞ»8éå2zZó”C†Ããùë ¾ÓìiÀë×µùûœ›äEpÍž¦;DâªßWt§§ HŽßMt‰,õ0XH±–=M²2¬||;Dc'Ž?Î#LJâ)©w¹^1×PkqnS$Æ»8f“ªT«È<[­þ9DÙz;xsÖ`&{Ä>3¨’‘¼Jly/5ïíqñëóéðô%GÊ"³0…K®ž?¨µÚÖ?‰VV>Â× =:: U2½Öíîx´9*=E­ÊwŒ ÓÖzUÔ\4õ†ø£oMFÈã~±‡}'ö)ý|%íPù?†¥§fî-¼™ÕQ sÊTÍQ¬¬µóK²¼6þœ±Š~qÆîïß¡ZÀ Ë2(eQ”ïBªˆJYUê(]ºÄBbŠ¿§LbVmuøPa–÷wì1­D zQ®µ´ž ]ú¿sÅZ~Z‰A3ËÓngD›[ëCHÃ9ôvu„EúŽ•À6KåL³éƒ>±6õ§¥.¾Ì‚cãÖt ñÙó ¥‚¡u¿¡óeüïËGƳ)J¥w×̧ñ1ö={ÁÞ¥¸Ós« ÙCp)òùî}-ý»ÅÇ…m¾Ÿ¸ÁÚ /ùþòãeòküú@«9žd¹o¾÷IZ’ÊsÛ·÷,wwþÜ5"ê± Çª!°*7ÂõÂ|:|IfÊ]Qx;ßÞ ¦ºy{QMÁÓÒë…„Ñ÷>Ï$6ã1ÇÚô¾QUo^¬=z¸÷Mú3ëƒîpLžž•uˆ>¶íª—ê>QÛ\HFt"™AFàÊõ€0BSÊVz&o;™tTKt‡z”ÝT èý`Óg«£Ô>ï?ß"’– XתŠ6B©à4Ù))Š#ŒuO;Äv…{!MÍ'£ªiƒ›ÐxK;{ŸÞ5uˆÄ^oF¦ÏÔã×Ã.½ Lz¦95z%wënÔm-ˆ•ˆFbJ$ðò†£òïñí*0º¶Æå¥ZüÇ7M}'݃cãp@/`ý`àº#vu.vF©þþc˜fæ ÏWÞ…Wí.;=ht]oÉëð'['²…¿BZ‰VŽú OºFiáÏô nx4S¼Ì{ˆQéèúÍP“ež,FW­`Q5jP#d¦DR¤À„¢~ÿRÝrïËIŠX¼¥ŒV¢»`Ž[á@:}ÿȪ¶1[@3â³}>3¢±oKˆíëDËåð:±Œì¶¼±ùöð —î’13!©›åÖàbpRíö"¯? OS×:ÄëÆ½(·ØŒ\9oÝüÙ*çùõ…°ž_‰‹}.  "&ªdvE¤E†õux0¢Õ’¦U~a„,fZ«Ö‚%¦/ï¯Â5ºacxó«Ì#–óWÜïìÐVý8PÃî¢ëù8Ø¡ÔÚ>ÇFÏ;‚ã¼zˆ³zˆRsn+_¤ß ;sô½ûnĉ×f¾7ÔÂ¥lÿ|gG _YCHŠ^­Cî±pþ¯ ¢*ªÊÉãÖ‡¤7 îÎìñp|:²ãÆÊð×Ö8ÇûøÔCØuÇËwjN½/ña£ôò¹Ñ¯´Oj"aæ·Z:Å¿,tuàDJÁU‘Wìí$~•ћ׺£{(`‹xô[át:”jýuðŽŒú–£œÄõCPK2†}¤É ‹÷ûùǼoQê)ï«%©O÷OÜT ¨H°kK´þÇ£•OÔéö÷_û¼÷ò˜¶ ï/+¬ÅþT­KNÒ£w¬>d¬nÕâdÝnW–Ä3×Çü…6ø2F©€A>OÓõ;#Ïö~¦e×~Å*ëé ÍñX Á¯i&]Íú7êe û¦ÑqJÏC½DÉ‚ØÃo|1ëׇ¤ªæ³ÀõÀtܰy¬·2ì¸ø¸ÔA¥ž‚Èí{%™«¬íö·ß÷ÊŒ^±Õ¢#O£¼4}_)MÀúžkø•G-·üJ°_8an¡_j¥2e:Àt"Û!¼z¸­)ä€0êR£¯ÊíÓáþüåþÁc©•ì(¶©í1iz7(IëÝ:5ÖWNºá©žxuxzسçši>¢b[Uè *,^²—Ëá´´ôA9 ðþíì4½~Ùúþàô‰h—]c%ñýü}¥ÐÜ®ž_C¡ØÕ—ksÒÁÔCx¹ZpÌâé²mK2˜Œé5…pyÚQ»>ð¸/6¢Ÿ*úEUoøüßk NG\õÁÉ5ÓÝ0Vnhb9ʯaÐ<®ÃaêXPš"ªùF„‚Xé _Â4Žó(ôKØãØ+gH°UõÇ{„/}_ö¥˜x›”?ök‚*”›+ºÁþÇ…ñÿ"¦ÃêyœðLE½¿ƒáû¿Ö:'LÃmö‹mÀÇhÅ•NbƦ*¥¬C…ðFÅT !\.Ø@m|”ú\v>ŸÝý°)®G^†UýÜÝìJ‡“=Ò1>´ Ú º¨”×-ô…TXšË+Ù÷S|€€Å×þ~O”WF"‹&¿Øå®ñ7çã([òýêË^åË/§ñ§O‰±âÿØËÂ3}¼è¿ä{Î_I²¿ dΤ€2pAÙGV&ÛƒP «,©R¢d•zš”pà®§o×ëïí‘L‘œëîÙ–ö.åDz”œJí È”ÓêuôËÀб7Í‘€.Ùí+á¡©nXØ}÷«u€YÐø`•%e‹C9–„¶žàÑKØ5ç¸F’ U- Âw”w”w…lâyå:°Â®¢Ò‘iS ³p©¯æ-)ƒ®·Ç^@.Û‚/ŠëÛÆoaãßÌï;øþê\Ø>›3çú—l?Ї·n ˆÉé±Ãb€j_´zd díú@=˜"Œ@]Áªõ£ÜÏ=8ÑÝ>ÒµiÒ?”ºAñÁûàD54jaËÓ›x×~CæKfàbV9WGQ: \Èãq©…Q©ŽÕÜKö8ì—òêCW‰“âlúÇßÛÄŽ¶* Ùøt‚iÍže;‚‡ *ÙåwHOn¢Ë.—³mw+ÔÍæ]Í̧•„ñ1P»û~ waüp¡†8,Ž¢!Øç†^.J]sƒw=[ûÍÏBÿÈïo÷- ëPPWº(³Hâ5ƒÀzy|â ±öðø{\8S™4 »‰¤-øyð' EúźÀºÀ÷Ù; °Ï¡¡x©‹^ÙÆ÷ÃÔÆ""Ûz2ª¯iYµ¢&ºÆõ^Rœ,Áu—i•ÖP¯ÄɃpøK¥šˆ çˆ5Þü!†N— •C¹ª8‚ÍÈ=®QÒXZâQëc mÜv×TZ¸ÙSŠ»ãB±É{r ._Þ0¯î{mt‚ï=3`Ï?Q0Ù)#Ó‹õ™ ÍÖƒzö¶·¸G½<]â@¼Ž×öTªj—:EïD ,šnYí7ú•Ú¼0¾S¬ h˜ñO$UܺÔy‡¬”ª¹1ɸB›Qä6ðã‚ÜOñ¯n¦ת“$C„§>¾Õ¯G¸ÃlEºâðÙ¹áw‰ ò"£ˆÂ×ìð TªV?øQ*jvM£IÆ«|j5§!C©ËDMòþ|Yô8(é*é ‹×€[Dy®ÜoYìDiöð´àuꦟjW£qUϸ¥]° çÁ2ñJZ>Ò™H-±lÙŽC'!ÞõÿHúž£QW."[¥ëGOÛÀ§ؽãÉP3ĘŠà”ÅAMòÍÔEUÜ®Lìàe›…›ÌÅÃq‡ n‘³m½ÄÏÃù–mä{ÿ±§ÛâšàuêgÚU6‹ ­·î8Ö¡•²¶@áÄpQˆêÆ[ˆòžcèb˜lž¿wÃ_É€:]ßç„wŸN%FáJðé÷¸TC&&ÐkQÎÅjøÚ=âÃ8‹5ÀÁ 6Ëwàá8ãµ@ª •È5©føf yb¼£R„Ç#}#*Hät}Oßß×ÑlEF}ˆ)®ze½ñ{öˆþå]°m—A¬LvRb³1jó1ONvŠYëùøÖ|·o×iÖ)ñýÿ‘³Ò”êOõúzhƒ.æãwîaâ)¨4ÜÑ|TÙŒ@!ÖWi [mâo”ß Ž'r ­ŸÄ´o¬'Xê~}l[zq-•£ÆÔBOE¹“|Šiö…z"®]û˜Ø<.¾Q‰|ÌTªúÇPë’øRÞª솓V’àªÛ?³gùöô”Äé8~Ú&bb噊Ƭj»s¸–favˆ››_2ëq °m<Æ,x%qZÁŽýÎ EÅÍèþüÀ- ßyzÞ!Û”W—ÁÇ×QÚ§ 13\i~ЯF⫟sq׆¸Þ•l±"-æIì00h ~~Ð_fŒ¯Ô•c—CûD©ôúÝ?¾1Î}! Éþ鯤¥Ð¯‰˜ z „»)Ÿ¬|y6¹¾‡Æ"%YÛû¬1­=œ}uö›oÊkÜ€Ó-슟kÝ!L’ž$󊗔Ƣ×a5ÆÞ “§´1Gj>‘Œk»áå˜UêebPÛ1Ñ.óÉrÞño|.Q1‘Ç0.Ž'…Íáß*–0¡Yÿ& ¸&C—>ÒÝnZ!W^俬£Á”ÉÄÑ|Ž2놿ØU…‘ÈR9O›æL}OÞSš?·Ôú>OOŒV½¾ÒAwÊîÈfÏe8.)Ÿu(6À i—3©½rÛ°éˆLTfP4o‰5ÄúÅ(•ì€_Iàþ¿¹àþ¿¹àþ¿¹àþ¿¸@¯‹û„(Ã=0&Èш¡£l{ð9I2e–2Ÿ2j K•ã+Ék]—À0¬ÔŒ³¤A[Kño/ŒUt—6V6d”sÁAlaõóx©“ŸÞ3ÁAlBaâ-Ì[o‚†X£Ž9¶%5ÁL@£}O'ñ7ôû=¹ï^<3¯‡¶4]ò:²³Ù(îµºàØºƒyåNš–ëLA%"î=‘‡ˆ¿ŽPÏ+1Zj%˜­YÓ›mðʧUA1âŠôh&S3:V¸­JÍñéQj·À¡¹Ð! 9  D ±ûñZ+˜ã·&¿ž§÷³Ôå&‡‡— #ÿ<½´šAwõÁÕMžÇPË,ßV¯pèG\‰ö?øå2Ž˜&rÕð§Ò••ÕâJ aL»â¼2-‚Ò7§9ÛŽYåÉWšY×ÉßÁúƒj±:>à H#’aº{'l '_V¼{†òšÈ'Šù=¢e“ëʃ9`, Ù Ç1N±:œ¦Ø­_UP[BäëÃèr î(–‘ðæÑǧWt°[Ào€RtsäáŒË§¸"šÜ@%žÆ+z"mîÍWT•€Þ%Ñ8"lözÃå÷åz·Ù§žÝfÑm¸ä¸_]K­p+®)k׫6³®W†â¬¹NïG1Û‰Ôâ[Ê9Û.‹mJ:2þûƒãë¯pª»%Ã]dñꘉ¿beu>îô¨.’ªÙx£c4ò'Od½Ýqóˆ<¢¯vo÷ÜͳÄ`¸·õ,Ê6åôûô§hã2—|„¾%Væ³-ä ªào¤/§#ÙÇÄ¿pÄky“Ê,”V`ôŠÛØCÝà8—..áé3ð¡3ì® wæ_6ýøu’ǤÈñàd®Šòë4˘˜”D¯CLDæÚã’ùò£”íÇ_¢‹Zæ0ÆÆFpfT£¼ðK?ðzâë.—“¿|<ŸcSÆÅoÙÓé,Ô©”Q[{ÛßÝðºÀ`z'–¸ fg|GŸûÏÔ<¦³ðÆž°¦¥ÑD¦¸ W”bu9Â-°bg½Lê9Åo“G#Œ!ßž¾.o€ÖbðZ%Ôë·ãþW®k$—Ø÷ëÆO‡O¯ —qzpK¨›zõ—^òŠk9‹Rý½’(Ù ]OÏ—~ÜÌ/F)ׂbà÷•[—cÀ.]¸Ó¾S¢W `,ªÜ¸{8[b牠Ëñüg&Ž"$éLªë(ï-1TL ÜèQégÖ/ð9)«ÿ‡ä/¶9PCPn]”ÊÍqwCq+ \®Ðh^MÇœƒè^ü†žmâÛ|šz@ëŸWñîB˜”U0«+´ÑÁŽ«1eƒŸÜ3Ë­@ó?Cš` m׫ FæQQΚÿÁÜe>Ï °Ì¹™y,sTõƒL°ÒÆï<6+‰QQ pß`Œ%›'ˆ©äÑçéMÙU@\pR¸mÊsW3Zü¿C¼7÷ˆFè^¯è€¨f-¼ËZ÷–·,1çË åŹwÁEôˆ˜}LR<ƒL´hÓ*Õšä§Pã*iÿƒlîûšýr:`Z(ö+k”fÛb^n"pS1V“¶ð j-à„ˬ xr +¤ á™àÁ]1m¿Jw‚’ÊÄ0]ˆÂ\Û|\; è_†Œwê¾o¹ S¨µìj`8‰P·qáÓ”ÜQk„³%%ñ@¦º\ (^£|;Ì,NÌDߨs UrŠÕJb güQ² |ï?÷›5û›ÄpTn%0j0ÜVðÌx0̼ŽZ¹‰}¸*'¦tâUY²·»†ÄðÔ{sØ{è7õî@P(èÁä²ãCU. MÄ%w–ޝÜE®PêQÕ”¢T®¥ŽãIUÁ"oÓêz#J¦›â*ŸðgVBV‡—ÅMþá‚YÙÁÜ'^F pìå ‹|ŽÑ)Nd s¢èàë(à-A3 é.µrŽ˜•¾W·v|«äP^·òˆÛŸœ ŽùüF¥ÞâWsg&&%*‰|‚un¢¶ô¤]ú$±ä?þ•Ödüþù»Ð…ÞdóLà5îø »=bx G9äbSÈÁ̵·ÍK¾¼ËX"x÷ò˜ ë×ã³oü… õ JÌM¤×ºÌaÉ‘ó}Ïr)ê‡XQ ×Ê;ÁMrZKï*’läîŠ&'‚%í7 1ÝL8—*ƒ=9õ¸D¶äPjŸðw!Òè:ý|9¯Œé£ô›ã±(éºGY§wÑûÀl³¦%C¼j7X‚°MÆê¥5qŽ,¶À–íè70r¨n4ææ!m¾ ªäÔ±Á#‰ä"¾.¾[ö~5‹×Æïé3:=a2xc€[Q/3®œzs.ŒÝʤN¸ÛÖÜ_‚&î5lÁ~{ŠØ/€:‹Ûж#·-f íTnf¶_!D2Ùÿ„:ߣÞ#ç6¼DÀ§éò‰L [Oâòê- FË:·Ö%»1gȸHN6Ëw‚¥¿@c/6àV¦üœ^.qª_^ ºú`÷#?%õ×Ó…5.ù\úøòtäÚfóìgõþðŽø[lBR]bÄs 1väÞH-¬ó&y݇ü(á=¿È^ƒ³Çñ®£Ø™²EW2 \¬qqèHÓ¨*̇RÒÑ“jô(qyÆn>ÌyžgùîEEYÇØ”ØL×r¼ âOx’»¢wO"©æïë<)çñx7~‘K¬Ì1gЕYfÙ€ðÑÁ Y7³]ˆ·¼ó™èßþ/kOáðûMÏ ïæ¥é¸Ý‰6ŸíF`é ~…t‹Ó•Fm|ÀZ")DJ8€¨tæ[,Jš jíèj1³Kk^ã…ÓÐ}0ñ1Â^3/´¸ôƒ e•i/²EÀ=AÚSW.i”gD—ÔLü#Wˆià남¶Íc„ÜJ&¦õÃxpú½¼Ž±Fý hôßü0šÙÚl,öýwˆ<Žê-óºOóˆ³küp2Ž+EžâAjYÛC˜ayÇ+·è²Î„1©m¹3íwÌ1ž sŇÓÜ›utÝïÁ™j¤ÙÅÜ7:q«%@ÙÃwÆé\\)ÄaLºe£‚ *c¹ÝÂ¥ñ,Ãít½j ™Æ~„À¿ ––.£º:i*‡¿ $|Û—;¹ÿ‡‡~=ߌ®Ëú?ÞQ¦jtu†.¯óÊøÜÿ% ŒÅ[&GåÉtA„6n\¸™^:ŒÇ ô—Þ]jk‹ iô>)r¨»Ÿ0}ʬ^O—Gá®8¨5Òa%u¹†8ÇÃnMK{ÅbÍ=b#O ®3¤-¿êˆlÓºün y\bÓZ<||¾ð¦ÇîŠÐ ߯! \‹¤|•>£þ"œ'^ñhíÈ’ëÃçg¶¢‹q2Ê:•T,[,r*e/ÇШL›#ws¼(ï ôáƒÒ«÷4…ö|}0*ˆ-ÐãÛg›N)ÙÆÿÇ•Á\”ð6åª8ÕY©^3Ù}å@ṂY€¾c h;㯗hЉ¤õ®{H†ýÓÿÃz¿»€ÖaÓßÜw3\-Šê ¾óÄØzÁY…Øxnk… @¥rÃ…ËãÑ%µ&ßËÓ‹_R¯´kEâï.;½·MqòÉúãƒqk\šáiW¨u–Ä ƒŠ"¢üÌîXhà/qtí ã†U蟂þ±§ÑŠ·£ëá(؉¾Ws7wèAtqÿ…0y>Î Ræ¬G“,E¶xp Ä­¸œ\Ð#h¶ÄË ‘pñ]üÏ´YÅ3*·hm˜p¤‡n]Aé‰vj(Üú¥›ÔW G,; )+üåEfâ“i™xšRÖu%£˜«£/¤VÇü`hª¿äd!¾,@S6ƒMÊ‹€´„Ó‰Œñs4ÆÆî[“§¸¸ê…u” ¸7u)àÝb%2p3ˆu1CRÎeC-EبaEþ_˜å¯s’h­ ^Rš•[Šï·Ç~"êú~`MºUMMôñ…‡ÇÃAÕVà­]ü1(>ݸY½C 8Ôñ¢AàÁ…Ç¿?ʳùæË3yÖóƲ?ŽO×Ó€J¼±*— ŸñQAtÍ¿-~9pRê VÊØ•ĢޣÔjd,½£¤µy{©k^¥î¢\ÌN.«:%ˆä İ9‹–†XÅ:tˆ_ \¯‘ùˆëê• þ·Üÿ¨  tï…¨!UÚ x}`#Þˆ –ó‚·¿ Ü£¼¢Q(Ü¢Q0G98[— iÊeq7\mWˆ|²þ=ìáËå‰dÔÚÄ´!u~†_¡.»¸)¸:Äõºkí|‚e%”-J¬prqÄ« ) \IÆaD{LÙÔ£URÆÿÈeH£ÖãG9D\¹q* ¨'BÊð¯æýÓ6wOãñ-›E¶ZjZË–3~C$Dâc0¥¨íåÜɘŠGð¢Û>mßÓѯáÙà˜pEL³›…c²¾?ã•MÍ'üKO;ñÇÆ*ï÷à^-*õÀk$ÆøbðtBÆÈƒ1 uŽ}âú2§W@º;u•àOÝVõv"xã??A¹ÉÒd—ÞPÃvw.\¹|0eŠ,N<£ÞVnlYÝ/ÛÎ\£gÝïúðôn«c'çÊž°ß ¥Ñá—×??óþ.€ Ñò~užr€‰Gx•¸™‹ÓŽ+¤»DT"±5‰uÉL(ÕzX9>7‡œÒÍïúýÅVßuì¹ÿ üåÇ¿^7mµxŠ­j #ÁÐEfT§r±Ò(K¹ã—ÒUôš •£±¯Hˆ% gŸ'õ0VK­ÃÓW„Q|M¯‡cï_øP\DÃé4ÿg§ÖSû¿ÖWŒÅQ29”¼0Kð—á]qú¥Ë€1Ï»ˆ7ã)b@ ‘/r—PS€±• ¸˜n_ÇïåîÎc¡ï+à'Y–åöвÙlÞg]ÜÚ!U"C°Ké"Û|ç‚6ÛàÔ³¢ªe·ëÄÚ½26ªT=Ì?¦ Vð4H¾0<1ét]û´è· ý` ŸÏ_aÚ¶¡v÷ügÒh{fTü8èV¸ÜV ±ßg%AÜÚ¦ \µÔ bVâÛqÀ–v”0´[૸ÎåGmãîÉÞ¿ˆMRóï1iÒê.»M¢W(YD¦hÏ ^Žƒ_°û IߺE”º¶»ÂZêŠj³ØïíjuyUå׊Š(gˆ\[n¾C|¦0Û)"u€Dç´³.£bˆ-"¥xËåö2ŠïëîÍWdÝp8òrsï‹Äo˜ ž/C,L™‰Ô”²ëÂY‡10O‚ óÁìDÒ ¸7¯sÅ"+WídCU—àâ|È•ÄÃqì‰]y ’¹n¢+'1VQ“n ôƒY–;•\H—ö&`?Ÿv­Ö.Áçß”ÄÌi¼‘W|›EWЏ&ÕúÆ G'ü ¢0¾˜>Xà.¢V L3)àïܶ[-–ñK*bkãÎ6S2ë5,]|¿®[®¾í-„"'ôð|;?Æn`â‘çÕ†8ÓWƒÝÚ屚Ï"Ù—¦_7>È×#¯øTú__„3Ѧ¼ðÉÉc¾Kä=RÝòàäX*^«ooöTt?7ÏÝÇx–w“Ë·Â&’ÖÉ}’ÍkOÒï(xb¥‹·ð›³Ö¿BÁÙûbŸÎ.£¸ÑÐCœÊ \¸"à{84tócse k€žÿäÓ2 [ßï¦tÂWnGS¤Ä®Òªtç¬[oÐ7u.QÁõz¾ï1Ëkø8§& ›xÑR¥TÅQìÙƒZˆ@4÷õÖÆX>[†¸hŽrq"ßRÝÀ)m”•®;ž)GxÌi¼Ê2kCùúAA’ËÅ|_óÝõ»N>qÈvaør%)\£Q:œER jcãç‚1érøkÞÐÔSpt÷î¿ûpÞûrj<L¢Èv 9ídÓ)‰0Åf¹«ÏX…Ñ£Åws·Ïü×¼•¬3÷5™tÏ¡`-̾äTòg…ÞÈ…¸"Ùwôj)¹¤÷çì>¸áiS¯+®/Nj+à;±V/f•Æ÷²tä|ˆÍR»;x¿xE:§f;io÷ ã–ºñ a®`*bxIÝú"%{[iïÀ]ÃîJïˆêÊíZå¦&W§ï§¼T'{Än!³1i–vƒÑh×2l'}4wñ|?»ÄòðÔ¼!DwQÓ(XK©w¦Ü\œ†làØ@¤:p7« ©ŒT§iQM¡UŒpßçÊ"N—Þ0©ÔPkû»ÀJФ®¬·€¶+ÌÕÎ?®žQÕ¾ÝwïHÅ z$\·ãEü¡‚Øp[Mr<^܆3ÀŽ8‚ = (^‡ëÉé¹Ò¼Sù–‹ð¨µäï,¨~¾_¾hôߟ_xÈ]?x›v}œF¢ßj¹¯Eì:Ÿ8åû}¤‚ïÞA¸Íö¾˜Ç‹bV¯Ï…6¨Å7¯ ë§O@zÃ5v|ÞŸ-Å:MM›+‹¾!qnü Ì^'Q•ÚR“!†Zâ.b wŒ¶þâåZÛÛÀýûÊúÖ/å‡ûÇÐ(éá“,ÀCjµ^.¿ÙÕèÿ±$å{¿¨ƒ½ÝËúúEm_¸ÂÒ:÷¯wf¼çi²ÉBÄ«äÌJx$©ÓŠø_ 2­)Ùü;‰VnÏê:…Ç0g(­À×’õ¾ŽžñÙš¸P4™b:áÂÌ3m‰)_?sŧ#¯zKÉ÷½x³eMÉ·‡NT™•Ãn‘¯Iß«ï>Á÷\S\4÷˜ˆ¾™ù|Cãp wãá<È}óÀz1Ú‡­ ˆK"IZŽîªkºÏ86¨•»ñÝ€ƒCóôm´îG½ Á†ºð~¸ãÆÍð2‹%È0(,í11+´éÀÔvòžç0YyE«OŽ"°ƒË/êÝ[Ýoéµÿ@ F¯¬t-§_zPÜ Ïx·PÇÅ(PâÚYúL»ç[8©_8”xñÌ[­ycí[êšH.ý×!ã°>=e%ȼ®³õ…ï‚emá©¿@`ˆ85V|ñ±îH€Öþ-ÿÃ×H.æýÔÄVÕùûÊ“>Ó!`ÒÉDÝ5e»¨‹¡ëS5_¤6t«úÔ¥[Úþ²¾L}`²ArTK*M»~qŠOr]€8ïev€ã«ÿM 07¯t‰__ýbY{˜ºÌC¸©[qðŠ»b Še`¦™lXºùL/~’®Ÿ™0ßI˜wàÿ®‹NG'ºhº+âçé,‘1%&*ú ZëÇÇþ?ß´ßý|S\޽Í"/¦~Y‚¡¶ã®†X´;÷˜Qf&&&&&&&&"’“Â%CëðÉüÍåÿ± j$Orñ†þº„,õ~ûb›šOqyѵÌ]k;}å õãÀÊXW «CÍ·ÇÿŒ@1)mà„þ:ˆ²°^š?—êâf)®Ÿ¸á>' ‰d@§‹*²Œ)n¾ýï Á5sΘ/3Íž;%örs6¾ÿùBX"ªûì56|ºÌËâ15ÄV¯ŽñSTÇÞ]@#Òoþ夂ïÑ}Sñ:¦ h‹…ò˜§yeüæga~¿7ÿ°uýþÄj_Lv¯ÛÇD&W¤Eo­åýT#°€‹¤WÂÆý‡ìÿÉP.¼û¡¤‚ïÐuŸÄþŒþŒþŒþŒþŒJϪŸÁgðX«Avû+ôËæþ¥Ïù„»h%*àË[Rü%øKð—á/Â-¶S·ÞS·Þ:éùù%웬yÿäÁíEÿ~|#—`?Y™ÙÜ÷=4‚î ë—ꟈÒêkˬuáˆj1ýƒ”úïd‘»Ó?(A¾p);Do÷~ °ßãÎ[zZ%qÓ”Ñõãoò??ø( „·‹·ûÊ!Zúž ¬$¸lçÞοq tµþâ$Õ¼hþ¿c}aãŒÅצó• VΪ^·³ ]%ÓMYšq‰§-­UZêêªõjHl仯 ¸ÍÕYZ"\¥ÑC’*óMcyªk­=ûDv«H˜\‰uêem¬Ö_×Њ­¤¾c-5Mv#Tº“œ…yUÞžF±Œç¼ât-¼ã&*ñW¤u¬Áœš+4·K*ŒË]4¶[±^1U—Gtʺ5§uu®Ñ¥N/VÕÒ÷Ó`+Ja2aÑšê˜ù“ Ã25+d?HÕ µ.þÿÕã[*Þ×ÖdBŸœñ{ôzng«‚_î%×èô‰-”ÒŒÎÖ(|’6âÓC Z¡lÃÞK%Õ>Xk© uI³R–¨8F«ÁºòKA!Ë5uuuW].àwË]ËEµE½Ø=ö[t‡vRšëPFtÝŽo¢ R™Ð²õŒ£¯G¬¥ ÅŠºê+å˜8l[èíéX¶ˆçX«Íbë‹ ÔTðÉá“Ã'†O ž<2xdðÉ…(É^ÉD–Àƺԫñá†Øuõ!Óéÿcí‚?ß?ø‘ZøýJ‰e(uëýþK±Õö×Öj9Ä5ñ +8lòt·¹¨ ÑZ´s”uª1¸vaeÐÍ;¾v¬hÒªÎù2wëqÈÄVLäÚúï[— UØu>*¶±¬7  ⣄ª»êæñÚ,¾I×'Vé¯(ЋhÆZ%-ôº4‘ˆ–šiTÜï„ð³@-¼ÒV+ï#Wx£ ÆZ£ÅqFHvõߝӯ=5@âð˜ge×ÖºK‰JÏbï]u/œ8Ù¥gaßr£R3mŽ).¬sÚâÐÛÖ\.²¦N·÷ü«;W·MÒ=ãzÍôÉx˜>`‡Uôn)YŸ,}ÿ0£g9ú×ê Fõ];|uuÕV;0ÍW-ý` C­ƒZ3ÝÙãCdraè`èøTöFQl¼è:|óCÐ4ÑeVŽ‹ë²ã+¥Û½œ^{ôºŒÔ—…Ù-xl¡ëÕï _Ëï¾ý>²ã…… umî÷2Ã&k‚v\SN<ºÅðƒñP*4(8Ø)ë‡ç幎 Uï>?¢=kUë½ÝÓëáÉ·åaŸÿÄ-!1 AQ0`a@Pqp‘¡±ÁðÑñá€ÿÚ?X¹zSƒüú P(”£|¡àº¦,µ‰l·U™@ò‡>c”^#’ÇÜ7)é®3âù@$ ?A¡\;„4ŽèÀ¦½Æ,«¹|3¹NÜ`Y Jî~:hvs]£W”Ùe¨-¦>Ü-t„[_ßÿɘÊú7)öbeÌ ˜ˆ.S-¾ë&±ÂËš¨¯má…?~ºË.7P©_™ƒLU R¢\ûOW‰QHüË>† xTa¸ÝPò‚4×÷>90qZÞºŽ12øtÜÛbÒ ¹¿½#BàUÉvŽÚ`æ1³00²ËG Åßýæ´ÜâZšú=ZŒÆ„bkMy:ÖUïÃeÿ‰Mnà´3~ˆÎÞ¶F `£–·j/¿o Iåƒzû±Òv‡€"-š‹‹ÅS0Χi‚0¹ÛÚ¨ª÷ú<®  z,0¯¥ Ô@j 2ðÿ÷ij¨eßZ–Ž(/ˆfṘñîMO,×ÜBà.£xJ›óÄâ7§Ž„¶4Dϵ&9õ’.¦õÑ©â³A]±£z¥MµcŒè åÆ,GEz®ÐÍôRF @ð¯l/////////À´ÎÔdíÛÁ-ÚRG€Â%ÌÉšèÞù¶*5Ö«ðœA •ë’þ„sŽÓºDµ>€o¤b8ßµöê ¿Q¼ÌÔýšˆw‘~×4I€~„GÁÖ´¶+f"³§pŒ1Æùc0ÅÇzâ® «ïâ˜EâÄ­ú¦ÿ£$W£O#t>%_kbQç?HÞg컜¢Vîg½¯kÞ~ƒPVgX3 ¥þz5¬<»DnÇxÍS=ºš77­ÊÉß"vE/ JkÑ"ú†Üñík5soT ¯DÌìωûº6¡¨©`YPŠkíqOçÖÖâ:8埃v@9kP<ȈÓЫ:gã”@ëЮ†¸X#ߊaµÞmú<$\„flW5¾3Û•BÌóèüÄCnœö±88ˆ#1111111;LLLU‘o/ éÎï1õ•šˆmaŒ$Zo|l©ŽÅ{_ÁãÕYCA^¡°}6l™ ±¡çxƒÚÁœb<TºƒYˆ1w™žI½Eî=¬E."À`:€!ºãfõQ¥pO††Òa £N‚+³ÚËt힃O¤mè­“ðŸ„ü&’o1õOºFc¶Ýú §§=Fù8N¦÷–Wüó‹ŒádNÅÅ èF9ƒ­S¼ŸñŒ(€Ò¾{Â}ˆ3³þüÄM¶}n#¶7—M=¬ C¼ºäÓéež%ž%ž%ž%ž%ž%ž%ž%üJ,ôáÌaò}=#å÷ÛúõE{Eµ¾¦×D1AÉÞU¦4‡[x-½-Ã-b•Ý3Înhb]c‘°à6‹+OÅø€n¦–’‡ý„/P:aK»%=°4úFÝa|™Ã:ç”Ò°œ@‰gÑ;=` >ú —ëÖé¯U‰qu¾,¡\ï¬ \À¡^þh#£_ß Ádf¦ë¡|ùˆu¬ÀNñDùì§ýãÿ3Íû`Óé}+›tT1KqŒí@޽|š˜3å‹"§}ûAl`f/"5ÏÔ·ˆú´n8ÍÍkŽýdPŽY¿AÄÔ+{€‰¨A«£Që(¢Öÿ»ÀÐ)Šöi,¦ûþ#¼}xTêel}!äÿ¼ÊbÉLY 4ËUÊbW@;€=úp•×´ØÅp ޽MjTy†ìÜJ=„ƒ²æ53ü@ mñOÓŠBÓQ£~xÖ¾€…*—pL·1¡¸…¬À>•0ÝqN5P5oñx\Ø™8ÑveÕ~À¯ðëÛíj£<}/#µU•x¯î%|ˆ¤{_Ïÿ%Ä»nSwãÌJ¿ÙtâਉýÿßÄcƒ|m+Ò)¨êÎ÷%×2ÉL¦1ˆÒ¾Ÿ{•å·?0Kwì}­Ô~ââÏAZ=|ræPÁsö”L6•4)êüJÈÃüı?0,Á“Eû÷”.ãÏiZ…Á¨çæ"€Çת¤5£_—µ)”«XæS ¦S)”Êe2™L´ 2™L¦S)”Êe2™LÛ¬YP¡Š«zpL¨Æ2šWÑÜ!XÏõ쑲þžÍA°MejcòÌxè¨gSç„h7)›Fe… D¶ÂÅŘɲk@4YÜ¥©•±è¦ÄEL%@! ø®Yß÷–‚ée›VaËþH‘i_ÄßFõÓ½@\Gù‚šjÜOÇæ8ŒPñíƒO¢Ö3(ó(ó(ó+¿@Öx¼¥æ&w(Eñ§yG™G™X¸&WÌùLLLKà°1‰¤æ g §áî î[UÂñgÄ:â|*ƵÏzŠlƨ•ÿê ˜áÝÁV™B]åß6÷o•¢d“1|‘%n£]]8ÊßÛ´ JÎà›"¸yïßù‚ö_*ÇÎÓÁ¬35¸ŠS3Ç‚&StM}.­l›FZî*û`ÓèíÑÛ­n«*¦-xàr馮]î%±0Ô[no35½³{‹Éî¥Gµê°Ü jÔüz¢YÓ˜&½D0ç–»ó†Îñì5ŵ BŒs»qf ÀÇ?y >ŽÝº´ ‹‰PNç4—n€¹•Â*À]J|A>cªJ5óî'Îö¦+¹’¸#S(bʳËôQO7꾤Á¸õ0&Ø H";סIÞãN¶~Éï#O£·Gn¡®;@½D­òÔPÂYâYâXš…2þ%ž"Ò•.»KÃ3A쎛毾0 ©EÍ<¨ïôBÚ†e¹ß…!³Û&÷äk¯¬uÜKßæuÑ4ˆTwêÔ¥ù@À¯@$6š²~ ƒ~Ù@¸q\¨ˆXËýsó?}j{Z&Ÿ¾9ò„Ol@u/^ 7qM ¬éðèl‡³\ g‡Öš{Jú&]Á!)Ol»ËÞògmï‚@AB#fwI…0}ýQCL¥.[#èêÞÐD³›Lã·¶w1…KÅA¦%=| #=Ò,‰s’ÈdúD¹TÜ%s.noêta¾yr5™ãµpȺ•^Á,½åQ(¬%™ôdpo„¥0 ª‰íc(ëŠÓÅ[±À. ®rMº •6!°øÇ VACé]‚%53ŒÇ×E澫IÞ&þÿ‹möª›ŽÃçŒé³_F)I µè2¯™ L  SuíÒP/;–Ãøˆ#‘Áa…9—áÄÀ‹É—1]pïcæ¾(>•,©‹ ¦H,>a˜jUCü&#·Õ®×ßјªûûQσ£0Á³fXQ|ü÷™µì÷U1ˆŸ»5ìóÇÞ_籦k„¥ÏŽæiÉÉ|2Ô¸ybÉ >ŸŠ?Àè+Ÿ\š@Ÿy·cœ¾ÅüO$Ê£ôCL ݱ= B 5*€"DBªhóÍrgšbW°®”³Š—ã À1ÉŽ•ÛÌÀ„£C5 .-¸{‹X!¤#GÓÚ»¨v§|Šx´…DÞ¾¸Zp„Èûº¡ì}îÎT;þŽ¥iá ½ÚŽ¨Ô –rè.mÉ–yóÞ`Ý,E4À;L†y½{J¨­OÃrÎI¥™QðàS‚ÓÃsñ-â1v8"o騨D‹¨ -¨½ÀTQnS¼ >´S'}вáZö=bê )®1dÒ¾‹æ l'-Mª-p›f+ÈAïÖhRá*p.` F"ˤ£²èòÁ5 .4Ä|ÀñQု€wå¬TÑõ+W5×{Œ9—ÀO‚81[DK/׊dˆ@i÷%ñ=’·íÁˆ’ÈÀšú-•,áK9šf+·B*nxóZÂz€R!*f-¯°Jbˆ¡ =5â+¼DÜ0,¶[̰Oxel cèWQ•¼Àf`Œ+;rüÁm¨|¸‚y–äx éÜLb ý‹Q Í'ÛóÞeÛo²JÝ ­'j}d¸ÏÌDJkïõÒ—5ýà‰L¨ÌµŠn¥V4”WÌ:—u)wãÖßwŠã—/a5˜#E[ ¤¬ñÒ ·ý$±¤§¨²ƒqh¶ëì@Þfƒí´ÙJŒPúÏ pïz¥"’Ó®Auõ9aiŸã2íöa¼Êªx†n Y—E B;Ëj"paÄ,õ¯–aB™\Aa^€Bd;#PõèB#T±âJ_F¸Z–Þ¥ý‹iŸµ‚ÖmŸ>ÌR^K­^=«M‰qÒ²t îké5¸å ¢‚°ÎË[óo¨ÏFõ>?Fª5ÞpÖ •1„ j+bÅCaó15©@úXõžúºü@[¼Êuqt‘P-–T¦Â*ñö—á1Qp²(Ê  1/…•ÛíEh½š¦zäå}c7Ä0° j6©ó¡q±ÿÖßÒwŠ`™5ÿTÑS|Û_o¥¦XΡuWa&f_4ÓÁÜÉFÅqIÈ&8€ìĽ@¨ܶè`ß Ì¡Èwu1­LPcÓ×[»J&=àXª‚ô<}¨6˜û6ÃÅEVëÙšyQÊT áuüþÿ@‚S˜µñUOÞTiô€::ÐD‰ðKú:÷q0;ù´ŽÑrµê=fæ¸Gk2¾°S$ MÏÄZs­öz;¹ˆ—’x«¸ùŒ¾•ËåÛ(‡’6oü'nÉG&,[UæÓ[Š?î ?Ô[oó<–Ÿ±é"7²›^Ìiè…ψ}A§Ê|šüþÿHËE¥E¾Ìøô äÐÖn¦¾…mÙ×.× ¦cDHéPú6{‘• Í0C˶ˆ­Ä˜YdÀRÜÅŒ£€uÞ ðÑÇÌV_>ƒ ïéµÞ E«Gh2ël*X…üækýÏšWö]$õö#¯f= E§q?NÀ;?Msô?€((ôìò£çÏÐÓGÅB²ô'¤„tvMÀ-2ŽÌJB´øÂT§Púj Ñ©p[U]ÌóSnó¢ Qw•€®)Aã*¯š â[¥A©b¥ßoõ+ø?¸¡tªPýÏñ Ÿñˆ¼"Ápm…„Á KW¯±¦Éö Ùž#¬ÓÀÓM¨óô ñ@ kÔ¶.¡”lúƒn‹nÄ| +®ƒ†kJüK‚As‘¬“ö!¡0Ô@¶¦@ÔDÜÑAbVIòÔ@LËù’‘’]„c¼=Pòg̺LÊU¯ÏhO“ŠŠj/²Ú «˜¬ZJføaÇ@«ŒWѵ ƒ@}š ^Zõ‰Db2íôVn+~;Jè>¸Öæ"aáSq)äF¥ŽG˜/0¦«¹L’¥xp(\Ú;'Õk ­¸>ä«28”b³*t…z™€¸>Ä)“€ \äNsèÛgxŽÇ¡Ä8±ú \’£-âVoê.^¨+D/'èn‡*íë^^¦Ë>¤Z Ÿ„Vã£J–Á‹mô…3L†Ù_áÅ×§J¢nÆPc1͸•ó+ædg赸C·Ù2D 4õPÁ.oN)ôl§g؈µÂY-kàˆ}l@o ú´¢1)¯N—D@S¿Ð¸Íðí O;–Ÿ[êyNõõ(1ë1 Z¸“V›å@¨Dì‰ãÄÒ5ª˜¡ ¹ÝD+IB@«yÿµ>`ûje¨„WH“-K²þ¾‚0@fÔB4ž¯ïÂ&Û¿ëÑwìû ÷†«Qæ,äªyõ‚§×'ŒúXn¡Ÿ¢ÞÿÍðÔ¡ˆ…p/ÅëÔ=¢¥ý{ZJ‚0XÃ$†à’Èmˆ¶òË_̧Úâ°A9©’ª5 Ýõø}¦CyšON³Ý}q¶|û?æ(ÛϘ/Äzbйâå>½œ=ñŸ¿Òï“oèƾ¬°£tÁ‹‹ÈÌ»†Bê[³ K`T¶!`aý•·m@þápüܨ/°ÿ¸"DmψÑbìˆgë’¨ÔòûVÓ>ˆ!€€ ô‘ì[=£â#šOÄÏÑüMÎ`+ì>`£è*??RL¢#O¯@ ßÕ×)s!)e0ˆÔnÊùTHÙrÇñr”“qÄRŠ1ZŠí‚-·Áúý1-Eïïȇ8µ>ÒEMÀĦ-ÐÕuë)ÀöC#ãШð~笶kߪ¬¯ÙWÙ ©K]2ŒÂv»E%Å…¤¤)¦×´T%«()_m ¦=tAÊûLi²Î §só>%K÷5 ô³õ¢ˆÊ¦ºÁTCYúFå@õÝ皬nƒÔbk·Ùß°Cþ¸V¼÷¸‘QÚ¿¹Dæ¥@w¸ÉôZ†Zà¾ò—RË©zÐDo©¶y#fjZ\v"Yn)?>˜le%ª¾·—;õç<}wæê–„ãýG|j8´"ë3[ô–ÿ´°îJ¤?Ìß±(–Jv}Ë¢QWlÍî„´«ËÎq(‰ã¡,¨¹é ;›ôÍY¹QzuiX¨ªžª|M뇀iæ'I™D1/ë¯ÎñGªŸ“è\F1ÕJŽ£w/Pÿ®£Œ2‡ã¡&ûê".Þ€+D(GíN× ýÄ•·Û«•U_dÒ9>øu¦å•ÝýF!A mªô`wž!±šÇS©.¨™fñ÷e¹[™oã´n,ï0iæQ±óþSö•-‡‚4ê ?3iJb´² ·1m¸™Xvc^‹DR™sÂ4ÎR˜GpôRÁ”×Þ†µ'I´ '¶LÐOÊý”Ú™PƒŽOŠÁú8zëqŒ,Ì[Þ 4Á5‡]ýÐTáÜSó¨Yø¡˜˜”evÅŠ¼E‘t%›™®a(`Já~ \°Ô´¼¾â •0€¥#eßCo"Œ²ô@,ûâÌÒrblÜǶËíf‘ÀÜ' SnÐÏR#ÙÜÃ33惘Ôi\ ™—µ¦J%á1‡7Q6Ø'WuŸÜ±Á+—·þÌ@ܹ3w-yKµ,Uhc_ª ÓYNÄ*¨„•oÛW÷݆fƒ”ÎÁQ[íô‰gö*x uö³O’ƒè b@n½BDÅtÜìœï^ÀîT0«Š™¹h•Š›Ÿ3P–å•å+ÊXS©Ci*n ê&U•0µ7 ¨Â×{ ë l/æxØ”$¹d³ïÈ€Ç]˜Š§Öüqž`¨H=±ì±³}þÇãöƤPG¸øú<øéK#§ĸü9C°ŸWÆø°§¼CMFœ³A€«y Ü[äÊùˆvK6A!)®ø"† ¥,ÁPZb!?yDUC‘Eh¤Á–‰Vñ7ï¼°$Wɧ üCÌÜEÜêÏÜüOÏÌæ¨êÿ}`‚#í +ébZ­ V=ZÜ«d¸ ƒ/¼¦/D bÛpN¡Bæú?Å΢–æ–ÿÄfû·Åÿù’gꣃ<“1rÌqe3´,»»oq¸Ž&ÃŒÿ3<5¹øšÒ vÀ >Ïâm£à&c(&óZŸ¿¡p`ܸŸyT¶ý†…z—XAFU ÅA9::+¼=~~æÅã¸_æ|QYs´µf åKĸƿ¨‚c¹Po7ñ 2v¸4v"[$ ‘:GA&[\O´´µ¢S¶(Æ$#¢˜µUÆ•QNùØ\r_'ï½é 8‡±š“èpÅÃæ@»Jƒøˆƒ$MýÚÇbvØh%:ŽïNL´åÜwè |õ™¯¹×©_3fÜKÞc.f¶¬ÊBÃɆ-–Ëe°pÂÕOÜgâEm~ÃЬ-Áe*®Œ„[ÌaM¹c¢ÿ‡~q_ö›™þ/¾®×Ò¢°YiNßuð„0QìZ9j wyÅìå_åè·Kî©\îw÷Ek‰*§ŠM£IŽÜá´ü ¬Tk}wó5Ðl…:ÛžÉüç vˆæ¦J÷à-¢,ÑÛŒ¿¦ G'Ñé¹àû¨çP£^ŵ$&3âÌŠÌAn˨Åp°$óG8Mÿ÷’8fùÅ‚,ý¿¹P­£-Á¡—1{GC"@Í1¹9Ô;ˆª%¼DÁMô¶ê5d± ±èÊø³*XÙs!€pÆéë7Ñ(i\þí °‘Â#èéCîBT‡V }`@d‰en3/ÜÍpêÇ~ 4îDMÏÀ¾‡}á¨ûŠ†Ø©lÅÚ 6G8w¡æÑ;‘Fà[Q·^8×íeÛxpùÃz”(pd®¬’öa…v/áÒSñ(KèÞãØB™8§¬|£¾âÏÄ¿—²°^Ñ-²%•£Ñ}hä9Nm™©ÃÚn]L‰Å¿ó÷Mnd± °¢æf>+ jnh¾TßøF ò´âUÈÌ3wEVÞvéØŽúÆ §—!øwö ¦H„ž˜Žž‚°û…ëj§³ W|i'OlÀwý0ok¿A(ø¿ oø>ÇpY•‚»€=jvŠÕ‘Â2²Æà»ä9Q0eýâŽBg´ÉUQpmɉW¨'DK·hˆ0i¸”ðˆÓÇ¡¥ðŽˆ VÙ`¦|B©ñ˜k{¨†æ“Ðrfv}Áݺ€)¯f·X˜#¦#B•{](@ì¸Zíê °>Æ‚ÖME–„¸Í€õZÄ`ÖXƒ‰…TÈ¨Š¢ ‰°b:ø…q©–É~m¾¨Rû‘+B®à¦N%¶ÃxÔàQäá~€Ô˽†_CÏ4¿º3&š– çØ`ÞfƒÒ°§ÛÚ‡PÙåGƒPuS+ž„±´! +=@)•ªû!aN}R(`U“ ö€ÌÕ¶[¤ûK4“Ápí(l×MA§ ¤ZK¨M0k3(CH‚™'öˆ›ê[ˆغ»¾b®ø{ ˜­wÉ}¡X3}à =‡´èúnÆÏ·"¤"ÑÉ:໩Yó¸u: ­…°‚û•«”Ýrªc$|X»–«â¿Ü[¦aø…dï¡-Ú#Ú-T¹µEÚwe¼Å\¼X¡Ä³#¡gr|ÐsY5ÒHÖ\îeªˆ›Œ Á¸”[®~þ*íæçæ"o•šèp~y©Óyÿ1ÛX%j Šs ÁÏÄïý v½‚Lt…_k @ï5ÏßÚ‚Ä&FÒfšŸ7ÿÄUv ÝÌ3¶äA{Bm>Ül[Ò4!6Ï.‚XÌ8%$±‰¹¯Î45/â^o¢”å^§Êg1Få °R«_N±ráAP­0ÆâÜ h†é®AuÍŸ1[|´@Žyæ+_zŠ­¼á|ųšàS$Ζ{IÞ:)_j$}½¬‹¹ÞgL0*Ý&YsÂàÃ=óËâ§íª„*´ÔC0,‘XÇGVŠã(æÕ ñdi+Qm¿@,¬±˜Æ÷ÍKÅrà¨, 8ÔÃ,Vç¡Ð`©¡åiN¯.sz>;þ ‰g°tרB¾Îh•L¸7ì"Jf"¶ú¯»c]øU`¸ß§\ldø‹ê‚ú±È[˜ °r¯ûtÙ’,Fô:ü9Ë=.«d3²fÜ6Õû4€ÀŽIù³_gƨ€ñôÉiá©ßØGÌ¿”EñÉËó÷k*ÆØÔ Ã&-·ÀÄÅ?€"±úPjŽñ@££·"š˜*+Q<Ž£òmÉW˜Q|¸sçö´˜ESöj%¶C¿¶kîø22\`ÀYÑèZû€™ÜÙ˜›EVã’Énú—ZஞS6¾”\S5Òq¨—“¦›bÛlÐêÊÎtNK1§+ˆ4XAj”vbHv‡°éƒ†v 2²a7žÝÄqÈ 1«Pe$,”ø•pI‰£rÏ?nÜ3M1+˜©Ì±Á‚âÜÔ³"Y—×ÑÔ1)§©›–èÚ)ܱS teâSŽ€ÄMÎâÞx&¹æÌûSQR›Œb4¯è~aíPˆ¸üÊpb®Y`®XÏ›íöÁ¸à;–¶FÇÌÔ0¨)å™÷™Så,ñÂ1+ѳ7սǷ—I‘9pùŠ7Ú‹L-ï¥SqQs³µÃ/‰•+Ê[·×a¿³—Ì–UT{ lCb4¯Ø1SãÛ÷öࢎÌÿŠ™#3@—âfû…Å;n-ƒ£j˜P²(¥ðk^‘¨Î±ybÞ`ÖãFŽžæwcLJè0_'Ä¥„Ã=[•ûø)8n=œ"x)G±k˜•1„[Ä]¦ ±”Ô¿\Âïrº Ÿ îãá÷2ˆÐÔK s˜SrÔ^ áÍsM_R‡Q|YÑÄî3ÊŽ— Œ5säŸ$ù%£sRû÷í,vé5Èwb®ù@Û 1Ï X¢ˬÈu(ÉŠ­³SÖXî~RÃQ—`×°Ähx®Í1[˜Ýæe‹mð`¾7™šß¬ù‰Cè¡Rþ‡Ð‡¼EDp%¡°M9Spm±PÈW³ RÉíþ ½g«·ñÐo SLù¥ÝÏÂY´–vŸ‡XàZ™D–àjXö Ïó_BÙ0üD©´Ñ{ „¬@§Š·2Á5³+¹ÇÊ)ñõ’ ^}5yýP\T;üG9–U¯jŠ‚ÜtÌÎH¨›*âŠÑ7*_=]Aè<‹sÛáQy.ñ*óna¶)£‡ÇU™‹mÁþ €ö%( ŽQÉ5¢ötwLDýJU˜Š†ÇÓŸ>ÅÁP]gïxɈ©ÌS K`‰¥TA¥eüÇ,E¸T¤¸;øŠ9.'B¸ ÔÓ篼ùN«89:0«œ kׂ¢Ÿd£Ù©ñÆå&Ž7˜•ŠcG,£Ì´°(…KË4b]ý@½èSÇè¥ÓWÅÒ™MÔð¾2ÌTÄ®È@¸ž"¡{ð6¿@™èž:4z•B¶úú nϲj2‡SDf¦ÑȬ͂Ìwz›³þ"~NsBÇc7¯¥¹AbØ`PÏQ{þ…¹bJ*ÕEÀ=å‹–XÝ片’¢¡*âîÜ(.ÁÌ¿,WÅÓÑuùz¢ì„* +DGh­¾5G}U¬?`=’‚2ÅU°-‹mÃsi‘`ʲâ‹ÓµÇ/ˆ7H7¯£m'@¥¢…‹"Ø{A²ú"üIŠïú¤²Ø¥Ç±Þop,2¶%—qGÌU§[ŽÜµàÔ ¢·óÐ]¥4ÏŽ. V¿W Á”¬n$å"i>käß[…îQìŒõ[y8Ø#Ýs³‡I²ù"W”y”šípGˆ‡3S„-@‚Ï¡zu ˆ—¹Z‚ÞÎÒö‡èoq ŠƒMĦ ¦¸b·‹< -˜¯ž]J Ï„¶U}aÓ”»À–\0£žu5×NßdØŸ¾˜ÐÕEh‚š‹q¼ªÜTugFÄB„”cÙkn[¸äÙ(îŽÔ€³×Ýé0W¨¤nAÆ?Çÿ%=È©H‘­%«p9½S™Œ4ô+'²,¼"ÛpªFk€¹G˜"V¢V:5-ÞU$Ýѽ½¦Ý¢QsÓ,#qÌ,É([bž›[­½@7s¢Ê"i?C¡,ˆ‰‡Ó ÿŸˆŠ“…C wcAÔƒ·æ&’ÕsHåø§ÁèÌ|˜·™oÂÝJ.æâ¶ø%ô fU¨& üD¦¢ òý½»úpðÔEÌÄh(åͺ‘¦bºEPïŵRÝàÙc%\ ±Eû%1¨le5>… :G0B(WS˜¤"¢ЈZâ#LÒ¡V hŠÞXª·().ÜÁ Fë;· _¿÷.»ÕŸÁ 6ùŸ$K/Ðî:÷àää5›»ÅCãÙ €KUßK€:ôù:7Ð@a„4ð)6âÛ¤AЛZ¨îæz.!BÆÝõ\¬ÁÖiây½¢Î½‰Ü*ܧpV^9q+~†`éŠllÂRÖ'Å/MëÇ¡…N¢ߘODzp¹VHÉw+Nÿ" ˆÿ?äDqÿB"§§SÈÜø%$@'v„ñ$°ùˆUœ6„Ã0ªÌŽ„P¢;fã3ó³°T6œÌ®¶£¯B‚¿E3:bT‘ºYA6íÁÚ÷1ƒ¹<%¥2™]Y¿(®¹¹}¢™Mï©A˜n`S¾i ê\X‘yu'Dz·#MÇ òï-Y—â[ÔS™‚ØòÀ=[èeÀþåEÔe ·;!-Òeñ/ Ñxt®+\{ ÎÓp/f j=Æø£V} AOЩ<¢·MædÎR„´J,‰Rô©ñsN¬GìqLøpÑánTј®Þ|àiÔ0_½’WxÔvçi´t欹G˜8iGÄUÛÍèç?~G³ qPAġӔĮû¸T¥¼CѸgKÞ¥­ã?Ñdñþæs–^꣭=0¼Û¦B™œƒqQƒmE¬S-5UxîypO>…¿‚+n jQ3 Μ¢é‰‹†—¨«¸kŒ_ Ù!fžÍcbÓ-çMM¸¡l[Ëε-æ[Ì·™nñøèî" 375-sÉlÔ³©†Øì•QïN­Ôf]æ Lwsbðôœý tž] ¸ü +D¡­ Œ·lÌ‹í?ä@1uÐUTT´Ä G´Åq}ª'ö7þt j<ºsñ—óÅ¢ þLJw³ŽdTò-jUÅä–Ùa¾,”Å2ÒÒð)¨hsìÔr¯ÌØu; A¦âS°Æ½EÕ~.†B¢Så‚“DY*>\"²àäÆy[š;À¹vßnE¤=Âd%<yãCƒ,NÜ ‚èM¿>Ð 'yˆ|Ì7‚s-—)8Ûö9 ¶»¶¥o·Ì3<8†³­J’œ]þØ”‹óÇq¨d ¸-òÄà'*™dæ´ýý:‡—Üʼ±+3 L¡L ?E†º-˜ ŽD؈›ç u0ÞD¬]ÑÔ½ Þd¨+¥®¢ÍGm¡Ú¸y¥eÔA–RÐ-8Bd~€±©„©ý°ÈütXá‰QÉ}ÏElïжâÞú)xè("|oüÀnÔÓ÷‹-Ù-⦱® Ùø”7ŽÒÅßê|Ýå °P=ŸñóüÅÌxà+D\A9Ìv#ïªTòÀ6}¨S'~€d×x5‰•2”Œøt9e2Ÿ ÄMÃO"Á«Í'cüÅk}¢ñŒ{xQwšôµnŒ 6óA¦Re*Y î-OÛ2D 4÷è u.Æ ^  xÔiÉΞÝLBb*·:¦ÙÄ®Æ\öW·?‰¼k¬DÅtL·ßÜÿ=M‘o¤SPw5É»Â"†ìf5xt£îZˆni=û¶¿SˇrªŠ ¦!tçáÊX·¼´¾žÑv×Wâˆïø€÷=²Ò询©ƒ¡S-‘L¾ë8´¿K¡ãnphŠŠsd¦IäÄ»E߇ VùÊ#oòÂ9ÊïÜ.cÛÙ™c¦š¾B÷(hêQ76û¾Ó>ù ŽÐŠ@¹Xà A^H0º]-wÎÄØx–ƒ‡Rª¥6É7 0Q96…wî;Ÿˆ4ÜB¬”ÕñKØí)º‹r<Ñ® ¼<íý1DB‡˜èJXA¨«¸)©·)Az2ŒRõ˜”^ül\üJ@j_ã1Æf v $y )¼¹÷!ìg**ˆØ® ±W}@ª'ÎÇ߃iy£ŽÐò”t†Úk£npk ÆxË(Ñ®ËÚ ¯¼*‡a……iÿÉ®Ïû–‹¾Ñ òA%Wÿ¨÷ÿ¯ÿ W¹0<Šj*ï”WRLæß`h"7r0ô÷0uï2Á,fZUÉ…wçnBâÜ Ÿ„)‡R‡P¶¾'ÁŠB6 Á6ŠÜKAæ:ö¿êOrº¥ß p!,PVÐ¬Ô ˜Š3ïÜc‡x­â^ÕÄé$¨µPLHš±¡+ÌÜÀ jU{4€ÀŽOu°‹ð„,AH·ÈÓpSŽó(ó*¹ k‹2™LÍC÷‹™w ¼'å¢nà/r+$ͤ„#dP„É$ ÔTß´§G'ºR·~FÑㆦýq·Ý#‰d²Y3• ~è¯k‹‘žæb%õg€·ç‹W„›Ÿ„¿£šŸ„[xîˆ1Ó¤x ƒ·ê¦H„žäÝó‚š 5·²kƒOY·Õôm¤°Ù§Çê Ö¢¤÷ *#0kñ<¥åóÀ•LÊ _ }=Ð} hQ÷óô! æi=¾ÄB0•7 « TÜjŽ®ï@-¢P þ –Ó>Þñdý¸)‡QÚ‡ŽJP¹Ic (J;´ÙÑŠk­Z.ÿ•×ߨç„mTÉx£³“-ChÂÈ­e"vW².QÿñP#¤’´_˜¦>jPŸ¼ƒÉ­cÇx¶ßV¢Úâàöâ¿Utak²ÿ„[gæ!¾ÄLž1 y}ÈŠ¹`ãïú³¤€ï~ÔtßäEk -u4[,\X@gwRÆ53ïÏõ ¿-ñþçí1ýˈÌVhý[] 0#¯i$Æ´JLó-ÕÊ.ØyµT[¹e¶V§‰.VàZS3„ «©§G'´/"´™[<Ã+ÒA¿Ö1dp‘ìå!B7 ÂÏø”(³¶ÔÂn Ö1LœÓÙ„Ú ¨mß>“Ѹ2—þ²Šj!§²ÙVEÒL'qÖ)®rÍÄÙóúѨ†æ“ØÿÁrÅE GE~}(e ýló4Æq ù–ŠÜÔqBÛ~…«^þ2@PË/ZÖ£P&4;„/º_çèJi‚«9ÃÑúüˆ O1fåå¹,ûrĹpÖn"ƈÕâ ] ÛÏ8®¹×n|æAýsÐM¦=/îFg¼73r™³íâ`ATíS0¹"Ûp]¢+y¢•ƒÿbÒ ‡ë¾’¼z¿3äŸ$ù'É>H±K?ìþÁ?ì}Þ>Öë%±b››¯|±,ñ,ñ,ñ,ñ,ñªkô£óí-$õÓýÉNüŠÜVô¿Ø}©7@kH ãþñÀ+‹ýþ ヽÊV®š?Ò¦ gæömÑjõ•ŒlÿQe?Øïç8ýçÂjîÊ«­Ýo‰M4]~sƒÆï0¤EÙ¡®òðCuâó¬o0¸Uìò~ë%‘¡.ëö×ç@ ×s¾±v~ó(~69;bhÆþOŸÎkâ+²³¬ÝvbAŒ·ÜÅ4Þe–ÛÁ°|üàÛýA®×ÛW_æ ¥6f·YÏí©Þ£C°Þ»÷†V…пĪ€ÿÏüâQîÉäÚ“1ï‡ùü|ê/ÿˆ;{ªí´?Ü/?»Æ07}ª#YóÙí¼Wk˜ßÃ^w©r`£Íå­TK¿žÏmöÅJ"ÜG[ʪóÚÿ³ù‡™øÍm ºùsf»yÆ3˜—ä+"îþ?ùÝ L~Mjëuó¨à?²ñ—h•yWs¾±óFïï’,VÐq¥¬ ãÇi~JZÉ]¯ËÌZ:¬y¿ŽñÏÊÈ•÷íòr`HÈ>>Ô|Ô]\E§*(ß[–jè2hDÂ;¹ûÞè— D·[xÎЮÿ˜€v–ß‚±ŒoæQÈÙYmÝøÿÈoE¯þpÿU,VHŸoUþå4]•ݬ||ןĤ‚‰Y³±ñðvýà†þMöñü— VÁI®õŸ2بµßŸØ¢|Õ‡=ªšÆ/÷Ì,0Åg¾<°RÙòò¿¡ñoÀxïWþå\´UßÍê¿ÜîžÔίÎ3k÷‚^:|œ"5ªˆfªæd«*gºÿÇò¬Ìì8Ý#œí¬±½Z4VKÇnû<÷ñ4λ%ŸÆ?h0+5á½\WÆV0Vu}ûæQnIl|?ÅAö#¬à »×ǘ´î*±á\ççâU’#5óÚ¥G„Az—·Ïô¢¯a£*äï·¿æ9 ¶Üøº¬c2ƒgxï-8Û{®Ýânj¼(ø­ÿ]êüÚjì¬ãgoñÏü?ÜUYžw5þ¦>8aFGàÎ1ƒO,Ê¢Ûò730¶&m¿Ûþó.Z‡ð¯î) v§ÿÄ,!1AQaq ‘¡0@P`±ÁðÑáñpÿÚ?`1ïQ¿S&6ˆMˆ&8”*¨R-”;t„±W[W*ùôcZÃéýøÏþüÅdÆ£k××× …=&ƒ¯ÄU ìé{÷ø„¹uîMèù¦E ß1ÿxpH£ ‚;óO¦&Ö‡‚Οî™L@T4ÔÿwÁ¢eG^þ˜ééÇØö䃼Ù.ì³¢zzg_ÓÔ@¥Y.ÍÞãœú˜³·…öã{"7n÷¶H`Ezo«Ïñ„¨Á ÷"ëò !|¸ðmv·3eúÞùÐy°ðñý}pº^ŽÚƧ„Zتxjq€Yä ÷Ç©xæâà†")÷Í>â ë?^esåлÅÄì¥g7lû`þÁ9уÁ…„œsÛž¹ËÙ&jNs‡\Щæ)þ¸é{|´".µŸï÷üÏ®_lXC Ý5®ÚÀ¬;²B€{Þñc…h¾DJ#GôÔÈØžšóš`Ÿª/ß1·TlÞsÆý3Œuκo9W놠P´_;åcRèsÁÓ"/sˆ}±¾’9‡{0†s«m¨?@Î?§’ÖFµgAzLwŠŠDhšÿæ"ÒÖV¯m}0´ËTÒBÿ_Wö¹[*I3\Âñ÷C;tç) ù™+Å'JcQ´ïœ1ÔORĨyhUAF_×PM¨Yî_Llœ§³»—0-WONºPŒh–‰=p"†r—7­G󓀨=V…Î^¥•^CO×ö˜0]WדÛÂ^5ÆÓòws®Lêþ_µËu^ÆqD:‡ìLAeeŽJ­á¯s³öÎÃ+묘kï!^M`•&†ŠkÚ‡ÕΈ€föíçß_¿Ä+ˆV5£¶€7¡öÓ¯Åïã'ûúÊÌlB >ðhFŸWÇÉþÿ¸Ú¨"ëõþ¿k”N£vÓÄm™FaãNJpµRV³eR³”¡rÕPÈLO=pz*Å7Øï HD ¢ÃF(5†Á˜nv9Å@££úÏo ÿ9Q Èÿ9Ñ»Ä>§êÁ/pÒn\ƒ<Ú*í¾ž0½‹G¡Áƒ†ø v½ò/gP6 >Ž9DÐ@Û…q¾2-Ú; ï„ü­ëŠ¢ª½_Ú.$pl±q~¿“{é;øÁŸlèeAÒ:wã)w5ÖÃRд_ÊsšA_‹Ì‘æ¿Û÷Æä²#öíýxÂØØzë ád3$ zð—/’©5¿|dJYâÐÇË‘‘“oÅ¿î竾üÉø¥ ±¨sŠ»¿D_LŠ:Ò_‚ž‘à½\C²}˜hQEÇÅÞgc§ñ{ú¿¸a߃PžøÂpå§Û|™ª„Yº¬2ñ”ÿç+ „‡Rè{Ï8Ÿî‡íi)t–cPNÕOoÅæeKU”Hº(P÷¿›Ýŵƒ h—¼×a"‰]Á<~N9´C¼E±©§Ìkp ¢Wl 4è:xî|ã ‘®óýra”"ˆõèä…¾²p´ã/ýü>€êKéã XtøöÿuÂM¨B…ÕìèÁ°ÛM§ŸÛÆ/´Hýœ*òöï!Èd2 †CÔVô]àL?O[°Âºë×ÙÃNÔT×Þè˜ä½`è%xÏãਃxïªÿ¿únøœâvêgýÄèRè­5úü§.-×МßÙFÜë0Þ[óe ûñ.X—\rQÅPùwÁ ÷¿}``KUuxs¥“®ºÉñr˜ãaìý\I­èïð!jê:›½:äî(CºôþuãßáÖuüˆl)}@ôû|óÿ†vôž‡–ÿ÷‡"<~ÛËT°;{<øÎÞxü ÂO'Ÿ9Ë‘ªréßäï5W]N¹TÙÏCIûh¤ø¸@9WGÌ/ä†-ý,íc‹êêÌ‚øŸï\wÿ¿%u¢Ž£§í1΃¸²Eû}ñ (t4>Ž?dsçÓy70´áÃ]rmϱfó³‚ú¯û­¯~’̈w™‘[ Çù8(ØÝðwsŠëäŸ\^EÈÇ’âŸ_ •@Á†GEp5ÖœA} 3’-ãêqª/ýOO‡8;¸-ô~àû¿'n?³ãÆ.Ô‰oÂu#Ï7ÖõÇX®©Áß?.€÷\?Îsü{þ>àèRÑà+N¿Ž¾Jc‡uñY]Ìö~Ÿ„²ºnƒ_—pW®ˆòzþØX,¾1,Át^9kÓäÍ*]3ý÷Ÿè?¼×þO®±þóÿsþ¹lxÿ¾HQ]žOÒ ³¾´óíµß–üÿüý œÓ,o©p8Xœ>Ø3íñá§9˾3ý?çlÜ\½û\. {¹'Ûç-¡U­uN¹îÿÜk^ŸÎ»<—¦ó Èª'sÎusafŸ_‰¼ÐÚRï&ºnŠN.†¢žŸ$@Õ#O²ý>&d2ìWë£ÓÐ1벚Lòwޝy›ÛŒ qY£S¡3øíùM4œd$`µû‚À<þ &Ùmq³ùùVE`‹c,¤Ô ÀÌå¥?n”¤R5¯“ýîÏÀ07ô`r±óñ€,%Z-søý­³År:ǧ\‡iò%' ¿:ÉR/|ð¡ìÀƒ@£×ðÅ:vöÎq–o£Ï¾pª|U”>Göcÿ\G‡Éã?…ªáÛîù@¯gùçÎ$%eëéñãÇhS~×ñ"#62¸ÿ#±ô;‡´ü þ¿ÁOo©®>™½ßO Ÿ{†ˆO—×ï¼R¼ïíû]-^Ѝ³KÓð…ÿ6Ÿ„3þˆ­ ‹“±·áôÖ±Á¶ #x÷ý¯qº«7¿÷·PÐr¹U,…¸=þt€”[Ôëš³^˜öL…ðëðû½ñˆ'c¶ ‰ºwø…½é¾Ïp'‚wƒv,¾ž¿õÀþ»†iÉ*4ž§á»Àä ÇÙû§±—äû`*«ÀqtþŒÿ¸aògª?ÆwzâvÌuéçYÒûüîAއ´ræ"ˆ’2þ_® KÁ?ÁΟ+ˆäMoçö´ô<ñ( WAƒOŸýîÏŒ‚ÔÎ~kã/s 4…7ª%órþz{âh]NzKþõÁJ/­úe  Nªó:ð`Nňkà׳2M€Ôãö½A¯ßiöéõ纻gŒ´ïv<ºçÒÿ€cž×Héí–nµ|·ãîãJ;ëÇcÃv'Á¡ë1á ®‹\ùéôÄmΞß!Ì€„Ø6/Óç·Óå‡AáÑ6?\P/_ÜçDQÔø·¬D½‘àÃitE}Ž™{|9Â4!DëÜ:¥KÆ‹ìçŒxeÈG'üÖ{o¶}ý7ðïãJšpï\qÁø B#Ã+ä—KwfŸ·ñûZ]+ÜôH*ŠíðJk M岎ÏñûüëV¥Zµ)ª|+èß*ÔVäIÝwÀ @>t¸Nä€ú|eüä3“¯KÚ÷uª”=îñá=ýß»®Ø–åÕé Ëõ‰»×]1U®Ý{ëö¹æ)ô/⟈¨5L¾ë’yq2«k?‚?³4:Oé‡F¯Ñ1×ë öù:ÿfDàá—¯7T:ŒâÊÜ^}>Ï|Œñ ûà+ „ï1("{|;´ùêiý¿'KŸýÖñ°qâ‚äaJh1y1­…ðvÍ€I}øøH'Ÿ÷Û‚üÆ=¡šõY¯i••¨zÛÎðú¸ç>^§øa8Bƒ{￟ûçv× àÚz?uÇ€#¥’{Ìð0Æ*U;=ÌLpÝ;<÷'aÇáÁcDôË–•áæ{å7×_µ ^ùQØŒý«ÄA”ƒ5üb…ëñû‡äÔ7ì‰ÔÆ:XCâ¹]Úÿ&÷°¾¯í?¬´•ô¡ƒù —¤›¹O·l”ð3øÎ·—»Ïí¾][OV¿ŸnqGž*=bøøñðŸ2ŽXÿCàŸ5„àm¯šµL[9FmÓ­ó”P"±¡épv×r Œ@›vùޏ'ªú”ÏlŸ±‡eÝ·!€ Æh¸X:nó°£ê3¿L-&‘à§}dr÷i…Ý"(ñjɀБt`+ư>wÐöôÆ¡zYÈ&z¦&<ë¸9öñùꧬÕzg/*ú6Dø‚‹à§ñŸO®wñûPÉÐÑÅÈÒœ?¸~Ên$_ à¨B¡ T&EÇ›³"dt ©6C«­yçäl”nÚû5z‡å]g\ ×øó h ÞcôýuRÍwüþÔÌO+‚Ï‿pu•v»^ïWßâтޡ‹A²]²^À‚…ÞºjüÎÚÔ Nþa¾w×ßçÿ\«n'°Uê¯w$;'>™ÇçK…2ðcÒWg•ÉQíõø„kBáÃ3p„–ïÌ¿LM1óÈl}Ãò)ɯÄ1Ó=‚hw%×2oXn¦–MŒO¨å÷˽ü —š¸(yÚŠt‡mè3òoã ÚvǑ؇ÕÉø‹"#ۦˋùuÑ5)8Ä¥SÊÿ½?]Ù!mökäèúLþ3øüÈØî ç×ñQª\E¿@^‡?L»rÝ›·ÏlØÃÅyÖ×Î{¿2¥ Ï”ãÖ í‹Ñ)­p}*µøñø7|Ú~4?xå¼B‡ÑÅa®<¦ï$¨;÷Ææì]΃‚6%tïðçÿ ˆ©3½’Ÿl5A¶„×O]šÉVÇEnD@„¡Î×}ùûc¤zpç϶u_³X@ ®/çÜ‘CÕÉŽ"*þT”iš×iF®‰ßöÂÑæÿ_ÂÿC±óóð™>%%Ë9ËáÃLIb4ÖWF0ªµ_®@DAçŸƒÉÆJ?ŒàK•I†¡dyËPÇUµ÷rñXScÿ¿Ö,¸c£ºú¬`ñ!«Û ¨¼]evľÎrV*qQ3º>¨fÁ6bZM$t UègÓë‡zëdú}0”îÙ7z{wưt‚Ÿ ºù¦Œ!E®³>;ŽLVx³õgšÞ¼^p/G ´øæü:ä:[ÙÆqºÒ=^þ0y8”¾ŸŸX„Á=ný_™AjAèõ›WÕPCo탳Ìÿ^Ÿ‚øbëTQ·–N¶o4 .ÑÔ´Ä@ín¦#7ú`zÓô/VØé®øÜ$Äl]‡,ÁOWšSO©ˆ- Ü Çj…ÍÒï¦E‰vyø”à/ç/pÙñ i³Ä¡ì ëÃð0 ‘M²ñH1@lvv¢špí”» GwÃLÛ^ßˆà€Ž§³S×S¶L×H/ÛŽ¡ébª¯?°L)““ßœßt»ú¼[æâòvèÞóÛÓó,¤–‰Ñüà§ÆŽïg膥ƒÕñš.°wóðŸßãòν±jÒ:®z€ž‰Ž´`"8'ø9Å %Òö:õÎê×½€Dºíœ|¡*ÂûàŒ•·@‡gÓ9+½^€À}œM`‘—Táßg¦4CÀ¾ ¿óh«a9¿µŽ_Hz»|>áø*ìå.ÉNLsÄàZéÎ €olærÙw…ïTÔrW‘TÓgĦ¥mÄFïhn1³X3NÚÍï8ˆ„ܱÐ*ûM>#žÀ#;ðû¡h@"\Ї|^×Lª"€52Ô²’x ÔVêÃàP«På Ñ¢&ÆâŸœ GºN|žøŠ5Ê6[QÒ!ô2ùË ·7§WÑiu0´5yÀê” ³‹ð_ÃP@Ÿø ?”¹IÀN¯[Ï©c¶nRâìíýþb£hpí¯ë&vþ¹Î”Êë(ð㎾ßÿܦ_‚1I;bòS‚]?[†hUd§¯ld}ýoÂýê=ÖÃÒoß´Iµó¦C´‰XpÎýp~àÇvösÛŽ:`†¢÷\‘× )èã‚ÀT/Ó8•D]º„ØóLÕ9Ú“ÉÇ^òˆïó*NKpwz~|*Sih™ã 8üÄÁ©“µßí_ ÚEC/n¬côÝöøxKq·eö÷ø,0ñáƒÒŠšØ-¦Ó‚šü3 xðÁƒâ‚ûôœx” v¶y‘ô0Ȩ²-¦úÆyèQƾç×ä ;¸\1ÕRõReŽÌCò#qö¨T'Λ0Óéû+0S‰Ùù`¨(^®iGJ¾¸ÑÜa[y²u²˜õ˜—ÑóÔùœU\7Õ€èÎùÉ7;]4ûàvNïú¸}ó¬ëÛ7Ï¿H÷ÄËQ ò Ó ú°zçÚçstO¼ÿæ$–UTû[¬"ŠtlXWËλöο0ºWÇ£¯Q‚v`u0 ,ؾ߃!€­ðnðHÑC¸cMl“égÐúç#ÆÜii´p–÷ã. Ixnt6âjÕJÄ RÔ48~Üa|Ãiê˜ÅS¬NdÁÖÈö~N½ý>N?Œã¸<è £Â~Kë‰) üëíšØ°9õ×lä½1C©‰9×®@¯Œé¾‹Óþ~Øû‡à­‰’Ï ò?"X^´ ƒÂ÷>JK„šÀ€v£Tè]ý®-$®¼ ;Ûõƒ‰ëKC-å5Ôg„vüD*.C’N”]Íû9E© $ŠG¯ü´4\%¥6T;©ÇÁnÓƒáR»8ÜÜëþÏçÄ9=¡~¹¸ µ7æ‡<àÅþ¡ü`è yó÷ï&¡z÷ëüâ#N|~Çâa©þ?-é—ëŸ_†¸ S¾}>å SÛŒš¹ ”ÿ<àfïÏÿY¼ Ki;Lÿ_‡ñß«à*ç¬H èaÖ/Oûb´½YRï ã0€ÔÔÇœÔ9AÓS[Ó˜pC,5ˆ¹^ýðØG°L¯x÷ø‡G©÷p:1Ï ü1S%çâ^—Ž˜½ÿÚ¨cPyMñ‡JÐH^·¾šuIº½@$¸vmJ¯xÊ8e¬çû×iÂÖÝ>¦÷ÇTó_2`€¼ÇýÍPÐûõÄp¹ë;½&iÖÔå¿”ÿ9 ›ï~Ž\Wð ë¼Þ¼Îì`f)~êkž)ûcî”jgbFL ½êÝpƒá³¯ÄpäN:Ì¢½_l$’¥n¬#Ì…÷éÇÁÔ^¼3ù~Q$ÔçÆ /cœ2ÀåÖuKÝr#’g1!³ÏŒʬ*›VeÁ¿7þæ“è»þq}½1e„¯+N)*Pæ4h׌çþóðþ~!@tMáÏi ^Þ˜jD‹=ÅÎ=ùóø¥P½AÐóÆ#r¢=¾Hˆ§†a:ª<¿áñpÚ0 º¿Lu|wÖ™²ky~›öÏlÐ>HÿfJe Cß½û|ñ…«ê~2èƒÕRÕóð›¾Ç× æÔ=ŸW)Hëq:+TÛðäG¤û~òû‡äÒˆv¤¦$ß¶å‚÷†3BÏZoÐ>˜”B»wÈLORc!Ù->,ÔT¤=U:Ý|MÙÁ¯’ mt雯>|PãÝ­„ówƒ"¬[WËýpt©ÔNûÏ·Úä Núø[ò‘R¸ž«ýŸ¸XíÀÑ©ûÜÛr5tBžf5cþIQß_ÆiáÃæw{g¡s¤^ù2Á+Íïç?ßîÿÎO ƒÓнõð6Q^›øšo^æŸÄ‡o®^4ÐT/_¶[=U¥ì\à*ÚOeÍ r ®1®'LFZ˜ÍÚ)é¯lç«ÿ>k?ŒO_ŽÀmì=°²/!#ÚbÍ$ _ô¼ü¢ ®tºþ?y}ÃòmD$ªí ÊÕp @î|oƈVq©f=vì;rôø!B%‚~Aaÿ\ޝÔ4`0Ž=C0èb€XšÁõè‰ÈDú‚Ò’¡äs¼àU\guå|¹ö?,†åÜlCN—“'Íú?ùŒ:µ`§j<úüfÔ•¢ÿXA#öç,zxt."@ê¯î$%=Ç»3ùø%,Âóõç=Jb@$¡×lyKƒJD_‘VUé‡Få;½?$K»êzà„ÉÖ€õöþq¶gšþðʱ'{Ó3©*¯9>wâ—L<ç0|Ï¢˜-¨½¸‹9 öørøa¾¨‡îà“àÛ G3/z¡Ž2£Ørä%J9RìUajâE(Φ}ÃñR”­ëî™ëÔë‰*Ó§|ðøÍqv¶‹Ø”_KöËSaQŽò|NbŸ|e÷Þ€NÎ:`„‹ºawxìü;RÕ*ü n@ú ü¬G&bbM!÷ÊÐy늖]‡Ñ>,\¾p™BòD¥Œuö&í>Ç$® ð”~C”9Òà7Úÿ—žàL´\)ÜÕÄxcjô œó1Ê+]îĞÓþŸ¸¿¬‘îïßóœ"qå|xÅ鶦–—ýéùëéœgññâ öüžó÷t ™¡v f­ãM®AØv yt—4©›õ%X†¼í“ˆ®Õϸ~A¨‡P²6(r•ûv ƒe+S zG¯/|MŒÄžŸåð—¿ß_øÆ”¦ƒÀ üIöÂÐt:@îÂz'×¢ Ñãlà.$ÕªtˆA{ü£»Ì_¡Ïߣì1’üwÞU|?ôü@©ÛXIj iÄÁè8iÀ7¡tª‘4‰Ñ>Šª#bZxŸ=Ò¢ìÕzK‰Wp÷'s‡ëäa´º àÛãÈžøÇq‰?n÷ÀÀü`÷k%.Pþ¾¹ühüþÈMäÄõ)\ƒÌú}Bjò°ÀŒd skû»±eÕÖ °T­ù>áù¢”NÞéæ\áÓ©½óš&„Ñìÿï€ lZu‡ÙÍÜšùPèeRBBÞš4|ž§¢çãK.ñŒwÅ?ŸÆ°¯/ÁÁ%£dD^¥¤Û»> Ø=X=jaª?Üäª;–>A%ëóò¥Æ9_ˆÄŸ³ÔTÕ—Ïlõ"×áwñ·aHwwÀæzé›üϾtRoŽ?¦Sƒë!öëõÅ„#›SÛ.WüÉø.¾ 9Rdw™‘†ÃÏäy„[§>ß¼þáù$£8+ æü˧¯ÀÔAmzgùݾF-‘§7÷×L` ˜·ƒ¿Àt WÂ!§±ñ] H7Õz.H‡Ñ}Ã4xO‘ÅVG ‰ýã¡Àáð"z7j©×%béÖµ¼;ýjç}v ×|2 Áçy|8Љ?f°tÙ!z1Š)uºuñøC³¸WW&ÿÛüïóß"¬ÛÏœšHl§Cè}Ã| ´ß¹…n솃ÇÏç®Ü=ט·Á|`AÀ {:{Ÿ|Û-ž—§N™î{düw°‰öš?¿îþõuz=þ:;AÔ“ÓŒû†*à•²j;Lÿwýf›þÿLQÔ"¥—E<ú|©V­ZòŒ€B©ã}ÿ;ÂÏl7ThÍß)Xbç”O tøè†°ß=v9CÀíñÿG»å#bBØ@Y:Ý=ð!>V†‹®"× ýc¢M¯Ð;÷v¿Šçyìâ-“ö²«UÈ:÷ý«ÅAÔg>aoU/>>C^0NÓØ}Íýàûþ±Öt W%/O¿>›ù{yù^Ò r¹Éû½(újÐ~¸b½È§¿sßàE®§ç ´ÅZvêåÂh%M§Žÿì,©M=¦o©85r£¼†0°±&÷•ONÀ&V$Ó0Ñóõt8»?÷+—0-k¥/ÑðÐÚFé—ä[ CzU¤Ùò›Ýœü€D7¬t4ô¨ÿ("° ®Mè¢<ÙÚú~A‰L'”ð⽓ö˜ÅB¾†}sÓÓô1¨ELd*mçüËH‚Ö2‰vçÿ}¾$ª½§?Ã=¾~«zÿ†O=ºþ0M£»†ŒÑMûãÌM¥'Óô#€ º5ˆÜ}g¦_$u?Þå4n=˜À)6úyÎj(,CG<áwþáó†rú½z Ó£@ŠE)ï IäDèü Mðz&5 ]†áJm•:ë¨õsx‹Òãc¡Jq¿¯à@º´í‚<|Pfñ„Þ Ò¸ôŸß /9;‚û¯ˆP¾ÃYÛ\¯ø¾ØJd!¡×|°JÍë¦îºgZê}°¥MXÐÞ+Õƒ¨ƒ†š'n8àƒ"!¸=Û„¨Ó¤žŒq#ì<…ÈõçÝ12°õXš©V“—¾0%pG\::záU+ø/äHï ååOÚ$©G×ôdS£ü²2!C¥ðôÅÀÑ(™dzûÛ&Dárêþÿ9×Þåazpø]ÈÒÑýþ|øøêíâúæõÜ€éÄãß zÿŸüü«V¸¯`ë“öþœ‘,º9~[(Ÿt@½Å7B‰Ãñ„(lviÞ?GV6sòÀ˜W¯ÄwßâùÝú$=­ë‰Dy8þ~ ΰ # nbó(Õêq 'ˆI¯«Îm( ²,áòÅPHpüÙiCX $*ËÊæÃFƒ#Éäï©4·©ï‹è­ ØöÂÂÅö9¶ÐnŽÉü|:±ŒŠ2ZÑôQ‚/]¿­÷ü§8|4ç2k¿ìò„¼úwΨAoÇèÿ\]‡ëý`“]¶kêþH=È¢¸1Ó4áøÿ’a#@­8ÍÏ /}ñ†=B8ëÕÓ~Þ3K@u“±ˆ°˜§ ûdè¡'ʶð¡>Á‚ŒÚk}¾BXó¸Qe,$ŽÂv3™ÅŠ9ÙÍp3¢¼?€f¨ b ôzûa¤™:ó¬õwÛP-ÚMaÔk_¦P Z—Ú¯ÓÙ‰‹Úte`6Ì\jо ¨„õ5zâ1¾v ˜lÆR†ˆ¶Åý±®é”ñMòž>BÒ³ÒL™ÄŠª8cŸî¹Ý~òÿÇ×'t±<†áÔ”ç‰×7¯bt~`ÏÎ}Ÿ¦iC_nâú7ùsŒÛ9Ó]ÏÙE ½>K¯ö±Y@ˆÙÔûñúåõYÂ,ÙãËÖSÇgä‰ÒezaòPå6/o¾rïyoþáʱK¾I,4Pà¹Ä\½Ÿ™4tAÛÆO\šTþtÇ,VÅìòpk—(ª…ÕÛñA(ôr*…Ž¢xp)‘ÖœbÇ©Ñ Žªú`eá§0À)¾xúa4™AгºèŸÀ‡­VÓÉ|õ5v;õÄĨ|`t@™jì{ŠdõZ¢;#ë¤|æc?lZU¹P¢¦q!ã\Cg†w8ONÿ ½‚Dò.Ø‚"Ƚ^]óà4W_‘ÊUà1ÛåbDˆž‡Ý~JFPð“*iPôK»7w~ªƒÜÔø.%¯N˜‹ X¯φ\@…])^Oíp†È²¡Buâï>˜an);õûÜ1°^ø"f¤z`$$êåôïü×{™æ¯8Рæ ›œ‰A {ëXYû™¯bà˜A,psC(íž›Ø<掊AÓµõ öÄR»Çðçí‘ ñú¹Õóµþíúõ à<ôÓþxÐ}q(×DX×!)rʹëÝ&Á¸¹JO£ˆÀˆ¢'S“ãml¥íõø}ßÚÌIJ5HÖº.Æt×vjÌ@® @zØ^¹={':Ê‘ ƒGí€ÈTœŒñÛÄù")z††ãŸt  J|Úá­t¼\¼ò PuñÔ3ÐhÇ©ÃðP嘅„È ¼{r¯aÀwmžzËù49 ¸ÐÒ¾áõÆ”9HWÕKðck¦œïïVÛ_q_Fs„Œª0Àº^‹Ï·æyÓ}Ìã6~¶E"žš„ “Žþ¹Ås·úeg»¡47«ñ–ÕÂg5 ^_¾u__Ø4JÓMôËúªFéˆ<¿\åi[øî},÷ô%ÓøÆ ö T7©6Þ¹Ç49©ð~%Q<µ¦žÎ\Ëu6Ï–}1×ú~ÐN•e)›aÄœv½±¡ä$òw<˜¬TȶmžLwm¨•#ðâ':X*†ÃÃqhu> Àz“Û6Åa(ûüE‘Æ*ú8-áy€ù ~@m˜M× Å{èÀ¤™TÍLº±wh]•¹Ùø5䎴KÍ&†ûÊ@Ó‰Šd-pçÜ­Á'€%Æé2$¥²yÞ {ä}Å=ñ±Õç»òï•Tö'oÐ=yóŒ#=ûÑÄ"¹Ð~Ç×_Î+Tñ|Ÿßæy“}ñølÄŸ¯\1™]áè?¿lþaˆK¤žóœ`ÀºœišíðçI—ðû`ˆ&Çcù Žõß X„æzο €' 4NÀSØ–ôúç :pèҕйÒK7öÿë)–Í5J^ÿ´7ÿsÇ£’ÂBjn4s‡ à8G®+ð‹­}®q=%E¨]RyÔÅ[åH«ôòÿòb@p Vª+] ½ÇDv'\ÅÖ6?»ÄôÁ¯ תitoN¥Ê4HÔ;…©ãLóˆ®X6Áz÷&Ú B,oSzëñŠ1OóŸN¸¤€.÷ÙÒs;W»R‘ã°U§QR´´¢uÄp|BByëÄÞ(&tØk¥§ÖSÓò¨WÉ5N¼£í’ 1N¤ûäÂì%3}J Þ¦mÐhô) ol¾ÉŒ¦ÖÿÄ1ƒ¡ÙDßlIÖ…¿>uÏ·œ ‰ù®TÆ;¼b#½~¹T¯±èxìa—)Ç_Ø z=Gÿ~'¥ÍÑp^z?N¿“Ik®Á6Þ™0|ˆ/#í‚R 9CGléð!®1e.ÎPN9ÉX$Ôãºm¢ØÎUȧ.8”Hžظ‡£û=rø—´í§¶V‡AѼE¯ƒ.$R’tÑ8*¸ÕîSWÅÎ8ãáHÓ–PçY¾º²x÷kÒ~pNËŒr¾EDë$ò§§C¾ `xسâºOï;yøWœ›\#ýzŸ’¼Ê¥ÔIŠUHzB±’V”¥½ñ:áͬyøýñHBB_~pêxØtÕèšµÇ*¢«ëŠ`G.Ù·´èðïGr›0ʹ”î}sý¼ÿsðuνuЧ39g_ Áå0ww¨þÂ)iz°f‰ÖÙÅ9öÅê_TïÊI.ò† œ#§ ¬Z”à=f#0! èßõÓY{¥okóØ•z{°„‡p„=¯µø+bW£Ãˆ4­ð ýð”@y;úäGèËAW†ýAÇšT£Hr?çyìâ(“õ}IF^ʇ×oìd¢;¢L×C-áäøsƒ€E)± ®×òtÅ¡wí×Ûa:o…0íxø“‘O\¨È=°w`ƒé€(©{™Ì`£ÈàÌwç×-$T/Ó CW…¯£ƒÔ0ˆÙI„Ò3?G݃\ŸN ÜmúÎq{ÞNh{Iˆû@HÎ1ÕGÐX:2íCø M½]Ð=öá¿Ø(ð›Î¶KO%ìÌ›áØzÇ÷†\hUaîûwÉÔ!¨KØï߃>\">rvó9#çb,³Nbj‡Q»À0];”`™¡çÀüu\E7ˆ W±‚á4%ÚP NoÑÎö$%¦ú9/8VðÑ´Î}Ê^õPèß=ú㡈jBe-ÑÈøÏ!^wK:aªŒb´q¤Íëð9.5àáÚko¿„=¿(â¨Ñ+zšçùÀKIäåþ©‰ÃmXïZ }gœ.ôs´=«þ5È'²>¦!šÐa(S4ÍK¦Ñài¦ä¬:Í#JÓò"f†L6ÄŠg¼ÇÚeûA $.b—- ÊV®„ÖݳinLäµZÇq:bˆxþäìÁÙ`„;A€¢DšÅvšá)§¿çP"\ï'‡lŸª8ñ€Û¼Ô©tzìv ìõû˜øAÃÛàœÒ¬ìõõÁ/Œ[=³¥éùhQÔúf…˜^DÐvé‹õ{¼à[G©ºÞœôp,ªÅ ‰¥îÀí$˜‚óõÏã·Àª-!;ƒ´ÈAÍ &&mB´º3¦Ó¹úQ:ŒOTàpÄéÛö[!¢v{™Uœõ¡Ÿ[ñE[Vj%¿ñl]¿ât;2nƒQýž¹ü|b-fŒ<)So³Çåx^Ež2ð]âÙô³ëƒô£E»ï5äÐ"DûÄ0vÕžƒËäÇ…¡ °Ý_V¹0Nô$ßòβAwˆ}pjQC¨ŒqT"Cv„¿ÉðŸózÁáAKªð`WÓPÿ€í‡†‰èãŽÉ¤ë¼‹<¹hÓCмõÑ‘áy¶7Ë\ #[zô¹¼A*tÐòÈ‚N\s%4ˆP´%›#Gžààà5Kò¨{àMްï;â^¼›ett‰PbC+XÕ0í4žzFŸ ¦ÃjÉVÒbÑéEØËø8DRE¡Û>QÀ-ê›öM|ûªÐsNö]Y>ÈHô  •ŒA5ÕáÙ §|eRZ2—^ÄècAU¨ï}î.åJÉåãŸ8ƒr줃ipà-kS¾¡·¿£ð@DèàHŠåýqXzì“§Ž2Ø3P™=G¸M«V™éÓ+riϾéÄ…ñí¢k¼ ­n5`—CR!±³‘$JE~!1máÝTêÂÙ³@‰ª‹#Žœcd?V¾€nöëÛ9 VlN?˼"™º^gX3XB£÷¾†Ôg"-ÎóÁë0Ár¦J>Q’8‡Jºë—[í1z–(ŸVXq¨¢Î眢˜QþÍñ›Zp©žöKâûçN°ú<>Ëú‰|g˜<~‘¼&eä}¿f[ˆƒ8žbüA üÄygæ®t!Ðeòö0Œ"°“°z~2±(ŽqeåòwΗýéžF+—Ðåöƨ S©·ìbc"<4~Pi@”SÓ!<ª¯›{ëO¶mUy¹íÛ:ü£¯«_èíˆ4v=òþM‹ª‡×!ëÛ^Ù¯­ïïšèwyõÇÖK0¯Ö1Êe•=7ë…çß—¬Œ.FéÛÓÆ!Ú Ý/át˜€Ú@8ñ„.ÆÛE›/`ÄAÝu›EÓ†»ßÒ0rÞ1p‘B×N£ñJ*Dz,Oä/LÀàëH.Ç$ˆmì¶X)e¼ “v–…t^¨¼®Oj ¶,J!KÆ3©`@„^‰nå)°1â@FèTÃ+ºÿË·ÌÞH$n1P;ñ†êzÿ'‡(¤NÛ'¸O|#ÒwâàƒÔàó“jz=Opï×Ãçм Öç§à¡Í"÷˜i°ì¾Mk¦tš ‰ñ:àÞLœš§AôÖ·P4&—¾æ’3už®} Fä2 »ÁNj<‡EG(Wª—ÈNw°­t©S§A°KÓ1’€v±Og  "$Ï@Àµû÷çàNC–²÷ëwìõõçô.ts8ÍŸ£ "Ϧ Ì6ˆŒ÷>ÿ³`¨S‹OïâÎlß@ê8€‘[#ÔéœþgDª(æÓñ-Uêëò ݨ’=Ì[gpãÛ.ú¾¸Š#­É¥ûßÉó…°Dn«þ>ÿ:¡g‘úඇ¢½§&‡Yù'X¤l(ð닇lêÿá×-çÿ¿Az.¶ÓöÎuíz\GM^W*þ  lêšžá=w€ØÂ ¿Á£ž›oúÈ–0/L‚÷éÔG¿(ˆÑ¦ò‘öØ0# œ‡™Ÿ À 2 ºÛýJ NÇ’áT]¬ü€Ü4åäY²”«ƒ¶0)þ…4“RÅU˜æÔ›)®³B£‰ÃA¿ú%NÈôp@&Çg˸Å4ˆ”ïÑÀ+‘ ¯c¡’ùwÔ•¾ ãµeP¬&weƒjæ4Þä¼ÕÈÎŽ‹­ßøï¡¾«~!ï0Eeb’ò}ÛÆ‰C€ Øüàju_s)c-P…ˆ‡KÇÀOºq¿÷õé³lºlÔ¬¾qµ’Ò«”ÜzåÌKl•ÓÅXM"sýxË C_r}ÙÇIZ9šè\$PˆÓ8;8yÂ5ïy"r Þäf'‹†™¦0ªmJtÄò,.YrÄè¢ÄQh•{,NÅD”­óm€MåaC ‚e¦2S›Ú`carF'H@^ކî¡s&ûç³8ýËzý?f9æþAGLB(AþÏæm(“Â|V„mUßWy–Û·åÀ&D·k÷ü”ˆž·ÐÍ&åѯ·ýõüàz”ûâÙ¶R=¹8À6kùë7f—¾%¥tññ™rÔv·¡a×X§E®ñJQAœŸˆIˆË­ÓÜ2(4w—5CMßG)=üûrDJ„{áûñ5ÈÐPZ%{ïƒAl× 64¦¥w…”b„AI[Ðój5ŠwMjÞ”Ž¯yñ–Ã^ï`î¼l4Úй³X˜€èL»ÈV'‚`¢òlß‚ÿ‰±·7¯2twƒS«‰]¸QHöÕÄåÉÑÓ¿©O|ŸáÐAb‘Î’>¾ÿ(2©EQÝèY€¶ ¡ÝÔ¼k_MB§ÿ:à 0$ˆò8£n±CÕ>²¿9¬Æ¹m‡%šÎ9Üjèë™ÅrL²1”û¹ã•°ì^=¾;Á–„p'Â-…ïð?QÂ=2Iœ$Þáw帬ÓQ$ ªë[Ö”8È B%£AÀËd¸^*²©±€2ñ’èȤ—‰m[SPL„#i§@/"w¦…tG Èå9G‘PPŠëˆy0#€R¯pÇÈ#Ã@åLc¸ÄŽõúØ6—ãöcÀNûÇȶ¢%á3[ôk¢~[ޏ,jJiÄœþT"¶k`(S·äoÚŽ¨ñé»mW÷ü)>L&5{œckH^EãC¯È˪©á£í~Aµœwó ÁØàø£xiš¿-Aæé Öt‰L¿²µ:æ«.»þFŽ *=³‘}ºLpød8éî/óÀF€€x1FP`Ô6ô7ÏL7 åKè¥ö1ˆ 9(Pm2Óêb©ó‚†ÅS‡E‚÷9$uH• ¦Ãx4œÁ&¥‘šõW}ïf°‚w°:NV󟱡¬ŒßŽqj.„NNxâf›búÊWÔéŽLè*ô¯¯gQkÍ^K3ÖcÏ(ú£G¦ûaR²uÒK§Zã+© ™[WÅ逇Ãý쯓@T«¼¦ÑO%áèiëL`nA¡íÍxü@dÀ*à±$„wäMÌQ_/ØÿH$Z¾¦‚žŠøÇ3‘윇 EWÁ^ïC+ 8ái rħXžD˨¦Ö£¢º’ê.këÕ°Ÿ@˜ˆ³{ÓÝñF‘à`B•J]èn's-Ü“™ÀV¢L!ÌÁ Nø°:Ѝ àI^T¤ÎG¤þÛµ´„À8\4%á`è …d+TTß8˜£ìÊÇ¡¦bx[·!Ñ ƒì¸“I[­áðåÈD$tÞÊëòOUÏVÅ{÷_ЙqŽWÈŒIú›Qê)>÷Ûöa TDñ3’Ã[‡‡'·¯É¡Îëèþò}*&º-EàÊÒ+øgWwò»a7£d÷_ǘµþ…ñŠ5´;^ÿ‰C⎇S(€*wJÏLûþ;²tħƒ¸Î~=“tUç *=ðð#Øf%KUïЧ¨}ÃIìæìU[ý‡Ð¸$*Ô=ÖÂ/õá©“ø|üCÉÀiÇ€¼Q¢Zj>AÅåx ‚ÕÄéåÕ×pE*ÀåÚY=kç¥$áG¢!òŠcZâúà쫃—îÅ£V2Îp ÎA騔f® ÷8`Ä`ò:fË@î zK¯Äñª\Å„ß;rÛøchM²;ã~¹Rý—ýÄò:àmËtyÅRq9V¤.£±xÊ ¡AP©»ÎŽ)™‘Н½Ï–Š—g??Ã6ÞXR…&z/õÉÊëút•"æ^ì¸q[‘ûdÄ(/znsƒž3é²A-ž/€IY®¹ gµÒ§C Dz¨[nx¤çµå­ ªÛ Ýàœ`Ž'«iÛ}>¹ $=wVF¤xœì›oQ2¨i†žîuH°“¨:ëx5›LAÂõVé1 ÚϦ*HvÊ|ýLÁ .‚^•M,ž˜*¦e¢©îã^;qޝLòÞËÆ–¸ÔDu6øUZ¹®˜®2V[<5ÓôäÀ(kÉ÷ýË糊Q'ç´ÝÿÅû5ˆéþ0ÿ” ö²ß¨yî{ÔÍbuÞß“;³"WèPà_®Pð'¯ú{~]XÜž£ÿÆ7ž£YÍÝîç5VW?˜€¹iÀìüoâªxºn;&„îáøÿ=é’Þài2 žUC í­¹¢ƒò& „-+“Ú†;+`žK²íÚt;àŠ\ WÌSQ±Õâ`²Ý:·¨ûþ†‚D¸O)ã쟊AH ŽÁó“[@"@Ô×s!+6[½ü„‡ ßßðQaeNÿ¿³û'³Õ?ùˆí~‘ðrRá$FwÆüºâ6¡S‹Éü~5úxgž†&÷ü¸ïºìÏg=Œïù–ä@›„„†'Z_ãðÚ^Ë¡ë—þ_È&½±pHIãá2ÉDíô24Цܭ·èÆpޱ=×ÏœÞ'QÜ{~i•j[º=}&4; ñ+èiÙçt­<¨ép}:,Oò±x‡D#õ(ú2®“D/]àdRÕG ≠oA_GG †§lbå%’Ο\T$ÁQ²\­jz01òƒW¯‰–Å)J ÆÊî{±dkZY]ž öÛAƒ ã•rjçE®X]²º»¯^‡ô3&Xõ9ç¢BÉ*:®øÄ "Œçç¶ ¢¡AGSžG*y(I~³îÈ20èY]Ñß®NúÇAKh> ï¼&ùQª¼²»4OŠQ …‡\Ù¿Ã:ÛÏmÕ1 b;²iuÕ} Ù˜ˆ@„/¿ådD‹ ®< E¤£¢;;ž~v‰íj' £5vx¢®—f»æá=löÿÏ@ý/8Ocœ‰ïø©zßë?ùš*û‹ð:a%òÿÏÙê‡G+ŸàÆoB.´ßáEÈ›ø ";:åNŠì>ߊiŠçÎ>WS¿ç–Ž"v]?×·àƒ*UíˆQaaTéëš2àÁŸçÏä^ªP˜,L_I rGnW金¡®]W¢U*㼬*õØ“ßñ¿× Äïþ?Ý2Å”ǨþlˆA¤l{L" Åw|Bo"wWkëð2úP:VÔì²”ó;€C Ÿí5äóÁ¾vÀ)Š×â— ¨âØT@û  ;дf=©ÝkÜsÞÆ°¬ Ui£]_®&]uóxוq|G(V®„©vb.¾¢XÄ0ÚCƒ.X²úÙ«a§IŒ^ Ö©h.»6uNÃÞ »NΓœ§…£,±æâ„ÓÔ†ÃGW¦,‡CBŠxN=OºÓÙ!à€òsÄ퇅¸ õ>ŸP¾¹á… `B~PË(¢`°Ù)AؾßC·À™p‘Ä.˜¢XÐV‰XùíúG§9“]ÿ«Ð1iI)ˆ=iKø¼Ó ¡8ÇGöë€<~ÏÁ@õ8O¦13÷“Gñޤ¶¤AÖ¿’L`½ZoZÔü'_÷˜‘”Ä^=ËœûïFž^¿Ÿb;ĉM/¾tüf€§1«ö2jkò†¸ëžzüU‡Yú¬þªÓ$דÓó`üÅÅ€4;QJ[µ#4ä la,PéˆwÄ+nEÒDiGn Bp -º™r ØT ¢<0ð…P"Œ¥çx5Æîбii²hDÕ}RÊ' rpü9À C§žn°$¥¬¢d dÜе\›mKÎÊHJ5µ“)ª3td¤q½@kаzkQÚÆlà_kÓ'%Ö(.´ï‡Ðmɰ (ªõu¿_bÔÉiYÉ ›ß>1ÛhÓæŽ?4±5“jJPŽœr$væm2žçM~•ÆiΛNçà›ˆë¤ãj{ƒŠ¡KÞäéøàW Ž!EéŸgö‡ÿp¥E]ã,'WÀLG©«ø.» b½ß4 Ø#ý³ÈkõwšÿuýÄ Mül çï¿çó/D úåŒîüsp¤S‚%Í5°»¸@XÐÒÐl²L  öE2Ðn rõüe+Tê!!Y i–L§”©TÃ+Ý+Z'1ì$Ï!ûQ8@!PÚám±Éº@¤´yˆ|àñ…×€Ž:½È-Jº*¶»yÀ©cSJplÝtß ¸MBР£NBvj\—SSt>’› Þ˜"€X@è h*L·€›”;Ð×CLÍÖ…,Uìï‘åqm–;¦šêç¼fˆ&‹ ñ©a.(Ðaô `BXõy}|`]FB§J·@ÕÅFù=7Î¥U½ccþ×9ÓLµ"t|~iׂ)ÀMŸUž0T``³ÛôÏøÏ2xü‹BЇ¸»`DUÛÚ[@íÿÌ VbaòL"’-q0AÐCÁÈëzâ¨p>©w}Þî±ÿqè&Ó§côHÕÐÙïÿ¡øì¼×õù/oõÆ…¥ÖÏ™&Œqð }[û|.¯LãÇ®pÿçâ*ctçšAf]N˜. z`¡.±T—YKkrå\Ÿ‘9•ÑŽtA _»#Û¯C2-Í»­7ÎkaOä×Kh¥…MA2\FмšmºÙÒ•h¥"(¤Ó“/.+F : NŽ»N°’’¡ ä‹ WÚX iP:· zß ÝVo8•Š©¥ ‚)±¿ÖØ€]%.”¶9rV:Í[xÀ–P)D§aç¦rŠ%%EYÓoæ™4â‚6uò¬æ°Hï@{ù·œ®0{úw˜ÎB;BVt“ôΛæq›<~8pŒ†ið0ß´ÁÑB¾Oë5µ™ ¿Ñš§ ·•¿G'ùÖUàÇžkcGÐÀRRO¶'¹±{Ëöiú7@>É‹’Ÿ;B\ï¦ð„‚Ð:~Få.î4({Ì3 {.¾iÓp ¯Ø¾ÚpÔADêwÊPÒ— $2>†h1× tg³ôü5i (ÄãÃú9èÖ¬^kë­îå’H‹Ò§K0÷ˆy¥™)„¡`¥¶€¨T'pŽ Û³b76³ÁDxQ!]ÒQ‹úËHPWeivT*ÓpàÞnõ ,bðpìÓÎ+…Ògß–úC¬³[;gVŽ5ÞøÏ^,=&z¼`?L°!Ý_˜ Kƒž‘þè9x(¶Gpuzí aÆ RˆQöËy›/Ó¹“}óŒÙø¦«@]0m¥i†øÛØý§ÌcPáéÿ0õN¢É¦/:‡ÕÆW?U¼‰7ëø?MàT ô~ µO_/!Œˆÿ¢|å/´ó´Wx'^Ç:i\g¹þ¿$Ãu‹àø•À¤õ¿3ð7ÔÍÆ#CgE¸.¨CX@Ñ8o[ß ÀÈC×Ëž¹¿\;ñGWYußÓø]¤B¯%žß¤ tŽ<=ê±®D饞(cI†µäÕHœ8ÆH’Aàl®VæÒ„ÍJÍ_¯¦"):%eÁÿHA4§ [†lh=Gœvý¡2ê¬Ü5ÿpÐü†3(cüÅ£ÓŸÉÕz:ó}¦%îcHù†ßï¾>„@«Ê²²åar­å“Žß÷5`P„][Ó“ÅÆÛjíÓ‰O2?ÄZ—ÏQô~ªâ dè•RKE”õÞ%ªœQúÿ¦ ò%^¶ðî+“HÙÌIªõíˆ*uqâðó**8#Që}5CÀâ°1Ò?ãˉ›”ƒìîyΔñaY¿ROEĵ?-YuµUÑÓ¶kk‹è~¥ÈžøÇq‰? §Óx®)ðk¹«œºÓfï|Øð‡Óö–„w þØMfdp¼¯ÍÓ:οV{`Ü©<Ó5ï1WŽoÓ§\J‰Ôf#Sr¥~ëŠä¼»üù„ƒËÿÇõƒT¤;|ÕÈÌ}R¿ÏÛò(AS¾VÄåpã½fÂj"ŸO™ m×Ç$/@Ž–ä^ÝUKÛ·¦PÈ÷ÛíñáÖG‘À¯?s¦ÁŸsÔÉüÏœ÷мßÐP7ç·é:Ù‚rõ²­™Ù\—zÜç»÷ðÒ8K눬‚)°óßOzŠºÊ 'S¯|vÀQ:Ÿ‡%ÿ¿®qh覇¸ò)×h‰€¬¼N¼ý3ZÉt+àÞ¼â7à ½@]º ©Š3ê‡Eèä+$‰Û„/ä|fð‰½ž"û® P: œ‘0y½²ÃFÏJãu5ñDT+ ‘úhyAŒ34¤8×ñ_>XùˆN“^&]ÆÃcxN=µ« ‘ÜpžO¦&µÝIx8µðgXô˜ð)//~†3M/ÔÄì¸Ç+ã“ðoH*4&¯>z}òŠãMç§Â+w·®9PÑ“s4xhÈ!ÃÉ’….å{8ˆŒG§Â¨v¿LJGIÑçhyÕõãªn¡ßlqcµûýÀùõߌN7êwƒ0m=ç«ü~‡pÔQÕÖ|ܦÿ4~DgœxÈÚÒ4œk^>pV^2¾ò O£šæ–†‚nKõ˜¶ÐGc×ýãäïÕïš>i ͧ¯8,ðž3íò}žØü@^¢wÄîÄÿYןƒ,#®Ëß•í|~‚ïARMˆySÉ‹ å­Ñ^vðÞpìJ«ñWE^q7XšÊ '89;Åõ<šoÂþôðÍUNËšåTS¡&Y¾H›¦½Iÿ¬7sA'°VåßÚ“àl² æÔiûc(cˆBðGºáHè”—°r¿Þwý(Î(·Örǹ ô'/¦Xó+ô@öÎQh4»s­IrÒ ­ Fzª÷Ö)¡Át­NKCœr¶‡ž!µoÖcP„–ÛÉߤŠVA£Ãí•™ zïž—Ʃ䑦‡ãŒr$ÚÜìN…xP Äâ‹j`0h[Ûœ¹Á1@h\SS\ìÈ1 £PAì]y?Vçy|8Љ>EËÿ×NÀy8t÷/Û¶¥Zä3OñŸÏímïšÔýG¯œ*ÔÕí¬)^°é›y/Ó ‡Ìõ”äkb>5úXHvšáû.”hð÷ù 9 d:ÿ§ä£ ‹·Öüê%S}:a¿µ ]Ž:ûã†T²Ö/¤¨ùÏãä³97§£ÉÛß:£¾zž.³‹Æ0žï‹ÝŒÙÓ5zÐÁr4AØ0Hݽß÷ðµ "÷ý”ètƒÈu+ƒøj‚`ÞhÞ˜–Þ®2Å$87–ÁdËY”[j47GZ7­R2 âÝò³¥N·^ #\ðñ‚<'À ‚©¤î ðWÓ ö‚N¿¿lFÐ/ýÏN~ˆUzb‚]â]¨}ãb@C•áܼ5¬* 7„½ß  7Rð%³¨ñÆ ‚mä¸t |‰¤C@höÆ0 .Ž¿VËá‹ud9 gŽ0mmÒEÑaDhJzÔ² `!Øñï`ÛZ ®¢Å,ˆJa²P#uÚ¸wÁ\5›€çë'^ ,G¤à©„†»3ÚÖ…MÐP)¡›!*W`im±†°ÅætKyØuÀfžÌ¤³®•õ C¦»Ä!6ž–÷È`Ñrô¯‡®I ÷AJœ÷œ5¼•[У‚výa%ÎóÙÄ['àYžä<ÛË“N‡ƒ¿í?ö¿ÈÂíµú%]ǾŸÇÅAVzã‘%Ð8úÿxÉn‡0wÝûdÓûÜü9^ öÁ-2¹u'Á0hQ€vëöÃ94w_½*ó>N0P Èý%Ä^s€E]_.Âð`Ô&•ðÇ~¿§"(3eéGGÐy3œ–°ˆk†Âë¢næð¾´+G‡_LߪàN§ptqf/)MJÍè[ɃEùÝ_§®)|™“YÜ”ß_Y+œœÁa×B:áÖX)¶ÚÝ7„ê@ê6[OA8çiЉ\)õsÄú&Llû·w—^ ÓÄõ÷é뜤 À4û=­:8HX&Í÷è˜x7g€ E^.•äCrBJÕPÂÈ»p»ÀkQ6ï_¦CÕÀ;zqÎ!(š™Õï9ÄåZšTöŠ;õ „d"b=ÃÏ­½pXj"1CDz#¼rqmª@@˜!z I&a¥Ä3%§D¦ál$n8>Igvu:hdŠLòŠO X•Æð\€v5§²yóPFˆ‰Í:`ô ¦Xw‚ÓLtuØ• Ñ3F0ëdDMuo³ðØÕJ†‘'Í“/zyaÖýšï?YA"S å<8¯dùÝ}8vËôýµcË­êûb©U[o¿è€X€ž¯ ö™/¿¯ÁWÆl&Í•=aÄ(îɨO€Ì Ê<'þãÌa¾‡¶$Ç¢r=¿ SRD3—Î(“S—Éÿƒ‚Š€E5Ócw 6òúãﺷ¯È4'O€7 mVt¸DýžïŒ«× Ùzä€ÉÛ\ÏÔC5áþ—B`À°ª­æ¢ ûd惫ÌËš÷Q9ôÍžBÒbZF&T^Á²îŠ}‰p쪓¤›”T¦€N‰ · Ê~!ýLJX§l}!uáÃ@ ày‰'(S¹ìÔ¶[Uè=ÂU—ÒLÑQcl8S°¢ž¸én#±vu ßt'¸)yEãºë*¡'L¯nµýL?@CÛÆ8€IÔh8²5Ã,‚±+ סÛ7ŠUjíwç7ïàWÐy=±×-Ã¥ Å¢è¡dÆG ¼O`Ñ}ºŒúS’ú/ÀàäƒÇ— .én0Ù½—BIºô–ök{ı߃ËúÞÊ Á·«ÀCStíÓÎ CxNÀú2®Ñ”{=LNCb£ëpdÝ ­Ð;b €@›È O:x0@#Gb~²‚GxOcœ©ò”„z¢8“3–vý°`6`c(ÞŸòûþŠëÓ ±ï†Ž;áè¸êY}!7_Šj:ßúsýqsìjvÕ?# ÂaGÓç­R\‰ zäR‹N—¾UÂoz†¢?2Óà˜±ä•ºø(…E×êUHü—õÀ+ÜSÑìøú\|zLñÏ»zLRÛ²¹ŠZnŒËadªcrHRox^w¹¬úXúc4á­ .„ѯ¦,1¥+1¾^5²ç{w=´ÞÜó‰.“X*6òkNý±H»`;sr$XRô7£×AŽÔ!xBz¦(´ŽX DVp‘ÁÃÓ·¶]¡•n‡s¾sü~KøïŸO…Õ¤ý€íñ ä5ú{Ú=¿K>,om©öOˆi<þBti§žøz¦ Ê›žÿçûé•A3cÁþ²zŒi±œÿÌØÚ–s·S¹ÜïŸÇëèJ=0µTh ‰Œ¢i¹ËT!Ô,î4» ÑQóZ0P@¢ë ý(ODE0oÎh•cwU< á¬…2aÈLùÁñyáÙÙÂ>˜&–Á4V²šèWd…1£^j«x›Ëiú#ï€ã¼_£Ãñ {Â6£É㔪 °a}L¹º[§­L¢u|使!:éHº/ ­[’Ö”[MMâ5€çavÖ¢)rV'`Ð>¶-„ ¤Rc©wƒÒ‚ñ Šª©ƒ°šBVσѲ,ìMóòU…É63”Hw¤›KIU ÀÓ¿^_B22Qf¹"ôÒ˜)4Ü­׸ðqë±–Žt<ŽOó¦ =Ô=E®lBQýgÄ>3¤Ó¹ðß„ˆøí€Ú–'G¢Q(þC_¦ÁûØ‹Ú>×Àõ_ë'zŸï÷býKýdËÖ¥ð}2~¼Ì÷TìØ{¿ÇéƒÀæ€üþ3¶ $q°4Z¹9 #þÞþ¾óåS…;—GUGq{øíÎ)[{8—þcƒë³ß4¾‰8ü~¢ÃS:tWEîö󗤀ú'QýõçñG›“×ÓßøM†'„+Îú:\ù#N;-tV¯4¹é›A”YÖùÀÐä·BR½ˆ÷5–A„ÀÓ§ŒSÀ£ìNÖC£¹~)ÀZŠÃÒ©ôó—ʳ Oàà€|^ªgÈi^àqpÉíd•+:õë4zÄ:€€µî€EšJƒ·/¶[øäðü©lê¡èûáa‚…©ÈtL0B÷™°eG"%…åé2xñEXôáí›…QI¡Vq¸&uôɬf'j¾$aZ"U'¥öõyZT,W\÷u¾&S[†àF›¯ãj|vu+Ä€¹ÜÖjzšÉÿÓ’åÁÙ׳bŲ~±M‰l^AS6ÀßqÇRH0¢Òƒp¶#j‘G$¤Ó–šyêd›]­ûžp ƒø|ã¯_‚Îrá¼ûzü;øçÆyV®b‡zöëœW¦8™ÕS«ß/ÿ_’½΃¨úã£8u“õÑÈ+ëØ<â‘@àtOÓß[½âÌO´=Œíù6(nÚÂj(&øC¾¸ƒ¡£•ÀÎóYÓãr+£i¦ûqñžÓpòðˆ"@h÷?QA#³ñ…ktF~¿`>õe %’Ö•¨óL, ïÆè™ ’UÎR&æ<™Tˆœªñ°¯Q턞ÚÍ)$+˜ôqÖQåd–¡i±Ë ëWJÈ,’;áà4É\˧]óÞüLØÐî8ý\ê‘Ý7ë{|¨‡j YÎ9yE…à ¡(ÊNt@¡M^DÁ'A–äAÍN ˆÐãÒš4€Ñ&«Z1‹ @¤Z¢ Á¼OQ¨©›Y~Ê»Ôݳœœ`¸i¯0–ŸD„õ'áÄ:)ý'|qI.”A²'wŽ®XÁäOS§‡·êä“ÄÙæ×8ó—˜ lSJ F7Y^΀m%¬…SœF9)*„QÕß: 8͘\Ößø>rÆ¿_oGà *€s\¾ÁôÞ¯ñíu>¸Ë|B AmwÊâ×À}çñŸOg%ã~˜ëÇ®²Ápt^Ù.®¿X‹^ðuÿö¡Ž£Ûï”é¬1´ ìDM<€-P;yú"=tó×"°º¸ö{b x1}ŸdwÙ ÇDX¸.(ˆâ•Ò’ rb¯'KQ¢KÔ<ŠÇ¼ÁŠÎ›âëu¸`‡¡1:]b^¶4$è¡"ê ¹ÍZT¨yõƵֆ8áœÆýC¦ ø•É”Iàv/’ëd)+Fêp¥ž™sä.ÝÊëBºYŠvÐÇx§éŠ&Ò3{SéÏŒ{U‡T¦ÒÃFN”@Sˆ„&ª V‚Ä0\‚P7"µPc"ºÜñyfÄ)d À`ƒÛX Yîý_‚ ˜)äÉ GCº½8ëšú¡yüĤ#ÕÑ–év ½:q,Ц§Åò"yŸ­ó&ûã–lÍ¡ôuŒðýcYö<|ƳÛð´f#Ôšžw¼$ÿ»„ ¯øLã)ÉRp˜†Z¶š_|wk¼o¯·ê½p«hŒ¿ýâÒ¼¶½üþÃa ä{á÷ ®Ã¿®t½;ümWtW®$O?×£?Þù.K¾ž>0„*|ÿôùÁà*é©ÐŸ©q' )»ü˜R¨Mñ{NQúÇžâHï‚Ç nšl6ûbÞ ÃX3¦„bpª®Ëü¦[¡턾”ÎÍ×£H›²Vñ5‹³VˆaƒžeàíÞ¼ª„–m®“Åîè.®³aÿ‡ñˆnçyôéÎÿL1€*ÇœU@æ\$¥Brmtx]ô™¥HL§<”x³Œ¬6ú½sdœQEQƒž çPü‚NOˆH:äpZ?†–×ÁÙ߉—„+Ρô2t+@¤ç׿ÈV!f…‡ž0шt@07ƒè4}rY]”…§ o”  ½ÈY›KΉù>"Ô¡Jöþ>15”<ѱòÏÓâ/riŒ\褈nkº†iF@aŒÆ,Xäî@"aZ30-F¹7 ¹ Xƒzà—hI97 ò渂t@•û¶]ðµ·~êôöÉu<dzIÍoWàeTÀ1” 67|¶vì ðk•v¨ ðM'…ï·;â´B—‡žŸ&Ó²Rî\=R¾Øp !DxO‘š¤ˆ ©tV÷é–Á–žÏËăeÕÔ}xyøN«zú¢ûàSÀC‚}Õ>Ô1yÍ©úYÃ)ñqFbåJ¨òjsøSR·€¸a; @¿À­%½$¾é€˜T“õá;.1Êøq?”ÿÜäÝð/õú£ñ_qqÀ:§+ßö([ƒÈ:Ì\•Ï1ØòbÝáÚ úéøêqš³_ýÀô%RÓŒÜÿš©äÍ+¼èõ ˜O´äÄN™:§S7Û]þ€÷ŸJñÛáp—°Þ¾ÿlþ~%5ÀÞ=°púÅùÒ2*GÀ àP5ò:PNÓ$¼äÅxTâD¾ÁGIã*^ñjÔq&ÜÒ‹h+àöÎ lµ×M ¿c¶3;ý˜¿SŽÇ«Š¨jLBé°=Ï!SŒ ¹I†Á!ÇLC€ô‡µì_LMé0ƒ–‹}Ï]â]X׎c(` U±~ø† ¢,ÎÝnI[GSPnò>r ˆì#Äì¼>G^< Z-І#QÚêëh8¼wé@ @Aß=NŸ $Ód‰1Ô|`Q8q BõZTƱb°©÷\ŠD‰ W+ 8Ø%;ì÷IJ•Ûp‘ÔûßLeZPá À¶8Ç WgÒnv¸%u>‰ztMk÷ ­Îø]ÿÛÇÁMcï=ÛøÈ)$>¯‡N”Ç „ý|K糈¢OÈ{ñÏÄ+D(‡„}çê=g\F+ÕΉ=ŸŸ|^-TÛú<F—_ŸôÊäÛÓí…vlô¸Ô= "›³ÜP7Þ˜`M±Vçó–mö8£ÞÌ5$]ø¹w÷"1¸GL8d£€dšÅ3„÷.V×o›fÍy3ŒRë;`-…œÌ=È×®òTóùî¹æµÜþÃ"‘¹þ~_\fÖ—1쟎B¢ލ3O‘à‘ë¿LåUÎcÝÏ‘µîdÊ@ßg¥Ó8ÕÊȣá§Ü0XÜD0:vóV2©h7ãíÓWÏÅÕÊAµÕ/œZñ«Qö|Ž0-úŽKœâÙv âÊ„¯µžß ~J˜ï7¬ØÀêä<ã—8x{Œ×ÁòEãÖÅ^0D¦Ï€m›å–,7¯ñ $_¨n>hèé;pS·Ès> #@4wX‰;[ý•ÔUy=7:ÜI}9qÆ Ö p$4Xø{QÖ}„Ñ8Öè¼>¿ N€;%¤Ä–è©Öæà` ñ{Ÿ¢ý?6m¢q·£ú"%ËmO+Ù?RÀ’'^~ïÇC;#5ª;sú‡ÿ2#IMû½Œtšhþ:Þ}H:~‚loWÛÑ.Ô=ò_þäA¿ù>¿%7Ó òfsöÃ*-]LŸÏÛ!À<0L-zmv2´è(¹ù=7ðWÎ<ÌIÓ§ñ‰„â0˜ ¦ƒ±;}ÿ>#©·{ý‡àA€±y'¯¶+4´ò¿Ô¹Æâ ZHžN«É/Ÿ ¹J„4ø-lº~Ò­Bò©U޷ωÍóqáéÞõ&î˜ ®´uTò5[ƒHŒVc̳|c‘´£ªuGs¢âýÇᾊªHìF=±šH%”m„ã qHÝÈWgÑnõ>n—H!bÇ8Ö@A@4žÿ†3ú°=ÌX dPåmÜ¥ñŽ"¦Eë¡i7ÍÃjºAtûáì+íòÿÙϰ|ûþl 4O|ÀŒ…÷—ßâ—Æ¿3¡ç 1ù{mˆ¨³IéK¾O­î=O'æÉˆ*mSõ4V>ÿ£ ‘ÞÊxÅ{? Š IÏM†û`q (jC·éóH6<ó¾'“¨çö\Å»¼´×¶ã~†qøgäÒ}'Óåѱ/©.ðÂÄ[WÙ{xçx,wƒ¨ø|fÒÂŒ ×[ò œüZ…W[/óú?Æ×É2.–r¾§¬'w è©ÐÖrûc&ÔÕÁcEÞúþ^¿‰ èï) ÷f0;tð«}Îõƒ•+ä6( þíÂH!HªÊ RE9Í‚)Ðé 4…zž+ y*Ù‘`¦šÕ‰ì×!:’¸"ÑzåºIµ‡Ht†ïÆ,{Ç-"]êb¿ð.ÞM|d‘%´ðcÿ1Üv€ú‘4Íé:óŒí‹Ó„‹jƒŒçq‚‡#Ðkÿ0Á%áã€YÂcôø-𯄣‚±Ó¥&80¸·SÂ|ÂÈ蹈†¥?€—ÍËx<Íe5©ø3Ý 4ßWg•; L©®ÃëÀ/L,Z €põÝuã‘–i°íô;et¥ Ob¸lÑ´lDêL.6Vòò>‰ç:cêìý„— ãNs'¿àj– p7GB‡@öøõ»zàCÓ¦ŸaWow|J Qµïû4 n¹ËIáHv/Fa28Ö•îá{ÅT×¢dù½@êÃþbça¼'9ã^Ô¡TѬH£§âÍB|ê#½x|`09uý 2<ï rq…@BÝgøÿ/Û.…ù½õÄ~NM÷uÑÑæñ*£·6uóø|XyÉNÌ_á…x'€_¥÷ÄKn<M+,C¾•¨ º½›h‚:¤r¡A‘`HE¹ÄÙZÍ5ßëS¯( 4Ç›Ý4 â‰ë<ë”ý&Í/;tHVŽ”Ñ´ ËÉ‘û=¯iä—ƒ“xµZNÆÓðy®Ú„ûù˃^_/œçjË%#’wãv¢à^CË¥Ø\b€”~EÉ¡· ¹1CO‡ÒçÛ€;@îý&¸)»ûÿœ ·ª6¢tœz+FJ*RºìuÇáèXh‹A[Ñ>_ÛàR:<¤Ò?8¢Ñ¢";ò? MšiÃ\Ž/@΄Wutxõq¨YTéÿOÄ— ¶óö¸ÎX'já2K´Rz6aÐÅ”­ëzwë6ÖÁèwè¸UÛÀ)èEöý…¼4ç:k¹òñò€®”ê†ïmïÓôó§ÝÄRt¶¿³Û0¥cÙÎ4©!Ý1Ó¤Dν|w>@0ùu&œåÍ‚ôqÂÓ':ûç}ûüªü{#‹.€·³“zûþ„õè*Q£î8>¨Äv}Ì\ëú.;â]¢àP¥ß°nþ † ñµ+íÏÛ <=èk»ÜÎ mBH\¤õgcàÜ6Ô0é÷sƒ1”•*4B7¶WNàR td#ÎÁ„ÖF…Zꋟb7Û jXUYQI¸qð¨+Ñ^Ë6zåçÚܓZO_Û!DÅM 5lŒäMN0Å!Ñò zç?$>sBaWƒBÚG~øv…h-€ÃO»B…éœVÒ°v8Î2%îq¨og÷œrNZ좋M׎¸o @ªâÎ8~U…td„aÒ¼9T۹⼆;¯GB½×“¿ýʈK”³®-ð†×Û48"Åycâ’¦åÎ{Z9Q:yÆùø½¤èMÊzä\Q×ñzµéŒMT4^Èñû ŒÓã:m;Ÿn©­O– lèè}ø}M¸ …[ügRWð횎¢‡$ìüÞœàˆ"#±ù:OãŒg&Ñ}Ÿû* 3´¨Ï|šd EÝvï–(ÑejYéD«’Ÿ¨:w›Jýo¾˜ÏelE s£Æ«°602)!©¬?‡ÎLš«Ð©‹Ãä`NFöGDÇžíë(Å ¦Ö"rû´¥ ¬ÃM.²L 5Qüîz¸0Ë íñÕ• LÛÛé’.Ëg}Ø]ñ“Èë?M„|èí…4QcÖº=— x3ovM丢s;7ì?"€««ƒÐ»ÏéÆ\ .—£ò”óg@?DçˆìLd²D…Ú‡WÕß0=øÄ@UÑ“ÃØŽžWs\xèà&é ¦Ô¶o§¬UXÀF'nw,òDº¼u„:„xõÊ$¶Š‡ðûd`¼Á1Øu[C({‹ôÀx*WWé~¸=E ¼bÅÚëõ;ÎÅŸt¼^ð¯ÉKT Ñ'Ð"ûe›^`L…½qÒ ê>[;tÇø Óê'Xá-_ª>‹ô¸d³Ã}¦8 £Â~Áé1îg˜<|¡’=¼á4D?'_|þJ¹ÇÝÙrÃHy?÷ö‘„#…G@,®¯ƒ9ë¸kÆk…v&§Ó嘼ʜ‰õ/³¶Ì*jë•{éú桳|o:\uÿ¹Ï;õßÃAéðh×üøOÓˆ#v—û`4‚E9¤nûzàX£M†»´Áp7ß“&éBL0‘yi#]z̨ۣq £É\?Œ8°êiÓ<™¥L¡’Cø0ÓDØOj¯¦¼sŽÜdMx“®°; ²«Ü¯v¾“áÈöeûœE3Éúüt2 Z¸a(ü0yNäò¿"±På ßÏÅ?åïjÿよ5«ÝÇÈü‰10ª¾ÅÆHpAA4ò^°ÝÜ¡!^‡`ýý]ߌß4‡óÑöŒ>WÕâ=Dèøý#ÜÎ3gËë€xœÐnwæÏçô•Uâbk&Ó=‡Lõý¦)À#¾A»¸½·›½_û΃Ðê|Ÿï\‡céΈţ§_¦$b¶Bqîáû ´’åéÉ‹UÞgÄu2w éô2@5¡fa…߯Lã¿Óô±,£‰µûc’ô Ýñ€;©Ë§ C£·*^(öì†ä*j%“§®DµD‘Yª­Íx¼8³Ð "P÷7¬é/4)h_iŽ–-ätû3Ûà!>@² Þ¸t²'ßyF´ì4bˆ9Ž$æÎ[ÖÇš3YØ·„¯±ÆjP—ûËñÿ˜µò(C¥&Ûï>¹¬5éÚÿÌH[ks ¯-¯ûx ˜;9Ïòè&7ŠÞûää°éúâèòeñ‹Â­Tz=ñ;êOMG›{ú† gÀvÚøÄ«Þ&¿ÏÅpº¸zutÁåõíŽÖÏàáËÛÄ”ªû|g0Èð½ûc&*µKQÝçÀ:¸  @ø(Ñ…6‚ä{¨2õŸ.hסãÈ-%šø#rpH È ëâžôïÓ ³  ;—ΧDù¦QC»¿/ÂÄÈ9³‹»Ôt<þ±Ì›ïœfÌãã±ìàöp¬›~sùý ÒÖ÷ôóû[¨ó¾¯þkÛ?×Ü}EÓÚ ü¨|$u>/óõÃ^˜@ÊvqZ•UÒ¯O“¦*XÀ¢¶)CÀáúE2éšßëå"¡ÐâÊlÞör¢”,m¶ ±V6C‰# T”õÆDD¨h£ßQ2%fÙ対!jNqöGßæ‰ k_†ê;ÀÃ×K'AD…ñƒ2 ÀÊ'#]—ÆÃ@ÑOEã®nË ø,< -yž0…ÆÓmñ†¹I´E,úOÊô0ܶôññõÍõ7úÿ¬1U€uÈTUèµ7ßà @Û”ô=X€x¼û`Å(â²ýþGS]ÏLmm\‹ôßÁ@®Œä(„uJ3ݰð^¿Kñ§Hs= óæ€4|-¥R¨5/¨üˆØÀ¯ö·É®Ò"s0}p¼m‘yïòÊ,ˆ@8_‚!¤$Èñ?ëú×*{ãÆ$w¯†Æ“ŽÓ‚÷8w}§ôp6Pùæèo û?–‹¸[¾0 §ì!ÀƒÆ 7ÔãÃÛärU†ì<ýÿQ#«úv?¯”^‚ÒY;”ßZyø £^äJ>¸º;¾Ì ÐM7Å~¶´°£|’vÖµò£>¿iµ(l×ÙÛ=œå×5êü@ yÍÄSAÕÀ€¹Ÿ† ²kãÈîm×ÐÚøÕñêà!ÕzÎß”:«¶Ÿ …€0ökì£ôùkt8|/ýp«*›¯Ø2Þb){§W—Ž˜ :|ƮƗ þ›z‡Å@Qp.ÇÔ×KèüŠX®õE®§I«ß1*z¾Ã÷}±( ;óÿÑ„±¨æ/JgÁ{®ž|>2‚Œ4Zw5€ $/VxÁ`¨ ©ü8 ÀUà:ä#>Â|Žá=ßÒê2‹Ó¥OõòÁ_õÏói³VŽGYÛš¬ÞPƒ t‚Ž¡Ï¶ !´HàUU]]Öz›Ã¹¶¶!¯6]¨Ž ˆ›²â Ž)å&ÿåÔÇä4['ËüyøÜ 00Ñ¥œþTÅÌ´ס\æ£å+ÚµùxzoàŽÇNiöAìèüº$ˆUÓÜ8ëƒD¶ 4b¥ ýÂ{?6ÉkC„ž}»_‰ÖOFO¯ðø¢Ä¡H½\R?Ž&Ôô/ƒx1³&ƒ¨@Ví¨/\ûШ+õÀ£”ÜÝÓ‘Hï “^ûF: Â" ðG·ì&-¥Ÿ%%Ç3€dð»NœÍ˜?D Œê1ñÉfõb»nÏ·~“·Ò¢K_KZëÿq¼h[¢xþñ¤£«¡öÍ¥'@z Å6„Cx÷“Ô#œ3¯låˆz<åd±Ad|L DpR\eöý"‰©€åªžå=ñÊDê|¼åµM÷]q¦è¹c[Ú¾î6‘#KPg¸'¾BÆ/ÔQ×Á_©^iÔ<é×þYC;`^„3bŒ+ ”Ì)÷"xÞòUDK½ðç~¿Ž*v?_˜;Ð ág è8´@Nãói¬]\PUƒ~ïÈ ¨] ó+íÂÔ‡Õ-¸,Gƒ Åé*SØ©íòõâ&î}YÀ]Ë›2ñ!/{ß߫߀Ğ•gÅ9£…n„lã¡«Ûy»ß"K픺 ƒŽè1gO6ÝfQë}à#9Ä4¾Ô@5u¹Çc§Äeb×TIöŸcöˆ©©Æ³HH¸ë¿¹3¬ëÛ/¹W‚½é“a÷JýrMeK^„IÎkæpŸ£Ól|¤9ËM¢=ÂÈžóZÛk–¾§Û›l‘òk]—»~úË P /mñˆLŒ24Uÿã4ð”}òê S /¥À€|®”Ã"½šC®Ó¼Úô´b'F†›ØÀËûDž±@{ž„Î<+îǃËpS¬xƒà¬Õìô×àŠDìæØh‡g™ó6ßTúëÊdÑ$6…âÇßóˆN_¿õ=þPË «¤‘äþàøÄj,;Ú>JG]LÓ±Y{ÍûbÉÇ`®é ìsœ®)à`{|ªž·_\™¾Ì}/Ψ¯¦ÿ×úÁ!q]ígo9Äè4ƒ{œ/oLU¦ÿ†ƒKjÎaé‘àà„xÜtöLl¡(ªì Æüc-¥z©8½. (ò9´Œ%-³u££²|¦ÔãÜžçî8î"*8×Sî}/Í'àÂÅ(«ÏFظt ²cår ÓÏÀɾ‘z—¾÷¼A¨… ?G;® þ³ýõŸè¿¬Û‰ƒšf>iä(ûa¤,G+q)´00!âKèç„ù·P_Ÿ—¬5ŠL¬9ƒ—‡‹ÀJ¥Ú‘ž2î6ÛNtæÎyz<ôJ‘Èo@;O5ì rÔqhøsñvt0„³^ã{µëò ÜšùªwåSò'êzzöÃéÎ8ýÃVdÐ9£&i_K ½ÿã/¼Œr©ÇGÈ´]â¡’Ÿ¾ìO„dßPœ¹pF ¾ÏßâÆ&˜ëEGOï¢ ¶P#¸›ïõÀ†§Wya×ãA91`nYÛ½`@ÄÕkjW[ÒŽ»`>ø!ßhý uïB2¨ˆ¸&U}>ÙÕý £×ºÿÃöšï†”ˆP:æ­u9 ˆr‚Šbjâ°,"ÜàXH8êM`Iåjºs½˜tZòj!`$”&‚COÓÓ)q^·«ßä?ºyŸO‰, ÅŠx©ˆšŒ¼&þO*|a¢„Œ¨úa~¶èê»ÜëÉ…RЬ½õ7ל[Rd9Ù.ý¹Þ$±Ap;Åw×aݵrý™™wI¢KNqTy0d–N˜ túšzfó‘ëÛø`t<¿± šè 11 h*Qh2ÓŸGÛ5Ä…ðFžmËíèŒ-Ûwâïo? z±+‰×ª'±eå8ó.sá(娙eäN£‡XÙ´&á·ÑöÖÐ €p|aè¢êÅꇇÁ•H,Ú|-ë?2̇ ÷öÞ^âü/÷¯â¡ÐߟؾFùgŒë]ùs?UŒ&€åt~¹B"£CÙ§Ê ]VÑ ÝgF/$×pb ­BF Ðu}°2h ì>îQÄņ‘þ¸Çx›cG/CE î¥ég§\J«˜@9+ÀïgkJÉ  ¹§§*I|ª¸p. b¡I„–PU*&Ç~:çb%‘¢Yºo‰±H8Cƒºû|¸*0¹’Ø;EŽ“iÐ3qbCJLÆ’¡Dm=~T—N!+–\é{±ò‘Í‹±*TH_ £¯Áùnwíñeõ¬Ÿ5¼ƒoÔ}þ)Hññ ,`¢5½©ß÷0SSÄ™€@€tø†œî;™kÎǬ[Tì^:™Mh÷¬Aó8žöô늖'ʈya†ë«¸¬û:á–é¨!6‚º÷Âh bÂ4ûä«x†óŸË’M»e³Ž¿"àÆC¡œÿ?þAÈÐz”v€‘!…ÃÙÐY³€0A$Ýf˜¤áÑ„>ImoZÔT5 -'r8ܽ ëw­òÁ@ #DÆ2Ö¼·~}p$- è"ûŠ`tT¦Ò£;ä~Cͺ.>¦Þ¶¸òêÅê tÕ;^‡Úã‡"‚¤„N7¶øëðEQB($¥ëŠF†ÄçNpDGbuùŒLŠº±ÓN‡Í/ÇDz&´ o‡ óϣ摇 ŒTv©z\'Çqv†Ä«·äá;kçA#³4„€8ç¿ìØ©Ý~0ú1 ÊpÏlV±í²ïñÏm‡¤f qåÏYüá Žã‹&’ù'üizËב§•:Öð¨9’'yˆ©Q…ÁBêPv<@Š&¢ù%‰É7gt";€vüi³J‚!M¿( “ÄE—¶ ÃòÇ SMä`”‘Ë_O¦LØ5ð,¸1;|ÂB^A ÿùÏì:–k¿ì9½ÂžR/Ñù ‰qð&ô¥¸¨³…F¡'Ú¦ÅA Ñx (Òrë!” € <Å!o{jbÛìïƒR&0O3]À-9’lx®hPõ|¾{Ýü â·+Q5¡ÕVZvŠýNg³ãã»ñ]MÐ<ªo§8¸èI¿å½íÇ"Q0Nµ, ×£ðL ß-sƒÙ1@ŸøöÁr0ÈÀ7v“¯9)µ„:4$’ìÄ")Ô/#ò ÈÒ_®åè~¦|^À&Ѩú¨{âLr¬­UÚì 3™2< ^•‚¾Ÿ<|š-/zÀ˜‹ªž•ù8½­>ePN4ïí÷1Ú¢ò®ÞõéŽÄ™úøxŸ±À1|–£›ã»åõ ?Àe‘µB  W¡\"ÊŠ G±¬RÌξ0g4˜b:#î9Ï’§Ë±MµWfÚâ#Bö.ÝŽr“KTˆA¥º£ßx5•ÖÚ-!@)u½°SZïåS³} ¾œôÈN€W`:¿¥j ¶s SUeà #Dü˜VíÊ5ü|‹Än@‡× ï]>ØQT†Ša%ù½¾pGšrÝ÷ÇYFÜ7ÏÓöàéÛöa /cèÓŽ~$“/ˆ"…ݹ}±õ^]‘ËmH“Õˆÿ §‡a›Ð=scdÜÀ@™DFªk½½S¢TaPî‹­pÜßÀªPão>=S yŒCô‹£z¬˜ÜCΧ۩äן€†t`B«å"¿¶XÜ_ÇÛü;ÎA}Äg²à°ŠâÛz¼ï^—áBt0.³J4‚°àž}jõÄ1 TA&°¦ì¸ÑM}§ÉJ<ØÓÞiíÞuÍ Ào7Â:|‚P;î½ðJnŸ‰Éß>›ÆîEcÈ^_ÅE"t‘¯žÃ®@ÿH žØË;òÔ8j!uË]pÑRó¹t ÏYMäh€½¶Ø%;ä€bôc¢ÛÁãQ”mUÕßþeºÚ>zʦúóÏÅFãXùª8ì·è_]ã4 ¿U]~È7Ö¥ G ¯l]‚ ;þ2 ©i°U³sÇoŒŠ#W®øÖ] ”{ü¢±ÀªO9¥ `ˆ£¢vø2„ÂöÞ9’¢ˆÏýùØiVê¯ÙÄ…}1ÒªªÙ¸ý¸öĸv(ޏásb’„§“Sÿ7·¥ÕïzNdzaàtŠ"^'¶@ɼÙN”ìCÛ× ;tRT*gŽØ I…ϺÝï£r­à਌Õ''s ¸UùÝœeh¯Æt‚ýÞ·ƒA´z'PîsÛ-½¯B/Ô0߀(]Þ{]qŽê>ù{)³‡œDGxåH ›µ£ßXÁ8TI´7†k P]›ízcØAÁ¨‡lj“_@*É®Fÿ à*Ä¿l*ÐÐs gÃB'‰ˆøŠïοüly¨‰ÑŽÙ$Á#§?gå?Òÿ1ÉoÿpV/¤›9àL)¡®jõ>Ì/Áþ†¤êvWI°E¸X× T„¬ÙöÃÆ„.€M%2À#ÁJQtúðü[àréà‘Úºñ>¤(ëÐêö}7>W&P(ñ\ÚÞSçú:|šØz•Ü_iè¸ë‚Xp ÿ0µ¸ õíó³ ®H5hfµã¦(Q"aTctKw‹Cך §À^ ¡k(âý°°Gϸ%Rµ’nuf´¢˜z~ +¯à†H®Ý8Ò«°RK& L(,xŽ²á›š<'Ê0Ó»í>¯ì†}Žÿ¸Lç‘Ñáãí0=šB©àÍ"}‘GÕùXè“ÛZÔ"»½þ ! ‘ä¢v>˜—²MN_^àâÔ.š«Ù>çvsý>ß°™±A]­[׌ò {Zþ2#Wº¯5Ó€aÿžg[PÄN×v5ÄSXºŽ eC*‚x0{æM£ÜˆZ­ ø™—„AªêjY‡†XÙ³«BàÒñãñZêT¡'‚¾¿„+I­¸©6úŸ"ÔÒG¼þïÿƒ„aGGD«û§¹ó/ Aô¼•˜ ¦`v!£‚¼³Ó (ä˜5˜“rúo/@È»C@^v><üvuô.¼`! ´º°håßO‚:!É¡@—‡o~ä…PÜ£½àõ÷Àò8ZLCÁ©+V»©Mî{ò Tñbž(_5Nby>Ÿ&ùx°fÚqáâã:Þábu½~eÂCÃæ‚³”;¨ï³8Ê=Ý“H…¯qŽ›bO5‚Šm®àQCRÊšŠ9†¬Jñ€Ça # "•F; ŒÌŠVi£QÇ»P= VY¸,û沄‰!½XöÛB#@@<[^ O‚D&]ÕWÓñö€æ<ŸŽ”ú‡þß²)X*6©Od/oFh`ë0©ä–uïÝÝŒNãðÙ–°]ƒ"ðÂ|ÄÏL3?NŸA±¨/ \žEGäpV4#\™ÀO9?ÄÉeTÑŽÞæòuªkRtü‹VNÅû:«,cë…´®Ïî3È.—“üÃù!C“¥Î©nZ¢ €›»©r?„† ƒ6«:Òž³AšFµ‘e/làa‚.zŸ†·u!Ðñ׿³:ÿî#"‡6º<ž`Ô»Íwµ‡Wý>&Zª;ñé"ê–Öÿø=aŒSIäóš HI{?S§Í³“rŒA“`§©¯Lè$`SãJ"rBãˆÄ…‚£¢h{ñˆ°"üò/0à÷’垸N%÷gDj=> ³V^ŸðWmN¹H¿qÿS9f‹ßõXý±d·r;Þn›õËðünlá¼Õ"VˆŽãˆ”Òƒ8D–ÌMDDH1 Ð_Á¥ ÄgŸ‹ÐiE¦¦ôûâÀe7/¾ …D%!(sÊ‘.B YÚåkn¸Í¶¯®CTî#p©€Ê¯u\¶”MÈ]HNnµZÊÀÚ°+Î~ $åÂ<½ß’‘ GI” ­ôJžÎ3 h bÇŒáõ…,pu)3°·ø§÷ÉSDF˜ëÝïÎŒé-:ñ¦þÿ˜‰÷J¤ÿ§*k ;àzj d…x©v™UèòÒ¾ˆzò#;BzÿS¶ûütu &¶Hþ^¾~aŒªižâ¯ 9 JP ì‰íòÒ!Ý žæ¥ÃŠ­*%}—áO «ªæysÜÓƒÔ>VJ/aìÂHÛ%ó€w@«=8C…3“Eo]:¶\ÔAÍùÃa‘E/]iädÅÂ#Xðô}>‰Jåñóó‹dGfÞšþòÇ5Wì|¨@ª€zçQð;Ül!·M½°ÃbP=/n2|‘ l)öõΟ‡B?¿ÿ T²¼úºžO\é¾]Ât||ýýzôúœ<õË#egïC¦‡¡Üqƒ­WŠ}±èj ÓdpœSΰ™“EIÑ÷#“ȹ”±E²ú`¿$O€kæÀix yç¦>»C!è›È‚a9Wºò¹¨€ŸX#5R€åÀ(2 JÎðC±"•t:%°›QCt;ØŽáü•É5&«ðh©Ú‹p8”þ±€Á¢Pp®ý?I¢4ØH#NÙ,¢ô•œС¤D$Úì„I#Í¿è|TOÅB‘ìüV'^iœ3¯™ð®˜HtBŸë_±Þ;a‘ê[B…÷Eïð™àè¼ú—H§Wß}¾Ä/·ÊêY'°J÷ƒç(Õ²,g[×§^p@#G„øÒä^P;<߯O“F̵ê¸Êá)¢(ׯoœKRMì0ÄætŠyBñ€€"Dzà†Ô+J $ú.¦ ÉÂt7«f+jí¹—®Ç™¹Œª`TMtŠöÂQëÐ*gF!Õ79Â(AD¿'ûy Ýö?îéæó~etçGaªûgºõç¯ÎTrDКSÎþ§ÿ…¶ï¥ävû½ÇÚ•À”Þ7«;ù&CÃÑ¡¥Ð~¹SM'vUíÓ9%¶Úì/™ß%27]Ný}q eD(ï2H£Lˆ°GÃDQÉ£s—}ü 4(¡Ý¿LhDþ¦sÖ,R^ïÎѬւ•½!ç¸d´¸ôààg~¸%×D4?Úç¦V*øWßDô¼$$‚`œ¢ò—N{2»†CÊÉÞº’ã9 ­ÛzLŒ¨Ûº^¬;ûä„‚»t5è<à2C†Õ° m”/‡ ßXø 󿌬i¤¯Bà¬×4iÒÊwªšf²”M•â-?gÕý‘bðkר{c~Àêw½VtóŠ´$’r-ß9iõ^®ß'Ó:?Ïl–±`ð ÐÀAQD›öÍâeçÿ%×ÛÅ÷Ë›‡(ìÆô˜àHArå?"Dy¦í”†ÙÐô€÷àMчŒäV»jï®ð6(iÚ£²ºãã¾rÜ6n:wX0‰¬óyv×lv{ªzhóÎ .@A,ÙXëß}¾ ë TúǶ"­Rö,·ÝwEçC•rŠ:ô©ÙBw<ñ†R³ÙÄs–8D<¶tà]á °œ`Ì€ªœ~/ÕãÕÆ€ÿ¼óAÒüx÷>‡à2å9ÅGñô;ø\Ì]L<×O±ñÎÈ#é;'OC–c3/SOyßΨh¿¿\ž-€”c륳¸dÙ¨RŽM*Yl5FAÅA“FF$G‡.;ò•¬ÖIY²¹á(¯L(&û6Dz‡³ŠDG¿ö:{‹¿ÉbmÈ18óy1ÕnRîu¸K–Wè3š£zGL0šEêÕóƒÑ<Ÿ4LÛTZ»±Ž”kz¦¤¦†ž/Ä1.`m÷Ï¡Òi̽³m_–=Ç#×~|f‡ÀI {„CÀ|É KÛ×ÙÝ»£\åÀ ŠŠUR¼¦¯ÄD. x~g]#(ò…þeí«…3Ó(ŒWHëNÙxÍð‘Ѳ<ñ7›­ ¬Û†º7Öá&†J8½cÙ)ÝB³öúÄ"t¦„8©qÈ©j€;á]Ë„­E”áóÙíƒ^¨áè#ï_?¹ä^«¼"K=6B"ù—Ž{S”¯`ÖsÂü o¤À R¬=îOÕ]ä1þp"C¢èëÀûÞø¤dºÊeûæærž“åþ¦ z.ê”àyÁ#Fš¢H ÐèéÎ(X©œ “ûçAÕW‹º7¯$fÔ8ËîZ½×DäEè6õòø 8ê§v«ÛÇÃsíT„8µuá” „/cÁÁãÀ^SÍÐñ/.ˆ Xì:[ƒLJ® ¡$`R^zè:&r²C#ìàš}…Ð&º¾yøC¸ØlN¾]‡ ]†CíU‰Ý8Î#gÀßË+ëgßç¸îÆývéöÿð”4D<žb²gKG£ùlôÁH!Dhüˆ&ÑUèWÆUáÂîÞWÛƒ­\Õhý°–®¦= ÂýÌ Auô2fk¸yý~#GðM(pPÇîbŠEšêêòeð!A9’#­ý_hCò£O®¼õÜ„”˜ï£}“ÐîÆå"BÀÀ ZÑã˜BÙÄNlçǾµ .«æ!à ÷'Ýíót(©Zh¤7Äó…LF-·JÞÜó¯€BqŒ*ã ¸Ù¡Ðß,³/G°liÁ•iΦŽïÒvÁ¥÷C+é×w ° 84r$W¹{j|¢.-ïC^ ¸>N‘PŠ€^ð¿_Ù$¢  Ýž\? ãf“Ç>‘ø¹Õ›õU÷1Õ²%íÖÀ(—hÒ£ÿ£ûHju>ùÿÔÅ]2øXˆýŸPøHrySy}> `yu\:T7ÏÀÈá2˜àÃ\ùP;_! º§f#¬7B»=yûeÛF¨¢Þ“œœ@òëÖÁöõÆ|\¼*7 |½sÀ=‹N³¢˜ˆ¨¥S©¾%Êöe(ƒ° MˆD\P#°!T‡Nëfò¥ ÐŒiŽÆV!Å[Â4KÞ[8cž€¨'mt‰ÑÍ?ñÐ,å Uè4Nˆë<Õˆ$0ØÎ³ „% !´EÎtßs8ÍŸ5Ó£Ÿ¯Û矻û³1Ýÿð厡_·W§ÑË+wÓÖƒG¨a€!DhæÄ6¯ƒûàÀ¨Žïwéñ Á{ôZ›©„¼ªëÕjçv%§Xµá㱃zm¶ –wãN?zQ¹Ñøª‹¢;©tµrB"4Sd£ ¼u|7šIaÔ:ŸÇ8³ÑZÐa-n€ÜGCaŽè1´—H`cÓS)יݩ…SÙ"tw%Ç+Èí—l†ˆTaËbË1Q¨£(¢8˜:F]Õß¶]a¼P=Súd*µ`ì*Û·ÓÃäè<˜dÞ·cæ»vÀÔŒ£í• ®£éE§²fšd\u½ÁÐxÀv>"ˆ1ïÑ÷ø€à‘Þ%ʦ”Xëõ›<'Œô.,'À®ê}ß²¬¶j®Šë÷ƒ¹FSàÊe…}ÿÜ$1·dêrY JªZƒEIÔàÆ¢ U¹õÁib£ß0—À©çI"°f[½ªý>9 ²8ÓËÏ?;/I5ì'Tï}óüõŽÀ5·¶û¤::Õf4$§a9Ïă® 0ÕK4$ sc¾ãðP‹³@Of¼Àãt‘†šy¯J҈ߎ àCXˆC²ŽEh]´`TØ$m€Ñ&IVŶç;ìQü»p¶›sª=Ò*­@ˆ9IÚ.Ö:(t'*à„ŽWS…àF¡­òÕê¿'2o¾1ÆÌIñJ£¿ì~×çh®v_PþÿüyüMú~ý±]»(šN¨z÷Äúùíðv<!NÖ¶‘9àÖA[ ¹„ÁPé…‡œ'Ìe¡§>ææ+®ÔÒ¡éÃ<©ÛáÇb%(]âç´íÙ8ˆ`’9 ŒvÓ rÕÑųa`.Êlis½%é©&ÃJ&²R¯4CÐF!ñktÐ ³N…V¯Å-~DM›œbEŒKÖ9é6|¿‚¬(ª¦²…`È=†ôªn—Y9鱥盈†ìì&KN¿ÝKŽAÚ^wÅ׳‘V  Î*ÝtÕÇJ§Þ¶(l?çÎ`CÖB„Ÿ\6f ABöñSè8]zè÷ʸ"ž:_¯ã;r0ÆsK%xïúÚ­è8öx|.E´èµèúœ>Ÿ"'— b@<QN“AN@'ß<%˜ÀV%ß²•R8)59ÉÑÄ(Ú'¹ ôøÕ«•ëoNïp6h£`ê”ONøŽ ¼Šˆë£:Ã0½p ’hØœÞ1I‚Šu:IöÀ’…B AQ‚€yÖ=Á xª¹Îy•½c¡8O~™¡™É,ùZ]î šE`¤#8CŸi>žä‚':vÞ“ ôå^ªõ|þ*cÞ1޳AÀ'C«–ÆÂ+×åÿ³%ŽÙ8†¿šûþ"ñ(‰Ò³éøÞf*ªóÿà1ÌÛŽ+~¿ã Q1Í jˆ$ÓÓ‘ †ÃS` XüGXVSĹÇÔw Aë¤<°_¦Ã‘l5j¨”{¬(S7©ÐF#]u³˜¸­ž4"€@EXV àn•4µqb½tt =×ìÀÐÚ&¢¥zpüAÌ`v?¿ÀvkXÄÃØÐ5<¿ˆìnáôzÿµð2¶%ÎnFÅÚן‡#X¾â’yéÃ\€Ð¸Á):o® [í‚:å% ×X`GUØ=r6MöÂ2yvz‘ï›ß ÕlïO­ãß-tÂØH»¨¾±øë ‡A7_ߨ`FÀ¸þ쪨v1ÛL©Ë[’DÕh4G²=ØÈEÊ `3J¨®âÃ脾ŒvdŒ‰ßD…»ßõò+Xš¨¨Ü§¹¶ŽxØå§.W Z„=6ëâaj»ç€[öq”­±¯QÊ=™è‚ë Q h§©†"ÆLúá0 6SC8ˆt°y¤ØÄ€[îèz¼f7Ð„èœ»Ž¨áü&F:züWÍ©B è«õeŠx&ƒ g"ÀR h^§n§§Àã¨RVØqB—+§Ë‘B‰Ôü3¤H×åf½Ó(Iu 8Ù=M¿/AV 7ÁüâÕ]¯?_Á|=ʈ|Kuô¾g©ÿáBÜL Á@úoÛ$ä—¨ŽEKÒ‡nu“V|>žgûLz)·QèÒ_ £4ŽºTÆ\0#+°ŽÕ¾+”§cšÒ&Þn'U)ÄE;½ðǰFÑ@Д]Ži˜±OÀÐôÃ$  qð±öEì…_H÷ø¡ë:•Ïà‘mÓ$‡‰UÐH¯Ü>k‡Âpì(8³¢Úí¯#¿ÅV‚d8£N¶’ôfYD9Aác³ëŒà' .úÿXãôÊ.ÚD€´˜q ÀLñ½zð&g{K}Ûéªùyevá£ô €‡a¥ ³¢… µT«ƒ-VX[^—!«d4;¯«×ö`ú/Q×Ü}ãÓ@A †Â‘ëMãŠGéð r›]Ç‘òdÛL†ô™~«TêÄó§^p‡Å“PŽ™ñ}³ý—õŸì¿¬K…Ù‚º?îp\9 îÏ#ò(·X‰C¬†xÇ*4U–uiw·¤sGø`0&c„2ë‚G~Eïιn:|72°ÐW¨“®ôuÉ ¯ …ðoÆn§ú@ìÆ{`…@Š®ÀsŽžu¥Í/Q[‡>؉˜Ey ÉõùH„¡@/âcÐùˆ"+Ùòq’† ûRé^Ž:“hÜêûµÉB(›­»ß ï†Æµ ×O’Š·ûü~ §5ã„y锇°]øb¶@VÈRINß4U MW¤c‹ ÄƒFÕ9âaçj˜6ç(8T ¤;:¼ã¸uèo)ÌiëpB‰Ô«ÐúÌ4Sˆ«µ=ƒ–vÀWh]w×^ÞØ"ªp#Ùqñ«±"¢ÚŒL.²3zîÌò¦=TÖ9j‚ª˜ Ø.êÇ @ ¯1Íø9½©òD«€záß,l #çv¦ú“âP4½æ"¬êümW»C­PûyöŸ¦œ…ÌÀ»!Å\€D·ì€€9Þ˜QFã&œ jDÝ"€ I…!þ!€ZA–9?[Ðl¯Ý<Ò~Î/Ð~¨¯Õ(÷ÓÉbÒà €^ Èö X›â}q) "”éM.öô“—N„ ³¡®‰Ïų¨_¡3ŽÇ/fUvøú+è„oÈ ]¦04±PugœϊΈ‡©‡(!·‚÷Ýò×W­D(^ðì×8F!VÀñúbˆ* ƒ”9~P¤$Á|8ÊÅ=seŒŠp*³“¬éšœCG·‰L>CÐzûtgE‹@R½S åsfFÃ}£°à:` Â·µ&0BáQOø œ2÷2%™ NÈú&þPâH &Mq6Jt+_lgRÕ²xbçâ­®‡eÕö·¹BDÐß± ȵ<ƒ^6N04U”—·CF°™¶Úi^w^ø ·¢›JÑ “žÎ v@á|óìoLÀv{e]8'Zßò¿þ.gFĽÀkÉòÌú ½-yrò@rJØ8¸ŒªäÊQ§¿Kés{‹dAì÷ÂZu¸·wIø KéIáÁû·ª:íøËQcé/ï è>0ËÃ(G„Àfë‹ÚÏÔs‘r»Í2ÛOض֡¡8Ö $†À†¬C§Åa›/°mþºâg°ÓÛ*95gìŒH—¤p‡;ߦ*‚·žWÃ=q4P׈Eáââà!£Ç†Î½¢Nð7àŒ @Hß„cÓž(̬½]ƒ,œ<úU¢½2À^%ßeXnh‰ø¤¼>ó‚†cÔóJK×yO…&»uï$_ ¸£ž§/–%i +jë‘jµœ¯uêùgo±ß·ÛŸlP|€´*}E÷ÀÀuÓTvvÈД˜£²Ð¸‰À&`ql] EBï/L8Kœoíí%ÍtNèü¼WJ–®€«@H·UAµ0Z**dôXMC d¶à“pŒpô/”%Û!廨xRšå"¶¬*ri½Ž°l`:Òˆõ(¬Ú®9ÁLF_UzkƒhÕâ9aÓA€˜ ®]K¡³ÕÕ„ò{a%·„:&¬°îvõ4Žü9´ 7‚Ô}vtuÛâ½J‡zè}2°M1A×]˜ÕD`€ç—mþ¾&¾¶=©—ox€)©áioØWÛ õ¡ Q“ÏŒX .6"iý2\ñ$>˜kpúæÞ,÷ŸiŠöø¯šO Å»ò½ç¿>“☔pÅ=ðãTj{î/Œˆ0¥Uà6mÁ8ŽÇg“Îr"¨»3M ²'ÅØ‰O_Ф‡ (uSÛ¦\¦©êê4ÜøÄn ¸*7>:™Î*ÔA€â@ÀXrƒÍFr@rŠÒ`r¶©@ ‚&‚ªTI:•\@"›zšóG³­c@ù™¡Â,Rºè€i ç°—„{Ã}'Âü¡«†m'BUQÙTÓ½>N[‚ß<æ—¨¯|òýr|JˆWzw—XÀ;-±üÊ —W ‚¦,f!R€³±;½̹aè=4šäóyoœØ©æùÚßSº2mÌi¡ @¬16êÑ_áÛ @p÷êþÏ1 òÁ0† mïžyÿ™Jq+uR¦Dñj|aÛ°X©LLFpŽ*:ÎI»$%/`Ñ3u{íÉ!p=cÀÁ[€èiIH-G.v,1:çØÌÿCŸèpfR ÒÃ~ÏÓ?Ðçúl–úøô\¯À‰¸æ½Ö^~Ç4„§%!SÛ%kVÙzõùl1²2 }E=þ(A3•ûó€F‰ð±©NÄ‚ûŸ‡;Ü·Å>ÎÆ A%ç Š‚"#¢&Ì*¦L êè÷#Í׈öî=Ï8'GDyí®8×8ˆ•ÐP‡¼^™Æ->1. 9Ìžÿþ&€D£ÈåÒ­š´@¾ Ó丵ZghYÉ·*æ’+¨z==&^’¤ @Sî}ðœqIŠ:}'×àò5£â ÈÓµ 'LÖp¢xCÈà×îZA¶­^šÀcIµ‡‰YC"ùKØB£-´ Ãl£jFɯŽÓ¶®yh÷)¬Ì¥tŠêÇ~EðZR 8ZP2¾Y2ô¯¦`HŽ5Ñk¾ó!³,.”9º×ÎÃUª‚Îȶwo\ ð«ÑzÚ¸ ¬==@ E¶£\’ ªÈFŽÄXä•° î{Â!U*P×ST cˆòtÐhP[[r:ðEJ¸4`ôý¦jE-qç ­ÛËAçb÷ê{˜vNX F€€Õç¦R„]•Ðå^öÏC`Ç¥È ¸†½Ol1qž—”ð©ã$±^Z Îqh, ¼Û_‘²Íšhm6ñßN4¨r{üf›îÈ b[ÀW~üãLõh5Ûâü¯Å–\wž¦NNŽAŠØj†%õ>D¥i×\cJ®›Ü‡ÁI­Æ¹SÙ~ÿ†5Q@PG‘³ßP.—¼.AŠ” ‚„Ð}]jˆÛmv(o=Ý/ L Ÿ k¨Ù¥êϪ`¿Õ¶5âyí銶DX=š 4é€geKiÒ·¡Ã2l¬+Ptߟ¾½hE(²ôÓãk2:ˆu¨éx.ÅM&è0sjú™†Ì]¸­¾ô%±qEæj\źWs–žŒÙ¶’IÖcWÒbyÉš-Œq«¸]¯í2ȉÌTíÑæ€- ž©hŸ"°u 1•=3 zvÎhî¸gkaê©=—Ò£ôh Asù®íSVnƒ u¬\Ñ‘ UHWE;áH*¡›½®®Sù v¡ßOG£}g'T€qh„#aáÅB<<8 N7 ,fVcTº™X]N†J*2A‹I³jûçÁg<Ç왺×H1”@ˆÑ¥*rœF—B=^ ¼´\¡Tí+¾Õz«ørtÕ:“Øö ‰FjtkµÓïï›h£…OH7ÅÀ %#ŠïAÈ¢ßç, ùµYï›%\§;꾌|gM§sÿÃÜ ¯•ÊhR8t©×ƒ) ¦¡+¡îdùÛAãÄ쨟Æ$Ëù?ñ€mSÌ~óO“S5ÓÏÆÃ Uz•ÖHn léNý[ð áDÈZ–¡P;'^í¼`&0…á…¡RÞç¦Ñ5‚AªŽ´Jê"˪֜»8ðѧ̳‡Í qb ±ª‹ÌÂDR`è饉¼â B0‰g…ÿ¿IÛlÀ¾ñÞI²uHŠ ú`Ðy ~¤þïL2 @ÚàÏt¬µ~¸—÷áœSjÚú3Ùøøà¥_\×ÂQÁF\Ó¶G's­hadœ?SŒºMÞº¤ê&ºá¿Úg·êlÙy0èó°(€$ +²|ìïx]iÖúÀAQR- »éQ*Ý#¼ß‰l`(D7B«€®¨ š¢Û}É´¸-I,þŽÊC“Ib Ò…P©ˆ­<ì†2r":éˆìÙ@䀦žHÜ©]tË4H¢&ÇXÀ¡¡h`wû¢ƒ`i4;uPÙ m€Etáú/÷^=¿&$QÅ„iaS„¥éèäêG@QìõêËÇlj±âŒY¶¸‘—­—°ó È‚¡(îÞ";\®«åkïø=&=Ìóÿ–°H¯l@<#øO¬|þìGA˜…é²`W(ÑÕúajYûÚ*°œõ%QzãâñóðØ5¨ÔCq^øCÝ`Wt/À=âÆÍ´Ø^N1ŠñÇ‹®Ïï¾÷ð1ÁöjÏ3è ÷VõÜ;žœsîK– –7pªb‘Pˆ‚P‰Mãu(,›HKÚÔ¨Åp#ªÌ×B$à·r Vô a„’D@gKªsºþ«Qw¤áí4.¿HÕÝçßÃöú­_>0PxI¬aä™Õgc‘Ò¥"Õ`ζõ(õ8yqÆ~` ëz8Î&&’~†¯ÓîÍ~çþG·í’–ì­ùqÝŒC¨ñØ6Ân4Šôêø«¶U>FÓ"Ylnôs{t2ª“Êú½q€ÑU^\‚­áP=L#*¶¾®0¥Í…¡>JŸÆ“86'šžÓ„FúâH=Ô4F>ù§‰y-[78×C6tù"¾¦l`.Tµ<«ð`©Pç~¾Ø*cÕ¯ª´N˜‡Ü•åÁJêdz)ö JT9uç1–eÁ¦¼gsh§³[ vpbä¶tu·Y¦Z#aK£Ð í‚!Bó£ñ)ò4ÄsÊïa úÏ6™Â,LP›ÂËÒèöŸ‹Î‘îg³öÐB{ÇQÃ{ðqKf(i;¤ÿÎ?CyÕ[Tmk¤r.HVÁlhimþ#¨¨9 ëŒõ4ïMƒë/¿Å%M³cØúžöü”ym+œ¸»T¯ ‘%°Ð !÷rV¥3"–i_´9q×ÏÇýìß È®¶Mó1<Ô¬'A=#8l¢z´(7GBcfÀë)äáæár1–ׂJSg 95ÀF~ç͈&NðzšØ¯Ñ²pl““\k®¨bõ„Ãro¸2Ô-›ãŒDœA¾Ä¾Ó$MÃêÿÓëŠgP2)ÁK³¿}gm* ‘Aew¿.;cMç^q)^‚T¢¼ñ‡í­Ðl¾¼¯ðeÝ~·öÊ TœóËÛ¥¦Ð@ÙyækT~t‰5˜¹#Eà ¯®wðÊwè0Ä‚’Cµ­ƒÓ×7í…z}K„å¢éÇfŸo—zxEJÚƒ!Øåó:k‹åéüÃ0ûÁHk¬øÃš1H«:Z} R¥U!4v*:ƒœv½LØàòiúæÿ…3Ñ@y'3Æqú2o¾q›3Ú+«-t _¥÷˜«µ èÙUà ïøýU ƒ¤»ÑU×nØ®†@A@,ø u ±ïxé„KõlBS¯ÅQ9꺤5ya2uú>ÝOWÉþwËc©Zm ÉÚq‚Änð ©khªléÇwá$r½YÓr3¡—aÓëÙBÅ%EÀ=1áÖ™dPn]f‚"`¨•zu8¬' 'Q©»Üã{ÇÝ)hß`}>T¤Q7¯ç¡]`ûíûdÒ!¨ä½Lä=&ðñüžˆtÇCAè'÷¼·),ZRó¿?…MÈ•8t‡-Á¥8ø!À ÛC@Ó´öë3A G:ÚæýÜÕ`ò#¬IÎå8«cý×MT4%h­‹8] Ã%¨Ý¯i߯Z-8ã»z”½)ÆX"Ä%œõDg× ©1@ "Yý„aH¯Göý•1Žã;×ìð`ˆ”L܈°Bþ¬ÀNƒhIºðOŠp.02 ìsS®ÅWSqÕéññˆ«)1¯j.Cpy}9à › ©®4üŸì÷dÏf ÿ6ñX´TœÆH*‰T÷wƒœ#aŠÏL xlÖ¼aø[›¼ ð}8yÎ:+‚+àeë‚ªí ±æ³xQ^…æoG°|#]ÛôƤ=‚4DØŽé‚Ée¼mZ>åþétBåâlØ*A¯ûbÑK}A—è?mQáþ“C½`L(GcðçäK^çö¾SyAZt¤Á]‹Ûr ëíÎðÞª¤.ù4[;ëµCtïNú¹§2Nl¨’õ|¿!pLª‘ØhN˜eo7^öâ;À„8ÆèGŠ¿÷ž—mmÓîýy—Ú¾1‰?ü Ô@«ˆ{p­ŸSzk¿Á1¦!vsŒ¨¶L\Õp“Ĩ4G¢§O²Âº¥AEœôÊS¨/¬ 1r‰ÀgŠ9Ò %›™bŠ(ÒÁò>áp:º>éŸü&ð™ÿÂeÀàÄ”ß ë¡‡` €ø.8¬•Í¢â Þh5ÏLÆ€5HIÀ¨Sî> BD#7Z›'ü`ü(G‡±Ù+[†ªƒ]áÏí¤(ºr@Ñ2‡‰~‡~Eú¿ÙOCçþ°—,ÌQœÔžš>'0QxçÍÇÛ˜I»P’À‚¤ …ëšÇSƹÍöRª®€3G+4@#‘«w‚è9>x1(@` ’¸ÑŠͬ¢¤éˆÖÖ`5Üø©S’i¥Ôë‘0{i³}óàyéñ!ÅŒêz\æÏP—ݯÒD¹Þ{8¥ø +©ƒ¤^¾³è9g­jtÓœ0R…½:ÇŽù¡ìqú.°è$lî?Áò$Aˆ@ÀQ±âÈ[ÎB‚£~ÚCØÜ¿&çgÕý~B‘h„ uØ×¿"§-È\ ÞÚÎÓøÂ€vÞ8•ŸiòUÀVHšÇ‰1ÓÆ 2èÀÏ´9zkªg=m/W™ÝòïÓÛ„FÉ..ÔŠ`Ê"š1­´Eü‘ޤDXW Qù0´‹róë(¢"êÑÅ's‘IB­Ø [Œ`+Wd´ÖTsˆERË‚7]*;lÑ™Ê; .îÌ•“%lŒ q­i¥´¥ÂÙù‚tu§ZÆA‰y!ënj"„56 0drkFe›(äß"ƒ…GaêÐv2…l – S]±ÊnÈâ&hT9I6UH óõßéH.wžÎ"Ù?³sÄ–üT¸IMôi Ò¶=wˆú@¶n0u4kšp€-ëËÎäÀ(DE!ÈÔ_¶ Õ×RíEÖ_ÉhÙ>â¸V­EÝ@»ƒÌÕ\d^ª÷§Ög™ QôÏøÛñeágÈ.p>¯?ít‚Á¹u) µ1l[Ôª5^U_Á¿ØVéÙŽþ³œ\ÙØ*û¾_ÛÚž¨Gðý0@#GcøBÄlï*èÝòN\öÀ¼uu±õøºßJˆnÝàß\W)VE! W§ ,QÐà.M ^Àuu¼èšº„¸«ùþäˆm;™+…J+N+$žß¦ ‘.ÊxÅæOß»BV°QFúaôð+¢ø!­kߊ ]³S6û1è!Áµøs³£ˆ ¹gÜzÇßàVl•@ês$WõCÏÁÂ<ë«}œ\„9¼žÑß^0hÅ®–öá÷#ð0á| ¢ú›À0¾êü¦è¨®ãÑÁûH‡Ãöiñœ)  ѽl^"eú7pÃÄúà€žåÛÈzeĵ²Ê­j‘xô5ðáÉ7À•#T)ÑÃ=HBø:±ú‚^pžÇ9“ß÷Û³&ÉÖÑøm¨]dðß ° o‰ Xˆ{‰³/Ãuv ‹æ ù‡x»ÊúÃÝñ`åÈS¸”§³o;À:m¢mÚuN¿¾ }©¿@oèr˜Ÿ ˜ ¥R8ipº1¢ÁÑ>üúšÖ°ÔÊîVŽ¿“‚[G WŽfýA1„(üº'#ŸºbVéúçÆ4*ùjs¾îý!Ó÷gàV=›èpc¤(h‘¾µëîE ´E+¥›ïÏ<þ4Óçzkío*ɢº×lZjqHñ6Ÿùï -y+Áà׌>’P]…xß«B &¼–xºÍ`8Ž”®Î63ž™ÈÖ;G‚ž†½_ÕfœæMwýò„­Nú þsœ‚'ÓJ¨:»=5òµÉwëç(¡K>¦½Á|dª:WªùŽŠíötøÈ`ºÈ/E®Íë @éÀ¿dVÖükÕ1=}à!ÄRÞmÄÔ¬§:{ôghúéF(òlø°Á‚.@þrmú}¿ýÈüÀ'£ûíÇ|ã÷¢ =!éN<†DDánº?÷ÍÍù¤)vÛè'ÛåL£D!Gyìü@#’ìÆ¦h*¬|}q¶÷ˆõ:;Þøùø* Άu>^#ƒÍ{`» êÜfŸÓiÜýîN`…S‚·í–×4\ª=-ß 7Ô¤«K›*Ø*G€0oæ"SÙ”8¦ßgXfT-ñ¾w(ƒe‚—}—I­H[4¢›¼â èYNì§ÓïŠ×¹ÕI¶òë^¸É HÓÖ;>´¾>ù±æ:]>ŸŽˆh£×Ó*X1r<³Ä™+ µòÖ†Æ2 ðÉÐ^…o>r*l Ç’£Z>†Œšw€µºß2å2€A4{€ÍSÐþµu´¨ì›ñÆ$ªè0ë@m èwã{11V…9;^îûL7åÀ~â½Ã'¥}Kïç6¿F…áëA ýζ8R¢(y> LQœÙìý3‘,€&N»zã Å… š4ìmÖt–VâCiÇÐΡ>Ô»5)Å¿02Ux  ‡àõÓ±¾¦M3î¯Uz¾YñŒó'Þ´m2«4JßA=ð\ '0v’O(«pHÍl1·BKj o &»¦ºƒˆøM䟵gWºõ|ànüžLldš}WÜ~‚é¸+OWõ•î|š ! ºû7þ¯À "9ßwøãÀ;|y>a O|˜ 1®dNåÙ¹—Ô"¼è FÝåܖн6¸Ä´ÀÜUyš#Ëã!#Ú-{Η]ã;¿eûáPk«:¼úeÅCjyXŽùx`€g"×±Î?q&ÙNé ^ÅMã™ Ç@÷îü°²ŽÂòZqÙÙסì[*à.Ö•õì1ºÂ<™¸µƒtb±QBã‹~;ö=5JozÜÂ(dhI¹µòý~`&HïIä4ÁØêøÊeKÉ+•óúçMs8Íž?tìCBíø[•4Ú—…M³‰Œ)<\ŠGb8ÂDµÄ°¾BvÉsJ%C’¿$€ÔhtÙü'Æ¢ aЀ@¾oÉÏ8¥kÔÏ øø&Ü"Áï˜Ää×TëNŸˆÓªGƒÊNÔL ç£ìÅ"–;äK$*$Ø:¸A-žRÖA¨)ö:FŠ,PYçxlG„8H"B›%®¸tÕ„ ‹¢†s™ÖŽ®ÎÇl»ªŒ :¼òøõ0XPAð|íq’bÔð€¸cõ æqêuøê–i¹9ôSß8-j§ÕvügÞDÀçŠß`Î~U;®Á×gx·cèy“5pZ®ÕÝz¾_æM÷Î3OÜ›³²M„‡•ô»C=„U^ºëן€ZÎDÚêš zÉ·z6þzªA"‚ °Î"° $©Ed˜Á‹`&âû¸¢Ï®D9!BD¥ßb®rô¹×dX(Ü ¯dí»ñÓÿ3úø‚OyN EÝüÕýp¬¿uðuÿ©€”r$z”)ÀΙ`„€•'©ãøÇ §ÕÊ“¤âåóH£K9BìGÛy°&Í?@æbÀZ…‡O·!¢5E,w².˜K“’B`šƒ„JˆÜ–þO’­\Â$#ÈnòÁƒxAI Ði©ƒ–§p»è{·§îFGá!OCPTÈvêIBÐA¢>U¬0tŒ ¤óŽo ›É¦ï‚ñ¾›øëôÄ6ôû_|£®¯ÐãÕC+/O|vDÇÔá¶4û ‘=ñŽã~á2ì—8ü4pQL²VÒ68ޝ-¤a M°\G))Åx Y § YÂÎìD’&:\õÍÈ"HžÄÀ¸Í>ÔXŽtê`ciâüzïíð÷ùJM£„%åðÃZpy\¼ó4z½ñŠN¸!ÒïŸL%wŽ!Ð/Oº·â—!&ë5#Ì–hÅ:O÷Ê;€E¨‚-ÕO}Í ©¥Mù zzÆqµœŸÍhš«Tñq¸ Ð\Òœ£Û _ÖÀãöŒÖD 1ÖÞÁ{° Ÿ¸–dŠ€D5Z•jê7®%4NøšûB¢ŽnµŽçÏnJ‡a!…KRGzõf°wêá̤»qK…I' &ŠSf¶dT…†Œ`cÙPHl!óQ ~ 6×Ì`sÒ?¯ã GRç„ÚL­Ü¦Šc‹{ÏqþYè'ÑûNËŒr¾1‰?u½*‚Ú{#rœúžsŠmõ‡îÀHCi/¨>uJòUôWâ& sƒyÝÿ6ç9K Tz`‘5åJ à—ê ŸE!׃Èz¸}ê,'¤ìÞ"Tذ•, "¾vs«ýf©¤68ð:Æì #ÔèÈWªÿ9@;»ëåþ¸?qŒÍj34B"–àæŠµôÀL£t”-GG{dDBšef°ZÀˆðXPA¡GTºÓ­é¨v°i.NG Äs.’@£¯o™Z7z>hbï\n£Í#ø?g€‰s¼öqJ$ýÒÎ?…àJù|üHQrƀᮺ'_ƒ¼âÑSo’˜%á‡óø%€í2æB æ Íåæ v1 lÌ“dỹí–b²Oïñ á¶Úü‚€Xž_¨îpWZRº9bƒí6ÀÀœ;¡ÁRGr¦b‘¡çfï1såz¼H©‘€ìt?ý‰L'”ð⽓÷¦%W­ŒÜ@ª#¾‡µËšQ;¬Ù”¾£×:³ˆ8{Ÿ8uÊF("÷FÚ²oh€¢] léŠDj‡‡ÿ9>pù×ÑÿéðÓ[ÔßÅŠJ òxµ’Bë{¨” ħ~DÕãP¶Hêab DJ0îEÂÆ >€>¢>Ÿôþ¾ðK”ÿà¨$w„ö9ÊŸ¸U-سݟ| À‚ì€ÃE·ÁÏ9 Û>lßg >r«C|ç¾J˜Š(9JÖuåéƒ æ <œ:óÂÕm‰µ4ï[ÜxÀüâ%‚Â9T÷~`¶(\ç”æ4Oœ(§uà7ôÅKè 7­y}|~¼‚´ ¥¢½!ž£€052è:Ò/Oÿ>s™5ßöÿ@û¢/®°0b!QE¥‚Vñstf"$vw5³¶u:x{>1•g£êÐú|‰2ê¨=^‹æ\C2 ï€''yÈþ0Fú-»\¹ æ;»íéú÷L í7I·O|ºQj“¦ƒ\~ÿÐ%Ã(E•ªÏç´„HQF®ÿ ŒÓœé®çíÑó»J¢y#]ïY )µqϪw˜€PNdwfÝ_ã®!ý hèhÃ, ‰ðrDÅó9ôÁ|£Š6¶W@y»Þbƺºi¡éëâÉNÞ>E¡(X럟Û^VßC—iHœë„Ù^ý3|£ÐØôöÿóð%IX¦ú8½ÿ\½_‰â_æO¶ú”Ü%ïŒÈ&ÈröW×âDæ¹i¸4ùÁš0v>ÊCЦmÉIR@ì)Ó›†mÕØ‹]|â#€ÅÊ5¾IôõÄT#@Qæèôß®#(N›L#&ÀÑÓÂø3ýnùKˆ¤{”ñn(šaH·D:“ðqˆ¼?ÏŒ¿ 9™ü3G½Ñ'±~øbrî©úb† £««êLV2òò¾W—ßÿЕ(;• yîå¦1!Њš_OÆñ¯s8ž?lñÍEÁ[lPy/VНLz{ƶôÓÕêZ‘)±Ð*†­¤äÀ¸`EJÊü8œw—~¿$Á+£ow«îïæ\'6—öwÃÿTçM÷3ŒÙû] E\Dô¯/ñ䢚¤-‚× ä×àß §ŒSš ¤q24H#K› çMV­Jªõ~FE£´ðÓŒèÄE¿ý ËaѾŒ à:et‰Ñݶ=6q€ÑØþÉyÃgóîÿø2{ãlÎ?j ©ØõUfq´P_bÏé›Éu=¥ÛMkŸ;ÖDÚí!cHìm>æZí$¦»Ckã(óM‘eßÐó㜪6‘M¯»œ1àæ¥BoÍ?2¡PDëdiý1œ Bx )¡*2’C&m2ކ£fk I½&ØÂ¶¢\ÙÌægºxÐw±WMýÅÕ ¥U˸Y¥ÆH—=Û#E© äTŠ€ªhwIwÁÆ'ú~É ·F[®vÎþ™dÜ÷!/ÿˆr¦1½ŽØˆÇ_´‘ˆe–ˆ½ÿ]²ã¯‡Q#û=a^0ëeÌ¿Ïiû`1`#ê3`vHq¼倢™_OÂ5ï/kð,+Æ/…‹Ç·ü¾œ€ pþ¾%ÎóÙÄQ'ìÔ&Æ{$}Ó4}Ýãa×£ƒ’fºaݺ š—]ñÙ‡zp?„Z•*U«B… KxÏ©?ŒÒ´d=-É„­3…á7æzà@û%ÂêžW²~Ë<(kÀú†IÔ¹Eí¦{f­tÔ~üî[¨@m§¹pœB¥c·)çð«4…`Gq·Kxu_Ls¥Ú{òû* á<¦+Ùû%H€gƒÖÔÄ"47r¡ÁìP ¯`åô×l` –—mí0î”QHµª«~r")··Ä ÆØzÜ9¬5ºí7}|`¶ˆ¤…æg*ÿû:\>s™=ÿcmÀsZ¾ØÖ‘GqÍ|˜èõÒ±Ÿ\G:Â(ï\SHUEоˆ_ý™M[iJ´GŽ oÀ±»á×%j!³°h÷ÿöÎ3Ns¦»Ÿ±4it^‚àÀƒ h„¹.÷¸¥ã ð½ŠÒ7\g„‡¨˜Á"AhiêjRJàˆ¨ ¥~¯?€ ^ätó—B}_sìÿöïøÎ›Nçì%­$kCªƒ¾˜\ƒ¢óϼËò€¶›½Ømûâ÷K=ŽŸ¿ß[ÐöUúL@]Ó}0²†Éá6ò_‘î#ôqù_§Æêm ÚnÌ}Úz’Ž/¿ÿ¸t˜÷3Ì??‚…PŠi«Oªx¡õ†E‚ÎÃÛ:i›Âá=/zÎ6ý¶6m:˜CLæ@*QˆõÏõŸÖT¯+Ð ¯?§-AѰúÿ\¨:Žq.©ù^øm„)Ù»¯NN¹ÀHlT=0›Ê®òà½ò!U¤`ötûΟˆ;×Ùú|L]5_cdB§í0è‡ ´…ë‚bû—:G¹œfÏá§’I¡‚–[ˆòö±–#R,¤Lj‚-ýµTá* IK:¤® ¡B­eæR¨·ôöDS@^øýß³yɦœiðnøË272°]ß ‰†³f¹;[¢lã T_«Š64¨“¿6ºq¾«:üU’± c×—Pèqs˜$½³^n”Ðè|ÒœQyÿòXpu–×±ø¿´9}óŒÙœ|îjÍZ†1æ|Ì0à xØ„@ئç§ÈI$÷Õ ´°¯©Ï:ý)œ+&u¹÷ké2Ÿ¢ ¹× í“XRá¾?çœ!‹ÀŸ œý{&è4… ý>T*¡Bähs2!¡þ3ÿ†ÌÿáŸóöL8þ‡ÿ’HT’^^†‡ÂåË¢5¬”é;™Æ)³šzw]` EPuƒ²v|žOÙü©ŒwbGzù€YBTDå;²rWY²Ò‚¬ Št6[h(ž_ùÂì.@:`(Ø%ƒÈ1Ž–U¯D ¿N Ñ#˜ì+íÙ´†¹Õ†rcõ|íÁ)¾%Àr„à)óg àB1EL@œ±õuŸýÁ 63œê:f¼ö£¯ÿ~[˜g€Òžøã9ëàŽª}øÂÆ£Ð1`åXÜ… *mÚqØ£’P{‚ûþ@­ TÊa¨D4%ºöîã4 f”ý'¾A«Ïmß z¾×"Ábðp¯èßêvdJh¥®ƒ_{ ‚f !©(ôäí `´ê»¡ˆš`]¢An -¨MãBjMM^‹ÝØÙ¨"¡ƒâê>uXɉŠäÔ×%Ÿ€O½¥Cê6pȪ`&„)JÈy)ec£ô”ésXABÛáQtëNV£U'4ï@9E¨È)@å3d&%¢ MàbðÐ[H*Ó¹”¸Å##Ø…)š0é¯#Õ¥-Ö6Õ‹ÑÈ("”Þÿ†àˆa@SzPÈ RÕ`S W=,š$ß)çxÞwC))á´àט8y] ¦”d¥¸ìˆE4R)}{džÁQB¼ f"USfˆjðñœó d³AàK‰åA†…axÏM$ lv ]wdžÑA$uΔ®õ‹M±–D°r`ª z%0u¸çl<–MªV’µ½ e,Æ &M‘i@ð8H)Hf„©Ž,FÀðZŠ  àÚÙYØH+qiĆWÆsŒ!\=µ´"S¤ÓÃbÀ¨ÊÏ8$s…á´ì @' Ø&Âôã€é8Q ä¡ßÐÊ )¦H‹0ð„y'DO‰@!E Ûr>sýg÷Ÿë?¼ÿYýçúÏï?Öyþ³ûÏõŸÞ¬þóýg÷‡|ÅÑ ½K#çô,QEÑúãÕ ­^ÛÃUh9ÞuÁg×Ú? ¢«oNr¤.H"„3}sŸ›ÝÜZìL+ªî‡ôየ:èú}¿üH2š}I=ñ|д£åœºvÇI(A„ãõ¯õ;2æ+ ƒ PˆÙ‡HlU90z#ºÆ€‹¶¯\Å$B®ðæ50¥Pž4H-A@š°±'‡D›¾,X÷7CöR·Mb… ¨7d!€À”$ ‘FžX†%Øó‚ÈœãZìXÀœƒT#EnöÒ Äâ/;pP M¬CaÌŽ¬9E*iÆ —$†jè2*»r$"j4À’Ñ•Ú æ(ÇÇ îTÀº[WC@iB¦Èm}Æéi²;c ní–ÇK‡)¤P„àQTõW&þ{Œ*õ£ä&®)G,j,¤^ …(&:•M.¡:@±d¥£ÁÛ - ˜EÐRqˆ’JÄÖ¦´µÚI1˜T €­¶÷EitlHÕv)Â×ùZ=a(ð¤èKöƒ…@V÷ÅgÅö…¡•Þ„C˜DéfÉ"䉨*DHZõ¦å@f e‰·¥QŒª¬/Å_~B6 $j‡'%¤8養ïNŽ$”+xU/ªéÓ•UE•¶0òÇST@•ð°Á Mu‚Z€^Á9ëD%²5‰A9×ct„j–Õ‡A[µ25 ÝD^ËÊbÈD4aFЭC ÊË‹Œ:+?ÿÙmod_tile-0.8.0/src/000077500000000000000000000000001474064163400141135ustar00rootroot00000000000000mod_tile-0.8.0/src/CMakeLists.txt000066400000000000000000000122071474064163400166550ustar00rootroot00000000000000#----------------------------------------------------------------------------- # # CMake Config # #----------------------------------------------------------------------------- include_directories(${PROJECT_SOURCE_DIR}/includes) include_directories(SYSTEM ${APR_INCLUDE_DIRS} ${CAIRO_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} ${GLIB_INCLUDE_DIRS} ${HTTPD_INCLUDE_DIRS} ) include_directories(SYSTEM ${ICU_INCLUDE_DIRS} ${INIPARSER_INCLUDE_DIRS} ${LIBMAPNIK_INCLUDE_DIRS} ${LIBMEMCACHED_INCLUDE_DIRS} ${LIBRADOS_INCLUDE_DIRS} ) link_directories(${CMAKE_LIBRARY_PATH}) set(COMMON_SRCS g_logger.c sys_utils.c ) set(COMMON_LIBRARIES ${GLIB_LIBRARIES} Threads::Threads ) set(RENDER_SRCS ${COMMON_SRCS} protocol_helper.c renderd_config.c ) set(RENDER_LIBRARIES ${COMMON_LIBRARIES} ${INIPARSER_LIBRARIES} ${MATH_LIBRARY} ) set(STORE_SRCS store.c store_file.c store_file_utils.c store_memcached.c store_null.c store_rados.c store_ro_composite.c store_ro_http_proxy.c ) set(STORE_LIBRARIES ${CAIRO_LIBRARIES} ${CURL_LIBRARIES} ${LIBMEMCACHED_LIBRARIES} ${LIBRADOS_LIBRARIES} ) if(NOT MALLOC_LIB STREQUAL "libc") message(STATUS "Prepending '${MALLOC_LIBRARY}' to RENDER_LIBRARIES") list(PREPEND RENDER_LIBRARIES ${MALLOC_LIBRARY}) endif() #----------------------------------------------------------------------------- # # Installed targets # #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # # mod_tile.so # #----------------------------------------------------------------------------- set(mod_tile_SRCS ${COMMON_SRCS} ${STORE_SRCS} mod_tile.c renderd_config.c ) set(mod_tile_LIBS ${APR_LIBRARIES} ${COMMON_LIBRARIES} ${INIPARSER_LIBRARIES} ${STORE_LIBRARIES} ) add_library(mod_tile SHARED ${mod_tile_SRCS}) target_link_libraries(mod_tile ${mod_tile_LIBS}) set_target_properties(mod_tile PROPERTIES PREFIX "" SUFFIX ".so") #----------------------------------------------------------------------------- # # render_expired # #----------------------------------------------------------------------------- set(render_expired_SRCS ${RENDER_SRCS} ${STORE_SRCS} render_expired.c render_submit_queue.c ) set(render_expired_LIBS ${RENDER_LIBRARIES} ${STORE_LIBRARIES} ) add_executable(render_expired ${render_expired_SRCS}) target_link_libraries(render_expired ${render_expired_LIBS}) #----------------------------------------------------------------------------- # # render_list # #----------------------------------------------------------------------------- set(render_list_SRCS ${RENDER_SRCS} ${STORE_SRCS} render_list.c render_submit_queue.c ) set(render_list_LIBS ${RENDER_LIBRARIES} ${STORE_LIBRARIES} ) add_executable(render_list ${render_list_SRCS}) target_link_libraries(render_list ${render_list_LIBS}) #----------------------------------------------------------------------------- # # render_old # #----------------------------------------------------------------------------- set(render_old_SRCS ${RENDER_SRCS} render_old.c render_submit_queue.c store_file_utils.c ) set(render_old_LIBS ${RENDER_LIBRARIES} ) add_executable(render_old ${render_old_SRCS}) target_link_libraries(render_old ${render_old_LIBS}) #----------------------------------------------------------------------------- # # render_speedtest # #----------------------------------------------------------------------------- set(render_speedtest_SRCS ${RENDER_SRCS} render_speedtest.cpp render_submit_queue.c ) set(render_speedtest_LIBS ${RENDER_LIBRARIES} ) add_executable(render_speedtest ${render_speedtest_SRCS}) target_link_libraries(render_speedtest ${render_speedtest_LIBS}) #----------------------------------------------------------------------------- # # renderd # #----------------------------------------------------------------------------- set(renderd_SRCS ${RENDER_SRCS} ${STORE_SRCS} cache_expire.c daemon_compat.c gen_tile.cpp metatile.cpp parameterize_style.cpp renderd.c request_queue.c ) set(renderd_LIBS ${ICU_LIBRARIES} ${LIBMAPNIK_LIBRARIES} ${RENDER_LIBRARIES} ${STORE_LIBRARIES} ) add_executable(renderd ${renderd_SRCS}) target_link_libraries(renderd ${renderd_LIBS}) if(ENABLE_TESTS) #----------------------------------------------------------------------------- # # Test targets # #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # # gen_tile_test # #----------------------------------------------------------------------------- set(gen_tile_test_SRCS $ ${renderd_SRCS} ${PROJECT_SOURCE_DIR}/tests/gen_tile_test.cpp ) set(gen_tile_test_LIBS ${renderd_LIBS} ) add_executable(gen_tile_test ${gen_tile_test_SRCS}) target_compile_definitions(gen_tile_test PRIVATE MAIN_ALREADY_DEFINED) target_include_directories(gen_tile_test PRIVATE ${PROJECT_SOURCE_DIR}/tests) target_link_libraries(gen_tile_test ${gen_tile_test_LIBS}) set_target_properties(gen_tile_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/tests) endif() mod_tile-0.8.0/src/cache_expire.c000066400000000000000000000100101474064163400166660ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include #include #include #include "cache_expire.h" #include "g_logger.h" /** * This function sends a HTCP cache clr request for a given * URL. * RFC for HTCP can be found at http://www.htcp.org/ */ static void cache_expire_url(int sock, char * url) { char * buf; if (sock < 0) { return; } int idx = 0; int url_len; url_len = strlen(url); buf = (char *) malloc(12 + 22 + url_len); if (!buf) { return; } idx = 0; //16 bit: Overall length of the datagram packet, including this header *((uint16_t *)(&buf[idx])) = htons(12 + 22 + url_len); idx += 2; //HTCP version. Currently at 0.0 buf[idx++] = 0; //Major version buf[idx++] = 0; //Minor version //Length of HTCP data, including this field *((uint16_t *)(&buf[idx])) = htons(8 + 22 + url_len); idx += 2; //HTCP opcode CLR=4 buf[idx++] = 4; //Reserved buf[idx++] = 0; //32 bit transaction id; *((uint32_t *)(&buf[idx])) = htonl(255); idx += 4; buf[idx++] = 0; buf[idx++] = 0; //HTCP reason //Length of the Method string *((uint16_t *)(&buf[idx])) = htons(4); idx += 2; ///Method string memcpy(&buf[idx], "HEAD", 4); idx += 4; //Length of the url string *((uint16_t *)(&buf[idx])) = htons(url_len); idx += 2; //Url string memcpy(&buf[idx], url, url_len); idx += url_len; //Length of version string *((uint16_t *)(&buf[idx])) = htons(8); idx += 2; //version string memcpy(&buf[idx], "HTTP/1.1", 8); idx += 8; //Length of request headers. Currently 0 as we don't have any headers to send *((uint16_t *)(&buf[idx])) = htons(0); if (send(sock, (void *) buf, (12 + 22 + url_len), 0) < (12 + 22 + url_len)) { g_logger(G_LOG_LEVEL_ERROR, "Failed to send HTCP purge for %s", url); }; free(buf); } void cache_expire(int sock, const char *host, const char *uri, int x, int y, int z) { if (sock < 0) { return; } char * url = (char *)malloc(1024); snprintf(url, 1024, "http://%s%s%i/%i/%i.png", host, uri, z, x, y); cache_expire_url(sock, url); free(url); } int init_cache_expire(const char * htcphost) { struct addrinfo hints; struct addrinfo *result, *rp; int sfd, s; /* Obtain address(es) matching host/port */ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ hints.ai_flags = 0; hints.ai_protocol = 0; /* Any protocol */ s = getaddrinfo(htcphost, HTCP_EXPIRE_CACHE_PORT, &hints, &result); if (s != 0) { g_logger(G_LOG_LEVEL_ERROR, "Failed to lookup HTCP cache host: %s", gai_strerror(s)); return -1; } /* getaddrinfo() returns a list of address structures. Try each address until we successfully connect(2). If socket(2) (or connect(2)) fails, we (close the socket and) try the next address. */ for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) { continue; } if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) { break; /* Success */ } close(sfd); } if (rp == NULL) { /* No address succeeded */ g_logger(G_LOG_LEVEL_ERROR, "Failed to create HTCP cache socket"); return -1; } freeaddrinfo(result); /* No longer needed */ return sfd; } mod_tile-0.8.0/src/convert_meta.c000066400000000000000000000123521474064163400167500ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "render_config.h" #include "dir_utils.h" #include "store.h" char *tile_dir = RENDERD_TILE_DIR; #ifndef METATILE #warning("convert_meta not implemented for non-metatile mode. Feel free to submit fix") int main(int argc, char **argv) { fprintf(stderr, "convert_meta not implemented for non-metatile mode. Feel free to submit fix!\n"); return -1; } #else static int minZoom = 0; static int maxZoom = MAX_ZOOM; static int verbose = 0; static int num_render = 0, num_all = 0; static struct timeval start, end; static int unpack; void display_rate(struct timeval start, struct timeval end, int num) { int d_s, d_us; float sec; d_s = end.tv_sec - start.tv_sec; d_us = end.tv_usec - start.tv_usec; sec = d_s + d_us / 1000000.0; printf("Converted %d tiles in %.2f seconds (%.2f tiles/s)\n", num, sec, num / sec); fflush(NULL); } static void descend(const char *search) { DIR *tiles = opendir(search); struct dirent *entry; char path[PATH_MAX]; if (!tiles) { //fprintf(stderr, "Unable to open directory: %s\n", search); return; } while ((entry = readdir(tiles))) { struct stat b; char *p; //check_load(); if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) { continue; } snprintf(path, sizeof(path), "%s/%s", search, entry->d_name); if (stat(path, &b)) { continue; } if (S_ISDIR(b.st_mode)) { descend(path); continue; } p = strrchr(path, '.'); if (p) { if (unpack) { if (!strcmp(p, ".meta")) { process_unpack(tile_dir, path); } } else { if (!strcmp(p, ".png")) { process_pack(tile_dir, path); } } } } closedir(tiles); } int main(int argc, char **argv) { int z, c; const char *map = "default"; while (1) { int option_index = 0; static struct option long_options[] = { {"map", 1, 0, 'm'}, {"min-zoom", 1, 0, 'z'}, {"max-zoom", 1, 0, 'Z'}, {"unpack", 0, 0, 'u'}, {"tile-dir", 1, 0, 't'}, {"verbose", 0, 0, 'v'}, {"help", 0, 0, 'h'}, {0, 0, 0, 0} }; c = getopt_long(argc, argv, "uhvz:Z:m:t:", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'z': minZoom = atoi(optarg); if (minZoom < 0 || minZoom > MAX_ZOOM) { fprintf(stderr, "Invalid minimum zoom selected, must be between 0 and %d\n", MAX_ZOOM); return 1; } break; case 'Z': maxZoom = atoi(optarg); if (maxZoom < 0 || maxZoom > MAX_ZOOM) { fprintf(stderr, "Invalid maximum zoom selected, must be between 0 and %d\n", MAX_ZOOM); return 1; } break; case 'm': map = strdup(optarg); break; case 't': tile_dir = strdup(optarg); break; case 'u': unpack = 1; break; case 'v': verbose = 1; break; case 'h': fprintf(stderr, "Usage: convert_meta [OPTION] ...\n"); fprintf(stderr, "Convert the rendered PNGs into the more efficient .meta format\n"); fprintf(stderr, " -m, --map convert tiles in this map (default is 'default')\n"); fprintf(stderr, " -t, --tile-dir tile cache directory (default is '" RENDERD_TILE_DIR "')\n"); fprintf(stderr, " -u, --unpack unpack the .meta files back to PNGs\n"); fprintf(stderr, " -z, --min-zoom only process tiles greater or equal to this zoom level (default is 0)\n"); fprintf(stderr, " -Z, --max-zoom only process tiles less than or equal to this zoom level (default is %d)\n", MAX_ZOOM); return -1; default: fprintf(stderr, "unhandled char '%c'\n", c); break; } } if (maxZoom < minZoom) { fprintf(stderr, "Invalid zoom range, max zoom must be greater or equal to minimum zoom\n"); return 1; } fprintf(stderr, "Converting tiles in map %s\n", map); gettimeofday(&start, NULL); for (z = minZoom; z <= maxZoom; z++) { char path[PATH_MAX]; snprintf(path, PATH_MAX, "%s/%s/%d", tile_dir, map, z); descend(path); } gettimeofday(&end, NULL); printf("\nTotal for all tiles converted\n"); printf("Meta tiles converted: "); display_rate(start, end, num_render); printf("Total tiles converted: "); display_rate(start, end, num_render * METATILE * METATILE); printf("Total tiles handled: "); display_rate(start, end, num_all); return 0; } #endif mod_tile-0.8.0/src/daemon_compat.c000066400000000000000000000052101474064163400170630ustar00rootroot00000000000000/* * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "config.h" #ifndef HAVE_DAEMON #ifdef HAVE_SYS_CDEFS_H #include #endif #include #include #include #include #include #ifdef HAVE_PATHS_H #include #endif #ifndef _PATH_DEVNULL #define _PATH_DEVNULL "/dev/null" #endif int daemon(nochdir, noclose) int nochdir, noclose; { struct sigaction osa, sa; int fd; pid_t newgrp; int oerrno; int osa_ok; /* A SIGHUP may be thrown when the parent exits below. */ sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_IGN; sa.sa_flags = 0; osa_ok = sigaction(SIGHUP, &sa, &osa); switch (fork()) { case -1: return (-1); case 0: break; default: exit(0); } newgrp = setsid(); oerrno = errno; if (osa_ok != -1) { sigaction(SIGHUP, &osa, NULL); } if (newgrp == -1) { errno = oerrno; return (-1); } if (!nochdir) { (void)chdir("/"); } if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); if (fd > 2) { close(fd); } } return (0); } #endif mod_tile-0.8.0/src/g_logger.c000066400000000000000000000066371474064163400160600ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #define _GNU_SOURCE 1 #define G_LOG_USE_STRUCTURED 1 #include #include #include int foreground = 0; const char *g_logger_level_name(int log_level) { switch (log_level) { case G_LOG_LEVEL_ERROR: return "ERROR"; case G_LOG_LEVEL_CRITICAL: return "CRITICAL"; case G_LOG_LEVEL_WARNING: return "WARNING"; case G_LOG_LEVEL_MESSAGE: return "MESSAGE"; case G_LOG_LEVEL_INFO: return "INFO"; case G_LOG_LEVEL_DEBUG: return "DEBUG"; default: return "UNKNOWN"; } } void g_logger(int log_level, const char *format, ...) { int size; char *log_message, *log_message_prefixed; va_list args; va_start(args, format); size = vasprintf(&log_message, format, args); if (size == -1) { g_error("ERROR: vasprintf failed in g_logger"); } const GLogField log_fields[] = {{"MESSAGE", log_message, -1}}; size = asprintf(&log_message_prefixed, "%s: %s", g_logger_level_name(log_level), log_message); if (size == -1) { g_error("ERROR: asprintf failed in g_logger"); } const GLogField log_fields_prefixed[] = {{"MESSAGE", log_message_prefixed, -1}}; if (foreground == 1) { switch (log_level) { // Levels >= G_LOG_LEVEL_ERROR will terminate the program case G_LOG_LEVEL_ERROR: g_log_writer_standard_streams(log_level, log_fields, 1, NULL); break; // Levels <= G_LOG_LEVEL_INFO will only show when using G_MESSAGES_DEBUG case G_LOG_LEVEL_INFO: g_log_writer_standard_streams(log_level, log_fields, 1, NULL); break; default: g_log_writer_default(log_level, log_fields, 1, NULL); } } else if (g_log_writer_is_journald(fileno(stderr))) { switch (log_level) { // Levels >= G_LOG_LEVEL_ERROR will terminate the program case G_LOG_LEVEL_ERROR: g_log_writer_journald(log_level, log_fields, 1, NULL); break; // Levels <= G_LOG_LEVEL_INFO will only show when using G_MESSAGES_DEBUG case G_LOG_LEVEL_INFO: g_log_writer_journald(log_level, log_fields, 1, NULL); break; default: g_log_writer_default(log_level, log_fields, 1, NULL); } } else { setlogmask(LOG_UPTO(LOG_INFO)); switch (log_level) { case G_LOG_LEVEL_ERROR: syslog(LOG_ERR, log_message_prefixed, NULL); break; case G_LOG_LEVEL_CRITICAL: syslog(LOG_CRIT, log_message_prefixed, NULL); break; case G_LOG_LEVEL_WARNING: syslog(LOG_WARNING, log_message_prefixed, NULL); break; case G_LOG_LEVEL_MESSAGE: syslog(LOG_INFO, log_message_prefixed, NULL); break; case G_LOG_LEVEL_INFO: syslog(LOG_INFO, log_message_prefixed, NULL); break; case G_LOG_LEVEL_DEBUG: syslog(LOG_DEBUG, log_message_prefixed, NULL); break; } } va_end(args); free(log_message_prefixed); free(log_message); } mod_tile-0.8.0/src/gen_tile.cpp000066400000000000000000000416431474064163400164150ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if MAPNIK_MAJOR_VERSION >= 4 #include #else #include #endif #include "cache_expire.h" #include "g_logger.h" #include "gen_tile.h" #include "metatile.h" #include "parameterize_style.hpp" #include "protocol.h" #include "render_config.h" #include "renderd.h" #include "request_queue.h" #include "store.h" #ifndef DEG_TO_RAD #define DEG_TO_RAD (M_PI / 180) #endif #ifndef RAD_TO_DEG #define RAD_TO_DEG (180 / M_PI) #endif #ifdef METATILE #define RENDER_SIZE (256 * (METATILE + 1)) #else #define RENDER_SIZE (512) #endif using namespace mapnik; #define image_data_32 image_rgba8 #define image_32 image_rgba8 struct projectionconfig { double bound_x0; double bound_y0; double bound_x1; double bound_y1; int aspect_x; int aspect_y; }; struct xmlmapconfig { Map map; const char *host; const char *htcphost; const char *output_format; const char *xmlfile; const char *xmlname; const char *xmluri; double scale; int htcpsock; int maxzoom; int minzoom; int ok; int tilesize; parameterize_function_ptr parameterize_function; struct projectionconfig *prj; struct storage_backend *store; xmlmapconfig() : map(256, 256) {} }; struct projectionconfig *get_projection(const char *srs) { struct projectionconfig *prj; if (strstr(srs, "+proj=merc +a=6378137 +b=6378137") != NULL) { g_logger(G_LOG_LEVEL_DEBUG, "Using web mercator projection settings"); prj = (struct projectionconfig *)malloc(sizeof(struct projectionconfig)); prj->bound_x0 = -20037508.3428; prj->bound_x1 = 20037508.3428; prj->bound_y0 = -20037508.3428; prj->bound_y1 = 20037508.3428; prj->aspect_x = 1; prj->aspect_y = 1; } else if (strcmp(srs, "+proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs") == 0) { g_logger(G_LOG_LEVEL_DEBUG, "Using plate carree projection settings"); prj = (struct projectionconfig *)malloc(sizeof(struct projectionconfig)); prj->bound_x0 = -20037508.3428; prj->bound_x1 = 20037508.3428; prj->bound_y0 = -10018754.1714; prj->bound_y1 = 10018754.1714; prj->aspect_x = 2; prj->aspect_y = 1; } else if (strcmp(srs, "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +datum=OSGB36 +units=m +no_defs") == 0) { g_logger(G_LOG_LEVEL_DEBUG, "Using bng projection settings"); prj = (struct projectionconfig *)malloc(sizeof(struct projectionconfig)); prj->bound_x0 = 0; prj->bound_y0 = 0; prj->bound_x1 = 700000; prj->bound_y1 = 1400000; prj->aspect_x = 1; prj->aspect_y = 2; } else { g_logger(G_LOG_LEVEL_WARNING, "Unknown projection string, using web mercator as never the less. %s", srs); prj = (struct projectionconfig *)malloc(sizeof(struct projectionconfig)); prj->bound_x0 = -20037508.3428; prj->bound_x1 = 20037508.3428; prj->bound_y0 = -20037508.3428; prj->bound_y1 = 20037508.3428; prj->aspect_x = 1; prj->aspect_y = 1; } return prj; } static void load_fonts(const char *font_dir, int recurse) { DIR *fonts = opendir(font_dir); if (!fonts) { g_logger(G_LOG_LEVEL_CRITICAL, "Unable to open font directory: %s", font_dir); return; } closedir(fonts); g_logger(G_LOG_LEVEL_INFO, "Loading fonts%sfrom directory '%s'", recurse ? " recursively " : " ", font_dir); freetype_engine::register_fonts(font_dir, recurse); freetype_engine::font_file_mapping_type face_name_mappings = freetype_engine::get_mapping(); freetype_engine::font_file_mapping_type::iterator face_name_mappings_iterator = face_name_mappings.begin(); while (face_name_mappings_iterator != face_name_mappings.end()) { const char *face_name = face_name_mappings_iterator->first.c_str(); const char *font_file_name = face_name_mappings_iterator->second.second.c_str(); g_logger(G_LOG_LEVEL_DEBUG, "Loaded font face '%s' from file '%s'", face_name, font_file_name); face_name_mappings_iterator++; } } /** * Set the connection pool size of mapnik's datasources to correspond to the number of * rendering threads used in renderd **/ static void parameterize_map_max_connections(Map &m, int num_threads) { unsigned int i; char *tmp = (char *)malloc(20); for (i = 0; i < m.layer_count(); i++) { layer &l = m.get_layer(i); parameters params = l.datasource()->params(); if (params.find("max_size") == params.end()) { snprintf(tmp, 20, "%i", num_threads + 2); params["max_size"] = std::string(tmp); } l.set_datasource(datasource_cache::instance().create(params)); } free(tmp); } static int check_xyz(int x, int y, int z, struct xmlmapconfig *map) { int oob, limit; // Validate tile co-ordinates oob = (z < map->minzoom || z > map->maxzoom); if (!oob) { // valid x/y for tiles are 0 ... 2^zoom-1 limit = (1 << z); oob = (x < 0 || x > (limit * map->prj->aspect_x - 1) || y < 0 || y > (limit * map->prj->aspect_y - 1)); } if (oob) { g_logger(G_LOG_LEVEL_INFO, "got bad co-ords: x(%d) y(%d) z(%d)", x, y, z); } return !oob; } #ifdef METATILE mapnik::box2d tile2prjbounds(struct projectionconfig *prj, int x, int y, int z) { int render_size_tx = MIN(METATILE, prj->aspect_x * (1 << z)); int render_size_ty = MIN(METATILE, prj->aspect_y * (1 << z)); double p0x = prj->bound_x0 + (prj->bound_x1 - prj->bound_x0) * ((double)x / (double)(prj->aspect_x * 1 << z)); double p0y = (prj->bound_y1 - (prj->bound_y1 - prj->bound_y0) * (((double)y + render_size_ty) / (double)(prj->aspect_y * 1 << z))); double p1x = prj->bound_x0 + (prj->bound_x1 - prj->bound_x0) * (((double)x + render_size_tx) / (double)(prj->aspect_x * 1 << z)); double p1y = (prj->bound_y1 - (prj->bound_y1 - prj->bound_y0) * ((double)y / (double)(prj->aspect_y * 1 << z))); g_logger(G_LOG_LEVEL_DEBUG, "Rendering projected coordinates %i %i %i -> %f|%f %f|%f to a %i x %i tile", z, x, y, p0x, p0y, p1x, p1y, render_size_tx, render_size_ty); mapnik::box2d bbox(p0x, p0y, p1x, p1y); return bbox; } static enum protoCmd render(struct xmlmapconfig *map, int x, int y, int z, char *options, metaTile &tiles) { unsigned int render_size_tx = MIN(METATILE, map->prj->aspect_x * (1 << z)); unsigned int render_size_ty = MIN(METATILE, map->prj->aspect_y * (1 << z)); map->map.resize(render_size_tx * map->tilesize, render_size_ty * map->tilesize); map->map.zoom_to_box(tile2prjbounds(map->prj, x, y, z)); if (map->map.buffer_size() == 0) { // Only set buffer size if the buffer size isn't explicitly set in the mapnik stylesheet. map->map.set_buffer_size((map->tilesize >> 1) * map->scale); } // m.zoom(size+1); mapnik::image_32 buf(render_size_tx * map->tilesize, render_size_ty * map->tilesize); try { if (map->parameterize_function) { Map map_parameterized = map->map; map->parameterize_function(map_parameterized, options); map_parameterized.load_fonts(); mapnik::agg_renderer ren(map_parameterized, buf, map->scale); ren.apply(); } else { mapnik::agg_renderer ren(map->map, buf, map->scale); ren.apply(); } } catch (std::exception const &ex) { g_logger(G_LOG_LEVEL_ERROR, "failed to render TILE %s %d %d-%d %d-%d", map->xmlname, z, x, x + render_size_tx - 1, y, y + render_size_ty - 1); g_logger(G_LOG_LEVEL_ERROR, " reason: %s", ex.what()); return cmdNotDone; } // Split the meta tile into an NxN grid of tiles unsigned int xx, yy; for (yy = 0; yy < render_size_ty; yy++) { for (xx = 0; xx < render_size_tx; xx++) { mapnik::image_view> vw1(xx * map->tilesize, yy * map->tilesize, map->tilesize, map->tilesize, buf); struct mapnik::image_view_any vw(vw1); tiles.set(xx, yy, save_to_string(vw, map->output_format)); } } return cmdDone; // OK } #else // METATILE static enum protoCmd render(Map &m, const char *tile_dir, char *xmlname, projection &prj, int x, int y, int z, char *outputFormat) { char filename[PATH_MAX]; char tmp[PATH_MAX]; double p0x = x * 256.0; double p0y = (y + 1) * 256.0; double p1x = (x + 1) * 256.0; double p1y = y * 256.0; tiling.fromPixelToLL(p0x, p0y, z); tiling.fromPixelToLL(p1x, p1y, z); prj.forward(p0x, p0y); prj.forward(p1x, p1y); mapnik::box2d bbox(p0x, p0y, p1x, p1y); bbox.width(bbox.width() * 2); bbox.height(bbox.height() * 2); m.zoomToBox(bbox); mapnik::image_32 buf(RENDER_SIZE, RENDER_SIZE); mapnik::agg_renderer ren(m, buf); ren.apply(); xyz_to_path(filename, sizeof(filename), tile_dir, xmlname, x, y, z); if (mkdirp(filename)) { return cmdNotDone; } snprintf(tmp, sizeof(tmp), "%s.tmp", filename); mapnik::image_view vw(128, 128, 256, 256, buf.data()); g_logger(G_LOG_LEVEL_DEBUG, "Render %i %i %i %s", z, x, y, filename) mapnik::save_to_file(vw, tmp, outputFormat); if (rename(tmp, filename)) { perror(tmp); return cmdNotDone; } return cmdDone; // OK } #endif // METATILE void render_init(const char *plugins_dir, const char *font_dir, int font_dir_recurse) { g_logger(G_LOG_LEVEL_INFO, "Renderd is using mapnik version %i.%i.%i", MAPNIK_MAJOR_VERSION, MAPNIK_MINOR_VERSION, MAPNIK_PATCH_VERSION); mapnik::datasource_cache::instance().register_datasources(plugins_dir); load_fonts(font_dir, font_dir_recurse); } void *render_thread(void *arg) { xmlconfigitem *parentxmlconfig = (xmlconfigitem *)arg; xmlmapconfig maps[XMLCONFIGS_MAX]; int i, iMaxConfigs; int render_time; g_logger(G_LOG_LEVEL_DEBUG, "Starting rendering thread: %lu", (unsigned long)pthread_self()); for (iMaxConfigs = 0; iMaxConfigs < XMLCONFIGS_MAX; ++iMaxConfigs) { if (parentxmlconfig[iMaxConfigs].xmlname == NULL || parentxmlconfig[iMaxConfigs].xmlfile == NULL) { break; } maps[iMaxConfigs].maxzoom = parentxmlconfig[iMaxConfigs].max_zoom; maps[iMaxConfigs].minzoom = parentxmlconfig[iMaxConfigs].min_zoom; maps[iMaxConfigs].output_format = strndup(parentxmlconfig[iMaxConfigs].output_format, PATH_MAX); maps[iMaxConfigs].parameterize_function = init_parameterization_function(parentxmlconfig[iMaxConfigs].parameterization); maps[iMaxConfigs].scale = parentxmlconfig[iMaxConfigs].scale_factor; maps[iMaxConfigs].store = init_storage_backend(parentxmlconfig[iMaxConfigs].tile_dir); maps[iMaxConfigs].tilesize = parentxmlconfig[iMaxConfigs].tile_px_size; maps[iMaxConfigs].xmlfile = strndup(parentxmlconfig[iMaxConfigs].xmlfile, PATH_MAX); maps[iMaxConfigs].xmlname = strndup(parentxmlconfig[iMaxConfigs].xmlname, PATH_MAX); if (maps[iMaxConfigs].store) { maps[iMaxConfigs].ok = 1; maps[iMaxConfigs].map.resize(RENDER_SIZE, RENDER_SIZE); try { mapnik::load_map(maps[iMaxConfigs].map, maps[iMaxConfigs].xmlfile); if (!maps[iMaxConfigs].parameterize_function) { maps[iMaxConfigs].map.load_fonts(); } /* If we have more than 10 rendering threads configured, we need to fix * up the mapnik datasources to support larger postgres connection pools */ if (parentxmlconfig[iMaxConfigs].num_threads > 10) { g_logger(G_LOG_LEVEL_DEBUG, "Updating max_connection parameter for mapnik layers to reflect thread count"); parameterize_map_max_connections(maps[iMaxConfigs].map, parentxmlconfig[iMaxConfigs].num_threads); } maps[iMaxConfigs].prj = get_projection(maps[iMaxConfigs].map.srs().c_str()); } catch (std::exception const &ex) { g_logger(G_LOG_LEVEL_ERROR, "An error occurred while loading the map layer '%s': %s", maps[iMaxConfigs].xmlname, ex.what()); maps[iMaxConfigs].ok = 0; } catch (...) { g_logger(G_LOG_LEVEL_ERROR, "An unknown error occurred while loading the map layer '%s'", maps[iMaxConfigs].xmlname); maps[iMaxConfigs].ok = 0; } #ifdef HTCP_EXPIRE_CACHE maps[iMaxConfigs].host = strndup(parentxmlconfig[iMaxConfigs].host, PATH_MAX); maps[iMaxConfigs].htcphost = strndup(parentxmlconfig[iMaxConfigs].htcpip, PATH_MAX); maps[iMaxConfigs].xmluri = strndup(parentxmlconfig[iMaxConfigs].xmluri, PATH_MAX); if (strlen(maps[iMaxConfigs].htcphost) > 0) { maps[iMaxConfigs].htcpsock = init_cache_expire(maps[iMaxConfigs].htcphost); if (maps[iMaxConfigs].htcpsock > 0) { g_logger(G_LOG_LEVEL_DEBUG, "Successfully opened socket for HTCP cache expiry"); } else { g_logger(G_LOG_LEVEL_ERROR, "Failed to open socket for HTCP cache expiry"); } } else { maps[iMaxConfigs].htcpsock = -1; } #endif // HTCP_EXPIRE_CACHE } else { maps[iMaxConfigs].ok = 0; } } while (1) { enum protoCmd ret; struct item *item = request_queue_fetch_request(render_request_queue); render_time = -1; if (item) { struct protocol *req = &item->req; #ifdef METATILE // At very low zoom the whole world may be smaller than METATILE unsigned int size = MIN(METATILE, 1 << req->z); for (i = 0; i < iMaxConfigs; ++i) { if (!strcmp(maps[i].xmlname, req->xmlname)) { if (maps[i].ok) { if (check_xyz(item->mx, item->my, req->z, &(maps[i]))) { metaTile tiles(req->xmlname, req->options, item->mx, item->my, req->z); timeval tim; gettimeofday(&tim, NULL); long t1 = tim.tv_sec * 1000 + (tim.tv_usec / 1000); struct stat_info sinfo = maps[i].store->tile_stat(maps[i].store, req->xmlname, req->options, item->mx, item->my, req->z); if (sinfo.size > 0) g_logger(G_LOG_LEVEL_DEBUG, "START TILE %s %d %d-%d %d-%d, age %.2f days", req->xmlname, req->z, item->mx, item->mx + size - 1, item->my, item->my + size - 1, (tim.tv_sec - sinfo.mtime) / 86400.0); else g_logger(G_LOG_LEVEL_DEBUG, "START TILE %s %d %d-%d %d-%d, new metatile", req->xmlname, req->z, item->mx, item->mx + size - 1, item->my, item->my + size - 1); ret = render(&(maps[i]), item->mx, item->my, req->z, req->options, tiles); gettimeofday(&tim, NULL); long t2 = tim.tv_sec * 1000 + (tim.tv_usec / 1000); g_logger(G_LOG_LEVEL_DEBUG, "DONE TILE %s %d %d-%d %d-%d in %.3lf seconds", req->xmlname, req->z, item->mx, item->mx + size - 1, item->my, item->my + size - 1, (t2 - t1) / 1000.0); render_time = t2 - t1; if (ret == cmdDone) { try { tiles.save(maps[i].store); #ifdef HTCP_EXPIRE_CACHE tiles.expire_tiles(maps[i].htcpsock, maps[i].host, maps[i].xmluri); #endif // HTCP_EXPIRE_CACHE } catch (std::exception const &ex) { g_logger(G_LOG_LEVEL_ERROR, "Received exception when writing metatile to disk: %s", ex.what()); ret = cmdNotDone; } catch (...) { // Treat any error as fatal and request end of processing g_logger(G_LOG_LEVEL_CRITICAL, "Failed writing metatile to disk with unknown error, requesting exit."); ret = cmdNotDone; request_exit(); } } #else // METATILE ret = render(maps[i].map, maps[i].tile_dir, req->xmlname, maps[i].prj, req->x, req->y, req->z, maps[i].output_format); #ifdef HTCP_EXPIRE_CACHE cache_expire(maps[i].htcpsock, maps[i].host, maps[i].xmluri, req->x, req->y, req->z); #endif // HTCP_EXPIRE_CACHE #endif // METATILE } else { g_logger(G_LOG_LEVEL_WARNING, "Received request for map layer %s is outside of acceptable bounds z(%i), x(%i), y(%i)", req->xmlname, req->z, req->x, req->y); ret = cmdIgnore; } } else { g_logger(G_LOG_LEVEL_ERROR, "Received request for map layer '%s' which failed to load", req->xmlname); ret = cmdNotDone; } send_response(item, ret, render_time); if ((ret != cmdDone) && (ret != cmdIgnore)) { sleep(10); // Something went wrong with rendering, delay next processing to allow temporary issues to fix them selves } break; } } if (i == iMaxConfigs) { g_logger(G_LOG_LEVEL_ERROR, "No map for: %s", req->xmlname); } } else { sleep(1); // TODO: Use an event to indicate there are new requests } } return NULL; } mod_tile-0.8.0/src/metatile.cpp000066400000000000000000000071601474064163400164270ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include "cache_expire.h" #include "g_logger.h" #include "metatile.h" #include "render_config.h" #include "store.h" metaTile::metaTile(const std::string &xmlconfig, const std::string &options, int x, int y, int z): x_(x), y_(y), z_(z), xmlconfig_(xmlconfig), options_(options) { clear(); } void metaTile::clear() { for (int x = 0; x < METATILE; x++) for (int y = 0; y < METATILE; y++) { tile[x][y] = ""; } } void metaTile::set(int x, int y, const std::string &data) { tile[x][y] = data; } const std::string metaTile::get(int x, int y) { return tile[x][y]; } // Returns the offset within the meta-tile index table int metaTile::xyz_to_meta_offset(int x, int y, int z) { unsigned char mask = METATILE - 1; return (x & mask) * METATILE + (y & mask); } void metaTile::save(struct storage_backend * store) { int ox, oy, limit; ssize_t offset; struct meta_layout m; struct entry offsets[METATILE * METATILE]; char * metatilebuffer; char *tmp; memset(&m, 0, sizeof(m)); memset(&offsets, 0, sizeof(offsets)); // Create and write header m.count = METATILE * METATILE; memcpy(m.magic, META_MAGIC, strlen(META_MAGIC)); m.x = x_; m.y = y_; m.z = z_; offset = header_size; limit = (1 << z_); limit = MIN(limit, METATILE); limit = METATILE; // Generate offset table for (ox = 0; ox < limit; ox++) { for (oy = 0; oy < limit; oy++) { int mt = xyz_to_meta_offset(x_ + ox, y_ + oy, z_); offsets[mt].offset = offset; offsets[mt].size = tile[ox][oy].size(); offset += offsets[mt].size; } } metatilebuffer = (char *) malloc(offset); if (metatilebuffer == 0) { g_logger(G_LOG_LEVEL_WARNING, "Failed to write metatile. Out of memory"); return; } memset(metatilebuffer, 0, offset); memcpy(metatilebuffer, &m, sizeof(m)); memcpy(metatilebuffer + sizeof(m), &offsets, sizeof(offsets)); // Write tiles for (ox = 0; ox < limit; ox++) { for (oy = 0; oy < limit; oy++) { memcpy(metatilebuffer + offsets[xyz_to_meta_offset(x_ + ox, y_ + oy, z_)].offset, (const void *)tile[ox][oy].data(), tile[ox][oy].size()); } } if (store->metatile_write(store, xmlconfig_.c_str(), options_.c_str(), x_, y_, z_, metatilebuffer, offset) != offset) { tmp = (char *)malloc(sizeof(char) * PATH_MAX); g_logger(G_LOG_LEVEL_WARNING, "Failed to write metatile to %s", store->tile_storage_id(store, xmlconfig_.c_str(), options_.c_str(), x_, y_, z_, tmp)); free(tmp); } free(metatilebuffer); } void metaTile::expire_tiles(int sock, const char *host, const char *uri) { if (sock < 0) { return; } g_logger(G_LOG_LEVEL_INFO, "Purging metatile via HTCP cache expiry"); int ox, oy; int limit = (1 << z_); limit = MIN(limit, METATILE); // Generate offset table for (ox = 0; ox < limit; ox++) { for (oy = 0; oy < limit; oy++) { cache_expire(sock, host, uri, (x_ + ox), (y_ + oy), z_); } } } mod_tile-0.8.0/src/mod_tile.c000066400000000000000000002570541474064163400160700ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #define APR_WANT_STRFUNC #define APR_WANT_MEMFUNC #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "mod_tile.h" #include "protocol.h" #include "render_config.h" #include "renderd.h" #include "renderd_config.h" #include "store.h" #include "sys_utils.h" module AP_MODULE_DECLARE_DATA tile_module; #if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE) #include #define MOD_TILE_SET_MUTEX_PERMS /* XXX Apache should define something */ #endif APLOG_USE_MODULE(tile); #if (defined(__FreeBSD__) || defined(__MACH__)) && !defined(s6_addr32) #define s6_addr32 __u6_addr.__u6_addr32 #endif apr_shm_t *stats_shm; apr_shm_t *delaypool_shm; apr_global_mutex_t *stats_mutex; apr_global_mutex_t *delaypool_mutex; char *stats_mutexfilename; char *delaypool_mutexfilename; int layerCount = 0; int global_max_zoom = 0; struct storage_backends { struct storage_backend **stores; int noBackends; }; static int error_message(request_rec *r, const char *format, ...) __attribute__((format(printf, 2, 3))); static int error_message(request_rec *r, const char *format, ...) { va_list ap; char *msg; va_start(ap, format); msg = apr_pvsprintf(r->pool, format, ap); if (msg) { // ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", msg); ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "%s", msg); r->content_type = "text/plain"; if (!r->header_only) { ap_rputs(msg, r); } } va_end(ap); return OK; } static int socket_init(request_rec *r) { struct addrinfo hints; struct addrinfo *result, *rp; struct sockaddr_un addr; char portnum[16]; char ipstring[INET6_ADDRSTRLEN]; int fd, s; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); if (scfg->renderd_socket_port > 0) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Connecting to renderd on %s:%i via TCP", scfg->renderd_socket_name, scfg->renderd_socket_port); memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_STREAM; /* TCP socket */ hints.ai_flags = 0; hints.ai_protocol = 0; /* Any protocol */ hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL; snprintf(portnum, 16, "%i", scfg->renderd_socket_port); s = getaddrinfo(scfg->renderd_socket_name, portnum, &hints, &result); if (s != 0) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "failed to resolve hostname of rendering daemon"); return FD_INVALID; } /* getaddrinfo() returns a list of address structures. Try each address until we successfully connect. */ for (rp = result; rp != NULL; rp = rp->ai_next) { switch (rp->ai_family) { case AF_INET: inet_ntop(AF_INET, &(((struct sockaddr_in *)rp->ai_addr)->sin_addr), ipstring, rp->ai_addrlen); break; case AF_INET6: inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr), ipstring, rp->ai_addrlen); break; default: snprintf(ipstring, sizeof(ipstring), "address family %d", rp->ai_family); break; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Connecting TCP socket to rendering daemon at %s", ipstring); fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (fd < 0) { continue; } if (connect(fd, rp->ai_addr, rp->ai_addrlen) != 0) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "failed to connect to rendering daemon (%s), trying next ip", ipstring); close(fd); fd = -1; continue; } else { break; } } freeaddrinfo(result); if (fd < 0) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "failed to create tcp socket"); return FD_INVALID; } } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Connecting to renderd on Unix socket %s", scfg->renderd_socket_name); fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "failed to create unix socket"); return FD_INVALID; } bzero(&addr, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, scfg->renderd_socket_name, sizeof(addr.sun_path) - sizeof(char)); if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "socket connect failed for: %s with reason: %s", scfg->renderd_socket_name, strerror(errno)); close(fd); return FD_INVALID; } } return fd; } static int request_tile(request_rec *r, struct protocol *cmd, int renderImmediately) { int fd; int ret = 0; int retry = 1; struct protocol resp; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); fd = socket_init(r); if (fd == FD_INVALID) { ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "Failed to connect to renderer"); return 0; } // cmd has already been partial filled, fill in the rest switch (renderImmediately) { case 0: { cmd->cmd = cmdDirty; break; } case 1: { cmd->cmd = cmdRenderLow; break; } case 2: { cmd->cmd = cmdRender; break; } case 3: { cmd->cmd = cmdRenderPrio; break; } } if (scfg->enable_bulk_mode) { cmd->cmd = cmdRenderBulk; } ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Requesting style(%s) z(%d) x(%d) y(%d) from renderer with priority %d", cmd->xmlname, cmd->z, cmd->x, cmd->y, cmd->cmd); do { switch (cmd->ver) { case 2: ret = send(fd, cmd, sizeof(struct protocol_v2), 0); break; case 3: ret = send(fd, cmd, sizeof(struct protocol), 0); break; } if ((ret == sizeof(struct protocol_v2)) || (ret == sizeof(struct protocol))) { break; } if (errno != EPIPE) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "request_tile: Failed to send request to renderer: %s", strerror(errno)); close(fd); return 0; } close(fd); ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "request_tile: Reconnecting to rendering socket after failed request due to sigpipe"); fd = socket_init(r); if (fd == FD_INVALID) { return 0; } } while (retry--); if (renderImmediately) { int timeout = (renderImmediately > 2 ? scfg->request_timeout_priority : scfg->request_timeout); struct pollfd rx; int s; while (1) { rx.fd = fd; rx.events = POLLIN; s = poll(&rx, 1, timeout * 1000); if (s > 0) { bzero(&resp, sizeof(struct protocol)); ret = recv(fd, &resp, sizeof(struct protocol_v2), 0); if (ret != sizeof(struct protocol_v2)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "request_tile: Failed to read response from rendering socket. Got %d bytes but expected %d. Errno %d (%s)", ret, (int)sizeof(struct protocol_v2), errno, strerror(errno)); break; } if (resp.ver == 3) { ret += recv(fd, ((void *)&resp) + sizeof(struct protocol_v2), sizeof(struct protocol) - sizeof(struct protocol_v2), 0); } if (cmd->x == resp.x && cmd->y == resp.y && cmd->z == resp.z && !strcmp(cmd->xmlname, resp.xmlname)) { close(fd); if (resp.cmd == cmdDone) { return 1; } else { return 0; } } else { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Response does not match request: xml(%s,%s) z(%d,%d) x(%d,%d) y(%d,%d)", cmd->xmlname, resp.xmlname, cmd->z, resp.z, cmd->x, resp.x, cmd->y, resp.y); } } else if (s == 0) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "request_tile: Request xml(%s) z(%d) x(%d) y(%d) could not be rendered in %i seconds", cmd->xmlname, cmd->z, cmd->x, cmd->y, timeout); break; } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "request_tile: Request xml(%s) z(%d) x(%d) y(%d) timeout %i seconds failed with reason: %s", cmd->xmlname, cmd->z, cmd->x, cmd->y, timeout, strerror(errno)); break; } } } close(fd); return 0; } static apr_status_t cleanup_storage_backend(void *data) { struct storage_backends *stores = (struct storage_backends *)data; int i; for (i = 0; i < stores->noBackends; i++) { if (stores->stores[i]) { stores->stores[i]->close_storage(stores->stores[i]); } } return APR_SUCCESS; } static struct storage_backend *get_storage_backend(request_rec *r, int tile_layer) { struct storage_backends *stores = NULL; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); tile_config_rec *tile_configs = (tile_config_rec *)scfg->configs->elts; tile_config_rec *tile_config = &tile_configs[tile_layer]; apr_thread_t *current_thread = r->connection->current_thread; apr_pool_t *lifecycle_pool = apr_thread_pool_get(current_thread); char *memkey = apr_psprintf(r->pool, "mod_tile_storage_backends"); apr_os_thread_t os_thread = apr_os_thread_current(); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "get_storage_backend: Retrieving storage back end for tile layer %i in pool %pp and thread %li", tile_layer, lifecycle_pool, (unsigned long)os_thread); if (apr_pool_userdata_get((void **)&stores, memkey, lifecycle_pool) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "get_storage_backend: Failed horribly!"); } if (stores == NULL) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "get_storage_backend: No storage backends for this lifecycle %pp, creating it in thread %li", lifecycle_pool, (unsigned long)os_thread); stores = (struct storage_backends *)apr_pcalloc(lifecycle_pool, sizeof(struct storage_backends)); stores->stores = (struct storage_backend **)apr_pcalloc(lifecycle_pool, sizeof(struct storage_backend *) * scfg->configs->nelts); stores->noBackends = scfg->configs->nelts; if (apr_pool_userdata_set(stores, memkey, &cleanup_storage_backend, lifecycle_pool) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "get_storage_backend: Failed horribly to set user_data!"); } } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "get_storage_backend: Found backends (%pp) for this lifecycle %pp in thread %li", stores, lifecycle_pool, (unsigned long)os_thread); } if (stores->stores[tile_layer] == NULL) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "get_storage_backend: No storage backend in current lifecycle %pp in thread %li for current tile layer %i", lifecycle_pool, (unsigned long)os_thread, tile_layer); stores->stores[tile_layer] = init_storage_backend(tile_config->store); } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "get_storage_backend: Storage backend found in current lifecycle %pp for current tile layer %i in thread %li", lifecycle_pool, tile_layer, (unsigned long)os_thread); } return stores->stores[tile_layer]; } static enum tileState tile_state(request_rec *r, struct protocol *cmd) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); struct stat_info stat; struct tile_request_data *rdata = (struct tile_request_data *)ap_get_module_config(r->request_config, &tile_module); stat = rdata->store->tile_stat(rdata->store, cmd->xmlname, cmd->options, cmd->x, cmd->y, cmd->z); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_state: determined state of %s %i %i %i on store %pp: Tile size: %" APR_OFF_T_FMT ", expired: %i created: %li", cmd->xmlname, cmd->x, cmd->y, cmd->z, rdata->store, stat.size, stat.expired, stat.mtime); r->finfo.mtime = stat.mtime * 1000000; r->finfo.atime = stat.atime * 1000000; r->finfo.ctime = stat.ctime * 1000000; if (stat.size < 0) { return tileMissing; } if (stat.expired) { if ((r->request_time - r->finfo.mtime) < scfg->very_old_threshold) { return tileOld; } else { return tileVeryOld; } } return tileCurrent; } /** * Add CORS ( Cross-origin resource sharing ) headers. http://www.w3.org/TR/cors/ * CORS allows requests that would otherwise be forbidden under the same origin policy. */ static int add_cors(request_rec *r, const char *cors) { const char *headers; const char *origin = apr_table_get(r->headers_in, "Origin"); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Checking if CORS headers need to be added: Origin: %s Policy: %s", origin, cors); if (!origin) { return DONE; } else { if ((strcmp(cors, "*") == 0) || strstr(cors, origin)) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Origin header is allowed under the CORS policy. Adding Access-Control-Allow-Origin"); if (strcmp(cors, "*") == 0) { apr_table_setn(r->headers_out, "Access-Control-Allow-Origin", apr_psprintf(r->pool, "%s", cors)); } else { apr_table_setn(r->headers_out, "Access-Control-Allow-Origin", apr_psprintf(r->pool, "%s", origin)); apr_table_setn(r->headers_out, "Vary", apr_psprintf(r->pool, "%s", "Origin")); } if (strcmp(r->method, "OPTIONS") == 0 && apr_table_get(r->headers_in, "Access-Control-Request-Method")) { headers = apr_table_get(r->headers_in, "Access-Control-Request-Headers"); if (headers) { apr_table_setn(r->headers_out, "Access-Control-Allow-Headers", apr_psprintf(r->pool, "%s", headers)); } apr_table_setn(r->headers_out, "Access-Control-Max-Age", apr_psprintf(r->pool, "%i", 604800)); return OK; } else { return DONE; } } else { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Origin header (%s) is NOT allowed under the CORS policy (%s). Rejecting request", origin, cors); return HTTP_FORBIDDEN; } } } static void add_expiry(request_rec *r, struct protocol *cmd) { apr_time_t holdoff; apr_table_t *t = r->headers_out; enum tileState state = tile_state(r, cmd); apr_finfo_t *finfo = &r->finfo; char *timestr; long int maxAge, minCache, lastModified; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); struct tile_request_data *rdata = (struct tile_request_data *)ap_get_module_config(r->request_config, &tile_module); tile_config_rec *tile_configs = (tile_config_rec *)scfg->configs->elts; tile_config_rec *tile_config = &tile_configs[rdata->layerNumber]; ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "expires(%s), uri(%s),, path_info(%s)\n", r->handler, r->uri, r->path_info); /* If the hostname matches the "extended caching hostname" then set the cache age accordingly */ if ((strlen(scfg->cache_extended_hostname) != 0) && (strstr(r->hostname, scfg->cache_extended_hostname) != NULL)) { maxAge = scfg->cache_extended_duration; } else { /* Test if the tile we are serving is out of date, then set a low maxAge*/ if (state == tileOld || state == tileVeryOld) { holdoff = (scfg->cache_duration_dirty / 2.0) * (rand() / (RAND_MAX + 1.0)); maxAge = scfg->cache_duration_dirty + holdoff; } else { // cache heuristic based on zoom level if (cmd->z > tile_config->maxzoom) { minCache = 0; ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "z (%i) is larger than MAXZOOM %i\n", cmd->z, tile_config->maxzoom); } else { minCache = scfg->mincachetime[cmd->z]; } // Time to the next known complete rerender // planetTimestamp = apr_time_sec(getPlanetTime(r) // + apr_time_from_sec(PLANET_INTERVAL) - r->request_time); // Time since the last render of this tile lastModified = (int)(((double)apr_time_sec(r->request_time - finfo->mtime)) * scfg->cache_duration_last_modified_factor); // Add a random jitter of 3 hours to space out cache expiry holdoff = (3 * 60 * 60) * (rand() / (RAND_MAX + 1.0)); // maxAge = MAX(minCache, planetTimestamp); maxAge = minCache; maxAge = MAX(maxAge, lastModified); maxAge += holdoff; ap_log_rerror( APLOG_MARK, APLOG_DEBUG, 0, r, "caching heuristics: zoom level based %ld; last modified %ld\n", minCache, lastModified); } maxAge = MIN(maxAge, scfg->cache_duration_max); } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Setting tiles maxAge to %ld\n", maxAge); apr_table_mergen(t, "Cache-Control", apr_psprintf(r->pool, "max-age=%li", maxAge)); timestr = (char *)apr_palloc(r->pool, APR_RFC822_DATE_LEN); apr_rfc822_date(timestr, (apr_time_from_sec(maxAge) + r->request_time)); apr_table_setn(t, "Expires", timestr); } static int get_global_lock(request_rec *r, apr_global_mutex_t *mutex) { apr_status_t rs; int camped; for (camped = 0; camped < MAXCAMP; camped++) { rs = apr_global_mutex_trylock(mutex); if (APR_STATUS_IS_EBUSY(rs)) { apr_sleep(CAMPOUT); } else if (rs == APR_SUCCESS) { return 1; } else if (APR_STATUS_IS_ENOTIMPL(rs)) { /* If it's not implemented, just hang in the mutex. */ rs = apr_global_mutex_lock(mutex); if (rs == APR_SUCCESS) { return 1; } else { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Could not get hardlock"); return 0; } } else { ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "Unknown return status from trylock"); return 0; } } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Timedout trylock"); return 0; } static int incRespCounter(int resp, request_rec *r, struct protocol *cmd, int layerNumber) { stats_data *stats; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); if (!scfg->enable_global_stats) { /* If tile stats reporting is not enable * pretend we correctly updated the counter to * not fill the logs with warnings about failed * stats */ return 1; } if (get_global_lock(r, stats_mutex) != 0) { stats = (stats_data *)apr_shm_baseaddr_get(stats_shm); switch (resp) { case OK: { stats->noResp200++; if (cmd != NULL) { stats->noRespZoom[cmd->z]++; stats->noResp200Layer[layerNumber]++; } break; } case HTTP_NOT_MODIFIED: { stats->noResp304++; if (cmd != NULL) { stats->noRespZoom[cmd->z]++; stats->noResp200Layer[layerNumber]++; } break; } case HTTP_NOT_FOUND: { stats->noResp404++; stats->noResp404Layer[layerNumber]++; break; } case HTTP_SERVICE_UNAVAILABLE: { stats->noResp503++; break; } case HTTP_INTERNAL_SERVER_ERROR: { stats->noResp5XX++; break; } default: { stats->noRespOther++; } } apr_global_mutex_unlock(stats_mutex); /* Swallowing the result because what are we going to do with it at * this stage? */ return 1; } else { return 0; } } static int incFreshCounter(int status, request_rec *r) { stats_data *stats; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); if (!scfg->enable_global_stats) { /* If tile stats reporting is not enable * pretend we correctly updated the counter to * not fill the logs with warnings about failed * stats */ return 1; } if (get_global_lock(r, stats_mutex) != 0) { stats = (stats_data *)apr_shm_baseaddr_get(stats_shm); switch (status) { case FRESH: { stats->noFreshCache++; break; } case FRESH_RENDER: { stats->noFreshRender++; break; } case OLD: { stats->noOldCache++; break; } case VERYOLD: { stats->noVeryOldCache++; break; } case OLD_RENDER: { stats->noOldRender++; break; } case VERYOLD_RENDER: { stats->noVeryOldRender++; break; } } apr_global_mutex_unlock(stats_mutex); /* Swallowing the result because what are we going to do with it at * this stage? */ return 1; } else { return 0; } } static int incTimingCounter(apr_uint64_t duration, int z, request_rec *r) { stats_data *stats; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); if (!scfg->enable_global_stats) { /* If tile stats reporting is not enable * pretend we correctly updated the counter to * not fill the logs with warnings about failed * stats */ return 1; } if (get_global_lock(r, stats_mutex) != 0) { stats = (stats_data *)apr_shm_baseaddr_get(stats_shm); stats->totalBufferRetrievalTime += duration; stats->zoomBufferRetrievalTime[z] += duration; stats->noTotalBufferRetrieval++; stats->noZoomBufferRetrieval[z]++; apr_global_mutex_unlock(stats_mutex); /* Swallowing the result because what are we going to do with it at * this stage? */ return 1; } else { return 0; } } static int delay_allowed(request_rec *r, enum tileState state) { delaypool *delayp; int delay = 0; int i, j; char *strtok_state; char *tmp; const char *ip_addr = NULL; apr_time_t now; int tiles_topup; int render_topup; uint32_t hashkey; struct in_addr sin_addr; struct in6_addr ip; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); delayp = (delaypool *)apr_shm_baseaddr_get(delaypool_shm); ip_addr = r->useragent_ip; if (scfg->enable_tile_throttling_xforward) { char *ip_addrs = apr_pstrdup(r->pool, apr_table_get(r->headers_in, "X-Forwarded-For")); if (ip_addrs) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Checking throttling delays: Found X-Forwarded-For header \"%s\", forwarded by %s", ip_addrs, r->connection->client_ip); // X-Forwarded-For can be a chain of proxies deliminated by , The first entry in the list is the client, the last entry is the remote address seen by the proxy // closest to the tileserver. strtok_state = NULL; tmp = apr_strtok(ip_addrs, ", ", &strtok_state); ip_addr = tmp; // Use the last entry in the chain of X-Forwarded-For instead of the client, i.e. the entry added by the proxy closest to the tileserver // If this is a reverse proxy under our control, its X-Forwarded-For can be trusted. if (scfg->enable_tile_throttling_xforward == 2) { while ((tmp = apr_strtok(NULL, ", ", &strtok_state)) != NULL) { ip_addr = tmp; } } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Checking throttling delays for IP %s, forwarded by %s", ip_addr, r->connection->client_ip); } } if (inet_pton(AF_INET, ip_addr, &sin_addr) > 0) { // ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Checking delays: for IP %s appears to be an IPv4 address", ip_addr); memset(ip.s6_addr, 0, 16); memcpy(&(ip.s6_addr[12]), &(sin_addr.s_addr), 4); hashkey = sin_addr.s_addr % DELAY_HASHTABLE_WHITELIST_SIZE; if (delayp->whitelist[hashkey] == sin_addr.s_addr) { return 1; } } else { if (inet_pton(AF_INET6, ip_addr, &ip) <= 0) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Checking delays: for IP %s. Don't know what it is", ip_addr); return 0; } } hashkey = (ip.s6_addr32[0] ^ ip.s6_addr32[1] ^ ip.s6_addr32[2] ^ ip.s6_addr32[3]) % DELAY_HASHTABLE_SIZE; /* If a delaypool fillup is ongoing, just skip accounting to not block on a lock */ if (delayp->locked) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "skipping delay pool accounting, during fillup procedure\n"); return 1; } if (get_global_lock(r, delaypool_mutex) == 0) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Could not acquire lock, skipping delay pool accounting\n"); return 1; }; if (memcmp(&(delayp->users[hashkey].ip_addr), &ip, sizeof(struct in6_addr)) == 0) { /* Repeat the process to determine if we have tockens in the bucket, as the fillup only runs once a client hits an empty bucket, so in the mean time, the bucket might have been filled */ for (j = 0; j < 3; j++) { // ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Checking delays: Current poolsize: %i tiles and %i renders\n", delayp->users[hashkey].available_tiles, delayp->users[hashkey].available_render_req); delay = 0; if (delayp->users[hashkey].available_tiles > 0) { delayp->users[hashkey].available_tiles--; } else { delay = 1; } if (state == tileMissing) { if (delayp->users[hashkey].available_render_req > 0) { delayp->users[hashkey].available_render_req--; } else { delay = 2; } } if (delay > 0) { /* If we are on the second round, we really hit an empty delaypool, timeout for a while to slow down clients */ if (j > 0) { apr_global_mutex_unlock(delaypool_mutex); ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Delaypool: Client %s has hit its limits, throttling (%i)\n", ip_addr, delay); sleep(CLIENT_PENALTY); if (get_global_lock(r, delaypool_mutex) == 0) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Could not acquire lock, but had to delay\n"); return 0; }; } /* We hit an empty bucket, so run the bucket fillup procedure to check if new tokens should have arrived in the mean time. */ now = apr_time_now(); tiles_topup = (now - delayp->last_tile_fillup) / scfg->delaypool_tile_rate; render_topup = (now - delayp->last_render_fillup) / scfg->delaypool_render_rate; // ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Filling up pools with %i tiles and %i renders\n", tiles_topup, render_topup); if ((tiles_topup > 0) || (render_topup > 0)) { delayp->locked = 1; for (i = 0; i < DELAY_HASHTABLE_SIZE; i++) { delayp->users[i].available_tiles += tiles_topup; delayp->users[i].available_render_req += render_topup; if (delayp->users[i].available_tiles > scfg->delaypool_tile_size) { delayp->users[i].available_tiles = scfg->delaypool_tile_size; } if (delayp->users[i].available_render_req > scfg->delaypool_render_size) { delayp->users[i].available_render_req = scfg->delaypool_render_size; } } delayp->locked = 0; } delayp->last_tile_fillup += scfg->delaypool_tile_rate * tiles_topup; delayp->last_render_fillup += scfg->delaypool_render_rate * render_topup; } else { break; } } } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Creating a new delaypool for ip %s\n", ip_addr); memcpy(&(delayp->users[hashkey].ip_addr), &ip, sizeof(struct in6_addr)); delayp->users[hashkey].available_tiles = scfg->delaypool_tile_size; delayp->users[hashkey].available_render_req = scfg->delaypool_render_size; delay = 0; } apr_global_mutex_unlock(delaypool_mutex); if (delay > 0) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Delaypool: Client %s has hit its limits, rejecting (%i)\n", ip_addr, delay); return 0; } else { return 1; } } static int tile_storage_hook(request_rec *r) { // char abs_path[PATH_MAX]; double avg; int renderPrio = 0; enum tileState state; tile_server_conf *scfg; struct tile_request_data *rdata; struct protocol *cmd; if (!r->handler) { return DECLINED; } ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "tile_storage_hook: handler(%s), uri(%s)", r->handler, r->uri); // Any status request is OK. tile_dirty also doesn't need to be handled, as tile_handler_dirty will take care of it if (!strcmp(r->handler, "tile_status") || !strcmp(r->handler, "tile_dirty") || !strcmp(r->handler, "tile_mod_stats") || !(strcmp(r->handler, "tile_json"))) { return OK; } if (strcmp(r->handler, "tile_serve")) { return DECLINED; } rdata = (struct tile_request_data *)ap_get_module_config(r->request_config, &tile_module); cmd = rdata->cmd; if (cmd == NULL) { return DECLINED; } avg = get_load_avg(); state = tile_state(r, cmd); scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); if (scfg->enable_tile_throttling && !delay_allowed(r, state)) { if (!incRespCounter(HTTP_SERVICE_UNAVAILABLE, r, cmd, rdata->layerNumber)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase response stats counter"); } return HTTP_SERVICE_UNAVAILABLE; } switch (state) { case tileCurrent: if (!incFreshCounter(FRESH, r)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase fresh stats counter"); } return OK; break; case tileOld: case tileVeryOld: if (scfg->enable_bulk_mode) { return OK; } else if (avg > scfg->max_load_old) { // Too much load to render it now, mark dirty but return old tile request_tile(r, cmd, 0); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Load (%f) greater than max_load_old (%d). Mark dirty and deliver from cache.", avg, scfg->max_load_old); if (!incFreshCounter((state == tileVeryOld) ? VERYOLD : OLD, r)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase fresh stats counter"); } return OK; } renderPrio = (state == tileVeryOld) ? 2 : 1; break; case tileMissing: if (avg > scfg->max_load_missing) { request_tile(r, cmd, 0); ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Load (%f) greater than max_load_missing (%d). Return HTTP_NOT_FOUND.", avg, scfg->max_load_missing); if (!incRespCounter(HTTP_NOT_FOUND, r, cmd, rdata->layerNumber)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase response stats counter"); } return HTTP_NOT_FOUND; } renderPrio = 3; break; } if (request_tile(r, cmd, renderPrio)) { // TODO: update finfo if (!incFreshCounter(FRESH_RENDER, r)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase fresh stats counter"); } return OK; } if (state == tileOld) { if (!incFreshCounter(OLD_RENDER, r)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase fresh stats counter"); } return OK; } if (state == tileVeryOld) { if (!incFreshCounter(VERYOLD_RENDER, r)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase fresh stats counter"); } return OK; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_storage_hook: Missing tile was not rendered in time. Returning File Not Found"); if (!incRespCounter(HTTP_NOT_FOUND, r, cmd, rdata->layerNumber)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase response stats counter"); } return HTTP_NOT_FOUND; } static int tile_translate(request_rec *r) { int i, n, limit, oob; char option[11]; char extension[256]; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); tile_config_rec *tile_configs = (tile_config_rec *)scfg->configs->elts; ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: uri(%s)", r->uri); /* * The page /mod_tile returns global stats about the number of tiles * handled and in what state those tiles were. * This should probably not be hard coded */ if (!strncmp("/mod_tile", r->uri, strlen("/mod_tile"))) { r->handler = "tile_mod_stats"; ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "tile_translate: retrieving global mod_tile stats"); return OK; } /* * The page /metrics returns global stats in Prometheus format. */ if (!strncmp("/metrics", r->uri, strlen("/metrics"))) { r->handler = "tile_metrics"; ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "tile_translate: retrieving global mod_tile metrics"); return OK; } for (i = 0; i < scfg->configs->nelts; ++i) { tile_config_rec *tile_config = &tile_configs[i]; ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: testing baseuri(%s) name(%s) extension(%s)", tile_config->baseuri, tile_config->xmlname, tile_config->fileExtension); if (!strncmp(tile_config->baseuri, r->uri, strlen(tile_config->baseuri))) { struct tile_request_data *rdata = (struct tile_request_data *)apr_pcalloc(r->pool, sizeof(struct tile_request_data)); struct protocol *cmd = (struct protocol *)apr_pcalloc(r->pool, sizeof(struct protocol)); bzero(cmd, sizeof(struct protocol)); bzero(rdata, sizeof(struct tile_request_data)); if (!strncmp(r->uri + strlen(tile_config->baseuri), "tile-layer.json", strlen("tile-layer.json"))) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: Requesting tileJSON for tilelayer %s", tile_config->xmlname); r->handler = "tile_json"; rdata->layerNumber = i; ap_set_module_config(r->request_config, &tile_module, rdata); return OK; } char parameters[XMLCONFIG_MAX]; if (tile_config->enableOptions) { cmd->ver = PROTO_VER; n = sscanf(r->uri + strlen(tile_config->baseuri), "%40[^/]/%d/%d/%d.%255[a-z]/%10s", parameters, &(cmd->z), &(cmd->x), &(cmd->y), extension, option); if (n < 5) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: Invalid URL for tilelayer %s with options", tile_config->xmlname); return DECLINED; } } else { cmd->ver = 2; n = sscanf(r->uri + strlen(tile_config->baseuri), "%d/%d/%d.%255[a-z]/%10s", &(cmd->z), &(cmd->x), &(cmd->y), extension, option); if (n < 4) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: Invalid URL for tilelayer %s without options", tile_config->xmlname); return DECLINED; } parameters[0] = 0; } if (strcmp(extension, tile_config->fileExtension) != 0) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: Invalid file extension (%s) for tilelayer %s, required %s", extension, tile_config->xmlname, tile_config->fileExtension); return DECLINED; } oob = (cmd->z < tile_config->minzoom || cmd->z > tile_config->maxzoom); if (!oob) { // valid x/y for tiles are 0 ... 2^zoom-1 limit = (1 << cmd->z); oob = (cmd->x < 0 || cmd->x > (limit * tile_config->aspect_x - 1) || cmd->y < 0 || cmd->y > (limit * tile_config->aspect_y - 1)); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: request for %s was %i %i %i", tile_config->xmlname, cmd->x, cmd->y, limit); } if (oob) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: request for %s was outside of allowed bounds", tile_config->xmlname); sleep(CLIENT_PENALTY); // Don't increase stats counter here, // As we are interested in valid tiles only return HTTP_NOT_FOUND; } strcpy(cmd->xmlname, tile_config->xmlname); strcpy(cmd->mimetype, tile_config->mimeType); strcpy(cmd->options, parameters); // Store a copy for later rdata->cmd = cmd; rdata->layerNumber = i; rdata->store = get_storage_backend(r, i); if (rdata->store == NULL || rdata->store->storage_ctx == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "tile_translate: failed to get valid storage backend/storage backend context"); if (!incRespCounter(HTTP_INTERNAL_SERVER_ERROR, r, cmd, rdata->layerNumber)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase response stats counter"); } return HTTP_INTERNAL_SERVER_ERROR; } ap_set_module_config(r->request_config, &tile_module, rdata); r->filename = NULL; if ((tile_config->enableOptions && (n == 6)) || (!tile_config->enableOptions && (n == 5))) { if (!strcmp(option, "status")) { r->handler = "tile_status"; } else if (!strcmp(option, "dirty")) { r->handler = "tile_dirty"; } else { return DECLINED; } } else { r->handler = "tile_serve"; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: op(%s) xml(%s) mime(%s) z(%d) x(%d) y(%d)", r->handler, cmd->xmlname, tile_config->mimeType, cmd->z, cmd->x, cmd->y); return OK; } } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_translate: No suitable tile layer found"); return DECLINED; } static int tile_handler_dirty(request_rec *r) { tile_server_conf *scfg; struct tile_request_data *rdata; struct protocol *cmd; if (strcmp(r->handler, "tile_dirty")) { return DECLINED; } rdata = (struct tile_request_data *)ap_get_module_config(r->request_config, &tile_module); cmd = rdata->cmd; if (cmd == NULL) { return DECLINED; } scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); // Is /dirty URL enabled? if (!scfg->enable_dirty_url) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "tile_handler_dirty: /dirty URL is not enabled"); return HTTP_NOT_FOUND; } if (scfg->enable_bulk_mode) { return OK; } request_tile(r, cmd, 0); return error_message(r, "Tile submitted for rendering\n"); } static int tile_handler_status(request_rec *r) { tile_server_conf *scfg; enum tileState state; char mtime_str[APR_CTIME_LEN]; char atime_str[APR_CTIME_LEN]; char storage_id[PATH_MAX]; struct tile_request_data *rdata; struct protocol *cmd; if (strcmp(r->handler, "tile_status")) { return DECLINED; } scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); // Is /status URL enabled? if (!scfg->enable_status_url) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "tile_handler_status: /status URL is not enabled"); return HTTP_NOT_FOUND; } rdata = (struct tile_request_data *)ap_get_module_config(r->request_config, &tile_module); cmd = rdata->cmd; if (cmd == NULL) { sleep(CLIENT_PENALTY); return HTTP_NOT_FOUND; } state = tile_state(r, cmd); if (state == tileMissing) return error_message(r, "No tile could not be found at storage location: %s\n", rdata->store->tile_storage_id(rdata->store, cmd->xmlname, cmd->options, cmd->x, cmd->y, cmd->z, storage_id)); apr_ctime(mtime_str, r->finfo.mtime); apr_ctime(atime_str, r->finfo.atime); return error_message(r, "Tile is %s. Last rendered at %s. Last accessed at %s. Stored in %s\n\n" "(Dates might not be accurate. Rendering time might be reset to an old date for tile expiry." " Access times might not be updated on all file systems)\n", (state == tileOld || state == tileVeryOld) ? "due to be rendered" : "clean", mtime_str, atime_str, rdata->store->tile_storage_id(rdata->store, cmd->xmlname, cmd->options, cmd->x, cmd->y, cmd->z, storage_id)); } /** * Implement a tilejson description page for the tile layer. * This follows the tilejson specification of mapbox ( https://github.com/mapbox/tilejson-spec/tree/master/2.0.0 ) */ static int tile_handler_json(request_rec *r) { char *buf; int len; char *timestr; long int maxAge = 7 * 24 * 60 * 60; apr_table_t *t = r->headers_out; int i; char *md5; struct tile_request_data *rdata; tile_server_conf *scfg; tile_config_rec *tile_configs; tile_config_rec *tile_config; if (strcmp(r->handler, "tile_json")) { return DECLINED; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Handling tile json request\n"); rdata = (struct tile_request_data *)ap_get_module_config(r->request_config, &tile_module); scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); tile_configs = (tile_config_rec *)scfg->configs->elts; tile_config = &tile_configs[rdata->layerNumber]; ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Handling tile json request for layer %s\n", tile_config->xmlname); if (tile_config->cors) { int resp = add_cors(r, tile_config->cors); if (resp != DONE) { return resp; } } buf = (char *)malloc(8 * 1024); snprintf(buf, 8 * 1024, "{\n" "\t\"tilejson\": \"2.0.0\",\n" "\t\"schema\": \"xyz\",\n" "\t\"name\": \"%s\",\n" "\t\"description\": \"%s\",\n" "\t\"attribution\": \"%s\",\n" "\t\"minzoom\": %i,\n" "\t\"maxzoom\": %i,\n" "\t\"tiles\": [\n", tile_config->xmlname, (tile_config->description ? tile_config->description : ""), tile_config->attribution, tile_config->minzoom, tile_config->maxzoom); for (i = 0; i < tile_config->noHostnames; i++) { strncat(buf, "\t\t\"", 8 * 1024 - strlen(buf) - 1); strncat(buf, tile_config->hostnames[i], 8 * 1024 - strlen(buf) - 1); strncat(buf, tile_config->baseuri, 8 * 1024 - strlen(buf) - 1); strncat(buf, "{z}/{x}/{y}.", 8 * 1024 - strlen(buf) - 1); strncat(buf, tile_config->fileExtension, 8 * 1024 - strlen(buf) - 1); strncat(buf, "\"", 8 * 1024 - strlen(buf) - 1); if (i < tile_config->noHostnames - 1) { strncat(buf, ",", 8 * 1024 - strlen(buf) - 1); } strncat(buf, "\n", 8 * 1024 - strlen(buf) - 1); } strncat(buf, "\t]\n}\n", 8 * 1024 - strlen(buf) - 1); len = strlen(buf); /* * Add HTTP headers. Make this file cachable for 1 week */ md5 = ap_md5_binary(r->pool, (unsigned char *)buf, len); apr_table_setn(r->headers_out, "ETag", apr_psprintf(r->pool, "\"%s\"", md5)); ap_set_content_type(r, "application/json"); ap_set_content_length(r, len); apr_table_mergen(t, "Cache-Control", apr_psprintf(r->pool, "max-age=%li", maxAge)); timestr = (char *)apr_palloc(r->pool, APR_RFC822_DATE_LEN); apr_rfc822_date(timestr, (apr_time_from_sec(maxAge) + r->request_time)); apr_table_setn(t, "Expires", timestr); ap_rwrite(buf, len, r); free(buf); return OK; } static int tile_handler_mod_stats(request_rec *r) { stats_data *stats; stats_data local_stats; int i; tile_server_conf *scfg; tile_config_rec *tile_configs; if (strcmp(r->handler, "tile_mod_stats")) { return DECLINED; } scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); tile_configs = (tile_config_rec *)scfg->configs->elts; if (!scfg->enable_global_stats) { return error_message(r, "Stats are not enabled for this server"); } if (get_global_lock(r, stats_mutex) != 0) { // Copy over the global counter variable into // local variables, that we can immediately // release the lock again stats = (stats_data *)apr_shm_baseaddr_get(stats_shm); memcpy(&local_stats, stats, sizeof(stats_data)); local_stats.noResp200Layer = (apr_uint64_t *)malloc(sizeof(apr_uint64_t) * scfg->configs->nelts); memcpy(local_stats.noResp200Layer, stats->noResp200Layer, sizeof(apr_uint64_t) * scfg->configs->nelts); local_stats.noResp404Layer = (apr_uint64_t *)malloc(sizeof(apr_uint64_t) * scfg->configs->nelts); memcpy(local_stats.noResp404Layer, stats->noResp404Layer, sizeof(apr_uint64_t) * scfg->configs->nelts); apr_global_mutex_unlock(stats_mutex); } else { return error_message(r, "Failed to acquire lock, can't display stats"); } ap_rprintf(r, "NoResp200: %" APR_UINT64_T_FMT "\n", local_stats.noResp200); ap_rprintf(r, "NoResp304: %" APR_UINT64_T_FMT "\n", local_stats.noResp304); ap_rprintf(r, "NoResp404: %" APR_UINT64_T_FMT "\n", local_stats.noResp404); ap_rprintf(r, "NoResp503: %" APR_UINT64_T_FMT "\n", local_stats.noResp503); ap_rprintf(r, "NoResp5XX: %" APR_UINT64_T_FMT "\n", local_stats.noResp5XX); ap_rprintf(r, "NoRespOther: %" APR_UINT64_T_FMT "\n", local_stats.noRespOther); ap_rprintf(r, "NoFreshCache: %" APR_UINT64_T_FMT "\n", local_stats.noFreshCache); ap_rprintf(r, "NoOldCache: %" APR_UINT64_T_FMT "\n", local_stats.noOldCache); ap_rprintf(r, "NoVeryOldCache: %" APR_UINT64_T_FMT "\n", local_stats.noVeryOldCache); ap_rprintf(r, "NoFreshRender: %" APR_UINT64_T_FMT "\n", local_stats.noFreshRender); ap_rprintf(r, "NoOldRender: %" APR_UINT64_T_FMT "\n", local_stats.noOldRender); ap_rprintf(r, "NoVeryOldRender: %" APR_UINT64_T_FMT "\n", local_stats.noVeryOldRender); for (i = 0; i <= global_max_zoom; i++) { ap_rprintf(r, "NoRespZoom%02i: %" APR_UINT64_T_FMT "\n", i, local_stats.noRespZoom[i]); } ap_rprintf(r, "NoTileBufferReads: %" APR_UINT64_T_FMT "\n", local_stats.noTotalBufferRetrieval); ap_rprintf(r, "DurationTileBufferReads: %" APR_UINT64_T_FMT "\n", local_stats.totalBufferRetrievalTime); for (i = 0; i <= global_max_zoom; i++) { ap_rprintf(r, "NoTileBufferReadZoom%02i: %" APR_UINT64_T_FMT "\n", i, local_stats.noZoomBufferRetrieval[i]); ap_rprintf(r, "DurationTileBufferReadZoom%02i: %" APR_UINT64_T_FMT "\n", i, local_stats.zoomBufferRetrievalTime[i]); } for (i = 0; i < scfg->configs->nelts; ++i) { tile_config_rec *tile_config = &tile_configs[i]; ap_rprintf(r, "NoRes200Layer%s: %" APR_UINT64_T_FMT "\n", tile_config->baseuri, local_stats.noResp200Layer[i]); ap_rprintf(r, "NoRes404Layer%s: %" APR_UINT64_T_FMT "\n", tile_config->baseuri, local_stats.noResp404Layer[i]); } free(local_stats.noResp200Layer); free(local_stats.noResp404Layer); return OK; } static int tile_handler_metrics(request_rec *r) { stats_data *stats; stats_data local_stats; int i; tile_server_conf *scfg; tile_config_rec *tile_configs; if (strcmp(r->handler, "tile_metrics")) { return DECLINED; } scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); tile_configs = (tile_config_rec *)scfg->configs->elts; if (!scfg->enable_global_stats) { return error_message(r, "Stats are not enabled for this server"); } if (get_global_lock(r, stats_mutex) != 0) { // Copy over the global counter variable into // local variables, that we can immediately // release the lock again stats = (stats_data *)apr_shm_baseaddr_get(stats_shm); memcpy(&local_stats, stats, sizeof(stats_data)); local_stats.noResp200Layer = (apr_uint64_t *)malloc(sizeof(apr_uint64_t) * scfg->configs->nelts); memcpy(local_stats.noResp200Layer, stats->noResp200Layer, sizeof(apr_uint64_t) * scfg->configs->nelts); local_stats.noResp404Layer = (apr_uint64_t *)malloc(sizeof(apr_uint64_t) * scfg->configs->nelts); memcpy(local_stats.noResp404Layer, stats->noResp404Layer, sizeof(apr_uint64_t) * scfg->configs->nelts); apr_global_mutex_unlock(stats_mutex); } else { return error_message(r, "Failed to acquire lock, can't display stats"); } ap_rprintf(r, "# HELP modtile_http_responses_total Number of HTTP responses by response code\n"); ap_rprintf(r, "# TYPE modtile_http_responses_total counter\n"); ap_rprintf(r, "modtile_http_responses_total{status=\"200\"} %" APR_UINT64_T_FMT "\n", local_stats.noResp200); ap_rprintf(r, "modtile_http_responses_total{status=\"304\"} %" APR_UINT64_T_FMT "\n", local_stats.noResp304); ap_rprintf(r, "modtile_http_responses_total{status=\"404\"} %" APR_UINT64_T_FMT "\n", local_stats.noResp404); ap_rprintf(r, "modtile_http_responses_total{status=\"503\"} %" APR_UINT64_T_FMT "\n", local_stats.noResp503); ap_rprintf(r, "modtile_http_responses_total{status=\"5XX\"} %" APR_UINT64_T_FMT "\n", local_stats.noResp5XX); ap_rprintf(r, "modtile_http_responses_total{status=\"other\"} %" APR_UINT64_T_FMT "\n", local_stats.noRespOther); ap_rprintf(r, "# HELP modtile_tiles_total Tiles served\n"); ap_rprintf(r, "# TYPE modtile_tiles_total counter\n"); ap_rprintf(r, "modtile_tiles_total{age=\"fresh\",rendered=\"no\"} %" APR_UINT64_T_FMT "\n", local_stats.noFreshCache); ap_rprintf(r, "modtile_tiles_total{age=\"old\",rendered=\"no\"} %" APR_UINT64_T_FMT "\n", local_stats.noOldCache); ap_rprintf(r, "modtile_tiles_total{age=\"outdated\",rendered=\"no\"} %" APR_UINT64_T_FMT "\n", local_stats.noVeryOldCache); ap_rprintf(r, "modtile_tiles_total{age=\"fresh\",rendered=\"yes\"} %" APR_UINT64_T_FMT "\n", local_stats.noFreshRender); ap_rprintf(r, "modtile_tiles_total{age=\"old\",rendered=\"attempted\"} %" APR_UINT64_T_FMT "\n", local_stats.noOldRender); ap_rprintf(r, "modtile_tiles_total{age=\"outdated\",rendered=\"attempted\"} %" APR_UINT64_T_FMT "\n", local_stats.noVeryOldRender); ap_rprintf(r, "# HELP modtile_zoom_responses_total Tiles served by zoom level\n"); ap_rprintf(r, "# TYPE modtile_zoom_responses_total counter\n"); for (i = 0; i <= global_max_zoom; i++) { ap_rprintf(r, "modtile_zoom_responses_total{zoom=\"%02i\"} %" APR_UINT64_T_FMT "\n", i, local_stats.noRespZoom[i]); } ap_rprintf(r, "# HELP modtile_tile_reads_total Tiles served from the tile buffer\n"); ap_rprintf(r, "# TYPE modtile_tile_reads_total counter\n"); for (i = 0; i <= global_max_zoom; i++) { ap_rprintf(r, "modtile_tile_reads_total{zoom=\"%02i\"} %" APR_UINT64_T_FMT "\n", i, local_stats.noZoomBufferRetrieval[i]); } ap_rprintf(r, "# HELP modtile_tile_reads_seconds_total Tile buffer duration\n"); ap_rprintf(r, "# TYPE modtile_tile_reads_seconds_total counter\n"); for (i = 0; i <= global_max_zoom; i++) { ap_rprintf(r, "modtile_tile_reads_seconds_total{zoom=\"%02i\"} %lf\n", i, (double)local_stats.zoomBufferRetrievalTime[i] / 1000000.0); } ap_rprintf(r, "# HELP modtile_layer_responses_total Layer responses\n"); ap_rprintf(r, "# TYPE modtile_layer_responses_total counter\n"); for (i = 0; i < scfg->configs->nelts; ++i) { tile_config_rec *tile_config = &tile_configs[i]; ap_rprintf(r, "modtile_layer_responses_total{layer=\"%s\",status=\"200\"} %" APR_UINT64_T_FMT "\n", tile_config->baseuri, local_stats.noResp200Layer[i]); ap_rprintf(r, "modtile_layer_responses_total{layer=\"%s\",status=\"404\"} %" APR_UINT64_T_FMT "\n", tile_config->baseuri, local_stats.noResp404Layer[i]); } free(local_stats.noResp200Layer); free(local_stats.noResp404Layer); return OK; } static int tile_handler_serve(request_rec *r) { const int tile_max = MAX_SIZE; char err_msg[PATH_MAX]; char id[PATH_MAX]; char *buf; int len; int compressed; apr_status_t errstatus; struct timeval start, end; char *md5; tile_config_rec *tile_configs; struct tile_request_data *rdata; struct protocol *cmd; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(r->server->module_config, &tile_module); if (strcmp(r->handler, "tile_serve")) { return DECLINED; } rdata = (struct tile_request_data *)ap_get_module_config(r->request_config, &tile_module); cmd = rdata->cmd; if (cmd == NULL) { sleep(CLIENT_PENALTY); if (!incRespCounter(HTTP_NOT_FOUND, r, cmd, rdata->layerNumber)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase response stats counter"); } return HTTP_NOT_FOUND; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "tile_handler_serve: xml(%s) z(%d) x(%d) y(%d)", cmd->xmlname, cmd->z, cmd->x, cmd->y); tile_configs = (tile_config_rec *)scfg->configs->elts; if (tile_configs[rdata->layerNumber].cors) { int resp = add_cors(r, tile_configs[rdata->layerNumber].cors); if (resp != DONE) { return resp; } } gettimeofday(&start, NULL); // FIXME: It is a waste to do the malloc + read if we are fulfilling a HEAD or returning a 304. buf = (char *)malloc(tile_max); if (!buf) { if (!incRespCounter(HTTP_INTERNAL_SERVER_ERROR, r, cmd, rdata->layerNumber)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase response stats counter"); } return HTTP_INTERNAL_SERVER_ERROR; } err_msg[0] = 0; len = rdata->store->tile_read(rdata->store, cmd->xmlname, cmd->options, cmd->x, cmd->y, cmd->z, buf, tile_max, &compressed, err_msg); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Read tile of length %i from %s: %s", len, rdata->store->tile_storage_id(rdata->store, cmd->xmlname, cmd->options, cmd->x, cmd->y, cmd->z, id), err_msg); if (len > 0) { if (compressed) { const char *accept_encoding = apr_table_get(r->headers_in, "Accept-Encoding"); if (accept_encoding && strstr(accept_encoding, "gzip")) { r->content_encoding = "gzip"; } else { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Tile data is compressed, but user agent doesn't support Content-Encoding and we don't know how to decompress it server side"); // TODO: decompress the output stream before sending it to client } } #if 0 // Set default Last-Modified and Etag headers ap_update_mtime(r, r->finfo.mtime); ap_set_last_modified(r); ap_set_etag(r); #else // Use MD5 hash as only cache attribute. // If a tile is re-rendered and produces the same output // then we can continue to use the previous cached copy md5 = ap_md5_binary(r->pool, (unsigned char *)buf, len); apr_table_setn(r->headers_out, "ETag", apr_psprintf(r->pool, "\"%s\"", md5)); #endif ap_set_content_type(r, tile_configs[rdata->layerNumber].mimeType); ap_set_content_length(r, len); add_expiry(r, cmd); gettimeofday(&end, NULL); incTimingCounter((end.tv_sec * 1000000 + end.tv_usec) - (start.tv_sec * 1000000 + start.tv_usec), cmd->z, r); if ((errstatus = ap_meets_conditions(r)) != OK) { free(buf); if (!incRespCounter(errstatus, r, cmd, rdata->layerNumber)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase response stats counter"); } return errstatus; } else { ap_rwrite(buf, len, r); free(buf); if (!incRespCounter(errstatus, r, cmd, rdata->layerNumber)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase response stats counter"); } return OK; } } free(buf); ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to read tile from disk: %s", err_msg); if (!incRespCounter(HTTP_NOT_FOUND, r, cmd, rdata->layerNumber)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Failed to increase response stats counter"); } return DECLINED; } /* * This routine is called in the parent, so we'll set up the shared * memory segment and mutex here. */ static int mod_tile_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { void *data; /* These two help ensure that we only init once. */ const char *userdata_key = "mod_tile_init_module"; apr_status_t rs; stats_data *stats; delaypool *delayp; int i; /* * The following checks if this routine has been called before. * This is necessary because the parent process gets initialized * a couple of times as the server starts up, and we don't want * to create any more mutexes and shared memory segments than * we're actually going to use. */ apr_pool_userdata_get(&data, userdata_key, s->process->pool); if (!data) { apr_pool_userdata_set((const void *)1, userdata_key, apr_pool_cleanup_null, s->process->pool); return OK; } /* Kilroy was here */ /* Create the shared memory segment * would prefer to use scfg->configs->nelts here but that does * not seem to be set at this stage, so rely on previously set layerCount */ rs = apr_shm_create(&stats_shm, sizeof(stats_data) + layerCount * 2 * sizeof(apr_uint64_t), NULL, pconf); if (rs != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to create 'stats' shared memory segment"); return HTTP_INTERNAL_SERVER_ERROR; } rs = apr_shm_create(&delaypool_shm, sizeof(delaypool), NULL, pconf); if (rs != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to create 'delaypool' shared memory segment"); return HTTP_INTERNAL_SERVER_ERROR; } /* Created it, now let's zero it out */ stats = (stats_data *)apr_shm_baseaddr_get(stats_shm); stats->noResp200 = 0; stats->noResp304 = 0; stats->noResp404 = 0; stats->noResp503 = 0; stats->noResp5XX = 0; for (i = 0; i <= global_max_zoom; i++) { stats->noRespZoom[i] = 0; } stats->totalBufferRetrievalTime = 0; stats->noTotalBufferRetrieval = 0; for (i = 0; i <= global_max_zoom; i++) { stats->zoomBufferRetrievalTime[i] = 0; stats->noZoomBufferRetrieval[i] = 0; } stats->noRespOther = 0; stats->noFreshCache = 0; stats->noFreshRender = 0; stats->noOldCache = 0; stats->noOldRender = 0; /* the "stats" block does not have a fixed size; it is a fixed-size struct * followed by two arrays with one element each per layer. All of this sits * in one shared memory block, and for ease of use, pointers from inside the * struct point to the arrays. */ stats->noResp404Layer = (apr_uint64_t *)((char *)stats + sizeof(stats_data)); stats->noResp200Layer = (apr_uint64_t *)((char *)stats + sizeof(stats_data) + sizeof(apr_uint64_t) * layerCount); /* zero out all the non-fixed-length stuff */ for (i = 0; i < layerCount; i++) { stats->noResp404Layer[i] = 0; stats->noResp200Layer[i] = 0; } delayp = (delaypool *)apr_shm_baseaddr_get(delaypool_shm); delayp->last_tile_fillup = apr_time_now(); delayp->last_render_fillup = apr_time_now(); for (i = 0; i < DELAY_HASHTABLE_SIZE; i++) { memset(&(delayp->users[i].ip_addr), 0, sizeof(struct in6_addr)); delayp->users[i].available_tiles = 0; delayp->users[i].available_render_req = 0; } for (i = 0; i < DELAY_HASHTABLE_WHITELIST_SIZE; i++) { delayp->whitelist[i] = (in_addr_t)0; } /* TODO: need a way to initialise the delaypool whitelist */ /* Create global mutex */ /* * Create another unique filename to lock upon. Note that * depending on OS and locking mechanism of choice, the file * may or may not be actually created. */ stats_mutexfilename = apr_psprintf(pconf, "%s/httpd_mutex_stats.%ld", P_tmpdir, (long int)getpid()); rs = apr_global_mutex_create(&stats_mutex, (const char *)stats_mutexfilename, APR_LOCK_DEFAULT, pconf); if (rs != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to create mutex on file %s", stats_mutexfilename); return HTTP_INTERNAL_SERVER_ERROR; } #ifdef MOD_TILE_SET_MUTEX_PERMS rs = ap_unixd_set_global_mutex_perms(stats_mutex); if (rs != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rs, s, "Parent could not set permissions on mod_tile " "mutex: check User and Group directives"); return HTTP_INTERNAL_SERVER_ERROR; } #endif /* MOD_TILE_SET_MUTEX_PERMS */ /* * Create another unique filename to lock upon. Note that * depending on OS and locking mechanism of choice, the file * may or may not be actually created. */ delaypool_mutexfilename = apr_psprintf(pconf, "%s/httpd_mutex_delaypool.%ld", P_tmpdir, (long int)getpid()); rs = apr_global_mutex_create(&delaypool_mutex, (const char *)delaypool_mutexfilename, APR_LOCK_DEFAULT, pconf); if (rs != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to create mutex on file %s", delaypool_mutexfilename); return HTTP_INTERNAL_SERVER_ERROR; } #ifdef MOD_TILE_SET_MUTEX_PERMS rs = ap_unixd_set_global_mutex_perms(delaypool_mutex); if (rs != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rs, s, "Parent could not set permissions on mod_tile " "mutex: check User and Group directives"); return HTTP_INTERNAL_SERVER_ERROR; } #endif /* MOD_TILE_SET_MUTEX_PERMS */ return OK; } /* * This routine gets called when a child inits. We use it to attach * to the shared memory segment, and reinitialize the mutex and setup * connections to storage backends. */ static void mod_tile_child_init(apr_pool_t *p, server_rec *s) { apr_status_t rs; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "Initialising a new Apache child instance"); /* * Re-open the mutex for the child. Note we're reusing * the mutex pointer global here. */ rs = apr_global_mutex_child_init(&stats_mutex, (const char *)stats_mutexfilename, p); if (rs != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rs, s, "Failed to reopen mutex on file %s", stats_mutexfilename); /* There's really nothing else we can do here, since * This routine doesn't return a status. */ exit(1); /* Ugly, but what else? */ } } static void register_hooks(__attribute__((unused)) apr_pool_t *p) { ap_hook_post_config(mod_tile_post_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_child_init(mod_tile_child_init, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(tile_handler_serve, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(tile_handler_dirty, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(tile_handler_status, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(tile_handler_json, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(tile_handler_mod_stats, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(tile_handler_metrics, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_translate_name(tile_translate, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_map_to_storage(tile_storage_hook, NULL, NULL, APR_HOOK_FIRST); } static const char *_add_tile_config(cmd_parms *cmd, const char *baseuri, const char *name, int minzoom, int maxzoom, int aspect_x, int aspect_y, const char *fileExtension, const char *mimeType, const char *description, const char *attribution, const char *server_alias, const char *cors, const char *tile_dir, const int enableOptions) { tile_server_conf *scfg; tile_config_rec *tilecfg; scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); tilecfg = (tile_config_rec *)apr_array_push(scfg->configs); int attribution_len = strnlen(attribution, PATH_MAX); int baseuri_len = strnlen(baseuri, PATH_MAX); int hostnames_len = 1; int server_alias_len = strnlen(server_alias, PATH_MAX); int tile_dir_len = strnlen(tile_dir, PATH_MAX); // Set attribution to default if (attribution_len == 0) { attribution = apr_pstrdup(cmd->pool, DEFAULT_ATTRIBUTION); } // Ensure URI string ends with a trailing slash if (baseuri_len == 0) { baseuri = apr_pstrdup(cmd->pool, "/"); } else if (baseuri[baseuri_len - 1] != '/') { baseuri = apr_psprintf(cmd->pool, "%s/", baseuri); } // If server_alias is set, increment hostnames_len if (server_alias_len > 0) { hostnames_len++; } // Set tile_dir to default if (tile_dir_len == 0) { tile_dir = apr_pstrndup(cmd->pool, scfg->tile_dir, PATH_MAX); } char **hostnames = (char **)malloc(sizeof(char *) * hostnames_len); // Set first hostname to server_hostname value (if set,) otherwise use localhost if (cmd->server->server_hostname == NULL) { hostnames[0] = apr_pstrdup(cmd->pool, "http://localhost"); ap_log_perror(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, cmd->pool, "Could not determine hostname of server to configure TileJSON request output for '%s', using '%s'.", name, hostnames[0]); } else { hostnames[0] = apr_pstrcat(cmd->pool, "http://", cmd->server->server_hostname, NULL); } ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cmd->pool, "Using server hostname '%s' to configure TileJSON request output for '%s'.", hostnames[0], name); // Set second hostname to server_alias value (if set) if (server_alias_len > 0) { // Ensure second hostname string does not end with a trailing slash if (server_alias[server_alias_len - 1] == '/') { hostnames[1] = apr_pstrndup(cmd->pool, server_alias, server_alias_len - 1); } else { hostnames[1] = apr_pstrdup(cmd->pool, server_alias); } ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cmd->pool, "Also using server hostname '%s' to configure TileJSON request output for '%s'.", hostnames[1], name); } tilecfg->aspect_x = aspect_x; tilecfg->aspect_y = aspect_y; tilecfg->attribution = attribution; tilecfg->baseuri = baseuri; tilecfg->cors = cors; tilecfg->description = description; tilecfg->enableOptions = enableOptions; tilecfg->fileExtension = fileExtension; tilecfg->hostnames = hostnames; tilecfg->maxzoom = maxzoom; tilecfg->mimeType = mimeType; tilecfg->minzoom = minzoom; tilecfg->noHostnames = hostnames_len; tilecfg->store = tile_dir; tilecfg->xmlname = name; if (maxzoom > global_max_zoom) { global_max_zoom = maxzoom; } ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, cmd->server, "Loading tile config %s at %s for zooms %i - %i from tile directory %s with extension .%s and mime type %s", tilecfg->xmlname, tilecfg->baseuri, tilecfg->minzoom, tilecfg->maxzoom, tilecfg->store, tilecfg->fileExtension, tilecfg->mimeType); layerCount++; return NULL; } static const char *add_tile_mime_config(cmd_parms *cmd, void *mconfig, const char *baseuri, const char *name, const char *fileExtension) { char *cors = NULL; char *mimeType = "image/png"; if (strcmp(fileExtension, "js") == 0) { cors = "*"; mimeType = "text/javascript"; } ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, cmd->server, "AddTileMimeConfig will be deprecated in a future release, please use the following instead: AddTileConfig %s %s mimetype=%s extension=%s", baseuri, name, mimeType, fileExtension); return _add_tile_config(cmd, baseuri, name, 0, MAX_ZOOM, 1, 1, fileExtension, mimeType, "", "", "", cors, "", 0); } static const char *add_tile_config(cmd_parms *cmd, void *mconfig, int argc, char *const argv[]) { if (argc < 1) { return ("AddTileConfig error, URL path not defined"); } if (argc < 2) { return ("AddTileConfig error, name of renderd config not defined"); } int maxzoom = MAX_ZOOM; int minzoom = 0; char *baseuri = argv[0]; char *name = argv[1]; char *fileExtension = "png"; char *mimeType = "image/png"; char *tile_dir = ""; for (int i = 2; i < argc; i++) { char *value = strchr(argv[i], '='); if (value) { *value++ = 0; if (!strcmp(argv[i], "maxzoom")) { maxzoom = strtol(value, NULL, 10); } else if (!strcmp(argv[i], "minzoom")) { minzoom = strtol(value, NULL, 10); } else if (!strcmp(argv[i], "extension")) { fileExtension = value; } else if (!strcmp(argv[i], "mimetype")) { mimeType = value; } else if (!strcmp(argv[i], "tile_dir")) { tile_dir = value; } } } if ((minzoom < 0) || (maxzoom > MAX_ZOOM_SERVER)) { return "AddTileConfig error, the configured zoom level lies outside of the range supported by this server"; } return _add_tile_config(cmd, baseuri, name, minzoom, maxzoom, 1, 1, fileExtension, mimeType, "", "", "", "", tile_dir, 0); } static const char *load_tile_config(cmd_parms *cmd, void *mconfig, const char *config_file_name) { struct stat buffer; if (stat(config_file_name, &buffer) != 0) { return "LoadTileConfigFile error, unable to open config file"; } const char *result; xmlconfigitem maps[XMLCONFIGS_MAX]; process_map_sections(config_file_name, maps, "", 0); for (int i = 0; i < XMLCONFIGS_MAX; i++) { if (maps[i].xmlname != NULL) { result = _add_tile_config(cmd, maps[i].xmluri, maps[i].xmlname, maps[i].min_zoom, maps[i].max_zoom, maps[i].aspect_x, maps[i].aspect_y, maps[i].file_extension, maps[i].mime_type, maps[i].description, maps[i].attribution, maps[i].server_alias, maps[i].cors, maps[i].tile_dir, strlen(maps[i].parameterization)); if (result != NULL) { return result; } } } return NULL; } static const char *arg_to_apr_int64_t(cmd_parms *cmd, const char *buf, apr_int64_t *dest, const char *config_directive_name) { char *end; apr_int64_t arg = apr_strtoi64(buf, &end, 10); if (end == buf) { return apr_pstrcat(cmd->pool, config_directive_name, " argument must be an integer", NULL); } ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cmd->pool, "Setting %s argument to %" APR_INT64_T_FMT, config_directive_name, arg); *dest = arg; return NULL; } static const char *arg_to_double(cmd_parms *cmd, const char *buf, double *dest, const char *config_directive_name) { char *end; double arg = strtod(buf, &end); if (end == buf) { return apr_pstrcat(cmd->pool, config_directive_name, " argument must be a float", NULL); } ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cmd->pool, "Setting %s argument to %f", config_directive_name, arg); *dest = arg; return NULL; } static const char *arg_to_int(cmd_parms *cmd, const char *buf, int *dest, const char *config_directive_name) { char *end; int arg = (int)apr_strtoi64(buf, &end, 10); if (end == buf) { return apr_pstrcat(cmd->pool, config_directive_name, " argument must be an integer", NULL); } ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cmd->pool, "Setting %s argument to %i", config_directive_name, arg); *dest = arg; return NULL; } static const char *arg_to_string(cmd_parms *cmd, const char *buf, const char **dest, const char *config_directive_name) { *dest = apr_pstrndup(cmd->pool, buf, PATH_MAX); ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cmd->pool, "Setting %s argument to %s", config_directive_name, *dest); return NULL; } static const char *mod_tile_request_timeout_config(cmd_parms *cmd, void *mconfig, const char *request_timeout_string) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); return arg_to_int(cmd, request_timeout_string, &scfg->request_timeout, cmd->directive->directive); } static const char *mod_tile_request_timeout_priority_config(cmd_parms *cmd, void *mconfig, const char *request_timeout_priority_string) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); return arg_to_int(cmd, request_timeout_priority_string, &scfg->request_timeout_priority, cmd->directive->directive); } static const char *mod_tile_max_load_old_config(cmd_parms *cmd, void *mconfig, const char *max_load_old_string) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); return arg_to_int(cmd, max_load_old_string, &scfg->max_load_old, cmd->directive->directive); } static const char *mod_tile_max_load_missing_config(cmd_parms *cmd, void *mconfig, const char *max_load_missing_string) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); return arg_to_int(cmd, max_load_missing_string, &scfg->max_load_missing, cmd->directive->directive); } static const char *mod_tile_very_old_threshold_config(cmd_parms *cmd, void *mconfig, const char *very_old_threshold_string) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); return arg_to_apr_int64_t(cmd, very_old_threshold_string, &scfg->very_old_threshold, cmd->directive->directive); } static const char *mod_tile_renderd_socket_name_config(cmd_parms *cmd, void *mconfig, const char *renderd_socket_name_string) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); return arg_to_string(cmd, renderd_socket_name_string, &scfg->renderd_socket_name, cmd->directive->directive); } static const char *mod_tile_renderd_socket_address_config(cmd_parms *cmd, void *mconfig, const char *renderd_socket_address_string, const char *renderd_socket_port_string) { const char *renderd_socket_address_result; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); renderd_socket_address_result = arg_to_string(cmd, renderd_socket_address_string, &scfg->renderd_socket_name, "ModTileRenderdSocketAddr first"); if (renderd_socket_address_result != NULL) { return renderd_socket_address_result; } return arg_to_int(cmd, renderd_socket_port_string, &scfg->renderd_socket_port, "ModTileRenderdSocketAddr second"); } static const char *mod_tile_tile_dir_config(cmd_parms *cmd, void *mconfig, const char *tile_dir_string) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); return arg_to_string(cmd, tile_dir_string, &scfg->tile_dir, cmd->directive->directive); } static const char *mod_tile_cache_extended_hostname_config(cmd_parms *cmd, void *mconfig, const char *cache_extended_hostname_string) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); return arg_to_string(cmd, cache_extended_hostname_string, &scfg->cache_extended_hostname, cmd->directive->directive); } static const char *mod_tile_cache_extended_duration_config(cmd_parms *cmd, void *mconfig, const char *cache_extended_duration_string) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); return arg_to_int(cmd, cache_extended_duration_string, &scfg->cache_extended_duration, cmd->directive->directive); } static const char *mod_tile_cache_duration_last_modified_factor_config(cmd_parms *cmd, void *mconfig, const char *cache_duration_last_modified_factor_string) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); return arg_to_double(cmd, cache_duration_last_modified_factor_string, &scfg->cache_duration_last_modified_factor, cmd->directive->directive); } static const char *mod_tile_cache_duration_max_config(cmd_parms *cmd, void *mconfig, const char *cache_duration_max_string) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); return arg_to_int(cmd, cache_duration_max_string, &scfg->cache_duration_max, cmd->directive->directive); } static const char *mod_tile_cache_duration_dirty_config(cmd_parms *cmd, void *mconfig, const char *cache_duration_dirty_string) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); return arg_to_int(cmd, cache_duration_dirty_string, &scfg->cache_duration_dirty, cmd->directive->directive); } static const char *mod_tile_cache_duration_minimum_config(cmd_parms *cmd, void *mconfig, const char *cache_duration_minimum_string) { tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); return arg_to_int(cmd, cache_duration_minimum_string, &scfg->cache_duration_minimum, cmd->directive->directive); } static const char *mod_tile_cache_duration_low_config(cmd_parms *cmd, void *mconfig, const char *cache_level_low_zoom_string, const char *cache_duration_low_zoom_string) { const char *cache_level_low_zoom_result; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); cache_level_low_zoom_result = arg_to_int(cmd, cache_level_low_zoom_string, &scfg->cache_level_low_zoom, "ModTileCacheDurationLowZoom first"); if (cache_level_low_zoom_result != NULL) { return cache_level_low_zoom_result; } return arg_to_int(cmd, cache_duration_low_zoom_string, &scfg->cache_duration_low_zoom, "ModTileCacheDurationLowZoom second"); } static const char *mod_tile_cache_duration_medium_config(cmd_parms *cmd, void *mconfig, const char *cache_level_medium_zoom_string, const char *cache_duration_medium_zoom_string) { const char *cache_level_medium_zoom_result; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); cache_level_medium_zoom_result = arg_to_int(cmd, cache_level_medium_zoom_string, &scfg->cache_level_medium_zoom, "ModTileCacheDurationMediumZoom first"); if (cache_level_medium_zoom_result != NULL) { return cache_level_medium_zoom_result; } return arg_to_int(cmd, cache_duration_medium_zoom_string, &scfg->cache_duration_medium_zoom, "ModTileCacheDurationMediumZoom second"); } static const char *mod_tile_enable_stats(cmd_parms *cmd, void *mconfig, int enable_global_stats) { ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cmd->pool, "Setting %s argument to %s", cmd->directive->directive, enable_global_stats ? "On" : "Off"); tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); scfg->enable_global_stats = enable_global_stats; return NULL; } static const char *mod_tile_enable_throttling(cmd_parms *cmd, void *mconfig, int enable_tile_throttling) { ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cmd->pool, "Setting %s argument to %s", cmd->directive->directive, enable_tile_throttling ? "On" : "Off"); tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); scfg->enable_tile_throttling = enable_tile_throttling; return NULL; } static const char *mod_tile_enable_throttling_xforward(cmd_parms *cmd, void *mconfig, const char *enable_tile_throttling_xforward_string) { ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cmd->pool, "Setting %s argument to %s", cmd->directive->directive, enable_tile_throttling_xforward_string); const char *enable_tile_throttling_xforward_result; int enable_tile_throttling_xforward; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); enable_tile_throttling_xforward_result = arg_to_int(cmd, enable_tile_throttling_xforward_string, &enable_tile_throttling_xforward, cmd->directive->directive); if (enable_tile_throttling_xforward_result != NULL) { return enable_tile_throttling_xforward_result; } if ((enable_tile_throttling_xforward < 0) || (enable_tile_throttling_xforward > 2)) { return "ModTileEnableTileThrottlingXForward needs integer argument between 0 and 2 (0 => off; 1 => use client; 2 => use last entry in chain"; } scfg->enable_tile_throttling_xforward = enable_tile_throttling_xforward; return NULL; } static const char *mod_tile_enable_bulk_mode(cmd_parms *cmd, void *mconfig, int enable_bulk_mode) { ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cmd->pool, "Setting %s argument to %s", cmd->directive->directive, enable_bulk_mode ? "On" : "Off"); tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); scfg->enable_bulk_mode = enable_bulk_mode; return NULL; } static const char *mod_tile_enable_status_url(cmd_parms *cmd, void *mconfig, int enable_status_url) { ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cmd->pool, "Setting %s argument to %s", cmd->directive->directive, enable_status_url ? "On" : "Off"); tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); scfg->enable_status_url = enable_status_url; return NULL; } static const char *mod_tile_enable_dirty_url(cmd_parms *cmd, void *mconfig, int enable_dirty_url) { ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, cmd->pool, "Setting %s argument to %s", cmd->directive->directive, enable_dirty_url ? "On" : "Off"); tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); scfg->enable_dirty_url = enable_dirty_url; return NULL; } static const char *mod_tile_delaypool_tiles_config(cmd_parms *cmd, void *mconfig, const char *delaypool_tile_size_string, const char *top_up_tile_rate_string) { const char *delaypool_tile_size_result, *top_up_tile_rate_result; double top_up_tile_rate; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); delaypool_tile_size_result = arg_to_int(cmd, delaypool_tile_size_string, &scfg->delaypool_tile_size, "ModTileThrottlingTiles first"); if (delaypool_tile_size_result != NULL) { return delaypool_tile_size_result; } top_up_tile_rate_result = arg_to_double(cmd, top_up_tile_rate_string, &top_up_tile_rate, "ModTileThrottlingTiles second"); if (top_up_tile_rate_result != NULL) { return top_up_tile_rate_result; } /*Convert topup rate into microseconds per tile */ scfg->delaypool_tile_rate = (APR_USEC_PER_SEC / top_up_tile_rate); return NULL; } static const char *mod_tile_delaypool_render_config(cmd_parms *cmd, void *mconfig, const char *delaypool_render_size_string, const char *top_up_render_rate_string) { const char *delaypool_render_size_result, *top_up_render_rate_result; double top_up_render_rate; tile_server_conf *scfg = (tile_server_conf *)ap_get_module_config(cmd->server->module_config, &tile_module); delaypool_render_size_result = arg_to_int(cmd, delaypool_render_size_string, &scfg->delaypool_render_size, "ModTileThrottlingRenders first"); if (delaypool_render_size_result != NULL) { return delaypool_render_size_result; } top_up_render_rate_result = arg_to_double(cmd, top_up_render_rate_string, &top_up_render_rate, "ModTileThrottlingRenders second"); if (top_up_render_rate_result != NULL) { return top_up_render_rate_result; } /*Convert topup rate into microseconds per tile */ scfg->delaypool_render_rate = (APR_USEC_PER_SEC / top_up_render_rate); return NULL; } static void *create_tile_config(apr_pool_t *p, server_rec *s) { ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, p, "Running create_tile_config"); tile_server_conf *scfg = (tile_server_conf *)apr_pcalloc(p, sizeof(tile_server_conf)); scfg->cache_duration_dirty = 15 * 60; scfg->cache_duration_last_modified_factor = 0.0; scfg->cache_duration_low_zoom = 6 * 24 * 60 * 60; scfg->cache_duration_max = 7 * 24 * 60 * 60; scfg->cache_duration_medium_zoom = 1 * 24 * 60 * 60; scfg->cache_duration_minimum = 3 * 60 * 60; scfg->cache_extended_duration = 0; scfg->cache_extended_hostname = ""; scfg->cache_level_low_zoom = 0; scfg->cache_level_medium_zoom = 0; scfg->configs = apr_array_make(p, 4, sizeof(tile_config_rec)); scfg->delaypool_render_rate = RENDER_TOPUP_RATE; scfg->delaypool_render_size = AVAILABLE_RENDER_BUCKET_SIZE; scfg->delaypool_tile_rate = RENDER_TOPUP_RATE; scfg->delaypool_tile_size = AVAILABLE_TILE_BUCKET_SIZE; scfg->enable_bulk_mode = 0; scfg->enable_dirty_url = 1; scfg->enable_global_stats = 1; scfg->enable_status_url = 1; scfg->enable_tile_throttling = 0; scfg->enable_tile_throttling_xforward = 0; scfg->max_load_missing = MAX_LOAD_MISSING; scfg->max_load_old = MAX_LOAD_OLD; scfg->renderd_socket_name = apr_pstrndup(p, RENDERD_SOCKET, PATH_MAX); scfg->renderd_socket_port = 0; scfg->request_timeout = REQUEST_TIMEOUT; scfg->request_timeout_priority = REQUEST_TIMEOUT; scfg->tile_dir = apr_pstrndup(p, RENDERD_TILE_DIR, PATH_MAX); scfg->very_old_threshold = VERYOLD_THRESHOLD; return scfg; } static void *merge_tile_config(apr_pool_t *p, void *basev, void *overridesv) { ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, p, "Running merge_tile_config"); tile_server_conf *scfg = (tile_server_conf *)apr_pcalloc(p, sizeof(tile_server_conf)); tile_server_conf *scfg_base = (tile_server_conf *)basev; tile_server_conf *scfg_over = (tile_server_conf *)overridesv; scfg->cache_duration_dirty = scfg_over->cache_duration_dirty; scfg->cache_duration_last_modified_factor = scfg_over->cache_duration_last_modified_factor; scfg->cache_duration_low_zoom = scfg_over->cache_duration_low_zoom; scfg->cache_duration_max = scfg_over->cache_duration_max; scfg->cache_duration_medium_zoom = scfg_over->cache_duration_medium_zoom; scfg->cache_duration_minimum = scfg_over->cache_duration_minimum; scfg->cache_extended_duration = scfg_over->cache_extended_duration; scfg->cache_extended_hostname = apr_pstrndup(p, scfg_over->cache_extended_hostname, PATH_MAX); scfg->cache_level_low_zoom = scfg_over->cache_level_low_zoom; scfg->cache_level_medium_zoom = scfg_over->cache_level_medium_zoom; scfg->configs = apr_array_append(p, scfg_base->configs, scfg_over->configs); scfg->delaypool_render_rate = scfg_over->delaypool_render_rate; scfg->delaypool_render_size = scfg_over->delaypool_render_size; scfg->delaypool_tile_rate = scfg_over->delaypool_tile_rate; scfg->delaypool_tile_size = scfg_over->delaypool_tile_size; scfg->enable_bulk_mode = scfg_over->enable_bulk_mode; scfg->enable_dirty_url = scfg_over->enable_dirty_url; scfg->enable_global_stats = scfg_over->enable_global_stats; scfg->enable_status_url = scfg_over->enable_status_url; scfg->enable_tile_throttling = scfg_over->enable_tile_throttling; scfg->enable_tile_throttling_xforward = scfg_over->enable_tile_throttling_xforward; scfg->max_load_missing = scfg_over->max_load_missing; scfg->max_load_old = scfg_over->max_load_old; scfg->renderd_socket_name = apr_pstrndup(p, scfg_over->renderd_socket_name, PATH_MAX); scfg->renderd_socket_port = scfg_over->renderd_socket_port; scfg->request_timeout = scfg_over->request_timeout; scfg->request_timeout_priority = scfg_over->request_timeout_priority; scfg->tile_dir = apr_pstrndup(p, scfg_over->tile_dir, PATH_MAX); scfg->very_old_threshold = scfg_over->very_old_threshold; // Construct a table of minimum cache times per zoom level for (int i = 0; i <= MAX_ZOOM_SERVER; i++) { if (i <= scfg->cache_level_low_zoom) { scfg->mincachetime[i] = scfg->cache_duration_low_zoom; } else if (i <= scfg->cache_level_medium_zoom) { scfg->mincachetime[i] = scfg->cache_duration_medium_zoom; } else { scfg->mincachetime[i] = scfg->cache_duration_minimum; } } return scfg; } static const command_rec tile_cmds[] = { AP_INIT_FLAG("ModTileBulkMode", mod_tile_enable_bulk_mode, NULL, OR_OPTIONS, "On Off - make all requests to renderd with bulk render priority, never mark tiles dirty"), AP_INIT_FLAG("ModTileEnableDirtyURL", mod_tile_enable_dirty_url, NULL, OR_OPTIONS, "On Off - whether to handle .../dirty urls "), AP_INIT_FLAG("ModTileEnableStats", mod_tile_enable_stats, NULL, OR_OPTIONS, "On Off - enable of keeping stats about what mod_tile is serving"), AP_INIT_FLAG("ModTileEnableStatusURL", mod_tile_enable_status_url, NULL, OR_OPTIONS, "On Off - whether to handle .../status urls "), AP_INIT_FLAG("ModTileEnableTileThrottling", mod_tile_enable_throttling, NULL, OR_OPTIONS, "On Off - enable of throttling of IPs that excessively download tiles such as scrapers"), AP_INIT_TAKE1("LoadTileConfigFile", load_tile_config, NULL, OR_OPTIONS, "load an entire renderd config file"), AP_INIT_TAKE1("ModTileCacheDurationDirty", mod_tile_cache_duration_dirty_config, NULL, OR_OPTIONS, "Set the cache expiry for serving dirty tiles"), AP_INIT_TAKE1("ModTileCacheDurationMax", mod_tile_cache_duration_max_config, NULL, OR_OPTIONS, "Set the maximum cache expiry in seconds"), AP_INIT_TAKE1("ModTileCacheDurationMinimum", mod_tile_cache_duration_minimum_config, NULL, OR_OPTIONS, "Set the minimum cache expiry"), AP_INIT_TAKE1("ModTileCacheExtendedDuration", mod_tile_cache_extended_duration_config, NULL, OR_OPTIONS, "set length of extended period caching"), AP_INIT_TAKE1("ModTileCacheExtendedHostName", mod_tile_cache_extended_hostname_config, NULL, OR_OPTIONS, "set hostname for extended period caching"), AP_INIT_TAKE1("ModTileCacheLastModifiedFactor", mod_tile_cache_duration_last_modified_factor_config, NULL, OR_OPTIONS, "Set the factor by which the last modified determines cache expiry"), AP_INIT_TAKE1("ModTileEnableTileThrottlingXForward", mod_tile_enable_throttling_xforward, NULL, OR_OPTIONS, "0 1 2 - use X-Forwarded-For http header to determine IP for throttling when available. 0 => off, 1 => use first entry, 2 => use last entry of the caching chain"), AP_INIT_TAKE1("ModTileMaxLoadMissing", mod_tile_max_load_missing_config, NULL, OR_OPTIONS, "Set max load for rendering missing tiles"), AP_INIT_TAKE1("ModTileMaxLoadOld", mod_tile_max_load_old_config, NULL, OR_OPTIONS, "Set max load for rendering old tiles"), AP_INIT_TAKE1("ModTileMissingRequestTimeout", mod_tile_request_timeout_priority_config, NULL, OR_OPTIONS, "Set timeout in seconds on missing mod_tile requests"), AP_INIT_TAKE1("ModTileRenderdSocketName", mod_tile_renderd_socket_name_config, NULL, OR_OPTIONS, "Set name of unix domain socket for connecting to rendering daemon"), AP_INIT_TAKE1("ModTileRequestTimeout", mod_tile_request_timeout_config, NULL, OR_OPTIONS, "Set timeout in seconds on mod_tile requests"), AP_INIT_TAKE1("ModTileTileDir", mod_tile_tile_dir_config, NULL, OR_OPTIONS, "Set name of tile cache directory"), AP_INIT_TAKE1("ModTileVeryOldThreshold", mod_tile_very_old_threshold_config, NULL, OR_OPTIONS, "set the time threshold from when an outdated tile ist considered very old and rendered with slightly higher priority."), AP_INIT_TAKE2("ModTileCacheDurationLowZoom", mod_tile_cache_duration_low_config, NULL, OR_OPTIONS, "Set the minimum cache duration and zoom level for low zoom tiles"), AP_INIT_TAKE2("ModTileCacheDurationMediumZoom", mod_tile_cache_duration_medium_config, NULL, OR_OPTIONS, "Set the minimum cache duration and zoom level for medium zoom tiles"), AP_INIT_TAKE2("ModTileRenderdSocketAddr", mod_tile_renderd_socket_address_config, NULL, OR_OPTIONS, "Set address and port of the TCP socket for connecting to rendering daemon"), AP_INIT_TAKE2("ModTileThrottlingRenders", mod_tile_delaypool_render_config, NULL, OR_OPTIONS, "Set the initial bucket size (number of tiles) and top up rate (tiles per second) for throttling tile request per IP"), AP_INIT_TAKE2("ModTileThrottlingTiles", mod_tile_delaypool_tiles_config, NULL, OR_OPTIONS, "Set the initial bucket size (number of tiles) and top up rate (tiles per second) for throttling tile request per IP"), AP_INIT_TAKE3("AddTileMimeConfig", add_tile_mime_config, NULL, OR_OPTIONS, "path, name of renderd config and file extension to use"), AP_INIT_TAKE_ARGV("AddTileConfig", add_tile_config, NULL, OR_OPTIONS, "path, name of renderd config and optional key-value pairs to use"), {NULL} }; module AP_MODULE_DECLARE_DATA tile_module = { STANDARD20_MODULE_STUFF, NULL, /* dir config creater */ NULL, /* dir merger --- default is to override */ create_tile_config, /* server config */ merge_tile_config, /* merge server config */ tile_cmds, /* command apr_table_t */ register_hooks /* register hooks */ }; mod_tile-0.8.0/src/mysql2file.c000066400000000000000000000104121474064163400163440ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define WWW_ROOT "/var/www/html" // TILE_PATH must have tile z directory z(0..18)/x/y.png #define TILE_PATH "/osm_tiles2" // Build parent directories for the specified file name // Note: the part following the trailing / is ignored // e.g. mkdirp("/a/b/foo.png") == shell mkdir -p /a/b static int mkdirp(const char *path) { struct stat s; char tmp[PATH_MAX]; char *p; strncpy(tmp, path, sizeof(tmp)); // Look for parent directory p = strrchr(tmp, '/'); if (!p) { return 0; } *p = '\0'; if (!stat(tmp, &s)) { return !S_ISDIR(s.st_mode); } *p = '/'; // Walk up the path making sure each element is a directory p = tmp; if (!*p) { return 0; } p++; // Ignore leading / while (*p) { if (*p == '/') { *p = '\0'; if (!stat(tmp, &s)) { if (!S_ISDIR(s.st_mode)) { return 1; } } else if (mkdir(tmp, 0777)) { return 1; } *p = '/'; } p++; } return 0; } void parseDate(struct tm *tm, const char *str) { // 2007-05-20 13:51:35 bzero(tm, sizeof(*tm)); int n = sscanf(str, "%d-%d-%d %d:%d:%d", &tm->tm_year, &tm->tm_mon, &tm->tm_mday, &tm->tm_hour, &tm->tm_min, &tm->tm_sec); if (n != 6) { printf("failed to parse date string, got(%d): %s\n", n, str); } tm->tm_year -= 1900; } int main(int argc, char **argv) { MYSQL mysql; char query[255]; MYSQL_RES *res; MYSQL_ROW row; mysql_init(&mysql); if (!(mysql_real_connect(&mysql, "", "tile", "tile", "tile", MYSQL_PORT, NULL, 0))) { fprintf(stderr, "%s: %s\n", argv[0], mysql_error(&mysql)); exit(1); } mysql.reconnect = 1; snprintf(query, sizeof(query), "SELECT x,y,z,data,created_at FROM tiles"); if ((mysql_query(&mysql, query)) || !(res = mysql_use_result(&mysql))) { fprintf(stderr, "Cannot query tiles: %s\n", mysql_error(&mysql)); exit(1); } while ((row = mysql_fetch_row(res))) { ulong *lengths = mysql_fetch_lengths(res); char path[PATH_MAX]; unsigned long int x, y, z, length; time_t created_at; const char *data; struct tm date; int fd; struct utimbuf utb; assert(mysql_num_fields(res) == 5); //printf("x(%s) y(%s) z(%s) data_length(%lu): %s\n", row[0], row[1], row[2], lengths[3], row[4]); x = strtoul(row[0], NULL, 10); y = strtoul(row[1], NULL, 10); z = strtoul(row[2], NULL, 10); data = row[3]; length = lengths[3]; parseDate(&date, row[4]); created_at = mktime(&date); //printf("x(%lu) y(%lu) z(%lu) data_length(%lu): %s", x,y,z,length,ctime(&created_at)); if (!length) { printf("skipping empty tile x(%lu) y(%lu) z(%lu) data_length(%lu): %s", x, y, z, length, ctime(&created_at)); continue; } snprintf(path, PATH_MAX, WWW_ROOT TILE_PATH "/%lu/%lu/%lu.png", z, x, y); printf("%s\n", path); mkdirp(path); fd = open(path, O_CREAT | O_WRONLY, 0644); if (fd < 0) { perror(path); exit(1); } if (write(fd, data, length) != length) { perror("writing tile"); exit(2); } close(fd); utb.actime = created_at; utb.modtime = created_at; if (utime(path, &utb) < 0) { perror("utime"); exit(3); } } printf("Number of rows: %lu\n", (unsigned long) mysql_num_rows(res)); mysql_free_result(res); mysql_close(&mysql); /* Close & free connection */ return 0; } mod_tile-0.8.0/src/parameterize_style.cpp000066400000000000000000000054611474064163400205350ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #if MAPNIK_MAJOR_VERSION < 4 #include #endif #include "g_logger.h" #include "parameterize_style.hpp" static void parameterize_map_language(mapnik::Map &m, char *parameter) { unsigned int i; char *data = strdup(parameter); char *tok; char name_replace[256]; name_replace[0] = 0; g_logger(G_LOG_LEVEL_DEBUG, "Internationalizing map to language parameter: %s", parameter); tok = strtok(data, ","); if (!tok) { free(data); return; // No parameterization given } strncat(name_replace, ", coalesce(", 255); while (tok) { if (strcmp(tok, "_") == 0) { strncat(name_replace, "name,", 255); } else { strncat(name_replace, "tags->'name:", 255); strncat(name_replace, tok, 255); strncat(name_replace, "',", 255); } tok = strtok(NULL, ","); } free(data); name_replace[strlen(name_replace) - 1] = 0; strncat(name_replace, ") as name", 255); for (i = 0; i < m.layer_count(); i++) { mapnik::layer &l = m.get_layer(i); mapnik::parameters params = l.datasource()->params(); if (params.find("table") != params.end()) { auto table = params.get("table"); if (table && table->find(",name") != std::string::npos) { std::string str = *table; size_t pos = str.find(",name"); str.replace(pos, 5, name_replace); params["table"] = str; l.set_datasource(mapnik::datasource_cache::instance().create(params)); } } } } parameterize_function_ptr init_parameterization_function(const char *function_name) { if (strcmp(function_name, "") == 0) { g_logger(G_LOG_LEVEL_DEBUG, "Parameterize_style not specified (or empty string specified)"); return NULL; } else if (strcmp(function_name, "language") == 0) { g_logger(G_LOG_LEVEL_DEBUG, "Loading parameterization function for '%s'", function_name); return parameterize_map_language; } else { g_logger(G_LOG_LEVEL_WARNING, "unknown parameterization function for '%s'", function_name); } return NULL; } mod_tile-0.8.0/src/protocol_helper.c000066400000000000000000000063631474064163400174670ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include "protocol.h" #include #include #include #include #include #include "g_logger.h" int send_cmd(struct protocol * cmd, int fd) { int ret; g_logger(G_LOG_LEVEL_DEBUG, "Sending render cmd(%i %s %i/%i/%i) with protocol version %i to fd %i", cmd->cmd, cmd->xmlname, cmd->z, cmd->x, cmd->y, cmd->ver, fd); if ((cmd->ver > 3) || (cmd->ver < 1)) { g_logger(G_LOG_LEVEL_ERROR, "Failed to send render cmd with unknown protocol version %i on fd %d", cmd->ver, fd); return -1; } switch (cmd->ver) { case 1: ret = send(fd, cmd, sizeof(struct protocol_v1), 0); break; case 2: ret = send(fd, cmd, sizeof(struct protocol_v2), 0); break; case 3: ret = send(fd, cmd, sizeof(struct protocol), 0); break; } if ((ret != sizeof(struct protocol)) && (ret != sizeof(struct protocol_v2)) && (ret != sizeof(struct protocol_v1))) { g_logger(G_LOG_LEVEL_ERROR, "Failed to send render cmd on fd %i", fd); g_logger(G_LOG_LEVEL_ERROR, "send error: %s", strerror(errno)); } return ret; } int recv_cmd(struct protocol * cmd, int fd, int block) { int ret, ret2; memset(cmd, 0, sizeof(*cmd)); ret = recv(fd, cmd, sizeof(struct protocol_v1), block ? MSG_WAITALL : MSG_DONTWAIT); if (ret < 1) { g_logger(G_LOG_LEVEL_DEBUG, "Failed to read cmd on fd %i", fd); return -1; } else if (ret < sizeof(struct protocol_v1)) { g_logger(G_LOG_LEVEL_DEBUG, "Read incomplete cmd on fd %i", fd); return 0; } if ((cmd->ver > 3) || (cmd->ver < 1)) { g_logger(G_LOG_LEVEL_WARNING, "Failed to receive render cmd with unknown protocol version %i", cmd->ver); return -1; } g_logger(G_LOG_LEVEL_DEBUG, "Got incoming request with protocol version %i", cmd->ver); switch (cmd->ver) { case 1: ret2 = 0; break; case 2: ret2 = recv(fd, ((void*)cmd) + sizeof(struct protocol_v1), sizeof(struct protocol_v2) - sizeof(struct protocol_v1), block ? MSG_WAITALL : MSG_DONTWAIT); break; case 3: ret2 = recv(fd, ((void*)cmd) + sizeof(struct protocol_v1), sizeof(struct protocol) - sizeof(struct protocol_v1), block ? MSG_WAITALL : MSG_DONTWAIT); break; } if ((cmd->ver > 1) && (ret2 < 1)) { g_logger(G_LOG_LEVEL_WARNING, "Socket prematurely closed: %i", fd); return -1; } ret += ret2; if ((ret == sizeof(struct protocol)) || (ret == sizeof(struct protocol_v1)) || (ret == sizeof(struct protocol_v2))) { return ret; } g_logger(G_LOG_LEVEL_WARNING, "Socket read wrong number of bytes: %i -> %li, %li", ret, sizeof(struct protocol_v2), sizeof(struct protocol)); return 0; } mod_tile-0.8.0/src/render_expired.c000066400000000000000000000411441474064163400172620ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include #include #include #include "config.h" #include "g_logger.h" #include "render_config.h" #include "render_submit_queue.h" #include "renderd_config.h" #include "store.h" // macros handling our tile marking arrays (these are essentially bit arrays // that have one bit for each tile on the repsective zoom level; since we only // need them for meta tile levels, even if someone were to render level 20, // we'd still only use 4^17 bits = 2 GB RAM (plus a little for the lower zoom // levels) - this saves us the hassle of working with a tree structure. #define TILE_REQUESTED(z, x, y) \ (tile_requested[z][((x) * twopow[z] + (y)) / (8 * sizeof(int))] >> (((x) * twopow[z] + (y)) % (8 * sizeof(int)))) & 0x01 #define SET_TILE_REQUESTED(z, x, y) \ tile_requested[z][((x) * twopow[z] + (y)) / (8 * sizeof(int))] |= (0x01 << (((x) * twopow[z] + (y)) % (8 * sizeof(int)))); #ifndef METATILE #warning("render_expired not implemented for non-metatile mode. Feel free to submit fix") int main(int argc, char **argv) { fprintf(stderr, "render_expired not implemented for non-metatile mode. Feel free to submit fix!\n"); return -1; } #else // tile marking arrays unsigned int **tile_requested; // "two raised to the power of [...]" - don't trust pow() to be efficient // for base 2 unsigned long long twopow[MAX_ZOOM]; void display_rate(struct timeval start, struct timeval end, int num) { int d_s, d_us; float sec; d_s = end.tv_sec - start.tv_sec; d_us = end.tv_usec - start.tv_usec; sec = d_s + d_us / 1000000.0; g_logger(G_LOG_LEVEL_MESSAGE, "\t%d in %.2f seconds (%.2f/s)", num, sec, num / sec); } int main(int argc, char **argv) { const char *config_file_name_default = RENDERD_CONFIG; const char *mapname_default = XMLCONFIG_DEFAULT; const char *socketname_default = RENDERD_SOCKET; const char *tile_dir_default = RENDERD_TILE_DIR; int delete_from_default = -1; int max_load_default = MAX_LOAD_OLD; int max_zoom_default = MAX_ZOOM; int min_zoom_default = 0; int num_threads_default = 1; int touch_from_default = -1; const char *config_file_name = config_file_name_default; const char *mapname = mapname_default; const char *socketname = socketname_default; const char *tile_dir = tile_dir_default; int delete_from = delete_from_default; int max_load = max_load_default; int max_zoom = max_zoom_default; int min_zoom = min_zoom_default; int num_threads = num_threads_default; int touch_from = touch_from_default; int config_file_name_passed = 0; int mapname_passed = 0; int socketname_passed = 0; int tile_dir_passed = 0; int delete_from_passed = 0; int max_load_passed = 0; int max_zoom_passed = 0; int min_zoom_passed = 0; int num_threads_passed = 0; int touch_from_passed = 0; int x, y, z; struct timeval start, end; int num_render = 0, num_all = 0, num_read = 0, num_ignore = 0, num_unlink = 0, num_touch = 0; int doRender = 0; int progress = 1; int verbose = 0; struct storage_backend *store; // excess_zoomlevels is how many zoom levels at the large end // we can ignore because their tiles will share one meta tile. // with the default METATILE==8 this is 3. int excess_zoomlevels = 0; int mt = METATILE; while (mt > 1) { excess_zoomlevels++; mt >>= 1; } foreground = 1; while (1) { int option_index = 0; static struct option long_options[] = { {"config", required_argument, 0, 'c'}, {"delete-from", required_argument, 0, 'd'}, {"map", required_argument, 0, 'm'}, {"max-load", required_argument, 0, 'l'}, {"max-zoom", required_argument, 0, 'Z'}, {"min-zoom", required_argument, 0, 'z'}, {"no-progress", no_argument, 0, 'N'}, {"num-threads", required_argument, 0, 'n'}, {"socket", required_argument, 0, 's'}, {"tile-dir", required_argument, 0, 't'}, {"touch-from", required_argument, 0, 'T'}, {"verbose", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {0, 0, 0, 0} }; int c = getopt_long(argc, argv, "c:d:m:l:Z:z:Nn:s:t:T:vhV", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'c': /* -c, --config */ config_file_name = strndup(optarg, PATH_MAX); config_file_name_passed = 1; struct stat buffer; if (stat(config_file_name, &buffer) != 0) { g_logger(G_LOG_LEVEL_CRITICAL, "Config file '%s' does not exist, please specify a valid file", config_file_name); return 1; } break; case 'd': /* -d, --delete-from */ delete_from = min_max_int_opt(optarg, "delete-from", 0, MAX_ZOOM); delete_from_passed = 1; break; case 'm': /* -m, --map */ mapname = strndup(optarg, XMLCONFIG_MAX); mapname_passed = 1; break; case 'l': /* -l, --max-load */ max_load = min_max_int_opt(optarg, "maximum load", 0, -1); max_load_passed = 1; break; case 'Z': /* -Z, --max-zoom */ max_zoom = min_max_int_opt(optarg, "maximum zoom", 0, MAX_ZOOM); max_zoom_passed = 1; break; case 'z': /* -z, --min-zoom */ min_zoom = min_max_int_opt(optarg, "minimum zoom", 0, MAX_ZOOM); min_zoom_passed = 1; break; case 'N': /* -N, --no-progress */ progress = 0; break; case 'n': /* -n, --num-threads */ num_threads = min_max_int_opt(optarg, "number of threads", 1, -1); num_threads_passed = 1; break; case 's': /* -s, --socket */ socketname = strndup(optarg, PATH_MAX); socketname_passed = 1; break; case 't': /* -t, --tile-dir */ tile_dir = strndup(optarg, PATH_MAX); tile_dir_passed = 1; break; case 'T': /* -T, --touch-from */ touch_from = min_max_int_opt(optarg, "touch-from", 0, MAX_ZOOM); touch_from_passed = 1; break; case 'v': /* -v, --verbose */ verbose = 1; break; case 'h': /* -h, --help */ fprintf(stderr, "Usage: render_expired [OPTION] ...\n"); fprintf(stderr, " -c, --config=CONFIG specify the renderd config file (default is off)\n"); fprintf(stderr, " -d, --delete-from=ZOOM when expiring tiles of ZOOM or higher, delete them instead of re-rendering (default is off)\n"); fprintf(stderr, " -l, --max-load=LOAD sleep if load is this high (default is '%d')\n", max_load_default); fprintf(stderr, " -m, --map=MAP render tiles in this map (default is '%s')\n", mapname_default); fprintf(stderr, " -N, --no-progress disable display of progress messages (default is off)\n"); fprintf(stderr, " -n, --num-threads=N the number of parallel request threads (default is '%d')\n", num_threads_default); fprintf(stderr, " -s, --socket=SOCKET|HOSTNAME:PORT unix domain socket name or hostname and port for contacting renderd (default is '%s')\n", socketname_default); fprintf(stderr, " -t, --tile-dir=TILE_DIR tile cache directory (default is '%s')\n", tile_dir_default); fprintf(stderr, " -T, --touch-from=ZOOM when expiring tiles of ZOOM or higher, touch them instead of re-rendering (default is off)\n"); fprintf(stderr, " -Z, --max-zoom=ZOOM filter input to only render tiles less than or equal to this zoom level (default is '%d')\n", max_zoom_default); fprintf(stderr, " -z, --min-zoom=ZOOM filter input to only render tiles greater than or equal to this zoom level (default is '%d')\n", min_zoom_default); fprintf(stderr, "\n"); fprintf(stderr, " -h, --help display this help and exit\n"); fprintf(stderr, " -V, --version display the version number and exit\n"); fprintf(stderr, "\n"); fprintf(stderr, "Send a list of tiles to be rendered from STDIN in the format:\n"); fprintf(stderr, " z/x/y\n"); fprintf(stderr, "e.g.\n"); fprintf(stderr, " 1/0/1\n"); fprintf(stderr, " 1/1/1\n"); fprintf(stderr, " 1/0/0\n"); fprintf(stderr, " 1/1/0\n"); fprintf(stderr, "The above would cause all 4 tiles at zoom 1 to be rendered\n"); return 0; case 'V': /* -V, --version */ fprintf(stdout, "%s\n", VERSION); return 0; default: g_logger(G_LOG_LEVEL_CRITICAL, "unhandled char '%c'", c); return 1; } } if (max_zoom < min_zoom) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified min zoom (%i) is larger than max zoom (%i).", min_zoom, max_zoom); return 1; } if (config_file_name_passed) { int map_section_num = -1; process_config_file(config_file_name, 0, G_LOG_LEVEL_DEBUG); for (int i = 0; i < XMLCONFIGS_MAX; ++i) { if (maps[i].xmlname && strcmp(maps[i].xmlname, mapname) == 0) { map_section_num = i; } } if (map_section_num < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "Map section '%s' does not exist in config file '%s'.", mapname, config_file_name); return 1; } if (!max_zoom_passed) { max_zoom = maps[map_section_num].max_zoom; max_zoom_passed = 1; } if (!min_zoom_passed) { min_zoom = maps[map_section_num].min_zoom; min_zoom_passed = 1; } if (!num_threads_passed) { num_threads = maps[map_section_num].num_threads; num_threads_passed = 1; } if (!socketname_passed) { socketname = strndup(config.socketname, PATH_MAX); socketname_passed = 1; } if (!tile_dir_passed) { tile_dir = strndup(maps[map_section_num].tile_dir, PATH_MAX); tile_dir_passed = 1; } } // initialise arrays for tile markings tile_requested = (unsigned int **)malloc((max_zoom - excess_zoomlevels + 1) * sizeof(unsigned int *)); for (int i = 0; i <= max_zoom - excess_zoomlevels; i++) { // initialize twopow array twopow[i] = (i == 0) ? 1 : twopow[i - 1] * 2; unsigned long long fourpow = twopow[i] * twopow[i]; tile_requested[i] = (unsigned int *)calloc((fourpow / METATILE) + 1, 1); if (NULL == tile_requested[i]) { g_logger(G_LOG_LEVEL_CRITICAL, "not enough memory available"); return 1; } } store = init_storage_backend(tile_dir); if (store == NULL) { g_logger(G_LOG_LEVEL_CRITICAL, "Failed to initialise storage backend %s", tile_dir); return 1; } g_logger(G_LOG_LEVEL_INFO, "Started render_expired with the following options:"); if (config_file_name_passed) { g_logger(G_LOG_LEVEL_INFO, "\t--config = '%s' (user-specified)", config_file_name); } if (delete_from_passed) { g_logger(G_LOG_LEVEL_INFO, "\t--delete-from = '%i' (user-specified)", delete_from); } g_logger(G_LOG_LEVEL_INFO, "\t--map = '%s' (%s)", mapname, mapname_passed ? "user-specified" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--max-load = '%i' (%s)", max_load, max_load_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--max-zoom = '%i' (%s)", max_zoom, max_zoom_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--min-zoom = '%i' (%s)", min_zoom, min_zoom_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--num-threads = '%i' (%s)", num_threads, num_threads_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--socket = '%s' (%s)", socketname, socketname_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--tile-dir = '%s' (%s)", tile_dir, tile_dir_passed ? "user-specified/from config" : "default"); if (touch_from_passed) { g_logger(G_LOG_LEVEL_INFO, "\t--touch-from = '%i' (user-specified)", touch_from); } if (min_zoom < excess_zoomlevels) { if (verbose) { g_logger(G_LOG_LEVEL_MESSAGE, "Raising --min-zoom from '%i' to '%i'", min_zoom, excess_zoomlevels); } min_zoom = excess_zoomlevels; } if ((touch_from_passed && min_zoom < touch_from) || (delete_from_passed && min_zoom < delete_from) || (!touch_from_passed && !delete_from_passed)) { // No need to spawn render threads, when we're not actually going to rerender tiles spawn_workers(num_threads, socketname, max_load); doRender = 1; } gettimeofday(&start, NULL); while (!feof(stdin)) { struct stat_info s; int n = fscanf(stdin, "%d/%d/%d", &z, &x, &y); if (n != 3) { // Discard input line char tmp[1024]; const char *r = fgets(tmp, sizeof(tmp), stdin); if (!r) { continue; } if (verbose) { g_logger(G_LOG_LEVEL_WARNING, "Read invalid line: %s", tmp); } continue; } if (verbose) { g_logger(G_LOG_LEVEL_MESSAGE, "Read valid line: %d/%d/%d", z, x, y); } while (z > max_zoom) { x >>= 1; y >>= 1; z--; } while (z < max_zoom) { x <<= 1; y <<= 1; z++; } num_read++; if (progress && (num_read % 100) == 0) { g_logger(G_LOG_LEVEL_INFO, "Read and expanded %i tiles from list.", num_read); } if (verbose) { g_logger(G_LOG_LEVEL_MESSAGE, "Starting loop on %d/%d/%d for zoom levels %d to %d", z, x, y, min_zoom, max_zoom); } for (; z >= min_zoom; z--, x >>= 1, y >>= 1) { char name[PATH_MAX]; // don't do anything if this tile was already requested. // renderd does keep a list internally to avoid enqueing the same tile // twice but in case it has already rendered the tile we don't want to // cause extra work. if (TILE_REQUESTED(z - excess_zoomlevels, x >> excess_zoomlevels, y >> excess_zoomlevels)) { if (verbose) { g_logger(G_LOG_LEVEL_MESSAGE, "Already requested metatile containing '%d/%d/%d', moving on to next input line", z, x, y); } break; } if (verbose) { g_logger(G_LOG_LEVEL_MESSAGE, "Processing: %d/%d/%d", z, x, y); } // mark tile as requested. (do this even if, below, the tile is not // actually requested due to not being present on disk, to avoid // unnecessary later stat'ing). SET_TILE_REQUESTED(z - excess_zoomlevels, x >> excess_zoomlevels, y >> excess_zoomlevels); // commented out - seems to cause problems in MT environment, // trying to write to already-closed file // check_load(); num_all++; s = store->tile_stat(store, mapname, "", x, y, z); store->tile_storage_id(store, mapname, "", x, y, z, name); if (s.size > 0) { // Tile exists // tile exists on disk; delete/touch/render it if (delete_from_passed && z >= delete_from) { if (progress) { g_logger(G_LOG_LEVEL_MESSAGE, "Deleting '%s'", name); } store->metatile_delete(store, mapname, x, y, z); num_unlink++; } else if (touch_from_passed && z >= touch_from) { if (progress) { g_logger(G_LOG_LEVEL_MESSAGE, "Touching '%s'", name); } store->metatile_expire(store, mapname, x, y, z); num_touch++; } else if (doRender) { if (progress) { g_logger(G_LOG_LEVEL_MESSAGE, "Rendering '%s'", name); } enqueue(mapname, x, y, z); num_render++; } } else { if (verbose) { g_logger(G_LOG_LEVEL_MESSAGE, "Skipping '%s' (metatile does not exist)", name); } num_ignore++; } } } if (doRender) { finish_workers(); } if (config_file_name_passed) { free((void *)config_file_name); } if (mapname_passed) { free((void *)mapname); } if (socketname_passed) { free((void *)socketname); } if (tile_dir_passed) { free((void *)tile_dir); } store->close_storage(store); free(store); for (int i = 0; i <= max_zoom - excess_zoomlevels; i++) { free(tile_requested[i]); } free(tile_requested); gettimeofday(&end, NULL); g_logger(G_LOG_LEVEL_MESSAGE, "Read and expanded %i tiles from list.", num_read); g_logger(G_LOG_LEVEL_MESSAGE, "Total for all tiles rendered"); g_logger(G_LOG_LEVEL_MESSAGE, "Metatiles rendered:"); display_rate(start, end, num_render); g_logger(G_LOG_LEVEL_MESSAGE, "Total tiles rendered:"); display_rate(start, end, num_render * METATILE * METATILE); g_logger(G_LOG_LEVEL_MESSAGE, "Total tiles in input: %d", num_read); g_logger(G_LOG_LEVEL_MESSAGE, "Total tiles expanded from input: %d", num_all); g_logger(G_LOG_LEVEL_MESSAGE, "Total metatiles deleted: %d", num_unlink); g_logger(G_LOG_LEVEL_MESSAGE, "Total metatiles touched: %d", num_touch); g_logger(G_LOG_LEVEL_MESSAGE, "Total tiles ignored (not on disk): %d", num_ignore); return 0; } #endif mod_tile-0.8.0/src/render_list.c000066400000000000000000000435571474064163400166070ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "g_logger.h" #include "protocol.h" #include "render_config.h" #include "render_submit_queue.h" #include "renderd_config.h" #include "store.h" #ifndef METATILE #warning("render_list not implemented for non-metatile mode. Feel free to submit fix") int main(int argc, char **argv) { fprintf(stderr, "render_list not implemented for non-metatile mode. Feel free to submit fix!\n"); return -1; } #else int lon2tilex(double lon, int z) { return (int)(floor((lon + 180.0) / 360.0 * pow(2.0, z))); } int lat2tiley(double lat, int z) { double latrad = lat * M_PI / 180.0; return (int)(floor((1.0 - log(tan(latrad) + (1.0 / cos(latrad))) / M_PI) / 2.0 * pow(2.0, z))); } void display_rate(struct timeval start, struct timeval end, int num) { int d_s, d_us; float sec; d_s = end.tv_sec - start.tv_sec; d_us = end.tv_usec - start.tv_usec; sec = d_s + d_us / 1000000.0; g_logger(G_LOG_LEVEL_MESSAGE, "\t%d in %.2f seconds (%.2f/s)", num, sec, num / sec); } int main(int argc, char **argv) { const char *config_file_name_default = RENDERD_CONFIG; const char *mapname_default = XMLCONFIG_DEFAULT; const char *socketname_default = RENDERD_SOCKET; const char *tile_dir_default = RENDERD_TILE_DIR; double max_lat_default = -1; double max_lon_default = -1; double min_lat_default = -1; double min_lon_default = -1; int max_load_default = MAX_LOAD_OLD; int max_x_default = -1; int max_y_default = -1; int max_zoom_default = MAX_ZOOM; int min_x_default = -1; int min_y_default = -1; int min_zoom_default = 0; int num_threads_default = 1; const char *config_file_name = config_file_name_default; const char *mapname = mapname_default; const char *socketname = socketname_default; const char *tile_dir = tile_dir_default; double max_lat = max_lat_default; double max_lon = max_lon_default; double min_lat = min_lat_default; double min_lon = min_lon_default; int max_load = max_load_default; int max_x = max_x_default; int max_y = max_y_default; int max_zoom = max_zoom_default; int min_x = min_x_default; int min_y = min_y_default; int min_zoom = min_zoom_default; int num_threads = num_threads_default; int config_file_name_passed = 0; int mapname_passed = 0; int socketname_passed = 0; int tile_dir_passed = 0; int max_lat_passed = 0; int max_lon_passed = 0; int min_lat_passed = 0; int min_lon_passed = 0; int max_load_passed = 0; int max_x_passed = 0; int max_y_passed = 0; int max_zoom_passed = 0; int min_x_passed = 0; int min_y_passed = 0; int min_zoom_passed = 0; int num_threads_passed = 0; int x, y, z; struct timeval start, end; int num_render = 0, num_all = 0; int all = 0; int force = 0; int verbose = 0; struct storage_backend *store; struct stat_info s; foreground = 1; while (1) { int option_index = 0; static struct option long_options[] = { {"all", no_argument, 0, 'a'}, {"config", required_argument, 0, 'c'}, {"force", no_argument, 0, 'f'}, {"map", required_argument, 0, 'm'}, {"max-lat", required_argument, 0, 'G'}, {"max-load", required_argument, 0, 'l'}, {"max-lon", required_argument, 0, 'W'}, {"max-x", required_argument, 0, 'X'}, {"max-y", required_argument, 0, 'Y'}, {"max-zoom", required_argument, 0, 'Z'}, {"min-lat", required_argument, 0, 'g'}, {"min-lon", required_argument, 0, 'w'}, {"min-x", required_argument, 0, 'x'}, {"min-y", required_argument, 0, 'y'}, {"min-zoom", required_argument, 0, 'z'}, {"num-threads", required_argument, 0, 'n'}, {"socket", required_argument, 0, 's'}, {"tile-dir", required_argument, 0, 't'}, {"verbose", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {0, 0, 0, 0} }; int c = getopt_long(argc, argv, "ac:fm:G:l:W:X:Y:Z:g:w:x:y:z:n:s:t:vhV", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'a': /* -a, --all */ all = 1; break; case 'c': /* -c, --config */ config_file_name = strndup(optarg, PATH_MAX); config_file_name_passed = 1; struct stat buffer; if (stat(config_file_name, &buffer) != 0) { g_logger(G_LOG_LEVEL_CRITICAL, "Config file '%s' does not exist, please specify a valid file", config_file_name); return 1; } break; case 'f': /* -f, --force */ force = 1; break; case 'm': /* -m, --map */ mapname = strndup(optarg, XMLCONFIG_MAX); mapname_passed = 1; break; case 'G': /* -G, --max-lat */ max_lat = min_max_double_opt(optarg, "maximum latitute", -85.0511, 85.0511); max_lat_passed = 1; break; case 'l': /* -l, --max-load */ max_load = min_max_int_opt(optarg, "maximum load", 0, -1); max_load_passed = 1; break; case 'W': /* -W, --max-lon */ max_lon = min_max_double_opt(optarg, "maximum longitude", -180, 180); max_lon_passed = 1; break; case 'X': /* -X, --max-x */ max_x = min_max_int_opt(optarg, "maximum X tile coordinate", 0, -1); max_x_passed = 1; break; case 'Y': /* -Y, --max-y */ max_y = min_max_int_opt(optarg, "maximum Y tile coordinate", 0, -1); max_y_passed = 1; break; case 'Z': /* -Z, --max-zoom */ max_zoom = min_max_int_opt(optarg, "maximum zoom", 0, MAX_ZOOM); max_zoom_passed = 1; break; case 'g': /* -g, --min-lat */ min_lat = min_max_double_opt(optarg, "minimum latitute", -85.0511, 85.0511); min_lat_passed = 1; break; case 'w': /* -w, --min-lon */ min_lon = min_max_double_opt(optarg, "minimum longitude", -180, 180); min_lon_passed = 1; break; case 'x': /* -x, --min-x */ min_x = min_max_int_opt(optarg, "minimum X tile coordinate", 0, -1); min_x_passed = 1; break; case 'y': /* -y, --min-y */ min_y = min_max_int_opt(optarg, "minimum Y tile coordinate", 0, -1); min_y_passed = 1; break; case 'z': /* -z, --min-zoom */ min_zoom = min_max_int_opt(optarg, "minimum zoom", 0, MAX_ZOOM); min_zoom_passed = 1; break; case 'n': /* -n, --num-threads */ num_threads = min_max_int_opt(optarg, "number of threads", 1, -1); num_threads_passed = 1; break; case 's': /* -s, --socket */ socketname = strndup(optarg, PATH_MAX); socketname_passed = 1; break; case 't': /* -t, --tile-dir */ tile_dir = strndup(optarg, PATH_MAX); tile_dir_passed = 1; break; case 'v': /* -v, --verbose */ verbose = 1; break; case 'h': /* -h, --help */ fprintf(stderr, "Usage: render_list [OPTION] ...\n"); fprintf(stderr, " -a, --all render all tiles in given zoom level range instead of reading from STDIN\n"); fprintf(stderr, " -c, --config=CONFIG specify the renderd config file (default is off)\n"); fprintf(stderr, " -f, --force render tiles even if they seem current\n"); fprintf(stderr, " -l, --max-load=LOAD sleep if load is this high (default is '%d')\n", max_load_default); fprintf(stderr, " -m, --map=MAP render tiles in this map (default is '%s')\n", mapname_default); fprintf(stderr, " -n, --num-threads=N the number of parallel request threads (default is '%i')\n", num_threads_default); fprintf(stderr, " -s, --socket=SOCKET|HOSTNAME:PORT unix domain socket name or hostname and port for contacting renderd (default is '%s')\n", socketname_default); fprintf(stderr, " -t, --tile-dir=TILE_DIR tile cache directory (default is '%s')\n", tile_dir_default); fprintf(stderr, " -Z, --max-zoom=ZOOM filter input to only render tiles less than or equal to this zoom level (default is '%d')\n", max_zoom_default); fprintf(stderr, " -z, --min-zoom=ZOOM filter input to only render tiles greater than or equal to this zoom level (default is '%d')\n", min_zoom_default); fprintf(stderr, "\n"); fprintf(stderr, " -h, --help display this help and exit\n"); fprintf(stderr, " -V, --version display the version number and exit\n"); fprintf(stderr, "\n"); fprintf(stderr, "If you are using --all, you can restrict the tile range by adding these options:\n"); fprintf(stderr, "(please note that tile coordinates must be positive integers and are not latitude and longitude values)\n"); fprintf(stderr, " -G, --max-lat=LATITUDE maximum latitude\n"); fprintf(stderr, " -g, --min-lat=LATITUDE minimum latitude\n"); fprintf(stderr, " -W, --max-lon=LONGITUDE maximum longitude\n"); fprintf(stderr, " -w, --min-lon=LONGITUDE minimum longitude\n"); fprintf(stderr, " -X, --max-x=X maximum X tile coordinate\n"); fprintf(stderr, " -x, --min-x=X minimum X tile coordinate\n"); fprintf(stderr, " -Y, --max-y=Y maximum Y tile coordinate\n"); fprintf(stderr, " -y, --min-y=Y minimum Y tile coordinate\n"); fprintf(stderr, "\n"); fprintf(stderr, "Without --all, send a list of tiles to be rendered from STDIN in the format:\n"); fprintf(stderr, " X Y Z\n"); fprintf(stderr, "e.g.\n"); fprintf(stderr, " 0 0 1\n"); fprintf(stderr, " 0 1 1\n"); fprintf(stderr, " 1 0 1\n"); fprintf(stderr, " 1 1 1\n"); fprintf(stderr, "The above would cause all 4 tiles at zoom 1 to be rendered\n"); return 0; case 'V': /* -V, --version */ fprintf(stdout, "%s\n", VERSION); return 0; default: g_logger(G_LOG_LEVEL_CRITICAL, "unhandled char '%c'", c); return 1; } } if (max_zoom < min_zoom) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified min zoom (%i) is larger than max zoom (%i).", min_zoom, max_zoom); return 1; } if (config_file_name_passed) { int map_section_num = -1; process_config_file(config_file_name, 0, G_LOG_LEVEL_DEBUG); for (int i = 0; i < XMLCONFIGS_MAX; ++i) { if (maps[i].xmlname && strcmp(maps[i].xmlname, mapname) == 0) { map_section_num = i; } } if (map_section_num < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "Map section '%s' does not exist in config file '%s'.", mapname, config_file_name); return 1; } if (!max_zoom_passed) { max_zoom = maps[map_section_num].max_zoom; max_zoom_passed = 1; } if (!min_zoom_passed) { min_zoom = maps[map_section_num].min_zoom; min_zoom_passed = 1; } if (!num_threads_passed) { num_threads = maps[map_section_num].num_threads; num_threads_passed = 1; } if (!socketname_passed) { socketname = strndup(config.socketname, PATH_MAX); socketname_passed = 1; } if (!tile_dir_passed) { tile_dir = strndup(maps[map_section_num].tile_dir, PATH_MAX); tile_dir_passed = 1; } } if (all) { if (min_lat_passed && min_lon_passed && max_lat_passed && max_lon_passed) { if (min_x_passed || min_y_passed || max_x_passed || max_y_passed) { g_logger(G_LOG_LEVEL_CRITICAL, "min-lat, min-lon, max-lat & max-lon cannot be used together with min-x, max-x, min-y, or max-y"); return 1; } if (max_lat < min_lat) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified min-lat (%f) is larger than max-lat (%f).", min_lat, max_lat); return 1; } if (max_lon < min_lon) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified min-lon (%f) is larger than max-lon (%f).", min_lon, max_lon); return 1; } } if (min_x_passed || min_y_passed || max_x_passed || max_y_passed) { if (min_zoom != max_zoom) { g_logger(G_LOG_LEVEL_CRITICAL, "min-zoom must be equal to max-zoom when using min-x, max-x, min-y, or max-y options"); return 1; } if (min_x_passed && max_x_passed && max_x < min_x) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified min-x (%i) is larger than max-x (%i).", min_x, max_x); return 1; } if (min_y_passed && max_y_passed && max_y < min_y) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified min-y (%i) is larger than max-y (%i).", min_y, max_y); return 1; } } if (!min_x_passed) { min_x = 0; } if (!min_y_passed) { min_y = 0; } int lz = (1 << min_zoom) - 1; if (min_zoom == max_zoom) { if (!max_x_passed) { max_x = lz; } if (!max_y_passed) { max_y = lz; } if (min_x > lz || min_y > lz || max_x > lz || max_y > lz) { g_logger(G_LOG_LEVEL_CRITICAL, "Invalid range, x and y values must be <= %d (2^zoom-1)", lz); return 1; } } } store = init_storage_backend(tile_dir); if (store == NULL) { g_logger(G_LOG_LEVEL_CRITICAL, "Failed to initialise storage backend %s", tile_dir); return 1; } g_logger(G_LOG_LEVEL_INFO, "Started render_list with the following options:"); if (config_file_name_passed) { g_logger(G_LOG_LEVEL_INFO, "\t--config = '%s' (user-specified)", config_file_name); } g_logger(G_LOG_LEVEL_INFO, "\t--map = '%s' (%s)", mapname, mapname_passed ? "user-specified" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--max-load = '%i' (%s)", max_load, max_load_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--max-zoom = '%i' (%s)", max_zoom, max_zoom_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--min-zoom = '%i' (%s)", min_zoom, min_zoom_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--num-threads = '%i' (%s)", num_threads, num_threads_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--socket = '%s' (%s)", socketname, socketname_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--tile-dir = '%s' (%s)", tile_dir, tile_dir_passed ? "user-specified/from config" : "default"); gettimeofday(&start, NULL); spawn_workers(num_threads, socketname, max_load); if (all) { g_logger(G_LOG_LEVEL_MESSAGE, "Rendering all tiles from zoom %d to zoom %d", min_zoom, max_zoom); for (z = min_zoom; z <= max_zoom; z++) { int current_max_x = max_x_passed ? max_x : (1 << z) - 1; int current_max_y = max_y_passed ? max_y : (1 << z) - 1; if (min_lat_passed && min_lon_passed && max_lat_passed && max_lon_passed) { int max_x_tmp = lon2tilex(max_lon, z); int max_y_tmp = lat2tiley(min_lat, z); int min_x_tmp = lon2tilex(min_lon, z); int min_y_tmp = lat2tiley(max_lat, z); current_max_x = max_x_tmp ? max_x_tmp - 1 : max_x_tmp; current_max_y = max_y_tmp; min_x = min_x_tmp; min_y = min_y_tmp; } g_logger(G_LOG_LEVEL_MESSAGE, "Rendering all tiles for zoom %i from (%i, %i) to (%i, %i)", z, min_x, min_y, current_max_x, current_max_y); for (x = min_x; x <= current_max_x; x += METATILE) { for (y = min_y; y <= current_max_y; y += METATILE) { if (!force) { s = store->tile_stat(store, mapname, "", x, y, z); } if (force || (s.size < 0) || (s.expired)) { enqueue(mapname, x, y, z); num_render++; } num_all++; } } } } else { while (!feof(stdin)) { int n = fscanf(stdin, "%d %d %d", &x, &y, &z); if (n != 3) { // Discard input line char tmp[1024]; const char *r = fgets(tmp, sizeof(tmp), stdin); if (!r) { continue; } if (verbose) { g_logger(G_LOG_LEVEL_WARNING, "bad line %d: %s", num_all, tmp); } continue; } if (verbose) { g_logger(G_LOG_LEVEL_MESSAGE, "got: x(%d) y(%d) z(%d)", x, y, z); } if (z < min_zoom || z > max_zoom) { g_logger(G_LOG_LEVEL_MESSAGE, "Ignoring tile, zoom %d outside valid range (%d..%d)", z, min_zoom, max_zoom); continue; } num_all++; if (!force) { s = store->tile_stat(store, mapname, "", x, y, z); } if (force || (s.size < 0) || (s.expired)) { // missing or old, render it enqueue(mapname, x, y, z); num_render++; // Attempts to adjust the stats for the QMAX tiles which are likely in the queue if (!(num_render % 10)) { gettimeofday(&end, NULL); g_logger(G_LOG_LEVEL_MESSAGE, "Metatiles rendered:"); display_rate(start, end, num_render); g_logger(G_LOG_LEVEL_MESSAGE, "Total tiles rendered:"); display_rate(start, end, num_render * METATILE * METATILE); g_logger(G_LOG_LEVEL_MESSAGE, "Total tiles handled:"); display_rate(start, end, num_all); } } else { if (verbose) { char name[PATH_MAX]; g_logger(G_LOG_LEVEL_MESSAGE, "Tile %s is clean, ignoring", store->tile_storage_id(store, mapname, "", x, y, z, name)); } } } } finish_workers(); if (config_file_name_passed) { free((void *)config_file_name); } if (mapname_passed) { free((void *)mapname); } if (socketname_passed) { free((void *)socketname); } if (tile_dir_passed) { free((void *)tile_dir); } store->close_storage(store); free(store); gettimeofday(&end, NULL); g_logger(G_LOG_LEVEL_MESSAGE, "Total for all tiles rendered"); g_logger(G_LOG_LEVEL_MESSAGE, "Metatiles rendered:"); display_rate(start, end, num_render); g_logger(G_LOG_LEVEL_MESSAGE, "Total tiles rendered:"); display_rate(start, end, num_render * METATILE * METATILE); g_logger(G_LOG_LEVEL_MESSAGE, "Total tiles handled:"); display_rate(start, end, num_all); print_statistics(); return 0; } #endif mod_tile-0.8.0/src/render_old.c000066400000000000000000000307141474064163400164010ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "g_logger.h" #include "protocol.h" #include "render_config.h" #include "render_submit_queue.h" #include "renderd_config.h" #include "store_file_utils.h" #include "sys_utils.h" #ifndef METATILE #warning("render_old not implemented for non-metatile mode. Feel free to submit fix") int main(int argc, char **argv) { fprintf(stderr, "render_old not implemented for non-metatile mode. Feel free to submit fix!\n"); return -1; } #else static int num_render = 0, num_all = 0; static int max_load; static time_t planet_timestamp = 0; static struct timeval start, end; void display_rate(struct timeval start, struct timeval end, int num) { int d_s, d_us; float sec; d_s = end.tv_sec - start.tv_sec; d_us = end.tv_usec - start.tv_usec; sec = d_s + d_us / 1000000.0; g_logger(G_LOG_LEVEL_MESSAGE, "\t%d in %.2f seconds (%.2f/s)", num, sec, num / sec); } static time_t get_planet_time(const char *tile_dir) { static time_t last_check; static time_t planet_time; time_t now = time(NULL); struct stat buf; char filename[PATH_MAX]; snprintf(filename, PATH_MAX - 1, "%s/%s", tile_dir, PLANET_TIMESTAMP); // Only check for updates periodically if (now < last_check + 300) { return planet_time; } last_check = now; if (stat(filename, &buf)) { g_logger(G_LOG_LEVEL_MESSAGE, "Planet timestamp file (%s) is missing", filename); // Make something up planet_time = now - 3 * 24 * 60 * 60; } else { if (buf.st_mtime != planet_time) { g_logger(G_LOG_LEVEL_MESSAGE, "Planet file updated at %s", strtok(ctime(&buf.st_mtime), "\n")); planet_time = buf.st_mtime; } } return planet_time; } static void check_load(void) { double avg = get_load_avg(); while (avg >= max_load) { g_logger(G_LOG_LEVEL_MESSAGE, "Load average %f, sleeping", avg); sleep(5); avg = get_load_avg(); } } static void descend(const char *tile_dir, const char *search, int verbose) { DIR *tiles = opendir(search); struct dirent *entry; char path[PATH_MAX]; char mapname[XMLCONFIG_MAX]; int x, y, z; if (!tiles) { if (verbose) { g_logger(G_LOG_LEVEL_MESSAGE, "%s: %s", strerror(errno), search); } return; } while ((entry = readdir(tiles))) { struct stat b; char *p; check_load(); if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) { continue; } snprintf(path, sizeof(path), "%s/%s", search, entry->d_name); if (stat(path, &b)) { continue; } if (S_ISDIR(b.st_mode)) { descend(tile_dir, path, verbose); continue; } p = strrchr(path, '.'); if (p && !strcmp(p, ".meta")) { if (planet_timestamp > b.st_mtime) { // request rendering of old tile path_to_xyz(tile_dir, path, mapname, &x, &y, &z); enqueue(mapname, x, y, z); num_render++; } num_all++; } } closedir(tiles); } void render_layer(const char *tile_dir, const char *mapname, int min_zoom, int max_zoom, int verbose) { for (int z = min_zoom; z <= max_zoom; z++) { if (verbose) { g_logger(G_LOG_LEVEL_MESSAGE, "Rendering zoom %d", z); } char search[PATH_MAX]; snprintf(search, PATH_MAX, "%s/%s/%d", tile_dir, mapname, z); descend(tile_dir, search, verbose); } } int main(int argc, char **argv) { const char *config_file_name_default = RENDERD_CONFIG; const char *mapname_default = XMLCONFIG_DEFAULT; const char *socketname_default = RENDERD_SOCKET; const char *tile_dir_default = RENDERD_TILE_DIR; int max_load_default = MAX_LOAD_OLD; int max_zoom_default = MAX_ZOOM; int min_zoom_default = 0; int num_threads_default = 1; const char *config_file_name = config_file_name_default; const char *mapname = mapname_default; const char *socketname = socketname_default; const char *tile_dir = tile_dir_default; max_load = max_load_default; int max_zoom = max_zoom_default; int min_zoom = min_zoom_default; int num_threads = num_threads_default; int config_file_name_passed = 0; int mapname_passed = 0; int socketname_passed = 0; int tile_dir_passed = 0; int max_load_passed = 0; int max_zoom_passed = 0; int min_zoom_passed = 0; int num_threads_passed = 0; int map_section_num = -1; int dd, mm, yy; int verbose = 0; struct tm tm; foreground = 1; while (1) { int option_index = 0; static struct option long_options[] = { {"config", required_argument, 0, 'c'}, {"map", required_argument, 0, 'm'}, {"max-load", required_argument, 0, 'l'}, {"max-zoom", required_argument, 0, 'Z'}, {"min-zoom", required_argument, 0, 'z'}, {"num-threads", required_argument, 0, 'n'}, {"socket", required_argument, 0, 's'}, {"tile-dir", required_argument, 0, 't'}, {"timestamp", required_argument, 0, 'T'}, {"verbose", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {0, 0, 0, 0} }; int c = getopt_long(argc, argv, "c:m:l:Z:z:n:s:t:T:vhV", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'c': /* -c, --config */ config_file_name = strndup(optarg, PATH_MAX); config_file_name_passed = 1; struct stat buffer; if (stat(config_file_name, &buffer) != 0) { g_logger(G_LOG_LEVEL_CRITICAL, "Config file '%s' does not exist, please specify a valid file", config_file_name); return 1; } break; case 'm': /* -m, --map */ mapname = strndup(optarg, XMLCONFIG_MAX); mapname_passed = 1; break; case 'l': /* -l, --max-load */ max_load = min_max_int_opt(optarg, "maximum load", 0, -1); max_load_passed = 1; break; case 'Z': /* -Z, --max-zoom */ max_zoom = min_max_int_opt(optarg, "maximum zoom", 0, MAX_ZOOM); max_zoom_passed = 1; break; case 'z': /* -z, --min-zoom */ min_zoom = min_max_int_opt(optarg, "minimum zoom", 0, MAX_ZOOM); min_zoom_passed = 1; break; case 'n': /* -n, --num-threads */ num_threads = min_max_int_opt(optarg, "number of threads", 1, -1); num_threads_passed = 1; break; case 's': /* -s, --socket */ socketname = strndup(optarg, PATH_MAX); socketname_passed = 1; break; case 't': /* -t, --tile-dir */ tile_dir = strndup(optarg, PATH_MAX); tile_dir_passed = 1; break; case 'T': if (sscanf(optarg, "%d/%d/%d", &dd, &mm, &yy) == 3) { if (yy > 100) { yy -= 1900; } if (yy < 70) { yy += 100; } memset(&tm, 0, sizeof(tm)); tm.tm_mday = dd; tm.tm_mon = mm - 1; tm.tm_year = yy; planet_timestamp = mktime(&tm); } else if (sscanf(optarg, "%d", &dd) == 1) { planet_timestamp = dd; } else { g_logger(G_LOG_LEVEL_CRITICAL, "Invalid planet timestamp, must be a unix timestamp or in the format dd/mm/yyyy"); return 1; } break; case 'v': /* -v, --verbose */ verbose = 1; break; case 'h': /* -h, --help */ fprintf(stderr, "Usage: render_old [OPTION] ...\n"); fprintf(stderr, "Search the rendered tiles and re-render tiles which are older then the last planet import\n"); fprintf(stderr, " -c, --config=CONFIG specify the renderd config file (default is '%s')\n", config_file_name_default); fprintf(stderr, " -l, --max-load=LOAD sleep if load is this high (default is '%d')\n", max_load_default); fprintf(stderr, " -m, --map=STYLE Instead of going through all styles of CONFIG, only use a specific map-style\n"); fprintf(stderr, " -n, --num-threads=N the number of parallel request threads (default is '%d')\n", num_threads_default); fprintf(stderr, " -s, --socket=SOCKET|HOSTNAME:PORT unix domain socket name or hostname and port for contacting renderd (default is '%s')\n", socketname_default); fprintf(stderr, " -t, --tile-dir=TILE_DIR tile cache directory (default is '%s')\n", tile_dir_default); fprintf(stderr, " -T, --timestamp=DD/MM/YY Overwrite the assumed data of the planet import\n"); fprintf(stderr, " -Z, --max-zoom=ZOOM filter input to only render tiles less than or equal to this zoom level (default is '%d')\n", max_zoom_default); fprintf(stderr, " -z, --min-zoom=ZOOM filter input to only render tiles greater than or equal to this zoom level (default is '%d')\n", min_zoom_default); fprintf(stderr, "\n"); fprintf(stderr, " -h, --help display this help and exit\n"); fprintf(stderr, " -V, --version display the version number and exit\n"); return 0; case 'V': /* -V, --version */ fprintf(stdout, "%s\n", VERSION); return 0; default: g_logger(G_LOG_LEVEL_CRITICAL, "unhandled char '%c'", c); return 1; } } if (max_zoom < min_zoom) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified min zoom (%i) is larger than max zoom (%i).", min_zoom, max_zoom); return 1; } process_config_file(config_file_name, 0, G_LOG_LEVEL_DEBUG); for (int i = 0; i < XMLCONFIGS_MAX; ++i) { if (mapname_passed && maps[i].xmlname && strcmp(maps[i].xmlname, mapname) == 0) { map_section_num = i; } } if (mapname_passed && map_section_num < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "Map section '%s' does not exist in config file '%s'.", mapname, config_file_name); return 1; } if (!socketname_passed) { socketname = strndup(config.socketname, PATH_MAX); socketname_passed = 1; } g_logger(G_LOG_LEVEL_INFO, "Started render_old with the following options:"); g_logger(G_LOG_LEVEL_INFO, "\t--config = '%s' (%s)", config_file_name, config_file_name_passed ? "user-specified" : "default"); if (mapname_passed) { g_logger(G_LOG_LEVEL_INFO, "\t--map = '%s' (user-specified)", mapname); } g_logger(G_LOG_LEVEL_INFO, "\t--max-load = '%i' (%s)", max_load, max_load_passed ? "user-specified/from config" : "default"); if (max_zoom_passed) { g_logger(G_LOG_LEVEL_INFO, "\t--max-zoom = '%i' (user-specified)", max_zoom); } if (min_zoom_passed) { g_logger(G_LOG_LEVEL_INFO, "\t--min-zoom = '%i' (user-specified)", min_zoom); } g_logger(G_LOG_LEVEL_INFO, "\t--num-threads = '%i' (%s)", num_threads, num_threads_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--socket = '%s' (%s)", socketname, socketname_passed ? "user-specified/from config" : "default"); if (tile_dir_passed) { g_logger(G_LOG_LEVEL_INFO, "\t--tile-dir = '%s' (user-specified)", tile_dir); } if (planet_timestamp == 0) { planet_timestamp = get_planet_time(tile_dir); } else { g_logger(G_LOG_LEVEL_MESSAGE, "Overwriting planet file update to %s", strtok(ctime(&planet_timestamp), "\n")); } gettimeofday(&start, NULL); spawn_workers(num_threads, socketname, max_load); for (int i = 0; i < XMLCONFIGS_MAX; ++i) { if (mapname_passed && maps[i].xmlname && strcmp(maps[i].xmlname, mapname) != 0) { continue; } if (maps[i].xmlname != NULL) { if (!max_zoom_passed) { max_zoom = maps[i].max_zoom; } if (!min_zoom_passed) { min_zoom = maps[i].min_zoom; } if (!tile_dir_passed) { tile_dir = strndup(maps[i].tile_dir, PATH_MAX); } if (verbose) { g_logger(G_LOG_LEVEL_MESSAGE, "Rendering map '%s' from zoom '%i' to zoom '%i'", maps[i].xmlname, min_zoom, max_zoom); } render_layer(tile_dir, maps[i].xmlname, min_zoom, max_zoom, verbose); } } finish_workers(); gettimeofday(&end, NULL); g_logger(G_LOG_LEVEL_MESSAGE, "Total for all tiles rendered"); g_logger(G_LOG_LEVEL_MESSAGE, "Metatiles rendered:"); display_rate(start, end, num_render); g_logger(G_LOG_LEVEL_MESSAGE, "Total tiles rendered:"); display_rate(start, end, num_render * METATILE * METATILE); g_logger(G_LOG_LEVEL_MESSAGE, "Total tiles handled:"); display_rate(start, end, num_all); return 0; } #endif mod_tile-0.8.0/src/render_speedtest.cpp000066400000000000000000000235171474064163400201660ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include #include #include #include "config.h" #include "g_logger.h" #include "render_config.h" #include "render_submit_queue.h" #include "renderd_config.h" #define DEG_TO_RAD (M_PI / 180) #define RAD_TO_DEG (180 / M_PI) #ifndef METATILE #warning("Speed test not implemented for non-metatile mode. Feel free to submit fix") int main(int argc, char **argv) { fprintf(stderr, "Speed test not implemented for non-metatile mode. Feel free to submit fix!\n"); return -1; } #else #if 1 static double boundx0 = -0.5; static double boundy0 = 51.25; static double boundx1 = 0.5; static double boundy1 = 51.75; #endif #if 0 // bbox = (-6.0, 50.0,3.0,58.0) static double boundx0 = -6.0; static double boundy0 = 50.0; static double boundx1 = 3.0; static double boundy1 = 58.0; #endif #if 0 // UK: 49.7,-7.6, 58.8, 3.2 static double boundx0 = -7.6; static double boundy0 = 49.7; static double boundx1 = 3.2; static double boundy1 = 58.8; #endif static double minmax(double a, double b, double c) { a = MAX(a, b); a = MIN(a, c); return a; } class GoogleProjection { double *Ac, *Bc, *Cc, *zc; public: GoogleProjection(int levels = 18) { Ac = new double[levels]; Bc = new double[levels]; Cc = new double[levels]; zc = new double[levels]; int d, c = 256; for (d = 0; d < levels; d++) { int e = c / 2; Bc[d] = c / 360.0; Cc[d] = c / (2 * M_PI); zc[d] = e; Ac[d] = c; c *= 2; } } void fromLLtoPixel(double &x, double &y, int zoom) { double d = zc[zoom]; double f = minmax(sin(DEG_TO_RAD * y), -0.9999, 0.9999); x = round(d + x * Bc[zoom]); y = round(d + 0.5 * log((1 + f) / (1 - f)) * -Cc[zoom]); } void fromPixelToLL(double &x, double &y, int zoom) { double e = zc[zoom]; double g = (y - e) / -Cc[zoom]; x = (x - e) / Bc[zoom]; y = RAD_TO_DEG * (2 * atan(exp(g)) - 0.5 * M_PI); } }; void display_rate(struct timeval start, struct timeval end, int num) { int d_s, d_us; float sec; d_s = end.tv_sec - start.tv_sec; d_us = end.tv_usec - start.tv_usec; sec = d_s + d_us / 1000000.0; g_logger(G_LOG_LEVEL_MESSAGE, "\t%d in %.2f seconds (%.2f/s)", num, sec, num / sec); } int main(int argc, char **argv) { const char *config_file_name_default = RENDERD_CONFIG; const char *mapname_default = XMLCONFIG_DEFAULT; const char *socketname_default = RENDERD_SOCKET; int max_zoom_default = MAX_ZOOM; int min_zoom_default = 0; int num_threads_default = 1; const char *config_file_name = config_file_name_default; const char *mapname = mapname_default; const char *socketname = socketname_default; int max_zoom = max_zoom_default; int min_zoom = min_zoom_default; int num_threads = num_threads_default; int config_file_name_passed = 0; int mapname_passed = 0; int socketname_passed = 0; int max_zoom_passed = 0; int min_zoom_passed = 0; int num_threads_passed = 0; int z; struct timeval start, end; struct timeval start_all, end_all; int num_all = 0; foreground = 1; while (1) { int option_index = 0; static struct option long_options[] = { {"config", required_argument, 0, 'c'}, {"map", required_argument, 0, 'm'}, {"max-zoom", required_argument, 0, 'Z'}, {"min-zoom", required_argument, 0, 'z'}, {"num-threads", required_argument, 0, 'n'}, {"socket", required_argument, 0, 's'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {0, 0, 0, 0} }; int c = getopt_long(argc, argv, "c:m:Z:z:n:s:hV", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'c': /* -c, --config */ config_file_name = strndup(optarg, PATH_MAX); config_file_name_passed = 1; struct stat buffer; if (stat(config_file_name, &buffer) != 0) { g_logger(G_LOG_LEVEL_CRITICAL, "Config file '%s' does not exist, please specify a valid file", config_file_name); return 1; } break; case 'm': /* -m, --map */ mapname = strndup(optarg, XMLCONFIG_MAX); mapname_passed = 1; break; case 'Z': /* -Z, --max-zoom */ max_zoom = min_max_int_opt(optarg, "maximum zoom", 0, MAX_ZOOM); max_zoom_passed = 1; break; case 'z': /* -z, --min-zoom */ min_zoom = min_max_int_opt(optarg, "minimum zoom", 0, MAX_ZOOM); min_zoom_passed = 1; break; case 'n': /* -n, --num-threads */ num_threads = min_max_int_opt(optarg, "number of threads", 1, -1); num_threads_passed = 1; break; case 's': /* -s, --socket */ socketname = strndup(optarg, PATH_MAX); socketname_passed = 1; break; case 'h': /* -h, --help */ fprintf(stderr, "Usage: render_speedtest [OPTION] ...\n"); fprintf(stderr, " -c, --config=CONFIG specify the renderd config file (default is off)\n"); fprintf(stderr, " -m, --map=MAP render tiles in this map (default is '%s')\n", mapname_default); fprintf(stderr, " -n, --num-threads=N the number of parallel request threads (default is '%d')\n", num_threads_default); fprintf(stderr, " -s, --socket=SOCKET|HOSTNAME:PORT unix domain socket name or hostname and port for contacting renderd (default is '%s')\n", socketname_default); fprintf(stderr, " -Z, --max-zoom=ZOOM only render tiles less than or equal to this zoom level (default is '%d')\n", max_zoom_default); fprintf(stderr, " -z, --min-zoom=ZOOM only render tiles greater than or equal to this zoom level (default is '%d')\n", min_zoom_default); fprintf(stderr, "\n"); fprintf(stderr, " -h, --help display this help and exit\n"); fprintf(stderr, " -V, --version display the version number and exit\n"); return 0; case 'V': /* -V, --version */ fprintf(stdout, "%s\n", VERSION); return 0; default: g_logger(G_LOG_LEVEL_CRITICAL, "unhandled char '%c'", c); return 1; } } if (max_zoom < min_zoom) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified min zoom (%i) is larger than max zoom (%i).", min_zoom, max_zoom); return 1; } if (config_file_name_passed) { int map_section_num = -1; process_config_file(config_file_name, 0, G_LOG_LEVEL_DEBUG); for (int i = 0; i < XMLCONFIGS_MAX; ++i) { if (maps[i].xmlname && strcmp(maps[i].xmlname, mapname) == 0) { map_section_num = i; } } if (map_section_num < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "Map section '%s' does not exist in config file '%s'.", mapname, config_file_name); return 1; } if (!max_zoom_passed) { max_zoom = maps[map_section_num].max_zoom; max_zoom_passed = 1; } if (!min_zoom_passed) { min_zoom = maps[map_section_num].min_zoom; min_zoom_passed = 1; } if (!num_threads_passed) { num_threads = maps[map_section_num].num_threads; num_threads_passed = 1; } if (!socketname_passed) { socketname = strndup(config.socketname, PATH_MAX); socketname_passed = 1; } } g_logger(G_LOG_LEVEL_INFO, "Started render_speedtest with the following options:"); if (config_file_name_passed) { g_logger(G_LOG_LEVEL_INFO, "\t--config = '%s' (user-specified)", config_file_name); } g_logger(G_LOG_LEVEL_INFO, "\t--map = '%s' (%s)", mapname, mapname_passed ? "user-specified" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--max-zoom = '%i' (%s)", max_zoom, max_zoom_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--min-zoom = '%i' (%s)", min_zoom, min_zoom_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--num-threads = '%i' (%s)", num_threads, num_threads_passed ? "user-specified/from config" : "default"); g_logger(G_LOG_LEVEL_INFO, "\t--socket = '%s' (%s)", socketname, socketname_passed ? "user-specified/from config" : "default"); static GoogleProjection gprj(max_zoom + 1); spawn_workers(num_threads, socketname, 1000); // Render something to counter act the startup costs // of obtaining the Postgis table extents g_logger(G_LOG_LEVEL_MESSAGE, "Initial startup costs"); gettimeofday(&start, NULL); enqueue(mapname, 0, 0, 0); gettimeofday(&end, NULL); display_rate(start, end, 1); gettimeofday(&start_all, NULL); for (z = min_zoom; z <= max_zoom; z++) { double px0 = boundx0; double py0 = boundy1; double px1 = boundx1; double py1 = boundy0; gprj.fromLLtoPixel(px0, py0, z); gprj.fromLLtoPixel(px1, py1, z); int x, xmin, xmax; xmin = (int)(px0 / 256.0); xmax = (int)(px1 / 256.0); int y, ymin, ymax; ymin = (int)(py0 / 256.0); ymax = (int)(py1 / 256.0); int num = (xmax - xmin + 1) * (ymax - ymin + 1); g_logger(G_LOG_LEVEL_MESSAGE, "Zoom(%d) Now rendering %d tiles", z, num); num_all += num; gettimeofday(&start, NULL); for (x = xmin; x <= xmax; x++) { for (y = ymin; y <= ymax; y++) { enqueue(mapname, x, y, z); } } wait_for_empty_queue(); gettimeofday(&end, NULL); display_rate(start, end, num); } finish_workers(); gettimeofday(&end_all, NULL); g_logger(G_LOG_LEVEL_MESSAGE, "Total for all tiles rendered"); display_rate(start_all, end_all, num_all); free_map_sections(maps); free_renderd_sections(config_slaves); return 0; } #endif mod_tile-0.8.0/src/render_submit_queue.c000066400000000000000000000242361474064163400203340ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "g_logger.h" #include "protocol.h" #include "protocol_helper.h" #include "render_config.h" #include "render_submit_queue.h" #include "sys_utils.h" static pthread_mutex_t qLock; static pthread_mutex_t qStatsLock; static pthread_cond_t qCondNotEmpty; static pthread_cond_t qCondNotFull; static int maxLoad = 0; static unsigned int qMaxLen; static unsigned int qLen; struct qItem { char *mapname; int x, y, z; struct qItem *next; }; struct speed_stat { time_t time_min; time_t time_max; time_t time_total; int noRendered; }; struct speed_stats { struct speed_stat stat[MAX_ZOOM + 1]; }; struct speed_stats performance_stats; static struct qItem *qHead, *qTail; static int no_workers; static pthread_t *workers; static int work_complete; static void check_load(void) { double avg = get_load_avg(); while (avg >= maxLoad) { int seconds = 5; g_logger(G_LOG_LEVEL_DEBUG, "Load average %d, sleeping %is", avg, seconds); sleep(seconds); avg = get_load_avg(); } } static int process(struct protocol *cmd, int fd) { struct timeval tim; time_t t1; time_t t2; int ret = 0; struct protocol rsp; gettimeofday(&tim, NULL); t1 = tim.tv_sec * 1000 + (tim.tv_usec / 1000); g_logger(G_LOG_LEVEL_DEBUG, "Sending request"); if (send_cmd(cmd, fd) < 1) { g_logger(G_LOG_LEVEL_ERROR, "send error: %s", strerror(errno)); }; bzero(&rsp, sizeof(rsp)); g_logger(G_LOG_LEVEL_DEBUG, "Waiting for response"); ret = recv_cmd(&rsp, fd, 1); if (ret < 1) { return 0; } g_logger(G_LOG_LEVEL_DEBUG, "Got response %i", rsp.cmd); if (rsp.cmd != cmdDone) { int seconds = 1; g_logger(G_LOG_LEVEL_DEBUG, "Rendering not done with command %i, sleeping %is", rsp.cmd, seconds); sleep(seconds); } else { gettimeofday(&tim, NULL); t2 = tim.tv_sec * 1000 + (tim.tv_usec / 1000); pthread_mutex_lock(&qStatsLock); t1 = t2 - t1; performance_stats.stat[cmd->z].noRendered++; performance_stats.stat[cmd->z].time_total += t1; if ((performance_stats.stat[cmd->z].time_min > t1) || (performance_stats.stat[cmd->z].time_min == 0)) { performance_stats.stat[cmd->z].time_min = t1; } if (performance_stats.stat[cmd->z].time_max < t1) { performance_stats.stat[cmd->z].time_max = t1; } pthread_mutex_unlock(&qStatsLock); } if (!ret) { g_logger(G_LOG_LEVEL_ERROR, "Socket send error: %s", strerror(errno)); } return ret; } static struct protocol *fetch(void) { pthread_mutex_lock(&qLock); while (qLen == 0) { if (work_complete) { pthread_mutex_unlock(&qLock); return NULL; } pthread_cond_wait(&qCondNotEmpty, &qLock); } // Fetch item from queue if (!qHead) { g_logger(G_LOG_LEVEL_CRITICAL, "Queue failure, null qHead with %d items in list", qLen); exit(1); } struct qItem *e = qHead; if (--qLen == 0) { qHead = NULL; qTail = NULL; } else { qHead = qHead->next; } pthread_cond_signal(&qCondNotFull); pthread_mutex_unlock(&qLock); struct protocol *cmd = malloc(sizeof(struct protocol)); cmd->ver = 2; cmd->cmd = cmdRenderBulk; cmd->z = e->z; cmd->x = e->x; cmd->y = e->y; strncpy(cmd->xmlname, e->mapname, XMLCONFIG_MAX - 1); free(e->mapname); free(e); return cmd; } void enqueue(const char *xmlname, int x, int y, int z) { // Add this path in the local render queue struct qItem *e = malloc(sizeof(struct qItem)); e->mapname = strdup(xmlname); e->x = x; e->y = y; e->z = z; e->next = NULL; if (!e->mapname) { g_logger(G_LOG_LEVEL_CRITICAL, "Malloc failure"); exit(1); } pthread_mutex_lock(&qLock); while (qLen == qMaxLen) { int ret = pthread_cond_wait(&qCondNotFull, &qLock); if (ret != 0) { g_logger(G_LOG_LEVEL_WARNING, "pthread_cond_wait(qCondNotFull): %s", strerror(ret)); } } // Append item to end of queue if (qTail) { qTail->next = e; } else { qHead = e; } qTail = e; qLen++; pthread_cond_signal(&qCondNotEmpty); pthread_mutex_unlock(&qLock); } int make_connection(const char *spath) { int fd; if (spath[0] == '/') { // Create a Unix socket struct sockaddr_un addr; fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "failed to create unix socket"); exit(2); } bzero(&addr, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, spath, sizeof(addr.sun_path) - 1); if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(fd); return -1; } } else { // Create a network socket const char *d = strchr(spath, ':'); char *hostname; u_int16_t port = RENDERD_PORT; char port_s[6]; size_t spath_len = strlen(spath); size_t hostname_len = d ? d - spath : spath_len; if (!hostname_len) { hostname = strdup(RENDERD_HOST); } else { hostname = malloc(hostname_len + sizeof('\0')); assert(hostname != NULL); strncpy(hostname, spath, hostname_len); } if (d) { port = atoi(d + 1); if (!port) { port = RENDERD_PORT; } } snprintf(port_s, sizeof(port_s), "%u", port); g_logger(G_LOG_LEVEL_DEBUG, "Connecting to %s, port %u/tcp", hostname, port); struct protoent *protocol = getprotobyname("tcp"); if (!protocol) { g_logger(G_LOG_LEVEL_CRITICAL, "cannot find TCP protocol number"); exit(2); } struct addrinfo hints; struct addrinfo *result; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = 0; hints.ai_protocol = protocol->p_proto; hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL; int ai = getaddrinfo(hostname, port_s, &hints, &result); if (ai != 0) { g_logger(G_LOG_LEVEL_CRITICAL, "cannot resolve hostname %s", hostname); exit(2); } struct addrinfo *rp; for (rp = result; rp != NULL; rp = rp->ai_next) { fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (fd == -1) { continue; } char resolved_addr[NI_MAXHOST]; char resolved_port[NI_MAXSERV]; int name_info = getnameinfo(rp->ai_addr, rp->ai_addrlen, resolved_addr, sizeof(resolved_addr), resolved_port, sizeof(resolved_port), NI_NUMERICHOST | NI_NUMERICSERV); if (name_info != 0) { g_logger(G_LOG_LEVEL_CRITICAL, "cannot retrieve name info: %d", name_info); exit(2); } g_logger(G_LOG_LEVEL_DEBUG, "Trying %s:%s", resolved_addr, resolved_port); if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) { g_logger(G_LOG_LEVEL_DEBUG, "Connected to %s:%s", resolved_addr, resolved_port); break; } } freeaddrinfo(result); if (rp == NULL) { g_logger(G_LOG_LEVEL_CRITICAL, "cannot connect to any address for %s", hostname); exit(2); } } return fd; } void *thread_main(void *arg) { const char *spath = (const char *)arg; int fd = make_connection(spath); if (fd < 0) { g_logger(G_LOG_LEVEL_ERROR, "connect failed for: %s", spath); return NULL; } while (1) { struct protocol *cmd; check_load(); if (!(cmd = fetch())) { break; } while (process(cmd, fd) < 1) { g_logger(G_LOG_LEVEL_ERROR, "connection to renderd lost"); close(fd); fd = -1; while (fd < 0) { int seconds = 30; g_logger(G_LOG_LEVEL_WARNING, "sleeping for %i seconds", seconds); sleep(30); g_logger(G_LOG_LEVEL_WARNING, "attempting to reconnect"); fd = make_connection(spath); } } free(cmd); } close(fd); return NULL; } void spawn_workers(int num, const char *spath, int max_load) { int i; no_workers = num; maxLoad = max_load; // Setup request queue pthread_mutex_init(&qLock, NULL); pthread_mutex_init(&qStatsLock, NULL); pthread_cond_init(&qCondNotEmpty, NULL); pthread_cond_init(&qCondNotFull, NULL); qMaxLen = no_workers; g_logger(G_LOG_LEVEL_MESSAGE, "Starting %d rendering threads", no_workers); workers = calloc(sizeof(pthread_t), no_workers); if (!workers) { g_logger(G_LOG_LEVEL_CRITICAL, "Error allocating worker memory: %s", strerror(errno)); exit(1); } for (i = 0; i < no_workers; i++) { if (pthread_create(&workers[i], NULL, thread_main, (void *)spath)) { g_logger(G_LOG_LEVEL_CRITICAL, "Thread creation failed: %s", strerror(errno)); exit(1); } } } void print_statistics(void) { int i; printf("*****************************************************\n"); for (i = 0; i <= MAX_ZOOM; i++) { if (performance_stats.stat[i].noRendered == 0) { continue; } printf("Zoom %02i: min: %4.1f avg: %4.1f max: %4.1f over a total of %8.1fs in %i requests\n", i, performance_stats.stat[i].time_min / 1000.0, (performance_stats.stat[i].time_total / (float)performance_stats.stat[i].noRendered) / 1000.0, performance_stats.stat[i].time_max / 1000.0, performance_stats.stat[i].time_total / 1000.0, performance_stats.stat[i].noRendered); } printf("*****************************************************\n"); printf("*****************************************************\n"); } void wait_for_empty_queue() { pthread_mutex_lock(&qLock); while (qLen > 0) { pthread_cond_wait(&qCondNotFull, &qLock); } pthread_mutex_unlock(&qLock); } void finish_workers(void) { g_logger(G_LOG_LEVEL_MESSAGE, "Waiting for rendering threads to finish"); pthread_mutex_lock(&qLock); work_complete = 1; pthread_mutex_unlock(&qLock); pthread_cond_broadcast(&qCondNotEmpty); for (int i = 0; i < no_workers; i++) { pthread_join(workers[i], NULL); } free(workers); workers = NULL; } mod_tile-0.8.0/src/renderd.c000066400000000000000000000633061474064163400157120ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "g_logger.h" #include "gen_tile.h" #include "protocol.h" #include "protocol_helper.h" #include "render_config.h" #include "renderd.h" #include "renderd_config.h" #include "request_queue.h" #define PFD_LISTEN 0 #define PFD_EXIT_PIPE 1 #define PFD_SPECIAL_COUNT 2 #ifndef MAIN_ALREADY_DEFINED static pthread_t *render_threads; static pthread_t *slave_threads; static struct sigaction sigPipeAction, sigExitAction; static pthread_t stats_thread; #endif static int exit_pipe_fd; struct request_queue * render_request_queue; static const char *cmdStr(enum protoCmd c) { switch (c) { case cmdIgnore: return "Ignore"; case cmdRender: return "Render"; case cmdRenderPrio: return "RenderPrio"; case cmdRenderLow: return "RenderLow"; case cmdRenderBulk: return "RenderBulk"; case cmdDirty: return "Dirty"; case cmdDone: return "Done"; case cmdNotDone: return "NotDone"; default: return "Unknown"; } } void send_response(struct item *item, enum protoCmd rsp, int render_time) { request_queue_remove_request(render_request_queue, item, render_time); while (item) { struct item *prev; struct protocol *req = &item->req; if ((item->fd != FD_INVALID) && ((req->cmd == cmdRender) || (req->cmd == cmdRenderPrio) || (req->cmd == cmdRenderLow) || (req->cmd == cmdRenderBulk))) { req->cmd = rsp; g_logger(G_LOG_LEVEL_DEBUG, "Sending message %s to %d", cmdStr(rsp), item->fd); send_cmd(req, item->fd); } prev = item; item = item->duplicates; free(prev); } } enum protoCmd rx_request(struct protocol *req, int fd) { struct item *item; // Upgrade version 1 and 2 to version 3 if (req->ver == 1) { strcpy(req->xmlname, "default"); } if (req->ver < 3) { strcpy(req->mimetype, "image/png"); strcpy(req->options, ""); } else if (req->ver != 3) { g_logger(G_LOG_LEVEL_ERROR, "Bad protocol version %d", req->ver); return cmdNotDone; } g_logger(G_LOG_LEVEL_DEBUG, "Got command %s fd(%d) xml(%s), z(%d), x(%d), y(%d), mime(%s), options(%s)", cmdStr(req->cmd), fd, req->xmlname, req->z, req->x, req->y, req->mimetype, req->options); if ((req->cmd != cmdRender) && (req->cmd != cmdRenderPrio) && (req->cmd != cmdRenderLow) && (req->cmd != cmdDirty) && (req->cmd != cmdRenderBulk)) { g_logger(G_LOG_LEVEL_WARNING, "Ignoring invalid command %s fd(%d) xml(%s), z(%d), x(%d), y(%d)", cmdStr(req->cmd), fd, req->xmlname, req->z, req->x, req->y); return cmdNotDone; } item = (struct item *)malloc(sizeof(*item)); if (!item) { g_logger(G_LOG_LEVEL_ERROR, "malloc failed"); return cmdNotDone; } item->req = *req; item->duplicates = NULL; item->fd = (req->cmd == cmdDirty) ? FD_INVALID : fd; #ifdef METATILE /* Round down request co-ordinates to the neareast N (should be a power of 2) * Note: request path is no longer consistent but this will be recalculated * when the metatile is being rendered. */ item->mx = item->req.x & ~(METATILE - 1); item->my = item->req.y & ~(METATILE - 1); #else item->mx = item->req.x; item->my = item->req.y; #endif return request_queue_add_request(render_request_queue, item); } void request_exit(void) { // Any write to the exit pipe will trigger a graceful exit char c = 0; g_logger(G_LOG_LEVEL_INFO, "Sending exit request"); if (write(exit_pipe_fd, &c, sizeof(c)) < 0) { g_logger(G_LOG_LEVEL_ERROR, "Failed to write to the exit pipe: %s", strerror(errno)); } } void process_loop(int listen_fd) { int num_cslots = 0; int num_conns = 0; int pipefds[2]; int exit_pipe_read; struct pollfd pfd[MAX_CONNECTIONS + 2]; bzero(pfd, sizeof(pfd)); // A pipe is used to allow the render threads to request an exit by the main process if (pipe(pipefds)) { g_logger(G_LOG_LEVEL_ERROR, "Failed to create pipe"); return; } exit_pipe_fd = pipefds[1]; exit_pipe_read = pipefds[0]; pfd[PFD_LISTEN].fd = listen_fd; pfd[PFD_LISTEN].events = POLLIN; pfd[PFD_EXIT_PIPE].fd = exit_pipe_read; pfd[PFD_EXIT_PIPE].events = POLLIN; while (1) { struct sockaddr_un in_addr; socklen_t in_addrlen = sizeof(in_addr); int incoming, num, i; // timeout -1 means infinite timeout, // a value of 0 would return immediately num = poll(pfd, num_cslots + PFD_SPECIAL_COUNT, -1); if (num == -1) { g_logger(G_LOG_LEVEL_DEBUG, "poll(): %s", strerror(errno)); } else if (num) { if (pfd[PFD_EXIT_PIPE].revents & POLLIN) { g_logger(G_LOG_LEVEL_INFO, "Received exit request, exiting process_loop"); break; } g_logger(G_LOG_LEVEL_DEBUG, "Data is available now on %d fds", num); if (pfd[PFD_LISTEN].revents & POLLIN) { incoming = accept(listen_fd, (struct sockaddr *) &in_addr, &in_addrlen); if (incoming < 0) { g_logger(G_LOG_LEVEL_ERROR, "accept(): %s", strerror(errno)); } else { int add = 0; // Search for unused slot for (i = 0; i < num_cslots; i++) { if (pfd[i + PFD_SPECIAL_COUNT].fd < 0) { add = 1; break; } } // No unused slot found, add at end if space available if (!add) { if (num_cslots == MAX_CONNECTIONS) { g_logger(G_LOG_LEVEL_WARNING, "Connection limit(%d) reached. Dropping connection", MAX_CONNECTIONS); close(incoming); } else { i = num_cslots; add = 1; num_cslots++; } } if (add) { pfd[i + PFD_SPECIAL_COUNT].fd = incoming; pfd[i + PFD_SPECIAL_COUNT].events = POLLIN; num_conns ++; g_logger(G_LOG_LEVEL_DEBUG, "Got incoming connection, fd %d, number %d, total conns %d, total slots %d", incoming, i, num_conns, num_cslots); } } } for (i = 0; num && (i < num_cslots); i++) { int fd = pfd[i + PFD_SPECIAL_COUNT].fd; if (fd >= 0 && pfd[i + PFD_SPECIAL_COUNT].revents & POLLIN) { struct protocol cmd; int ret = 0; memset(&cmd, 0, sizeof(cmd)); // TODO: to get highest performance we should loop here until we get EAGAIN ret = recv_cmd(&cmd, fd, 0); if (ret < 1) { num_conns--; g_logger(G_LOG_LEVEL_DEBUG, "Connection %d, fd %d closed, now %d left, total slots %d", i, fd, num_conns, num_cslots); request_queue_clear_requests_by_fd(render_request_queue, fd); close(fd); pfd[i + PFD_SPECIAL_COUNT].fd = -1; } else { enum protoCmd rsp = rx_request(&cmd, fd); if (rsp == cmdNotDone) { cmd.cmd = rsp; g_logger(G_LOG_LEVEL_DEBUG, "Sending NotDone response(%d)", rsp); ret = send_cmd(&cmd, fd); } } } } } else { g_logger(G_LOG_LEVEL_ERROR, "Poll timeout"); } } } /** * Periodically write out current stats to a stats file. This information * can then be used to monitor performance of renderd e.g. with a munin plugin */ void *stats_writeout_thread(void * arg) { stats_struct lStats; int dirtQueueLength; int reqQueueLength; int reqPrioQueueLength; int reqLowQueueLength; int reqBulkQueueLength; int i; int noFailedAttempts = 0; char tmpName[PATH_MAX]; snprintf(tmpName, sizeof(tmpName), "%s.tmp", config.stats_filename); g_logger(G_LOG_LEVEL_DEBUG, "Starting stats writeout thread: %lu", (unsigned long) pthread_self()); while (1) { request_queue_copy_stats(render_request_queue, &lStats); reqPrioQueueLength = request_queue_no_requests_queued(render_request_queue, cmdRenderPrio); reqQueueLength = request_queue_no_requests_queued(render_request_queue, cmdRender); reqLowQueueLength = request_queue_no_requests_queued(render_request_queue, cmdRenderLow); dirtQueueLength = request_queue_no_requests_queued(render_request_queue, cmdDirty); reqBulkQueueLength = request_queue_no_requests_queued(render_request_queue, cmdRenderBulk); FILE * statfile = fopen(tmpName, "w"); if (statfile == NULL) { g_logger(G_LOG_LEVEL_WARNING, "Failed to open stats file: %i", errno); noFailedAttempts++; if (noFailedAttempts > 3) { g_logger(G_LOG_LEVEL_ERROR, "Failed repeatedly to write stats, giving up"); break; } continue; } else { fprintf(statfile, "ReqQueueLength: %i\n", reqQueueLength); fprintf(statfile, "ReqPrioQueueLength: %i\n", reqPrioQueueLength); fprintf(statfile, "ReqLowQueueLength: %i\n", reqLowQueueLength); fprintf(statfile, "ReqBulkQueueLength: %i\n", reqBulkQueueLength); fprintf(statfile, "DirtQueueLength: %i\n", dirtQueueLength); fprintf(statfile, "DropedRequest: %li\n", lStats.noReqDroped); fprintf(statfile, "ReqRendered: %li\n", lStats.noReqRender); fprintf(statfile, "TimeRendered: %li\n", lStats.timeReqRender); fprintf(statfile, "ReqPrioRendered: %li\n", lStats.noReqPrioRender); fprintf(statfile, "TimePrioRendered: %li\n", lStats.timeReqPrioRender); fprintf(statfile, "ReqLowRendered: %li\n", lStats.noReqLowRender); fprintf(statfile, "TimeLowRendered: %li\n", lStats.timeReqLowRender); fprintf(statfile, "ReqBulkRendered: %li\n", lStats.noReqBulkRender); fprintf(statfile, "TimeBulkRendered: %li\n", lStats.timeReqBulkRender); fprintf(statfile, "DirtyRendered: %li\n", lStats.noDirtyRender); fprintf(statfile, "TimeDirtyRendered: %li\n", lStats.timeReqDirty); for (i = 0; i <= MAX_ZOOM; i++) { fprintf(statfile, "ZoomRendered%02i: %li\n", i, lStats.noZoomRender[i]); } for (i = 0; i <= MAX_ZOOM; i++) { fprintf(statfile, "TimeRenderedZoom%02i: %li\n", i, lStats.timeZoomRender[i]); } fclose(statfile); if (rename(tmpName, config.stats_filename)) { g_logger(G_LOG_LEVEL_WARNING, "Failed to overwrite stats file: %i", errno); noFailedAttempts++; if (noFailedAttempts > 6) { g_logger(G_LOG_LEVEL_ERROR, "Failed repeatedly to overwrite stats, giving up"); break; } continue; } } sleep(10); } return NULL; } int client_socket_init(renderd_config * sConfig) { int fd, s; struct sockaddr_un * addrU; struct addrinfo hints; struct addrinfo *result, *rp; char portnum[16]; char ipstring[INET6_ADDRSTRLEN]; if (sConfig->ipport > 0) { g_logger(G_LOG_LEVEL_INFO, "Initialising TCP/IP client socket to %s:%i", sConfig->iphostname, sConfig->ipport); memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_STREAM; /* TCP socket */ hints.ai_flags = 0; hints.ai_protocol = 0; /* Any protocol */ hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL; snprintf(portnum, 16, "%i", sConfig->ipport); s = getaddrinfo(sConfig->iphostname, portnum, &hints, &result); if (s != 0) { g_logger(G_LOG_LEVEL_INFO, "failed to resolve hostname of rendering slave"); return FD_INVALID; } /* getaddrinfo() returns a list of address structures. Try each address until we successfully connect. */ for (rp = result; rp != NULL; rp = rp->ai_next) { switch (rp->ai_family) { case AF_INET: inet_ntop(AF_INET, &(((struct sockaddr_in *)rp->ai_addr)->sin_addr), ipstring, rp->ai_addrlen); break; case AF_INET6: inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr), ipstring, rp->ai_addrlen); break; default: snprintf(ipstring, sizeof(ipstring), "address family %d", rp->ai_family); break; } g_logger(G_LOG_LEVEL_DEBUG, "Connecting TCP socket to rendering daemon at %s", ipstring); fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (fd < 0) { continue; } if (connect(fd, rp->ai_addr, rp->ai_addrlen) != 0) { g_logger(G_LOG_LEVEL_WARNING, "failed to connect to rendering daemon (%s), trying next ip", ipstring); close(fd); fd = -1; continue; } else { break; } } freeaddrinfo(result); if (fd < 0) { g_logger(G_LOG_LEVEL_WARNING, "failed to connect to %s:%i", sConfig->iphostname, sConfig->ipport); return FD_INVALID; } g_logger(G_LOG_LEVEL_INFO, "socket %s:%i initialised to fd %i", sConfig->iphostname, sConfig->ipport, fd); } else { g_logger(G_LOG_LEVEL_INFO, "Initialising unix client socket on %s", sConfig->socketname); addrU = (struct sockaddr_un *)malloc(sizeof(struct sockaddr_un)); fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { g_logger(G_LOG_LEVEL_WARNING, "Could not obtain socket: %i", fd); free(addrU); return FD_INVALID; } bzero(addrU, sizeof(struct sockaddr_un)); addrU->sun_family = AF_UNIX; strncpy(addrU->sun_path, sConfig->socketname, sizeof(addrU->sun_path) - 1); if (connect(fd, (struct sockaddr *) addrU, sizeof(struct sockaddr_un)) < 0) { g_logger(G_LOG_LEVEL_WARNING, "socket connect failed for: %s", sConfig->socketname); close(fd); free(addrU); return FD_INVALID; } free(addrU); g_logger(G_LOG_LEVEL_INFO, "socket %s initialised to fd %i", sConfig->socketname, fd); } return fd; } int server_socket_init(renderd_config *sConfig) { struct sockaddr_un addrU; struct sockaddr_in6 addrI; mode_t old; int fd; if (sConfig->ipport > 0) { const int enable = 1; g_logger(G_LOG_LEVEL_INFO, "Initialising TCP/IP server socket on %s:%i", sConfig->iphostname, sConfig->ipport); fd = socket(PF_INET6, SOCK_STREAM, 0); if (fd < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "failed to create IP socket"); exit(2); } if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "setsockopt SO_REUSEADDR failed for: %s:%i", sConfig->iphostname, sConfig->ipport); exit(3); } if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)) < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "setsockopt SO_REUSEPORT failed for: %s:%i", sConfig->iphostname, sConfig->ipport); exit(3); } bzero(&addrI, sizeof(addrI)); addrI.sin6_family = AF_INET6; addrI.sin6_addr = in6addr_any; addrI.sin6_port = htons(sConfig->ipport); if (bind(fd, (struct sockaddr *) &addrI, sizeof(addrI)) < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "socket bind failed for: %s:%i", sConfig->iphostname, sConfig->ipport); close(fd); exit(3); } } else { g_logger(G_LOG_LEVEL_INFO, "Initialising unix server socket on %s", sConfig->socketname); fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "failed to create unix socket"); exit(2); } bzero(&addrU, sizeof(addrU)); addrU.sun_family = AF_UNIX; strncpy(addrU.sun_path, sConfig->socketname, sizeof(addrU.sun_path) - 1); unlink(addrU.sun_path); old = umask(0); // Need daemon socket to be writeable by apache if (bind(fd, (struct sockaddr *) &addrU, sizeof(addrU)) < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "socket bind failed for: %s", sConfig->socketname); close(fd); exit(3); } umask(old); } if (listen(fd, QUEUE_MAX) < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "socket listen failed for %d", QUEUE_MAX); close(fd); exit(4); } g_logger(G_LOG_LEVEL_DEBUG, "Created server socket %i", fd); return fd; } /** * This function is used as a the start function for the slave renderer thread. * It pulls a request from the central queue of requests and dispatches it to * the slave renderer. It then blocks and waits for the response with no timeout. * As it only sends one request at a time (there are as many slave_thread threads as there * are rendering threads on the slaves) nothing gets queued on the slave and should get * rendererd immediately. Thus overall, requests should be nicely load balanced between * all the rendering threads available both locally and in the slaves. */ void *slave_thread(void * arg) { renderd_config * sConfig = (renderd_config *) arg; int pfd = FD_INVALID; int retry, seconds = 30; size_t ret_size; struct protocol * resp; struct protocol * req_slave; req_slave = (struct protocol *)malloc(sizeof(struct protocol)); resp = (struct protocol *)malloc(sizeof(struct protocol)); bzero(req_slave, sizeof(struct protocol)); bzero(resp, sizeof(struct protocol)); g_logger(G_LOG_LEVEL_DEBUG, "Starting slave thread: %lu", (unsigned long) pthread_self()); while (1) { if (pfd == FD_INVALID) { pfd = client_socket_init(sConfig); if (pfd == FD_INVALID) { if (sConfig->ipport > 0) { g_logger(G_LOG_LEVEL_ERROR, "Failed to connect to Renderd slave at %s:%i, trying again in %i seconds", sConfig->iphostname, sConfig->ipport, seconds); } else { g_logger(G_LOG_LEVEL_ERROR, "Failed to connect to Renderd slave at %s, trying again in %i seconds", sConfig->socketname, seconds); } sleep(seconds); continue; } } enum protoCmd ret; struct item *item = request_queue_fetch_request(render_request_queue); if (item) { struct protocol *req = &item->req; req_slave->ver = PROTO_VER; req_slave->cmd = cmdRender; strcpy(req_slave->xmlname, req->xmlname); strcpy(req_slave->mimetype, req->mimetype); strcpy(req_slave->options, req->options); req_slave->x = req->x; req_slave->y = req->y; req_slave->z = req->z; /*Dispatch request to slave renderd*/ retry = 2; if (sConfig->ipport > 0) { g_logger(G_LOG_LEVEL_INFO, "Dispatching request to Renderd slave at %s:%i on fd %i", sConfig->iphostname, sConfig->ipport, pfd); } else { g_logger(G_LOG_LEVEL_INFO, "Dispatching request to Renderd slave at %s on fd %i", sConfig->socketname, pfd); } do { ret_size = send_cmd(req_slave, pfd); if (ret_size == sizeof(struct protocol)) { //correctly sent command to slave break; } if (errno != EPIPE) { g_logger(G_LOG_LEVEL_ERROR, "Failed to send cmd to Renderd slave, shutting down slave thread"); free(resp); free(req_slave); close(pfd); return NULL; } g_logger(G_LOG_LEVEL_WARNING, "Failed to send cmd to Renderd slave, retrying"); close(pfd); pfd = client_socket_init(sConfig); if (pfd == FD_INVALID) { g_logger(G_LOG_LEVEL_ERROR, "Failed to re-connect to Renderd slave, dropping request"); ret = cmdNotDone; send_response(item, ret, -1); break; } } while (retry--); if (pfd == FD_INVALID || ret_size != sizeof(struct protocol)) { continue; } ret_size = 0; retry = 10; while ((ret_size < sizeof(struct protocol)) && (retry > 0)) { ret_size = recv(pfd, resp + ret_size, (sizeof(struct protocol) - ret_size), 0); if ((errno == EPIPE) || ret_size == 0) { close(pfd); pfd = FD_INVALID; ret_size = 0; g_logger(G_LOG_LEVEL_ERROR, "Pipe to Renderd slave closed"); break; } retry--; } if (ret_size < sizeof(struct protocol)) { if (sConfig->ipport > 0) { g_logger(G_LOG_LEVEL_ERROR, "Invalid reply from Renderd slave at %s:%i, trying again in %i seconds", sConfig->iphostname, sConfig->ipport, seconds); } else { g_logger(G_LOG_LEVEL_ERROR, "Invalid reply from Renderd slave at %s, trying again in %i seconds", sConfig->socketname, seconds); } ret = cmdNotDone; send_response(item, ret, -1); sleep(seconds); } else { ret = resp->cmd; send_response(item, ret, -1); if (resp->cmd != cmdDone) { if (sConfig->ipport > 0) { g_logger(G_LOG_LEVEL_ERROR, "Request from Renderd slave at %s:%i did not complete correctly", sConfig->iphostname, sConfig->ipport); } else { g_logger(G_LOG_LEVEL_ERROR, "Request from Renderd slave at %s did not complete correctly", sConfig->socketname); } //Sleep for a while to make sure we don't overload the renderer //This only happens if it didn't correctly block on the rendering //request sleep(seconds); } } } else { sleep(1); // TODO: Use an event to indicate there are new requests } } free(resp); free(req_slave); return NULL; } #ifndef MAIN_ALREADY_DEFINED int main(int argc, char **argv) { const char *config_file_name_default = RENDERD_CONFIG; int active_renderd_section_num_default = 0; const char *config_file_name = config_file_name_default; int active_renderd_section_num = active_renderd_section_num_default; int config_file_name_passed = 0; int active_renderd_section_num_passed = 0; int fd, i, j, k; int c; while (1) { int option_index = 0; static struct option long_options[] = { {"config", required_argument, 0, 'c'}, {"foreground", no_argument, 0, 'f'}, {"slave", required_argument, 0, 's'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {0, 0, 0, 0} }; c = getopt_long(argc, argv, "c:fs:hV", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'c': /* -c, --config */ config_file_name = strndup(optarg, PATH_MAX); config_file_name_passed = 1; struct stat buffer; if (stat(config_file_name, &buffer) != 0) { g_logger(G_LOG_LEVEL_CRITICAL, "Config file '%s' does not exist, please specify a valid file", config_file_name); return 1; } break; case 'f': /* -f, --foreground */ foreground = 1; break; case 's': /* -s, --slave */ active_renderd_section_num = min_max_int_opt(optarg, "active renderd section", 0, -1); active_renderd_section_num_passed = 1; break; case 'h': /* -h, --help */ fprintf(stderr, "Usage: renderd [OPTION] ...\n"); fprintf(stderr, "Mapnik rendering daemon\n"); fprintf(stderr, " -c, --config=CONFIG specify the renderd config file (default is '%s')\n", config_file_name_default); fprintf(stderr, " -f, --foreground run in foreground\n"); fprintf(stderr, " -s, --slave=CONFIG_SECTION_NR set which renderd section to use (default is '%i')\n", active_renderd_section_num_default); fprintf(stderr, "\n"); fprintf(stderr, " -h, --help display this help and exit\n"); fprintf(stderr, " -V, --version display the version number and exit\n"); return 0; case 'V': /* -V, --version */ fprintf(stdout, "%s\n", VERSION); return 0; default: fprintf(stderr, "unknown config option '%c'\n", c); return 1; } } g_logger(G_LOG_LEVEL_INFO, "Renderd started (version %s)", VERSION); process_config_file(config_file_name, active_renderd_section_num, G_LOG_LEVEL_INFO); if (config_file_name_passed) { free((void *)config_file_name); } g_logger(G_LOG_LEVEL_INFO, "Initialising request queue"); render_request_queue = request_queue_init(); if (render_request_queue == NULL) { g_logger(G_LOG_LEVEL_CRITICAL, "Failed to initialise request queue"); return 1; } fd = server_socket_init(&config); #if 0 if (fcntl(fd, F_SETFD, O_RDWR | O_NONBLOCK) < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "setting socket non-block failed"); close(fd); return 5; } #endif sigPipeAction.sa_handler = SIG_IGN; if (sigaction(SIGPIPE, &sigPipeAction, NULL) < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "failed to register signal handler"); close(fd); return 6; } sigExitAction.sa_handler = (void *) request_exit; sigaction(SIGHUP, &sigExitAction, NULL); sigaction(SIGINT, &sigExitAction, NULL); sigaction(SIGTERM, &sigExitAction, NULL); render_init(config.mapnik_plugins_dir, config.mapnik_font_dir, config.mapnik_font_dir_recurse); /* unless the command line said to run in foreground mode, fork and detach from terminal */ if (foreground) { g_logger(G_LOG_LEVEL_INFO, "Running in foreground mode..."); } else { if (daemon(0, 0) != 0) { g_logger(G_LOG_LEVEL_ERROR, "can't daemonize: %s", strerror(errno)); } /* write pid file */ FILE *pidfile = fopen(config.pid_filename, "w"); if (pidfile) { (void) fprintf(pidfile, "%d\n", getpid()); (void) fclose(pidfile); } } if (strnlen(config.stats_filename, PATH_MAX - 1)) { if (pthread_create(&stats_thread, NULL, stats_writeout_thread, NULL)) { g_logger(G_LOG_LEVEL_CRITICAL, "Could not spawn stats writeout thread"); close(fd); return 7; } } else { g_logger(G_LOG_LEVEL_INFO, "No stats file specified in config. Stats reporting disabled"); } render_threads = (pthread_t *) malloc(sizeof(pthread_t) * config.num_threads); for (i = 0; i < config.num_threads; i++) { if (pthread_create(&render_threads[i], NULL, render_thread, (void *)maps)) { g_logger(G_LOG_LEVEL_CRITICAL, "Could not spawn rendering thread"); close(fd); return 7; } } if (active_renderd_section_num == 0) { // Only the master renderd opens connections to its slaves k = 0; slave_threads = (pthread_t *) malloc(sizeof(pthread_t) * num_slave_threads); for (i = 1; i < MAX_SLAVES; i++) { for (j = 0; j < config_slaves[i].num_threads; j++) { if (pthread_create(&slave_threads[k++], NULL, slave_thread, (void *) &config_slaves[i])) { g_logger(G_LOG_LEVEL_CRITICAL, "Could not spawn slave thread"); close(fd); return 7; } } } } else { for (i = 0; i < MAX_SLAVES; i++) { if (active_renderd_section_num != i && config_slaves[i].num_threads != 0) { g_logger(G_LOG_LEVEL_DEBUG, "Freeing unused renderd config section %i: %s", i, config_slaves[i].name); free_renderd_section(config_slaves[i]); } } } process_loop(fd); unlink(config.socketname); free_map_sections(maps); free_renderd_sections(config_slaves); close(fd); return 0; } #endif mod_tile-0.8.0/src/renderd_config.c000066400000000000000000000460261474064163400172370ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2024 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #define _GNU_SOURCE #include #include #include "config.h" #include "g_logger.h" #include "render_config.h" #include "renderd.h" #ifdef HAVE_INIPARSER_INIPARSER_H #include #else #include #endif static void copy_string(const char *src, const char **dest, size_t maxlen) { size_t size = sizeof(char) * strnlen(src, maxlen); *dest = strndup(src, size); if (*dest == NULL) { g_logger(G_LOG_LEVEL_CRITICAL, "copy_string: strndup error"); exit(7); } } static char *name_with_section(const char *section, const char *name) { int len; char *key; len = asprintf(&key, "%s:%s", section, name); if (len == -1) { g_logger(G_LOG_LEVEL_CRITICAL, "name_with_section: asprintf error"); exit(7); } return key; } static void process_config_bool(const dictionary *ini, const char *section, const char *name, int *dest, int notfound) { char *key = name_with_section(section, name); int src = iniparser_getboolean(ini, key, notfound); g_logger(G_LOG_LEVEL_DEBUG, "\tRead %s: '%s'", key, src ? "true" : "false"); *dest = src; free(key); } static void process_config_double(const dictionary *ini, const char *section, const char *name, double *dest, double notfound) { char *key = name_with_section(section, name); double src = iniparser_getdouble(ini, key, notfound); g_logger(G_LOG_LEVEL_DEBUG, "\tRead %s: '%lf'", key, src); *dest = src; free(key); } static void process_config_int(const dictionary *ini, const char *section, const char *name, int *dest, int notfound) { char *key = name_with_section(section, name); int src = iniparser_getint(ini, key, notfound); g_logger(G_LOG_LEVEL_DEBUG, "\tRead %s: '%i'", key, src); *dest = src; free(key); } static void process_config_string(const dictionary *ini, const char *section, const char *name, const char **dest, const char *notfound, size_t maxlen) { char *key = name_with_section(section, name); const char *src = iniparser_getstring(ini, key, notfound); g_logger(G_LOG_LEVEL_DEBUG, "\tRead %s: '%s'", key, src); copy_string(src, dest, maxlen); free(key); } void free_map_section(xmlconfigitem map_section) { free((void *)map_section.attribution); free((void *)map_section.cors); free((void *)map_section.description); free((void *)map_section.file_extension); free((void *)map_section.host); free((void *)map_section.htcpip); free((void *)map_section.mime_type); free((void *)map_section.output_format); free((void *)map_section.parameterization); free((void *)map_section.server_alias); free((void *)map_section.tile_dir); free((void *)map_section.xmlfile); free((void *)map_section.xmlname); free((void *)map_section.xmluri); bzero(&map_section, sizeof(xmlconfigitem)); } void free_map_sections(xmlconfigitem *map_sections) { for (int i = 0; i < XMLCONFIGS_MAX; i++) { if (map_sections[i].xmlname != NULL) { free_map_section(map_sections[i]); } } } void free_renderd_section(renderd_config renderd_section) { free((void *)renderd_section.iphostname); free((void *)renderd_section.mapnik_font_dir); free((void *)renderd_section.mapnik_plugins_dir); free((void *)renderd_section.name); free((void *)renderd_section.pid_filename); free((void *)renderd_section.socketname); free((void *)renderd_section.stats_filename); free((void *)renderd_section.tile_dir); bzero(&renderd_section, sizeof(renderd_config)); } void free_renderd_sections(renderd_config *renderd_sections) { for (int i = 0; i < MAX_SLAVES; i++) { if (renderd_sections[i].num_threads != 0) { free_renderd_section(renderd_sections[i]); } } } double min_max_double_opt(const char *opt_arg, const char *opt_type_name, double minimum, double maximum) { char *endptr; double opt = strtod(opt_arg, &endptr); if (endptr == opt_arg) { g_logger(G_LOG_LEVEL_CRITICAL, "Invalid %s, must be a double (%s was provided)", opt_type_name, opt_arg); exit(1); } else if (minimum != -1 && opt < minimum) { g_logger(G_LOG_LEVEL_CRITICAL, "Invalid %s, must be >= %f (%s was provided)", opt_type_name, minimum, opt_arg); exit(1); } else if (maximum != -1 && opt > maximum) { g_logger(G_LOG_LEVEL_CRITICAL, "Invalid %s, must be <= %f (%s was provided)", opt_type_name, maximum, opt_arg); exit(1); } return opt; } int min_max_int_opt(const char *opt_arg, const char *opt_type_name, int minimum, int maximum) { char *endptr, *endptr_float; int opt = strtol(opt_arg, &endptr, 10); float opt_float = strtof(opt_arg, &endptr_float); if (endptr == opt_arg || endptr_float == opt_arg || (float)opt != opt_float) { g_logger(G_LOG_LEVEL_CRITICAL, "Invalid %s, must be an integer (%s was provided)", opt_type_name, opt_arg); exit(1); } else if (minimum != -1 && opt < minimum) { g_logger(G_LOG_LEVEL_CRITICAL, "Invalid %s, must be >= %i (%s was provided)", opt_type_name, minimum, opt_arg); exit(1); } else if (maximum != -1 && opt > maximum) { g_logger(G_LOG_LEVEL_CRITICAL, "Invalid %s, must be <= %i (%s was provided)", opt_type_name, maximum, opt_arg); exit(1); } return opt; } void process_map_sections(const char *config_file_name, xmlconfigitem *maps_dest, const char *default_tile_dir, int num_threads) { int map_section_num = -1; dictionary *ini = iniparser_load(config_file_name); if (!ini) { g_logger(G_LOG_LEVEL_CRITICAL, "Failed to load config file: %s", config_file_name); exit(1); } bzero(maps_dest, sizeof(xmlconfigitem) * XMLCONFIGS_MAX); g_logger(G_LOG_LEVEL_DEBUG, "Parsing map config section(s)"); for (int section_num = 0; section_num < iniparser_getnsec(ini); section_num++) { const char *section = iniparser_getsecname(ini, section_num); if (strncmp(section, "renderd", 7) && strcmp(section, "mapnik")) { // this is a map config section char *ini_type_copy, *ini_type_part, *ini_type_context; const char *ini_type; int ini_type_part_maxlen = 64, ini_type_part_num = 0; map_section_num++; g_logger(G_LOG_LEVEL_DEBUG, "Parsing map config section %i: %s", map_section_num, section); if (map_section_num >= XMLCONFIGS_MAX) { g_logger(G_LOG_LEVEL_CRITICAL, "Can't handle more than %i map config sections", XMLCONFIGS_MAX); exit(7); } copy_string(section, &maps_dest[map_section_num].xmlname, XMLCONFIG_MAX); process_config_int(ini, section, "aspectx", &maps_dest[map_section_num].aspect_x, 1); process_config_int(ini, section, "aspecty", &maps_dest[map_section_num].aspect_y, 1); process_config_int(ini, section, "tilesize", &maps_dest[map_section_num].tile_px_size, 256); process_config_string(ini, section, "attribution", &maps_dest[map_section_num].attribution, "", PATH_MAX); process_config_string(ini, section, "cors", &maps_dest[map_section_num].cors, "", PATH_MAX); process_config_string(ini, section, "description", &maps_dest[map_section_num].description, "", PATH_MAX); process_config_string(ini, section, "host", &maps_dest[map_section_num].host, "", PATH_MAX); process_config_string(ini, section, "htcphost", &maps_dest[map_section_num].htcpip, "", PATH_MAX); process_config_string(ini, section, "parameterize_style", &maps_dest[map_section_num].parameterization, "", PATH_MAX); process_config_string(ini, section, "server_alias", &maps_dest[map_section_num].server_alias, "", PATH_MAX); process_config_string(ini, section, "tiledir", &maps_dest[map_section_num].tile_dir, default_tile_dir, PATH_MAX); process_config_string(ini, section, "uri", &maps_dest[map_section_num].xmluri, "", PATH_MAX); process_config_string(ini, section, "xml", &maps_dest[map_section_num].xmlfile, "", PATH_MAX); process_config_double(ini, section, "scale", &maps_dest[map_section_num].scale_factor, 1.0); if (maps_dest[map_section_num].scale_factor < 0.1) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified scale factor (%lf) is too small, must be greater than or equal to %lf.", maps_dest[map_section_num].scale_factor, 0.1); exit(7); } else if (maps_dest[map_section_num].scale_factor > 8.0) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified scale factor (%lf) is too large, must be less than or equal to %lf.", maps_dest[map_section_num].scale_factor, 8.0); exit(7); } process_config_int(ini, section, "maxzoom", &maps_dest[map_section_num].max_zoom, MAX_ZOOM); if (maps_dest[map_section_num].max_zoom < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified max zoom (%i) is too small, must be greater than or equal to %i.", maps_dest[map_section_num].max_zoom, 0); exit(7); } else if (maps_dest[map_section_num].max_zoom > MAX_ZOOM) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified max zoom (%i) is too large, must be less than or equal to %i.", maps_dest[map_section_num].max_zoom, MAX_ZOOM); exit(7); } process_config_int(ini, section, "minzoom", &maps_dest[map_section_num].min_zoom, 0); if (maps_dest[map_section_num].min_zoom < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified min zoom (%i) is too small, must be greater than or equal to %i.", maps_dest[map_section_num].min_zoom, 0); exit(7); } else if (maps_dest[map_section_num].min_zoom > maps_dest[map_section_num].max_zoom) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified min zoom (%i) is larger than max zoom (%i).", maps_dest[map_section_num].min_zoom, maps_dest[map_section_num].max_zoom); exit(7); } process_config_string(ini, section, "type", &ini_type, "png image/png png256", INILINE_MAX); ini_type_copy = strndup(ini_type, INILINE_MAX); for (ini_type_part = strtok_r(ini_type_copy, " ", &ini_type_context); ini_type_part; ini_type_part = strtok_r(NULL, " ", &ini_type_context)) { switch (ini_type_part_num) { case 0: copy_string(ini_type_part, &maps_dest[map_section_num].file_extension, ini_type_part_maxlen); break; case 1: copy_string(ini_type_part, &maps_dest[map_section_num].mime_type, ini_type_part_maxlen); break; case 2: copy_string(ini_type_part, &maps_dest[map_section_num].output_format, ini_type_part_maxlen); break; default: g_logger(G_LOG_LEVEL_CRITICAL, "Specified type (%s) has too many parts, there must be no more than 3, e.g., 'png image/png png256'.", ini_type); exit(7); } ini_type_part_num++; } if (ini_type_part_num < 2) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified type (%s) has too few parts, there must be at least 2, e.g., 'png image/png'.", ini_type); exit(7); } if (ini_type_part_num < 3) { copy_string("png256", &maps_dest[map_section_num].output_format, ini_type_part_maxlen); } g_logger(G_LOG_LEVEL_DEBUG, "\tRead %s:%s:file_extension: '%s'", section, "type", maps_dest[map_section_num].file_extension); g_logger(G_LOG_LEVEL_DEBUG, "\tRead %s:%s:mime_type: '%s'", section, "type", maps_dest[map_section_num].mime_type); g_logger(G_LOG_LEVEL_DEBUG, "\tRead %s:%s:output_format: '%s'", section, "type", maps_dest[map_section_num].output_format); /* Pass this information into the rendering threads, * as it is needed to configure mapniks number of connections */ maps_dest[map_section_num].num_threads = num_threads; free(ini_type_copy); free(ini_type_part); free((void *)ini_type); } } iniparser_freedict(ini); if (map_section_num < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "No map config sections were found in file: %s", config_file_name); exit(1); } } void process_mapnik_section(const char *config_file_name, renderd_config *config_dest) { int mapnik_section_num = -1; dictionary *ini = iniparser_load(config_file_name); if (!ini) { g_logger(G_LOG_LEVEL_CRITICAL, "Failed to load config file: %s", config_file_name); exit(1); } g_logger(G_LOG_LEVEL_DEBUG, "Parsing mapnik config section"); for (int section_num = 0; section_num < iniparser_getnsec(ini); section_num++) { const char *section = iniparser_getsecname(ini, section_num); if (strcmp(section, "mapnik") == 0) { // this is a mapnik config section mapnik_section_num = section_num; process_config_bool(ini, section, "font_dir_recurse", &config_dest->mapnik_font_dir_recurse, MAPNIK_FONTS_DIR_RECURSE); process_config_string(ini, section, "font_dir", &config_dest->mapnik_font_dir, MAPNIK_FONTS_DIR, PATH_MAX); process_config_string(ini, section, "plugins_dir", &config_dest->mapnik_plugins_dir, MAPNIK_PLUGINS_DIR, PATH_MAX); break; } } iniparser_freedict(ini); if (mapnik_section_num < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "No mapnik config section was found in file: %s", config_file_name); exit(1); } } void process_renderd_sections(const char *config_file_name, renderd_config *configs_dest) { int renderd_section_num = -1; int renderd_socketname_maxlen = sizeof(((struct sockaddr_un *)0)->sun_path); dictionary *ini = iniparser_load(config_file_name); if (!ini) { g_logger(G_LOG_LEVEL_CRITICAL, "Failed to load config file: %s", config_file_name); exit(1); } bzero(configs_dest, sizeof(renderd_config) * MAX_SLAVES); g_logger(G_LOG_LEVEL_DEBUG, "Parsing renderd config section(s)"); for (int section_num = 0; section_num < iniparser_getnsec(ini); section_num++) { const char *section = iniparser_getsecname(ini, section_num); int renderd_strlen = 7; if (strncmp(section, "renderd", renderd_strlen) == 0) { // this is a renderd config section if (strcmp(section, "renderd") == 0 || strcmp(section, "renderd0") == 0) { renderd_section_num = 0; } else { char *endptr; renderd_section_num = strtol(§ion[renderd_strlen], &endptr, 10); if (endptr == §ion[renderd_strlen]) { g_logger(G_LOG_LEVEL_CRITICAL, "Invalid renderd section name: %s", section); exit(7); } } g_logger(G_LOG_LEVEL_DEBUG, "Parsing renderd config section %i: %s", renderd_section_num, section); if (renderd_section_num >= MAX_SLAVES) { g_logger(G_LOG_LEVEL_CRITICAL, "Can't handle more than %i renderd config sections", MAX_SLAVES); exit(7); } if (configs_dest[renderd_section_num].name != NULL) { g_logger(G_LOG_LEVEL_CRITICAL, "Duplicate renderd config section names for section %i: %s & %s", renderd_section_num, configs_dest[renderd_section_num].name, section); exit(7); } copy_string(section, &configs_dest[renderd_section_num].name, renderd_strlen + 2); process_config_int(ini, section, "ipport", &configs_dest[renderd_section_num].ipport, 0); process_config_int(ini, section, "num_threads", &configs_dest[renderd_section_num].num_threads, NUM_THREADS); process_config_string(ini, section, "iphostname", &configs_dest[renderd_section_num].iphostname, "", INILINE_MAX); process_config_string(ini, section, "pid_file", &configs_dest[renderd_section_num].pid_filename, RENDERD_PIDFILE, PATH_MAX); process_config_string(ini, section, "socketname", &configs_dest[renderd_section_num].socketname, RENDERD_SOCKET, PATH_MAX); process_config_string(ini, section, "stats_file", &configs_dest[renderd_section_num].stats_filename, "", PATH_MAX); process_config_string(ini, section, "tile_dir", &configs_dest[renderd_section_num].tile_dir, RENDERD_TILE_DIR, PATH_MAX); if (configs_dest[renderd_section_num].num_threads == -1) { configs_dest[renderd_section_num].num_threads = sysconf(_SC_NPROCESSORS_ONLN); } if (strnlen(configs_dest[renderd_section_num].socketname, PATH_MAX) >= renderd_socketname_maxlen) { g_logger(G_LOG_LEVEL_CRITICAL, "Specified socketname (%s) exceeds maximum allowed length of %i.", configs_dest[renderd_section_num].socketname, renderd_socketname_maxlen); exit(7); } } } iniparser_freedict(ini); if (renderd_section_num < 0) { g_logger(G_LOG_LEVEL_CRITICAL, "No renderd config sections were found in file: %s", config_file_name); exit(1); } } void process_config_file(const char *config_file_name, int active_renderd_section_num, int log_level) { extern int num_slave_threads; extern renderd_config config; extern renderd_config config_slaves[MAX_SLAVES]; extern xmlconfigitem maps[XMLCONFIGS_MAX]; num_slave_threads = 0; g_logger(log_level, "Parsing renderd config file '%s':", config_file_name); process_renderd_sections(config_file_name, config_slaves); process_mapnik_section(config_file_name, &config_slaves[active_renderd_section_num]); process_map_sections(config_file_name, maps, config_slaves[active_renderd_section_num].tile_dir, config_slaves[active_renderd_section_num].num_threads); config = config_slaves[active_renderd_section_num]; for (int i = 0; i < MAX_SLAVES; i++) { if (config_slaves[i].num_threads == 0) { continue; } if (i == active_renderd_section_num) { g_logger(G_LOG_LEVEL_DEBUG, "\trenderd(%i): Active", i); } else { num_slave_threads += config_slaves[i].num_threads; } if (config_slaves[i].ipport > 0) { g_logger(G_LOG_LEVEL_DEBUG, "\trenderd(%i): ip socket = '%s:%i'", i, config_slaves[i].iphostname, config_slaves[i].ipport); } else { g_logger(G_LOG_LEVEL_DEBUG, "\trenderd(%i): unix socketname = '%s'", i, config_slaves[i].socketname); } g_logger(G_LOG_LEVEL_DEBUG, "\trenderd(%i): num_threads = '%i'", i, config_slaves[i].num_threads); g_logger(G_LOG_LEVEL_DEBUG, "\trenderd(%i): pid_file = '%s'", i, config_slaves[i].pid_filename); if (strnlen(config_slaves[i].stats_filename, PATH_MAX)) { g_logger(G_LOG_LEVEL_DEBUG, "\trenderd(%i): stats_file = '%s'", i, config_slaves[i].stats_filename); } g_logger(G_LOG_LEVEL_DEBUG, "\trenderd(%i): tile_dir = '%s'", i, config_slaves[i].tile_dir); } if (config.ipport > 0) { g_logger(log_level, "\trenderd: ip socket = '%s':%i", config.iphostname, config.ipport); } else { g_logger(log_level, "\trenderd: unix socketname = '%s'", config.socketname); } g_logger(log_level, "\trenderd: num_threads = '%i'", config.num_threads); if (active_renderd_section_num == 0 && num_slave_threads > 0) { g_logger(log_level, "\trenderd: num_slave_threads = '%i'", num_slave_threads); } g_logger(log_level, "\trenderd: pid_file = '%s'", config.pid_filename); if (strnlen(config.stats_filename, PATH_MAX)) { g_logger(log_level, "\trenderd: stats_file = '%s'", config.stats_filename); } g_logger(log_level, "\trenderd: tile_dir = '%s'", config.tile_dir); g_logger(log_level, "\tmapnik: font_dir = '%s'", config.mapnik_font_dir); g_logger(log_level, "\tmapnik: font_dir_recurse = '%s'", config.mapnik_font_dir_recurse ? "true" : "false"); g_logger(log_level, "\tmapnik: plugins_dir = '%s'", config.mapnik_plugins_dir); for (int i = 0; i < XMLCONFIGS_MAX; i++) { if (maps[i].xmlname != NULL) { g_logger(log_level, "\tmap %i: name(%s) file(%s) uri(%s) output_format(%s) htcp(%s) host(%s)", i, maps[i].xmlname, maps[i].xmlfile, maps[i].xmluri, maps[i].output_format, maps[i].htcpip, maps[i].host); } } } mod_tile-0.8.0/src/request_queue.c000066400000000000000000000310011474064163400171460ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include "render_config.h" #include "request_queue.h" #include "g_logger.h" static int calcHashKey(struct request_queue *queue, struct item *item) { uint64_t xmlnameHash = 0; uint64_t key; for (int i = 0; (item->req.xmlname[i] != 0) && (i < sizeof(item->req.xmlname)); i++) { xmlnameHash += item->req.xmlname[i]; } key = ((uint64_t)(xmlnameHash & 0x1FF) << 52) + ((uint64_t)(item->req.z) << 48) + ((uint64_t)(item->mx & 0xFFFFFF) << 24) + (item->my & 0xFFFFFF); return key % queue->hashidxSize; } static struct item * lookup_item_idx(struct request_queue * queue, struct item * item) { struct item_idx * nextItem; struct item * test; int key = calcHashKey(queue, item); if (queue->item_hashidx[key].item == NULL) { return NULL; } else { nextItem = &(queue->item_hashidx[key]); while (nextItem != NULL) { test = nextItem->item; if ((item->mx == test->mx) && (item->my == test->my) && (item->req.z == test->req.z) && (!strcmp( item->req.xmlname, test->req.xmlname))) { return test; } else { nextItem = nextItem->next; } } } return NULL; } static void insert_item_idx(struct request_queue * queue, struct item *item) { struct item_idx * nextItem; struct item_idx * prevItem; int key = calcHashKey(queue, item); if (queue->item_hashidx[key].item == NULL) { queue->item_hashidx[key].item = item; } else { prevItem = &(queue->item_hashidx[key]); nextItem = queue->item_hashidx[key].next; while (nextItem) { prevItem = nextItem; nextItem = nextItem->next; } nextItem = (struct item_idx *)malloc(sizeof(struct item_idx)); nextItem->item = item; nextItem->next = NULL; prevItem->next = nextItem; } } static void remove_item_idx(struct request_queue * queue, struct item * item) { int key = calcHashKey(queue, item); struct item_idx * nextItem; struct item_idx * prevItem; struct item * test; if (queue->item_hashidx[key].item == NULL) { //item not in index; return; } prevItem = &(queue->item_hashidx[key]); nextItem = &(queue->item_hashidx[key]); while (nextItem != NULL) { test = nextItem->item; if ((item->mx == test->mx) && (item->my == test->my) && (item->req.z == test->req.z) && (!strcmp(item->req.xmlname, test->req.xmlname))) { /* * Found item, removing it from list */ nextItem->item = NULL; if (nextItem->next != NULL) { if (nextItem == &(queue->item_hashidx[key])) { prevItem = nextItem->next; memcpy(&(queue->item_hashidx[key]), nextItem->next, sizeof(struct item_idx)); free(prevItem); } else { prevItem->next = nextItem->next; } } else { prevItem->next = NULL; } if (nextItem != &(queue->item_hashidx[key])) { free(nextItem); } return; } else { prevItem = nextItem; nextItem = nextItem->next; } } } static enum protoCmd pending(struct request_queue * queue, struct item *test) { // check all queues and render list to see if this request already queued // If so, add this new request as a duplicate // call with qLock held struct item *item; item = lookup_item_idx(queue, test); if (item != NULL) { if ((item->inQueue == queueRender) || (item->inQueue == queueRequest) || (item->inQueue == queueRequestPrio) || (item->inQueue == queueRequestLow)) { test->duplicates = item->duplicates; item->duplicates = test; test->inQueue = queueDuplicate; return cmdIgnore; } else if ((item->inQueue == queueDirty) || (item->inQueue == queueRequestBulk)) { return cmdNotDone; } } return cmdRender; } struct item *request_queue_fetch_request(struct request_queue * queue) { struct item *item = NULL; pthread_mutex_lock(&(queue->qLock)); while ((queue->reqNum == 0) && (queue->dirtyNum == 0) && (queue->reqLowNum == 0) && (queue->reqPrioNum == 0) && (queue->reqBulkNum == 0)) { pthread_cond_wait(&(queue->qCond), &(queue->qLock)); } if (queue->reqPrioNum) { item = queue->reqPrioHead.next; queue->reqPrioNum--; queue->stats.noReqPrioRender++; } else if (queue->reqNum) { item = queue->reqHead.next; queue->reqNum--; queue->stats.noReqRender++; } else if (queue->reqLowNum) { item = queue->reqLowHead.next; queue->reqLowNum--; queue->stats.noReqLowRender++; } else if (queue->dirtyNum) { item = queue->dirtyHead.next; queue->dirtyNum--; queue->stats.noDirtyRender++; } else if (queue->reqBulkNum) { item = queue->reqBulkHead.next; queue->reqBulkNum--; queue->stats.noReqBulkRender++; } if (item) { item->next->prev = item->prev; item->prev->next = item->next; //Add item to render queue item->prev = &(queue->renderHead); item->next = queue->renderHead.next; queue->renderHead.next->prev = item; queue->renderHead.next = item; item->inQueue = queueRender; } pthread_mutex_unlock(&queue->qLock); return item; } /* If a fd becomes invalid for returning request information, remove it from all * requests to not send feedback to invalid FDs */ void request_queue_clear_requests_by_fd(struct request_queue * queue, int fd) { struct item *item, *dupes, *queueHead; /**Only need to look up on the shorter request and render queue, * as the all requests on the dirty queue already have a FD_INVALID * as a file descriptor, so using the linear list shouldn't be a problem */ pthread_mutex_lock(&(queue->qLock)); for (int i = 0; i < 4; i++) { switch (i) { case 0: { queueHead = &(queue->reqHead); break; } case 1: { queueHead = &(queue->renderHead); break; } case 2: { queueHead = &(queue->reqPrioHead); break; } case 3: { queueHead = &(queue->reqBulkHead); break; } } item = queueHead->next; while (item != queueHead) { if (item->fd == fd) { item->fd = FD_INVALID; } dupes = item->duplicates; while (dupes) { if (dupes->fd == fd) { dupes->fd = FD_INVALID; } dupes = dupes->duplicates; } item = item->next; } } pthread_mutex_unlock(&(queue->qLock)); } enum protoCmd request_queue_add_request(struct request_queue * queue, struct item *item) { enum protoCmd status; const struct protocol *req; struct item *list = NULL; req = &(item->req); if (queue == NULL) { g_logger(G_LOG_LEVEL_CRITICAL, "queue os NULL"); exit(3); } pthread_mutex_lock(&(queue->qLock)); // Check for a matching request in the current rendering or dirty queues status = pending(queue, item); if (status == cmdNotDone) { // We found a match in the dirty queue, can not wait for it pthread_mutex_unlock(&(queue->qLock)); free(item); return cmdNotDone; } if (status == cmdIgnore) { // Found a match in render queue, item added as duplicate pthread_mutex_unlock(&(queue->qLock)); return cmdIgnore; } // New request, add it to render or dirty queue if ((req->cmd == cmdRender) && (queue->reqNum < REQ_LIMIT)) { list = &(queue->reqHead); item->inQueue = queueRequest; item->originatedQueue = queueRequest; queue->reqNum++; } else if ((req->cmd == cmdRenderPrio) && (queue->reqPrioNum < REQ_LIMIT)) { list = &(queue->reqPrioHead); item->inQueue = queueRequestPrio; item->originatedQueue = queueRequestPrio; queue->reqPrioNum++; } else if ((req->cmd == cmdRenderLow) && (queue->reqLowNum < REQ_LIMIT)) { list = &(queue->reqLowHead); item->inQueue = queueRequestLow; item->originatedQueue = queueRequestLow; queue->reqLowNum++; } else if ((req->cmd == cmdRenderBulk) && (queue->reqBulkNum < REQ_LIMIT)) { list = &(queue->reqBulkHead); item->inQueue = queueRequestBulk; item->originatedQueue = queueRequestBulk; queue->reqBulkNum++; } else if (queue->dirtyNum < DIRTY_LIMIT) { list = &(queue->dirtyHead); item->inQueue = queueDirty; item->originatedQueue = queueDirty; queue->dirtyNum++; item->fd = FD_INVALID; // No response after render } else { // The queue is severely backlogged. Drop request queue->stats.noReqDroped++; pthread_mutex_unlock(&(queue->qLock)); free(item); return cmdNotDone; } if (list) { item->next = list; item->prev = list->prev; item->prev->next = item; list->prev = item; /* In addition to the linked list, add item to a hash table index * for faster lookup of pending requests. */ insert_item_idx(queue, item); pthread_cond_signal(&queue->qCond); } else { free(item); } pthread_mutex_unlock(&queue->qLock); return (list == &(queue->dirtyHead)) ? cmdNotDone : cmdIgnore; } void request_queue_remove_request(struct request_queue * queue, struct item * request, int render_time) { pthread_mutex_lock(&(queue->qLock)); if (request->inQueue != queueRender) { g_logger(G_LOG_LEVEL_WARNING, "Removing request from queue, even though not on rendering queue"); } if (render_time > 0) { switch (request->originatedQueue) { case queueRequestPrio: { queue->stats.timeReqPrioRender += render_time; break; } case queueRequest: { queue->stats.timeReqRender += render_time; break; } case queueRequestLow: { queue->stats.timeReqLowRender += render_time; break; } case queueDirty: { queue->stats.timeReqDirty += render_time; break; } case queueRequestBulk: { queue->stats.timeReqBulkRender += render_time; break; } default: break; } queue->stats.noZoomRender[request->req.z]++; queue->stats.timeZoomRender[request->req.z] += render_time; } request->next->prev = request->prev; request->prev->next = request->next; remove_item_idx(queue, request); pthread_mutex_unlock(&(queue->qLock)); } int request_queue_no_requests_queued(struct request_queue * queue, enum protoCmd priority) { int noReq = -1; pthread_mutex_lock(&(queue->qLock)); switch (priority) { case cmdRenderPrio: noReq = queue->reqPrioNum; break; case cmdRender: noReq = queue->reqNum; break; case cmdRenderLow: noReq = queue->reqLowNum; break; case cmdDirty: noReq = queue->dirtyNum; break; case cmdRenderBulk: noReq = queue->reqBulkNum; break; default: break; } pthread_mutex_unlock(&queue->qLock); return noReq; } void request_queue_copy_stats(struct request_queue * queue, stats_struct * stats) { pthread_mutex_lock(&(queue->qLock)); memcpy(stats, &(queue->stats), sizeof(stats_struct)); pthread_mutex_unlock(&queue->qLock); } struct request_queue * request_queue_init() { int res; struct request_queue * queue = calloc(1, sizeof(struct request_queue)); if (queue == NULL) { return NULL; } res = pthread_mutex_init(&(queue->qLock), NULL); if (res != 0) { g_logger(G_LOG_LEVEL_ERROR, "Failed to create mutex for request_queue"); free(queue); return NULL; } res = pthread_cond_init(&(queue->qCond), NULL); if (res != 0) { g_logger(G_LOG_LEVEL_ERROR, "Failed to create condition variable for request_queue"); pthread_mutex_destroy(&(queue->qLock)); free(queue); return NULL; } queue->stats.noDirtyRender = 0; queue->stats.noReqDroped = 0; queue->stats.noReqRender = 0; queue->stats.noReqPrioRender = 0; queue->stats.noReqLowRender = 0; queue->stats.noReqBulkRender = 0; queue->reqHead.next = queue->reqHead.prev = &(queue->reqHead); queue->reqPrioHead.next = queue->reqPrioHead.prev = &(queue->reqPrioHead); queue->reqLowHead.next = queue->reqLowHead.prev = &(queue->reqLowHead); queue->reqBulkHead.next = queue->reqBulkHead.prev = &(queue->reqBulkHead); queue->dirtyHead.next = queue->dirtyHead.prev = &(queue->dirtyHead); queue->renderHead.next = queue->renderHead.prev = &(queue->renderHead); queue->hashidxSize = HASHIDX_SIZE; queue->item_hashidx = (struct item_idx *) malloc(sizeof(struct item_idx) * queue->hashidxSize); bzero(queue->item_hashidx, sizeof(struct item_idx) * queue->hashidxSize); return queue; } void request_queue_close(struct request_queue * queue) { //TODO: Free items if the queues are not empty at closing time pthread_mutex_destroy(&(queue->qLock)); free(queue->item_hashidx); free(queue); } mod_tile-0.8.0/src/store.c000066400000000000000000000056301474064163400154170ustar00rootroot00000000000000/* wrapper for storage engines */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_PTHREAD #include #endif #include "store.h" #include "store_file.h" #include "store_memcached.h" #include "store_rados.h" #include "store_ro_http_proxy.h" #include "store_ro_composite.h" #include "store_null.h" #include "g_logger.h" /** * In Apache 2.2, we call the init_storage_backend once per process. For mpm_worker and mpm_event multiple threads therefore use the same * storage context, and all storage backends need to be thread-safe in order not to cause issues with these mpm's * * In Apache 2.4, we call the init_storage_backend once per thread, and therefore each thread has its own storage context to work with. */ struct storage_backend * init_storage_backend(const char * options) { struct stat st; struct storage_backend * store = NULL; //Determine the correct storage backend based on the options string if (strlen(options) == 0) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_backend: Options string was empty"); return NULL; } if (options[0] == '/') { if (stat(options, &st) != 0) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_backend: Failed to stat %s with error: %s", options, strerror(errno)); return NULL; } if (S_ISDIR(st.st_mode)) { g_logger(G_LOG_LEVEL_DEBUG, "init_storage_backend: initialising file storage backend at: %s", options); store = init_storage_file(options); return store; } else { g_logger(G_LOG_LEVEL_ERROR, "init_storage_backend: %s is not a directory", options, strerror(errno)); return NULL; } } if (strstr(options, "rados://") == options) { g_logger(G_LOG_LEVEL_DEBUG, "init_storage_backend: initialising rados storage backend at: %s", options); store = init_storage_rados(options); return store; } if (strstr(options, "memcached://") == options) { g_logger(G_LOG_LEVEL_DEBUG, "init_storage_backend: initialising memcached storage backend at: %s", options); store = init_storage_memcached(options); return store; } if (strstr(options, "ro_http_proxy://") == options) { g_logger(G_LOG_LEVEL_DEBUG, "init_storage_backend: initialising ro_http_proxy storage backend at: %s", options); store = init_storage_ro_http_proxy(options); return store; } if (strstr(options, "composite:{") == options) { g_logger(G_LOG_LEVEL_DEBUG, "init_storage_backend: initialising ro_composite storage backend at: %s", options); store = init_storage_ro_composite(options); return store; } if (strstr(options, "null://") == options) { g_logger(G_LOG_LEVEL_DEBUG, "init_storage_backend: initialising null storage backend at: %s", options); store = init_storage_null(); return store; } g_logger(G_LOG_LEVEL_ERROR, "init_storage_backend: No valid storage backend found for options: %s", options); return store; } mod_tile-0.8.0/src/store_file.c000066400000000000000000000211101474064163400164050ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ /* Meta-tile optimised file storage * * Instead of storing each individual tile as a file, * bundle the 8x8 meta tile into a special meta-file. * This reduces the Inode usage and more efficient * utilisation of disk space. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "store.h" #include "metatile.h" #include "render_config.h" #include "store_file.h" #include "store_file_utils.h" #include "protocol.h" #include "g_logger.h" static time_t getPlanetTime(const char * tile_dir, const char * xmlname) { struct stat st_stat; char filename[PATH_MAX]; snprintf(filename, PATH_MAX - 1, "%s/%s%s", tile_dir, xmlname, PLANET_TIMESTAMP); if (stat(filename, &st_stat) < 0) { snprintf(filename, PATH_MAX - 1, "%s/%s", tile_dir, PLANET_TIMESTAMP); if (stat(filename, &st_stat) < 0) { // Make something up return time(NULL) - (3 * 24 * 60 * 60); } } return st_stat.st_mtime; } static int file_tile_read(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, char *buf, size_t sz, int * compressed, char * log_msg) { char path[PATH_MAX]; int meta_offset, fd; unsigned int pos; unsigned int header_len = sizeof(struct meta_layout) + METATILE * METATILE * sizeof(struct entry); struct meta_layout *m = (struct meta_layout *)malloc(header_len); size_t file_offset, tile_size; meta_offset = xyzo_to_meta(path, sizeof(path), store->storage_ctx, xmlconfig, options, x, y, z); fd = open(path, O_RDONLY); if (fd < 0) { snprintf(log_msg, PATH_MAX - 1, "Could not open metatile %s. Reason: %s\n", path, strerror(errno)); free(m); return -1; } pos = 0; while (pos < header_len) { size_t len = header_len - pos; int got = read(fd, ((unsigned char *) m) + pos, len); if (got < 0) { snprintf(log_msg, PATH_MAX - 1, "Failed to read complete header for metatile %s Reason: %s\n", path, strerror(errno)); close(fd); free(m); return -2; } else if (got > 0) { pos += got; } else { break; } } if (pos < header_len) { snprintf(log_msg, PATH_MAX - 1, "Meta file %s too small to contain header\n", path); close(fd); free(m); return -3; } if (memcmp(m->magic, META_MAGIC, strlen(META_MAGIC))) { if (memcmp(m->magic, META_MAGIC_COMPRESSED, strlen(META_MAGIC_COMPRESSED))) { snprintf(log_msg, PATH_MAX - 1, "Meta file %s header magic mismatch\n", path); close(fd); free(m); return -4; } else { *compressed = 1; } } else { *compressed = 0; } // Currently this code only works with fixed metatile sizes (due to xyz_to_meta above) if (m->count != (METATILE * METATILE)) { snprintf(log_msg, PATH_MAX - 1, "Meta file %s header bad count %d != %d\n", path, m->count, METATILE * METATILE); free(m); close(fd); return -5; } file_offset = m->index[meta_offset].offset; tile_size = m->index[meta_offset].size; free(m); if (tile_size > sz) { snprintf(log_msg, PATH_MAX - 1, "Truncating tile %zd to fit buffer of %zd\n", tile_size, sz); tile_size = sz; close(fd); return -6; } if (lseek(fd, file_offset, SEEK_SET) < 0) { snprintf(log_msg, PATH_MAX - 1, "Meta file %s seek error: %s\n", path, strerror(errno)); close(fd); return -7; } pos = 0; while (pos < tile_size) { size_t len = tile_size - pos; int got = read(fd, buf + pos, len); if (got < 0) { snprintf(log_msg, PATH_MAX - 1, "Failed to read data from file %s. Reason: %s\n", path, strerror(errno)); close(fd); return -8; } else if (got > 0) { pos += got; } else { break; } } close(fd); return pos; } static struct stat_info file_tile_stat(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z) { struct stat_info tile_stat; struct stat st_stat; char meta_path[PATH_MAX]; xyzo_to_meta(meta_path, sizeof(meta_path), (char *)(store->storage_ctx), xmlconfig, options, x, y, z); if (stat(meta_path, &st_stat)) { tile_stat.size = -1; tile_stat.mtime = 0; tile_stat.atime = 0; tile_stat.ctime = 0; } else { tile_stat.size = st_stat.st_size; tile_stat.mtime = st_stat.st_mtime; tile_stat.atime = st_stat.st_atime; tile_stat.ctime = st_stat.st_ctime; } if (tile_stat.mtime < getPlanetTime(store->storage_ctx, xmlconfig)) { tile_stat.expired = 1; } else { tile_stat.expired = 0; } return tile_stat; } static char * file_tile_storage_id(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, char * string) { char meta_path[PATH_MAX]; xyzo_to_meta(meta_path, sizeof(meta_path), (char *)(store->storage_ctx), xmlconfig, options, x, y, z); snprintf(string, PATH_MAX - 1, "file://%s", meta_path); return string; } static int file_metatile_write(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, const char *buf, int sz) { int fd; char meta_path[PATH_MAX]; char * tmp; int res; xyzo_to_meta(meta_path, sizeof(meta_path), (char *)(store->storage_ctx), xmlconfig, options, x, y, z); g_logger(G_LOG_LEVEL_DEBUG, "Creating and writing a metatile to %s", meta_path); tmp = malloc(sizeof(char) * strlen(meta_path) + 24); snprintf(tmp, strlen(meta_path) + 24, "%s.%lu", meta_path, (unsigned long) pthread_self()); if (mkdirp(tmp)) { free(tmp); return -1; } fd = open(tmp, O_WRONLY | O_TRUNC | O_CREAT, 0666); if (fd < 0) { g_logger(G_LOG_LEVEL_WARNING, "Error creating file %s: %s", meta_path, strerror(errno)); free(tmp); return -1; } res = write(fd, buf, sz); if (res != sz) { g_logger(G_LOG_LEVEL_WARNING, "Error writing file %s: %s", meta_path, strerror(errno)); close(fd); free(tmp); return -1; } close(fd); rename(tmp, meta_path); free(tmp); return sz; } static int file_metatile_delete(struct storage_backend * store, const char *xmlconfig, int x, int y, int z) { char meta_path[PATH_MAX]; //TODO: deal with options xyz_to_meta(meta_path, sizeof(meta_path), (char *)(store->storage_ctx), xmlconfig, x, y, z); g_logger(G_LOG_LEVEL_DEBUG, "Deleting metatile from %s", meta_path); return unlink(meta_path); } static int file_metatile_expire(struct storage_backend * store, const char *xmlconfig, int x, int y, int z) { char name[PATH_MAX]; struct stat s; static struct tm touchCalendar; struct utimbuf touchTime; //TODO: deal with options xyz_to_meta(name, sizeof(name), store->storage_ctx, xmlconfig, x, y, z); if (stat(name, &s) == 0) {// 0 is success // tile exists on disk; mark it as expired if (!gmtime_r(&(s.st_mtime), &touchCalendar)) { touchTime.modtime = 315558000; } else { if (touchCalendar.tm_year > 105) { // Tile hasn't already been marked as expired touchCalendar.tm_year -= 20; //Set back by 20 years, to keep the creation time as reference. touchTime.modtime = mktime(&touchCalendar); } else { touchTime.modtime = s.st_mtime; } } touchTime.actime = s.st_atime; // Don't modify atime, as that is used for tile cache purging if (-1 == utime(name, &touchTime)) { perror("modifying timestamp failed"); } } return 0; } static int file_close_storage(struct storage_backend * store) { free(store->storage_ctx); store->storage_ctx = NULL; return 0; } struct storage_backend * init_storage_file(const char * tile_dir) { struct storage_backend * store = malloc(sizeof(struct storage_backend)); if (store == NULL) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_file: Failed to allocate memory for storage backend"); return NULL; } store->storage_ctx = strdup(tile_dir); store->tile_read = &file_tile_read; store->tile_stat = &file_tile_stat; store->metatile_write = &file_metatile_write; store->metatile_delete = &file_metatile_delete; store->metatile_expire = &file_metatile_expire; store->tile_storage_id = &file_tile_storage_id; store->close_storage = &file_close_storage; return store; } mod_tile-0.8.0/src/store_file_utils.c000066400000000000000000000134011474064163400176310ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include #include #include "protocol.h" #include "render_config.h" #include "store_file.h" #include "store_file_utils.h" #include "g_logger.h" // Build parent directories for the specified file name // Note: the part following the trailing / is ignored // e.g. mkdirp("/a/b/foo.png") == shell mkdir -p /a/b int mkdirp(const char *path) { struct stat s; char tmp[PATH_MAX]; char *p; strncpy(tmp, path, sizeof(tmp) - 1); // Look for parent directory p = strrchr(tmp, '/'); if (!p) { return 0; } *p = '\0'; if (!stat(tmp, &s)) { return !S_ISDIR(s.st_mode); } *p = '/'; // Walk up the path making sure each element is a directory p = tmp; if (!*p) { return 0; } p++; // Ignore leading / while (*p) { if (*p == '/') { *p = '\0'; if (!stat(tmp, &s)) { if (!S_ISDIR(s.st_mode)) { g_logger(G_LOG_LEVEL_ERROR, "Error, not a directory: %s", tmp); return 1; } } else if (mkdir(tmp, 0777)) { // Ignore multiple threads attempting to create the same directory if (errno != EEXIST) { perror(tmp); return 1; } } *p = '/'; } p++; } return 0; } /* File path hashing. Used by both mod_tile and render daemon * The two must both agree on the file layout for meta-tiling * to work */ static int check_xyz(int x, int y, int z) { int oob, limit; // Validate tile co-ordinates oob = (z < 0 || z > MAX_ZOOM); if (!oob) { // valid x/y for tiles are 0 ... 2^zoom-1 limit = (1 << z) - 1; oob = (x < 0 || x > limit || y < 0 || y > limit); } if (oob) { g_logger(G_LOG_LEVEL_ERROR, "got bad co-ords: x(%d) y(%d) z(%d)", x, y, z); } return oob; } int path_to_xyz(const char *tilepath, const char *path, char *xmlconfig, int *px, int *py, int *pz) { #ifdef DIRECTORY_HASH int i, n, hash[5], x, y, z; for (i = 0; tilepath[i] && tilepath[i] == path[i]; ++i) ; if (tilepath[i]) { g_logger(G_LOG_LEVEL_ERROR, "Tile path does not match settings (%s): %s", tilepath, path); return 1; } n = sscanf(path + i, "/%40[^/]/%d/%d/%d/%d/%d/%d", xmlconfig, pz, &hash[0], &hash[1], &hash[2], &hash[3], &hash[4]); if (n != 7) { g_logger(G_LOG_LEVEL_ERROR, "Failed to parse tile path: %s", path); return 1; } else { x = y = 0; for (i = 0; i < 5; i++) { if (hash[i] < 0 || hash[i] > 255) { g_logger(G_LOG_LEVEL_ERROR, "Failed to parse tile path (invalid %d): %s", hash[i], path); return 2; } x <<= 4; y <<= 4; x |= (hash[i] & 0xf0) >> 4; y |= (hash[i] & 0x0f); } z = *pz; *px = x; *py = y; return check_xyz(x, y, z); } #else int n; n = sscanf(path, TILE_PATH "/%40[^/]/%d/%d/%d", xmlconfig, pz, px, py); if (n != 4) { g_logger(G_LOG_LEVEL_ERROR, "Failed to parse tile path: %s", path); return 1; } else { return check_xyz(*px, *py, *pz); } #endif } #ifdef METATILE // Returns the path to the meta-tile and the offset within the meta-tile int xyzo_to_meta(char *path, size_t len, const char *tile_dir, const char *xmlconfig, const char *options, int x, int y, int z) { unsigned char i, hash[5], offset, mask; // Each meta tile winds up in its own file, with several in each leaf directory // the .meta tile name is beasd on the sub-tile at (0,0) mask = METATILE - 1; offset = (x & mask) * METATILE + (y & mask); x &= ~mask; y &= ~mask; for (i = 0; i < 5; i++) { hash[i] = ((x & 0x0f) << 4) | (y & 0x0f); x >>= 4; y >>= 4; } #ifdef DIRECTORY_HASH if (strlen(options)) { snprintf(path, len, "%s/%s/%d/%u/%u/%u/%u/%u.%s.meta", tile_dir, xmlconfig, z, hash[4], hash[3], hash[2], hash[1], hash[0], options); } else { snprintf(path, len, "%s/%s/%d/%u/%u/%u/%u/%u.meta", tile_dir, xmlconfig, z, hash[4], hash[3], hash[2], hash[1], hash[0]); } #else // DIRECTORY_HASH if (strlen(options)) { snprintf(path, len, "%s/%s/%d/%u/%u.%s.meta", tile_dir, xmlconfig, z, x, y, options); } else { snprintf(path, len, "%s/%s/%d/%u/%u.meta", tile_dir, xmlconfig, z, x, y); } #endif // DIRECTORY_HASH return offset; } // Returns the path to the meta-tile and the offset within the meta-tile int xyz_to_meta(char *path, size_t len, const char *tile_dir, const char *xmlconfig, int x, int y, int z) { return xyzo_to_meta(path, len, tile_dir, xmlconfig, "", x, y, z); } #else // METATILE void xyz_to_path(char *path, size_t len, const char *tile_dir, const char *xmlconfig, int x, int y, int z) { #ifdef DIRECTORY_HASH // We attempt to cluster the tiles so that a 16x16 square of tiles will be in a single directory // Hash stores our 40 bit result of mixing the 20 bits of the x & y co-ordinates // 4 bits of x & y are used per byte of output unsigned char i, hash[5]; for (i = 0; i < 5; i++) { hash[i] = ((x & 0x0f) << 4) | (y & 0x0f); x >>= 4; y >>= 4; } snprintf(path, len, "%s/%s/%d/%u/%u/%u/%u/%u.png", tile_dir, xmlconfig, z, hash[4], hash[3], hash[2], hash[1], hash[0]); #else // DIRECTORY_HASH snprintf(path, len, TILE_PATH "/%s/%d/%d/%d.png", xmlconfig, z, x, y); #endif // DIRECTORY_HASH return; } #endif // METATILE mod_tile-0.8.0/src/store_memcached.c000066400000000000000000000214741474064163400174110ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ /* Meta-tile optimised file storage * * Instead of storing each individual tile as a file, * bundle the 8x8 meta tile into a special meta-file. * This reduces the Inode usage and more efficient * utilisation of disk space. */ #include "config.h" #include #include #include #include #include #include #include #ifdef HAVE_LIBMEMCACHED #include #endif #include "store.h" #include "metatile.h" #include "render_config.h" #include "protocol.h" #include "g_logger.h" #ifdef HAVE_LIBMEMCACHED static char * memcached_xyzo_to_storagekey(const char *xmlconfig, const char *options, int x, int y, int z, char * key) { int mask; mask = METATILE - 1; x &= ~mask; y &= ~mask; if (strlen(options)) { snprintf(key, PATH_MAX - 1, "%s/%d/%d/%d.%s.meta", xmlconfig, x, y, z, options); } else { snprintf(key, PATH_MAX - 1, "%s/%d/%d/%d.meta", xmlconfig, x, y, z); } return key; } static char * memcached_xyz_to_storagekey(const char *xmlconfig, int x, int y, int z, char * key) { return memcached_xyzo_to_storagekey(xmlconfig, "", x, y, z, key); } static int memcached_tile_read(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, char *buf, size_t sz, int * compressed, char * log_msg) { char meta_path[PATH_MAX]; int meta_offset; unsigned int header_len = sizeof(struct meta_layout) + METATILE * METATILE * sizeof(struct entry); struct meta_layout *m = (struct meta_layout *)malloc(header_len); size_t file_offset, tile_size; int mask; uint32_t flags; size_t len; memcached_return_t rc; char * buf_raw; mask = METATILE - 1; meta_offset = (x & mask) * METATILE + (y & mask); memcached_xyzo_to_storagekey(xmlconfig, options, x, y, z, meta_path); buf_raw = memcached_get(store->storage_ctx, meta_path, strlen(meta_path), &len, &flags, &rc); if (rc != MEMCACHED_SUCCESS) { free(m); return -1; } memcpy(m, buf_raw + sizeof(struct stat_info), header_len); if (memcmp(m->magic, META_MAGIC, strlen(META_MAGIC))) { if (memcmp(m->magic, META_MAGIC_COMPRESSED, strlen(META_MAGIC_COMPRESSED))) { snprintf(log_msg, 1024, "Meta file header magic mismatch\n"); free(m); return -4; } else { *compressed = 1; } } else { *compressed = 0; } // Currently this code only works with fixed metatile sizes (due to xyz_to_meta above) if (m->count != (METATILE * METATILE)) { snprintf(log_msg, 1024, "Meta file header bad count %d != %d\n", m->count, METATILE * METATILE); free(m); return -5; } file_offset = m->index[meta_offset].offset + sizeof(struct stat_info); tile_size = m->index[meta_offset].size; free(m); if (tile_size > sz) { snprintf(log_msg, 1024, "Truncating tile %zd to fit buffer of %zd\n", tile_size, sz); tile_size = sz; return -6; } memcpy(buf, buf_raw + file_offset, tile_size); free(buf_raw); return tile_size; } static struct stat_info memcached_tile_stat(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z) { struct stat_info tile_stat; char meta_path[PATH_MAX]; unsigned int header_len = sizeof(struct meta_layout) + METATILE * METATILE * sizeof(struct entry); struct meta_layout *m = (struct meta_layout *)malloc(header_len); char * buf; size_t len; uint32_t flags; memcached_return_t rc; int offset, mask; mask = METATILE - 1; offset = (x & mask) * METATILE + (y & mask); memcached_xyzo_to_storagekey(xmlconfig, options, x, y, z, meta_path); buf = memcached_get(store->storage_ctx, meta_path, strlen(meta_path), &len, &flags, &rc); if (rc != MEMCACHED_SUCCESS) { tile_stat.size = -1; tile_stat.expired = 0; tile_stat.mtime = 0; tile_stat.atime = 0; tile_stat.ctime = 0; free(m); return tile_stat; } memcpy(&tile_stat, buf, sizeof(struct stat_info)); memcpy(m, buf + sizeof(struct stat_info), header_len); tile_stat.size = m->index[offset].size; free(m); free(buf); return tile_stat; } static char * memcached_tile_storage_id(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, char * string) { snprintf(string, PATH_MAX - 1, "memcached:///%s/%d/%d/%d.meta", xmlconfig, x, y, z); return string; } static int memcached_metatile_write(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, const char *buf, int sz) { char meta_path[PATH_MAX]; char tmp[PATH_MAX]; struct stat_info tile_stat; int sz2 = sz + sizeof(struct stat_info); char * buf2 = malloc(sz2); memcached_return_t rc; if (buf2 == NULL) { return -2; } tile_stat.expired = 0; tile_stat.size = sz; tile_stat.mtime = time(NULL); tile_stat.atime = tile_stat.mtime; tile_stat.ctime = tile_stat.mtime; memcpy(buf2, &tile_stat, sizeof(tile_stat)); memcpy(buf2 + sizeof(tile_stat), buf, sz); g_logger(G_LOG_LEVEL_DEBUG, "Trying to create and write a metatile to %s", memcached_tile_storage_id(store, xmlconfig, options, x, y, z, tmp)); snprintf(meta_path, PATH_MAX - 1, "%s/%d/%d/%d.meta", xmlconfig, x, y, z); rc = memcached_set(store->storage_ctx, meta_path, strlen(meta_path), buf2, sz2, (time_t)0, (uint32_t)0); free(buf2); if (rc != MEMCACHED_SUCCESS) { return -1; } memcached_flush_buffers(store->storage_ctx); return sz; } static int memcached_metatile_delete(struct storage_backend * store, const char *xmlconfig, int x, int y, int z) { char meta_path[PATH_MAX]; memcached_return_t rc; //TODO: deal with options memcached_xyz_to_storagekey(xmlconfig, x, y, z, meta_path); rc = memcached_delete(store->storage_ctx, meta_path, strlen(meta_path), 0); if (rc != MEMCACHED_SUCCESS) { return -1; } return 0; } static int memcached_metatile_expire(struct storage_backend * store, const char *xmlconfig, int x, int y, int z) { char meta_path[PATH_MAX]; char * buf; size_t len; uint32_t flags; uint64_t cas; memcached_return_t rc; //TODO: deal with options memcached_xyz_to_storagekey(xmlconfig, x, y, z, meta_path); buf = memcached_get(store->storage_ctx, meta_path, strlen(meta_path), &len, &flags, &rc); if (rc != MEMCACHED_SUCCESS) { return -1; } //cas = memcached_result_cas(&rc); ((struct stat_info *)buf)->expired = 1; rc = memcached_cas(store->storage_ctx, meta_path, strlen(meta_path), buf, len, 0, flags, cas); if (rc != MEMCACHED_SUCCESS) { free(buf); return -1; } free(buf); return 0; } static int memcached_close_storage(struct storage_backend * store) { memcached_free(store->storage_ctx); return 0; } #endif //Have memcached struct storage_backend * init_storage_memcached(const char * connection_string) { #ifndef HAVE_LIBMEMCACHED g_logger(G_LOG_LEVEL_ERROR, "init_storage_memcached: Support for memcached has not been compiled into this program"); return NULL; #else struct storage_backend * store = malloc(sizeof(struct storage_backend)); memcached_st * ctx; char * connection_str = "--server=localhost"; if (store == NULL) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_memcached: Failed to allocate memory for storage backend"); return NULL; } if (strcmp(connection_string, "memcached://")) { int connection_string_len = strnlen(connection_string, PATH_MAX); connection_str = malloc(sizeof(char) * connection_string_len); // The length of the string "memcached://" is 12 snprintf(connection_str, connection_string_len - 2, "--server=%.*s", connection_string_len - 12, connection_string + 12); } g_logger(G_LOG_LEVEL_DEBUG, "init_storage_memcached: Creating memcached ctx with options '%s'", connection_str); ctx = memcached(connection_str, strlen(connection_str)); if (strcmp(connection_string, "memcached://")) { free(connection_str); } if (ctx == NULL) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_memcached: Failed to create memcached ctx"); free(store); return NULL; } store->storage_ctx = ctx; store->tile_read = &memcached_tile_read; store->tile_stat = &memcached_tile_stat; store->metatile_write = &memcached_metatile_write; store->metatile_delete = &memcached_metatile_delete; store->metatile_expire = &memcached_metatile_expire; store->tile_storage_id = &memcached_tile_storage_id; store->close_storage = &memcached_close_storage; return store; #endif } mod_tile-0.8.0/src/store_null.c000066400000000000000000000054651474064163400164570ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include "g_logger.h" #include "store.h" #include "store_null.h" static int tile_read(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, char *buf, size_t sz, int * compressed, char * err_msg) { snprintf(err_msg, PATH_MAX - 1, "Cannot read from NULL storage."); return -1; } static struct stat_info tile_stat(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z) { struct stat_info tile_stat; tile_stat.size = -1; tile_stat.atime = 0; tile_stat.mtime = 0; tile_stat.ctime = 0; tile_stat.expired = 1; return tile_stat; } static int metatile_write(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, const char *buf, int sz) { // fake like we actually wrote the tile, but we didn't... return sz; } static int metatile_delete(struct storage_backend * store, const char *xmlconfig, int x, int y, int z) { return 0; } static int metatile_expire(struct storage_backend * store, const char *xmlconfig, int x, int y, int z) { return 0; } static char * tile_storage_id(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, char * string) { snprintf(string, PATH_MAX - 1, "null://"); return string; } static int close_storage(struct storage_backend * store) { return 0; } struct storage_backend *init_storage_null() { struct storage_backend *store = malloc(sizeof * store); if (store == NULL) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_null: Failed to allocate memory for storage backend"); return NULL; } store->storage_ctx = NULL; store->tile_read = &tile_read; store->tile_stat = &tile_stat; store->metatile_write = &metatile_write; store->metatile_delete = &metatile_delete; store->metatile_expire = &metatile_expire; store->tile_storage_id = &tile_storage_id; store->close_storage = &close_storage; return store; } mod_tile-0.8.0/src/store_rados.c000066400000000000000000000302311474064163400166020ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ /* Meta-tile optimised file storage * * Instead of storing each individual tile as a file, * bundle the 8x8 meta tile into a special meta-file. * This reduces the Inode usage and more efficient * utilisation of disk space. */ #include "config.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBRADOS #include #endif #include "store.h" #include "store_rados.h" #include "metatile.h" #include "render_config.h" #include "protocol.h" #include "g_logger.h" #ifdef HAVE_LIBRADOS static pthread_mutex_t qLock; struct metadata_cache { char * data; int x, y, z; char xmlname[XMLCONFIG_MAX]; }; struct rados_ctx { char * pool; rados_t cluster; rados_ioctx_t io; struct metadata_cache metadata_cache; }; static char * rados_xyzo_to_storagekey(const char *xmlconfig, const char *options, int x, int y, int z, char * key) { int mask; mask = METATILE - 1; x &= ~mask; y &= ~mask; if (strlen(options)) { snprintf(key, PATH_MAX - 1, "%s/%d/%d/%d.%s.meta", xmlconfig, z, x, y, options); } else { snprintf(key, PATH_MAX - 1, "%s/%d/%d/%d.meta", xmlconfig, z, x, y); } return key; } static char * read_meta_data(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z) { int mask; int err; char meta_path[PATH_MAX]; struct rados_ctx * ctx = (struct rados_ctx *)store->storage_ctx; unsigned int header_len = sizeof(struct stat_info) + sizeof(struct meta_layout) + METATILE * METATILE * sizeof(struct entry); mask = METATILE - 1; x &= ~mask; y &= ~mask; if ((ctx->metadata_cache.x == x) && (ctx->metadata_cache.y == y) && (ctx->metadata_cache.z == z) && (strcmp(ctx->metadata_cache.xmlname, xmlconfig) == 0)) { g_logger(G_LOG_LEVEL_DEBUG, "Returning cached data for %s %i %i %i", ctx->metadata_cache.xmlname, ctx->metadata_cache.x, ctx->metadata_cache.y, ctx->metadata_cache.z); return ctx->metadata_cache.data; } else { g_logger(G_LOG_LEVEL_DEBUG, "Retrieving fresh metadata"); rados_xyzo_to_storagekey(xmlconfig, options, x, y, z, meta_path); err = rados_read(ctx->io, meta_path, ctx->metadata_cache.data, header_len, 0); if (err < 0) { if (-err == ENOENT) { g_logger(G_LOG_LEVEL_DEBUG, "cannot read data from rados pool %s: %s", ctx->pool, strerror(-err)); } else { g_logger(G_LOG_LEVEL_ERROR, "cannot read data from rados pool %s: %s", ctx->pool, strerror(-err)); } ctx->metadata_cache.x = -1; ctx->metadata_cache.y = -1; ctx->metadata_cache.z = -1; return NULL; } ctx->metadata_cache.x = x; ctx->metadata_cache.y = y; ctx->metadata_cache.z = z; strncpy(ctx->metadata_cache.xmlname, xmlconfig, XMLCONFIG_MAX - 1); return ctx->metadata_cache.data; } } static int rados_tile_read(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, char *buf, size_t sz, int * compressed, char * log_msg) { char meta_path[PATH_MAX]; int meta_offset; unsigned int header_len = sizeof(struct meta_layout) + METATILE * METATILE * sizeof(struct entry); struct meta_layout *m = (struct meta_layout *)malloc(header_len); size_t file_offset, tile_size; int mask; int err; char * buf_raw; mask = METATILE - 1; meta_offset = (x & mask) * METATILE + (y & mask); rados_xyzo_to_storagekey(xmlconfig, options, x, y, z, meta_path); buf_raw = read_meta_data(store, xmlconfig, options, x, y, z); if (buf_raw == NULL) { snprintf(log_msg, 1024, "Failed to read metadata of tile\n"); free(m); return -3; } memcpy(m, buf_raw + sizeof(struct stat_info), header_len); if (memcmp(m->magic, META_MAGIC, strlen(META_MAGIC))) { if (memcmp(m->magic, META_MAGIC_COMPRESSED, strlen(META_MAGIC_COMPRESSED))) { snprintf(log_msg, 1024, "Meta file header magic mismatch\n"); free(m); return -4; } else { *compressed = 1; } } else { *compressed = 0; } // Currently this code only works with fixed metatile sizes (due to xyz_to_meta above) if (m->count != (METATILE * METATILE)) { snprintf(log_msg, 1024, "Meta file header bad count %d != %d\n", m->count, METATILE * METATILE); free(m); return -5; } file_offset = m->index[meta_offset].offset + sizeof(struct stat_info); tile_size = m->index[meta_offset].size; free(m); if (tile_size > sz) { snprintf(log_msg, 1024, "Truncating tile %zd to fit buffer of %zd\n", tile_size, sz); tile_size = sz; return -6; } err = rados_read(((struct rados_ctx *)store->storage_ctx)->io, meta_path, buf, tile_size, file_offset); if (err < 0) { snprintf(log_msg, 1024, "Failed to read tile data from rados %s offset: %li length: %li: %s\n", meta_path, file_offset, tile_size, strerror(-err)); return -1; } return tile_size; } static struct stat_info rados_tile_stat(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z) { struct stat_info tile_stat; char * buf; int offset, mask; mask = METATILE - 1; offset = (x & mask) * METATILE + (y & mask); buf = read_meta_data(store, xmlconfig, options, x, y, z); if (buf == NULL) { tile_stat.size = -1; tile_stat.expired = 0; tile_stat.mtime = 0; tile_stat.atime = 0; tile_stat.ctime = 0; return tile_stat; } memcpy(&tile_stat, buf, sizeof(struct stat_info)); tile_stat.size = ((struct meta_layout *)(buf + sizeof(struct stat_info)))->index[offset].size; return tile_stat; } static char * rados_tile_storage_id(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, char * string) { char meta_path[PATH_MAX]; rados_xyzo_to_storagekey(xmlconfig, options, x, y, z, meta_path); snprintf(string, PATH_MAX - 1, "rados://%s/%s", ((struct rados_ctx *)(store->storage_ctx))->pool, meta_path); return string; } static int rados_metatile_write(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, const char *buf, int sz) { char meta_path[PATH_MAX]; char tmp[PATH_MAX]; struct stat_info tile_stat; int sz2 = sz + sizeof(struct stat_info); char * buf2 = malloc(sz2); int err; tile_stat.expired = 0; tile_stat.size = sz; tile_stat.mtime = time(NULL); tile_stat.atime = tile_stat.mtime; tile_stat.ctime = tile_stat.mtime; memcpy(buf2, &tile_stat, sizeof(tile_stat)); memcpy(buf2 + sizeof(tile_stat), buf, sz); rados_xyzo_to_storagekey(xmlconfig, options, x, y, z, meta_path); g_logger(G_LOG_LEVEL_DEBUG, "Trying to create and write a tile to %s", rados_tile_storage_id(store, xmlconfig, options, x, y, z, tmp)); err = rados_write_full(((struct rados_ctx *)store->storage_ctx)->io, meta_path, buf2, sz2); if (err < 0) { g_logger(G_LOG_LEVEL_ERROR, "cannot write %s: %s", rados_tile_storage_id(store, xmlconfig, options, x, y, z, tmp), strerror(-err)); free(buf2); return -1; } free(buf2); return sz; } static int rados_metatile_delete(struct storage_backend * store, const char *xmlconfig, int x, int y, int z) { struct rados_ctx * ctx = (struct rados_ctx *)store->storage_ctx; char meta_path[PATH_MAX]; char tmp[PATH_MAX]; int err; //TODO: deal with options const char *options = ""; rados_xyzo_to_storagekey(xmlconfig, options, x, y, z, meta_path); err = rados_remove(ctx->io, meta_path); if (err < 0) { g_logger(G_LOG_LEVEL_ERROR, "failed to delete %s: %s", rados_tile_storage_id(store, xmlconfig, options, x, y, z, tmp), strerror(-err)); return -1; } return 0; } static int rados_metatile_expire(struct storage_backend * store, const char *xmlconfig, int x, int y, int z) { struct stat_info tile_stat; struct rados_ctx * ctx = (struct rados_ctx *)store->storage_ctx; char meta_path[PATH_MAX]; char tmp[PATH_MAX]; int err; //TODO: deal with options const char *options = ""; rados_xyzo_to_storagekey(xmlconfig, options, x, y, z, meta_path); err = rados_read(ctx->io, meta_path, (char *)&tile_stat, sizeof(struct stat_info), 0); if (err < 0) { if (-err == ENOENT) { g_logger(G_LOG_LEVEL_DEBUG, "Tile %s does not exist, can't expire", rados_tile_storage_id(store, xmlconfig, options, x, y, z, tmp)); return -1; } else { g_logger(G_LOG_LEVEL_ERROR, "Failed to read tile metadata for %s: %s", rados_tile_storage_id(store, xmlconfig, options, x, y, z, tmp), strerror(-err)); } return -2; } tile_stat.expired = 1; err = rados_write(ctx->io, meta_path, (char *)&tile_stat, sizeof(struct stat_info), 0); if (err < 0) { g_logger(G_LOG_LEVEL_ERROR, "failed to write expiry data for %s: %s", rados_tile_storage_id(store, xmlconfig, options, x, y, z, tmp), strerror(-err)); return -3; } return 0; } static int rados_close_storage(struct storage_backend * store) { struct rados_ctx * ctx = (struct rados_ctx *)store->storage_ctx; rados_ioctx_destroy(ctx->io); rados_shutdown(ctx->cluster); g_logger(G_LOG_LEVEL_DEBUG, "rados_close_storage: Closed rados backend"); free(ctx->metadata_cache.data); free(ctx->pool); free(ctx); return 0; } #endif //Have rados struct storage_backend * init_storage_rados(const char * connection_string) { #ifndef HAVE_LIBRADOS g_logger(G_LOG_LEVEL_ERROR, "init_storage_rados: Support for rados has not been compiled into this program"); return NULL; #else struct rados_ctx * ctx = malloc(sizeof(struct rados_ctx)); struct storage_backend * store = malloc(sizeof(struct storage_backend)); char * conf = NULL; const char * tmp; int err; int i; if (ctx == NULL) { free(ctx); free(store); return NULL; } tmp = &(connection_string[strlen("rados://")]); i = 0; while ((tmp[i] != '/') && (tmp[i] != 0)) { i++; } ctx->pool = calloc(i + 1, sizeof(char)); memcpy(ctx->pool, tmp, i * sizeof(char)); conf = strdup(&(tmp[i])); err = rados_create(&(ctx->cluster), NULL); if (err < 0) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_rados: cannot create a cluster handle: %s", strerror(-err)); free(ctx); free(store); return NULL; } err = rados_conf_read_file(ctx->cluster, conf); if (err < 0) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_rados: failed to read rados config file %s: %s", conf, strerror(-err)); free(ctx); free(store); return NULL; } pthread_mutex_lock(&qLock); err = rados_connect(ctx->cluster); pthread_mutex_unlock(&qLock); if (err < 0) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_rados: failed to connect to rados cluster: %s", strerror(-err)); free(ctx); free(store); return NULL; } err = rados_ioctx_create(ctx->cluster, ctx->pool, &(ctx->io)); if (err < 0) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_rados: failed to initialise rados io context to pool %s: %s", ctx->pool, strerror(-err)); rados_shutdown(ctx->cluster); free(ctx); free(store); return NULL; } g_logger(G_LOG_LEVEL_DEBUG, "init_storage_rados: Initialised rados backend for pool %s with config %s", ctx->pool, conf); ctx->metadata_cache.data = malloc(sizeof(struct stat_info) + sizeof(struct meta_layout) + METATILE * METATILE * sizeof(struct entry)); if (ctx->metadata_cache.data == NULL) { rados_ioctx_destroy(ctx->io); rados_shutdown(ctx->cluster); free(ctx); free(store); return NULL; } free(conf); ctx->metadata_cache.x = -1; ctx->metadata_cache.y = -1; ctx->metadata_cache.z = -1; ctx->metadata_cache.xmlname[0] = 0; store->storage_ctx = ctx; store->tile_read = &rados_tile_read; store->tile_stat = &rados_tile_stat; store->metatile_write = &rados_metatile_write; store->metatile_delete = &rados_metatile_delete; store->metatile_expire = &rados_metatile_expire; store->tile_storage_id = &rados_tile_storage_id; store->close_storage = &rados_close_storage; return store; #endif } mod_tile-0.8.0/src/store_ro_composite.c000066400000000000000000000233341474064163400202020ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include "config.h" #include #include #include #include //TODO: need to create an appropriate configure check. #ifdef HAVE_CAIRO #define WANT_STORE_COMPOSITE #endif #ifdef WANT_STORE_COMPOSITE #include #endif #include "store.h" #include "store_ro_composite.h" #include "render_config.h" #include "protocol.h" #include "g_logger.h" #ifdef WANT_STORE_COMPOSITE struct tile_cache { struct stat_info st_stat; char * tile; int x, y, z; char xmlname[XMLCONFIG_MAX]; }; struct ro_composite_ctx { struct storage_backend * store_primary; char xmlconfig_primary[XMLCONFIG_MAX]; struct storage_backend * store_secondary; char xmlconfig_secondary[XMLCONFIG_MAX]; struct tile_cache cache; int render_size; }; typedef struct { char *data; unsigned int max_size; unsigned int pos; } png_stream_to_byte_array_closure_t; static cairo_status_t write_png_stream_to_byte_array(void *in_closure, const unsigned char *data, unsigned int length) { png_stream_to_byte_array_closure_t *closure = (png_stream_to_byte_array_closure_t *) in_closure; g_logger(G_LOG_LEVEL_DEBUG, "ro_composite_tile: writing to byte array: pos: %i, length: %i", closure->pos, length); if ((closure->pos + length) > (closure->max_size)) { return CAIRO_STATUS_WRITE_ERROR; } memcpy((closure->data + closure->pos), data, length); closure->pos += length; return CAIRO_STATUS_SUCCESS; } static cairo_status_t read_png_stream_from_byte_array(void *in_closure, unsigned char *data, unsigned int length) { png_stream_to_byte_array_closure_t *closure = (png_stream_to_byte_array_closure_t *) in_closure; g_logger(G_LOG_LEVEL_DEBUG, "ro_composite_tile: reading from byte array: pos: %i, length: %i", closure->pos, length); if ((closure->pos + length) > (closure->max_size)) { return CAIRO_STATUS_READ_ERROR; } memcpy(data, (closure->data + closure->pos), length); closure->pos += length; return CAIRO_STATUS_SUCCESS; } static int ro_composite_tile_read(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, char *buf, size_t sz, int * compressed, char * log_msg) { struct ro_composite_ctx * ctx = (struct ro_composite_ctx *)(store->storage_ctx); cairo_surface_t *imageA; cairo_surface_t *imageB; cairo_surface_t *imageC; cairo_t *cr; png_stream_to_byte_array_closure_t closure; if (ctx->store_primary->tile_read(ctx->store_primary, ctx->xmlconfig_primary, options, x, y, z, buf, sz, compressed, log_msg) < 0) { snprintf(log_msg, 1024, "ro_composite_tile_read: Failed to read tile data of primary backend\n"); return -1; } closure.data = buf; closure.pos = 0; closure.max_size = sz; imageA = cairo_image_surface_create_from_png_stream(&read_png_stream_from_byte_array, &closure); if (!imageA) { snprintf(log_msg, 1024, "ro_composite_tile_read: Failed to decode png data from primary backend\n"); return -1; } if (ctx->store_secondary->tile_read(ctx->store_secondary, ctx->xmlconfig_secondary, options, x, y, z, buf, sz, compressed, log_msg) < 0) { snprintf(log_msg, 1024, "ro_composite_tile_read: Failed to read tile data of secondary backend\n"); cairo_surface_destroy(imageA); return -1; } closure.data = buf; closure.pos = 0; closure.max_size = sz; imageB = cairo_image_surface_create_from_png_stream(&read_png_stream_from_byte_array, &closure); if (!imageB) { snprintf(log_msg, 1024, "ro_composite_tile_read: Failed to decode png data from secondary backend\n"); cairo_surface_destroy(imageA); return -1; } imageC = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ctx->render_size, ctx->render_size); if (!imageC) { snprintf(log_msg, 1024, "ro_composite_tile_read: Failed to create output png\n"); cairo_surface_destroy(imageA); cairo_surface_destroy(imageB); return -1; } //Create the cairo context cr = cairo_create(imageC); cairo_set_source_surface(cr, imageA, 0, 0); cairo_paint(cr); cairo_set_source_surface(cr, imageB, 0, 0); cairo_paint(cr); cairo_surface_flush(imageC); cairo_destroy(cr); closure.data = buf; closure.pos = 0; closure.max_size = sz; cairo_surface_write_to_png_stream(imageC, &write_png_stream_to_byte_array, &closure); cairo_surface_destroy(imageA); cairo_surface_destroy(imageB); cairo_surface_destroy(imageC); return closure.pos; } static struct stat_info ro_composite_tile_stat(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z) { struct ro_composite_ctx * ctx = (struct ro_composite_ctx *)(store->storage_ctx); return ctx->store_primary->tile_stat(ctx->store_primary, ctx->xmlconfig_primary, options, x, y, z); } static char * ro_composite_tile_storage_id(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, char * string) { return "Coposite tile"; } static int ro_composite_metatile_write(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, const char *buf, int sz) { g_logger(G_LOG_LEVEL_ERROR, "ro_composite_metatile_write: This is a readonly storage backend. Write functionality isn't implemented"); return -1; } static int ro_composite_metatile_delete(struct storage_backend * store, const char *xmlconfig, int x, int y, int z) { g_logger(G_LOG_LEVEL_ERROR, "ro_composite_metatile_expire: This is a readonly storage backend. Write functionality isn't implemented"); return -1; } static int ro_composite_metatile_expire(struct storage_backend * store, const char *xmlconfig, int x, int y, int z) { g_logger(G_LOG_LEVEL_ERROR, "ro_composite_metatile_expire: This is a readonly storage backend. Write functionality isn't implemented"); return -1; } static int ro_composite_close_storage(struct storage_backend * store) { struct ro_composite_ctx * ctx = (struct ro_composite_ctx *)(store->storage_ctx); ctx->store_primary->close_storage(ctx->store_primary); ctx->store_secondary->close_storage(ctx->store_secondary); if (ctx->cache.tile) { free(ctx->cache.tile); } free(ctx); free(store); return 0; } #endif //WANT_COMPOSITE struct storage_backend * init_storage_ro_composite(const char * connection_string) { #ifndef WANT_STORE_COMPOSITE g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_coposite: Support for compositing storage has not been compiled into this program"); return NULL; #else struct storage_backend * store = malloc(sizeof(struct storage_backend)); struct ro_composite_ctx * ctx = malloc(sizeof(struct ro_composite_ctx)); char * connection_string_primary; char * connection_string_secondary; char * tmp; g_logger(G_LOG_LEVEL_DEBUG, "init_storage_ro_composite: initialising compositing storage backend for %s", connection_string); if (!store || !ctx) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_composite: failed to allocate memory for context"); if (store) { free(store); } if (ctx) { free(ctx); } return NULL; } connection_string_secondary = strstr(connection_string, "}{"); connection_string_primary = malloc(strlen(connection_string) - strlen("composite:{") - strlen(connection_string_secondary) + 1); memcpy(connection_string_primary, connection_string + strlen("composite:{"), strlen(connection_string) - strlen("composite:{") - strlen(connection_string_secondary)); connection_string_primary[strlen(connection_string) - strlen("composite:{") - strlen(connection_string_secondary)] = 0; connection_string_secondary = strdup(connection_string_secondary + 2); connection_string_secondary[strlen(connection_string_secondary) - 1] = 0; g_logger(G_LOG_LEVEL_DEBUG, "init_storage_ro_composite: Primary storage backend: %s", connection_string_primary); g_logger(G_LOG_LEVEL_DEBUG, "init_storage_ro_composite: Secondary storage backend: %s", connection_string_secondary); tmp = strstr(connection_string_primary, ","); memcpy(ctx->xmlconfig_primary, connection_string_primary, tmp - connection_string_primary); ctx->xmlconfig_primary[tmp - connection_string_primary] = 0; ctx->store_primary = init_storage_backend(tmp + 1); if (ctx->store_primary == NULL) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_composite: failed to initialise primary storage backend"); free(ctx); free(store); return NULL; } tmp = strstr(connection_string_secondary, ","); memcpy(ctx->xmlconfig_secondary, connection_string_secondary, tmp - connection_string_secondary); ctx->xmlconfig_secondary[tmp - connection_string_secondary] = 0; ctx->store_secondary = init_storage_backend(tmp + 1); if (ctx->store_secondary == NULL) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_composite: failed to initialise secondary storage backend"); ctx->store_primary->close_storage(ctx->store_primary); free(ctx); free(store); return NULL; } ctx->render_size = 256; store->storage_ctx = ctx; store->tile_read = &ro_composite_tile_read; store->tile_stat = &ro_composite_tile_stat; store->metatile_write = &ro_composite_metatile_write; store->metatile_delete = &ro_composite_metatile_delete; store->metatile_expire = &ro_composite_metatile_expire; store->tile_storage_id = &ro_composite_tile_storage_id; store->close_storage = &ro_composite_close_storage; return store; #endif } mod_tile-0.8.0/src/store_ro_http_proxy.c000066400000000000000000000224671474064163400204260ustar00rootroot00000000000000/* * Copyright (c) 2007 - 2023 by mod_tile contributors (see AUTHORS file) * * This program 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 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; If not, see http://www.gnu.org/licenses/. */ #include "config.h" #include #include #include #include #include #include #include #ifdef HAVE_LIBCURL #include #include #endif #include "store.h" #include "store_ro_http_proxy.h" #include "render_config.h" #include "protocol.h" #include "g_logger.h" #ifdef HAVE_LIBCURL static pthread_mutex_t qLock; static int done_global_init = 0; struct tile_cache { struct stat_info st_stat; char * tile; int x, y, z; char xmlname[XMLCONFIG_MAX]; }; struct ro_http_proxy_ctx { CURL * ctx; char * baseurl; struct tile_cache cache; }; struct MemoryStruct { char *memory; size_t size; }; static size_t write_memory_callback(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; struct MemoryStruct * chunk = userp; if (chunk->memory) { chunk->memory = realloc(chunk->memory, chunk->size + realsize); } else { chunk->memory = malloc(realsize); } g_logger(G_LOG_LEVEL_DEBUG, "ro_http_proxy_tile_read: writing a chunk: Position %lu, size %lu", chunk->size, realsize); memcpy(&(chunk->memory[chunk->size]), contents, realsize); chunk->size += realsize; return realsize; } static char * ro_http_proxy_xyz_to_storagekey(struct storage_backend * store, int x, int y, int z, char * key) { snprintf(key, PATH_MAX - 1, "http://%s/%i/%i/%i.png", ((struct ro_http_proxy_ctx *)(store->storage_ctx))->baseurl, z, x, y); return key; } static int ro_http_proxy_tile_retrieve(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z) { struct ro_http_proxy_ctx * ctx = (struct ro_http_proxy_ctx *)(store->storage_ctx); char * path; CURLcode res; struct MemoryStruct chunk; long httpCode; //TODO: Deal with options if ((ctx->cache.x == x) && (ctx->cache.y == y) && (ctx->cache.z == z) && (strcmp(ctx->cache.xmlname, xmlconfig) == 0)) { g_logger(G_LOG_LEVEL_DEBUG, "ro_http_proxy_tile_fetch: Got a cached tile"); return 1; } else { g_logger(G_LOG_LEVEL_DEBUG, "ro_http_proxy_tile_fetch: Fetching tile"); chunk.memory = NULL; chunk.size = 0; path = malloc(PATH_MAX); ro_http_proxy_xyz_to_storagekey(store, x, y, z, path); g_logger(G_LOG_LEVEL_DEBUG, "ro_http_proxy_tile_fetch: proxing file %s", path); curl_easy_setopt(ctx->ctx, CURLOPT_URL, path); curl_easy_setopt(ctx->ctx, CURLOPT_WRITEFUNCTION, write_memory_callback); curl_easy_setopt(ctx->ctx, CURLOPT_WRITEDATA, (void *)&chunk); res = curl_easy_perform(ctx->ctx); free(path); if (res != CURLE_OK) { g_logger(G_LOG_LEVEL_ERROR, "ro_http_proxy_tile_fetch: failed to retrieve file: %s", curl_easy_strerror(res)); ctx->cache.x = -1; ctx->cache.y = -1; ctx->cache.z = -1; return -1; } res = curl_easy_getinfo(ctx->ctx, CURLINFO_RESPONSE_CODE, &httpCode); if (res != CURLE_OK) { g_logger(G_LOG_LEVEL_ERROR, "ro_http_proxy_tile_fetch: failed to retrieve HTTP code: %s", curl_easy_strerror(res)); ctx->cache.x = -1; ctx->cache.y = -1; ctx->cache.z = -1; return -1; } switch (httpCode) { case 200: { if (ctx->cache.tile != NULL) { free(ctx->cache.tile); } ctx->cache.tile = chunk.memory; ctx->cache.st_stat.size = chunk.size; ctx->cache.st_stat.expired = 0; res = curl_easy_getinfo(ctx->ctx, CURLINFO_FILETIME, &(ctx->cache.st_stat.mtime)); ctx->cache.st_stat.atime = 0; g_logger(G_LOG_LEVEL_DEBUG, "ro_http_proxy_tile_read: Read file of size %lu", chunk.size); break; } case 404: { if (ctx->cache.tile != NULL) { free(ctx->cache.tile); } ctx->cache.st_stat.size = -1; ctx->cache.st_stat.expired = 0; break; } } ctx->cache.x = x; ctx->cache.y = y; ctx->cache.z = z; strcpy(ctx->cache.xmlname, xmlconfig); return 1; } } static int ro_http_proxy_tile_read(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, char *buf, size_t sz, int * compressed, char * log_msg) { struct ro_http_proxy_ctx * ctx = (struct ro_http_proxy_ctx *)(store->storage_ctx); if (ro_http_proxy_tile_retrieve(store, xmlconfig, options, x, y, z) > 0) { if (ctx->cache.st_stat.size > sz) { g_logger(G_LOG_LEVEL_ERROR, "ro_http_proxy_tile_read: size was too big, overrun %lu %li", sz, ctx->cache.st_stat.size); return -1; } memcpy(buf, ctx->cache.tile, ctx->cache.st_stat.size); return ctx->cache.st_stat.size; } else { g_logger(G_LOG_LEVEL_ERROR, "ro_http_proxy_tile_read: Fetching didn't work"); return -1; } } static struct stat_info ro_http_proxy_tile_stat(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z) { struct stat_info tile_stat; struct ro_http_proxy_ctx * ctx = (struct ro_http_proxy_ctx *)(store->storage_ctx); if (ro_http_proxy_tile_retrieve(store, xmlconfig, options, x, y, z) > 0) { return ctx->cache.st_stat; } else { tile_stat.size = -1; tile_stat.expired = 0; tile_stat.mtime = 0; tile_stat.atime = 0; tile_stat.ctime = 0; return tile_stat; } } static char * ro_http_proxy_tile_storage_id(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, char * string) { return ro_http_proxy_xyz_to_storagekey(store, x, y, z, string); } static int ro_http_proxy_metatile_write(struct storage_backend * store, const char *xmlconfig, const char *options, int x, int y, int z, const char *buf, int sz) { g_logger(G_LOG_LEVEL_ERROR, "ro_http_proxy_metatile_write: This is a readonly storage backend. Write functionality isn't implemented"); return -1; } static int ro_http_proxy_metatile_delete(struct storage_backend * store, const char *xmlconfig, int x, int y, int z) { g_logger(G_LOG_LEVEL_ERROR, "ro_http_proxy_metatile_delete: This is a readonly storage backend. Write functionality isn't implemented"); return -1; } static int ro_http_proxy_metatile_expire(struct storage_backend * store, const char *xmlconfig, int x, int y, int z) { g_logger(G_LOG_LEVEL_ERROR, "ro_http_proxy_metatile_expire: This is a readonly storage backend. Write functionality isn't implemented"); return -1; } static int ro_http_proxy_close_storage(struct storage_backend * store) { struct ro_http_proxy_ctx * ctx = (struct ro_http_proxy_ctx *)(store->storage_ctx); free(ctx->baseurl); if (ctx->cache.tile) { free(ctx->cache.tile); } curl_easy_cleanup(ctx->ctx); free(ctx); free(store); return 0; } #endif //Have curl struct storage_backend * init_storage_ro_http_proxy(const char * connection_string) { #ifndef HAVE_LIBCURL g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_http_proxy: Support for curl and therefore the http proxy storage has not been compiled into this program"); return NULL; #else struct storage_backend * store = malloc(sizeof(struct storage_backend)); struct ro_http_proxy_ctx * ctx = malloc(sizeof(struct ro_http_proxy_ctx)); CURLcode res; g_logger(G_LOG_LEVEL_DEBUG, "init_storage_ro_http_proxy: initialising proxy storage backend for %s", connection_string); if (!store || !ctx) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_http_proxy: failed to allocate memory for context"); if (store) { free(store); } if (ctx) { free(ctx); } return NULL; } ctx->cache.x = -1; ctx->cache.y = -1; ctx->cache.z = -1; ctx->cache.tile = NULL; ctx->cache.xmlname[0] = 0; ctx->baseurl = strdup(&(connection_string[strlen("ro_http_proxy://")])); pthread_mutex_lock(&qLock); if (!done_global_init) { g_logger(G_LOG_LEVEL_DEBUG, "init_storage_ro_http_proxy: Global init of curl", connection_string); res = curl_global_init(CURL_GLOBAL_DEFAULT); done_global_init = 1; } else { res = CURLE_OK; } pthread_mutex_unlock(&qLock); if (res != CURLE_OK) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_http_proxy: failed to initialise global curl: %s", curl_easy_strerror(res)); free(ctx); free(store); return NULL; } ctx->ctx = curl_easy_init(); if (!ctx->ctx) { g_logger(G_LOG_LEVEL_ERROR, "init_storage_ro_http_proxy: failed to initialise curl"); free(ctx); free(store); return NULL; } curl_easy_setopt(ctx->ctx, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(ctx->ctx, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(ctx->ctx, CURLOPT_USERAGENT, "mod_tile/1.0"); curl_easy_setopt(ctx->ctx, CURLOPT_FILETIME, 1L); store->storage_ctx = ctx; store->tile_read = &ro_http_proxy_tile_read; store->tile_stat = &ro_http_proxy_tile_stat; store->metatile_write = &ro_http_proxy_metatile_write; store->metatile_delete = &ro_http_proxy_metatile_delete; store->metatile_expire = &ro_http_proxy_metatile_expire; store->tile_storage_id = &ro_http_proxy_tile_storage_id; store->close_storage = &ro_http_proxy_close_storage; return store; #endif } mod_tile-0.8.0/src/sys_utils.c000066400000000000000000000012151474064163400163140ustar00rootroot00000000000000#include "config.h" #include #include #ifdef HAVE_SYS_LOADAVG_H #include #endif double get_load_avg(void) { #ifdef HAVE_GETLOADAVG double loadavg[1]; int n = getloadavg(loadavg, 1); if (n < 1) { return 1000.0; } else { return loadavg[0]; } #else FILE *loadavg = fopen("/proc/loadavg", "r"); double avg = 1000.0; if (!loadavg) { g_logger(G_LOG_LEVEL_ERROR, "failed to read /proc/loadavg"); return 1000.0; } if (fscanf(loadavg, "%lf", &avg) != 1) { g_logger(G_LOG_LEVEL_ERROR, "failed to parse /proc/loadavg"); fclose(loadavg); return 1000.0; } fclose(loadavg); return avg; #endif } mod_tile-0.8.0/tests/000077500000000000000000000000001474064163400144665ustar00rootroot00000000000000mod_tile-0.8.0/tests/CMakeLists.txt000066400000000000000000001345151474064163400172370ustar00rootroot00000000000000#----------------------------------------------------------------------------- # # CMake Config # #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # # Find external dependencies # #----------------------------------------------------------------------------- include(CTest) include(ProcessorCount) processorcount(PROCESSOR_COUNT) execute_process(COMMAND ${APXS_EXECUTABLE} -q progname OUTPUT_VARIABLE HTTPD_PROGNAME OUTPUT_STRIP_TRAILING_WHITESPACE ) find_package(UnixCommands REQUIRED) find_program(CAT_EXECUTABLE NAMES cat REQUIRED) find_program(CURL_EXECUTABLE NAMES curl REQUIRED) find_program(GREP_EXECUTABLE NAMES grep REQUIRED) find_program(HTTPD_EXECUTABLE NAMES ${HTTPD_PROGNAME} REQUIRED) find_program(ID_EXECUTABLE NAMES id REQUIRED) find_program(JQ_EXECUTABLE NAMES jq) find_program(KILL_EXECUTABLE NAMES kill REQUIRED) find_program(MEMCACHED_EXECUTABLE NAMES memcached) find_program(MKDIR_EXECUTABLE NAMES mkdir REQUIRED) find_program(PS_EXECUTABLE NAMES ps REQUIRED) find_program(SHA256SUM_EXECUTABLE NAMES gsha256sum sha256sum REQUIRED) find_program(SLEEP_EXECUTABLE NAMES sleep REQUIRED) find_program(TOUCH_EXECUTABLE NAMES gtouch touch REQUIRED) # Sets the host to be used for CTest test services if(DEFINED ENV{CTEST_SERVER_HOST}) # To the value of environment variable "CTEST_SERVER_HOST" set(CTEST_SERVER_HOST "$ENV{CTEST_SERVER_HOST}") else() # Or to 0.0.0.0 by default set(CTEST_SERVER_HOST "0.0.0.0") endif() # Sets the host to be used for CTest client if(DEFINED ENV{CTEST_CLIENT_HOST}) # To the value of environment variable "CTEST_CLIENT_HOST" set(CTEST_CLIENT_HOST "$ENV{CTEST_CLIENT_HOST}") else() # Or to 127.0.0.1 by default set(CTEST_CLIENT_HOST "127.0.0.1") endif() #----------------------------------------------------------------------------- # # Test configurations # #----------------------------------------------------------------------------- set(DEFAULT_MAP_NAME "default") set(HTTPD0_HOST "${CTEST_SERVER_HOST}") set(HTTPD0_PORT_BASE "59000") set(HTTPD1_HOST "${CTEST_SERVER_HOST}") set(HTTPD1_PORT_BASE "59100") set(HTTPD2_HOST "${CTEST_SERVER_HOST}") set(HTTPD2_PORT_BASE "59200") set(MEMCACHED_HOST "${CTEST_SERVER_HOST}") set(MEMCACHED_PORT_BASE "60000") set(RENDERD1_HOST "${CTEST_SERVER_HOST}") set(RENDERD1_PORT_BASE "59500") set(CURL_CMD "${CURL_EXECUTABLE} --fail --silent") set(MAP_NAMES jpg png256 png32 webp) set(TESTS_CONF_DIR "${PROJECT_BINARY_DIR}/tests/conf") set(TESTS_LOGS_DIR "${PROJECT_BINARY_DIR}/tests/logs") set(TESTS_RUN_DIR "${PROJECT_BINARY_DIR}/tests/run") set(TESTS_TILES_DIR "${PROJECT_BINARY_DIR}/tests/tiles") file(COPY tiles.sha256sum DESTINATION ${PROJECT_BINARY_DIR}/tests) set(TILE_ZXY "9/297/191") set(WWW_USER_NAME "nobody") execute_process(COMMAND ${ID_EXECUTABLE} -gn ${WWW_USER_NAME} OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE WWW_GROUP_NAME ) execute_process(COMMAND ${ID_EXECUTABLE} -un OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE USER_NAME ) # Storage backend name (for test display and configuration only) set(STORAGE_BACKENDS file) if(MEMCACHED_EXECUTABLE AND LIBMEMCACHED_FOUND) # Add MemcacheD storage backend list(APPEND STORAGE_BACKENDS memcached_custom memcached_default) endif() list(LENGTH STORAGE_BACKENDS STORAGE_BACKENDS_LENGTH) math(EXPR STORAGE_BACKENDS_LENGTH "${STORAGE_BACKENDS_LENGTH} - 1") #----------------------------------------------------------------------------- # # Test functions # #----------------------------------------------------------------------------- function(add_bad_tile_download_test TEST_NAME TEST_URL REQUIRED_FIXTURE_NAME) add_test(NAME bad_download_tile_${TEST_NAME} COMMAND ${BASH} -c " echo 'Downloading ${TEST_URL}' while true; do HTTP_CODE=$(${CURL_CMD} --write-out '%{http_code}' ${TEST_URL}) echo \"HTTP Code: '\${HTTP_CODE}'\" if [ \"\${HTTP_CODE}\" == \"404\" ] || [ \"\${HTTP_CODE}\" == \"500\" ]; then exit 0; fi echo 'Sleeping 1s'; ${SLEEP_EXECUTABLE} 1; done " WORKING_DIRECTORY tests ) set_tests_properties(bad_download_tile_${TEST_NAME} PROPERTIES FIXTURES_REQUIRED ${REQUIRED_FIXTURE_NAME} TIMEOUT 10 # Needs to be more than ModTileMissingRequestTimeout in httpd.conf ) endfunction() function(add_good_tile_download_test TEST_NAME TEST_URL FILE_NAME FIXTURE_NAME REQUIRED_FIXTURE_NAME) add_test(NAME download_tile_${TEST_NAME} COMMAND ${BASH} -c " echo 'Downloading ${TEST_URL}' until $(${CURL_CMD} ${TEST_URL} --output ${FILE_NAME}); do echo 'Sleeping 1s'; ${SLEEP_EXECUTABLE} 1; done " WORKING_DIRECTORY tests ) set_tests_properties(download_tile_${TEST_NAME} PROPERTIES FIXTURES_REQUIRED ${REQUIRED_FIXTURE_NAME} FIXTURES_SETUP ${FIXTURE_NAME} TIMEOUT 10 ) add_test(NAME check_tile_${TEST_NAME} COMMAND ${BASH} -c " ${SHA256SUM_EXECUTABLE} -c tiles.sha256sum | ${GREP_EXECUTABLE} ${FILE_NAME} | ${GREP_EXECUTABLE} -q OK " WORKING_DIRECTORY tests ) set_tests_properties(check_tile_${TEST_NAME} PROPERTIES DEPENDS_ON download_tile_${TEST_NAME} FIXTURES_REQUIRED ${FIXTURE_NAME} REQUIRED_FILES ${FILE_NAME} ) add_test(NAME remove_tile_${TEST_NAME} COMMAND ${RM} -v ${FILE_NAME} WORKING_DIRECTORY tests ) set_tests_properties(remove_tile_${TEST_NAME} PROPERTIES DEPENDS_ON download_tile_${TEST_NAME} FIXTURES_CLEANUP ${FIXTURE_NAME} REQUIRED_FILES ${FILE_NAME} ) endfunction() #----------------------------------------------------------------------------- # # Tests # #----------------------------------------------------------------------------- add_test(NAME gen_tile_test COMMAND gen_tile_test ) add_test( NAME render_expired_test COMMAND render_expired_test ) add_test( NAME render_list_test COMMAND render_list_test ) add_test( NAME render_old_test COMMAND render_old_test ) add_test( NAME render_speedtest_test COMMAND render_speedtest_test ) add_test( NAME renderd_test COMMAND renderd_test ) add_test( NAME renderd_config_test COMMAND renderd_config_test ) foreach(STORAGE_BACKEND_INDEX RANGE ${STORAGE_BACKENDS_LENGTH}) # Get STORAGE_BACKEND from STORAGE_BACKENDS list list(GET STORAGE_BACKENDS ${STORAGE_BACKEND_INDEX} STORAGE_BACKEND) # Increment Ports math(EXPR HTTPD0_PORT "${HTTPD0_PORT_BASE} + ${STORAGE_BACKEND_INDEX}") math(EXPR HTTPD1_PORT "${HTTPD1_PORT_BASE} + ${STORAGE_BACKEND_INDEX}") math(EXPR HTTPD2_PORT "${HTTPD2_PORT_BASE} + ${STORAGE_BACKEND_INDEX}") math(EXPR RENDERD1_PORT "${RENDERD1_PORT_BASE} + ${STORAGE_BACKEND_INDEX}") if(STORAGE_BACKEND STREQUAL memcached_custom) math(EXPR MEMCACHED_PORT "${MEMCACHED_PORT_BASE} + ${STORAGE_BACKEND_INDEX}") elseif(STORAGE_BACKEND STREQUAL memcached_default) set(MEMCACHED_PORT "11211") endif() # Set STORAGE_BACKEND-level directory names set(TEST_CONF_DIR "${TESTS_CONF_DIR}/${STORAGE_BACKEND}") set(TEST_LOGS_DIR "${TESTS_LOGS_DIR}/${STORAGE_BACKEND}") set(TEST_RUN_DIR "${TESTS_RUN_DIR}/${STORAGE_BACKEND}") set(TEST_TILES_DIR "${TESTS_TILES_DIR}/${STORAGE_BACKEND}") # Set STORAGE_BACKEND-level URLs set(METRICS_OFF_URL "http://${HTTPD1_HOST}:${HTTPD1_PORT}/metrics") set(METRICS_ON_URL "http://${HTTPD0_HOST}:${HTTPD0_PORT}/metrics") set(MOD_TILE_OFF_URL "http://${HTTPD1_HOST}:${HTTPD1_PORT}/mod_tile") set(MOD_TILE_ON_URL "http://${HTTPD0_HOST}:${HTTPD0_PORT}/mod_tile") set(TILE_DEFAULT_TILEJSON_URL "http://${HTTPD0_HOST}:${HTTPD0_PORT}/tiles/${DEFAULT_MAP_NAME}/tile-layer.json") # Set STORAGE_BACKEND-level config file/log/pid/socket file names set(HTTPD_CONF "${TEST_CONF_DIR}/httpd.conf") set(HTTPD_LOG "${TEST_LOGS_DIR}/httpd.log") set(HTTPD_LOG_ACCESS "${TEST_LOGS_DIR}/httpd_access.log") set(HTTPD_LOG_ERROR "${TEST_LOGS_DIR}/httpd_error.log") set(HTTPD_PID "${TEST_RUN_DIR}/httpd.pid") set(MEMCACHED_LOG "${TEST_LOGS_DIR}/memcached.log") set(MEMCACHED_PID "${TEST_RUN_DIR}/memcached.pid") set(RENDERD0_LOG "${TEST_LOGS_DIR}/renderd0.log") set(RENDERD0_PID "${TEST_RUN_DIR}/renderd0.pid") set(RENDERD0_SOCKET "${TEST_RUN_DIR}/renderd0.sock") set(RENDERD1_LOG "${TEST_LOGS_DIR}/renderd1.log") set(RENDERD1_PID "${TEST_RUN_DIR}/renderd1.pid") set(RENDERD2_LOG "${TEST_LOGS_DIR}/renderd2.log") set(RENDERD2_PID "${TEST_RUN_DIR}/renderd2.pid") set(RENDERD2_SOCKET "${TEST_RUN_DIR}/renderd2.sock") set(RENDERD_CONF "${TEST_CONF_DIR}/renderd.conf") # Set TILE_DIR value if(STORAGE_BACKEND STREQUAL file) # Use TEST_TILES_DIR for file backend set(TILE_DIR "${TEST_TILES_DIR}") elseif(STORAGE_BACKEND STREQUAL memcached_custom) # MemcacheD backend "custom" host:port set(TILE_DIR "memcached://${MEMCACHED_HOST}:${MEMCACHED_PORT}") elseif(STORAGE_BACKEND STREQUAL memcached_default) # MemcacheD backend "default" set(TILE_DIR "memcached://") endif() # Generate renderd.conf file configure_file(renderd.conf.in ${RENDERD_CONF}) # Generate httpd.conf filelogs configure_file(httpd.conf.in ${HTTPD_CONF}) # Set list of service start commands set(SERVICES_START_CMDS "$ --config ${RENDERD_CONF} --slave 0 --foreground > ${RENDERD0_LOG} 2>&1 & PID=\${!}\; ${SLEEP_EXECUTABLE} 1\; ${PS_EXECUTABLE} -p \${PID} || exit 1\; echo \${PID} > ${RENDERD0_PID}" "$ --config ${RENDERD_CONF} --slave 1 --foreground > ${RENDERD1_LOG} 2>&1 & PID=\${!}\; ${SLEEP_EXECUTABLE} 1\; ${PS_EXECUTABLE} -p \${PID} || exit 1\; echo \${PID} > ${RENDERD1_PID}" "$ --config ${RENDERD_CONF} --slave 2" "${HTTPD_EXECUTABLE} -e debug -E ${HTTPD_LOG} -f ${HTTPD_CONF} -k start" ) # Conditionally append memcached start commands to SERVICES_START_CMDS based on STORAGE_BACKEND value if(STORAGE_BACKEND MATCHES "memcached_.+") list(APPEND SERVICES_START_CMDS "${MEMCACHED_EXECUTABLE} -l ${MEMCACHED_HOST} -p ${MEMCACHED_PORT} -u ${USER_NAME} -vvv > ${MEMCACHED_LOG} 2>&1 & PID=\${!}\; ${SLEEP_EXECUTABLE} 1\; ${PS_EXECUTABLE} -p \${PID} || exit 1\; echo \${PID} > ${MEMCACHED_PID}" ) endif() add_test(NAME create_dirs_${STORAGE_BACKEND} COMMAND ${MKDIR_EXECUTABLE} -p -v ${TEST_LOGS_DIR} ${TEST_RUN_DIR} ${TEST_TILES_DIR} WORKING_DIRECTORY tests ) set_tests_properties(create_dirs_${STORAGE_BACKEND} PROPERTIES FIXTURES_SETUP services_started_${STORAGE_BACKEND} ) list(LENGTH SERVICES_START_CMDS SERVICES_START_CMDS_LENGTH) math(EXPR SERVICES_START_CMDS_LENGTH "${SERVICES_START_CMDS_LENGTH} - 1") foreach(SERVICES_START_CMDS_INDEX RANGE ${SERVICES_START_CMDS_LENGTH}) # Get SERVICES_START_CMD from SERVICES_START_CMDS list list(GET SERVICES_START_CMDS ${SERVICES_START_CMDS_INDEX} SERVICES_START_CMD) add_test(NAME start_services_${STORAGE_BACKEND}_${SERVICES_START_CMDS_INDEX} COMMAND ${BASH} -c "${SERVICES_START_CMD}" WORKING_DIRECTORY tests ) set_tests_properties(start_services_${STORAGE_BACKEND}_${SERVICES_START_CMDS_INDEX} PROPERTIES DEPENDS create_dirs_${STORAGE_BACKEND} FIXTURES_SETUP services_started_${STORAGE_BACKEND} ) endforeach() foreach(SOCKET_TYPE sock tcp) # Use socket file as --socket value for communicating with renderd process if(SOCKET_TYPE STREQUAL sock) set(SOCKET ${RENDERD0_SOCKET}) endif() # Use TCP host:port as --socket value for communicating with renderd process if(SOCKET_TYPE STREQUAL tcp) set(SOCKET ${RENDERD1_HOST}:${RENDERD1_PORT}) endif() add_test(NAME render_expired_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " printf '0/0/0\n%.0s' {0..100} | $ \ --map ${DEFAULT_MAP_NAME} \ --max-zoom 5 \ --min-zoom 0 \ --num-threads 1 \ --socket ${SOCKET} \ --tile-dir ${TILE_DIR} \ --verbose " WORKING_DIRECTORY tests ) set_tests_properties(render_expired_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES DEPENDS render_speedtest_${SOCKET_TYPE}_${STORAGE_BACKEND} FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} TIMEOUT 90 ) add_test(NAME render_expired_config_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " echo '100/100/100' | $ \ --config ${RENDERD_CONF} \ --no-progress " WORKING_DIRECTORY tests ) set_tests_properties(render_expired_config_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES DEPENDS render_speedtest_${SOCKET_TYPE}_${STORAGE_BACKEND} FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} TIMEOUT 60 ) add_test(NAME render_expired_delete_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " echo '0/0/0' | $ \ --delete-from 0 \ --map ${DEFAULT_MAP_NAME} \ --max-zoom 5 \ --min-zoom 0 \ --num-threads 1 \ --socket ${SOCKET} \ --tile-dir ${TILE_DIR} \ --verbose " WORKING_DIRECTORY tests ) set_tests_properties(render_expired_delete_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES DEPENDS render_expired_touch_${SOCKET_TYPE}_${STORAGE_BACKEND} FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} TIMEOUT 90 ) add_test(NAME render_expired_touch_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " echo '0/0/0' | $ \ --map ${DEFAULT_MAP_NAME} \ --max-zoom 5 \ --min-zoom 0 \ --num-threads 1 \ --socket ${SOCKET} \ --tile-dir ${TILE_DIR} \ --touch-from 0 \ --verbose " WORKING_DIRECTORY tests ) set_tests_properties(render_expired_touch_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES DEPENDS render_speedtest_${SOCKET_TYPE}_${STORAGE_BACKEND} FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} TIMEOUT 90 ) add_test(NAME render_list_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " $ \ --all \ --force \ --map ${DEFAULT_MAP_NAME} \ --max-zoom 5 \ --min-zoom 0 \ --num-threads 1 \ --socket ${SOCKET} \ --tile-dir ${TILE_DIR} \ --verbose " WORKING_DIRECTORY tests ) set_tests_properties(render_list_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES DEPENDS render_speedtest_${SOCKET_TYPE}_${STORAGE_BACKEND} FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} TIMEOUT 90 ) add_test(NAME render_list_config_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " $ \ --all \ --config ${RENDERD_CONF} \ --max-lat 85.0511 \ --max-lon 180 \ --max-zoom 5 \ --min-lat -85.0511 \ --min-lon -180 \ --min-zoom 5 " WORKING_DIRECTORY tests ) set_tests_properties(render_list_config_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES DEPENDS render_speedtest_${SOCKET_TYPE}_${STORAGE_BACKEND} FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} TIMEOUT 60 ) add_test(NAME render_list_stdin_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " printf '0 0 0\n%.0s' {0..10} | $ \ --force \ --map ${DEFAULT_MAP_NAME} \ --max-zoom 5 \ --min-zoom 0 \ --num-threads 1 \ --socket ${SOCKET} \ --tile-dir ${TILE_DIR} \ --verbose " WORKING_DIRECTORY tests ) set_tests_properties(render_list_stdin_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES DEPENDS render_speedtest_${SOCKET_TYPE}_${STORAGE_BACKEND} FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} TIMEOUT 90 ) add_test(NAME render_list_stdin_config_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " echo '0 0 0' | $ \ --config ${RENDERD_CONF} \ --verbose " WORKING_DIRECTORY tests ) set_tests_properties(render_list_stdin_config_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES DEPENDS render_speedtest_${SOCKET_TYPE}_${STORAGE_BACKEND} FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} TIMEOUT 60 ) add_test(NAME render_old_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " ${TOUCH_EXECUTABLE} -d '+1 month' ${TEST_TILES_DIR}/planet-import-complete $ \ --config ${RENDERD_CONF} \ --map ${DEFAULT_MAP_NAME} \ --max-zoom 5 \ --min-zoom 0 \ --num-threads 1 \ --socket ${SOCKET} \ --tile-dir ${TILE_DIR} \ --verbose " WORKING_DIRECTORY tests ) set_tests_properties(render_old_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES DEPENDS render_speedtest_${SOCKET_TYPE}_${STORAGE_BACKEND} FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} TIMEOUT 90 ) add_test(NAME render_old_config_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " ${TOUCH_EXECUTABLE} -d '+1 month' ${TEST_TILES_DIR}/planet-import-complete $ \ --config ${RENDERD_CONF} " WORKING_DIRECTORY tests ) set_tests_properties(render_old_config_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES DEPENDS render_speedtest_${SOCKET_TYPE}_${STORAGE_BACKEND} FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} TIMEOUT 60 ) add_test(NAME render_old_config_timestamp_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " $ \ --config ${RENDERD_CONF} \ --timestamp 01/01/2024 " WORKING_DIRECTORY tests ) set_tests_properties(render_old_config_timestamp_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES DEPENDS render_speedtest_${SOCKET_TYPE}_${STORAGE_BACKEND} FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} TIMEOUT 60 ) add_test(NAME render_speedtest_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " $ \ --map ${DEFAULT_MAP_NAME} \ --max-zoom 10 \ --min-zoom 0 \ --num-threads 1 \ --socket ${SOCKET} " WORKING_DIRECTORY tests ) set_tests_properties(render_speedtest_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES FIXTURES_REQUIRED "services_started_${STORAGE_BACKEND};tiles_downloaded_${STORAGE_BACKEND}" TIMEOUT 90 ) add_test(NAME render_speedtest_config_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " $ \ --config ${RENDERD_CONF} " WORKING_DIRECTORY tests ) set_tests_properties(render_speedtest_config_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES FIXTURES_REQUIRED "services_started_${STORAGE_BACKEND};tiles_downloaded_${STORAGE_BACKEND}" TIMEOUT 60 ) add_test(NAME add_tile_config_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " CONFIG_NAME=\"bad_tile_config_${SOCKET_TYPE}\" SEARCH_LINE=\$(${GREP_EXECUTABLE} -m1 \"Loading tile config \${CONFIG_NAME}\" ${HTTPD_LOG}) SEARCH_STRS=( \" at /\${CONFIG_NAME}/ \" \" extension .jpg \" \" mime type image/jpeg$\" \" tile directory ${TILE_DIR} \" \" zooms 10 - 15 \" ) echo \"Searching log line '\${SEARCH_LINE}'\" for SEARCH_STR in \"\${SEARCH_STRS[@]}\"; do echo \"\tFor '\${SEARCH_STR}'\" echo \"\${SEARCH_LINE}\" | ${GREP_EXECUTABLE} -q -e \"\${SEARCH_STR}\" || exit 1 done " WORKING_DIRECTORY tests ) set_tests_properties(add_tile_config_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} ) add_test(NAME add_tile_mime_config_${SOCKET_TYPE}_${STORAGE_BACKEND} COMMAND ${BASH} -c " for SEARCH_CONFIG in js png; do CONFIG_NAME=\"bad_tile_mime_config_\${SEARCH_CONFIG}_${SOCKET_TYPE}\" MIME_TYPE=image/png if [ \"\${SEARCH_CONFIG}\" = \"js\" ]; then MIME_TYPE=text/javascript fi SEARCH_LINE=\$(${GREP_EXECUTABLE} -m1 \"Loading tile config \${CONFIG_NAME}\" ${HTTPD_LOG}) SEARCH_STRS=( \" at /\${CONFIG_NAME}/ \" \" extension .\${SEARCH_CONFIG} \" \" mime type \${MIME_TYPE}$\" \" tile directory ${RENDERD_TILE_DIR} \" \" zooms 0 - 20 \" ) echo \"Searching log line '\${SEARCH_LINE}'\" for SEARCH_STR in \"\${SEARCH_STRS[@]}\"; do echo \"\tFor '\${SEARCH_STR}'\" echo \"\${SEARCH_LINE}\" | ${GREP_EXECUTABLE} -q -e \"\${SEARCH_STR}\" || exit 1 done SEARCH_LINE=\$(${GREP_EXECUTABLE} \"AddTileMimeConfig will be deprecated\" ${HTTPD_LOG} | \ ${GREP_EXECUTABLE} -m1 \"\${CONFIG_NAME}\") echo \"Searching log line '\${SEARCH_LINE}'\" SEARCH_STR=\"AddTileConfig /\${CONFIG_NAME}/ \${CONFIG_NAME} mimetype=\${MIME_TYPE} extension=\${SEARCH_CONFIG}\" echo \"\tFor '\${SEARCH_STR}'\" echo \"\${SEARCH_LINE}\" | ${GREP_EXECUTABLE} -q -e \"\${SEARCH_STR}\" || exit 1 done " WORKING_DIRECTORY tests ) set_tests_properties(add_tile_mime_config_${SOCKET_TYPE}_${STORAGE_BACKEND} PROPERTIES FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} ) foreach(BAD_ADD_CONFIG bad_tile_config bad_tile_mime_config_js bad_tile_mime_config_png) if(BAD_ADD_CONFIG STREQUAL bad_tile_config) set(EXTENSION jpg) elseif(BAD_ADD_CONFIG STREQUAL bad_tile_mime_config_js) set(EXTENSION js) elseif(BAD_ADD_CONFIG STREQUAL bad_tile_mime_config_png) set(EXTENSION png) endif() # Generate URL path for tiles set(TILE_URL_PATH "/${BAD_ADD_CONFIG}_${SOCKET_TYPE}/${TILE_ZXY}.${EXTENSION}") # Generate tile URLs set(HTTPD0_URL "http://${HTTPD0_HOST}:${HTTPD0_PORT}${TILE_URL_PATH}") set(HTTPD1_URL "http://${HTTPD1_HOST}:${HTTPD1_PORT}${TILE_URL_PATH}") add_bad_tile_download_test( ${BAD_ADD_CONFIG}_${SOCKET_TYPE}_${STORAGE_BACKEND}_0 ${HTTPD0_URL} services_started_${STORAGE_BACKEND} ) add_bad_tile_download_test( ${BAD_ADD_CONFIG}_${SOCKET_TYPE}_${STORAGE_BACKEND}_1 ${HTTPD1_URL} services_started_${STORAGE_BACKEND} ) endforeach() endforeach() foreach(MAP_NAME IN LISTS MAP_NAMES) # Set EXTENSION from MAP_NAME (only works for map names containing an actual extension) string(REGEX REPLACE "[0-9]+" "" EXTENSION ${MAP_NAME}) # Set count for stats to 0 set(STATS_COUNTER 0) # Generate file and URL paths for tiles set(TILE_FILE_NAME "tile.${MAP_NAME}.${STORAGE_BACKEND}") set(TILE_URL_PATH "/tiles/${MAP_NAME}/${TILE_ZXY}.${EXTENSION}") # Generate tile URLs set(HTTPD0_URL "http://${HTTPD0_HOST}:${HTTPD0_PORT}${TILE_URL_PATH}") set(HTTPD1_URL "http://${HTTPD1_HOST}:${HTTPD1_PORT}${TILE_URL_PATH}") set(HTTPD2_URL "http://${HTTPD2_HOST}:${HTTPD2_PORT}${TILE_URL_PATH}") # Generate tile dirty and status URLs set(DIRTY_OFF_URL "${HTTPD1_URL}/dirty") set(DIRTY_ON_URL "${HTTPD0_URL}/dirty") set(STATUS_OFF_URL "${HTTPD1_URL}/status") set(STATUS_ON_URL "${HTTPD0_URL}/status") # Increment count for stats (only counted by requests to HTTPD0_URL, HTTPD1_URL has stats disabled) math(EXPR STATS_COUNTER "${STATS_COUNTER} + 1") add_good_tile_download_test( ${MAP_NAME}_${STORAGE_BACKEND}_0 ${HTTPD0_URL} ${TILE_FILE_NAME}.0 tiles_downloaded_${STORAGE_BACKEND} services_started_${STORAGE_BACKEND} ) add_good_tile_download_test( ${MAP_NAME}_${STORAGE_BACKEND}_1 ${HTTPD1_URL} ${TILE_FILE_NAME}.1 tiles_downloaded_${STORAGE_BACKEND} services_started_${STORAGE_BACKEND} ) add_test(NAME dirty_and_status_${MAP_NAME}_${STORAGE_BACKEND} COMMAND ${BASH} -c " TILE_DIRTY_ON_CMD=\"${CURL_CMD} ${DIRTY_ON_URL}\" TILE_STATUS_ON_CMD=\"${CURL_CMD} ${STATUS_ON_URL}\" TILE_LAST_RENDERED_AT_OLD=$(\${TILE_STATUS_ON_CMD} | ${GREP_EXECUTABLE} -o 'Last rendered at [^\\.]*.') echo \"Tile Last Rendered At (Old): \${TILE_LAST_RENDERED_AT_OLD}\" ${SLEEP_EXECUTABLE} 1 TILE_DIRTY_ON_OUTPUT=$(\${TILE_DIRTY_ON_CMD}) echo \"Dirty On Output: \${TILE_DIRTY_ON_OUTPUT}\" until [ \"\${TILE_DIRTY_ON_OUTPUT}\" == \"Tile submitted for rendering\" ]; do echo 'Sleeping 1s'; ${SLEEP_EXECUTABLE} 1; TILE_DIRTY_ON_OUTPUT=$(\${TILE_DIRTY_ON_CMD}) echo \"Dirty On Output: \${TILE_DIRTY_ON_OUTPUT}\" done TILE_LAST_RENDERED_AT_NEW=$(\${TILE_STATUS_ON_CMD} | ${GREP_EXECUTABLE} -o 'Last rendered at [^\\.]*.') echo \"Tile Last Rendered At (New): \${TILE_LAST_RENDERED_AT_NEW}\" until [ \"\${TILE_LAST_RENDERED_AT_OLD}\" != \"\${TILE_LAST_RENDERED_AT_NEW}\" ]; do echo 'Sleeping 1s'; ${SLEEP_EXECUTABLE} 1; TILE_LAST_RENDERED_AT_NEW=$(\${TILE_STATUS_ON_CMD} | ${GREP_EXECUTABLE} -o 'Last rendered at [^\\.]*.'); echo \"Tile Last Rendered At (New): \${TILE_LAST_RENDERED_AT_NEW}\"; done TILE_DIRTY_OFF_CODE=$(${CURL_CMD} --write-out '%{http_code}' ${DIRTY_OFF_URL}) echo \"Dirty Off code: '\${TILE_DIRTY_OFF_CODE}'\" until [ \"\${TILE_DIRTY_OFF_CODE}\" == \"404\" ]; do echo 'Sleeping 1s'; ${SLEEP_EXECUTABLE} 1; TILE_DIRTY_OFF_CODE=$(${CURL_CMD} --write-out '%{http_code}' ${DIRTY_OFF_URL}) echo \"Dirty Off code: '\${TILE_DIRTY_OFF_CODE}'\" done TILE_STATUS_OFF_CODE=$(${CURL_CMD} --write-out '%{http_code}' ${STATUS_OFF_URL}) echo \"Status Off code: '\${TILE_STATUS_OFF_CODE}'\" until [ \"\${TILE_STATUS_OFF_CODE}\" == \"404\" ]; do echo 'Sleeping 1s'; ${SLEEP_EXECUTABLE} 1; TILE_STATUS_OFF_CODE=$(${CURL_CMD} --write-out '%{http_code}' ${STATUS_OFF_URL}) echo \"Status Off code: '\${TILE_STATUS_OFF_CODE}'\" done " WORKING_DIRECTORY tests ) set_tests_properties(dirty_and_status_${MAP_NAME}_${STORAGE_BACKEND} PROPERTIES FIXTURES_REQUIRED "services_started_${STORAGE_BACKEND};tiles_downloaded_${STORAGE_BACKEND}" TIMEOUT 20 ) add_test(NAME stats_urls_${MAP_NAME}_${STORAGE_BACKEND} COMMAND ${BASH} -c " METRICS_ON_CMD=\"${CURL_CMD} ${METRICS_ON_URL}\" METRICS_ON_OUTPUT=$(\${METRICS_ON_CMD}) echo \"Metrics On output: \${METRICS_ON_OUTPUT}\" METRICS_200=\"modtile_layer_responses_total{layer=\\\"/tiles/${MAP_NAME}/\\\",status=\\\"200\\\"} ${STATS_COUNTER}\" echo \"\${METRICS_200}\"; if [[ \"\${METRICS_ON_OUTPUT}\" != *\"\${METRICS_200}\"* ]]; then exit 1; fi METRICS_OFF_OUTPUT=$(${CURL_CMD} ${METRICS_OFF_URL}) echo \"Metrics Off output: '\${METRICS_OFF_OUTPUT}'\"; if [ \"\${METRICS_OFF_OUTPUT}\" != \"Stats are not enabled for this server\" ]; then exit 1; fi MOD_TILE_ON_CMD=\"${CURL_CMD} ${MOD_TILE_ON_URL}\" MOD_TILE_ON_OUTPUT=$(\${MOD_TILE_ON_CMD}) echo \"Mod_tile On output: \${MOD_TILE_ON_OUTPUT}\" MOD_TILE_200=\"NoRes200Layer/tiles/${MAP_NAME}/: ${STATS_COUNTER}\" echo \"\${MOD_TILE_200}\"; if [[ \"\${MOD_TILE_ON_OUTPUT}\" != *\"\${MOD_TILE_200}\"* ]]; then exit 1; fi MOD_TILE_OFF_OUTPUT=$(${CURL_CMD} ${MOD_TILE_OFF_URL}) echo \"Mod_tile Off output: '\${MOD_TILE_OFF_OUTPUT}'\"; if [ \"\${MOD_TILE_OFF_OUTPUT}\" != \"Stats are not enabled for this server\" ]; then exit 1; fi " WORKING_DIRECTORY tests ) set_tests_properties(stats_urls_${MAP_NAME}_${STORAGE_BACKEND} PROPERTIES FIXTURES_REQUIRED "services_started_${STORAGE_BACKEND};tiles_downloaded_${STORAGE_BACKEND}" ) add_test(NAME tile_expired_${MAP_NAME}_${STORAGE_BACKEND} COMMAND ${BASH} -c " ${TOUCH_EXECUTABLE} -d '-1 month' ${TEST_TILES_DIR}/planet-import-complete if ! ${CURL_CMD} --output /dev/null ${HTTPD1_URL}; then echo \"Past import: ${HTTPD1_URL}\"; fi if ! ${CURL_CMD} --output /dev/null ${HTTPD2_URL}; then echo \"Past import: ${HTTPD2_URL}\"; fi ${TOUCH_EXECUTABLE} -d '+1 month' ${TEST_TILES_DIR}/planet-import-complete if ! ${CURL_CMD} --output /dev/null ${HTTPD1_URL}; then echo \"Future import: ${HTTPD1_URL}\"; fi if ! ${CURL_CMD} --output /dev/null ${HTTPD2_URL}; then echo \"Future import: ${HTTPD2_URL}\"; fi " WORKING_DIRECTORY tests ) set_tests_properties(tile_expired_${MAP_NAME}_${STORAGE_BACKEND} PROPERTIES FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} ) endforeach() # Generate file and URL paths for tiles set(TILE_FILE_NAME "tile.add_tile_config.${STORAGE_BACKEND}") set(TILE_URL_PATH "/good_add_tile_config/${TILE_ZXY}.png") # Generate tile URLs set(HTTPD0_URL "http://${HTTPD0_HOST}:${HTTPD0_PORT}${TILE_URL_PATH}") set(HTTPD1_URL "http://${HTTPD1_HOST}:${HTTPD1_PORT}${TILE_URL_PATH}") add_good_tile_download_test( add_tile_config_${STORAGE_BACKEND}_0 ${HTTPD0_URL} ${TILE_FILE_NAME}.0 tiles_downloaded_${STORAGE_BACKEND} services_started_${STORAGE_BACKEND} ) add_good_tile_download_test( add_tile_config_${STORAGE_BACKEND}_1 ${HTTPD1_URL} ${TILE_FILE_NAME}.1 tiles_downloaded_${STORAGE_BACKEND} services_started_${STORAGE_BACKEND} ) if(NOT PROCESSOR_COUNT EQUAL 0) # Set CTEST_NUM_SLAVE_THREADS to 5 (renderd1 = 1, renderd2 = 4 [NUM_THREADS]) set(CTEST_NUM_SLAVE_THREADS 5) add_test(NAME renderd_num_threads_${STORAGE_BACKEND} COMMAND ${BASH} -c " if ! ${GREP_EXECUTABLE} -q \"renderd: num_threads = '${PROCESSOR_COUNT}'\" \"${RENDERD0_LOG}\"; then ${GREP_EXECUTABLE} \"renderd: num_threads = \" \"${RENDERD0_LOG}\" exit 1; fi if ! ${GREP_EXECUTABLE} -q \"renderd: num_slave_threads = '${CTEST_NUM_SLAVE_THREADS}'\" \"${RENDERD0_LOG}\"; then ${GREP_EXECUTABLE} \"renderd: num_slave_threads = \" \"${RENDERD0_LOG}\" exit 1; fi " WORKING_DIRECTORY tests ) set_tests_properties(renderd_num_threads_${STORAGE_BACKEND} PROPERTIES FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} REQUIRED_FILES ${RENDERD0_LOG} ) endif() add_test(NAME stop_services_${STORAGE_BACKEND} COMMAND ${BASH} -c " for SERVICE_PID_FILE in ${TEST_RUN_DIR}/*.pid; do ${KILL_EXECUTABLE} $(${CAT_EXECUTABLE} \${SERVICE_PID_FILE}); ${RM} \${SERVICE_PID_FILE}; ${SLEEP_EXECUTABLE} 1; done " WORKING_DIRECTORY tests ) set_tests_properties(stop_services_${STORAGE_BACKEND} PROPERTIES FIXTURES_CLEANUP services_started_${STORAGE_BACKEND} ) add_test(NAME clear_dirs_${STORAGE_BACKEND} COMMAND ${BASH} -c " ${RM} -f -r -v \ ${TEST_LOGS_DIR}/* \ ${TEST_RUN_DIR}/* \ ${TEST_TILES_DIR}/* " WORKING_DIRECTORY tests ) set_tests_properties(clear_dirs_${STORAGE_BACKEND} PROPERTIES DEPENDS stop_services_${STORAGE_BACKEND} FIXTURES_CLEANUP services_started_${STORAGE_BACKEND} REQUIRED_FILES "${TEST_LOGS_DIR};${TEST_RUN_DIR};${TEST_TILES_DIR}" ) if(STORAGE_BACKEND STREQUAL file) # Generate file and URL paths for tiles set(TILE_FILE_NAME "tile.parameterization.${STORAGE_BACKEND}") set(TILE_URL_PATH "/tiles/parameterization/en,de,_/${TILE_ZXY}.png") # Generate tile URLs set(HTTPD0_URL "http://${HTTPD0_HOST}:${HTTPD0_PORT}${TILE_URL_PATH}") set(HTTPD1_URL "http://${HTTPD1_HOST}:${HTTPD1_PORT}${TILE_URL_PATH}") add_good_tile_download_test( parameterization_${STORAGE_BACKEND}_0 ${HTTPD0_URL} ${TILE_FILE_NAME}.0 tiles_downloaded_${STORAGE_BACKEND} services_started_${STORAGE_BACKEND} ) add_good_tile_download_test( parameterization_${STORAGE_BACKEND}_1 ${HTTPD1_URL} ${TILE_FILE_NAME}.1 tiles_downloaded_${STORAGE_BACKEND} services_started_${STORAGE_BACKEND} ) # Generate file and URL paths for tiles set(TILE_FILE_NAME "tile.htcp.${STORAGE_BACKEND}") set(TILE_URL_PATH "/tiles/htcp/${TILE_ZXY}.png") # Generate tile URLs set(HTTPD0_URL "http://${HTTPD0_HOST}:${HTTPD0_PORT}${TILE_URL_PATH}") set(HTTPD1_URL "http://${HTTPD1_HOST}:${HTTPD1_PORT}${TILE_URL_PATH}") add_good_tile_download_test( htcp_${STORAGE_BACKEND}_0 ${HTTPD0_URL} ${TILE_FILE_NAME}.0 tiles_downloaded_${STORAGE_BACKEND} services_started_${STORAGE_BACKEND} ) add_good_tile_download_test( htcp_${STORAGE_BACKEND}_1 ${HTTPD1_URL} ${TILE_FILE_NAME}.1 tiles_downloaded_${STORAGE_BACKEND} services_started_${STORAGE_BACKEND} ) # Generate URL path for tiles set(TILE_URL_PATH "/tiles/cors_all/${TILE_ZXY}.png") # Generate tile URLs set(HTTPD0_URL "http://${HTTPD0_HOST}:${HTTPD0_PORT}${TILE_URL_PATH}") set(HTTPD1_URL "http://${HTTPD1_HOST}:${HTTPD1_PORT}${TILE_URL_PATH}") add_test(NAME cors_all_${STORAGE_BACKEND} COMMAND ${BASH} -c " RESPONSE_CODE_CMD=\"${CURL_CMD} --write-out %{http_code} --output /dev/null\" RESPONSE_CODE=$(\${RESPONSE_CODE_CMD} ${HTTPD0_URL}) echo \"Response code: '\${RESPONSE_CODE}'\" if [ \"\${RESPONSE_CODE}\" != \"200\" ]; then exit 1; fi RESPONSE_CODE=$(\${RESPONSE_CODE_CMD} ${HTTPD1_URL}) echo \"Response code: '\${RESPONSE_CODE}'\" if [ \"\${RESPONSE_CODE}\" != \"200\" ]; then exit 1; fi RESPONSE_CODE=$(\${RESPONSE_CODE_CMD} --header \"Origin: example.com\" ${HTTPD0_URL}) echo \"Response code: '\${RESPONSE_CODE}'\" if [ \"\${RESPONSE_CODE}\" != \"200\" ]; then exit 1; fi RESPONSE_CODE=$(\${RESPONSE_CODE_CMD} --header \"Origin: example.com\" ${HTTPD1_URL}) echo \"Response code: '\${RESPONSE_CODE}'\" if [ \"\${RESPONSE_CODE}\" != \"200\" ]; then exit 1; fi " WORKING_DIRECTORY tests ) set_tests_properties(cors_all_${STORAGE_BACKEND} PROPERTIES FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} ) # Generate URL path for tiles set(TILE_URL_PATH "/tiles/cors_localhost/${TILE_ZXY}.png") # Generate tile URLs set(HTTPD0_URL "http://${HTTPD0_HOST}:${HTTPD0_PORT}${TILE_URL_PATH}") set(HTTPD1_URL "http://${HTTPD1_HOST}:${HTTPD1_PORT}${TILE_URL_PATH}") add_test(NAME cors_localhost_${STORAGE_BACKEND} COMMAND ${BASH} -c " RESPONSE_CODE_CMD=\"${CURL_CMD} --write-out %{http_code}\" RESPONSE_CODE=$(\${RESPONSE_CODE_CMD} --header \"Origin: notlocalhost\" ${HTTPD0_URL}) echo \"Response code: '\${RESPONSE_CODE}'\" if [ \"\${RESPONSE_CODE}\" != \"403\" ]; then exit 1; fi RESPONSE_CODE=$(\${RESPONSE_CODE_CMD} --header \"Origin: notlocalhost\" ${HTTPD1_URL}) echo \"Response code: '\${RESPONSE_CODE}'\" if [ \"\${RESPONSE_CODE}\" != \"403\" ]; then exit 1; fi RESPONSE_CODE=$(\${RESPONSE_CODE_CMD} --header \"Origin: localhost\" ${HTTPD0_URL}) echo \"Response code: '\${RESPONSE_CODE}'\" if [ \"\${RESPONSE_CODE}\" = \"403\" ]; then exit 1; fi RESPONSE_CODE=$(\${RESPONSE_CODE_CMD} --header \"Origin: localhost\" ${HTTPD1_URL}) echo \"Response code: '\${RESPONSE_CODE}'\" if [ \"\${RESPONSE_CODE}\" = \"403\" ]; then exit 1; fi " WORKING_DIRECTORY tests ) set_tests_properties(cors_localhost_${STORAGE_BACKEND} PROPERTIES FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} ) # Generate URL path for tiles set(TILE_URL_PATH "/tiles/png256/18/16125/115188.png") # Generate tile URL set(HTTPD2_URL "http://${HTTPD2_HOST}:${HTTPD2_PORT}${TILE_URL_PATH}") add_test(NAME max_load_missing_${STORAGE_BACKEND} COMMAND ${BASH} -c " RESPONSE_CODE_CMD=\"${CURL_CMD} --write-out %{http_code}\" RESPONSE_CODE=$(\${RESPONSE_CODE_CMD} ${HTTPD2_URL}) echo \"Response code: '\${RESPONSE_CODE}'\" if [ \"\${RESPONSE_CODE}\" != \"404\" ]; then exit 1; fi " WORKING_DIRECTORY tests ) set_tests_properties(max_load_missing_${STORAGE_BACKEND} PROPERTIES FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} ) add_test(NAME throttling_xforward_${STORAGE_BACKEND} COMMAND ${BASH} -c " RESPONSE_CODE_CMD=\"${CURL_CMD} --write-out %{http_code}\" for i in {0..20}; do RESPONSE_CODE=$(\${RESPONSE_CODE_CMD} --header \"X-Forwarded-For: ${CTEST_SERVER_HOST}, ${CTEST_CLIENT_HOST}\" \ http://${HTTPD2_HOST}:${HTTPD2_PORT}/tiles/webp/\$i/0/0.webp) echo \"Response code: '\${RESPONSE_CODE}'\" if [ \"\${RESPONSE_CODE}\" = \"503\" ]; then echo 'Request being rejected'; exit 0; fi done echo 'Request was never rejected'; exit 1; " WORKING_DIRECTORY tests ) set_tests_properties(throttling_xforward_${STORAGE_BACKEND} PROPERTIES FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} RUN_SERIAL TRUE ) endif() if(JQ_EXECUTABLE) add_test(NAME tilejson_url_${STORAGE_BACKEND} COMMAND ${BASH} -c " TILE_DEFAULT_TILEJSON_CMD=\"${CURL_CMD} ${TILE_DEFAULT_TILEJSON_URL}\" TILE_DEFAULT_TILEJSON_OUTPUT=$(\${TILE_DEFAULT_TILEJSON_CMD}) TILE_DEFAULT_TILEJSON_ATTRIBUTION=$(echo \"\${TILE_DEFAULT_TILEJSON_OUTPUT}\" | ${JQ_EXECUTABLE} -r .attribution) if [ \"\${TILE_DEFAULT_TILEJSON_ATTRIBUTION}\" != \"Attribution for ${DEFAULT_MAP_NAME}\" ]; then exit 1; fi TILE_DEFAULT_TILEJSON_DESCRIPTION=$(echo \"\${TILE_DEFAULT_TILEJSON_OUTPUT}\" | ${JQ_EXECUTABLE} -r .description) if [ \"\${TILE_DEFAULT_TILEJSON_DESCRIPTION}\" != \"Description for ${DEFAULT_MAP_NAME}\" ]; then exit 1; fi TILE_DEFAULT_TILEJSON_NAME=$(echo \"\${TILE_DEFAULT_TILEJSON_OUTPUT}\" | ${JQ_EXECUTABLE} -r .name) if [ \"\${TILE_DEFAULT_TILEJSON_NAME}\" != \"${DEFAULT_MAP_NAME}\" ]; then exit 1; fi TILE_DEFAULT_TILEJSON_VERSION=$(echo \"\${TILE_DEFAULT_TILEJSON_OUTPUT}\" | ${JQ_EXECUTABLE} -r .tilejson) if [ \"\${TILE_DEFAULT_TILEJSON_VERSION}\" != \"2.0.0\" ]; then exit 1; fi TILE_DEFAULT_TILEJSON_TILES_0=$(echo \"\${TILE_DEFAULT_TILEJSON_OUTPUT}\" | ${JQ_EXECUTABLE} -r .tiles[0]) if [ \"\${TILE_DEFAULT_TILEJSON_TILES_0}\" != \"http://localhost/tiles/${DEFAULT_MAP_NAME}/{z}/{x}/{y}.png\" ]; then exit 1; fi TILE_DEFAULT_TILEJSON_TILES_1=$(echo \"\${TILE_DEFAULT_TILEJSON_OUTPUT}\" | ${JQ_EXECUTABLE} -r .tiles[1]) if [ \"\${TILE_DEFAULT_TILEJSON_TILES_1}\" != \"http://alias/tiles/${DEFAULT_MAP_NAME}/{z}/{x}/{y}.png\" ]; then exit 1; fi " WORKING_DIRECTORY tests ) set_tests_properties(tilejson_url_${STORAGE_BACKEND} PROPERTIES FIXTURES_REQUIRED services_started_${STORAGE_BACKEND} ) endif() endforeach() # Test mal-formed HTTPD configuration directives set(DIRECTIVES "AddTileConfig" "AddTileConfig string" "AddTileConfig string string maxzoom=100" "LoadTileConfigFile" "LoadTileConfigFile /tmp/bad/file/name" "ModTileCacheDurationDirty string" "ModTileCacheDurationLowZoom 1 string" "ModTileCacheDurationLowZoom string 1" "ModTileCacheDurationMax string" "ModTileCacheDurationMediumZoom 1 string" "ModTileCacheDurationMediumZoom string 1" "ModTileCacheDurationMinimum string" "ModTileCacheExtendedDuration string" "ModTileCacheLastModifiedFactor string" "ModTileEnableTileThrottlingXForward -1" "ModTileEnableTileThrottlingXForward 3" "ModTileEnableTileThrottlingXForward string" "ModTileMaxLoadMissing string" "ModTileMaxLoadOld string" "ModTileMissingRequestTimeout string" "ModTileRenderdSocketAddr string string" "ModTileRequestTimeout string" "ModTileThrottlingRenders 1 string" "ModTileThrottlingRenders string 1" "ModTileThrottlingTiles 1 string" "ModTileThrottlingTiles string 1" "ModTileVeryOldThreshold string" ) set(DIRECTIVE_ERRORS "AddTileConfig error, URL path not defined" "AddTileConfig error, name of renderd config not defined" "AddTileConfig error, the configured zoom level lies outside of the range supported by this server" "LoadTileConfigFile takes one argument, load an entire renderd config file" "LoadTileConfigFile error, unable to open config file" "ModTileCacheDurationDirty argument must be an integer" "ModTileCacheDurationLowZoom second argument must be an integer" "ModTileCacheDurationLowZoom first argument must be an integer" "ModTileCacheDurationMax argument must be an integer" "ModTileCacheDurationMediumZoom second argument must be an integer" "ModTileCacheDurationMediumZoom first argument must be an integer" "ModTileCacheDurationMinimum argument must be an integer" "ModTileCacheExtendedDuration argument must be an integer" "ModTileCacheLastModifiedFactor argument must be a float" "ModTileEnableTileThrottlingXForward needs integer argument between 0 and 2 (0 => off\; 1 => use client\; 2 => use last entry in chain" "ModTileEnableTileThrottlingXForward needs integer argument between 0 and 2 (0 => off\; 1 => use client\; 2 => use last entry in chain" "ModTileEnableTileThrottlingXForward argument must be an integer" "ModTileMaxLoadMissing argument must be an integer" "ModTileMaxLoadOld argument must be an integer" "ModTileMissingRequestTimeout argument must be an integer" "ModTileRenderdSocketAddr second argument must be an integer" "ModTileRequestTimeout argument must be an integer" "ModTileThrottlingRenders second argument must be a float" "ModTileThrottlingRenders first argument must be an integer" "ModTileThrottlingTiles second argument must be a float" "ModTileThrottlingTiles first argument must be an integer" "ModTileVeryOldThreshold argument must be an integer" ) list(LENGTH DIRECTIVES DIRECTIVES_LENGTH) math(EXPR DIRECTIVES_LENGTH "${DIRECTIVES_LENGTH} - 1") foreach(DIRECTIVE_INDEX RANGE ${DIRECTIVES_LENGTH}) set(HTTPD_CONF "${TESTS_CONF_DIR}/httpd_bad_${DIRECTIVE_INDEX}.conf") # Get DIRECTIVE from DIRECTIVES list list(GET DIRECTIVES ${DIRECTIVE_INDEX} DIRECTIVE) # Get DIRECTIVE_ERROR from DIRECTIVE_ERRORS list list(GET DIRECTIVE_ERRORS ${DIRECTIVE_INDEX} DIRECTIVE_ERROR) # Generate httpd.conf file configure_file(httpd.conf.in ${HTTPD_CONF}) add_test(NAME bad_httpd_config_${DIRECTIVE_INDEX} COMMAND ${BASH} -c " HTTPD_OUTPUT=$(${HTTPD_EXECUTABLE} -e debug -f ${HTTPD_CONF} -t 2>&1) if [ \"\${?}\" -eq \"0\" ]; then echo \"Unexpected success.\" echo \"\${HTTPD_OUTPUT}\" exit 1; fi if echo \"\${HTTPD_OUTPUT}\" | ${GREP_EXECUTABLE} -q \"${DIRECTIVE_ERROR}\"; then exit 0; else echo \"\${HTTPD_OUTPUT}\" exit 1; fi " WORKING_DIRECTORY tests ) endforeach() #----------------------------------------------------------------------------- # # Test targets # #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # # catch_main_o # #----------------------------------------------------------------------------- add_library(catch_main_o OBJECT ${PROJECT_SOURCE_DIR}/tests/catch_main.cpp ${PROJECT_SOURCE_DIR}/tests/catch_test_common.cpp ) target_include_directories(catch_main_o PRIVATE ${GLIB_INCLUDE_DIRS}) #----------------------------------------------------------------------------- # # catch_test_common_o # #----------------------------------------------------------------------------- add_library(catch_test_common_o OBJECT ${PROJECT_SOURCE_DIR}/tests/catch_test_common.cpp ) target_include_directories(catch_test_common_o PRIVATE ${GLIB_INCLUDE_DIRS}) #----------------------------------------------------------------------------- # # gen_tile_test # #----------------------------------------------------------------------------- # Added by ${PROJECT_SOURCE_DIR}/src/CMakeLists.txt # (in order to reduce redundant source and library definitions) #----------------------------------------------------------------------------- # # Additional options for targets added hereafter # #----------------------------------------------------------------------------- add_compile_definitions( PROJECT_BINARY_DIR="${PROJECT_BINARY_DIR}/src" RENDERD_CONF="${PROJECT_SOURCE_DIR}/etc/renderd/renderd.conf.examples" ) include_directories(${PROJECT_SOURCE_DIR}/includes) link_libraries(${GLIB_LIBRARIES}) #----------------------------------------------------------------------------- # # render_expired_test # #----------------------------------------------------------------------------- add_executable(render_expired_test $ render_expired_test.cpp ) #----------------------------------------------------------------------------- # # render_list_test # #----------------------------------------------------------------------------- add_executable(render_list_test $ render_list_test.cpp ) #----------------------------------------------------------------------------- # # render_old_test # #----------------------------------------------------------------------------- add_executable(render_old_test $ render_old_test.cpp ) #----------------------------------------------------------------------------- # # render_speedtest_test # #----------------------------------------------------------------------------- add_executable(render_speedtest_test $ render_speedtest_test.cpp ) #----------------------------------------------------------------------------- # # renderd_test # #----------------------------------------------------------------------------- add_executable(renderd_test $ renderd_test.cpp ) #----------------------------------------------------------------------------- # # renderd_config_test # #----------------------------------------------------------------------------- add_executable(renderd_config_test $ renderd_config_test.cpp ) mod_tile-0.8.0/tests/catch/000077500000000000000000000000001474064163400155505ustar00rootroot00000000000000mod_tile-0.8.0/tests/catch/catch.hpp000066400000000000000000024040031474064163400173460ustar00rootroot00000000000000/* * Catch v2.13.10 * Generated: 2022-10-16 11:01:23.452308 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED // start catch.hpp #define CATCH_VERSION_MAJOR 2 #define CATCH_VERSION_MINOR 13 #define CATCH_VERSION_PATCH 10 #ifdef __clang__ # pragma clang system_header #elif defined __GNUC__ # pragma GCC system_header #endif // start catch_suppress_warnings.h #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(push) # pragma warning(disable: 161 1682) # else // __ICC # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" # pragma clang diagnostic ignored "-Wswitch-enum" # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ // Because REQUIREs trigger GCC's -Wparentheses, and because still // supported version of g++ have only buggy support for _Pragmas, // Wparentheses have to be suppressed globally. # pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic ignored "-Wpadded" #endif // end catch_suppress_warnings.h #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL # define CATCH_CONFIG_ALL_PARTS #endif // In the impl file, we want to have access to all parts of the headers // Can also be used to sanely support PCHs #if defined(CATCH_CONFIG_ALL_PARTS) # define CATCH_CONFIG_EXTERNAL_INTERFACES # if defined(CATCH_CONFIG_DISABLE_MATCHERS) # undef CATCH_CONFIG_DISABLE_MATCHERS # endif # if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) # define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER # endif #endif #if !defined(CATCH_CONFIG_IMPL_ONLY) // start catch_platform.h // See e.g.: // https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html #ifdef __APPLE__ # include # if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) # define CATCH_PLATFORM_MAC # elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) # define CATCH_PLATFORM_IPHONE # endif #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) # define CATCH_PLATFORM_WINDOWS #endif // end catch_platform.h #ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED # define CLARA_CONFIG_MAIN # endif #endif // start catch_user_interfaces.h namespace Catch { unsigned int rngSeed(); } // end catch_user_interfaces.h // start catch_tag_alias_autoregistrar.h // start catch_common.h // start catch_compiler_capabilities.h // Detect a number of compiler features - by compiler // The following features are defined: // // CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? // CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? // CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? // **************** // Note to maintainers: if new toggles are added please document them // in configuration.md, too // **************** // In general each macro has a _NO_ form // (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. #ifdef __cplusplus # if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) # define CATCH_CPP14_OR_GREATER # endif # if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define CATCH_CPP17_OR_GREATER # endif #endif // Only GCC compiler should be used in this block, so other compilers trying to // mask themselves as GCC should be ignored. #if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) # define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) #endif #if defined(__clang__) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) // As of this writing, IBM XL's implementation of __builtin_constant_p has a bug // which results in calls to destructors being emitted for each temporary, // without a matching initialization. In practice, this can result in something // like `std::string::~string` being called on an uninitialized value. // // For example, this code will likely segfault under IBM XL: // ``` // REQUIRE(std::string("12") + "34" == "1234") // ``` // // Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. # if !defined(__ibmxl__) && !defined(__CUDACC__) # define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ # endif # define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) # define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) # define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) # define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // Assume that non-Windows platforms support posix signals by default #if !defined(CATCH_PLATFORM_WINDOWS) #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS #endif //////////////////////////////////////////////////////////////////////////////// // We know some environments not to support full POSIX signals #if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS #endif #ifdef __OS400__ # define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS # define CATCH_CONFIG_COLOUR_NONE #endif //////////////////////////////////////////////////////////////////////////////// // Android somehow still does not support std::to_string #if defined(__ANDROID__) # define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING # define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE #endif //////////////////////////////////////////////////////////////////////////////// // Not all Windows environments support SEH properly #if defined(__MINGW32__) # define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH #endif //////////////////////////////////////////////////////////////////////////////// // PS4 #if defined(__ORBIS__) # define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE #endif //////////////////////////////////////////////////////////////////////////////// // Cygwin #ifdef __CYGWIN__ // Required for some versions of Cygwin to declare gettimeofday // see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin # define _BSD_SOURCE // some versions of cygwin (most) do not support std::to_string. Use the libstd check. // https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 # if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) # define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING # endif #endif // __CYGWIN__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #if defined(_MSC_VER) // Universal Windows platform does not support SEH // Or console colours (or console at all...) # if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) # define CATCH_CONFIG_COLOUR_NONE # else # define CATCH_INTERNAL_CONFIG_WINDOWS_SEH # endif # if !defined(__clang__) // Handle Clang masquerading for msvc // MSVC traditional preprocessor needs some workaround for __VA_ARGS__ // _MSVC_TRADITIONAL == 0 means new conformant preprocessor // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor # if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) # define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR # endif // MSVC_TRADITIONAL // Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop` # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) # endif // __clang__ #endif // _MSC_VER #if defined(_REENTRANT) || defined(_MSC_VER) // Enable async processing, as -pthread is specified or no additional linking is required # define CATCH_INTERNAL_CONFIG_USE_ASYNC #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// // Check if we are compiled with -fno-exceptions or equivalent #if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) # define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED #endif //////////////////////////////////////////////////////////////////////////////// // DJGPP #ifdef __DJGPP__ # define CATCH_INTERNAL_CONFIG_NO_WCHAR #endif // __DJGPP__ //////////////////////////////////////////////////////////////////////////////// // Embarcadero C++Build #if defined(__BORLANDC__) #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN #endif //////////////////////////////////////////////////////////////////////////////// // Use of __COUNTER__ is suppressed during code analysis in // CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly // handled by it. // Otherwise all supported compilers support COUNTER macro, // but user still might want to turn it off #if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) #define CATCH_INTERNAL_CONFIG_COUNTER #endif //////////////////////////////////////////////////////////////////////////////// // RTX is a special version of Windows that is real time. // This means that it is detected as Windows, but does not provide // the same set of capabilities as real Windows does. #if defined(UNDER_RTSS) || defined(RTX64_BUILD) #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH #define CATCH_INTERNAL_CONFIG_NO_ASYNC #define CATCH_CONFIG_COLOUR_NONE #endif #if !defined(_GLIBCXX_USE_C99_MATH_TR1) #define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER #endif // Various stdlib support checks that require __has_include #if defined(__has_include) // Check if string_view is available and usable #if __has_include() && defined(CATCH_CPP17_OR_GREATER) # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW #endif // Check if optional is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) // Check if byte is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) # include # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) # define CATCH_INTERNAL_CONFIG_CPP17_BYTE # endif # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) // Check if variant is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) # if defined(__clang__) && (__clang_major__ < 8) // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 // fix should be in clang 8, workaround in libstdc++ 8.2 # include # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) # define CATCH_CONFIG_NO_CPP17_VARIANT # else # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) # else # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT # endif // defined(__clang__) && (__clang_major__ < 8) # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) #endif // defined(__has_include) #if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) # define CATCH_CONFIG_COUNTER #endif #if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) # define CATCH_CONFIG_WINDOWS_SEH #endif // This is set by default, because we assume that unix compilers are posix-signal-compatible by default. #if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) # define CATCH_CONFIG_POSIX_SIGNALS #endif // This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. #if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) # define CATCH_CONFIG_WCHAR #endif #if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) # define CATCH_CONFIG_CPP11_TO_STRING #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) # define CATCH_CONFIG_CPP17_OPTIONAL #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) # define CATCH_CONFIG_CPP17_STRING_VIEW #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) # define CATCH_CONFIG_CPP17_VARIANT #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) # define CATCH_CONFIG_CPP17_BYTE #endif #if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) # define CATCH_INTERNAL_CONFIG_NEW_CAPTURE #endif #if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) # define CATCH_CONFIG_NEW_CAPTURE #endif #if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) # define CATCH_CONFIG_DISABLE_EXCEPTIONS #endif #if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) # define CATCH_CONFIG_POLYFILL_ISNAN #endif #if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) # define CATCH_CONFIG_USE_ASYNC #endif #if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) # define CATCH_CONFIG_ANDROID_LOGWRITE #endif #if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) # define CATCH_CONFIG_GLOBAL_NEXTAFTER #endif // Even if we do not think the compiler has that warning, we still have // to provide a macro that can be used by the code. #if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION #endif #if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #endif #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS #endif // The goal of this macro is to avoid evaluation of the arguments, but // still have the compiler warn on problems inside... #if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) # define CATCH_INTERNAL_IGNORE_BUT_WARN(...) #endif #if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) # undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #elif defined(__clang__) && (__clang_major__ < 5) # undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif #if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) #define CATCH_TRY if ((true)) #define CATCH_CATCH_ALL if ((false)) #define CATCH_CATCH_ANON(type) if ((false)) #else #define CATCH_TRY try #define CATCH_CATCH_ALL catch (...) #define CATCH_CATCH_ANON(type) catch (type) #endif #if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) #define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #endif // end catch_compiler_capabilities.h #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line #define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) #ifdef CATCH_CONFIG_COUNTER # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) #else # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) #endif #include #include #include // We need a dummy global operator<< so we can bring it into Catch namespace later struct Catch_global_namespace_dummy {}; std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); namespace Catch { struct CaseSensitive { enum Choice { Yes, No }; }; class NonCopyable { NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable && ) = delete; NonCopyable& operator = ( NonCopyable const& ) = delete; NonCopyable& operator = ( NonCopyable && ) = delete; protected: NonCopyable(); virtual ~NonCopyable(); }; struct SourceLineInfo { SourceLineInfo() = delete; SourceLineInfo( char const* _file, std::size_t _line ) noexcept : file( _file ), line( _line ) {} SourceLineInfo( SourceLineInfo const& other ) = default; SourceLineInfo& operator = ( SourceLineInfo const& ) = default; SourceLineInfo( SourceLineInfo&& ) noexcept = default; SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; bool empty() const noexcept { return file[0] == '\0'; } bool operator == ( SourceLineInfo const& other ) const noexcept; bool operator < ( SourceLineInfo const& other ) const noexcept; char const* file; std::size_t line; }; std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); // Bring in operator<< from global namespace into Catch namespace // This is necessary because the overload of operator<< above makes // lookup stop at namespace Catch using ::operator<<; // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as // >> stuff +StreamEndStop struct StreamEndStop { std::string operator+() const; }; template T const& operator + ( T const& value, StreamEndStop ) { return value; } } #define CATCH_INTERNAL_LINEINFO \ ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) // end catch_common.h namespace Catch { struct RegistrarForTagAliases { RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); }; } // end namespace Catch #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION // end catch_tag_alias_autoregistrar.h // start catch_test_registry.h // start catch_interfaces_testcase.h #include namespace Catch { class TestSpec; struct ITestInvoker { virtual void invoke () const = 0; virtual ~ITestInvoker(); }; class TestCase; struct IConfig; struct ITestCaseRegistry { virtual ~ITestCaseRegistry(); virtual std::vector const& getAllTests() const = 0; virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; }; bool isThrowSafe( TestCase const& testCase, IConfig const& config ); bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); std::vector const& getAllTestCasesSorted( IConfig const& config ); } // end catch_interfaces_testcase.h // start catch_stringref.h #include #include #include #include namespace Catch { /// A non-owning string class (similar to the forthcoming std::string_view) /// Note that, because a StringRef may be a substring of another string, /// it may not be null terminated. class StringRef { public: using size_type = std::size_t; using const_iterator = const char*; private: static constexpr char const* const s_empty = ""; char const* m_start = s_empty; size_type m_size = 0; public: // construction constexpr StringRef() noexcept = default; StringRef( char const* rawChars ) noexcept; constexpr StringRef( char const* rawChars, size_type size ) noexcept : m_start( rawChars ), m_size( size ) {} StringRef( std::string const& stdString ) noexcept : m_start( stdString.c_str() ), m_size( stdString.size() ) {} explicit operator std::string() const { return std::string(m_start, m_size); } public: // operators auto operator == ( StringRef const& other ) const noexcept -> bool; auto operator != (StringRef const& other) const noexcept -> bool { return !(*this == other); } auto operator[] ( size_type index ) const noexcept -> char { assert(index < m_size); return m_start[index]; } public: // named queries constexpr auto empty() const noexcept -> bool { return m_size == 0; } constexpr auto size() const noexcept -> size_type { return m_size; } // Returns the current start pointer. If the StringRef is not // null-terminated, throws std::domain_exception auto c_str() const -> char const*; public: // substrings and searches // Returns a substring of [start, start + length). // If start + length > size(), then the substring is [start, size()). // If start > size(), then the substring is empty. auto substr( size_type start, size_type length ) const noexcept -> StringRef; // Returns the current start pointer. May not be null-terminated. auto data() const noexcept -> char const*; constexpr auto isNullTerminated() const noexcept -> bool { return m_start[m_size] == '\0'; } public: // iterators constexpr const_iterator begin() const { return m_start; } constexpr const_iterator end() const { return m_start + m_size; } }; auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { return StringRef( rawChars, size ); } } // namespace Catch constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { return Catch::StringRef( rawChars, size ); } // end catch_stringref.h // start catch_preprocessor.hpp #define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ #define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) #ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ // MSVC needs more evaluations #define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) #define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) #else #define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) #endif #define CATCH_REC_END(...) #define CATCH_REC_OUT #define CATCH_EMPTY() #define CATCH_DEFER(id) id CATCH_EMPTY() #define CATCH_REC_GET_END2() 0, CATCH_REC_END #define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 #define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 #define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT #define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) #define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) #define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) #define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) #define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) #define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) #define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) #define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) // Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, // and passes userdata as the first parameter to each invocation, // e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) #define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) #define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) #define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) #define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ #define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ #define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF #define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ #define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) #else // MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF #define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) #define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ #define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) #endif #define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ #define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) #define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) #else #define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) #endif #define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) #define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) #define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) #define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) #define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) #define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) #define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) #define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) #define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) #define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) #define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) #define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) #define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N #define INTERNAL_CATCH_TYPE_GEN\ template struct TypeList {};\ template\ constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ template class...> struct TemplateTypeList{};\ template class...Cs>\ constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ template\ struct append;\ template\ struct rewrap;\ template class, typename...>\ struct create;\ template class, typename>\ struct convert;\ \ template \ struct append { using type = T; };\ template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ template< template class L1, typename...E1, typename...Rest>\ struct append, TypeList, Rest...> { using type = L1; };\ \ template< template class Container, template class List, typename...elems>\ struct rewrap, List> { using type = TypeList>; };\ template< template class Container, template class List, class...Elems, typename...Elements>\ struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ \ template