Give inkmap a JSON spec and it will use it to generate a PNG image in the background.
Use the print API to begin printing a spec. This function returns a Promise which resolves to a Blob containing the map image.
import { downloadBlob, print } from '@camptocamp/inkmap';
const root = document.getElementById('example-01');
const btn = /** @type {CustomButton} */ root.querySelector('custom-button');
const spec = /** @type {PrintSpecEditor} */ root.querySelector('print-spec');
// make sure the spec is valid to allow printing
spec.onValidityCheck((valid) => (btn.enabled = valid));
btn.addEventListener('click', async () => {
btn.showSpinner();
// create a job, get a promise that resolves when the job is finished
const blob = await print(spec.value);
btn.hideSpinner();
downloadBlob(blob, 'inkmap.png');
});
inkmap also enables finer monitoring by making the job status observable.
Use the enqueuePrint API to start a print job asynchronously. This function returns a Promise which resolves to a job id. You can then feed the job id to getJobStatus which will return an observable that you can subscribe to, emitting updates on the given print job status.
import { downloadBlob, getJobStatus, queuePrint } from '@camptocamp/inkmap';
const root = document.getElementById('example-02');
const btn = /** @type {CustomButton} */ root.querySelector('custom-button');
const bar = /** @type {CustomProgress} */ root.querySelector('custom-progress');
const spec = /** @type {PrintSpecEditor} */ root.querySelector('print-spec');
// make sure the spec is valid to allow printing
spec.onValidityCheck((valid) => (btn.enabled = valid));
btn.addEventListener('click', async () => {
btn.showSpinner();
// display the job progress
bar.progress = 0;
bar.status = 'pending';
// create a job, get a promise that resolves with the job id
const jobId = await queuePrint(spec.value);
getJobStatus(jobId).subscribe((printStatus) => {
// update the job progress
bar.progress = printStatus.progress;
bar.status = printStatus.status;
// job is finished
if (printStatus.progress === 1) {
btn.hideSpinner();
downloadBlob(printStatus.imageBlob, 'inkmap.png');
}
});
});
inkmap also enables job cancellation.
Use cancelJob after having started a job with enqueuePrint.
import {
cancelJob,
downloadBlob,
getJobStatus,
queuePrint,
} from '@camptocamp/inkmap';
const root = document.getElementById('example-03');
const btn = /** @type {CustomButton} */ root.querySelector('custom-button');
const bar = /** @type {CustomProgress} */ root.querySelector('custom-progress');
const spec = /** @type {PrintSpecEditor} */ root.querySelector('print-spec');
const btnCancel = /** @type {Button} */ root.querySelector('.cancel-btn');
// make sure the spec is valid to allow printing
spec.onValidityCheck((valid) => (btn.enabled = valid));
let jobId;
btn.addEventListener('click', async () => {
btn.showSpinner();
// display the job progress
bar.progress = 0;
bar.status = 'pending';
// create a job, get a promise that resolves with the job id
jobId = await queuePrint(spec.value);
getJobStatus(jobId).subscribe((printStatus) => {
// update the job progress
bar.progress = printStatus.progress;
bar.status = printStatus.status;
// job is finished or canceled
if (printStatus.progress === 1 || printStatus.progress === -1) {
btn.hideSpinner();
}
// job is finished
if (printStatus.progress === 1) {
downloadBlob(printStatus.imageBlob, 'inkmap.png');
}
});
});
btnCancel.addEventListener('click', async () => {
// cancel job based on job id
cancelJob(jobId);
});
Multiple jobs can be monitored at once through a long lived observable.
Use getJobsStatus to receive a long-lived observable that you can subscribe to and which will regularly give you status updates on all running jobs.
import {
getJobsStatus,
queuePrint,
downloadBlob,
getJobStatus,
} from '@camptocamp/inkmap';
const root = document.getElementById('example-04');
const btn = /** @type {CustomButton} */ root.querySelector('custom-button');
const bars = /** @type {ProgressBars} */ root.querySelector('progress-bars');
const spec = /** @type {PrintSpecEditor} */ root.querySelector('print-spec');
// make sure the spec is valid to allow printing
spec.onValidityCheck((valid) => (btn.enabled = valid));
// subscribe to the jobs status updates
// ATTENTION! subscriptions to long-running observables might cause memory leaks!
getJobsStatus().subscribe((jobs) => {
bars.jobsStatus = jobs;
});
btn.addEventListener('click', async () => {
// create a job, get a promise that resolves with the job id
const jobId = await queuePrint(spec.value);
getJobStatus(jobId).subscribe((printStatus) => {
// job is finished
if (printStatus.progress === 1) {
downloadBlob(printStatus.imageBlob, `inkmap-${jobId}.png`);
}
});
});
inkmap jobs give back a Blob which can be inserted into a PDF. This is for example supported by jsPDF, as showcased here.
import { print, getAttributionsText, getNorthArrow } from '@camptocamp/inkmap';
import { jsPDF } from 'jspdf';
import { getScaleBar } from '../../src/main';
const root = document.getElementById('example-05');
const btn = /** @type {CustomButton} */ root.querySelector('custom-button');
const spec = /** @type {PrintSpecEditor} */ root.querySelector('print-spec');
// make sure the spec is valid to allow printing
spec.onValidityCheck((valid) => (btn.enabled = valid));
btn.addEventListener('click', async () => {
btn.showSpinner();
const mapWidth = 277; // mm
const mapHeight = 150; // mm
// Force map size to fit the PDF document
const specValue = {
...spec.value,
size: [mapWidth, mapHeight, 'mm'],
attributions: null, // do not print widgets on the map
northArrow: false,
scaleBar: false,
};
// create a job, get a promise that resolves when the job is finished
const blob = await print(specValue);
btn.hideSpinner();
// initializes the PDF document
const doc = new jsPDF({
orientation: 'landscape',
unit: 'mm',
format: 'a4', // 210 by 297mm
putOnlyUsedFonts: true,
});
// create an Object URL from the map image blob and add it to the PDF
const imgUrl = URL.createObjectURL(blob);
doc.addImage(imgUrl, 'JPEG', 10, 40, mapWidth, mapHeight);
// add a title
doc.setFont('times', 'bold');
doc.setFontSize(20);
doc.text('A fantastic map.', 148.5, 13, null, null, 'center');
// add north arrow
const arrow = getNorthArrow(specValue, [16, 'mm']);
const arrowSizeMm = arrow.getRealWorldDimensions('mm');
doc.addImage(
arrow.getImage(),
'PNG',
140,
21,
arrowSizeMm[0],
arrowSizeMm[1]
);
// add scalebar next to the north arrow
const scalebar = getScaleBar(specValue, [30, 'mm']);
const scalebarSizeMm = scalebar.getRealWorldDimensions('mm');
doc.addImage(
scalebar.getImage(),
'PNG',
287 - scalebarSizeMm[0],
37 - scalebarSizeMm[1],
scalebarSizeMm[0],
scalebarSizeMm[1]
);
// add a creation date
doc.setFont('courier', 'normal');
doc.setFontSize(12);
doc.text(
`Print date : ${new Date().toLocaleString()}`,
10,
200,
null,
null,
'left'
);
// add attribution
doc.setFont('courier', 'normal');
doc.setFontSize(12);
doc.text(getAttributionsText(spec.value), 287, 200, null, null, 'right');
// download the result
doc.save('inkmap.pdf');
});
inkmap supports a large amount of projections for rendering the map. Projections can be defined and registered using registerProjection. If the map projection is not registered, inkmap will look it up on epsg.io.
Under the hood, inkmap uses proj4js.
import { downloadBlob, print, registerProjection } from '@camptocamp/inkmap';
const root = document.getElementById('example-06');
const btn = /** @type {CustomButton} */ root.querySelector('custom-button');
const spec = /** @type {PrintSpecEditor} */ root.querySelector('print-spec');
// make sure the spec is valid to allow printing
spec.onValidityCheck((valid) => (btn.enabled = valid));
btn.addEventListener('click', async () => {
btn.showSpinner();
// registers the projection EPSG:2154
registerProjection({
name: 'EPSG:2154',
bbox: [51.56, -9.86, 41.15, 10.38],
proj4:
'+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs',
});
// create a job, get a promise that resolves when the job is finished
const blob = await print(spec.value);
btn.hideSpinner();
downloadBlob(blob, 'inkmap.png');
});
When errors are encountered while loading data for one or several layers, inkmap includes a list of error objects with urls in the status updates.
import { downloadBlob, getJobStatus, queuePrint } from '@camptocamp/inkmap';
const root = document.getElementById('example-07');
const btn = /** @type {CustomButton} */ root.querySelector('custom-button');
const spec = /** @type {PrintSpecEditor} */ root.querySelector('print-spec');
const errors = root.querySelector('#errors');
// make sure the spec is valid to allow printing
spec.onValidityCheck((valid) => (btn.enabled = valid));
btn.addEventListener('click', async () => {
btn.showSpinner();
// create a job, get a promise that resolves with the job id
const jobId = await queuePrint(spec.value);
getJobStatus(jobId).subscribe((status) => {
// display the job progress
btn.progress = status.progress;
// job is finished
if (status.progress === 1) {
btn.hideSpinner();
// display urls with errors
if (status.sourceLoadErrors.length > 0) {
let errorMessage = 'The following layers encountered errors:<br>';
status.sourceLoadErrors.forEach((element) => {
errorMessage = `${errorMessage} - ${element.url}<br>`;
});
errors.innerHTML = errorMessage;
} else {
errors.innerHTML = '';
}
downloadBlob(status.imageBlob, 'inkmap.png');
}
});
});
This examples shows how to print legends for vector and rasterlayers by providing the configuration option legend on a layer with a value of true. Currently this supports WMS, WMTS, GeoJSON and WFS layers.
The print will generate a separate image containing all the legends at once.
import { downloadBlob, getJobStatus, queuePrint } from '@camptocamp/inkmap';
import { createLegends } from '../../src/main';
const root = document.getElementById('example-08');
const mapBtn = /** @type {CustomButton} */ root.querySelector(
'custom-button.map-btn'
);
const legendBtn = /** @type {CustomButton} */ root.querySelector(
'custom-button.legend-btn'
);
root.querySelector('custom-button.legend-btn');
const bar = /** @type {CustomProgress} */ root.querySelector('custom-progress');
const spec = /** @type {PrintSpecEditor} */ root.querySelector('print-spec');
// make sure the spec is valid to allow printing
spec.onValidityCheck((valid) => {
mapBtn.enabled = valid;
legendBtn.enabled = valid;
});
mapBtn.addEventListener('click', async () => {
mapBtn.showSpinner();
// display the job progress
bar.progress = 0;
bar.status = 'pending';
// create a job, get a promise that resolves with the job id
const jobId = await queuePrint(spec.value);
getJobStatus(jobId).subscribe((printStatus) => {
// update the job progress
bar.progress = printStatus.progress;
bar.status = printStatus.status;
// job is finished
if (printStatus.progress === 1) {
mapBtn.hideSpinner();
downloadBlob(printStatus.imageBlob, 'inkmap.png');
}
});
});
legendBtn.addEventListener('click', async () => {
const blob = await createLegends(spec.value);
downloadBlob(blob, 'legend.svg');
});