Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00aaafa6b5 | |||
| bb45d0bf6d | |||
| 0dc8d45265 | |||
| 42171015b9 | |||
| edfb05186f | |||
| 41027dd975 | |||
| 8585720380 | |||
| cf729bc893 | |||
| 539125df6f |
@@ -1,8 +0,0 @@
|
|||||||
*
|
|
||||||
!src
|
|
||||||
!public
|
|
||||||
!test
|
|
||||||
!package.json
|
|
||||||
!mapbox-mbtiles-0.15.1.tgz
|
|
||||||
!package-lock.json
|
|
||||||
!docker-entrypoint.sh
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
public
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
node: true,
|
|
||||||
es6: true,
|
|
||||||
},
|
|
||||||
parserOptions: {
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
ecmaVersion: 2020,
|
|
||||||
sourceType: 'module',
|
|
||||||
lib: ['es2020'],
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
tsx: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: ['prettier', 'jsdoc', 'security'],
|
|
||||||
extends: [
|
|
||||||
'prettier',
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:@typescript-eslint/eslint-recommended',
|
|
||||||
'plugin:prettier/recommended',
|
|
||||||
'plugin:jsdoc/recommended',
|
|
||||||
'plugin:security/recommended',
|
|
||||||
],
|
|
||||||
// add your custom rules here
|
|
||||||
rules: {
|
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# Set default behavior to automatically normalize line endings.
|
|
||||||
###############################################################################
|
|
||||||
* text=auto
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# behavior for Unix scripts
|
|
||||||
#
|
|
||||||
# Unix scripts are treated as binary by default.
|
|
||||||
###############################################################################
|
|
||||||
*.sh eol=lf
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
name: Build tileserver-gl image
|
|
||||||
run-name: Build tileserver-gl image
|
|
||||||
on: [push]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: https://github.com/actions/checkout@v4
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: https://github.com/docker/setup-buildx-action@v3
|
|
||||||
with:
|
|
||||||
config-inline: |
|
|
||||||
[registry."10.10.8.83:32000"]
|
|
||||||
http = true
|
|
||||||
insecure = true
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: https://github.com/docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
push: true
|
|
||||||
tags: "10.10.8.83:32000/tsgl2002:latest"
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: npm
|
|
||||||
versioning-strategy: increase
|
|
||||||
directory: '/'
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
commit-message:
|
|
||||||
prefix: fix
|
|
||||||
prefix-development: chore
|
|
||||||
include: scope
|
|
||||||
- package-ecosystem: github-actions
|
|
||||||
directory: '/'
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
commit-message:
|
|
||||||
prefix: fix
|
|
||||||
prefix-development: chore
|
|
||||||
include: scope
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
name: 'Auto Merge PRs'
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
automerge:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: fastify/github-action-merge-dependabot@v3
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
name: 'Continuous Integration'
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
checks: write
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ci:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- name: Check out repository ✨ (non-dependabot)
|
|
||||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Check out repository 🎉 (dependabot)
|
|
||||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
|
|
||||||
- name: Setup node env 📦
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version-file: 'package.json'
|
|
||||||
check-latest: true
|
|
||||||
cache: 'npm'
|
|
||||||
|
|
||||||
- name: Install dependencies 🚀
|
|
||||||
run: npm ci --prefer-offline --no-audit --omit=optional
|
|
||||||
|
|
||||||
- name: Run linter(s) 💅
|
|
||||||
uses: wearerequired/lint-action@v2
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
continue_on_error: false
|
|
||||||
git_name: github-actions[bot]
|
|
||||||
git_email: github-actions[bot]@users.noreply.github.com
|
|
||||||
auto_fix: false
|
|
||||||
eslint: true
|
|
||||||
eslint_extensions: js,cjs,mjs,ts
|
|
||||||
prettier: true
|
|
||||||
prettier_extensions: js,cjs,ts,json
|
|
||||||
|
|
||||||
- name: Run hadolint 🐳
|
|
||||||
uses: hadolint/hadolint-action@v3.1.0
|
|
||||||
with:
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
ignore: DL3008,DL3015
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
name: 'CodeQL'
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
schedule:
|
|
||||||
- cron: '45 23 * * 2'
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: [javascript]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v2
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
queries: +security-and-quality
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v2
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v2
|
|
||||||
with:
|
|
||||||
category: '/language:${{ matrix.language }}'
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
name: 'Continuous Testing'
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
checks: write
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ct:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- name: Check out repository ✨ (non-dependabot)
|
|
||||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Check out repository 🎉 (dependabot)
|
|
||||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
|
|
||||||
- name: Update apt-get 🚀
|
|
||||||
run: sudo apt-get update -qq
|
|
||||||
|
|
||||||
- name: Install dependencies (Ubuntu) 🚀
|
|
||||||
run: >-
|
|
||||||
sudo apt-get install -qq libcairo2-dev libjpeg8-dev libpango1.0-dev
|
|
||||||
libgif-dev build-essential g++ xvfb libgles2-mesa-dev libgbm-dev
|
|
||||||
libxxf86vm-dev
|
|
||||||
|
|
||||||
- name: Setup node env 📦
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version-file: 'package.json'
|
|
||||||
check-latest: true
|
|
||||||
cache: 'npm'
|
|
||||||
|
|
||||||
- name: Install dependencies 🚀
|
|
||||||
run: npm ci --prefer-offline --no-audit --omit=optional
|
|
||||||
|
|
||||||
- name: Pull test data 📦
|
|
||||||
run: >-
|
|
||||||
wget -O test_data.zip
|
|
||||||
https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/test_data.zip
|
|
||||||
|
|
||||||
- name: Prepare test data 📦
|
|
||||||
run: unzip -q test_data.zip -d test_data
|
|
||||||
|
|
||||||
- name: Run tests 🧪
|
|
||||||
run: xvfb-run --server-args="-screen 0 1024x768x24" npm test
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
with:
|
|
||||||
platforms: 'arm64'
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Test Docker Build
|
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: false
|
|
||||||
platforms: linux/arm64,linux/amd64
|
|
||||||
# experimental: https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md#cache-backend-api
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
name: 'The Pipeline'
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ci-${{ github.ref }}-1
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
extract-branch:
|
|
||||||
name: 'Fetch branch'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
current_branch: ${{ steps.get-branch.outputs.current_branch }}
|
|
||||||
steps:
|
|
||||||
- name: Extract branch name 🕊
|
|
||||||
id: get-branch
|
|
||||||
run: echo "current_branch=${GITHUB_REF##*/}" >> $GITHUB_OUTPUT
|
|
||||||
ci:
|
|
||||||
name: 'CI'
|
|
||||||
needs:
|
|
||||||
- extract-branch
|
|
||||||
uses: ./.github/workflows/ci.yml
|
|
||||||
ct:
|
|
||||||
name: 'CT'
|
|
||||||
needs:
|
|
||||||
- extract-branch
|
|
||||||
uses: ./.github/workflows/ct.yml
|
|
||||||
automerger:
|
|
||||||
name: 'Automerge Dependabot PRs'
|
|
||||||
needs:
|
|
||||||
- ci
|
|
||||||
- ct
|
|
||||||
- extract-branch
|
|
||||||
if: >
|
|
||||||
github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]'
|
|
||||||
uses: ./.github/workflows/automerger.yml
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
name: 'Build, Test, Release'
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
docker_user:
|
|
||||||
description: 'Docker Username'
|
|
||||||
required: true
|
|
||||||
docker_token:
|
|
||||||
description: 'Docker Token'
|
|
||||||
required: true
|
|
||||||
npm_token:
|
|
||||||
description: 'NPM Token'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: 'Build, Test, Publish'
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- name: Check out repository ✨
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Update apt-get 🚀
|
|
||||||
run: sudo apt-get update -qq
|
|
||||||
|
|
||||||
- name: Install dependencies (Ubuntu) 🚀
|
|
||||||
run: >-
|
|
||||||
sudo apt-get install -qq libcairo2-dev libjpeg8-dev libpango1.0-dev
|
|
||||||
libgif-dev build-essential g++ xvfb libgles2-mesa-dev libgbm-dev
|
|
||||||
libxxf86vm-dev
|
|
||||||
|
|
||||||
- name: Setup node env 📦
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version-file: 'package.json'
|
|
||||||
check-latest: true
|
|
||||||
cache: 'npm'
|
|
||||||
|
|
||||||
- name: Install dependencies 🚀
|
|
||||||
run: npm ci --prefer-offline --no-audit --omit=optional
|
|
||||||
|
|
||||||
- name: Pull test data 📦
|
|
||||||
run: >-
|
|
||||||
wget -O test_data.zip
|
|
||||||
https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/test_data.zip
|
|
||||||
|
|
||||||
- name: Prepare test data 📦
|
|
||||||
run: unzip -q test_data.zip -d test_data
|
|
||||||
|
|
||||||
- name: Run tests 🧪
|
|
||||||
run: xvfb-run --server-args="-screen 0 1024x768x24" npm test
|
|
||||||
|
|
||||||
- name: Remove Test Data
|
|
||||||
run: rm -R test_data*
|
|
||||||
|
|
||||||
- name: Publish to Full Version NPM
|
|
||||||
run: |
|
|
||||||
npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
|
|
||||||
npm publish --access public
|
|
||||||
env:
|
|
||||||
NPM_TOKEN: ${{ github.event.inputs.npm_token }}
|
|
||||||
|
|
||||||
- name: Get version
|
|
||||||
run: |
|
|
||||||
echo "PACKAGE_VERSION=$(grep '"version"' package.json | cut -d '"' -f 4 | head -n 1)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
with:
|
|
||||||
platforms: 'arm64'
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ github.event.inputs.docker_user }}
|
|
||||||
password: ${{ github.event.inputs.docker_token }}
|
|
||||||
|
|
||||||
- name: Build and publish Full Version to Docker Hub
|
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: maptiler/tileserver-gl:latest, maptiler/tileserver-gl:v${{ env.PACKAGE_VERSION }}
|
|
||||||
platforms: linux/arm64,linux/amd64
|
|
||||||
# experimental: https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md#cache-backend-api
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
||||||
- name: Create Tileserver Light Directory
|
|
||||||
run: node publish.js --no-publish
|
|
||||||
|
|
||||||
- name: Install node dependencies
|
|
||||||
run: npm install
|
|
||||||
working-directory: ./light
|
|
||||||
|
|
||||||
- name: Publish to Light Version NPM
|
|
||||||
working-directory: ./light
|
|
||||||
run: |
|
|
||||||
npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
|
|
||||||
npm publish --access public
|
|
||||||
env:
|
|
||||||
NPM_TOKEN: ${{ github.event.inputs.npm_token }}
|
|
||||||
|
|
||||||
- name: Build and publish Light Version to Docker Hub
|
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
with:
|
|
||||||
context: ./light
|
|
||||||
file: ./light/Dockerfile
|
|
||||||
push: true
|
|
||||||
tags: maptiler/tileserver-gl-light:latest, maptiler/tileserver-gl-light:v${{ env.PACKAGE_VERSION }}
|
|
||||||
platforms: linux/arm64,linux/amd64
|
|
||||||
# experimental: https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md#cache-backend-api
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
docs/_build
|
|
||||||
node_modules
|
|
||||||
test_data
|
|
||||||
data
|
|
||||||
light
|
|
||||||
plugins
|
|
||||||
config.json
|
|
||||||
*.mbtiles
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ignored:
|
|
||||||
- DL3008
|
|
||||||
- DL3015
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
NAME=$(git config user.name)
|
|
||||||
EMAIL=$(git config user.email)
|
|
||||||
|
|
||||||
if [ -z "$NAME" ]; then
|
|
||||||
echo "empty git config user.name"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$EMAIL" ]; then
|
|
||||||
echo "empty git config user.email"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
git interpret-trailers --if-exists doNothing --trailer \
|
|
||||||
"Signed-off-by: $NAME <$EMAIL>" \
|
|
||||||
--in-place "$1"
|
|
||||||
|
|
||||||
npm exec --no -- commitlint --edit $1
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
npm exec --no -- lint-staged --no-stash
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
public
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Read the Docs configuration file for Sphinx projects
|
|
||||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
|
||||||
|
|
||||||
# Required
|
|
||||||
version: 2
|
|
||||||
|
|
||||||
# Set the version of Python and other tools you might need
|
|
||||||
build:
|
|
||||||
os: ubuntu-22.04
|
|
||||||
tools:
|
|
||||||
python: "3.11"
|
|
||||||
|
|
||||||
# Build documentation in the doc/help/ directory with Sphinx
|
|
||||||
sphinx:
|
|
||||||
configuration: docs/conf.py
|
|
||||||
|
|
||||||
formats:
|
|
||||||
- pdf
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
FROM ubuntu:focal AS builder
|
|
||||||
|
|
||||||
ENV NODE_ENV="production"
|
|
||||||
|
|
||||||
RUN set -ex; \
|
|
||||||
export DEBIAN_FRONTEND=noninteractive; \
|
|
||||||
apt-get -qq update; \
|
|
||||||
apt-get -y --no-install-recommends install \
|
|
||||||
build-essential \
|
|
||||||
ca-certificates \
|
|
||||||
wget \
|
|
||||||
pkg-config \
|
|
||||||
xvfb \
|
|
||||||
libglfw3-dev \
|
|
||||||
libuv1-dev \
|
|
||||||
libjpeg-turbo8 \
|
|
||||||
libicu66 \
|
|
||||||
libcairo2-dev \
|
|
||||||
libpango1.0-dev \
|
|
||||||
libjpeg-dev \
|
|
||||||
libgif-dev \
|
|
||||||
librsvg2-dev \
|
|
||||||
gir1.2-rsvg-2.0 \
|
|
||||||
librsvg2-2 \
|
|
||||||
librsvg2-common \
|
|
||||||
libcurl4-openssl-dev \
|
|
||||||
libpixman-1-dev \
|
|
||||||
libpixman-1-0; \
|
|
||||||
apt-get -y --purge autoremove; \
|
|
||||||
apt-get clean; \
|
|
||||||
rm -rf /var/lib/apt/lists/*;
|
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
|
||||||
|
|
||||||
RUN wget -qO- https://deb.nodesource.com/setup_18.x | bash; \
|
|
||||||
apt-get install -y nodejs; \
|
|
||||||
npm i -g npm@latest; \
|
|
||||||
apt-get -y remove wget; \
|
|
||||||
apt-get -y --purge autoremove; \
|
|
||||||
apt-get clean; \
|
|
||||||
rm -rf /var/lib/apt/lists/*;
|
|
||||||
|
|
||||||
RUN mkdir -p /usr/src/app
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
|
|
||||||
COPY mapbox-mbtiles-0.15.1.tgz /usr/src/app
|
|
||||||
COPY package.json /usr/src/app
|
|
||||||
COPY package-lock.json /usr/src/app
|
|
||||||
|
|
||||||
RUN npm ci --omit=dev; \
|
|
||||||
chown -R root:root /usr/src/app;
|
|
||||||
|
|
||||||
FROM ubuntu:focal AS final
|
|
||||||
|
|
||||||
ENV \
|
|
||||||
NODE_ENV="production" \
|
|
||||||
CHOKIDAR_USEPOLLING=1 \
|
|
||||||
CHOKIDAR_INTERVAL=500
|
|
||||||
|
|
||||||
RUN set -ex; \
|
|
||||||
export DEBIAN_FRONTEND=noninteractive; \
|
|
||||||
groupadd -r node; \
|
|
||||||
useradd -r -g node node; \
|
|
||||||
apt-get -qq update; \
|
|
||||||
apt-get -y --no-install-recommends install \
|
|
||||||
ca-certificates \
|
|
||||||
wget \
|
|
||||||
xvfb \
|
|
||||||
libglfw3 \
|
|
||||||
libuv1 \
|
|
||||||
libjpeg-turbo8 \
|
|
||||||
libicu66 \
|
|
||||||
libcairo2 \
|
|
||||||
libgif7 \
|
|
||||||
libopengl0 \
|
|
||||||
libpixman-1-0 \
|
|
||||||
libcurl4 \
|
|
||||||
librsvg2-2 \
|
|
||||||
libpango-1.0-0; \
|
|
||||||
apt-get -y --purge autoremove; \
|
|
||||||
apt-get clean; \
|
|
||||||
rm -rf /var/lib/apt/lists/*;
|
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
|
||||||
|
|
||||||
RUN wget -qO- https://deb.nodesource.com/setup_18.x | bash; \
|
|
||||||
apt-get install -y nodejs; \
|
|
||||||
npm i -g npm@latest; \
|
|
||||||
apt-get -y remove wget; \
|
|
||||||
apt-get -y --purge autoremove; \
|
|
||||||
apt-get clean; \
|
|
||||||
rm -rf /var/lib/apt/lists/*;
|
|
||||||
|
|
||||||
COPY --from=builder /usr/src/app /usr/src/app
|
|
||||||
|
|
||||||
COPY . /usr/src/app
|
|
||||||
|
|
||||||
RUN mkdir -p /data && chown node:node /data
|
|
||||||
VOLUME /data
|
|
||||||
WORKDIR /data
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
USER node:node
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/src/app/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
HEALTHCHECK CMD node /usr/src/app/src/healthcheck.js
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
FROM ubuntu:focal
|
|
||||||
|
|
||||||
ENV \
|
|
||||||
NODE_ENV="production" \
|
|
||||||
CHOKIDAR_USEPOLLING=1 \
|
|
||||||
CHOKIDAR_INTERVAL=500
|
|
||||||
|
|
||||||
RUN set -ex; \
|
|
||||||
export DEBIAN_FRONTEND=noninteractive; \
|
|
||||||
groupadd -r node; \
|
|
||||||
useradd -r -g node node; \
|
|
||||||
apt-get -qq update; \
|
|
||||||
apt-get -y --no-install-recommends install \
|
|
||||||
ca-certificates \
|
|
||||||
wget; \
|
|
||||||
wget -qO- https://deb.nodesource.com/setup_18.x | bash; \
|
|
||||||
apt-get install -y nodejs; \
|
|
||||||
apt-get -y remove wget; \
|
|
||||||
apt-get -y --purge autoremove; \
|
|
||||||
apt-get clean; \
|
|
||||||
rm -rf /var/lib/apt/lists/*;
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
RUN mkdir -p /data; \
|
|
||||||
chown node:node /data; \
|
|
||||||
mkdir -p /usr/src/app;
|
|
||||||
|
|
||||||
VOLUME /data
|
|
||||||
|
|
||||||
WORKDIR /data
|
|
||||||
|
|
||||||
COPY / /usr/src/app
|
|
||||||
|
|
||||||
RUN cd /usr/src/app; \
|
|
||||||
npm install --omit=dev; \
|
|
||||||
chown -R root:root /usr/src/app; \
|
|
||||||
chmod +x /usr/src/app/docker-entrypoint.sh;
|
|
||||||
|
|
||||||
USER node:node
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/src/app/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
HEALTHCHECK CMD node /usr/src/app/src/healthcheck.js
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# Run tests inside docker without requiring full installation of dependencies on local machine
|
|
||||||
# Simply run "docker build -f Dockerfile_test ."
|
|
||||||
# WARNING: sometimes it fails with a core dumped exception
|
|
||||||
|
|
||||||
FROM ubuntu:focal
|
|
||||||
|
|
||||||
ENV NODE_ENV="development"
|
|
||||||
|
|
||||||
RUN set -ex; \
|
|
||||||
export DEBIAN_FRONTEND=noninteractive; \
|
|
||||||
apt-get -qq update; \
|
|
||||||
apt-get -y --no-install-recommends install \
|
|
||||||
unzip \
|
|
||||||
build-essential \
|
|
||||||
ca-certificates \
|
|
||||||
wget \
|
|
||||||
pkg-config \
|
|
||||||
xvfb \
|
|
||||||
libglfw3-dev \
|
|
||||||
libuv1-dev \
|
|
||||||
libjpeg-turbo8 \
|
|
||||||
libicu66 \
|
|
||||||
libcairo2-dev \
|
|
||||||
libpango1.0-dev \
|
|
||||||
libjpeg-dev \
|
|
||||||
libgif-dev \
|
|
||||||
librsvg2-dev \
|
|
||||||
libcurl4-openssl-dev \
|
|
||||||
libpixman-1-dev; \
|
|
||||||
wget -qO- https://deb.nodesource.com/setup_18.x | bash; \
|
|
||||||
apt-get install -y nodejs; \
|
|
||||||
apt-get clean;
|
|
||||||
|
|
||||||
RUN mkdir -p /usr/src/app
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
|
|
||||||
RUN wget -O test_data.zip https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/test_data.zip; \
|
|
||||||
unzip -q test_data.zip -d test_data
|
|
||||||
|
|
||||||
COPY package.json .
|
|
||||||
RUN npm install
|
|
||||||
COPY / .
|
|
||||||
|
|
||||||
RUN xvfb-run --server-args="-screen 0 1024x768x24" npm test
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
It is great you want to help us making TileServer GL project better!
|
|
||||||
|
|
||||||
This is the right place only for a software bug report or a new software feature request.
|
|
||||||
|
|
||||||
NOTE: Questions about OpenMapTiles data, OpenMapTiles Server, TileHosting and other software/products do not belong here (and will not be answered)!
|
|
||||||
|
|
||||||
The usage and installation questions belongs to https://stackoverflow.com/questions/tagged/openmaptiles
|
|
||||||
A guaranteed support and consulting from the core developers via https://openmaptiles.com/support/
|
|
||||||
|
|
||||||
Please search this GitHub repo for similar requests before posting (check also closed tickets).
|
|
||||||
|
|
||||||
When reporting a problem you have with the TileServer GL software please provide:
|
|
||||||
|
|
||||||
- Clear description of the problem: What steps will lead to reproducing the error on our computer? What is exactly wrong?
|
|
||||||
- Version of the TileServer GL software you have used
|
|
||||||
- Version and name of the operating system you use or other details of your setup
|
|
||||||
- Information about your used config / styles / vector tiles
|
|
||||||
- URL / link to the specific location in a live map demo where a bug is visible is always great
|
|
||||||
- Screenshot of the problem are cool! Drag&drop an image to the report here...
|
|
||||||
|
|
||||||
We love pull requests! If you are able to code, please send us your fix or code modification via GitHub... Thanks!
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# Publishing new version
|
|
||||||
|
|
||||||
- Update version in `package.json`
|
|
||||||
- `git tag vx.x.x`
|
|
||||||
- `git push --tags`
|
|
||||||
- `docker buildx build --platform linux/amd64 -t maptiler/tileserver-gl:latest -t maptiler/tileserver-gl:[version] .`
|
|
||||||
- `docker push maptiler/tileserver-gl --all-tags`
|
|
||||||
- `npm publish --access public` or `node publish.js`
|
|
||||||
- `node publish.js --no-publish`
|
|
||||||
- `cd light`
|
|
||||||
- `docker buildx build --platform linux/amd64 -t maptiler/tileserver-gl-light:latest -t maptiler/tileserver-gl-light:[version] .`
|
|
||||||
- `docker push maptiler/tileserver-gl-light --all-tags`
|
|
||||||
- `npm publish --access public`
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||

|
|
||||||
|
|
||||||
|
|
||||||
# TileServer GL
|
|
||||||
[](https://github.com/maptiler/tileserver-gl/actions/workflows/pipeline.yml)
|
|
||||||
[](https://hub.docker.com/r/maptiler/tileserver-gl/)
|
|
||||||
|
|
||||||
Vector and raster maps with GL styles. Server-side rendering by MapLibre GL Native. Map tile server for MapLibre GL JS, Android, iOS, Leaflet, OpenLayers, GIS via WMTS, etc.
|
|
||||||
|
|
||||||
Download vector tiles from [OpenMapTiles](https://data.maptiler.com/downloads/planet/).
|
|
||||||
## Getting Started with Node
|
|
||||||
|
|
||||||
Make sure you have Node.js version **14.20.0** or above installed. Node 18 is recommended. (running `node -v` it should output something like `v18.x.x`). Running without docker requires [Native dependencies](https://maptiler-tileserver.readthedocs.io/en/latest/installation.html#npm) to be installed first.
|
|
||||||
|
|
||||||
Install `tileserver-gl` with server-side raster rendering of vector tiles with npm.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install -g tileserver-gl
|
|
||||||
```
|
|
||||||
|
|
||||||
Once installed, you can use it like the following examples.
|
|
||||||
|
|
||||||
using a mbtiles file
|
|
||||||
```bash
|
|
||||||
wget https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles
|
|
||||||
tileserver-gl --mbtiles zurich_switzerland.mbtiles
|
|
||||||
[in your browser, visit http://[server ip]:8080]
|
|
||||||
```
|
|
||||||
|
|
||||||
using a config.json + style + mbtiles file
|
|
||||||
```bash
|
|
||||||
wget https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/test_data.zip
|
|
||||||
unzip test_data.zip
|
|
||||||
tileserver-gl
|
|
||||||
[in your browser, visit http://[server ip]:8080]
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can use the `tileserver-gl-light` npm package instead, which is pure javascript, does not have any native dependencies, and can run anywhere, but does not contain rasterization on the server side made with Maplibre GL Native.
|
|
||||||
|
|
||||||
## Getting Started with Docker
|
|
||||||
|
|
||||||
An alternative to npm to start the packed software easier is to install [Docker](https://www.docker.com/) on your computer and then run from the tileserver-gl directory
|
|
||||||
|
|
||||||
Example using a mbtiles file
|
|
||||||
```bash
|
|
||||||
wget https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles
|
|
||||||
docker run --rm -it -v $(pwd):/data -p 8080:8080 maptiler/tileserver-gl --mbtiles zurich_switzerland.mbtiles
|
|
||||||
[in your browser, visit http://[server ip]:8080]
|
|
||||||
```
|
|
||||||
|
|
||||||
Example using a config.json + style + mbtiles file
|
|
||||||
```bash
|
|
||||||
wget https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/test_data.zip
|
|
||||||
unzip test_data.zip
|
|
||||||
docker run --rm -it -v $(pwd):/data -p 8080:8080 maptiler/tileserver-gl
|
|
||||||
[in your browser, visit http://[server ip]:8080]
|
|
||||||
```
|
|
||||||
|
|
||||||
Example using a different path
|
|
||||||
```bash
|
|
||||||
docker run --rm -it -v /your/local/config/path:/data -p 8080:8080 maptiler/tileserver-gl
|
|
||||||
```
|
|
||||||
replace '/your/local/config/path' with the path to your config file
|
|
||||||
|
|
||||||
|
|
||||||
Alternatively, you can use the `maptiler/tileserver-gl-light` docker image instead, which is pure javascript, does not have any native dependencies, and can run anywhere, but does not contain rasterization on the server side made with Maplibre GL Native.
|
|
||||||
|
|
||||||
## Getting Started with Linux cli
|
|
||||||
|
|
||||||
Test from command line
|
|
||||||
```bash
|
|
||||||
wget https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/test_data.zip
|
|
||||||
unzip -q test_data.zip -d test_data
|
|
||||||
xvfb-run --server-args="-screen 0 1024x768x24" npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
Run from command line
|
|
||||||
```bash
|
|
||||||
xvfb-run --server-args="-screen 0 1024x768x24" node .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
You can read the full documentation of this project at https://maptiler-tileserver.readthedocs.io/.
|
|
||||||
|
|
||||||
## Alternative
|
|
||||||
|
|
||||||
Discover MapTiler Server if you need a [map server with easy setup and user-friendly interface](https://www.maptiler.com/server/).
|
|
||||||
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# TileServer GL light
|
|
||||||
[](https://travis-ci.org/maptiler/tileserver-gl)
|
|
||||||
[](https://hub.docker.com/r/maptiler/tileserver-gl/)
|
|
||||||
|
|
||||||
Vector maps with GL styles. Map tile server for Mapbox Android, iOS, GL JS, Leaflet, OpenLayers, etc. without server side rendering.
|
|
||||||
|
|
||||||
## Quickstart
|
|
||||||
Use `npm install -g tileserver-gl-light` to install the package from npm.
|
|
||||||
|
|
||||||
Then you can simply run `tileserver-gl-light zurich_switzerland.mbtiles` to start the server for the given mbtiles.
|
|
||||||
|
|
||||||
See also `tileserver-gl` which contains server side rendering.
|
|
||||||
|
|
||||||
Prepared vector tiles can be downloaded from [OpenMapTiles.com](https://openmaptiles.com/downloads/planet/).
|
|
||||||
|
|
||||||
## Building docker image
|
|
||||||
|
|
||||||
You can build TileServer GL light image from source.
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone https://github.com/maptiler/tileserver-gl.git
|
|
||||||
cd tileserver-gl
|
|
||||||
node publish.js --no-publish
|
|
||||||
cd light
|
|
||||||
docker build -t tileserver-gl-light .
|
|
||||||
```
|
|
||||||
|
|
||||||
[Download from OpenMapTiles.com](https://openmaptiles.com/downloads/planet/) or [create](https://github.com/openmaptiles/openmaptiles) your vector tile, and run following in directory contains your *.mbtiles.
|
|
||||||
|
|
||||||
```
|
|
||||||
docker run --rm -it -v $(pwd):/data -p 8000:80 tileserver-gl-light
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
You can read full documentation of this project at https://tileserver.readthedocs.io/.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
extends: ['@commitlint/config-conventional'],
|
|
||||||
};
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
if ! which -- "${1}"; then
|
|
||||||
# first arg is not an executable
|
|
||||||
if [ -e /tmp/.X99-lock ]; then rm /tmp/.X99-lock -f; fi
|
|
||||||
export DISPLAY=:99
|
|
||||||
Xvfb "${DISPLAY}" -nolisten unix &
|
|
||||||
exec node /usr/src/app/ "$@"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$@"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
if ! which -- "${1}"; then
|
|
||||||
# first arg is not an executable
|
|
||||||
exec node /usr/src/app/ "$@"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$@"
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
# Makefile for Sphinx documentation
|
|
||||||
#
|
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
|
||||||
SPHINXOPTS =
|
|
||||||
SPHINXBUILD = sphinx-build
|
|
||||||
PAPER =
|
|
||||||
BUILDDIR = _build
|
|
||||||
|
|
||||||
# User-friendly check for sphinx-build
|
|
||||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
|
||||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Internal variables.
|
|
||||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
|
||||||
PAPEROPT_letter = -D latex_paper_size=letter
|
|
||||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
|
||||||
# the i18n builder cannot share the environment and doctrees with the others
|
|
||||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
|
||||||
|
|
||||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
|
||||||
|
|
||||||
help:
|
|
||||||
@echo "Please use \`make <target>' where <target> is one of"
|
|
||||||
@echo " html to make standalone HTML files"
|
|
||||||
@echo " dirhtml to make HTML files named index.html in directories"
|
|
||||||
@echo " singlehtml to make a single large HTML file"
|
|
||||||
@echo " pickle to make pickle files"
|
|
||||||
@echo " json to make JSON files"
|
|
||||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
|
||||||
@echo " qthelp to make HTML files and a qthelp project"
|
|
||||||
@echo " devhelp to make HTML files and a Devhelp project"
|
|
||||||
@echo " epub to make an epub"
|
|
||||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
|
||||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
|
||||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
|
||||||
@echo " text to make text files"
|
|
||||||
@echo " man to make manual pages"
|
|
||||||
@echo " texinfo to make Texinfo files"
|
|
||||||
@echo " info to make Texinfo files and run them through makeinfo"
|
|
||||||
@echo " gettext to make PO message catalogs"
|
|
||||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
|
||||||
@echo " xml to make Docutils-native XML files"
|
|
||||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
|
||||||
@echo " linkcheck to check all external links for integrity"
|
|
||||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf $(BUILDDIR)/*
|
|
||||||
|
|
||||||
html:
|
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
|
||||||
|
|
||||||
dirhtml:
|
|
||||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
|
||||||
|
|
||||||
singlehtml:
|
|
||||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
|
||||||
|
|
||||||
pickle:
|
|
||||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the pickle files."
|
|
||||||
|
|
||||||
json:
|
|
||||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the JSON files."
|
|
||||||
|
|
||||||
htmlhelp:
|
|
||||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
|
||||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
|
||||||
|
|
||||||
qthelp:
|
|
||||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
|
||||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
|
||||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/TileServerGL.qhcp"
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/TileServerGL.qhc"
|
|
||||||
|
|
||||||
devhelp:
|
|
||||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished."
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/TileServerGL"
|
|
||||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/TileServerGL"
|
|
||||||
@echo "# devhelp"
|
|
||||||
|
|
||||||
epub:
|
|
||||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
|
||||||
|
|
||||||
latex:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
|
||||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
|
||||||
"(use \`make latexpdf' here to do that automatically)."
|
|
||||||
|
|
||||||
latexpdf:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo "Running LaTeX files through pdflatex..."
|
|
||||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
|
||||||
|
|
||||||
latexpdfja:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
|
||||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
|
||||||
|
|
||||||
text:
|
|
||||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
|
||||||
|
|
||||||
man:
|
|
||||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
|
||||||
|
|
||||||
texinfo:
|
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
|
||||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
|
||||||
"(use \`make info' here to do that automatically)."
|
|
||||||
|
|
||||||
info:
|
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
|
||||||
@echo "Running Texinfo files through makeinfo..."
|
|
||||||
make -C $(BUILDDIR)/texinfo info
|
|
||||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
|
||||||
|
|
||||||
gettext:
|
|
||||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
|
||||||
|
|
||||||
changes:
|
|
||||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
|
||||||
@echo
|
|
||||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
|
||||||
|
|
||||||
linkcheck:
|
|
||||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
|
||||||
@echo
|
|
||||||
@echo "Link check complete; look for any errors in the above output " \
|
|
||||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
|
||||||
|
|
||||||
doctest:
|
|
||||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
|
||||||
@echo "Testing of doctests in the sources finished, look at the " \
|
|
||||||
"results in $(BUILDDIR)/doctest/output.txt."
|
|
||||||
|
|
||||||
xml:
|
|
||||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
|
||||||
|
|
||||||
pseudoxml:
|
|
||||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
|
||||||
@@ -1,258 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# TileServer GL documentation build configuration file, created by
|
|
||||||
# sphinx-quickstart on Thu Aug 4 23:48:49 2016.
|
|
||||||
#
|
|
||||||
# This file is execfile()d with the current directory set to its
|
|
||||||
# containing dir.
|
|
||||||
#
|
|
||||||
# Note that not all possible configuration values are present in this
|
|
||||||
# autogenerated file.
|
|
||||||
#
|
|
||||||
# All configuration values have a default; values that are commented out
|
|
||||||
# serve to show the default.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
#sys.path.insert(0, os.path.abspath('.'))
|
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
|
||||||
#needs_sphinx = '1.0'
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
|
||||||
# ones.
|
|
||||||
extensions = []
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
|
||||||
templates_path = ['_templates']
|
|
||||||
|
|
||||||
# The suffix of source filenames.
|
|
||||||
source_suffix = '.rst'
|
|
||||||
|
|
||||||
# The encoding of source files.
|
|
||||||
#source_encoding = 'utf-8-sig'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = u'TileServer GL'
|
|
||||||
copyright = u'2023, MapTiler.com'
|
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
|
||||||
# |version| and |release|, also used in various other places throughout the
|
|
||||||
# built documents.
|
|
||||||
#
|
|
||||||
# The short X.Y version.
|
|
||||||
version = '1'
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
|
||||||
release = '1.0'
|
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
||||||
# for a list of supported languages.
|
|
||||||
#language = None
|
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
|
||||||
# non-false value, then it is used:
|
|
||||||
#today = ''
|
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
|
||||||
#today_fmt = '%B %d, %Y'
|
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
|
||||||
# directories to ignore when looking for source files.
|
|
||||||
exclude_patterns = ['_build']
|
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all
|
|
||||||
# documents.
|
|
||||||
#default_role = None
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
#add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
#add_module_names = True
|
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
||||||
# output. They are ignored by default.
|
|
||||||
#show_authors = False
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
|
||||||
#modindex_common_prefix = []
|
|
||||||
|
|
||||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
|
||||||
#keep_warnings = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ----------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
||||||
# a list of builtin themes.
|
|
||||||
html_theme = 'default'
|
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
|
||||||
# further. For a list of options available for each theme, see the
|
|
||||||
# documentation.
|
|
||||||
#html_theme_options = {}
|
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
|
||||||
#html_theme_path = []
|
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
|
||||||
# "<project> v<release> documentation".
|
|
||||||
#html_title = None
|
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
|
||||||
#html_short_title = None
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
|
||||||
# of the sidebar.
|
|
||||||
#html_logo = None
|
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
||||||
# pixels large.
|
|
||||||
#html_favicon = None
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
||||||
html_static_path = ['_static']
|
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
|
||||||
# directly to the root of the documentation.
|
|
||||||
#html_extra_path = []
|
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
|
||||||
# using the given strftime format.
|
|
||||||
#html_last_updated_fmt = '%b %d, %Y'
|
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
|
||||||
# typographically correct entities.
|
|
||||||
#html_use_smartypants = True
|
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
|
||||||
#html_sidebars = {}
|
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
|
||||||
# template names.
|
|
||||||
#html_additional_pages = {}
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#html_domain_indices = True
|
|
||||||
|
|
||||||
# If false, no index is generated.
|
|
||||||
#html_use_index = True
|
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
|
||||||
#html_split_index = False
|
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
|
||||||
#html_show_sourcelink = True
|
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
|
||||||
#html_show_sphinx = True
|
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
|
||||||
#html_show_copyright = True
|
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
|
||||||
# base URL from which the finished HTML is served.
|
|
||||||
#html_use_opensearch = ''
|
|
||||||
|
|
||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
|
||||||
#html_file_suffix = None
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = 'TileServerGLdoc'
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
|
||||||
|
|
||||||
latex_elements = {
|
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
|
||||||
#'papersize': 'letterpaper',
|
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
|
||||||
#'pointsize': '10pt',
|
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
|
||||||
#'preamble': '',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title,
|
|
||||||
# author, documentclass [howto, manual, or own class]).
|
|
||||||
latex_documents = [
|
|
||||||
('index', 'TileServerGL.tex', u'TileServer GL Documentation',
|
|
||||||
u'MapTiler.com', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
|
||||||
# the title page.
|
|
||||||
#latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
||||||
# not chapters.
|
|
||||||
#latex_use_parts = False
|
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
|
||||||
#latex_show_pagerefs = False
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#latex_show_urls = False
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#latex_domain_indices = True
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output ---------------------------------------
|
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
|
||||||
# (source start file, name, description, authors, manual section).
|
|
||||||
man_pages = [
|
|
||||||
('index', 'tileservergl', u'TileServer GL Documentation',
|
|
||||||
[u'MapTiler.com'], 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#man_show_urls = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output -------------------------------------------
|
|
||||||
|
|
||||||
# Grouping the document tree into Texinfo files. List of tuples
|
|
||||||
# (source start file, target name, title, author,
|
|
||||||
# dir menu entry, description, category)
|
|
||||||
texinfo_documents = [
|
|
||||||
('index', 'TileServerGL', u'TileServer GL Documentation',
|
|
||||||
u'MapTiler.com', 'TileServerGL', 'One line description of project.',
|
|
||||||
'Miscellaneous'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#texinfo_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#texinfo_domain_indices = True
|
|
||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
|
||||||
#texinfo_show_urls = 'footnote'
|
|
||||||
|
|
||||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
|
||||||
#texinfo_no_detailmenu = False
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
==================
|
|
||||||
Configuration file
|
|
||||||
==================
|
|
||||||
|
|
||||||
The configuration file defines the behavior of the application. It's a regular JSON file.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
.. code-block:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"options": {
|
|
||||||
"paths": {
|
|
||||||
"root": "",
|
|
||||||
"fonts": "fonts",
|
|
||||||
"sprites": "sprites",
|
|
||||||
"icons": "icons",
|
|
||||||
"styles": "styles",
|
|
||||||
"mbtiles": ""
|
|
||||||
},
|
|
||||||
"domains": [
|
|
||||||
"localhost:8080",
|
|
||||||
"127.0.0.1:8080"
|
|
||||||
],
|
|
||||||
"formatQuality": {
|
|
||||||
"jpeg": 80,
|
|
||||||
"webp": 90
|
|
||||||
},
|
|
||||||
"maxScaleFactor": 3,
|
|
||||||
"maxSize": 2048,
|
|
||||||
"pbfAlias": "pbf",
|
|
||||||
"serveAllFonts": false,
|
|
||||||
"serveAllStyles": false,
|
|
||||||
"serveStaticMaps": true,
|
|
||||||
"allowRemoteMarkerIcons": true,
|
|
||||||
"tileMargin": 0
|
|
||||||
},
|
|
||||||
"styles": {
|
|
||||||
"basic": {
|
|
||||||
"style": "basic.json",
|
|
||||||
"tilejson": {
|
|
||||||
"type": "overlay",
|
|
||||||
"bounds": [8.44806, 47.32023, 8.62537, 47.43468]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hybrid": {
|
|
||||||
"style": "satellite-hybrid.json",
|
|
||||||
"serve_rendered": false,
|
|
||||||
"tilejson": {
|
|
||||||
"format": "webp"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"zurich-vector": {
|
|
||||||
"mbtiles": "zurich.mbtiles"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
``options``
|
|
||||||
===========
|
|
||||||
|
|
||||||
``paths``
|
|
||||||
---------
|
|
||||||
|
|
||||||
Defines where to look for the different types of input data.
|
|
||||||
|
|
||||||
The value of ``root`` is used as prefix for all data types.
|
|
||||||
|
|
||||||
``domains``
|
|
||||||
-----------
|
|
||||||
|
|
||||||
You can use this to optionally specify on what domains the rendered tiles are accessible. This can be used for basic load-balancing or to bypass browser's limit for the number of connections per domain.
|
|
||||||
|
|
||||||
``frontPage``
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Path to the html (relative to ``root`` path) to use as a front page.
|
|
||||||
|
|
||||||
Use ``true`` (or nothing) to serve the default TileServer GL front page with list of styles and data.
|
|
||||||
Use ``false`` to disable the front page altogether (404).
|
|
||||||
|
|
||||||
``formatQuality``
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Quality of the compression of individual image formats. [0-100]
|
|
||||||
|
|
||||||
``maxScaleFactor``
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Maximum scale factor to allow in raster tile and static maps requests (e.g. ``@3x`` suffix).
|
|
||||||
Also see ``maxSize`` below.
|
|
||||||
Default value is ``3``, maximum ``9``.
|
|
||||||
|
|
||||||
``maxSize``
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Maximum image side length to be allowed to be rendered (including scale factor).
|
|
||||||
Be careful when changing this value since there are hardware limits that need to be considered.
|
|
||||||
Default is ``2048``.
|
|
||||||
|
|
||||||
``tileMargin``
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Additional image side length added during tile rendering that is cropped from the delivered tile. This is useful for resolving the issue with cropped labels,
|
|
||||||
but it does come with a performance degradation, because additional, adjacent vector tiles need to be loaded to generate a single tile.
|
|
||||||
Default is ``0`` to disable this processing.
|
|
||||||
|
|
||||||
``minRendererPoolSizes``
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
Minimum amount of raster tile renderers per scale factor.
|
|
||||||
The value is an array: the first element is the minimum amount of renderers for scale factor one, the second for scale factor two and so on.
|
|
||||||
If the array has less elements than ``maxScaleFactor``, then the last element is used for all remaining scale factors as well.
|
|
||||||
Selecting renderer pool sizes is a trade-off between memory use and speed.
|
|
||||||
A reasonable value will depend on your hardware and your amount of styles and scale factors.
|
|
||||||
If you have plenty of memory, you'll want to set this equal to ``maxRendererPoolSizes`` to avoid increased latency due to renderer destruction and recreation.
|
|
||||||
If you need to conserve memory, you'll want something lower than ``maxRendererPoolSizes``, possibly allocating more renderers to scale factors that are more common.
|
|
||||||
Default is ``[8, 4, 2]``.
|
|
||||||
|
|
||||||
``maxRendererPoolSizes``
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
Maximum amount of raster tile renderers per scale factor.
|
|
||||||
The value and considerations are similar to ``minRendererPoolSizes`` above.
|
|
||||||
If you have plenty of memory, try setting these equal to or slightly above your processor count, e.g. if you have four processors, try a value of ``[6]``.
|
|
||||||
If you need to conserve memory, try lower values for scale factors that are less common.
|
|
||||||
Default is ``[16, 8, 4]``.
|
|
||||||
|
|
||||||
``serveAllStyles``
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
If this option is enabled, all the styles from the ``paths.styles`` will be served. (No recursion, only ``.json`` files are used.)
|
|
||||||
The process will also watch for changes in this directory and remove/add more styles dynamically.
|
|
||||||
It is recommended to also use the ``serveAllFonts`` option when using this option.
|
|
||||||
|
|
||||||
``watermark``
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Optional string to be rendered into the raster tiles (and static maps) as watermark (bottom-left corner).
|
|
||||||
Can be used for hard-coding attributions etc. (can also be specified per-style).
|
|
||||||
Not used by default.
|
|
||||||
|
|
||||||
``allowRemoteMarkerIcons``
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Allows the rendering of marker icons fetched via http(s) hyperlinks.
|
|
||||||
For security reasons only allow this if you can control the origins from where the markers are fetched!
|
|
||||||
Default is to disallow fetching of icons from remote sources.
|
|
||||||
|
|
||||||
``styles``
|
|
||||||
==========
|
|
||||||
|
|
||||||
Each item in this object defines one style (map). It can have the following options:
|
|
||||||
|
|
||||||
* ``style`` -- name of the style json file [required]
|
|
||||||
* ``serve_rendered`` -- whether to render the raster tiles for this style or not
|
|
||||||
* ``serve_data`` -- whether to allow access to the original tiles, sprites and required glyphs
|
|
||||||
* ``tilejson`` -- properties to add to the TileJSON created for the raster data
|
|
||||||
|
|
||||||
* ``format`` and ``bounds`` can be especially useful
|
|
||||||
|
|
||||||
``data``
|
|
||||||
========
|
|
||||||
|
|
||||||
Each item specifies one data source which should be made accessible by the server. It has the following options:
|
|
||||||
|
|
||||||
* ``mbtiles`` -- name of the mbtiles file [required]
|
|
||||||
|
|
||||||
The mbtiles file does not need to be specified here unless you explicitly want to serve the raw data.
|
|
||||||
|
|
||||||
Referencing local files from style JSON
|
|
||||||
=======================================
|
|
||||||
|
|
||||||
You can link various data sources from the style JSON (for example even remote TileJSONs).
|
|
||||||
|
|
||||||
MBTiles
|
|
||||||
-------
|
|
||||||
|
|
||||||
To specify that you want to use local mbtiles, use to following syntax: ``mbtiles://switzerland.mbtiles``.
|
|
||||||
The TileServer-GL will try to find the file ``switzerland.mbtiles`` in ``root`` + ``mbtiles`` path.
|
|
||||||
|
|
||||||
For example::
|
|
||||||
|
|
||||||
"sources": {
|
|
||||||
"source1": {
|
|
||||||
"url": "mbtiles://switzerland.mbtiles",
|
|
||||||
"type": "vector"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Alternatively, you can use ``mbtiles://{zurich-vector}`` to reference existing data object from the config.
|
|
||||||
In this case, the server will look into the ``config.json`` to determine what mbtiles file to use.
|
|
||||||
For the config above, this is equivalent to ``mbtiles://zurich.mbtiles``.
|
|
||||||
|
|
||||||
Sprites
|
|
||||||
-------
|
|
||||||
|
|
||||||
If your style requires any sprites, make sure the style JSON contains proper path in the ``sprite`` property.
|
|
||||||
|
|
||||||
It can be a local path (e.g. ``my-style/sprite``) or remote http(s) location (e.g. ``https://mycdn.com/my-style/sprite``). Several possible extension are added to this path, so the following files should be present:
|
|
||||||
|
|
||||||
* ``sprite.json``
|
|
||||||
* ``sprite.png``
|
|
||||||
* ``sprite@2x.json``
|
|
||||||
* ``sprite@2x.png``
|
|
||||||
|
|
||||||
You can also use the following placeholders in the sprite path for easier use:
|
|
||||||
|
|
||||||
* ``{style}`` -- gets replaced with the name of the style file (``xxx.json``)
|
|
||||||
* ``{styleJsonFolder}`` -- gets replaced with the path to the style file
|
|
||||||
|
|
||||||
Fonts (glyphs)
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Similarly to the sprites, the style JSON also needs to contain proper paths to the font glyphs (in the ``glyphs`` property) and can be both local and remote.
|
|
||||||
|
|
||||||
It should contain the following placeholders:
|
|
||||||
|
|
||||||
* ``{fontstack}`` -- name of the font and variant
|
|
||||||
* ``{range}`` -- range of the glyphs
|
|
||||||
|
|
||||||
For example ``"glyphs": "{fontstack}/{range}.pbf"`` will instruct TileServer-GL to look for the files such as ``fonts/Open Sans/0-255.pbf`` (``fonts`` come from the ``paths`` property of the ``config.json`` example above).
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
==========
|
|
||||||
Deployment
|
|
||||||
==========
|
|
||||||
|
|
||||||
Typically, you should use nginx, lighttpd or apache on the frontend. The tileserver-gl server is hidden behind it in production deployment.
|
|
||||||
|
|
||||||
Caching
|
|
||||||
=======
|
|
||||||
|
|
||||||
There is a plenty of options you can use to create proper caching infrastructure: Varnish, Cloudflare, ...
|
|
||||||
|
|
||||||
Cloudflare Cache Rules
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Cloudflare supports custom rules for configuring caching:
|
|
||||||
https://developers.cloudflare.com/cache/about/cache-rules/
|
|
||||||
|
|
||||||
tileserver-gl renders tiles in multiple formats - ``.png``, ``.jpg (jpeg)``, ``.webp`` for the raster endpoints, ``.pbf`` for vector endpoint. In addition, style information is generated with ``.json`` format.
|
|
||||||
|
|
||||||
Endpoint data can be configured to be cached by Cloudflare. For example to cache vector endpoint you will need to configure Cloudflare rules for the ``.pbf`` and ``.json`` data.
|
|
||||||
|
|
||||||
Create a rule which matches ``hostname (equal)`` and ``URI Path (ends with)`` for ``.pbf`` and ``.json`` fields. Set cache status to eligible for cache to enable the caching and overwrite the ``Edge TTL`` with ``Browser TTL`` to be 7 days (depends on your application usage).
|
|
||||||
|
|
||||||
This will ensure that your tiles are cached on the client side and by Cloudflare for seven days. If the tileserver is down or user has no internet access it will try to use cached tiles.
|
|
||||||
|
|
||||||
Note that ``Browser TTL`` will overwrite expiration dates on the client device. If you rebuild your maps, old tiles will be rendered until it expires or cache is cleared on the client device.
|
|
||||||
|
|
||||||
Nginx Cache
|
|
||||||
-----------
|
|
||||||
|
|
||||||
If you have a reverse proxy setup in front of the tileserver you may want to enable caching as it will greatly offload requests from the application.
|
|
||||||
|
|
||||||
Configure the proxy cache path directive to initialize your cache store:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
proxy_cache_path /var/cache/nginx/tileserver
|
|
||||||
keys_zone=TileserverCache:50m
|
|
||||||
levels=1:2
|
|
||||||
inactive=2w
|
|
||||||
max_size=10g;
|
|
||||||
|
|
||||||
Make sure to give proper permissions for the /var/cache/nginx/tileserver folder. Usually nginx is running with www-data user.
|
|
||||||
Enable caching on specific proxy pass:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
location / {
|
|
||||||
include proxy_params;
|
|
||||||
proxy_pass http://127.0.0.1:8080/;
|
|
||||||
|
|
||||||
proxy_cache TileserverCache;
|
|
||||||
proxy_cache_valid 200 1w;
|
|
||||||
|
|
||||||
# add_header X-Cache-Status $upstream_cache_status;
|
|
||||||
}
|
|
||||||
|
|
||||||
If you need to confirm whether caching works or not, uncomment the X-Cache-Status header. This will return a header on response with `HIT` or `MISS` header value which indicates if nginx cached the response or not.
|
|
||||||
|
|
||||||
Make sure to clean your cache by removing files in the configured directory after you change your styles or tile information. You may experiment with the caching values to fit your needs.
|
|
||||||
|
|
||||||
More about Nginx caching: https://docs.nginx.com/nginx/admin-guide/content-cache/content-caching/
|
|
||||||
|
|
||||||
Securing
|
|
||||||
========
|
|
||||||
|
|
||||||
Nginx can be used to add protection via https, password, referrer, IP address restriction, access keys, etc.
|
|
||||||
|
|
||||||
Running behind a proxy or a load-balancer
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
If you need to run TileServer GL behind a proxy, make sure the proxy sends ``X-Forwarded-*`` headers to the server (most importantly ``X-Forwarded-Host`` and ``X-Forwarded-Proto``) to ensure the URLs generated inside TileJSON, etc. are using the desired domain and protocol.
|
|
||||||
|
|
||||||
Nginx Reverse Proxy
|
|
||||||
-----------
|
|
||||||
|
|
||||||
An example nginx reverse proxy server configuration for HTTPS connections. It enables caching, CORS and Cloudflare Authenticated Pulls.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
proxy_cache_path /var/cache/nginx/tileserver
|
|
||||||
keys_zone=TileserverCache:50m
|
|
||||||
levels=1:2
|
|
||||||
inactive=2w
|
|
||||||
max_size=1g;
|
|
||||||
|
|
||||||
map_hash_bucket_size 128;
|
|
||||||
map $http_origin $allow_origin {
|
|
||||||
https://www.example.com $http_origin;
|
|
||||||
default "";
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
listen [::]:443 ssl http2;
|
|
||||||
|
|
||||||
ssl_certificate /etc/ssl/www.example.com/cert.pem;
|
|
||||||
ssl_certificate_key /etc/ssl/www.example.com/key.pem;
|
|
||||||
|
|
||||||
# https://developers.cloudflare.com/ssl/origin-configuration/authenticated-origin-pull/
|
|
||||||
ssl_client_certificate /etc/ssl/cloudflare.pem;
|
|
||||||
ssl_verify_client on;
|
|
||||||
|
|
||||||
server_name www.example.com example.com;
|
|
||||||
|
|
||||||
# Disable root application access. You may want to allow this in development.
|
|
||||||
location ~ ^/$ {
|
|
||||||
return 404;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Disable root application access. You may want to allow this in development.
|
|
||||||
location /favicon.ico {
|
|
||||||
return 404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
# This include directive sets up required headers for proxy and proxy cache.
|
|
||||||
# As well it includes the required ``X-Forwarded-*`` headers for tileserver to properly generate tiles.
|
|
||||||
include proxy_params;
|
|
||||||
|
|
||||||
proxy_pass http://127.0.0.1:8080/;
|
|
||||||
|
|
||||||
# Disable default CORS headers
|
|
||||||
proxy_hide_header Access-Control-Allow-Origin;
|
|
||||||
|
|
||||||
# Enable proxy cache
|
|
||||||
proxy_cache TileserverCache;
|
|
||||||
proxy_cache_valid 200 1w;
|
|
||||||
|
|
||||||
# Set our custom CORS
|
|
||||||
add_header 'Access-Control-Allow-Origin' $allow_origin;
|
|
||||||
|
|
||||||
# If you need to see nginx cache status. Uncomment line below.
|
|
||||||
# add_header X-Cache-Status $upstream_cache_status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
===================
|
|
||||||
Available endpoints
|
|
||||||
===================
|
|
||||||
|
|
||||||
If you visit the server on the configured port (default 8080) you can see your maps appearing in the browser.
|
|
||||||
|
|
||||||
Styles
|
|
||||||
======
|
|
||||||
* Styles are served at ``/styles/{id}/style.json`` (+ array at ``/styles.json``)
|
|
||||||
|
|
||||||
* Sprites at ``/styles/{id}/sprite[@2x].{format}``
|
|
||||||
* Fonts at ``/fonts/{fontstack}/{start}-{end}.pbf``
|
|
||||||
|
|
||||||
Rendered tiles
|
|
||||||
==============
|
|
||||||
* Rendered tiles are served at ``/styles/{id}/{z}/{x}/{y}[@2x].{format}``
|
|
||||||
|
|
||||||
* The optional ``@2x`` (or ``@3x``, ``@4x``) part can be used to render HiDPI (retina) tiles
|
|
||||||
* Available formats: ``png``, ``jpg`` (``jpeg``), ``webp``
|
|
||||||
* TileJSON at ``/styles/{id}.json``
|
|
||||||
|
|
||||||
* The rendered tiles are not available in the ``tileserver-gl-light`` version.
|
|
||||||
|
|
||||||
WMTS Capabilities
|
|
||||||
==============
|
|
||||||
* WMTS Capabilities are served at ``/styles/{id}/wmts.xml``
|
|
||||||
|
|
||||||
Static images
|
|
||||||
=============
|
|
||||||
* Several endpoints:
|
|
||||||
|
|
||||||
* ``/styles/{id}/static/{lon},{lat},{zoom}[@{bearing}[,{pitch}]]/{width}x{height}[@2x].{format}`` (center-based)
|
|
||||||
* ``/styles/{id}/static/{minx},{miny},{maxx},{maxy}/{width}x{height}[@2x].{format}`` (area-based)
|
|
||||||
* ``/styles/{id}/static/auto/{width}x{height}[@2x].{format}`` (autofit path -- see below)
|
|
||||||
|
|
||||||
* All the static image endpoints additionally support following query parameters:
|
|
||||||
|
|
||||||
* ``path`` - ``((fill|stroke|width)\:[^\|]+\|)*((enc:.+)|((-?\d+\.?\d*,-?\d+\.?\d*\|)+(-?\d+\.?\d*,-?\d+\.?\d*)))``
|
|
||||||
|
|
||||||
* comma-separated ``lng,lat``, pipe-separated pairs
|
|
||||||
|
|
||||||
* e.g. ``path=5.9,45.8|5.9,47.8|10.5,47.8|10.5,45.8|5.9,45.8``
|
|
||||||
|
|
||||||
* `Google Encoded Polyline Format <https://developers.google.com/maps/documentation/utilities/polylinealgorithm>`_
|
|
||||||
|
|
||||||
* e.g. ``path=enc:_p~iF~ps|U_ulLnnqC_mqNvxq`@``
|
|
||||||
* If 'enc:' is used, the rest of the path parameter is considered to be part of the encoded polyline string -- do not specify the coordinate pairs.
|
|
||||||
|
|
||||||
* With options (fill|stroke|width)
|
|
||||||
|
|
||||||
* e.g. ``path=stroke:yellow|width:2|fill:green|5.9,45.8|5.9,47.8|10.5,47.8|10.5,45.8|5.9,45.8`` or ``path=stroke:blue|width:1|fill:yellow|enc:_p~iF~ps|U_ulLnnqC_mqNvxq`@``
|
|
||||||
|
|
||||||
* can be provided multiple times
|
|
||||||
|
|
||||||
* ``latlng`` - indicates coordinates are in ``lat,lng`` order rather than the usual ``lng,lat``
|
|
||||||
* ``fill`` - color to use as the fill (e.g. ``red``, ``rgba(255,255,255,0.5)``, ``#0000ff``)
|
|
||||||
* ``stroke`` - color of the path stroke
|
|
||||||
* ``width`` - width of the stroke
|
|
||||||
* ``linecap`` - rendering style for the start and end points of the path
|
|
||||||
* ``linejoin`` - rendering style for overlapping segments of the path with differing directions
|
|
||||||
* ``border`` - color of the optional border path stroke
|
|
||||||
* ``borderwidth`` - width of the border stroke (default 10% of width)
|
|
||||||
* ``marker`` - Marker in format ``lng,lat|iconPath|option|option|...``
|
|
||||||
|
|
||||||
* Will be rendered with the bottom center at the provided location
|
|
||||||
* ``lng,lat`` and ``iconPath`` are mandatory and icons won't be rendered without them
|
|
||||||
* ``iconPath`` is either a link to an image served via http(s) or a path to a file relative to the configured icon path
|
|
||||||
* ``option`` must adhere to the format ``optionName:optionValue`` and supports the following names
|
|
||||||
|
|
||||||
* ``scale`` - Factor to scale image by
|
|
||||||
|
|
||||||
* e.g. ``0.5`` - Scales the image to half it's original size
|
|
||||||
|
|
||||||
* ``offset`` - Image offset as positive or negative pixel value in format ``[offsetX],[offsetY]``
|
|
||||||
|
|
||||||
* scales with ``scale`` parameter since image placement is relative to it's size
|
|
||||||
* e.g. ``2,-4`` - Image will be moved 2 pixel to the right and 4 pixel in the upwards direction from the provided location
|
|
||||||
|
|
||||||
* e.g. ``5.9,45.8|marker-start.svg|scale:0.5|offset:2,-4``
|
|
||||||
* can be provided multiple times
|
|
||||||
|
|
||||||
* ``padding`` - "percentage" padding for fitted endpoints (area-based and path autofit)
|
|
||||||
|
|
||||||
* value of ``0.1`` means "add 10% size to each side to make sure the area of interest is nicely visible"
|
|
||||||
|
|
||||||
* ``maxzoom`` - Maximum zoom level (only for auto endpoint where zoom level is calculated and not provided)
|
|
||||||
|
|
||||||
* You can also use (experimental) ``/styles/{id}/static/raw/...`` endpoints with raw spherical mercator coordinates (EPSG:3857) instead of WGS84.
|
|
||||||
|
|
||||||
* The static images are not available in the ``tileserver-gl-light`` version.
|
|
||||||
|
|
||||||
Source data
|
|
||||||
===========
|
|
||||||
* Source data are served at ``/data/{mbtiles}/{z}/{x}/{y}.{format}``
|
|
||||||
|
|
||||||
* Format depends on the source file (usually ``png`` or ``pbf``)
|
|
||||||
|
|
||||||
* ``geojson`` is also available (useful for inspecting the tiles) in case the original format is ``pbf``
|
|
||||||
|
|
||||||
* TileJSON at ``/data/{mbtiles}.json``
|
|
||||||
|
|
||||||
TileJSON arrays
|
|
||||||
===============
|
|
||||||
Array of all TileJSONs is at ``/index.json`` (``/rendered.json``; ``/data.json``)
|
|
||||||
|
|
||||||
List of available fonts
|
|
||||||
=======================
|
|
||||||
Array of names of the available fonts is at ``/fonts.json``
|
|
||||||
|
|
||||||
Health check
|
|
||||||
============
|
|
||||||
Endpoint reporting health status is at ``/health`` and currently returns:
|
|
||||||
|
|
||||||
* ``503`` Starting - for a short period before everything is initialized
|
|
||||||
* ``200`` OK - when the server is running
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
.. TileServer GL documentation master file, created by
|
|
||||||
sphinx-quickstart on Thu Aug 4 23:48:49 2016.
|
|
||||||
You can adapt this file completely to your liking, but it should at least
|
|
||||||
contain the root `toctree` directive.
|
|
||||||
|
|
||||||
Welcome to TileServer GL's documentation!
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
Contents:
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
installation
|
|
||||||
usage
|
|
||||||
config
|
|
||||||
deployment
|
|
||||||
endpoints
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Indices and tables
|
|
||||||
==================
|
|
||||||
|
|
||||||
* :ref:`genindex`
|
|
||||||
* :ref:`modindex`
|
|
||||||
* :ref:`search`
|
|
||||||
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
============
|
|
||||||
Installation
|
|
||||||
============
|
|
||||||
|
|
||||||
Docker
|
|
||||||
======
|
|
||||||
|
|
||||||
When running docker image, no special installation is needed -- the docker will automatically download the image if not present.
|
|
||||||
|
|
||||||
Just run ``docker run --rm -it -v $(pwd):/data -p 8080:8080 maptiler/tileserver-gl``.
|
|
||||||
|
|
||||||
Additional options (see :doc:`/usage`) can be passed to the TileServer GL by appending them to the end of this command. You can, for example, do the following:
|
|
||||||
|
|
||||||
* ``docker run ... maptiler/tileserver-gl --mbtiles my-tiles.mbtiles`` -- explicitly specify which mbtiles to use (if you have more in the folder)
|
|
||||||
* ``docker run ... maptiler/tileserver-gl --verbose`` -- to see the default config created automatically
|
|
||||||
|
|
||||||
npm
|
|
||||||
===
|
|
||||||
|
|
||||||
Just run ``npm install -g tileserver-gl``.
|
|
||||||
|
|
||||||
|
|
||||||
Native dependencies
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
There are some native dependencies that you need to make sure are installed if you plan to run the TileServer GL natively without docker.
|
|
||||||
The precise package names you need to install may differ on various platforms.
|
|
||||||
|
|
||||||
These are required on Debian 11:
|
|
||||||
* ``libgles2-mesa``
|
|
||||||
* ``libegl1``
|
|
||||||
* ``xvfb``
|
|
||||||
* ``xauth``
|
|
||||||
* ``libopengl0``
|
|
||||||
* ``libcurl4``
|
|
||||||
* ``curl``
|
|
||||||
* ``libuv1-dev``
|
|
||||||
* ``libc6-dev``
|
|
||||||
* ``http://archive.ubuntu.com/ubuntu/pool/main/libj/libjpeg-turbo/libjpeg-turbo8_2.0.3-0ubuntu1_amd64.deb``
|
|
||||||
* ``http://archive.ubuntu.com/ubuntu/pool/main/i/icu/libicu66_66.1-2ubuntu2_amd64.deb``
|
|
||||||
|
|
||||||
These are required on Ubuntu 20.04:
|
|
||||||
* ``libcairo2-dev``
|
|
||||||
* ``libjpeg8-dev``
|
|
||||||
* ``libpango1.0-dev``
|
|
||||||
* ``libgif-dev``
|
|
||||||
* ``build-essential``
|
|
||||||
* ``g++``
|
|
||||||
* ``xvfb``
|
|
||||||
* ``libgles2-mesa-dev``
|
|
||||||
* ``libgbm-dev``
|
|
||||||
* ``libxxf86vm-dev``
|
|
||||||
|
|
||||||
``tileserver-gl-light`` on npm
|
|
||||||
==============================
|
|
||||||
|
|
||||||
Alternatively, you can use ``tileserver-gl-light`` package instead, which is pure javascript (does not have any native dependencies) and can run anywhere, but does not contain rasterization features.
|
|
||||||
|
|
||||||
|
|
||||||
From source
|
|
||||||
===========
|
|
||||||
|
|
||||||
Make sure you have Node v18 (nvm install 18) and run::
|
|
||||||
|
|
||||||
npm install
|
|
||||||
node .
|
|
||||||
|
|
||||||
|
|
||||||
On OSX
|
|
||||||
======
|
|
||||||
|
|
||||||
Make sure to have dependencies of canvas_ package installed::
|
|
||||||
|
|
||||||
brew install pkg-config cairo libpng jpeg giflib
|
|
||||||
|
|
||||||
|
|
||||||
.. _canvas: https://www.npmjs.com/package/canvas
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
=====
|
|
||||||
Usage
|
|
||||||
=====
|
|
||||||
|
|
||||||
Getting started
|
|
||||||
======
|
|
||||||
::
|
|
||||||
|
|
||||||
Usage: main.js tileserver-gl [mbtiles] [options]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
|
|
||||||
-h, --help output usage information
|
|
||||||
--mbtiles <file> MBTiles file (uses demo configuration);
|
|
||||||
ignored if the configuration file is also specified
|
|
||||||
-c, --config <file> Configuration file [config.json]
|
|
||||||
-b, --bind <address> Bind address
|
|
||||||
-p, --port <port> Port [8080]
|
|
||||||
-C|--no-cors Disable Cross-origin resource sharing headers
|
|
||||||
-u|--public_url <url> Enable exposing the server on subpaths, not necessarily the root of the domain
|
|
||||||
-V, --verbose More verbose output
|
|
||||||
-s, --silent Less verbose output
|
|
||||||
-v, --version Version info
|
|
||||||
|
|
||||||
|
|
||||||
Default preview style and configuration
|
|
||||||
======
|
|
||||||
|
|
||||||
- If no configuration file is specified, a default preview style (compatible with openmaptiles) is used.
|
|
||||||
- If no mbtiles file is specified (and is not found in the current working directory), a sample file is downloaded (showing the Zurich area)
|
|
||||||
|
|
||||||
Reloading the configuration
|
|
||||||
======
|
|
||||||
|
|
||||||
It is possible to reload the configuration file without restarting the whole process by sending a SIGHUP signal to the node process.
|
|
||||||
|
|
||||||
- The `docker kill -s HUP tileserver-gl` command can be used when running the tileserver-gl docker container.
|
|
||||||
- The `docker-compose kill -s HUP tileserver-gl-service-name` can be used when tileserver-gl is run as a docker-compose service.
|
|
||||||
|
|
||||||
Docker and `--port`
|
|
||||||
======
|
|
||||||
|
|
||||||
When running tileserver-gl in a Docker container, using the `--port` option would make the container incorrectly seem unhealthy.
|
|
||||||
Instead, it is advised to use Docker's port mapping and map the default port 8080 to the desired external port.
|
|
||||||
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 250 KiB |
|
After Width: | Height: | Size: 530 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -17,101 +17,91 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body{
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
color: #212121;
|
color: #212121;
|
||||||
font-family: 'OpenSans', sans-serif, Arial;
|
font-family:'OpenSans', sans-serif, Arial;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin: 0;
|
margin:0;
|
||||||
background-repeat: no-repeat !important;
|
|
||||||
background-size: contain !important;
|
|
||||||
background-image: url(/images/header-map-1280px.png);
|
|
||||||
}
|
}
|
||||||
a {
|
a{
|
||||||
color: #499dce;
|
color: #499DCE;
|
||||||
transition: color 0.2s;
|
transition: color .2s;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover{
|
||||||
color: #395d73;
|
color: #395D73;
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
text-align: center;
|
text-align:center;
|
||||||
margin: 90px 0 0 0;
|
margin:90px 0 0 0;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
.title.light:after {
|
section{
|
||||||
content: 'light';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
bottom: -5px;
|
|
||||||
color: #499dce;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
margin: 15px auto;
|
margin: 15px auto;
|
||||||
width: 930px;
|
width: 930px;
|
||||||
padding: 30px 0;
|
padding: 30px 0;
|
||||||
}
|
}
|
||||||
|
section p {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.title img {
|
.title img {
|
||||||
width: 320px;
|
width: 320px;
|
||||||
}
|
}
|
||||||
.subtitle {
|
.subtitle {
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
font-weight: normal;
|
font-weight:normal;
|
||||||
text-align: center;
|
text-align:center;
|
||||||
margin: 10px 0 95px 0;
|
margin:10px 0 95px 0;
|
||||||
}
|
}
|
||||||
.box-header {
|
.box-header {
|
||||||
text-align: left;
|
text-align:left;
|
||||||
text-transform: uppercase;
|
text-transform:uppercase;
|
||||||
border: 1px solid #ededed;
|
border:1px solid #ededed;
|
||||||
margin: 25px 0 0 0;
|
margin:25px 0 0 0;
|
||||||
padding: 12px 30px;
|
padding:12px 30px;
|
||||||
font-size: 20px;
|
font-size:20px;
|
||||||
background: #fff;
|
background:#fff;
|
||||||
}
|
}
|
||||||
.item {
|
.item{
|
||||||
background: #fff;
|
background:#fff;
|
||||||
height: 191px;
|
height: 191px;
|
||||||
border: 1px solid #ededed;
|
border: 1px solid #ededed;
|
||||||
border-top: none;
|
border-top:none;
|
||||||
}
|
}
|
||||||
.item:nth-child(odd) {
|
.item:nth-child(odd) {
|
||||||
background-color: #fbfbfb;
|
background-color:#fbfbfb;
|
||||||
}
|
}
|
||||||
.item img {
|
.item img{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: block;
|
|
||||||
margin: 30px;
|
margin: 30px;
|
||||||
width: 128px;
|
width: 128px;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
.details {
|
.details {
|
||||||
float: left;
|
float:left;
|
||||||
|
width:180px;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
padding: 20px 30px 20px 188px;
|
padding: 20px 30px 20px 188px;
|
||||||
}
|
}
|
||||||
.details h3 {
|
.details h3 {
|
||||||
font-size: 18px;
|
font-size:18px;
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
}
|
}
|
||||||
.details p {
|
.details p {
|
||||||
padding: 0;
|
padding:0;
|
||||||
margin: 18px 0;
|
margin:18px 0;
|
||||||
}
|
}
|
||||||
.viewers {
|
.viewers {
|
||||||
float: right;
|
float:right;
|
||||||
text-align: center;
|
text-align:center;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
padding-right: 30px;
|
padding-right: 30px;
|
||||||
}
|
}
|
||||||
.btn {
|
.btn {
|
||||||
display: block;
|
display:block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
}
|
}
|
||||||
@@ -119,84 +109,96 @@ section {
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 4px;
|
border-radius:4px;
|
||||||
background-color: #499dce;
|
background-color: #499DCE;
|
||||||
background: linear-gradient(90deg, #5aaad8, #4a9ecf);
|
background: linear-gradient(90deg, #5aaad8, #4a9ecf);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.btn:first-child:hover {
|
.btn:first-child:hover{
|
||||||
background: #395d73;
|
background: #395D73;
|
||||||
}
|
}
|
||||||
footer {
|
footer{
|
||||||
width: 100%;
|
width:100%;
|
||||||
border-top: 1px solid #ededed;
|
border-top:1px solid #ededed;
|
||||||
text-align: center;
|
text-align:center;
|
||||||
color: #d3d3d3;
|
color:#d3d3d3;
|
||||||
padding-top: 10px;
|
padding-top:10px;
|
||||||
font-size: 12px;
|
font-size:12px;
|
||||||
}
|
}
|
||||||
footer img {
|
footer img{
|
||||||
width: 113px;
|
width: 252px;
|
||||||
height: 31px;
|
height: auto;
|
||||||
}
|
margin: 20px 0 10px 0;
|
||||||
footer .t {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
footer p {
|
footer p {
|
||||||
margin-top: 0;
|
margin-top:0;
|
||||||
}
|
}
|
||||||
footer a {
|
footer a {
|
||||||
color: #787878;
|
color: #787878;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.details h3,
|
|
||||||
.identifier {
|
/* body background image */
|
||||||
max-width: 550px;
|
body {
|
||||||
word-break: break-all;
|
background-repeat:no-repeat !important;
|
||||||
|
background-size: contain !important;
|
||||||
|
background-image: url(/images/header-map-640px.png);
|
||||||
|
}
|
||||||
|
@media only screen and (min-width: 641px) {
|
||||||
|
body {
|
||||||
|
background-image: url(/images/header-map-1280px.png);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media only screen and (min-width: 1281px) {
|
||||||
|
body {
|
||||||
|
background-image: url(/images/header-map-1600px.png);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media only screen and (min-width: 1601px) {
|
||||||
|
body {
|
||||||
|
background-image: url(/images/header-map-2560px.png);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 950px) {
|
@media (max-width: 950px) {
|
||||||
section {
|
section{
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 96%;
|
width: 96%;
|
||||||
padding: 2%;
|
padding: 2%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.title {
|
.title{
|
||||||
margin: 25px 0 0 0;
|
margin: 25px 0 0 0;
|
||||||
}
|
}
|
||||||
.title.light:after {
|
.title img{
|
||||||
font-size: 0.6em;
|
|
||||||
}
|
|
||||||
.title img {
|
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
.subtitle {
|
.subtitle{
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
margin: 0 0 35px 0;
|
margin: 0 0 35px 0;
|
||||||
}
|
}
|
||||||
.item {
|
.item{
|
||||||
height: 245px;
|
height: 245px;
|
||||||
}
|
}
|
||||||
.viewers {
|
.viewers{
|
||||||
float: left;
|
float: left;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-left: 30px;
|
margin-left: 30px;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
.viewers a {
|
.viewers a{
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
.btn {
|
.btn{
|
||||||
margin: 0 20px 0 0;
|
margin: 0 20px 0 0;
|
||||||
}
|
}
|
||||||
.btn:first-child {
|
.btn:first-child{
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>TileServerGL</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/index.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section>
|
||||||
|
<h1 class="title"><img src="/images/logo.png" alt="TileServerGL" /></h1>
|
||||||
|
<h2 class="subtitle">Vector and raster maps with GL styles</h2>
|
||||||
|
<p>
|
||||||
|
An open-source map server made for vector tiles, and able to render into raster tiles with MapBox GL Native engine on the server side.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
It provides maps to web and mobile applications. Supported are Mapbox GL JS, Android SDK, iOS SDK, Leaflet, OpenLayers, HighDPI/Retina, GIS via WMTS, etc.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
For details see the project repository on GitHub:<br/>
|
||||||
|
<a href="https://github.com/maptiler/tileserver-gl">https://github.com/maptiler/tileserver-gl</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
<a href="https://www.maptiler.com/" target="_blank"><img src="/images/maptiler-logo.svg" /></a>
|
||||||
|
<p>
|
||||||
|
<a href="https://github.com/maptiler/tileserver-gl" target="_blank">TileServer GL</a>
|
||||||
|
<a href="https://www.maptiler.com/" target="_blank">is an open-source project from MapTiler team.</a>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
'*.{js,ts}': 'npm run lint:js',
|
|
||||||
'*.{yml}': 'npm run lint:yml',
|
|
||||||
};
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "tileserver-gl",
|
|
||||||
"version": "4.5.1-skt1",
|
|
||||||
"description": "Map tile server for JSON GL styles - vector and server side generated raster tiles",
|
|
||||||
"main": "src/main.js",
|
|
||||||
"bin": "src/main.js",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"test": "mocha test/**.js --timeout 10000",
|
|
||||||
"lint:yml": "yamllint --schema=CORE_SCHEMA *.{yml,yaml}",
|
|
||||||
"lint:js": "npm run lint:eslint && npm run lint:prettier",
|
|
||||||
"lint:js:fix": "npm run lint:eslint:fix && npm run lint:prettier:fix",
|
|
||||||
"lint:eslint": "eslint \"{,!(node_modules|dist|static|public)/**/}*.{js,ts,cjs,mjs}\" --ignore-path .gitignore",
|
|
||||||
"lint:eslint:fix": "eslint --fix \"{,!(node_modules|dist|static|public)/**/}*.{js,ts,cjs,mjs}\" --ignore-path .gitignore",
|
|
||||||
"lint:prettier": "prettier --check \"{,!(node_modules|dist|static|public)/**/}*.{js,ts,cjs,mjs,json}\" --ignore-path .gitignore",
|
|
||||||
"lint:prettier:fix": "prettier --write \"{,!(node_modules|dist|static|public)/**/}*.{js,ts,cjs,mjs,json}\" --ignore-path .gitignore",
|
|
||||||
"docker": "docker build -f Dockerfile . && docker run --rm -i -p 8080:8080 $(docker build -q .)",
|
|
||||||
"prepare": "node -e \"if (process.env.NODE_ENV !== 'production'){ process.exit(1) } \" || husky install"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@mapbox/glyph-pbf-composite": "0.0.3",
|
|
||||||
"@mapbox/mbtiles": "file:/./mapbox-mbtiles-0.15.1.tgz",
|
|
||||||
"@mapbox/polyline": "^1.2.1",
|
|
||||||
"@mapbox/sphericalmercator": "1.2.0",
|
|
||||||
"@mapbox/vector-tile": "1.3.1",
|
|
||||||
"@maplibre/maplibre-gl-native": "5.2.0",
|
|
||||||
"@maplibre/maplibre-gl-style-spec": "18.0.0",
|
|
||||||
"advanced-pool": "0.3.3",
|
|
||||||
"canvas": "2.11.2",
|
|
||||||
"chokidar": "3.5.3",
|
|
||||||
"clone": "2.1.2",
|
|
||||||
"color": "4.2.3",
|
|
||||||
"commander": "11.0.0",
|
|
||||||
"cors": "2.8.5",
|
|
||||||
"express": "4.18.2",
|
|
||||||
"handlebars": "4.7.8",
|
|
||||||
"http-shutdown": "1.2.2",
|
|
||||||
"morgan": "1.10.0",
|
|
||||||
"nocache": "^4.0.0",
|
|
||||||
"pbf": "3.2.1",
|
|
||||||
"proj4": "2.9.0",
|
|
||||||
"request": "2.88.2",
|
|
||||||
"sanitize-filename": "1.6.3",
|
|
||||||
"sharp": "0.32.6",
|
|
||||||
"tileserver-gl-styles": "2.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@commitlint/cli": "^17.7.2",
|
|
||||||
"@commitlint/config-conventional": "^17.7.0",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.61.0",
|
|
||||||
"@typescript-eslint/parser": "^5.62.0",
|
|
||||||
"chai": "4.3.10",
|
|
||||||
"eslint": "^8.50.0",
|
|
||||||
"eslint-config-prettier": "^9.0.0",
|
|
||||||
"eslint-plugin-jsdoc": "^46.8.2",
|
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
|
||||||
"eslint-plugin-security": "^1.7.1",
|
|
||||||
"husky": "^8.0.3",
|
|
||||||
"lint-staged": "^14.0.1",
|
|
||||||
"mocha": "^10.2.0",
|
|
||||||
"prettier": "^2.8.8",
|
|
||||||
"should": "^13.2.3",
|
|
||||||
"supertest": "^6.3.3",
|
|
||||||
"yaml-lint": "^1.7.0"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"maptiler",
|
|
||||||
"tileserver-gl",
|
|
||||||
"maplibre-gl",
|
|
||||||
"tileserver"
|
|
||||||
],
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.20.0 <19"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"url": "git+https://github.com/maptiler/tileserver-gl.git",
|
|
||||||
"type": "git"
|
|
||||||
},
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/maptiler/tileserver-gl/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/maptiler/tileserver-gl#readme"
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
$schema: 'http://json.schemastore.org/prettierrc',
|
|
||||||
semi: true,
|
|
||||||
arrowParens: 'always',
|
|
||||||
singleQuote: true,
|
|
||||||
trailingComma: 'all',
|
|
||||||
bracketSpacing: true,
|
|
||||||
htmlWhitespaceSensitivity: 'css',
|
|
||||||
insertPragma: false,
|
|
||||||
tabWidth: 2,
|
|
||||||
useTabs: false,
|
|
||||||
endOfLine: 'lf',
|
|
||||||
};
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
// @class TileLayer
|
|
||||||
|
|
||||||
L.TileLayer.mergeOptions({
|
|
||||||
// @option keepBuffer
|
|
||||||
// The amount of tiles outside the visible map area to be kept in the stitched
|
|
||||||
// `TileLayer`.
|
|
||||||
|
|
||||||
// @option dumpToCanvas: Boolean = true
|
|
||||||
// Whether to dump loaded tiles to a `<canvas>` to prevent some rendering
|
|
||||||
// artifacts. (Disabled by default in IE)
|
|
||||||
dumpToCanvas: L.Browser.canvas && !L.Browser.ie,
|
|
||||||
});
|
|
||||||
|
|
||||||
L.TileLayer.include({
|
|
||||||
_onUpdateLevel: function(z, zoom) {
|
|
||||||
if (this.options.dumpToCanvas) {
|
|
||||||
this._levels[z].canvas.style.zIndex =
|
|
||||||
this.options.maxZoom - Math.abs(zoom - z);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onRemoveLevel: function(z) {
|
|
||||||
if (this.options.dumpToCanvas) {
|
|
||||||
L.DomUtil.remove(this._levels[z].canvas);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onCreateLevel: function(level) {
|
|
||||||
if (this.options.dumpToCanvas) {
|
|
||||||
level.canvas = L.DomUtil.create(
|
|
||||||
"canvas",
|
|
||||||
"leaflet-tile-container leaflet-zoom-animated",
|
|
||||||
this._container
|
|
||||||
);
|
|
||||||
level.ctx = level.canvas.getContext("2d");
|
|
||||||
this._resetCanvasSize(level);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_removeTile: function(key) {
|
|
||||||
if (this.options.dumpToCanvas) {
|
|
||||||
var tile = this._tiles[key];
|
|
||||||
var level = this._levels[tile.coords.z];
|
|
||||||
var tileSize = this.getTileSize();
|
|
||||||
|
|
||||||
if (level) {
|
|
||||||
// Where in the canvas should this tile go?
|
|
||||||
var offset = L.point(tile.coords.x, tile.coords.y)
|
|
||||||
.subtract(level.canvasRange.min)
|
|
||||||
.scaleBy(this.getTileSize());
|
|
||||||
|
|
||||||
level.ctx.clearRect(offset.x, offset.y, tileSize.x, tileSize.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
L.GridLayer.prototype._removeTile.call(this, key);
|
|
||||||
},
|
|
||||||
|
|
||||||
_resetCanvasSize: function(level) {
|
|
||||||
var buff = this.options.keepBuffer,
|
|
||||||
pixelBounds = this._getTiledPixelBounds(this._map.getCenter()),
|
|
||||||
tileRange = this._pxBoundsToTileRange(pixelBounds),
|
|
||||||
tileSize = this.getTileSize();
|
|
||||||
|
|
||||||
tileRange.min = tileRange.min.subtract([buff, buff]); // This adds the no-prune buffer
|
|
||||||
tileRange.max = tileRange.max.add([buff + 1, buff + 1]);
|
|
||||||
|
|
||||||
var pixelRange = L.bounds(
|
|
||||||
tileRange.min.scaleBy(tileSize),
|
|
||||||
tileRange.max.add([1, 1]).scaleBy(tileSize) // This prevents an off-by-one when checking if tiles are inside
|
|
||||||
),
|
|
||||||
mustRepositionCanvas = false,
|
|
||||||
neededSize = pixelRange.max.subtract(pixelRange.min);
|
|
||||||
|
|
||||||
// Resize the canvas, if needed, and only to make it bigger.
|
|
||||||
if (
|
|
||||||
neededSize.x > level.canvas.width ||
|
|
||||||
neededSize.y > level.canvas.height
|
|
||||||
) {
|
|
||||||
// Resizing canvases erases the currently drawn content, I'm afraid.
|
|
||||||
// To keep it, dump the pixels to another canvas, then display it on
|
|
||||||
// top. This could be done with getImageData/putImageData, but that
|
|
||||||
// would break for tainted canvases (in non-CORS tilesets)
|
|
||||||
var oldSize = { x: level.canvas.width, y: level.canvas.height };
|
|
||||||
// console.info('Resizing canvas from ', oldSize, 'to ', neededSize);
|
|
||||||
|
|
||||||
var tmpCanvas = L.DomUtil.create("canvas");
|
|
||||||
tmpCanvas.style.width = (tmpCanvas.width = oldSize.x) + "px";
|
|
||||||
tmpCanvas.style.height = (tmpCanvas.height = oldSize.y) + "px";
|
|
||||||
tmpCanvas.getContext("2d").drawImage(level.canvas, 0, 0);
|
|
||||||
// var data = level.ctx.getImageData(0, 0, oldSize.x, oldSize.y);
|
|
||||||
|
|
||||||
level.canvas.style.width = (level.canvas.width = neededSize.x) + "px";
|
|
||||||
level.canvas.style.height = (level.canvas.height = neededSize.y) + "px";
|
|
||||||
level.ctx.drawImage(tmpCanvas, 0, 0);
|
|
||||||
// level.ctx.putImageData(data, 0, 0, 0, 0, oldSize.x, oldSize.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate the canvas contents if it's moved around
|
|
||||||
if (level.canvasRange) {
|
|
||||||
var offset = level.canvasRange.min
|
|
||||||
.subtract(tileRange.min)
|
|
||||||
.scaleBy(this.getTileSize());
|
|
||||||
|
|
||||||
// console.info('Offsetting by ', offset);
|
|
||||||
|
|
||||||
if (!L.Browser.safari) {
|
|
||||||
// By default, canvases copy things "on top of" existing pixels, but we want
|
|
||||||
// this to *replace* the existing pixels when doing a drawImage() call.
|
|
||||||
// This will also clear the sides, so no clearRect() calls are needed to make room
|
|
||||||
// for the new tiles.
|
|
||||||
level.ctx.globalCompositeOperation = "copy";
|
|
||||||
level.ctx.drawImage(level.canvas, offset.x, offset.y);
|
|
||||||
level.ctx.globalCompositeOperation = "source-over";
|
|
||||||
} else {
|
|
||||||
// Safari clears the canvas when copying from itself :-(
|
|
||||||
if (!this._tmpCanvas) {
|
|
||||||
var t = (this._tmpCanvas = L.DomUtil.create("canvas"));
|
|
||||||
t.width = level.canvas.width;
|
|
||||||
t.height = level.canvas.height;
|
|
||||||
this._tmpContext = t.getContext("2d");
|
|
||||||
}
|
|
||||||
this._tmpContext.clearRect(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
level.canvas.width,
|
|
||||||
level.canvas.height
|
|
||||||
);
|
|
||||||
this._tmpContext.drawImage(level.canvas, 0, 0);
|
|
||||||
level.ctx.clearRect(0, 0, level.canvas.width, level.canvas.height);
|
|
||||||
level.ctx.drawImage(this._tmpCanvas, offset.x, offset.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
mustRepositionCanvas = true; // Wait until new props are set
|
|
||||||
}
|
|
||||||
|
|
||||||
level.canvasRange = tileRange;
|
|
||||||
level.canvasPxRange = pixelRange;
|
|
||||||
level.canvasOrigin = pixelRange.min;
|
|
||||||
|
|
||||||
// console.log('Canvas tile range: ', level, tileRange.min, tileRange.max );
|
|
||||||
// console.log('Canvas pixel range: ', pixelRange.min, pixelRange.max );
|
|
||||||
// console.log('Level origin: ', level.origin );
|
|
||||||
|
|
||||||
if (mustRepositionCanvas) {
|
|
||||||
this._setCanvasZoomTransform(
|
|
||||||
level,
|
|
||||||
this._map.getCenter(),
|
|
||||||
this._map.getZoom()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/// set transform/position of canvas, in addition to the transform/position of the individual tile container
|
|
||||||
_setZoomTransform: function(level, center, zoom) {
|
|
||||||
L.GridLayer.prototype._setZoomTransform.call(this, level, center, zoom);
|
|
||||||
if (this.options.dumpToCanvas) {
|
|
||||||
this._setCanvasZoomTransform(level, center, zoom);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// This will get called twice:
|
|
||||||
// * From _setZoomTransform
|
|
||||||
// * When the canvas has shifted due to a new tile being loaded
|
|
||||||
_setCanvasZoomTransform: function(level, center, zoom) {
|
|
||||||
// console.log('_setCanvasZoomTransform', level, center, zoom);
|
|
||||||
if (!level.canvasOrigin) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var scale = this._map.getZoomScale(zoom, level.zoom),
|
|
||||||
translate = level.canvasOrigin
|
|
||||||
.multiplyBy(scale)
|
|
||||||
.subtract(this._map._getNewPixelOrigin(center, zoom))
|
|
||||||
.round();
|
|
||||||
|
|
||||||
if (L.Browser.any3d) {
|
|
||||||
L.DomUtil.setTransform(level.canvas, translate, scale);
|
|
||||||
} else {
|
|
||||||
L.DomUtil.setPosition(level.canvas, translate);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onOpaqueTile: function(tile) {
|
|
||||||
if (!this.options.dumpToCanvas) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Guard against an NS_ERROR_NOT_AVAILABLE (or similar) exception
|
|
||||||
// when a non-image-tile has been loaded (e.g. a WMS error).
|
|
||||||
// Checking for tile.el.complete is not enough, as it has been
|
|
||||||
// already marked as loaded and ready somehow.
|
|
||||||
try {
|
|
||||||
this.dumpPixels(tile.coords, tile.el);
|
|
||||||
} catch (ex) {
|
|
||||||
return this.fire("tileerror", {
|
|
||||||
error: "Could not copy tile pixels: " + ex,
|
|
||||||
tile: tile,
|
|
||||||
coods: tile.coords,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If dumping the pixels was successful, then hide the tile.
|
|
||||||
// Do not remove the tile itself, as it is needed to check if the whole
|
|
||||||
// level (and its canvas) should be removed (via level.el.children.length)
|
|
||||||
tile.el.style.display = "none";
|
|
||||||
},
|
|
||||||
|
|
||||||
// @section Extension methods
|
|
||||||
// @uninheritable
|
|
||||||
|
|
||||||
// @method dumpPixels(coords: Object, imageSource: CanvasImageSource): this
|
|
||||||
// Dumps pixels from the given `CanvasImageSource` into the layer, into
|
|
||||||
// the space for the tile represented by the `coords` tile coordinates (an object
|
|
||||||
// like `{x: Number, y: Number, z: Number}`; the image source must have the
|
|
||||||
// same size as the `tileSize` option for the layer. Has no effect if `dumpToCanvas`
|
|
||||||
// is `false`.
|
|
||||||
dumpPixels: function(coords, imageSource) {
|
|
||||||
var level = this._levels[coords.z],
|
|
||||||
tileSize = this.getTileSize();
|
|
||||||
|
|
||||||
if (!level.canvasRange || !this.options.dumpToCanvas) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the tile is inside the currently visible map bounds
|
|
||||||
// There is a possible race condition when tiles are loaded after they
|
|
||||||
// have been panned outside of the map.
|
|
||||||
if (!level.canvasRange.contains(coords)) {
|
|
||||||
this._resetCanvasSize(level);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Where in the canvas should this tile go?
|
|
||||||
var offset = L.point(coords.x, coords.y)
|
|
||||||
.subtract(level.canvasRange.min)
|
|
||||||
.scaleBy(this.getTileSize());
|
|
||||||
|
|
||||||
level.ctx.drawImage(imageSource, offset.x, offset.y, tileSize.x, tileSize.y);
|
|
||||||
|
|
||||||
// TODO: Clear the pixels of other levels' canvases where they overlap
|
|
||||||
// this newly dumped tile.
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
Before Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 696 B |
@@ -1,162 +0,0 @@
|
|||||||
(function(window) {
|
|
||||||
var HAS_HASHCHANGE = (function() {
|
|
||||||
var doc_mode = window.documentMode;
|
|
||||||
return ('onhashchange' in window) &&
|
|
||||||
(doc_mode === undefined || doc_mode > 7);
|
|
||||||
})();
|
|
||||||
|
|
||||||
L.Hash = function(map) {
|
|
||||||
this.onHashChange = L.Util.bind(this.onHashChange, this);
|
|
||||||
|
|
||||||
if (map) {
|
|
||||||
this.init(map);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
L.Hash.parseHash = function(hash) {
|
|
||||||
if(hash.indexOf('#') === 0) {
|
|
||||||
hash = hash.substr(1);
|
|
||||||
}
|
|
||||||
var args = hash.split("/");
|
|
||||||
if (args.length == 3) {
|
|
||||||
var zoom = parseInt(args[0], 10),
|
|
||||||
lat = parseFloat(args[1]),
|
|
||||||
lon = parseFloat(args[2]);
|
|
||||||
if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
center: new L.LatLng(lat, lon),
|
|
||||||
zoom: zoom
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
L.Hash.formatHash = function(map) {
|
|
||||||
var center = map.getCenter(),
|
|
||||||
zoom = map.getZoom(),
|
|
||||||
precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2));
|
|
||||||
|
|
||||||
return "#" + [zoom,
|
|
||||||
center.lat.toFixed(precision),
|
|
||||||
center.lng.toFixed(precision)
|
|
||||||
].join("/");
|
|
||||||
},
|
|
||||||
|
|
||||||
L.Hash.prototype = {
|
|
||||||
map: null,
|
|
||||||
lastHash: null,
|
|
||||||
|
|
||||||
parseHash: L.Hash.parseHash,
|
|
||||||
formatHash: L.Hash.formatHash,
|
|
||||||
|
|
||||||
init: function(map) {
|
|
||||||
this.map = map;
|
|
||||||
|
|
||||||
// reset the hash
|
|
||||||
this.lastHash = null;
|
|
||||||
this.onHashChange();
|
|
||||||
|
|
||||||
if (!this.isListening) {
|
|
||||||
this.startListening();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
removeFrom: function(map) {
|
|
||||||
if (this.changeTimeout) {
|
|
||||||
clearTimeout(this.changeTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isListening) {
|
|
||||||
this.stopListening();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.map = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
onMapMove: function() {
|
|
||||||
// bail if we're moving the map (updating from a hash),
|
|
||||||
// or if the map is not yet loaded
|
|
||||||
|
|
||||||
if (this.movingMap || !this.map._loaded) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hash = this.formatHash(this.map);
|
|
||||||
if (this.lastHash != hash) {
|
|
||||||
location.replace(hash);
|
|
||||||
this.lastHash = hash;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
movingMap: false,
|
|
||||||
update: function() {
|
|
||||||
var hash = location.hash;
|
|
||||||
if (hash === this.lastHash) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var parsed = this.parseHash(hash);
|
|
||||||
if (parsed) {
|
|
||||||
this.movingMap = true;
|
|
||||||
|
|
||||||
this.map.setView(parsed.center, parsed.zoom);
|
|
||||||
|
|
||||||
this.movingMap = false;
|
|
||||||
} else {
|
|
||||||
this.onMapMove(this.map);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// defer hash change updates every 100ms
|
|
||||||
changeDefer: 100,
|
|
||||||
changeTimeout: null,
|
|
||||||
onHashChange: function() {
|
|
||||||
// throttle calls to update() so that they only happen every
|
|
||||||
// `changeDefer` ms
|
|
||||||
if (!this.changeTimeout) {
|
|
||||||
var that = this;
|
|
||||||
this.changeTimeout = setTimeout(function() {
|
|
||||||
that.update();
|
|
||||||
that.changeTimeout = null;
|
|
||||||
}, this.changeDefer);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isListening: false,
|
|
||||||
hashChangeInterval: null,
|
|
||||||
startListening: function() {
|
|
||||||
this.map.on("moveend", this.onMapMove, this);
|
|
||||||
|
|
||||||
if (HAS_HASHCHANGE) {
|
|
||||||
L.DomEvent.addListener(window, "hashchange", this.onHashChange);
|
|
||||||
} else {
|
|
||||||
clearInterval(this.hashChangeInterval);
|
|
||||||
this.hashChangeInterval = setInterval(this.onHashChange, 50);
|
|
||||||
}
|
|
||||||
this.isListening = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
stopListening: function() {
|
|
||||||
this.map.off("moveend", this.onMapMove, this);
|
|
||||||
|
|
||||||
if (HAS_HASHCHANGE) {
|
|
||||||
L.DomEvent.removeListener(window, "hashchange", this.onHashChange);
|
|
||||||
} else {
|
|
||||||
clearInterval(this.hashChangeInterval);
|
|
||||||
}
|
|
||||||
this.isListening = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
L.hash = function(map) {
|
|
||||||
return new L.Hash(map);
|
|
||||||
};
|
|
||||||
L.Map.prototype.addHash = function() {
|
|
||||||
this._hash = L.hash(this);
|
|
||||||
};
|
|
||||||
L.Map.prototype.removeHash = function() {
|
|
||||||
this._hash.removeFrom();
|
|
||||||
};
|
|
||||||
})(window);
|
|
||||||
@@ -1,656 +0,0 @@
|
|||||||
/* required styles */
|
|
||||||
|
|
||||||
.leaflet-pane,
|
|
||||||
.leaflet-tile,
|
|
||||||
.leaflet-marker-icon,
|
|
||||||
.leaflet-marker-shadow,
|
|
||||||
.leaflet-tile-container,
|
|
||||||
.leaflet-pane > svg,
|
|
||||||
.leaflet-pane > canvas,
|
|
||||||
.leaflet-zoom-box,
|
|
||||||
.leaflet-image-layer,
|
|
||||||
.leaflet-layer {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
.leaflet-container {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.leaflet-tile,
|
|
||||||
.leaflet-marker-icon,
|
|
||||||
.leaflet-marker-shadow {
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-drag: none;
|
|
||||||
}
|
|
||||||
/* Prevents IE11 from highlighting tiles in blue */
|
|
||||||
.leaflet-tile::selection {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
|
|
||||||
.leaflet-safari .leaflet-tile {
|
|
||||||
image-rendering: -webkit-optimize-contrast;
|
|
||||||
}
|
|
||||||
/* hack that prevents hw layers "stretching" when loading new tiles */
|
|
||||||
.leaflet-safari .leaflet-tile-container {
|
|
||||||
width: 1600px;
|
|
||||||
height: 1600px;
|
|
||||||
-webkit-transform-origin: 0 0;
|
|
||||||
}
|
|
||||||
.leaflet-marker-icon,
|
|
||||||
.leaflet-marker-shadow {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
|
||||||
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
|
||||||
.leaflet-container .leaflet-overlay-pane svg {
|
|
||||||
max-width: none !important;
|
|
||||||
max-height: none !important;
|
|
||||||
}
|
|
||||||
.leaflet-container .leaflet-marker-pane img,
|
|
||||||
.leaflet-container .leaflet-shadow-pane img,
|
|
||||||
.leaflet-container .leaflet-tile-pane img,
|
|
||||||
.leaflet-container img.leaflet-image-layer,
|
|
||||||
.leaflet-container .leaflet-tile {
|
|
||||||
max-width: none !important;
|
|
||||||
max-height: none !important;
|
|
||||||
width: auto;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-container.leaflet-touch-zoom {
|
|
||||||
-ms-touch-action: pan-x pan-y;
|
|
||||||
touch-action: pan-x pan-y;
|
|
||||||
}
|
|
||||||
.leaflet-container.leaflet-touch-drag {
|
|
||||||
-ms-touch-action: pinch-zoom;
|
|
||||||
/* Fallback for FF which doesn't support pinch-zoom */
|
|
||||||
touch-action: none;
|
|
||||||
touch-action: pinch-zoom;
|
|
||||||
}
|
|
||||||
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
|
|
||||||
-ms-touch-action: none;
|
|
||||||
touch-action: none;
|
|
||||||
}
|
|
||||||
.leaflet-container {
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
}
|
|
||||||
.leaflet-container a {
|
|
||||||
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
|
|
||||||
}
|
|
||||||
.leaflet-tile {
|
|
||||||
filter: inherit;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
.leaflet-tile-loaded {
|
|
||||||
visibility: inherit;
|
|
||||||
}
|
|
||||||
.leaflet-zoom-box {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
z-index: 800;
|
|
||||||
}
|
|
||||||
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
|
||||||
.leaflet-overlay-pane svg {
|
|
||||||
-moz-user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-pane { z-index: 400; }
|
|
||||||
|
|
||||||
.leaflet-tile-pane { z-index: 200; }
|
|
||||||
.leaflet-overlay-pane { z-index: 400; }
|
|
||||||
.leaflet-shadow-pane { z-index: 500; }
|
|
||||||
.leaflet-marker-pane { z-index: 600; }
|
|
||||||
.leaflet-tooltip-pane { z-index: 650; }
|
|
||||||
.leaflet-popup-pane { z-index: 700; }
|
|
||||||
|
|
||||||
.leaflet-map-pane canvas { z-index: 100; }
|
|
||||||
.leaflet-map-pane svg { z-index: 200; }
|
|
||||||
|
|
||||||
.leaflet-vml-shape {
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
.lvml {
|
|
||||||
behavior: url(#default#VML);
|
|
||||||
display: inline-block;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* control positioning */
|
|
||||||
|
|
||||||
.leaflet-control {
|
|
||||||
position: relative;
|
|
||||||
z-index: 800;
|
|
||||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
.leaflet-top,
|
|
||||||
.leaflet-bottom {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1000;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.leaflet-top {
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
.leaflet-right {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
.leaflet-bottom {
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
.leaflet-left {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
.leaflet-control {
|
|
||||||
float: left;
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
.leaflet-right .leaflet-control {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.leaflet-top .leaflet-control {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.leaflet-bottom .leaflet-control {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.leaflet-left .leaflet-control {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
.leaflet-right .leaflet-control {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* zoom and fade animations */
|
|
||||||
|
|
||||||
.leaflet-fade-anim .leaflet-popup {
|
|
||||||
opacity: 0;
|
|
||||||
-webkit-transition: opacity 0.2s linear;
|
|
||||||
-moz-transition: opacity 0.2s linear;
|
|
||||||
transition: opacity 0.2s linear;
|
|
||||||
}
|
|
||||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.leaflet-zoom-animated {
|
|
||||||
-webkit-transform-origin: 0 0;
|
|
||||||
-ms-transform-origin: 0 0;
|
|
||||||
transform-origin: 0 0;
|
|
||||||
}
|
|
||||||
svg.leaflet-zoom-animated {
|
|
||||||
will-change: transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
|
||||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
|
||||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
|
||||||
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
|
||||||
}
|
|
||||||
.leaflet-zoom-anim .leaflet-tile,
|
|
||||||
.leaflet-pan-anim .leaflet-tile {
|
|
||||||
-webkit-transition: none;
|
|
||||||
-moz-transition: none;
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-zoom-anim .leaflet-zoom-hide {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* cursors */
|
|
||||||
|
|
||||||
.leaflet-interactive {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.leaflet-grab {
|
|
||||||
cursor: -webkit-grab;
|
|
||||||
cursor: -moz-grab;
|
|
||||||
cursor: grab;
|
|
||||||
}
|
|
||||||
.leaflet-crosshair,
|
|
||||||
.leaflet-crosshair .leaflet-interactive {
|
|
||||||
cursor: crosshair;
|
|
||||||
}
|
|
||||||
.leaflet-popup-pane,
|
|
||||||
.leaflet-control {
|
|
||||||
cursor: auto;
|
|
||||||
}
|
|
||||||
.leaflet-dragging .leaflet-grab,
|
|
||||||
.leaflet-dragging .leaflet-grab .leaflet-interactive,
|
|
||||||
.leaflet-dragging .leaflet-marker-draggable {
|
|
||||||
cursor: move;
|
|
||||||
cursor: -webkit-grabbing;
|
|
||||||
cursor: -moz-grabbing;
|
|
||||||
cursor: grabbing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* marker & overlays interactivity */
|
|
||||||
.leaflet-marker-icon,
|
|
||||||
.leaflet-marker-shadow,
|
|
||||||
.leaflet-image-layer,
|
|
||||||
.leaflet-pane > svg path,
|
|
||||||
.leaflet-tile-container {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-marker-icon.leaflet-interactive,
|
|
||||||
.leaflet-image-layer.leaflet-interactive,
|
|
||||||
.leaflet-pane > svg path.leaflet-interactive,
|
|
||||||
svg.leaflet-image-layer.leaflet-interactive path {
|
|
||||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* visual tweaks */
|
|
||||||
|
|
||||||
.leaflet-container {
|
|
||||||
background: #ddd;
|
|
||||||
outline-offset: 1px;
|
|
||||||
}
|
|
||||||
.leaflet-container a {
|
|
||||||
color: #0078A8;
|
|
||||||
}
|
|
||||||
.leaflet-zoom-box {
|
|
||||||
border: 2px dotted #38f;
|
|
||||||
background: rgba(255,255,255,0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* general typography */
|
|
||||||
.leaflet-container {
|
|
||||||
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
|
||||||
font-size: 12px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* general toolbar styles */
|
|
||||||
|
|
||||||
.leaflet-bar {
|
|
||||||
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.leaflet-bar a {
|
|
||||||
background-color: #fff;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
line-height: 26px;
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
.leaflet-bar a,
|
|
||||||
.leaflet-control-layers-toggle {
|
|
||||||
background-position: 50% 50%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.leaflet-bar a:hover,
|
|
||||||
.leaflet-bar a:focus {
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
}
|
|
||||||
.leaflet-bar a:first-child {
|
|
||||||
border-top-left-radius: 4px;
|
|
||||||
border-top-right-radius: 4px;
|
|
||||||
}
|
|
||||||
.leaflet-bar a:last-child {
|
|
||||||
border-bottom-left-radius: 4px;
|
|
||||||
border-bottom-right-radius: 4px;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
.leaflet-bar a.leaflet-disabled {
|
|
||||||
cursor: default;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
color: #bbb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-touch .leaflet-bar a {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
||||||
.leaflet-touch .leaflet-bar a:first-child {
|
|
||||||
border-top-left-radius: 2px;
|
|
||||||
border-top-right-radius: 2px;
|
|
||||||
}
|
|
||||||
.leaflet-touch .leaflet-bar a:last-child {
|
|
||||||
border-bottom-left-radius: 2px;
|
|
||||||
border-bottom-right-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* zoom control */
|
|
||||||
|
|
||||||
.leaflet-control-zoom-in,
|
|
||||||
.leaflet-control-zoom-out {
|
|
||||||
font: bold 18px 'Lucida Console', Monaco, monospace;
|
|
||||||
text-indent: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* layers control */
|
|
||||||
|
|
||||||
.leaflet-control-layers {
|
|
||||||
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers-toggle {
|
|
||||||
background-image: url(images/layers.png);
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
.leaflet-retina .leaflet-control-layers-toggle {
|
|
||||||
background-image: url(images/layers-2x.png);
|
|
||||||
background-size: 26px 26px;
|
|
||||||
}
|
|
||||||
.leaflet-touch .leaflet-control-layers-toggle {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers .leaflet-control-layers-list,
|
|
||||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers-expanded {
|
|
||||||
padding: 6px 10px 6px 6px;
|
|
||||||
color: #333;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers-scrollbar {
|
|
||||||
overflow-y: scroll;
|
|
||||||
overflow-x: hidden;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers-selector {
|
|
||||||
margin-top: 2px;
|
|
||||||
position: relative;
|
|
||||||
top: 1px;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers label {
|
|
||||||
display: block;
|
|
||||||
font-size: 13px;
|
|
||||||
font-size: 1.08333em;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers-separator {
|
|
||||||
height: 0;
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
margin: 5px -10px 5px -6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Default icon URLs */
|
|
||||||
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
|
|
||||||
background-image: url(images/marker-icon.png);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* attribution and scale controls */
|
|
||||||
|
|
||||||
.leaflet-container .leaflet-control-attribution {
|
|
||||||
background: #fff;
|
|
||||||
background: rgba(255, 255, 255, 0.8);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.leaflet-control-attribution,
|
|
||||||
.leaflet-control-scale-line {
|
|
||||||
padding: 0 5px;
|
|
||||||
color: #333;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
.leaflet-control-attribution a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.leaflet-control-attribution a:hover,
|
|
||||||
.leaflet-control-attribution a:focus {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.leaflet-attribution-flag {
|
|
||||||
display: inline !important;
|
|
||||||
vertical-align: baseline !important;
|
|
||||||
width: 1em;
|
|
||||||
height: 0.6669em;
|
|
||||||
}
|
|
||||||
.leaflet-left .leaflet-control-scale {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
.leaflet-bottom .leaflet-control-scale {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
.leaflet-control-scale-line {
|
|
||||||
border: 2px solid #777;
|
|
||||||
border-top: none;
|
|
||||||
line-height: 1.1;
|
|
||||||
padding: 2px 5px 1px;
|
|
||||||
white-space: nowrap;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background: rgba(255, 255, 255, 0.8);
|
|
||||||
text-shadow: 1px 1px #fff;
|
|
||||||
}
|
|
||||||
.leaflet-control-scale-line:not(:first-child) {
|
|
||||||
border-top: 2px solid #777;
|
|
||||||
border-bottom: none;
|
|
||||||
margin-top: -2px;
|
|
||||||
}
|
|
||||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
|
||||||
border-bottom: 2px solid #777;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-touch .leaflet-control-attribution,
|
|
||||||
.leaflet-touch .leaflet-control-layers,
|
|
||||||
.leaflet-touch .leaflet-bar {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
.leaflet-touch .leaflet-control-layers,
|
|
||||||
.leaflet-touch .leaflet-bar {
|
|
||||||
border: 2px solid rgba(0,0,0,0.2);
|
|
||||||
background-clip: padding-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* popup */
|
|
||||||
|
|
||||||
.leaflet-popup {
|
|
||||||
position: absolute;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.leaflet-popup-content-wrapper {
|
|
||||||
padding: 1px;
|
|
||||||
text-align: left;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
.leaflet-popup-content {
|
|
||||||
margin: 13px 24px 13px 20px;
|
|
||||||
line-height: 1.3;
|
|
||||||
font-size: 13px;
|
|
||||||
font-size: 1.08333em;
|
|
||||||
min-height: 1px;
|
|
||||||
}
|
|
||||||
.leaflet-popup-content p {
|
|
||||||
margin: 17px 0;
|
|
||||||
margin: 1.3em 0;
|
|
||||||
}
|
|
||||||
.leaflet-popup-tip-container {
|
|
||||||
width: 40px;
|
|
||||||
height: 20px;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
margin-top: -1px;
|
|
||||||
margin-left: -20px;
|
|
||||||
overflow: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.leaflet-popup-tip {
|
|
||||||
width: 17px;
|
|
||||||
height: 17px;
|
|
||||||
padding: 1px;
|
|
||||||
|
|
||||||
margin: -10px auto 0;
|
|
||||||
pointer-events: auto;
|
|
||||||
|
|
||||||
-webkit-transform: rotate(45deg);
|
|
||||||
-moz-transform: rotate(45deg);
|
|
||||||
-ms-transform: rotate(45deg);
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
.leaflet-popup-content-wrapper,
|
|
||||||
.leaflet-popup-tip {
|
|
||||||
background: white;
|
|
||||||
color: #333;
|
|
||||||
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
|
||||||
}
|
|
||||||
.leaflet-container a.leaflet-popup-close-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
border: none;
|
|
||||||
text-align: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
font: 16px/24px Tahoma, Verdana, sans-serif;
|
|
||||||
color: #757575;
|
|
||||||
text-decoration: none;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.leaflet-container a.leaflet-popup-close-button:hover,
|
|
||||||
.leaflet-container a.leaflet-popup-close-button:focus {
|
|
||||||
color: #585858;
|
|
||||||
}
|
|
||||||
.leaflet-popup-scrolled {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-oldie .leaflet-popup-content-wrapper {
|
|
||||||
-ms-zoom: 1;
|
|
||||||
}
|
|
||||||
.leaflet-oldie .leaflet-popup-tip {
|
|
||||||
width: 24px;
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
|
||||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-oldie .leaflet-control-zoom,
|
|
||||||
.leaflet-oldie .leaflet-control-layers,
|
|
||||||
.leaflet-oldie .leaflet-popup-content-wrapper,
|
|
||||||
.leaflet-oldie .leaflet-popup-tip {
|
|
||||||
border: 1px solid #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* div icon */
|
|
||||||
|
|
||||||
.leaflet-div-icon {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Tooltip */
|
|
||||||
/* Base styles for the element that has a tooltip */
|
|
||||||
.leaflet-tooltip {
|
|
||||||
position: absolute;
|
|
||||||
padding: 6px;
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #fff;
|
|
||||||
border-radius: 3px;
|
|
||||||
color: #222;
|
|
||||||
white-space: nowrap;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
pointer-events: none;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
|
||||||
}
|
|
||||||
.leaflet-tooltip.leaflet-interactive {
|
|
||||||
cursor: pointer;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-top:before,
|
|
||||||
.leaflet-tooltip-bottom:before,
|
|
||||||
.leaflet-tooltip-left:before,
|
|
||||||
.leaflet-tooltip-right:before {
|
|
||||||
position: absolute;
|
|
||||||
pointer-events: none;
|
|
||||||
border: 6px solid transparent;
|
|
||||||
background: transparent;
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Directions */
|
|
||||||
|
|
||||||
.leaflet-tooltip-bottom {
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-top {
|
|
||||||
margin-top: -6px;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-bottom:before,
|
|
||||||
.leaflet-tooltip-top:before {
|
|
||||||
left: 50%;
|
|
||||||
margin-left: -6px;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-top:before {
|
|
||||||
bottom: 0;
|
|
||||||
margin-bottom: -12px;
|
|
||||||
border-top-color: #fff;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-bottom:before {
|
|
||||||
top: 0;
|
|
||||||
margin-top: -12px;
|
|
||||||
margin-left: -6px;
|
|
||||||
border-bottom-color: #fff;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-left {
|
|
||||||
margin-left: -6px;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-right {
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-left:before,
|
|
||||||
.leaflet-tooltip-right:before {
|
|
||||||
top: 50%;
|
|
||||||
margin-top: -6px;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-left:before {
|
|
||||||
right: 0;
|
|
||||||
margin-right: -12px;
|
|
||||||
border-left-color: #fff;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-right:before {
|
|
||||||
left: 0;
|
|
||||||
margin-left: -12px;
|
|
||||||
border-right-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Printing */
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
/* Prevent printers from removing background-images of controls. */
|
|
||||||
.leaflet-control {
|
|
||||||
-webkit-print-color-adjust: exact;
|
|
||||||
print-color-adjust: exact;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
.maplibregl-inspect_popup {
|
|
||||||
color: #333;
|
|
||||||
display: table;
|
|
||||||
}
|
|
||||||
|
|
||||||
.maplibregl-inspect_feature:not(:last-child) {
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.maplibregl-inspect_layer:before {
|
|
||||||
content: '#';
|
|
||||||
}
|
|
||||||
|
|
||||||
.maplibregl-inspect_layer {
|
|
||||||
display: block;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.maplibregl-inspect_property {
|
|
||||||
display: table-row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.maplibregl-inspect_property-value {
|
|
||||||
display: table-cell;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.maplibregl-inspect_property-name {
|
|
||||||
display: table-cell;
|
|
||||||
padding-right: 10px;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.maplibregl-ctrl-inspect {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23333333' preserveAspectRatio='xMidYMid meet' viewBox='-10 -10 60 60'%3E%3Cg%3E%3Cpath d='m15 21.6q0-2 1.5-3.5t3.5-1.5 3.5 1.5 1.5 3.5-1.5 3.6-3.5 1.4-3.5-1.4-1.5-3.6z m18.4 11.1l-6.4-6.5q1.4-2.1 1.4-4.6 0-3.4-2.5-5.8t-5.9-2.4-5.9 2.4-2.5 5.8 2.5 5.9 5.9 2.5q2.4 0 4.6-1.4l7.4 7.4q-0.9 0.6-2 0.6h-20q-1.3 0-2.3-0.9t-1.1-2.3l0.1-26.8q0-1.3 1-2.3t2.3-0.9h13.4l10 10v19.3z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
|
|
||||||
.maplibregl-ctrl-map {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23333333' viewBox='-10 -10 60 60' preserveAspectRatio='xMidYMid meet'%3E%3Cg%3E%3Cpath d='m25 31.640000000000004v-19.766666666666673l-10-3.511666666666663v19.766666666666666z m9.140000000000008-26.640000000000004q0.8599999999999923 0 0.8599999999999923 0.8600000000000003v25.156666666666666q0 0.625-0.625 0.783333333333335l-9.375 3.1999999999999993-10-3.5133333333333354-8.906666666666668 3.4383333333333326-0.2333333333333334 0.07833333333333314q-0.8616666666666664 0-0.8616666666666664-0.8599999999999994v-25.156666666666663q0-0.625 0.6233333333333331-0.7833333333333332l9.378333333333334-3.198333333333334 10 3.5133333333333336 8.905000000000001-3.4383333333333344z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>{{name}} - TileServer GL</title>
|
|
||||||
{{#is_vector}}
|
|
||||||
<link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl.css{{&key_query}}" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl-inspect.css{{&key_query}}" />
|
|
||||||
<script src="{{public_url}}maplibre-gl.js{{&key_query}}"></script>
|
|
||||||
<script src="{{public_url}}maplibre-gl-inspect.min.js{{&key_query}}"></script>
|
|
||||||
<style>
|
|
||||||
body {background:#fff;color:#333;font-family:Arial, sans-serif;}
|
|
||||||
#map {position:absolute;top:0;left:0;right:250px;bottom:0;}
|
|
||||||
h1 {position:absolute;top:5px;right:0;width:240px;margin:0;line-height:20px;font-size:20px;}
|
|
||||||
#layerList {position:absolute;top:35px;right:0;bottom:0;width:240px;overflow:auto;}
|
|
||||||
#layerList div div {width:15px;height:15px;display:inline-block;}
|
|
||||||
</style>
|
|
||||||
{{/is_vector}}
|
|
||||||
{{^is_vector}}
|
|
||||||
<link rel="stylesheet" type="text/css" href="{{public_url}}leaflet.css{{&key_query}}" />
|
|
||||||
<script src="{{public_url}}leaflet.js{{&key_query}}"></script>
|
|
||||||
<script src="{{public_url}}leaflet-hash.js{{&key_query}}"></script>
|
|
||||||
<script src="{{public_url}}L.TileLayer.NoGap.js{{&key_query}}"></script>
|
|
||||||
<style>
|
|
||||||
body { margin:0; padding:0; }
|
|
||||||
#map { position:absolute; top:0; bottom:0; width:100%; }
|
|
||||||
.leaflet-control-layers-toggle {
|
|
||||||
background-image: url({{public_url}}images/layers.png{{&key_query}});
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
.leaflet-retina .leaflet-control-layers-toggle {
|
|
||||||
background-image: url({{public_url}}images/layers-2x.png{{&key_query}});
|
|
||||||
background-size: 26px 26px;
|
|
||||||
}
|
|
||||||
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
|
|
||||||
background-image: url({{public_url}}images/marker-icon.png{{&key_query}});
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{{/is_vector}}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{{#is_vector}}
|
|
||||||
<h1>{{name}}</h1>
|
|
||||||
<div id="map"></div>
|
|
||||||
<div id="layerList"></div>
|
|
||||||
<pre id="propertyList"></pre>
|
|
||||||
<script>
|
|
||||||
var keyMatch = location.search.match(/[\?\&]key=([^&]+)/i);
|
|
||||||
var keyParam = keyMatch ? '?key=' + keyMatch[1] : '';
|
|
||||||
|
|
||||||
var map = new maplibregl.Map({
|
|
||||||
container: 'map',
|
|
||||||
hash: true,
|
|
||||||
maplibreLogo: true,
|
|
||||||
maxPitch: 85,
|
|
||||||
style: {
|
|
||||||
version: 8,
|
|
||||||
sources: {
|
|
||||||
'vector_layer_': {
|
|
||||||
type: 'vector',
|
|
||||||
url: '{{public_url}}data/{{id}}.json' + keyParam
|
|
||||||
}
|
|
||||||
},
|
|
||||||
layers: []
|
|
||||||
}
|
|
||||||
});
|
|
||||||
map.addControl(new maplibregl.NavigationControl());
|
|
||||||
var inspect = new MaplibreInspect({
|
|
||||||
showInspectMap: true,
|
|
||||||
showInspectButton: false
|
|
||||||
});
|
|
||||||
map.addControl(inspect);
|
|
||||||
map.on('styledata', function() {
|
|
||||||
var layerList = document.getElementById('layerList');
|
|
||||||
layerList.innerHTML = '';
|
|
||||||
Object.keys(inspect.sources).forEach(function(sourceId) {
|
|
||||||
var layerIds = inspect.sources[sourceId];
|
|
||||||
layerIds.forEach(function(layerId) {
|
|
||||||
var item = document.createElement('div');
|
|
||||||
item.innerHTML = '<div style="' +
|
|
||||||
'background:' + inspect.assignLayerColor(layerId) + ';' +
|
|
||||||
'"></div> ' + layerId;
|
|
||||||
layerList.appendChild(item);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{{/is_vector}}
|
|
||||||
{{^is_vector}}
|
|
||||||
<h1 style="display:none;">{{name}}</h1>
|
|
||||||
<div id='map'></div>
|
|
||||||
<script>
|
|
||||||
var keyMatch = location.search.match(/[\?\&]key=([^&]+)/i);
|
|
||||||
var keyParam = keyMatch ? '?key=' + keyMatch[1] : '';
|
|
||||||
|
|
||||||
var map = L.map('map', { zoomControl: false });
|
|
||||||
new L.Control.Zoom({ position: 'topright' }).addTo(map);
|
|
||||||
|
|
||||||
var tile_urls = [], tile_attribution, tile_minzoom, tile_maxzoom;
|
|
||||||
var url = '{{public_url}}data/{{id}}.json' + keyParam;
|
|
||||||
var req = new XMLHttpRequest();
|
|
||||||
req.overrideMimeType("application/json");
|
|
||||||
req.open('GET', url, true);
|
|
||||||
req.onload = function() {
|
|
||||||
var jsonResponse = JSON.parse(req.responseText);
|
|
||||||
for (key in jsonResponse) {
|
|
||||||
var keyl = key.toLowerCase();
|
|
||||||
switch(keyl) {
|
|
||||||
case "tiles":
|
|
||||||
tile_urls = jsonResponse[key];
|
|
||||||
break;
|
|
||||||
case "attribution":
|
|
||||||
tile_attribution = jsonResponse[key];
|
|
||||||
break;
|
|
||||||
case "minzoom":
|
|
||||||
tile_minzoom = jsonResponse[key];
|
|
||||||
break;
|
|
||||||
case "maxzoom":
|
|
||||||
tile_maxzoom = jsonResponse[key];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (tile_url in tile_urls) {
|
|
||||||
L.tileLayer(tile_urls[tile_url], {
|
|
||||||
minZoom: tile_minzoom,
|
|
||||||
maxZoom: tile_maxzoom,
|
|
||||||
attribution: tile_attribution
|
|
||||||
}).addTo(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
map.eachLayer(function(layer) {
|
|
||||||
// do not add scale prefix even if retina display is detected
|
|
||||||
layer.scalePrefix = '.';
|
|
||||||
});
|
|
||||||
};
|
|
||||||
req.send(null);
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
new L.Hash(map);
|
|
||||||
}, 0);
|
|
||||||
</script>
|
|
||||||
{{/is_vector}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>TileServer GL - Server for vector and raster maps with GL styles</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="{{public_url}}index.css{{&key_query}}" />
|
|
||||||
<script>
|
|
||||||
function toggle_xyz(id) {
|
|
||||||
var el = document.getElementById(id);
|
|
||||||
var s = el.style;
|
|
||||||
s.display = s.display == 'none' ? 'inline-block' : 'none';
|
|
||||||
el.setSelectionRange(0, el.value.length);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<section>
|
|
||||||
<h1 class="title {{#if is_light}}light{{/if}}"><img src="{{public_url}}images/logo.png{{&key_query}}" alt="TileServer GL" /></h1>
|
|
||||||
<h2 class="subtitle">Vector {{#if is_light}}<s>and raster</s>{{else}}and raster{{/if}} maps with GL styles</h2>
|
|
||||||
{{#if styles}}
|
|
||||||
<h2 class="box-header">Styles</h2>
|
|
||||||
<div class="box">
|
|
||||||
{{#each styles}}
|
|
||||||
<div class="item">
|
|
||||||
{{#if thumbnail}}
|
|
||||||
<img src="{{public_url}}styles/{{@key}}/{{thumbnail}}{{&../key_query}}" alt="{{name}} preview" />
|
|
||||||
{{else}}
|
|
||||||
<img src="{{public_url}}images/placeholder.png{{&key_query}}" alt="{{name}} preview" />
|
|
||||||
{{/if}}
|
|
||||||
<div class="details">
|
|
||||||
<h3>{{name}}</h3>
|
|
||||||
<p class="identifier">identifier: {{@key}}</p>
|
|
||||||
<p class="services">
|
|
||||||
services:
|
|
||||||
{{#if serving_data}}
|
|
||||||
<a href="{{public_url}}styles/{{@key}}/style.json{{&../key_query}}">GL Style</a>
|
|
||||||
{{/if}}
|
|
||||||
{{#if serving_rendered}}
|
|
||||||
{{#if serving_data}}| {{/if}}<a href="{{public_url}}styles/{{@key}}.json{{&../key_query}}">TileJSON</a>
|
|
||||||
{{/if}}
|
|
||||||
{{#if serving_rendered}}
|
|
||||||
| <a href="{{public_url}}styles/{{@key}}/wmts.xml{{&../key_query}}">WMTS</a>
|
|
||||||
{{/if}}
|
|
||||||
{{#if xyz_link}}
|
|
||||||
| <a href="#" onclick="return toggle_xyz('xyz_style_{{@key}}');">XYZ</a>
|
|
||||||
<input id="xyz_style_{{@key}}" type="text" value="{{&xyz_link}}" style="display:none;" />
|
|
||||||
{{/if}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="viewers">
|
|
||||||
{{#if serving_data}}
|
|
||||||
{{#if serving_rendered}}
|
|
||||||
<a class="btn" href="{{public_url}}styles/{{@key}}/{{&../key_query}}{{viewer_hash}}">Viewer</a>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if serving_rendered}}
|
|
||||||
<a class="btn" href="{{public_url}}styles/{{@key}}/?{{&../key_query_part}}raster{{viewer_hash}}">Raster</a>
|
|
||||||
{{/if}}
|
|
||||||
{{#if serving_data}}
|
|
||||||
<a class="btn" href="{{public_url}}styles/{{@key}}/?{{&../key_query_part}}vector{{viewer_hash}}">Vector</a>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{#if data}}
|
|
||||||
<h2 class="box-header">Data</h2>
|
|
||||||
<div class="box">
|
|
||||||
{{#each data}}
|
|
||||||
<div class="item">
|
|
||||||
{{#if thumbnail}}
|
|
||||||
<img src="{{public_url}}data/{{@key}}/{{thumbnail}}{{&../key_query}}" alt="{{name}} preview" />
|
|
||||||
{{else}}
|
|
||||||
<img src="{{public_url}}images/placeholder.png{{&key_query}}" alt="{{name}} preview" />
|
|
||||||
{{/if}}
|
|
||||||
<div class="details">
|
|
||||||
<h3>{{name}}</h3>
|
|
||||||
<p class="identifier">identifier: {{@key}}{{#if formatted_filesize}} | size: {{formatted_filesize}}{{/if}} | type: {{#is_vector}}vector{{/is_vector}}{{^is_vector}}raster{{/is_vector}} data</p>
|
|
||||||
<p class="services">
|
|
||||||
services: <a href="{{public_url}}data/{{@key}}.json{{&../key_query}}">TileJSON</a>
|
|
||||||
{{#if wmts_link}}
|
|
||||||
| <a href="{{&wmts_link}}">WMTS</a>
|
|
||||||
{{/if}}
|
|
||||||
{{#if xyz_link}}
|
|
||||||
| <a href="#" onclick="return toggle_xyz('xyz_data_{{@key}}');">XYZ</a>
|
|
||||||
<input id="xyz_data_{{@key}}" type="text" value="{{&xyz_link}}" style="display:none;" />
|
|
||||||
{{/if}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="viewers">
|
|
||||||
{{#is_vector}}
|
|
||||||
<a class="btn" href="{{public_url}}data/{{@key}}/{{&../key_query}}{{viewer_hash}}">Inspect</a>
|
|
||||||
{{/is_vector}}
|
|
||||||
{{^is_vector}}
|
|
||||||
<a class="btn" href="{{public_url}}data/{{@key}}/{{&../key_query}}{{viewer_hash}}">View</a>
|
|
||||||
{{/is_vector}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
<a href="https://www.maptiler.com/" target="_blank"><img src="{{public_url}}images/maptiler-logo.svg{{&key_query}}" /></a>
|
|
||||||
<p>
|
|
||||||
<a href="https://github.com/maptiler/tileserver-gl" target="_blank">Powered by TileServer GL ({{server_version}})</a> – <a href="https://www.maptiler.com/" target="_blank">an open-source project from MapTiler.</a>
|
|
||||||
</p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>{{name}} - TileServer GL</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl.css{{&key_query}}" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl-inspect.css{{&key_query}}" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="{{public_url}}leaflet.css{{&key_query}}" />
|
|
||||||
<script src="{{public_url}}maplibre-gl.js{{&key_query}}"></script>
|
|
||||||
<script src="{{public_url}}maplibre-gl-inspect.min.js{{&key_query}}"></script>
|
|
||||||
<script src="{{public_url}}leaflet.js{{&key_query}}"></script>
|
|
||||||
<script src="{{public_url}}leaflet-hash.js{{&key_query}}"></script>
|
|
||||||
<script src="{{public_url}}L.TileLayer.NoGap.js{{&key_query}}"></script>
|
|
||||||
<style>
|
|
||||||
body { margin:0; padding:0; }
|
|
||||||
#map { position:absolute; top:0; bottom:0; width:100%; }
|
|
||||||
.leaflet-control-layers-toggle {
|
|
||||||
background-image: url({{public_url}}images/layers.png{{&key_query}});
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
.leaflet-retina .leaflet-control-layers-toggle {
|
|
||||||
background-image: url({{public_url}}images/layers-2x.png{{&key_query}});
|
|
||||||
background-size: 26px 26px;
|
|
||||||
}
|
|
||||||
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
|
|
||||||
background-image: url({{public_url}}images/marker-icon.png{{&key_query}});
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1 style="display:none;">{{name}}</h1>
|
|
||||||
<div id='map'></div>
|
|
||||||
<script>
|
|
||||||
function isWebglSupported() {
|
|
||||||
if (window.WebGLRenderingContext) {
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
try {
|
|
||||||
const context = canvas.getContext('webgl2') || canvas.getContext('webgl');
|
|
||||||
if (context && typeof context.getParameter == 'function') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// WebGL is supported, but disabled
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// WebGL not supported
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var q = (location.search || '').substr(1).split('&');
|
|
||||||
var preference =
|
|
||||||
q.indexOf('vector') >= 0 ? 'vector' :
|
|
||||||
(q.indexOf('raster') >= 0 ? 'raster' :
|
|
||||||
(isWebglSupported() ? 'vector' : 'raster'));
|
|
||||||
|
|
||||||
var keyMatch = location.search.match(/[\?\&]key=([^&]+)/i);
|
|
||||||
var keyParam = keyMatch ? '?key=' + keyMatch[1] : '';
|
|
||||||
|
|
||||||
if (preference == 'vector') {
|
|
||||||
maplibregl.setRTLTextPlugin('{{public_url}}mapbox-gl-rtl-text.js' + keyParam);
|
|
||||||
var map = new maplibregl.Map({
|
|
||||||
container: 'map',
|
|
||||||
style: '{{public_url}}styles/{{id}}/style.json' + keyParam,
|
|
||||||
hash: true,
|
|
||||||
maplibreLogo: true,
|
|
||||||
maxPitch: 85
|
|
||||||
});
|
|
||||||
map.addControl(new maplibregl.NavigationControl({
|
|
||||||
visualizePitch: true,
|
|
||||||
showZoom: true,
|
|
||||||
showCompass: true
|
|
||||||
}));
|
|
||||||
map.addControl(new MaplibreInspect({
|
|
||||||
showMapPopupOnHover: false,
|
|
||||||
showInspectMapPopupOnHover: false,
|
|
||||||
selectThreshold: 5
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
var map = L.map('map', { zoomControl: false });
|
|
||||||
new L.Control.Zoom({ position: 'topright' }).addTo(map);
|
|
||||||
|
|
||||||
var tile_urls = [], tile_attribution, tile_minzoom, tile_maxzoom;
|
|
||||||
var url = '{{public_url}}styles/{{id}}.json' + keyParam;
|
|
||||||
var req = new XMLHttpRequest();
|
|
||||||
req.overrideMimeType("application/json");
|
|
||||||
req.open('GET', url, true);
|
|
||||||
req.onload = function() {
|
|
||||||
var jsonResponse = JSON.parse(req.responseText);
|
|
||||||
for (key in jsonResponse) {
|
|
||||||
var keyl = key.toLowerCase();
|
|
||||||
switch(keyl) {
|
|
||||||
case "tiles":
|
|
||||||
tile_urls = jsonResponse[key];
|
|
||||||
break;
|
|
||||||
case "attribution":
|
|
||||||
tile_attribution = jsonResponse[key];
|
|
||||||
break;
|
|
||||||
case "minzoom":
|
|
||||||
tile_minzoom = jsonResponse[key];
|
|
||||||
break;
|
|
||||||
case "maxzoom":
|
|
||||||
tile_maxzoom = jsonResponse[key];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (tile_url in tile_urls) {
|
|
||||||
L.tileLayer(tile_urls[tile_url], {
|
|
||||||
minZoom: tile_minzoom,
|
|
||||||
maxZoom: tile_maxzoom,
|
|
||||||
attribution: tile_attribution
|
|
||||||
}).addTo(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
map.eachLayer(function(layer) {
|
|
||||||
// do not add scale prefix even if retina display is detected
|
|
||||||
layer.scalePrefix = '.';
|
|
||||||
});
|
|
||||||
};
|
|
||||||
req.send(null);
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
new L.Hash(map);
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,407 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" version="1.0.0">
|
|
||||||
<!-- Service Identification -->
|
|
||||||
<ows:ServiceIdentification>
|
|
||||||
<ows:Title>TileServer GL</ows:Title>
|
|
||||||
<ows:ServiceType>OGC WMTS</ows:ServiceType>
|
|
||||||
<ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>
|
|
||||||
</ows:ServiceIdentification>
|
|
||||||
<!-- Operations Metadata -->
|
|
||||||
<ows:OperationsMetadata>
|
|
||||||
<ows:Operation name="GetCapabilities">
|
|
||||||
<ows:DCP>
|
|
||||||
<ows:HTTP>
|
|
||||||
<ows:Get xlink:href="{{baseUrl}}wmts/{{id}}/">
|
|
||||||
<ows:Constraint name="GetEncoding">
|
|
||||||
<ows:AllowedValues>
|
|
||||||
<ows:Value>RESTful</ows:Value>
|
|
||||||
</ows:AllowedValues>
|
|
||||||
</ows:Constraint>
|
|
||||||
</ows:Get>
|
|
||||||
</ows:HTTP>
|
|
||||||
</ows:DCP>
|
|
||||||
</ows:Operation>
|
|
||||||
<ows:Operation name="GetTile">
|
|
||||||
<ows:DCP>
|
|
||||||
<ows:HTTP>
|
|
||||||
<ows:Get xlink:href="{{baseUrl}}styles/">
|
|
||||||
<ows:Constraint name="GetEncoding">
|
|
||||||
<ows:AllowedValues>
|
|
||||||
<ows:Value>RESTful</ows:Value>
|
|
||||||
</ows:AllowedValues>
|
|
||||||
</ows:Constraint>
|
|
||||||
</ows:Get>
|
|
||||||
</ows:HTTP>
|
|
||||||
</ows:DCP>
|
|
||||||
</ows:Operation>
|
|
||||||
</ows:OperationsMetadata>
|
|
||||||
<Contents>
|
|
||||||
<Layer>
|
|
||||||
<ows:Title>{{name}}</ows:Title>
|
|
||||||
<ows:Identifier>{{id}}</ows:Identifier>
|
|
||||||
<ows:WGS84BoundingBox crs="urn:ogc:def:crs:OGC:2:84">
|
|
||||||
<ows:LowerCorner>-180 -85.051128779807</ows:LowerCorner>
|
|
||||||
<ows:UpperCorner>180 85.051128779807</ows:UpperCorner>
|
|
||||||
</ows:WGS84BoundingBox>
|
|
||||||
<Style isDefault="true">
|
|
||||||
<ows:Identifier>default</ows:Identifier>
|
|
||||||
</Style>
|
|
||||||
<Format>image/png</Format>
|
|
||||||
<TileMatrixSetLink>
|
|
||||||
<TileMatrixSet>GoogleMapsCompatible</TileMatrixSet>
|
|
||||||
</TileMatrixSetLink>
|
|
||||||
<ResourceURL format="image/png" resourceType="tile" template="{{baseUrl}}styles/{{id}}/{TileMatrix}/{TileCol}/{TileRow}.png{{key_query}}"/>
|
|
||||||
</Layer><TileMatrixSet>
|
|
||||||
<ows:Title>GoogleMapsCompatible</ows:Title>
|
|
||||||
<ows:Abstract>GoogleMapsCompatible EPSG:3857</ows:Abstract>
|
|
||||||
<ows:Identifier>GoogleMapsCompatible</ows:Identifier>
|
|
||||||
<ows:SupportedCRS>urn:ogc:def:crs:EPSG::3857</ows:SupportedCRS>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>0</ows:Identifier>
|
|
||||||
<ScaleDenominator>559082264.02872</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>1</MatrixWidth>
|
|
||||||
<MatrixHeight>1</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>1</ows:Identifier>
|
|
||||||
<ScaleDenominator>279541132.01436</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>2</MatrixWidth>
|
|
||||||
<MatrixHeight>2</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>2</ows:Identifier>
|
|
||||||
<ScaleDenominator>139770566.00718</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>4</MatrixWidth>
|
|
||||||
<MatrixHeight>4</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>3</ows:Identifier>
|
|
||||||
<ScaleDenominator>69885283.00359</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>8</MatrixWidth>
|
|
||||||
<MatrixHeight>8</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>4</ows:Identifier>
|
|
||||||
<ScaleDenominator>34942641.501795</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>16</MatrixWidth>
|
|
||||||
<MatrixHeight>16</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>5</ows:Identifier>
|
|
||||||
<ScaleDenominator>17471320.750897</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>32</MatrixWidth>
|
|
||||||
<MatrixHeight>32</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>6</ows:Identifier>
|
|
||||||
<ScaleDenominator>8735660.3754487</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>64</MatrixWidth>
|
|
||||||
<MatrixHeight>64</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>7</ows:Identifier>
|
|
||||||
<ScaleDenominator>4367830.1877244</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>128</MatrixWidth>
|
|
||||||
<MatrixHeight>128</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>8</ows:Identifier>
|
|
||||||
<ScaleDenominator>2183915.0938622</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>256</MatrixWidth>
|
|
||||||
<MatrixHeight>256</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>9</ows:Identifier>
|
|
||||||
<ScaleDenominator>1091957.5469311</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>512</MatrixWidth>
|
|
||||||
<MatrixHeight>512</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>10</ows:Identifier>
|
|
||||||
<ScaleDenominator>545978.77346554</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>1024</MatrixWidth>
|
|
||||||
<MatrixHeight>1024</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>11</ows:Identifier>
|
|
||||||
<ScaleDenominator>272989.38673277</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>2048</MatrixWidth>
|
|
||||||
<MatrixHeight>2048</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>12</ows:Identifier>
|
|
||||||
<ScaleDenominator>136494.69336639</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>4096</MatrixWidth>
|
|
||||||
<MatrixHeight>4096</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>13</ows:Identifier>
|
|
||||||
<ScaleDenominator>68247.346683193</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>8192</MatrixWidth>
|
|
||||||
<MatrixHeight>8192</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>14</ows:Identifier>
|
|
||||||
<ScaleDenominator>34123.673341597</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>16384</MatrixWidth>
|
|
||||||
<MatrixHeight>16384</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>15</ows:Identifier>
|
|
||||||
<ScaleDenominator>17061.836670798</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>32768</MatrixWidth>
|
|
||||||
<MatrixHeight>32768</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>16</ows:Identifier>
|
|
||||||
<ScaleDenominator>8530.9183353991</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>65536</MatrixWidth>
|
|
||||||
<MatrixHeight>65536</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>17</ows:Identifier>
|
|
||||||
<ScaleDenominator>4265.4591676996</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>131072</MatrixWidth>
|
|
||||||
<MatrixHeight>131072</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>18</ows:Identifier>
|
|
||||||
<ScaleDenominator>2132.7295838498</ScaleDenominator>
|
|
||||||
<TopLeftCorner>-20037508.34 20037508.34</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>262144</MatrixWidth>
|
|
||||||
<MatrixHeight>262144</MatrixHeight>
|
|
||||||
</TileMatrix></TileMatrixSet><TileMatrixSet>
|
|
||||||
<ows:Title>WGS84</ows:Title>
|
|
||||||
<ows:Abstract>WGS84 EPSG:4326</ows:Abstract>
|
|
||||||
<ows:Identifier>WGS84</ows:Identifier>
|
|
||||||
<ows:SupportedCRS>urn:ogc:def:crs:EPSG::4326</ows:SupportedCRS>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>0</ows:Identifier>
|
|
||||||
<ScaleDenominator>279541132.01436</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>2</MatrixWidth>
|
|
||||||
<MatrixHeight>1</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>1</ows:Identifier>
|
|
||||||
<ScaleDenominator>139770566.00718</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>4</MatrixWidth>
|
|
||||||
<MatrixHeight>2</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>2</ows:Identifier>
|
|
||||||
<ScaleDenominator>69885283.00359</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>8</MatrixWidth>
|
|
||||||
<MatrixHeight>4</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>3</ows:Identifier>
|
|
||||||
<ScaleDenominator>34942641.501795</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>16</MatrixWidth>
|
|
||||||
<MatrixHeight>8</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>4</ows:Identifier>
|
|
||||||
<ScaleDenominator>17471320.750897</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>32</MatrixWidth>
|
|
||||||
<MatrixHeight>16</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>5</ows:Identifier>
|
|
||||||
<ScaleDenominator>8735660.3754487</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>64</MatrixWidth>
|
|
||||||
<MatrixHeight>32</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>6</ows:Identifier>
|
|
||||||
<ScaleDenominator>4367830.1877244</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>128</MatrixWidth>
|
|
||||||
<MatrixHeight>64</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>7</ows:Identifier>
|
|
||||||
<ScaleDenominator>2183915.0938622</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>256</MatrixWidth>
|
|
||||||
<MatrixHeight>128</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>8</ows:Identifier>
|
|
||||||
<ScaleDenominator>1091957.5469311</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>512</MatrixWidth>
|
|
||||||
<MatrixHeight>256</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>9</ows:Identifier>
|
|
||||||
<ScaleDenominator>545978.77346554</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>1024</MatrixWidth>
|
|
||||||
<MatrixHeight>512</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>10</ows:Identifier>
|
|
||||||
<ScaleDenominator>272989.38673277</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>2048</MatrixWidth>
|
|
||||||
<MatrixHeight>1024</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>11</ows:Identifier>
|
|
||||||
<ScaleDenominator>136494.69336639</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>4096</MatrixWidth>
|
|
||||||
<MatrixHeight>2048</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>12</ows:Identifier>
|
|
||||||
<ScaleDenominator>68247.346683193</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>8192</MatrixWidth>
|
|
||||||
<MatrixHeight>4096</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>13</ows:Identifier>
|
|
||||||
<ScaleDenominator>34123.673341597</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>16384</MatrixWidth>
|
|
||||||
<MatrixHeight>8192</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>14</ows:Identifier>
|
|
||||||
<ScaleDenominator>17061.836670798</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>32768</MatrixWidth>
|
|
||||||
<MatrixHeight>16384</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>15</ows:Identifier>
|
|
||||||
<ScaleDenominator>8530.9183353991</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>65536</MatrixWidth>
|
|
||||||
<MatrixHeight>32768</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>16</ows:Identifier>
|
|
||||||
<ScaleDenominator>4265.4591676996</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>131072</MatrixWidth>
|
|
||||||
<MatrixHeight>65536</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>17</ows:Identifier>
|
|
||||||
<ScaleDenominator>2132.7295838498</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>262144</MatrixWidth>
|
|
||||||
<MatrixHeight>131072</MatrixHeight>
|
|
||||||
</TileMatrix>
|
|
||||||
<TileMatrix>
|
|
||||||
<ows:Identifier>18</ows:Identifier>
|
|
||||||
<ScaleDenominator>1066.3647919249</ScaleDenominator>
|
|
||||||
<TopLeftCorner>90 -180</TopLeftCorner>
|
|
||||||
<TileWidth>256</TileWidth>
|
|
||||||
<TileHeight>256</TileHeight>
|
|
||||||
<MatrixWidth>524288</MatrixWidth>
|
|
||||||
<MatrixHeight>262144</MatrixHeight>
|
|
||||||
</TileMatrix></TileMatrixSet>
|
|
||||||
</Contents>
|
|
||||||
<ServiceMetadataURL xlink:href="{{baseUrl}}wmts/{{id}}/"/>
|
|
||||||
</Capabilities>
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This script creates `tileserver-gl-light` version
|
|
||||||
* (without native dependencies) and publishes
|
|
||||||
* `tileserver-gl` and `tileserver-gl-light` to npm.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* CREATE tileserver-gl-light */
|
|
||||||
|
|
||||||
// SYNC THE `light` FOLDER
|
|
||||||
|
|
||||||
import child_process from 'child_process';
|
|
||||||
child_process.execSync(
|
|
||||||
'rsync -av --exclude="light" --exclude=".git" --exclude="node_modules" --delete . light',
|
|
||||||
{
|
|
||||||
stdio: 'inherit',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// PATCH `package.json`
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = path.dirname(__filename);
|
|
||||||
const packageJson = JSON.parse(
|
|
||||||
fs.readFileSync(__dirname + '/package.json', 'utf8'),
|
|
||||||
);
|
|
||||||
|
|
||||||
packageJson.name += '-light';
|
|
||||||
packageJson.description =
|
|
||||||
'Map tile server for JSON GL styles - serving vector tiles';
|
|
||||||
delete packageJson.dependencies['canvas'];
|
|
||||||
delete packageJson.dependencies['@maplibre/maplibre-gl-native'];
|
|
||||||
delete packageJson.dependencies['sharp'];
|
|
||||||
|
|
||||||
delete packageJson.scripts['prepare'];
|
|
||||||
|
|
||||||
delete packageJson.optionalDependencies;
|
|
||||||
delete packageJson.devDependencies;
|
|
||||||
|
|
||||||
packageJson.engines.node = '>= 14.15.0';
|
|
||||||
|
|
||||||
const str = JSON.stringify(packageJson, undefined, 2);
|
|
||||||
fs.writeFileSync('light/package.json', str);
|
|
||||||
fs.renameSync('light/README_light.md', 'light/README.md');
|
|
||||||
fs.renameSync('light/Dockerfile_light', 'light/Dockerfile');
|
|
||||||
fs.renameSync('light/docker-entrypoint_light.sh', 'light/docker-entrypoint.sh');
|
|
||||||
|
|
||||||
// for Build tileserver-gl-light docker image, don't publish
|
|
||||||
if (process.argv.length > 2 && process.argv[2] == '--no-publish') {
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* PUBLISH */
|
|
||||||
|
|
||||||
// tileserver-gl
|
|
||||||
child_process.execSync('npm publish . --access public', {
|
|
||||||
stdio: 'inherit',
|
|
||||||
});
|
|
||||||
|
|
||||||
// tileserver-gl-light
|
|
||||||
child_process.execSync('npm publish ./light --access public', {
|
|
||||||
stdio: 'inherit',
|
|
||||||
});
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import * as http from 'http';
|
|
||||||
var options = {
|
|
||||||
timeout: 2000,
|
|
||||||
};
|
|
||||||
var url = 'http://localhost:8080/health';
|
|
||||||
var request = http.request(url, options, (res) => {
|
|
||||||
console.log(`STATUS: ${res.statusCode}`);
|
|
||||||
if (res.statusCode == 200) {
|
|
||||||
process.exit(0);
|
|
||||||
} else {
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
request.on('error', function (err) {
|
|
||||||
console.log('ERROR');
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
request.end();
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import fs from 'node:fs';
|
|
||||||
import path from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import request from 'request';
|
|
||||||
import { server } from './server.js';
|
|
||||||
|
|
||||||
import MBTiles from '@mapbox/mbtiles';
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = path.dirname(__filename);
|
|
||||||
const packageJson = JSON.parse(
|
|
||||||
fs.readFileSync(__dirname + '/../package.json', 'utf8'),
|
|
||||||
);
|
|
||||||
|
|
||||||
const args = process.argv;
|
|
||||||
if (args.length >= 3 && args[2][0] !== '-') {
|
|
||||||
args.splice(2, 0, '--mbtiles');
|
|
||||||
}
|
|
||||||
|
|
||||||
import { program } from 'commander';
|
|
||||||
program
|
|
||||||
.description('tileserver-gl startup options')
|
|
||||||
.usage('tileserver-gl [mbtiles] [options]')
|
|
||||||
.option(
|
|
||||||
'--mbtiles <file>',
|
|
||||||
'MBTiles file (uses demo configuration);\n' +
|
|
||||||
'\t ignored if the configuration file is also specified',
|
|
||||||
)
|
|
||||||
.option(
|
|
||||||
'-c, --config <file>',
|
|
||||||
'Configuration file [config.json]',
|
|
||||||
'config.json',
|
|
||||||
)
|
|
||||||
.option('-b, --bind <address>', 'Bind address')
|
|
||||||
.option('-p, --port <port>', 'Port [8080]', 8080, parseInt)
|
|
||||||
.option('-C|--no-cors', 'Disable Cross-origin resource sharing headers')
|
|
||||||
.option(
|
|
||||||
'-u|--public_url <url>',
|
|
||||||
'Enable exposing the server on subpaths, not necessarily the root of the domain',
|
|
||||||
)
|
|
||||||
.option('-V, --verbose', 'More verbose output')
|
|
||||||
.option('-s, --silent', 'Less verbose output')
|
|
||||||
.option('-l|--log_file <file>', 'output log file (defaults to standard out)')
|
|
||||||
.option(
|
|
||||||
'-f|--log_format <format>',
|
|
||||||
'define the log format: https://github.com/expressjs/morgan#morganformat-options',
|
|
||||||
)
|
|
||||||
.version(packageJson.version, '-v, --version');
|
|
||||||
program.parse(process.argv);
|
|
||||||
const opts = program.opts();
|
|
||||||
|
|
||||||
console.log(`Starting ${packageJson.name} v${packageJson.version}`);
|
|
||||||
|
|
||||||
const startServer = (configPath, config) => {
|
|
||||||
let publicUrl = opts.public_url;
|
|
||||||
if (publicUrl && publicUrl.lastIndexOf('/') !== publicUrl.length - 1) {
|
|
||||||
publicUrl += '/';
|
|
||||||
}
|
|
||||||
return server({
|
|
||||||
configPath: configPath,
|
|
||||||
config: config,
|
|
||||||
bind: opts.bind,
|
|
||||||
port: opts.port,
|
|
||||||
cors: opts.cors,
|
|
||||||
verbose: opts.verbose,
|
|
||||||
silent: opts.silent,
|
|
||||||
logFile: opts.log_file,
|
|
||||||
logFormat: opts.log_format,
|
|
||||||
publicUrl: publicUrl,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const startWithMBTiles = (mbtilesFile) => {
|
|
||||||
console.log(`[INFO] Automatically creating config file for ${mbtilesFile}`);
|
|
||||||
console.log(`[INFO] Only a basic preview style will be used.`);
|
|
||||||
console.log(
|
|
||||||
`[INFO] See documentation to learn how to create config.json file.`,
|
|
||||||
);
|
|
||||||
|
|
||||||
mbtilesFile = path.resolve(process.cwd(), mbtilesFile);
|
|
||||||
|
|
||||||
const mbtilesStats = fs.statSync(mbtilesFile);
|
|
||||||
if (!mbtilesStats.isFile() || mbtilesStats.size === 0) {
|
|
||||||
console.log(`ERROR: Not valid MBTiles file: ${mbtilesFile}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
const instance = new MBTiles(mbtilesFile + '?mode=ro', (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.log('ERROR: Unable to open MBTiles.');
|
|
||||||
console.log(`Make sure ${path.basename(mbtilesFile)} is valid MBTiles.`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.getInfo((err, info) => {
|
|
||||||
if (err || !info) {
|
|
||||||
console.log('ERROR: Metadata missing in the MBTiles.');
|
|
||||||
console.log(
|
|
||||||
`Make sure ${path.basename(mbtilesFile)} is valid MBTiles.`,
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
const bounds = info.bounds;
|
|
||||||
|
|
||||||
const styleDir = path.resolve(
|
|
||||||
__dirname,
|
|
||||||
'../node_modules/tileserver-gl-styles/',
|
|
||||||
);
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
options: {
|
|
||||||
paths: {
|
|
||||||
root: styleDir,
|
|
||||||
fonts: 'fonts',
|
|
||||||
styles: 'styles',
|
|
||||||
mbtiles: path.dirname(mbtilesFile),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
styles: {},
|
|
||||||
data: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
|
||||||
info.format === 'pbf' &&
|
|
||||||
info.name.toLowerCase().indexOf('openmaptiles') > -1
|
|
||||||
) {
|
|
||||||
config['data'][`v3`] = {
|
|
||||||
mbtiles: path.basename(mbtilesFile),
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = fs.readdirSync(path.resolve(styleDir, 'styles'));
|
|
||||||
for (const styleName of styles) {
|
|
||||||
const styleFileRel = styleName + '/style.json';
|
|
||||||
const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
|
|
||||||
if (fs.existsSync(styleFile)) {
|
|
||||||
config['styles'][styleName] = {
|
|
||||||
style: styleFileRel,
|
|
||||||
tilejson: {
|
|
||||||
bounds: bounds,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`WARN: MBTiles not in "openmaptiles" format. Serving raw data only...`,
|
|
||||||
);
|
|
||||||
config['data'][
|
|
||||||
(info.id || 'mbtiles')
|
|
||||||
.replace(/\//g, '_')
|
|
||||||
.replace(/:/g, '_')
|
|
||||||
.replace(/\?/g, '_')
|
|
||||||
] = {
|
|
||||||
mbtiles: path.basename(mbtilesFile),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.verbose) {
|
|
||||||
console.log(JSON.stringify(config, undefined, 2));
|
|
||||||
} else {
|
|
||||||
console.log('Run with --verbose to see the config file here.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return startServer(null, config);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.stat(path.resolve(opts.config), (err, stats) => {
|
|
||||||
if (err || !stats.isFile() || stats.size === 0) {
|
|
||||||
let mbtiles = opts.mbtiles;
|
|
||||||
if (!mbtiles) {
|
|
||||||
// try to find in the cwd
|
|
||||||
const files = fs.readdirSync(process.cwd());
|
|
||||||
for (const filename of files) {
|
|
||||||
if (filename.endsWith('.mbtiles')) {
|
|
||||||
const mbTilesStats = fs.statSync(filename);
|
|
||||||
if (mbTilesStats.isFile() && mbTilesStats.size > 0) {
|
|
||||||
mbtiles = filename;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mbtiles) {
|
|
||||||
console.log(`No MBTiles specified, using ${mbtiles}`);
|
|
||||||
return startWithMBTiles(mbtiles);
|
|
||||||
} else {
|
|
||||||
const url =
|
|
||||||
'https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
|
|
||||||
const filename = 'zurich_switzerland.mbtiles';
|
|
||||||
const stream = fs.createWriteStream(filename);
|
|
||||||
console.log(`No MBTiles found`);
|
|
||||||
console.log(`[DEMO] Downloading sample data (${filename}) from ${url}`);
|
|
||||||
stream.on('finish', () => startWithMBTiles(filename));
|
|
||||||
return request.get(url).pipe(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mbtiles) {
|
|
||||||
return startWithMBTiles(mbtiles);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(`Using specified config file from ${opts.config}`);
|
|
||||||
return startServer(opts.config, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import fs from 'node:fs';
|
|
||||||
import path from 'path';
|
|
||||||
import zlib from 'zlib';
|
|
||||||
|
|
||||||
import clone from 'clone';
|
|
||||||
import express from 'express';
|
|
||||||
import MBTiles from '@mapbox/mbtiles';
|
|
||||||
import Pbf from 'pbf';
|
|
||||||
import { VectorTile } from '@mapbox/vector-tile';
|
|
||||||
|
|
||||||
import { getTileUrls, fixTileJSONCenter } from './utils.js';
|
|
||||||
|
|
||||||
export const serve_data = {
|
|
||||||
init: (options, repo) => {
|
|
||||||
const app = express().disable('x-powered-by');
|
|
||||||
|
|
||||||
app.get(
|
|
||||||
'/:id/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)',
|
|
||||||
(req, res, next) => {
|
|
||||||
const item = repo[req.params.id];
|
|
||||||
if (!item) {
|
|
||||||
return res.sendStatus(404);
|
|
||||||
}
|
|
||||||
const tileJSONFormat = item.tileJSON.format;
|
|
||||||
const z = req.params.z | 0;
|
|
||||||
const x = req.params.x | 0;
|
|
||||||
const y = req.params.y | 0;
|
|
||||||
let format = req.params.format;
|
|
||||||
if (format === options.pbfAlias) {
|
|
||||||
format = 'pbf';
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
format !== tileJSONFormat &&
|
|
||||||
!(format === 'geojson' && tileJSONFormat === 'pbf')
|
|
||||||
) {
|
|
||||||
return res.status(404).send('Invalid format');
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
z < item.tileJSON.minzoom ||
|
|
||||||
0 ||
|
|
||||||
x < 0 ||
|
|
||||||
y < 0 ||
|
|
||||||
z > item.tileJSON.maxzoom ||
|
|
||||||
x >= Math.pow(2, z) ||
|
|
||||||
y >= Math.pow(2, z)
|
|
||||||
) {
|
|
||||||
return res.status(404).send('Out of bounds');
|
|
||||||
}
|
|
||||||
item.source.getTile(z, x, y, (err, data, headers) => {
|
|
||||||
let isGzipped;
|
|
||||||
if (err) {
|
|
||||||
if (/does not exist/.test(err.message)) {
|
|
||||||
return res.status(204).send();
|
|
||||||
} else {
|
|
||||||
return res
|
|
||||||
.status(500)
|
|
||||||
.header('Content-Type', 'text/plain')
|
|
||||||
.send(err.message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (data == null) {
|
|
||||||
return res.status(404).send('Not found');
|
|
||||||
} else {
|
|
||||||
if (tileJSONFormat === 'pbf') {
|
|
||||||
isGzipped =
|
|
||||||
data.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0;
|
|
||||||
if (options.dataDecoratorFunc) {
|
|
||||||
if (isGzipped) {
|
|
||||||
data = zlib.unzipSync(data);
|
|
||||||
isGzipped = false;
|
|
||||||
}
|
|
||||||
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (format === 'pbf') {
|
|
||||||
headers['Content-Type'] = 'application/x-protobuf';
|
|
||||||
} else if (format === 'geojson') {
|
|
||||||
headers['Content-Type'] = 'application/json';
|
|
||||||
|
|
||||||
if (isGzipped) {
|
|
||||||
data = zlib.unzipSync(data);
|
|
||||||
isGzipped = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tile = new VectorTile(new Pbf(data));
|
|
||||||
const geojson = {
|
|
||||||
type: 'FeatureCollection',
|
|
||||||
features: [],
|
|
||||||
};
|
|
||||||
for (const layerName in tile.layers) {
|
|
||||||
const layer = tile.layers[layerName];
|
|
||||||
for (let i = 0; i < layer.length; i++) {
|
|
||||||
const feature = layer.feature(i);
|
|
||||||
const featureGeoJSON = feature.toGeoJSON(x, y, z);
|
|
||||||
featureGeoJSON.properties.layer = layerName;
|
|
||||||
geojson.features.push(featureGeoJSON);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data = JSON.stringify(geojson);
|
|
||||||
}
|
|
||||||
delete headers['ETag']; // do not trust the tile ETag -- regenerate
|
|
||||||
headers['Content-Encoding'] = 'gzip';
|
|
||||||
res.set(headers);
|
|
||||||
|
|
||||||
if (!isGzipped) {
|
|
||||||
data = zlib.gzipSync(data);
|
|
||||||
isGzipped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
app.get('/:id/:t(\\d+)/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)', (req, res, next) => {
|
|
||||||
const item = repo[req.params.id];
|
|
||||||
if (!item) {
|
|
||||||
return res.sendStatus(404);
|
|
||||||
}
|
|
||||||
let tileJSONFormat = item.tileJSON.format;
|
|
||||||
const t = req.params.t | 0;
|
|
||||||
const z = req.params.z | 0;
|
|
||||||
const x = req.params.x | 0;
|
|
||||||
const y = req.params.y | 0;
|
|
||||||
let format = req.params.format;
|
|
||||||
if (format === options.pbfAlias) {
|
|
||||||
format = 'pbf';
|
|
||||||
}
|
|
||||||
tileJSONFormat = format;
|
|
||||||
// if (format !== tileJSONFormat &&
|
|
||||||
// !(format === 'geojson' && tileJSONFormat === 'pbf') && format !== 'png') {
|
|
||||||
// return res.status(404).send('Invalid format');
|
|
||||||
// }
|
|
||||||
if (z < item.tileJSON.minzoom || 0 || x < 0 || y < 0 ||
|
|
||||||
z > item.tileJSON.maxzoom ||
|
|
||||||
x >= Math.pow(2, z) || y >= Math.pow(2, z) ||
|
|
||||||
t < 0) {
|
|
||||||
return res.status(404).send('Out of bounds');
|
|
||||||
}
|
|
||||||
item.source.getTileT(t, z, x, y, (err, data, headers) => {
|
|
||||||
let isGzipped;
|
|
||||||
if (err) {
|
|
||||||
if (/does not exist/.test(err.message)) {
|
|
||||||
return res.status(204).send();
|
|
||||||
} else {
|
|
||||||
return res.status(500).send(err.message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (data == null) {
|
|
||||||
return res.status(404).send('Not found');
|
|
||||||
} else {
|
|
||||||
if (tileJSONFormat === 'pbf') {
|
|
||||||
isGzipped = data.slice(0, 2).indexOf(
|
|
||||||
Buffer.from([0x1f, 0x8b])) === 0;
|
|
||||||
if (options.dataDecoratorFunc) {
|
|
||||||
if (isGzipped) {
|
|
||||||
data = zlib.unzipSync(data);
|
|
||||||
isGzipped = false;
|
|
||||||
}
|
|
||||||
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (format === 'pbf') {
|
|
||||||
headers['Content-Type'] = 'application/x-protobuf';
|
|
||||||
} else if (format === 'png') {
|
|
||||||
headers['Content-Type'] = 'image/png';
|
|
||||||
} else if (format === 'geojson') {
|
|
||||||
headers['Content-Type'] = 'application/json';
|
|
||||||
|
|
||||||
if (isGzipped) {
|
|
||||||
data = zlib.unzipSync(data);
|
|
||||||
isGzipped = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tile = new VectorTile(new Pbf(data));
|
|
||||||
const geojson = {
|
|
||||||
"type": "FeatureCollection",
|
|
||||||
"features": []
|
|
||||||
};
|
|
||||||
for (let layerName in tile.layers) {
|
|
||||||
const layer = tile.layers[layerName];
|
|
||||||
for (let i = 0; i < layer.length; i++) {
|
|
||||||
const feature = layer.feature(i);
|
|
||||||
const featureGeoJSON = feature.toGeoJSON(x, y, z);
|
|
||||||
featureGeoJSON.properties.layer = layerName;
|
|
||||||
geojson.features.push(featureGeoJSON);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data = JSON.stringify(geojson);
|
|
||||||
}
|
|
||||||
delete headers['ETag']; // do not trust the tile ETag -- regenerate
|
|
||||||
headers['Content-Encoding'] = 'gzip';
|
|
||||||
res.set(headers);
|
|
||||||
|
|
||||||
if (!isGzipped) {
|
|
||||||
data = zlib.gzipSync(data);
|
|
||||||
isGzipped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/:id.json', (req, res, next) => {
|
|
||||||
const item = repo[req.params.id];
|
|
||||||
if (!item) {
|
|
||||||
return res.sendStatus(404);
|
|
||||||
}
|
|
||||||
const info = clone(item.tileJSON);
|
|
||||||
info.tiles = getTileUrls(
|
|
||||||
req,
|
|
||||||
info.tiles,
|
|
||||||
`data/${req.params.id}`,
|
|
||||||
info.format,
|
|
||||||
item.publicUrl,
|
|
||||||
{
|
|
||||||
pbf: options.pbfAlias,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return res.send(info);
|
|
||||||
});
|
|
||||||
|
|
||||||
return app;
|
|
||||||
},
|
|
||||||
add: (options, repo, params, id, publicUrl) => {
|
|
||||||
const mbtilesFile = path.resolve(options.paths.mbtiles, params.mbtiles);
|
|
||||||
let tileJSON = {
|
|
||||||
tiles: params.domains || options.domains,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mbtilesFileStats = fs.statSync(mbtilesFile);
|
|
||||||
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
|
|
||||||
throw Error(`Not valid MBTiles file: ${mbtilesFile}`);
|
|
||||||
}
|
|
||||||
let source;
|
|
||||||
const sourceInfoPromise = new Promise((resolve, reject) => {
|
|
||||||
source = new MBTiles(mbtilesFile + '?mode=ro', (err) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
source.getInfo((err, info) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tileJSON['name'] = id;
|
|
||||||
tileJSON['format'] = 'pbf';
|
|
||||||
|
|
||||||
Object.assign(tileJSON, info);
|
|
||||||
|
|
||||||
tileJSON['tilejson'] = '2.0.0';
|
|
||||||
delete tileJSON['filesize'];
|
|
||||||
delete tileJSON['mtime'];
|
|
||||||
delete tileJSON['scheme'];
|
|
||||||
|
|
||||||
Object.assign(tileJSON, params.tilejson || {});
|
|
||||||
fixTileJSONCenter(tileJSON);
|
|
||||||
|
|
||||||
if (options.dataDecoratorFunc) {
|
|
||||||
tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON);
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return sourceInfoPromise.then(() => {
|
|
||||||
repo[id] = {
|
|
||||||
tileJSON,
|
|
||||||
publicUrl,
|
|
||||||
source,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import express from 'express';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
import { getFontsPbf } from './utils.js';
|
|
||||||
|
|
||||||
export const serve_font = (options, allowedFonts) => {
|
|
||||||
const app = express().disable('x-powered-by');
|
|
||||||
|
|
||||||
const lastModified = new Date().toUTCString();
|
|
||||||
|
|
||||||
const fontPath = options.paths.fonts;
|
|
||||||
|
|
||||||
const existingFonts = {};
|
|
||||||
const fontListingPromise = new Promise((resolve, reject) => {
|
|
||||||
fs.readdir(options.paths.fonts, (err, files) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const file of files) {
|
|
||||||
fs.stat(path.join(fontPath, file), (err, stats) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
stats.isDirectory() &&
|
|
||||||
fs.existsSync(path.join(fontPath, file, '0-255.pbf'))
|
|
||||||
) {
|
|
||||||
existingFonts[path.basename(file)] = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/fonts/:fontstack/:range([\\d]+-[\\d]+).pbf', (req, res, next) => {
|
|
||||||
const fontstack = decodeURI(req.params.fontstack);
|
|
||||||
const range = req.params.range;
|
|
||||||
|
|
||||||
getFontsPbf(
|
|
||||||
options.serveAllFonts ? null : allowedFonts,
|
|
||||||
fontPath,
|
|
||||||
fontstack,
|
|
||||||
range,
|
|
||||||
existingFonts,
|
|
||||||
).then(
|
|
||||||
(concated) => {
|
|
||||||
res.header('Content-type', 'application/x-protobuf');
|
|
||||||
res.header('Last-Modified', lastModified);
|
|
||||||
return res.send(concated);
|
|
||||||
},
|
|
||||||
(err) => res.status(400).header('Content-Type', 'text/plain').send(err),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/fonts.json', (req, res, next) => {
|
|
||||||
res.header('Content-type', 'application/json');
|
|
||||||
return res.send(
|
|
||||||
Object.keys(options.serveAllFonts ? existingFonts : allowedFonts).sort(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return fontListingPromise.then(() => app);
|
|
||||||
};
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
export const serve_rendered = {
|
|
||||||
init: (options, repo) => {},
|
|
||||||
add: (options, repo, params, id, publicUrl, dataResolver) => {},
|
|
||||||
remove: (repo, id) => {},
|
|
||||||
};
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import path from 'path';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
|
|
||||||
import clone from 'clone';
|
|
||||||
import express from 'express';
|
|
||||||
import { validate } from '@maplibre/maplibre-gl-style-spec';
|
|
||||||
|
|
||||||
import { getPublicUrl } from './utils.js';
|
|
||||||
|
|
||||||
const httpTester = /^(http(s)?:)?\/\//;
|
|
||||||
|
|
||||||
const fixUrl = (req, url, publicUrl, opt_nokey) => {
|
|
||||||
if (!url || typeof url !== 'string' || url.indexOf('local://') !== 0) {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
const queryParams = [];
|
|
||||||
if (!opt_nokey && req.query.key) {
|
|
||||||
queryParams.unshift(`key=${encodeURIComponent(req.query.key)}`);
|
|
||||||
}
|
|
||||||
let query = '';
|
|
||||||
if (queryParams.length) {
|
|
||||||
query = `?${queryParams.join('&')}`;
|
|
||||||
}
|
|
||||||
return url.replace('local://', getPublicUrl(publicUrl, req)) + query;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const serve_style = {
|
|
||||||
init: (options, repo) => {
|
|
||||||
const app = express().disable('x-powered-by');
|
|
||||||
|
|
||||||
app.get('/:id/style.json', (req, res, next) => {
|
|
||||||
const item = repo[req.params.id];
|
|
||||||
if (!item) {
|
|
||||||
return res.sendStatus(404);
|
|
||||||
}
|
|
||||||
const styleJSON_ = clone(item.styleJSON);
|
|
||||||
for (const name of Object.keys(styleJSON_.sources)) {
|
|
||||||
const source = styleJSON_.sources[name];
|
|
||||||
source.url = fixUrl(req, source.url, item.publicUrl);
|
|
||||||
}
|
|
||||||
// mapbox-gl-js viewer cannot handle sprite urls with query
|
|
||||||
if (styleJSON_.sprite) {
|
|
||||||
styleJSON_.sprite = fixUrl(
|
|
||||||
req,
|
|
||||||
styleJSON_.sprite,
|
|
||||||
item.publicUrl,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (styleJSON_.glyphs) {
|
|
||||||
styleJSON_.glyphs = fixUrl(
|
|
||||||
req,
|
|
||||||
styleJSON_.glyphs,
|
|
||||||
item.publicUrl,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return res.send(styleJSON_);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/:id/sprite:scale(@[23]x)?.:format([\\w]+)', (req, res, next) => {
|
|
||||||
const item = repo[req.params.id];
|
|
||||||
if (!item || !item.spritePath) {
|
|
||||||
return res.sendStatus(404);
|
|
||||||
}
|
|
||||||
const scale = req.params.scale;
|
|
||||||
const format = req.params.format;
|
|
||||||
const filename = `${item.spritePath + (scale || '')}.${format}`;
|
|
||||||
return fs.readFile(filename, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
console.log('Sprite load error:', filename);
|
|
||||||
return res.sendStatus(404);
|
|
||||||
} else {
|
|
||||||
if (format === 'json') res.header('Content-type', 'application/json');
|
|
||||||
if (format === 'png') res.header('Content-type', 'image/png');
|
|
||||||
return res.send(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return app;
|
|
||||||
},
|
|
||||||
remove: (repo, id) => {
|
|
||||||
delete repo[id];
|
|
||||||
},
|
|
||||||
add: (options, repo, params, id, publicUrl, reportTiles, reportFont) => {
|
|
||||||
const styleFile = path.resolve(options.paths.styles, params.style);
|
|
||||||
|
|
||||||
let styleFileData;
|
|
||||||
try {
|
|
||||||
styleFileData = fs.readFileSync(styleFile);
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Error reading style file');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const validationErrors = validate(styleFileData);
|
|
||||||
if (validationErrors.length > 0) {
|
|
||||||
console.log(
|
|
||||||
`The file "${params.style}" is not valid a valid style file:`,
|
|
||||||
);
|
|
||||||
for (const err of validationErrors) {
|
|
||||||
console.log(`${err.line}: ${err.message}`);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const styleJSON = JSON.parse(styleFileData);
|
|
||||||
|
|
||||||
for (const name of Object.keys(styleJSON.sources)) {
|
|
||||||
const source = styleJSON.sources[name];
|
|
||||||
const url = source.url;
|
|
||||||
if (url && url.lastIndexOf('mbtiles:', 0) === 0) {
|
|
||||||
let mbtilesFile = url.substring('mbtiles://'.length);
|
|
||||||
const fromData =
|
|
||||||
mbtilesFile[0] === '{' && mbtilesFile[mbtilesFile.length - 1] === '}';
|
|
||||||
|
|
||||||
if (fromData) {
|
|
||||||
mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
|
|
||||||
const mapsTo = (params.mapping || {})[mbtilesFile];
|
|
||||||
if (mapsTo) {
|
|
||||||
mbtilesFile = mapsTo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const identifier = reportTiles(mbtilesFile, fromData);
|
|
||||||
if (!identifier) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
source.url = `local://data/${identifier}.json`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const obj of styleJSON.layers) {
|
|
||||||
if (obj['type'] === 'symbol') {
|
|
||||||
const fonts = (obj['layout'] || {})['text-font'];
|
|
||||||
if (fonts && fonts.length) {
|
|
||||||
fonts.forEach(reportFont);
|
|
||||||
} else {
|
|
||||||
reportFont('Open Sans Regular');
|
|
||||||
reportFont('Arial Unicode MS Regular');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let spritePath;
|
|
||||||
|
|
||||||
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
|
||||||
spritePath = path.join(
|
|
||||||
options.paths.sprites,
|
|
||||||
styleJSON.sprite
|
|
||||||
.replace('{style}', path.basename(styleFile, '.json'))
|
|
||||||
.replace(
|
|
||||||
'{styleJsonFolder}',
|
|
||||||
path.relative(options.paths.sprites, path.dirname(styleFile)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
styleJSON.sprite = `local://styles/${id}/sprite`;
|
|
||||||
}
|
|
||||||
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
|
|
||||||
styleJSON.glyphs = 'local://fonts/{fontstack}/{range}.pbf';
|
|
||||||
}
|
|
||||||
|
|
||||||
repo[id] = {
|
|
||||||
styleJSON,
|
|
||||||
spritePath,
|
|
||||||
publicUrl,
|
|
||||||
name: styleJSON.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,680 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import os from 'os';
|
|
||||||
process.env.UV_THREADPOOL_SIZE = Math.ceil(Math.max(4, os.cpus().length * 1.5));
|
|
||||||
|
|
||||||
import fs from 'node:fs';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
import chokidar from 'chokidar';
|
|
||||||
import clone from 'clone';
|
|
||||||
import cors from 'cors';
|
|
||||||
import enableShutdown from 'http-shutdown';
|
|
||||||
import express from 'express';
|
|
||||||
import handlebars from 'handlebars';
|
|
||||||
import SphericalMercator from '@mapbox/sphericalmercator';
|
|
||||||
const mercator = new SphericalMercator();
|
|
||||||
import morgan from 'morgan';
|
|
||||||
import { serve_data } from './serve_data.js';
|
|
||||||
import { serve_style } from './serve_style.js';
|
|
||||||
import { serve_font } from './serve_font.js';
|
|
||||||
import { getTileUrls, getPublicUrl } from './utils.js';
|
|
||||||
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import nocache from 'nocache';
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = path.dirname(__filename);
|
|
||||||
const packageJson = JSON.parse(
|
|
||||||
fs.readFileSync(__dirname + '/../package.json', 'utf8'),
|
|
||||||
);
|
|
||||||
|
|
||||||
const isLight = packageJson.name.slice(-6) === '-light';
|
|
||||||
const serve_rendered = (
|
|
||||||
await import(`${!isLight ? `./serve_rendered.js` : `./serve_light.js`}`)
|
|
||||||
).serve_rendered;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param opts
|
|
||||||
*/
|
|
||||||
function start(opts) {
|
|
||||||
console.log('Starting server');
|
|
||||||
|
|
||||||
const app = express().disable('x-powered-by');
|
|
||||||
const serving = {
|
|
||||||
styles: {},
|
|
||||||
rendered: {},
|
|
||||||
data: {},
|
|
||||||
fonts: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
app.enable('trust proxy');
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
|
||||||
const defaultLogFormat =
|
|
||||||
process.env.NODE_ENV === 'production' ? 'tiny' : 'dev';
|
|
||||||
const logFormat = opts.logFormat || defaultLogFormat;
|
|
||||||
app.use(
|
|
||||||
morgan(logFormat, {
|
|
||||||
stream: opts.logFile
|
|
||||||
? fs.createWriteStream(opts.logFile, { flags: 'a' })
|
|
||||||
: process.stdout,
|
|
||||||
skip: (req, res) =>
|
|
||||||
opts.silent && (res.statusCode === 200 || res.statusCode === 304),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = opts.config || null;
|
|
||||||
let configPath = null;
|
|
||||||
if (opts.configPath) {
|
|
||||||
configPath = path.resolve(opts.configPath);
|
|
||||||
try {
|
|
||||||
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
||||||
} catch (e) {
|
|
||||||
console.log('ERROR: Config file not found or invalid!');
|
|
||||||
console.log(' See README.md for instructions and sample data.');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!config) {
|
|
||||||
console.log('ERROR: No config file not specified!');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = config.options || {};
|
|
||||||
|
|
||||||
const cacheFlag = options.cache || false;
|
|
||||||
if (!cacheFlag) {
|
|
||||||
console.log('Cache disabled');
|
|
||||||
app.use(nocache());
|
|
||||||
}
|
|
||||||
|
|
||||||
const paths = options.paths || {};
|
|
||||||
options.paths = paths;
|
|
||||||
paths.root = path.resolve(
|
|
||||||
configPath ? path.dirname(configPath) : process.cwd(),
|
|
||||||
paths.root || '',
|
|
||||||
);
|
|
||||||
paths.styles = path.resolve(paths.root, paths.styles || '');
|
|
||||||
paths.fonts = path.resolve(paths.root, paths.fonts || '');
|
|
||||||
paths.sprites = path.resolve(paths.root, paths.sprites || '');
|
|
||||||
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
|
|
||||||
paths.icons = path.resolve(paths.root, paths.icons || '');
|
|
||||||
|
|
||||||
const startupPromises = [];
|
|
||||||
|
|
||||||
const checkPath = (type) => {
|
|
||||||
if (!fs.existsSync(paths[type])) {
|
|
||||||
console.error(
|
|
||||||
`The specified path for "${type}" does not exist (${paths[type]}).`,
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
checkPath('styles');
|
|
||||||
checkPath('fonts');
|
|
||||||
checkPath('sprites');
|
|
||||||
checkPath('mbtiles');
|
|
||||||
checkPath('icons');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively get all files within a directory.
|
|
||||||
* Inspired by https://stackoverflow.com/a/45130990/10133863
|
|
||||||
* @param {string} directory Absolute path to a directory to get files from.
|
|
||||||
*/
|
|
||||||
const getFiles = async (directory) => {
|
|
||||||
// Fetch all entries of the directory and attach type information
|
|
||||||
const dirEntries = await fs.promises.readdir(directory, {
|
|
||||||
withFileTypes: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Iterate through entries and return the relative file-path to the icon directory if it is not a directory
|
|
||||||
// otherwise initiate a recursive call
|
|
||||||
const files = await Promise.all(
|
|
||||||
dirEntries.map((dirEntry) => {
|
|
||||||
const entryPath = path.resolve(directory, dirEntry.name);
|
|
||||||
return dirEntry.isDirectory()
|
|
||||||
? getFiles(entryPath)
|
|
||||||
: entryPath.replace(paths.icons + path.sep, '');
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Flatten the list of files to a single array
|
|
||||||
return files.flat();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load all available icons into a settings object
|
|
||||||
startupPromises.push(
|
|
||||||
new Promise((resolve) => {
|
|
||||||
getFiles(paths.icons).then((files) => {
|
|
||||||
paths.availableIcons = files;
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (options.dataDecorator) {
|
|
||||||
try {
|
|
||||||
options.dataDecoratorFunc = require(path.resolve(
|
|
||||||
paths.root,
|
|
||||||
options.dataDecorator,
|
|
||||||
));
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = clone(config.data || {});
|
|
||||||
|
|
||||||
if (opts.cors) {
|
|
||||||
app.use(cors());
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use('/data/', serve_data.init(options, serving.data));
|
|
||||||
app.use('/styles/', serve_style.init(options, serving.styles));
|
|
||||||
if (!isLight) {
|
|
||||||
startupPromises.push(
|
|
||||||
serve_rendered.init(options, serving.rendered).then((sub) => {
|
|
||||||
app.use('/styles/', sub);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const addStyle = (id, item, allowMoreData, reportFonts) => {
|
|
||||||
let success = true;
|
|
||||||
if (item.serve_data !== false) {
|
|
||||||
success = serve_style.add(
|
|
||||||
options,
|
|
||||||
serving.styles,
|
|
||||||
item,
|
|
||||||
id,
|
|
||||||
opts.publicUrl,
|
|
||||||
(mbtiles, fromData) => {
|
|
||||||
let dataItemId;
|
|
||||||
for (const id of Object.keys(serving.data)) {
|
|
||||||
if (fromData) {
|
|
||||||
if (id === mbtiles) {
|
|
||||||
dataItemId = id;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (serving.data[id].mbtiles === mbtiles) {
|
|
||||||
dataItemId = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dataItemId) {
|
|
||||||
// mbtiles exist in the data config
|
|
||||||
return dataItemId;
|
|
||||||
} else {
|
|
||||||
if (fromData || !allowMoreData) {
|
|
||||||
console.log(
|
|
||||||
`ERROR: style "${item.style}" using unknown mbtiles "${mbtiles}"! Skipping...`,
|
|
||||||
);
|
|
||||||
return undefined;
|
|
||||||
} else {
|
|
||||||
let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
|
|
||||||
//while (data[id]) id += '_';
|
|
||||||
data[id] = {
|
|
||||||
mbtiles: mbtiles,
|
|
||||||
};
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(font) => {
|
|
||||||
if (reportFonts) {
|
|
||||||
serving.fonts[font] = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (success && item.serve_rendered !== false) {
|
|
||||||
if (!isLight) {
|
|
||||||
startupPromises.push(
|
|
||||||
serve_rendered.add(
|
|
||||||
options,
|
|
||||||
serving.rendered,
|
|
||||||
item,
|
|
||||||
id,
|
|
||||||
opts.publicUrl,
|
|
||||||
(mbtiles) => {
|
|
||||||
let mbtilesFile;
|
|
||||||
for (const id of Object.keys(data)) {
|
|
||||||
if (id === mbtiles) {
|
|
||||||
mbtilesFile = data[id].mbtiles;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mbtilesFile;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
item.serve_rendered = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let addData = (id, item) => {
|
|
||||||
console.log(`Add data ${id}`);
|
|
||||||
Promise.all([serve_data.add(options, serving.data, item, id, opts.publicUrl)]);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const id of Object.keys(config.styles || {})) {
|
|
||||||
const item = config.styles[id];
|
|
||||||
if (!item.style || item.style.length === 0) {
|
|
||||||
console.log(`Missing "style" property for ${id}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
addStyle(id, item, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
startupPromises.push(
|
|
||||||
serve_font(options, serving.fonts).then((sub) => {
|
|
||||||
app.use('/', sub);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const id of Object.keys(data)) {
|
|
||||||
const item = data[id];
|
|
||||||
if (!item.mbtiles || item.mbtiles.length === 0) {
|
|
||||||
console.log(`Missing "mbtiles" property for ${id}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
startupPromises.push(
|
|
||||||
serve_data.add(options, serving.data, item, id, opts.publicUrl),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.serveAllStyles) {
|
|
||||||
fs.readdir(options.paths.mbtiles, {withFileTypes: true}, (err, files) => {
|
|
||||||
if (err) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const file of files) {
|
|
||||||
if (file.isFile() && path.extname(file.name).toLowerCase() == '.mbtiles') {
|
|
||||||
let id = path.basename(file.name, '.mbtiles');
|
|
||||||
let item = {
|
|
||||||
mbtiles: file.name
|
|
||||||
};
|
|
||||||
addData(id, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.readdir(options.paths.styles, { withFileTypes: true }, (err, files) => {
|
|
||||||
if (err) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const file of files) {
|
|
||||||
if (file.isFile() && path.extname(file.name).toLowerCase() == '.json') {
|
|
||||||
const id = path.basename(file.name, '.json');
|
|
||||||
const item = {
|
|
||||||
style: file.name,
|
|
||||||
};
|
|
||||||
addStyle(id, item, true, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const watcherData = chokidar.watch(path.join(options.paths.mbtiles, '*.mbtiles'),
|
|
||||||
{
|
|
||||||
persistent: true
|
|
||||||
});
|
|
||||||
watcherData.on('all', (eventType, filename) => {
|
|
||||||
if (filename) {
|
|
||||||
let id = path.basename(filename, '.mbtiles');
|
|
||||||
|
|
||||||
if (eventType === "add") {
|
|
||||||
console.log(`Data "${id}" added`);
|
|
||||||
let item = {
|
|
||||||
mbtiles: filename
|
|
||||||
};
|
|
||||||
addData(id, item);
|
|
||||||
} else if (eventType === "change") {
|
|
||||||
console.log(`Data "${id}" changed`);
|
|
||||||
let item = serving.data[id];
|
|
||||||
if (item) {
|
|
||||||
item.source.reInit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const watcher = chokidar.watch(
|
|
||||||
path.join(options.paths.styles, '*.json'),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
watcher.on('all', (eventType, filename) => {
|
|
||||||
if (filename) {
|
|
||||||
const id = path.basename(filename, '.json');
|
|
||||||
console.log(`Style "${id}" changed, updating...`);
|
|
||||||
|
|
||||||
serve_style.remove(serving.styles, id);
|
|
||||||
if (!isLight) {
|
|
||||||
serve_rendered.remove(serving.rendered, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventType == 'add' || eventType == 'change') {
|
|
||||||
const item = {
|
|
||||||
style: filename,
|
|
||||||
};
|
|
||||||
addStyle(id, item, true, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get('/styles.json', (req, res, next) => {
|
|
||||||
const result = [];
|
|
||||||
const query = req.query.key
|
|
||||||
? `?key=${encodeURIComponent(req.query.key)}`
|
|
||||||
: '';
|
|
||||||
for (const id of Object.keys(serving.styles)) {
|
|
||||||
const styleJSON = serving.styles[id].styleJSON;
|
|
||||||
result.push({
|
|
||||||
version: styleJSON.version,
|
|
||||||
name: styleJSON.name,
|
|
||||||
id: id,
|
|
||||||
url: `${getPublicUrl(
|
|
||||||
opts.publicUrl,
|
|
||||||
req,
|
|
||||||
)}styles/${id}/style.json${query}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
res.send(result);
|
|
||||||
});
|
|
||||||
|
|
||||||
const addTileJSONs = (arr, req, type) => {
|
|
||||||
for (const id of Object.keys(serving[type])) {
|
|
||||||
const info = clone(serving[type][id].tileJSON);
|
|
||||||
let path = '';
|
|
||||||
if (type === 'rendered') {
|
|
||||||
path = `styles/${id}`;
|
|
||||||
} else {
|
|
||||||
path = `${type}/${id}`;
|
|
||||||
}
|
|
||||||
info.tiles = getTileUrls(
|
|
||||||
req,
|
|
||||||
info.tiles,
|
|
||||||
path,
|
|
||||||
info.format,
|
|
||||||
opts.publicUrl,
|
|
||||||
{
|
|
||||||
pbf: options.pbfAlias,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
arr.push(info);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
};
|
|
||||||
|
|
||||||
app.get('/rendered.json', (req, res, next) => {
|
|
||||||
res.send(addTileJSONs([], req, 'rendered'));
|
|
||||||
});
|
|
||||||
app.get('/data.json', (req, res, next) => {
|
|
||||||
res.send(addTileJSONs([], req, 'data'));
|
|
||||||
});
|
|
||||||
app.get('/index.json', (req, res, next) => {
|
|
||||||
res.send(addTileJSONs(addTileJSONs([], req, 'rendered'), req, 'data'));
|
|
||||||
});
|
|
||||||
|
|
||||||
// ------------------------------------
|
|
||||||
// serve web presentations
|
|
||||||
app.use('/', express.static(path.join(__dirname, '../public/resources')));
|
|
||||||
|
|
||||||
const templates = path.join(__dirname, '../public/templates');
|
|
||||||
const serveTemplate = (urlPath, template, dataGetter) => {
|
|
||||||
let templateFile = `${templates}/${template}.tmpl`;
|
|
||||||
if (template === 'index') {
|
|
||||||
if (options.frontPage === false) {
|
|
||||||
return;
|
|
||||||
} else if (
|
|
||||||
options.frontPage &&
|
|
||||||
options.frontPage.constructor === String
|
|
||||||
) {
|
|
||||||
templateFile = path.resolve(paths.root, options.frontPage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
startupPromises.push(
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
fs.readFile(templateFile, (err, content) => {
|
|
||||||
if (err) {
|
|
||||||
err = new Error(`Template not found: ${err.message}`);
|
|
||||||
reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const compiled = handlebars.compile(content.toString());
|
|
||||||
|
|
||||||
app.use(urlPath, (req, res, next) => {
|
|
||||||
let data = {};
|
|
||||||
if (dataGetter) {
|
|
||||||
data = dataGetter(req);
|
|
||||||
if (!data) {
|
|
||||||
return res.status(404).send('Not found');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data[
|
|
||||||
'server_version'
|
|
||||||
] = `${packageJson.name} v${packageJson.version}`;
|
|
||||||
data['public_url'] = opts.publicUrl || '/';
|
|
||||||
data['is_light'] = isLight;
|
|
||||||
data['key_query_part'] = req.query.key
|
|
||||||
? `key=${encodeURIComponent(req.query.key)}&`
|
|
||||||
: '';
|
|
||||||
data['key_query'] = req.query.key
|
|
||||||
? `?key=${encodeURIComponent(req.query.key)}`
|
|
||||||
: '';
|
|
||||||
if (template === 'wmts') res.set('Content-Type', 'text/xml');
|
|
||||||
return res.status(200).send(compiled(data));
|
|
||||||
});
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
serveTemplate('/$', 'index', (req) => {
|
|
||||||
const styles = clone(serving.styles || {});
|
|
||||||
for (const id of Object.keys(styles)) {
|
|
||||||
const style = styles[id];
|
|
||||||
style.name = (serving.styles[id] || serving.rendered[id] || {}).name;
|
|
||||||
style.serving_data = serving.styles[id];
|
|
||||||
style.serving_rendered = serving.rendered[id];
|
|
||||||
if (style.serving_rendered) {
|
|
||||||
const center = style.serving_rendered.tileJSON.center;
|
|
||||||
if (center) {
|
|
||||||
style.viewer_hash = `#${center[2]}/${center[1].toFixed(
|
|
||||||
5,
|
|
||||||
)}/${center[0].toFixed(5)}`;
|
|
||||||
|
|
||||||
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
|
||||||
style.thumbnail = `${center[2]}/${Math.floor(
|
|
||||||
centerPx[0] / 256,
|
|
||||||
)}/${Math.floor(centerPx[1] / 256)}.png`;
|
|
||||||
}
|
|
||||||
|
|
||||||
style.xyz_link = getTileUrls(
|
|
||||||
req,
|
|
||||||
style.serving_rendered.tileJSON.tiles,
|
|
||||||
`styles/${id}`,
|
|
||||||
style.serving_rendered.tileJSON.format,
|
|
||||||
opts.publicUrl,
|
|
||||||
)[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const data = clone(serving.data || {});
|
|
||||||
for (const id of Object.keys(data)) {
|
|
||||||
const data_ = data[id];
|
|
||||||
const tilejson = data[id].tileJSON;
|
|
||||||
const center = tilejson.center;
|
|
||||||
if (center) {
|
|
||||||
data_.viewer_hash = `#${center[2]}/${center[1].toFixed(
|
|
||||||
5,
|
|
||||||
)}/${center[0].toFixed(5)}`;
|
|
||||||
}
|
|
||||||
data_.is_vector = tilejson.format === 'pbf';
|
|
||||||
if (!data_.is_vector) {
|
|
||||||
if (center) {
|
|
||||||
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
|
||||||
data_.thumbnail = `${center[2]}/${Math.floor(
|
|
||||||
centerPx[0] / 256,
|
|
||||||
)}/${Math.floor(centerPx[1] / 256)}.${data_.tileJSON.format}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
data_.xyz_link = getTileUrls(
|
|
||||||
req,
|
|
||||||
tilejson.tiles,
|
|
||||||
`data/${id}`,
|
|
||||||
tilejson.format,
|
|
||||||
opts.publicUrl,
|
|
||||||
{
|
|
||||||
pbf: options.pbfAlias,
|
|
||||||
},
|
|
||||||
)[0];
|
|
||||||
}
|
|
||||||
if (data_.filesize) {
|
|
||||||
let suffix = 'kB';
|
|
||||||
let size = parseInt(data_.filesize, 10) / 1024;
|
|
||||||
if (size > 1024) {
|
|
||||||
suffix = 'MB';
|
|
||||||
size /= 1024;
|
|
||||||
}
|
|
||||||
if (size > 1024) {
|
|
||||||
suffix = 'GB';
|
|
||||||
size /= 1024;
|
|
||||||
}
|
|
||||||
data_.formatted_filesize = `${size.toFixed(2)} ${suffix}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
styles: Object.keys(styles).length ? styles : null,
|
|
||||||
data: Object.keys(data).length ? data : null,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
serveTemplate('/styles/:id/$', 'viewer', (req) => {
|
|
||||||
const id = req.params.id;
|
|
||||||
const style = clone(((serving.styles || {})[id] || {}).styleJSON);
|
|
||||||
if (!style) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
style.id = id;
|
|
||||||
style.name = (serving.styles[id] || serving.rendered[id]).name;
|
|
||||||
style.serving_data = serving.styles[id];
|
|
||||||
style.serving_rendered = serving.rendered[id];
|
|
||||||
return style;
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
app.use('/rendered/:id/$', function(req, res, next) {
|
|
||||||
return res.redirect(301, '/styles/' + req.params.id + '/');
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
serveTemplate('/styles/:id/wmts.xml', 'wmts', (req) => {
|
|
||||||
const id = req.params.id;
|
|
||||||
const wmts = clone((serving.styles || {})[id]);
|
|
||||||
if (!wmts) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (wmts.hasOwnProperty('serve_rendered') && !wmts.serve_rendered) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
wmts.id = id;
|
|
||||||
wmts.name = (serving.styles[id] || serving.rendered[id]).name;
|
|
||||||
if (opts.publicUrl) {
|
|
||||||
wmts.baseUrl = opts.publicUrl;
|
|
||||||
} else {
|
|
||||||
wmts.baseUrl = `${
|
|
||||||
req.get('X-Forwarded-Protocol')
|
|
||||||
? req.get('X-Forwarded-Protocol')
|
|
||||||
: req.protocol
|
|
||||||
}://${req.get('host')}/`;
|
|
||||||
}
|
|
||||||
return wmts;
|
|
||||||
});
|
|
||||||
|
|
||||||
serveTemplate('/data/:id/$', 'data', (req) => {
|
|
||||||
const id = req.params.id;
|
|
||||||
const data = clone(serving.data[id]);
|
|
||||||
if (!data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
data.id = id;
|
|
||||||
data.is_vector = data.tileJSON.format === 'pbf';
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
|
|
||||||
let startupComplete = false;
|
|
||||||
const startupPromise = Promise.all(startupPromises).then(() => {
|
|
||||||
console.log('Startup complete');
|
|
||||||
startupComplete = true;
|
|
||||||
});
|
|
||||||
app.get('/health', (req, res, next) => {
|
|
||||||
if (startupComplete) {
|
|
||||||
return res.status(200).send('OK');
|
|
||||||
} else {
|
|
||||||
return res.status(503).send('Starting');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const server = app.listen(
|
|
||||||
process.env.PORT || opts.port,
|
|
||||||
process.env.BIND || opts.bind,
|
|
||||||
function () {
|
|
||||||
let address = this.address().address;
|
|
||||||
if (address.indexOf('::') === 0) {
|
|
||||||
address = `[${address}]`; // literal IPv6 address
|
|
||||||
}
|
|
||||||
console.log(`Listening at http://${address}:${this.address().port}/`);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// add server.shutdown() to gracefully stop serving
|
|
||||||
enableShutdown(server);
|
|
||||||
|
|
||||||
return {
|
|
||||||
app: app,
|
|
||||||
server: server,
|
|
||||||
startupPromise: startupPromise,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the server gracefully
|
|
||||||
* @param {string} signal Name of the received signal
|
|
||||||
*/
|
|
||||||
function stopGracefully(signal) {
|
|
||||||
console.log(`Caught signal ${signal}, stopping gracefully`);
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param opts
|
|
||||||
*/
|
|
||||||
export function server(opts) {
|
|
||||||
const running = start(opts);
|
|
||||||
|
|
||||||
running.startupPromise.catch((err) => {
|
|
||||||
console.error(err.message);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('SIGINT', stopGracefully);
|
|
||||||
process.on('SIGTERM', stopGracefully);
|
|
||||||
|
|
||||||
process.on('SIGHUP', (signal) => {
|
|
||||||
console.log(`Caught signal ${signal}, refreshing`);
|
|
||||||
console.log('Stopping server and reloading config');
|
|
||||||
|
|
||||||
running.server.shutdown(() => {
|
|
||||||
const restarted = start(opts);
|
|
||||||
running.server = restarted.server;
|
|
||||||
running.app = restarted.app;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return running;
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
import path from 'path';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
|
|
||||||
import clone from 'clone';
|
|
||||||
import glyphCompose from '@mapbox/glyph-pbf-composite';
|
|
||||||
|
|
||||||
|
|
||||||
var EARTH_RADIUS = 6371.0088;
|
|
||||||
|
|
||||||
function degToRad(degrees) {
|
|
||||||
return degrees * (Math.PI / 180);
|
|
||||||
}
|
|
||||||
|
|
||||||
function tileToLon(tileX, zoom) {
|
|
||||||
return ((tileX / 2**zoom) * 360.0) - 180.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tileToLat(tileY, zoom) {
|
|
||||||
var n = Math.PI - 2 * Math.PI * tileY / 2**zoom;
|
|
||||||
return (180.0 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate new URL object
|
|
||||||
* @param req
|
|
||||||
* @params {object} req - Express request
|
|
||||||
* @returns {URL} object
|
|
||||||
*/
|
|
||||||
const getUrlObject = (req) => {
|
|
||||||
const urlObject = new URL(`${req.protocol}://${req.headers.host}/`);
|
|
||||||
// support overriding hostname by sending X-Forwarded-Host http header
|
|
||||||
urlObject.hostname = req.hostname;
|
|
||||||
return urlObject;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPublicUrl = (publicUrl, req) => {
|
|
||||||
if (publicUrl) {
|
|
||||||
return publicUrl;
|
|
||||||
}
|
|
||||||
return getUrlObject(req).toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTileUrls = (req, domains, path, format, publicUrl, aliases) => {
|
|
||||||
const urlObject = getUrlObject(req);
|
|
||||||
if (domains) {
|
|
||||||
if (domains.constructor === String && domains.length > 0) {
|
|
||||||
domains = domains.split(',');
|
|
||||||
}
|
|
||||||
const hostParts = urlObject.host.split('.');
|
|
||||||
const relativeSubdomainsUsable =
|
|
||||||
hostParts.length > 1 &&
|
|
||||||
!/^([0-9]{1,3}\.){3}[0-9]{1,3}(\:[0-9]+)?$/.test(urlObject.host);
|
|
||||||
const newDomains = [];
|
|
||||||
for (const domain of domains) {
|
|
||||||
if (domain.indexOf('*') !== -1) {
|
|
||||||
if (relativeSubdomainsUsable) {
|
|
||||||
const newParts = hostParts.slice(1);
|
|
||||||
newParts.unshift(domain.replace('*', hostParts[0]));
|
|
||||||
newDomains.push(newParts.join('.'));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newDomains.push(domain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
domains = newDomains;
|
|
||||||
}
|
|
||||||
if (!domains || domains.length == 0) {
|
|
||||||
domains = [urlObject.host];
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryParams = [];
|
|
||||||
if (req.query.key) {
|
|
||||||
queryParams.push(`key=${encodeURIComponent(req.query.key)}`);
|
|
||||||
}
|
|
||||||
if (req.query.style) {
|
|
||||||
queryParams.push(`style=${encodeURIComponent(req.query.style)}`);
|
|
||||||
}
|
|
||||||
const query = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
|
|
||||||
|
|
||||||
if (aliases && aliases[format]) {
|
|
||||||
format = aliases[format];
|
|
||||||
}
|
|
||||||
|
|
||||||
const uris = [];
|
|
||||||
if (!publicUrl) {
|
|
||||||
for (const domain of domains) {
|
|
||||||
uris.push(
|
|
||||||
`${req.protocol}://${domain}/${path}/{z}/{x}/{y}.${format}${query}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uris.push(`${publicUrl}${path}/{z}/{x}/{y}.${format}${query}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return uris;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fixTileJSONCenter = (tileJSON) => {
|
|
||||||
if (tileJSON.bounds && !tileJSON.center) {
|
|
||||||
const fitWidth = 1024;
|
|
||||||
const tiles = fitWidth / 256;
|
|
||||||
tileJSON.center = [
|
|
||||||
(tileJSON.bounds[0] + tileJSON.bounds[2]) / 2,
|
|
||||||
(tileJSON.bounds[1] + tileJSON.bounds[3]) / 2,
|
|
||||||
Math.round(
|
|
||||||
-Math.log((tileJSON.bounds[2] - tileJSON.bounds[0]) / 360 / tiles) /
|
|
||||||
Math.LN2,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFontPbf = (allowedFonts, fontPath, name, range, fallbacks) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
|
|
||||||
const filename = path.join(fontPath, name, `${range}.pbf`);
|
|
||||||
if (!fallbacks) {
|
|
||||||
fallbacks = clone(allowedFonts || {});
|
|
||||||
}
|
|
||||||
delete fallbacks[name];
|
|
||||||
fs.readFile(filename, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(`ERROR: Font not found: ${name}`);
|
|
||||||
if (fallbacks && Object.keys(fallbacks).length) {
|
|
||||||
let fallbackName;
|
|
||||||
|
|
||||||
let fontStyle = name.split(' ').pop();
|
|
||||||
if (['Regular', 'Bold', 'Italic'].indexOf(fontStyle) < 0) {
|
|
||||||
fontStyle = 'Regular';
|
|
||||||
}
|
|
||||||
fallbackName = `Noto Sans ${fontStyle}`;
|
|
||||||
if (!fallbacks[fallbackName]) {
|
|
||||||
fallbackName = `Open Sans ${fontStyle}`;
|
|
||||||
if (!fallbacks[fallbackName]) {
|
|
||||||
fallbackName = Object.keys(fallbacks)[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(`ERROR: Trying to use ${fallbackName} as a fallback`);
|
|
||||||
delete fallbacks[fallbackName];
|
|
||||||
getFontPbf(null, fontPath, fallbackName, range, fallbacks).then(
|
|
||||||
resolve,
|
|
||||||
reject,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
reject(`Font load error: ${name}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resolve(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
reject(`Font not allowed: ${name}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getFontsPbf = (
|
|
||||||
allowedFonts,
|
|
||||||
fontPath,
|
|
||||||
names,
|
|
||||||
range,
|
|
||||||
fallbacks,
|
|
||||||
) => {
|
|
||||||
const fonts = names.split(',');
|
|
||||||
const queue = [];
|
|
||||||
for (const font of fonts) {
|
|
||||||
queue.push(
|
|
||||||
getFontPbf(
|
|
||||||
allowedFonts,
|
|
||||||
fontPath,
|
|
||||||
font,
|
|
||||||
range,
|
|
||||||
clone(allowedFonts || fallbacks),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(queue).then((values) => glyphCompose.combine(values));
|
|
||||||
};
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
const testTileJSONArray = function (url) {
|
|
||||||
describe(url + ' is array of TileJSONs', function () {
|
|
||||||
it('is json', function (done) {
|
|
||||||
supertest(app)
|
|
||||||
.get(url)
|
|
||||||
.expect(200)
|
|
||||||
.expect('Content-Type', /application\/json/, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('is non-empty array', function (done) {
|
|
||||||
supertest(app)
|
|
||||||
.get(url)
|
|
||||||
.expect(function (res) {
|
|
||||||
expect(res.body).to.be.a('array');
|
|
||||||
expect(res.body.length).to.be.greaterThan(0);
|
|
||||||
})
|
|
||||||
.end(done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const testTileJSON = function (url) {
|
|
||||||
describe(url + ' is TileJSON', function () {
|
|
||||||
it('is json', function (done) {
|
|
||||||
supertest(app)
|
|
||||||
.get(url)
|
|
||||||
.expect(200)
|
|
||||||
.expect('Content-Type', /application\/json/, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has valid tiles', function (done) {
|
|
||||||
supertest(app)
|
|
||||||
.get(url)
|
|
||||||
.expect(function (res) {
|
|
||||||
expect(res.body.tiles.length).to.be.greaterThan(0);
|
|
||||||
})
|
|
||||||
.end(done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Metadata', function () {
|
|
||||||
describe('/health', function () {
|
|
||||||
it('returns 200', function (done) {
|
|
||||||
supertest(app).get('/health').expect(200, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
testTileJSONArray('/index.json');
|
|
||||||
testTileJSONArray('/rendered.json');
|
|
||||||
testTileJSONArray('/data.json');
|
|
||||||
|
|
||||||
describe('/styles.json is valid array', function () {
|
|
||||||
it('is json', function (done) {
|
|
||||||
supertest(app)
|
|
||||||
.get('/styles.json')
|
|
||||||
.expect(200)
|
|
||||||
.expect('Content-Type', /application\/json/, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('contains valid item', function (done) {
|
|
||||||
supertest(app)
|
|
||||||
.get('/styles.json')
|
|
||||||
.expect(function (res) {
|
|
||||||
expect(res.body).to.be.a('array');
|
|
||||||
expect(res.body.length).to.be.greaterThan(0);
|
|
||||||
expect(res.body[0].version).to.be.equal(8);
|
|
||||||
expect(res.body[0].id).to.be.a('string');
|
|
||||||
expect(res.body[0].name).to.be.a('string');
|
|
||||||
})
|
|
||||||
.end(done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
testTileJSON('/styles/test-style.json');
|
|
||||||
testTileJSON('/data/openmaptiles.json');
|
|
||||||
});
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
process.env.NODE_ENV = 'test';
|
|
||||||
|
|
||||||
import { expect } from 'chai';
|
|
||||||
import supertest from 'supertest';
|
|
||||||
import { server } from '../src/server.js';
|
|
||||||
|
|
||||||
global.expect = expect;
|
|
||||||
global.supertest = supertest;
|
|
||||||
|
|
||||||
before(function () {
|
|
||||||
console.log('global setup');
|
|
||||||
process.chdir('test_data');
|
|
||||||
const running = server({
|
|
||||||
configPath: 'config.json',
|
|
||||||
port: 8888,
|
|
||||||
publicUrl: '/test/',
|
|
||||||
});
|
|
||||||
global.app = running.app;
|
|
||||||
global.server = running.server;
|
|
||||||
return running.startupPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
after(function () {
|
|
||||||
console.log('global teardown');
|
|
||||||
global.server.close(function () {
|
|
||||||
console.log('Done');
|
|
||||||
process.exit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
const testStatic = function (prefix, q, format, status, scale, type, query) {
|
|
||||||
if (scale) q += '@' + scale + 'x';
|
|
||||||
let path = '/styles/' + prefix + '/static/' + q + '.' + format;
|
|
||||||
if (query) {
|
|
||||||
path += query;
|
|
||||||
}
|
|
||||||
it(path + ' returns ' + status, function (done) {
|
|
||||||
const test = supertest(app).get(path);
|
|
||||||
if (status) test.expect(status);
|
|
||||||
if (type) test.expect('Content-Type', type);
|
|
||||||
test.end(done);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const prefix = 'test-style';
|
|
||||||
|
|
||||||
describe('Static endpoints', function () {
|
|
||||||
describe('center-based', function () {
|
|
||||||
describe('valid requests', function () {
|
|
||||||
describe('various formats', function () {
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'0,0,0/256x256',
|
|
||||||
'png',
|
|
||||||
200,
|
|
||||||
undefined,
|
|
||||||
/image\/png/,
|
|
||||||
);
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'0,0,0/256x256',
|
|
||||||
'jpg',
|
|
||||||
200,
|
|
||||||
undefined,
|
|
||||||
/image\/jpeg/,
|
|
||||||
);
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'0,0,0/256x256',
|
|
||||||
'jpeg',
|
|
||||||
200,
|
|
||||||
undefined,
|
|
||||||
/image\/jpeg/,
|
|
||||||
);
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'0,0,0/256x256',
|
|
||||||
'webp',
|
|
||||||
200,
|
|
||||||
undefined,
|
|
||||||
/image\/webp/,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('different parameters', function () {
|
|
||||||
testStatic(prefix, '0,0,0/300x300', 'png', 200, 2);
|
|
||||||
testStatic(prefix, '0,0,0/300x300', 'png', 200, 3);
|
|
||||||
|
|
||||||
testStatic(prefix, '0,0,1.5/256x256', 'png', 200);
|
|
||||||
|
|
||||||
testStatic(prefix, '80,40,20/600x300', 'png', 200, 3);
|
|
||||||
testStatic(prefix, '8.5,40.5,20/300x150', 'png', 200, 3);
|
|
||||||
testStatic(prefix, '-8.5,-40.5,20/300x150', 'png', 200, 3);
|
|
||||||
|
|
||||||
testStatic(prefix, '8,40,2@0,0/300x150', 'png', 200);
|
|
||||||
testStatic(prefix, '8,40,2@180,45/300x150', 'png', 200, 2);
|
|
||||||
testStatic(prefix, '8,40,2@10/300x150', 'png', 200, 3);
|
|
||||||
testStatic(prefix, '8,40,2@10.3,20.4/300x300', 'png', 200);
|
|
||||||
testStatic(prefix, '0,0,2@390,120/300x300', 'png', 200);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('invalid requests return 4xx', function () {
|
|
||||||
testStatic(prefix, '190,0,0/256x256', 'png', 400);
|
|
||||||
testStatic(prefix, '0,86,0/256x256', 'png', 400);
|
|
||||||
testStatic(prefix, '80,40,20/0x0', 'png', 400);
|
|
||||||
testStatic(prefix, '0,0,0/256x256', 'gif', 400);
|
|
||||||
testStatic(prefix, '0,0,0/256x256', 'png', 404, 1);
|
|
||||||
|
|
||||||
testStatic(prefix, '0,0,-1/256x256', 'png', 404);
|
|
||||||
testStatic(prefix, '0,0,0/256.5x256.5', 'png', 404);
|
|
||||||
|
|
||||||
testStatic(prefix, '0,0,0,/256x256', 'png', 404);
|
|
||||||
testStatic(prefix, '0,0,0,0,/256x256', 'png', 404);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('area-based', function () {
|
|
||||||
describe('valid requests', function () {
|
|
||||||
describe('various formats', function () {
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'-180,-80,180,80/10x10',
|
|
||||||
'png',
|
|
||||||
200,
|
|
||||||
undefined,
|
|
||||||
/image\/png/,
|
|
||||||
);
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'-180,-80,180,80/10x10',
|
|
||||||
'jpg',
|
|
||||||
200,
|
|
||||||
undefined,
|
|
||||||
/image\/jpeg/,
|
|
||||||
);
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'-180,-80,180,80/10x10',
|
|
||||||
'jpeg',
|
|
||||||
200,
|
|
||||||
undefined,
|
|
||||||
/image\/jpeg/,
|
|
||||||
);
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'-180,-80,180,80/10x10',
|
|
||||||
'webp',
|
|
||||||
200,
|
|
||||||
undefined,
|
|
||||||
/image\/webp/,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('different parameters', function () {
|
|
||||||
testStatic(prefix, '-180,-90,180,90/20x20', 'png', 200, 2);
|
|
||||||
testStatic(prefix, '0,0,1,1/200x200', 'png', 200, 3);
|
|
||||||
|
|
||||||
testStatic(prefix, '-280,-80,0,80/280x160', 'png', 200);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('invalid requests return 4xx', function () {
|
|
||||||
testStatic(prefix, '0,87,1,88/5x2', 'png', 400);
|
|
||||||
|
|
||||||
testStatic(prefix, '0,0,1,1/1x1', 'gif', 400);
|
|
||||||
|
|
||||||
testStatic(prefix, '-180,-80,180,80/0.5x2.6', 'png', 404);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('autofit path', function () {
|
|
||||||
describe('valid requests', function () {
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'auto/256x256',
|
|
||||||
'png',
|
|
||||||
200,
|
|
||||||
undefined,
|
|
||||||
/image\/png/,
|
|
||||||
'?path=10,10|20,20',
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('different parameters', function () {
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'auto/20x20',
|
|
||||||
'png',
|
|
||||||
200,
|
|
||||||
2,
|
|
||||||
/image\/png/,
|
|
||||||
'?path=10,10|20,20',
|
|
||||||
);
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'auto/200x200',
|
|
||||||
'png',
|
|
||||||
200,
|
|
||||||
3,
|
|
||||||
/image\/png/,
|
|
||||||
'?path=-10,-10|-20,-20',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('encoded path', function () {
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'auto/20x20',
|
|
||||||
'png',
|
|
||||||
200,
|
|
||||||
2,
|
|
||||||
/image\/png/,
|
|
||||||
'?path=' + decodeURIComponent('enc:{{biGwvyGoUi@s_A|{@'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('invalid requests return 4xx', function () {
|
|
||||||
testStatic(prefix, 'auto/256x256', 'png', 400);
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'auto/256x256',
|
|
||||||
'png',
|
|
||||||
400,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
'?path=invalid',
|
|
||||||
);
|
|
||||||
testStatic(
|
|
||||||
prefix,
|
|
||||||
'auto/2560x2560',
|
|
||||||
'png',
|
|
||||||
400,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
'?path=10,10|20,20',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
const testIs = function (url, type, status) {
|
|
||||||
it(
|
|
||||||
url + ' return ' + (status || 200) + ' and is ' + type.toString(),
|
|
||||||
function (done) {
|
|
||||||
supertest(app)
|
|
||||||
.get(url)
|
|
||||||
.expect(status || 200)
|
|
||||||
.expect('Content-Type', type, done);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const prefix = 'test-style';
|
|
||||||
|
|
||||||
describe('Styles', function () {
|
|
||||||
describe('/styles/' + prefix + '/style.json is valid style', function () {
|
|
||||||
testIs('/styles/' + prefix + '/style.json', /application\/json/);
|
|
||||||
|
|
||||||
it('contains expected properties', function (done) {
|
|
||||||
supertest(app)
|
|
||||||
.get('/styles/' + prefix + '/style.json')
|
|
||||||
.expect(function (res) {
|
|
||||||
expect(res.body.version).to.be.equal(8);
|
|
||||||
expect(res.body.name).to.be.a('string');
|
|
||||||
expect(res.body.sources).to.be.a('object');
|
|
||||||
expect(res.body.glyphs).to.be.a('string');
|
|
||||||
expect(res.body.sprite).to.be.a('string');
|
|
||||||
expect(res.body.sprite).to.be.equal('/test/styles/test-style/sprite');
|
|
||||||
expect(res.body.layers).to.be.a('array');
|
|
||||||
})
|
|
||||||
.end(done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('/styles/streets/style.json is not served', function () {
|
|
||||||
testIs('/styles/streets/style.json', /./, 404);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('/styles/' + prefix + '/sprite[@2x].{format}', function () {
|
|
||||||
testIs('/styles/' + prefix + '/sprite.json', /application\/json/);
|
|
||||||
testIs('/styles/' + prefix + '/sprite@2x.json', /application\/json/);
|
|
||||||
testIs('/styles/' + prefix + '/sprite.png', /image\/png/);
|
|
||||||
testIs('/styles/' + prefix + '/sprite@2x.png', /image\/png/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Fonts', function () {
|
|
||||||
testIs('/fonts/Open Sans Bold/0-255.pbf', /application\/x-protobuf/);
|
|
||||||
testIs('/fonts/Open Sans Regular/65280-65535.pbf', /application\/x-protobuf/);
|
|
||||||
testIs(
|
|
||||||
'/fonts/Open Sans Bold,Open Sans Regular/0-255.pbf',
|
|
||||||
/application\/x-protobuf/,
|
|
||||||
);
|
|
||||||
testIs('/fonts/Nonsense,Open Sans Bold/0-255.pbf', /./, 400);
|
|
||||||
|
|
||||||
testIs('/fonts/Nonsense/0-255.pbf', /./, 400);
|
|
||||||
testIs('/fonts/Nonsense1,Nonsense2/0-255.pbf', /./, 400);
|
|
||||||
});
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
const testTile = function (prefix, z, x, y, status) {
|
|
||||||
const path = '/data/' + prefix + '/' + z + '/' + x + '/' + y + '.pbf';
|
|
||||||
it(path + ' returns ' + status, function (done) {
|
|
||||||
const test = supertest(app).get(path);
|
|
||||||
if (status) test.expect(status);
|
|
||||||
if (status == 200) test.expect('Content-Type', /application\/x-protobuf/);
|
|
||||||
test.end(done);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const prefix = 'openmaptiles';
|
|
||||||
|
|
||||||
describe('Vector tiles', function () {
|
|
||||||
describe('existing tiles', function () {
|
|
||||||
testTile(prefix, 0, 0, 0, 200);
|
|
||||||
testTile(prefix, 14, 8581, 5738, 200);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('non-existent requests return 4xx', function () {
|
|
||||||
testTile('non_existent', 0, 0, 0, 404);
|
|
||||||
testTile(prefix, -1, 0, 0, 404); // err zoom
|
|
||||||
testTile(prefix, 20, 0, 0, 404); // zoom out of bounds
|
|
||||||
testTile(prefix, 0, 1, 0, 404);
|
|
||||||
testTile(prefix, 0, 0, 1, 404);
|
|
||||||
|
|
||||||
testTile(prefix, 14, 0, 0, 204); // non existent tile
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
const testTile = function (prefix, z, x, y, format, status, scale, type) {
|
|
||||||
if (scale) y += '@' + scale + 'x';
|
|
||||||
const path = '/styles/' + prefix + '/' + z + '/' + x + '/' + y + '.' + format;
|
|
||||||
it(path + ' returns ' + status, function (done) {
|
|
||||||
const test = supertest(app).get(path);
|
|
||||||
test.expect(status);
|
|
||||||
if (type) test.expect('Content-Type', type);
|
|
||||||
test.end(done);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const prefix = 'test-style';
|
|
||||||
|
|
||||||
describe('Raster tiles', function () {
|
|
||||||
describe('valid requests', function () {
|
|
||||||
describe('various formats', function () {
|
|
||||||
testTile(prefix, 0, 0, 0, 'png', 200, undefined, /image\/png/);
|
|
||||||
testTile(prefix, 0, 0, 0, 'jpg', 200, undefined, /image\/jpeg/);
|
|
||||||
testTile(prefix, 0, 0, 0, 'jpeg', 200, undefined, /image\/jpeg/);
|
|
||||||
testTile(prefix, 0, 0, 0, 'webp', 200, undefined, /image\/webp/);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('different coordinates and scales', function () {
|
|
||||||
testTile(prefix, 1, 1, 1, 'png', 200);
|
|
||||||
|
|
||||||
testTile(prefix, 0, 0, 0, 'png', 200, 2);
|
|
||||||
testTile(prefix, 0, 0, 0, 'png', 200, 3);
|
|
||||||
testTile(prefix, 2, 1, 1, 'png', 200, 3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('invalid requests return 4xx', function () {
|
|
||||||
testTile('non_existent', 0, 0, 0, 'png', 404);
|
|
||||||
testTile(prefix, -1, 0, 0, 'png', 404);
|
|
||||||
testTile(prefix, 25, 0, 0, 'png', 404);
|
|
||||||
testTile(prefix, 0, 1, 0, 'png', 404);
|
|
||||||
testTile(prefix, 0, 0, 1, 'png', 404);
|
|
||||||
testTile(prefix, 0, 0, 0, 'gif', 400);
|
|
||||||
testTile(prefix, 0, 0, 0, 'pbf', 400);
|
|
||||||
|
|
||||||
testTile(prefix, 0, 0, 0, 'png', 404, 1);
|
|
||||||
testTile(prefix, 0, 0, 0, 'png', 404, 5);
|
|
||||||
|
|
||||||
// testTile('hybrid', 0, 0, 0, 'png', 404); //TODO: test this
|
|
||||||
});
|
|
||||||
});
|
|
||||||