refactor(source): source

This commit is contained in:
thinkinggis 2019-02-25 23:56:37 +08:00
parent 99e128d11d
commit 6e583a1ec7
48 changed files with 717 additions and 363 deletions

View File

@ -10,6 +10,9 @@
<title>point_circle</title>
<style>
#map { position:absolute; top:0; bottom:0; width:100%; }
.amap-maps {
cursor: auto !important
}
</style>
</head>
@ -55,15 +58,15 @@ scene.on('loaded', () => {
.shape('2d:circle')
.size('value', [ 2, 80]) // default 1
//.size('value', [ 10, 300]) // default 1
.active(false)
.active(true)
.filter('value', field_8 => {
return field_8 * 1 > 500;
})
.color('type', colorObj.red)
.color('type', colorObj.blue)
.style({
stroke: 'rgb(255,255,255)',
strokeWidth: 1,
opacity: 0.9
opacity: 1.
})
.render();

View File

@ -34,9 +34,11 @@ scene.on('loaded', () => {
zIndex: 2
})
.source(data.list, {
type: 'array',
parser:{
type: 'json',
x: 'j',
y: 'w',
}
})
.shape('cylinder')
.size('t',(level)=> {

View File

@ -34,9 +34,11 @@ scene.on('loaded', () => {
zIndex: 2
})
.source(data, {
parser:{
type: 'csv',
y: 'lat',
x: 'lng'
}
})
.size(1.0)
.color('#0D408C')

View File

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="geometry" content="diagram">
<link rel="stylesheet" href="./assets/common.css">
<link rel="stylesheet" href="./assets/info.css">
<title>point_circle</title>
<style>
#map { position:absolute; top:0; bottom:0; width:100%; }
.amap-maps {
cursor: auto !important
}
</style>
</head>
<body>
<div id="map"></div>
<script src="https://webapi.amap.com/maps?v=1.4.8&key=15cd8a57710d40c9b7c0e3cc120f1200&plugin=Map3D"></script>
<script src="./assets/jquery-3.2.1.min.js"></script>
<script src="./assets/dat.gui.min.js"></script>
<script src="../build/L7.js"></script>
<script>
const colorObj ={
blue: ["#E8FCFF", "#CFF6FF", "#A1E9ff", "#65CEF7", "#3CB1F0", "#2894E0", "#1772c2", "#105CB3", "#0D408C", "#002466"].reverse(),
red: ["#FFF4F2", "#FFDFDB", "#FAADAA", "#F77472", "#F04850", "#D63147", "#BD223E", "#A81642", "#820C37", "#5C0023"].reverse(),
orange:["#FFF7EB", "#FFECD4", "#FAD09D", "#F7B16A", "#F08D41", "#DB6C2C", "#C2491D", "#AD2B11", "#871D0C", "#610800"].reverse(),
green:["#FAFFF0", "#EBF7D2", "#C8E695", "#A5D660", "#7DC238", "#59A616", "#3F8C0B", "#237804", "#125200", "#082B00"].reverse(),
yellow:["#FFFFE8", "#FFFECC", "#FAF896", "#F7E463", "#F0CE3A", "#DBB125", "#C29117", "#AD7410", "#87500C", "#613000"].reverse(),
purple:["#FCF2FF", "#F5DEFF", "#DDB3F2", "#BE7BE3", "#9B4ECF", "#7737B3", "#5B2899", "#411C85", "#270F5E", "#100338"].reverse()
}
const scene = new L7.Scene({
id: 'map',
mapStyle: 'light', // 样式URL
center: [ 120.19382669582967, 30.258134 ],
pitch: 0,
zoom: 4,
maxZoom:14,
minZoom:4,
});
window.scene = scene;
scene.on('loaded', () => {
$.get('https://gw.alipayobjects.com/os/basement_prod/7359a5e9-3c5e-453f-b207-bc892fb23b84.csv', data => {
const circleLayer = scene.PointLayer({
zIndex: 2
})
.source(data,{
type: 'csv',
x: 'lng',
y: 'lat',
})
.size(1.0)
.color('#0D408C')
.style({
stroke: 'rgb(255,255,255)',
strokeWidth: 1,
opacity: 1.
})
.render();
circleLayer.on('click',(e)=>{
console.log(e);
})
});
});
</script>
</body>
</html>

View File

@ -33,8 +33,11 @@ const scene = new L7.Scene({
scene.on('loaded', () => {
const imageLayer = scene.ImageLayer().
source('https://gw.alipayobjects.com/zos/rmsportal/FnHFeFklTzKDdUESRNDv.jpg',{
parser:{
type:'image',
extent: [ 121.1680, 30.2828, 121.3840, 30.4219 ]
}
})
.style({
opacity:1.0,

View File

@ -48,13 +48,14 @@ scene.on('loaded', () => {
const layer = scene.RasterLayer({ zIndex: 2 }).
source(values, {
parser: {
type: 'raster',
width: n,
height: m,
min: 0,
max: 8000,
extent: [ 73.482190241, 3.82501784112, 135.106618732, 57.6300459963 ]
}
})
.style({
rampColors: {

74
demos/grid.html Normal file
View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="geometry" content="diagram">
<link rel="stylesheet" href="./assets/common.css">
<title>point_circle</title>
<style>
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
</head>
<body>
<div id="map"></div>
<script src="https://webapi.amap.com/maps?v=1.4.8&key=15cd8a57710d40c9b7c0e3cc120f1200&plugin=Map3D"></script>
<script src="./assets/jquery-3.2.1.min.js"></script>
<script src="./assets/dat.gui.min.js"></script>
<script src="../build/L7.js"></script>
<script>
const scene = new L7.Scene({
id: 'map',
mapStyle: 'dark', // 样式URL
center: [120.132624,30.281774],
pitch: 0,
zoom: 3.88
});
scene.on('loaded', () => {
$.get('https://gw.alipayobjects.com/os/basement_prod/7359a5e9-3c5e-453f-b207-bc892fb23b84.csv', data => {
var layer = scene.HeatMapLayer({
zIndex: 2
})
.source(data, {
parser: {
type: 'csv',
x: 'lng',
y: 'lat'
},
transforms:[
{
type: 'map',
callback:function(item){
const [x, y] = item.coordinates;
item.lat = item.lat*1;
item.lng = item.lng*1;
item.v = item.v *1;
item.coordinates = [x*1,y*1];
return item;
}
},
{
type: 'grid',
size: 15000,
field:'v',
method:'sum'
}
]
})
.shape('gird')
.style({
coverage: 0.8
})
.color('count', ["#002466","#105CB3","#2894E0","#CFF6FF","#FFF5B8","#FFAB5C","#F27049","#730D1C"])
.render();
console.log(layer);
});
});
</script>
</body>
</html>

View File

@ -107,6 +107,7 @@
"lodash": "^4.17.5",
"polyline-normals": "^2.0.2",
"rbush": "^2.0.2",
"simple-statistics": "^7.0.1",
"three": "^0.96.0",
"venn.js": "^0.2.20",
"viewport-mercator-project": "^5.2.0",

View File

@ -42,7 +42,7 @@ class Picking {
// this._world._container.addEventListener('mousemove', this._onWorldMove.bind(this), false);
}
pickdata(event) {
const point = { x: event.clientX, y: event.clientY, type: event.type };
const point = { x: event.offsetX, y: event.offsetY, type: event.type };
const normalisedPoint = { x: 0, y: 0 };
normalisedPoint.x = (point.x / this._width) * 2 - 1;
normalisedPoint.y = -(point.y / this._height) * 2 + 1;
@ -53,7 +53,6 @@ class Picking {
// if (event.button !== 0) {
// return;
// }
const point = { x: event.clientX, y: event.clientY, type: event.type };
const normalisedPoint = { x: 0, y: 0 };
normalisedPoint.x = (point.x / this._width) * 2 - 1;

View File

@ -5,7 +5,7 @@
import Base from './base';
import * as THREE from './three';
import ColorUtil from '../attr/color-util';
import * as source from '../source/index';
import source from './source';
import PickingMaterial from '../core/engine/picking/pickingMaterial';
import Attr from '../attr/index';
import Util from '../util';
@ -106,12 +106,10 @@ export default class Layer extends Base {
this._object3D.visible = this.get('visible');
}
source(data, cfg = {}) {
const dataType = this._getDataType(data);
const { type = dataType } = cfg;
cfg.data = data;
cfg.mapType = this.get('mapType');
this.layerSource = new source[type](cfg);
this.layerSource = new source(cfg);
// this.scene.workerPool.runTask({
// command: 'geojson',
// data: cfg
@ -279,10 +277,10 @@ export default class Layer extends Base {
const { featureId } = e;
if (featureId < 0) return;
const activeStyle = this.get('activedOptions');
const selectFeatureIds = this.layerSource.getSelectFeatureId(featureId);
// const selectFeatureIds = this.layerSource.getSelectFeatureId(featureId);
// 如果数据不显示状态则不进行高亮
if (this.StyleData[selectFeatureIds[0]].hasOwnProperty('filter') && this.StyleData[selectFeatureIds[0]].filter === false) { return; }
const style = Util.assign({}, this.StyleData[featureId]);
if (this.layerData[featureId].hasOwnProperty('filter') && this.layerData[featureId].filter === false) { return; }
const style = Util.assign({}, this.layerData[featureId]);
style.color = ColorUtil.toRGB(activeStyle.fill).map(e => e / 255);
this.updateStyle([ featureId ], style);
}
@ -321,7 +319,7 @@ export default class Layer extends Base {
_updateSize(zoom) {
const sizeOption = this.get('attrOptions').size;
const fields = parseFields(sizeOption.field);
const data = this.layerSource.propertiesData;
const data = this.layerSource.data.dataArray;
if (!this.zoomSizeCache) this.zoomSizeCache = {};
if (!this.zoomSizeCache[zoom]) {
this.zoomSizeCache[zoom] = [];
@ -339,7 +337,8 @@ export default class Layer extends Base {
const self = this;
const attrs = self.get('attrs');
const mappedData = [];
const data = this.layerSource.propertiesData;
// const data = this.layerSource.propertiesData;
const data = this.layerSource.data.dataArray;
for (let i = 0; i < data.length; i++) {
const record = data[i];
const newRecord = {};
@ -362,18 +361,17 @@ export default class Layer extends Base {
}
}
}
newRecord.coordinates = record.coordinates;
mappedData.push(newRecord);
}
this.StyleData = mappedData;
return mappedData;
this.layerData = mappedData;
}
// 更新地图映射
_updateMaping() {
const self = this;
const attrs = self.get('attrs');
const data = this.layerSource.propertiesData;
const data = this.layerSource.data.dataArray;
for (let i = 0; i < data.length; i++) {
const record = data[i];
for (const attrName in attrs) {
@ -385,10 +383,10 @@ export default class Layer extends Base {
for (let j = 0; j < values.length; j++) {
const val = values[j];
const name = names[j];
this.StyleData[i][name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
this.layerData[i][name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
}
} else {
this.StyleData[i][names[0]] = values.length === 1 ? values[0] : values;
this.layerData[i][names[0]] = values.length === 1 ? values[0] : values;
}
attr.neadUpdate = true;
@ -468,6 +466,7 @@ export default class Layer extends Base {
pickingMesh.material.setUniformsValue('u_zoom', zoom);
};
this._pickingMesh.add(pickingMesh);
}
_setPickingId() {
this._pickingId = this.getPickingId();
@ -532,13 +531,13 @@ export default class Layer extends Base {
*/
_updateFilter(object) {
this._updateMaping();
const filterData = this.StyleData;
const filterData = this.layerData;
this._activeIds = null; // 清空选中元素
const colorAttr = object.geometry.attributes.a_color;
const pickAttr = object.geometry.attributes.pickingId;
pickAttr.array.forEach((id, index) => {
id = Math.abs(id);
const color = [ ...this.StyleData[id - 1].color ];
const color = [ ...this.layerData[id - 1].color ];
id = Math.abs(id);
const item = filterData[id - 1];
if (item.hasOwnProperty('filter') && item.filter === false) {
@ -584,7 +583,7 @@ export default class Layer extends Base {
const pickingId = this.layerMesh.geometry.attributes.pickingId.array;
const colorAttr = this.layerMesh.geometry.attributes.a_color;
this._activeIds.forEach(index => {
const color = this.StyleData[index].color;
const color = this.layerData[index].color;
const firstId = pickingId.indexOf(index + 1);
for (let i = firstId; i < pickingId.length; i++) {
if (pickingId[i] === index + 1) {

View File

@ -2,16 +2,19 @@
* @Author: ThinkGIS
* @Date: 2018-06-08 11:19:06
* @Last Modified by: mikey.zhaopeng
* @Last Modified time: 2018-11-01 11:50:43
* @Last Modified time: 2019-02-25 20:58:08
*/
import Base from './base';
const Controller = require('./controller/index');
import { aProjectFlat } from '../geo/project';
import { getTransform, getParser } from '../source';
export default class Source extends Base {
getDefaultCfg() {
return {
data: null,
defs: {},
parser: {},
transforms: [],
scales: {
},
options: {}
@ -19,26 +22,40 @@ export default class Source extends Base {
}
constructor(cfg) {
super(cfg);
const transform = this.get('transforms');
this._transforms = transform || [];
this._initControllers();
this.prepareData();
// 数据解析
this._excuteParser();
// 数据转换 统计,聚合,分类
this._executeTrans();
// 坐标转换
this._projectCoords();
}
// 标准化数据
prepareData() {
_excuteParser() {
const parser = this.get('parser');
const { type = 'geojson' } = parser;
const data = this.get('data');
this.propertiesData = [];// 临时使用
this.geoData = [];
data.coordinates.forEach(geo => {
const coord = this._coordProject(geo);
this.geoData.push(coord);
this.propertiesData.push([]);
this.data = getParser(type)(data, parser);
}
/**
* 数据统计
*/
_executeTrans() {
const trans = this._transforms;
trans.forEach(tran => {
const { type } = tran;
this.data = getTransform(type)(this.data, tran);
});
this._transforms = trans;
}
_projectCoords() {
this.data.dataArray.forEach(data => {
data.coordinates = this._coordProject(data.coordinates);
});
}
createScale(field) {
const data = this.propertiesData;
const data = this.data.dataArray;
const scales = this.get('scales');
let scale = scales[field];
const scaleController = this.get('scaleController');
@ -87,5 +104,10 @@ export default class Source extends Base {
return [ ll.x, -ll.y, geo[2] || 0 ];
}
getSelectFeature(featureId) {
const data = this.get('data');
// 如果是GeoJSON 数据返回原数
return data.features ? data.features[featureId] : this.data.dataArray[featureId];
}
}

View File

@ -0,0 +1,32 @@
export default function gridBuffer(layerData) {
const attribute = {
vertices: [],
miter: [],
colors: [],
pickingIds: []
};
layerData.forEach(element => {
const { color, id } = element;
const [ x, y, z ] = element.coordinates;
attribute.vertices.push(x, y, z);
attribute.miter.push(-1, -1);
attribute.vertices.push(x, y, z);
attribute.miter.push(1, 1);
attribute.vertices.push(x, y, z);
attribute.miter.push(-1, 1);
attribute.vertices.push(x, y, z);
attribute.miter.push(-1, -1);
attribute.vertices.push(x, y, z);
attribute.miter.push(1, -1);
attribute.vertices.push(x, y, z);
attribute.miter.push(1, 1);
attribute.colors.push(...color);
attribute.colors.push(...color);
attribute.colors.push(...color);
attribute.colors.push(...color);
attribute.colors.push(...color);
attribute.colors.push(...color);
attribute.pickingIds.push(id, id, id, id, id, id);
});
return attribute;
}

View File

@ -4,8 +4,9 @@ import * as THREE from '../../core/three';
export default class ImageBuffer extends BufferBase {
geometryBuffer() {
const coordinates = this.get('coordinates');
const images = this.get('image');
const layerData = this.get('layerData');
const coordinates = layerData[0].coordinates;
const images = layerData[0].images;
const positions = [ ...coordinates[0],
coordinates[1][0], coordinates[0][1], 0,
...coordinates[1],

View File

@ -3,8 +3,7 @@ import { lineShape } from '../shape';
export default class LineBuffer extends BufferBase {
geometryBuffer() {
const coordinates = this.get('coordinates');
const properties = this.get('properties');
const layerData = this.get('layerData');
const shapeType = this.shapeType = this.get('shapeType');
const positions = [];
const positionsIndex = [];
@ -16,16 +15,16 @@ export default class LineBuffer extends BufferBase {
this.attributes = this._getArcLineAttributes();
return;
}
coordinates.forEach((geo, index) => {
const props = properties[index];
const attrData = this._getShape(geo, props, index);
layerData.forEach((item, index) => {
const props = item;
const attrData = this._getShape(item.coordinates, props, index);
positions.push(...attrData.positions);
positionsIndex.push(...attrData.indexes);
if (attrData.hasOwnProperty('instances')) {
instances.push(...attrData.instances);
}
});
this.bufferStruct.style = properties;
this.bufferStruct.style = layerData;
this.bufferStruct.verts = positions;
this.bufferStruct.indexs = positionsIndex;
if (instances.length > 0) {
@ -50,17 +49,16 @@ export default class LineBuffer extends BufferBase {
}
_getArcLineAttributes() {
const coordinates = this.get('coordinates');
const properties = this.get('properties');
const layerData = this.get('layerData');
const positions = [];
const colors = [];
const indexArray = [];
const sizes = [];
const instances = [];
coordinates.forEach((geo, index) => {
const props = properties[index];
layerData.forEach(item => {
const props = item;
const positionCount = positions.length / 3;
const attrData = this._getShape(geo, props, positionCount);
const attrData = this._getShape(item.coordinates, props, positionCount);
positions.push(...attrData.positions);
colors.push(...attrData.colors);
indexArray.push(...attrData.indexArray);
@ -76,8 +74,7 @@ export default class LineBuffer extends BufferBase {
};
}
_getMeshLineAttributes() {
const coordinates = this.get('coordinates');
const properties = this.get('properties');
const layerData = this.get('layerData');
const { lineType } = this.get('style');
const positions = [];
const pickingIds = [];
@ -87,10 +84,10 @@ export default class LineBuffer extends BufferBase {
const indexArray = [];
const sizes = [];
const attrDistance = [];
coordinates.forEach((geo, index) => {
const props = properties[index];
layerData.forEach(item => {
const props = item;
const positionCount = positions.length / 3;
const attr = lineShape.Line(geo, props, positionCount, (lineType !== 'soild'));
const attr = lineShape.Line(item.coordinates, props, positionCount, (lineType !== 'soild'));
positions.push(...attr.positions);
normal.push(...attr.normal);
miter.push(...attr.miter);

View File

@ -3,7 +3,7 @@ import * as THREE from '../../../core/three';
import * as polygonShape from '../../shape/polygon';
import * as polygonPath from '../../shape/path';
import Util from '../../../util';
export default function fillBuffer(coordinates, properties) {
export default function fillBuffer(layerData) {
const attribute = {
vertices: [],
normals: [],
@ -14,8 +14,8 @@ export default function fillBuffer(coordinates, properties) {
faceUv: []
};
coordinates.forEach((geo, index) => {
let { size, shape, color, id } = properties[index];
layerData.forEach(item => {
let { size, shape, color, id, coordinates } = item;
let polygon = null;
const path = polygonPath[shape]();
if (pointShape['2d'].indexOf(shape) !== -1) {
@ -27,7 +27,7 @@ export default function fillBuffer(coordinates, properties) {
} else {
throw new Error('Invalid shape type: ' + shape);
}
toPointShapeAttributes(polygon, geo, { size, shape, color, id }, attribute);
toPointShapeAttributes(polygon, coordinates, { size, shape, color, id }, attribute);
});
return attribute;

View File

@ -1,4 +1,4 @@
export default function ImageBuffer(coordinates, properties, opt) {
export default function ImageBuffer(layerData, opt) {
const attributes = {
vertices: [],
colors: [],
@ -7,10 +7,10 @@ export default function ImageBuffer(coordinates, properties, opt) {
pickingIds: [],
uv: []
};
coordinates.forEach((pos, index) => {
const { color, size, id, shape } = properties[index];
layerData.forEach(item => {
const { color, size, id, shape, coordinates } = item;
const { x, y } = opt.imagePos[shape];
attributes.vertices.push(...pos);
attributes.vertices.push(...coordinates);
attributes.colors.push(...color);
attributes.pickingIds.push(id);
attributes.sizes.push(size * window.devicePixelRatio); //

View File

@ -1,13 +1,13 @@
export default function NormalBuffer(coordinates, properties) {
export default function NormalBuffer(layerData) {
const attributes = {
vertices: [],
colors: [],
sizes: [],
pickingIds: []
};
coordinates.forEach((pos, index) => {
const { color, size, id } = properties[index];
attributes.vertices.push(...pos);
layerData.forEach(item => {
const { color, size, id, coordinates} = item;
attributes.vertices.push(...coordinates);
attributes.colors.push(...color);
attributes.pickingIds.push(id);
attributes.sizes.push(size);

View File

@ -3,7 +3,7 @@ import * as polygonShape from '../../shape/polygon';
import * as lineShape from '../../shape/line';
import { pointShape } from '../../../global';
import Util from '../../../util';
export default function StrokeBuffer(coordinates, properties, style) {
export default function StrokeBuffer(layerData, style) {
const attribute = {
shapes: [],
normal: [],
@ -15,8 +15,8 @@ export default function StrokeBuffer(coordinates, properties, style) {
colors: []
};
const { stroke, strokeWidth } = style;
coordinates.forEach((geo, index) => {
let { size, shape, id } = properties[index];
layerData.forEach(item => {
let { size, shape, id, coordinates } = item;
const path = polygonPath[shape]();
const positionsIndex = attribute.miter.length;
let polygon = null;
@ -33,7 +33,7 @@ export default function StrokeBuffer(coordinates, properties, style) {
} else {
throw new Error('Invalid shape type: ' + shape);
}
polygonLineBuffer(polygon, geo, size, attribute);
polygonLineBuffer(polygon, coordinates, size, attribute);
});
return attribute;

View File

@ -9,7 +9,7 @@ const metrics = {
family: 'ios9',
size: 24
};
export default function TextBuffer(coordinates, properties, style) {
export default function TextBuffer(layerData, style) {
EventEmitter.call(this);
const attributes = {
originPoints: [],
@ -21,7 +21,7 @@ export default function TextBuffer(coordinates, properties, style) {
const { textOffset = [ 0, 0 ] } = style;
const chars = [];
const textChars = {};
properties.forEach(element => {
layerData.forEach(element => {
let text = element.shape || '';
text = text.toString();
for (let j = 0; j < text.length; j++) {
@ -33,9 +33,9 @@ export default function TextBuffer(coordinates, properties, style) {
}
});
loadTextInfo(chars, (chars, texture) => {
properties.forEach((element, index) => {
layerData.forEach(element => {
const size = element.size;
const pos = coordinates[index];
const pos = layerData.coordinates;
const pen = { x: textOffset[0], y: textOffset[1] };
let text = element.shape || '';
text = text.toString();

View File

@ -3,22 +3,21 @@ import BufferBase from './bufferBase';
export default class PolygonBuffer extends BufferBase {
geometryBuffer() {
const coordinates = this.get('coordinates');
const properties = this.get('properties');
const layerData = this.get('layerData');
const shape = this.get('shape');
const positions = [];
const faceUv = [];
const sizes = [];
const positionsIndex = [];
let indexCount = 0;
this.bufferStruct.style = properties;
const isExtrude = properties[0].hasOwnProperty('size');
this.bufferStruct.style = layerData;
const isExtrude = layerData[0].hasOwnProperty('size');
// indices, normals, colors, UVs
coordinates.forEach((geo, index) => {
const heightValue = properties[index].size;
let extrudeData = polygonShape[shape](geo);
layerData.forEach(item => {
const heightValue = item.size;
let extrudeData = polygonShape[shape](item.coordinates);
if (isExtrude && shape === 'extrude') {
extrudeData = polygonShape.extrude(geo);
extrudeData = polygonShape.extrude(item.coordinates);
extrudeData.positions = extrudeData.positions.map(pos => {
pos[2] *= heightValue;
return pos;
@ -48,7 +47,7 @@ export default class PolygonBuffer extends BufferBase {
this.bufferStruct.indices = positionsIndex;
this.bufferStruct.position = positions;
this.bufferStruct.indexCount = indexCount;
this.bufferStruct.style = properties;
this.bufferStruct.style = layerData;
this.bufferStruct.faceUv = faceUv;
this.bufferStruct.sizes = sizes;
if (shape !== 'line') {

View File

@ -3,8 +3,8 @@ import { colorScales } from '../../attr/colorscales';
import * as THREE from '../../core/three';
export class RasterBuffer extends BufferBase {
geometryBuffer() {
const coordinates = this.get('coordinates');
const layerData = this.get('layerData');
const { coordinates, width, data, height } = layerData.dataArray[0];
const positions = [
...coordinates[0],
coordinates[1][0], coordinates[0][1], 0,
@ -14,9 +14,8 @@ export class RasterBuffer extends BufferBase {
coordinates[0][0], coordinates[1][1], 0
];
const imgPosUv = [ 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0 ];
const raster = this.get('raster');
const size = this.get('size');
const texture = new THREE.DataTexture(new Float32Array(raster.data), raster.width, raster.height, THREE.LuminanceFormat, THREE.FloatType);
const texture = new THREE.DataTexture(new Float32Array(data), width, height, THREE.LuminanceFormat, THREE.FloatType);
texture.generateMipmaps = true;
texture.needsUpdate = true;
const colors = this.get('rampColors');
@ -28,7 +27,7 @@ export class RasterBuffer extends BufferBase {
this.bufferStruct.u_extent = [ coordinates[0][0], coordinates[0][1], coordinates[1][0], coordinates[1][1] ];
this.bufferStruct.u_colorTexture = colorTexture; // 颜色表‘=
const triangles = this._buildTriangles(raster, size, this.bufferStruct.u_extent);
const triangles = this._buildTriangles(width, height, size, this.bufferStruct.u_extent);
const attributes = {
vertices: new Float32Array(triangles.vertices),
uvs: new Float32Array(triangles.uvs),
@ -78,9 +77,8 @@ export class RasterBuffer extends BufferBase {
texture1.needsUpdate = true;
return texture1;
}
_buildTriangles(raster, size = 1, extent) {
_buildTriangles(width, height, size = 1, extent) {
// const extent = [ 73.482190241, 3.82501784112, 135.106618732, 57.6300459963 ]
const { width, height } = raster;
const indices = [];
const vertices = [];
const uvs = [];

View File

@ -13,12 +13,11 @@ export default class TextBuffer extends BufferBase {
family: 'ios9',
size: 24
};
const coordinates = this.get('coordinates');
const properties = this.get('properties');
const layerData = this.get('layerData');
const { textOffset = [ 0, 0 ] } = this.get('style');
const chars = [];
const textChars = {};
properties.forEach(element => {
layerData.forEach(element => {
let text = element.shape || '';
text = text.toString();
for (let j = 0; j < text.length; j++) {
@ -39,9 +38,9 @@ export default class TextBuffer extends BufferBase {
const originPoints = [];
const textSizes = [];
const textOffsets = [];
properties.forEach((element, index) => {
layerData.forEach(element => {
const size = element.size;
const pos = coordinates[index];
const pos = element.coordinates;
// const pen = { x: pos[0] - dimensions.advance / 2, y: pos[1] };
const pen = { x: textOffset[0], y: textOffset[1] };
let text = element.shape || '';
@ -53,7 +52,7 @@ export default class TextBuffer extends BufferBase {
this._drawGlyph(pos, text[i], pen, size, colors, textureElements, originPoints, textSizes, textOffsets, color);
}
});
this.bufferStruct.style = properties;
this.bufferStruct.style = layerData;
this.attributes = {
originPoints,
textSizes,

31
src/geom/material/grid.js Normal file
View File

@ -0,0 +1,31 @@
import grid_frag from '../shader/grid_frag.glsl';
import grid_vert from '../shader/grid_vert.glsl';
import Material from './material';
export default class GridMaterial extends Material {
getDefaultParameters() {
return {
uniforms: {
u_opacity: { value: 1.0 },
u_time: { value: 0 },
u_xOffset: { value: 0.01 },
u_yOffset: { value: 0.01 },
u_coverage: { value: 0.8 }
},
defines: {
}
};
}
constructor(_uniforms, _defines, parameters) {
super(parameters);
const { uniforms, defines } = this.getDefaultParameters();
this.uniforms = Object.assign(uniforms, this.setUniform(_uniforms));
this.type = 'GridMaterial';
this.defines = Object.assign(defines, _defines);
this.vertexShader = grid_vert;
this.fragmentShader = grid_frag;
this.transparent = true;
}
}

View File

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

View File

@ -0,0 +1,15 @@
precision highp float;
attribute vec2 miter;
attribute vec4 a_color;
uniform float u_xOffset;
uniform float u_yOffset;
uniform float u_coverage;
varying vec4 v_color;
void main() {
mat4 matModelViewProjection = projectionMatrix * modelViewMatrix;
v_color = a_color;
float x = position.x + miter.x * u_xOffset * u_coverage;
float y = position.y + miter.y * u_yOffset * u_coverage;
gl_Position = matModelViewProjection * vec4(x, y, position.z, 1.0);
}

28
src/layer/heatmap.js Normal file
View File

@ -0,0 +1,28 @@
import Layer from '../core/layer';
import gridBuffer from '../geom/buffer/heatmap/grid';
import DrawGrid from './render/heatmap/gird';
export default class HeatMapLayer extends Layer {
shape(type) {
this.shapeType = type;
return this;
}
render() {
this._prepareRender();
return this;
}
_prepareRender() {
this.init();
this.type = 'heatmap';
const style = this.get('styleOptions');
const { xOffset, yOffset } = this.layerSource.data;
this._buffer = new gridBuffer(this.layerData);
const config = {
...style,
xOffset,
yOffset
};
const girdMesh = new DrawGrid(this._buffer, config);
this.add(girdMesh);
}
}

View File

@ -1,26 +1,19 @@
import Layer from '../core/layer';
import * as THREE from '../core/three';
import imageSource from '../source/imageSource';
import ImageBuffer from '../geom/buffer/image';
// import ImageGeometry from '../geom/bufferGeometry/image';
import ImageMaterial from '../geom/material/imageMaterial';
export default class imageLayer extends Layer {
source(data, cfg = {}) {
cfg.mapType = this.get('mapType');
cfg.data = data;
this.layerSource = new imageSource(cfg);
return this;
}
render() {
this.init();
this.type = 'image';
const source = this.layerSource;
const { opacity } = this.get('styleOptions');
// 加载 完成事件
source.on('imageLoaded', () => {
source.data.images.then(images => {
this.layerData[0].images = images;
const buffer = new ImageBuffer({
coordinates: source.geoData,
image: source.image
layerData: this.layerData
});
this.initGeometry(buffer.attributes);
const material = new ImageMaterial({

View File

@ -4,17 +4,14 @@ import PointLayer from './pointLayer';
import LineLayer from './lineLayer';
import ImageLayer from './imageLayer';
import RasterLayer from './rasterLayer';
import HeatMapLayer from './heatmap';
registerLayer('PolygonLayer', PolygonLayer);
registerLayer('PointLayer', PointLayer);
registerLayer('LineLayer', LineLayer);
registerLayer('ImageLayer', ImageLayer);
registerLayer('RasterLayer', RasterLayer);
registerLayer('HeatMapLayer', HeatMapLayer);
export { LAYER_MAP } from './factory';
export { default as PolygonLayer } from './polygonLayer';
export { default as PointLayer } from './pointLayer';
export { default as LineLayer } from './lineLayer';
export { default as ImageLayer } from './imageLayer';
export { default as RasterLayer } from './rasterLayer';

View File

@ -12,11 +12,10 @@ export default class LineLayer extends Layer {
this.type = 'polyline';
this.init();
const source = this.layerSource;
const StyleData = this.StyleData;
const layerData = this.layerData;
const style = this.get('styleOptions');
const buffer = this._buffer = new LineBuffer({
coordinates: source.geoData,
properties: StyleData,
layerData,
shapeType: this.shapeType,
style
});

View File

@ -46,12 +46,12 @@ export default class PointLayer extends Layer {
case 'fill' :// 填充图形
{
if (fill !== 'none') { // 是否填充
const attributes = PointBuffer.FillBuffer(source.geoData, this.StyleData, style);
const attributes = PointBuffer.FillBuffer(this.layerData, style);
const meshfill = drawPoint.DrawFill(attributes, this.get('styleOptions'));
this.add(meshfill);
}
if (stroke !== 'none') { // 是否绘制边界
const lineAttribute = PointBuffer.StrokeBuffer(source.geoData, this.StyleData, style);
const lineAttribute = PointBuffer.StrokeBuffer(this.layerData, style);
const meshStroke = drawPoint.DrawStroke(lineAttribute, this.get('styleOptions'));
this.add(meshStroke, 'line');
}
@ -59,14 +59,14 @@ export default class PointLayer extends Layer {
}
case 'image':// 绘制图片标注
{
const imageAttribute = PointBuffer.ImageBuffer(source.geoData, this.StyleData, { imagePos: this.scene.image.imagePos });
const imageAttribute = PointBuffer.ImageBuffer(this.layerData, { imagePos: this.scene.image.imagePos });
const imageMesh = drawPoint.DrawImage(imageAttribute, { ...style, texture: this.scene.image.texture });
this.add(imageMesh);
break;
}
case 'normal' : // 原生点
{
const normalAttribute = PointBuffer.NormalBuffer(source.geoData, this.StyleData, style);
const normalAttribute = PointBuffer.NormalBuffer(this.layerData, style);
const normalPointMesh = drawPoint.DrawNormal(normalAttribute, style);
this.add(normalPointMesh);
break;
@ -78,11 +78,11 @@ export default class PointLayer extends Layer {
_getShape() {
let shape = null;
if (!this.StyleData[0].hasOwnProperty('shape')) {
if (!this.layerData[0].hasOwnProperty('shape')) {
return 'normal';
}
for (let i = 0; i < this.StyleData.length; i++) {
shape = this.StyleData[i].shape;
for (let i = 0; i < this.layerData.length; i++) {
shape = this.layerData[i].shape;
if (shape !== undefined) {
break;
}
@ -103,8 +103,7 @@ export default class PointLayer extends Layer {
const styleOptions = this.get('styleOptions');
const buffer = new TextBuffer({
type: this.shapeType,
coordinates: source.geoData,
properties: this.StyleData,
layerData: this.layerData,
style: this.get('styleOptions')
});

View File

@ -21,11 +21,9 @@ export default class PolygonLayer extends Layer {
_prepareRender() {
this.init();
this.type = 'polygon';
const source = this.layerSource;
this._buffer = new PolygonBuffer({
shape: this.shape,
coordinates: source.geoData,
properties: this.StyleData
layerData: this.layerData
});
this.add(this._getLayerRender());
}

View File

@ -1,16 +1,10 @@
import Layer from '../core/layer';
import * as THREE from '../core/three';
import RasterSource from '../source/rasterSource';
import RasterMaterial from '../geom/material/rasterMaterial';
import { RasterBuffer } from '../geom/buffer/raster';
export default class RasterLayer extends Layer {
source(data, cfg = {}) {
cfg.mapType = this.get('mapType');
cfg.data = data;
this.layerSource = new RasterSource(cfg);
return this;
}
render() {
this.type = 'raster';
this.init();
@ -18,18 +12,18 @@ export default class RasterLayer extends Layer {
// 加载 完成事件
const styleOptions = this.get('styleOptions');
const buffer = new RasterBuffer({
coordinates: source.geoData,
raster: source.rasterData,
layerData: source.data,
rampColors: styleOptions.rampColors
});
this.initGeometry(buffer.attributes);
const rasterConfig = source.data.dataArray[0];
const material = new RasterMaterial({
u_texture: buffer.bufferStruct.u_raster,
u_colorTexture: buffer.bufferStruct.u_colorTexture,
u_opacity: 1.0,
u_extent: buffer.bufferStruct.u_extent,
u_min: source.rasterData.min,
u_max: source.rasterData.max,
u_min: rasterConfig.min,
u_max: rasterConfig.max,
u_dimension: buffer.attributes.dimension
});

View File

@ -0,0 +1,21 @@
import * as THREE from '../../../core/three';
import GridMaterial from '../../../geom/material/grid';
export default function DrawGrid(attributes, style) {
const { opacity, xOffset, yOffset, coverage } = style;
const geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3));
geometry.addAttribute('miter', new THREE.Float32BufferAttribute(attributes.miter, 2));
geometry.addAttribute('a_color', new THREE.Float32BufferAttribute(attributes.colors, 4));
geometry.addAttribute('pickingId', new THREE.Float32BufferAttribute(attributes.pickingIds, 1));
const material = new GridMaterial({
u_opacity: opacity,
u_xOffset: xOffset,
u_yOffset: yOffset,
u_coverage: coverage
}, {
SHAPE: false
});
const gridMesh = new THREE.Mesh(geometry, material);
return gridMesh;
}

View File

@ -1,69 +0,0 @@
import Source from '../core/source';
import FeatureIndex from '../geo/featureIndex';
import { csvParse } from 'd3-dsv';
export default class CSVSource extends Source {
prepareData() {
this.type = 'csv';
const data = this.get('data');
const x = this.get('x');
const y = this.get('y');
const x1 = this.get('x1');
const y1 = this.get('y1');
const coords = this.get('coordinates');
this.propertiesData = [];// 临时使用
this.geoData = [];
let csvdata = data;
Array.isArray(csvdata) || (csvdata = csvParse(data));
this.propertiesData = csvdata;
csvdata.forEach((col, featureIndex) => {
let coordinates = [];
if (col.coordinates) {
coordinates = col.coordinates;
}
if (x && y) { coordinates = [ col[x], col[y] ]; } // 点数据
if (x1 && y1) { // 弧线 或者线段
coordinates = [[ col[x], col[y] ], [ col[x1], col[y1] ]];
}
if (coords && col.coords) { coordinates = col.coords; }
col._id = featureIndex + 1;
this._coordProject(coordinates);
this.geoData.push(this._coordProject(coordinates));
});
}
featureIndex() {
const data = this.get('data');
this.featureIndex = new FeatureIndex(data);
}
getSelectFeatureId(featureId) {
return [ featureId ];
}
getSelectFeature(featureId) {
return this.propertiesData[featureId];
}
_getCoord(geo) {
if (geo.geometry) {
// GeoJSON feature
geo = geo.geometry.coordinates;
} else if (geo.coordinates) {
// GeoJSON geometry
geo = geo.coordinates;
}
return geo;
}
_coordProject(geo) {
if (Array.isArray(geo[0][0])) {
return geo.map(coor => {
return this._coordProject(coor);
});
}
if (!Array.isArray(geo[0])) {
return this._coorConvert(geo);
}
return geo.map(coor => {
return this._coorConvert(coor);
});
}
}

11
src/source/factory.js Normal file
View File

@ -0,0 +1,11 @@
const TRANSFORMS = {};
const PARSERS = {};
export const getParser = type => PARSERS[type];
export const registerParser = (type, parserFunction) => {
PARSERS[type] = parserFunction;
};
export const getTransform = type => TRANSFORMS[type];
export const registerTransform = (type, transFunction) => {
TRANSFORMS[type] = transFunction;
};

View File

@ -1,47 +0,0 @@
import Source from '../core/source';
import * as turfMeta from '@turf/meta';
import { default as cleanCoords } from '@turf/clean-coords';
import { getCoords } from '@turf/invariant';
import FeatureIndex from '../geo/featureIndex';
export default class GeojsonSource extends Source {
prepareData() {
this.type = 'geojson';
const data = this.get('data');
this.propertiesData = [];
this.geoData = [];
turfMeta.flattenEach(data, (currentFeature, featureIndex) => {
const coord = getCoords(cleanCoords(currentFeature));
this.geoData.push(this._coordProject(coord));
currentFeature.properties._id = featureIndex + 1;
this.propertiesData.push(currentFeature.properties);
});
}
featureIndex() {
const data = this.get('data');
this.featureIndex = new FeatureIndex(data);
}
getSelectFeatureId(featureId) {
const data = this.get('data');
const selectFeatureIds = [];
let featureStyleId = 0;
/* eslint-disable */
turfMeta.flattenEach(data, (currentFeature, featureIndex, multiFeatureIndex) => {
/* eslint-disable */
if (featureIndex === (featureId)) {
selectFeatureIds.push(featureStyleId);
}
featureStyleId++;
if (featureIndex > featureId) {
return;
}
});
return selectFeatureIds;
}
getSelectFeature(featureId){
const data = this.get('data');
return data.features[featureId];
}
}

View File

@ -1,37 +0,0 @@
import Source from '../core/source';
import { getImage } from '../util/ajax';
export default class ImageSource extends Source {
prepareData() {
this.type = 'image';
const extent = this.get('extent');
const lb = this._coorConvert(extent.slice(0, 2));
const tr = this._coorConvert(extent.slice(2, 4));
this.geoData = [ lb, tr ];
this.propertiesData = [];
this._loadData();
}
_loadData() {
const url = this.get('data');
this.image = [];
if (typeof (url) === 'string') {
getImage({ url }, (err, img) => {
this.image = img;
this.emit('imageLoaded');
});
} else {
const imageCount = url.length;
let imageindex = 0;
url.forEach(item => {
getImage({ url: item }, (err, img) => {
imageindex++;
this.image.push(img);
if (imageindex === imageCount) {
this.emit('imageLoaded');
}
});
});
}
}
}

View File

@ -1,5 +1,23 @@
export { default as geojson } from './geojsonSource';
export { default as csv } from './csvSource';
export { default as array } from './csvSource';
export { default as basic } from '../core/source';
export { default as imageSource } from './imageSource';
// source parser
import geojson from './parser/geojson';
import image from './parser/image';
import csv from './parser/csv';
import json from './parser/json';
import raster from './parser/raster';
import { registerTransform, registerParser } from './factory';
import { aggregatorToGrid } from './transform/grid-aggregator';
import { map } from './transform/map';
registerParser('geojson', geojson);
registerParser('image', image);
registerParser('csv', csv);
registerParser('json', json);
registerParser('raster', raster);
// 注册transform
registerTransform('grid', aggregatorToGrid);
registerTransform('map', map);
export { getTransform, registerTransform, getParser, registerParser } from './factory';

23
src/source/parser/csv.js Normal file
View File

@ -0,0 +1,23 @@
import { csvParse } from 'd3-dsv';
export default function csv(data, cfg) {
const { x, y, x1, y1 } = cfg;
const csvdata = csvParse(data);
const resultdata = [];
csvdata.forEach((col, featureIndex) => {
let coordinates = [];
if (x && y) { coordinates = [ col[x], col[y] ]; } // 点数据
if (x1 && y1) { // 弧线 或者线段
coordinates = [[ col[x], col[y] ], [ col[x1], col[y1] ]];
}
col._id = featureIndex + 1;
const dataItem = {
...col,
coordinates
};
resultdata.push(dataItem);
});
return {
dataArray: resultdata
};
}

View File

@ -0,0 +1,19 @@
import * as turfMeta from '@turf/meta';
import { default as cleanCoords } from '@turf/clean-coords';
import { getCoords } from '@turf/invariant';
export default function geoJSON(data) {
const resultData = [];
turfMeta.flattenEach(data, (currentFeature, featureIndex) => { // 多个polygon 拆成一个
const coord = getCoords(cleanCoords(currentFeature));
const dataItem = {
...currentFeature.properties,
coordinates: coord,
_id: featureIndex + 1
};
resultData.push(dataItem);
});
return {
dataArray: resultData
};
}

View File

@ -0,0 +1,41 @@
import { getImage } from '../../util/ajax';
export default function image(data, cfg) {
const { extent } = cfg;
const images = new Promise(resolve => {
loadData(data, res => {
resolve(res);
});
});
const resultData = {
images,
_id: 1,
dataArray: [{ coordinates: [[ extent[0], extent[1] ], [ extent[2], extent[3] ]] }]
};
return resultData;
}
function loadData(data, done) {
const url = data;
let image = [];
if (typeof (url) === 'string') {
getImage({ url }, (err, img) => {
image = img;
done(image);
});
} else {
const imageCount = url.length;
let imageindex = 0;
url.forEach(item => {
getImage({ url: item }, (err, img) => {
imageindex++;
image.push(img);
if (imageindex === imageCount) {
done(image);
}
});
});
}
return image;
}

21
src/source/parser/json.js Normal file
View File

@ -0,0 +1,21 @@
export default function json(data, cfg) {
const { x, y, x1, y1 } = cfg;
const resultdata = [];
data.forEach((col, featureIndex) => {
let coordinates = [];
if (x && y) { coordinates = [ col[x], col[y] ]; } // 点数据
if (x1 && y1) { // 弧线 或者线段
coordinates = [[ col[x], col[y] ], [ col[x1], col[y1] ]];
}
col._id = featureIndex + 1;
const dataItem = {
...col,
coordinates
};
resultdata.push(dataItem);
});
return {
dataArray: resultdata
};
}

View File

@ -0,0 +1,15 @@
export default function raster(data, cfg) {
const { extent, width, height, min, max } = cfg;
const resultData = {
_id: 1,
dataArray: [
{
data,
width,
height,
min,
max,
coordinates: [[ extent[0], extent[1] ], [ extent[2], extent[3] ]] }]
};
return resultData;
}

View File

@ -1,31 +0,0 @@
import imageSource from './imageSource';
export class RainSource extends imageSource {
prepareData() {
const extent = this.get('extent');
const lb = this._coorConvert(extent.slice(0, 2));
const tr = this._coorConvert(extent.slice(2, 4));
this.extent = [ lb, tr ];
this.propertiesData = [];
this._genaratePoints();
this._loadData();
}
_genaratePoints() {
const numParticles = 512 * 512;
const particleRes = this.particleRes = Math.ceil(Math.sqrt(numParticles));
const numPoints = particleRes * particleRes;
const particleState = [];
const particleState0 = new Uint8ClampedArray(numPoints * 4);
const particleState1 = new Uint8ClampedArray(numPoints * 4);
const emptyPixels = new Uint8ClampedArray(numPoints * 4);
for (let i = 0; i < particleState0.length; i++) {
particleState0[i] = Math.floor(Math.random() * 256); // randomize the initial particle positions
}
this.particleIndices = new Float32Array(numPoints);
for (let i = 0; i < numPoints; i++) this.particleIndices[i] = i;
this.particleImage0 = new ImageData(particleState0, particleRes, particleRes);
this.particleImage1 = new ImageData(particleState1, particleRes, particleRes);
this.backgroundImage = new ImageData(emptyPixels, particleRes, particleRes);
this.geoData = particleState;
}
}

View File

@ -1,20 +0,0 @@
import Source from '../core/source';
export default class RasterSource extends Source {
prepareData() {
this.type = 'raster';
const extent = this.get('extent');
const lb = this._coorConvert(extent.slice(0, 2));
const tr = this._coorConvert(extent.slice(2, 4));
this.geoData = [ lb, tr ];
this.propertiesData = [];
this.rasterData = {
data: this.get('data'),
width: this.get('width'),
height: this.get('height'),
min: this.get('min'),
max: this.get('max')
};
}
}

View File

@ -0,0 +1,101 @@
/**
* 生成四边形热力图
*/
import * as statistics from './statistics';
const R_EARTH = 6378000;
/**
* 计算方格密度图
* @param {*} data 经纬度数据 和属性数据
* @param {*} size 半径大小 单位 km
* @return
*/
export function aggregatorToGrid(data, option) {
const dataArray = data.dataArray;
const { size = 10 } = option;
const { gridHash, gridOffset } = _pointsGridHash(dataArray, size);
const layerData = _getGridLayerDataFromGridHash(gridHash, gridOffset, option);
return {
xOffset: gridOffset.xOffset / 360 * (256 << 20) / 2,
yOffset: gridOffset.xOffset / 360 * (256 << 20) / 2,
dataArray: layerData
};
}
function _pointsGridHash(dataArray, size) {
let latMin = Infinity;
let latMax = -Infinity;
let pLat;
for (let index = 0; index < dataArray.length; index++) {
const point = dataArray[index];
pLat = point.coordinates[1];
if (Number.isFinite(pLat)) {
latMin = pLat < latMin ? pLat : latMin;
latMax = pLat > latMax ? pLat : latMax;
}
}
// const centerLat = (latMin + latMax) / 2;
const centerLat = 34.54083;
const gridOffset = _calculateGridLatLonOffset(size, centerLat);
if (gridOffset.xOffset <= 0 || gridOffset.yOffset <= 0) {
return { gridHash: {}, gridOffset };
}
const gridHash = {};
for (let index = 0; index < dataArray.length; index++) {
const point = dataArray[index];
const lat = point.coordinates[1];
const lng = point.coordinates[0];
if (Number.isFinite(lat) && Number.isFinite(lng)) {
const latIdx = Math.floor((lat + 90) / gridOffset.yOffset);
const lonIdx = Math.floor((lng + 180) / gridOffset.xOffset);
const key = `${latIdx}-${lonIdx}`;
gridHash[key] = gridHash[key] || { count: 0, points: [] };
gridHash[key].count += 1;
gridHash[key].points.push(point);
}
}
return { gridHash, gridOffset };
}
// 计算网格偏移量
function _calculateGridLatLonOffset(cellSize, latitude) {
const yOffset = _calculateLatOffset(cellSize);
const xOffset = _calculateLonOffset(latitude, cellSize);
return { yOffset, xOffset };
}
function _calculateLatOffset(dy) {
return (dy / R_EARTH) * (180 / Math.PI);
}
function _calculateLonOffset(lat, dx) {
return ((dx / R_EARTH) * (180 / Math.PI)) / Math.cos((lat * Math.PI) / 180);
}
function _getGridLayerDataFromGridHash(gridHash, gridOffset, option) {
return Object.keys(gridHash).reduce((accu, key, i) => {
const idxs = key.split('-');
const latIdx = parseInt(idxs[0], 10);
const lonIdx = parseInt(idxs[1], 10);
const item = {};
if (option.field && option.method) {
const columns = getColumn(gridHash[key].points, option.field);
item[option.method] = statistics[option.method](columns);
}
Object.assign(item, {
_id: i + 1,
coordinates: [ -180 + gridOffset.xOffset * lonIdx, -90 + gridOffset.yOffset * latIdx ],
count: gridHash[key].count
});
accu.push(item);
return accu;
}, []);
}
function getColumn(data, columnName) {
return data.map(item => {
return item[columnName];
});
}

View File

@ -0,0 +1,7 @@
export function map(data, options) {
const { callback } = options;
if (callback) {
data.dataArray = data.dataArray.map(callback);
}
return data;
}

View File

@ -0,0 +1,10 @@
/* 'max', 'mean', 'median', 'min', 'mode', 'product', 'standardDeviation',
* 'sum', 'sumSimple', 'variance', 'count', 'distinct'
*/
export { default as min } from 'simple-statistics/src/min';
export { default as max } from 'simple-statistics/src/max';
export { default as mean } from 'simple-statistics/src/mean';
export { default as sum } from 'simple-statistics/src/sum';
export { default as median } from 'simple-statistics/src/median';
export { default as standardDeviation } from 'simple-statistics/src/standard_deviation';