Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Maps] Load Maki icons from spritesheet #42499

Merged
merged 10 commits into from
Aug 6, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,39 @@ 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.

---
This product includes code that is adapted from mapbox-gl-js, which is
available under a "BSD-3-Clause" license.
https://github.com/mapbox/mapbox-gl-js/blob/master/src/util/image.js

Copyright (c) 2016, Mapbox

All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Mapbox GL JS nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

---
This product includes code that is based on facebookincubator/idx, which was
available under a "MIT" license.
Expand Down
378 changes: 0 additions & 378 deletions packages/kbn-maki/index.js

This file was deleted.

10 changes: 0 additions & 10 deletions packages/kbn-maki/package.json

This file was deleted.

6 changes: 0 additions & 6 deletions packages/kbn-maki/readme.md

This file was deleted.

2 changes: 0 additions & 2 deletions x-pack/legacy/plugins/maps/common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
export const EMS_DATA_FILE_PATH = 'ems/file';
export const EMS_DATA_TMS_PATH = 'ems/tms';
export const EMS_META_PATH = 'ems/meta';
export const SPRITE_PATH = '/maps/sprite';
export const MAKI_SPRITE_PATH = `${SPRITE_PATH}/maki`;

export const MAP_SAVED_OBJECT_TYPE = 'map';
export const APP_ID = 'maps';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

