src/utils/export-utils.js (148 lines of code) (raw):

// Copyright (c) 2020 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // @ts-nocheck import domtoimage from 'utils/dom-to-image'; import {Blob, URL, atob, Uint8Array, ArrayBuffer, document} from 'global/window'; import { EXPORT_IMG_RESOLUTION_OPTIONS, EXPORT_IMG_RATIO_OPTIONS, RESOLUTIONS, EXPORT_IMG_RATIOS, EXPORT_DATA_TYPE } from 'constants/default-settings'; import {exportMapToHTML} from 'templates/export-map-html'; import {formatCsv} from 'processors/data-processor'; import get from 'lodash.get'; import {set, generateHashId} from 'utils/utils'; /** * Default file names */ export const DEFAULT_IMAGE_NAME = 'kepler-gl.png'; export const DEFAULT_HTML_NAME = 'kepler.gl.html'; export const DEFAULT_JSON_NAME = 'keplergl.json'; export const DEFAULT_DATA_NAME = 'kepler-gl'; /** * Default json export settings * @type {{hasData: boolean}} */ export const DEFAULT_EXPORT_JSON_SETTINGS = { hasData: true }; const defaultResolution = EXPORT_IMG_RESOLUTION_OPTIONS.find(op => op.id === RESOLUTIONS.ONE_X); const defaultRatio = EXPORT_IMG_RATIO_OPTIONS.find(op => op.id === EXPORT_IMG_RATIOS.FOUR_BY_THREE); export function isMSEdge(window) { return Boolean(window.navigator && window.navigator.msSaveOrOpenBlob); } export function getScaleFromImageSize(imageW, imageH, mapW, mapH) { if ([imageW, imageH, mapW, mapH].some(d => d <= 0)) { return 1; } const base = imageW / imageH > 1 ? imageW : imageH; const mapBase = imageW / imageH > 1 ? mapW : mapH; const scale = base / mapBase; return scale; } export function calculateExportImageSize({mapW, mapH, ratio, resolution}) { if (mapW <= 0 || mapH <= 0) { return null; } const ratioItem = EXPORT_IMG_RATIO_OPTIONS.find(op => op.id === ratio) || defaultRatio; const resolutionItem = EXPORT_IMG_RESOLUTION_OPTIONS.find(op => op.id === resolution) || defaultResolution; const {width: scaledWidth, height: scaledHeight} = resolutionItem.getSize(mapW, mapH); const {width: imageW, height: imageH} = ratioItem.getSize(scaledWidth, scaledHeight); const {scale} = ratioItem.id === EXPORT_IMG_RATIOS.CUSTOM ? {} : resolutionItem; return { scale, imageW, imageH }; } export function convertToPng(sourceElem, options) { return domtoimage.toPng(sourceElem, options); } export function dataURItoBlob(dataURI) { const binary = atob(dataURI.split(',')[1]); // separate out the mime component const mimeString = dataURI .split(',')[0] .split(':')[1] .split(';')[0]; // write the bytes of the string to an ArrayBuffer const ab = new ArrayBuffer(binary.length); // create a view into the buffer const ia = new Uint8Array(ab); for (let i = 0; i < binary.length; i++) { ia[i] = binary.charCodeAt(i); } return new Blob([ab], {type: mimeString}); } export function downloadFile(fileBlob, fileName) { if (isMSEdge(window)) { window.navigator.msSaveOrOpenBlob(fileBlob, fileName); } else { const url = URL.createObjectURL(fileBlob); const link = document.createElement('a'); link.setAttribute('href', url); link.setAttribute('download', fileName); document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } } export function exportImage(state) { const {imageDataUri} = state.uiState.exportImage; if (imageDataUri) { const file = dataURItoBlob(imageDataUri); downloadFile(file, DEFAULT_IMAGE_NAME); } } export function exportToJsonString(data) { try { return JSON.stringify(data); } catch (e) { return e.description; } } export function getMapJSON(state, options = DEFAULT_EXPORT_JSON_SETTINGS) { const {hasData} = options; const schema = state.visState.schema; if (!hasData) { return schema.getConfigToSave(state); } let mapToSave = schema.save(state); // add file name if title is not provided const title = get(mapToSave, ['info', 'title']); if (!title || !title.length) { mapToSave = set(['info', 'title'], `keplergl_${generateHashId(6)}`, mapToSave); } return mapToSave; } export function exportJson(state, options = {}) { const map = getMapJSON(state, options); const fileBlob = new Blob([exportToJsonString(map)], {type: 'application/json'}); downloadFile(fileBlob, DEFAULT_JSON_NAME); } export function exportHtml(state, options) { const {userMapboxToken, exportMapboxAccessToken, mode} = options; const data = { ...getMapJSON(state), mapboxApiAccessToken: (userMapboxToken || '') !== '' ? userMapboxToken : exportMapboxAccessToken, mode }; const fileBlob = new Blob([exportMapToHTML(data)], {type: 'text/html'}); downloadFile(fileBlob, DEFAULT_HTML_NAME); } export function exportData(state, option) { const {visState} = state; const {datasets} = visState; const {selectedDataset, dataType, filtered} = option; // get the selected data const filename = DEFAULT_DATA_NAME; const selectedDatasets = datasets[selectedDataset] ? [datasets[selectedDataset]] : Object.values(datasets); if (!selectedDatasets.length) { // error: selected dataset not found. return; } selectedDatasets.forEach(selectedData => { const {allData, fields, label, filteredIdxCPU = []} = selectedData; const toExport = filtered ? filteredIdxCPU.map(i => allData[i]) : allData; // start to export data according to selected data type switch (dataType) { case EXPORT_DATA_TYPE.CSV: { const csv = formatCsv(toExport, fields); const fileBlob = new Blob([csv], {type: 'text/csv'}); downloadFile(fileBlob, `${filename}_${label}.csv`); break; } // TODO: support more file types. default: break; } }); } export function exportMap(state, option) { const {imageDataUri} = state.uiState.exportImage; const thumbnail = imageDataUri ? dataURItoBlob(imageDataUri) : null; const mapToSave = getMapJSON(state, option); return { map: mapToSave, thumbnail }; } const exporters = { exportImage, exportJson, exportHtml, exportData }; export default exporters;