Merge branch 'grid-heatMap' into 'master'

Grid heat map

增加mapbox
增加多边形热力图

See merge request !5
This commit is contained in:
象数 2019-02-26 17:28:35 +08:00
commit e611930470
68 changed files with 2731 additions and 472 deletions

1
.gitignore vendored
View File

@ -69,7 +69,6 @@ lib
*.sw*
*.un~
demos/mapbox.html
demos/gd.html
demos/data
demos/image

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',
x: 'j',
y: 'w',
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, {
type: 'csv',
y: 'lat',
x: 'lng'
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 ]
}
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, {
type: 'raster',
width: n,
height: m,
min: 0,
max: 8000,
extent: [ 73.482190241, 3.82501784112, 135.106618732, 57.6300459963 ]
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>

93
demos/mapbox.html Normal file
View File

@ -0,0 +1,93 @@
<!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>
body {
margin:0;
padding:0;
}
#map { position:absolute; top:0; bottom:0; width:100%; margin:0; }
</style>
</head>
<body>
<div id="map"></div>
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.34.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.34.0/mapbox-gl.css' rel='stylesheet' />
<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({
container: 'map',
mapType:'mapbox',
style: 'mapbox://styles/mapbox/streets-v9', // stylesheet location
center: [ 120.19382669582967, 30.258134 ],
pitch: 0,
hash:true,
zoom: 1,
});
window.scene = scene;
scene.on('loaded', () => {
var colors = ["#FFF5B8","#FFDC7D","#FFAB5C","#F27049","#D42F31","#730D1C"];
$.getJSON('https://gw.alipayobjects.com/os/rmsportal/JToMOWvicvJOISZFCkEI.json', city => {
const citylayer = scene.PolygonLayer()
.source(city)
//.color('pm2_5_24h',["#FFF5B8","#FFDC7D","#FFAB5C","#F27049","#D42F31","#730D1C"])
.color('pm2_5_24h',(p)=>{
if(p>120){
return colors[5];
} else if(p>65){
return colors[4];
} else if(p>30) {
return colors[3];
} else if(p>15){
return colors[2];
} else if(p>8){
return colors[1];
}else {
return colors[0];
}
})
.shape('fill')
.active(true)
.style({
opacity: 1
})
.render();
console.log('success');
});
$.get('https://gw.alipayobjects.com/os/rmsportal/ggFwDClGjjvpSMBIrcEx.json', data => {
citylayer = scene.PolygonLayer({
zIndex: 2
})
.source(data)
.shape('extrude')
.active({fill:'red'})
.size('floor',[0.1,1])
.color('#aaa')
.render();
})
});
</script>
</body>
</html>

View File

@ -101,12 +101,14 @@
"@turf/invariant": "^6.1.2",
"@turf/meta": "^6.0.2",
"d3-dsv": "^1.0.10",
"d3-hexbin": "^0.2.2",
"earcut": "^2.1.3",
"fecha": "^2.3.3",
"gl-matrix": "^2.4.1",
"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

@ -1,36 +0,0 @@
import geom from '../../geom/geom';
import { GeoBuffer, bufferGeometry, Material } from '../../geom/index';
// geom shape buffer geometry material
// shape name type()
// buffer 1:n geometry
// geometry
//
export default function polygonGeom(shape, coordinates, properties, layerid) {
const polygongeom = geom.polygon;
const { buffer, geometry, material } = polygongeom[shape];// polygon 映射表
const bufferData = new GeoBuffer[buffer]({
coordinates,
properties,
shape
});
bufferData.bufferStruct.name = layerid;
const bg = new bufferGeometry[geometry](bufferData.bufferStruct);
const mtl = Material[material]();
return {
geometry: bg,
mtl
};
}
export function pointGeom(shape, bufferData) {
const pointgeom = geom.point;
const { geometry, material } = pointgeom[shape];
const bg = new bufferGeometry[geometry](bufferData.bufferStruct);
const mtl = Material[material]();
return {
geometry: bg,
mtl
};
}

View File

@ -0,0 +1,24 @@
import { getMap } from '../../map';
import Base from '../base';
export default class MapContorller extends Base {
constructor(cfg, engine, scene) {
super(cfg);
this._engine = engine;
this.scene = scene;
}
_init() {
const mapType = this.get('mapType');
const mapCfg = this.get('mapCfg');
this.map = new getMap(mapType)(mapCfg);
this.map('mapLoad', this._mapload.bind(this));
}
_mapload() {
this.map.asyncCamera(this._engine);
this.emit('loaded');
}
_bindMapMethod() {
}
}

View File

@ -11,6 +11,9 @@ export default class Engine extends EventEmitter {
this._camera = new Camera(container).camera;
this._renderer = new Renderer(container).renderer;
this._world = world;
// for MapBox
this.world = new THREE.Group();
this._scene.add(this.world);
this._picking = Picking(this._world, this._renderer, this._camera, this._scene);
this.clock = new THREE.Clock();
}

View File

@ -11,6 +11,8 @@ class Picking {
this.scene = scene;
this._raycaster.linePrecision = 10;
this._pickingScene = PickingScene;
this.world = new THREE.Group();
this._pickingScene.add(this.world);
const size = this._renderer.getSize();
this._width = size.width;
this._height = size.height;
@ -34,8 +36,7 @@ class Picking {
window.addEventListener('resize', this._resizeHandler, false);
}
pickdata(event) {
const point = { x: event.pixel.x, y: event.pixel.y, 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;
@ -61,12 +62,12 @@ class Picking {
}
_filterObject(id) {
this._pickingScene.children.forEach((object, index) => {
this.world.children.forEach((object, index) => {
index === id ? object.visible = true : object.visible = false;
});
}
_pickAllObject(point, normalisedPoint) {
this._pickingScene.children.forEach((object, index) => {
this.world.children.forEach((object, index) => {
this._filterObject(index);
const item = this._pick(point, normalisedPoint, object.name);
item.type = point.type;
@ -86,7 +87,6 @@ class Picking {
id = -999;
// return;
}
this._raycaster.setFromCamera(normalisedPoint, this._camera);
const intersects = this._raycaster.intersectObjects(this._pickingScene.children, true);
@ -111,13 +111,14 @@ class Picking {
//
// Picking ID should already be added as an attribute
add(mesh) {
this._pickingScene.add(mesh);
this.world.add(mesh);
this._needUpdate = true;
}
// Remove mesh from picking scene
remove(mesh) {
this._pickingScene.remove(mesh);
this.world.remove(mesh);
this._needUpdate = true;
}

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';
@ -63,7 +63,8 @@ export default class Layer extends Base {
const layerId = this._getUniqueId();
this.layerId = layerId;
this._activeIds = null;
scene._engine._scene.add(this._object3D);
const world = scene._engine.world;
world.add(this._object3D);
this.layerMesh = null;
this.layerLineMesh = null;
this._initEvents();
@ -106,12 +107,9 @@ 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);
cfg.mapType = this.scene.mapType;
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

@ -3,8 +3,8 @@ import { LAYER_MAP } from '../layer';
import Base from './base';
import LoadImage from './image';
import WorkerPool from './worker';
import { MapProvider } from '../map/provider';
import GaodeMap from '../map/gaodeMap';
// import { MapProvider } from '../map/AMap';
import { getMap } from '../map/index';
import Global from '../global';
import { compileBuiltinModules } from '../geom/shader';
export default class Scene extends Base {
@ -31,16 +31,14 @@ export default class Scene extends Base {
}
_initMap() {
this.mapContainer = this.get('id');
this._container = document.getElementById(this.mapContainer);
const Map = new MapProvider(this.mapContainer, this._attrs);
this.mapType = this.get('mapType') || 'amap';
const MapProvider = getMap(this.mapType);
const Map = new MapProvider(this._attrs);
Map.mixMap(this);
this._container = document.getElementById(Map.container);
// const Map = new MapProvider(this.mapContainer, this._attrs);
Map.on('mapLoad', () => {
this._initEngine(Map.renderDom);
const sceneMap = new GaodeMap(Map.map);
// eslint-disable-next-line
Object.getOwnPropertyNames(sceneMap.__proto__).forEach((key)=>{
// eslint-disable-next-line
if ('key' !== 'constructor') { this.__proto__[key] = sceneMap.__proto__[key]; }
});
this.map = Map.map;
Map.asyncCamera(this._engine);
this.initLayer();
@ -101,8 +99,10 @@ export default class Scene extends Base {
'dblclick'
];
events.forEach(event => {
this.map.on(event, e => {
this._container.addEventListener(event, e => {
// 要素拾取
e.pixel || (e.pixel = e.point);
this._engine._picking.pickdata(e);
}, false);
});

View File

@ -1,17 +1,15 @@
/*
* @Author: ThinkGIS
* @Date: 2018-06-08 11:19:06
* @Last Modified by: mikey.zhaopeng
* @Last Modified time: 2018-11-01 11:50:43
*/
import Base from './base';
const Controller = require('./controller/index');
import { aProjectFlat } from '../geo/project';
import { getTransform, getParser } from '../source';
import { getMap } from '../map/index';
export default class Source extends Base {
getDefaultCfg() {
return {
data: null,
defs: {},
parser: {},
transforms: [],
scales: {
},
options: {}
@ -19,26 +17,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');
@ -50,6 +62,8 @@ export default class Source extends Base {
}
_initControllers() {
const defs = this.get('defs');
const mapType = this.get('mapType');
this.projectFlat = getMap(mapType).project;
const scaleController = new Controller.Scale({
defs
});
@ -83,9 +97,14 @@ export default class Source extends Base {
}
_coorConvert(geo) {
const ll = aProjectFlat(geo);
return [ ll.x, -ll.y, geo[2] || 0 ];
const ll = this.projectFlat(geo);
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

@ -4,6 +4,7 @@ export { Scene } from 'three/src/scenes/Scene.js';
export { WebGLRenderer } from 'three/src/renderers/WebGLRenderer.js';
export { CanvasTexture } from 'three/src/textures/CanvasTexture.js';
export { Object3D } from 'three/src/core/Object3D.js';
export { Group } from 'three/src/objects/Group';
export { Clock } from 'three/src/core/Clock';
export { Points } from 'three/src/objects/Points.js';
export { LineSegments } from 'three/src/objects/LineSegments.js';

File diff suppressed because one or more lines are too long

View File

@ -13,5 +13,20 @@ export function aProjectFlat(lnglat) {
d = 0.5;
x = scale * (a * x + b) - 215440491;
y = scale * (c * y + d) - 106744817;
return { x, y };
return { x: parseInt(x), y: parseInt(y) };
}
export function unProjectFlat(px) {
const a = 0.5 / Math.PI,
b = 0.5,
c = -0.5 / Math.PI;
let d = 0.5;
const scale = 256 << 20;
let [ x, y ] = px;
x = ((x + 215440491) / scale - b) / a;
y = ((y + 106744817) / scale - d) / c;
y = (Math.atan(Math.pow(Math.E, y)) - (Math.PI / 4)) * 2;
d = Math.PI / 180;
const lat = y / d;
const lng = x / d;
return [ lng, lat ];
}

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

@ -1,28 +1,5 @@
import Material from './material';
import { getModule } from '../../util/shaderModule';
// export default function PolygonMaterial(options) {
// const material = new Material({
// uniforms: {
// u_opacity: { value: options.u_opacity || 1.0 },
// u_texture: { value: options.u_texture },
// u_time: { value: options.u_time || 0 },
// u_zoom: { value: options.u_zoom || 0 },
// u_baseColor: { value: options.u_baseColor || [ 1.0, 0, 0, 1.0 ] },
// u_brightColor: { value: options.u_brightColor || [ 1.0, 0, 0, 1.0 ] },
// u_windowColor: { value: options.u_windowColor || [ 1.0, 0, 0, 1.0 ] },
// u_near: { value: options.u_near || 0.0 },
// u_far: { value: options.u_far || 1.0 }
// },
// vertexShader: polygon_vert,
// fragmentShader: polygon_frag,
// transparent: true,
// defines: {
// TEXCOORD_0: !!options.u_texture
// }
// });
// return material;
// }
export default class PolygonMaterial extends Material {
getDefaultParameters() {
return {

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);
}

View File

@ -1,7 +1,8 @@
precision highp float;
#pragma import "common"
#define PI 3.14159265359
#define TWO_PI 6.28318530718
uniform float u_strokeWidth;
uniform vec4 u_stroke;

View File

@ -15,7 +15,7 @@ varying float v_size;
void main() {
float scale = pow(2.0,(20.0 - u_zoom));
mat4 matModelViewProjection = projectionMatrix * modelViewMatrix;
mat4 matModelViewProjection = projectionMatrix * modelViewMatrix * 100.;
vec3 newposition = position;
// newposition.x -= 128.0;
#ifdef SHAPE

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

@ -45,12 +45,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');
}
@ -58,14 +58,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;
@ -77,11 +77,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;
}
@ -102,8 +102,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,18 +1,34 @@
import Base from '../core/base';
import { scene } from '../global';
import * as Theme from '../theme/index';
import Util from '../util';
import { scene } from '../global';
const DEG2RAD = Math.PI / 180;
export class MapProvider extends Base {
export default class GaodeMap extends Base {
getDefaultCfg() {
return Util.assign(scene, {
resizeEnable: true,
viewMode: '3D'
});
}
constructor(container, cfg) {
static project(lnglat) {
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,
b = 0.5,
c = -0.5 / Math.PI;
d = 0.5;
x = scale * (a * x + b) - 215440491;
y = -(scale * (c * y + d) - 106744817);
return { x, y };
}
constructor(cfg) {
super(cfg);
this.container = container;
this.container = this.get('id');
this.initMap();
this.addOverLayer();
setTimeout(() => {
@ -85,4 +101,31 @@ export class MapProvider extends Base {
this.renderDom.id = 'l7_canvaslayer';
canvasContainer.appendChild(this.renderDom);
}
mixMap(scene) {
const map = this.map;
scene.getZoom = () => { return map.getZoom(); };
scene.getCenter = () => { return map.getCenter(); };
scene.getSize = () => { return map.getSize(); };
scene.getPitch = () => { return map.getPitch(); };
scene.getRotation = () => { return map.getRotation(); };
scene.getStatus = () => { return map.getStatus(); };
scene.getScale = () => { return map.getScale(); };
scene.getZoom = () => { return map.getZoom(); };
scene.setZoom = () => { return map.setZoom(); };
scene.setBounds = () => { return map.setBounds(); };
scene.setRotation = () => { return map.setRotation(); };
scene.zoomIn = () => { return map.zoomIn(); };
scene.setRotation = () => { return map.setRotation(); };
scene.zoomOut = () => { return map.zoomOut(); };
scene.panTo = () => { return map.panTo(); };
scene.panBy = () => { return map.panBy(); };
scene.setPitch = () => { return map.setPitch(); };
scene.pixelToLngLat = () => { return map.pixelToLngLat(); };
scene.lngLatToPixel = () => { return map.lngLatToPixel(); };
scene.setMapStyle = () => { return map.setMapStyle(); };
scene.containerToLngLat = pixel => {
const ll = new AMap.Pixel(pixel.x, pixel.y);
return map.containerToLngLat(ll);
};
}
}

View File

View File

@ -21,7 +21,7 @@ export default class GaodeMap {
return this.map.getStatus();
}
getScale() {
return this.getScale();
return this.map.getScale();
}
setZoom(zoom) {
return this.map.setZoom(zoom);

22
src/map/index.js Normal file
View File

@ -0,0 +1,22 @@
import MapBox from './mapbox';
import { default as AMap } from './AMap';
export {
AMap,
MapBox
};
const MapType = {
amap: AMap,
mapbox: MapBox
};
export const getMap = type => {
return MapType[type.toLowerCase()];
};
export const registerMap = (type, map) => {
if (getMap(type)) {
throw new Error(`Map type '${type}' existed.`);
}
map.type = type;
// 存储到 map 中
MapType[type.toLowerCase()] = map;
};

154
src/map/mapbox.js Normal file
View File

@ -0,0 +1,154 @@
import Base from '../core/base';
import Util from '../util';
import { scene } from '../global';
import * as THREE from '../core/three';
const WORLD_SIZE = 512;
const MERCATOR_A = 6378137.0;
const WORLD_SCALE = 1 / 100;
const PROJECTION_WORLD_SIZE = WORLD_SIZE / (MERCATOR_A * Math.PI) / 2;
export default class MapBox extends Base {
getDefaultCfg() {
return Util.assign(scene, {
resizeEnable: true,
viewMode: '3D'
});
}
static project(lnglat) {
const d = Math.PI / 180;
const x = -MERCATOR_A * lnglat[0] * d * PROJECTION_WORLD_SIZE;
const y = -MERCATOR_A * Math.log(Math.tan((Math.PI * 0.25) + (0.5 * lnglat[1] * d))) * PROJECTION_WORLD_SIZE;
return { x, y };
}
constructor(cfg) {
super(cfg);
this.container = this.get('container');
this.initMap();
this.addOverLayer();
setTimeout(() => {
this.emit('mapLoad');
}, 100);
}
initMap() {
mapboxgl.accessToken = 'pk.eyJ1IjoibHp4dWUiLCJhIjoiYnhfTURyRSJ9.Ugm314vAKPHBzcPmY1p4KQ';
this.map = new mapboxgl.Map(this._attrs);
}
asyncCamera(engine) {
this.engine = engine;
const camera = engine._camera;
const scene = engine.world;
const pickScene = engine._picking.world;
camera.matrixAutoUpdate = false;
scene.position.x = scene.position.y = WORLD_SIZE / 2;
scene.matrixAutoUpdate = false;
pickScene.position.x = pickScene.position.y = WORLD_SIZE / 2;
pickScene.matrixAutoUpdate = false;
this.updateCamera();
this.map.on('move', () => {
this.updateCamera();
});
}
updateCamera() {
const engine = this.engine;
const scene = engine.world;
const pickScene = engine._picking.world;
const camera = engine._camera;
// Build a projection matrix, paralleling the code found in Mapbox GL JS
const fov = 0.6435011087932844;
const cameraToCenterDistance = 0.5 / Math.tan(fov / 2) * this.map.transform.height * WORLD_SCALE;
const halfFov = fov / 2;
const groundAngle = Math.PI / 2 + this.map.transform._pitch;
const topHalfSurfaceDistance = Math.sin(halfFov) * cameraToCenterDistance / Math.sin(Math.PI - groundAngle - halfFov);
// Calculate z distance of the farthest fragment that should be rendered.
const furthestDistance = Math.cos(Math.PI / 2 - this.map.transform._pitch) * topHalfSurfaceDistance + cameraToCenterDistance;
// Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance`
let farZ = furthestDistance * 1.1;
if (this.pitch > 50) {
farZ = 1000;
}
const { x, y } = this.map.transform.point;
camera.projectionMatrix = this.makePerspectiveMatrix(fov, this.map.transform.width / this.map.transform.height, 1, farZ);
const cameraWorldMatrix = new THREE.Matrix4();
const cameraTranslateZ = new THREE.Matrix4().makeTranslation(0, 0, cameraToCenterDistance);
const cameraRotateX = new THREE.Matrix4().makeRotationX(this.map.transform._pitch);
const cameraRotateZ = new THREE.Matrix4().makeRotationZ(this.map.transform.angle);
const cameraTranslateXY = new THREE.Matrix4().makeTranslation(x * WORLD_SCALE, -y * WORLD_SCALE, 0);
// const cameraTranslateCenter = new THREE.Matrix4().makeTranslation(0, 0, cameraToCenterDistance);
// Unlike the Mapbox GL JS camera, separate camera translation and rotation out into its world matrix
// If this is applied directly to the projection matrix, it will work OK but break raycasting
cameraWorldMatrix
.premultiply(cameraTranslateZ)
.premultiply(cameraRotateX)
.premultiply(cameraRotateZ)
.premultiply(cameraTranslateXY);
camera.matrixWorld.copy(cameraWorldMatrix);
const zoomPow = this.map.transform.scale * WORLD_SCALE;
// Handle scaling and translation of objects in the map in the world's matrix transform, not the camera
const scale = new THREE.Matrix4();
const translateCenter = new THREE.Matrix4();
const translateMap = new THREE.Matrix4();
const rotateMap = new THREE.Matrix4();
scale
.makeScale(zoomPow, zoomPow, 1.0);
translateCenter
.makeTranslation(WORLD_SIZE / 2, -WORLD_SIZE / 2, 0);
translateMap
.makeTranslation(-this.map.transform.x, this.map.transform.y, 0);
rotateMap
.makeRotationZ(Math.PI);
scene.matrix = new THREE.Matrix4();
scene.matrix
.premultiply(rotateMap)
.premultiply(translateCenter)
.premultiply(scale);
pickScene.matrix = new THREE.Matrix4();
pickScene.matrix
.premultiply(rotateMap)
.premultiply(translateCenter)
.premultiply(scale);
}
makePerspectiveMatrix(fovy, aspect, near, far) {
const out = new THREE.Matrix4();
const f = 1.0 / Math.tan(fovy / 2),
nf = 1 / (near - far);
const newMatrix = [
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, (far + near) * nf, -1,
0, 0, (2 * far * near) * nf, 0
];
out.elements = newMatrix;
return out;
}
projectFlat(lnglat) {
return this.map.lngLatToGeodeticCoord(lnglat);
}
getCenter() {
return this.map.getCenter();
}
getCenterFlat() {
return this.projectFlat(this.getCenter());
}
addOverLayer() {
const canvasContainer = document.getElementById(this.container);
this.canvasContainer = canvasContainer;
this.renderDom = document.createElement('div');
this.renderDom.style.cssText += 'position: absolute;top: 0; z-index:10;height: 100%;width: 100%;pointer-events: none;';
this.renderDom.id = 'l7_canvaslayer';
canvasContainer.appendChild(this.renderDom);
}
mixMap(scene) {
const map = this.map;
scene.getZoom = () => { return map.getZoom(); };
scene.getCenter = () => { return map.getCenter(); };
scene.getPitch = () => { return map.getPitch(); };
scene.containerToLngLat = point => { return map.unproject(point); };
}
}

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,45 +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;
turfMeta.flattenEach(data, (currentFeature, featureIndex/* , multiFeatureIndex*/) => {
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';
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,41 @@
import { hexbin } from 'd3-hexbin';
import { aProjectFlat, unProjectFlat } from '../../geo/project';
import * as statistics from './statistics';
const R_EARTH = 6378000;
export function pointToHexbin(data, option) {
const dataArray = data.dataArray;
const { size = 10 } = option;
const pixlSize = size / (2 * Math.PI * R_EARTH) * (256 << 20) / 2;
const screenPoints = dataArray.map(point => {
const { x, y } = aProjectFlat(point.coordinates);
return {
...point,
coordinates: [ x, y ]
};
});
const newHexbin = hexbin()
.radius(pixlSize)
.x(d => d.coordinates[0])
.y(d => d.coordinates[1]);
const hexbinBins = newHexbin(screenPoints);
const result = {
size: pixlSize
};
result.dataArray = hexbinBins.map((hex, index) => {
if (option.field && option.method) {
const columns = getColumn(hex, option.field);
hex[option.method] = statistics[option.method](columns);
}
return {
coordinates: unProjectFlat([ hex.x, hex.y ]),
id: index + 1
};
});
return result;
}
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';

1490
test/asset/data/point.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
import { expect } from 'chai';
import { pointData } from '../../../asset/data/point';
import { pointToHexbin } from '../../../../src/source/transform/hexagon';
describe('hexagon Test', function() {
it('pointToHexbin', function() {
const dataArray = pointData.map(item => {
const lng = 1e-6 * (250 * item.grid_x + 125),
lat = 1e-6 * (250 * item.grid_y + 125);
return {
v: item.count * 1,
coordinates: [ lng, lat ]
};
});
const data = {
dataArray
};
const hexgonGrid = pointToHexbin(data, { size: 100, field: 'count', method: 'sum' });
});
});