react-spatial
Version:
Components to build React map apps.
502 lines (443 loc) • 15.1 kB
JavaScript
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import OLMap from 'ol/Map';
import { getTopLeft, getBottomRight } from 'ol/extent';
import Button from '../Button';
import NorthArrowSimple from '../../images/northArrow.url.svg';
import NorthArrowCircle from '../../images/northArrowCircle.url.svg';
var propTypes = {
/**
* Title of the button.
*/
title: PropTypes.string.isRequired,
/**
* Children content of the button.
*/
children: PropTypes.node.isRequired,
/**
* CSS class of the button.
*/
className: PropTypes.string,
/**
* HTML tabIndex attribute
*/
tabIndex: PropTypes.number,
/**
* HTML disabled attribute
*/
disabled: PropTypes.bool,
/**
* Format to save the image.
*/
saveFormat: PropTypes.oneOf(['image/jpeg', 'image/png']),
/** An existing [ol/Map](https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html). */
map: PropTypes.instanceOf(OLMap),
/**
* Extent for the export. If no extent is given, the whole map is exported.
*/
extent: PropTypes.arrayOf(PropTypes.number),
/**
* Array of 4 [ol/Coordinate](https://openlayers.org/en/latest/apidoc/module-ol_coordinate.html#~Coordinate).
* If no coordinates and no extent are given, the whole map is exported.
* This property must be used to export rotated map.
* If you don't need to export rotated map the extent property can be used as well.
* If extent is specified, coordinates property is ignored.
*/
coordinates: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)),
/**
* Scale the map for better quality. Possible values: 1, 2 or 3.
* WARNING: The tiled layer with a WMTS or XYZ source must provides an url
* for each scale in the config file.
*/
scale: PropTypes.number,
/**
* Function called before the dowload process begins.
*/
onSaveStart: PropTypes.func,
/**
* Function called after the dowload process ends.
*
* @param {object} error Error message the process fails.
*/
onSaveEnd: PropTypes.func,
/**
* Extra data, such as copyright, north arrow configuration.
* All extra data is optional.
*
* Example 1:
*
{
copyright: {
text: 'Example copyright', // Copyright text or function
font: '10px Arial', // Font, default is '12px Arial'
fillStyle: 'blue', // Fill style, default is 'black'
},
northArrow, // True if the north arrow
// should be placed with default configuration
// (default image, rotation=0, circled=False)
}
* Example 2:
*
{
northArrow: {
src: NorthArrowCustom,
width: 60, // Width in px, default is 80
height: 100, // Height in px, default is 80
rotation: 25, // Absolute rotation in degrees as number or function
}
}
* Example 3:
*
{
copyright: {
text: () => { // Copyright as function
return this.getCopyright();
},
},
northArrow: {
rotation: () => { // Rotation as function
return NorthArrow.radToDeg(this.map.getView().getRotation());
},
circled, // Display circle around the north arrow (Does not work for custom src)
},
}
*/
extraData: PropTypes.object,
};
var defaultProps = {
map: null,
tabIndex: 0,
className: 'tm-canvas-save-button tm-button',
saveFormat: 'image/png',
extent: null,
extraData: null,
coordinates: null,
scale: 1,
disabled: undefined,
onSaveStart: function (map) { return Promise.resolve(map); },
onSaveEnd: function () {},
};
/**
* This component displays a button to save canvas as an image.
*/
var CanvasSaveButton = /*@__PURE__*/(function (PureComponent) {
function CanvasSaveButton(props) {
PureComponent.call(this, props);
var ref = this.props;
var saveFormat = ref.saveFormat;
this.options = {
format: saveFormat,
};
this.fileExt = this.options.format === 'image/jpeg' ? 'jpg' : 'png';
this.padding = 5;
}
if ( PureComponent ) CanvasSaveButton.__proto__ = PureComponent;
CanvasSaveButton.prototype = Object.create( PureComponent && PureComponent.prototype );
CanvasSaveButton.prototype.constructor = CanvasSaveButton;
CanvasSaveButton.prototype.getDownloadImageName = function getDownloadImageName () {
return (
"" + (window.document.title.replace(/ /g, '_').toLowerCase()) +
"." + (this.fileExt)
);
};
// Ensure the font size fita with the image width.
CanvasSaveButton.prototype.decreaseFontSize = function decreaseFontSize (destContext, maxWidth, copyright, scale) {
var minFontSize = 8;
var sizeMatch;
var fontSize;
do {
sizeMatch = destContext.font.match(/[0-9]+(?:\.[0-9]+)?(px)/i);
fontSize = parseInt(sizeMatch[0].replace(sizeMatch[1], ''), 10);
// eslint-disable-next-line no-param-reassign
destContext.font = destContext.font.replace(fontSize, fontSize - 1);
if (fontSize - 1 === minFontSize) {
this.multilineCopyright = true;
}
} while (
fontSize - 1 > minFontSize &&
destContext.measureText(copyright).width * scale > maxWidth
);
return destContext.font;
};
// If minimal fontsize is reached, divide copyright in two lines.
CanvasSaveButton.prototype.splitCopyrightLine = function splitCopyrightLine (destContext, destCanvas, maxWidth, copyright, scale) {
var newCopyright = copyright;
var wordNumber = copyright.split(' ').length;
for (var i = 0; i < wordNumber; i += 1) {
newCopyright = newCopyright.substring(0, newCopyright.lastIndexOf(' '));
// Stop removing word when fits within one line.
if (destContext.measureText(newCopyright).width * scale < maxWidth) {
break;
}
}
// Draw fist line (line break isn't supported for fillText).
destContext.fillText(
newCopyright,
this.padding,
destCanvas.height / scale - 3 * this.padding
);
// Draw second line.
destContext.fillText(
copyright.replace(newCopyright, ''),
this.padding,
destCanvas.height / scale - this.padding
);
};
CanvasSaveButton.prototype.drawCopyright = function drawCopyright (destContext, destCanvas, maxWidth) {
var ref = this.props;
var extraData = ref.extraData;
var scale = ref.scale;
var ref$1 = extraData.copyright;
var text = ref$1.text;
var font = ref$1.font;
var fillStyle = ref$1.fillStyle;
var copyright = typeof text === 'function' ? text() : text;
this.multilineCopyright = false;
destContext.save();
destContext.scale(scale, scale);
// eslint-disable-next-line no-param-reassign
destContext.font = font || '12px Arial';
// eslint-disable-next-line no-param-reassign
destContext.fillStyle = fillStyle || 'black';
this.decreaseFontSize(destContext, maxWidth, copyright, scale);
if (this.multilineCopyright) {
this.splitCopyrightLine(
destContext,
destCanvas,
maxWidth,
copyright,
scale
);
} else {
destContext.fillText(
copyright,
this.padding,
destCanvas.height / scale - this.padding
);
}
destContext.restore();
};
CanvasSaveButton.prototype.drawNorthArrow = function drawNorthArrow (destContext, destCanvas) {
var this$1 = this;
var ref = this.props;
var scale = ref.scale;
var extraData = ref.extraData;
var ref$1 = extraData.northArrow;
var src = ref$1.src;
var circled = ref$1.circled;
var width = ref$1.width;
var height = ref$1.height;
var rotation = ref$1.rotation;
return new Promise(function (resolve) {
var img = new Image();
img.crossOrigin = 'Anonymous';
img.src = src || (circled ? NorthArrowCircle : NorthArrowSimple);
img.onload = function () {
destContext.save();
var arrowWidth = (width || 80) * scale;
var arrowHeight = (height || 80) * scale;
destContext.translate(
destCanvas.width - 2 * this$1.padding - arrowWidth / 2,
destCanvas.height - 2 * this$1.padding - arrowHeight / 2
);
if (rotation) {
var angle = typeof rotation === 'function' ? rotation() : rotation;
destContext.rotate(angle * (Math.PI / 180));
}
destContext.drawImage(
img,
-arrowWidth / 2,
-arrowHeight / 2,
arrowWidth,
arrowHeight
);
destContext.restore();
// Return the pixels width of the arrow and the margin right,
// that must not be occupied by the copyright.
resolve(arrowWidth + 2 * this$1.padding);
};
img.onerror = function () {
resolve();
};
});
};
CanvasSaveButton.prototype.calculatePixelsToExport = function calculatePixelsToExport (mapToExport) {
var assign;
var ref = this.props;
var extent = ref.extent;
var coordinates = ref.coordinates;
var firstCoordinate;
var oppositeCoordinate;
if (extent) {
firstCoordinate = getTopLeft(extent);
oppositeCoordinate = getBottomRight(extent);
} else if (coordinates) {
// In case of coordinates coming from DragBox interaction:
// firstCoordinate is the first coordinate drawn by the user.
// oppositeCoordinate is the coordinate of the point dragged by the user.
(assign = coordinates, firstCoordinate = assign[0], oppositeCoordinate = assign[2]);
}
if (firstCoordinate && oppositeCoordinate) {
var firstPixel = mapToExport.getPixelFromCoordinate(firstCoordinate);
var oppositePixel = mapToExport.getPixelFromCoordinate(
oppositeCoordinate
);
var pixelTopLeft = [
firstPixel[0] <= oppositePixel[0] ? firstPixel[0] : oppositePixel[0],
firstPixel[1] <= oppositePixel[1] ? firstPixel[1] : oppositePixel[1] ];
var pixelBottomRight = [
firstPixel[0] > oppositePixel[0] ? firstPixel[0] : oppositePixel[0],
firstPixel[1] > oppositePixel[1] ? firstPixel[1] : oppositePixel[1] ];
return {
x: pixelTopLeft[0],
y: pixelTopLeft[1],
w: pixelBottomRight[0] - pixelTopLeft[0],
h: pixelBottomRight[1] - pixelTopLeft[1],
};
}
return null;
};
CanvasSaveButton.prototype.createCanvasImage = function createCanvasImage (mapToExport) {
var this$1 = this;
var ref = this.props;
var extraData = ref.extraData;
return new Promise(function (resolve) {
mapToExport.once('rendercomplete', function () {
// Find all layer canvases and add it to dest canvas.
var canvases = mapToExport
.getTargetElement()
.getElementsByTagName('canvas');
// Create the canvas to export with the good size.
var destCanvas;
var destContext;
canvases.forEach(function (canvas) {
if (!canvas.width || !canvas.height) {
return;
}
var clip = this$1.calculatePixelsToExport(mapToExport) || {
x: 0,
y: 0,
w: canvas.width,
h: canvas.height,
};
if (!destCanvas) {
destCanvas = document.createElement('canvas');
destCanvas.width = clip.w;
destCanvas.height = clip.h;
destContext = destCanvas.getContext('2d');
}
// Draw canvas to the canvas to export.
destContext.drawImage(
canvas,
clip.x,
clip.y,
clip.w,
clip.h,
0,
0,
destCanvas.width,
destCanvas.height
);
});
// North arrow
var p = Promise.resolve();
if (destContext && extraData && extraData.northArrow) {
p = this$1.drawNorthArrow(destContext, destCanvas);
}
p.then(function (arrowWidth) {
// Copyright
if (
destContext &&
extraData &&
extraData.copyright &&
extraData.copyright.text
) {
var maxWidth = arrowWidth
? destContext.canvas.width - arrowWidth
: destContext.canvas.width;
this$1.drawCopyright(destContext, destCanvas, maxWidth);
}
resolve(destCanvas);
});
});
mapToExport.renderSync();
});
};
CanvasSaveButton.prototype.downloadCanvasImage = function downloadCanvasImage (canvas) {
var this$1 = this;
if (/msie (9|10)/gi.test(window.navigator.userAgent.toLowerCase())) {
// ie 9 and 10
var url = canvas.toDataURL(this.options.format);
var w = window.open('about:blank', '');
w.document.write(("<img src=\"" + url + "\" alt=\"from canvas\"/>"));
} else if (window.navigator.msSaveBlob) {
// ie 11 and higher
var image;
try {
image = canvas.msToBlob();
} catch (e) {
// eslint-disable-next-line no-console
console.log(e);
}
window.navigator.msSaveBlob(
new Blob([image], {
type: this.options.format,
}),
this.getDownloadImageName()
);
} else {
// Use blob for large images
canvas.toBlob(function (blob) {
var link = document.createElement('a');
link.download = this$1.getDownloadImageName();
link.href = URL.createObjectURL(blob);
// append child to document for firefox to be able to download.
document.body.appendChild(link);
link.click();
}, this.options.format);
}
};
CanvasSaveButton.prototype.render = function render () {
var this$1 = this;
var ref = this.props;
var title = ref.title;
var map = ref.map;
var children = ref.children;
var tabIndex = ref.tabIndex;
var className = ref.className;
var disabled = ref.disabled;
var onSaveStart = ref.onSaveStart;
var onSaveEnd = ref.onSaveEnd;
return (
React.createElement( Button, {
className: className, title: title, tabIndex: tabIndex, disabled: disabled, onClick: function (e) {
if (window.navigator.msSaveBlob) {
// ie only
e.preventDefault();
e.stopPropagation();
}
onSaveStart(map).then(function (mapToExport) {
return this$1.createCanvasImage(mapToExport || map)
.then(function (canvas) {
this$1.downloadCanvasImage(canvas);
onSaveEnd(mapToExport);
})
.catch(function (err) {
if (err) {
// eslint-disable-next-line no-console
console.error(err);
}
onSaveEnd(mapToExport, err);
});
});
} },
children
)
);
};
return CanvasSaveButton;
}(PureComponent));
CanvasSaveButton.propTypes = propTypes;
CanvasSaveButton.defaultProps = defaultProps;
export default CanvasSaveButton;
//# sourceMappingURL=CanvasSaveButton.js.map