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 queuePrint()
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 queuePrint()
.
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/index.js';
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 rendering maps in many different projections. Projections can be defined using the projectionDefinitions
field of a spec. If a projection is not defined in the spec but still needed for rendering, inkmap will look it up on epsg.io. This only works for projections with an EPSG code!
Under the hood, inkmap uses proj4js.
import { downloadBlob, print } 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();
// 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 raster layers by providing the configuration option legend
on a layer with a value of true
. Currently this supports WMS, WMTS, GeoJSON and WFS layers. Use the createLegends()
function to generate the print spec legends.
The print will generate a separate image containing all the legends at once.
import {
createLegends,
downloadBlob,
getJobStatus,
queuePrint,
} from '@camptocamp/inkmap';
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');
});