├── .github
└── workflows
│ ├── codeql.yml
│ ├── deploy-lambda.yml
│ ├── node.js.yml
│ └── sonarcloud.yml
├── .gitignore
├── .travis.yml
├── DEVELOPMENT.md
├── LICENSE
├── README.md
├── docs
├── architecture-v2.drawio
├── architecture.drawio
├── architecture.odg
├── benchmark.ods
├── icon.svg
├── icon.svg.png
├── img
│ ├── CloudFront-static.png
│ ├── CloudFront-tiles-simple.png
│ ├── CloudFront-tiles.png
│ ├── CodeBuild-Docker.png
│ ├── architecture.png
│ ├── benchmark.png
│ ├── map_screenshot.png
│ ├── map_screenshot2.png
│ ├── stack-with-timing.png
│ └── stack.png
└── stack.odg
├── html
├── cyclemap-style.json
├── favicon.ico
├── gpx.svg
├── index.html
├── json.svg
├── local.html
├── localhost-style.json
├── mountain.svg
├── serve.sh
├── shadow-style.json
├── sprites
│ ├── cyclemap.json
│ ├── cyclemap.png
│ ├── cyclemap@2x.json
│ ├── cyclemap@2x.png
│ ├── cyclemap@4x.json
│ └── cyclemap@4x.png
├── togeojson.js
├── togpx.js
├── transport-style.json
└── xray-style.json
├── jest.config.js
├── local
└── local.ts
├── package-lock.json
├── package.json
├── sonar-project.properties
├── sprites
├── bar.svg
├── bicycle.svg
├── bus_stop.svg
├── camp_site.svg
├── caravan_site.svg
├── church.svg
├── circle-black.svg
├── circle-blue.svg
├── circle-red.svg
├── climbing.svg
├── hospital.svg
├── hotel.svg
├── information.svg
├── miniature_golf.svg
├── nautilid.svg
├── no-access.svg
├── parking.garage.svg
├── parking.svg
├── peak.svg
├── playground.svg
├── pub.svg
├── restaurant.svg
├── ring-black.svg
├── roundedsquare.svg
├── shelter.svg
├── shield-3.blue.svg
├── shield-3.yellow.svg
├── shield-4.blue.svg
├── shield-4.yellow.svg
├── shield-5.blue.svg
├── shield-5.yellow.svg
├── shop.svg
├── shop1.svg
├── shop2.svg
├── square.svg
├── square.white.svg
├── star.white.svg
├── station.blue.svg
├── station.red.svg
├── stripes.svg
├── subway.svg
├── supermarket.svg
├── tower.svg
├── viewpoint.svg
├── wetland.svg
├── zoo.svg
└── zoo2.svg
├── src
├── index.ts
├── projection.ts
├── sources.json
├── sources.toml
└── tileserver.ts
├── terraform
├── .terraform.lock.hcl
├── main.tf
├── terraform-state.tf
├── tileserver-apigateway.tf
├── tileserver-cert.tf
├── tileserver-cloudfront.tf
├── tileserver-lambda.tf
├── tileserver-networking.tf
├── tileserver-route53.tf
├── tileserver-s3.tf
└── variables.tf
├── test
├── database.test.ts
├── env.test.ts
├── fixtures
│ ├── duplicate_layername.toml
│ ├── local_14_8691_5677.js
│ ├── simple.toml
│ ├── simple_dbconfig.toml
│ └── simple_z13.sql
├── handler.test.ts
├── layer.test.ts
├── logger.test.ts
├── parser.test.ts
├── projection.test.ts
├── queries.test.ts
└── vectortile.test.ts
├── tileserver-openapi.yaml
├── tileserver_layer
└── nodejs
│ ├── package-lock.json
│ └── package.json
├── tools
├── benchmark.sh
├── gensprites.js
├── toml2json.js
└── toml2json.ts
├── tsconfig.json
└── typedoc.json
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 | schedule:
9 | - cron: "55 3 * * 6"
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze
14 | runs-on: ubuntu-latest
15 | permissions:
16 | actions: read
17 | contents: read
18 | security-events: write
19 |
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | language: [ javascript ]
24 |
25 | steps:
26 | - name: Checkout
27 | uses: actions/checkout@v3
28 |
29 | - name: Initialize CodeQL
30 | uses: github/codeql-action/init@v2
31 | with:
32 | languages: ${{ matrix.language }}
33 | queries: +security-and-quality
34 |
35 | - name: Autobuild
36 | uses: github/codeql-action/autobuild@v2
37 |
38 | - name: Perform CodeQL Analysis
39 | uses: github/codeql-action/analyze@v2
40 | with:
41 | category: "/language:${{ matrix.language }}"
42 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-lambda.yml:
--------------------------------------------------------------------------------
1 | name: Deploy Nodejs Lambda
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | tags: [ '*' ]
7 |
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@main
14 | - name: Use Node.js 20.x
15 | uses: actions/setup-node@main
16 | with:
17 | node-version: 20.x
18 | cache: 'npm'
19 | - name: predeploy
20 | run: |
21 | npm ci
22 | npm run predeploy
23 | - name: Setup AWS CLI
24 | uses: aws-actions/configure-aws-credentials@main
25 | with:
26 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
27 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
28 | aws-region: eu-central-1
29 | - name: deploy to AWS lambda
30 | run: aws lambda update-function-code --function-name tileserver --zip-file fileb://dist/function.zip
31 | #run: aws lambda create-function --dry-run --function-name tileserver --description "Deploy commit ${{github.sha}} by ${{github.actor}}" --runtime nodejs18.x --handler handler --role ${{ secrets.AWS_LAMBDA_TILESERVER_ROLE }} --zip-file fileb://dist/function.zip
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [16.x, 18.x, 20.x]
20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21 |
22 | steps:
23 | - uses: actions/checkout@main
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@main
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'npm'
29 | - name: test
30 | run: |
31 | npm ci
32 | npm run test:solo
33 |
34 | - name: Coveralls
35 | uses: coverallsapp/github-action@master
36 | with:
37 | github-token: ${{ secrets.GITHUB_TOKEN }}
38 |
--------------------------------------------------------------------------------
/.github/workflows/sonarcloud.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 |
6 | # This workflow helps you trigger a SonarCloud analysis of your code and populates
7 | # GitHub Code Scanning alerts with the vulnerabilities found.
8 | # Free for open source project.
9 |
10 | # 1. Login to SonarCloud.io using your GitHub account
11 |
12 | # 2. Import your project on SonarCloud
13 | # * Add your GitHub organization first, then add your repository as a new project.
14 | # * Please note that many languages are eligible for automatic analysis,
15 | # which means that the analysis will start automatically without the need to set up GitHub Actions.
16 | # * This behavior can be changed in Administration > Analysis Method.
17 | #
18 | # 3. Follow the SonarCloud in-product tutorial
19 | # * a. Copy/paste the Project Key and the Organization Key into the args parameter below
20 | # (You'll find this information in SonarCloud. Click on "Information" at the bottom left)
21 | #
22 | # * b. Generate a new token and add it to your Github repository's secrets using the name SONAR_TOKEN
23 | # (On SonarCloud, click on your avatar on top-right > My account > Security
24 | # or go directly to https://sonarcloud.io/account/security/)
25 |
26 | # Feel free to take a look at our documentation (https://docs.sonarcloud.io/getting-started/github/)
27 | # or reach out to our community forum if you need some help (https://community.sonarsource.com/c/help/sc/9)
28 |
29 | name: SonarCloud analysis
30 |
31 | on:
32 | push:
33 | branches: [ "master" ]
34 | pull_request:
35 | branches: [ "master" ]
36 | workflow_dispatch:
37 |
38 | permissions:
39 | pull-requests: read # allows SonarCloud to decorate PRs with analysis results
40 |
41 | jobs:
42 | Analysis:
43 | runs-on: ubuntu-latest
44 |
45 | steps:
46 | - name: Analyze with SonarCloud
47 |
48 | # You can pin the exact commit or the version.
49 | # uses: SonarSource/sonarcloud-github-action@de2e56b42aa84d0b1c5b622644ac17e505c9a049
50 | uses: SonarSource/sonarcloud-github-action@master
51 | env:
52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information
53 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Generate a token on Sonarcloud.io, add it to the secrets of this repo with the name SONAR_TOKEN (Settings > Secrets > Actions > add new repository secret)
54 | with:
55 | # Additional arguments for the sonarcloud scanner
56 | args:
57 | # Unique keys of your project and organization. You can find them in SonarCloud > Information (bottom-left menu)
58 | # mandatory
59 | -Dsonar.projectKey=henrythasler_cloud-tileserver
60 | -Dsonar.organization=henrythasler
61 | # Comma-separated paths to directories containing main source files.
62 | #-Dsonar.sources= # optional, default is project base directory
63 | # When you need the analysis to take place in a directory other than the one from which it was launched
64 | #-Dsonar.projectBaseDir= # optional, default is .
65 | # Comma-separated paths to directories containing test source files.
66 | #-Dsonar.tests= # optional. For more info about Code Coverage, please refer to https://docs.sonarcloud.io/enriching/test-coverage/overview/
67 | # Adds more detail to both client and server-side analysis logs, activating DEBUG mode for the scanner, and adding client-side environment variables and system properties to the server-side log of analysis report processing.
68 | #-Dsonar.verbose= # optional, default is false
69 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | # terraform stuff
64 | .terraform/
65 | secret.auto.tfvars
66 | *.tfstate*
67 |
68 | # generated typedocs (for now)
69 | docs/out
70 | .coveralls.yml
71 | dist
72 | local/local.js
73 | src/projection.js
74 | src/tileserver.js
75 | tools/*.csv
76 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | dist: jammy
3 | node_js:
4 | - 18
5 | #jdk: openjdk17
6 |
7 | addons:
8 | sonarcloud:
9 | organization: "henrythasler"
10 |
11 | script:
12 | - npm run test
13 | # - sonar-scanner
14 |
15 | before_deploy:
16 | - npm run predeploy
17 |
18 | # see `https://docs.travis-ci.com/user/deployment/lambda/` for instructions
19 | # see `terraform/tileserver-lambda.tf` for values
20 | deploy:
21 | provider: lambda
22 | edge:
23 | source: travis-ci/dpl
24 | branch: qa-add-lambda-runtime
25 | function_name: "tileserver"
26 | region: "eu-central-1"
27 | role: "arn:aws:iam::324094553422:role/tileserver_role"
28 | runtime: "nodejs18.x"
29 | handler_name: "handler"
30 | zip: "dist/function.zip"
31 | on:
32 | branch: master
33 | tags: true
34 | # edge: true # force dpl v2
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 henrythasler
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cloud-Tileserver
2 |
3 | [](https://github.com/henrythasler/cloud-tileserver/actions/workflows/node.js.yml) [](https://coveralls.io/github/henrythasler/cloud-tileserver?branch=master)
4 | [](https://sonarcloud.io/dashboard?id=henrythasler_cloud-tileserver)
5 | [](https://github.com/henrythasler/cloud-tileserver/actions/workflows/codeql.yml)
6 | [](https://snyk.io//test/github/henrythasler/cloud-tileserver?targetFile=package.json)
7 |
8 | Serve mapbox vectortiles via AWS stack. Please visit the [Wiki](https://github.com/henrythasler/cloud-tileserver/wiki) for installation instructions.
9 |
10 | ## Goals
11 |
12 | These are the main project goals:
13 |
14 | ```
15 | [x] Setup the AWS infrastructure with terraform
16 | [x] Create an AWS lambda function to handle vectortile queries via REST
17 | [x] Create mapbox vectortiles directly with postgis using ST_AsMvtGeom() and ST_AsMVT()
18 | [x] Write a parser to read config-files that define the vectortiles layout
19 | [x] Create fully automated deployment pipeline.
20 | [x] Use some caching mechanism for vectortiles
21 | [x] Use Typescript and typed interfaces where possible
22 | [x] Have module tests with tsjest/chai
23 | [ ] Generate useful documentation with typedocs
24 | [ ] Learn more about AWS, terraform and typescript
25 | [ ] Use free-tier if possible.
26 | [x] Have fun
27 | ```
28 | Checked items are already fulfilled.
29 |
30 | ## Overall Architecture
31 |
32 | 1. Client requests tile from CloudFront/S3 .
33 | 2. Missing tiles are created via API Gateway and Lambda.
34 |
35 | 
36 |
37 | A more detailled description can be found in [DEVELOPMENT.md](DEVELOPMENT.md)
38 |
39 | ## Screenshots, Live Demo
40 |
41 | The Live-Demo is available at: [cyclemap.link](https://cyclemap.link)
42 |
43 | [](https://cyclemap.link/#15/48.17374/11.54676)
44 |
45 | [](https://cyclemap.link/#14/47.01863/11.5046)
46 |
--------------------------------------------------------------------------------
/docs/architecture-v2.drawio:
--------------------------------------------------------------------------------
1 | 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
--------------------------------------------------------------------------------
/docs/architecture.drawio:
--------------------------------------------------------------------------------
1 | 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
--------------------------------------------------------------------------------
/docs/architecture.odg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/architecture.odg
--------------------------------------------------------------------------------
/docs/benchmark.ods:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/benchmark.ods
--------------------------------------------------------------------------------
/docs/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
112 |
--------------------------------------------------------------------------------
/docs/icon.svg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/icon.svg.png
--------------------------------------------------------------------------------
/docs/img/CloudFront-static.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/img/CloudFront-static.png
--------------------------------------------------------------------------------
/docs/img/CloudFront-tiles-simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/img/CloudFront-tiles-simple.png
--------------------------------------------------------------------------------
/docs/img/CloudFront-tiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/img/CloudFront-tiles.png
--------------------------------------------------------------------------------
/docs/img/CodeBuild-Docker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/img/CodeBuild-Docker.png
--------------------------------------------------------------------------------
/docs/img/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/img/architecture.png
--------------------------------------------------------------------------------
/docs/img/benchmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/img/benchmark.png
--------------------------------------------------------------------------------
/docs/img/map_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/img/map_screenshot.png
--------------------------------------------------------------------------------
/docs/img/map_screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/img/map_screenshot2.png
--------------------------------------------------------------------------------
/docs/img/stack-with-timing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/img/stack-with-timing.png
--------------------------------------------------------------------------------
/docs/img/stack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/img/stack.png
--------------------------------------------------------------------------------
/docs/stack.odg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/docs/stack.odg
--------------------------------------------------------------------------------
/html/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/html/favicon.ico
--------------------------------------------------------------------------------
/html/gpx.svg:
--------------------------------------------------------------------------------
1 |
2 |
36 |
--------------------------------------------------------------------------------
/html/mountain.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/html/serve.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | python3 -m http.server 8082
--------------------------------------------------------------------------------
/html/sprites/cyclemap.json:
--------------------------------------------------------------------------------
1 | {"wetland":{"width":32,"height":32,"x":0,"y":0,"pixelRatio":1},"church":{"width":16,"height":26,"x":32,"y":0,"pixelRatio":1},"shelter":{"width":17,"height":26,"x":0,"y":32,"pixelRatio":1},"shield-3.blue":{"width":39,"height":26,"x":17,"y":32,"pixelRatio":1},"shield-4.blue":{"width":46,"height":26,"x":56,"y":32,"pixelRatio":1},"shield-5.blue":{"width":54,"height":26,"x":48,"y":0,"pixelRatio":1},"tower":{"width":16,"height":26,"x":102,"y":32,"pixelRatio":1},"viewpoint":{"width":29,"height":26,"x":0,"y":58,"pixelRatio":1},"bar":{"width":24,"height":24,"x":29,"y":58,"pixelRatio":1},"bicycle":{"width":24,"height":24,"x":53,"y":58,"pixelRatio":1},"camp_site":{"width":24,"height":24,"x":77,"y":58,"pixelRatio":1},"caravan_site":{"width":24,"height":24,"x":101,"y":58,"pixelRatio":1},"hotel":{"width":24,"height":24,"x":102,"y":0,"pixelRatio":1},"no-access":{"width":24,"height":24,"x":0,"y":84,"pixelRatio":1},"pub":{"width":24,"height":24,"x":24,"y":84,"pixelRatio":1},"restaurant":{"width":24,"height":24,"x":48,"y":84,"pixelRatio":1},"shop2":{"width":24,"height":24,"x":72,"y":84,"pixelRatio":1},"supermarket":{"width":24,"height":24,"x":96,"y":84,"pixelRatio":1},"shield-3.yellow":{"width":39,"height":22,"x":120,"y":84,"pixelRatio":1},"shield-4.yellow":{"width":48,"height":22,"x":159,"y":84,"pixelRatio":1},"shield-5.yellow":{"width":57,"height":22,"x":118,"y":32,"pixelRatio":1},"hospital":{"width":19,"height":20,"x":207,"y":84,"pixelRatio":1},"information":{"width":13,"height":19,"x":226,"y":84,"pixelRatio":1},"bus_stop":{"width":18,"height":18,"x":175,"y":32,"pixelRatio":1},"climbing":{"width":18,"height":18,"x":193,"y":32,"pixelRatio":1},"miniature_golf":{"width":18,"height":18,"x":211,"y":32,"pixelRatio":1},"parking":{"width":18,"height":18,"x":229,"y":32,"pixelRatio":1},"parking.garage":{"width":18,"height":18,"x":125,"y":58,"pixelRatio":1},"playground":{"width":18,"height":18,"x":143,"y":58,"pixelRatio":1},"station.red":{"width":18,"height":18,"x":161,"y":58,"pixelRatio":1},"subway":{"width":18,"height":18,"x":179,"y":58,"pixelRatio":1},"circle-black":{"width":15,"height":15,"x":239,"y":84,"pixelRatio":1},"circle-blue":{"width":15,"height":15,"x":197,"y":58,"pixelRatio":1},"circle-red":{"width":15,"height":15,"x":212,"y":58,"pixelRatio":1},"nautilid":{"width":12,"height":12,"x":227,"y":58,"pixelRatio":1},"peak":{"width":12,"height":12,"x":239,"y":58,"pixelRatio":1},"roundedsquare":{"width":12,"height":12,"x":126,"y":0,"pixelRatio":1},"shop":{"width":12,"height":12,"x":138,"y":0,"pixelRatio":1},"shop1":{"width":12,"height":12,"x":150,"y":0,"pixelRatio":1},"square":{"width":12,"height":12,"x":162,"y":0,"pixelRatio":1},"square.white":{"width":12,"height":12,"x":174,"y":0,"pixelRatio":1},"star.white":{"width":12,"height":12,"x":186,"y":0,"pixelRatio":1},"station.blue":{"width":12,"height":12,"x":198,"y":0,"pixelRatio":1},"stripes":{"width":12,"height":12,"x":210,"y":0,"pixelRatio":1},"zoo":{"width":12,"height":12,"x":222,"y":0,"pixelRatio":1},"zoo2":{"width":12,"height":12,"x":234,"y":0,"pixelRatio":1},"ring-black":{"width":8,"height":8,"x":247,"y":32,"pixelRatio":1}}
--------------------------------------------------------------------------------
/html/sprites/cyclemap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/html/sprites/cyclemap.png
--------------------------------------------------------------------------------
/html/sprites/cyclemap@2x.json:
--------------------------------------------------------------------------------
1 | {"wetland":{"width":64,"height":64,"x":0,"y":0,"pixelRatio":2},"church":{"width":33,"height":53,"x":64,"y":0,"pixelRatio":2},"tower":{"width":33,"height":53,"x":0,"y":64,"pixelRatio":2},"shelter":{"width":35,"height":52,"x":33,"y":64,"pixelRatio":2},"shield-3.blue":{"width":79,"height":52,"x":68,"y":64,"pixelRatio":2},"shield-4.blue":{"width":92,"height":52,"x":147,"y":64,"pixelRatio":2},"shield-5.blue":{"width":108,"height":52,"x":97,"y":0,"pixelRatio":2},"viewpoint":{"width":58,"height":52,"x":0,"y":117,"pixelRatio":2},"bar":{"width":48,"height":48,"x":58,"y":117,"pixelRatio":2},"bicycle":{"width":48,"height":48,"x":106,"y":117,"pixelRatio":2},"camp_site":{"width":48,"height":48,"x":154,"y":117,"pixelRatio":2},"caravan_site":{"width":48,"height":48,"x":202,"y":117,"pixelRatio":2},"hotel":{"width":48,"height":48,"x":205,"y":0,"pixelRatio":2},"no-access":{"width":48,"height":48,"x":0,"y":169,"pixelRatio":2},"pub":{"width":48,"height":48,"x":48,"y":169,"pixelRatio":2},"restaurant":{"width":48,"height":48,"x":96,"y":169,"pixelRatio":2},"shop2":{"width":48,"height":48,"x":144,"y":169,"pixelRatio":2},"supermarket":{"width":48,"height":48,"x":192,"y":169,"pixelRatio":2},"shield-3.yellow":{"width":79,"height":44,"x":240,"y":169,"pixelRatio":2},"shield-4.yellow":{"width":96,"height":44,"x":319,"y":169,"pixelRatio":2},"shield-5.yellow":{"width":114,"height":44,"x":250,"y":117,"pixelRatio":2},"hospital":{"width":39,"height":40,"x":415,"y":169,"pixelRatio":2},"information":{"width":26,"height":39,"x":454,"y":169,"pixelRatio":2},"bus_stop":{"width":36,"height":36,"x":364,"y":117,"pixelRatio":2},"climbing":{"width":36,"height":36,"x":400,"y":117,"pixelRatio":2},"miniature_golf":{"width":36,"height":36,"x":436,"y":117,"pixelRatio":2},"parking":{"width":36,"height":36,"x":472,"y":117,"pixelRatio":2},"parking.garage":{"width":36,"height":36,"x":239,"y":64,"pixelRatio":2},"playground":{"width":36,"height":36,"x":275,"y":64,"pixelRatio":2},"station.red":{"width":36,"height":36,"x":311,"y":64,"pixelRatio":2},"subway":{"width":36,"height":36,"x":347,"y":64,"pixelRatio":2},"circle-black":{"width":30,"height":30,"x":480,"y":169,"pixelRatio":2},"circle-blue":{"width":30,"height":30,"x":383,"y":64,"pixelRatio":2},"circle-red":{"width":30,"height":30,"x":413,"y":64,"pixelRatio":2},"nautilid":{"width":24,"height":24,"x":443,"y":64,"pixelRatio":2},"peak":{"width":24,"height":24,"x":467,"y":64,"pixelRatio":2},"roundedsquare":{"width":24,"height":24,"x":253,"y":0,"pixelRatio":2},"shop":{"width":24,"height":24,"x":277,"y":0,"pixelRatio":2},"shop1":{"width":24,"height":24,"x":301,"y":0,"pixelRatio":2},"square":{"width":24,"height":24,"x":325,"y":0,"pixelRatio":2},"square.white":{"width":24,"height":24,"x":349,"y":0,"pixelRatio":2},"star.white":{"width":24,"height":24,"x":373,"y":0,"pixelRatio":2},"station.blue":{"width":24,"height":24,"x":397,"y":0,"pixelRatio":2},"stripes":{"width":24,"height":24,"x":421,"y":0,"pixelRatio":2},"zoo":{"width":24,"height":24,"x":445,"y":0,"pixelRatio":2},"zoo2":{"width":24,"height":24,"x":469,"y":0,"pixelRatio":2},"ring-black":{"width":16,"height":16,"x":491,"y":64,"pixelRatio":2}}
--------------------------------------------------------------------------------
/html/sprites/cyclemap@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/html/sprites/cyclemap@2x.png
--------------------------------------------------------------------------------
/html/sprites/cyclemap@4x.json:
--------------------------------------------------------------------------------
1 | {"wetland":{"width":128,"height":128,"x":0,"y":0,"pixelRatio":4},"church":{"width":67,"height":106,"x":128,"y":0,"pixelRatio":4},"tower":{"width":67,"height":106,"x":0,"y":128,"pixelRatio":4},"shelter":{"width":71,"height":105,"x":67,"y":128,"pixelRatio":4},"shield-3.blue":{"width":159,"height":104,"x":138,"y":128,"pixelRatio":4},"shield-4.blue":{"width":184,"height":104,"x":297,"y":128,"pixelRatio":4},"shield-5.blue":{"width":216,"height":104,"x":195,"y":0,"pixelRatio":4},"viewpoint":{"width":116,"height":104,"x":0,"y":234,"pixelRatio":4},"bar":{"width":96,"height":96,"x":116,"y":234,"pixelRatio":4},"bicycle":{"width":96,"height":96,"x":212,"y":234,"pixelRatio":4},"camp_site":{"width":96,"height":96,"x":308,"y":234,"pixelRatio":4},"caravan_site":{"width":96,"height":96,"x":404,"y":234,"pixelRatio":4},"hotel":{"width":96,"height":96,"x":411,"y":0,"pixelRatio":4},"no-access":{"width":96,"height":96,"x":0,"y":338,"pixelRatio":4},"pub":{"width":96,"height":96,"x":96,"y":338,"pixelRatio":4},"restaurant":{"width":96,"height":96,"x":192,"y":338,"pixelRatio":4},"shop2":{"width":96,"height":96,"x":288,"y":338,"pixelRatio":4},"supermarket":{"width":96,"height":96,"x":384,"y":338,"pixelRatio":4},"shield-3.yellow":{"width":158,"height":88,"x":480,"y":338,"pixelRatio":4},"shield-4.yellow":{"width":193,"height":88,"x":638,"y":338,"pixelRatio":4},"shield-5.yellow":{"width":228,"height":88,"x":500,"y":234,"pixelRatio":4},"hospital":{"width":79,"height":80,"x":831,"y":338,"pixelRatio":4},"information":{"width":52,"height":79,"x":910,"y":338,"pixelRatio":4},"bus_stop":{"width":72,"height":72,"x":728,"y":234,"pixelRatio":4},"climbing":{"width":72,"height":72,"x":800,"y":234,"pixelRatio":4},"miniature_golf":{"width":72,"height":72,"x":872,"y":234,"pixelRatio":4},"parking":{"width":72,"height":72,"x":944,"y":234,"pixelRatio":4},"parking.garage":{"width":72,"height":72,"x":481,"y":128,"pixelRatio":4},"playground":{"width":72,"height":72,"x":553,"y":128,"pixelRatio":4},"station.red":{"width":72,"height":72,"x":625,"y":128,"pixelRatio":4},"subway":{"width":72,"height":72,"x":697,"y":128,"pixelRatio":4},"circle-black":{"width":60,"height":60,"x":962,"y":338,"pixelRatio":4},"circle-blue":{"width":60,"height":60,"x":769,"y":128,"pixelRatio":4},"circle-red":{"width":60,"height":60,"x":829,"y":128,"pixelRatio":4},"nautilid":{"width":48,"height":48,"x":889,"y":128,"pixelRatio":4},"peak":{"width":48,"height":48,"x":937,"y":128,"pixelRatio":4},"roundedsquare":{"width":48,"height":48,"x":507,"y":0,"pixelRatio":4},"shop":{"width":48,"height":48,"x":555,"y":0,"pixelRatio":4},"shop1":{"width":48,"height":48,"x":603,"y":0,"pixelRatio":4},"square":{"width":48,"height":48,"x":651,"y":0,"pixelRatio":4},"square.white":{"width":48,"height":48,"x":699,"y":0,"pixelRatio":4},"star.white":{"width":48,"height":48,"x":747,"y":0,"pixelRatio":4},"station.blue":{"width":48,"height":48,"x":795,"y":0,"pixelRatio":4},"stripes":{"width":48,"height":48,"x":843,"y":0,"pixelRatio":4},"zoo":{"width":48,"height":48,"x":891,"y":0,"pixelRatio":4},"zoo2":{"width":48,"height":48,"x":939,"y":0,"pixelRatio":4},"ring-black":{"width":32,"height":32,"x":985,"y":128,"pixelRatio":4}}
--------------------------------------------------------------------------------
/html/sprites/cyclemap@4x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henrythasler/cloud-tileserver/4adbb0eabfbccec16873cbf932f4829df09d07e4/html/sprites/cyclemap@4x.png
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node',
4 | coverageReporters: ["json", "lcov", "text", "clover"],
5 | testPathIgnorePatterns: ["/dist/", "/node_modules/"]
6 | };
--------------------------------------------------------------------------------
/local/local.ts:
--------------------------------------------------------------------------------
1 | import { Tileserver, Config } from "../src/tileserver";
2 | import * as http from "http"
3 | import { readFileSync } from "fs";
4 | import { parse } from "@iarna/toml";
5 |
6 | const fixturesPath = "src/";
7 |
8 | const config = parse(readFileSync(`${fixturesPath}sources.toml`, "utf8")) as unknown as Config;
9 |
10 | const gzip = process.env.GZIP?process.env.GZIP!=="false":true;
11 | const logLevel = (process.env.LOG_LEVEL)?parseInt(process.env.LOG_LEVEL):2;
12 | const tileserver = new Tileserver(config, "", logLevel, gzip);
13 |
14 | // docker run --rm -ti -p 5432:5432 -v /media/mapdata/pgdata_mvt:/pgdata -v $(pwd)/postgis.conf:/etc/postgresql/postgresql.conf -e PGDATA=/pgdata img-postgis:0.9 -c 'config_file=/etc/postgresql/postgresql.conf'
15 | process.env.PGPASSWORD = "";
16 | process.env.PGUSER = "postgres";
17 |
18 | async function listener(req: http.IncomingMessage, res: http.ServerResponse): Promise {
19 | let path = req.url?req.url:"/";
20 | let vectortile = await tileserver.getVectortile(path);
21 | if ((vectortile.res >= 0) && (vectortile.data)) {
22 | res.writeHead(200, {
23 | 'Content-Type': 'application/vnd.mapbox-vector-tile',
24 | 'Content-Encoding': (gzip) ? "gzip" : "identity",
25 | 'Content-Length' : `${vectortile.data.byteLength}`,
26 | 'access-control-allow-origin': '*'
27 | });
28 | res.end(vectortile.data);
29 | }
30 | else {
31 | res.writeHead(500, { 'Content-Type': 'text/html' });
32 | res.end(JSON.stringify(vectortile));
33 | }
34 | }
35 |
36 | const webserver = http.createServer();
37 | webserver.on('request', listener);
38 | webserver.listen(8000);
39 | console.log(`(Nodejs ${process.version}) awaiting connections...`);
40 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cloud-tileserver",
3 | "version": "1.3.1",
4 | "description": "AWS lambda function to handle vectortile queries via REST",
5 | "main": "index.js",
6 | "scripts": {
7 | "tools": "tsc tools/toml2json.ts",
8 | "gen:sources": "node tools/toml2json.js < src/sources.toml > src/sources.json",
9 | "gen:sprites": "node tools/gensprites.js && mv sprites/cyclemap* html/sprites",
10 | "test": "LOG_LEVEL=3 jest --coverage && coveralls < coverage/lcov.info",
11 | "test:solo": "LOG_LEVEL=1 jest --coverage",
12 | "test:single": "LOG_LEVEL=1 jest --coverage vectortile.test.ts",
13 | "layer": "cd tileserver_layer/nodejs && npm i",
14 | "html": "aws s3 cp ./html/ s3://cyclemap.link/ --recursive",
15 | "docs": "node node_modules/typedoc/bin/typedoc",
16 | "sim": "npm run predeploy && LOG_LEVEL=5 PGUSER=postgres PGHOST=127.0.0.1 PGPORT=5432 node node_modules/lambda-local/build/cli.js -l dist/index.js -h handler -e test/fixtures/local_14_8691_5677.js",
17 | "local": "tsc local/local.ts && LOG_LEVEL=3 node local/local.js",
18 | "predeploy": "rm -rf ./dist/* && npm run gen:sources && tsc && cp ./src/sources.json ./dist && zip -j ./dist/function.zip ./dist/*.js* && cd tileserver_layer/nodejs && npm i && cd .. && zip -qr ../dist/tileserver_layer.zip nodejs",
19 | "deploy": "aws lambda update-function-code --function-name tileserver --zip-file fileb://dist/function.zip"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/henrythasler/cloud-tileserver.git"
24 | },
25 | "keywords": [
26 | "vectortiles",
27 | "postgis",
28 | "lambda",
29 | "mapbox",
30 | "mvt",
31 | "terraform",
32 | "aws",
33 | "cloud"
34 | ],
35 | "author": "Henry Thasler",
36 | "license": "MIT",
37 | "bugs": {
38 | "url": "https://github.com/henrythasler/cloud-tileserver/issues"
39 | },
40 | "homepage": "https://github.com/henrythasler/cloud-tileserver#readme",
41 | "devDependencies": {
42 | "@iarna/toml": "^2.2.5",
43 | "@mapbox/spritezero": "^8.0.3",
44 | "@types/aws-lambda": "^8.10.119",
45 | "@types/chai": "^4.3.5",
46 | "@types/jest": "^29.5.3",
47 | "@types/node": "^14.18.54",
48 | "@types/pg": "^8.10.2",
49 | "chai": "^4.3.7",
50 | "coveralls": "^3.1.1",
51 | "jest": "^29.6.2",
52 | "lambda-local": "^2.1.1",
53 | "ts-jest": "^29.1.1",
54 | "typedoc": "^0.24.8",
55 | "typescript": "^5.1.6"
56 | },
57 | "dependencies": {
58 | "@aws-sdk/client-s3": "^3.395.0",
59 | "aws-lambda": "^1.0.7",
60 | "pg": "^8.11.3"
61 | },
62 | "np": {
63 | "publish": false
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | sonar.projectKey=henrythasler_cloud-tileserver
2 | sonar.sources=src
3 | sonar.typescript.lcov.reportPaths=coverage/lcov.info
--------------------------------------------------------------------------------
/sprites/bus_stop.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
99 |
--------------------------------------------------------------------------------
/sprites/camp_site.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
85 |
--------------------------------------------------------------------------------
/sprites/church.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
88 |
--------------------------------------------------------------------------------
/sprites/circle-black.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
87 |
--------------------------------------------------------------------------------
/sprites/circle-blue.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
87 |
--------------------------------------------------------------------------------
/sprites/circle-red.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
87 |
--------------------------------------------------------------------------------
/sprites/climbing.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
86 |
--------------------------------------------------------------------------------
/sprites/hospital.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
103 |
--------------------------------------------------------------------------------
/sprites/information.svg:
--------------------------------------------------------------------------------
1 |
2 |
81 |
--------------------------------------------------------------------------------
/sprites/miniature_golf.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
118 |
--------------------------------------------------------------------------------
/sprites/parking.garage.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
91 |
--------------------------------------------------------------------------------
/sprites/parking.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
90 |
--------------------------------------------------------------------------------
/sprites/peak.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
78 |
--------------------------------------------------------------------------------
/sprites/playground.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
103 |
--------------------------------------------------------------------------------
/sprites/ring-black.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
87 |
--------------------------------------------------------------------------------
/sprites/roundedsquare.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
79 |
--------------------------------------------------------------------------------
/sprites/shelter.svg:
--------------------------------------------------------------------------------
1 |
2 |
108 |
--------------------------------------------------------------------------------
/sprites/shield-3.blue.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
87 |
--------------------------------------------------------------------------------
/sprites/shield-3.yellow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
77 |
--------------------------------------------------------------------------------
/sprites/shield-4.blue.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
87 |
--------------------------------------------------------------------------------
/sprites/shield-4.yellow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
77 |
--------------------------------------------------------------------------------
/sprites/shield-5.blue.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
87 |
--------------------------------------------------------------------------------
/sprites/shield-5.yellow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
77 |
--------------------------------------------------------------------------------
/sprites/square.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
78 |
--------------------------------------------------------------------------------
/sprites/square.white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
81 |
--------------------------------------------------------------------------------
/sprites/star.white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
90 |
--------------------------------------------------------------------------------
/sprites/station.blue.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
79 |
--------------------------------------------------------------------------------
/sprites/station.red.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
82 |
--------------------------------------------------------------------------------
/sprites/stripes.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
114 |
--------------------------------------------------------------------------------
/sprites/subway.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
89 |
--------------------------------------------------------------------------------
/sprites/tower.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
78 |
--------------------------------------------------------------------------------
/sprites/zoo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
88 |
--------------------------------------------------------------------------------
/sprites/zoo2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
120 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Handler, Context } from "aws-lambda";
2 | import { Tileserver, Vectortile } from "./tileserver";
3 | import configJSON from "./sources.json";
4 |
5 |
6 | const cacheBucketName = process.env.CACHE_BUCKET || "";
7 | const gzip = process.env.GZIP?process.env.GZIP!=="false":true;
8 | const logLevel = (process.env.LOG_LEVEL)?parseInt(process.env.LOG_LEVEL):undefined;
9 | const tileserver: Tileserver = new Tileserver(configJSON, cacheBucketName, logLevel, gzip);
10 |
11 | interface Event {
12 | path?: string // used by API-Gateway
13 | rawPath?: string // used by Lambda function URLs
14 | }
15 |
16 | export const handler: Handler = async (event: Event, context: Context): Promise => {
17 | let response;
18 | const vectortile: Vectortile = await tileserver.getVectortile(event.path ?? event.rawPath ?? "");
19 | if ((vectortile.res >= 0) && (vectortile.data)) {
20 | response = {
21 | statusCode: 200,
22 | headers: {
23 | 'Content-Type': 'application/vnd.mapbox-vector-tile',
24 | 'Content-Encoding': (gzip) ? "gzip" : "identity",
25 | 'access-control-allow-origin': '*'
26 | },
27 | body: vectortile.data.toString('base64'),
28 | isBase64Encoded: true
29 | }
30 | }
31 | else {
32 | response = {
33 | statusCode: 500,
34 | headers: {
35 | 'Content-Type': 'text/html',
36 | 'access-control-allow-origin': '*',
37 | 'Content-Encoding': 'identity'
38 | },
39 | body: JSON.stringify(vectortile),
40 | isBase64Encoded: false
41 | }
42 | }
43 | return Promise.resolve(response)
44 | }
45 |
--------------------------------------------------------------------------------
/src/projection.ts:
--------------------------------------------------------------------------------
1 | export interface Wgs84 {
2 | /** in degrees */
3 | lng: number,
4 | /** in degrees */
5 | lat: number
6 | }
7 |
8 | export interface Mercator {
9 | /** in meters */
10 | x: number,
11 | /** in meters */
12 | y: number
13 | }
14 |
15 | export interface Vector {
16 | x: number,
17 | y: number
18 | }
19 |
20 | export interface Tile {
21 | z: number,
22 | x: number,
23 | y: number
24 | }
25 |
26 | export interface TileList extends Array { }
27 |
28 | export interface WGS84BoundingBox {
29 | leftbottom: Wgs84,
30 | righttop: Wgs84
31 | }
32 |
33 | export interface MercatorBoundingBox {
34 | leftbottom: Mercator,
35 | righttop: Mercator
36 | }
37 |
38 | export class Projection {
39 | protected originShift = 2 * Math.PI * 6378137 / 2.0;
40 |
41 | /** Converts XY point from Pseudo-Mercator (https://epsg.io/3857) to WGS84 (https://epsg.io/4326) */
42 | getWGS84FromMercator(pos: Mercator): Wgs84 {
43 | const lon = (pos.x / this.originShift) * 180.0;
44 | let lat = (pos.y / this.originShift) * 180.0;
45 | lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0)
46 | return ({ lng: lon % 360, lat: lat % 180 } as Wgs84)
47 | }
48 |
49 | /** Converts pixel coordinates (Origin is top-left) in given zoom level of pyramid to EPSG:900913 */
50 | getMercatorFromPixels(pos: Vector, zoom: number, tileSize = 256): Mercator {
51 | // zoom = Math.max(0, zoom + 1 - tileSize / 256)
52 | const res = 2 * Math.PI * 6378137 / tileSize / Math.pow(2, zoom);
53 | return ({ x: pos.x * res - this.originShift, y: this.originShift - pos.y * res } as Mercator)
54 | }
55 |
56 | /** Returns bounds of the given tile in Pseudo-Mercator (https://epsg.io/3857) coordinates */
57 | getMercatorTileBounds(tile: Tile, tileSize = 256): MercatorBoundingBox {
58 | const leftbottom = this.getMercatorFromPixels({ x: tile.x * tileSize, y: (tile.y + 1) * tileSize } as Vector, tile.z, tileSize);
59 | const righttop = this.getMercatorFromPixels({ x: (tile.x + 1) * tileSize, y: tile.y * tileSize } as Vector, tile.z, tileSize);
60 | return ({ leftbottom, righttop } as MercatorBoundingBox)
61 | }
62 |
63 | /** Returns bounds of the given tile in WGS84 (https://epsg.io/4326) coordinates */
64 | getWGS84TileBounds(tile: Tile, tileSize = 256): WGS84BoundingBox {
65 | const bounds: MercatorBoundingBox = this.getMercatorTileBounds(tile, tileSize);
66 | return ({
67 | leftbottom: this.getWGS84FromMercator(bounds.leftbottom),
68 | righttop: this.getWGS84FromMercator(bounds.righttop)
69 | } as WGS84BoundingBox)
70 | }
71 |
72 | /** Returns center of the given tile in WGS84 (https://epsg.io/4326) coordinates */
73 | getWGS84TileCenter(tile: Tile, tileSize = 256): Wgs84 {
74 | const bounds: WGS84BoundingBox = this.getWGS84TileBounds(tile, tileSize);
75 | return ({
76 | lng: (bounds.righttop.lng + bounds.leftbottom.lng) / 2,
77 | lat: (bounds.righttop.lat + bounds.leftbottom.lat) / 2,
78 | } as Wgs84)
79 | }
80 |
81 | /** Return a list of zxy-Tilecoordinates `depth`-levels below the given tile
82 | * @param tile Top-level tile to start the pyramid; will also be part of the return value
83 | * @param depth How many levels the resulting pyramid will have.
84 | * @return An array of tiles
85 | */
86 | getTilePyramid(tile: Tile, depth = 1): TileList {
87 | const list: TileList = [];
88 | depth = Math.max(0, depth); // do not allow negative values
89 | for (let zoom = 0; zoom <= depth; zoom++) {
90 | for (let y = tile.y * 2 ** zoom; y < (tile.y + 1) * 2 ** zoom; y++) {
91 | for (let x = tile.x * 2 ** zoom; x < (tile.x + 1) * 2 ** zoom; x++) {
92 | list.push({
93 | x,
94 | y,
95 | z: tile.z + zoom
96 | } as Tile)
97 | }
98 | }
99 | }
100 | return list
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/terraform/.terraform.lock.hcl:
--------------------------------------------------------------------------------
1 | # This file is maintained automatically by "terraform init".
2 | # Manual edits may be lost in future updates.
3 |
4 | provider "registry.terraform.io/hashicorp/aws" {
5 | version = "5.13.1"
6 | constraints = "~> 5.13"
7 | hashes = [
8 | "h1:uYJsEJhxGO/dFeK2MsC65wV/NKu7XxnTikh5rbrizBo=",
9 | "zh:0d107e410ecfbd5d2fb5ff9793f88e2ce03ae5b3bda4e3b772b5d146cdd859d8",
10 | "zh:1080cf6a402939ec4ad393380f2ab2dfdc0e175903e08ed796aa22eb95868847",
11 | "zh:300420d642c3ada48cfe633444eafa7bcd410cd6a8503de2384f14ac54dc3ce3",
12 | "zh:4e0121014a8d6ef0b1ab4634877545737bb54e951340f1b67ffea8cd22b2d252",
13 | "zh:59b401bbf95dc8c6bea58085ff286543380f176271251193eac09cb7fcf619b7",
14 | "zh:5dfaf51e979131710ce8e1572e6012564e68c7c842e3d9caaaeb0fe6af15c351",
15 | "zh:84bb75dafca056d7c3783be5185187fdd3294f902e9d72f7655f2efb5e066650",
16 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
17 | "zh:aa4e2b9f699d497041679bc05ca34ac21a5184298eb1813f35455b1883977910",
18 | "zh:b51a4f08d84b071128df68a95cfa5114301a50bf8ab8e655dcf7e384e5bc6458",
19 | "zh:bce284ac6ebb65053d9b703048e3157bf4175782ea9dbaec9f64dc2b6fcefb0c",
20 | "zh:c748f78b79b354794098b06b18a02aefdb49be144dff8876c9204083980f7de0",
21 | "zh:ee69d9aef5ca532392cdc26499096f3fa6e55f8622387f78194ebfaadb796aef",
22 | "zh:ef561bee58e4976474bc056649095737fa3b2bcb74602032415d770bfc620c1f",
23 | "zh:f696d8416c57c31f144d432779ba34764560a95937db3bb3dd2892a791a6d5a7",
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/terraform/main.tf:
--------------------------------------------------------------------------------
1 | provider "aws" {
2 | profile = "default"
3 | region = "${var.region}"
4 | }
5 |
6 | provider "aws" {
7 | profile = "default"
8 | region = "us-east-1"
9 | alias = "us-east-1"
10 | }
11 |
12 | terraform {
13 | required_version = ">= 1.5"
14 | backend "s3" {
15 | bucket = "terraform-state-0000"
16 | key = "cyclemap.link/terraform.tfstate"
17 | region = "eu-central-1"
18 | dynamodb_table = "terraform-state-lock"
19 | encrypt = true
20 | }
21 |
22 | required_providers {
23 | aws = {
24 | source = "hashicorp/aws"
25 | version = "~> 5.13"
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/terraform/terraform-state.tf:
--------------------------------------------------------------------------------
1 | resource "aws_s3_bucket" "terraform_state" {
2 | bucket = "terraform-state-0000"
3 | }
4 |
5 | resource "aws_s3_bucket_versioning" "terraform_state_versioning" {
6 | bucket = aws_s3_bucket.terraform_state.id
7 | versioning_configuration {
8 | status = "Enabled"
9 | }
10 | }
11 |
12 | resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
13 | bucket = aws_s3_bucket.terraform_state.id
14 | rule {
15 | apply_server_side_encryption_by_default {
16 | sse_algorithm = "AES256"
17 | }
18 | }
19 | }
20 |
21 | resource "aws_dynamodb_table" "terraform_locks" {
22 | name = "terraform-state-lock"
23 | billing_mode = "PAY_PER_REQUEST"
24 | hash_key = "LockID"
25 | attribute {
26 | name = "LockID"
27 | type = "S"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/terraform/tileserver-apigateway.tf:
--------------------------------------------------------------------------------
1 | resource "aws_api_gateway_rest_api" "tileserver" {
2 | name = "${var.tileserver_prefix}${var.domain}"
3 | description = "Vectortiles Server"
4 | binary_media_types = [
5 | "*/*"
6 | ]
7 | minimum_compression_size = 5000
8 | endpoint_configuration {
9 | types = [
10 | "REGIONAL"
11 | ]
12 | }
13 | }
14 |
15 | resource "aws_api_gateway_resource" "proxy" {
16 | rest_api_id = "${aws_api_gateway_rest_api.tileserver.id}"
17 | parent_id = "${aws_api_gateway_rest_api.tileserver.root_resource_id}"
18 | path_part = "{proxy+}"
19 | }
20 |
21 | resource "aws_api_gateway_method" "proxy" {
22 | rest_api_id = "${aws_api_gateway_rest_api.tileserver.id}"
23 | resource_id = "${aws_api_gateway_resource.proxy.id}"
24 | http_method = "GET"
25 | authorization = "NONE"
26 | }
27 |
28 | resource "aws_api_gateway_integration" "lambda" {
29 | rest_api_id = "${aws_api_gateway_rest_api.tileserver.id}"
30 | resource_id = "${aws_api_gateway_method.proxy.resource_id}"
31 | http_method = "${aws_api_gateway_method.proxy.http_method}"
32 |
33 | integration_http_method = "POST"
34 | type = "AWS_PROXY"
35 | uri = "${aws_lambda_function.tileserver.invoke_arn}"
36 | }
37 |
38 | resource "aws_api_gateway_method" "proxy_root" {
39 | rest_api_id = "${aws_api_gateway_rest_api.tileserver.id}"
40 | resource_id = "${aws_api_gateway_rest_api.tileserver.root_resource_id}"
41 | http_method = "GET"
42 | authorization = "NONE"
43 | }
44 |
45 | resource "aws_api_gateway_integration" "lambda_root" {
46 | rest_api_id = "${aws_api_gateway_rest_api.tileserver.id}"
47 | resource_id = "${aws_api_gateway_method.proxy_root.resource_id}"
48 | http_method = "${aws_api_gateway_method.proxy_root.http_method}"
49 |
50 | integration_http_method = "POST"
51 | type = "AWS_PROXY"
52 | uri = "${aws_lambda_function.tileserver.invoke_arn}"
53 | }
54 |
55 | resource "aws_api_gateway_deployment" "testing" {
56 | depends_on = [
57 | aws_api_gateway_integration.lambda,
58 | aws_api_gateway_integration.lambda_root,
59 | ]
60 |
61 | rest_api_id = "${aws_api_gateway_rest_api.tileserver.id}"
62 | stage_name = "testing"
63 | }
64 |
65 |
66 | resource "aws_api_gateway_domain_name" "tileserver_domain" {
67 | certificate_arn = "${data.aws_acm_certificate.acm_certificate.arn}"
68 | domain_name = "${aws_api_gateway_rest_api.tileserver.name}"
69 | # security_policy = "TLS_1_2"
70 | endpoint_configuration {
71 | types = ["EDGE"]
72 | }
73 | }
74 |
75 | resource "aws_api_gateway_base_path_mapping" "tileserver_mapping" {
76 | api_id = "${aws_api_gateway_rest_api.tileserver.id}"
77 | stage_name = "${aws_api_gateway_deployment.testing.stage_name}"
78 | domain_name = "${aws_api_gateway_domain_name.tileserver_domain.domain_name}"
79 | }
80 |
81 | # output "base_url" {
82 | # value = "${aws_api_gateway_deployment.testing.invoke_url}"
83 | # }
--------------------------------------------------------------------------------
/terraform/tileserver-cert.tf:
--------------------------------------------------------------------------------
1 | data "aws_acm_certificate" "acm_certificate" {
2 | provider = aws.us-east-1
3 | domain = "${var.domain}"
4 | statuses = ["ISSUED"]
5 | }
6 |
--------------------------------------------------------------------------------
/terraform/tileserver-cloudfront.tf:
--------------------------------------------------------------------------------
1 | resource "aws_cloudfront_distribution" "website_distribution" {
2 | origin {
3 | domain_name = "${aws_s3_bucket.website.bucket_regional_domain_name}"
4 | origin_id = "S3-Website"
5 | }
6 |
7 | enabled = true
8 | is_ipv6_enabled = true
9 | price_class = "PriceClass_100"
10 | default_root_object = "index.html"
11 |
12 | aliases = ["cyclemap.link", "www.cyclemap.link"]
13 |
14 | default_cache_behavior {
15 | allowed_methods = ["GET", "HEAD", "OPTIONS"]
16 | cached_methods = ["GET", "HEAD"]
17 | target_origin_id = "S3-Website"
18 |
19 | forwarded_values {
20 | query_string = false
21 |
22 | cookies {
23 | forward = "none"
24 | }
25 | }
26 |
27 | viewer_protocol_policy = "redirect-to-https"
28 | compress = true
29 | min_ttl = 0
30 | default_ttl = 86400
31 | max_ttl = 604800
32 | }
33 |
34 | restrictions {
35 | geo_restriction {
36 | restriction_type = "none"
37 | }
38 | }
39 |
40 | viewer_certificate {
41 | acm_certificate_arn = "${data.aws_acm_certificate.acm_certificate.arn}"
42 | minimum_protocol_version = "TLSv1.2_2018"
43 | ssl_support_method = "sni-only"
44 | }
45 | }
46 |
47 |
48 | resource "aws_cloudfront_distribution" "tiles" {
49 | origin {
50 | domain_name = "${aws_s3_bucket_website_configuration.tilecache_config.website_endpoint}"
51 | origin_id = "S3-Tilecache"
52 | custom_origin_config {
53 | http_port = 80
54 | https_port = 443
55 | origin_keepalive_timeout = 5
56 | origin_read_timeout = 30
57 | origin_protocol_policy = "http-only"
58 | origin_ssl_protocols = ["TLSv1.2", "TLSv1.1", "TLSv1"]
59 | }
60 | }
61 |
62 |
63 | enabled = true
64 | is_ipv6_enabled = true
65 | price_class = "PriceClass_100"
66 |
67 | aliases = ["tiles.cyclemap.link"]
68 |
69 | default_cache_behavior {
70 | allowed_methods = ["GET", "HEAD", "OPTIONS"]
71 | cached_methods = ["GET", "HEAD"]
72 | target_origin_id = "S3-Tilecache"
73 |
74 | forwarded_values {
75 | query_string = false
76 | headers = ["origin", "access-control-request-headers", "access-control-request-method"]
77 |
78 | cookies {
79 | forward = "none"
80 | }
81 | }
82 |
83 | viewer_protocol_policy = "redirect-to-https"
84 | min_ttl = 1
85 | default_ttl = 86400
86 | max_ttl = 220752000
87 | }
88 |
89 | restrictions {
90 | geo_restriction {
91 | restriction_type = "none"
92 | }
93 | }
94 |
95 | viewer_certificate {
96 | acm_certificate_arn = "${data.aws_acm_certificate.acm_certificate.arn}"
97 | minimum_protocol_version = "TLSv1.2_2018"
98 | ssl_support_method = "sni-only"
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/terraform/tileserver-lambda.tf:
--------------------------------------------------------------------------------
1 | resource "aws_iam_role" "tileserver_role" {
2 | name = "tileserver_role"
3 |
4 | assume_role_policy = < ({
14 | Client: class {
15 | connect = jest.fn().mockResolvedValue(null)
16 | query = mockQuery
17 | end = jest.fn().mockResolvedValue(null)
18 | }
19 | }));
20 |
21 | const fixturesPath = "test/fixtures/";
22 | const testOutputPath = "test/out/";
23 |
24 | describe("getClientConfig", function () {
25 | it("empty config", function () {
26 | let config = parse(readFileSync(`${fixturesPath}simple.toml`, "utf8")) as unknown as Config;
27 | let server = new Tileserver(config, "testBucket");
28 | let pgconfig: ClientConfig = server.getClientConfig("local");
29 | expect(pgconfig).to.be.empty;
30 | });
31 |
32 | it("full database config", function () {
33 | let config = parse(readFileSync(`${fixturesPath}simple_dbconfig.toml`, "utf8")) as unknown as Config;
34 | let server = new Tileserver(config, "testBucket");
35 | let pgconfig: ClientConfig = server.getClientConfig("local");
36 | expect(pgconfig).to.deep.equal({
37 | host: "localhost",
38 | port: 5432,
39 | user: "user",
40 | password: "secret",
41 | database: "local"
42 | });
43 | });
44 |
45 | it("source not found", function () {
46 | let config = parse(readFileSync(`${fixturesPath}simple_dbconfig.toml`, "utf8")) as unknown as Config;
47 | let server = new Tileserver(config, "testBucket");
48 | let pgconfig: ClientConfig = server.getClientConfig("unknown");
49 | expect(pgconfig).to.be.empty;
50 | });
51 |
52 | });
53 |
54 |
55 | describe("fetchTileFromDatabase", function () {
56 | beforeEach(() => {
57 | mockQuery.mockReset();
58 | });
59 |
60 | it("regular response", async function () {
61 | mockQuery.mockResolvedValue({ rows: [{ mvt: Buffer.from("data") }, { mvt: Buffer.from("something") }] })
62 | let config = parse(readFileSync(`${fixturesPath}simple.toml`, "utf8")) as unknown as Config;
63 | let server = new Tileserver(config, "testBucket");
64 | let pgconfig: ClientConfig = server.getClientConfig("local");
65 |
66 | let res = await server.fetchTileFromDatabase("SELECT true", pgconfig);
67 | expect(mockQuery.mock.calls.length).to.be.equal(1);
68 | expect(res.toString()).to.equal("data");
69 | });
70 |
71 | it("row not found", async function () {
72 | mockQuery.mockResolvedValue({ rows: [{ wrong: Buffer.from("data") }, { mvt: Buffer.from("something") }] })
73 | let config = parse(readFileSync(`${fixturesPath}simple.toml`, "utf8")) as unknown as Config;
74 | let server = new Tileserver(config, "testBucket");
75 | let pgconfig: ClientConfig = server.getClientConfig("local");
76 |
77 | try {
78 | await server.fetchTileFromDatabase("SELECT true", pgconfig)
79 | } catch (e) {
80 | expect(e).to.be.an("Error");
81 | expect(e).to.have.property("message", "Property \'mvt\' does not exist in res.rows[0]");
82 | }
83 | expect(mockQuery.mock.calls.length).to.be.equal(1);
84 | });
85 | });
--------------------------------------------------------------------------------
/test/env.test.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'aws-lambda';
2 | import { expect } from "chai";
3 |
4 | import { gzip } from "zlib";
5 | import { promisify } from "util";
6 | const asyncgzip = promisify(gzip);
7 |
8 | import "jest";
9 |
10 | /** Setup mocks for postgres */
11 | jest.mock('pg', () => ({
12 | Client: class {
13 | connect = jest.fn().mockResolvedValue(this);
14 | query = jest.fn().mockResolvedValue({ rows: [{ mvt: Buffer.from("data") }, { mvt: Buffer.from("something") }] });
15 | end = jest.fn().mockResolvedValue(this);
16 | }
17 | }));
18 |
19 | /** Setup mocks for aws */
20 | const mockPutObject = jest.fn().mockResolvedValue(null);
21 | jest.mock('@aws-sdk/client-s3', () => {
22 | return {
23 | S3Client: jest.fn(() => ({
24 | send: mockPutObject
25 | })),
26 | PutObjectCommand: jest.fn(),
27 | };
28 | });
29 |
30 |
31 | // fake Context
32 | const ctx: Context = {
33 | callbackWaitsForEmptyEventLoop: true,
34 | functionName: "",
35 | functionVersion: "",
36 | invokedFunctionArn: "",
37 | memoryLimitInMB: "128",
38 | awsRequestId: "",
39 | logGroupName: "",
40 | logStreamName: "",
41 |
42 | getRemainingTimeInMillis: () => { return 3000; },
43 | done: () => { },
44 | fail: () => { },
45 | succeed: () => { }
46 | }
47 |
48 | describe('environmental variables', () => {
49 | const OLD_ENV = process.env;
50 |
51 | beforeEach(() => {
52 | mockPutObject.mockClear()
53 | jest.resetModules() // this is important - it clears the cache
54 | process.env = { ...OLD_ENV };
55 | delete process.env.LOG_LEVEL; // as this was specified in package.json for test:*
56 | });
57 |
58 | afterEach(() => {
59 | process.env = OLD_ENV;
60 | });
61 |
62 | it("no environmental variables set", async function () {
63 | const index = require('../src/index');
64 | const response = await index.handler({ path: "/local/14/8691/5677.mvt" }, ctx, () => { });
65 | const gzipped = await asyncgzip("data") as Buffer;
66 | expect(response).to.deep.equal({
67 | statusCode: 200,
68 | headers: {
69 | 'Content-Type': 'application/vnd.mapbox-vector-tile',
70 | 'Content-Encoding': 'gzip',
71 | 'access-control-allow-origin': '*'
72 | },
73 | body: gzipped.toString('base64'),
74 | isBase64Encoded: true
75 | });
76 | expect(mockPutObject.mock.calls.length).to.be.equal(0);
77 | });
78 |
79 | it("LOG_LEVEL=1", async function () {
80 | process.env.LOG_LEVEL = "1";
81 | const index = require('../src/index');
82 | const response = await index.handler({ path: "/local/14/8691/5677.mvt" }, ctx, () => { });
83 | const gzipped = await asyncgzip("data") as Buffer;
84 | expect(response).to.deep.equal({
85 | statusCode: 200,
86 | headers: {
87 | 'Content-Type': 'application/vnd.mapbox-vector-tile',
88 | 'Content-Encoding': 'gzip',
89 | 'access-control-allow-origin': '*'
90 | },
91 | body: gzipped.toString('base64'),
92 | isBase64Encoded: true
93 | });
94 | expect(mockPutObject.mock.calls.length).to.be.equal(0);
95 | });
96 |
97 | it("CACHE_BUCKET=sampleBucket", async function () {
98 | process.env.CACHE_BUCKET = "sampleBucket";
99 | const index = require('../src/index');
100 | const response = await index.handler({ path: "/local/14/8691/5677.mvt" }, ctx, () => { });
101 | const gzipped = await asyncgzip("data") as Buffer;
102 | expect(response).to.deep.equal({
103 | statusCode: 200,
104 | headers: {
105 | 'Content-Type': 'application/vnd.mapbox-vector-tile',
106 | 'Content-Encoding': 'gzip',
107 | 'access-control-allow-origin': '*'
108 | },
109 | body: gzipped.toString('base64'),
110 | isBase64Encoded: true
111 | });
112 | expect(mockPutObject.mock.calls.length).to.be.equal(1);
113 | });
114 |
115 | it("GZIP=false", async function () {
116 | process.env.GZIP = "false";
117 | const index = require('../src/index');
118 | const response = await index.handler({ path: "/local/14/8691/5677.mvt" }, ctx, () => { });
119 | expect(response).to.deep.equal({
120 | statusCode: 200,
121 | headers: {
122 | 'Content-Type': 'application/vnd.mapbox-vector-tile',
123 | 'Content-Encoding': 'identity',
124 | 'access-control-allow-origin': '*'
125 | },
126 | body: Buffer.from('data').toString('base64'),
127 | isBase64Encoded: true
128 | });
129 | expect(mockPutObject.mock.calls.length).to.be.equal(0);
130 | });
131 |
132 | });
--------------------------------------------------------------------------------
/test/fixtures/duplicate_layername.toml:
--------------------------------------------------------------------------------
1 | [[sources]]
2 | name = "local"
3 |
4 | [[sources.layers]]
5 | name = "landuse"
6 | minzoom = 8
7 | table = "import.landuse_gen8"
8 |
9 | [[sources.layers]]
10 | name = "landuse"
11 | minzoom = 9
12 | table = "import.landuse_gen9"
13 |
--------------------------------------------------------------------------------
/test/fixtures/local_14_8691_5677.js:
--------------------------------------------------------------------------------
1 | // Sample event data
2 | module.exports = {
3 | path: "/local/14/8686/5691.mvt",
4 | };
--------------------------------------------------------------------------------
/test/fixtures/simple.toml:
--------------------------------------------------------------------------------
1 | [[sources]]
2 | name = "local"
3 | namespace = "import"
4 |
5 | [[sources.layers]]
6 | name = "landuse"
7 | minzoom = 8
8 | table = "landuse_gen8"
9 |
--------------------------------------------------------------------------------
/test/fixtures/simple_dbconfig.toml:
--------------------------------------------------------------------------------
1 | [[sources]]
2 | name = "local"
3 | host = "localhost"
4 | port = 5432
5 | database = "local"
6 | user = "user"
7 | password = "secret"
8 |
9 | [[sources.layers]]
10 | name = "landuse"
11 | minzoom = 8
12 | namespace = "import"
13 | table = "landuse_gen8"
14 |
--------------------------------------------------------------------------------
/test/fixtures/simple_z13.sql:
--------------------------------------------------------------------------------
1 | SELECT ( (SELECT ST_AsMVT(q, 'landuse', 4096, 'geom') AS l FROM
2 | (SELECT ST_AsMvtGeom(
3 | geometry,
4 | ST_Transform(ST_MakeEnvelope(!BBOX!, 4326), 3857),
5 | 4096,
6 | 64,
7 | true
8 | ) AS geom
9 | FROM import.landuse_gen8 WHERE (geometry && ST_Transform(ST_MakeEnvelope(!BBOX!, 4326), 3857))) AS q) ) AS mvt
--------------------------------------------------------------------------------
/test/logger.test.ts:
--------------------------------------------------------------------------------
1 | import { Log, LogLevels } from "../src/tileserver";
2 | import "jest";
3 |
4 | describe("logger", function () {
5 | it("regular constructor", function () {
6 | let log = new Log(LogLevels.INFO);
7 | log.show("log", LogLevels.TRACE);
8 | });
9 |
10 | it("use default value in constructor", function () {
11 | let log = new Log();
12 | log.show("log", LogLevels.TRACE);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/test/parser.test.ts:
--------------------------------------------------------------------------------
1 | import { Tile } from "../src/projection";
2 | import { Tileserver } from "../src/tileserver";
3 | import { expect } from "chai";
4 |
5 | import "jest";
6 |
7 | const tileserver = new Tileserver({ sources: [] }, "testBucket")
8 |
9 | describe("Parsing functions", function () {
10 | it("extractTile regular #1 - simple path", function () {
11 | let tile: Tile | null = tileserver.extractTile("/local/0/0/0.mvt");
12 | expect(tile).to.be.an('object');
13 | expect(tile).to.deep.equal({ "x": 0, "y": 0, "z": 0 });
14 | });
15 | it("extractTile regular #2 - complex path", function () {
16 | let tile: Tile | null = tileserver.extractTile("/local/11/1087/714.mvt");
17 | expect(tile).to.be.an('object');
18 | expect(tile).to.deep.equal({ "x": 1087, "y": 714, "z": 11 });
19 | });
20 | it("extractTile regular #3 - strange path", function () {
21 | let tile: Tile | null = tileserver.extractTile("/local/1337/something/11/1087/714.mvt");
22 | expect(tile).to.be.an('object');
23 | expect(tile).to.deep.equal({ "x": 1087, "y": 714, "z": 11 });
24 | });
25 |
26 | it("extractTile negative #1 - y-value missing", function () {
27 | let tile: Tile | null = tileserver.extractTile("/local/1087/714.mvt");
28 | expect(tile).to.be.null;
29 | });
30 | it("extractTile negative #2 - wrong extension", function () {
31 | let tile: Tile | null = tileserver.extractTile("/local/11/1087/714.pbf");
32 | expect(tile).to.be.null;
33 | });
34 | it("extractTile negative #3 - totally useless request", function () {
35 | let tile: Tile | null = tileserver.extractTile("foo");
36 | expect(tile).to.be.null;
37 | });
38 | it("extractTile negative #4 - invalid extension", function () {
39 | let tile: Tile | null = tileserver.extractTile("/local/14/8691/5677.mvtinvalid");
40 | expect(tile).to.be.null;
41 | });
42 | it("extractTile negative #5 - oversized input", function () {
43 | const longString = '9'.repeat(1024);
44 | let tile: Tile | null = tileserver.extractTile(longString);
45 | expect(tile).to.be.null;
46 | });
47 |
48 |
49 | it("extractSource regular #1 - simple path", function () {
50 | let source: string | null = tileserver.extractSource("/local/0/0/0.mvt");
51 | expect(source).to.be.equal('local');
52 | });
53 | it("extractSource regular #2 - strange path", function () {
54 | let source: string | null = tileserver.extractSource("/foo/13bar37/global/11/1087/714.mvt/foo2/local/11/1087/714.mvt");
55 | expect(source).to.be.equal('local');
56 | });
57 |
58 | it("extractSource negative #1 - incomplete path", function () {
59 | let source: string | null = tileserver.extractSource("/local/");
60 | expect(source).to.be.null;
61 | });
62 | it("extractSource negative #2 - totally useless request", function () {
63 | let source: string | null = tileserver.extractSource("foo");
64 | expect(source).to.be.null;
65 | });
66 | it("extractSource negative #3 - input length limit exceeded", function () {
67 | const longString = '9'.repeat(1024);
68 | let source: string | null = tileserver.extractSource(longString);
69 | expect(source).to.be.null;
70 | });
71 | it("extractSource SQL-Injection #1 - `select now()`", function () {
72 | let source: string | null = tileserver.extractSource("/select+now%28%29/0/0/0.mvt");
73 | expect(source).to.be.equal('29');
74 | });
75 |
76 | })
77 |
--------------------------------------------------------------------------------
/tileserver-openapi.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.0
2 | info:
3 | title: cyclemap.link
4 | description: |
5 | generate mapbox vectortiles
6 | contact: {}
7 | version: 1.2.3
8 |
9 | paths:
10 | /local/{zoom}/{x}/{y}.mvt:
11 | parameters:
12 | - name: zoom
13 | in: path
14 | required: true
15 | schema:
16 | type: integer
17 | minimum: 1
18 | maximum: 20
19 | - name: x
20 | in: path
21 | required: true
22 | schema:
23 | type: integer
24 | minimum: 0
25 | - name: y
26 | in: path
27 | required: true
28 | schema:
29 | type: integer
30 | minimum: 0
31 | get:
32 | summary: gets a tile from the local layer
33 | description: cool endpoint
34 | operationId: ewe
35 | tags:
36 | - vectortile
37 | responses:
38 | "200":
39 | description: "OK"
40 | content:
41 | application/vnd.mapbox-vector-tile:
42 | schema:
43 | type: string
44 | format: binary
45 | tags:
46 | - name: vectortile
47 | description: a mapbox vectortile
48 | externalDocs:
49 | url: http://mapbox.github.io/vector-tile-spec/
50 | servers:
51 | - url: https://4vci3n7djxnwdhltigbeptswua0vzafy.lambda-url.eu-central-1.on.aws
52 | description: Temporary Lambda Function URL for testing purposes
53 | - url: https://tileserver.cyclemap.link
54 | description: Lambda Function Endpoint
55 |
--------------------------------------------------------------------------------
/tileserver_layer/nodejs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pg_layer",
3 | "version": "1.0.0",
4 | "description": "Layer for tileserver lambda-function",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/henrythasler/cloud-tileserver.git"
12 | },
13 | "author": "Henry Thasler",
14 | "license": "MIT",
15 | "dependencies": {
16 | "pg": "^8.11.3"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tools/benchmark.sh:
--------------------------------------------------------------------------------
1 | # ENDPOINT=https://4vci3n7djxnwdhltigbeptswua0vzafy.lambda-url.eu-central-1.on.aws/
2 | ENDPOINT=https://tileserver.cyclemap.link/
3 | FILE=local/14/8691/5677.mvt
4 |
5 | printf "run;response_code;http_version;size_download;time_namelookup;time_connect;time_appconnect;time_starttransfer;time_total\n"
6 | for i in {1..50}; do
7 | curl -s -w "${i};%{response_code};%{http_version};%{size_download};%{time_namelookup};%{time_connect};%{time_appconnect};%{time_starttransfer};%{time_total}\n" -o /dev/null ${ENDPOINT}${FILE}
8 | done
--------------------------------------------------------------------------------
/tools/gensprites.js:
--------------------------------------------------------------------------------
1 | var spritezero = require('@mapbox/spritezero');
2 | var fs = require('fs');
3 | var glob = require('glob');
4 | var path = require('path');
5 |
6 | var spritePath = "sprites";
7 |
8 | [1].forEach(function(pxRatio) {
9 | var svgs = glob.sync(path.resolve(path.join(spritePath, '*.svg')))
10 | .map(function(f) {
11 | return {
12 | svg: fs.readFileSync(f),
13 | id: path.basename(f).replace('.svg', '')
14 | };
15 | });
16 | var pngPath = path.resolve(path.join(spritePath, 'cyclemap.png'));
17 | var jsonPath = path.resolve(path.join(spritePath, 'cyclemap.json'));
18 |
19 | // Pass `true` in the layout parameter to generate a data layout
20 | // suitable for exporting to a JSON sprite manifest file.
21 | spritezero.generateLayout({ imgs: svgs, pixelRatio: pxRatio, format: true }, function(err, dataLayout) {
22 | if (err) return;
23 | fs.writeFileSync(jsonPath, JSON.stringify(dataLayout));
24 | });
25 |
26 | // Pass `false` in the layout parameter to generate an image layout
27 | // suitable for exporting to a PNG sprite image file.
28 | spritezero.generateLayout({ imgs: svgs, pixelRatio: pxRatio, format: false }, function(err, imageLayout) {
29 | spritezero.generateImage(imageLayout, function(err, image) {
30 | if (err) return;
31 | fs.writeFileSync(pngPath, image);
32 | });
33 | });
34 | });
35 |
36 | [2, 4].forEach(function(pxRatio) {
37 | var svgs = glob.sync(path.resolve(path.join(spritePath, '*.svg')))
38 | .map(function(f) {
39 | return {
40 | svg: fs.readFileSync(f),
41 | id: path.basename(f).replace('.svg', '')
42 | };
43 | });
44 | var pngPath = path.resolve(path.join(spritePath, 'cyclemap@' + pxRatio + 'x.png'));
45 | var jsonPath = path.resolve(path.join(spritePath, 'cyclemap@' + pxRatio + 'x.json'));
46 |
47 | // Pass `true` in the layout parameter to generate a data layout
48 | // suitable for exporting to a JSON sprite manifest file.
49 | spritezero.generateLayout({ imgs: svgs, pixelRatio: pxRatio, format: true }, function(err, dataLayout) {
50 | if (err) return;
51 | fs.writeFileSync(jsonPath, JSON.stringify(dataLayout));
52 | });
53 |
54 | // Pass `false` in the layout parameter to generate an image layout
55 | // suitable for exporting to a PNG sprite image file.
56 | spritezero.generateLayout({ imgs: svgs, pixelRatio: pxRatio, format: false }, function(err, imageLayout) {
57 | spritezero.generateImage(imageLayout, function(err, image) {
58 | if (err) return;
59 | fs.writeFileSync(pngPath, image);
60 | });
61 | });
62 | });
--------------------------------------------------------------------------------
/tools/toml2json.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | exports.__esModule = true;
3 | var toml_1 = require("@iarna/toml");
4 | var fs_1 = require("fs");
5 | console.log(JSON.stringify(toml_1.parse(fs_1.readFileSync("/dev/stdin", "utf8")), null, 2));
6 |
--------------------------------------------------------------------------------
/tools/toml2json.ts:
--------------------------------------------------------------------------------
1 | import { parse } from "@iarna/toml";
2 | import { readFileSync } from "fs";
3 | console.log(JSON.stringify(parse(readFileSync("/dev/stdin", "utf8")), null, 2));
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target": "ES2022", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
6 | // "lib": [], /* Specify library files to be included in the compilation. */
7 | // "allowJs": true, /* Allow javascript files to be compiled. */
8 | // "checkJs": true, /* Report errors in .js files. */
9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
12 | // "sourceMap": true, /* Generates corresponding '.map' file. */
13 | // "outFile": "./", /* Concatenate and emit output to single file. */
14 | "outDir": "./dist", /* Redirect output structure to the directory. */
15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
16 | // "composite": true, /* Enable project compilation */
17 | // "incremental": true, /* Enable incremental compilation */
18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
19 | // "removeComments": true, /* Do not emit comments to output. */
20 | // "noEmit": true, /* Do not emit outputs. */
21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
24 |
25 | /* Strict Type-Checking Options */
26 | "strict": true, /* Enable all strict type-checking options. */
27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
28 | // "strictNullChecks": true, /* Enable strict null checks. */
29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
34 |
35 | /* Additional Checks */
36 | // "noUnusedLocals": true, /* Report errors on unused locals. */
37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
40 |
41 | /* Module Resolution Options */
42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
46 | "typeRoots": ["node_modules/@types"], /* List of folders to include type definitions from. */
47 | "types": ["node", "jest"], /* Type declaration files to be included in compilation. */
48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
51 |
52 | /* Source Map Options */
53 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
57 |
58 | /* Experimental Options */
59 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
61 | "resolveJsonModule": true
62 | },
63 | "include": [
64 | "src/**/*.ts"
65 | ]
66 | }
67 |
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoints": ["src"],
3 | "entryPointStrategy": "expand",
4 | "out": "docs/out",
5 | }
--------------------------------------------------------------------------------