/* @notice
* This product includes code that is adapted from mapbox-gl-js, which is
* available under a "BSD-3-Clause" license.
* https://github.com/mapbox/mapbox-gl-js/blob/master/src/util/image.js
*
* Copyright (c) 2016, Mapbox
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Mapbox GL JS nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import assert from 'assert';

function createImage(image, { width, height }, channels, data) {
if (!data) {
data = new Uint8Array(width * height * channels);
} else if (data instanceof Uint8ClampedArray) {
data = new Uint8Array(data.buffer);
} else if (data.length !== width * height * channels) {
throw new RangeError('mismatched image size');
}
image.width = width;
image.height = height;
image.data = data;
return image;
}

function resizeImage(image, { width, height }, channels) {
if (width === image.width && height === image.height) {
return;
}

const newImage = createImage({}, { width, height }, channels);

copyImage(image, newImage, { x: 0, y: 0 }, { x: 0, y: 0 }, {
width: Math.min(image.width, width),
height: Math.min(image.height, height)
}, channels);

image.width = width;
image.height = height;
image.data = newImage.data;
}

function copyImage(srcImg, dstImg, srcPt, dstPt, size, channels) {
if (size.width === 0 || size.height === 0) {
return dstImg;
}

if (size.width > srcImg.width ||
size.height > srcImg.height ||
srcPt.x > srcImg.width - size.width ||
srcPt.y > srcImg.height - size.height) {
throw new RangeError('out of range source coordinates for image copy');
}

if (size.width > dstImg.width ||
size.height > dstImg.height ||
dstPt.x > dstImg.width - size.width ||
dstPt.y > dstImg.height - size.height) {
throw new RangeError('out of range destination coordinates for image copy');
}

const srcData = srcImg.data;
const dstData = dstImg.data;

assert(srcData !== dstData);

for (let y = 0; y < size.height; y++) {
const srcOffset = ((srcPt.y + y) * srcImg.width + srcPt.x) * channels;
const dstOffset = ((dstPt.y + y) * dstImg.width + dstPt.x) * channels;
for (let i = 0; i < size.width * channels; i++) {
dstData[dstOffset + i] = srcData[srcOffset + i];
}
}

return dstImg;
}

export class AlphaImage {

constructor(size, data) {
createImage(this, size, 1, data);
}

resize(size) {
resizeImage(this, size, 1);
}

clone() {
return new AlphaImage({ width: this.width, height: this.height }, new Uint8Array(this.data));
}

static copy(srcImg, dstImg, srcPt, dstPt, size) {
copyImage(srcImg, dstImg, srcPt, dstPt, size, 1);
}
}

// Not premultiplied, because ImageData is not premultiplied.
// UNPACK_PREMULTIPLY_ALPHA_WEBGL must be used when uploading to a texture.
export class RGBAImage {

// data must be a Uint8Array instead of Uint8ClampedArray because texImage2D does not
// support Uint8ClampedArray in all browsers

constructor(size, data) {
createImage(this, size, 4, data);
}

resize(size) {
resizeImage(this, size, 4);
}

replace(data, copy) {
if (copy) {
this.data.set(data);
} else if (data instanceof Uint8ClampedArray) {
this.data = new Uint8Array(data.buffer);
} else {
this.data = data;
}
}

clone() {
return new RGBAImage({ width: this.width, height: this.height }, new Uint8Array(this.data));
}

static copy(srcImg, dstImg, srcPt, dstPt, size) {
copyImage(srcImg, dstImg, srcPt, dstPt, size, 4);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import _ from 'lodash';
import { RGBAImage } from './image_utils';

export function removeOrphanedSourcesAndLayers(mbMap, layerList) {

Expand Down Expand Up @@ -94,3 +95,32 @@ export function syncLayerOrderForSingleLayer(mbMap, layerList) {
});

}

function getImageData(img) {
const canvas = window.document.createElement('canvas');
const context = canvas.getContext('2d');
if (!context) {
throw new Error('failed to create canvas 2d context');
}
canvas.width = img.width;
canvas.height = img.height;
context.drawImage(img, 0, 0, img.width, img.height);
return context.getImageData(0, 0, img.width, img.height);
}

export async function addSpritesheetToMap(json, img, mbMap) {
const image = new Image();
image.onload = (el) => {
const imgData = getImageData(el.currentTarget);
for (const imageId in json) {
if (json.hasOwnProperty(imageId)) {
const { width, height, x, y, sdf, pixelRatio } = json[imageId];
const data = new RGBAImage({ width, height });
RGBAImage.copy(imgData, data, { x, y }, { x: 0, y: 0 }, { width, height });
// TODO not sure how to catch errors?
mbMap.addImage(imageId, data, { pixelRatio, sdf });
}
}
};
image.src = img;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import _ from 'lodash';
import React from 'react';
import ReactDOM from 'react-dom';
import { ResizeChecker } from 'ui/resize_checker';
import { syncLayerOrderForSingleLayer, removeOrphanedSourcesAndLayers } from './utils';
import {
syncLayerOrderForSingleLayer,
removeOrphanedSourcesAndLayers,
addSpritesheetToMap
} from './utils';
import {
DECIMAL_DEGREES_PRECISION,
FEATURE_ID_PROPERTY_NAME,
ZOOM_PRECISION,
MAKI_SPRITE_PATH
ZOOM_PRECISION
} from '../../../../common/constants';
import mapboxgl from 'mapbox-gl';
import MapboxDraw from '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw-unminified';
Expand All @@ -22,13 +25,11 @@ import { FeatureTooltip } from '../feature_tooltip';
import { DRAW_TYPE } from '../../../actions/map_actions';
import { createShapeFilterWithMeta, createExtentFilterWithMeta } from '../../../elasticsearch_geo_utils';
import chrome from 'ui/chrome';
import { spritesheet } from '@elastic/maki';
import sprites1 from '@elastic/maki/dist/sprite@1.png';
import sprites2 from '@elastic/maki/dist/sprite@2.png';

function relativeToAbsolute(url) {
const a = document.createElement('a');
a.setAttribute('href', url);
return a.href;
}

const isRetina = window.devicePixelRatio === 2;
const mbDrawModes = MapboxDraw.modes;
mbDrawModes.draw_rectangle = DrawRectangle;

Expand Down Expand Up @@ -351,16 +352,14 @@ export class MBMapContainer extends React.Component {

async _createMbMapInstance() {
const initialView = this.props.goto ? this.props.goto.center : null;
const makiUrl = relativeToAbsolute(chrome.addBasePath(MAKI_SPRITE_PATH));
return new Promise((resolve) => {
const options = {
attributionControl: false,
container: this.refs.mapContainer,
style: {
version: 8,
sources: {},
layers: [],
sprite: makiUrl
layers: []
},
scrollZoom: this.props.scrollZoom,
preserveDrawingBuffer: chrome.getInjected('preserveDrawingBuffer', false)
Expand Down Expand Up @@ -396,6 +395,8 @@ export class MBMapContainer extends React.Component {
return;
}

this._loadMakiSprites();

this._initResizerChecker();

// moveend callback is debounced to avoid updating map extent state while map extent is still changing
Expand Down Expand Up @@ -436,6 +437,12 @@ export class MBMapContainer extends React.Component {
});
}

_loadMakiSprites() {
const sprites = isRetina ? sprites2 : sprites1;
const json = isRetina ? spritesheet[2] : spritesheet[1];
addSpritesheetToMap(json, sprites, this._mbMap);
}

_hideTooltip() {
if (this._mbPopup.isOpen()) {
this._mbPopup.remove();
Expand Down
30 changes: 1 addition & 29 deletions x-pack/legacy/plugins/maps/public/layers/styles/symbol_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { maki } from '@kbn/maki';
import maki from '@elastic/maki';
import xml2js from 'xml2js';
import { parseXmlString } from '../../../common/parse_xml_string';

Expand Down Expand Up @@ -70,31 +70,3 @@ export async function styleSvg(svgString, fill) {
const builder = new xml2js.Builder();
return builder.buildObject(svgXml);
}

function addImageToMap(imageUrl, imageId, symbolId, mbMap) {
return new Promise((resolve, reject) => {
const img = new Image(LARGE_MAKI_ICON_SIZE, LARGE_MAKI_ICON_SIZE);
img.onload = () => {
mbMap.addImage(imageId, img);
resolve();
};
img.onerror = (err) => {
reject(err);
};
img.src = imageUrl;
});
}

export async function loadImage(imageId, symbolId, color, mbMap) {
let symbolSvg;
try {
symbolSvg = getMakiSymbolSvg(symbolId);
} catch(error) {
return;
}

const styledSvg = await styleSvg(symbolSvg, color);
const imageUrl = buildSrcUrl(styledSvg);

await addImageToMap(imageUrl, imageId, symbolId, mbMap);
}
Loading