feat(layer): add imagelayer

This commit is contained in:
thinkinggis 2019-10-16 10:13:44 +08:00
parent 6f33e5f72b
commit a995815284
44 changed files with 797 additions and 345 deletions

View File

@ -1,4 +1,4 @@
import { ISourceCFG } from '@l7/source';
import { ISourceCFG } from '@l7/core';
import { AsyncParallelHook, SyncHook } from 'tapable';
import { IModel } from '../renderer/IModel';
import { IMultiPassRenderer } from '../renderer/IMultiPassRenderer';

View File

@ -33,6 +33,7 @@ export interface ITexture2DInitializationOptions {
*/
data?:
| undefined
| HTMLImageElement
| number[]
| number[][]
| Uint8Array

View File

@ -12,7 +12,7 @@ type CallBack = (...args: any[]) => any;
export interface ITransform {
type: string;
[key: string]: any;
callback: CallBack;
callback?: CallBack;
}
export interface ISourceCFG {

View File

@ -29,7 +29,9 @@
"earcut": "^2.2.1",
"eventemitter3": "^3.1.0",
"gl-matrix": "^3.1.0",
"gl-vec2": "^1.3.0",
"lodash": "^4.17.15",
"polyline-miter-util": "^1.0.1",
"tapable": "^2.0.0-beta.8"
},
"devDependencies": {

View File

@ -1,9 +1,10 @@
import { ILayerStyleOptions } from '@l7/core';
import { IICONMap, ILayerStyleOptions } from '@l7/core';
import { lngLatToMeters } from '@l7/utils';
import { vec3 } from 'gl-matrix';
import { IExtrudeGeomety } from '../point/shape/extrude';
interface IBufferCfg {
data: unknown[];
imagePos?: unknown;
iconMap?: IICONMap;
style?: ILayerStyleOptions;
}
export type Position = number[];
@ -26,6 +27,7 @@ export interface IEncodeFeature {
coordinates: unknown;
bufferInfo: unknown;
}
export default class Buffer {
public attributes: {
[key: string]: Float32Array;
@ -33,14 +35,14 @@ export default class Buffer {
public verticesCount: number = 0;
public indexArray: Uint32Array = new Uint32Array(0);
public indexCount: number = 0;
public instanceGeometry: IExtrudeGeomety;
protected data: unknown[];
protected imagePos: unknown;
protected iconMap: IICONMap;
protected style: any;
constructor({ data, imagePos, style }: IBufferCfg) {
constructor({ data, iconMap, style }: IBufferCfg) {
this.data = data;
this.imagePos = imagePos;
this.iconMap = iconMap as IICONMap;
this.style = style;
this.init();
}
@ -126,7 +128,7 @@ export default class Buffer {
const { color, id, pattern, size } = feature;
const bufferInfo = feature.bufferInfo as IBufferInfo;
const { verticesOffset } = bufferInfo;
const imagePos = this.imagePos;
const imagePos = this.iconMap;
const start1 = verticesOffset;
for (let i = 0; i < num; i++) {
if (color) {

View File

@ -10,12 +10,13 @@ import {
IMultiPassRenderer,
IRendererService,
ISource,
ISourceCFG,
lazyInject,
StyleAttributeField,
StyleAttributeOption,
TYPES,
} from '@l7/core';
import Source, { ISourceCFG } from '@l7/source';
import Source from '@l7/source';
import { isFunction } from 'lodash';
import { SyncHook } from 'tapable';
import DataEncodePlugin from '../plugins/DataEncodePlugin';

View File

@ -46,7 +46,9 @@ export default class StyleAttribute implements ILayerStyleAttribute {
if (scales.some((scale) => scale.type === StyleScaleType.VARIABLE)) {
this.type = StyleScaleType.VARIABLE;
scales.forEach((scale) => {
scale.scale.range(this.values);
if (this.values.length > 0) {
scale.scale.range(this.values);
}
});
} else {
// 设置attribute 常量值

View File

@ -0,0 +1,46 @@
import BufferBase, { IEncodeFeature, Position } from '../../core/BaseBuffer';
import extrudePolygon, {
fillPolygon,
IExtrudeGeomety,
} from '../../point/shape/extrude';
import {
geometryShape,
ShapeType2D,
ShapeType3D,
} from '../../point/shape/Path';
export default class GridHeatMapBuffer extends BufferBase {
private verticesOffset: number = 0;
protected buildFeatures() {
this.verticesOffset = 0;
const layerData = this.data as IEncodeFeature[];
layerData.forEach((feature: IEncodeFeature) => {
this.calculateFill(feature);
});
}
protected calculateFeatures() {
const layerData = this.data as IEncodeFeature[];
const shape = layerData[0].shape as ShapeType3D | ShapeType2D;
this.verticesCount = layerData.length;
this.indexCount = 0;
this.instanceGeometry = this.getGeometry(shape as
| ShapeType2D
| ShapeType3D);
}
protected calculateFill(feature: IEncodeFeature) {
feature.bufferInfo = { verticesOffset: this.verticesOffset };
const coordinates = feature.coordinates as Position;
this.encodeArray(feature, 1);
this.attributes.positions.set([...coordinates, 1], this.verticesOffset * 3);
this.verticesOffset++;
}
private getGeometry(shape: ShapeType2D | ShapeType3D): IExtrudeGeomety {
const path = geometryShape[shape]
? geometryShape[shape]()
: geometryShape.circle();
// const geometry = ShapeType2D[str as ShapeType2D]
// ? fillPolygon([path])
// : extrudePolygon([path]);
const geometry = fillPolygon([path]);
return geometry;
}
}

View File

@ -0,0 +1,114 @@
import {
gl,
IRendererService,
IShaderModuleService,
lazyInject,
TYPES,
} from '@l7/core';
import BaseLayer from '../core/BaseLayer';
import GridHeatMapBuffer from './buffers/GridBuffer';
import hexagon_frag from './shaders/hexagon_frag.glsl';
import hexagon_vert from './shaders/hexagon_vert.glsl';
export default class HeatMapLayer extends BaseLayer {
public name: string = 'HeatMapLayer';
@lazyInject(TYPES.IShaderModuleService)
private readonly shaderModule: IShaderModuleService;
@lazyInject(TYPES.IRendererService)
private readonly renderer: IRendererService;
protected renderModels() {
this.models.forEach((model) =>
model.draw({
uniforms: {
u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
},
}),
);
return this;
}
protected buildModels(): void {
this.shaderModule.registerModule('grid', {
vs: hexagon_vert,
fs: hexagon_frag,
});
this.models = [];
const { vs, fs, uniforms } = this.shaderModule.getModule('grid');
const buffer = new GridHeatMapBuffer({
data: this.getEncodedData(),
});
console.log(this.getSource());
console.log(buffer);
const {
createAttribute,
createBuffer,
createElements,
createModel,
} = this.renderer;
this.models.push(
createModel({
attributes: {
a_miter: createAttribute({
buffer: createBuffer({
data: buffer.instanceGeometry.positions,
type: gl.FLOAT,
}),
size: 3,
divisor: 0,
}),
// a_normal: createAttribute({
// buffer: createBuffer({
// data: buffer.attributes.normals,
// type: gl.FLOAT,
// }),
// size: 3,
// }),
a_color: createAttribute({
buffer: createBuffer({
data: buffer.attributes.colors,
type: gl.FLOAT,
}),
size: 4,
divisor: 1,
}),
// a_size: createAttribute({
// buffer: createBuffer({
// data: buffer.attributes.sizes,
// type: gl.FLOAT,
// }),
// size: 1,
// divisor: 1,
// }),
a_Position: createAttribute({
buffer: createBuffer({
data: buffer.attributes.positions,
type: gl.FLOAT,
}),
size: 3,
divisor: 1,
}),
},
uniforms: {
...uniforms,
u_opacity: (this.styleOption.opacity as number) || 1.0,
u_radius: [
this.getSource().data.xOffset,
this.getSource().data.yOffset,
],
},
fs,
vs,
count: buffer.instanceGeometry.index.length,
instances: buffer.verticesCount,
elements: createElements({
data: buffer.instanceGeometry.index,
type: gl.UNSIGNED_INT,
}),
}),
);
}
}

View File

@ -0,0 +1,7 @@
precision highp float;
varying vec4 v_color;
uniform float u_opacity: 0.1;
void main() {
gl_FragColor = v_color;
gl_FragColor.a *= u_opacity;
}

View File

@ -0,0 +1,18 @@
precision highp float;
attribute vec3 a_Position;
attribute vec3 a_miter;
attribute float a_size;
attribute vec4 a_color;
uniform vec2 u_radius;
uniform float u_coverage: 1.;
uniform float u_angle: 0;
uniform mat4 u_ModelMatrix;
varying vec4 v_color;
#pragma include "projection"
void main() {
v_color = a_color;
mat2 rotationMatrix = mat2(cos(u_angle), sin(u_angle), -sin(u_angle), cos(u_angle));
vec2 offset =(vec2(a_miter.xy * u_radius * u_coverage * rotationMatrix));
vec4 project_pos = project_position(vec4(a_Position.xy + offset, 0, 1.0));
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy, 0., 1.0));
}

View File

@ -1,5 +1,16 @@
import BaseLayer from './core/BaseLayer';
import HeatMapLayer from './heatmap';
import Line from './line';
import PointLayer from './point';
import Point from './point/point';
import PolygonLayer from './polygon';
export { BaseLayer, PointLayer, PolygonLayer, Point };
import ImageLayer from './raster';
export {
BaseLayer,
PointLayer,
PolygonLayer,
Point,
Line,
ImageLayer,
HeatMapLayer,
};

View File

@ -1,4 +1,6 @@
import { lngLatToMeters, Point } from '@l7/utils';
import BufferBase, { IEncodeFeature, Position } from '../../core/BaseBuffer';
import getNormals from '../../utils/polylineNormal';
interface IBufferInfo {
normals: number[];
arrayIndex: number[];
@ -8,7 +10,7 @@ interface IBufferInfo {
verticesOffset: number;
indexOffset: number;
}
export default class FillBuffer extends BufferBase {
export default class LineBuffer extends BufferBase {
private hasPattern: boolean;
protected buildFeatures() {
const layerData = this.data as IEncodeFeature[];
@ -32,13 +34,19 @@ export default class FillBuffer extends BufferBase {
protected calculateFeatures() {
const layerData = this.data as IEncodeFeature[];
// 计算长
layerData.forEach((feature: IEncodeFeature) => {
let { coordinates } = feature;
layerData.forEach((feature: IEncodeFeature, index: number) => {
let coordinates = feature.coordinates as Position[] | Position[][];
if (Array.isArray(coordinates[0][0])) {
coordinates = coordinates[0];
coordinates = coordinates[0] as Position[];
}
// @ts-ignore
const projectCoord: number[][] = coordinates.map((item: Position[]) => {
// @ts-ignore
const p: Point = [...item];
return lngLatToMeters(p);
});
const { normals, attrIndex, attrPos, attrDistance, miters } = getNormals(
coordinates,
coordinates as number[][],
false,
this.verticesCount,
);

View File

@ -1,9 +1,9 @@
uniform float u_blur : 0.9;
uniform float u_blur : 0.99;
varying vec4 v_color;
varying vec3 v_normal;
void main() {
gl_FragColor = v_color;
// anti-alias
// float blur = 1. - smoothstep(u_blur, 1., length(v_normal));
// gl_FragColor.a *= blur;
float blur = smoothstep(u_blur, 1., length(v_normal.xy));
gl_FragColor.a *= blur;
}

View File

@ -60,7 +60,11 @@ export default class DataEncodePlugin implements ILayerPlugin {
let scale = this.scaleCache[field as string];
if (!scale) {
scale = this.scaleController.createScale(field as string, data);
if (scale.type === StyleScaleType.VARIABLE) {
if (
scale.type === StyleScaleType.VARIABLE &&
attribute.values &&
attribute.values.length > 0
) {
scale.scale.range(attribute.values);
}
this.scaleCache[field as string] = scale;

View File

@ -9,7 +9,8 @@ import {
StyleScaleType,
TYPES,
} from '@l7/core';
import Source, { ISourceCFG } from '@l7/source';
import { ISourceCFG } from '@l7/core';
import Source from '@l7/source';
export default class DataSourcePlugin implements ILayerPlugin {
public apply(layer: ILayer) {
layer.hooks.init.tap('DataSourcePlugin', () => {

View File

@ -4,7 +4,7 @@ import BaseBuffer, {
Position,
} from '../../core/BaseBuffer';
import extrudePolygon, { IExtrudeGeomety } from '../shape/extrude';
import { geometryShape, ShapeType } from '../shape/Path';
import { geometryShape, ShapeType2D, ShapeType3D } from '../shape/Path';
interface IGeometryCache {
[key: string]: IExtrudeGeomety;
}
@ -26,7 +26,7 @@ export default class ExtrudeBuffer extends BaseBuffer {
this.indexOffset = 0;
layerData.forEach((feature: IEncodeFeature) => {
const { shape } = feature;
const { positions, index } = this.getGeometry(shape as ShapeType);
const { positions, index } = this.getGeometry(shape as ShapeType3D);
this.verticesCount += positions.length / 3;
this.indexCount += index.length;
});
@ -39,7 +39,7 @@ export default class ExtrudeBuffer extends BaseBuffer {
}
private calculateFill(feature: IEncodeFeature) {
const { coordinates, shape } = feature;
const instanceGeometry = this.getGeometry(shape as ShapeType);
const instanceGeometry = this.getGeometry(shape as ShapeType3D);
const numPoint = instanceGeometry.positions.length / 3;
feature.bufferInfo = {
verticesOffset: this.verticesOffset,
@ -65,7 +65,7 @@ export default class ExtrudeBuffer extends BaseBuffer {
this.indexOffset += indexArray.length;
}
private getGeometry(shape: ShapeType): IExtrudeGeomety {
private getGeometry(shape: ShapeType3D): IExtrudeGeomety {
if (this.geometryCache && this.geometryCache[shape]) {
return this.geometryCache[shape];
}

View File

@ -9,11 +9,11 @@ export default class ImageBuffer extends BaseBuffer {
const layerData = this.data as IEncodeFeature[];
layerData.forEach((item: IEncodeFeature, index: number) => {
const { color = [0, 0, 0, 0], size, id, shape, coordinates } = item;
const { x, y } = this.imagePos[shape];
const { x, y } = this.iconMap[shape as string];
const coor = coordinates as Position;
this.attributes.vertices.set([coor[0], coor[1], coor[2] || 0], index * 3);
this.attributes.colors.set(color, index * 4);
this.attributes.pickingIds.set([id], index);
this.attributes.pickingIds.set([id as number], index);
this.attributes.sizes.set([size as number], index); //
this.attributes.uv.set([x, y], index * 2);
});

View File

@ -42,7 +42,6 @@ export default class PointLayer extends BaseLayer {
data: this.getEncodedData(),
});
buffer.computeVertexNormals('miters', false);
console.log(buffer); // TODO: normal
const {
createAttribute,
createBuffer,

View File

@ -1,11 +1,18 @@
type IPosition = [number, number, number];
export type IPath = IPosition[];
export enum ShapeType {
CIRCLE = 'cylinder',
SQUARE = 'squareColumn',
TRIANGLE = 'triangleColumn',
HEXAGON = 'hexagonColumn',
PENTAGON = 'pentagonColumn',
export enum ShapeType3D {
CYLINDER = 'cylinder',
SQUARECOLUMN = 'squareColumn',
TRIANGLECOLUMN = 'triangleColumn',
HEXAGONCOLUMN = 'hexagonColumn',
PENTAGONCOLUMN = 'pentagonColumn',
}
export enum ShapeType2D {
CIRCLE = 'circle',
SQUARE = 'square',
TRIANGLE = 'triangle',
HEXAGON = 'hexagon',
PENTAGON = 'pentagon',
}
/**
@ -24,7 +31,7 @@ export function polygonPath(pointCount: number, start: number = 0): IPath {
const y = Math.cos(t + Math.PI / 4);
return [x, y, 0];
});
path.push(path[0]);
// path.push(path[0]);
return path;
}
@ -45,9 +52,14 @@ export function pentagon(): IPath {
}
export const geometryShape = {
[ShapeType.CIRCLE]: circle,
[ShapeType.HEXAGON]: hexagon,
[ShapeType.TRIANGLE]: triangle,
[ShapeType.SQUARE]: square,
[ShapeType.PENTAGON]: pentagon,
[ShapeType2D.CIRCLE]: circle,
[ShapeType2D.HEXAGON]: hexagon,
[ShapeType2D.TRIANGLE]: triangle,
[ShapeType2D.SQUARE]: square,
[ShapeType2D.PENTAGON]: pentagon,
[ShapeType3D.CYLINDER]: circle,
[ShapeType3D.HEXAGONCOLUMN]: hexagon,
[ShapeType3D.TRIANGLECOLUMN]: triangle,
[ShapeType3D.SQUARECOLUMN]: square,
[ShapeType3D.PENTAGONCOLUMN]: pentagon,
};

View File

@ -60,3 +60,15 @@ export default function extrudePolygon(path: IPath[]): IExtrudeGeomety {
index: indexArray,
};
}
export function fillPolygon(points: IPath[]) {
const flattengeo = earcut.flatten(points);
const triangles = earcut(
flattengeo.vertices,
flattengeo.holes,
flattengeo.dimensions,
);
return {
positions: flattengeo.vertices,
index: triangles,
};
}

View File

@ -4,14 +4,13 @@ interface IImageFeature extends IEncodeFeature {
}
export default class ImageBuffer extends BaseBuffer {
protected calculateFeatures() {
const layerData = this.data as IImageFeature[];
this.verticesCount = 4;
this.verticesCount = 6;
this.indexCount = 6;
}
protected buildFeatures() {
this.attributes.uv = new Float32Array(this.verticesCount * 2);
const layerData = this.data as IImageFeature[];
const coordinates = layerData[0].coordinates as Position[];
const images = layerData[0].images;
const positions: number[] = [
...coordinates[0],
0,

View File

@ -0,0 +1,87 @@
import {
gl,
IRendererService,
IShaderModuleService,
ITexture2D,
lazyInject,
TYPES,
} from '@l7/core';
import BaseLayer from '../core/BaseLayer';
import ImageBuffer from './buffers/ImageBuffer';
import image_frag from './shaders/image_frag.glsl';
import image_vert from './shaders/image_vert.glsl';
export default class ImageLayer extends BaseLayer {
public name: string = 'imageLayer';
@lazyInject(TYPES.IShaderModuleService)
private readonly shaderModule: IShaderModuleService;
@lazyInject(TYPES.IRendererService)
private readonly renderer: IRendererService;
protected renderModels() {
this.models.forEach((model) =>
model.draw({
uniforms: {
u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
},
}),
);
return this;
}
protected buildModels() {
const {
createAttribute,
createBuffer,
createElements,
createTexture2D,
createModel,
} = this.renderer;
this.shaderModule.registerModule('image', {
vs: image_vert,
fs: image_frag,
});
this.models = [];
const { vs, fs, uniforms } = this.shaderModule.getModule('image');
const source = this.getSource();
// const imageData = await source.data.images;
const buffer = new ImageBuffer({
data: this.getEncodedData(),
});
source.data.images.then((imageData: HTMLImageElement[]) => {
const texture: ITexture2D = createTexture2D({
data: imageData[0],
width: imageData[0].width,
height: imageData[0].height,
});
this.models.push(
createModel({
attributes: {
a_Position: createAttribute({
buffer: createBuffer({
data: buffer.attributes.positions,
type: gl.FLOAT,
}),
size: 3,
}),
a_uv: createAttribute({
buffer: createBuffer({
data: buffer.attributes.uv,
type: gl.FLOAT,
}),
size: 2,
}),
},
uniforms: {
...uniforms,
u_texture: texture,
u_opacity: 1.0,
},
fs,
vs,
count: buffer.verticesCount,
}),
);
});
}
}

View File

@ -1,8 +1,8 @@
precision mediump float;
uniform float u_opacity: 1.0;
uniform sampler2D u_texture;
uniform float u_opacity;
varying vec2 v_texCoord;
void main() {
vec4 color = texture2D(u_texture,vec2(v_texCoord.x,1.0-v_texCoord.y));
gl_FragColor = color * u_opacity;
vec4 color = texture2D(u_texture,vec2(v_texCoord.x,v_texCoord.y));
gl_FragColor = color;
}

View File

@ -1,9 +1,12 @@
precision highp float;
varying vec2 v_texCoord;
uniform mat4 u_ModelMatrix;
attribute vec3 a_Position;
attribute vec2 a_uv;
varying vec2 v_texCoord;
#pragma include "projection"
void main() {
v_texCoord = uv;
v_texCoord = a_uv;
vec4 project_pos = project_position(vec4(a_Position, 1.0));
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xyz, 1.0));
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy,0., 1.0));
}

View File

@ -1,195 +0,0 @@
/**
* polyline-normal
* miter bevel
* Three.js THREE.FrontFaceDirectionCCW
* @see https://zhuanlan.zhihu.com/p/59541559
*/
// @ts-ignore
import { copy, create, dot } from 'gl-vec2';
// @ts-ignore
import { computeMiter, direction, normal } from 'polyline-miter-util';
// @ts-ignore
function extrusions(positions, out, miters, point, normal1, scale) {
addNext(out, miters, normal1, -scale);
addNext(out, miters, normal1, scale);
positions.push(...point, 0);
positions.push(...point, 0);
}
// @ts-ignore
// tslint:disable-next-line:no-shadowed-variable
function addNext(out, miters, normal, length) {
out.push(normal[0], normal[1], 0);
miters.push(length);
}
// @ts-ignore
function lineSegmentDistance(end, start) {
const dx = start[0] - end[0];
const dy = start[1] - end[1];
const dz = start[2] - end[2];
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
// @ts-ignore
function isPointEqual(a, b) {
return a[0] === b[0] && a[1] === b[1];
}
// @ts-ignore
export default function(points, closed, indexOffset) {
const lineA = [0, 0];
const lineB = [0, 0];
const tangent = [0, 0];
const miter = [0, 0];
// tslint:disable-next-line:variable-name
let _started = false;
// tslint:disable-next-line:variable-name
let _normal = null;
const tmp = create();
let count = indexOffset || 0;
const miterLimit = 3;
// @ts-ignore
const out = [];
const attrPos = [];
const attrIndex = [];
// @ts-ignore
const miters = [];
const attrDistance = [0, 0];
if (closed) {
points = points.slice();
points.push(points[0]);
}
const total = points.length;
for (let i = 1; i < total; i++) {
const index = count;
const last = points[i - 1];
const cur = points[i];
let next = i < points.length - 1 ? points[i + 1] : null;
// 如果当前点和前一点相同,跳过
if (isPointEqual(last, cur)) {
continue;
}
if (next) {
let nextIndex = i + 1;
// 找到不相同的下一点
while (next && isPointEqual(cur, next)) {
next = nextIndex < points.length - 1 ? points[++nextIndex] : null;
}
}
const lineDistance = lineSegmentDistance(cur, last);
const d = lineDistance + attrDistance[attrDistance.length - 1];
direction(lineA, cur, last);
if (!_normal) {
_normal = [0, 0];
normal(_normal, lineA);
}
if (!_started) {
_started = true;
// @ts-ignore
extrusions(attrPos, out, miters, last, _normal, 1);
}
attrIndex.push(index + 0, index + 2, index + 1);
// no miter, simple segment
if (!next) {
// reset normal
normal(_normal, lineA);
// @ts-ignore
extrusions(attrPos, out, miters, cur, _normal, 1);
attrDistance.push(d, d);
attrIndex.push(index + 1, index + 2, index + 3);
count += 2;
} else {
// get unit dir of next line
direction(lineB, next, cur);
// stores tangent & miter
let miterLen = computeMiter(tangent, miter, lineA, lineB, 1);
// get orientation
const flip = dot(tangent, _normal) < 0 ? -1 : 1;
const bevel = Math.abs(miterLen) > miterLimit;
// 处理前后两条线段重合的情况这种情况不需要使用任何接头miter/bevel
// 理论上这种情况下 miterLen = Infinity本应通过 isFinite(miterLen) 判断,
// 但是 AMap 投影变换后丢失精度只能通过一个阈值1000判断。
if (Math.abs(miterLen) > 1000) {
// @ts-ignore
extrusions(attrPos, out, miters, cur, _normal, 1);
attrIndex.push(index + 1, index + 2, index + 3);
attrIndex.push(index + 2, index + 4, index + 3);
normal(tmp, lineB);
copy(_normal, tmp); // store normal for next round
// @ts-ignore
extrusions(attrPos, out, miters, cur, _normal, 1);
attrDistance.push(d, d, d, d);
// the miter is now the normal for our next join
count += 4;
continue;
}
if (bevel) {
miterLen = miterLimit;
// next two points in our first segment
// @ts-ignore
extrusions(attrPos, out, miters, cur, _normal, 1);
attrIndex.push(index + 1, index + 2, index + 3);
// now add the bevel triangle
attrIndex.push(
...(flip === 1
? [index + 2, index + 4, index + 5]
: [index + 4, index + 5, index + 3]),
);
normal(tmp, lineB);
copy(_normal, tmp); // store normal for next round
// @ts-ignore
extrusions(attrPos, out, miters, cur, _normal, 1);
attrDistance.push(d, d, d, d);
// the miter is now the normal for our next join
count += 4;
} else {
// next two points in our first segment
// @ts-ignore
extrusions(attrPos, out, miters, cur, _normal, 1);
attrIndex.push(index + 1, index + 2, index + 3);
// now add the miter triangles
// @ts-ignore
addNext(out, miters, miter, miterLen * -flip);
attrPos.push(...cur, 0);
attrIndex.push(index + 2, index + 4, index + 3);
attrIndex.push(index + 4, index + 5, index + 6);
normal(tmp, lineB);
copy(_normal, tmp); // store normal for next round
// @ts-ignore
extrusions(attrPos, out, miters, cur, _normal, 1);
attrDistance.push(d, d, d, d, d);
// the miter is now the normal for our next join
count += 5;
}
}
}
// @ts-ignore
return {
// @ts-ignore
normals: out,
attrIndex,
attrPos,
attrDistance,
// @ts-ignore
miters,
};
}

View File

@ -1,5 +1,5 @@
import { aProjectFlat, lngLatToMeters, Point } from '@l7/utils';
import { vec2 } from 'gl-matrix';
export function computeMiter(
tangent: vec2,
miter: vec2,
@ -17,55 +17,66 @@ export function computeNormal(out: vec2, dir: vec2) {
return vec2.set(out, -dir[1], dir[0]);
}
export function direction(out: vec2, a: vec2, b: vec2) {
vec2.sub(out, a, b);
const a1 = aProjectFlat([a[0], a[1]]);
const b1 = aProjectFlat([b[0], b[1]]);
vec2.sub(out, a1, b1);
vec2.normalize(out, out);
return out;
}
function extrusions(
positions: number[],
out: vec2,
miters: vec2,
out: number[],
miters: number[],
point: vec2,
normal: vec2,
scale,
scale: number,
) {
addNext(out, miters, normal, -scale);
addNext(out, miters, normal, scale);
positions.push(...point);
positions.push(...point);
positions.push(point[0], point[1], 0);
positions.push(point[0], point[1], 0);
}
function addNext(out, miters, normal, length) {
function addNext(
out: number[],
miters: number[],
normal: vec2,
length: number,
) {
out.push(normal[0], normal[1], 0);
miters.push(length);
}
function lineSegmentDistance(end, start) {
function lineSegmentDistance(end: vec2, start: vec2) {
const dx = start[0] - end[0];
const dy = start[1] - end[1];
const dz = start[2] - end[2];
return Math.sqrt(dx * dx + dy * dy + dz * dz);
// const dz = start[2] - end[2];
return Math.sqrt(dx * dx + dy * dy);
}
function isPointEqual(a, b) {
function isPointEqual(a: vec2, b: vec2) {
return a[0] === b[0] && a[1] === b[1];
}
export default function(points, closed, indexOffset) {
export default function(
points: number[][],
closed: boolean,
indexOffset: number,
) {
const lineA = vec2.fromValues(0, 0);
const lineB = vec2.fromValues(0, 0);
const tangent = vec2.fromValues(0, 0);
const miter = vec2.fromValues(0, 0);
let _started = false;
let _normal = null;
const miter: vec2 = vec2.create();
let started = false;
let lineNormal = null;
const tmp = vec2.create();
let count = indexOffset || 0;
const miterLimit = 3;
const out = [];
const attrPos = [];
const attrIndex = [];
const miters = [];
const out: number[] = [];
const attrPos: number[] = [];
const attrIndex: number[] = [];
const miters: number[] = [];
const attrDistance = [0, 0];
if (closed) {
points = points.slice();
@ -76,9 +87,12 @@ export default function(points, closed, indexOffset) {
for (let i = 1; i < total; i++) {
const index = count;
const last = points[i - 1];
const cur = points[i];
let next = i < points.length - 1 ? points[i + 1] : null;
const last = vec2.fromValues(points[i - 1][0], points[i - 1][1]);
const cur = vec2.fromValues(points[i][0], points[i][1]);
let next =
i < points.length - 1
? vec2.fromValues(points[i + 1][0], points[i + 1][1])
: null;
// 如果当前点和前一点相同,跳过
if (isPointEqual(last, cur)) {
continue;
@ -87,22 +101,23 @@ export default function(points, closed, indexOffset) {
let nextIndex = i + 1;
// 找到不相同的下一点
while (next && isPointEqual(cur, next)) {
next = nextIndex < points.length - 1 ? points[++nextIndex] : null;
next =
nextIndex < points.length - 1
? vec2.fromValues(points[++nextIndex][0], points[nextIndex][1])
: null;
}
}
const lineDistance = lineSegmentDistance(cur, last);
const lineDistance = lineSegmentDistance(cur, last); // TODO: 根据平面坐标计算距离
const d = lineDistance + attrDistance[attrDistance.length - 1];
direction(lineA, cur, last);
if (!_normal) {
_normal = [0, 0];
computeNormal(_normal, lineA);
if (!lineNormal) {
lineNormal = vec2.create();
computeNormal(lineNormal, lineA);
}
if (!_started) {
_started = true;
extrusions(attrPos, out, miters, last, _normal, 1);
if (!started) {
started = true;
extrusions(attrPos, out, miters, last, lineNormal, 1);
}
attrIndex.push(index + 0, index + 2, index + 1);
@ -110,8 +125,8 @@ export default function(points, closed, indexOffset) {
// no miter, simple segment
if (!next) {
// reset normal
computeNormal(_normal, lineA);
extrusions(attrPos, out, miters, cur, _normal, 1);
computeNormal(lineNormal, lineA);
extrusions(attrPos, out, miters, cur, lineNormal, 1);
attrDistance.push(d, d);
attrIndex.push(index + 1, index + 2, index + 3);
count += 2;
@ -120,23 +135,30 @@ export default function(points, closed, indexOffset) {
direction(lineB, next, cur);
// stores tangent & miter
let miterLen = computeMiter(tangent, miter, lineA, lineB, 1);
let miterLen = computeMiter(
tangent,
vec2.fromValues(miter[0], miter[1]),
lineA,
lineB,
1,
);
// get orientation
const flip = vec2.dot(tangent, _normal) < 0 ? -1 : 1;
const flip = vec2.dot(tangent, lineNormal) < 0 ? -1 : 1;
const bevel = Math.abs(miterLen) > miterLimit;
// 处理前后两条线段重合的情况这种情况不需要使用任何接头miter/bevel
// 理论上这种情况下 miterLen = Infinity本应通过 isFinite(miterLen) 判断,
// 但是 AMap 投影变换后丢失精度只能通过一个阈值1000判断。
if (Math.abs(miterLen) > 1000) {
extrusions(attrPos, out, miters, cur, _normal, 1);
extrusions(attrPos, out, miters, cur, lineNormal, 1);
attrIndex.push(index + 1, index + 2, index + 3);
attrIndex.push(index + 2, index + 4, index + 3);
computeNormal(tmp, lineB);
vec2.copy(_normal, tmp); // store normal for next round
vec2.copy(lineNormal, tmp); // store normal for next round
extrusions(attrPos, out, miters, cur, _normal, 1);
extrusions(attrPos, out, miters, cur, lineNormal, 1);
attrDistance.push(d, d, d, d);
// the miter is now the normal for our next join
@ -148,7 +170,7 @@ export default function(points, closed, indexOffset) {
miterLen = miterLimit;
// next two points in our first segment
extrusions(attrPos, out, miters, cur, _normal, 1);
extrusions(attrPos, out, miters, cur, lineNormal, 1);
attrIndex.push(index + 1, index + 2, index + 3);
@ -160,27 +182,27 @@ export default function(points, closed, indexOffset) {
);
computeNormal(tmp, lineB);
vec2.copy(_normal, tmp); // store normal for next round
vec2.copy(lineNormal, tmp); // store normal for next round
extrusions(attrPos, out, miters, cur, _normal, 1);
extrusions(attrPos, out, miters, cur, lineNormal, 1);
attrDistance.push(d, d, d, d);
// the miter is now the normal for our next join
count += 4;
} else {
// next two points in our first segment
extrusions(attrPos, out, miters, cur, _normal, 1);
extrusions(attrPos, out, miters, cur, lineNormal, 1);
attrIndex.push(index + 1, index + 2, index + 3);
// now add the miter triangles
addNext(out, miters, miter, miterLen * -flip);
attrPos.push(...cur);
addNext(out, miters, lineNormal, miterLen * -flip);
attrPos.push(cur[0], cur[1], 0);
attrIndex.push(index + 2, index + 4, index + 3);
attrIndex.push(index + 4, index + 5, index + 6);
computeNormal(tmp, lineB);
vec2.copy(_normal, tmp); // store normal for next round
vec2.copy(lineNormal, tmp); // store normal for next round
extrusions(attrPos, out, miters, cur, _normal, 1);
extrusions(attrPos, out, miters, cur, lineNormal, 1);
attrDistance.push(d, d, d, d, d);
// the miter is now the normal for our next join

View File

@ -44,8 +44,8 @@ export default class ReglModel implements IModel {
blend,
stencil,
cull,
instances,
} = options;
const reglUniforms: { [key: string]: IUniform } = {};
if (uniforms) {
this.uniforms = uniforms;
@ -60,7 +60,6 @@ export default class ReglModel implements IModel {
Object.keys(attributes).forEach((name: string) => {
reglAttributes[name] = (attributes[name] as ReglAttribute).get();
});
const drawParams: regl.DrawConfig = {
attributes: reglAttributes,
frag: fs,
@ -70,6 +69,9 @@ export default class ReglModel implements IModel {
primitiveMap[primitive === undefined ? gl.TRIANGLES : primitive],
count,
};
if (instances) {
drawParams.instances = instances;
}
if (elements) {
drawParams.elements = (elements as ReglElements).get();

View File

@ -56,6 +56,7 @@ export default class ReglRendererService implements IRendererService {
'EXT_SRGB', // baseColor emmisive
'OES_texture_float', // shadow map
'WEBGL_depth_texture',
'angle_instanced_arrays',
'EXT_texture_filter_anisotropic', // VSM shadow map
],
optionalExtensions: ['oes_texture_float_linear'],

View File

@ -1,24 +1,4 @@
export type DataType = string | object[] | object;
export interface IParserCfg {
type: string;
x?: string;
y?: string;
x1?: string;
y1?: string;
coordinates?: string;
[key: string]: any;
}
type CallBack = (...args: any[]) => any;
export interface ITransform {
type: string;
[key: string]: any;
callback: CallBack;
}
export interface ISourceCFG {
parser?: IParserCfg;
transforms?: ITransform[];
}
export interface IDictionary<TValue> {
[key: string]: TValue;
}

View File

@ -1,5 +1,5 @@
import { csvParse } from 'd3-dsv';
import { IJsonData, IParserCfg, IParserData } from '../interface';
import { IJsonData, IParserCfg, IParserData } from '@l7/core';
import json from './json';
export default function csv(data: string, cfg: IParserCfg): IParserData {
const csvData: IJsonData = csvParse(data);

View File

@ -1,5 +1,5 @@
import { getImage } from '@l7/utils';
import { IParserData } from '../interface';
interface IImageCfg {
extent: [number, number, number, number];
}
@ -8,9 +8,13 @@ export default function image(
cfg: IImageCfg,
): IParserData {
const { extent } = cfg;
const images = new Promise((resolve) => {
loadData(data, (res: any) => {
resolve(res);
});
});
const resultData: IParserData = {
images: loadData(data),
images,
_id: 1,
dataArray: [
{
@ -21,16 +25,26 @@ export default function image(
};
return resultData;
}
function loadData(data: string | string[]): Promise<Response | Response[]> {
function loadData(data: string | string[], done: any) {
const url = data;
const imageDatas: HTMLImageElement[] = [];
if (typeof url === 'string') {
const imageRequest = new Request(url);
return fetch(imageRequest);
} else {
const fetchs = url.map((item: string) => {
const imageRequest = new Request(item);
return fetch(imageRequest);
getImage({ url }, (err: string, img: HTMLImageElement) => {
imageDatas.push(img);
done(imageDatas);
});
} else {
const imageCount = url.length;
let imageindex = 0;
url.forEach((item) => {
getImage({ url: item }, (err: any, img: HTMLImageElement) => {
imageindex++;
imageDatas.push(img);
if (imageindex === imageCount) {
done(imageDatas);
}
});
});
return Promise.all(fetchs);
}
return image;
}

View File

@ -6,7 +6,7 @@ import {
IParseDataItem,
IParserCfg,
IParserData,
} from '../interface';
} from '@l7/core';
export default function json(data: IJsonData, cfg: IParserCfg): IParserData {
const { x, y, x1, y1, coordinates } = cfg;
const resultData: IParseDataItem[] = [];

View File

@ -36,6 +36,9 @@ export default class Source extends EventEmitter {
this.hooks.init.tap('parser', () => {
this.excuteParser();
});
this.hooks.init.tap('transform', () => {
this.executeTrans();
});
this.init();
}
@ -60,6 +63,5 @@ export default class Source extends EventEmitter {
}
private init() {
this.hooks.init.call(this);
// this.excuteParser(); // 数据解析
}
}

View File

@ -24,9 +24,9 @@ export function aggregatorToGrid(data: IParserData, option: ITransform) {
const { gridHash, gridOffset } = _pointsGridHash(dataArray, size);
const layerData = _getGridLayerDataFromGridHash(gridHash, gridOffset, option);
return {
yOffset: ((gridOffset.xOffset / 360) * (256 << 20)) / 2,
xOffset: ((gridOffset.xOffset / 360) * (256 << 20)) / 2,
radius: ((gridOffset.xOffset / 360) * (256 << 20)) / 2,
yOffset: gridOffset.yOffset / 1.8,
xOffset: gridOffset.xOffset / 1.8,
radius: gridOffset.xOffset,
dataArray: layerData,
};
}
@ -42,8 +42,8 @@ function _pointsGridHash(dataArray: any[], size: number) {
latMax = pLat > latMax ? pLat : latMax;
}
}
// const centerLat = (latMin + latMax) / 2;
const centerLat = 34.54083;
const centerLat = (latMin + latMax) / 2;
// const centerLat = 34.54083;
const gridOffset = _calculateGridLatLonOffset(size, centerLat);
if (gridOffset.xOffset <= 0 || gridOffset.yOffset <= 0) {
return { gridHash: {}, gridOffset };

View File

@ -0,0 +1,124 @@
class AJAXError extends Error {
private status: number;
private url: string;
constructor(message: string, status: number, url: string) {
super(message);
this.status = status;
this.url = url;
// work around for https://github.com/Rich-Harris/buble/issues/40
this.name = this.constructor.name;
this.message = message;
}
public toString() {
return `${this.name}: ${this.message} (${this.status}): ${this.url}`;
}
}
function makeRequest(requestParameters: any) {
const xhr = new XMLHttpRequest();
xhr.open('GET', requestParameters.url, true);
for (const k in requestParameters.headers) {
if (requestParameters.headers.hasOwnProperty(k)) {
xhr.setRequestHeader(k, requestParameters.headers[k]);
}
}
xhr.withCredentials = requestParameters.credentials === 'include';
return xhr;
}
export const getJSON = (requestParameters: any, callback: any) => {
const xhr = makeRequest(requestParameters);
xhr.setRequestHeader('Accept', 'application/json');
xhr.onerror = () => {
callback(new Error(xhr.statusText));
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300 && xhr.response) {
let data;
try {
data = JSON.parse(xhr.response);
} catch (err) {
return callback(err);
}
callback(null, data);
} else {
if (xhr.status === 401) {
callback(
new AJAXError(`${xhr.statusText}`, xhr.status, requestParameters.url),
);
} else {
callback(
new AJAXError(xhr.statusText, xhr.status, requestParameters.url),
);
}
}
};
xhr.send();
return xhr;
};
export const getArrayBuffer = (requestParameters: any, callback: any) => {
const xhr = makeRequest(requestParameters);
xhr.responseType = 'arraybuffer';
xhr.onerror = () => {
callback(new Error(xhr.statusText));
};
xhr.onload = () => {
const response = xhr.response;
if (response.byteLength === 0 && xhr.status === 200) {
return callback(new Error('http status 200 returned without content.'));
}
if (xhr.status >= 200 && xhr.status < 300 && xhr.response) {
callback(null, {
data: response,
cacheControl: xhr.getResponseHeader('Cache-Control'),
expires: xhr.getResponseHeader('Expires'),
});
} else {
callback(
new AJAXError(xhr.statusText, xhr.status, requestParameters.url),
);
}
};
xhr.send();
return xhr;
};
function sameOrigin(url: string) {
const a = window.document.createElement('a');
a.href = url;
return (
a.protocol === window.document.location.protocol &&
a.host === window.document.location.host
);
}
const transparentPngUrl =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII=';
export const getImage = (requestParameters: any, callback: any) => {
// request the image with XHR to work around caching issues
// see https://github.com/mapbox/mapbox-gl-js/issues/1470
return getArrayBuffer(requestParameters, (err: string, imgData: any) => {
if (err) {
callback(err);
} else if (imgData) {
const img = new window.Image();
const URL = window.URL || window.webkitURL;
img.onload = () => {
callback(null, img);
URL.revokeObjectURL(img.src);
};
const blob = new window.Blob([new Uint8Array(imgData.data)], {
type: 'image/png',
});
img.src = imgData.data.byteLength
? URL.createObjectURL(blob)
: transparentPngUrl;
}
});
};

View File

@ -1,6 +1,6 @@
import { BBox } from '@turf/helpers';
const originShift = (2 * Math.PI * 6378137) / 2.0;
type Point = [number, number] | [number, number, number];
export type Point = [number, number] | [number, number, number];
/**
*
* @param {dataArray} data
@ -137,3 +137,20 @@ export function validateLngLat(lnglat: Point, validate: boolean): Point {
}
return lnglat.length === 3 ? [lng, lat, lnglat[2]] : [lng, lat];
}
export function aProjectFlat(lnglat: number[]) {
const maxs = 85.0511287798;
const lat = Math.max(Math.min(maxs, lnglat[1]), -maxs);
const scale = 256 << 20;
let d = Math.PI / 180;
let x = lnglat[0] * d;
let y = lat * d;
y = Math.log(Math.tan(Math.PI / 4 + y / 2));
const a = 0.5 / Math.PI;
const b = 0.5;
const c = -0.5 / Math.PI;
d = 0.5;
x = scale * (a * x + b) - 215440491;
y = scale * (c * y + d) - 106744817;
return [parseInt(x.toString(), 10), parseInt(y.toString(), 10)];
}

View File

@ -1,2 +1,3 @@
export { djb2hash, BKDRHash } from './hash';
export * from './fetchData';
export * from './geo';

View File

@ -4,6 +4,9 @@ import AMap from './components/AMap';
import Mapbox from './components/Mapbox';
import Polygon from './components/Polygon';
import Point3D from './components/Point3D';
import Line from './components/Line';
import ImageLayer from './components/Image';
import GridHeatMap from './components/GridHeatmap';
// @ts-ignore
import notes from './Map.md';
@ -15,4 +18,7 @@ storiesOf('地图底图测试', module)
notes: { markdown: notes },
})
.add('Polygon', () => <Polygon />)
.add('Point3D', () => <Point3D />);
.add('Point3D', () => <Point3D />)
.add('Line', () => <Line />)
.add('GridHeatMap', () => <GridHeatMap />)
.add('Image', () => <ImageLayer />);

View File

@ -0,0 +1,77 @@
import { HeatMapLayer } from '@l7/layers';
import { Scene } from '@l7/scene';
import * as React from 'react';
export default class GridHeatMap extends React.Component {
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public async componentDidMount() {
const response = await fetch(
'https://gw.alipayobjects.com/os/basement_prod/c3f8bda2-081b-449d-aa9f-9413b779205b.json',
);
const scene = new Scene({
center: [116.49434030056, 39.868073421167621],
id: 'map',
pitch: 0,
type: 'amap',
style: 'mapbox://styles/mapbox/streets-v9',
zoom: 16,
});
const layer = new HeatMapLayer({});
layer
.source(await response.json(), {
parser: {
type: 'json',
x: 'lng',
y: 'lat',
},
transforms: [
{
type: 'grid',
size: 50,
field: 'count',
method: 'sum',
},
],
})
.size('sum', (value: number) => {
return value;
})
.shape('circle')
.style({
coverage: 1.2,
angle: 0,
})
.color('count', [
'#002466',
'#105CB3',
'#2894E0',
'#CFF6FF',
'#FFF5B8',
'#FFAB5C',
'#F27049',
'#730D1C',
]);
scene.addLayer(layer);
scene.render();
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -0,0 +1,50 @@
import { ImageLayer } from '@l7/layers';
import { Scene } from '@l7/scene';
import * as React from 'react';
export default class ImageLayerDemo extends React.Component {
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public componentDidMount() {
const scene = new Scene({
center: [121.2680, 30.3628],
id: 'map',
pitch: 0,
type: 'mapbox',
style: 'mapbox://styles/mapbox/streets-v9',
zoom: 10,
});
const layer = new ImageLayer({});
layer.source(
'https://gw.alipayobjects.com/zos/rmsportal/FnHFeFklTzKDdUESRNDv.jpg',
{
parser: {
type: 'image',
extent: [121.168, 30.2828, 121.384, 30.4219],
},
},
);
scene.addLayer(layer);
scene.render();
this.scene = scene;
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -36,15 +36,29 @@ export default class Point3D extends React.Component {
pitch: 0,
type: 'mapbox',
style: 'mapbox://styles/mapbox/dark-v9',
zoom: 2,
zoom: 13,
});
const LineLayer = new Line({});
LineLayer.source(testdata)
.size(5)
.color('red')
LineLayer.source(await response.json())
.size(1)
.shape('line')
.size(10);
.color(
'ELEV',
[
'#E8FCFF',
'#CFF6FF',
'#A1E9ff',
'#65CEF7',
'#3CB1F0',
'#2894E0',
'#1772c2',
'#105CB3',
'#0D408C',
'#002466',
].reverse(),
)
.render();
scene.addLayer(LineLayer);
// function run() {
// scene.render();

View File

@ -46,10 +46,6 @@ export default class Point3D extends React.Component {
// requestAnimationFrame(run);
scene.render();
this.scene = scene;
console.log(pointLayer);
// @ts-ignore
window.layer = pointLayer;
}
public render() {

View File

@ -7295,6 +7295,11 @@ gl-matrix@^3.0.0, gl-matrix@^3.1.0:
resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.1.0.tgz#f5b2de17d8fed95a79e5025b10cded0ab9ccbed0"
integrity sha512-526NA+3EA+ztAQi0IZpSWiM0fyQXIp7IbRvfJ4wS/TjjQD0uv0fVybXwwqqSOlq33UckivI0yMDlVtboWm3k7A==
gl-vec2@^1.0.0, gl-vec2@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/gl-vec2/-/gl-vec2-1.3.0.tgz#83d472ed46034de8e09cbc857123fb6c81c51199"
integrity sha512-YiqaAuNsheWmUV0Sa8k94kBB0D6RWjwZztyO+trEYS8KzJ6OQB/4686gdrf59wld4hHFIvaxynO3nRxpk1Ij/A==
glob-parent@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
@ -11387,6 +11392,13 @@ polished@^3.3.1:
dependencies:
"@babel/runtime" "^7.4.5"
polyline-miter-util@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/polyline-miter-util/-/polyline-miter-util-1.0.1.tgz#b693f2389ea0ded36a6bcf5ecd2ece4b6917d957"
integrity sha1-tpPyOJ6g3tNqa89ezS7OS2kX2Vc=
dependencies:
gl-vec2 "^1.0.0"
popper.js@^1.14.4, popper.js@^1.14.7:
version "1.15.0"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2"