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');
});