├── sites
└── demo
│ ├── .npmrc
│ ├── .env.example
│ ├── static
│ ├── favicon.png
│ └── assets
│ │ ├── plugin-overview.webp
│ │ ├── maplibre-cdn-example.txt
│ │ └── mapbox-cdn-example.txt
│ ├── postcss.config.cjs
│ ├── .prettierignore
│ ├── src
│ ├── app.postcss
│ ├── app.d.ts
│ ├── routes
│ │ ├── mapbox
│ │ │ ├── +page.ts
│ │ │ └── +page.svelte
│ │ ├── maplibre
│ │ │ ├── +page.ts
│ │ │ └── +page.svelte
│ │ ├── +layout.ts
│ │ ├── +layout.svelte
│ │ └── +page.svelte
│ ├── app.html
│ └── lib
│ │ └── LanguageSelector.svelte
│ ├── .gitignore
│ ├── .prettierrc
│ ├── vite.config.ts
│ ├── tsconfig.json
│ ├── tailwind.config.ts
│ ├── README.md
│ ├── svelte.config.js
│ ├── eslint.config.js
│ └── package.json
├── .npmrc
├── packages
├── mapbox-gl-export
│ ├── .env.example
│ ├── src
│ │ ├── lib
│ │ │ ├── interfaces
│ │ │ │ ├── index.ts
│ │ │ │ └── ControlOptions.ts
│ │ │ ├── index.ts
│ │ │ ├── export-control.ts
│ │ │ └── map-generator.ts
│ │ ├── vite-env.d.ts
│ │ └── scss
│ │ │ └── mapbox-gl-export.scss
│ ├── postcss.config.js
│ ├── .prettierrc
│ ├── .prettierignore
│ ├── .gitignore
│ ├── eslint.config.js
│ ├── README.md
│ ├── index_cdn.js
│ ├── tsconfig.json
│ ├── vite.config.ts
│ ├── index.ts
│ ├── index.html
│ ├── LICENSE
│ ├── index_cdn.html
│ ├── package.json
│ └── CHANGELOG.md
└── maplibre-gl-export
│ ├── postcss.config.js
│ ├── .prettierrc
│ ├── src
│ ├── lib
│ │ ├── interfaces
│ │ │ ├── Language.ts
│ │ │ ├── DPI.ts
│ │ │ ├── Format.ts
│ │ │ ├── Unit.ts
│ │ │ ├── PageOrientation.ts
│ │ │ ├── Translation.ts
│ │ │ ├── index.ts
│ │ │ ├── AttributionStyle.ts
│ │ │ ├── Size.ts
│ │ │ └── ControlOptions.ts
│ │ ├── index.ts
│ │ ├── local
│ │ │ ├── zhHans.ts
│ │ │ ├── zhHant.ts
│ │ │ ├── ja.ts
│ │ │ ├── ca.ts
│ │ │ ├── fi.ts
│ │ │ ├── en.ts
│ │ │ ├── sv.ts
│ │ │ ├── fr.ts
│ │ │ ├── pt.ts
│ │ │ ├── es.ts
│ │ │ ├── uk.ts
│ │ │ ├── de.ts
│ │ │ ├── vi.ts
│ │ │ ├── ru.ts
│ │ │ └── index.ts
│ │ ├── map-generator.ts
│ │ ├── printable-area-manager.ts
│ │ ├── crosshair-manager.ts
│ │ ├── export-control.ts
│ │ └── map-generator-base.ts
│ └── scss
│ │ └── maplibre-gl-export.scss
│ ├── .prettierignore
│ ├── .eslintrc.cjs
│ ├── eslint.config.js
│ ├── README.md
│ ├── tsconfig.json
│ ├── vite.config.ts
│ ├── index.html
│ ├── index_cdn.html
│ ├── index.ts
│ ├── .gitignore
│ ├── package.json
│ └── CHANGELOG.md
├── pnpm-workspace.yaml
├── .github
├── FUNDING.yml
├── PULL_REQUEST_TEMPLATE.md
├── workflows
│ ├── build.yml
│ └── release.yml
└── ISSUE_TEMPLATE
│ ├── feature_request.yml
│ └── bug_report.yml
├── .gitignore
├── .changeset
├── config.json
└── README.md
├── lefthook.yml
├── renovate.json
├── package.json
├── LICENSE
├── README.md
└── CONTRIBUTING.md
/sites/demo/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/sites/demo/.env.example:
--------------------------------------------------------------------------------
1 | PUBLIC_MAPBOX_ACCESSTOKEN=
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | link-workspace-packages = true
2 | engine-strict = true
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/.env.example:
--------------------------------------------------------------------------------
1 | VITE_MAPBOX_ACCESSTOKEN=
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "packages/*"
3 | - "sites/*"
4 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/src/lib/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ControlOptions';
2 |
--------------------------------------------------------------------------------
/sites/demo/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/watergis/maplibre-gl-export/HEAD/sites/demo/static/favicon.png
--------------------------------------------------------------------------------
/sites/demo/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [JinIgarashi]
4 | open_collective: watergis
5 |
--------------------------------------------------------------------------------
/sites/demo/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore files for PNPM, NPM and YARN
2 | pnpm-lock.yaml
3 | package-lock.json
4 | yarn.lock
5 | /.netlify
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /package
5 | .env
6 | .env.*
7 | !.env.example
8 | # Local Netlify folder
9 | .netlify
10 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/postcss.config.js:
--------------------------------------------------------------------------------
1 | import autoprefixer from 'autoprefixer';
2 |
3 | export default {
4 | plugins: [autoprefixer]
5 | };
6 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/postcss.config.js:
--------------------------------------------------------------------------------
1 | import autoprefixer from 'autoprefixer';
2 |
3 | export default {
4 | plugins: [autoprefixer]
5 | };
6 |
--------------------------------------------------------------------------------
/sites/demo/static/assets/plugin-overview.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/watergis/maplibre-gl-export/HEAD/sites/demo/static/assets/plugin-overview.webp
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "printWidth": 100,
6 | "plugins": []
7 | }
8 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "printWidth": 100,
6 | "plugins": []
7 | }
8 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/interfaces/Language.ts:
--------------------------------------------------------------------------------
1 | import { AvailableLanguages } from '../local';
2 |
3 | export type Language = (typeof AvailableLanguages)[number];
4 |
--------------------------------------------------------------------------------
/sites/demo/src/app.postcss:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 | @tailwind variants;
5 |
6 | html,
7 | body {
8 | @apply h-full overflow-hidden;
9 | }
10 |
--------------------------------------------------------------------------------
/sites/demo/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | vite.config.js.timestamp-*
10 | vite.config.ts.timestamp-*
11 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/interfaces/DPI.ts:
--------------------------------------------------------------------------------
1 | export const DPI = {
2 | 72: 72,
3 | 96: 96,
4 | 200: 200,
5 | 300: 300,
6 | 400: 400
7 | } as const;
8 | export type DPIType = (typeof DPI)[keyof typeof DPI];
9 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/interfaces/Format.ts:
--------------------------------------------------------------------------------
1 | export const Format = {
2 | JPEG: 'jpg',
3 | PNG: 'png',
4 | PDF: 'pdf',
5 | SVG: 'svg'
6 | } as const;
7 | export type FormatType = (typeof Format)[keyof typeof Format];
8 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | export { default as MapboxExportControl } from './export-control';
2 | export { Size, PageOrientation, Format, DPI, type Language } from '@watergis/maplibre-gl-export';
3 | export * from './interfaces';
4 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/interfaces/Unit.ts:
--------------------------------------------------------------------------------
1 | export const Unit = {
2 | // don't use inch unit. because page size setting is using mm unit.
3 | in: 'in',
4 | mm: 'mm'
5 | } as const;
6 | export type UnitType = (typeof Unit)[keyof typeof Unit];
7 |
--------------------------------------------------------------------------------
/sites/demo/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "printWidth": 100,
6 | "plugins": ["prettier-plugin-svelte"],
7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/src/lib/interfaces/ControlOptions.ts:
--------------------------------------------------------------------------------
1 | import { type ControlOptions as MaplibreControlOptions } from '@watergis/maplibre-gl-export';
2 |
3 | export interface ControlOptions extends MaplibreControlOptions {
4 | accessToken?: string;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/interfaces/PageOrientation.ts:
--------------------------------------------------------------------------------
1 | export const PageOrientation = {
2 | Landscape: 'landscape',
3 | Portrait: 'portrait'
4 | } as const;
5 | export type PageOrientationType = (typeof PageOrientation)[keyof typeof PageOrientation];
6 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/interfaces/Translation.ts:
--------------------------------------------------------------------------------
1 | export type Translation = {
2 | PageSize: string;
3 | PageOrientation: string;
4 | Format: string;
5 | DPI: string;
6 | Generate: string;
7 | LanguageName: string;
8 | LanguageCode: string;
9 | };
10 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface ImportMetaEnv {
4 | readonly VITE_MAPBOX_ACCESSTOKEN: string;
5 | // more env variables...
6 | }
7 |
8 | interface ImportMeta {
9 | readonly env: ImportMetaEnv;
10 | }
11 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/.prettierignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | /dist
7 | .env
8 | .env.*
9 | !.env.example
10 |
11 | # Ignore files for PNPM, NPM and YARN
12 | pnpm-lock.yaml
13 | package-lock.json
14 | yarn.lock
15 | *.md
16 | .github/*
17 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/.prettierignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | /dist
7 | .env
8 | .env.*
9 | !.env.example
10 |
11 | # Ignore files for PNPM, NPM and YARN
12 | pnpm-lock.yaml
13 | package-lock.json
14 | yarn.lock
15 | *.md
16 | .github/*
17 |
--------------------------------------------------------------------------------
/sites/demo/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | // and what to do when importing types
4 | declare namespace App {
5 | // interface Locals {}
6 | // interface PageData {}
7 | // interface Error {}
8 | // interface Platform {}
9 | }
10 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "public",
8 | "baseBranch": "main",
9 | "updateInternalDependencies": "patch",
10 | "ignore": ["demo"]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AttributionStyle';
2 | export * from './ControlOptions';
3 | export * from './DPI';
4 | export * from './Format';
5 | export * from './Language';
6 | export * from './PageOrientation';
7 | export * from './Size';
8 | export * from './Translation';
9 | export * from './Unit';
10 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | export { default as MaplibreExportControl } from './export-control';
2 | export { default as CrosshairManager } from './crosshair-manager';
3 | export { default as PrintableAreaManager } from './printable-area-manager';
4 |
5 | export * from './local';
6 | export * from './interfaces';
7 | export * from './map-generator-base';
8 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/zhHans.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: '页面大小',
5 | PageOrientation: '页面方向',
6 | Format: '格式',
7 | DPI: '像素',
8 | Generate: '导出',
9 | LanguageName: '简体字',
10 | LanguageCode: 'zhHans'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/zhHant.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: '頁面大小',
5 | PageOrientation: '頁面方向',
6 | Format: '格式',
7 | DPI: '像素',
8 | Generate: '導出',
9 | LanguageName: '繁体字',
10 | LanguageCode: 'zhHant'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/ja.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: 'ページサイズ',
5 | PageOrientation: 'ページ方向',
6 | Format: 'フォーマット',
7 | DPI: 'DPI(解像度)',
8 | Generate: '出力',
9 | LanguageName: '日本語',
10 | LanguageCode: 'ja'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/ca.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: 'Mida',
5 | PageOrientation: 'Orientació',
6 | Format: 'Format',
7 | DPI: 'DPI',
8 | Generate: 'Genera',
9 | LanguageName: 'Catalan',
10 | LanguageCode: 'ca'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/fi.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: 'Sivukoko',
5 | PageOrientation: 'Sivun suunta',
6 | Format: 'Muoto',
7 | DPI: 'DPI',
8 | Generate: 'Generoi',
9 | LanguageName: 'Suomalainen',
10 | LanguageCode: 'fi'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/en.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: 'Page Size',
5 | PageOrientation: 'Page Orientation',
6 | Format: 'Format',
7 | DPI: 'DPI',
8 | Generate: 'Generate',
9 | LanguageName: 'English',
10 | LanguageCode: 'en'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/sv.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: 'Sidstorlek',
5 | PageOrientation: 'Sidorientering',
6 | Format: 'Format',
7 | DPI: 'DPI',
8 | Generate: 'Generera',
9 | LanguageName: 'Svenska',
10 | LanguageCode: 'sv'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/fr.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: 'Taille de page',
5 | PageOrientation: 'Orientation de la page',
6 | Format: 'Format',
7 | DPI: 'DPI',
8 | Generate: 'Générer',
9 | LanguageName: 'Français',
10 | LanguageCode: 'fr'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/pt.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: 'Tamanho da página',
5 | PageOrientation: 'Orientação da página',
6 | Format: 'Formato',
7 | DPI: 'DPI',
8 | Generate: 'Gerar',
9 | LanguageName: 'Português',
10 | LanguageCode: 'pt'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/lefthook.yml:
--------------------------------------------------------------------------------
1 | # EXAMPLE USAGE
2 | # Refer for explanation to following link:
3 | # https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md
4 |
5 | pre-commit:
6 | parallel: true
7 | commands:
8 | lint+format:
9 | run: |
10 | pnpm lint
11 | pnpm format
12 |
13 | pre-push:
14 | parallel: true
15 | commands:
16 | build:
17 | run: pnpm build
18 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/es.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: 'Tamaño de página',
5 | PageOrientation: 'Orientación de página',
6 | Format: 'Formato',
7 | DPI: 'DPI',
8 | Generate: 'Generar',
9 | LanguageName: 'Española',
10 | LanguageCode: 'es'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/uk.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: 'Розмір сторінки',
5 | PageOrientation: 'Орієнтація сторінки',
6 | Format: 'Формат',
7 | DPI: 'DPI',
8 | Generate: 'Згенерувати',
9 | LanguageName: 'українська',
10 | LanguageCode: 'uk'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/de.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: 'Papierformat',
5 | PageOrientation: 'Papierausrichtung',
6 | Format: 'Dateiformat',
7 | DPI: 'Druckauflösung',
8 | Generate: 'Erstellen',
9 | LanguageName: 'Deutsch',
10 | LanguageCode: 'de'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/vi.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: 'Kích thước trang',
5 | PageOrientation: 'Loại trang',
6 | Format: 'Định dạng',
7 | DPI: 'Mật độ điểm ảnh (DPI)',
8 | Generate: 'Tạo',
9 | LanguageName: 'Tiếng Việt',
10 | LanguageCode: 'vi'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/ru.ts:
--------------------------------------------------------------------------------
1 | import { Translation } from '../interfaces/Translation';
2 |
3 | const translation: Translation = {
4 | PageSize: 'Размер страницы',
5 | PageOrientation: 'Ориентация страницы',
6 | Format: 'Формат',
7 | DPI: 'Разрешение (DPI)',
8 | Generate: 'Сгенерировать',
9 | LanguageName: 'русский',
10 | LanguageCode: 'ru'
11 | };
12 |
13 | export default translation;
14 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
5 | plugins: ['@typescript-eslint'],
6 | ignorePatterns: ['*.cjs'],
7 | parserOptions: {
8 | sourceType: 'module',
9 | ecmaVersion: 2020
10 | },
11 | env: {
12 | browser: true,
13 | es2017: true,
14 | node: true
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/sites/demo/src/routes/mapbox/+page.ts:
--------------------------------------------------------------------------------
1 | import type { PageLoad } from './$types';
2 |
3 | export const load: PageLoad = async () => {
4 | const title = 'Mapbox GL Export';
5 | const site_name = 'Mapbox GL Export Demo';
6 | const site_description = 'Demo website for mapbox-gl-export plugin';
7 |
8 | return {
9 | title,
10 | site_name,
11 | site_description
12 | };
13 | };
14 |
15 | export const prerender = true;
16 | export const ssr = false;
17 |
--------------------------------------------------------------------------------
/sites/demo/src/routes/maplibre/+page.ts:
--------------------------------------------------------------------------------
1 | import type { PageLoad } from './$types';
2 |
3 | export const load: PageLoad = async () => {
4 | const title = 'Maplibre GL Export';
5 | const site_name = 'Maplibre GL Export Demo';
6 | const site_description = 'Demo website for maplibre-gl-export plugin';
7 |
8 | return {
9 | title,
10 | site_name,
11 | site_description
12 | };
13 | };
14 |
15 | export const prerender = true;
16 | export const ssr = false;
17 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/interfaces/AttributionStyle.ts:
--------------------------------------------------------------------------------
1 | export interface AttributionStyle {
2 | textSize: number;
3 | textHaloColor: string;
4 | textHaloWidth: number;
5 | textColor: string;
6 | fallbackTextFont: string[];
7 | }
8 |
9 | export interface AttributionOptions {
10 | style?: AttributionStyle;
11 | visibility?: 'visible' | 'none';
12 | // TODO: top-left and bottom-left have issues of text-max-width setting
13 | position?: 'top-right' | 'bottom-right';
14 | }
15 |
--------------------------------------------------------------------------------
/sites/demo/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 | %sveltekit.head%
11 |
12 |
13 | %sveltekit.body%
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import ts from 'typescript-eslint';
3 | import prettier from 'eslint-config-prettier';
4 | import globals from 'globals';
5 |
6 | /** @type {import('eslint').Linter.Config[]} */
7 | export default [
8 | js.configs.recommended,
9 | ...ts.configs.recommended,
10 | prettier,
11 | {
12 | languageOptions: {
13 | globals: {
14 | ...globals.browser,
15 | ...globals.node
16 | }
17 | }
18 | },
19 | {
20 | ignores: ['build/', 'dist/']
21 | }
22 | ];
23 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import ts from 'typescript-eslint';
3 | import prettier from 'eslint-config-prettier';
4 | import globals from 'globals';
5 |
6 | /** @type {import('eslint').Linter.Config[]} */
7 | export default [
8 | js.configs.recommended,
9 | ...ts.configs.recommended,
10 | prettier,
11 | {
12 | languageOptions: {
13 | globals: {
14 | ...globals.browser,
15 | ...globals.node
16 | }
17 | }
18 | },
19 | {
20 | ignores: ['build/', 'dist/']
21 | }
22 | ];
23 |
--------------------------------------------------------------------------------
/sites/demo/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { purgeCss } from 'vite-plugin-tailwind-purgecss';
2 | import { sveltekit } from '@sveltejs/kit/vite';
3 | import { defineConfig } from 'vite';
4 |
5 | export default defineConfig({
6 | plugins: [
7 | sveltekit(),
8 | purgeCss({
9 | safelist: {
10 | // any selectors that begin with "hljs-" will not be purged
11 | greedy: [/^hljs-/]
12 | }
13 | })
14 | ],
15 | ssr: {
16 | noExternal: ['maplibre-gl', 'mapbox-gl']
17 | },
18 | css: {
19 | preprocessorOptions: {
20 | scss: {
21 | api: 'modern-compiler'
22 | }
23 | }
24 | }
25 | });
26 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/interfaces/Size.ts:
--------------------------------------------------------------------------------
1 | export const Size = {
2 | // A0, A1, B0, B1 are not working well.
3 | // A0: [1189, 841],
4 | // A1: [841, 594],
5 | LETTER: [279, 216], // 8.5x11 - works
6 | //TABLOID: [432,279] // 11x17 - not working currently prints to 11.68x8.27 in landscape
7 | A2: [594, 420],
8 | A3: [420, 297],
9 | A4: [297, 210],
10 | A5: [210, 148],
11 | A6: [148, 105],
12 | // B0: [1414, 1000],
13 | // B1: [1000, 707],
14 | B2: [707, 500],
15 | B3: [500, 353],
16 | B4: [353, 250],
17 | B5: [250, 176],
18 | B6: [176, 125]
19 | } as const;
20 | export type SizeType = (typeof Size)[keyof typeof Size];
21 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/README.md:
--------------------------------------------------------------------------------
1 | # maplibre-gl-export
2 |
3 | [](https://www.npmjs.com/package/@watergis/maplibre-gl-export)
4 |
5 | This module adds control which can export PDF and images. It was forked from [mapbox-gl-export](https://github.com/watergis/mapbox-gl-export).
6 |
7 | This module is using source code of [mpetroff/print-maps](https://github.com/mpetroff/print-maps). I just adopted this library to normal Mapbox GL Plugin. Thanks so much to develop this library!
8 |
9 | ## Demo & usage
10 |
11 | See [documentation](https://maplibre-gl-export.water-gis.com/).
12 |
--------------------------------------------------------------------------------
/sites/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "moduleResolution": "bundler"
13 | }
14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
15 | //
16 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
17 | // from the referenced tsconfig.json - TypeScript does not merge them in
18 | }
19 |
--------------------------------------------------------------------------------
/sites/demo/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 | import type { Config } from 'tailwindcss';
3 | import forms from '@tailwindcss/forms';
4 | import typography from '@tailwindcss/typography';
5 | import { skeleton } from '@skeletonlabs/tw-plugin';
6 |
7 | export default {
8 | darkMode: 'class',
9 | content: [
10 | './src/**/*.{html,js,svelte,ts}',
11 | join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')
12 | ],
13 | theme: {
14 | extend: {}
15 | },
16 | plugins: [
17 | forms,
18 | typography,
19 | skeleton({
20 | themes: {
21 | preset: [{ name: 'wintry', enhancements: true }]
22 | }
23 | })
24 | ]
25 | } satisfies Config;
26 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/README.md:
--------------------------------------------------------------------------------
1 | # mapbox-gl-export
2 |
3 | [](https://www.npmjs.com/package/@watergis/mapbox-gl-export)
4 |
5 | This module adds control which can export PDF and images.
6 |
7 | This module is using source code of [mpetroff/print-maps](https://github.com/mpetroff/print-maps). I just adopted this library to normal Mapbox GL Plugin. Thanks so much to develop this library!
8 |
9 | ## for Maplibre GL users
10 |
11 | Please consider to use [maplibre-gl-export](https://github.com/watergis/maplibre-gl-export) plugin for Maplibre GL.
12 |
13 | ## Demo & usage
14 |
15 | See [documentation](https://maplibre-gl-export.water-gis.com/).
16 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/index_cdn.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef*/
2 |
3 | mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESSTOKEN;
4 | const map = new mapboxgl.Map({
5 | container: 'map',
6 | style: 'mapbox://styles/mapbox/streets-v11',
7 | center: [35.87063, -1.08551],
8 | zoom: 9
9 | });
10 |
11 | map.addControl(
12 | new MapboxExportControl.MapboxExportControl({
13 | PageSize: MapboxExportControl.Size.A4,
14 | PageOrientation: MapboxExportControl.PageOrientation.Landscape,
15 | Format: MapboxExportControl.Format.PNG,
16 | DPI: MapboxExportControl.DPI[300],
17 | Crosshair: true,
18 | PrintableArea: true,
19 | Local: 'fr',
20 | accessToken: mapboxgl.accessToken
21 | }),
22 | 'top-right'
23 | );
24 |
--------------------------------------------------------------------------------
/sites/demo/README.md:
--------------------------------------------------------------------------------
1 | # demo
2 |
3 | demo website for `maplibre-gl-export` and `mapbox-gl-export`.
4 |
5 | ## Developing
6 |
7 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
8 |
9 | ```bash
10 | npm run dev
11 |
12 | # or start the server and open the app in a new browser tab
13 | npm run dev -- --open
14 | ```
15 |
16 | ## Building
17 |
18 | To create a production version of your app:
19 |
20 | ```bash
21 | npm run build
22 | ```
23 |
24 | You can preview the production build with `npm run preview`.
25 |
26 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
27 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "alwaysStrict": true,
4 | "baseUrl": ".",
5 | "emitDeclarationOnly": true,
6 | "declaration": true,
7 | "declarationMap": true,
8 | "declarationDir": "./dist/types",
9 | "experimentalDecorators": true,
10 | "lib": ["dom", "ESNext"],
11 | "module": "ESNext",
12 | "moduleResolution": "node",
13 | "noImplicitThis": true,
14 | "noUnusedLocals": true,
15 | "outDir": "./dist/lib",
16 | "preserveConstEnums": true,
17 | "removeComments": true,
18 | "strictFunctionTypes": true,
19 | "strictNullChecks": true,
20 | "sourceMap": true,
21 | "target": "ESNext",
22 | "esModuleInterop": true,
23 | "skipLibCheck": true
24 | },
25 | "include": ["src/lib"]
26 | }
27 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "alwaysStrict": true,
4 | "baseUrl": ".",
5 | "emitDeclarationOnly": true,
6 | "declaration": true,
7 | "declarationMap": true,
8 | "declarationDir": "./dist/types",
9 | "experimentalDecorators": true,
10 | "lib": ["dom", "ESNext"],
11 | "module": "ESNext",
12 | "moduleResolution": "node",
13 | "noImplicitThis": true,
14 | "noUnusedLocals": true,
15 | "outDir": "./dist/lib",
16 | "preserveConstEnums": true,
17 | "removeComments": true,
18 | "strictFunctionTypes": true,
19 | "strictNullChecks": true,
20 | "sourceMap": true,
21 | "target": "ESNext",
22 | "esModuleInterop": true,
23 | "skipLibCheck": true
24 | },
25 | "include": ["src/lib"]
26 | }
27 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "mergeConfidence:all-badges",
5 | "config:recommended",
6 | ":preserveSemverRanges",
7 | ":semanticCommitTypeAll(chore)"
8 | ],
9 | "lockFileMaintenance": {
10 | "enabled": true,
11 | "automerge": true
12 | },
13 | "packageRules": [
14 | {
15 | "matchUpdateTypes": [
16 | "minor",
17 | "patch"
18 | ],
19 | "matchCurrentVersion": "!/^0/",
20 | "automerge": true
21 | }
22 | ],
23 | "major": {
24 | "minimumReleaseAge": "7 days"
25 | },
26 | "minor": {
27 | "minimumReleaseAge": "3 days"
28 | },
29 | "patch": {
30 | "minimumReleaseAge": "2 days"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/sites/demo/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | extensions: ['.svelte'],
7 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors
8 | // for more information about preprocessors
9 | preprocess: [vitePreprocess()],
10 |
11 | kit: {
12 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
13 | // If your environment is not supported or you settled on a specific environment, switch out the adapter.
14 | // See https://kit.svelte.dev/docs/adapters for more information about adapters.
15 | adapter: adapter()
16 | }
17 | };
18 | export default config;
19 |
--------------------------------------------------------------------------------
/sites/demo/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import ts from 'typescript-eslint';
3 | import svelte from 'eslint-plugin-svelte';
4 | import prettier from 'eslint-config-prettier';
5 | import globals from 'globals';
6 |
7 | /** @type {import('eslint').Linter.Config[]} */
8 | export default [
9 | js.configs.recommended,
10 | ...ts.configs.recommended,
11 | ...svelte.configs['flat/recommended'],
12 | prettier,
13 | ...svelte.configs['flat/prettier'],
14 | {
15 | languageOptions: {
16 | globals: {
17 | ...globals.browser,
18 | ...globals.node
19 | }
20 | }
21 | },
22 | {
23 | files: ['**/*.svelte'],
24 | languageOptions: {
25 | parserOptions: {
26 | parser: ts.parser
27 | }
28 | }
29 | },
30 | {
31 | ignores: ['build/', '.svelte-kit/', 'dist/', '.netlify']
32 | }
33 | ];
34 |
--------------------------------------------------------------------------------
/sites/demo/src/routes/+layout.ts:
--------------------------------------------------------------------------------
1 | import type { LayoutLoad } from './$types';
2 |
3 | export const load: LayoutLoad = async () => {
4 | return {
5 | metadata: {
6 | title: 'Maplibre/Mapbox GL Export',
7 | description:
8 | 'Maplibre/Mapbox GL Export is a Maplibre/Mapbox GL JS plugin that can export a map image in various image format such as PNG, JPEG, PDF and SVG without any server!',
9 | author: 'JinIgarashi',
10 | licenses: [
11 | 'The source code is licensed MIT',
12 | 'The website content is licensed CC BY NC SA 4.0.'
13 | ]
14 | },
15 | nav: [
16 | { href: 'https://twitter.com/j_igarashi', icon: 'fa-brands fa-x-twitter' },
17 | {
18 | href: 'https://github.com/watergis/maplibre-gl-export',
19 | icon: 'fa-brands fa-github'
20 | }
21 | ]
22 | };
23 | };
24 |
25 | export const prerender = true;
26 | export const ssr = false;
27 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path';
2 | import { defineConfig } from 'vite';
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | build: {
7 | emptyOutDir: false,
8 | outDir: 'dist',
9 | assetsDir: 'assets',
10 | sourcemap: true,
11 | lib: {
12 | entry: resolve(__dirname, 'src/lib/index.ts'),
13 | name: 'MapboxExportControl',
14 | fileName: (format) => `mapbox-gl-export.${format}.js`,
15 | formats: ['es', 'umd']
16 | },
17 | rollupOptions: {
18 | external: ['mapbox-gl'], // バンドルしたくない依存関係を指定
19 | output: {
20 | globals: {
21 | 'mapbox-gl': 'mapboxgl' // UMDビルド時に、external指定した依存関係をscript タグで読み込まれた場合に使用される変数名を指定
22 | }
23 | }
24 | }
25 | },
26 | plugins: [],
27 | css: {
28 | preprocessorOptions: {
29 | scss: {
30 | api: 'modern-compiler'
31 | }
32 | }
33 | }
34 | });
35 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path';
2 | import { defineConfig } from 'vite';
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | build: {
7 | emptyOutDir: false,
8 | outDir: 'dist',
9 | assetsDir: 'assets',
10 | sourcemap: true,
11 | lib: {
12 | entry: resolve(__dirname, 'src/lib/index.ts'),
13 | name: 'MaplibreExportControl',
14 | fileName: (format) => `maplibre-gl-export.${format}.js`,
15 | formats: ['es', 'umd']
16 | },
17 | rollupOptions: {
18 | external: ['maplibre-gl'], // バンドルしたくない依存関係を指定
19 | output: {
20 | globals: {
21 | 'maplibre-gl': 'maplibregl' // UMDビルド時に、external指定した依存関係をscript タグで読み込まれた場合に使用される変数名を指定
22 | }
23 | }
24 | }
25 | },
26 | plugins: [],
27 | css: {
28 | preprocessorOptions: {
29 | scss: {
30 | api: 'modern-compiler'
31 | }
32 | }
33 | }
34 | });
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@watergis/maplibre-gl-export",
3 | "version": "1.0.0",
4 | "description": "This repository to manage plugin of maplibre-gl-export.",
5 | "scripts": {
6 | "lint": "pnpm -r lint",
7 | "format": "pnpm -r format",
8 | "build": "pnpm -r build",
9 | "release": "changeset publish"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/watergis/maplibre-gl-export.git"
14 | },
15 | "keywords": [],
16 | "author": "Jin IGARASHI",
17 | "license": "MIT",
18 | "bugs": {
19 | "url": "https://github.com/watergis/maplibre-gl-export/issues"
20 | },
21 | "homepage": "https://github.com/watergis/maplibre-gl-export#readme",
22 | "devDependencies": {
23 | "@changesets/cli": "^2.29.6",
24 | "lefthook": "^1.12.3"
25 | },
26 | "packageManager": "pnpm@10.26.1",
27 | "engines": {
28 | "pnpm": "^10.0.0"
29 | },
30 | "type": "module"
31 | }
32 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Thank you for submitting a pull request!
2 |
3 | ## Description
4 |
5 | Please describe what you changed briefly.
6 |
7 | ## Plugin
8 |
9 | Select a plugin related to this PR.
10 |
11 | - [ ] maplibre-gl-export
12 | - [ ] mapbox-gl-export
13 |
14 | ## Type of Pull Request
15 |
16 | - [ ] Adding a feature
17 | - [ ] Fixing a bug
18 | - [ ] Maintaining documents
19 | - [ ] Others ()
20 |
21 |
22 | ## Verify the followings
23 |
24 | - [ ] Code is up-to-date with the `main` branch
25 | - [ ] No lint errors after `pnpm lint`
26 | - [ ] Make sure all the existing features working well
27 | - [ ] Make sure a changeset file is added if your PR changes package code
28 |
29 |
30 | Refer to [CONTRIBUTING.MD](https://github.com/watergis/maplibre-gl-export/tree/master/CONTRIBUTING.md) for more details.
31 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/index.ts:
--------------------------------------------------------------------------------
1 | import mapboxgl from 'mapbox-gl';
2 | import { MapboxExportControl, Size, PageOrientation, Format, DPI } from './src/lib/index';
3 | import './src/scss/mapbox-gl-export.scss';
4 | import { IControl } from 'mapbox-gl';
5 |
6 | mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESSTOKEN;
7 | const map = new mapboxgl.Map({
8 | container: 'map',
9 | style: 'mapbox://styles/mapbox/standard',
10 | // style: 'https://narwassco.github.io/mapbox-stylefiles/unvt/style.json',
11 | center: [35.87063, -1.08551],
12 | zoom: 12,
13 | hash: true
14 | });
15 | map.addControl(
16 | new MapboxExportControl({
17 | PageSize: Size.A3,
18 | PageOrientation: PageOrientation.Portrait,
19 | Format: Format.PNG,
20 | DPI: DPI[96],
21 | Crosshair: true,
22 | PrintableArea: true,
23 | Local: 'en'
24 | }) as unknown as IControl,
25 | 'top-right'
26 | );
27 |
28 | new mapboxgl.Marker().setLngLat([37.30467, -0.15943]).addTo(map);
29 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example
6 |
7 |
11 |
24 |
25 |
26 | Fork me on GitHub
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/interfaces/ControlOptions.ts:
--------------------------------------------------------------------------------
1 | import { CirclePaint } from 'mapbox-gl';
2 | import { FormatType } from './Format';
3 | import { Language } from './Language';
4 | import { PageOrientationType } from './PageOrientation';
5 | import { SizeType } from './Size';
6 | import { AttributionOptions } from './AttributionStyle';
7 |
8 | export interface NorthIconOptions {
9 | image?: string;
10 | imageName?: string;
11 | imageSizeFraction?: number;
12 | visibility?: 'visible' | 'none';
13 | position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
14 | }
15 |
16 | export interface ControlOptions {
17 | PageSize?: SizeType;
18 | PageOrientation?: PageOrientationType;
19 | Format?: FormatType;
20 | DPI?: number;
21 | Crosshair?: boolean;
22 | PrintableArea?: boolean;
23 | Local?: Language;
24 | AllowedSizes?: ('LETTER' | 'A2' | 'A3' | 'A4' | 'A5' | 'A6' | 'B2' | 'B3' | 'B4' | 'B5' | 'B6')[];
25 | Filename?: string;
26 | markerCirclePaint?: CirclePaint;
27 | attributionOptions?: AttributionOptions;
28 | northIconOptions?: NorthIconOptions;
29 | }
30 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example
6 |
7 |
11 |
12 |
25 |
26 |
27 | Fork me on GitHub
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Build
5 |
6 | on:
7 | push:
8 | branches: [ main ]
9 | pull_request:
10 |
11 | env:
12 | CI: true
13 | PNPM_CACHE_FOLDER: .pnpm-store
14 |
15 | jobs:
16 | build:
17 |
18 | runs-on: ubuntu-latest
19 | strategy:
20 | matrix:
21 | node-version: [22]
22 |
23 | steps:
24 | - uses: actions/checkout@v5
25 | - uses: pnpm/action-setup@v4.2.0
26 | - name: Setup Node.js ${{ matrix.node-version }}
27 | uses: actions/setup-node@v4
28 | with:
29 | node-version: ${{ matrix.node-version }}
30 | cache: pnpm
31 | - name: install dependencies
32 | run: pnpm install --frozen-lockfile
33 |
34 | - run: pnpm lint
35 |
36 | - name: build
37 | env:
38 | PUBLIC_MAPBOX_ACCESSTOKEN: ${{ secrets.PUBLIC_MAPBOX_ACCESSTOKEN }}
39 | run:
40 | pnpm build
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Jin IGARASHI
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Jin IGARASHI
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/local/index.ts:
--------------------------------------------------------------------------------
1 | import english from './en';
2 | import french from './fr';
3 | import finnish from './fi';
4 | import german from './de';
5 | import swedish from './sv';
6 | import spanish from './es';
7 | import catalan from './ca';
8 | import vietnam from './vi';
9 | import ukranian from './uk';
10 | import zhHans from './zhHans';
11 | import zhHant from './zhHant';
12 | import ja from './ja';
13 | import pt from './pt';
14 | import ru from './ru';
15 | import { Language } from '../interfaces/Language';
16 | import { Translation } from '../interfaces/Translation';
17 |
18 | export const Languages: Translation[] = [
19 | english,
20 | french,
21 | finnish,
22 | german,
23 | swedish,
24 | spanish,
25 | catalan,
26 | vietnam,
27 | ukranian,
28 | zhHans,
29 | zhHant,
30 | ja,
31 | pt,
32 | ru
33 | ];
34 |
35 | export const AvailableLanguages = [
36 | 'en',
37 | 'fr',
38 | 'fi',
39 | 'de',
40 | 'sv',
41 | 'es',
42 | 'ca',
43 | 'vi',
44 | 'uk',
45 | 'zhHans',
46 | 'zhHant',
47 | 'ja',
48 | 'pt',
49 | 'ru'
50 | ] as const;
51 |
52 | export const getTranslation = (lang: Language) => {
53 | return Languages.find((l) => l.LanguageCode === lang) ?? english;
54 | };
55 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/index_cdn.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
30 |
31 |
32 | Fork me on GitHub
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/src/lib/export-control.ts:
--------------------------------------------------------------------------------
1 | import { IControl, Map as MapboxMap } from 'mapbox-gl';
2 | import {
3 | SizeType,
4 | DPIType,
5 | FormatType,
6 | MaplibreExportControl,
7 | UnitType,
8 | ControlOptions as MaplibreControlOptions
9 | } from '@watergis/maplibre-gl-export';
10 | import { ControlOptions } from './interfaces';
11 | import MapGenerator from './map-generator';
12 |
13 | /**
14 | * Mapbox GL Export Control.
15 | * @param {Object} targets - Object of layer.id and title
16 | */
17 | // eslint-disable-next-line
18 | // @ts-ignore
19 | export default class MapboxExportControl extends MaplibreExportControl implements IControl {
20 | private accessToken: string | undefined;
21 |
22 | constructor(options: ControlOptions) {
23 | super(options as MaplibreControlOptions);
24 | this.MAPLIB_CSS_PREFIX = 'mapboxgl';
25 | this.accessToken = options.accessToken;
26 | }
27 |
28 | // eslint-disable-next-line
29 | // @ts-ignore
30 | protected generateMap(
31 | map: MapboxMap,
32 | size: SizeType,
33 | dpi: DPIType,
34 | format: FormatType,
35 | unit: UnitType,
36 | filename?: string
37 | ) {
38 | const mapGenerator = new MapGenerator(
39 | map as MapboxMap,
40 | size,
41 | dpi,
42 | format,
43 | unit,
44 | filename,
45 | this.options.markerCirclePaint,
46 | this.options.attributionOptions,
47 | this.options.northIconOptions,
48 | this.accessToken
49 | );
50 | mapGenerator.generate();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/sites/demo/static/assets/maplibre-cdn-example.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
26 |
27 |
28 |
29 |
30 |
31 |
46 |
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # maplibre-gl-export
2 |
3 | 
4 | [](https://github.com/watergis/maplibre-gl-export/actions/workflows/build.yml)
5 | 
6 |
7 | This monorepo manages the source code and demo page for `@watergis/maplibre-gl-export` and `@watergis/mapbox-gl-export`.
8 |
9 | 
10 |
11 | ## Repositories
12 |
13 | | repository | version | description | changelog |
14 | |---|---|---|---|
15 | |[@watergis/maplibre-gl-export](./packages/maplibre-gl-export/)| [](https://www.npmjs.com/package/@watergis/mapbox-gl-export) | To manage maplibre export plugin source code|[CHANGELOG](./packages/maplibre-gl-export/CHANGELOG.md)|
16 | |[@watergis/mapbox-gl-export](./packages/mapbox-gl-export/)| [](https://www.npmjs.com/package/@watergis/maplibre-gl-export) | To manage mapbox export plugin source code|[CHANGELOG](./packages/mapbox-gl-export/CHANGELOG.md)|
17 | |[Demo & Documentation](./sites/demo/)| - | Demo website code for maplibre/mapbox export plugins |-|
18 |
19 | ## Contribution
20 |
21 | See [CONTRIBUTING](./CONTRIBUTING.md)
22 |
23 | ## License
24 |
25 | [MIT License](LICENSE)
26 |
--------------------------------------------------------------------------------
/sites/demo/static/assets/mapbox-cdn-example.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
26 |
27 |
28 |
29 |
30 |
31 |
48 |
49 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 |
7 | env:
8 | CI: true
9 | PNPM_CACHE_FOLDER: .pnpm-store
10 |
11 | jobs:
12 | release:
13 | # prevents this action from running on forks
14 | if: github.repository == 'watergis/maplibre-gl-export'
15 | permissions:
16 | contents: write # to create release (changesets/action)
17 | pull-requests: write # to create pull request (changesets/action)
18 | name: Release
19 | runs-on: ubuntu-latest
20 | strategy:
21 | matrix:
22 | node-version: [22]
23 |
24 | steps:
25 | - name: checkout code repository
26 | uses: actions/checkout@v5
27 | with:
28 | fetch-depth: 0
29 | - uses: pnpm/action-setup@v4.2.0
30 | - name: Setup Node.js ${{ matrix.node-version }}
31 | uses: actions/setup-node@v4
32 | with:
33 | node-version: ${{ matrix.node-version }}
34 | cache: pnpm
35 |
36 | - name: install dependencies
37 | run: pnpm install --frozen-lockfile
38 |
39 | - name: build packages
40 | run: |
41 | pnpm --filter="./packages/**" build
42 |
43 | - name: Create Release Pull Request or Publish to npm
44 | id: changesets
45 | uses: changesets/action@v1
46 | with:
47 | # This expects you to have a script called release which does a build for your packages and calls changeset publish
48 | publish: pnpm release
49 | env:
50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51 | NPM_TOKEN: ${{ secrets.npm_token }}
52 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: "New Feature Request"
2 | description: "Suggest an idea for this project"
3 | title: "[FEATURE] "
4 | labels: [enhancement]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | Thanks for taking the time to fill out this feature request! Before filling this feature request, please make sure that:
11 | - You have the latest version of the project.
12 | - You have searched the [issues](https://github.com/UNDP-Data/geohub/issues) for similar feature requests.
13 |
14 | - type: dropdown
15 | id: plugins
16 | attributes:
17 | label: Select plugins which related to this issue
18 | multiple: true
19 | options:
20 | - maplibre-gl-export
21 | - mapbox-gl-export
22 | - Documentation
23 | validations:
24 | required: true
25 |
26 | - type: textarea
27 | id: feature-description
28 | attributes:
29 | label: "Feature Description"
30 | description: "A clear and concise description of what the feature is."
31 | validations:
32 | required: true
33 |
34 | - type: textarea
35 | id: feature-implementation
36 | attributes:
37 | label: "Feature Implementation"
38 | description: "A clear and concise description of how the feature should be implemented."
39 | validations:
40 | required: false
41 |
42 | - type: textarea
43 | id: feature-alternatives
44 | attributes:
45 | label: "Feature Alternatives"
46 | description: "A clear and concise description of any alternative solutions or features you've considered."
47 | validations:
48 | required: false
49 |
--------------------------------------------------------------------------------
/sites/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite dev",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
11 | "lint": "prettier --check . && eslint .",
12 | "format": "prettier --write ."
13 | },
14 | "devDependencies": {
15 | "@eslint/js": "^9.34.0",
16 | "@skeletonlabs/skeleton": "2.11.0",
17 | "@skeletonlabs/tw-plugin": "0.4.1",
18 | "@sveltejs/adapter-auto": "^6.1.0",
19 | "@sveltejs/kit": "^2.37.0",
20 | "@sveltejs/vite-plugin-svelte": "^6.1.3",
21 | "@tailwindcss/forms": "0.5.10",
22 | "@tailwindcss/typography": "0.5.16",
23 | "@types/eslint": "^9.6.1",
24 | "@types/mapbox-gl": "^3.4.1",
25 | "@types/node": "22.19.3",
26 | "@typescript-eslint/eslint-plugin": "^8.41.0",
27 | "@typescript-eslint/parser": "^8.41.0",
28 | "@watergis/mapbox-gl-export": "workspace:^",
29 | "@watergis/maplibre-gl-export": "workspace:^",
30 | "autoprefixer": "10.4.23",
31 | "eslint": "^9.34.0",
32 | "eslint-config-prettier": "^10.1.8",
33 | "eslint-plugin-svelte": "^3.11.0",
34 | "globals": "^16.3.0",
35 | "mapbox-gl": "^3.14.0",
36 | "maplibre-gl": "^5.7.0",
37 | "pmtiles": "^4.3.0",
38 | "postcss": "8.5.6",
39 | "prettier": "^3.6.2",
40 | "prettier-plugin-svelte": "^3.4.0",
41 | "svelte": "^5.38.6",
42 | "svelte-check": "^4.3.1",
43 | "tailwindcss": "3.4.19",
44 | "tslib": "^2.8.1",
45 | "typescript": "^5.9.2",
46 | "typescript-eslint": "^8.41.0",
47 | "vite": "^7.1.3",
48 | "vite-plugin-tailwind-purgecss": "0.3.5"
49 | },
50 | "type": "module",
51 | "dependencies": {
52 | "highlight.js": "11.11.1"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/index_cdn.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
30 |
31 |
32 | Fork me on GitHub
39 |
40 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | addProtocol,
3 | AttributionControl,
4 | Map,
5 | Marker,
6 | NavigationControl,
7 | TerrainControl
8 | } from 'maplibre-gl';
9 | import 'maplibre-gl/dist/maplibre-gl.css';
10 | import { MaplibreExportControl, Size, PageOrientation, Format, DPI } from './src/lib/index';
11 | import './src/scss/maplibre-gl-export.scss';
12 | import { Protocol } from 'pmtiles';
13 |
14 | const protocol = new Protocol();
15 | addProtocol('pmtiles', protocol.tile);
16 |
17 | const map = new Map({
18 | container: 'map',
19 | // narok vector style
20 | // style: 'https://narwassco.github.io/mapbox-stylefiles/unvt/style.json',
21 | // center: [35.87063, -1.08551],
22 | // zoom: 12,
23 | // terrain testing with Bing aerial
24 | style: 'https://narwassco.github.io/mapbox-stylefiles/unvt/style-aerial.json',
25 | center: [0, 0],
26 | zoom: 1,
27 | hash: true,
28 | attributionControl: false
29 | });
30 |
31 | map.addControl(new NavigationControl({ visualizePitch: true }), 'top-right');
32 | map.addControl(new AttributionControl({ compact: false }), 'bottom-right');
33 | map.addControl(
34 | new MaplibreExportControl({
35 | PageSize: Size.A3,
36 | PageOrientation: PageOrientation.Portrait,
37 | Format: Format.PNG,
38 | DPI: DPI[96],
39 | Crosshair: true,
40 | PrintableArea: true,
41 | Local: 'en',
42 | attributionOptions: {
43 | position: 'bottom-right',
44 | visibility: 'visible'
45 | },
46 | northIconOptions: {
47 | position: 'top-right',
48 | visibility: 'visible'
49 | }
50 | }),
51 | 'top-right'
52 | );
53 |
54 | map.once('load', () => {
55 | new Marker().setLngLat([37.30467, -0.15943]).addTo(map);
56 |
57 | if (map.getSource('terrarium')) {
58 | map.addControl(
59 | new TerrainControl({
60 | source: 'terrarium',
61 | exaggeration: 1
62 | }),
63 | 'top-right'
64 | );
65 |
66 | map.setMaxPitch(85);
67 | }
68 | });
69 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: "Bug Report"
2 | description: Create a report to help us improve
3 | title: "[BUG] "
4 | labels: [bug]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | Thanks for taking the time to fill out this bug report! Before filling this feature request, please make sure that:
11 | - You have the latest version of the project.
12 | - You have searched the [issues](https://github.com/UNDP-Data/geohub/issues) for similar feature requests.
13 |
14 | - type: dropdown
15 | id: plugins
16 | attributes:
17 | label: Select plugins which related to this issue
18 | multiple: true
19 | options:
20 | - maplibre-gl-export
21 | - mapbox-gl-export
22 | - Documentation
23 | validations:
24 | required: true
25 |
26 | - type: textarea
27 | id: bug-description
28 | attributes:
29 | label: "Bug Description"
30 | description: "A clear and concise description of what the bug is."
31 | validations:
32 | required: true
33 | - type: textarea
34 | id: bug-reproduction
35 | attributes:
36 | label: "Bug Reproduction"
37 | description:
38 | A clear and concise description of how to reproduce the bug.
39 |
40 | 1. Go to '...'
41 | 2. Click on '....'
42 | 3. Scroll down to '....'
43 | 4. See error
44 | validations:
45 | required: true
46 | - type: textarea
47 | id: bug-screenshots
48 | attributes:
49 | label: "Bug Screenshots"
50 | description: "If applicable, add screenshots to help explain your problem."
51 | validations:
52 | required: false
53 | - type: textarea
54 | id: bug-system-information
55 | attributes:
56 | label: "Bug System Information"
57 | description: |
58 | If applicable, add information about your system configuration.
59 | value: |
60 | - OS: [e.g. Windows 10]
61 | - Browser: [e.g. Google Chrome, Firefox]
62 | validations:
63 | required: false
64 |
65 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | This document contains a set of guidelines to help developers during the contribution process.
4 |
5 | ## Development
6 |
7 | ### Download and install dependencies
8 |
9 | ```shell
10 | git clone git@github.com:watergis/maplibre-gl-export.git
11 | cd maplibre-gl-export
12 | pnpm i
13 | ```
14 |
15 | When you download the repository first time, please also install `lefthook` by the following command.
16 |
17 | ```shell
18 | pnpm lefthook install
19 | ```
20 |
21 | ### Run locally
22 |
23 | - maplibre-gl-export
24 |
25 | ```shell
26 | cd packages/maplibre-gl-export
27 | pnpm dev
28 | ```
29 |
30 | - mapbox-gl-export
31 |
32 | ```shell
33 | cd packages/mapbox-gl-export
34 | pnpm dev
35 | ```
36 |
37 | Note. Most of common features are imported from `maplibre-gl-export` plugin
38 |
39 | - Demo documentation
40 |
41 | ```shell
42 | # build maplibre-gl-export and mapbox-gl-export at the root folder of the repository
43 | pnpm build
44 | cd sites/demo
45 | pnpm dev
46 | ```
47 |
48 | ### Build packages
49 |
50 | Both packages and documentation are built by the below command.
51 |
52 | ```shell
53 | pnpm build
54 | ```
55 |
56 | ## Release packages
57 |
58 | It uses changeset to release. Please create release notes by the following command. Changeset will release package once the PR is merged into main branch.
59 |
60 | ```zsh
61 | pnpm changeset
62 | ```
63 |
64 | ## How to make a pull request
65 |
66 | 1. [Fork the repo](https://help.github.com/articles/fork-a-repo) and create a branch for your new feature or bug fix.
67 |
68 | 2. Install NPM packages by `pnpm install && pnpm lefthook install`.
69 |
70 | 3. Run the plugins to check all of plugin's features work well. Run: `pnpm dev`
71 |
72 | 4. Make sure you submit a change specific to exactly one issue. If you have ideas for multiple changes please create separate pull requests.
73 |
74 | 5. Make prettier pass. Run: `pnpm format`
75 |
76 | 6. Make eslint pass. Run: `pnpm lint`
77 |
78 | 7. Add a changeset file by following [this](#release)
79 |
80 | 8. Commit local changes in git. Run: `git add . && git commit -m "precise commit message"
81 |
82 | 9. Push local branch to your forked remote repository.
83 |
84 | 10. Push to your fork and [submit a pull request](https://help.github.com/articles/using-pull-requests). A button should appear on your fork its github page afterwards.
85 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/src/scss/mapbox-gl-export.scss:
--------------------------------------------------------------------------------
1 | .mapboxgl-export-list {
2 | display: none;
3 | }
4 |
5 | .mapboxgl-ctrl-group .mapboxgl-export-list button {
6 | background: none;
7 | border: none;
8 | cursor: pointer;
9 | display: block;
10 | font-size: 14px;
11 | padding: 8px 8px 6px;
12 | text-align: right;
13 | width: 100%;
14 | height: auto;
15 | text-align: center;
16 | }
17 |
18 | .mapboxgl-export-list button.active {
19 | font-weight: bold;
20 | }
21 |
22 | .mapboxgl-export-list button:hover {
23 | background-color: rgba(0, 0, 0, 0.05);
24 | }
25 |
26 | .mapboxgl-export-list button + button {
27 | border-top: 1px solid #ddd;
28 | }
29 |
30 | .mapboxgl-export-control {
31 | background: url('data:image/svg+xml;charset=UTF-8,');
32 | background-position: center;
33 | background-repeat: no-repeat;
34 | background-size: 70%;
35 | }
36 |
37 | /*
38 | * Hide high-res map rendering
39 | */
40 | .hidden-map {
41 | overflow: hidden;
42 | height: 0;
43 | width: 0;
44 | position: fixed;
45 | }
46 |
47 | .map-export-loader {
48 | &.is-active {
49 | position: absolute;
50 | z-index: 100;
51 | top: 50%;
52 | left: 50%;
53 | transform: translate(-50%, -50%);
54 |
55 | width: 48px;
56 | height: 48px;
57 | border: 5px solid #ffffff;
58 | border-bottom-color: transparent;
59 | border-radius: 50%;
60 | display: inline-block;
61 | box-sizing: border-box;
62 | animation: rotation 1s linear infinite;
63 | }
64 |
65 | @keyframes rotation {
66 | 0% {
67 | transform: rotate(0deg);
68 | }
69 | 100% {
70 | transform: rotate(360deg);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/scss/maplibre-gl-export.scss:
--------------------------------------------------------------------------------
1 | .maplibregl-export-list {
2 | display: none;
3 | }
4 |
5 | .maplibregl-ctrl-group .maplibregl-export-list button {
6 | background: none;
7 | border: none;
8 | cursor: pointer;
9 | display: block;
10 | font-size: 14px;
11 | padding: 8px 8px 6px;
12 | text-align: right;
13 | width: 100%;
14 | height: auto;
15 | text-align: center;
16 | }
17 |
18 | .maplibregl-export-list button.active {
19 | font-weight: bold;
20 | }
21 |
22 | .maplibregl-export-list button:hover {
23 | background-color: rgba(0, 0, 0, 0.05);
24 | }
25 |
26 | .maplibregl-export-list button + button {
27 | border-top: 1px solid #ddd;
28 | }
29 |
30 | .maplibregl-export-control {
31 | background: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' id='Capa_1' height='512' width='512' fill='%23333' viewBox='0 0 512 512'%3E%3Cpath d='m422.5 99v-24c0-41.355-33.645-75-75-75h-184c-41.355 0-75 33.645-75 75v24z'/>
2 | import mapboxgl, { Map, Marker, NavigationControl } from 'mapbox-gl';
3 | import {
4 | MapboxExportControl,
5 | Size,
6 | PageOrientation,
7 | Format,
8 | DPI,
9 | type Language
10 | } from '@watergis/mapbox-gl-export';
11 | import { onMount } from 'svelte';
12 | import '@watergis/mapbox-gl-export/dist/mapbox-gl-export.css';
13 | import { PUBLIC_MAPBOX_ACCESSTOKEN } from '$env/static/public';
14 | import 'mapbox-gl/dist/mapbox-gl.css';
15 | import LanguageSelector from '$lib/LanguageSelector.svelte';
16 | import { page } from '$app/stores';
17 |
18 | let map: Map | undefined = $state();
19 | let language: Language = $state(($page.url.searchParams.get('language') as Language) ?? 'en');
20 | let mapContainer: HTMLDivElement | undefined = $state();
21 |
22 | let exportControl: MapboxExportControl;
23 |
24 | const initExportControl = () => {
25 | if (!map) return;
26 | if (!language) return;
27 | if (exportControl) {
28 | map.removeControl(exportControl);
29 | }
30 |
31 | exportControl = new MapboxExportControl({
32 | PageSize: Size.A3,
33 | PageOrientation: PageOrientation.Portrait,
34 | Format: Format.PNG,
35 | DPI: DPI[96],
36 | Crosshair: true,
37 | PrintableArea: true,
38 | Local: language
39 | });
40 |
41 | map.addControl(exportControl, 'top-right');
42 | };
43 |
44 | onMount(() => {
45 | if (!mapContainer) return;
46 | mapboxgl.accessToken = PUBLIC_MAPBOX_ACCESSTOKEN;
47 | map = new Map({
48 | container: mapContainer,
49 | style: 'mapbox://styles/mapbox/standard',
50 | // style: 'https://narwassco.github.io/mapbox-stylefiles/unvt/style.json',
51 | center: [0, 0],
52 | zoom: 1,
53 | hash: true,
54 | accessToken: PUBLIC_MAPBOX_ACCESSTOKEN
55 | });
56 |
57 | (map as Map).addControl(new NavigationControl(), 'bottom-right');
58 |
59 | new Marker().setLngLat([37.30467, -0.15943]).addTo(map);
60 | new Marker().setLngLat([30.0824, -1.9385]).addTo(map);
61 |
62 | initExportControl();
63 | });
64 |
65 | const onLanguageChange = (lang: Language) => {
66 | language = lang;
67 | initExportControl();
68 | };
69 |
70 |
71 |
72 |
73 | {#if map}
74 |
75 | {/if}
76 |
77 |
84 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@watergis/mapbox-gl-export",
3 | "version": "3.6.0",
4 | "description": "This module adds control which can export PDF and images.",
5 | "type": "module",
6 | "main": "dist/mapbox-gl-export.umd.js",
7 | "types": "./dist/types/index.d.ts",
8 | "module": "dist/mapbox-gl-export.es.js",
9 | "exports": {
10 | ".": {
11 | "types": "./dist/types/index.d.ts",
12 | "import": "./dist/mapbox-gl-export.es.js",
13 | "require": "./dist/mapbox-gl-export.umd.js"
14 | },
15 | "./dist/mapbox-gl-export.css": {
16 | "import": "./dist/mapbox-gl-export.css",
17 | "require": "./dist/mapbox-gl-export.css"
18 | }
19 | },
20 | "files": [
21 | "dist",
22 | "package.json"
23 | ],
24 | "scripts": {
25 | "dev": "vite",
26 | "build": "npm run build:js && npm run build:css",
27 | "build:js": "tsc && vite build",
28 | "build:scss": "sass --no-source-map --style=compressed src/scss/mapbox-gl-export.scss:dist/mapbox-gl-export.css",
29 | "build:postcss": "postcss dist/*.css -r",
30 | "build:css": "npm run build:scss && npm run build:postcss",
31 | "preview": "vite preview",
32 | "lint": "prettier --check . && eslint .",
33 | "format": "prettier --write ."
34 | },
35 | "keywords": [
36 | "mapbox",
37 | "mapbox-gl-js",
38 | "export",
39 | "image",
40 | "pdf"
41 | ],
42 | "repository": {
43 | "type": "git",
44 | "url": "git+https://github.com/watergis/maplibre-gl-export.git"
45 | },
46 | "author": "Jin IGARASHI",
47 | "license": "MIT",
48 | "bugs": {
49 | "url": "https://github.com/watergis/maplibre-gl-export/issues"
50 | },
51 | "homepage": "https://github.com/watergis/maplibre-gl-export#readme",
52 | "devDependencies": {
53 | "@eslint/js": "^9.34.0",
54 | "@types/geojson": "^7946.0.16",
55 | "@types/mapbox-gl": "^3.4.1",
56 | "@types/node": "^22.18.0",
57 | "@typescript-eslint/eslint-plugin": "^8.41.0",
58 | "@typescript-eslint/parser": "^8.41.0",
59 | "@watergis/maplibre-gl-export": "workspace:^",
60 | "autoprefixer": "^10.4.21",
61 | "eslint": "^9.34.0",
62 | "eslint-config-prettier": "^10.1.8",
63 | "eslint-plugin-import": "^2.32.0",
64 | "globals": "^16.3.0",
65 | "path": "^0.12.7",
66 | "postcss": "^8.5.6",
67 | "postcss-cli": "^11.0.1",
68 | "prettier": "^3.6.2",
69 | "sass": "^1.91.0",
70 | "sass-loader": "^16.0.5",
71 | "typescript": "^5.9.2",
72 | "typescript-eslint": "^8.41.0",
73 | "vite": "^7.1.3"
74 | },
75 | "dependencies": {
76 | "mapbox-gl": "^3.14.0"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@watergis/maplibre-gl-export",
3 | "version": "4.1.0",
4 | "description": "This module adds control which can export PDF and images.",
5 | "type": "module",
6 | "main": "dist/maplibre-gl-export.umd.js",
7 | "types": "./dist/types/index.d.ts",
8 | "module": "dist/maplibre-gl-export.es.js",
9 | "exports": {
10 | ".": {
11 | "types": "./dist/types/index.d.ts",
12 | "import": "./dist/maplibre-gl-export.es.js",
13 | "require": "./dist/maplibre-gl-export.umd.js"
14 | },
15 | "./dist/maplibre-gl-export.css": {
16 | "import": "./dist/maplibre-gl-export.css",
17 | "require": "./dist/maplibre-gl-export.css"
18 | }
19 | },
20 | "files": [
21 | "dist",
22 | "package.json"
23 | ],
24 | "scripts": {
25 | "dev": "vite",
26 | "build": "npm run build:js && npm run build:css",
27 | "build:js": "tsc && vite build",
28 | "build:scss": "sass --no-source-map --style=compressed src/scss/maplibre-gl-export.scss:dist/maplibre-gl-export.css",
29 | "build:postcss": "postcss dist/*.css -r",
30 | "build:css": "npm run build:scss && npm run build:postcss",
31 | "preview": "vite preview",
32 | "lint": "prettier --check . && eslint .",
33 | "format": "prettier --write .",
34 | "deploy": "gh-pages -d dist -m 'deploy to gh-pages'"
35 | },
36 | "keywords": [
37 | "mapbox",
38 | "mapbox-gl-js",
39 | "export",
40 | "image",
41 | "pdf"
42 | ],
43 | "repository": {
44 | "type": "git",
45 | "url": "git+https://github.com/watergis/maplibre-gl-export.git"
46 | },
47 | "author": "Jin IGARASHI",
48 | "license": "MIT",
49 | "bugs": {
50 | "url": "https://github.com/watergis/maplibre-gl-export/issues"
51 | },
52 | "homepage": "https://github.com/watergis/maplibre-gl-export/tree/main/packages/maplibre-gl-export#readme",
53 | "devDependencies": {
54 | "@eslint/js": "^9.34.0",
55 | "@types/geojson": "^7946.0.16",
56 | "@types/mapbox-gl": "^3.4.1",
57 | "@types/node": "^22.18.0",
58 | "@typescript-eslint/eslint-plugin": "^8.41.0",
59 | "@typescript-eslint/parser": "^8.41.0",
60 | "autoprefixer": "^10.4.21",
61 | "eslint": "^9.34.0",
62 | "eslint-config-prettier": "^10.1.8",
63 | "eslint-plugin-import": "^2.32.0",
64 | "globals": "^16.3.0",
65 | "path": "^0.12.7",
66 | "pmtiles": "^4.3.0",
67 | "postcss": "^8.5.6",
68 | "postcss-cli": "^11.0.1",
69 | "prettier": "^3.6.2",
70 | "sass": "^1.91.0",
71 | "sass-loader": "^16.0.5",
72 | "typescript": "^5.9.2",
73 | "typescript-eslint": "^8.41.0",
74 | "vite": "^7.1.3"
75 | },
76 | "dependencies": {
77 | "jspdf": "^3.0.2",
78 | "maplibre-gl": "^5.7.0"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/sites/demo/src/lib/LanguageSelector.svelte:
--------------------------------------------------------------------------------
1 |
37 |
38 |
75 |
76 |
80 | {#each AvailableLanguages as lang (lang)}
81 |
89 | {/each}
90 |
91 |
92 |
102 |
--------------------------------------------------------------------------------
/sites/demo/src/routes/maplibre/+page.svelte:
--------------------------------------------------------------------------------
1 |
89 |
90 |
91 |
92 | {#if map}
93 |
94 | {/if}
95 |
96 |
103 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/map-generator.ts:
--------------------------------------------------------------------------------
1 | import { Map as MaplibreMap, StyleSpecification } from 'maplibre-gl';
2 | import { DPIType, Format, FormatType, Size, SizeType, Unit, UnitType } from './interfaces';
3 | import {
4 | MapGeneratorBase,
5 | defaultAttributionOptions,
6 | defaultMarkerCirclePaint,
7 | defaultNorthIconOptions
8 | } from './map-generator-base';
9 |
10 | export default class MapGenerator extends MapGeneratorBase {
11 | /**
12 | * Constructor
13 | * @param map MaplibreMap object
14 | * @param size layout size. default is A4
15 | * @param dpi dpi value. default is 300
16 | * @param format image format. default is PNG
17 | * @param unit length unit. default is mm
18 | * @param fileName file name. default is 'map'
19 | */
20 | constructor(
21 | map: MaplibreMap,
22 | size: SizeType = Size.A4,
23 | dpi: DPIType = 300,
24 | format: FormatType = Format.PNG,
25 | unit: UnitType = Unit.mm,
26 | fileName = 'map',
27 | markerCirclePaint = defaultMarkerCirclePaint,
28 | attributionOptions = defaultAttributionOptions,
29 | northIconOptions = defaultNorthIconOptions
30 | ) {
31 | super(
32 | map,
33 | size,
34 | dpi,
35 | format,
36 | unit,
37 | fileName,
38 | 'maplibregl-marker',
39 | markerCirclePaint,
40 | 'maplibregl-ctrl-attrib-inner',
41 | attributionOptions,
42 | northIconOptions
43 | );
44 | }
45 |
46 | protected getRenderedMap(container: HTMLElement, style: StyleSpecification) {
47 | // Render map
48 | const renderMap: MaplibreMap = new MaplibreMap({
49 | container,
50 | style,
51 | center: this.map.getCenter(),
52 | zoom: this.map.getZoom(),
53 | bearing: this.map.getBearing(),
54 | pitch: this.map.getPitch(),
55 | interactive: false,
56 | canvasContextAttributes: {
57 | preserveDrawingBuffer: true
58 | },
59 | fadeDuration: 0,
60 | // attributionControl: false,
61 | // hack to read transform request callback function
62 | // eslint-disable-next-line
63 | // @ts-ignore
64 | transformRequest: (this.map as unknown)._requestManager._transformRequestFn
65 | });
66 |
67 | const terrain = (this.map as MaplibreMap).getTerrain();
68 | if (terrain) {
69 | // if terrain is enabled, restore pitch correctly
70 | renderMap.setMaxPitch(85);
71 | renderMap.setPitch(this.map.getPitch());
72 | }
73 |
74 | // the below code was added by https://github.com/watergis/maplibre-gl-export/pull/18.
75 | const images = ((this.map as MaplibreMap).style.imageManager || {}).images || [];
76 | Object.keys(images).forEach((key) => {
77 | if (!images[key].data) return;
78 | renderMap.addImage(key, images[key].data);
79 | });
80 |
81 | return renderMap;
82 | }
83 |
84 | protected renderMapPost(renderMap: MaplibreMap) {
85 | const terrain = (this.map as MaplibreMap).getTerrain();
86 | if (terrain) {
87 | // if terrain is enabled, set terrain for rendered map object
88 | renderMap.setTerrain({
89 | source: terrain.source,
90 | exaggeration: terrain.exaggeration
91 | });
92 | }
93 |
94 | return renderMap;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/printable-area-manager.ts:
--------------------------------------------------------------------------------
1 | import { type Map as MaplibreMap } from 'maplibre-gl';
2 | import { type Map as MapboxMap } from 'mapbox-gl';
3 | import { Unit } from './interfaces';
4 |
5 | export default class PrintableAreaManager {
6 | private map: MaplibreMap | MapboxMap | undefined;
7 |
8 | private width: number;
9 |
10 | private height: number;
11 |
12 | private unit: string;
13 |
14 | private svgCanvas: SVGElement | undefined;
15 |
16 | private svgPath: SVGElement | undefined;
17 |
18 | constructor(map: MaplibreMap | MapboxMap | undefined) {
19 | this.map = map;
20 | if (this.map === undefined) {
21 | return;
22 | }
23 | this.mapResize = this.mapResize.bind(this);
24 | this.map.on('resize', this.mapResize);
25 | const clientWidth = this.map?.getCanvas().clientWidth;
26 | const clientHeight = this.map?.getCanvas().clientHeight;
27 | const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
28 | svg.style.position = 'absolute';
29 | svg.style.top = '0px';
30 | svg.style.left = '0px';
31 | svg.setAttribute('width', `${clientWidth}px`);
32 | svg.setAttribute('height', `${clientHeight}px`);
33 | const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
34 | path.setAttribute('style', 'fill:#888888;stroke-width:0');
35 | path.setAttribute('fill-opacity', '0.5');
36 | svg.append(path);
37 | this.map?.getCanvasContainer().appendChild(svg);
38 | this.svgCanvas = svg;
39 | this.svgPath = path;
40 | }
41 |
42 | private mapResize() {
43 | this.generateCutOut();
44 | }
45 |
46 | public updateArea(width: number, height: number) {
47 | this.width = width;
48 | this.height = height;
49 | this.unit = Unit.mm;
50 | this.generateCutOut();
51 | }
52 |
53 | private generateCutOut() {
54 | if (this.map === undefined || this.svgCanvas === undefined || this.svgPath === undefined) {
55 | return;
56 | }
57 | const width = this.toPixels(this.width);
58 | const height = this.toPixels(this.height);
59 | const clientWidth = this.map?.getCanvas().clientWidth;
60 | const clientHeight = this.map?.getCanvas().clientHeight;
61 | const startX = clientWidth / 2 - width / 2;
62 | const endX = startX + width;
63 | const startY = clientHeight / 2 - height / 2;
64 | const endY = startY + height;
65 |
66 | this.svgCanvas.setAttribute('width', `${clientWidth}px`);
67 | this.svgCanvas.setAttribute('height', `${clientHeight}px`);
68 | this.svgPath.setAttribute(
69 | 'd',
70 | `M 0 0 L ${clientWidth} 0 L ${clientWidth} ${clientHeight} L 0 ${clientHeight} M ${startX} ${startY} L ${startX} ${endY} L ${endX} ${endY} L ${endX} ${startY}`
71 | );
72 | }
73 |
74 | public destroy() {
75 | if (this.svgCanvas !== undefined) {
76 | this.svgCanvas.remove();
77 | this.svgCanvas = undefined;
78 | }
79 |
80 | if (this.map !== undefined) {
81 | this.map = undefined;
82 | }
83 | }
84 |
85 | /**
86 | * Convert mm/inch to pixel
87 | * @param length mm/inch length
88 | * @param conversionFactor DPI value. default is 96.
89 | */
90 | private toPixels(length: number, conversionFactor = 96) {
91 | if (this.unit === Unit.mm) {
92 | conversionFactor /= 25.4;
93 | }
94 | return conversionFactor * length;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/src/lib/map-generator.ts:
--------------------------------------------------------------------------------
1 | import mapboxgl, { Map as MapboxMap } from 'mapbox-gl';
2 | import {
3 | defaultAttributionOptions,
4 | defaultMarkerCirclePaint,
5 | defaultNorthIconOptions,
6 | DPIType,
7 | Format,
8 | FormatType,
9 | MapGeneratorBase,
10 | Size,
11 | SizeType,
12 | Unit,
13 | UnitType
14 | } from '@watergis/maplibre-gl-export';
15 |
16 | export default class MapGenerator extends MapGeneratorBase {
17 | private accesstoken: string | undefined;
18 |
19 | /**
20 | * Constructor
21 | * @param map MapboxMap object
22 | * @param size layout size. default is A4
23 | * @param dpi dpi value. default is 300
24 | * @param format image format. default is PNG
25 | * @param unit length unit. default is mm
26 | * @param fileName file name. default is 'map'
27 | */
28 | constructor(
29 | map: MapboxMap,
30 | size: SizeType = Size.A4,
31 | dpi: DPIType = 300,
32 | format: FormatType = Format.PNG,
33 | unit: UnitType = Unit.mm,
34 | fileName = 'map',
35 | markerCirclePaint = defaultMarkerCirclePaint,
36 | attributionOptions = defaultAttributionOptions,
37 | northIconOptions = defaultNorthIconOptions,
38 | accesstoken?: string
39 | ) {
40 | super(
41 | // eslint-disable-next-line
42 | // @ts-ignore
43 | map,
44 | size,
45 | dpi,
46 | format,
47 | unit,
48 | fileName,
49 | 'mapboxgl-marker',
50 | markerCirclePaint,
51 | 'mapboxgl-ctrl-attrib-inner',
52 | attributionOptions,
53 | northIconOptions
54 | );
55 | this.accesstoken = accesstoken;
56 | }
57 |
58 | /**
59 | * This function is required to solve an error of Converting circular structure to JSON in mapbox
60 | */
61 | private stringify(obj) {
62 | let cache = [];
63 | const str = JSON.stringify(obj, function (key, value) {
64 | if (typeof value === 'object' && value !== null) {
65 | // eslint-disable-next-line
66 | // @ts-ignore
67 | if (cache.indexOf(value) !== -1) {
68 | // Circular reference found, discard key
69 | return;
70 | }
71 | // Store value in our collection
72 | // eslint-disable-next-line
73 | // @ts-ignore
74 | cache.push(value);
75 | }
76 | return value;
77 | });
78 | // eslint-disable-next-line
79 | // @ts-ignore
80 | cache = null; // reset the cache
81 | return str;
82 | }
83 |
84 | // eslint-disable-next-line
85 | // @ts-ignore
86 | protected getRenderedMap(container: HTMLElement, style: mapboxgl.Style) {
87 | const s = this.stringify(style);
88 | // Render map
89 | const renderMap = new MapboxMap({
90 | accessToken: this.accesstoken || (mapboxgl.accessToken as string),
91 | container,
92 | style: JSON.parse(s),
93 | center: this.map.getCenter(),
94 | zoom: this.map.getZoom(),
95 | bearing: this.map.getBearing(),
96 | pitch: this.map.getPitch(),
97 | interactive: false,
98 | preserveDrawingBuffer: true,
99 | fadeDuration: 0,
100 | // attributionControl: false,
101 | // hack to read transform request callback function
102 | // eslint-disable-next-line
103 | // @ts-ignore
104 | transformRequest: (this.map as unknown)._requestManager._transformRequestFn
105 | });
106 |
107 | // eslint-disable-next-line
108 | // @ts-ignore
109 | const images = (this.map.style.imageManager || {}).images || [];
110 | if (images && Object.keys(images)?.length > 0) {
111 | Object.keys(images).forEach((key) => {
112 | if (!key) return;
113 | if (!images[key].data) return;
114 | renderMap.addImage(key, images[key].data);
115 | });
116 | }
117 |
118 | return renderMap;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/crosshair-manager.ts:
--------------------------------------------------------------------------------
1 | import { type Map as MaplibreMap } from 'maplibre-gl';
2 | import { type Map as MapboxMap } from 'mapbox-gl';
3 |
4 | export default class CrosshairManager {
5 | private map: MaplibreMap | MapboxMap | undefined;
6 |
7 | private width: number | undefined;
8 |
9 | private height: number | undefined;
10 |
11 | private svgCanvas: SVGElement | undefined;
12 |
13 | private xLine: SVGElement | undefined;
14 |
15 | private yLine: SVGElement | undefined;
16 |
17 | private color = '#535353';
18 |
19 | constructor(map: MaplibreMap | MapboxMap | undefined) {
20 | this.map = map;
21 | this.mapResize = this.mapResize.bind(this);
22 | }
23 |
24 | public create() {
25 | this.updateValues();
26 | if (this.map !== undefined) {
27 | this.map.on('resize', this.mapResize);
28 | this.createCanvas(this.map.getCanvasContainer());
29 | } else {
30 | console.error('map object is null');
31 | }
32 | }
33 |
34 | private updateValues() {
35 | this.width = this.map?.getCanvas().clientWidth;
36 | this.height = this.map?.getCanvas().clientHeight;
37 | }
38 |
39 | private mapResize() {
40 | this.updateValues();
41 | this.updateCanvas();
42 | }
43 |
44 | private updateCanvas() {
45 | if (
46 | this.svgCanvas !== undefined &&
47 | this.yLine !== undefined &&
48 | this.xLine !== undefined &&
49 | this.width !== undefined &&
50 | this.height !== undefined
51 | ) {
52 | this.svgCanvas.setAttribute('width', `${this.width}px`);
53 | this.svgCanvas.setAttribute('height', `${this.height}px`);
54 | const halfWidth = this.width / 2;
55 | const halfHeight = this.height / 2;
56 | this.yLine.setAttribute('x1', `${halfWidth}px`);
57 | this.yLine.setAttribute('y1', '0px');
58 | this.yLine.setAttribute('x2', `${halfWidth}px`);
59 | this.yLine.setAttribute('y2', `${this.height}px`);
60 |
61 | this.xLine.setAttribute('x1', `${0}px`);
62 | this.xLine.setAttribute('y1', `${halfHeight}px`);
63 | this.xLine.setAttribute('x2', `${this.width}px`);
64 | this.xLine.setAttribute('y2', `${halfHeight}px`);
65 | } else {
66 | console.error('element value is null');
67 | }
68 | }
69 |
70 | private createCanvas(container) {
71 | if (this.width !== undefined && this.height !== undefined) {
72 | const canvas = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
73 | canvas.style.position = 'relative';
74 | canvas.setAttribute('width', `${this.width}px`);
75 | canvas.setAttribute('height', `${this.height}px`);
76 | const halfWidth = this.width / 2;
77 | const halfHeight = this.height / 2;
78 | this.yLine = canvas.appendChild(
79 | this.createLine(halfWidth, 0, halfWidth, this.height, this.color, '2px')
80 | );
81 | this.xLine = canvas.appendChild(
82 | this.createLine(0, halfHeight, this.width, halfHeight, this.color, '2px')
83 | );
84 | container?.appendChild(canvas);
85 | this.svgCanvas = canvas;
86 | }
87 | }
88 |
89 | private createLine(x1, y1, x2, y2, color, w) {
90 | const aLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
91 | aLine.setAttribute('x1', x1);
92 | aLine.setAttribute('y1', y1);
93 | aLine.setAttribute('x2', x2);
94 | aLine.setAttribute('y2', y2);
95 | aLine.setAttribute('stroke-dasharray', '5,5');
96 | aLine.setAttribute('stroke', color);
97 | aLine.setAttribute('stroke-width', w);
98 | return aLine;
99 | }
100 |
101 | public destroy() {
102 | if (this.xLine !== undefined) {
103 | this.xLine.remove();
104 | this.xLine = undefined;
105 | }
106 |
107 | if (this.yLine !== undefined) {
108 | this.yLine.remove();
109 | this.yLine = undefined;
110 | }
111 |
112 | if (this.svgCanvas !== undefined) {
113 | this.svgCanvas.remove();
114 | this.svgCanvas = undefined;
115 | }
116 |
117 | if (this.map !== undefined) {
118 | this.map.off('resize', this.mapResize);
119 | this.map = undefined;
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/sites/demo/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
50 |
51 |
52 | {data.metadata.title}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
69 |
70 |
71 |
72 |
73 | {#snippet header()}
74 |
75 |
76 | {#snippet lead()}
77 |
93 | {/snippet}
94 | {#snippet trail()}
95 |
96 |
97 |
98 |
99 | {#each data.nav as link (data.nav.indexOf(link))}
100 |
107 |
108 |
109 | {/each}
110 |
111 | {/snippet}
112 |
113 | {/snippet}
114 |
115 |
116 | {data.metadata.title}
117 |
118 |
119 |
145 |
146 |
147 | {@render children?.()}
148 |
149 | {#snippet footer()}
150 |
151 |
©{year} {data.metadata.author}
152 |
153 | {/snippet}
154 |
155 |
--------------------------------------------------------------------------------
/packages/mapbox-gl-export/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @watergis/mapbox-gl-export
2 |
3 | ## 3.6.0
4 |
5 | ### Minor Changes
6 |
7 | - a84469e: feat: add Russian translation
8 |
9 | ### Patch Changes
10 |
11 | - 2689f24: chore: updated dependencies in package.json
12 |
13 | ## 3.5.7
14 |
15 | ### Patch Changes
16 |
17 | - 513f0e5: chore: updated dependencies
18 |
19 | ## 3.5.6
20 |
21 | ### Patch Changes
22 |
23 | - 3862737: chore: update dependencies in mapbox-gl-export
24 |
25 | ## 3.5.5
26 |
27 | ### Patch Changes
28 |
29 | - 7728399: chore: updated dependencies
30 |
31 | ## 3.5.4
32 |
33 | ### Patch Changes
34 |
35 | - c3c5f8c: fix: repalced js-loading-overlay to pure css to show loader.
36 |
37 | ## 3.5.3
38 |
39 | ### Patch Changes
40 |
41 | - 5a45f61: chore: updated all dependencies of mapbox-gl-export and maplibre-gl-export (particularly for eslint v9)
42 |
43 | ## 3.5.2
44 |
45 | ### Patch Changes
46 |
47 | - b7a4bff: Fix an error: Style is not done loading
48 |
49 | ## 3.5.1
50 |
51 | ### Patch Changes
52 |
53 | - 26a1d32: fix: It has a breaking change on attribution options. `options.attributionStyle` is changed to `options.attributionOptions.style`. add `visibility` and `position` option into `options.attributionOptions`. Due to technical issue, only `top-right` or `bottom-right` position are supported currently.
54 |
55 | ## 3.5.0
56 |
57 | ### Minor Changes
58 |
59 | - a66f15b: feat: export north icon on the map. North icon image, size, visibility and position can be customized through `northIconOptions`. The below is default settings for north icon.
60 |
61 | ```js
62 | {
63 | "image": ``,
64 | "imageName": "gl-export-north-icon",
65 | "imageSizeFraction": 0.05,
66 | "visibility": "visible",
67 | "position": "top-right"
68 | }
69 | ```
70 |
71 | ## 3.4.1
72 |
73 | ### Patch Changes
74 |
75 | - 6dd9820: fix: add check for actual image data before calling addImage by @jmbarbier
76 |
77 | ## 3.4.0
78 |
79 | ### Minor Changes
80 |
81 | - bf0467a: feat: add Catalan language contributed by @lstiz
82 |
83 | ## 3.3.0
84 |
85 | ### Minor Changes
86 |
87 | - 406978e: feat: add attribution to the bottom-right of an exported image. `attributionStyle` property is added into options. The default attribution style is
88 |
89 | ```js
90 | {
91 | attributionStyle: {
92 | textSize: 16,
93 | textHaloColor: '#FFFFFF',
94 | textHaloWidth: 0.8,
95 | textColor: '#000000',
96 | fallbackTextFont: ['Open Sans Regular']
97 | }
98 | }
99 | ```
100 |
101 | This plugin will try to get attribution from HTMLElement by class name of 'maplibregl-ctrl-attrib-inner' or 'mapboxgl-ctrl-attrib-inner' first. If elements are not available, it will try to make attribution text from 'attribution' property of map style source.
102 |
103 | If `glyphs` property is not set to map style, attribution will not be added since the plugin will add attribution as a symbol layer of maplibre/mapbox.
104 |
105 | In terms of text-font, the plugin will use the same font of the first layer which has text-font property in its layer style. If a text-font is not available from style object, fallbackTextFont will be used instead.
106 |
107 | ## 3.2.0
108 |
109 | ### Minor Changes
110 |
111 | - 85470db: feat: added Portuguese language which the translation was contributed by @leoneljdias via https://github.com/watergis/maplibre-gl-export/pull/133
112 |
113 | ### Patch Changes
114 |
115 | - 1a9b3d8: fix: use local language name for Translation object
116 | - 64d39b0: refactor: export AvailableLanguages as an array from maplibre-gl-export
117 | - a86c079: fix: add LanguageCode in Translation interface
118 |
119 | ## 3.1.0
120 |
121 | ### Minor Changes
122 |
123 | - 7639be0: feat: export markers as circle layer if they are added to map object. `markerCirclePaint` option is added to allow changing default circle style for marker. The default marker style is:
124 |
125 | ```json
126 | {
127 | "circle-radius": 8,
128 | "circle-color": "red",
129 | "circle-stroke-width": 1,
130 | "circle-stroke-color": "black"
131 | }
132 | ```
133 |
134 | ## 3.0.4
135 |
136 | ### Patch Changes
137 |
138 | - eba7503: refactor: Extends MaplibreExportControl to MapboxExportContorl to use the same logic and UI. Fixed type error for languages
139 |
140 | ## 3.0.3
141 |
142 | ### Patch Changes
143 |
144 | - 93722dd: refactor: import interfaces from maplibre-gl-export
145 |
146 | ## 3.0.2
147 |
148 | ### Patch Changes
149 |
150 | - 7d86d84: - feat: add languageName prop in Translation interface
151 | - fix: remove PrintableArea when the control is removed from map instance.
152 |
153 | ## 3.0.1
154 |
155 | ### Patch Changes
156 |
157 | - f7b81d9: refactor: Use Translations, PrintableAreaManager and CrosshairManager from maplibre-gl-export at mapbox-gl-export
158 |
159 | ## 3.0.0
160 |
161 | ### Major Changes
162 |
163 | - a03a84b: This release has breaking changes.
164 |
165 | - merged [mapbox-gl-export](https://github.com/watergis/mapbox-gl-export) repository to `maplibre-gl-export`.
166 | - added Spanish language and Japanese language from `maplibre-gl-export`
167 | - added `Filename` option from `maplibre-gl-export` to allow to change default file name from `map`.
168 | - added `AllowedSizes` option from `maplibre-gl-export` to allow to set available file sizes.
169 | - the URL of CDN script and css were changed. Use the below URLs for CDN.
170 |
171 | ```html
172 |
176 |
177 | ```
178 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @watergis/maplibre-gl-export
2 |
3 | ## 4.1.0
4 |
5 | ### Minor Changes
6 |
7 | - a84469e: feat: add Russian translation
8 |
9 | ### Patch Changes
10 |
11 | - 2689f24: chore: updated dependencies in package.json
12 |
13 | ## 4.0.1
14 |
15 | ### Patch Changes
16 |
17 | - 513f0e5: chore: updated dependencies
18 |
19 | ## 4.0.0
20 |
21 | ### Major Changes
22 |
23 | - 3862737: chore: upgrade maplibre-gl dependency from v4 to v5.
24 |
25 | ## 3.8.5
26 |
27 | ### Patch Changes
28 |
29 | - 7728399: chore: updated dependencies
30 |
31 | ## 3.8.4
32 |
33 | ### Patch Changes
34 |
35 | - c3c5f8c: fix: repalced js-loading-overlay to pure css to show loader.
36 |
37 | ## 3.8.3
38 |
39 | ### Patch Changes
40 |
41 | - 5a45f61: chore: updated all dependencies of mapbox-gl-export and maplibre-gl-export (particularly for eslint v9)
42 |
43 | ## 3.8.2
44 |
45 | ### Patch Changes
46 |
47 | - b7a4bff: Fix an error: Style is not done loading
48 |
49 | ## 3.8.1
50 |
51 | ### Patch Changes
52 |
53 | - 26a1d32: fix: It has a breaking change on attribution options. `options.attributionStyle` is changed to `options.attributionOptions.style`. add `visibility` and `position` option into `options.attributionOptions`. Due to technical issue, only `top-right` or `bottom-right` position are supported currently.
54 |
55 | ## 3.8.0
56 |
57 | ### Minor Changes
58 |
59 | - a66f15b: feat: export north icon on the map. North icon image, size, visibility and position can be customized through `northIconOptions`. The below is default settings for north icon.
60 |
61 | ```js
62 | {
63 | "image": ``,
64 | "imageName": "gl-export-north-icon",
65 | "imageSizeFraction": 0.05,
66 | "visibility": "visible",
67 | "position": "top-right"
68 | }
69 | ```
70 |
71 | ## 3.7.1
72 |
73 | ### Patch Changes
74 |
75 | - 6dd9820: fix: add check for actual image data before calling addImage by @jmbarbier
76 |
77 | ## 3.7.0
78 |
79 | ### Minor Changes
80 |
81 | - bf0467a: feat: add Catalan language contributed by @lstiz
82 |
83 | ## 3.6.0
84 |
85 | ### Minor Changes
86 |
87 | - 406978e: feat: add attribution to the bottom-right of an exported image. `attributionStyle` property is added into options. The default attribution style is
88 |
89 | ```js
90 | {
91 | attributionStyle: {
92 | textSize: 16,
93 | textHaloColor: '#FFFFFF',
94 | textHaloWidth: 0.8,
95 | textColor: '#000000',
96 | fallbackTextFont: ['Open Sans Regular']
97 | }
98 | }
99 | ```
100 |
101 | This plugin will try to get attribution from HTMLElement by class name of 'maplibregl-ctrl-attrib-inner' or 'mapboxgl-ctrl-attrib-inner' first. If elements are not available, it will try to make attribution text from 'attribution' property of map style source.
102 |
103 | If `glyphs` property is not set to map style, attribution will not be added since the plugin will add attribution as a symbol layer of maplibre/mapbox.
104 |
105 | In terms of text-font, the plugin will use the same font of the first layer which has text-font property in its layer style. If a text-font is not available from style object, fallbackTextFont will be used instead.
106 |
107 | ## 3.5.0
108 |
109 | ### Minor Changes
110 |
111 | - 85470db: feat: added Portuguese language which the translation was contributed by @leoneljdias via https://github.com/watergis/maplibre-gl-export/pull/133
112 |
113 | ### Patch Changes
114 |
115 | - 1a9b3d8: fix: use local language name for Translation object
116 | - 64d39b0: refactor: export AvailableLanguages as an array from maplibre-gl-export
117 | - a86c079: fix: add LanguageCode in Translation interface
118 |
119 | ## 3.4.1
120 |
121 | ### Patch Changes
122 |
123 | - 4b16e32: fix: enabled addImage code for maplibre-gl-export again
124 | - 352c82d: refactor: removed unused variable from MapGeneratorBase class
125 |
126 | ## 3.4.0
127 |
128 | ### Minor Changes
129 |
130 | - 7639be0: feat: export markers as circle layer if they are added to map object. `markerCirclePaint` option is added to allow changing default circle style for marker. The default marker style is:
131 |
132 | ```json
133 | {
134 | "circle-radius": 8,
135 | "circle-color": "red",
136 | "circle-stroke-width": 1,
137 | "circle-stroke-color": "black"
138 | }
139 | ```
140 |
141 | ## 3.3.0
142 |
143 | ### Minor Changes
144 |
145 | - 1c9327a: feat: apply maplibre terrain mode for image exporting if it is used.
146 |
147 | ## 3.2.4
148 |
149 | ### Patch Changes
150 |
151 | - eba7503: refactor: Extends MaplibreExportControl to MapboxExportContorl to use the same logic and UI. Fixed type error for languages
152 | - eba7503: fix: fixed class name to get page size
153 |
154 | ## 3.2.3
155 |
156 | ### Patch Changes
157 |
158 | - 93722dd: refactor: export interfaces from maplibre-gl-export
159 |
160 | ## 3.2.2
161 |
162 | ### Patch Changes
163 |
164 | - 7d86d84: - feat: add languageName prop in Translation interface
165 | - fix: remove PrintableArea when the control is removed from map instance.
166 |
167 | ## 3.2.1
168 |
169 | ### Patch Changes
170 |
171 | - f7b81d9: refactor: Use Translations, PrintableAreaManager and CrosshairManager from maplibre-gl-export at mapbox-gl-export
172 |
173 | ## 3.2.0
174 |
175 | ### Minor Changes
176 |
177 | - a03a84b: feat: upgrade maplibre dependencies to v4
178 |
179 | ### Patch Changes
180 |
181 | - a03a84b: feat: added `LETTER` page size (migrated it from `mapbox-gl-export`)
182 |
183 | ## 3.1.1
184 |
185 | ### Patch Changes
186 |
187 | - fdddb32: fix: Fix export icon color inconsistency fixed by @sudolev
188 |
189 | ## 3.1.0
190 |
191 | ### Minor Changes
192 |
193 | - eb5270d: feat: add Japanese language (ja)
194 | - eb5270d: feat: bring new translations (Vietnam, Ukranian, Chinese simplified and Chinese traditional) from mapbox-gl-export
195 |
196 | ## 3.0.1
197 |
198 | ### Patch Changes
199 |
200 | - fc5adb2: fix: fixed the bug of export PDF of A3 size spondered by @PivnoyBaronDmitry through the PR of https://github.com/watergis/mapbox-gl-export/pull/48
201 |
202 | ## 3.0.0
203 |
204 | ### Major Changes
205 |
206 | - 47c956a: [**breaking change**] feat: upgraded maplibre-gl-js to v3
207 |
208 | ## 2.0.1
209 |
210 | ### Patch Changes
211 |
212 | - 83c85bc: fix: added exports for maplibre-gl-export.css
213 |
214 | ## 2.0.0
215 |
216 | ### Major Changes
217 |
218 | - f5cc0c5: This release has breaking changes. It changes as follows.
219 |
220 | - migrated webpack to vite
221 | - migrated yarn to pnpm
222 | - introduced monorepo by using pnpm workspaces
223 | - introduced changeset for releasing package
224 | - CDN path will be changed
225 |
226 | ### Patch Changes
227 |
228 | - 1f8fd87: fixed release CI and bug of demo page settings
229 | - 45fdc37: fix: fixed repository name at if statement in release CI
230 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/export-control.ts:
--------------------------------------------------------------------------------
1 | import { ControlPosition, IControl, Map as MaplibreMap } from 'maplibre-gl';
2 | import { Map as MapboxMap } from 'mapbox-gl';
3 | import CrosshairManager from './crosshair-manager';
4 | import PrintableAreaManager from './printable-area-manager';
5 | import { getTranslation } from './local';
6 | import MapGenerator from './map-generator';
7 | import {
8 | Format,
9 | type ControlOptions,
10 | FormatType,
11 | Unit,
12 | SizeType,
13 | Size,
14 | Translation,
15 | PageOrientation,
16 | DPI,
17 | DPIType,
18 | UnitType,
19 | type Language
20 | } from './interfaces';
21 | import {
22 | defaultAttributionOptions,
23 | defaultMarkerCirclePaint,
24 | defaultNorthIconOptions
25 | } from './map-generator-base';
26 |
27 | /**
28 | * Maplibre GL Export Control.
29 | * @param {Object} targets - Object of layer.id and title
30 | */
31 | export default class MaplibreExportControl implements IControl {
32 | protected controlContainer: HTMLElement;
33 |
34 | protected exportContainer: HTMLElement;
35 |
36 | protected crosshair: CrosshairManager | undefined;
37 |
38 | protected printableArea: PrintableAreaManager | undefined;
39 |
40 | protected map?: MaplibreMap | MapboxMap;
41 |
42 | protected exportButton: HTMLButtonElement;
43 |
44 | protected options: ControlOptions = {
45 | PageSize: Size.A4 as SizeType,
46 | PageOrientation: PageOrientation.Landscape,
47 | Format: Format.PDF,
48 | DPI: DPI[300],
49 | Crosshair: false,
50 | PrintableArea: false,
51 | Local: 'en',
52 | AllowedSizes: Object.keys(Size) as (
53 | | 'LETTER'
54 | | 'A2'
55 | | 'A3'
56 | | 'A4'
57 | | 'A5'
58 | | 'A6'
59 | | 'B2'
60 | | 'B3'
61 | | 'B4'
62 | | 'B5'
63 | | 'B6'
64 | )[],
65 | Filename: 'map',
66 | markerCirclePaint: defaultMarkerCirclePaint,
67 | attributionOptions: defaultAttributionOptions,
68 | northIconOptions: defaultNorthIconOptions
69 | };
70 |
71 | protected MAPLIB_CSS_PREFIX: string = 'maplibregl';
72 |
73 | constructor(options: ControlOptions) {
74 | if (options) {
75 | options.attributionOptions = Object.assign(
76 | defaultAttributionOptions,
77 | options.attributionOptions
78 | );
79 | options.northIconOptions = Object.assign(defaultNorthIconOptions, options.northIconOptions);
80 | this.options = Object.assign(this.options, options);
81 | }
82 | this.onDocumentClick = this.onDocumentClick.bind(this);
83 | }
84 |
85 | public getDefaultPosition(): ControlPosition {
86 | const defaultPosition = 'top-right';
87 | return defaultPosition;
88 | }
89 |
90 | public getTranslation(): Translation {
91 | const lang: Language = this.options.Local ?? 'en';
92 | return getTranslation(lang);
93 | }
94 |
95 | public onAdd(map: MaplibreMap | MapboxMap): HTMLElement {
96 | this.map = map;
97 | this.controlContainer = document.createElement('div');
98 | this.controlContainer.classList.add(`${this.MAPLIB_CSS_PREFIX}-ctrl`);
99 | this.controlContainer.classList.add(`${this.MAPLIB_CSS_PREFIX}-ctrl-group`);
100 | this.exportContainer = document.createElement('div');
101 | this.exportContainer.classList.add(`${this.MAPLIB_CSS_PREFIX}-export-list`);
102 | this.exportButton = document.createElement('button');
103 | this.exportButton.classList.add(`${this.MAPLIB_CSS_PREFIX}-ctrl-icon`);
104 | this.exportButton.classList.add(`${this.MAPLIB_CSS_PREFIX}-export-control`);
105 | this.exportButton.type = 'button';
106 | this.exportButton.addEventListener('click', () => {
107 | this.exportButton.style.display = 'none';
108 | this.exportContainer.style.display = 'block';
109 | this.toggleCrosshair(true);
110 | this.togglePrintableArea(true);
111 | });
112 | document.addEventListener('click', this.onDocumentClick);
113 | this.controlContainer.appendChild(this.exportButton);
114 | this.controlContainer.appendChild(this.exportContainer);
115 |
116 | const table = document.createElement('TABLE');
117 | table.className = 'print-table';
118 |
119 | const sizes = {};
120 | this.options.AllowedSizes?.forEach((size) => {
121 | const dimensions = Size[size];
122 | if (dimensions) {
123 | sizes[size] = Size[size];
124 | }
125 | });
126 | const tr1 = this.createSelection(
127 | sizes,
128 | this.getTranslation().PageSize,
129 | 'page-size',
130 | this.options.PageSize as [number, number],
131 | (data: { [key: string]: unknown }, key) => JSON.stringify(data[key])
132 | );
133 | table.appendChild(tr1);
134 |
135 | const tr2 = this.createSelection(
136 | PageOrientation,
137 | this.getTranslation().PageOrientation,
138 | 'page-orientation',
139 | this.options.PageOrientation as string,
140 | (data: { [key: string]: unknown }, key) => data[key]
141 | );
142 | table.appendChild(tr2);
143 |
144 | const tr3 = this.createSelection(
145 | Format,
146 | this.getTranslation().Format,
147 | 'format-type',
148 | this.options.Format as string,
149 | (data: { [key: string]: unknown }, key) => data[key]
150 | );
151 | table.appendChild(tr3);
152 |
153 | const tr4 = this.createSelection(
154 | DPI,
155 | this.getTranslation().DPI,
156 | 'dpi-type',
157 | this.options.DPI as number,
158 | (data: { [key: string]: unknown }, key) => data[key]
159 | );
160 | table.appendChild(tr4);
161 |
162 | this.exportContainer.appendChild(table);
163 |
164 | const generateButton = document.createElement('button');
165 | generateButton.type = 'button';
166 | generateButton.textContent = this.getTranslation().Generate;
167 | generateButton.classList.add('generate-button');
168 | generateButton.addEventListener('click', () => {
169 | const pageSize: HTMLSelectElement = (
170 | document.getElementById('mapbox-gl-export-page-size')
171 | );
172 | const pageOrientation: HTMLSelectElement = (
173 | document.getElementById('mapbox-gl-export-page-orientation')
174 | );
175 | const formatType: HTMLSelectElement = (
176 | document.getElementById('mapbox-gl-export-format-type')
177 | );
178 | const dpiType: HTMLSelectElement = (
179 | document.getElementById('mapbox-gl-export-dpi-type')
180 | );
181 | const orientValue = pageOrientation.value;
182 | let pageSizeValue = JSON.parse(pageSize.value);
183 | if (orientValue === PageOrientation.Portrait) {
184 | pageSizeValue = pageSizeValue.reverse();
185 | }
186 | this.generateMap(
187 | map,
188 | pageSizeValue,
189 | Number(dpiType.value) as DPIType,
190 | formatType.value as FormatType,
191 | Unit.mm,
192 | this.options.Filename
193 | );
194 | });
195 | this.exportContainer.appendChild(generateButton);
196 |
197 | return this.controlContainer;
198 | }
199 |
200 | protected generateMap(
201 | map: MaplibreMap | MapboxMap,
202 | size: SizeType,
203 | dpi: DPIType,
204 | format: FormatType,
205 | unit: UnitType,
206 | filename?: string
207 | ) {
208 | const mapGenerator = new MapGenerator(
209 | map as MaplibreMap,
210 | size,
211 | dpi,
212 | format,
213 | unit,
214 | filename,
215 | this.options.markerCirclePaint,
216 | this.options.attributionOptions,
217 | this.options.northIconOptions
218 | );
219 | mapGenerator.generate();
220 | }
221 |
222 | private createSelection(
223 | data: Record,
224 | title: string,
225 | type: string,
226 | defaultValue: string | number | [number, number],
227 | converter: (data: { [key: string]: unknown }, key: string) => unknown
228 | ): HTMLElement {
229 | const label = document.createElement('label');
230 | label.textContent = title;
231 |
232 | const content = document.createElement('select');
233 | content.setAttribute('id', `mapbox-gl-export-${type}`);
234 | content.style.width = '100%';
235 | Object.keys(data).forEach((key) => {
236 | const optionLayout = document.createElement('option');
237 | optionLayout.setAttribute('value', converter(data, key) as string);
238 | optionLayout.appendChild(document.createTextNode(key));
239 | optionLayout.setAttribute('name', type);
240 | if (defaultValue === data[key]) {
241 | optionLayout.selected = true;
242 | }
243 | content.appendChild(optionLayout);
244 | });
245 | content.addEventListener('change', () => {
246 | this.updatePrintableArea();
247 | });
248 |
249 | const tr1 = document.createElement('TR');
250 | const tdLabel = document.createElement('TD');
251 | const tdContent = document.createElement('TD');
252 | tdLabel.appendChild(label);
253 | tdContent.appendChild(content);
254 | tr1.appendChild(tdLabel);
255 | tr1.appendChild(tdContent);
256 | return tr1;
257 | }
258 |
259 | public onRemove(): void {
260 | if (
261 | !this.controlContainer ||
262 | !this.controlContainer.parentNode ||
263 | !this.map ||
264 | !this.exportButton
265 | ) {
266 | return;
267 | }
268 | this.exportButton.removeEventListener('click', this.onDocumentClick);
269 | this.controlContainer.parentNode.removeChild(this.controlContainer);
270 | document.removeEventListener('click', this.onDocumentClick);
271 |
272 | if (this.crosshair !== undefined) {
273 | this.crosshair.destroy();
274 | this.crosshair = undefined;
275 | }
276 |
277 | if (this.printableArea !== undefined) {
278 | this.printableArea.destroy();
279 | this.printableArea = undefined;
280 | }
281 |
282 | this.map = undefined;
283 | }
284 |
285 | private onDocumentClick(event: MouseEvent): void {
286 | if (
287 | this.controlContainer &&
288 | !this.controlContainer.contains(event.target as Element) &&
289 | this.exportContainer &&
290 | this.exportButton
291 | ) {
292 | this.exportContainer.style.display = 'none';
293 | this.exportButton.style.display = 'block';
294 | this.toggleCrosshair(false);
295 | this.togglePrintableArea(false);
296 | }
297 | }
298 |
299 | private toggleCrosshair(state: boolean) {
300 | if (this.options.Crosshair === true) {
301 | if (state === false) {
302 | if (this.crosshair !== undefined) {
303 | this.crosshair.destroy();
304 | this.crosshair = undefined;
305 | }
306 | } else {
307 | this.crosshair = new CrosshairManager(this.map);
308 | this.crosshair.create();
309 | }
310 | }
311 | }
312 |
313 | private togglePrintableArea(state: boolean) {
314 | if (this.options.PrintableArea === true) {
315 | if (state === false) {
316 | if (this.printableArea !== undefined) {
317 | this.printableArea.destroy();
318 | this.printableArea = undefined;
319 | }
320 | } else {
321 | this.printableArea = new PrintableAreaManager(this.map);
322 | this.updatePrintableArea();
323 | }
324 | }
325 | }
326 |
327 | private updatePrintableArea() {
328 | if (this.printableArea === undefined) {
329 | return;
330 | }
331 | const pageSize: HTMLSelectElement = (
332 | document.getElementById('mapbox-gl-export-page-size')
333 | );
334 | const pageOrientation: HTMLSelectElement = (
335 | document.getElementById('mapbox-gl-export-page-orientation')
336 | );
337 | const orientValue = pageOrientation.value;
338 | let pageSizeValue = JSON.parse(pageSize.value);
339 | if (orientValue === PageOrientation.Portrait) {
340 | pageSizeValue = pageSizeValue.reverse();
341 | }
342 | this.printableArea.updateArea(pageSizeValue[0], pageSizeValue[1]);
343 | }
344 | }
345 |
--------------------------------------------------------------------------------
/sites/demo/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
178 |
179 |
180 |
181 |
Welcome to {data.metadata.title}
182 |
183 |
184 | {data.metadata.description}
185 |
186 |
187 |
188 |

193 |
194 |
195 |
196 |
197 |
198 |
Select a map library
199 |
200 |
201 | {#each tabs as tab (tab.value)}
202 |
203 | {tab.label}
204 | {#if tab.value === 'maplibre'}
205 | ({maplibreExportVersion})
206 | {:else}
207 | ({mapboxExportVersion})
208 | {/if}
209 |
210 | {/each}
211 |
212 |
213 |
Demo
214 |
215 |
216 | Open {tabSet} DEMO
217 |
218 |
219 |
220 |
221 |
Language
222 |
{Languages.length} languages are available in the plugin.
223 |
224 |
232 |
233 |
234 |
235 | {#each imprtTypeTabs as tab (tab.value)}
236 | {tab.label}
237 | {/each}
238 |
239 |
240 |
241 |
Mapbox access token
242 |
246 |
247 |
248 |
249 |
Install
250 |
Getting start with installing the package
251 |
252 |
253 | npm
254 | yarn
255 | pnpm
256 |
257 |
258 |
259 | {#if packageManager === 'npm'}
260 |
264 | {:else if packageManager === 'yarn'}
265 |
266 | {:else if packageManager === 'pnpm'}
267 |
268 | {/if}
269 |
270 |
271 |
Usage
272 |
273 |
Copy and past the below code.
274 |
275 |
309 |
310 |
311 |
312 |
Usage
313 |
314 |
Copy and past the below code.
315 |
316 |
334 |
335 |
336 |
Parameters
337 |
338 |
339 | The first argument of the constructor can accept the various parameters to customize your own
340 | settings.
341 |
342 |
343 |
344 |
345 |
346 |
347 | | Parameter |
348 | Default value |
349 | Description |
350 |
351 |
352 |
353 | {#each parameters as param (parameters.indexOf(param))}
354 | {#if !(tabSet === 'maplibre' && param.name === 'accessToken')}
355 |
356 | | {param.name} |
357 |
358 | {param.default}
359 | |
360 | {param.description} |
361 |
362 | {/if}
363 | {/each}
364 |
365 |
366 |
367 |
368 |
376 |
377 |
378 |
379 | {#each data.metadata.licenses as license (data.metadata.licenses.indexOf(license))}
380 |
{license}
381 | {/each}
382 |
383 |
384 |
385 |
387 |
--------------------------------------------------------------------------------
/packages/maplibre-gl-export/src/lib/map-generator-base.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * mpetroff/print-maps
3 | * https://github.com/mpetroff/print-maps
4 | *
5 | * I used the source code from the above repository. Thanks so much!
6 | *
7 | * -----LICENSE------
8 | * Print Maps - High-resolution maps in the browser, for printing
9 | * Copyright (c) 2015-2020 Matthew Petroff
10 | *
11 | * Permission is hereby granted, free of charge, to any person obtaining a copy
12 | * of this software and associated documentation files (the "Software"), to deal
13 | * in the Software without restriction, including without limitation the rights
14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 | * copies of the Software, and to permit persons to whom the Software is
16 | * furnished to do so, subject to the following conditions:
17 | *
18 | * The above copyright notice and this permission notice shall be included in
19 | * all copies or substantial portions of the Software.
20 | *
21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | * THE SOFTWARE.
28 | */
29 |
30 | import { jsPDF } from 'jspdf';
31 | import {
32 | Map as MaplibreMap,
33 | PointLike,
34 | SourceSpecification,
35 | StyleSpecification,
36 | SymbolLayerSpecification
37 | } from 'maplibre-gl';
38 | import { CirclePaint, Map as MapboxMap } from 'mapbox-gl';
39 | import {
40 | AttributionOptions,
41 | AttributionStyle,
42 | DPIType,
43 | Format,
44 | FormatType,
45 | NorthIconOptions,
46 | Size,
47 | SizeType,
48 | Unit,
49 | UnitType
50 | } from './interfaces';
51 |
52 | export const defaultMarkerCirclePaint: CirclePaint = {
53 | 'circle-radius': 8,
54 | 'circle-color': 'red',
55 | 'circle-stroke-width': 1,
56 | 'circle-stroke-color': 'black'
57 | };
58 |
59 | export const defaultAttributionOptions: AttributionOptions = {
60 | style: {
61 | textSize: 16,
62 | textHaloColor: '#FFFFFF',
63 | textHaloWidth: 0.8,
64 | textColor: '#000000',
65 | fallbackTextFont: ['Open Sans Regular']
66 | },
67 | visibility: 'visible',
68 | position: 'bottom-right'
69 | };
70 |
71 | export const defaultNorthIconOptions: NorthIconOptions = {
72 | image: ``,
73 | imageName: 'gl-export-north-icon',
74 | imageSizeFraction: 0.05,
75 | visibility: 'visible',
76 | position: 'top-right'
77 | };
78 |
79 | export abstract class MapGeneratorBase {
80 | protected map: MaplibreMap | MapboxMap;
81 |
82 | protected width: number;
83 |
84 | protected height: number;
85 |
86 | protected dpi: number;
87 |
88 | protected format: FormatType;
89 |
90 | protected unit: UnitType;
91 |
92 | protected fileName: string;
93 |
94 | protected markerClassName: string;
95 |
96 | protected markerCirclePaint: CirclePaint;
97 |
98 | protected attributionClassName: string;
99 |
100 | protected attributionOptions: AttributionOptions;
101 |
102 | protected northIconOptions: NorthIconOptions;
103 |
104 | /**
105 | * Constructor
106 | * @param map MaplibreMap object
107 | * @param size layout size. default is A4
108 | * @param dpi dpi value. default is 300
109 | * @param format image format. default is PNG
110 | * @param unit length unit. default is mm
111 | * @param fileName file name. default is 'map'
112 | */
113 | constructor(
114 | map: MaplibreMap | MapboxMap,
115 | size: SizeType = Size.A4,
116 | dpi: DPIType = 300,
117 | format: FormatType = Format.PNG,
118 | unit: UnitType = Unit.mm,
119 | fileName = 'map',
120 | markerClassName = 'maplibregl-marker',
121 | markerCirclePaint = defaultMarkerCirclePaint,
122 | attributionClassName = 'maplibregl-ctrl-attrib-inner',
123 | attributionOptions = defaultAttributionOptions,
124 | northIconOptions = defaultNorthIconOptions
125 | ) {
126 | this.map = map;
127 | this.width = size[0];
128 | this.height = size[1];
129 | this.dpi = dpi;
130 | this.format = format;
131 | this.unit = unit;
132 | this.fileName = fileName;
133 | this.markerClassName = markerClassName;
134 | this.markerCirclePaint = markerCirclePaint;
135 | this.attributionClassName = attributionClassName;
136 | this.attributionOptions = attributionOptions;
137 | this.northIconOptions = northIconOptions;
138 | }
139 |
140 | protected abstract getRenderedMap(
141 | container: HTMLElement,
142 | style: StyleSpecification | mapboxgl.Style
143 | ): MaplibreMap | MapboxMap;
144 |
145 | protected renderMapPost(renderMap: MaplibreMap | MapboxMap) {
146 | return renderMap;
147 | }
148 |
149 | private getMarkers() {
150 | return this.map.getCanvasContainer().getElementsByClassName(this.markerClassName);
151 | }
152 |
153 | protected renderMarkers(renderMap: MaplibreMap | MapboxMap) {
154 | const markers = this.getMarkers();
155 | for (let i = 0; i < markers.length; i++) {
156 | const marker = markers.item(i);
157 | if (!marker) continue;
158 | const style = marker.getAttribute('style');
159 | if (!style) continue;
160 | const translateRegex = /translate\(([^,]+)px,\s*([^,]+)px\)/;
161 | const match = style.match(translateRegex);
162 | if (!match) continue;
163 | const translateX = parseInt(match[1]);
164 | const translateY = parseInt(match[2]);
165 |
166 | const lngLat = this.map.unproject([translateX, translateY]);
167 |
168 | const markerId = `point${i}`;
169 | renderMap.addSource(markerId, {
170 | type: 'geojson',
171 | data: {
172 | type: 'Point',
173 | coordinates: [lngLat.lng, lngLat.lat]
174 | }
175 | });
176 |
177 | (renderMap as MapboxMap).addLayer({
178 | id: markerId,
179 | source: markerId,
180 | type: 'circle',
181 | paint: this.markerCirclePaint
182 | });
183 | }
184 | return renderMap;
185 | }
186 |
187 | /**
188 | * Generate and download Map image
189 | */
190 | generate() {
191 | // eslint-disable-next-line
192 | const this_ = this;
193 |
194 | this.addLoader();
195 | this.showLoader();
196 |
197 | // Calculate pixel ratio
198 | const actualPixelRatio: number = window.devicePixelRatio;
199 | Object.defineProperty(window, 'devicePixelRatio', {
200 | get() {
201 | return this_.dpi / 96;
202 | }
203 | });
204 | // Create map container
205 | const hidden = document.createElement('div');
206 | hidden.className = 'hidden-map';
207 | document.body.appendChild(hidden);
208 | const container = document.createElement('div');
209 | container.style.width = this.toPixels(this.width);
210 | container.style.height = this.toPixels(this.height);
211 | hidden.appendChild(container);
212 |
213 | const style = this.map.getStyle();
214 | if (style && style.sources) {
215 | const sources = style.sources;
216 | Object.keys(sources).forEach((name) => {
217 | const src = sources[name];
218 | Object.keys(src).forEach((key) => {
219 | // delete properties if value is undefined.
220 | // for instance, raster-dem might has undefined value in "url" and "bounds"
221 | if (!src[key]) delete src[key];
222 | });
223 | });
224 | }
225 |
226 | // Render map
227 | let renderMap = this.getRenderedMap(container, style);
228 |
229 | renderMap.on('load', () => {
230 | this.addNorthIconToMap(renderMap).then(() => {
231 | renderMap.once('idle', () => {
232 | const isAttributionAdded = this.addAttributions(renderMap);
233 | if (isAttributionAdded) {
234 | renderMap.once('idle', () => {
235 | renderMap = this.renderMapPost(renderMap);
236 | const markers = this.getMarkers();
237 | if (markers.length === 0) {
238 | this.exportImage(renderMap, hidden, actualPixelRatio);
239 | } else {
240 | renderMap = this.renderMarkers(renderMap);
241 | renderMap.once('idle', () => {
242 | this.exportImage(renderMap, hidden, actualPixelRatio);
243 | });
244 | }
245 | });
246 | } else {
247 | renderMap = this.renderMapPost(renderMap);
248 | const markers = this.getMarkers();
249 | if (markers.length === 0) {
250 | this.exportImage(renderMap, hidden, actualPixelRatio);
251 | } else {
252 | renderMap = this.renderMarkers(renderMap);
253 | renderMap.once('idle', () => {
254 | this.exportImage(renderMap, hidden, actualPixelRatio);
255 | });
256 | }
257 | }
258 | });
259 | });
260 | });
261 | }
262 |
263 | private stripHtml(htmlString: string) {
264 | const tempElement = document.createElement('div');
265 | tempElement.innerHTML = htmlString;
266 | return tempElement.textContent || tempElement.innerText || '';
267 | }
268 |
269 | /**
270 | * Get icon width against exported map size by using fraction rate
271 | * @param renderMap Map object
272 | * @param fraction adjust icon size by using this fraction rate. Default is 8%
273 | * @returns Icon width calculated
274 | */
275 | private getIconWidth(renderMap: MaplibreMap | MapboxMap, fraction: number) {
276 | const containerDiv = renderMap.getContainer();
277 | const width = parseInt(containerDiv.style.width.replace('px', ''));
278 | return parseInt(`${width * fraction}`);
279 | }
280 |
281 | /**
282 | * Get element position's pixel values based on selected position setting
283 | * @param renderMap Map object
284 | * @param position Position of element inserted
285 | * @param offset Offset value to adjust position
286 | * @returns Pixels [width, height]
287 | */
288 | private getElementPosition(
289 | renderMap: MaplibreMap | MapboxMap,
290 | position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right',
291 | offset = 0
292 | ) {
293 | const containerDiv = renderMap.getContainer();
294 | let width = 0;
295 | let height = 0;
296 |
297 | switch (position) {
298 | case 'top-left':
299 | width = 0 + offset;
300 | height = 0 + offset;
301 | break;
302 | case 'top-right':
303 | width = parseInt(containerDiv.style.width.replace('px', '')) - offset;
304 | height = 0 + offset;
305 | break;
306 | case 'bottom-left':
307 | width = 0 + offset;
308 | height = parseInt(containerDiv.style.height.replace('px', '')) - offset;
309 | break;
310 | case 'bottom-right':
311 | width = parseInt(containerDiv.style.width.replace('px', '')) - offset;
312 | height = parseInt(containerDiv.style.height.replace('px', '')) - offset;
313 | break;
314 | default:
315 | break;
316 | }
317 |
318 | const pixels = [width, height] as PointLike;
319 | return pixels;
320 | }
321 |
322 | /**
323 | * Add North Icon SVG to map object
324 | * @param renderMap Map object
325 | * @returns void
326 | */
327 | private addNorthIconImage(renderMap: MaplibreMap | MapboxMap) {
328 | const iconSize = this.getIconWidth(renderMap, this.northIconOptions.imageSizeFraction ?? 0.08);
329 | return new Promise((resolve) => {
330 | const svgImage = new Image(iconSize, iconSize);
331 | svgImage.onload = () => {
332 | if (this.northIconOptions.imageName) {
333 | renderMap.addImage(this.northIconOptions.imageName, svgImage);
334 | }
335 | resolve();
336 | };
337 | function svgStringToImageSrc(svgString: string) {
338 | return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgString);
339 | }
340 | if (this.northIconOptions.image) {
341 | svgImage.src = svgStringToImageSrc(this.northIconOptions.image);
342 | }
343 | });
344 | }
345 |
346 | /**
347 | * Add North Icon Symbol layer to renderMap object
348 | * @param renderMap Map object
349 | * @returns
350 | */
351 | private addNorthIconToMap(renderMap: MaplibreMap | MapboxMap) {
352 | let visibility: 'visible' | 'none' = this.northIconOptions.visibility ?? 'visible';
353 | if (renderMap.getZoom() < 2 && this.width > this.height) {
354 | // if zoom level is less than 2, it will appear twice.
355 | visibility = 'none';
356 | }
357 | return new Promise((resolve) => {
358 | this.addNorthIconImage(renderMap).then(() => {
359 | const iconSize = this.getIconWidth(
360 | renderMap,
361 | this.northIconOptions.imageSizeFraction ?? 0.08
362 | );
363 | const iconOffset = iconSize * 0.8;
364 | const pixels = this.getElementPosition(
365 | renderMap,
366 | this.northIconOptions.position ?? 'top-right',
367 | iconOffset
368 | );
369 | const lngLat = (renderMap as MaplibreMap).unproject(pixels);
370 |
371 | const layerId = this.northIconOptions.imageName ?? 'gl-export-north-icon';
372 | renderMap.addSource(layerId, {
373 | type: 'geojson',
374 | data: {
375 | type: 'Feature',
376 | geometry: {
377 | type: 'Point',
378 | coordinates: [lngLat.lng, lngLat.lat]
379 | },
380 | properties: {}
381 | }
382 | });
383 |
384 | (renderMap as MapboxMap).addLayer({
385 | id: layerId,
386 | source: layerId,
387 | type: 'symbol',
388 | layout: {
389 | 'icon-image': layerId,
390 | 'icon-size': 1.0,
391 | 'icon-rotate': renderMap.getBearing() * -1,
392 | 'icon-allow-overlap': true,
393 | 'icon-ignore-placement': true,
394 | visibility: visibility
395 | },
396 | paint: {}
397 | });
398 | resolve();
399 | });
400 | });
401 | }
402 |
403 | private addAttributions(renderMap: MaplibreMap | MapboxMap) {
404 | const glyphs = this.map.getStyle().glyphs;
405 | // skip if glyphs is not available in style.
406 | if (!glyphs) return false;
407 |
408 | const containerDiv = renderMap.getContainer();
409 | const elementPosition = this.attributionOptions.position ?? 'bottom-right';
410 | const pixels = this.getElementPosition(renderMap, elementPosition, 5);
411 | const width = pixels[0];
412 | const lngLat = (renderMap as MaplibreMap).unproject(pixels);
413 |
414 | const attrElements = containerDiv.getElementsByClassName(this.attributionClassName);
415 | const attributions: string[] = [];
416 | if (attrElements?.length > 0) {
417 | // try getting attribution from html elements
418 | const attrs = attrElements.item(0);
419 | if (attrs) {
420 | for (let i = 0; i < attrs.children.length; i++) {
421 | const child = attrs.children.item(i);
422 | if (!child) continue;
423 | attributions.push(this.stripHtml(child.outerHTML));
424 | }
425 | }
426 | } else {
427 | // if not, try to make attribution from style
428 | const sources = this.map.getStyle().sources;
429 | Object.keys(sources).forEach((key) => {
430 | const src: SourceSpecification = sources[key] as SourceSpecification;
431 | if ('attribution' in src) {
432 | const attribution = src.attribution as string;
433 | attributions.push(this.stripHtml(attribution));
434 | }
435 | });
436 | }
437 |
438 | if (attributions.length === 0) return false;
439 |
440 | const attributionText = attributions.join(' | ');
441 |
442 | const attributionId = `attribution`;
443 | renderMap.addSource(attributionId, {
444 | type: 'geojson',
445 | data: {
446 | type: 'Feature',
447 | geometry: {
448 | type: 'Point',
449 | coordinates: [lngLat.lng, lngLat.lat]
450 | },
451 | properties: {
452 | attribution: attributionText
453 | }
454 | }
455 | });
456 |
457 | const fontLayers = this.map
458 | .getStyle()
459 | .layers.filter(
460 | (l) => l.type === 'symbol' && l.layout && 'text-font' in l.layout
461 | ) as SymbolLayerSpecification[];
462 | const font: string[] =
463 | fontLayers.length > 0 && fontLayers[0].layout
464 | ? (fontLayers[0].layout['text-font'] as string[])
465 | : (this.attributionOptions.style?.fallbackTextFont as string[]);
466 |
467 | let visibility: 'visible' | 'none' = this.attributionOptions.visibility ?? 'visible';
468 | if (renderMap.getZoom() < 2 && this.width > this.height) {
469 | // if zoom level is less than 2, it will appear twice.
470 | visibility = 'none';
471 | }
472 |
473 | const attrStyle = this.attributionOptions.style as AttributionStyle;
474 |
475 | (renderMap as MapboxMap).addLayer({
476 | id: attributionId,
477 | source: attributionId,
478 | type: 'symbol',
479 | layout: {
480 | 'text-field': ['get', 'attribution'],
481 | 'text-font': font,
482 | 'text-max-width': parseInt(`${width / attrStyle.textSize}`),
483 | 'text-anchor': elementPosition,
484 | 'text-justify': ['top-right', 'bottom-right'].includes(elementPosition) ? 'right' : 'left',
485 | 'text-size': attrStyle.textSize,
486 | 'text-allow-overlap': true,
487 | visibility: visibility
488 | },
489 | paint: {
490 | 'text-halo-color': attrStyle.textHaloColor,
491 | 'text-halo-width': attrStyle.textHaloWidth,
492 | 'text-color': attrStyle.textColor
493 | }
494 | });
495 |
496 | return true;
497 | }
498 |
499 | private exportImage(
500 | renderMap: MaplibreMap | MapboxMap,
501 | hiddenDiv: HTMLElement,
502 | actualPixelRatio: number
503 | ) {
504 | const canvas = renderMap.getCanvas();
505 |
506 | const fileName = `${this.fileName}.${this.format}`;
507 | switch (this.format) {
508 | case Format.PNG:
509 | this.toPNG(canvas, fileName);
510 | break;
511 | case Format.JPEG:
512 | this.toJPEG(canvas, fileName);
513 | break;
514 | case Format.PDF:
515 | this.toPDF(renderMap, fileName);
516 | break;
517 | case Format.SVG:
518 | this.toSVG(canvas, fileName);
519 | break;
520 | default:
521 | console.error(`Invalid file format: ${this.format}`);
522 | break;
523 | }
524 |
525 | renderMap.remove();
526 | hiddenDiv.parentNode?.removeChild(hiddenDiv);
527 | Object.defineProperty(window, 'devicePixelRatio', {
528 | get() {
529 | return actualPixelRatio;
530 | }
531 | });
532 | hiddenDiv.remove();
533 |
534 | this.hideLoader();
535 | }
536 |
537 | /**
538 | * Convert canvas to PNG
539 | * @param canvas Canvas element
540 | * @param fileName file name
541 | */
542 | private toPNG(canvas: HTMLCanvasElement, fileName: string) {
543 | const a = document.createElement('a');
544 | a.href = canvas.toDataURL();
545 | a.download = fileName;
546 | a.click();
547 | a.remove();
548 | }
549 |
550 | /**
551 | * Convert canvas to JPEG
552 | * @param canvas Canvas element
553 | * @param fileName file name
554 | */
555 | private toJPEG(canvas: HTMLCanvasElement, fileName: string) {
556 | const uri = canvas.toDataURL('image/jpeg', 0.85);
557 | const a = document.createElement('a');
558 | a.href = uri;
559 | a.download = fileName;
560 | a.click();
561 | a.remove();
562 | }
563 |
564 | /**
565 | * Convert Map object to PDF
566 | * @param map Map object
567 | * @param fileName file name
568 | */
569 | private toPDF(map: MaplibreMap | MapboxMap, fileName: string) {
570 | const canvas = map.getCanvas();
571 | const pdf = new jsPDF({
572 | orientation: this.width > this.height ? 'l' : 'p',
573 | unit: this.unit,
574 | compress: true,
575 | format: [this.width, this.height]
576 | });
577 |
578 | pdf.addImage(
579 | canvas.toDataURL('image/png'),
580 | 'png',
581 | 0,
582 | 0,
583 | this.width,
584 | this.height,
585 | undefined,
586 | 'FAST'
587 | );
588 |
589 | const { lng, lat } = map.getCenter();
590 | pdf.setProperties({
591 | title: map.getStyle().name,
592 | subject: `center: [${lng}, ${lat}], zoom: ${map.getZoom()}`,
593 | creator: 'Mapbox GL Export Plugin',
594 | author: '(c)Mapbox, (c)OpenStreetMap'
595 | });
596 |
597 | pdf.save(fileName);
598 | }
599 |
600 | /**
601 | * Convert canvas to SVG
602 | * @param canvas Canvas element
603 | * @param fileName file name
604 | */
605 | private toSVG(canvas: HTMLCanvasElement, fileName: string) {
606 | const uri = canvas.toDataURL('image/png');
607 |
608 | const pxWidth = Number(this.toPixels(this.width, this.dpi).replace('px', ''));
609 | const pxHeight = Number(this.toPixels(this.height, this.dpi).replace('px', ''));
610 |
611 | const svg = `
612 | `;
622 |
623 | const a = document.createElement('a');
624 | a.href = `data:application/xml,${encodeURIComponent(svg)}`;
625 | a.download = fileName;
626 | a.click();
627 | a.remove();
628 | }
629 |
630 | /**
631 | * Convert mm/inch to pixel
632 | * @param length mm/inch length
633 | * @param conversionFactor DPI value. default is 96.
634 | */
635 | private toPixels(length: number, conversionFactor = 96) {
636 | if (this.unit === Unit.mm) {
637 | conversionFactor /= 25.4;
638 | }
639 | return `${conversionFactor * length}px`;
640 | }
641 |
642 | /**
643 | * Add loader in the parent element of maplibre map.
644 | */
645 | private addLoader() {
646 | const canvas = this.map.getCanvas();
647 | const grandParent = canvas.parentElement?.parentElement;
648 | if (!grandParent) return;
649 | const loaderElements = grandParent.getElementsByClassName('map-export-loader');
650 | if (loaderElements.length > 0) return;
651 | const loader = document.createElement('span');
652 | loader.classList.add('map-export-loader');
653 | loader.classList.add('loader-default');
654 | grandParent.appendChild(loader);
655 | }
656 |
657 | /**
658 | * Show loader
659 | */
660 | private showLoader() {
661 | const canvas = this.map.getCanvas();
662 | const grandParent = canvas.parentElement?.parentElement;
663 | if (!grandParent) return;
664 | const loaderElements = grandParent.getElementsByClassName('map-export-loader');
665 | if (loaderElements && loaderElements.length > 0) {
666 | loaderElements.item(0)?.classList.add('is-active');
667 | }
668 | }
669 |
670 | /**
671 | * Hide loader
672 | */
673 | private hideLoader() {
674 | const canvas = this.map.getCanvas();
675 | const grandParent = canvas.parentElement?.parentElement;
676 | if (!grandParent) return;
677 | const loaderElements = grandParent.getElementsByClassName('map-export-loader');
678 | if (loaderElements && loaderElements.length > 0) {
679 | loaderElements.item(0)?.classList.remove('is-active');
680 | }
681 | }
682 | }
683 |
--------------------------------------------------------------------------------