├── 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 | [![version](https://img.shields.io/npm/v/@watergis/maplibre-gl-export.svg)](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 | [![version](https://img.shields.io/npm/v/@watergis/mapbox-gl-export.svg)](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 | ![License](https://img.shields.io/github/license/watergis/maplibre-gl-export) 4 | [![build](https://img.shields.io/github/actions/workflow/status/watergis/maplibre-gl-export/build.yml)](https://github.com/watergis/maplibre-gl-export/actions/workflows/build.yml) 5 | ![GitHub repo size](https://img.shields.io/github/repo-size/watergis/maplibre-gl-export) 6 | 7 | This monorepo manages the source code and demo page for `@watergis/maplibre-gl-export` and `@watergis/mapbox-gl-export`. 8 | 9 | ![plugin-image](./sites/demo/static/assets/plugin-overview.webp) 10 | 11 | ## Repositories 12 | 13 | | repository | version | description | changelog | 14 | |---|---|---|---| 15 | |[@watergis/maplibre-gl-export](./packages/maplibre-gl-export/)| [![version](https://img.shields.io/npm/v/@watergis/mapbox-gl-export.svg)](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/)| [![version](https://img.shields.io/npm/v/@watergis/maplibre-gl-export.svg)](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 |
78 | 91 | {data.metadata.title} 92 |
93 | {/snippet} 94 | {#snippet trail()} 95 | 98 | 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 | Overview of Plugin 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 | 247 | 248 | 310 | 311 | 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 | 348 | 349 | 350 | 351 | 352 | 353 | {#each parameters as param (parameters.indexOf(param))} 354 | {#if !(tabSet === 'maplibre' && param.name === 'accessToken')} 355 | 356 | 357 | 360 | 361 | 362 | {/if} 363 | {/each} 364 | 365 |
ParameterDefault valueDescription
{param.name} 358 | {param.default} 359 | {param.description}
366 |
367 | 368 |
369 | See implementation 375 |
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 | 619 | 621 | `; 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 | --------------------------------------------------------------------------------