├── .clang-format ├── .clang-tidy ├── .gitignore ├── .npmignore ├── .nvmrc ├── .travis.yml ├── CHANGELOG.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── TUTORIAL.md ├── bench ├── bench.js └── rules.js ├── binding.gyp ├── cloudformation └── ci.template.js ├── codecov.yml ├── common.gypi ├── lib └── index.js ├── mason-versions.ini ├── package-lock.json ├── package.json ├── scripts ├── clang-format.sh ├── clang-tidy.sh ├── coverage.sh ├── create_scheme.sh ├── generate_compile_commands.py ├── library.xcscheme ├── liftoff.sh ├── node.xcscheme ├── publish.sh └── sanitize.sh ├── src ├── feature_builder.hpp ├── module.cpp ├── module_utils.hpp ├── vtcomposite.cpp ├── vtcomposite.hpp └── zxy_math.hpp ├── test ├── fixtures │ ├── 0.mvt │ ├── 1.mvt │ ├── 2.mvt │ ├── 3.mvt │ ├── 4.mvt │ ├── 5.mvt │ ├── clipping-test-tile.mvt │ ├── empty-overzoom-8-33-63.mvt │ ├── four-linestring-quadrants.mvt │ ├── four-linestrings.js │ ├── four-points-quadrants.mvt │ ├── four-points-z0.geojson │ ├── four-points.js │ ├── linestrings-16-10498-22872.mvt │ ├── linestrings-properties-16-10498-22872.mvt │ ├── linestrings-sf-15-5239-12666.mvt │ ├── mapbox-vector-terrain-v2-hillshade-15-6105-12723.mvt │ ├── multiline.mvt │ ├── multipoint.mvt │ ├── multipolygon.mvt │ ├── points-16-10498-22872.mvt │ ├── points-poi-sf-15-5239-12666.js │ ├── points-poi-sf-15-5239-12666.mvt │ ├── points-properties-16-10498-22872.mvt │ ├── polygon-with-hole.mvt │ ├── polygons-16-10498-22872.mvt │ ├── polygons-buildings-sf-15-5239-12666.mvt │ ├── polygons-hillshade-sf-15-5239-12666.mvt │ ├── polygons-properties-16-10498-22872.mvt │ ├── polygons-with-holes-4-13-6.mvt │ ├── simple-line.mvt │ ├── simple_line.js │ ├── v1-6.mvt │ ├── v1-7.mvt │ ├── v1-8.mvt │ ├── v1-multipoint.mvt │ └── z15-road-segments.mvt ├── test-utils.js ├── vtcomposite-composite-param-validation.test.js ├── vtcomposite-linestrings.test.js ├── vtcomposite-localize-class.test.js ├── vtcomposite-localize-language.test.js ├── vtcomposite-localize-param-validation.test.js ├── vtcomposite-localize-worldview.test.js ├── vtcomposite-multis.test.js ├── vtcomposite-non-localize.test.js ├── vtcomposite-points.test.js ├── vtcomposite-polygons.test.js └── vtcomposite.test.js └── viz ├── app.js ├── fixtures ├── lines.mvt ├── points.mvt └── polygons.mvt ├── package-lock.json ├── package.json └── views └── index.html /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # Mapbox.Variant C/C+ style 3 | Language: Cpp 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: true 16 | AllowShortLoopsOnASingleLine: true 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: false 29 | AfterObjCDeclaration: true 30 | AfterStruct: true 31 | AfterUnion: true 32 | BeforeCatch: true 33 | BeforeElse: true 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Custom 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | ColumnLimit: 0 40 | CommentPragmas: '^ IWYU pragma:' 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 42 | ConstructorInitializerIndentWidth: 4 43 | ContinuationIndentWidth: 4 44 | Cpp11BracedListStyle: true 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ExperimentalAutoDetectBinPacking: false 48 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 49 | IncludeCategories: 50 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 51 | Priority: 2 52 | - Regex: '^(<|"(gtest|isl|json)/)' 53 | Priority: 3 54 | - Regex: '.*' 55 | Priority: 1 56 | IndentCaseLabels: false 57 | IndentWidth: 4 58 | IndentWrappedFunctionNames: false 59 | KeepEmptyLinesAtTheStartOfBlocks: true 60 | MacroBlockBegin: '' 61 | MacroBlockEnd: '' 62 | MaxEmptyLinesToKeep: 1 63 | NamespaceIndentation: None 64 | ObjCBlockIndentWidth: 2 65 | ObjCSpaceAfterProperty: false 66 | ObjCSpaceBeforeProtocolList: true 67 | PenaltyBreakBeforeFirstCallParameter: 19 68 | PenaltyBreakComment: 300 69 | PenaltyBreakFirstLessLess: 120 70 | PenaltyBreakString: 1000 71 | PenaltyExcessCharacter: 1000000 72 | PenaltyReturnTypeOnItsOwnLine: 60 73 | PointerAlignment: Left 74 | ReflowComments: true 75 | SortIncludes: false 76 | SpaceAfterCStyleCast: false 77 | SpaceBeforeAssignmentOperators: true 78 | SpaceBeforeParens: ControlStatements 79 | SpaceInEmptyParentheses: false 80 | SpacesBeforeTrailingComments: 1 81 | SpacesInAngles: false 82 | SpacesInContainerLiterals: true 83 | SpacesInCStyleCastParentheses: false 84 | SpacesInParentheses: false 85 | SpacesInSquareBrackets: false 86 | Standard: Cpp11 87 | TabWidth: 4 88 | UseTab: Never -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: '*,-fuchsia*,-misc-non-private-member-variables-in-classes,-google-runtime-references,-llvm-header-guard,-google-readability-todo,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-owning-memory,-modernize-use-trailing-return-type,-readability-magic-numbers,-cppcoreguidelines-avoid-magic-numbers' 3 | WarningsAsErrors: '*,-modernize-avoid-c-arrays,-hicpp-avoid-c-arrays,-cppcoreguidelines-avoid-c-arrays' 4 | HeaderFilterRegex: '\/src\/' 5 | AnalyzeTemporaryDtors: false 6 | CheckOptions: 7 | - key: google-readability-braces-around-statements.ShortStatementLines 8 | value: '1' 9 | - key: google-readability-function-size.StatementThreshold 10 | value: '800' 11 | - key: google-readability-namespace-comments.ShortNamespaceLines 12 | value: '10' 13 | - key: google-readability-namespace-comments.SpacesBeforeComments 14 | value: '2' 15 | - key: modernize-loop-convert.MaxCopySize 16 | value: '16' 17 | - key: modernize-loop-convert.MinConfidence 18 | value: reasonable 19 | - key: modernize-loop-convert.NamingStyle 20 | value: CamelCase 21 | - key: modernize-pass-by-value.IncludeStyle 22 | value: llvm 23 | - key: modernize-replace-auto-ptr.IncludeStyle 24 | value: llvm 25 | - key: modernize-use-nullptr.NullMacros 26 | value: 'NULL' 27 | ... 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | *.profdata 4 | *.profraw 5 | *tgz 6 | build 7 | lib/binding 8 | mason_packages 9 | node_modules 10 | npm-debug.log 11 | viz/index.html -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib/binding 2 | node_modules 3 | build 4 | mason_packages 5 | .DS_Store 6 | *tgz 7 | *.profdata 8 | *.profraw 9 | npm-debug.log 10 | viz 11 | scripts 12 | .clang-* 13 | .travis.yml 14 | codecov.yml 15 | bench 16 | cloudformation 17 | docs 18 | test 19 | API.md 20 | CODE_OF_CONDUCT.md 21 | CONTRIBUTING.md 22 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: bionic 3 | 4 | node_js: 16 5 | 6 | # enable c++11/14 builds 7 | addons: 8 | apt: 9 | sources: [ 'ubuntu-toolchain-r-test' ] 10 | packages: [ 'libstdc++-6-dev' ] 11 | 12 | install: 13 | - node -v 14 | - which node 15 | - clang++ -v 16 | - which clang++ 17 | - make ${BUILDTYPE} 18 | # Build should be standalone now, so remove mason deps 19 | - rm -rf mason_packages 20 | 21 | # *Here we run tests* 22 | # We prefer running tests in the 'before_script' section rather than 'script' to ensure fast failure. 23 | # Be aware that if you use the 'script' section it will continue running all commands in the section even if one line fails. 24 | # This is documented at https://docs.travis-ci.com/user/customizing-the-build#Breaking-the-Build 25 | # We don't want this behavior because otherwise we might risk publishing builds when the tests did not pass. 26 | # For this reason, we disable the 'script' section below, since we prefer using 'before_script'. 27 | before_script: 28 | - npm test 29 | 30 | script: 31 | # after successful tests, publish binaries if specified in commit message 32 | - ./scripts/publish.sh --toolset=${TOOLSET:-} --debug=$([ "${BUILDTYPE}" == 'debug' ] && echo "true" || echo "false") 33 | 34 | # the matrix allows you to specify different operating systems and environments to 35 | # run your tests and build binaries 36 | matrix: 37 | include: 38 | 39 | ## ** Builds that are published ** 40 | # linux cfi build node release 41 | - os: linux 42 | env: BUILDTYPE=release TOOLSET=cfi CXXFLAGS="-flto -fsanitize=cfi -fvisibility=hidden" LDFLAGS="-flto -fsanitize=cfi" 43 | # linux publishable node release 44 | - os: linux 45 | env: BUILDTYPE=release 46 | # linux publishable node debug 47 | - os: linux 48 | env: BUILDTYPE=debug 49 | # osx publishable node release 50 | - os: osx 51 | osx_image: xcode11 52 | env: BUILDTYPE=release 53 | # osx publishable node asan 54 | - os: linux 55 | env: BUILDTYPE=debug TOOLSET=asan 56 | sudo: required 57 | # Overrides `install` to set up custom asan flags 58 | install: 59 | - make sanitize 60 | # Overrides `before_script` (tests are already run in `make sanitize`) 61 | before_script: true 62 | 63 | ## ** Builds that do not get published ** 64 | 65 | # g++ build (default builds all use clang++) 66 | - os: linux 67 | env: BUILDTYPE=debug CXX="g++-6" CC="gcc-6" LINK="g++-6" AR="ar" NM="nm" CXXFLAGS="-fext-numeric-literals" 68 | addons: 69 | apt: 70 | sources: 71 | - ubuntu-toolchain-r-test 72 | packages: 73 | - libstdc++-6-dev 74 | - g++-6 75 | # Overrides `install` to avoid initializing clang toolchain 76 | install: 77 | - make ${BUILDTYPE} 78 | before_script: 79 | - npm test 80 | # Overrides `script` to disable publishing 81 | script: true 82 | # Coverage build 83 | - os: linux 84 | env: BUILDTYPE=debug CXXFLAGS="--coverage" LDFLAGS="--coverage" 85 | # Overrides `script` to publish coverage data to codecov 86 | script: 87 | - export PATH=$(pwd)/mason_packages/.link/bin/:${PATH} 88 | - which llvm-cov 89 | - pip install --user codecov 90 | - codecov --gcov-exec "llvm-cov gcov -l" 91 | # Clang format build 92 | - os: linux 93 | env: CLANG_FORMAT 94 | # Overrides `install` to avoid initializing clang toolchain 95 | install: 96 | # Run the clang-format script. Any code formatting changes 97 | # will trigger the build to fail (idea here is to get us to pay attention 98 | # and get in the habit of running these locally before committing) 99 | - make format 100 | # Overrides `before_script`, no need to run tests 101 | before_script: true 102 | # Overrides `script` to disable publishing 103 | script: true 104 | # Clang tidy build 105 | - os: linux 106 | env: CLANG_TIDY 107 | # Overrides `install` to avoid initializing clang toolchain 108 | install: 109 | # First run the clang-tidy target 110 | # Any code formatting fixes automatically applied by clang-tidy 111 | # will trigger the build to fail (idea here is to get us to pay attention 112 | # and get in the habit of running these locally before committing) 113 | - make tidy 114 | # Overrides `before_script`, no need to run tests 115 | before_script: true 116 | # Overrides `script` to disable publishing 117 | script: true 118 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.3.1 2 | 3 | - Fix a bug in `localize` when language propery is missing in raw data [#142](https://github.com/mapbox/vtcomposite/pull/142) 4 | 5 | # 2.3.0 6 | 7 | - Add wordlview code "ALL" [#141](https://github.com/mapbox/vtcomposite/pull/141) 8 | 9 | # 2.2.0 10 | 11 | - Add language code "all" [#136](https://github.com/mapbox/vtcomposite/pull/136) 12 | 13 | # 2.1.0 14 | 15 | - Add language code "local" [#133](https://github.com/mapbox/vtcomposite/pull/133) 16 | - Fixes a bug where `worldview` value in a non-localized tile can be truncated [#133](https://github.com/mapbox/vtcomposite/pull/133) 17 | 18 | # 2.0.2 19 | 20 | - Fixes a bug in `localize` where the class and worldview key prefixes were true for soft matches, which unintentionally filters out features where `class = class*`. Now the logic uses an exact match so `class != classes`. [#134](https://github.com/mapbox/vtcomposite/pull/134) 21 | 22 | # 2.0.1 23 | 24 | - Fixes a bug in `localize` function that throws an error when languages or worldviews is an empty array [#131](https://github.com/mapbox/vtcomposite/pull/131) 25 | 26 | # 2.0.0 27 | 28 | - Updates the `localize` function to return features with either all properties localized or features with no properties localized; nothing in between [#129](https://github.com/mapbox/vtcomposite/pull/129). 29 | 30 | # 1.1.0 31 | 32 | - Updates the `localize` function to translate `_mbx_class` to `class` when `_mbx_worldview` is provided along with matching worldview filtering 33 | 34 | 35 | # 1.0.0 36 | 37 | **BREAKING** Module now returns an object with two functions [#127](https://github.com/mapbox/vtcomposite/pull/127) 38 | 39 | ```js 40 | const { composite, localize } = require('@mapbox/vtcomposite'); 41 | ``` 42 | 43 | - Adds a new function `localize` which finds and removes unused translations and worldviews from features. 44 | - Update mvt-fixtures@3.9.0 45 | - Update tape@4.15.1 46 | - Move tutorial from README.md into TUTORIAL.md 47 | - Move benchmarking from README.md into CONTRIBUTING.md 48 | 49 | 50 | # 0.6.1 51 | - Build binaries with node v16 -> works at runtime with node v8 -> v16 (and likely others) 52 | - Remove `-D_GLIBCXX_USE_CXX11_ABI=0` build flag 53 | - Upgrade dependencies [#119](https://github.com/mapbox/vtcomposite/pull/119) 54 | - `node-addon-api` ^4.3.0 55 | - `@mapbox/node-pre-gyp` ^1.0.8 56 | - `@mapbox/cloudfriend` ^5.1.0 57 | - `@mapbox/mvt-fixtures` ^3.7.0 58 | - `@mapbox/sphericalmercator` ^1.2.0 59 | - `@mapbox/tilebelt` ^1.0.2 60 | - `aws-sdk` ^2.1074.0 61 | - `bytes` ^3.1.2 62 | - `d3-queue` ^3.0. 63 | - `mapnik` ^4.5.9 64 | - `pbf` ^3.2.1 65 | - `tape` ^4.5.2 66 | 67 | # 0.6.0 68 | 69 | - Return empty `Buffer` if a composite operation results in a empty tile, even if `compress: true` is set. This can happen if a tile is overzoomed and results in no layers, but the resulting buffer was gzipped, which leads to a non-empty gzipped buffer with no data, specifically: ``. The result of this change is that users should check the response `buffer.length > 0` if they need to handle empty tiles separately from non-empty tiles. 70 | 71 | # 0.5.1 72 | 73 | - Bugfix for v0.5.0 to fix an issue where compositing multiple tiles with specific layers included would drop same-named layers [#114](https://github.com/mapbox/vtcomposite/pull/114) 74 | 75 | # 0.5.0 76 | 77 | - Add `buffers[n].layers` array to allow keeping of specific layers during compositing [#113](https://github.com/mapbox/vtcomposite/pull/113) 78 | - Remove copied documentation from node-cpp-skel [#113](https://github.com/mapbox/vtcomposite/pull/113) 79 | 80 | # 0.4.0 81 | 82 | - Upgrade to use `@mapbox/node-pre-gyp` >= v1.0.0 [#108](https://github.com/mapbox/vtcomposite/pull/108) 83 | - Upgrade to use `node-addon-api` >= v3.1.0 [#108](https://github.com/mapbox/vtcomposite/pull/108) 84 | - Upgrade to use `node-mapnik` >= 4.5.6 [#108](https://github.com/mapbox/vtcomposite/pull/108) 85 | - Check `tile_buffer` is not empty before accessing internal data [info](https://github.com/mapbox/vtcomposite/pull/108#discussion_r580344270) 86 | 87 | # 0.3.0 88 | 89 | - Now supporting node v12/v14 by switching to "universal" binaries that work across all major node major versions that support N-API. Binaries were built using node v12, but work at runtime with node v8 -> v14 (and likely others) 90 | - Upgraded dependencies including node-addon-api, node-pre-gyp, boost, geometry.hpp, protozero, variant and vtzero 91 | - Binaries are now compiled with clang 10.x 92 | 93 | # 0.2.1 94 | 95 | - Revert polygon decoding PR that snuck into 0.2.0 release [#91](https://github.com/mapbox/vtcomposite/pull/91) 96 | - Remove `valid=false`, which fixes a linestring clipping bug [#98](https://github.com/mapbox/vtcomposite/pull/98) 97 | - Polygon clipping fix [#101](https://github.com/mapbox/vtcomposite/pull/101) 98 | 99 | # 0.2.0 100 | 101 | - Upgrade to use N-API and remove nan/node-pre-gyp deps 102 | - Fix issue with comparison operators in feature builder [942a560](https://github.com/mapbox/vtcomposite/commit/942a560bbb2152ea9fc98b0ac970bdcec498429a) 103 | 104 | # 0.1.3 105 | 106 | - Upgrade nan and node-pre-gyp 107 | 108 | # 0.1.2 109 | 110 | - Reduced the package size 111 | - Upgraded to latest @mapbox/mvt-fixtures and @mapbox/mason-js 112 | 113 | # 1/9/2018 114 | 115 | * Add memory stats option to bench tests 116 | 117 | # 1/4/2018 118 | 119 | * Add doc note about remote vs local coverage using `LCOV` 120 | 121 | # 11/17/2017 122 | 123 | * Add liftoff script, setup docs, and more contributing details per https://github.com/mapbox/node-cpp-skel/pull/87 124 | 125 | # 10/31/2017 126 | 127 | * Add [sanitzer flag doc](https://github.com/mapbox/node-cpp-skel/pull/84) 128 | * Add [sanitizer script](hhttps://github.com/mapbox/node-cpp-skel/pull/85) and enable [leak sanitizer](https://github.com/mapbox/node-cpp-skel/commit/725601e4c7df6cb8477a128f018fb064a9f6f9aa) 129 | * 130 | 131 | # 10/20/2017 132 | 133 | * Add [code of conduct](https://github.com/mapbox/node-cpp-skel/pull/82) 134 | * Add [CC0 license](https://github.com/mapbox/node-cpp-skel/pull/82) 135 | * Point to [cpp glossary](https://github.com/mapbox/node-cpp-skel/pull/83) 136 | 137 | # 10/12/2017 138 | 139 | * Update compiler flags per best practices per https://github.com/mapbox/cpp/issues/37 140 | - https://github.com/mapbox/node-cpp-skel/pull/80 141 | - https://github.com/mapbox/node-cpp-skel/pull/78 142 | - https://github.com/mapbox/node-cpp-skel/pull/77 143 | 144 | # 09/10/2017 145 | 146 | * [Sanitize update](https://github.com/mapbox/node-cpp-skel/pull/74) 147 | 148 | # 08/24/2017 149 | 150 | * Clang tidy updates 151 | - https://github.com/mapbox/node-cpp-skel/pull/68 152 | - https://github.com/mapbox/node-cpp-skel/issues/65 153 | - https://github.com/mapbox/node-cpp-skel/pull/64 154 | - https://github.com/mapbox/node-cpp-skel/pull/66 155 | 156 | # 08/15/2017 157 | 158 | * Add [bench scripts](https://github.com/mapbox/node-cpp-skel/pull/61) for async examples 159 | 160 | # 08/09/2017 161 | 162 | * Add comments about "new" allocation 163 | 164 | # 08/08/2017 165 | 166 | * Add [clang-format](https://github.com/mapbox/node-cpp-skel/pull/56) 167 | 168 | # 08/04/2017 169 | 170 | * Use Nan's safer and high performance `Nan::Utf8String` when accepting string args per https://github.com/mapbox/node-cpp-skel/pull/55 171 | 172 | # 08/3/2017 173 | 174 | * Reorganize [documentation](https://github.com/mapbox/node-cpp-skel/pull/53) 175 | 176 | # 07/21/2017 177 | 178 | * Add [object_async example](https://github.com/mapbox/node-cpp-skel/pull/52) 179 | 180 | # 07/11/2017 181 | 182 | * Add [build docs](https://github.com/mapbox/node-cpp-skel/pull/51) 183 | 184 | * It begins 185 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # global owners 2 | * @mapbox/tilesets-api 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | 3 | Everyone is invited to participate in Mapbox’s open source projects and public discussions: we want to create a welcoming and friendly environment. Harassment of participants or other unethical and unprofessional behavior will not be tolerated in our spaces. The [Contributor Covenant](http://contributor-covenant.org) applies to all projects under the Mapbox organization and we ask that you please read [the full text](http://contributor-covenant.org/version/1/2/0/). 4 | 5 | You can learn more about our open source philosophy on [mapbox.com](https://www.mapbox.com/about/open/). -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for getting involved and contributing to vt-composite :tada: Below are a few things to setup when submitting a PR. 4 | 5 | # Installation 6 | 7 | Each `make` command is specified in [`Makefile`](./Makefile) 8 | 9 | ```bash 10 | git clone git@github.com:mapbox/vtcomposite.git 11 | cd vtcomposite 12 | 13 | # Build binaries. This looks to see if there were changes in the C++ code. This does not reinstall deps. 14 | make 15 | 16 | # Run tests 17 | make test 18 | 19 | # Cleans your current builds and removes potential cache 20 | make clean 21 | 22 | # Cleans everything, including the things you download from the network in order to compile (ex: npm packages). 23 | # This is useful if you want to nuke everything and start from scratch. 24 | # For example, it's super useful for making sure everything works for Travis, production, someone else's machine, etc 25 | make distclean 26 | 27 | # This skel uses documentation.js to auto-generate API docs. 28 | # If you'd like to generate docs for your code, you'll need to install documentation.js, 29 | # and then add your subdirectory to the docs command in package.json 30 | npm install -g documentation 31 | npm run docs 32 | ``` 33 | 34 | * Note for MacOS: if you're having issue building on MacOS, try commenting out [`make_global_settings` in binding.gyp](https://github.com/mapbox/vtcomposite/blob/main/binding.gyp#L5-L9) (for more info see [this](https://github.com/mapbox/node-cpp-skel/pull/169#issuecomment-1068127191)). 35 | 36 | # Benchmarks 37 | 38 | Benchmarks can be run with the bench/bench.js script to test vtcomposite against common real-world fixtures (provided by mvt-fixtures) and to test vtcomposite against its predecessor compositing library node-mapnik. When making changes in a pull request, please provide the benchmarks from the master branch and the HEAD of your current branch. You can control the `concurrency`, `iterations`, and `package` of the benchmarks with the following command: 39 | 40 | node bench/bench.js --iterations 1000 --concurrency 5 --package vtcomposite 41 | 42 | And the output will show how many times the library was able to execute per second, per fixture: 43 | 44 | 1: single tile in/out ... 16667 runs/s (3ms) 45 | 2: two different tiles at the same zoom level, zero buffer ... 4167 runs/s (12ms) 46 | 3: two different tiles from different zoom levels (separated by one zoom), zero buffer ... 633 runs/s (79ms) 47 | 4: two different tiles from different zoom levels (separated by more than one zoom), zero buffer ... 1429 runs/s (35ms) 48 | 5: tiles completely made of points, overzooming, no properties ... 3846 runs/s (13ms) 49 | 6: tiles completely made of points, same zoom, no properties ... 50000 runs/s (1ms) 50 | 7: tiles completely made of points, overzoooming, lots of properties ... 3333 runs/s (15ms) 51 | 8: tiles completely made of points, same zoom, lots of properties ... 50000 runs/s (1ms) 52 | 9: buffer_size 128 - tiles completely made of points, same zoom, lots of properties ... 50000 runs/s (1ms) 53 | 10: tiles completely made of linestrings, overzooming and lots of properties ... 1163 runs/s (43ms) 54 | 11: tiles completely made of polygons, overzooming and lots of properties ... 254 runs/s (197ms) 55 | 12: tiles completely made of points and linestrings, overzooming and lots of properties ... 10000 runs/s (5ms) 56 | 13: returns compressed buffer - tiles completely made of points and linestrings, overzooming and lots of properties ... 5556 runs/s (9ms) 57 | 14: buffer_size 128 - tiles completely made of points and linestrings, overzooming and lots of properties ... 12500 runs/s (4ms) 58 | 15: tiles completely made of points and linestrings, overzooming (2x) and lots of properties ... 16667 runs/s (3ms) 59 | 16: tiles completely made of polygons, overzooming and lots of properties ... 1042 runs/s (48ms) 60 | 17: tiles completely made of polygons, overzooming (2x) and lots of properties ... 2174 runs/s (23ms) 61 | 18: return compressed buffer - tiles completely made of polygons, overzooming (2x) and lots of properties ... 2083 runs/s (24ms) 62 | 19: buffer_size 4096 - tiles completely made of polygons, overzooming (2x) and lots of properties ... 1087 runs/s (46ms) 63 | 64 | # Viz 65 | 66 | The viz/ directory contains a small node application that is helpful for visual QA of vtcomposite results. It requests a single Mapbox street tile at z6 and uses the `composite` function to overzoom the tile at `z7`. In order to request tiles, you'll need a `MapboxAccessToken` environment variable and you'll need to run both a local tile server and a simple server for your `viz` application. 67 | 68 | ```shell 69 | cd viz 70 | npm install 71 | MapboxAccessToken={token} node app.js 72 | # localhost:3000 73 | 74 | #in a separate terminal tab, run a simple server on a port of your choosing 75 | #navigate to this port in your browser 76 | python -m SimpleHTTPServer x000 77 | ``` 78 | 79 | # Code comments 80 | 81 | If adding new code, be sure to include relevant code comments. Code comments are a great way for others to learn from your code. This is especially true within the skeleton, since it is made for learning. 82 | 83 | # Update Documentation 84 | 85 | Be sure to update any documentation relevant to your change. This includes updating the [CHANGELOG.md](https://github.com/mapbox/node-cpp-skel/blob/master/CHANGELOG.md). 86 | 87 | # [Code Formatting](/docs/extended-tour.md#clang-tools) 88 | 89 | We use [this script](/scripts/clang-format.sh#L20) to install a consistent version of [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html) to format the code base. The format is automatically checked via a Travis CI build as well. Run the following script locally to ensure formatting is ready to merge: 90 | 91 | make format 92 | 93 | We also use [`clang-tidy`](https://clang.llvm.org/extra/clang-tidy/) as a C++ linter. Run the following command to lint and ensure your code is ready to merge: 94 | 95 | make tidy 96 | 97 | These commands are set from within [the Makefile](./Makefile). 98 | 99 | # Releasing 100 | 101 | In short, you'll need to push a commit with the log line containing `[publish binary]` to build the binary, followed by an `npm publish`. See [node-cpp-skel](https://github.com/mapbox/node-cpp-skel/blob/d2848ed5bcc5a798ff39a2ac139b70844043ff11/docs/publishing-binaries.md) for all the details. 102 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This Makefile serves a few purposes: 2 | # 3 | # 1. It provides an interface to iterate quickly while developing the C++ code in src/ 4 | # by typing `make` or `make debug`. To make iteration as fast as possible it calls out 5 | # directly to underlying build tools and skips running steps that appear to have already 6 | # been run (determined by the presence of a known file or directory). What `make` does is 7 | # the same as running `npm install --build-from-source` except that it is faster when 8 | # run a second time because it will skip re-running expensive steps. 9 | # Note: in rare cases (like if you hit `crtl-c` during an install) you might end up with 10 | # build deps only partially installed. In this case you should run `make distclean` to fully 11 | # restore your repo to is starting state and then running `make` again should start from 12 | # scratch, fixing any inconsistencies. 13 | # 14 | # 2. It provides a few commands that call out to external scripts like `make coverage` or 15 | # `make tidy`. These scripts can be called directly but this Makefile provides a more uniform 16 | # interface to call them. 17 | # 18 | # To learn more about the build system see https://github.com/mapbox/node-cpp-skel/blob/master/docs/extended-tour.md#builds 19 | 20 | # Whether to turn compiler warnings into errors 21 | export WERROR ?= true 22 | 23 | # the default target. This line means that 24 | # just typing `make` will call `make release` 25 | default: release 26 | 27 | # --ignore-scripts allows us to install only the dependencies of this 28 | # module without running the build itself (which allows us to run that later, directly, using node-pre-gyp) 29 | # But we then run `npm rebuild` to insure those deps are fully installed. This is needed since `--ignore-scripts` 30 | # also means that npm will not run the "scripts" for those dependencies and this means that modules which override the 31 | # install target, like node-pre-gyp based modules, will not have their native module installed. So by running `npm rebuild` as a 32 | # followup we fix that problem. Ideally there would be a cleaner way to say "please install the deps of vtcomposite and not vtcomposite itself" 33 | # but until then this works 34 | node_modules: 35 | npm install --ignore-scripts && npm rebuild 36 | 37 | mason_packages/headers: node_modules 38 | node_modules/.bin/mason-js install 39 | 40 | mason_packages/.link/include: mason_packages/headers 41 | node_modules/.bin/mason-js link 42 | 43 | build-deps: mason_packages/.link/include 44 | 45 | release: build-deps 46 | V=1 ./node_modules/.bin/node-pre-gyp configure build --error_on_warnings=$(WERROR) --loglevel=error 47 | @echo "run 'make clean' for full rebuild" 48 | 49 | debug: mason_packages/.link/include 50 | V=1 ./node_modules/.bin/node-pre-gyp configure build --error_on_warnings=$(WERROR) --loglevel=error --debug 51 | @echo "run 'make clean' for full rebuild" 52 | 53 | coverage: build-deps 54 | ./scripts/coverage.sh 55 | 56 | tidy: build-deps 57 | ./scripts/clang-tidy.sh 58 | 59 | format: build-deps 60 | ./scripts/clang-format.sh 61 | 62 | sanitize: build-deps 63 | ./scripts/sanitize.sh 64 | 65 | clean: 66 | rm -rf lib/binding 67 | rm -rf build 68 | # remove remains from running 'make coverage' 69 | rm -f *.profraw 70 | rm -f *.profdata 71 | @echo "run 'make distclean' to also clear node_modules, mason_packages, and .mason directories" 72 | 73 | distclean: clean 74 | rm -rf node_modules 75 | rm -rf mason_packages 76 | 77 | # variable used in the `xcode` target below 78 | MODULE_NAME := $(shell node -e "console.log(require('./package.json').binary.module_name)") 79 | 80 | xcode: node_modules 81 | ./node_modules/.bin/node-pre-gyp configure -- -f xcode 82 | @# If you need more targets, e.g. to run other npm scripts, duplicate the last line and change NPM_ARGUMENT 83 | SCHEME_NAME="$(MODULE_NAME)" SCHEME_TYPE=library BLUEPRINT_NAME=$(MODULE_NAME) BUILDABLE_NAME=$(MODULE_NAME).node scripts/create_scheme.sh 84 | SCHEME_NAME="npm test" SCHEME_TYPE=node BLUEPRINT_NAME=$(MODULE_NAME) BUILDABLE_NAME=$(MODULE_NAME).node NODE_ARGUMENT="`npm bin tape`/tape test/*.test.js" scripts/create_scheme.sh 85 | 86 | open build/binding.xcodeproj 87 | 88 | testpack: 89 | rm -f ./*tgz 90 | npm pack 91 | tar -ztvf *tgz 92 | 93 | testpacked: testpack 94 | rm -rf /tmp/package 95 | tar -xf *tgz --directory=/tmp/ 96 | du -h -d 0 /tmp/package 97 | cp -r test /tmp/package/ 98 | ln -s `pwd`/mason_packages /tmp/package/mason_packages 99 | (cd /tmp/package && make && make test) 100 | 101 | docs: 102 | npm run docs 103 | 104 | test: 105 | npm test 106 | 107 | .PHONY: test docs 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vtcomposite 2 | 3 | [![Build Status](https://travis-ci.com/mapbox/vtcomposite.svg?branch=master)](https://travis-ci.com/mapbox/vtcomposite) 4 | [![codecov](https://codecov.io/gh/mapbox/vtcomposite/branch/master/graph/badge.svg)](https://codecov.io/gh/mapbox/vtcomposite) 5 | [![node-cpp-skel](https://raw.githubusercontent.com/mapbox/cpp/master/assets/node-cpp-skel-badge_blue.svg)](https://github.com/mapbox/node-cpp-skel) 6 | 7 | 8 | ```shell 9 | npm install @mapbox/vtcomposite --save 10 | ``` 11 | 12 | vtcomposite is a tool to combine multiple [vector tiles](https://github.com/mapbox/vector-tile-spec) into a single tile. It allows you to ... 13 | 14 | - **Merge tiles.** Combine 2 or more tiles into a single tile at the same zoom level. 15 | - **Overzoom tiles.** For displaying data at a higher zoom level than that the tile's original zoom level. 16 | - **Clip tiles.** Clips the extraneous buffer of a tile that’s been overzoomed to match a tile's extent or to retain data beyond the extent. 17 | - **Drop layers.** Remove any layers from a tile. 18 | - **Localize.** Modify localization-related features and properties such as language and worldview properties. 19 | 20 | You can learn more about compositing in [TUTORIAL.md](/TUTORIAL.md). This module is a [Node.js Addon](https://nodejs.org/api/addons.html) and will install prebuilt binaries for your version of Node.js and computer architecture. Uncommon setups will build from source when installed via NPM. 21 | 22 | # Usage 23 | 24 | ### `composite` 25 | 26 | Combine multiple vector tiles from different zooms into a single tile at one zoom. This function will overzoom geometries to match the extent of the destination zoom. 27 | 28 | #### Parameters 29 | 30 | - `tiles` **Array(Object)** an array of tile objects 31 | - `buffer` **Buffer** a vector tile buffer, gzip compressed or not 32 | - `z` **Number** z value of the input tile buffer 33 | - `x` **Number** x value of the input tile buffer 34 | - `y` **Number** y value of the input tile buffer 35 | - `layers` **Array** an array of layer names to keep in the final tile. An empty array is invalid. (optional, default keep all layers) 36 | - `zxy` **Object** the output tile zxy location, used to determine if the incoming tiles need to overzoom their data 37 | - `z` **Number** z value of the output tile buffer 38 | - `x` **Number** x value of the output tile buffer 39 | - `y` **Number** y value of the output tile buffer 40 | - `options` **Object** 41 | - `options.compress` **Boolean** a boolean value indicating whether or not to return a compressed buffer. Default is to return a uncompressed buffer. (optional, default `false`) 42 | - `options.buffer_size` **Number** the buffer size of a tile, indicating the tile extent that should be composited and/or clipped. Default is `buffer_size=0`. (optional, default `0`) 43 | - `callback` **Function** callback function that returns `err`, and `buffer` parameters 44 | 45 | #### Example 46 | 47 | ```js 48 | const { composite } = require('@mapbox/vtcomposite'); 49 | const fs = require('fs'); 50 | 51 | const tiles = [ 52 | { buffer: fs.readFileSync('./path/to/tile.mvt'), z: 15, x: 5238, y: 12666 }, 53 | { buffer: fs.readFileSync('./path/to/tile.mvt'), z: 15, x: 5238, y: 12666, layers: ['building'] } 54 | ]; 55 | 56 | const zxy = { z: 5, x: 5, y: 12 }; 57 | 58 | const options = { 59 | compress: true, 60 | buffer_size: 0 61 | }; 62 | 63 | composite(tiles, zxy, options, function(err, result) { 64 | if (err) throw err; 65 | console.log(result); // tile buffer 66 | }); 67 | ``` 68 | 69 | ### `localize` 70 | 71 | A filtering function for modifying a tile's features and properties to support localized languages and worldviews. This function requires the input vector tiles to match a specific schema for language translation and worldviews. 72 | 73 | #### Parameters 74 | 75 | - `params` **Object** 76 | - `params.buffer` **Buffer** a vector tile buffer, gzip compressed or not. 77 | - `params.compress` **Boolean** a boolean value indicating whether or not to return a compressed buffer. 78 | - Default value: `false` (i.e. return an uncompressed buffer). 79 | - `params.hidden_prefix` **String** prefix for any additional properties that will be used to override non-prefixed properties. 80 | - Default value: `_mbx_`. 81 | - Any property that starts with this prefix are considered hidden properties and thus will be dropped. 82 | - `params.languages` **Array>** array of IETF BCP 47 language codes or the string `local`, used to search for matching translations available in a feature's properties. 83 | - Optional parameter. 84 | - All language-related properties must match the following format: `{hidden_prefix}{language_property}_{language}`. 85 | - Default properties are `_mbx_name_{language}`; for example, the `_mbx_name_jp` property contains the Japanese translation for the value in `name`. 86 | - `local` language code represents "`{language_property}` is in a script that is not in the `params.omit_scripts` list". 87 | - The script of `{language_property}`, if available, must be stored in the `{language_property}_script` property. 88 | - If `{language_property}_script` not in the `params.omit_scripts` list, use `{language_property}` when searching for matching translation. 89 | - If `{language_property}_script` is in the `params.omit_scripts` list, skip `{language_property}` when searching for matching translation. 90 | - `all` language code returns `{language_property}`, `{language_property}_local` and all possible language properties that have different values than `{language_property}` 91 | - `params.omit_scripts` **Array>** array of scripts to skip `local` language code. 92 | - `params.language_property` **String** the primary property in features that identifies the feature in a language. 93 | - Default value: `name`. 94 | - This values is used to search for additional translations that match the following format `{language_property}_{language}`. 95 | - `params.worldviews` **Array>** array of ISO 3166-1 alpha-2 country codes used to filter out features of different worldviews. 96 | - Optional parameter. 97 | - For now, only the first item in the array will be processed; the rest are discarded (*TODO in the future*: expand support for more than one worldviews). 98 | - When there is only the single value `ALL`, all the different worldviews are returned. 99 | - `params.worldview_property` **String** the name of the property that specifies in which worldview a feature belongs. 100 | - Default value: `worldview`. 101 | - The vector tile encoded property must contain a single ISO 3166-1 alpha-2 country code or a comma-separated string of country codes that define which worldviews the feature represents (for example: `JP`, `IN,RU,US`). 102 | - `params.worldview_default` **String** default worldview to assume when `params.worldviews` is not provided. 103 | - Default value: `US`. 104 | - `params.class_property` **String** the name of the property that specifies the class category of a feature. 105 | - Default value: `class`. 106 | - `callback` **Function** callback function that returns `err` and `buffer` parameters 107 | 108 | The existence of the parameters `params.languages` and `params.worldviews` determines the type of features that will be returned: 109 | 110 | - Non-localized feature: when `params.languages` and `params.worldviews` both do not exist. 111 | - No new language property. 112 | - The property `{language_property}` retains its original value. 113 | - Properties like `{language_property}_{language}` are kept. 114 | - Properties like `{hidden_prefix}{language_property}_{language}` are dropped. 115 | - All features with `{worldview_property}` are kept. 116 | - All features with `{hidden_prefix}{worldview_property}` are dropped except for those that have the value `all`. 117 | - Property `{class_property}` retains its original value. 118 | - Property `{hidden_prefix}{class_property}` is dropped. 119 | 120 | - Localized feature: when either `params.languages` or `params.worldviews` exists. 121 | - A new `{language_property}_local` property is created to keep the original value of `{language_property}` 122 | - The value of `{language_property}` is replaced with the first translation found by looping through `params.languages`. 123 | - First searches for `{language_property}_{language}` and then `{hidden_prefix}{language_property}_{language}` before moving on to the next language in `params.languages`. 124 | - Properties like `{language_property}_{language}` are dropped. 125 | - Properties like `{hidden_prefix}{language_property}_{language}` are dropped. 126 | - All features with `{worldview_property}` are dropped except for those that have the value `all`. 127 | - Features with `{hidden_prefix}{worldview_property}` are kept if their `{hidden_prefix}{worldview_property}` value is 128 | - `all`, or 129 | - a comma-separated list that contains the first item of `parmas.worldviews`, in which a property `{worldview_property}` is created from that one single worldview country code and the property `{hidden_prefix}{worldview_property}` is dropped. 130 | - If `{hidden_prefix}{class_property}` exists, 131 | - Property `{class_property}` is replaced with the value in `{hidden_prefix}{class_property}`. 132 | - Property `{hidden_prefix}{class_property}` is dropped. 133 | 134 | #### Example 135 | 136 | Example 1: a tile of non-localized features 137 | 138 | ```js 139 | const { localize } = require('@mapbox/vtcomposite'); 140 | 141 | const params = { 142 | // REQUIRED 143 | buffer: require('fs').readFileSync('./path/to/tile.mvt'), 144 | }; 145 | 146 | localize(params, function(err, result) { 147 | if (err) throw err; 148 | console.log(result); // tile buffer 149 | }); 150 | ``` 151 | 152 | Example 2: a tile of localized features in Japan worldview 153 | 154 | ```js 155 | const { localize } = require('@mapbox/vtcomposite'); 156 | 157 | const params = { 158 | // REQUIRED 159 | buffer: require('fs').readFileSync('./path/to/tile.mvt'), 160 | // OPTIONAL (defaults) 161 | languages: ['ja'] 162 | worldviews: ['JP'], 163 | compress: true 164 | }; 165 | 166 | localize(params, function(err, result) { 167 | if (err) throw err; 168 | console.log(result); // tile buffer 169 | }); 170 | ``` 171 | 172 | # Contributing & License 173 | 174 | - [LICENSE](https://github.com/mapbox/vtcomposite/blob/master/LICENSE.md) 175 | - [CONTRIBUTING](https://github.com/mapbox/vtcomposite/blob/master/CONTRIBUTING.md) 176 | - [CODE OF CONDUCT](https://github.com/mapbox/vtcomposite/blob/master/CODE_OF_CONDUCT.md) 177 | 178 | This project is based off the node-cpp-skel framework, which is licensed under [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/). [![node-cpp-skel](https://raw.githubusercontent.com/mapbox/cpp/master/assets/node-cpp-skel-badge_blue.svg)](https://github.com/mapbox/node-cpp-skel) 179 | -------------------------------------------------------------------------------- /TUTORIAL.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | ## What is compositing? 4 | 5 | Compositing is a tool to combine multiple vector tiles into a single tile. Compositing allows a user to: 6 | 7 | - **Merge tiles.** Merges 2 or more tiles into a single tile at the same zoom level. 8 | - **Overzoom tiles.** Displays data at a higher zoom level than that the tileset max zoom. 9 | - **Clip tiles.** Clips the extraneous portion of a tile that’s been overzoomed. 10 | 11 | ## Compositing: Merging 2+ Tiles 12 | 13 | Let’s say you have two tiles at `z5` - `santacruz.mvt` & `losangeles.mvt`. Each tile contains a single point that corresponds to one of the two cities. You could generate a single tile, `santa_cruz_plus_la-5-5-12.mvt` that contains both points by compositing the two tiles. 14 | 15 | ## Source Tiles 16 | 17 | `santacruz.mvt` - single point 18 | 19 | ![](https://d2mxuefqeaa7sj.cloudfront.net/s_04E22B61D71C1B99F8EBA3C41F5DDF0F28DDD0F66171831E6A32600C9DBCD6E9_1531946395305_sc.png) 20 | 21 | 22 | `losangeles.mvt` - single point 23 | 24 | ![](https://d2mxuefqeaa7sj.cloudfront.net/s_04E22B61D71C1B99F8EBA3C41F5DDF0F28DDD0F66171831E6A32600C9DBCD6E9_1531946414805_la.png) 25 | 26 | 27 | ## Output Tile 28 | 29 | **Composited Tile:** `santa_cruz_plus_la-5-5-12.mvt` 30 | 31 | 32 | ![](https://d2mxuefqeaa7sj.cloudfront.net/s_04E22B61D71C1B99F8EBA3C41F5DDF0F28DDD0F66171831E6A32600C9DBCD6E9_1531946439263_scla.png) 33 | 34 | 35 | **`vtcomposite` code:** 36 | 37 | 38 | const santaCruzBuffer = fs.readFileSync('/santacruz.mvt'); 39 | const losAngelesBuffer = fs.readFileSync('/losangeles.mvt'); 40 | 41 | const tiles = [ 42 | {buffer: santaCruzBuffer, z:5, x:5, y:12}, 43 | {buffer: losAngelesBuffer, z:5, x:5, y:12} 44 | ]; 45 | 46 | const zxy = {z:5, x:5, y:12}; 47 | 48 | composite(tiles, zxy, {}, (err, vtBuffer) => { 49 | fs.writeFileSync('/santa_cruz_plus_la-5-5-12.mvt', vtBuffer); 50 | }); 51 | 52 | 53 | ## Compositing: Overzooming & Clipping Tiles 54 | ![](https://d2mxuefqeaa7sj.cloudfront.net/s_04E22B61D71C1B99F8EBA3C41F5DDF0F28DDD0F66171831E6A32600C9DBCD6E9_1531946439263_scla.png) 55 | 56 | 57 | 58 | Let’s say we want to display our composited tile: `santa_cruz_plus_la-5-5-12.mvt` at `z6`. 59 | 60 | We know that as zoom levels increase, each tile divides into four smaller tiles. We can calculate each the `zxy` of the z6 tiles using the formula outlined below. There are also libraries, such as [*mapbox/tilebelt*](http://github.com/mapbox/tilebelt) that calculate the parent or children tiles for you, as well as other tile math calculations. 61 | 62 | 63 | 64 | If the `zxy` is `5/5/12`, the `z6` children tiles are located at: 65 | 66 | ![](https://d2mxuefqeaa7sj.cloudfront.net/s_04E22B61D71C1B99F8EBA3C41F5DDF0F28DDD0F66171831E6A32600C9DBCD6E9_1532040176336_Screen+Shot+2018-07-19+at+3.42.16+PM.png) 67 | 68 | 69 | **`vtcomposite` code:** 70 | 71 | 72 | const santaCruzAndLABuffer = fs.readFileSync('/santa_cruz_plus_la-5-5-12.mvt'); 73 | 74 | const tiles = [ 75 | {buffer: santaCruzAndLABuffer, z:5, x:5, y:12} 76 | ]; 77 | 78 | //map request 79 | const zxy = {z:6, x:10, y:24}; 80 | 81 | composite(tiles, zxy, {}, (err, vtBuffer) => { 82 | fs.writeFileSync('/santa_cruz_plus_la-6-10-24.mvt', vtBuffer); 83 | }); 84 | 85 | In this example, the tile being requested is at z6, but our source tile is a z5 tile. In this scenario, we must **overzoom**. 86 | 87 | Each zoom level scales geometries by a power of 2. Thus, you can calculate coordinates at each zoom level knowing the original geometry and the (over)zoom factor. 88 | 89 | 90 | // original geometry = Santa Cruz tile coordinate at 5/5/12 91 | const originalGeometry = {x:637, y:1865}; 92 | let x = originalGeometry.x; 93 | let y = originalGeometry.y; 94 | 95 | //increasing geometry size by a zoom factor of 1 96 | const zoom_factor = 1; 97 | 98 | const scale = Math.pow(2,zoom_factor); //1 << 1 99 | 100 | //scale x and y geometries by the zoom_factor 101 | let xScale = x*scale; 102 | let yScale = y*scale; 103 | 104 | //divide the scaled geometries by the tile extent (4096) to see the point moves to another tile 105 | let xtileOffset = Math.floor(xScale/4096); 106 | let ytileOffset = Math.floor(yScale/4096); 107 | 108 | //subtract the difference between the x and y tileoffsets. 109 | let xOffset = xScale - (xtileOffset * 4096); 110 | let yOffset = yScale - (ytileOffset * 4096); 111 | 112 | //the xOffset and yOffset will be the x,y point at z6 113 | 114 | 115 | Based off these equations, we know that resulting `(x,y)` point geometries for Santa Cruz and Los Angeles overzoomed at `z6` are: 116 | 117 | 118 | Santa Cruz point = [1274, 3730] at zxy 6/10/24 119 | Los Angeles point = [90, 2318] at zxy 6/11/25 120 | 121 | 122 | ## Clipping 123 | 124 | Wait a second…! Los Angeles isn’t the tile we requested - `{z:6, x:10, y:24}` - it’s in `{z:6, x:11, y:25}`. 125 | 126 | That means we need to **clip** the overzoomed geometries to only include the point(s) we need for tile `{z:6, x:10, y:24}`. Since Santa Cruz is the only geometry in `{z:6, x:10, y:24}`, we **clip** extraneous data, which means we remove any geometries that are not included in the `z6` tile, but *are* included in the parent tile that’s been overzoomed - `{z:5, x:5, y:12}`. See ya Los Angeles! 127 | 128 | ## Clipping with a `buffer_size` 129 | 130 | In the example above, we clipped geometries based on the default tile boundaries (4096X4096). However, the `composite` function always us to have control over which geometries we include/exclude outside the requested tile when clipping. By passing in a `buffer_size` to the compositing function, we are able to explicitly state if we want to keep geometries outside the tile extent when overzooming. 131 | 132 | -------------------------------------------------------------------------------- /bench/bench.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const argv = require('minimist')(process.argv.slice(2)); 3 | if (!argv.iterations || !argv.concurrency || !argv.package) { 4 | console.error('Please provide desired iterations, concurrency'); 5 | console.error('Example: \nnode bench/bench.js --iterations 50 --concurrency 10 --package vtcomposite\nPackage options: vtcomposite or node-mapnik\nPass --compress to bench decompressing and compressing tiles.'); 6 | process.exit(1); 7 | } 8 | 9 | // This env var sets the libuv threadpool size. 10 | // This value is locked in once a function interacts with the threadpool 11 | // Therefore we need to set this value either in the shell or at the very 12 | // top of a JS file (like we do here) 13 | process.env.UV_THREADPOOL_SIZE = argv.concurrency; 14 | 15 | const fs = require('fs'); 16 | const zlib = require('zlib'); 17 | const path = require('path'); 18 | const assert = require('assert'); 19 | const bytes = require('bytes'); 20 | const Queue = require('d3-queue').queue; 21 | const composite = require('../lib/index.js'); 22 | var rules = require('./rules'); 23 | let ruleCount = 1; 24 | const mapnik = require('mapnik'); 25 | 26 | const track_mem = argv.mem ? true : false; 27 | const runs = 0; 28 | const memstats = { 29 | max_rss:0, 30 | max_heap:0, 31 | max_heap_total:0 32 | }; 33 | 34 | // run each rule synchronously 35 | const ruleQueue = Queue(1); 36 | 37 | if(argv.only) { 38 | rules = rules.filter(function(e) { 39 | return e.description === argv.only; 40 | }); 41 | } 42 | 43 | if (rules.length < 1) { 44 | console.error('Error: Could not match any rules based on "' + argv.only + '"'); 45 | process.exit(1); 46 | } 47 | 48 | rules.forEach(function(rule) { 49 | if(argv.compress){ 50 | rule.tiles.forEach(function(t){ 51 | const compressedTile = zlib.gzipSync(t.buffer); 52 | t.buffer = compressedTile; 53 | }); 54 | ruleQueue.defer(runRule, rule); 55 | }else{ 56 | ruleQueue.defer(runRule, rule); 57 | } 58 | }); 59 | 60 | ruleQueue.awaitAll(function(err, res) { 61 | if (err) throw err; 62 | process.stdout.write('\n'); 63 | }); 64 | 65 | function runRule(rule, ruleCallback) { 66 | 67 | process.stdout.write(`\n${ruleCount}: ${rule.description} ... `); 68 | 69 | let runs = 0; 70 | let runsQueue = Queue(); 71 | 72 | // If --compress force all benchmarks to compress final buffer 73 | if(argv.compress){ 74 | rule.options.compress = true; 75 | } 76 | 77 | function run(cb) { 78 | 79 | function done(err,result,callback) { 80 | if (rule.options.compress){ 81 | if(result[0] !== 0x1F && result[1] !== 0x8B){ 82 | throw new Error('resulting buffer is not compressed!'); 83 | } 84 | } 85 | ++runs; 86 | 87 | if (track_mem && runs % 1000) { 88 | var mem = process.memoryUsage(); 89 | if (mem.rss > memstats.max_rss) memstats.max_rss = mem.rss; 90 | if (mem.heapTotal > memstats.max_heap_total) memstats.max_heap_total = mem.heapTotal; 91 | if (mem.heapUsed > memstats.max_heap) memstats.max_heap = mem.heapUsed; 92 | } 93 | 94 | return cb(); 95 | } 96 | 97 | switch(argv.package){ 98 | case 'vtcomposite': 99 | composite(rule.tiles, rule.zxy, rule.options, function(err, result) { 100 | if (err) { 101 | throw err; 102 | } 103 | return done(null,result); 104 | }); 105 | break; 106 | case 'node-mapnik': 107 | var target_vt = new mapnik.VectorTile(rule.zxy.z, rule.zxy.x, rule.zxy.y); 108 | target_vt.bufferSize = rule.options.buffer_size; 109 | let addDataQueue = Queue(); 110 | function addData(tile,done) { 111 | var vt = new mapnik.VectorTile(tile.z,tile.x,tile.y); 112 | vt.bufferSize = rule.options.buffer_size; 113 | vt.addData(tile.buffer,function(err) { 114 | if (err) throw err; 115 | return done(null,vt); 116 | }); 117 | } 118 | for (var i = 0; i < rule.tiles.length; ++i) 119 | { 120 | addDataQueue.defer(addData,rule.tiles[i]); 121 | } 122 | addDataQueue.awaitAll(function(error,source_tiles) { 123 | if (error) throw error; 124 | // http://mapnik.org/documentation/node-mapnik/3.6/#VectorTile.composite 125 | target_vt.composite(source_tiles, rule.options, function(err, result) { 126 | if (err) { 127 | return cb(err); 128 | } 129 | 130 | let options = {compression:'none'} 131 | if (rule.options.compress){ 132 | options.compression = 'gzip'; 133 | } 134 | result.getData(options, done); 135 | }); 136 | }); 137 | break; 138 | default: 139 | throw new Error("invalid --package option: "+ argv.package) 140 | } 141 | } 142 | 143 | // Start monitoring time before async work begins within the defer iterator below. 144 | // AsyncWorkers will kick off actual work before the defer iterator is finished, 145 | // and we want to make sure we capture the time of the work of that initial cycle. 146 | var time = +(new Date()); 147 | 148 | for (var i = 0; i < argv.iterations; i++) { 149 | runsQueue.defer(run); 150 | } 151 | 152 | runsQueue.awaitAll(function(error) { 153 | if (error) throw error; 154 | if (runs != argv.iterations) { 155 | throw new Error(`Error: did not run as expected - ${runs} != ${argv.iterations}`); 156 | } 157 | // check rate 158 | time = +(new Date()) - time; 159 | 160 | if (time == 0) { 161 | console.log("Warning: ms timer not high enough resolution to reliably track rate. Try more iterations"); 162 | } else { 163 | // number of milliseconds per iteration 164 | var rate = runs/(time/1000); 165 | process.stdout.write(rate.toFixed(0) + ' runs/s (' + time + 'ms)'); 166 | } 167 | 168 | // There may be instances when you want to assert some performance metric 169 | //assert.equal(rate > 1000, true, 'speed not at least 1000/second ( rate was ' + rate + ' runs/s )'); 170 | ++ruleCount; 171 | return ruleCallback(); 172 | }); 173 | } 174 | 175 | function log(message) { 176 | if (argv.output && argv.output === 'json') { 177 | // handle JSON output 178 | } else { 179 | process.stdout.write(message); 180 | } 181 | } 182 | 183 | process.on('exit',function() { 184 | if (track_mem) { 185 | console.log('Benchmark peak mem (max_rss, max_heap, max_heap_total): ', bytes(memstats.max_rss), bytes(memstats.max_heap), bytes(memstats.max_heap_total)); 186 | } else { 187 | console.log('Note: pass --mem to track memory usage'); 188 | } 189 | console.log('Benchmark iterations:',argv.iterations,'concurrency:',argv.concurrency); 190 | }) 191 | -------------------------------------------------------------------------------- /bench/rules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const mvtFixtures = require('@mapbox/mvt-fixtures'); 6 | 7 | module.exports = [ 8 | { 9 | description: 'single tile in/out', 10 | options: {buffer_size: 128}, 11 | tiles: [ 12 | { z: 15, x: 5239, y: 12666, buffer: getTile('sanfrancisco', '15-5239-12666.mvt')} 13 | ], 14 | zxy: { z: 15, x: 5239, y: 12666} 15 | }, 16 | { 17 | description: 'two different tiles at the same zoom level, zero buffer', 18 | options: {buffer_size: 128}, 19 | tiles: [ 20 | { z: 15, x: 5239, y: 12666, buffer: getTile('sanfrancisco', '15-5239-12666.mvt')}, 21 | { z: 15, x: 5239, y: 12666, buffer: getTile('osm-qa-astana', '12-2861-1368.mvt')} 22 | ], 23 | zxy: { z: 15, x: 5239, y: 12666} 24 | }, 25 | { 26 | description: 'two different tiles from different zoom levels (separated by one zoom), zero buffer', 27 | options: {buffer_size: 128 }, 28 | tiles: [ 29 | { z: 0, x: 0, y: 0, buffer: getTile('sanfrancisco', '15-5239-12666.mvt')}, 30 | { z: 1, x: 0, y: 0, buffer: getTile('osm-qa-astana', '12-2861-1368.mvt')} 31 | ], 32 | zxy: { z: 1, x: 0, y: 0} 33 | }, 34 | { 35 | description: 'two different tiles from different zoom levels (separated by more than one zoom), zero buffer', 36 | options: {buffer_size: 128 }, 37 | tiles: [ 38 | { z: 0, x: 0, y: 0, buffer: getTile('sanfrancisco', '15-5239-12666.mvt')}, 39 | { z: 10, x: 0, y: 0, buffer: getTile('osm-qa-astana', '12-2861-1368.mvt')} 40 | ], 41 | zxy: { z: 10, x: 0, y: 0} 42 | }, 43 | { 44 | description: 'tiles completely made of points, overzooming, no properties', 45 | options: {buffer_size: 128 }, 46 | tiles: [ 47 | { z: 0, x: 0, y: 0, buffer: fs.readFileSync('./test/fixtures/points-16-10498-22872.mvt')} 48 | ], 49 | zxy: { z: 1, x: 0, y: 0} 50 | }, 51 | { 52 | description: 'tiles completely made of points, same zoom, no properties', 53 | options: {buffer_size: 128 }, 54 | tiles: [ 55 | { z: 0, x: 0, y: 0, buffer: fs.readFileSync('./test/fixtures/points-16-10498-22872.mvt')} 56 | ], 57 | zxy: { z: 0, x: 0, y: 0} 58 | }, 59 | { 60 | description: 'tiles completely made of points, overzoooming, lots of properties', 61 | options: {buffer_size: 128 }, 62 | tiles: [ 63 | { z: 0, x: 0, y: 0, buffer: fs.readFileSync('./test/fixtures/points-properties-16-10498-22872.mvt')} 64 | ], 65 | zxy: { z: 1, x: 0, y: 0} 66 | }, 67 | { 68 | description: 'tiles completely made of points, same zoom, lots of properties', 69 | options: {buffer_size: 128 }, 70 | tiles: [ 71 | { z: 0, x: 0, y: 0, buffer: fs.readFileSync('./test/fixtures/points-properties-16-10498-22872.mvt')} 72 | ], 73 | zxy: { z: 0, x: 0, y: 0} 74 | }, 75 | { 76 | description: 'buffer_size 128 - tiles completely made of points, same zoom, lots of properties', 77 | options: { buffer_size: 128}, 78 | tiles: [ 79 | { z: 0, x: 0, y: 0, buffer: fs.readFileSync('./test/fixtures/points-properties-16-10498-22872.mvt')} 80 | ], 81 | zxy: { z: 0, x: 0, y: 0} 82 | }, 83 | { 84 | description: 'tiles completely made of linestrings, overzooming and lots of properties', 85 | options: {buffer_size: 128 }, 86 | tiles: [ 87 | { z: 0, x: 0, y: 0, buffer: fs.readFileSync('./test/fixtures/linestrings-properties-16-10498-22872.mvt')} 88 | ], 89 | zxy: { z: 1, x: 0, y: 0} 90 | }, 91 | { 92 | description: 'tiles completely made of polygons, overzooming and lots of properties', 93 | options: {buffer_size: 128 }, 94 | tiles: [ 95 | { z: 0, x: 0, y: 0, buffer: fs.readFileSync('./test/fixtures/polygons-properties-16-10498-22872.mvt')} 96 | ], 97 | zxy: { z: 1, x: 0, y: 0} 98 | }, 99 | { 100 | description: 'tiles completely made of points and linestrings, overzooming and lots of properties', 101 | options: {buffer_size: 128 }, 102 | tiles: [ 103 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/points-poi-sf-15-5239-12666.mvt')}, 104 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/linestrings-sf-15-5239-12666.mvt')} 105 | ], 106 | zxy: { z: 16, x: 10478, y: 25332} 107 | }, 108 | { 109 | description: 'buffer_size 128 - tiles completely made of points and linestrings, overzooming and lots of properties', 110 | options: { buffer_size: 128 }, 111 | tiles: [ 112 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/points-poi-sf-15-5239-12666.mvt')}, 113 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/linestrings-sf-15-5239-12666.mvt')} 114 | ], 115 | zxy: { z: 16, x: 10478, y: 25332} 116 | }, 117 | { 118 | description: 'tiles completely made of points and linestrings, overzooming (2x) and lots of properties', 119 | options: {buffer_size: 128 }, 120 | tiles: [ 121 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/points-poi-sf-15-5239-12666.mvt')}, 122 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/linestrings-sf-15-5239-12666.mvt')} 123 | ], 124 | zxy: { z: 17, x: 20956, y: 50664} 125 | }, 126 | { 127 | description: 'tiles completely made of polygons, overzooming and lots of properties', 128 | options: { buffer_size: 128}, 129 | tiles: [ 130 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/polygons-buildings-sf-15-5239-12666.mvt')}, 131 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/polygons-hillshade-sf-15-5239-12666.mvt')} 132 | ], 133 | zxy: { z: 16, x: 10478, y: 25332} 134 | }, 135 | { 136 | description: 'tiles completely made of polygons, overzooming (2x) and lots of properties', 137 | options: {buffer_size: 128 }, 138 | tiles: [ 139 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/polygons-buildings-sf-15-5239-12666.mvt')}, 140 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/polygons-hillshade-sf-15-5239-12666.mvt')} 141 | ], 142 | zxy: { z: 17, x: 20956, y: 50664} 143 | }, 144 | { 145 | description: 'buffer_size 4096 - tiles completely made of polygons, overzooming (2x) and lots of properties', 146 | options: {buffer_size: 4096}, 147 | tiles: [ 148 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/polygons-buildings-sf-15-5239-12666.mvt')}, 149 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/polygons-hillshade-sf-15-5239-12666.mvt')} 150 | ], 151 | zxy: { z: 17, x: 20956, y: 50664} 152 | }, 153 | { 154 | description: 'single tile, dropping layers', 155 | options: {buffer_size: 128}, 156 | tiles: [ 157 | { z: 15, x: 5239, y: 12666, buffer: getTile('sanfrancisco', '15-5239-12666.mvt'), layers: ['building']} 158 | ], 159 | zxy: { z: 15, x: 5239, y: 12666} 160 | }, 161 | ]; 162 | 163 | function getTile(name, file) { 164 | return fs.readFileSync(path.join(__dirname, '..', 'node_modules', '@mapbox', 'mvt-fixtures', 'real-world', name, file)) 165 | } 166 | 167 | // get all tiles 168 | function getTiles(name) { 169 | let tiles = []; 170 | let dir = `./node_modules/@mapbox/mvt-fixtures/real-world/${name}`; 171 | var files = fs.readdirSync(dir); 172 | files.forEach(function(file) { 173 | let buffer = fs.readFileSync(path.join(dir, '/', file)); 174 | file = file.replace('.mvt', ''); 175 | let zxy = file.split('-'); 176 | tiles.push({ buffer: buffer, z: parseInt(zxy[0]), x: parseInt(zxy[1]), y: parseInt(zxy[2]) }); 177 | }); 178 | return tiles; 179 | } 180 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | # This file inherits default targets for Node addons, see https://github.com/nodejs/node-gyp/blob/master/addon.gypi 2 | { 3 | # https://github.com/springmeyer/gyp/blob/master/test/make_global_settings/wrapper/wrapper.gyp 4 | 'make_global_settings': [ 5 | ['CXX', '<(module_root_dir)/mason_packages/.link/bin/clang++'], 6 | ['CC', '<(module_root_dir)/mason_packages/.link/bin/clang'], 7 | ['LINK', '<(module_root_dir)/mason_packages/.link/bin/clang++'], 8 | ['AR', '<(module_root_dir)/mason_packages/.link/bin/llvm-ar'], 9 | ['NM', '<(module_root_dir)/mason_packages/.link/bin/llvm-nm'] 10 | ], 11 | 'includes': [ 'common.gypi'], # brings in a default set of options that are inherited from gyp 12 | 'variables': { # custom variables we use specific to this file 13 | 'error_on_warnings%':'true', # can be overriden by a command line variable because of the % sign using "WERROR" (defined in Makefile) 14 | # Use this variable to silence warnings from mason dependencies 15 | # It's a variable to make easy to pass to 16 | # cflags (linux) and xcode (mac) 17 | 'system_includes': [ 18 | "-isystem build/compile_commands.json 40 | fi 41 | 42 | fi 43 | 44 | # change into the build directory so that clang-tidy can find the files 45 | # at the right paths (since this is where the actual build happens) 46 | cd build 47 | ${PATH_TO_CLANG_TIDY_SCRIPT} -fix 48 | cd ../ 49 | 50 | # Print list of modified files 51 | dirty=$(git ls-files --modified src/) 52 | 53 | if [[ $dirty ]]; then 54 | echo "The following files have been modified:" 55 | echo $dirty 56 | git diff 57 | exit 1 58 | else 59 | exit 0 60 | fi 61 | -------------------------------------------------------------------------------- /scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | # http://clang.llvm.org/docs/UsersManual.html#profiling-with-instrumentation 7 | # https://www.bignerdranch.com/blog/weve-got-you-covered/ 8 | 9 | make clean 10 | export CXXFLAGS="-fprofile-instr-generate -fcoverage-mapping" 11 | export LDFLAGS="-fprofile-instr-generate" 12 | make debug 13 | rm -f *profraw 14 | rm -f *gcov 15 | rm -f *profdata 16 | LLVM_PROFILE_FILE="code-%p.profraw" npm test 17 | CXX_MODULE=$(./node_modules/.bin/node-pre-gyp reveal module --silent) 18 | export PATH=$(pwd)/mason_packages/.link/bin/:${PATH} 19 | llvm-profdata merge -output=code.profdata code-*.profraw 20 | llvm-cov report ${CXX_MODULE} -instr-profile=code.profdata -use-color 21 | llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -filename-equivalence -use-color 22 | llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -filename-equivalence -use-color --format html > /tmp/coverage.html 23 | echo "open /tmp/coverage.html for HTML version of this report" 24 | -------------------------------------------------------------------------------- /scripts/create_scheme.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | CONTAINER=build/binding.xcodeproj 7 | OUTPUT="${CONTAINER}/xcshareddata/xcschemes/${SCHEME_NAME}.xcscheme" 8 | 9 | # Required ENV vars: 10 | # - SCHEME_TYPE: type of the scheme 11 | # - SCHEME_NAME: name of the scheme 12 | 13 | # Optional ENV vars: 14 | # - NODE_ARGUMENT (defaults to "") 15 | # - BUILDABLE_NAME (defaults ot SCHEME_NAME) 16 | # - BLUEPRINT_NAME (defaults ot SCHEME_NAME) 17 | 18 | 19 | # Try to reuse the existing Blueprint ID if the scheme already exists. 20 | if [ -f "${OUTPUT}" ]; then 21 | BLUEPRINT_ID=$(sed -n "s/[ \t]*BlueprintIdentifier *= *\"\([A-Z0-9]\{24\}\)\"/\\1/p" "${OUTPUT}" | head -1) 22 | fi 23 | 24 | NODE_ARGUMENT=${NODE_ARGUMENT:-} 25 | BLUEPRINT_ID=${BLUEPRINT_ID:-$(hexdump -n 12 -v -e '/1 "%02X"' /dev/urandom)} 26 | BUILDABLE_NAME=${BUILDABLE_NAME:-${SCHEME_NAME}} 27 | BLUEPRINT_NAME=${BLUEPRINT_NAME:-${SCHEME_NAME}} 28 | 29 | mkdir -p "${CONTAINER}/xcshareddata/xcschemes" 30 | 31 | sed "\ 32 | s#{{BLUEPRINT_ID}}#${BLUEPRINT_ID}#;\ 33 | s#{{BLUEPRINT_NAME}}#${BLUEPRINT_NAME}#;\ 34 | s#{{BUILDABLE_NAME}}#${BUILDABLE_NAME}#;\ 35 | s#{{CONTAINER}}#${CONTAINER}#;\ 36 | s#{{WORKING_DIRECTORY}}#$(pwd)#;\ 37 | s#{{NODE_PATH}}#$(dirname `which node`)#;\ 38 | s#{{NODE_ARGUMENT}}#${NODE_ARGUMENT}#" \ 39 | scripts/${SCHEME_TYPE}.xcscheme > "${OUTPUT}" 40 | -------------------------------------------------------------------------------- /scripts/generate_compile_commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import json 5 | import os 6 | import re 7 | 8 | # Script to generate compile_commands.json based on Makefile output 9 | # Works by accepting Makefile output from stdin, parsing it, and 10 | # turning into json records. These are then printed to stdout. 11 | # More details on the compile_commands format at: 12 | # https://clang.llvm.org/docs/JSONCompilationDatabase.html 13 | # 14 | # Note: make must be run in verbose mode, e.g. V=1 make or VERBOSE=1 make 15 | # 16 | # Usage with node-cpp-skel: 17 | # 18 | # make | ./scripts/generate_compile_commands.py > build/compile_commands.json 19 | 20 | # These work for node-cpp-skel to detect the files being compiled 21 | # They may need to be modified if you adapt this to another tool 22 | matcher = re.compile('^(.*) (.+cpp)') 23 | build_dir = os.path.join(os.getcwd(),"build") 24 | TOKEN_DENOTING_COMPILED_FILE='NODE_GYP_MODULE_NAME' 25 | 26 | def generate(): 27 | compile_commands = [] 28 | for line in sys.stdin.readlines(): 29 | if TOKEN_DENOTING_COMPILED_FILE in line: 30 | match = matcher.match(line) 31 | compile_commands.append({ 32 | "directory": build_dir, 33 | "command": line.strip(), 34 | "file": os.path.normpath(os.path.join(build_dir,match.group(2))) 35 | }) 36 | print(json.dumps(compile_commands,indent=4)) 37 | 38 | if __name__ == '__main__': 39 | generate() 40 | -------------------------------------------------------------------------------- /scripts/library.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /scripts/liftoff.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | # First create new repo on GitHub and copy the SSH repo url 6 | # Then run "./scripts/liftoff.sh" from within your local node-cpp-skel root directory 7 | # and it will create your new local project repo side by side with node-cpp-skel directory 8 | 9 | echo "What is the name of your new project? " 10 | read name 11 | echo "What is the remote repo url for your new project? " 12 | read url 13 | 14 | mkdir ../$name 15 | cp -R ../node-cpp-skel/. ../$name/ 16 | cd ../$name/ 17 | rm -rf .git 18 | git init 19 | 20 | git checkout -b node-cpp-skel-port 21 | git add . 22 | git commit -m "Port from node-cpp-skel" 23 | git remote add origin $url 24 | git push -u origin node-cpp-skel-port 25 | 26 | # Perhaps useful for fresh start, also check out https://github.com/mapbox/node-cpp-skel#add-custom-code 27 | # cp /dev/null CHANGELOG.md 28 | # cp /dev/null README.md -------------------------------------------------------------------------------- /scripts/node.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 46 | 49 | 50 | 51 | 57 | 58 | 59 | 60 | 63 | 64 | 65 | 66 | 70 | 71 | 72 | 73 | 74 | 75 | 81 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | export COMMIT_MESSAGE=$(git log --format=%B --no-merges -n 1 | tr -d '\n') 7 | 8 | # `is_pr_merge` is designed to detect if a gitsha represents a normal 9 | # push commit (to any branch) or whether it represents travis attempting 10 | # to merge between the origin and the upstream branch. 11 | # For more details see: https://docs.travis-ci.com/user/pull-requests 12 | function is_pr_merge() { 13 | # Get the commit message via git log 14 | # This should always be the exactly the text the developer provided 15 | local COMMIT_LOG=${COMMIT_MESSAGE} 16 | 17 | # Get the commit message via git show 18 | # If the gitsha represents a merge then this will 19 | # look something like "Merge e3b1981 into 615d2a3" 20 | # Otherwise it will be the same as the "git log" output 21 | export COMMIT_SHOW=$(git show -s --format=%B | tr -d '\n') 22 | 23 | if [[ "${COMMIT_LOG}" != "${COMMIT_SHOW}" ]]; then 24 | echo true 25 | fi 26 | } 27 | 28 | # `publish` is used to publish binaries to s3 via commit messages if: 29 | # - the commit message includes [publish binary] 30 | # - the commit message includes [republish binary] 31 | # - the commit is not a pr_merge (checked with `is_pr_merge` function) 32 | function publish() { 33 | echo "dumping binary meta..." 34 | ./node_modules/.bin/node-pre-gyp reveal --loglevel=error $@ 35 | 36 | echo "determining publishing status..." 37 | 38 | if [[ $(is_pr_merge) ]]; then 39 | echo "Skipping publishing because this is a PR merge commit" 40 | else 41 | echo "Commit message: ${COMMIT_MESSAGE}" 42 | 43 | if [[ ${COMMIT_MESSAGE} =~ "[publish binary]" ]]; then 44 | echo "Publishing" 45 | ./node_modules/.bin/node-pre-gyp package publish $@ 46 | elif [[ ${COMMIT_MESSAGE} =~ "[republish binary]" ]]; then 47 | echo "Re-Publishing" 48 | ./node_modules/.bin/node-pre-gyp package unpublish publish $@ 49 | else 50 | echo "Skipping publishing since we did not detect either [publish binary] or [republish binary] in commit message" 51 | fi 52 | fi 53 | } 54 | 55 | function usage() { 56 | >&2 echo "Usage" 57 | >&2 echo "" 58 | >&2 echo "$ ./scripts/publish.sh " 59 | >&2 echo "" 60 | >&2 echo "All args are forwarded to node-pre-gyp like --debug" 61 | >&2 echo "" 62 | exit 1 63 | } 64 | 65 | # https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash 66 | for i in "$@" 67 | do 68 | case $i in 69 | -h | --help) 70 | usage 71 | shift 72 | ;; 73 | *) 74 | ;; 75 | esac 76 | done 77 | 78 | publish $@ -------------------------------------------------------------------------------- /scripts/sanitize.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | : ' 7 | 8 | Rebuilds the code with the sanitizers and runs the tests 9 | 10 | ' 11 | 12 | # See https://github.com/mapbox/node-cpp-skel/blob/master/docs/extended-tour.md#configuration-files 13 | 14 | make clean 15 | 16 | # https://github.com/google/sanitizers/wiki/AddressSanitizerAsDso 17 | SHARED_LIB_EXT=.so 18 | if [[ $(uname -s) == 'Darwin' ]]; then 19 | SHARED_LIB_EXT=.dylib 20 | fi 21 | 22 | export MASON_LLVM_RT_PRELOAD=$(pwd)/$(ls mason_packages/.link/lib/clang/*/lib/*/libclang_rt.asan*${SHARED_LIB_EXT}) 23 | SUPPRESSION_FILE="/tmp/leak_suppressions.txt" 24 | echo "leak:__strdup" > ${SUPPRESSION_FILE} 25 | echo "leak:v8::internal" >> ${SUPPRESSION_FILE} 26 | echo "leak:node::NodeMainInstance::CreateMainEnvironment" >> ${SUPPRESSION_FILE} 27 | echo "leak:node::ExecuteBootstrapper" >> ${SUPPRESSION_FILE} 28 | echo "leak:node::native_module::NativeModuleLoader" >> ${SUPPRESSION_FILE} 29 | export ASAN_SYMBOLIZER_PATH=$(pwd)/mason_packages/.link/bin/llvm-symbolizer 30 | export MSAN_SYMBOLIZER_PATH=$(pwd)/mason_packages/.link/bin/llvm-symbolizer 31 | export UBSAN_OPTIONS=print_stacktrace=1 32 | export LSAN_OPTIONS=suppressions=${SUPPRESSION_FILE} 33 | export ASAN_OPTIONS=detect_leaks=1:symbolize=1:abort_on_error=1:detect_container_overflow=1:check_initialization_order=1:detect_stack_use_after_return=1 34 | export MASON_SANITIZE="-fsanitize=address,undefined,integer,leak -fno-sanitize=vptr,function" 35 | export MASON_SANITIZE_CXXFLAGS="${MASON_SANITIZE} -fno-sanitize=vptr,function -fsanitize-address-use-after-scope -fno-omit-frame-pointer -fno-common" 36 | export MASON_SANITIZE_LDFLAGS="${MASON_SANITIZE}" 37 | # Note: to build without stopping on errors remove the -fno-sanitize-recover=all flag 38 | # You might want to do this if there are multiple errors and you want to see them all before fixing 39 | export CXXFLAGS="${MASON_SANITIZE_CXXFLAGS} ${CXXFLAGS:-} -fno-sanitize-recover=all" 40 | export LDFLAGS="${MASON_SANITIZE_LDFLAGS} ${LDFLAGS:-}" 41 | make debug 42 | export ASAN_OPTIONS=fast_unwind_on_malloc=0:${ASAN_OPTIONS} 43 | if [[ $(uname -s) == 'Darwin' ]]; then 44 | # NOTE: we must call node directly here rather than `npm test` 45 | # because OS X blocks `DYLD_INSERT_LIBRARIES` being inherited by sub shells 46 | # If this is not done right we'll see 47 | # ==18464==ERROR: Interceptors are not working. This may be because AddressSanitizer is loaded too late (e.g. via dlopen). 48 | # 49 | DYLD_INSERT_LIBRARIES=${MASON_LLVM_RT_PRELOAD} \ 50 | node node_modules/.bin/tape test/*test.js 51 | else 52 | LD_PRELOAD=${MASON_LLVM_RT_PRELOAD} \ 53 | npm test 54 | fi 55 | 56 | -------------------------------------------------------------------------------- /src/feature_builder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // geometry.hpp 4 | #include 5 | #include 6 | // vtzero 7 | #include 8 | #include 9 | // boost 10 | #include 11 | #include 12 | // stl 13 | #include 14 | //BOOST_GEOMETRY_REGISTER_POINT_2D(mapbox::geometry::point, int, boost::geometry::cs::cartesian, x, y) 15 | // ^ Uncomment to enable coordinate_type = int ^ 16 | 17 | namespace vtile { 18 | namespace detail { 19 | 20 | template 21 | struct point_handler 22 | { 23 | using geom_type = mapbox::geometry::multi_point; 24 | point_handler(geom_type& geom, std::uint32_t dx, std::uint32_t dy, std::uint32_t zoom_factor, mapbox::geometry::box const& bbox) 25 | : geom_(geom), 26 | bbox_(bbox), 27 | dx_(dx), 28 | dy_(dy), 29 | zoom_factor_(zoom_factor) 30 | { 31 | } 32 | 33 | void points_begin(std::uint32_t /*unused*/) 34 | { 35 | } 36 | 37 | void points_point(vtzero::point const& pt) 38 | { 39 | CoordinateType x = pt.x * static_cast(zoom_factor_) - static_cast(dx_); 40 | CoordinateType y = pt.y * static_cast(zoom_factor_) - static_cast(dy_); 41 | mapbox::geometry::point pt0{x, y}; 42 | if (boost::geometry::covered_by(pt0, bbox_)) 43 | { 44 | geom_.push_back(std::move(pt0)); 45 | } 46 | } 47 | 48 | void points_end() {} 49 | 50 | geom_type& geom_; 51 | mapbox::geometry::box const& bbox_; 52 | std::uint32_t const dx_; 53 | std::uint32_t const dy_; 54 | std::uint32_t const zoom_factor_; 55 | }; 56 | 57 | template 58 | struct line_string_handler 59 | { 60 | using geom_type = mapbox::geometry::multi_line_string; 61 | 62 | line_string_handler(geom_type& geom, std::uint32_t dx, std::uint32_t dy, std::uint32_t zoom_factor) 63 | : geom_(geom), 64 | dx_(dx), 65 | dy_(dy), 66 | zoom_factor_(zoom_factor) 67 | { 68 | } 69 | 70 | void linestring_begin(std::uint32_t count) 71 | { 72 | first_ = true; 73 | geom_.emplace_back(); 74 | geom_.back().reserve(count); 75 | } 76 | 77 | void linestring_point(vtzero::point const& pt) 78 | { 79 | if (first_ || pt.x != cur_x_ || pt.y != cur_y_) 80 | { 81 | CoordinateType x = pt.x * static_cast(zoom_factor_) - static_cast(dx_); 82 | CoordinateType y = pt.y * static_cast(zoom_factor_) - static_cast(dy_); 83 | geom_.back().emplace_back(x, y); 84 | cur_x_ = pt.x; 85 | cur_y_ = pt.y; 86 | first_ = false; 87 | } 88 | } 89 | 90 | void linestring_end() {} 91 | 92 | geom_type& geom_; 93 | std::uint32_t const dx_; 94 | std::uint32_t const dy_; 95 | std::uint32_t const zoom_factor_; 96 | CoordinateType cur_x_ = 0; 97 | CoordinateType cur_y_ = 0; 98 | bool first_ = true; 99 | }; 100 | 101 | template 102 | using annotated_ring = std::pair, vtzero::ring_type>; 103 | 104 | template 105 | struct polygon_handler 106 | { 107 | using geom_type = std::vector>; 108 | polygon_handler(geom_type& geom, std::uint32_t dx, std::uint32_t dy, std::uint32_t zoom_factor) 109 | : geom_(geom), 110 | dx_(dx), 111 | dy_(dy), 112 | zoom_factor_(zoom_factor) {} 113 | 114 | void ring_begin(std::uint32_t count) 115 | { 116 | first_ = true; 117 | geom_.emplace_back(); 118 | geom_.back().first.reserve(count); 119 | } 120 | 121 | void ring_point(vtzero::point const& pt) 122 | { 123 | if (first_ || pt.x != cur_x_ || pt.y != cur_y_) 124 | { 125 | CoordinateType x = pt.x * static_cast(zoom_factor_) - static_cast(dx_); 126 | CoordinateType y = pt.y * static_cast(zoom_factor_) - static_cast(dy_); 127 | geom_.back().first.emplace_back(x, y); 128 | cur_x_ = pt.x; 129 | cur_y_ = pt.y; 130 | first_ = false; 131 | } 132 | } 133 | 134 | void ring_end(vtzero::ring_type type) 135 | { 136 | geom_.back().second = type; 137 | } 138 | 139 | geom_type& geom_; 140 | std::uint32_t const dx_; 141 | std::uint32_t const dy_; 142 | std::uint32_t const zoom_factor_; 143 | CoordinateType cur_x_ = 0; 144 | CoordinateType cur_y_ = 0; 145 | bool first_ = true; 146 | }; 147 | 148 | } // namespace detail 149 | 150 | template 151 | struct overzoomed_feature_builder 152 | { 153 | using coordinate_type = CoordinateType; 154 | overzoomed_feature_builder(vtzero::layer_builder& layer_builder, 155 | vtzero::property_mapper& mapper, 156 | mapbox::geometry::box const& bbox, 157 | std::uint32_t dx, std::uint32_t dy, std::uint32_t zoom_factor) 158 | : layer_builder_{layer_builder}, 159 | mapper_{mapper}, 160 | bbox_{bbox}, 161 | dx_{dx}, 162 | dy_{dy}, 163 | zoom_factor_{zoom_factor} {} 164 | 165 | template 166 | void finalize(FeatureBuilder& builder, vtzero::feature const& feature) 167 | { 168 | // add properties 169 | builder.copy_properties(feature, mapper_); 170 | builder.commit(); 171 | } 172 | 173 | void apply_geometry_point(vtzero::feature const& feature) 174 | { 175 | mapbox::geometry::multi_point multi_point; 176 | vtzero::decode_point_geometry(feature.geometry(), detail::point_handler(multi_point, dx_, dy_, zoom_factor_, bbox_)); 177 | if (!multi_point.empty()) 178 | { 179 | vtzero::point_feature_builder feature_builder{layer_builder_}; 180 | feature_builder.copy_id(feature); 181 | feature_builder.add_points_from_container(multi_point); 182 | finalize(feature_builder, feature); 183 | } 184 | } 185 | 186 | void apply_geometry_linestring(vtzero::feature const& feature) 187 | { 188 | mapbox::geometry::multi_line_string multi_line; 189 | vtzero::decode_linestring_geometry(feature.geometry(), detail::line_string_handler(multi_line, dx_, dy_, zoom_factor_)); 190 | std::vector> result; 191 | boost::geometry::intersection(multi_line, bbox_, result); 192 | if (!result.empty()) 193 | { 194 | vtzero::linestring_feature_builder feature_builder{layer_builder_}; 195 | feature_builder.copy_id(feature); 196 | bool valid = false; 197 | for (auto const& l : result) 198 | { 199 | if (l.size() > 1) 200 | { 201 | feature_builder.add_linestring(static_cast(l.size())); 202 | auto itr = l.cbegin(); 203 | auto last_pt = *itr++; 204 | feature_builder.set_point(static_cast(last_pt.x), static_cast(last_pt.y)); 205 | for (auto const& end = l.end(); itr != end; ++itr) 206 | { 207 | if (*itr != last_pt) 208 | { 209 | valid = true; 210 | feature_builder.set_point(static_cast(itr->x), static_cast(itr->y)); 211 | last_pt = *itr; 212 | } 213 | } 214 | } 215 | } 216 | if (valid) 217 | { 218 | finalize(feature_builder, feature); 219 | } 220 | } 221 | } 222 | void apply_geometry_polygon(vtzero::feature const& feature) 223 | { 224 | std::vector> rings; 225 | vtzero::decode_polygon_geometry(feature.geometry(), detail::polygon_handler(rings, dx_, dy_, zoom_factor_)); 226 | std::vector> polygons; 227 | bool process = false; 228 | for (auto const& r : rings) 229 | { 230 | if (r.second == vtzero::ring_type::outer) 231 | { 232 | auto extent = mapbox::geometry::envelope(r.first); 233 | process = boost::geometry::intersects(extent, bbox_); 234 | if (process) 235 | { 236 | polygons.emplace_back(); // start new polygon 237 | } 238 | } 239 | if (process && r.first.size() > 3) 240 | { 241 | polygons.back().push_back(std::move(r.first)); 242 | } 243 | } 244 | if (!polygons.empty()) 245 | { 246 | vtzero::polygon_feature_builder feature_builder{layer_builder_}; 247 | feature_builder.copy_id(feature); 248 | bool valid = false; 249 | for (auto const& poly : polygons) 250 | { 251 | std::vector> result; 252 | boost::geometry::intersection(poly, bbox_, result); 253 | for (auto const& p : result) 254 | { 255 | for (auto const& ring : p) 256 | { 257 | if (ring.size() > 3) 258 | { 259 | valid = true; 260 | feature_builder.add_ring(static_cast(ring.size())); 261 | std::for_each(ring.begin(), ring.end(), 262 | [&feature_builder](auto const& pt) { feature_builder.set_point(static_cast(pt.x), static_cast(pt.y)); }); 263 | } 264 | } 265 | } 266 | } 267 | if (valid) 268 | { 269 | finalize(feature_builder, feature); 270 | } 271 | } 272 | } 273 | 274 | void apply(vtzero::feature const& feature) 275 | { 276 | switch (feature.geometry_type()) 277 | { 278 | case vtzero::GeomType::POINT: 279 | apply_geometry_point(feature); 280 | break; 281 | case vtzero::GeomType::LINESTRING: 282 | apply_geometry_linestring(feature); 283 | break; 284 | case vtzero::GeomType::POLYGON: 285 | apply_geometry_polygon(feature); 286 | break; 287 | default: 288 | // LCOV_EXCL_START 289 | break; 290 | // LCOV_EXCL_STOP 291 | } 292 | } 293 | vtzero::layer_builder& layer_builder_; 294 | vtzero::property_mapper& mapper_; 295 | mapbox::geometry::box const& bbox_; 296 | std::uint32_t dx_; 297 | std::uint32_t dy_; 298 | std::uint32_t zoom_factor_; 299 | }; 300 | 301 | } // namespace vtile 302 | -------------------------------------------------------------------------------- /src/module.cpp: -------------------------------------------------------------------------------- 1 | #include "vtcomposite.hpp" 2 | #include 3 | 4 | Napi::Object init(Napi::Env env, Napi::Object exports) 5 | { 6 | exports.Set(Napi::String::New(env, "composite"), Napi::Function::New(env, vtile::composite)); 7 | exports.Set(Napi::String::New(env, "localize"), Napi::Function::New(env, vtile::localize)); 8 | return exports; 9 | } 10 | 11 | NODE_API_MODULE(module, init) // NOLINT 12 | -------------------------------------------------------------------------------- /src/module_utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace utils { 9 | 10 | inline Napi::Value CallbackError(std::string const& message, Napi::CallbackInfo const& info) 11 | { 12 | Napi::Object obj = Napi::Object::New(info.Env()); 13 | obj.Set("message", message); 14 | auto func = info[info.Length() - 1].As(); 15 | return func.Call({obj}); 16 | } 17 | 18 | // splits a string by comma 19 | inline std::vector split(std::string const& input) 20 | { 21 | std::vector values; 22 | std::stringstream s_stream(input); 23 | while (s_stream.good()) 24 | { 25 | std::string substr; 26 | std::getline(s_stream, substr, ','); 27 | values.push_back(substr); 28 | } 29 | return values; 30 | } 31 | 32 | // checks if a string starts with a given substring 33 | inline bool startswith(std::string const& astring, std::string const& substring) 34 | { 35 | return substring.length() <= astring.length() && std::equal(substring.begin(), substring.end(), astring.begin()); 36 | } 37 | 38 | // finds the intersection of two vectors of strings 39 | // and assigns the intersection to a new vector passed by reference 40 | // results are returned in alphabetically ascending order 41 | // {"CN", "RU", "US"} + {"RU", "US"} => {"US", "RU"} 42 | void inline intersection( 43 | std::vector& v1, 44 | std::vector& v2, 45 | std::vector& result) 46 | { 47 | std::sort(v1.begin(), v1.end()); 48 | std::sort(v2.begin(), v2.end()); 49 | std::set_intersection(v1.begin(), v1.end(), 50 | v2.begin(), v2.end(), 51 | std::back_inserter(result)); 52 | } 53 | } // namespace utils 54 | -------------------------------------------------------------------------------- /src/vtcomposite.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace vtile { 5 | 6 | Napi::Value composite(const Napi::CallbackInfo& info); 7 | Napi::Value localize(const Napi::CallbackInfo& info); 8 | 9 | } // namespace vtile 10 | -------------------------------------------------------------------------------- /src/zxy_math.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace vtile { 7 | 8 | template 9 | inline bool within_target(T const& vt, std::uint32_t z, std::uint32_t x, std::uint32_t y) 10 | { 11 | if (vt.z > z) 12 | { 13 | return false; 14 | } 15 | auto dz = static_cast(z - vt.z); 16 | return ((x >> dz) == vt.x) && ((y >> dz) == vt.y); 17 | } 18 | 19 | inline std::tuple displacement(std::uint32_t source_z, std::uint32_t tile_size, std::uint32_t z, std::uint32_t x, std::uint32_t y) 20 | { 21 | std::uint32_t half_tile = tile_size >> 1U; 22 | std::uint32_t dx = 0; 23 | std::uint32_t dy = 0; 24 | std::uint32_t delta_z = z - source_z; 25 | for (std::uint32_t zi = delta_z; zi > 0; --zi) 26 | { 27 | half_tile <<= 1U; 28 | if ((x & 1U) != 0U) 29 | { 30 | dx += half_tile; 31 | } 32 | if ((y & 1U) != 0U) 33 | { 34 | dy += half_tile; 35 | } 36 | x >>= 1U; 37 | y >>= 1U; 38 | } 39 | return std::make_tuple(dx, dy); 40 | } 41 | 42 | } // namespace vtile 43 | -------------------------------------------------------------------------------- /test/fixtures/0.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/0.mvt -------------------------------------------------------------------------------- /test/fixtures/1.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/1.mvt -------------------------------------------------------------------------------- /test/fixtures/2.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/2.mvt -------------------------------------------------------------------------------- /test/fixtures/3.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/3.mvt -------------------------------------------------------------------------------- /test/fixtures/4.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/4.mvt -------------------------------------------------------------------------------- /test/fixtures/5.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/5.mvt -------------------------------------------------------------------------------- /test/fixtures/clipping-test-tile.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/clipping-test-tile.mvt -------------------------------------------------------------------------------- /test/fixtures/empty-overzoom-8-33-63.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/empty-overzoom-8-33-63.mvt -------------------------------------------------------------------------------- /test/fixtures/four-linestring-quadrants.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/four-linestring-quadrants.mvt -------------------------------------------------------------------------------- /test/fixtures/four-linestrings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": {}, 7 | "geometry": { 8 | "type": "LineString", 9 | "coordinates": [ 10 | [ 11 | -153.28125, 12 | 72.50172235139388 13 | ], 14 | [ 15 | -149.765625, 16 | 73.32785809840696 17 | ] 18 | ] 19 | } 20 | }, 21 | { 22 | "type": "Feature", 23 | "properties": {}, 24 | "geometry": { 25 | "type": "LineString", 26 | "coordinates": [ 27 | [ 28 | 96.328125, 29 | 72.28906720017675 30 | ], 31 | [ 32 | 99.84374999999999, 33 | 72.81607371878991 34 | ] 35 | ] 36 | } 37 | }, 38 | { 39 | "type": "Feature", 40 | "properties": {}, 41 | "geometry": { 42 | "type": "LineString", 43 | "coordinates": [ 44 | [ 45 | 84.375, 46 | -23.88583769986199 47 | ], 48 | [ 49 | 85.78125, 50 | -23.24134610238612 51 | ] 52 | ] 53 | } 54 | }, 55 | { 56 | "type": "Feature", 57 | "properties": {}, 58 | "geometry": { 59 | "type": "LineString", 60 | "coordinates": [ 61 | [ 62 | -129.375, 63 | -25.799891182088306 64 | ], 65 | [ 66 | -127.61718749999999, 67 | -25.48295117535531 68 | ] 69 | ] 70 | } 71 | } 72 | ] 73 | } -------------------------------------------------------------------------------- /test/fixtures/four-points-quadrants.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/four-points-quadrants.mvt -------------------------------------------------------------------------------- /test/fixtures/four-points-z0.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "quadrant": "top-left" 8 | }, 9 | "geometry": { 10 | "type": "Point", 11 | "coordinates": [ 12 | -123.74999999999999, 13 | 72.39570570653261 14 | ] 15 | } 16 | }, 17 | { 18 | "type": "Feature", 19 | "properties": { 20 | "quadrant": "top-right" 21 | }, 22 | "geometry": { 23 | "type": "Point", 24 | "coordinates": [ 25 | 87.1875, 26 | 74.77584300649235 27 | ] 28 | } 29 | }, 30 | { 31 | "type": "Feature", 32 | "properties": { 33 | "quadrant": "bottom-left" 34 | }, 35 | "geometry": { 36 | "type": "Point", 37 | "coordinates": [ 38 | -109.6875, 39 | -42.03297433244139 40 | ] 41 | } 42 | }, 43 | { 44 | "type": "Feature", 45 | "properties": { 46 | "quadrant": "bottom-right" 47 | }, 48 | "geometry": { 49 | "type": "Point", 50 | "coordinates": [ 51 | 77.34374999999999, 52 | -20.632784250388013 53 | ] 54 | } 55 | } 56 | ] 57 | } -------------------------------------------------------------------------------- /test/fixtures/four-points.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "quadrant": "top-left" 8 | }, 9 | "geometry": { 10 | "type": "Point", 11 | "coordinates": [ 12 | -123.74999999999999, 13 | 72.39570570653261 14 | ] 15 | } 16 | }, 17 | { 18 | "type": "Feature", 19 | "properties": { 20 | "quadrant": "top-right" 21 | }, 22 | "geometry": { 23 | "type": "Point", 24 | "coordinates": [ 25 | 87.1875, 26 | 74.77584300649235 27 | ] 28 | } 29 | }, 30 | { 31 | "type": "Feature", 32 | "properties": { 33 | "quadrant": "bottom-left" 34 | }, 35 | "geometry": { 36 | "type": "Point", 37 | "coordinates": [ 38 | -109.6875, 39 | -42.03297433244139 40 | ] 41 | } 42 | }, 43 | { 44 | "type": "Feature", 45 | "properties": { 46 | "quadrant": "bottom-right" 47 | }, 48 | "geometry": { 49 | "type": "Point", 50 | "coordinates": [ 51 | 77.34374999999999, 52 | -20.632784250388013 53 | ] 54 | } 55 | } 56 | ] 57 | } -------------------------------------------------------------------------------- /test/fixtures/linestrings-16-10498-22872.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/linestrings-16-10498-22872.mvt -------------------------------------------------------------------------------- /test/fixtures/linestrings-properties-16-10498-22872.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/linestrings-properties-16-10498-22872.mvt -------------------------------------------------------------------------------- /test/fixtures/linestrings-sf-15-5239-12666.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/linestrings-sf-15-5239-12666.mvt -------------------------------------------------------------------------------- /test/fixtures/mapbox-vector-terrain-v2-hillshade-15-6105-12723.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/mapbox-vector-terrain-v2-hillshade-15-6105-12723.mvt -------------------------------------------------------------------------------- /test/fixtures/multiline.mvt: -------------------------------------------------------------------------------- 1 | 7x 2 | goodbye"   3 | goodbye" 4 | world -------------------------------------------------------------------------------- /test/fixtures/multipoint.mvt: -------------------------------------------------------------------------------- 1 | *x 2 | hello" 3 |  hello" 4 | world -------------------------------------------------------------------------------- /test/fixtures/multipolygon.mvt: -------------------------------------------------------------------------------- 1 | Fx 2 | seeya+"!    seeya" 3 | world -------------------------------------------------------------------------------- /test/fixtures/points-16-10498-22872.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/points-16-10498-22872.mvt -------------------------------------------------------------------------------- /test/fixtures/points-poi-sf-15-5239-12666.js: -------------------------------------------------------------------------------- 1 | module.exports = {"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.43624061346054,37.76231826635359]},"properties":{"localrank":1,"maki":"monument","name":"Pink Triangle Park","name_ar":"Pink Triangle Park","name_de":"Pink Triangle Park and Memorial","name_en":"Pink Triangle Park","name_es":"Parque del Triángulo Rosa","name_fr":"Pink Triangle Park","name_pt":"Pink Triangle Park","name_ru":"Мемориальный парк Розового треугольника","name_zh":"Pink Triangle Park","name_zh-Hans":"Pink Triangle Park","ref":"","scalerank":4,"type":"Memorial"},"id":2190363091},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.43788480758667,37.76437930688486]},"properties":{"localrank":1,"maki":"museum","name":"Randall Museum","name_ar":"Randall Museum","name_de":"Randall Museum","name_en":"Randall Museum","name_es":"Randall Museum","name_fr":"Randall Museum","name_pt":"Randall Museum","name_ru":"Randall Museum","name_zh":"Randall Museum","name_zh-Hans":"Randall Museum","ref":"","scalerank":3,"type":"Museum"},"id":2885501841},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.44127780199051,37.76836339833562]},"properties":{"localrank":1,"maki":"park","name":"Buena Vista Park","name_ar":"Buena Vista Park","name_de":"Buena Vista Park","name_en":"Buena Vista Park","name_es":"Buena Vista Park","name_fr":"Buena Vista Park","name_pt":"Buena Vista Park","name_ru":"Buena Vista Park","name_zh":"舊金山麗景公園","name_zh-Hans":"美景公园","ref":"","scalerank":1,"type":"Park"},"id":74599011},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.43061065673828,37.766539945117316]},"properties":{"localrank":1,"maki":"restaurant","name":"Aatxe","name_ar":"Aatxe","name_de":"Aatxe","name_en":"Aatxe","name_es":"Aatxe","name_fr":"Aatxe","name_pt":"Aatxe","name_ru":"Aatxe","name_zh":"Aatxe","name_zh-Hans":"Aatxe","ref":"","scalerank":4,"type":"Restaurant"},"id":45278195890},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.4322172999382,37.769906937548214]},"properties":{"localrank":1,"maki":"park","name":"Duboce Park","name_ar":"Duboce Park","name_de":"Duboce Park","name_en":"Duboce Park","name_es":"Duboce Park","name_fr":"Duboce Park","name_pt":"Duboce Park","name_ru":"Duboce Park","name_zh":"杜伯斯公園","name_zh-Hans":"杜伯斯公园","ref":"","scalerank":3,"type":"Park"},"id":245628541},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.4393680691719,37.76464435422564]},"properties":{"localrank":2,"maki":"park","name":"Corona Heights Park","name_ar":"Corona Heights Park","name_de":"Corona Heights Park","name_en":"Corona Heights Park","name_es":"Corona Heights Park","name_fr":"Corona Heights Park","name_pt":"Corona Heights Park","name_ru":"Corona Heights Park","name_zh":"Corona Heights Park","name_zh-Hans":"Corona Heights Park","ref":"","scalerank":2,"type":"Park"},"id":2251828801},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.44474858045578,37.770193167239995]},"properties":{"localrank":1,"maki":"shop","name":"Bound Together Anarchist Bookstore","name_ar":"Bound Together Anarchist Bookstore","name_de":"Bound Together Bookstore Collective","name_en":"Bound Together Bookstore Collective","name_es":"Bound Together Anarchist Bookstore","name_fr":"Bound Together Anarchist Bookstore","name_pt":"Bound Together Anarchist Bookstore","name_ru":"Bound Together Anarchist Bookstore","name_zh":"Bound Together Anarchist Bookstore","name_zh-Hans":"Bound Together Anarchist Bookstore","ref":"","scalerank":4,"type":"Books"},"id":43463434420},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.43251234292984,37.771921122955845]},"properties":{"localrank":1,"maki":"marker","name":"Lower Haight","name_ar":"Lower Haight","name_de":"Lower Haight","name_en":"Lower Haight","name_es":"Lower Haight","name_fr":"Lower Haight","name_pt":"Lower Haight","name_ru":"Lower Haight","name_zh":"Lower Haight","name_zh-Hans":"Lower Haight","ref":"","scalerank":2,"type":"Retail"},"id":4881145221},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.43462055921555,37.768257384844674]},"properties":{"localrank":2,"maki":"hospital","name":"CPMC Davies Campus","name_ar":"CPMC Davies Campus","name_de":"CPMC Davies Campus","name_en":"CPMC Davies Campus","name_es":"CPMC Davies Campus","name_fr":"CPMC Davies Campus","name_pt":"CPMC Davies Campus","name_ru":"CPMC Davies Campus","name_zh":"CPMC Davies Campus","name_zh-Hans":"CPMC Davies Campus","ref":"","scalerank":3,"type":"Hospital"},"id":1120733511},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.44294077157974,37.76867719737818]},"properties":{"localrank":2,"maki":"dog-park","name":"Buena Vista Dog Run","name_ar":"Buena Vista Dog Run","name_de":"Buena Vista Dog Run","name_en":"Buena Vista Dog Run","name_es":"Buena Vista Dog Run","name_fr":"Buena Vista Dog Run","name_pt":"Buena Vista Dog Run","name_ru":"Buena Vista Dog Run","name_zh":"Buena Vista Dog Run","name_zh-Hans":"Buena Vista Dog Run","ref":"","scalerank":3,"type":"Dog Park"},"id":4801491751},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.42912203073502,37.76367957734142]},"properties":{"localrank":2,"maki":"marker","name":"Everett Middle School","name_ar":"Everett Middle School","name_de":"Everett Middle School","name_en":"Everett Middle School","name_es":"Everett Middle School","name_fr":"Everett Middle School","name_pt":"Everett Middle School","name_ru":"Everett Middle School","name_zh":"Everett Middle School","name_zh-Hans":"Everett Middle School","ref":"","scalerank":3,"type":"School"},"id":34959204},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.42941439151764,37.763520546976494]},"properties":{"localrank":1,"maki":"school","name":"Everett Middle School","name_ar":"Everett Middle School","name_de":"Everett Middle School","name_en":"Everett Middle School","name_es":"Everett Middle School","name_fr":"Everett Middle School","name_pt":"Everett Middle School","name_ru":"Everett Middle School","name_zh":"Everett Middle School","name_zh-Hans":"Everett Middle School","ref":"","scalerank":3,"type":"School"},"id":3007322881},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.43037730455399,37.76346117555259]},"properties":{"localrank":3,"maki":"school","name":"Sanchez Elementary School","name_ar":"Sanchez Elementary School","name_de":"Sanchez Elementary School","name_en":"Sanchez Elementary School","name_es":"Sanchez Elementary School","name_fr":"Sanchez Elementary School","name_pt":"Sanchez Elementary School","name_ru":"Sanchez Elementary School","name_zh":"Sanchez Elementary School","name_zh-Hans":"Sanchez Elementary School","ref":"","scalerank":3,"type":"School"},"id":3007322861},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.43630230426788,37.76688343625359]},"properties":{"localrank":3,"maki":"school","name":"McKinley Elementary School","name_ar":"McKinley Elementary School","name_de":"McKinley Elementary School","name_en":"McKinley Elementary School","name_es":"McKinley Elementary School","name_fr":"McKinley Elementary School","name_pt":"McKinley Elementary School","name_ru":"McKinley Elementary School","name_zh":"McKinley Elementary School","name_zh-Hans":"McKinley Elementary School","ref":"","scalerank":3,"type":"School"},"id":1165538241}]} -------------------------------------------------------------------------------- /test/fixtures/points-poi-sf-15-5239-12666.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/points-poi-sf-15-5239-12666.mvt -------------------------------------------------------------------------------- /test/fixtures/points-properties-16-10498-22872.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/points-properties-16-10498-22872.mvt -------------------------------------------------------------------------------- /test/fixtures/polygon-with-hole.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/polygon-with-hole.mvt -------------------------------------------------------------------------------- /test/fixtures/polygons-16-10498-22872.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/polygons-16-10498-22872.mvt -------------------------------------------------------------------------------- /test/fixtures/polygons-buildings-sf-15-5239-12666.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/polygons-buildings-sf-15-5239-12666.mvt -------------------------------------------------------------------------------- /test/fixtures/polygons-hillshade-sf-15-5239-12666.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/polygons-hillshade-sf-15-5239-12666.mvt -------------------------------------------------------------------------------- /test/fixtures/polygons-properties-16-10498-22872.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/polygons-properties-16-10498-22872.mvt -------------------------------------------------------------------------------- /test/fixtures/polygons-with-holes-4-13-6.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/polygons-with-holes-4-13-6.mvt -------------------------------------------------------------------------------- /test/fixtures/simple-line.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/simple-line.mvt -------------------------------------------------------------------------------- /test/fixtures/simple_line.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": {}, 7 | "geometry": { 8 | "type": "LineString", 9 | "coordinates": [ 10 | [ 11 | -145.546875, 12 | 69.77895177646761 13 | ], 14 | [ 15 | 29.179687499999996, 16 | 19.642587534013032 17 | ] 18 | ] 19 | } 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /test/fixtures/v1-6.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/v1-6.mvt -------------------------------------------------------------------------------- /test/fixtures/v1-7.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/v1-7.mvt -------------------------------------------------------------------------------- /test/fixtures/v1-8.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/v1-8.mvt -------------------------------------------------------------------------------- /test/fixtures/v1-multipoint.mvt: -------------------------------------------------------------------------------- 1 | ( 2 | saucy" 3 |  saucy" 4 | world -------------------------------------------------------------------------------- /test/fixtures/z15-road-segments.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/test/fixtures/z15-road-segments.mvt -------------------------------------------------------------------------------- /test/test-utils.js: -------------------------------------------------------------------------------- 1 | const vt = require('@mapbox/vector-tile').VectorTile; 2 | const pbf = require('pbf'); 3 | const mapnik = require('mapnik'); 4 | 5 | function vtinfo(buffer) { 6 | const tile = new vt(new pbf(buffer)); 7 | return tile; 8 | } 9 | 10 | const getFeatureById = (layer, id) => { 11 | for (let fidx = 0; fidx < layer.length; fidx++) { 12 | if (layer.feature(fidx).id === id) { 13 | return layer.feature(fidx); 14 | } 15 | } 16 | 17 | return null; 18 | }; 19 | 20 | function vt1infoValid(buffer) { 21 | const tile = new vt(new pbf(buffer)); 22 | var vtt = new mapnik.VectorTile(4,8,5); 23 | vtt.addData(buffer); 24 | vtt.toGeoJSON('__all__'); 25 | return tile; 26 | } 27 | 28 | module.exports = { vtinfo, getFeatureById, vt1infoValid } -------------------------------------------------------------------------------- /test/vtcomposite-composite-param-validation.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | const vt = require('../lib/index.js'); 5 | 6 | const composite = vt.composite; 7 | 8 | test('[composite] failure: fails without callback function', (assert) => { 9 | try { 10 | composite(); 11 | } catch (err) { 12 | assert.ok(/last argument must be a callback function/.test(err.message), 'expected error message'); 13 | assert.end(); 14 | } 15 | }); 16 | 17 | test('[composite] failure: buffers is not an array', (assert) => { 18 | composite('i am not an array', { z:3, x:1, y:0 }, {}, (err) => { 19 | assert.ok(err); 20 | assert.equal(err.message, 'first arg \'tiles\' must be an array of tile objects'); 21 | assert.end(); 22 | }); 23 | }); 24 | 25 | test('[composite] failure: buffers array is empty', (assert) => { 26 | const buffs = []; 27 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 28 | assert.ok(err); 29 | assert.equal(err.message, '\'tiles\' array must be of length greater than 0'); 30 | assert.end(); 31 | }); 32 | }); 33 | 34 | test('[composite] failure: item in buffers array is not an object', (assert) => { 35 | const buffs = [ 36 | 'not an object' 37 | ]; 38 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 39 | assert.ok(err); 40 | assert.equal(err.message, 'items in \'tiles\' array must be objects'); 41 | assert.end(); 42 | }); 43 | }); 44 | 45 | test('[composite] failure: buffer value does not exist', (assert) => { 46 | const buffs = [ 47 | { 48 | z: 0, 49 | x: 0, 50 | y: 0 51 | } 52 | ]; 53 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 54 | assert.ok(err); 55 | assert.equal(err.message, 'item in \'tiles\' array does not include a buffer value'); 56 | assert.end(); 57 | }); 58 | }); 59 | 60 | test('[composite] failure: buffer value is null', (assert) => { 61 | const buffs = [ 62 | { 63 | buffer: null, 64 | z: 0, 65 | x: 0, 66 | y: 0 67 | } 68 | ]; 69 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 70 | assert.ok(err); 71 | assert.equal(err.message, 'buffer value in \'tiles\' array item is null or undefined'); 72 | assert.end(); 73 | }); 74 | }); 75 | 76 | test('[composite] failure: buffer value is not a buffer', (assert) => { 77 | const buffs = [ 78 | { 79 | buffer: 'not a buffer', 80 | z: 0, 81 | x: 0, 82 | y: 0 83 | } 84 | ]; 85 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 86 | assert.ok(err); 87 | assert.equal(err.message, 'buffer value in \'tiles\' array item is not a true buffer'); 88 | assert.end(); 89 | }); 90 | }); 91 | 92 | test('[composite] failure: buffer object missing z value', (assert) => { 93 | const buffs = [ 94 | { 95 | buffer: Buffer.from('hey'), 96 | // z: 0, 97 | x: 0, 98 | y: 0 99 | } 100 | ]; 101 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 102 | assert.ok(err); 103 | assert.equal(err.message, 'item in \'tiles\' array does not include a \'z\' value'); 104 | assert.end(); 105 | }); 106 | }); 107 | 108 | test('[composite] failure: buffer object missing x value', (assert) => { 109 | const buffs = [ 110 | { 111 | buffer: Buffer.from('hey'), 112 | z: 0, 113 | // x: 0, 114 | y: 0 115 | } 116 | ]; 117 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 118 | assert.ok(err); 119 | assert.equal(err.message, 'item in \'tiles\' array does not include a \'x\' value'); 120 | assert.end(); 121 | }); 122 | }); 123 | 124 | test('[composite] failure: buffer object missing y value', (assert) => { 125 | const buffs = [ 126 | { 127 | buffer: Buffer.from('hey'), 128 | z: 0, 129 | x: 0, 130 | // y: 0 131 | } 132 | ]; 133 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 134 | assert.ok(err); 135 | assert.equal(err.message, 'item in \'tiles\' array does not include a \'y\' value'); 136 | assert.end(); 137 | }); 138 | }); 139 | 140 | test('[composite] failure: buffer object z value is not an int32', (assert) => { 141 | const buffs = [ 142 | { 143 | buffer: Buffer.from('hey'), 144 | z: 'zero', 145 | x: 0, 146 | y: 0 147 | } 148 | ]; 149 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 150 | assert.ok(err); 151 | assert.equal(err.message, '\'z\' value in \'tiles\' array item is not an int32'); 152 | assert.end(); 153 | }); 154 | }); 155 | 156 | test('[composite] failure: buffer object x value is not an int32', (assert) => { 157 | const buffs = [ 158 | { 159 | buffer: Buffer.from('hey'), 160 | z: 0, 161 | x: 'zero', 162 | y: 0 163 | } 164 | ]; 165 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 166 | assert.ok(err); 167 | assert.equal(err.message, '\'x\' value in \'tiles\' array item is not an int32'); 168 | assert.end(); 169 | }); 170 | }); 171 | 172 | test('[composite] failure: buffer object y value is not an int32', (assert) => { 173 | const buffs = [ 174 | { 175 | buffer: Buffer.from('hey'), 176 | z: 0, 177 | x: 0, 178 | y: 'zero' 179 | } 180 | ]; 181 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 182 | assert.ok(err); 183 | assert.equal(err.message, '\'y\' value in \'tiles\' array item is not an int32'); 184 | assert.end(); 185 | }); 186 | }); 187 | 188 | test('[composite] failure: buffer object z value is negative', (assert) => { 189 | const buffs = [ 190 | { 191 | buffer: Buffer.from('hey'), 192 | z: -10, 193 | x: 0, 194 | y: 0 195 | } 196 | ]; 197 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 198 | assert.ok(err); 199 | assert.equal(err.message, '\'z\' value must not be less than zero'); 200 | assert.end(); 201 | }); 202 | }); 203 | 204 | test('[composite] failure: buffer object x value is negative', (assert) => { 205 | const buffs = [ 206 | { 207 | buffer: Buffer.from('hey'), 208 | z: 0, 209 | x: -5, 210 | y: 0 211 | } 212 | ]; 213 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 214 | assert.ok(err); 215 | assert.equal(err.message, '\'x\' value must not be less than zero'); 216 | assert.end(); 217 | }); 218 | }); 219 | 220 | test('[composite] failure: buffer object y value is negative', (assert) => { 221 | const buffs = [ 222 | { 223 | buffer: Buffer.from('hey'), 224 | z: 0, 225 | x: 0, 226 | y: -4 227 | } 228 | ]; 229 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 230 | assert.ok(err); 231 | assert.equal(err.message, '\'y\' value must not be less than zero'); 232 | assert.end(); 233 | }); 234 | }); 235 | 236 | test('[composite] failure: layers option is not an array', (assert) => { 237 | const buffs = [ 238 | { 239 | buffer: Buffer.from('hey'), 240 | z: 0, 241 | x: 0, 242 | y: 0, 243 | layers: 'not an array' 244 | } 245 | ]; 246 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 247 | assert.ok(err); 248 | assert.equal(err.message, '\'layers\' value in the \'tiles\' array must be an array'); 249 | assert.end(); 250 | }); 251 | }); 252 | 253 | test('[composite] failure: layers option is an empty array', (assert) => { 254 | const buffs = [ 255 | { 256 | buffer: Buffer.from('hey'), 257 | z: 0, 258 | x: 0, 259 | y: 0, 260 | layers: [] 261 | } 262 | ]; 263 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 264 | assert.ok(err); 265 | assert.equal(err.message, '\'layers\' array must be of length greater than 0'); 266 | assert.end(); 267 | }); 268 | }); 269 | 270 | test('[composite] failure: layers option is an array with invalid types (not strings)', (assert) => { 271 | const buffs = [ 272 | { 273 | buffer: Buffer.from('hey'), 274 | z: 0, 275 | x: 0, 276 | y: 0, 277 | layers: [1, 2, 3, 'correct'] 278 | } 279 | ]; 280 | composite(buffs, { z:3, x:1, y:0 }, {}, (err) => { 281 | assert.ok(err); 282 | assert.equal(err.message, 'items in \'layers\' array must be strings'); 283 | assert.end(); 284 | }); 285 | }); 286 | 287 | // TESTS FOR ZXY MAP REQUEST! 288 | 289 | test('[composite] failure: map request zxy missing z value', (assert) => { 290 | const buffs = [ 291 | { 292 | buffer: Buffer.from('hey'), 293 | z: 0, 294 | x: 0, 295 | y: 0 296 | } 297 | ]; 298 | composite(buffs, { x:1, y:0 }, {}, (err) => { 299 | assert.ok(err); 300 | assert.equal(err.message, 'item in \'tiles\' array does not include a \'z\' value'); 301 | assert.end(); 302 | }); 303 | }); 304 | 305 | test('[composite] failure: map request zxy missing x value', (assert) => { 306 | const buffs = [ 307 | { 308 | buffer: Buffer.from('hey'), 309 | z: 0, 310 | x: 0, 311 | y: 0 312 | } 313 | ]; 314 | composite(buffs, { z:3, y:0 }, {}, (err) => { 315 | assert.ok(err); 316 | assert.equal(err.message, 'item in \'tiles\' array does not include a \'x\' value'); 317 | assert.end(); 318 | }); 319 | }); 320 | 321 | test('[composite] failure: map request zxy missing y value', (assert) => { 322 | const buffs = [ 323 | { 324 | buffer: Buffer.from('hey'), 325 | z: 0, 326 | x: 0, 327 | y: 0 328 | } 329 | ]; 330 | composite(buffs, { z:3, x:1 }, {}, (err) => { 331 | assert.ok(err); 332 | assert.equal(err.message, 'item in \'tiles\' array does not include a \'y\' value'); 333 | assert.end(); 334 | }); 335 | }); 336 | 337 | test('[composite] failure: map request zxy z value is not an int32', (assert) => { 338 | const buffs = [ 339 | { 340 | buffer: Buffer.from('hey'), 341 | z: 0, 342 | x: 0, 343 | y: 0 344 | } 345 | ]; 346 | composite(buffs, { z:'zero', x:1, y:0 }, {}, (err) => { 347 | assert.ok(err); 348 | assert.equal(err.message, '\'z\' value in \'tiles\' array item is not an int32'); 349 | assert.end(); 350 | }); 351 | }); 352 | 353 | test('[composite] failure: map request zxy x value is not an int32', (assert) => { 354 | const buffs = [ 355 | { 356 | buffer: Buffer.from('hey'), 357 | z: 0, 358 | x: 0, 359 | y: 0 360 | } 361 | ]; 362 | composite(buffs, { z:3, x:'zero', y:0 }, {}, (err) => { 363 | assert.ok(err); 364 | assert.equal(err.message, '\'x\' value in \'tiles\' array item is not an int32'); 365 | assert.end(); 366 | }); 367 | }); 368 | 369 | test('[composite] failure: map request zxy y value is not an int32', (assert) => { 370 | const buffs = [ 371 | { 372 | buffer: Buffer.from('hey'), 373 | z: 0, 374 | x: 0, 375 | y: 0 376 | } 377 | ]; 378 | composite(buffs, { z:3, x:1, y:'zero' }, {}, (err) => { 379 | assert.ok(err); 380 | assert.equal(err.message, '\'y\' value in \'tiles\' array item is not an int32'); 381 | assert.end(); 382 | }); 383 | }); 384 | 385 | test('[composite] failure: map request zxy z value is negative', (assert) => { 386 | const buffs = [ 387 | { 388 | buffer: Buffer.from('hey'), 389 | z: 10, 390 | x: 0, 391 | y: 0 392 | } 393 | ]; 394 | composite(buffs, { z:-3, x:1, y:0 }, {}, (err) => { 395 | assert.ok(err); 396 | assert.equal(err.message, '\'z\' value must not be less than zero'); 397 | assert.end(); 398 | }); 399 | }); 400 | 401 | test('[composite] failure: map request zxy x value is negative', (assert) => { 402 | const buffs = [ 403 | { 404 | buffer: Buffer.from('hey'), 405 | z: 0, 406 | x: 0, 407 | y: 0 408 | } 409 | ]; 410 | composite(buffs, { z:3, x:-1, y:0 }, {}, (err) => { 411 | assert.ok(err); 412 | assert.equal(err.message, '\'x\' value must not be less than zero'); 413 | assert.end(); 414 | }); 415 | }); 416 | 417 | test('[composite] failure: map request zxy y value is negative', (assert) => { 418 | const buffs = [ 419 | { 420 | buffer: Buffer.from('hey'), 421 | z: 0, 422 | x: 0, 423 | y: 0 424 | } 425 | ]; 426 | composite(buffs, { z:3, x:1, y:-4 }, {}, (err) => { 427 | assert.ok(err); 428 | assert.equal(err.message, '\'y\' value must not be less than zero'); 429 | assert.end(); 430 | }); 431 | }); 432 | 433 | test('[composite] failure: map request zxy is not an object', (assert) => { 434 | const buffs = [ 435 | { 436 | buffer: Buffer.from('hey'), 437 | z: 0, 438 | x: 0, 439 | y: 0 440 | } 441 | ]; 442 | composite(buffs, true, {}, (err) => { 443 | assert.ok(err); 444 | assert.equal(err.message, '\'zxy_maprequest\' must be an object'); 445 | assert.end(); 446 | }); 447 | }); 448 | 449 | test('[composite] failure: compress must be a boolean', (assert) => { 450 | const buffs = [ 451 | { 452 | buffer: Buffer.from('hey'), 453 | z: 0, 454 | x: 0, 455 | y: 0 456 | } 457 | ]; 458 | composite(buffs, { z:0, x:0, y:0 }, { compress:'hi' }, (err) => { 459 | assert.ok(err); 460 | assert.equal(err.message, '\'compress\' must be a boolean'); 461 | assert.end(); 462 | }); 463 | }); 464 | 465 | test('[composite] failure: options must be an object', (assert) => { 466 | const buffs = [ 467 | { 468 | buffer: Buffer.from('hey'), 469 | z: 0, 470 | x: 0, 471 | y: 0 472 | } 473 | ]; 474 | composite(buffs, { z:0, x:0, y:0 }, true, (err) => { 475 | assert.ok(err); 476 | assert.equal(err.message, '\'options\' arg must be an object'); 477 | assert.end(); 478 | }); 479 | }); 480 | 481 | test('[composite] failure: buffer size is not int32', (assert) => { 482 | const buffs = [ 483 | { 484 | buffer: new Buffer.alloc(10), 485 | z: 0, 486 | x: 0, 487 | y: 0 488 | } 489 | ]; 490 | composite(buffs, { z:0, x:0, y:0 }, { buffer_size:'hi' }, (err) => { 491 | assert.ok(err); 492 | assert.equal(err.message, '\'buffer_size\' must be an int32'); 493 | assert.end(); 494 | }); 495 | }); 496 | 497 | test('[composite] failure: buffer size is not positive int32', (assert) => { 498 | const buffs = [ 499 | { 500 | buffer: new Buffer.alloc(10), 501 | z: 0, 502 | x: 0, 503 | y: 0 504 | } 505 | ]; 506 | composite(buffs, { z:0, x:0, y:0 }, { buffer_size:-10 }, (err) => { 507 | assert.ok(err); 508 | assert.equal(err.message, '\'buffer_size\' must be a positive int32'); 509 | assert.end(); 510 | }); 511 | }); 512 | -------------------------------------------------------------------------------- /test/vtcomposite-linestrings.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var composite = require('../lib/index.js').composite; 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var mvtFixtures = require('@mapbox/mvt-fixtures'); 6 | var vtinfo = require('./test-utils.js').vtinfo; 7 | 8 | test('[composite] overzooming success (linestring), no buffer - different zooms between two tiles', function(assert) { 9 | const buffer1 = fs.readFileSync(__dirname + '/fixtures/simple-line.mvt'); 10 | const info = vtinfo(buffer1); 11 | assert.equal(info.layers.quadrants.length, 1); 12 | const originalGeometryLineStringPoint1 = info.layers.quadrants.feature(0).loadGeometry()[0][0]; 13 | const originalGeometryLineStringPoint2 = info.layers.quadrants.feature(0).loadGeometry()[0][1]; 14 | 15 | const tiles = [ 16 | {buffer: buffer1, z:0, x:0, y:0} 17 | ]; 18 | 19 | const zxy = {z:1, x:0, y:0}; 20 | 21 | composite(tiles, zxy, {buffer_size:128}, (err, vtBuffer) => { 22 | assert.notOk(err); 23 | const outputInfo = vtinfo(vtBuffer); 24 | 25 | assert.deepEqual( 26 | outputInfo.layers.quadrants.feature(0).loadGeometry()[0][0], 27 | { x: 784, y: 1848 }, 28 | 'first feature scales as expected' 29 | ); 30 | 31 | assert.deepEqual( 32 | { x: 4224, y: 3398 }, 33 | outputInfo.layers.quadrants.feature(0).loadGeometry()[0][1], 34 | 'check that new coordinates shifted properly (since zoom factor is 3)' 35 | ); 36 | 37 | assert.end(); 38 | }); 39 | }); 40 | 41 | test('[composite] overzooming success (linestring), with buffer - different zooms between two tiles', function(assert) { 42 | const buffer1 = fs.readFileSync(__dirname + '/fixtures/simple-line.mvt'); 43 | const info = vtinfo(buffer1); 44 | assert.equal(info.layers.quadrants.length, 1); 45 | const originalGeometryLineStringPoint1 = info.layers.quadrants.feature(0).loadGeometry()[0][0]; 46 | const originalGeometryLineStringPoint2 = info.layers.quadrants.feature(0).loadGeometry()[0][1]; 47 | 48 | const tiles = [ 49 | {buffer: buffer1, z:0, x:0, y:0} 50 | ]; 51 | 52 | const zxy = {z:1, x:0, y:0}; 53 | 54 | composite(tiles, zxy, {buffer_size:128}, (err, vtBuffer) => { 55 | assert.notOk(err); 56 | const outputInfo = vtinfo(vtBuffer); 57 | 58 | assert.deepEqual( 59 | outputInfo.layers.quadrants.feature(0).loadGeometry()[0][0], 60 | { x: 784, y: 1848 }, 61 | 'first feature scales as expected' 62 | ); 63 | 64 | assert.deepEqual( 65 | { x: 4224, y: 3398 }, 66 | outputInfo.layers.quadrants.feature(0).loadGeometry()[0][1], 67 | 'check that new coordinates shifted properly (since zoom factor is 3)' 68 | ); 69 | 70 | assert.end(); 71 | }); 72 | }); 73 | 74 | 75 | test('[composite] overzooming success (linestring), with buffer - z14 -> z15', function(assert) { 76 | const buffer1 = fs.readFileSync(__dirname + '/fixtures/z15-road-segments.mvt'); 77 | 78 | const tiles = [ 79 | {buffer: buffer1, z:15, x: 29570, y:20109} 80 | ]; 81 | 82 | const zxy = {z:16, x:59140, y:40218}; 83 | 84 | composite(tiles, zxy, {buffer_size:4080}, (err, vtBuffer) => { 85 | assert.notOk(err); 86 | const outputInfo = vtinfo(vtBuffer); 87 | // Currently failing since it drops a very large feature when buffer_size = 4080 88 | // Although it does not drop this feature when buffer_size = 4079 or 4081 89 | assert.equal(outputInfo.layers.roads._features.length,5); 90 | assert.end(); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /test/vtcomposite-localize-param-validation.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | const vt = require('../lib/index.js'); 5 | const mvtFixtures = require('@mapbox/mvt-fixtures'); 6 | 7 | const localize = vt.localize; 8 | 9 | test('[localize] success with all parameters', (assert) => { 10 | localize({ 11 | buffer: mvtFixtures.get('064').buffer, 12 | hidden_prefix: 'whatever', 13 | languages: ['en'], 14 | language_property: 'lang', 15 | worldviews: ['US'], 16 | worldview_property: 'wv', 17 | class_property: 'klass', 18 | compress: true 19 | }, (err, buffer) => { 20 | assert.ifError(err); 21 | assert.ok(buffer); 22 | assert.end(); 23 | }); 24 | }); 25 | 26 | test('[localize] parameter validation', (assert) => { 27 | assert.throws(() => { 28 | localize(); 29 | }, /expected params and callback arguments/); 30 | assert.throws(() => { 31 | localize(Object(), Function(), 'something extra'); 32 | }, /expected params and callback arguments/); 33 | assert.throws(() => { 34 | localize('not an object', Function()); 35 | }, /first argument must be an object/); 36 | assert.throws(() => { 37 | localize(Object(), 'not a function'); 38 | }, /second argument must be a callback function/); 39 | assert.end(); 40 | }); 41 | 42 | test('[localize] params.buffer', (assert) => { 43 | localize({ 44 | // buffer: // not defined 45 | }, (err) => { 46 | assert.ok(err); 47 | assert.equal(err.message, 'params.buffer is required', 'expected error message'); 48 | }); 49 | 50 | localize({ 51 | buffer: 1, // not a buffer 52 | }, (err) => { 53 | assert.ok(err); 54 | assert.equal(err.message, 'params.buffer must be a Buffer', 'expected error message'); 55 | }); 56 | 57 | localize({ 58 | buffer: null, // set to "null" 59 | }, (err) => { 60 | assert.ok(err); 61 | assert.equal(err.message, 'params.buffer must be a Buffer', 'expected error message'); 62 | }); 63 | 64 | localize({ 65 | buffer: undefined, // set to "undefined" 66 | }, (err) => { 67 | assert.ok(err); 68 | assert.equal(err.message, 'params.buffer must be a Buffer', 'expected error message'); 69 | }); 70 | 71 | localize({ 72 | buffer: Object(), // not a true buffer 73 | }, (err) => { 74 | assert.ok(err); 75 | assert.equal(err.message, 'params.buffer is not a true Buffer', 'expected error message'); 76 | }); 77 | 78 | assert.end(); 79 | }); 80 | 81 | test('[localize] params.hidden_prefix', (assert) => { 82 | localize({ 83 | buffer: Buffer.from('howdy'), 84 | hidden_prefix: 1 // not a string 85 | }, (err) => { 86 | assert.ok(err); 87 | assert.equal(err.message, 'params.hidden_prefix must be a non-empty string', 'expected error message'); 88 | }); 89 | 90 | localize({ 91 | buffer: Buffer.from('howdy'), 92 | hidden_prefix: null // null value 93 | }, (err) => { 94 | assert.ok(err); 95 | assert.equal(err.message, 'params.hidden_prefix must be a non-empty string', 'expected error message'); 96 | }); 97 | 98 | localize({ 99 | buffer: Buffer.from('howdy'), 100 | hidden_prefix: undefined 101 | }, (err) => { 102 | assert.ok(err); 103 | assert.equal(err.message, 'params.hidden_prefix must be a non-empty string', 'expected error message'); 104 | }); 105 | 106 | localize({ 107 | buffer: Buffer.from('howdy'), 108 | hidden_prefix: '' 109 | }, (err) => { 110 | assert.ok(err); 111 | assert.equal(err.message, 'params.hidden_prefix must be a non-empty string', 'expected error message'); 112 | }); 113 | 114 | assert.end(); 115 | }); 116 | 117 | test('[localize] params.languages', (assert) => { 118 | localize({ 119 | buffer: Buffer.from('hi'), 120 | languages: undefined 121 | }, (err) => { 122 | assert.ok(err); 123 | assert.equal(err.message, 'params.languages must be an array', 'expected error message'); 124 | }); 125 | 126 | localize({ 127 | buffer: Buffer.from('hi'), 128 | languages: null 129 | }, (err) => { 130 | assert.ok(err); 131 | assert.equal(err.message, 'params.languages must be an array', 'expected error message'); 132 | }); 133 | 134 | localize({ 135 | buffer: Buffer.from('howdy'), 136 | languages: 1 137 | }, (err) => { 138 | assert.ok(err); 139 | assert.equal(err.message, 'params.languages must be an array', 'expected error message'); 140 | }); 141 | 142 | localize({ 143 | buffer: Buffer.from('hi'), 144 | languages: '' // empty string 145 | }, (err) => { 146 | assert.ok(err); 147 | assert.equal(err.message, 'params.languages must be an array', 'expected error message'); 148 | }); 149 | 150 | localize({ 151 | buffer: Buffer.from('hi'), 152 | languages: 'hi' 153 | }, (err) => { 154 | assert.ok(err); 155 | assert.equal(err.message, 'params.languages must be an array', 'expected error message'); 156 | }); 157 | 158 | localize({ 159 | buffer: mvtFixtures.get('064').buffer, 160 | languages: [] 161 | }, (err) => { 162 | assert.ifError(err); // [] is valid; should not have any error 163 | }); 164 | 165 | localize({ 166 | buffer: Buffer.from('hi'), 167 | languages: [1, 2, 3] 168 | }, (err) => { 169 | assert.ok(err); 170 | assert.equal(err.message, 'params.languages must be an array of non-empty strings', 'expected error message'); 171 | }); 172 | 173 | localize({ 174 | buffer: Buffer.from('hi'), 175 | languages: ['hi', null] 176 | }, (err) => { 177 | assert.ok(err); 178 | assert.equal(err.message, 'params.languages must be an array of non-empty strings', 'expected error message'); 179 | }); 180 | 181 | localize({ 182 | buffer: Buffer.from('hi'), 183 | languages: [undefined, 'hi'] 184 | }, (err) => { 185 | assert.ok(err); 186 | assert.equal(err.message, 'params.languages must be an array of non-empty strings', 'expected error message'); 187 | }); 188 | 189 | localize({ 190 | buffer: Buffer.from('hi'), 191 | languages: ['hi', ''] 192 | }, (err) => { 193 | assert.ok(err); 194 | assert.equal(err.message, 'params.languages must be an array of non-empty strings', 'expected error message'); 195 | }); 196 | 197 | assert.end(); 198 | }); 199 | 200 | test('[localize] params.language_property', (assert) => { 201 | localize({ 202 | buffer: Buffer.from('howdy'), 203 | languages: ['es'], 204 | language_property: 1 // not a string 205 | }, (err) => { 206 | assert.ok(err); 207 | assert.equal(err.message, 'params.language_property must be a non-empty string', 'expected error message'); 208 | }); 209 | 210 | localize({ 211 | buffer: Buffer.from('howdy'), 212 | languages: ['es'], 213 | language_property: null // null value 214 | }, (err) => { 215 | assert.ok(err); 216 | assert.equal(err.message, 'params.language_property must be a non-empty string', 'expected error message'); 217 | }); 218 | 219 | localize({ 220 | buffer: Buffer.from('howdy'), 221 | languages: ['es'], 222 | language_property: undefined 223 | }, (err) => { 224 | assert.ok(err); 225 | assert.equal(err.message, 'params.language_property must be a non-empty string', 'expected error message'); 226 | }); 227 | 228 | localize({ 229 | buffer: Buffer.from('howdy'), 230 | languages: ['es'], 231 | language_property: '' 232 | }, (err) => { 233 | assert.ok(err); 234 | assert.equal(err.message, 'params.language_property must be a non-empty string', 'expected error message'); 235 | }); 236 | 237 | assert.end(); 238 | }); 239 | 240 | test('[localize] params.worldviews', (assert) => { 241 | localize({ 242 | buffer: Buffer.from('howdy'), 243 | worldviews: null 244 | }, (err) => { 245 | assert.ok(err); 246 | assert.equal(err.message, 'params.worldviews must be an array', 'expected error message'); 247 | }); 248 | 249 | localize({ 250 | buffer: Buffer.from('howdy'), 251 | worldviews: undefined 252 | }, (err) => { 253 | assert.ok(err); 254 | assert.equal(err.message, 'params.worldviews must be an array', 'expected error message'); 255 | }); 256 | 257 | localize({ 258 | buffer: Buffer.from('howdy'), 259 | worldviews: 1 // not an array 260 | }, (err) => { 261 | assert.ok(err); 262 | assert.equal(err.message, 'params.worldviews must be an array', 'expected error message'); 263 | }); 264 | 265 | localize({ 266 | buffer: Buffer.from('howdy'), 267 | worldviews: '' 268 | }, (err) => { 269 | assert.ok(err); 270 | assert.equal(err.message, 'params.worldviews must be an array', 'expected error message'); 271 | }); 272 | 273 | localize({ 274 | buffer: Buffer.from('howdy'), 275 | worldviews: 'US' 276 | }, (err) => { 277 | assert.ok(err); 278 | assert.equal(err.message, 'params.worldviews must be an array', 'expected error message'); 279 | }); 280 | 281 | localize({ 282 | buffer: mvtFixtures.get('064').buffer, 283 | worldviews: [] 284 | }, (err) => { 285 | assert.ifError(err); // [] is valid; should not have any error 286 | }); 287 | 288 | localize({ 289 | buffer: Buffer.from('howdy'), 290 | worldviews: [1, 2, 3] 291 | }, (err) => { 292 | assert.ok(err); 293 | assert.equal(err.message, 'params.worldviews must be an array of non-empty strings', 'expected error message'); 294 | }); 295 | 296 | localize({ 297 | buffer: Buffer.from('howdy'), 298 | worldviews: ['hi', null] 299 | }, (err) => { 300 | assert.ok(err); 301 | assert.equal(err.message, 'params.worldviews must be an array of non-empty strings', 'expected error message'); 302 | }); 303 | 304 | localize({ 305 | buffer: Buffer.from('howdy'), 306 | worldviews: [undefined, 'howdy'] 307 | }, (err) => { 308 | assert.ok(err); 309 | assert.equal(err.message, 'params.worldviews must be an array of non-empty strings', 'expected error message'); 310 | }); 311 | 312 | localize({ 313 | buffer: Buffer.from('howdy'), 314 | worldviews: ['howdy', ''] 315 | }, (err) => { 316 | assert.ok(err); 317 | assert.equal(err.message, 'params.worldviews must be an array of non-empty strings', 'expected error message'); 318 | }); 319 | 320 | assert.end(); 321 | }); 322 | 323 | test('[localize] params.worldview_property', (assert) => { 324 | localize({ 325 | buffer: Buffer.from('howdy'), 326 | worldviews: ['US'], 327 | worldview_property: 1 // not a string 328 | }, (err) => { 329 | assert.ok(err); 330 | assert.equal(err.message, 'params.worldview_property must be a non-empty string', 'expected error message'); 331 | }); 332 | 333 | localize({ 334 | buffer: Buffer.from('howdy'), 335 | worldviews: ['US'], 336 | worldview_property: null 337 | }, (err) => { 338 | assert.ok(err); 339 | assert.equal(err.message, 'params.worldview_property must be a non-empty string', 'expected error message'); 340 | }); 341 | 342 | localize({ 343 | buffer: Buffer.from('howdy'), 344 | worldviews: ['US'], 345 | worldview_property: undefined 346 | }, (err) => { 347 | assert.ok(err); 348 | assert.equal(err.message, 'params.worldview_property must be a non-empty string', 'expected error message'); 349 | }); 350 | 351 | localize({ 352 | buffer: Buffer.from('howdy'), 353 | worldviews: ['US'], 354 | worldview_property: '' 355 | }, (err) => { 356 | assert.ok(err); 357 | assert.equal(err.message, 'params.worldview_property must be a non-empty string', 'expected error message'); 358 | }); 359 | 360 | assert.end(); 361 | }); 362 | 363 | test('[localize] params.worldview_default', (assert) => { 364 | localize({ 365 | buffer: Buffer.from('howdy'), 366 | worldview_default: 1 // not a string 367 | }, (err) => { 368 | assert.ok(err); 369 | assert.equal(err.message, 'params.worldview_default must be a non-empty string', 'expected error message'); 370 | }); 371 | 372 | localize({ 373 | buffer: Buffer.from('howdy'), 374 | worldview_default: null 375 | }, (err) => { 376 | assert.ok(err); 377 | assert.equal(err.message, 'params.worldview_default must be a non-empty string', 'expected error message'); 378 | }); 379 | 380 | localize({ 381 | buffer: Buffer.from('howdy'), 382 | worldview_default: undefined 383 | }, (err) => { 384 | assert.ok(err); 385 | assert.equal(err.message, 'params.worldview_default must be a non-empty string', 'expected error message'); 386 | }); 387 | 388 | localize({ 389 | buffer: Buffer.from('howdy'), 390 | worldview_default: '' 391 | }, (err) => { 392 | assert.ok(err); 393 | assert.equal(err.message, 'params.worldview_default must be a non-empty string', 'expected error message'); 394 | }); 395 | 396 | assert.end(); 397 | }); 398 | 399 | test('[localize] params.class_property', (assert) => { 400 | localize({ 401 | buffer: Buffer.from('howdy'), 402 | class_property: 1 // not a string 403 | }, (err) => { 404 | assert.ok(err); 405 | assert.equal(err.message, 'params.class_property must be a non-empty string', 'expected error message'); 406 | }); 407 | 408 | localize({ 409 | buffer: Buffer.from('howdy'), 410 | class_property: null 411 | }, (err) => { 412 | assert.ok(err); 413 | assert.equal(err.message, 'params.class_property must be a non-empty string', 'expected error message'); 414 | }); 415 | 416 | localize({ 417 | buffer: Buffer.from('howdy'), 418 | class_property: undefined 419 | }, (err) => { 420 | assert.ok(err); 421 | assert.equal(err.message, 'params.class_property must be a non-empty string', 'expected error message'); 422 | }); 423 | 424 | localize({ 425 | buffer: Buffer.from('howdy'), 426 | class_property: '' 427 | }, (err) => { 428 | assert.ok(err); 429 | assert.equal(err.message, 'params.class_property must be a non-empty string', 'expected error message'); 430 | }); 431 | 432 | assert.end(); 433 | }); 434 | 435 | test('[localize] params.compress', (assert) => { 436 | localize({ 437 | buffer: Buffer.from('howdy'), 438 | compress: 1 // not a boolean 439 | }, (err) => { 440 | assert.ok(err); 441 | assert.equal(err.message, 'params.compress must be a boolean', 'expected error message'); 442 | }); 443 | 444 | assert.end(); 445 | }); 446 | -------------------------------------------------------------------------------- /test/vtcomposite-multis.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var composite = require('../lib/index.js').composite; 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var mvtFixtures = require('@mapbox/mvt-fixtures'); 6 | var vtinfo = require('./test-utils.js').vtinfo; 7 | 8 | test('[composite] composite success multi geometries - different layer name, different features, same zoom, no buffer', function(assert) { 9 | const tiles = [ 10 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/multipoint.mvt')}, 11 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/multiline.mvt')}, 12 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/multipolygon.mvt')} 13 | ] 14 | const zxy = { z: 15, x: 5239, y: 12666} 15 | 16 | const info0 = vtinfo(tiles[0].buffer); 17 | const info1 = vtinfo(tiles[1].buffer); 18 | const info2 = vtinfo(tiles[2].buffer); 19 | 20 | assert.equal(info0.layers.hello.length, 1); 21 | assert.equal(info1.layers.goodbye.length, 1); 22 | assert.equal(info2.layers.seeya.length, 1); 23 | 24 | composite(tiles, zxy, {}, (err, vtBuffer) => { 25 | assert.notOk(err); 26 | const outputInfo = vtinfo(vtBuffer); 27 | assert.equal(outputInfo.layers.goodbye.length, 1); 28 | assert.equal(outputInfo.layers.seeya.length, 1); 29 | assert.equal(outputInfo.layers.hello.length, 1); 30 | assert.end(); 31 | }); 32 | }); 33 | 34 | test('[composite] composite success multi geometries - different layer name, different features, same zoom, no buffer', function(assert) { 35 | const tiles = [ 36 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/v1-multipoint.mvt')}, 37 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/multiline.mvt')}, 38 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/multipolygon.mvt')} 39 | ] 40 | const zxy = { z: 15, x: 5239, y: 12666} 41 | 42 | const info0 = vtinfo(tiles[0].buffer); 43 | const info1 = vtinfo(tiles[1].buffer); 44 | const info2 = vtinfo(tiles[2].buffer); 45 | 46 | assert.equal(info0.layers.saucy.version, 1); 47 | assert.equal(info0.layers.saucy.length, 1); 48 | assert.equal(info1.layers.goodbye.length, 1); 49 | assert.equal(info2.layers.seeya.length, 1); 50 | 51 | composite(tiles, zxy, {}, (err, vtBuffer) => { 52 | assert.notOk(err); 53 | const outputInfo = vtinfo(vtBuffer); 54 | assert.equal(vtBuffer.length, 171); 55 | assert.equal(outputInfo.layers.goodbye.length, 1); 56 | assert.equal(outputInfo.layers.seeya.length, 1); 57 | assert.equal(outputInfo.layers.saucy.length, 1); 58 | assert.end(); 59 | }); 60 | }); 61 | 62 | test('[composite] composite and overzooming success multi geometries - different layer name, different features, zoom factor 2, with and without buffer', function(assert) { 63 | const tiles = [ 64 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/multipoint.mvt')}, 65 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/multiline.mvt')}, 66 | { z: 16, x: 10479, y: 25332, buffer: fs.readFileSync('./test/fixtures/multipolygon.mvt')} 67 | ] 68 | const zxy = { z: 16, x: 10479, y: 25332} 69 | 70 | const info0 = vtinfo(tiles[0].buffer); 71 | const info1 = vtinfo(tiles[1].buffer); 72 | const info2 = vtinfo(tiles[2].buffer); 73 | 74 | assert.equal(info0.layers.hello.length, 1); 75 | assert.equal(info1.layers.goodbye.length, 1); 76 | assert.equal(info2.layers.seeya.length, 1); 77 | 78 | composite(tiles, zxy, {}, (err, vtBuffer) => { 79 | assert.notOk(err); 80 | const outputInfo = vtinfo(vtBuffer); 81 | assert.equal(Object.keys(outputInfo.layers).length, 1); 82 | 83 | composite(tiles, zxy, {'buffer_size': 4096}, (err, vtBuffer) => { 84 | assert.notOk(err); 85 | const outputInfo = vtinfo(vtBuffer); 86 | assert.equal(vtBuffer.length, 181); 87 | assert.equal(Object.keys(outputInfo.layers).length, 3); 88 | assert.end(); 89 | }); 90 | }); 91 | }); 92 | 93 | test('[composite] composite and overzooming success multi geometries with v1 tile - different layer name, different features, zoom factor 2, with and without buffer', function(assert) { 94 | const tiles = [ 95 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/v1-multipoint.mvt')}, 96 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/multiline.mvt')}, 97 | { z: 16, x: 10479, y: 25332, buffer: fs.readFileSync('./test/fixtures/multipolygon.mvt')} 98 | ] 99 | const zxy = { z: 16, x: 10479, y: 25332} 100 | 101 | const info0 = vtinfo(tiles[0].buffer); 102 | const info1 = vtinfo(tiles[1].buffer); 103 | const info2 = vtinfo(tiles[2].buffer); 104 | 105 | assert.equal(info0.layers.saucy.version, 1); 106 | assert.equal(info0.layers.saucy.length, 1); 107 | assert.equal(info1.layers.goodbye.length, 1); 108 | assert.equal(info2.layers.seeya.length, 1); 109 | 110 | composite(tiles, zxy, {}, (err, vtBuffer) => { 111 | assert.notOk(err); 112 | const outputInfo = vtinfo(vtBuffer); 113 | assert.equal(Object.keys(outputInfo.layers).length, 1); 114 | 115 | composite(tiles, zxy, {'buffer_size': 4096}, (err, vtBuffer) => { 116 | assert.notOk(err); 117 | const outputInfo = vtinfo(vtBuffer); 118 | assert.equal(vtBuffer.length, 181); 119 | assert.equal(Object.keys(outputInfo.layers).length, 3); 120 | assert.end(); 121 | }); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /test/vtcomposite-non-localize.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const localize = require('../lib/index.js').localize; 4 | const { vtinfo } = require('./test-utils.js'); 5 | 6 | const mvtFixtures = require('@mapbox/mvt-fixtures'); 7 | 8 | const test = require('tape'); 9 | const zlib = require('zlib'); 10 | 11 | 12 | test('[localize] no localization param, feature in all worldview', (assert) => { 13 | const params = { 14 | buffer: mvtFixtures.create({ 15 | layers: [ 16 | { 17 | version: 2, 18 | name: 'places', 19 | features: [ 20 | { 21 | id: 10, 22 | tags: [ 23 | 0, 0, 24 | 1, 1, 25 | 2, 2, 26 | 3, 3, 27 | 4, 4, 28 | 5, 5, 29 | 6, 6, 30 | 7, 7, 31 | 8, 8, 32 | 9, 9, 33 | 10, 10, 34 | ], 35 | type: 1, // point 36 | geometry: [ 9, 54, 38 ] 37 | }, 38 | ], 39 | keys: [ '_mbx_worldview', 'worldview', 'worldview_type', '_mbx_class', 'class', 'class_type', 'name', 'name_script', 'name_zh-Hant', 'name_en', '_mbx_fi'], 40 | values: [ 41 | { string_value: 'all' }, 42 | { string_value: 'all' }, 43 | { string_value: 'multiple' }, 44 | { string_value: 'city' }, 45 | { string_value: 'city' }, 46 | { string_value: 'single' }, 47 | { string_value: '你好' }, 48 | { string_value: 'Han' }, 49 | { string_value: 'Nǐ hǎo' }, 50 | { string_value: 'hello' }, 51 | { string_value: 'moi' }, 52 | ], 53 | extent: 4096 54 | } 55 | ] 56 | }).buffer 57 | }; 58 | 59 | localize(params, (err, buffer) => { 60 | assert.notOk(err); 61 | const info = vtinfo(buffer); 62 | assert.equal(info.layers.places.length, 1, 'expected number of features'); // feature is retained when _mbx_worldview is all 63 | assert.deepEqual(info.layers.places.feature(0).properties, { 64 | worldview: 'all', 65 | worldview_type: 'multiple', // fields that starst with `worldview*` are not modified 66 | class: 'city', 67 | class_type: 'single', // fields that starst with `class*` are not modified 68 | // name* fields are not modified 69 | name: '你好', 70 | name_script: 'Han', 71 | 'name_zh-Hant': 'Nǐ hǎo', 72 | name_en: 'hello' 73 | // _mbx_* fields are dropped out 74 | }, 'expected properties'); 75 | assert.end(); 76 | }); 77 | }); 78 | 79 | 80 | test('[localize] no localization param, feature without _mbx_worldview', (assert) => { 81 | const params = { 82 | buffer: mvtFixtures.create({ 83 | layers: [ 84 | { 85 | version: 2, 86 | name: 'places', 87 | features: [ 88 | { 89 | id: 10, 90 | tags: [ 91 | 0, 0, 92 | 1, 1, 93 | 2, 2, 94 | 3, 3, 95 | 4, 4, 96 | 5, 5, 97 | 6, 6, 98 | 7, 7, 99 | ], 100 | type: 1, // point 101 | geometry: [ 9, 54, 38 ] 102 | }, 103 | ], 104 | keys: [ 'worldview', 'worldview_type', 'class', 'class_type', 'name', 'name_script', 'name_en', '_mbx_fi'], 105 | values: [ 106 | { string_value: 'CN,JP,US' }, 107 | { string_value: 'multiple' }, 108 | { string_value: 'disputed_city' }, 109 | { string_value: 'single' }, 110 | { string_value: '你好' }, 111 | { string_value: 'Han' }, 112 | { string_value: 'hello' }, 113 | { string_value: 'moi' }, 114 | ], 115 | extent: 4096 116 | } 117 | ] 118 | }).buffer 119 | }; 120 | 121 | localize(params, (err, buffer) => { 122 | assert.notOk(err); 123 | const info = vtinfo(buffer); 124 | assert.equal(info.layers.places.length, 1, 'expected number of features'); 125 | assert.deepEqual(info.layers.places.feature(0).properties, { 126 | worldview: 'CN,JP,US', 127 | worldview_type: 'multiple', 128 | class: 'disputed_city', 129 | class_type: 'single', 130 | name: '你好', 131 | name_script: 'Han', 132 | name_en: 'hello' 133 | }, 'expected properties'); 134 | assert.end(); 135 | }); 136 | }); 137 | 138 | 139 | test('[localize] no localization param, feature with _mbx_worldview', (assert) => { 140 | const params = { 141 | buffer: mvtFixtures.create({ 142 | layers: [ 143 | { 144 | version: 2, 145 | name: 'places', 146 | features: [ 147 | { 148 | id: 10, 149 | tags: [ 150 | 0, 0, 151 | 1, 1, 152 | 2, 2, 153 | 3, 3, 154 | 4, 4, 155 | 5, 5, 156 | 6, 6, 157 | 7, 7, 158 | ], 159 | type: 1, // point 160 | geometry: [ 9, 54, 38 ] 161 | }, 162 | ], 163 | keys: [ '_mbx_worldview', 'worldview_type', '_mbx_class', 'class_type', 'name', 'name_script', 'name_en', '_mbx_fi'], 164 | values: [ 165 | { string_value: 'CN,JP,US' }, 166 | { string_value: 'multiple' }, 167 | { string_value: 'city' }, 168 | { string_value: 'single' }, 169 | { string_value: '你好' }, 170 | { string_value: 'Han' }, 171 | { string_value: 'hello' }, 172 | { string_value: 'moi' }, 173 | ], 174 | extent: 4096 175 | } 176 | ] 177 | }).buffer 178 | }; 179 | 180 | localize(params, (err, buffer) => { 181 | assert.notOk(err); 182 | const info = vtinfo(buffer); 183 | assert.deepEqual(info.layers, {}, 'expected number of features'); // feature is dropped when _mbx_worldview is not all 184 | assert.end(); 185 | }); 186 | }); 187 | -------------------------------------------------------------------------------- /test/vtcomposite-points.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var composite = require('../lib/index.js').composite; 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var mvtFixtures = require('@mapbox/mvt-fixtures'); 6 | var geoData = require('./fixtures/four-points.js'); 7 | var vtinfo = require('./test-utils.js').vtinfo; 8 | 9 | // can replace long2tile and lat2tile with existing lib 10 | 11 | function long2tile(lon,zoom) { 12 | return (((lon+180)/360*Math.pow(2,zoom))); 13 | } 14 | 15 | function lat2tile(lat,zoom) { 16 | return (((1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom))); 17 | } 18 | 19 | test('[composite] success compositing - different layer name, different features, same zoom, no buffer', function(assert) { 20 | const buffer1 = mvtFixtures.get('017').buffer; 21 | const buffer2 = mvtFixtures.get('053').buffer; 22 | 23 | const tiles = [ 24 | {buffer: buffer1, z:15, x:5238, y:12666}, 25 | {buffer: buffer2, z:15, x:5238, y:12666} 26 | ]; 27 | 28 | const zxy = {z:15, x:5238, y:12666}; 29 | 30 | composite(tiles, zxy, {}, (err, vtBuffer) => { 31 | assert.notOk(err); 32 | const outputInfo = vtinfo(vtBuffer); 33 | assert.equal(vtBuffer.length, 110); 34 | assert.ok(outputInfo.layers.hello, 'expected layer name'); 35 | assert.ok(outputInfo.layers['clipped-square'], 'expected layer name'); 36 | assert.equal(Object.keys(outputInfo.layers).length, 2, 'expected number of layers'); 37 | assert.end(); 38 | }); 39 | }); 40 | 41 | test('[composite] success overzooming - different zooms between two tiles, no buffer', function(assert) { 42 | const buffer1 = fs.readFileSync(__dirname + '/fixtures/four-points-quadrants.mvt'); 43 | 44 | const info = vtinfo(buffer1); 45 | assert.equal(info.layers.quadrants.length, 4); 46 | const originalGeometry = info.layers.quadrants.feature(0).loadGeometry()[0][0]; 47 | 48 | const tiles = [ 49 | {buffer: buffer1, z:0, x:0, y:0} 50 | ]; 51 | 52 | const zxy = {z:1, x:0, y:0}; 53 | 54 | composite(tiles, zxy, {}, (err, vtBuffer) => { 55 | assert.notOk(err); 56 | const outputInfo = vtinfo(vtBuffer); 57 | assert.equal(vtBuffer.length, 57); 58 | assert.equal(outputInfo.layers.quadrants.length, 1,'clips all but one feature when overzooming'); 59 | assert.deepEqual( 60 | outputInfo.layers.quadrants.feature(0).loadGeometry(), 61 | [ [ { x: 1280, y: 1664 } ] ], 62 | 'first feature scales as expected' 63 | ); 64 | 65 | assert.deepEqual( 66 | {x:originalGeometry.x *2, y:originalGeometry.y *2}, 67 | outputInfo.layers.quadrants.feature(0).loadGeometry()[0][0], 68 | 'check that new coordinates are 2x original coordinates (since zoom factor is 2)' 69 | ); 70 | 71 | assert.end(); 72 | }); 73 | }); 74 | 75 | test('[composite] overzooming success - overzooming zoom factor of 4 between two tiles, no buffer', function(assert) { 76 | const buffer1 = fs.readFileSync(__dirname + '/fixtures/four-points-quadrants.mvt'); 77 | const info = vtinfo(buffer1); 78 | assert.equal(info.layers.quadrants.length, 4); 79 | 80 | const originalGeometry = info.layers.quadrants.feature(0).loadGeometry()[0][0]; 81 | const tiles = [ 82 | {buffer: buffer1, z:0, x:0, y:0} 83 | ]; 84 | 85 | const zxy = {z:3, x:1, y:1}; 86 | 87 | const long = geoData.features[0].geometry.coordinates[0]; 88 | const lat = geoData.features[0].geometry.coordinates[1]; 89 | const longInt = Math.round(parseFloat('.' + (long2tile(long,zxy.z)).toString().split('.')[1])*4096); 90 | const latInt = Math.round(parseFloat('.' + (lat2tile(lat,zxy.z)).toString().split('.')[1])*4096); 91 | 92 | composite(tiles, zxy, {}, (err, vtBuffer) => { 93 | assert.notOk(err); 94 | const outputInfo = vtinfo(vtBuffer); 95 | assert.equal(vtBuffer.length, 57); 96 | assert.equal(outputInfo.layers.quadrants.length, 1,'clips all but one feature when overzooming'); 97 | 98 | assert.deepEqual( 99 | outputInfo.layers.quadrants.feature(0).loadGeometry(), 100 | [ [ { x: 1024, y: 2560 } ] ], 101 | 'first feature scales as expected' 102 | ); 103 | 104 | assert.deepEqual( 105 | {x:longInt, y:latInt}, 106 | outputInfo.layers.quadrants.feature(0).loadGeometry()[0][0], 107 | 'check that new coordinates shifted properly (since zoom factor is 3)' 108 | ); 109 | 110 | assert.end(); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /test/vtcomposite-polygons.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var composite = require('../lib/index.js').composite; 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var vtinfo = require('./test-utils.js').vtinfo; 6 | 7 | test('[composite] composite success polygons - same zoom, different features, without and without buffer', function(assert) { 8 | const tiles = [ 9 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/polygons-buildings-sf-15-5239-12666.mvt')}, 10 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/polygons-hillshade-sf-15-5239-12666.mvt')}, 11 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/points-poi-sf-15-5239-12666.mvt')} 12 | ] 13 | const zxy = { z: 15, x: 5239, y: 12666} 14 | 15 | const info0 = vtinfo(tiles[0].buffer); 16 | const info1 = vtinfo(tiles[1].buffer); 17 | const info2 = vtinfo(tiles[2].buffer); 18 | 19 | assert.equal(info0.layers.building.length, 1718); 20 | assert.equal(info1.layers.hillshade.length, 17); 21 | assert.equal(info2.layers.poi_label.length, 14); 22 | 23 | composite(tiles, zxy, {}, (err, vtBuffer) => { 24 | assert.notOk(err); 25 | const outputInfo = vtinfo(vtBuffer); 26 | assert.equal(outputInfo.layers.building.length, 1718); 27 | assert.equal(outputInfo.layers.hillshade.length, 17); 28 | assert.equal(outputInfo.layers.poi_label.length, 14); 29 | 30 | composite(tiles, zxy, {buffer_size:128}, (err, vtBuffer) => { 31 | assert.notOk(err); 32 | const outputInfoWithBuffer = vtinfo(vtBuffer); 33 | assert.equal(outputInfo.layers.building.length, 1718); 34 | assert.equal(outputInfo.layers.hillshade.length, 17); 35 | assert.equal(outputInfo.layers.poi_label.length, 14); 36 | assert.end(); 37 | }); 38 | }); 39 | }); 40 | 41 | test('[composite] composite and overzooming success polygons - overzooming zoom factor of 2 between two tiles, buffer_size & no buffer_size', function(assert) { 42 | const tiles = [ 43 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/polygons-buildings-sf-15-5239-12666.mvt')}, 44 | { z: 15, x: 5239, y: 12666, buffer: fs.readFileSync('./test/fixtures/polygons-hillshade-sf-15-5239-12666.mvt')}, 45 | { z: 16, x: 10478, y: 25332, buffer: fs.readFileSync('./test/fixtures/points-poi-sf-15-5239-12666.mvt')} 46 | ] 47 | const zxy = { z: 16, x: 10478, y: 25332} 48 | 49 | const info0 = vtinfo(tiles[0].buffer); 50 | const info1 = vtinfo(tiles[1].buffer); 51 | const info2 = vtinfo(tiles[2].buffer); 52 | 53 | assert.equal(info0.layers.building.length, 1718); 54 | assert.equal(info1.layers.hillshade.length, 17); 55 | assert.equal(info2.layers.poi_label.length, 14); 56 | 57 | composite(tiles, zxy, {}, (err, vtBuffer) => { 58 | assert.notOk(err); 59 | const outputInfo = vtinfo(vtBuffer); 60 | assert.equal(outputInfo.layers.building.length, 238); 61 | assert.equal(outputInfo.layers.hillshade.length, 11); 62 | assert.equal(outputInfo.layers.poi_label.length, 14); 63 | 64 | composite(tiles, zxy, {buffer_size:128}, (err, vtBuffer) => { 65 | assert.notOk(err); 66 | const outputInfoWithBuffer = vtinfo(vtBuffer); 67 | 68 | assert.equal(outputInfoWithBuffer.layers.building.length, 275); 69 | assert.end(); 70 | }); 71 | }); 72 | }); 73 | 74 | test('[composite] composite and overzooming success polygons - overzooming multipolygon with large buffer', function(assert) { 75 | const zxy = { z:16, x:12210, y:25447 }; 76 | const parent = { z:15, x:6105, y:12723 }; 77 | 78 | const tiles = [ 79 | { buffer: fs.readFileSync('./test/fixtures/mapbox-vector-terrain-v2-hillshade-15-6105-12723.mvt'), z: parent.z, x: parent.x, y: parent.y } 80 | ]; 81 | 82 | const ogOutputInfo = vtinfo(tiles[0].buffer); 83 | assert.equal(ogOutputInfo.layers.hillshade.length, 1); 84 | var hillshade = ogOutputInfo.layers.hillshade; 85 | var feature = hillshade.feature(0); 86 | var geojson = feature.toGeoJSON(zxy.x,zxy.y,zxy.z); 87 | var coords = geojson.geometry.coordinates; 88 | assert.equal(coords.length, 23); 89 | 90 | composite(tiles, zxy, {buffer_size: 5000}, (err, vtBuffer) => { 91 | assert.notOk(err); 92 | const outputInfo = vtinfo(vtBuffer); 93 | assert.equal(outputInfo.layers.hillshade.length, 1); 94 | var hillshade = outputInfo.layers.hillshade; 95 | var feature = hillshade.feature(0); 96 | var geojson = feature.toGeoJSON(zxy.x,zxy.y,zxy.z); 97 | var coords = geojson.geometry.coordinates; 98 | assert.equal(coords.length, 23); 99 | assert.end(); 100 | }); 101 | }); 102 | 103 | 104 | test('[composite] composite and overzooming success polygons - overzooming polygon with hole', function(assert) { 105 | const zxy = { z:1, x:0, y:0 }; 106 | const parent = { z:0, x:0, y:0 }; 107 | 108 | const tiles = [ 109 | { buffer: fs.readFileSync('./test/fixtures/polygon-with-hole.mvt'), z: parent.z, x: parent.x, y: parent.y } 110 | ]; 111 | 112 | const ogOutputInfo = vtinfo(tiles[0].buffer); 113 | assert.equal(ogOutputInfo.layers.polygon.length, 1); 114 | var polygon_layer = ogOutputInfo.layers.polygon; 115 | var feature = polygon_layer.feature(0); 116 | var geojson = feature.toGeoJSON(zxy.x,zxy.y,zxy.z); 117 | var coords = geojson.geometry.coordinates; 118 | assert.equal(coords.length, 2); 119 | 120 | composite(tiles, zxy, {buffer_size: 0}, (err, vtBuffer) => { 121 | assert.notOk(err); 122 | const outputInfo = vtinfo(vtBuffer); 123 | assert.equal(outputInfo.layers.polygon.length, 1); 124 | var polygon_layer = outputInfo.layers.polygon; 125 | var feature = polygon_layer.feature(0); 126 | var geojson = feature.toGeoJSON(zxy.x,zxy.y,zxy.z); 127 | var coords = geojson.geometry.coordinates; 128 | assert.equal(coords.length, 2); 129 | assert.end(); 130 | }); 131 | }); 132 | 133 | test('[composite] composite and overzooming polygons with holes', function(assert) { 134 | const zxy = { z:8, x:221, y:99 }; 135 | const parent = { z:4, x:13, y:6 }; 136 | 137 | const tiles = [ 138 | { buffer: fs.readFileSync('./test/fixtures/polygons-with-holes-4-13-6.mvt'), z: parent.z, x: parent.x, y: parent.y } 139 | ]; 140 | 141 | composite(tiles, zxy, {buffer_size:4080}, (err, vtBuffer) => { 142 | assert.notOk(err); 143 | assert.equal(vtBuffer.length, 848); 144 | const outputInfo = vtinfo(vtBuffer); 145 | // one feature 146 | assert.equal(outputInfo.layers.polygons.feature.length, 1); 147 | var feature = outputInfo.layers.polygons.feature(0); 148 | var geojson = feature.toGeoJSON(zxy.x,zxy.y,zxy.z); 149 | var coords = geojson.geometry.coordinates; 150 | // two polygons 151 | assert.equal(coords.length,2); 152 | // first polygons 8 rings 153 | assert.equal(coords[0].length, 8); 154 | assert.equal(coords[0][0].length, 95); 155 | assert.equal(coords[0][1].length, 25); 156 | assert.equal(coords[0][2].length, 23); 157 | assert.equal(coords[0][3].length, 18); 158 | assert.equal(coords[0][4].length, 26); 159 | assert.equal(coords[0][5].length, 18); 160 | assert.equal(coords[0][6].length, 14); 161 | assert.equal(coords[0][7].length, 16); 162 | // second polygon 1 ring 163 | assert.equal(coords[1].length,1); 164 | assert.equal(coords[1][0].length, 6); 165 | assert.end(); 166 | }); 167 | }); 168 | -------------------------------------------------------------------------------- /test/vtcomposite.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const composite = require('../lib/index.js').composite; 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const zlib = require('zlib'); 6 | const mvtFixtures = require('@mapbox/mvt-fixtures'); 7 | const vtinfo = require('./test-utils.js').vtinfo; 8 | const vt1infoValid = require('./test-utils.js').vt1infoValid; 9 | const tilebelt = require('@mapbox/tilebelt'); 10 | 11 | const bufferSF = fs.readFileSync(path.resolve(__dirname+'/../node_modules/@mapbox/mvt-fixtures/real-world/sanfrancisco/15-5238-12666.mvt')); 12 | 13 | test('[composite] success: buffer size stays the same when no compositing needed', function(assert) { 14 | const tiles = [ 15 | {buffer: bufferSF, z:15, x:5238, y:12666} 16 | ]; 17 | 18 | const zxy = {z:15, x:5238, y:12666}; 19 | 20 | composite(tiles, zxy, {}, (err, vtBuffer) => { 21 | assert.notOk(err); 22 | assert.equal(vtBuffer.length, bufferSF.length, 'same size'); 23 | assert.end(); 24 | }); 25 | }); 26 | 27 | test('[composite] success compositing - same layer name, same features, same zoom', function(assert) { 28 | const singlePointBuffer = mvtFixtures.get('017').buffer; 29 | 30 | const tiles = [ 31 | {buffer: singlePointBuffer, z:15, x:5238, y:12666}, 32 | {buffer: singlePointBuffer, z:15, x:5238, y:12666} 33 | ]; 34 | 35 | const expectedLayerName = 'hello'; 36 | 37 | const zxy = {z:15, x:5238, y:12666}; 38 | 39 | composite(tiles, zxy, {}, (err, vtBuffer) => { 40 | assert.notOk(err); 41 | const outputInfo = vtinfo(vtBuffer); 42 | 43 | assert.ok(outputInfo.layers.hello, 'hello', 'expected layer name'); 44 | assert.equal(Object.keys(outputInfo.layers).length, 1, 'expected number of layers'); 45 | assert.equal(outputInfo.layers.hello.length, 1, 'expected number of features'); 46 | assert.end(); 47 | }); 48 | }); 49 | 50 | test('[composite] success compositing - same layer name, different features, same zoom', function(assert) { 51 | const buffer1 = mvtFixtures.get('059').buffer; // mud lake 52 | const buffer2 = mvtFixtures.get('060').buffer; // crater lake 53 | 54 | const tiles = [ 55 | {buffer: buffer1, z:15, x:5238, y:12666}, // this layer wins 56 | {buffer: buffer2, z:15, x:5238, y:12666} 57 | ]; 58 | 59 | const zxy = {z:15, x:5238, y:12666}; 60 | 61 | composite(tiles, zxy, {}, (err, vtBuffer) => { 62 | assert.notOk(err); 63 | const outputInfo = vtinfo(vtBuffer); 64 | 65 | assert.ok(outputInfo.layers.water, 'returns layer water'); 66 | assert.equal(Object.keys(outputInfo.layers).length, 1, 'return 1 layers'); 67 | assert.equal(outputInfo.layers.water.length, 1, 'only has one feature'); 68 | assert.equal(outputInfo.layers.water.feature(0).properties.name, 'mud lake', 'expected feature'); 69 | assert.end(); 70 | }); 71 | }); 72 | 73 | const gzipped_bufferSF = zlib.gzipSync(bufferSF); 74 | 75 | test('[composite] success: compositing single gzipped VT', function(assert) { 76 | const tiles = [ 77 | {buffer: gzipped_bufferSF, z:15, x:5238, y:12666} 78 | ]; 79 | 80 | const zxy = {z:15, x:5238, y:12666}; 81 | 82 | composite(tiles, zxy, {}, (err, vtBuffer) => { 83 | assert.notOk(err); 84 | assert.equal(vtBuffer.length, zlib.gunzipSync(gzipped_bufferSF).length, 'same size'); 85 | assert.end(); 86 | }); 87 | }); 88 | 89 | test('[composite] success: gzipped output', function(assert) { 90 | const tiles = [ 91 | {buffer: bufferSF, z:15, x:5238, y:12666} 92 | ]; 93 | 94 | const zxy = {z:15, x:5238, y:12666}; 95 | 96 | composite(tiles, zxy, {compress:true}, (err, vtBuffer) => { 97 | assert.notOk(err); 98 | assert.equal(zlib.gunzipSync(vtBuffer).length, bufferSF.length, 'same size'); 99 | assert.end(); 100 | }); 101 | }); 102 | 103 | // TEST - check vt is within target 104 | 105 | test('[composite] failure: discards layer that is not within target', function(assert) { 106 | 107 | const buffer1 = mvtFixtures.get('017').buffer; 108 | const buffer2 = mvtFixtures.get('053').buffer; 109 | 110 | const tiles = [ 111 | {buffer: buffer1, z:15, x:5238, y:12666}, 112 | {buffer: buffer2, z:15, x:5239, y:12666} 113 | ]; 114 | 115 | const zxy = {z:15, x:5238, y:12666}; 116 | 117 | composite(tiles, zxy, {}, (err, vtBuffer) => { 118 | assert.ok(err); 119 | assert.equal(err.message, 'Invalid tile composite request: SOURCE(15,5239,12666) TARGET(15,5238,12666)'); 120 | assert.end(); 121 | }); 122 | }); 123 | 124 | test('[composite] failure: tile does not intersect target zoom level ', function(assert) { 125 | // TBD whether it's a failure or silently discards the tile that isn't in the target 126 | const buffer1 = mvtFixtures.get('017').buffer; 127 | const buffer2 = mvtFixtures.get('053').buffer; 128 | 129 | const tiles = [ 130 | {buffer: buffer1, z:7, x:19, y:44}, 131 | {buffer: buffer2, z:6, x:10, y:22} //this tile does not intersect target zoom level 132 | ]; 133 | 134 | const zxy = {z:7, x:19, y:44}; 135 | 136 | composite(tiles, zxy, {}, (err, vtBuffer) => { 137 | assert.ok(err); 138 | assert.equal(err.message, 'Invalid tile composite request: SOURCE(6,10,22) TARGET(7,19,44)'); 139 | assert.end(); 140 | }); 141 | }); 142 | 143 | test('[composite] failure: tile with zoom level higher than requested zoom is discarded', function(assert) { 144 | const buffer1 = mvtFixtures.get('017').buffer; 145 | const buffer2 = mvtFixtures.get('053').buffer; 146 | 147 | const tiles = [ 148 | {buffer: buffer2, z:7, x:10, y:22}, 149 | {buffer: buffer2, z:6, x:10, y:22} 150 | ]; 151 | 152 | const zxy = {z:6, x:10, y:22}; 153 | 154 | composite(tiles, zxy, {}, (err, vtBuffer) => { 155 | assert.ok(err); 156 | assert.equal(err.message, 'Invalid tile composite request: SOURCE(7,10,22) TARGET(6,10,22)'); 157 | assert.end(); 158 | }); 159 | }); 160 | 161 | test('[composite] underzooming generates out of bounds error', function(assert) { 162 | const buffer1 = fs.readFileSync(__dirname + '/fixtures/four-points-quadrants.mvt'); 163 | const info = vtinfo(buffer1); 164 | assert.equal(info.layers.quadrants.length, 4); 165 | 166 | const tiles = [ 167 | {buffer: buffer1, z:3, x:1, y:1} 168 | ]; 169 | 170 | const zxy = {z:0, x:0, y:0}; 171 | 172 | composite(tiles, zxy, {}, (err, vtBuffer) => { 173 | assert.ok(err); 174 | assert.equal(err.message, 'Invalid tile composite request: SOURCE(3,1,1) TARGET(0,0,0)') 175 | assert.end(); 176 | }); 177 | }); 178 | 179 | test('[composite] huge overzoom z0 - z14', function(assert) { 180 | const buffer1 = fs.readFileSync(__dirname + '/fixtures/four-points-quadrants.mvt'); 181 | const info = vtinfo(buffer1); 182 | assert.equal(info.layers.quadrants.length, 4); 183 | 184 | const tiles = [ 185 | {buffer: buffer1, z:0, x:0, y:0} 186 | ]; 187 | 188 | const zoom = 14; 189 | const coords = require('./fixtures/four-points.js')['features'][0]['geometry']['coordinates']; 190 | const overzoomedZXY = tilebelt.pointToTile(coords[0], coords[1], zoom); 191 | const zxy = {z:overzoomedZXY[2], x:overzoomedZXY[0], y:overzoomedZXY[1]}; 192 | 193 | composite(tiles, zxy, {}, (err, vtBuffer) => { 194 | assert.notOk(err); 195 | const outputInfo = vtinfo(vtBuffer); 196 | assert.equal(outputInfo.layers.quadrants.length, 1); 197 | assert.end(); 198 | }); 199 | }); 200 | 201 | test('[composite] huge overzoom z15 - z27', function(assert) { 202 | const buffer1 = fs.readFileSync(__dirname + '/fixtures/points-poi-sf-15-5239-12666.mvt'); 203 | const info = vtinfo(buffer1); 204 | assert.equal(info.layers.poi_label.length, 14); 205 | 206 | const tiles = [ 207 | {buffer: buffer1, z:15, x:5239, y:12666} 208 | ]; 209 | 210 | const zoom = 27; 211 | const coords = require('./fixtures/points-poi-sf-15-5239-12666.js')['features'][0]['geometry']['coordinates']; 212 | const overzoomedZXY = tilebelt.pointToTile(coords[0], coords[1], zoom); 213 | const zxy = {z:overzoomedZXY[2], x:overzoomedZXY[0], y:overzoomedZXY[1]}; 214 | 215 | composite(tiles, zxy, {}, (err, vtBuffer) => { 216 | assert.notOk(err); 217 | const outputInfo = vtinfo(vtBuffer); 218 | assert.equal(outputInfo.layers.poi_label.length, 1); 219 | assert.end(); 220 | }); 221 | }); 222 | 223 | test('[composite] processing V1 tiles with malformed geometries', function(assert) { 224 | const buffer1 = fs.readFileSync(__dirname + '/fixtures/0.mvt'); 225 | const buffer2 = fs.readFileSync(__dirname + '/fixtures/1.mvt'); 226 | const buffer3 = fs.readFileSync(__dirname + '/fixtures/2.mvt'); 227 | 228 | const tiles = [ 229 | {buffer: buffer1, z:14, x:4396, y:6458}, 230 | {buffer: buffer2, z:14, x:4396, y:6458}, 231 | {buffer: buffer3, z:12, x:1099, y:1614} 232 | ]; 233 | 234 | const zxy = {z:14, x:4396, y:6458}; 235 | 236 | composite(tiles, zxy, {}, (err, vtBuffer) => { 237 | assert.notOk(err); 238 | const outputInfo = vtinfo(vtBuffer); 239 | var count = 0; 240 | for (var name in outputInfo.layers) 241 | { 242 | count += outputInfo.layers[name].length; 243 | } 244 | assert.equal(count, 567, 'v1 tiles with polygons composite successfully'); 245 | assert.end(); 246 | }); 247 | }); 248 | 249 | test('[composite] resolves zero length linestring error for overzoomed V1 tiles with polygons', function(assert) { 250 | const buffer1 = fs.readFileSync(__dirname + '/fixtures/3.mvt'); 251 | const buffer2 = fs.readFileSync(__dirname + '/fixtures/4.mvt'); 252 | const buffer3 = fs.readFileSync(__dirname + '/fixtures/5.mvt'); 253 | 254 | const tiles = [ 255 | {buffer: buffer1, z:14, x:5088, y:5937}, 256 | {buffer: buffer2, z:14, x:5088, y:5937}, 257 | {buffer: buffer3, z:12, x:1272, y:1484} 258 | ]; 259 | 260 | const zxy = {z:14, x:5088, y:5937}; 261 | 262 | composite(tiles, zxy, {buffer_size:4080}, (err, vtBuffer) => { 263 | assert.notOk(err); 264 | const outputInfo = vtinfo(vtBuffer); 265 | assert.equal(Object.keys(outputInfo.layers).length, 11, 'v1 tiles with polygons composite successfully'); 266 | assert.end(); 267 | }); 268 | }); 269 | 270 | test('[composite] check all geometries are clipped to the tile extent', function(assert) { 271 | 272 | const buffer = require('fs').readFileSync('./test/fixtures/clipping-test-tile.mvt'); 273 | const tiles = [ 274 | {buffer: buffer, z:1, x:1, y:1}, 275 | ]; 276 | 277 | const zxy = {z:4, x:10, y:14}; 278 | const options = { compress: false, buffer_size: 4080 }; 279 | var tile_extent = [ -4080, -4080, 4096 + 4080, 4096 + 4080 ]; 280 | composite(tiles, zxy, options, (err, output_buffer) => { 281 | assert.notOk(err); 282 | const info = vtinfo(output_buffer); 283 | for (var name in info.layers) 284 | { 285 | var layer = info.layers[name]; 286 | for (var index = 0; index < layer.length; ++index) 287 | { 288 | var bbox = layer.feature(index).bbox(); 289 | var within = (bbox[0] >= tile_extent[0] && 290 | bbox[1] >= tile_extent[1] && 291 | bbox[2] <= tile_extent[2] && 292 | bbox[3] <= tile_extent[3]); 293 | assert.ok(within); 294 | } 295 | } 296 | assert.end(); 297 | }); 298 | }); 299 | 300 | // These tiles are invalid v2 tiles such that boost::geometry can't handle some zero-area polygons 301 | // and results in unsigned integer overflow. So, we skip this test for the sanitize job 302 | // This should be able to be enabled again after upgrading to https://github.com/boostorg/geometry/pull/505 303 | if (!process.env.ASAN_OPTIONS) { 304 | test('[composite] resolves polygon clockwise error in overzoomed V1 tiles', function(assert) { 305 | const buffer1 = fs.readFileSync(__dirname + '/fixtures/v1-6.mvt'); // note this tile uses zlib coding rather than gzip 306 | const buffer2 = fs.readFileSync(__dirname + '/fixtures/v1-7.mvt'); 307 | const buffer3 = fs.readFileSync(__dirname + '/fixtures/v1-8.mvt'); 308 | 309 | const tiles = [ 310 | {buffer: buffer1, z:3, x:4, y:2}, 311 | {buffer: buffer2, z:3, x:4, y:2}, 312 | {buffer: buffer3, z:2, x:2, y:1} 313 | ]; 314 | 315 | const zxy = {z:4, x:8, y:5}; 316 | 317 | composite(tiles, zxy, {buffer_size:4080}, (err, vtBuffer) => { 318 | assert.notOk(err); 319 | const outputInfo = vt1infoValid(vtBuffer); 320 | assert.equal(Object.keys(outputInfo.layers).length, 7, 'v1 tiles with polygons composite successfully'); 321 | assert.end(); 322 | }); 323 | }); 324 | } 325 | 326 | test('[composite] success: drop layers if "layers" array is in tiles object', function(assert) { 327 | const tiles = [ 328 | { buffer: bufferSF, z:15, x:5238, y:12666, layers: ['building', 'poi_label'] } 329 | ]; 330 | 331 | const zxy = {z:15, x:5238, y:12666}; 332 | 333 | composite(tiles, zxy, {}, (err, vtBuffer) => { 334 | assert.notOk(err); 335 | assert.deepEqual(Object.keys(vtinfo(vtBuffer).layers), ['building', 'poi_label'], 'expected layers'); 336 | assert.notEqual(vtBuffer.length, bufferSF.length, 'buffer is not of the same size'); 337 | assert.end(); 338 | }); 339 | }); 340 | 341 | test('[composite] success: composite and drop layers', function(assert) { 342 | const tiles = [ 343 | { buffer: mvtFixtures.get('059').buffer, z:15, x:5238, y:12666 }, 344 | { buffer: bufferSF, z:15, x:5238, y:12666, layers: ['building', 'poi_label'] } 345 | ]; 346 | 347 | const zxy = {z:15, x:5238, y:12666}; 348 | 349 | composite(tiles, zxy, {}, (err, vtBuffer) => { 350 | assert.notOk(err); 351 | assert.deepEqual(Object.keys(vtinfo(vtBuffer).layers), ['water', 'building', 'poi_label'], 'expected layers'); 352 | assert.end(); 353 | }); 354 | }); 355 | 356 | test('[composite] success: composite and drop same layer names', function(assert) { 357 | const tiles = [ 358 | { buffer: bufferSF, z:15, x:5238, y:12666, layers: ['building'] }, 359 | { buffer: bufferSF, z:15, x:5238, y:12666, layers: ['poi_label'] } 360 | ]; 361 | 362 | const zxy = {z:15, x:5238, y:12666}; 363 | 364 | composite(tiles, zxy, {}, (err, vtBuffer) => { 365 | assert.notOk(err); 366 | assert.deepEqual(Object.keys(vtinfo(vtBuffer).layers), ['building', 'poi_label'], 'expected layers'); 367 | assert.end(); 368 | }); 369 | }); 370 | 371 | test('[composite] success: composite and drop same layer names reversed', function(assert) { 372 | const tiles = [ 373 | { buffer: bufferSF, z:15, x:5238, y:12666, layers: ['poi_label'] }, 374 | { buffer: bufferSF, z:15, x:5238, y:12666, layers: ['building'] } 375 | ]; 376 | 377 | const zxy = {z:15, x:5238, y:12666}; 378 | 379 | composite(tiles, zxy, {}, (err, vtBuffer) => { 380 | assert.notOk(err); 381 | assert.deepEqual(Object.keys(vtinfo(vtBuffer).layers), ['poi_label', 'building'], 'expected layers'); 382 | assert.end(); 383 | }); 384 | }); 385 | 386 | test('[composite] success: empty overzoomed tile returns empty buffer even if compress: true is set', function(assert) { 387 | const buffer = fs.readFileSync(path.resolve(__dirname+'/fixtures/empty-overzoom-8-33-63.mvt')); 388 | const tiles = [ 389 | { buffer, z: 8, x: 33, y: 63 } 390 | ]; 391 | 392 | const zxy = { z: 12, x: 543, y: 1019 }; 393 | 394 | composite(tiles, zxy, {compress: true}, (err, vtBuffer) => { 395 | assert.notOk(err); 396 | assert.equal(vtBuffer.length, 0, 'zero length buffer'); 397 | assert.end(); 398 | }); 399 | }); 400 | -------------------------------------------------------------------------------- /viz/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const mvtFixtures = require('@mapbox/mvt-fixtures'); 3 | const app = express(); 4 | const zlib = require('zlib'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const vtcomposite = require('../lib/index.js'); 8 | const ejs = require('ejs'); 9 | const bodyParser = require('body-parser'); 10 | 11 | app.engine('html', require('ejs').renderFile); 12 | app.use(bodyParser.json()); 13 | app.use(bodyParser.urlencoded({ extended: true })); 14 | 15 | app.get('/', (req, res) => { 16 | let layer; 17 | let typeStyling = 'line'; 18 | let paintStyling = { 19 | "line-opacity": 0.6, 20 | "line-color": "rgb(53, 175, 109)", 21 | "line-width": 2 22 | } 23 | let layout = { 24 | "line-cap": "round", 25 | "line-join": "round" 26 | }; 27 | 28 | let paintStylingCopy = Object.assign({}, paintStyling); 29 | paintStylingCopy["line-color"] = "rgb(255, 0, 0)"; 30 | 31 | 32 | switch(req.query.type){ 33 | case 'points': 34 | layer = 'poi_label'; 35 | paintStyling = { 36 | "circle-radius": 10, 37 | "circle-color": "#007cbf" 38 | }; 39 | typeStyling = 'circle'; 40 | layout = {}; 41 | let colorCopy = Object.assign({}, paintStyling); 42 | colorCopy["circle-color"] = "rgb(255, 0, 0)"; 43 | paintStylingCopy = colorCopy; 44 | break; 45 | case 'lines': 46 | layer = 'road'; 47 | break; 48 | case 'polygons': 49 | layer = 'building'; 50 | break; 51 | default: 52 | layer = 'lines'; 53 | break 54 | } 55 | 56 | return res.render('./index.html', {type:req.query.type, 57 | layer:layer, 58 | typeStyling:typeStyling, 59 | paintStyling:JSON.stringify(paintStyling), 60 | layout:JSON.stringify(layout), 61 | paintStylingCopy: JSON.stringify(paintStylingCopy) 62 | }); 63 | }); 64 | 65 | 66 | app.get('/:type/:z(\\d+)/:x(\\d+)/:y(\\d+).mvt', (req, res) => { 67 | //* is where you can specify domains to allow requests from 68 | res.set({ 69 | 'Content-Type': 'application/vnd.mapbox-vector-tile', 70 | 'Content-Encoding': 'gzip', 71 | "Access-Control-Allow-Origin": "*", 72 | "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept" 73 | }); 74 | // mapbox gl requries of tilejson spec, therefore requires url to zxy in it 75 | const z = parseInt(req.params.z); 76 | const x = parseInt(req.params.x); 77 | const y = parseInt(req.params.y); 78 | 79 | console.log(`${z}/${x}/${y}`); 80 | 81 | const type = req.params.type; 82 | 83 | console.log('path', type, path.join(__dirname, 'fixtures', `${type}.mvt`)); 84 | 85 | if(z === 6 && x === 10 && y === 22){ 86 | const tile = fs.readFileSync(path.join(__dirname, 'fixtures', `${type}.mvt`)); 87 | return res.send(zlib.gzipSync(tile)); 88 | } 89 | 90 | if (z === 7 && x === 20 && y === 44) { 91 | const tile = fs.readFileSync(path.join(__dirname, 'fixtures', `${type}.mvt`)); 92 | 93 | vtcomposite([{buffer:zlib.gzipSync(tile),z:6, x:10, y:22}], {z:z, x:x, y:y}, {}, function(e, vtBuffer){ 94 | return res.send(zlib.gzipSync(vtBuffer)); 95 | }); 96 | }else{ 97 | return res.status(404).send("Sorry can't find that!"); 98 | } 99 | }); 100 | 101 | app.listen(3000, () => console.log('Example app listening on port 3000!')); 102 | -------------------------------------------------------------------------------- /viz/fixtures/lines.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/viz/fixtures/lines.mvt -------------------------------------------------------------------------------- /viz/fixtures/points.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/viz/fixtures/points.mvt -------------------------------------------------------------------------------- /viz/fixtures/polygons.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vtcomposite/b704a0c518b0176837ebc95827a7a9c0d75a2459/viz/fixtures/polygons.mvt -------------------------------------------------------------------------------- /viz/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "viz", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 10 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 11 | "requires": { 12 | "mime-types": "~2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "bytes": { 22 | "version": "3.1.0", 23 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 24 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 25 | }, 26 | "content-disposition": { 27 | "version": "0.5.3", 28 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 29 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 30 | "requires": { 31 | "safe-buffer": "5.1.2" 32 | } 33 | }, 34 | "content-type": { 35 | "version": "1.0.4", 36 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 37 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 38 | }, 39 | "cookie": { 40 | "version": "0.4.0", 41 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 42 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 43 | }, 44 | "cookie-signature": { 45 | "version": "1.0.6", 46 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 47 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 48 | }, 49 | "debug": { 50 | "version": "2.6.9", 51 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 52 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 53 | "requires": { 54 | "ms": "2.0.0" 55 | } 56 | }, 57 | "depd": { 58 | "version": "1.1.2", 59 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 60 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 61 | }, 62 | "destroy": { 63 | "version": "1.0.4", 64 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 65 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 66 | }, 67 | "ee-first": { 68 | "version": "1.1.1", 69 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 70 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 71 | }, 72 | "ejs": { 73 | "version": "2.7.4", 74 | "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", 75 | "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==" 76 | }, 77 | "encodeurl": { 78 | "version": "1.0.2", 79 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 80 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 81 | }, 82 | "escape-html": { 83 | "version": "1.0.3", 84 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 85 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 86 | }, 87 | "etag": { 88 | "version": "1.8.1", 89 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 90 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 91 | }, 92 | "express": { 93 | "version": "4.17.1", 94 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 95 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 96 | "requires": { 97 | "accepts": "~1.3.7", 98 | "array-flatten": "1.1.1", 99 | "body-parser": "1.19.0", 100 | "content-disposition": "0.5.3", 101 | "content-type": "~1.0.4", 102 | "cookie": "0.4.0", 103 | "cookie-signature": "1.0.6", 104 | "debug": "2.6.9", 105 | "depd": "~1.1.2", 106 | "encodeurl": "~1.0.2", 107 | "escape-html": "~1.0.3", 108 | "etag": "~1.8.1", 109 | "finalhandler": "~1.1.2", 110 | "fresh": "0.5.2", 111 | "merge-descriptors": "1.0.1", 112 | "methods": "~1.1.2", 113 | "on-finished": "~2.3.0", 114 | "parseurl": "~1.3.3", 115 | "path-to-regexp": "0.1.7", 116 | "proxy-addr": "~2.0.5", 117 | "qs": "6.7.0", 118 | "range-parser": "~1.2.1", 119 | "safe-buffer": "5.1.2", 120 | "send": "0.17.1", 121 | "serve-static": "1.14.1", 122 | "setprototypeof": "1.1.1", 123 | "statuses": "~1.5.0", 124 | "type-is": "~1.6.18", 125 | "utils-merge": "1.0.1", 126 | "vary": "~1.1.2" 127 | }, 128 | "dependencies": { 129 | "body-parser": { 130 | "version": "1.19.0", 131 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 132 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 133 | "requires": { 134 | "bytes": "3.1.0", 135 | "content-type": "~1.0.4", 136 | "debug": "2.6.9", 137 | "depd": "~1.1.2", 138 | "http-errors": "1.7.2", 139 | "iconv-lite": "0.4.24", 140 | "on-finished": "~2.3.0", 141 | "qs": "6.7.0", 142 | "raw-body": "2.4.0", 143 | "type-is": "~1.6.17" 144 | } 145 | } 146 | } 147 | }, 148 | "finalhandler": { 149 | "version": "1.1.2", 150 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 151 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 152 | "requires": { 153 | "debug": "2.6.9", 154 | "encodeurl": "~1.0.2", 155 | "escape-html": "~1.0.3", 156 | "on-finished": "~2.3.0", 157 | "parseurl": "~1.3.3", 158 | "statuses": "~1.5.0", 159 | "unpipe": "~1.0.0" 160 | } 161 | }, 162 | "forwarded": { 163 | "version": "0.1.2", 164 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 165 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 166 | }, 167 | "fresh": { 168 | "version": "0.5.2", 169 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 170 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 171 | }, 172 | "http-errors": { 173 | "version": "1.7.2", 174 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 175 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 176 | "requires": { 177 | "depd": "~1.1.2", 178 | "inherits": "2.0.3", 179 | "setprototypeof": "1.1.1", 180 | "statuses": ">= 1.5.0 < 2", 181 | "toidentifier": "1.0.0" 182 | } 183 | }, 184 | "iconv-lite": { 185 | "version": "0.4.24", 186 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 187 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 188 | "requires": { 189 | "safer-buffer": ">= 2.1.2 < 3" 190 | } 191 | }, 192 | "inherits": { 193 | "version": "2.0.3", 194 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 195 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 196 | }, 197 | "ipaddr.js": { 198 | "version": "1.9.1", 199 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 200 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 201 | }, 202 | "media-typer": { 203 | "version": "0.3.0", 204 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 205 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 206 | }, 207 | "merge-descriptors": { 208 | "version": "1.0.1", 209 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 210 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 211 | }, 212 | "methods": { 213 | "version": "1.1.2", 214 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 215 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 216 | }, 217 | "mime": { 218 | "version": "1.6.0", 219 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 220 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 221 | }, 222 | "mime-db": { 223 | "version": "1.44.0", 224 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 225 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 226 | }, 227 | "mime-types": { 228 | "version": "2.1.27", 229 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 230 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 231 | "requires": { 232 | "mime-db": "1.44.0" 233 | } 234 | }, 235 | "ms": { 236 | "version": "2.0.0", 237 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 238 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 239 | }, 240 | "negotiator": { 241 | "version": "0.6.2", 242 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 243 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 244 | }, 245 | "on-finished": { 246 | "version": "2.3.0", 247 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 248 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 249 | "requires": { 250 | "ee-first": "1.1.1" 251 | } 252 | }, 253 | "parseurl": { 254 | "version": "1.3.3", 255 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 256 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 257 | }, 258 | "path-to-regexp": { 259 | "version": "0.1.7", 260 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 261 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 262 | }, 263 | "proxy-addr": { 264 | "version": "2.0.6", 265 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 266 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 267 | "requires": { 268 | "forwarded": "~0.1.2", 269 | "ipaddr.js": "1.9.1" 270 | } 271 | }, 272 | "qs": { 273 | "version": "6.7.0", 274 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 275 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 276 | }, 277 | "range-parser": { 278 | "version": "1.2.1", 279 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 280 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 281 | }, 282 | "raw-body": { 283 | "version": "2.4.0", 284 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 285 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 286 | "requires": { 287 | "bytes": "3.1.0", 288 | "http-errors": "1.7.2", 289 | "iconv-lite": "0.4.24", 290 | "unpipe": "1.0.0" 291 | } 292 | }, 293 | "safe-buffer": { 294 | "version": "5.1.2", 295 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 296 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 297 | }, 298 | "safer-buffer": { 299 | "version": "2.1.2", 300 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 301 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 302 | }, 303 | "send": { 304 | "version": "0.17.1", 305 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 306 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 307 | "requires": { 308 | "debug": "2.6.9", 309 | "depd": "~1.1.2", 310 | "destroy": "~1.0.4", 311 | "encodeurl": "~1.0.2", 312 | "escape-html": "~1.0.3", 313 | "etag": "~1.8.1", 314 | "fresh": "0.5.2", 315 | "http-errors": "~1.7.2", 316 | "mime": "1.6.0", 317 | "ms": "2.1.1", 318 | "on-finished": "~2.3.0", 319 | "range-parser": "~1.2.1", 320 | "statuses": "~1.5.0" 321 | }, 322 | "dependencies": { 323 | "ms": { 324 | "version": "2.1.1", 325 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 326 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 327 | } 328 | } 329 | }, 330 | "serve-static": { 331 | "version": "1.14.1", 332 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 333 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 334 | "requires": { 335 | "encodeurl": "~1.0.2", 336 | "escape-html": "~1.0.3", 337 | "parseurl": "~1.3.3", 338 | "send": "0.17.1" 339 | } 340 | }, 341 | "setprototypeof": { 342 | "version": "1.1.1", 343 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 344 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 345 | }, 346 | "statuses": { 347 | "version": "1.5.0", 348 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 349 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 350 | }, 351 | "toidentifier": { 352 | "version": "1.0.0", 353 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 354 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 355 | }, 356 | "type-is": { 357 | "version": "1.6.18", 358 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 359 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 360 | "requires": { 361 | "media-typer": "0.3.0", 362 | "mime-types": "~2.1.24" 363 | } 364 | }, 365 | "unpipe": { 366 | "version": "1.0.0", 367 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 368 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 369 | }, 370 | "utils-merge": { 371 | "version": "1.0.1", 372 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 373 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 374 | }, 375 | "vary": { 376 | "version": "1.1.2", 377 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 378 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 379 | } 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /viz/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "viz", 3 | "version": "1.0.0", 4 | "description": "tile server", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "body-parser": "^1.18.3", 13 | "ejs": "^2.7.4", 14 | "express": "^4.17.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /viz/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Display a map 6 | 7 | 8 | 9 | 13 | 14 | 15 |
16 | 63 | 64 | 65 | --------------------------------------------------------------------------------