refactor(src) file name

This commit is contained in:
thinkinggis 2019-06-26 14:30:29 +08:00
commit 629d388520
240 changed files with 8993 additions and 4486 deletions

View File

@ -8,9 +8,7 @@ node_modules/
demos/assets/
demos/index.html
demos/*
<<<<<<< HEAD
rollup/*
webpack/*
=======
src/core/three.js
>>>>>>> master
testdemo/*

3
.gitignore vendored
View File

@ -74,4 +74,5 @@ demos/data
demos/image
.vscode
demos/hexagon.html
demos/model
demos/model
testdemo

View File

@ -16,6 +16,6 @@ $ npm run dev
$ npm run demos
```
visit online demo
http://site.alipay.net/datavis/L7/demos/index.html
https://antv.alipay.com/zh-cn/l7/1.x/demo/index.html
## How to Contribute

106
demos/01_animatePoint.html Normal file
View File

@ -0,0 +1,106 @@
<!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>
var radius = 0.2;
function pointOnCircle(angle) {
return {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
120.19382669582967 + Math.cos(angle) * radius,
30.258134 + Math.sin(angle) * radius
]
}
}
]
}
}
console.log(pointOnCircle(10))
const scene = new L7.Scene({
id: 'map',
mapStyle: 'light', // 样式URL
center: [ 120.19382669582967, 30.258134 ],
pitch: 0,
zoom: 2,
maxZoom:20,
hash:true,
minZoom:0,
});
window.scene = scene;
scene.on('loaded', () => {
const circleLayer = scene.PointLayer({
zIndex: 0,
})
.source(pointOnCircle(0))
.shape('circle')
.size(10) // default 1
.active(true)
.color('#2894E0')
.style({
stroke: 'rgb(255,255,255)',
strokeWidth: 1,
opacity: 0.8
})
.render();
function animateMarker(timestamp) {
circleLayer.setData(pointOnCircle(timestamp / 1000));
requestAnimationFrame(animateMarker);
}
//animateMarker(0);
/**
const layerText = scene.PointLayer({
zIndex: 3
})
.source(circleLayer.layerSource)
.shape('point_count', 'text')
.active(true)
.size('point_count', [ 0, 16]) // default 1
.color('#f00')
.style({
stroke: '#999',
strokeWidth: 0,
opacity: 1.0
})
.render();
console.log(layerText);
});
**/
});
</script>
</body>
</html>

View File

@ -24,55 +24,79 @@
<script src="../build/L7.js"></script>
<script>
const colorObj ={
blue: ["#E8FCFF", "#CFF6FF", "#A1E9ff", "#65CEF7", "#3CB1F0", "#2894E0", "#1772c2", "#105CB3", "#0D408C", "#002466"].reverse(),
blue: ["#E8FCFF", "#CFF6FF", "#A1E9ff", "#65CEF7", "#3CB1F0", "#2894E0", "#1772c2", "#105CB3", "#0D408C", "#002466"],
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()
}
var mapinstance = new AMap.Map('map',{
center: [ 120.19382669582967, 30.258134 ],
viewMode: '3D',
pitch: 0,
zoom: 12,
maxZoom:20,
minZoom:0,
});
const scene = new L7.Scene({
id: 'map',
mapStyle: 'light', // 样式URL
mapStyle: 'dark', // 样式URL
map:mapinstance,
center: [ 120.19382669582967, 30.258134 ],
pitch: 0,
zoom: 12,
maxZoom:14,
minZoom:11,
maxZoom:20,
minZoom:0,
});
window.scene = scene;
scene.on('loaded', () => {
$.get('https://gw.alipayobjects.com/os/rmsportal/epnZEheZeDgsiSjSPcCv.json', data => {
const circleLayer = scene.PointLayer({
zIndex: 2
zIndex: 0,
})
.source(data,{
scale:{
min:0,
max:1000,
type:'linear'
}
isCluster:true
})
.shape('2d:circle')
.size('value', [ 2, 80]) // default 1
// .shape('circle')
.shape('point_count', [ 'circle', 'triangle', 'hexagon' ])
// .shape('triangle')
// .shape('square')
// .shape('hexagon')
// .shape('octogon')
// .shape('hexagram')
// .shape('pentagon')
.size('point_count', [ 5, 40]) // default 1
//.size('value', [ 10, 300]) // default 1
.active(true)
.filter('value', field_8 => {
return field_8 * 1 > 500;
})
.color('type', colorObj.blue)
.color('point_count',["#002466","#105CB3","#2894E0","#CFF6FF","#FFF5B8","#FFAB5C","#F27049","#730D1C"])
.style({
stroke: 'rgb(255,255,255)',
strokeWidth: 1,
opacity: 1.
strokeWidth: 2,
opacity: 1
})
.render();
circleLayer.on('click',(e)=>{
console.log(e);
})
window.circleLayer = circleLayer;
const layerText = scene.PointLayer({
zIndex: 3
})
.source(circleLayer.layerSource)
.shape('point_count', 'text')
.active(false)
.filter('point_count',(p)=>{
return p > 50
})
.size('point_count', [ 5, 20]) // default 1
.color('#fff')
.style({
stroke: '#999',
strokeWidth: 1,
opacity: 1.0
})
.render();
console.log(layerText);
});
});

View File

@ -30,7 +30,7 @@ const scene = new L7.Scene({
});
scene.on('loaded', () => {
$.get('https://gw.alipayobjects.com/os/rmsportal/oVTMqfzuuRFKiDwhPSFL.json', data => {
scene.PointLayer({
window.layer = scene.PointLayer({
zIndex: 2
})
.source(data.list, {

99
demos/02_animateline.html Normal file
View File

@ -0,0 +1,99 @@
<!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>animateLine</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>
var geojson = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[0, 0],
[0.001, 0.0001]
]
}
} ]
};
var speedFactor = 30; // number of frames per longitude degree
var animation; // to store and cancel the animation
var startTime = 0;
var progress = 0; // progress = timestamp - startTime
var resetTime = false; // indicator of whether time reset is needed for the animation
var pauseButton = document.getElementById('pause');
const scene = new L7.Scene({
id: 'map',
mapStyle: 'light', // 样式URL
center: [ 120.19382669582967, 30.258134 ],
pitch: 0,
zoom: 2,
maxZoom:20,
minZoom:0,
});
scene.on('loaded', () => {
const linelayer = scene.LineLayer({
zIndex: 2
})
.source(geojson)
.size([2,1])
.shape('line')
.color( "#2894E0")
.render();
startTime = performance.now();
animateLine(0);
function animateLine(timestamp) {
if (resetTime) {
// resume previous progress
startTime = performance.now() - progress;
resetTime = false;
} else {
progress = timestamp - startTime;
}
// restart if it finishes a loop
if (progress > speedFactor * 360) {
startTime = timestamp;
geojson.features[0].geometry.coordinates = [
[0, 0],
[0.001, 0.0001]
];
} else {
var x = progress / speedFactor;
// draw a sine wave with some math.
var y = Math.sin(x * Math.PI / 90) * 40;
// append new coordinates to the lineString
geojson.features[0].geometry.coordinates.push([x, y]);
// then update the map
linelayer.setData(geojson);
}
// Request the next frame of the animation.
animation = requestAnimationFrame(animateLine);
}
});
</script>
</body>
</html>

View File

@ -30,8 +30,7 @@ const scene = new L7.Scene({
zoom: 14.82,
});
scene.on('loaded', () => {
$.get('./data/contour.geojson', data => {
// data.features = data.features.slice(0,1);
$.get('https://gw.alipayobjects.com/os/rmsportal/ZVfOvhVCzwBkISNsuKCc.json', data => {
scene.LineLayer({
zIndex: 2
})

View File

@ -59,6 +59,7 @@ $.getJSON('https://gw.alipayobjects.com/os/rmsportal/kwUdcXnxQtexeGRvTGtA.json',
})
.render();
});
$.getJSON('https://gw.alipayobjects.com/os/rmsportal/dzpMOiLYBKxpdmsgBLoE.json', contourData => {
const landlayer = scene.LineLayer(
{zIndex:2}
@ -72,6 +73,7 @@ $.getJSON('https://gw.alipayobjects.com/os/rmsportal/dzpMOiLYBKxpdmsgBLoE.json',
})
.render();
});
$.getJSON('https://gw.alipayobjects.com/os/rmsportal/opYqFyDGyGUAUXkLUhBV.json', city => {
var makerLayer = scene.PointLayer({
zIndex: 4
@ -81,7 +83,6 @@ $.getJSON('https://gw.alipayobjects.com/os/rmsportal/opYqFyDGyGUAUXkLUhBV.json',
.shape('image:local')
.color('#0D408C')
.render();
var makerText = scene.PointLayer({
zIndex: 8,
minZoom:5,
@ -96,6 +97,7 @@ $.getJSON('https://gw.alipayobjects.com/os/rmsportal/opYqFyDGyGUAUXkLUhBV.json',
strokeWidth: 4,
})
.render();
return
})
})

View File

@ -94,9 +94,9 @@ scene.on('loaded', () => {
.style({
opacity: 1.0
})
.render();
//.render();
console.log(citylayer);
citylayer.on('click',(e)=>{
$("#info").css({'left': e.pixel.x,'top':e.pixel.y, display:'block'});
$("#info").html(`<p>${e.feature.properties.area || e.feature.properties.name }<span">${e.feature.properties.pm2_5_24h || 0}</span></p>`);

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>hexagon demo</title>
<style>
body {margin: 0;}
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
</head>
<body>
<div id="map"></div>
<div id ="info" class ="tooltip" style="display:none"></div>
<div class='info-panel top-right'>
<p>
<label>min</label><input name="minaqi" type="range" step="1" min="0" max="200" value=0> <label>0</label>
</p>
<p>
<label>max</label><input name="maxaqi" type="range" step="1" min="0" max="300" value=300><label>300</label>
</p>
<p><label>color</label><select>
<option value ="default">default</option>
<option value ="blue">blue</option>
<option value ="red">red</option>
<option value="orange">orange</option>
<option value="green">green</option>
<option value="yellow">yellow</option>
<option value="purple">purple</option>
</select> </p>
<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", "#98E3FA", "#65CEF7", "#3CB4F0", "#2894E0", "#1A76C7", "#105CB3", "#0D408C", "#002466"],
red: ["#FFF4F2", "#FFDFDB", "#FAADAA", "#F77472", "#F04850", "#D63147", "#BD223E", "#A81642", "#820C37", "#5C0023"],
orange:["#FFF7EB", "#FFECD4", "#FAD09D", "#F7B16A", "#F08D41", "#DB6C2C", "#C2491D", "#AD2B11", "#871D0C", "#610800"],
green:["#FAFFF0", "#EBF7D2", "#C8E695", "#A5D660", "#7DC238", "#59A616", "#3F8C0B", "#237804", "#125200", "#082B00"],
yellow:["#FFFFE8", "#FFFECC", "#FAF896", "#F7E463", "#F0CE3A", "#DBB125", "#C29117", "#AD7410", "#87500C", "#613000"],
purple:["#FCF2FF", "#F5DEFF", "#DDB3F2", "#BE7BE3", "#9B4ECF", "#7737B3", "#5B2899", "#411C85", "#270F5E", "#100338"],
}
const scene = new L7.Scene({
id: 'map',
mapStyle: 'dark', // 样式URL
center: [104.838088,34.075889 ],
pitch: 0,
zoom: 4.5,
});
window.scene = scene;
scene.on('loaded', () => {
var colors = ["#FFF5B8","#FFDC7D","#FFAB5C","#F27049","#D42F31","#730D1C"];
$.getJSON('https://gw.alipayobjects.com/os/basement_prod/77497aa8-8dd0-4a0c-bf3b-3bb55c5d453c.json', city => {
const citylayer = scene.PolygonLayer()
.source(city)
.color('#F27049')
.shape('fill')
.active(true)
.style({
opacity: 0.8
})
.render();
const citylayer2 = scene.PolygonLayer()
.source(city)
.shape('line')
.color('#fff')
.style({
opacity: 1.0
})
.render();
console.log(citylayer);
});
});
</script>
</body>
</html>

View File

@ -26,9 +26,9 @@ const scene = new L7.Scene({
id: 'map',
viewMode: '3D',
mapStyle: 'amap://styles/ba3e9759545cd618392ef073c0dfda8c?isPublic=true', // 样式URL
center: [ 110.770672, 34.159869 ],
center: [ 110.770672, 44.159869 ],
pitch: 0,
zoom: 4
zoom: 3
});
scene.on('loaded', () => {
@ -42,10 +42,9 @@ scene.on('loaded', () => {
const blob = this.response;
const tiff = GeoTIFF.parse(blob);
const image = tiff.getImage();
const values = image.readRasters()[0];
const values = image.readRasters()[0].values();
const m = image.getHeight();
const n = image.getWidth();
const layer = scene.RasterLayer({ zIndex: 2 }).
source(values, {
parser: {
@ -64,11 +63,7 @@ scene.on('loaded', () => {
}
})
.render();
setTimeout(()=>{
layer.style({
rampColors: 'viridis',
}).render();
},3000)
console.log(layer);
}
};

View File

@ -25,22 +25,23 @@ const scene = new L7.Scene({
mapStyle: 'dark', // 样式URL
center: [ 120.19382669582967, 30.258134 ],
pitch: 0,
zoom: 3
zoom: 1
});
window.scene = scene;
scene.on('loaded', () => {
$.get('./data/provincePoint.geojson', data => {
$.get('./data/provincePoint.json', data => {
scene.PointLayer({
zIndex: 2
})
.source(data)
.shape('name', 'text')
.active(true)
.size(12) // default 1
.color('#fff')
.color('name')
.style({
stroke: '#999',
strokeWidth: 2,
opacity: 0.85
strokeWidth: 0,
opacity: 1.0
})
.render();
});

View File

@ -32,35 +32,35 @@ const scene = new L7.Scene({
});
window.scene = scene;
scene.on('loaded', () => {
$.get('https://gw.alipayobjects.com/os/rmsportal/XHMbjQwrSrajvLLvMPbK.json', data => {
scene.PolygonLayer({
zIndex: 0
})
.source(data)
.shape('fill')
.active({fill:'blue'})
.color('rgb(79,174,234)')
.render();
});
$.get('https://gw.alipayobjects.com/os/rmsportal/VifgwJEyBIXnDrjCwWdK.json', data => {
scene.PolygonLayer({
zIndex: 0
})
.source(data)
.shape('fill')
.color('rgb(156,194,116)')
.render();
});
$.get('https://gw.alipayobjects.com/os/rmsportal/ZseLNWMOPGrgqQYfvtli.json', data => {
scene.LineLayer({
zIndex: 2
})
.source(data)
.shape('line')
.size([3,0])
.color('rgb(79,174,234)')
.render();
});
// $.get('https://gw.alipayobjects.com/os/rmsportal/XHMbjQwrSrajvLLvMPbK.json', data => {
// scene.PolygonLayer({
// zIndex: 0
// })
// .source(data)
// .shape('fill')
// .active({fill:'blue'})
// .color('rgb(79,174,234)')
// .render();
// });
// $.get('https://gw.alipayobjects.com/os/rmsportal/VifgwJEyBIXnDrjCwWdK.json', data => {
// scene.PolygonLayer({
// zIndex: 0
// })
// .source(data)
// .shape('fill')
// .color('rgb(156,194,116)')
// .render();
// });
// $.get('https://gw.alipayobjects.com/os/rmsportal/ZseLNWMOPGrgqQYfvtli.json', data => {
// scene.LineLayer({
// zIndex: 2
// })
// .source(data)
// .shape('line')
// .size([3,0])
// .color('rgb(79,174,234)')
// .render();
// });
$.get('https://gw.alipayobjects.com/os/rmsportal/ggFwDClGjjvpSMBIrcEx.json', data => {
citylayer = scene.PolygonLayer({
@ -69,6 +69,24 @@ scene.on('loaded', () => {
.source(data)
.shape('extrude')
.active({fill:'red'})
.style({
lights: [
{
type: 'directional',
direction: [ 1, 10.5, 12 ],
ambient: [ 0.2, 0.2, 0.2 ],
diffuse: 'red',
specular: [ 0.1, 0.1, 0.1 ]
},
{
type: 'directional',
direction: [ 1, -10.5, 12 ],
ambient: [ 0.2, 0.2, 0.2 ],
diffuse: 'green',
specular: [ 0.1, 0.1, 0.1 ]
},
]
})
.size('floor',[10,2000])
.color('rgba(242,246,250,0.96)')
.render();

View File

@ -41,7 +41,7 @@ scene.on('loaded', () => {
features.push(greatCircle);
}
var fc = turf.featureCollection(features);
scene.LineLayer({
const layer = scene.LineLayer({
zIndex: 2
})
.source(fc)
@ -51,6 +51,7 @@ scene.on('loaded', () => {
})
//.animate({enable:true})
.render();
console.log(layer);
/**
scene.LineLayer({
zIndex: 2

173
demos/assets/color-hash.js Normal file
View File

@ -0,0 +1,173 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ColorHash = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/**
* BKDR Hash (modified version)
*
* @param {String} str string to hash
* @returns {Number}
*/
var BKDRHash = function(str) {
var seed = 131;
var seed2 = 137;
var hash = 0;
// make hash more sensitive for short string like 'a', 'b', 'c'
str += 'x';
// Note: Number.MAX_SAFE_INTEGER equals 9007199254740991
var MAX_SAFE_INTEGER = parseInt(9007199254740991 / seed2);
for(var i = 0; i < str.length; i++) {
if(hash > MAX_SAFE_INTEGER) {
hash = parseInt(hash / seed2);
}
hash = hash * seed + str.charCodeAt(i);
}
return hash;
};
module.exports = BKDRHash;
},{}],2:[function(require,module,exports){
var BKDRHash = require('./bkdr-hash');
/**
* Convert RGB Array to HEX
*
* @param {Array} RGBArray - [R, G, B]
* @returns {String} 6 digits hex starting with #
*/
var RGB2HEX = function(RGBArray) {
var hex = '#';
RGBArray.forEach(function(value) {
if (value < 16) {
hex += 0;
}
hex += value.toString(16);
});
return hex;
};
/**
* Convert HSL to RGB
*
* @see {@link http://zh.wikipedia.org/wiki/HSL和HSV色彩空间} for further information.
* @param {Number} H Hue [0, 360)
* @param {Number} S Saturation [0, 1]
* @param {Number} L Lightness [0, 1]
* @returns {Array} R, G, B [0, 255]
*/
var HSL2RGB = function(H, S, L) {
H /= 360;
var q = L < 0.5 ? L * (1 + S) : L + S - L * S;
var p = 2 * L - q;
return [H + 1/3, H, H - 1/3].map(function(color) {
if(color < 0) {
color++;
}
if(color > 1) {
color--;
}
if(color < 1/6) {
color = p + (q - p) * 6 * color;
} else if(color < 0.5) {
color = q;
} else if(color < 2/3) {
color = p + (q - p) * 6 * (2/3 - color);
} else {
color = p;
}
return Math.round(color * 255);
});
};
function isArray(o) {
return Object.prototype.toString.call(o) === '[object Array]';
}
/**
* Color Hash Class
*
* @class
*/
var ColorHash = function(options) {
options = options || {};
var LS = [options.lightness, options.saturation].map(function(param) {
param = param || [0.35, 0.5, 0.65]; // note that 3 is a prime
return isArray(param) ? param.concat() : [param];
});
this.L = LS[0];
this.S = LS[1];
if (typeof options.hue === 'number') {
options.hue = {min: options.hue, max: options.hue};
}
if (typeof options.hue === 'object' && !isArray(options.hue)) {
options.hue = [options.hue];
}
if (typeof options.hue === 'undefined') {
options.hue = [];
}
this.hueRanges = options.hue.map(function (range) {
return {
min: typeof range.min === 'undefined' ? 0 : range.min,
max: typeof range.max === 'undefined' ? 360: range.max
};
});
this.hash = options.hash || BKDRHash;
};
/**
* Returns the hash in [h, s, l].
* Note that H [0, 360); S [0, 1]; L [0, 1];
*
* @param {String} str string to hash
* @returns {Array} [h, s, l]
*/
ColorHash.prototype.hsl = function(str) {
var H, S, L;
var hash = this.hash(str);
if (this.hueRanges.length) {
var range = this.hueRanges[hash % this.hueRanges.length];
var hueResolution = 727; // note that 727 is a prime
H = ((hash / this.hueRanges.length) % hueResolution) * (range.max - range.min) / hueResolution + range.min;
} else {
H = hash % 359; // note that 359 is a prime
}
hash = parseInt(hash / 360);
S = this.S[hash % this.S.length];
hash = parseInt(hash / this.S.length);
L = this.L[hash % this.L.length];
return [H, S, L];
};
/**
* Returns the hash in [r, g, b].
* Note that R, G, B [0, 255]
*
* @param {String} str string to hash
* @returns {Array} [r, g, b]
*/
ColorHash.prototype.rgb = function(str) {
var hsl = this.hsl(str);
return HSL2RGB.apply(this, hsl);
};
/**
* Returns the hash in hex
*
* @param {String} str string to hash
* @returns {String} hex with #
*/
ColorHash.prototype.hex = function(str) {
var rgb = this.rgb(str);
return RGB2HEX(rgb);
};
module.exports = ColorHash;
},{"./bkdr-hash":1}]},{},[2])(2)
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

@ -6,7 +6,7 @@
<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>
<title>dashline demo</title>
<style>
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
@ -20,38 +20,36 @@
<script src="../build/L7.js"></script>
<script>
const color1 = [ 'rgba(37, 140, 249, 0.8)', 'rgba(14, 241, 242, 0.8)', 'rgba(255, 255, 255, 0.8)' ];
const scene = new L7.Scene({
id: 'map',
mapStyle: 'dark', // 样式URL
center: [104.838088,34.075889 ],
pitch: 0,
zoom: 9,
minZoom: 8,
rotation:0
center: [ 102.602992, 23.107329],
pitch: 15,
zoom: 14.82,
});
scene.on('loaded', () => {
scene.image.addImage('local', 'https://gw.alipayobjects.com/zos/rmsportal/xZXhTxbglnuTmZEwqQrE.png');
$.get('https://gw.alipayobjects.com/os/rmsportal/oVTMqfzuuRFKiDwhPSFL.json', data => {
scene.PointLayer({
$.get('https://gw.alipayobjects.com/os/rmsportal/ZVfOvhVCzwBkISNsuKCc.json', data => {
scene.LineLayer({
zIndex: 2
})
.source(data.list, {
type: 'array',
x: 'j',
y: 'w',
.source(data)
.size('ELEV',(value)=>{
return [2,(value-1000)*7];
})
.shape('m','text')
.size(20)
.color('#fff')
.active(true)
.shape('line')
.style({
stroke:'#fff',
strokeWidth:1.0,
opacity:1.0,
lineType: 'dash',
dashArray: 200,
dashOffset: 0.2,
dashRatio: 0.5
})
.color('ELEV',["#E8FCFF", "#CFF6FF", "#A1E9ff", "#65CEF7", "#3CB1F0", "#2894E0", "#1772c2", "#105CB3", "#0D408C", "#002466"].reverse())
.render();
});
});
</script>
</body>
</html>
</html>

View File

@ -29,7 +29,7 @@ const scene = new L7.Scene({
});
scene.on('loaded', () => {
$.get('https://gw.alipayobjects.com/os/basement_prod/7359a5e9-3c5e-453f-b207-bc892fb23b84.csv', data => {
var layer = scene.HeatMapLayer({
var layer = scene.HeatmapLayer({
zIndex: 2
})
.source(data, {
@ -59,7 +59,8 @@ scene.on('loaded', () => {
}
]
})
.shape('gird')
.shape('grid')
.active({fill:'red'})
.style({
coverage: 0.8
})

60
demos/heatmap.html Normal file
View File

@ -0,0 +1,60 @@
<!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>heatmap</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=f28fca5384129d180ad82915156a9baf&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: 'amap://styles/c9f1d10cae34f8ab05e425462c5a58d7', // 样式URL
center: [ -155, 60 ],
pitch: 0,
zoom: 4.5
});
window.scene = scene;
scene.on('loaded', () => {
$.get('https://gw.alipayobjects.com/os/basement_prod/08c6ea00-dc5f-4bb0-b0b5-52bde5edf0a3.json', data => {
scene.HeatmapLayer({
zIndex: 2
})
.source(data)
.size('mag', [ 0, 1 ]) // weight映射通道
.style({
intensity: 10,
radius: 10,
opacity:1,
rampColors: {
colors: [ '#ffda45ff', '#fde725ff', '#ffc934ff', '#ffb824ff', '#ffb824ff', '#fa8100ff' ],
positions: [ 0, 0.2, 0.4, 0.6, 0.9, 1.0 ]
}
})
.render();
/*scene.PointLayer({
zIndex: 2
})
.source(data)
.shape('2d:circle')
.size(2) // weight映射通道
.color('red')
.render();*/
});
});
</script>
</body>
</html>

View File

@ -29,7 +29,7 @@ const scene = new L7.Scene({
});
scene.on('loaded', () => {
$.get('https://gw.alipayobjects.com/os/basement_prod/7359a5e9-3c5e-453f-b207-bc892fb23b84.csv', data => {
var layer = scene.HeatMapLayer({
var layer = scene.HeatmapLayer({
zIndex: 2
})
.source(data, {

1905
demos/index.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -39,7 +39,7 @@ scene.on('loaded', () => {
//.color('#F08D41')
.color('#ff893a')
.animate({enable:true})
.render();
//.render();
});
$.get('https://gw.alipayobjects.com/os/rmsportal/vmvAxgsEwbpoSWbSYvix.json', data => {
buildLayer = scene.PolygonLayer({
@ -55,7 +55,23 @@ scene.on('loaded', () => {
baseColor:'rgb(16,16,16)',
windowColor:'rgb(30,60,89)',
//brightColor:'rgb(155,217,255)'
brightColor:'rgb(255,176,38)'
brightColor:'rgb(255,176,38)',
lights: [
{
type: 'directional',
direction: [ 1, 10.5, 12 ],
ambient: [ 0.2, 0.2, 0.2 ],
diffuse: 'red',
specular: [ 0.1, 0.1, 0.1 ]
},
{
type: 'directional',
direction: [ 1, -10.5, 12 ],
ambient: [ 0.2, 0.2, 0.2 ],
diffuse: 'green',
specular: [ 0.1, 0.1, 0.1 ]
},
]
})
.render();

65
demos/tile.html Normal file
View File

@ -0,0 +1,65 @@
<!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>hexagon demo</title>
<style>
body {margin: 0;}
#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: 'light', // 样式URL
center: [104.838088,34.075889 ],
pitch: 0,
hash:true,
zoom: 3,
});
window.scene = scene;
scene.on('loaded', () => {
scene.ImageTileLayer({
zIndex:4
})
.source('http://t1.tianditu.com/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk=174705aebfe31b79b3587279e211cb9a')
.render();
$.getJSON('https://gw.alipayobjects.com/os/rmsportal/JToMOWvicvJOISZFCkEI.json', city => {
const citylayer = scene.PolygonLayer(
{
zIndex:0
}
)
.source(city)
.active(false)
.color('pm2_5_24h',["#FFF5B8","#FFDC7D","#FFAB5C","#F27049","#D42F31","#730D1C"])
.shape('fill')
.style({
opacity: 1.0
})
.render();
});
});
</script>
</body>
</html>

83
demos/vectorTile.html Normal file
View File

@ -0,0 +1,83 @@
<!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>hexagon demo</title>
<style>
body {margin: 0;}
#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: [116.5909,39.9225 ],
pitch: 0,
hash:true,
zoom: 14,
});
window.scene = scene;
scene.on('loaded', () => {
const layer = scene.VectorTileLayer({
zIndex:0,
layerType:'point'
})
//.source('https://pre-lbs-show.taobao.com/gettile?x={x}&y={y}&z={z}&pipeId=pipe_vt_test')
// http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/thinkgis/tile/point/{z}/{x}/{y}.pbf
// https://mvt.amap.com/district/CHN2/8/203/105/4096?key=
.source('http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/thinkgis/tile/point2/{z}/{x}/{y}.pbf',{
parser:{
type: 'mvt',
sourceLayer:'layer',
// idField:'adcode',
maxZoom: 14,
minZoom: 13,
}
})
.scale({
total:{
min:0,
max:100000,
type:'log'
}
})
.shape('normal')
.size(1)
.active({fill:'red'})
.color('total', ['#d53e4f','#f46d43','#fdae61','#fee08b','#ffffbf','#e6f598','#abdda4','#66c2a5','#3288bd'].reverse())
//.color('#0D408C')
.style({
stroke: 'rgba(255,255,255,0.8)',
strokeWidth: 1,
strokeOpacity:0.6,
opacity: 1
})
.render(
);
layer.on('mousemove',(feature)=>{
console.log(feature);
})
console.log(layer);
});
</script>
</body>
</html>

View File

@ -0,0 +1,75 @@
<!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>hexagon demo</title>
<style>
body {margin: 0;}
#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/color-hash.js"></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: [116.5909,39.9225 ],
pitch: 0,
hash:true,
zoom: 4,
});
window.scene = scene;
var colorHash = new ColorHash();
scene.on('loaded', () => {
const layer = scene.VectorTileLayer({
zIndex:0,
layerType:'polygon'
})
.source('http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/thinkgis/tile/china/province/{z}/{x}/{y}.pbf',{
parser:{
type: 'mvt',
sourceLayer:'layer',
idField:'code',
maxZoom: 5,
}
})
.scale({
total:{
type:'linear',
min:0,
max:5000000
}
})
.shape('fill')
.size(2)
.active(false)
.color('total', ['#ffffe5','#fff7bc','#fee391','#fec44f','#fe9929','#ec7014','#cc4c02','#993404','#662506'])
.style({
opacity:1.0
})
.render();
});
</script>
</body>
</html>

74
demos/vectorheatMap.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">
<link rel="stylesheet" href="./assets/info.css">
<title>hexagon demo</title>
<style>
body {margin: 0;}
#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: 'light', // 样式URL
center: [116.5909,39.9225 ],
pitch: 0,
hash:true,
zoom: 16,
});
window.scene = scene;
scene.on('loaded', () => {
const layer = scene.VectorTileLayer({
zIndex:0,
layerType:'point'
})
.source('http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/thinkgis/tile/china/all_point/{z}/{x}/{y}.pbf',{
parser:{
type: 'mvt',
sourceLayer:'layer',
maxZoom:14,
}
})
.scale({
bc_grade:{
type:'cat',
values:[1, 2 ,3, 4]
},
open_mode:{
type:'cat',
values:['线上','线下','自助']
}
})
.active(false)
// 'circle', 'triangle', 'square', 'pentagon', 'hexagon', 'octogon', 'hexagram', 'rhombus', 'vesica'
.shape('open_mode', ['circle','hexagon','hexagram'])
.size('bc_grade', [2,15])
.color('bc_grade', ['#ffffcc','#d9f0a3','#addd8e','#78c679','#31a354','#006837'])
.style({
stroke: '#fff',
strokeWidth: 1.0,
strokeOpacity:1.,
})
.render(
);
});
</script>
</body>
</html>

View File

@ -1,6 +1,6 @@
{
"name": "@antv/l7",
"version": "1.0.3",
"version": "1.2.0-beta.0",
"description": "Large-scale WebGL-powered Geospatial Data Visualization",
"main": "build/l7.js",
"browser": "build/l7.js",
@ -21,9 +21,9 @@
"author": "https://github.com/orgs/antvis/people",
"license": "MIT",
"devDependencies": {
"@babel/cli": "~7.0.0",
"@babel/core": "~7.0.0",
"@babel/preset-env": "~7.1.0",
"@babel/cli": "^7.4.3",
"@babel/core": "~7.4.3",
"@babel/preset-env": "~7.4.3",
"babel-eslint": "~8.0.3",
"babel-loader": "~8.0.0",
"babel-plugin-transform-remove-strict-mode": "~0.0.2",
@ -48,6 +48,7 @@
"open": "~0.0.5",
"parseurl": "~1.3.2",
"pre-commit": "~1.2.2",
"rollup": "^1.16.2",
"rollup-plugin-buble": "^0.19.6",
"rollup-plugin-commonjs": "^9.2.1",
"rollup-plugin-node-resolve": "^4.0.1",
@ -58,11 +59,14 @@
"torchjs": "~2.1.0",
"uglify-js": "~3.1.10",
"webpack": "~4.29.6",
"webpack-cli": "^3.2.3",
"worker-loader": "^2.0.0"
},
"scripts": {
"build-dev": "rollup -c --environment BUILD:dev && npm run demos-web",
"watch-dev": "rollup -c --environment BUILD:dev --watch",
"build-dev": "rollup -c --environment BUILD:dev && npm run demos-web",
"watch-dev": "rollup -c --environment BUILD:dev --watch & npm run demos-web ",
"build-prod": "rollup -c --environment BUILD:production",
"build-prod-min": "rollup -c --environment BUILD:production,MINIFY:true",
"build": "webpack",
"build-lib": "babel src --out-dir lib",
"bundler": "electron ./bundler/app.js",
@ -83,8 +87,8 @@
"mkdir-dist": "node ./bin/mkdir-dist.js",
"prepublishOnly": "npm run build-lib && npm run dist",
"screenshot": "node ./bin/screenshot.js",
"start": "npm run dev",
"test": "torch --compile-opts ./.torch.compile.opts.js --compile --renderer --recursive test/unit",
"start": "npm run watch-dev",
"test": "torch --compile-opts ./.torch.compile.opts.js --compile --renderer --recursive test/unit",
"test-all": "npm run test && npm run test-bugs",
"test-bugs": "torch --compile --renderer --recursive test/bugs",
"test-bugs-live": "torch --compile --interactive --watch --recursive test/bugs",
@ -94,14 +98,17 @@
},
"pre-commit": {
"run": [
"lint"
"lint",
"test"
],
"silent": false
},
"dependencies": {
"@antv/g": "^3.1.3",
"@antv/util": "~1.2.5",
"@antv/geo-coord": "^1.0.8",
"@antv/util": "~2.0.1",
"@mapbox/tiny-sdf": "^1.1.0",
"@mapbox/vector-tile": "^1.3.1",
"@turf/bbox": "^6.0.1",
"@turf/clean-coords": "^6.0.1",
"@turf/invariant": "^6.1.2",
@ -111,10 +118,13 @@
"earcut": "^2.1.3",
"fecha": "^2.3.3",
"gl-matrix": "^2.4.1",
"gl-vec2": "^1.3.0",
"lodash": "^4.17.5",
"polyline-normals": "^2.0.2",
"pbf": "^3.2.0",
"polyline-miter-util": "^1.0.1",
"rbush": "^2.0.2",
"simple-statistics": "^7.0.1",
"supercluster": "^6.0.1",
"three": "^0.101.1",
"venn.js": "^0.2.20",
"viewport-mercator-project": "^5.2.0",

View File

@ -24,11 +24,11 @@ const config = [
indent: false,
chunkFileNames: 'shared.js'
},
experimentalCodeSplitting: true,
treeshake: production,
// experimentalCodeSplitting: true,
treeshake: false,
plugins: [
glsl(
[ './src/geom/shader/*.glsl', './src/core/engine/picking/*.glsl' ],
[ './src/geom/shader/*.glsl', './src/core/engine/picking/*.glsl', './src/geom/shader/**/*.glsl' ],
production
),
minified ? terser() : false,

View File

@ -84,6 +84,9 @@ const ColorUtil = {
const rgba = this.toRGB(str);
return rgba.map(v => v / 255);
},
colorArray2RGBA(arr) {
return `rgba(${arr[0] * 255},${arr[1] * 255},${arr[2] * 255},${arr[3]})`;
},
color2RGBA(str) {
return this.color2Arr(str);
},

View File

@ -42,10 +42,11 @@ class Size extends Base {
_scaling(scale, v) {
if (scale.type === 'identity') {
return v;
} else if (scale.type === 'linear') {
const percent = scale.scale(v);
return this.getLinearValue(percent);
}
const percent = scale.scale(v);
return this.getLinearValue(percent);
// else if (scale.type === 'linear') {
}
getLinearValue(percent) {

View File

@ -0,0 +1,227 @@
import TinySDF from '@mapbox/tiny-sdf';
import { buildMapping } from '../../util/font-util';
import * as THREE from '../../core/three';
import LRUCache from '../../util/lru-cache';
export const DEFAULT_CHAR_SET = getDefaultCharacterSet();
export const DEFAULT_FONT_FAMILY = 'sans-serif';
export const DEFAULT_FONT_WEIGHT = 'normal';
export const DEFAULT_FONT_SIZE = 24;
export const DEFAULT_BUFFER = 3;
export const DEFAULT_CUTOFF = 0.25;
export const DEFAULT_RADIUS = 8;
const MAX_CANVAS_WIDTH = 1024;
const BASELINE_SCALE = 0.9;
const HEIGHT_SCALE = 1.2;
const CACHE_LIMIT = 3;
const cache = new LRUCache(CACHE_LIMIT);
const VALID_PROPS = [
'fontFamily',
'fontWeight',
'characterSet',
'fontSize',
'sdf',
'buffer',
'cutoff',
'radius'
];
function getDefaultCharacterSet() {
const charSet = [];
for (let i = 32; i < 128; i++) {
charSet.push(String.fromCharCode(i));
}
return charSet;
}
function setTextStyle(ctx, fontFamily, fontSize, fontWeight) {
ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
ctx.fillStyle = '#000';
ctx.textBaseline = 'baseline';
ctx.textAlign = 'left';
}
function getNewChars(key, characterSet) {
const cachedFontAtlas = cache.get(key);
if (!cachedFontAtlas) {
return characterSet;
}
const newChars = [];
const cachedMapping = cachedFontAtlas.mapping;
let cachedCharSet = Object.keys(cachedMapping);
cachedCharSet = new Set(cachedCharSet);
let charSet = characterSet;
if (charSet instanceof Array) {
charSet = new Set(charSet);
}
charSet.forEach(char => {
if (!cachedCharSet.has(char)) {
newChars.push(char);
}
});
return newChars;
}
function populateAlphaChannel(alphaChannel, imageData) {
// populate distance value from tinySDF to image alpha channel
for (let i = 0; i < alphaChannel.length; i++) {
imageData.data[4 * i + 3] = alphaChannel[i];
}
}
export default class FontAtlasManager {
constructor() {
// font settings
this.props = {
fontFamily: DEFAULT_FONT_FAMILY,
fontWeight: DEFAULT_FONT_WEIGHT,
characterSet: DEFAULT_CHAR_SET,
fontSize: DEFAULT_FONT_SIZE,
buffer: DEFAULT_BUFFER,
// sdf only props
// https://github.com/mapbox/tiny-sdf
sdf: true,
cutoff: DEFAULT_CUTOFF,
radius: DEFAULT_RADIUS
};
// key is used for caching generated fontAtlas
this._key = null;
this._texture = new THREE.Texture();
}
get texture() {
return this._texture;
}
get mapping() {
const data = cache.get(this._key);
return data && data.mapping;
}
get scale() {
return HEIGHT_SCALE;
}
get fontAtlas() {
return this._fontAtlas;
}
setProps(props = {}) {
VALID_PROPS.forEach(prop => {
if (prop in props) {
this.props[prop] = props[prop];
}
});
// update cache key
const oldKey = this._key;
this._key = this._getKey();
const charSet = getNewChars(this._key, this.props.characterSet);
const cachedFontAtlas = cache.get(this._key);
// if a fontAtlas associated with the new settings is cached and
// there are no new chars
if (cachedFontAtlas && charSet.length === 0) {
// update texture with cached fontAtlas
if (this._key !== oldKey) {
this._updateTexture(cachedFontAtlas);
}
return;
}
// update fontAtlas with new settings
const fontAtlas = this._generateFontAtlas(this._key, charSet, cachedFontAtlas);
this._fontAtlas = fontAtlas;
this._updateTexture(fontAtlas);
// update cache
cache.set(this._key, fontAtlas);
}
_updateTexture({ data: canvas }) {
this._texture = new THREE.CanvasTexture(canvas);
this._texture.wrapS = THREE.ClampToEdgeWrapping;
this._texture.wrapT = THREE.ClampToEdgeWrapping;
this._texture.minFilter = THREE.LinearFilter;
this._texture.flipY = false;
this._texture.needUpdate = true;
}
_generateFontAtlas(key, characterSet, cachedFontAtlas) {
const { fontFamily, fontWeight, fontSize, buffer, sdf, radius, cutoff } = this.props;
let canvas = cachedFontAtlas && cachedFontAtlas.data;
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = MAX_CANVAS_WIDTH;
}
const ctx = canvas.getContext('2d');
setTextStyle(ctx, fontFamily, fontSize, fontWeight);
// 1. build mapping
const { mapping, canvasHeight, xOffset, yOffset } = buildMapping(
Object.assign(
{
getFontWidth: char => ctx.measureText(char).width,
fontHeight: fontSize * HEIGHT_SCALE,
buffer,
characterSet,
maxCanvasWidth: MAX_CANVAS_WIDTH
},
cachedFontAtlas && {
mapping: cachedFontAtlas.mapping,
xOffset: cachedFontAtlas.xOffset,
yOffset: cachedFontAtlas.yOffset
}
)
);
// 2. update canvas
// copy old canvas data to new canvas only when height changed
if (canvas.height !== canvasHeight) {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
canvas.height = canvasHeight;
ctx.putImageData(imageData, 0, 0);
}
setTextStyle(ctx, fontFamily, fontSize, fontWeight);
// 3. layout characters
if (sdf) {
const tinySDF = new TinySDF(fontSize, buffer, radius, cutoff, fontFamily, fontWeight);
// used to store distance values from tinySDF
// tinySDF.size equals `fontSize + buffer * 2`
const imageData = ctx.getImageData(0, 0, tinySDF.size, tinySDF.size);
for (const char of characterSet) {
populateAlphaChannel(tinySDF.draw(char), imageData);
ctx.putImageData(imageData, mapping[char].x - buffer, mapping[char].y - buffer);
}
} else {
for (const char of characterSet) {
ctx.fillText(char, mapping[char].x, mapping[char].y + fontSize * BASELINE_SCALE);
}
}
return {
xOffset,
yOffset,
mapping,
data: canvas,
width: canvas.width,
height: canvas.height
};
}
_getKey() {
const { fontFamily, fontWeight, fontSize, buffer, sdf, radius, cutoff } = this.props;
if (sdf) {
return `${fontFamily} ${fontWeight} ${fontSize} ${buffer} ${radius} ${cutoff}`;
}
return `${fontFamily} ${fontWeight} ${fontSize} ${buffer}`;
}
}

View File

@ -0,0 +1,52 @@
import { buildIconMaping } from '../../util/font-util';
import * as THREE from '../../../../core/three';
const BUFFER = 3;
const MAX_CANVAS_WIDTH = 1024;
export default class IconManager {
constructor() {
this._getIcon = null;
this._mapping = {};
this._autoPacking = false;
this.iconData = {};
this._canvas = document.createElement('canvas');
this._texture = new THREE.Texture(this._canvas);
this.ctx = this._canvas.getContext('2d');
}
getTexture() {
return this._texture;
}
_updateIconAtlas() {
this._canvas.width = MAX_CANVAS_WIDTH;
this._canvas.height = this._canvasHeigth;
for (const key in this.mapping) {
const icon = this.mapping[key];
const { x, y, image } = icon;
this.ctx.drawImage(image, x, y, this.imageWidth, this.imageWidth);
}
this.texture.magFilter = THREE.LinearFilter;
this.texture.minFilter = THREE.LinearFilter;
this.texture.needsUpdate = true;
}
addImage(id, opt) {
this._loadImage(opt).then(image => {
this.iconData.push({ id, image });
const { mapping, canvasHeight } = buildIconMaping(this.iconData, BUFFER, MAX_CANVAS_WIDTH);
this._mapping = mapping;
this._canvasHeigth = canvasHeight;
});
}
_loadImage(url) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
}

View File

@ -0,0 +1,57 @@
import Util from '../../util';
import { updateObjecteUniform } from '../../util/object3d-util';
export default class BufferController {
constructor(cfg) {
// defs 列定义
Util.assign(this, cfg);
if (!this.mesh) this.mesh = this.layer;
}
_updateColorAttributes() {
const filterData = this.mesh.layerData;
const colorKey = {};
for (let e = 0; e < filterData.length; e++) {
const item = filterData[e];
colorKey[item.id] = item.color;
}
this.layer._activeIds = null; // 清空选中元素xwxw
const colorAttr = this.mesh.mesh.geometry.attributes.a_color;
const pickAttr = this.mesh.mesh.geometry.attributes.pickingId;
pickAttr.array.forEach((id, index) => {
let newId = Math.abs(id);
let item = null;
let color = null;
if (this.mesh.layerSource.data.featureKeys) { // hash数据映射
newId = this.mesh.layerSource.data.featureKeys[newId].index;
item = filterData[newId];
color = colorKey[item.id];
} else {
item = filterData[newId - 1];
color = colorKey[newId];
}
if (item.hasOwnProperty('filter') && item.filter === false) {
colorAttr.array[index * 4 + 0] = 0;
colorAttr.array[index * 4 + 1] = 0;
colorAttr.array[index * 4 + 2] = 0;
colorAttr.array[index * 4 + 3] = 0;
pickAttr.array[index] = -id; // 通过Id数据过滤 id<0 不显示
} else {
colorAttr.array[index * 4 + 0] = color[0];
colorAttr.array[index * 4 + 1] = color[1];
colorAttr.array[index * 4 + 2] = color[2];
colorAttr.array[index * 4 + 3] = color[3];
pickAttr.array[index] = id;
}
});
colorAttr.needsUpdate = true;
pickAttr.needsUpdate = true;
}
_updateStyle(option) {
const newOption = { };
for (const key in option) {
newOption['u_' + key] = option[key];
}
updateObjecteUniform(this.mesh._object3D, newOption);
}
}

View File

@ -0,0 +1,34 @@
import Util from '../../util';
export default class EventContoller {
constructor(cfg) {
Util.assign(this, cfg);
}
_init() {
this.layer.scene.on('pick-' + this.layer.layerId, e => {
let { featureId, point2d, type } = e;
if (featureId < 0 && this._activeIds !== null) {
type = 'mouseleave';
}
this._activeIds = featureId;
// TODO 瓦片图层获取选中数据信息
const lnglat = this.layer.scene.containerToLngLat(point2d);
const { feature, style } = this.layer.getSelectFeature(featureId, lnglat);
// const style = this.layerData[featureId - 1];
const target = {
featureId,
feature,
style,
pixel: point2d,
type,
lnglat: { lng: lnglat.lng, lat: lnglat.lat }
};
if (featureId >= 0 || this._activeIds >= 0) { // 拾取到元素,或者离开元素
this.layer.emit(type, target);
}
});
}
_initMapEvent() {
}
}

View File

@ -1,4 +1,14 @@
import Scale from './scale';
import Mapping from './mapping';
import Picking from './pick';
import Interaction from './interaction';
import Event from './event';
import Buffer from './buffer';
export default {
Scale
Scale,
Mapping,
Picking,
Interaction,
Event,
Buffer
};

View File

@ -0,0 +1,39 @@
import Util from '../../util';
import { getInteraction } from '../../interaction/index';
export default class InteractionController {
constructor(cfg) {
// defs 列定义
Util.assign(this, cfg);
}
// interaction 方法
clearAllInteractions() {
const interactions = this.layer.get('interactions');
Util.each(interactions, (interaction, key) => {
interaction.destory();
delete interactions[key];
});
return this;
}
clearInteraction(type) {
const interactions = this.layer.get('interactions');
if (interactions[type]) {
interactions[type].destory();
delete interactions[type];
}
return this;
}
addInteraction(type, cfg = {}) {
cfg.layer = this.layer;
const Ctor = getInteraction(type);
const interaction = new Ctor(cfg);
this._setInteraction(type, interaction);
return this;
}
_setInteraction(type, interaction) {
const interactions = this.layer.get('interactions');
if (interactions[type]) {
interactions[type].destory();
}
interactions[type] = interaction;
}
}

View File

@ -1,3 +0,0 @@
export class layerControl {
}

View File

@ -1,24 +0,0 @@
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

@ -0,0 +1,188 @@
import Util from '../../util';
import Global from '../../global';
import ScaleController from './scale';
import Attr from '../../attr/index';
export default class Mapping {
/** mapping
* 初始化mapping
* @param {*} cfg 配置
* @param {*} cfg.layer layer对象
* @param {*} cfg.mesh mesh对象
*/
constructor(cfg) {
Util.assign(this, cfg);
if (!this.mesh) this.mesh = this.layer;
this._init();
}
_init() {
this._initControllers();
this._initTileAttrs();
this._mapping();
}
update() {
this.mesh.set('scales', {});
this._initTileAttrs();
this._updateMaping();
}
_initControllers() {
const scalesOption = this.layer.get('scaleOptions');
const scaleController = new ScaleController({
defs: {
...scalesOption
}
});
this.mesh.set('scaleController', scaleController);
}
_createScale(field) {
const scales = this.mesh.get('scales');
this._initControllers(); // scale更新
let scale = scales[field];
if (!scale) {
scale = this.createScale(field);
scales[field] = scale;
}
return scale;
}
createScale(field) {
const data = this.mesh.layerSource.data.dataArray;
const scales = this.mesh.get('scales');
let scale = scales[field];
const scaleController = this.mesh.get('scaleController');
if (!scale) {
scale = scaleController.createScale(field, data);
scales[field] = scale;
}
return scale;
}
// 获取属性映射的值
_getAttrValues(attr, record) {
const scales = attr.scales;
const params = [];
for (let i = 0; i < scales.length; i++) {
const scale = scales[i];
const field = scale.field;
if (scale.type === 'identity') {
params.push(scale.value);
} else {
params.push(record[field]);
}
}
const indexZoom = params.indexOf('zoom');
indexZoom !== -1 ? params[indexZoom] = attr.zoom : null;
const values = attr.mapping(...params);
return values;
}
_mapping() {
const attrs = this.mesh.get('attrs');
const mappedData = [];
const data = this.mesh.layerSource.data.dataArray;
for (let i = 0; i < data.length; i++) {
const record = data[i];
const newRecord = {};
newRecord.id = data[i]._id;
for (const k in attrs) {
if (attrs.hasOwnProperty(k)) {
const attr = attrs[k];
const names = attr.names;
const values = this._getAttrValues(attr, record);
if (names.length > 1) { // position 之类的生成多个字段的属性
for (let j = 0; j < values.length; j++) {
const val = values[j];
const name = names[j];
newRecord[name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
}
} else {
newRecord[names[0]] = values.length === 1 ? values[0] : values;
}
}
}
newRecord.coordinates = record.coordinates;
mappedData.push(newRecord);
}
// 通过透明度过滤数据
if (attrs.hasOwnProperty('filter')) {
mappedData.forEach(item => {
if (item.filter === false) {
(item.color[3] = 0);
item.id = -item.id;
}
});
}
this.mesh.layerData = mappedData;
}
/**
* 更新数据maping
* @param {*} layerSource 数据源
* @param {*} layer map
*/
_updateMaping() {
const attrs = this.mesh.get('attrs');
const data = this.mesh.layerSource.data.dataArray;
const layerData = this.mesh.layerData;
for (let i = 0; i < data.length; i++) {
const record = data[i];
for (const attrName in attrs) {
if (attrs.hasOwnProperty(attrName) && attrs[attrName].neadUpdate) {
const attr = attrs[attrName];
const names = attr.names;
const values = this._getAttrValues(attr, record);
if (names.length > 1) { // position 之类的生成多个字段的属性
for (let j = 0; j < values.length; j++) {
const val = values[j];
const name = names[j];
layerData[i][name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
}
} else {
layerData[i][names[0]] = values.length === 1 ? values[0] : values;
}
attr.neadUpdate = true;
}
}
}
}
_initTileAttrs() {
const attrOptions = this.layer.get('attrOptions');
for (const type in attrOptions) {
if (attrOptions.hasOwnProperty(type)) {
this._updateTileAttr(type);
}
}
}
_updateTileAttr(type) {
const self = this;
const attrs = this.mesh.get('attrs');
const attrOptions = this.layer.get('attrOptions');
const option = attrOptions[type];
option.neadUpdate = true;
const className = Util.upperFirst(type);
const fields = this._parseFields(option.field);
const scales = [];
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
const scale = self._createScale(field);
if (type === 'color' && Util.isNil(option.values)) { // 设置 color 的默认色值
option.values = Global.colors;
}
scales.push(scale);
}
option.scales = scales;
const attr = new Attr[className](option);
attrs[type] = attr;
}
_parseFields(field) {
if (Util.isArray(field)) {
return field;
}
if (Util.isString(field)) {
return field.split('*');
}
return [ field ];
}
}

View File

@ -0,0 +1,50 @@
import Util from '../../util';
import * as THREE from '../three';
import pickingFragmentShader from '../engine/picking/picking_frag.glsl';
import { updateObjecteUniform, destoryObject } from '../../util/object3d-util';
export default class PickContoller {
constructor(cfg) {
Util.assign(this, cfg);
this.pickObject3D = new THREE.Object3D();
this.addToPicking(this.pickObject3D);
}
getPickingId() {
return this.layer.scene._engine._picking.getNextId();
}
addToPicking(object) {
object.name = this.layer.layerId;
this.layer.scene._engine._picking.add(object);
}
removePickingObject(object) {
this.layer.scene._engine._picking.remove(object);
}
removePickingMesh(mesh) {
this.pickObject3D.remove(mesh);
destoryObject(mesh);
}
removePickMeshByName(name) {
for (let i = 0; i < this.pickObject3D.children.length; i++) {
if (this.pickObject3D.children[i].name === name) {
this.removePickingMesh(this.pickObject3D.children[i]);
}
}
}
removeAllMesh() {
this.pickObject3D.children.forEach(element => {
this.pickObject3D.remove(element);
destoryObject(element);
});
}
addPickMesh(mesh) {
const pickmaterial = mesh.material.clone();
pickmaterial.fragmentShader = pickingFragmentShader;
const pickingMesh = new THREE[mesh.type](mesh.geometry, pickmaterial);
pickingMesh.name = mesh.name;
pickingMesh.onBeforeRender = () => {
const zoom = this.layer.scene.getZoom();
updateObjecteUniform(pickingMesh, { u_zoom: zoom });
};
this.pickObject3D.add(pickingMesh);
}
}

149
src/core/engine/composer.js Executable file
View File

@ -0,0 +1,149 @@
// jscs:disable
/* eslint-disable */
import * as THREE from '../three';
import CopyShader from './copy-shader';
import ShaderPass from './shader-pass';
import MaskPass, {ClearMaskPass} from './mask-pass';
/**
* @author alteredq / http://alteredqualia.com/
*/
var EffectComposer = function ( renderer, renderTarget ) {
this.renderer = renderer;
if ( renderTarget === undefined ) {
var pixelRatio = renderer.getPixelRatio();
var width = Math.floor( renderer.context.canvas.width / pixelRatio ) || 1;
var height = Math.floor( renderer.context.canvas.height / pixelRatio ) || 1;
var parameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, stencilBuffer: false };
renderTarget = new THREE.WebGLRenderTarget( width, height, parameters );
}
this.renderTarget1 = renderTarget;
this.renderTarget2 = renderTarget.clone();
this.writeBuffer = this.renderTarget1;
this.readBuffer = this.renderTarget2;
this.passes = [];
if ( CopyShader === undefined )
console.error( "EffectComposer relies on THREE.CopyShader" );
this.copyPass = new ShaderPass( CopyShader );
};
EffectComposer.prototype = {
swapBuffers: function() {
var tmp = this.readBuffer;
this.readBuffer = this.writeBuffer;
this.writeBuffer = tmp;
},
visible:true,
type:'composer',
addPass: function ( pass ) {
this.passes.push( pass );
},
insertPass: function ( pass, index ) {
this.passes.splice( index, 0, pass );
},
render: function ( delta ) {
this.writeBuffer = this.renderTarget1;
this.readBuffer = this.renderTarget2;
var maskActive = false;
var pass, i, il = this.passes.length;
for ( i = 0; i < il; i ++ ) {
pass = this.passes[ i ];
if ( ! pass.enabled ) continue;
pass.render( this.renderer, this.writeBuffer, this.readBuffer, delta, maskActive );
if ( pass.needsSwap ) {
if ( maskActive ) {
var context = this.renderer.context;
context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff );
this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, delta );
context.stencilFunc( context.EQUAL, 1, 0xffffffff );
}
this.swapBuffers();
}
if ( pass instanceof MaskPass ) {
maskActive = true;
} else if ( pass instanceof ClearMaskPass ) {
maskActive = false;
}
}
},
reset: function ( renderTarget ) {
if ( renderTarget === undefined ) {
renderTarget = this.renderTarget1.clone();
var pixelRatio = this.renderer.getPixelRatio();
renderTarget.setSize(
Math.floor( this.renderer.context.canvas.width / pixelRatio ),
Math.floor( this.renderer.context.canvas.height / pixelRatio )
);
}
this.renderTarget1.dispose();
this.renderTarget1 = renderTarget;
this.renderTarget2.dispose();
this.renderTarget2 = renderTarget.clone();
this.writeBuffer = this.renderTarget1;
this.readBuffer = this.renderTarget2;
},
setSize: function ( width, height ) {
this.renderTarget1.setSize( width, height );
this.renderTarget2.setSize( width, height );
}
};
export default EffectComposer;

53
src/core/engine/copy-shader.js Executable file
View File

@ -0,0 +1,53 @@
// jscs:disable
/* eslint-disable */
import * as THREE from '../three';
/**
* @author alteredq / http://alteredqualia.com/
*
* Full-screen textured quad shader
*/
var CopyShader = {
uniforms: {
"tDiffuse": { type: "t", value: null },
"opacity": { type: "f", value: 1.0 }
},
vertexShader: [
"varying vec2 vUv;",
"void main() {",
"vUv = uv;",
"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
"}"
].join( "\n" ),
fragmentShader: [
"uniform float opacity;",
"uniform sampler2D tDiffuse;",
"varying vec2 vUv;",
"void main() {",
"vec4 texel = texture2D( tDiffuse, vUv );",
"gl_FragColor = opacity * texel;",
"}"
].join( "\n" )
};
export default CopyShader;

View File

@ -0,0 +1,21 @@
import EffectComposer from './composer';
export default function(renderer, container) {
const composer = new EffectComposer(renderer);
const updateSize = function() {
// TODO: Re-enable this when perf issues can be solved
//
// Rendering double the resolution of the screen can be really slow
// var pixelRatio = window.devicePixelRatio;
const pixelRatio = 1;
composer.setSize(container.clientWidth * pixelRatio, container.clientHeight * pixelRatio);
};
window.addEventListener('resize', updateSize, false);
updateSize();
return composer;
}

View File

@ -10,26 +10,31 @@ export default class Engine extends EventEmitter {
this._scene = Scene;
this._camera = new Camera(container).camera;
this._renderer = new Renderer(container).renderer;
this._world = world;
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._picking = Picking(this._world, this._renderer, this._camera);
this.clock = new THREE.Clock();
this.composerLayers = [];
}
_initPostProcessing() {
this.composerLayers.forEach(layer => {
layer.visible && layer.render();
});
}
update() {
this._renderer.clear();
this._renderer.render(this._scene, this._camera);
this._initPostProcessing();
}
destroy() {
}
// 渲染第三方Scene对象
renderScene(scene) {
this._renderer.render(scene, this._camera);
}
run() {
this.update();
this.engineID = requestAnimationFrame(this.run.bind(this));
}

94
src/core/engine/mask-pass.js Executable file
View File

@ -0,0 +1,94 @@
// jscs:disable
/* eslint-disable */
import * as THREE from '../three';
/**
* @author alteredq / http://alteredqualia.com/
*/
var MaskPass = function ( scene, camera ) {
this.scene = scene;
this.camera = camera;
this.enabled = true;
this.clear = true;
this.needsSwap = false;
this.inverse = false;
};
MaskPass.prototype = {
render: function ( renderer, writeBuffer, readBuffer, delta ) {
var context = renderer.context;
// don't update color or depth
context.colorMask( false, false, false, false );
context.depthMask( false );
// set up stencil
var writeValue, clearValue;
if ( this.inverse ) {
writeValue = 0;
clearValue = 1;
} else {
writeValue = 1;
clearValue = 0;
}
context.enable( context.STENCIL_TEST );
context.stencilOp( context.REPLACE, context.REPLACE, context.REPLACE );
context.stencilFunc( context.ALWAYS, writeValue, 0xffffffff );
context.clearStencil( clearValue );
// draw into the stencil buffer
renderer.render( this.scene, this.camera, readBuffer, this.clear );
renderer.render( this.scene, this.camera, writeBuffer, this.clear );
// re-enable update of color and depth
context.colorMask( true, true, true, true );
context.depthMask( true );
// only render where stencil is set to 1
context.stencilFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1
context.stencilOp( context.KEEP, context.KEEP, context.KEEP );
}
};
var ClearMaskPass = function () {
this.enabled = true;
};
ClearMaskPass.prototype = {
render: function ( renderer, writeBuffer, readBuffer, delta ) {
var context = renderer.context;
context.disable( context.STENCIL_TEST );
}
};
export default MaskPass;
export {ClearMaskPass as ClearMaskPass};

View File

@ -3,13 +3,10 @@ import * as THREE from '../../three';
let nextId = 1;
class Picking {
constructor(world, renderer, camera, scene) {
constructor(world, renderer, camera) {
this._world = world;
this._renderer = renderer;
this._camera = camera;
this._raycaster = new THREE.Raycaster();
this.scene = scene;
this._raycaster.linePrecision = 10;
this._pickingScene = PickingScene;
this.world = new THREE.Group();
this._pickingScene.add(this.world);
@ -49,13 +46,11 @@ class Picking {
this._height = size.height;
this._pickingTexture.setSize(this._width, this._height);
this._pixelBuffer = new Uint8Array(4 * this._width * this._height);
this._needUpdate = true;
}
_update(point) {
const texture = this._pickingTexture;
this._renderer.render(this._pickingScene, this._camera, this._pickingTexture);
this._renderer.render(this._pickingScene, this._camera, texture);
this.pixelBuffer = new Uint8Array(4);
this._renderer.readRenderTargetPixels(texture, point.x, this._height - point.y, 1, 1, this.pixelBuffer);
@ -66,8 +61,23 @@ class Picking {
index === id ? object.visible = true : object.visible = false;
});
}
_layerIsVisable(object) {
const layers = this._world.getLayers();
let isVisable = false;
for (let i = 0; i < layers.length; i++) {
const layer = layers[i];
if (object.name === layer.layerId) {
isVisable = layer.get('visible');
break;
}
}
return isVisable;
}
_pickAllObject(point, normalisedPoint) {
this.world.children.forEach((object, index) => {
if (!this._layerIsVisable(object)) {
return;
}
this._filterObject(index);
const item = this._pick(point, normalisedPoint, object.name);
item.type = point.type;
@ -76,9 +86,9 @@ class Picking {
});
}
_updateRender() {
this._renderer.render(this._pickingScene, this._camera, this._pickingTexture);
}
// _updateRender() {
// this._renderer.render(this._pickingScene, this._camera, this._pickingTexture);
// }
_pick(point, normalisedPoint, layerId) {
this._update(point);
@ -87,21 +97,11 @@ class Picking {
id = -999;
// return;
}
this._raycaster.setFromCamera(normalisedPoint, this._camera);
const intersects = this._raycaster.intersectObjects(this._pickingScene.children, true);
const _point2d = { x: point.x, y: point.y };
let _point3d;
if (intersects.length > 0) {
_point3d = intersects[0].point;
}
const item = {
layerId,
featureId: id - 1,
point2d: _point2d,
point3d: _point3d,
intersects
featureId: id,
point2d: _point2d
};
return item;
@ -135,8 +135,6 @@ class Picking {
this._world._container.removeEventListener(event[0], event[1], false);
});
this._world.off('move', this._onWorldMove);
if (this._pickingScene.children) {
// Remove everything else in the layer
let child;

View File

@ -1,13 +1,13 @@
import Material from '../../../geom/material/material';
import picking_frag from './picking_frag.glsl';
import picking_vert from './picking_vert.glsl';
// import picking_vert from './picking_vert.glsl';
export default function PickingMaterial(options) {
const material = new Material({
uniforms: {
u_zoom: { value: options.u_zoom || 1 }
},
vertexShader: picking_vert,
vertexShader: options.vs,
fragmentShader: picking_frag,
transparent: false
});

View File

@ -0,0 +1,58 @@
// jscs:disable
/* eslint-disable */
import * as THREE from '../three';
/**
* @author alteredq / http://alteredqualia.com/
*/
var RenderPass = function ( scene, camera, overrideMaterial, clearColor, clearAlpha ) {
this.scene = scene;
this.camera = camera;
this.overrideMaterial = overrideMaterial;
this.clearColor = clearColor;
this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 1;
this.oldClearColor = new THREE.Color();
this.oldClearAlpha = 1;
this.enabled = true;
this.clear = false;
this.needsSwap = false;
};
RenderPass.prototype = {
render: function ( renderer, writeBuffer, readBuffer, delta ) {
this.scene.overrideMaterial = this.overrideMaterial;
if ( this.clearColor ) {
this.oldClearColor.copy( renderer.getClearColor() );
this.oldClearAlpha = renderer.getClearAlpha();
renderer.setClearColor( this.clearColor, this.clearAlpha );
}
renderer.render( this.scene, this.camera, readBuffer, this.clear );
if ( this.clearColor ) {
renderer.setClearColor( this.oldClearColor, this.oldClearAlpha );
}
this.scene.overrideMaterial = null;
}
};
export default RenderPass;

View File

@ -9,7 +9,8 @@ export default class Renderer {
initRender() {
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
alpha: true,
autoClear: false
});
this.renderer.setClearColor(0xff0000, 0.0);
this.pixelRatio = window.devicePixelRatio;

View File

@ -1,41 +0,0 @@
import * as THREE from '../three';
export class RenderPass {
constructor(cfg) {
this.scene;
this.camera = cfg.camera;
this.renderer = cfg.renderer;
this.clearColor = cfg.clear.clearColor;
this.clearAlpha = cfg.clear.clearAlpha;
this._init(cfg);
}
_init() {
this.scene = new THREE.Scene();
const parameters = { minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat,
stencilBuffer: false,
depthBuffer: false
};
const size = this.renderer.getSize();
this.pass = new THREE.WebGLRenderTarget(size.width, size.height, parameters);
this.originClearColor = this.renderer.getClearColor();
this.originClearAlpha = this.renderer.getClearAlpha();
this.texture = this.pass.texture;
}
add(mesh) {
this.scene.add(mesh);
}
remove(mesh) {
this.scene.remove(mesh);
}
render() {
this.renderer.setClearColor(this.clearColor, this.clearAlpha);
this.renderer.render(this.scene, this.camera, this.pass, true); // this.pass,true
this.renderer.setClearColor(this.clearColor, this.clearAlpha);
}
}

View File

@ -0,0 +1,74 @@
// jscs:disable
/* eslint-disable */
import * as THREE from '../three';
/**
* @author alteredq / http://alteredqualia.com/
*/
var ShaderPass = function( shader, textureID ) {
this.textureID = ( textureID !== undefined ) ? textureID : "tDiffuse";
if ( shader instanceof THREE.ShaderMaterial ) {
this.uniforms = shader.uniforms;
this.material = shader;
}
else if ( shader ) {
this.uniforms = THREE.UniformsUtils.clone( shader.uniforms );
this.material = new THREE.ShaderMaterial( {
defines: shader.defines || {},
uniforms: this.uniforms,
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader
} );
}
this.renderToScreen = false;
this.enabled = true;
this.needsSwap = true;
this.clear = true;
this.camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
this.scene = new THREE.Scene();
this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null );
this.scene.add( this.quad );
};
ShaderPass.prototype = {
render: function( renderer, writeBuffer, readBuffer, delta ) {
if ( this.uniforms[ this.textureID ] ) {
this.uniforms[ this.textureID ].value = readBuffer.texture;
}
renderer.autoClear = false;
this.quad.material = this.material;
if ( this.renderToScreen ) {
renderer.render( this.scene, this.camera );
} else {
renderer.render( this.scene, this.camera, writeBuffer, this.clear );
}
renderer.autoClear = true;
}
};
export default ShaderPass;

View File

@ -28,14 +28,11 @@ export default class LoadImage extends EventEmitter {
if (typeof opt === 'string') {
getImage({ url: opt }, (err, img) => {
img.id = id;
this.images.push(img);
this.ctx.drawImage(img, x, y, this.imageWidth, this.imageWidth);
this.texture.magFilter = THREE.LinearFilter;
this.texture.minFilter = THREE.LinearFilter;
this.texture.needsUpdate = true;
if (this.images.length === this.imagesCount) {
this.emit('imageLoaded');
}

View File

@ -5,9 +5,10 @@
import Base from './base';
import * as THREE from './three';
import ColorUtil from '../attr/color-util';
import Controller from './controller/index';
import source from './source';
import PickingMaterial from '../core/engine/picking/pickingMaterial';
import Attr from '../attr/index';
import diff from '../util/diff';
import { updateObjecteUniform } from '../util/object3d-util';
import Util from '../util';
import Global from '../global';
let id = 1;
@ -20,7 +21,6 @@ function parseFields(field) {
}
return [ field ];
}
export default class Layer extends Base {
getDefaultCfg() {
return {
@ -30,8 +30,11 @@ export default class Layer extends Base {
minZoom: 0,
maxZoom: 22,
rotation: 0,
option: {},
attrOptions: {
},
scaleOptions: {},
preScaleOptions: null,
scales: {},
attrs: {},
// 样式配置项
@ -46,7 +49,10 @@ export default class Layer extends Base {
// 选中时的配置项
selectedOptions: null,
// active 时的配置项
activedOptions: null,
activedOptions: {
fill: [ 1.0, 0, 0, 1.0 ]
},
interactions: {},
animateOptions: {
enable: false
}
@ -60,6 +66,7 @@ export default class Layer extends Base {
this._pickObject3D = new THREE.Object3D();
this._object3D.visible = this.get('visible');
this._object3D.renderOrder = this.get('zIndex') || 0;
this._mapEventHandlers = [];
const layerId = this._getUniqueId();
this.layerId = layerId;
this._activeIds = null;
@ -76,27 +83,40 @@ export default class Layer extends Base {
* @param {*} type mesh类型是区别是填充还是边线
*/
add(object, type = 'fill') {
type === 'fill' ? this.layerMesh = object : this.layerLineMesh = object;
this._visibleWithZoom();
this._zoomchangeHander = this._visibleWithZoom.bind(this);
this.scene.on('zoomchange', this._zoomchangeHander);
object.onBeforeRender = () => {
const zoom = this.scene.getZoom();
object.material.setUniformsValue('u_time', this.scene._engine.clock.getElapsedTime());
object.material.setUniformsValue('u_zoom', zoom);
this._preRender();
};
// 更新
if (this._needUpdateFilter) {
this._updateFilter(object);
// composer合图层绘制
if (object.type === 'composer') {
this._object3D = object;
this.scene._engine.composerLayers.push(object);
setTimeout(() => this.scene._engine.update(), 500);
return;
}
type === 'fill' ? this.layerMesh = object : this.layerLineMesh = object;
this._visibleWithZoom();
object.onBeforeRender = () => { // 每次渲染前改变状态
const zoom = this.scene.getZoom();
updateObjecteUniform(this._object3D, {
u_time: this.scene._engine.clock.getElapsedTime(),
u_zoom: zoom
});
this.preRender();
};
object.onAfterRender = () => { // 每次渲染后改变状态
this.afterRender();
};
this._object3D.add(object);
if (type === 'fill') { this._addPickMesh(object); }
if (type === 'fill') {
this.get('pickingController').addPickMesh(object);
}
setTimeout(() => this.scene._engine.update(), 500);
}
remove(object) {
if (object.type === 'composer') {
this.scene._engine.composerLayers = this.scene._engine.composerLayers.filter(layer => {
return (layer !== object);
});
return;
}
this._object3D.remove(object);
}
_getUniqueId() {
@ -107,20 +127,20 @@ export default class Layer extends Base {
this._object3D.visible = this.get('visible');
}
source(data, cfg = {}) {
if (data instanceof source) {
this.layerSource = data;
return this;
}
cfg.data = data;
cfg.mapType = this.scene.mapType;
cfg.zoom = this.scene.getZoom();
this.layerSource = new source(cfg);
// this.scene.workerPool.runTask({
// command: 'geojson',
// data: cfg
// }).then(data => {
// this.scene.workerPool.runTask(cfg).then(data => {
// console.log(data);
// });
return this;
}
color(field, values) {
this._needUpdateColor = true;// 标识颜色是否需要更新
this._createAttrOption('color', field, values, Global.colors);
return this;
}
@ -133,6 +153,16 @@ export default class Layer extends Base {
this._createAttrOption('size', field, values, Global.size);
return this;
}
scale(field, cfg) {
const options = this.get('scaleOptions');
const scaleDefs = options;
if (Util.isObject(field)) {
Util.mix(scaleDefs, field);
} else {
scaleDefs[field] = cfg;
}
return this;
}
shape(field, values) {
if (field.split(':').length === 2) {
this.shapeType = field.split(':')[0];
@ -152,12 +182,13 @@ export default class Layer extends Base {
active(enable, cfg) {
if (enable === false) {
this.set('allowActive', false);
} else if (Util.isObject(enable)) {
} else if (Util.isObject(enable) && enable.fill) {
this.set('allowActive', true);
if (enable.fill) enable.fill = ColorUtil.color2RGBA(enable.fill);
this.set('activedOptions', enable);
} else {
this.set('allowActive', true);
this.set('activedOptions', cfg || { fill: Global.activeColor });
this.set('activedOptions', cfg || { fill: ColorUtil.color2RGBA(Global.activeColor) });
}
return this;
}
@ -187,11 +218,12 @@ export default class Layer extends Base {
this.set('styleOptions', styleOptions);
return this;
}
filter(field, values) {
this._needUpdateFilter = true;
this._createAttrOption('filter', field, values, true);
return this;
}
animate(field, cfg) {
let animateOptions = this.get('animateOptions');
if (!animateOptions) {
@ -213,6 +245,10 @@ export default class Layer extends Base {
}
texture() {
}
fitBounds() {
const extent = this.layerSource.data.extent;
this.scene.fitBounds(extent);
}
hide() {
this._visible(false);
@ -222,11 +258,16 @@ export default class Layer extends Base {
this._visible(true);
return this;
}
setData(data, cfg) {
this.layerSource.setData(data, cfg);
this.repaint();
}
_createScale(field) {
// TODO scale更新
const scales = this.get('scales');
let scale = scales[field];
if (!scale) {
scale = this.layerSource.createScale(field);
scale = this.createScale(field);
scales[field] = scale;
}
return scale;
@ -253,68 +294,133 @@ export default class Layer extends Base {
}
this._setAttrOptions(attrName, attrCfg);
}
_initControllers() {
const mappingCtr = new Controller.Mapping({ layer: this });
const pickCtr = new Controller.Picking({ layer: this });
const interactionCtr = new Controller.Interaction({ layer: this });
this.set('mappingController', mappingCtr);
this.set('pickingController', pickCtr);
this.set('interacionController', interactionCtr);
}
render() {
this.init();
this.scene._engine.update();
return this;
}
// 重绘 度量, 映射,顶点构建
repaint() {
this.set('scales', {});
const mappingCtr = new Controller.Mapping({ layer: this });
this.set('mappingController', mappingCtr);
// this._initAttrs();
// this._mapping();
this.redraw();
}
// 初始化图层
init() {
this._initAttrs();
this._scaleByZoom();
this._mapping();
const activeHander = this._addActiveFeature.bind(this);
const resetHander = this._resetStyle.bind(this);
this._initControllers();
// this._initAttrs();
this._updateDraw();
}
_initInteraction() {
if (this.get('allowActive')) {
this.on('mousemove', activeHander);
this.on('mouseleave', resetHander);
} else {
this.off('mousemove', activeHander);
this.off('mouseleave', resetHander);
this.get('interacionController').addInteraction('active');
}
}
_initMapEvent() {
// zoomchange mapmove resize
const EVENT_TYPES = [ 'zoomchange', 'dragend' ];
Util.each(EVENT_TYPES, type => {
const handler = Util.wrapBehavior(this, `${type}`);
this.map.on(`${type}`, handler);
this._mapEventHandlers.push({ type, handler });
});
}
clearMapEvent() {
const eventHandlers = this._mapEventHandlers;
Util.each(eventHandlers, eh => {
this.map.off(eh.type, eh.handler);
});
}
zoomchange(ev) {
// 地图缩放等级变化
this._visibleWithZoom(ev);
}
dragend() {
}
resize() {
}
setActive(id, color) {
this._activeIds = id;
if (!Array.isArray(color)) {
color = ColorUtil.color2RGBA(color);
}
updateObjecteUniform(this._object3D, {
u_activeColor: color,
u_activeId: id
});
this.scene._engine.update();
}
_addActiveFeature(e) {
const { featureId } = e;
if (featureId < 0) return;
const activeStyle = this.get('activedOptions');
// const selectFeatureIds = this.layerSource.getSelectFeatureId(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);
this._activeIds = featureId;
updateObjecteUniform(this._object3D, { u_activeId: featureId });
}
_initAttrs() {
const attrOptions = this.get('attrOptions');
for (const type in attrOptions) {
if (attrOptions.hasOwnProperty(type)) {
this._updateAttr(type);
}
_setPreOption() {
const nextAttrs = this.get('attrOptions');
const nextStyle = this.get('styleOptions');
this.set('preAttrOptions', Util.clone(nextAttrs));
this.set('preStyleOption', Util.clone(nextStyle));
}
_updateDraw() {
const preAttrs = this.get('preAttrOptions');
const nextAttrs = this.get('attrOptions');
const preStyle = this.get('preStyleOption');
const nextStyle = this.get('styleOptions');
if (preAttrs === undefined && preStyle === undefined) { // 首次渲染
// this._mapping();
this._setPreOption();
this._scaleByZoom();
this._initInteraction();
this._initMapEvent();
this.draw();
return;
}
}
_updateAttr(type) {
const self = this;
const attrs = this.get('attrs');
const attrOptions = this.get('attrOptions');
const option = attrOptions[type];
option.neadUpdate = true;
const className = Util.upperFirst(type);
const fields = parseFields(option.field);
const scales = [];
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
const scale = self._createScale(field);
if (type === 'color' && Util.isNil(option.values)) { // 设置 color 的默认色值
option.values = Global.colors;
}
scales.push(scale);
if (!Util.isEqual(preAttrs.color, nextAttrs.color)) {
this._updateAttributes(this.layerMesh);
}
option.scales = scales;
const attr = new Attr[className](option);
attrs[type] = attr;
// 更新数据过滤 filter
if (!Util.isEqual(preAttrs.filter, nextAttrs.filter)) {
// 更新color
this._updateAttributes(this.layerMesh);
}
// 更新Size
if (!Util.isEqual(preAttrs.size, nextAttrs.size)) {
// 更新color
this._updateSize();
}
// 更新形状
if (!Util.isEqual(preAttrs.shape, nextAttrs.shape)) {
// 更新color
this._updateShape();
}
if (!Util.isEqual(preStyle, nextStyle)) {
// 判断新增,修改,删除
const newStyle = {};
Util.each(diff(preStyle, nextStyle), ({ type, key, value }) => {
(type !== 'remove') && (newStyle[key] = value);
// newStyle[key] = type === 'remove' ? null : value;
});
this._updateStyle(newStyle);
}
this._setPreOption();
}
_updateSize(zoom) {
const sizeOption = this.get('attrOptions').size;
const fields = parseFields(sizeOption.field);
@ -332,95 +438,12 @@ export default class Layer extends Base {
}
this.emit('sizeUpdated', this.zoomSizeCache[zoom]);
}
_mapping() {
const self = this;
const attrs = self.get('attrs');
const mappedData = [];
// 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 = {};
newRecord.id = data[i]._id;
for (const k in attrs) {
if (attrs.hasOwnProperty(k)) {
const attr = attrs[k];
attr.needUpdate = false;
const names = attr.names;
const values = self._getAttrValues(attr, record);
if (names.length > 1) { // position 之类的生成多个字段的属性
for (let j = 0; j < values.length; j++) {
const val = values[j];
const name = names[j];
newRecord[name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
}
} else {
newRecord[names[0]] = values.length === 1 ? values[0] : values;
}
}
}
newRecord.coordinates = record.coordinates;
mappedData.push(newRecord);
_updateStyle(option) {
const newOption = { };
for (const key in option) {
newOption['u_' + key] = option[key];
}
this.layerData = mappedData;
}
// 更新地图映射
_updateMaping() {
const self = this;
const attrs = self.get('attrs');
const data = this.layerSource.data.dataArray;
for (let i = 0; i < data.length; i++) {
const record = data[i];
for (const attrName in attrs) {
if (attrs.hasOwnProperty(attrName) && attrs[attrName].neadUpdate) {
const attr = attrs[attrName];
const names = attr.names;
const values = self._getAttrValues(attr, record);
if (names.length > 1) { // position 之类的生成多个字段的属性
for (let j = 0; j < values.length; j++) {
const val = values[j];
const name = names[j];
this.layerData[i][name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
}
} else {
this.layerData[i][names[0]] = values.length === 1 ? values[0] : values;
}
attr.neadUpdate = true;
}
}
}
}
// 获取属性映射的值
_getAttrValues(attr, record) {
const scales = attr.scales;
const params = [];
for (let i = 0; i < scales.length; i++) {
const scale = scales[i];
const field = scale.field;
if (scale.type === 'identity') {
params.push(scale.value);
} else {
params.push(record[field]);
}
}
const indexZoom = params.indexOf('zoom');
indexZoom !== -1 ? params[indexZoom] = attr.zoom : null;
const values = attr.mapping(...params);
return values;
}
// temp
_getDataType(data) {
if (data.hasOwnProperty('type')) {
const type = data.type;
if (type === 'FeatureCollection') {
return 'geojson';
}
}
return 'basic';
updateObjecteUniform(this._object3D, newOption);
}
_scaleByZoom() {
if (this._zoomScale) {
@ -430,106 +453,52 @@ export default class Layer extends Base {
});
}
}
// on(type, callback) {
// this._addPickingEvents();
// super.on(type, callback);
// }
getPickingId() {
return this.scene._engine._picking.getNextId();
}
addToPicking(object) {
this.scene._engine._picking.add(object);
}
removeFromPicking(object) {
this.scene._engine._picking.remove(object);
}
_addPickMesh(mesh) {
this._pickingMesh = new THREE.Object3D();
this._pickingMesh.name = this.layerId;
// this._visibleWithZoom();
// this.scene.on('zoomchange', () => {
// this._visibleWithZoom();
// });
this.addToPicking(this._pickingMesh);
const pickmaterial = new PickingMaterial({
u_zoom: this.scene.getZoom()
});
const pickingMesh = new THREE[mesh.type](mesh.geometry, pickmaterial);
pickingMesh.name = this.layerId;
pickmaterial.setDefinesvalue(this.type, true);
pickingMesh.onBeforeRender = () => {
const zoom = this.scene.getZoom();
pickingMesh.material.setUniformsValue('u_zoom', zoom);
};
this._pickingMesh.add(pickingMesh);
}
_setPickingId() {
this._pickingId = this.getPickingId();
}
_initEvents() {
this.scene.on('pick-' + this.layerId, e => {
const { featureId, point2d, type } = e;
if (featureId < -100 && this._activeIds !== null) {
this.emit('mouseleave');
return;
}
const feature = this.layerSource.getSelectFeature(featureId);
let { featureId, point2d, type } = e;
// TODO 瓦片图层获取选中数据信息
const lnglat = this.scene.containerToLngLat(point2d);
let feature = null;
let style = null;
if (featureId !== -999) {
const res = this.getSelectFeature(featureId, lnglat);
feature = res.feature;
style = res.style;
}
const target = {
featureId,
feature,
style,
pixel: point2d,
type,
lnglat: { lng: lnglat.lng, lat: lnglat.lat }
};
if (featureId >= 0) {
if (featureId >= 0) { // 拾取到元素,或者离开元素
this.emit(type, target);
}
if (featureId < 0 && this._activeIds >= 0) {
type = 'mouseleave';
this.emit(type, target);
}
this._activeIds = featureId;
});
}
/**
* 更新active操作
* @param {*} featureStyleId 需要更新的要素Id
* @param {*} style 更新的要素样式
*/
updateStyle(featureStyleId, style) {
if (this._activeIds) {
this._resetStyle();
}
this._activeIds = featureStyleId;
const pickingId = this.layerMesh.geometry.attributes.pickingId.array;
const color = style.color;
const colorAttr = this.layerMesh.geometry.attributes.a_color;
const firstId = pickingId.indexOf(featureStyleId[0] + 1);
for (let i = firstId; i < pickingId.length; i++) {
if (pickingId[i] === featureStyleId[0] + 1) {
colorAttr.array[i * 4 + 0] = color[0];
colorAttr.array[i * 4 + 1] = color[1];
colorAttr.array[i * 4 + 2] = color[2];
colorAttr.array[i * 4 + 3] = color[3];
} else {
break;
}
}
colorAttr.needsUpdate = true;
return;
}
_updateColor() {
this._updateMaping();
getSelectFeature(featureId) {
const feature = this.layerSource.getSelectFeature(featureId);
const style = this.layerData[featureId - 1];
return {
feature,
style
};
}
/**
* 用于过滤数据
* @param {*} object 需要过滤的mesh
* @param {*} object 更新颜色和数据过滤
*/
_updateFilter(object) {
this._updateMaping();
_updateAttributes(object) {
this.get('mappingController').update();
const filterData = this.layerData;
this._activeIds = null; // 清空选中元素
const colorAttr = object.geometry.attributes.a_color;
@ -557,6 +526,7 @@ export default class Layer extends Base {
pickAttr.needsUpdate = true;
}
_visibleWithZoom() {
if (this._object3D === null) return;
const zoom = this.scene.getZoom();
const minZoom = this.get('minZoom');
const maxZoom = this.get('maxZoom');
@ -564,43 +534,56 @@ export default class Layer extends Base {
let offset = 0;
if (this.type === 'point') {
offset = 5;
} else if (this.type === 'polyline') {
this.shapeType = 'text' && (offset = 10);
} else if (this.type === 'polyline' || this.type === 'line') {
offset = 2;
} else if (this.type === 'polygon') {
offset = 1;
}
this._object3D.position.z = offset * Math.pow(2, 20 - zoom);
if (zoom < minZoom || zoom > maxZoom) {
this._object3D.position && (this._object3D.position.z = offset * Math.pow(2, 20 - zoom));
if (zoom < minZoom || zoom >= maxZoom) {
this._object3D.visible = false;
} else if (this.get('visible')) {
this._object3D.visible = true;
}
}
// 重新构建mesh
redraw() {
this._object3D.children.forEach(child => {
this._object3D.remove(child);
});
this.get('pickingController').removeAllMesh();
this.draw();
}
// 更新mesh
updateDraw() {
}
styleCfg() {
}
/**
* 重置高亮要素
*/
_resetStyle() {
const pickingId = this.layerMesh.geometry.attributes.pickingId.array;
const colorAttr = this.layerMesh.geometry.attributes.a_color;
this._activeIds.forEach(index => {
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) {
colorAttr.array[i * 4 + 0] = color[0];
colorAttr.array[i * 4 + 1] = color[1];
colorAttr.array[i * 4 + 2] = color[2];
colorAttr.array[i * 4 + 3] = color[3];
}
}
});
colorAttr.needsUpdate = true;
this._activeIds = null;
updateObjecteUniform(this._object3D, { u_activeId: 0 });
}
/**
* 销毁Layer对象
*/
destroy() {
this.removeAllListeners();
this.clearAllInteractions();
this.clearMapEvent();
if (this._object3D.type === 'composer') {
this.remove(this._object3D);
return;
}
if (this._object3D && this._object3D.children) {
let child;
for (let i = 0; i < this._object3D.children.length; i++) {
@ -625,13 +608,55 @@ export default class Layer extends Base {
child = null;
}
}
this.layerMesh.geometry = null;
this.layerMesh.material.dispose();
this.layerMesh.material = null;
if (this._pickingMesh) {
this._pickingMesh.children[0].geometry = null;
this._pickingMesh.children[0].material.dispose();
this._pickingMesh.children[0].material = null;
}
this._buffer = null;
this._object3D = null;
this.scene._engine._scene.remove(this._object3D);
this.layerData.length = 0;
this.layerSource = null;
this.scene._engine._picking.remove(this._pickingMesh);
this.scene.off('zoomchange', this._zoomchangeHander);
this.destroyed = true;
}
_preRender() {
/**
* 获取图例配置项
* @param {*} field 字段
* @param {*} type 图例类型 color, size
* @return {*} 图例配置项
*/
getLegendCfg(field, type = 'color') {
// todo heatmap
if (this.type === 'heatmap' && this.shapeType === 'heatmap') {
return this.get('styleOptions').rampColors;
}
const scales = this.get('scales');
const scale = scales[field];
const colorAttrs = this.get('attrs')[type];
const lengendCfg = {};
if (scale) {
const ticks = scale.ticks;
lengendCfg.value = ticks;
lengendCfg.type = scale.type;
const values = ticks.map(value => {
const v = this._getAttrValues(colorAttrs, { [field]: value });
return type === 'color' ? ColorUtil.colorArray2RGBA(v) : v;
});
lengendCfg[type] = values;
}
return lengendCfg;
}
preRender() {
}
afterRender() {
}
}

View File

@ -2,10 +2,12 @@ import Engine from './engine';
import { LAYER_MAP } from '../layer';
import Base from './base';
import LoadImage from './image';
// import WorkerPool from './worker';
import FontAtlasManager from '../geom/buffer/point/text/font-manager';
import WorkerPool from '../worker/worker_pool';
// import { MapProvider } from '../map/AMap';
import { getMap } from '../map/index';
import Global from '../global';
import { getInteraction } from '../interaction/index';
import { compileBuiltinModules } from '../geom/shader';
export default class Scene extends Base {
getDefaultCfg() {
@ -16,13 +18,16 @@ export default class Scene extends Base {
this._initMap();
// this._initAttribution(); // 暂时取消,后面作为组件去加载
this.addImage();
this.fontAtlasManager = new FontAtlasManager();
this._layers = [];
this.animateCount = 0;
}
_initEngine(mapContainer) {
this._engine = new Engine(mapContainer, this);
this._engine.run();
// this.workerPool = new WorkerPool();
this.registerMapEvent();
// this._engine.run();
this.workerPool = new WorkerPool();
compileBuiltinModules();
}
// 为pickup场景添加 object 对象
@ -35,14 +40,20 @@ export default class Scene extends Base {
const MapProvider = getMap(this.mapType);
const Map = new MapProvider(this._attrs);
Map.mixMap(this);
this._container = document.getElementById(Map.container);
this._container = Map.container;
// const Map = new MapProvider(this.mapContainer, this._attrs);
Map.on('mapLoad', () => {
this._initEngine(Map.renderDom);
this.map = Map.map;
this._initEngine(Map.renderDom);
Map.asyncCamera(this._engine);
this.initLayer();
this._registEvents();
const hash = this.get('hash');
if (hash) {
const Ctor = getInteraction('hash');
const interaction = new Ctor({ layer: this });
interaction._onHashChange();
}
this.emit('loaded');
});
@ -63,17 +74,8 @@ export default class Scene extends Base {
}
off(type, hander) {
if (this.map) { this.map.off(type, hander); }
super.off(type, hander);
}
_initAttribution() {
const message = '<a href="http://antv.alipay.com/zh-cn/index.html title="Large-scale WebGL-powered Geospatial Data Visualization">AntV | L7 </a>';
const element = document.createElement('div');
element.innerHTML = message;
element.style.cssText += 'position: absolute; pointer-events:none;background: rgba(255, 255, 255, 0.7);font-size: 11px;z-index:100; padding:4px;bottom: 0;right:0px;';
this._container.appendChild(element);
}
addImage() {
this.image = new LoadImage();
}
@ -103,7 +105,9 @@ export default class Scene extends Base {
this._container.addEventListener(event, e => {
// 要素拾取
e.pixel || (e.pixel = e.point);
this._engine._picking.pickdata(e);
requestAnimationFrame(() => {
this._engine._picking.pickdata(e);
});
}, false);
});
}
@ -115,5 +119,31 @@ export default class Scene extends Base {
layer.destroy();
layer = null;
}
startAnimate() {
if (this.animateCount === 0) {
this.unRegsterMapEvent();
this._engine.run();
}
this.animateCount++;
}
stopAnimate() {
if (this.animateCount === 1) {
this._engine.stop();
this.registerMapEvent();
}
this.animateCount++;
}
// 地图状态变化时更新可视化渲染
registerMapEvent() {
this._updateRender = () => this._engine.update();
this.map.on('mousemove', this._updateRender);
this.map.on('mapmove', this._updateRender);
this.map.on('camerachange', this._updateRender);
}
unRegsterMapEvent() {
this.map.off('mousemove', this._updateRender);
this.map.off('mapmove', this._updateRender);
this.map.off('camerachange', this._updateRender);
}
}

View File

@ -1,7 +1,8 @@
import Base from './base';
import Controller from './controller/index';
import { getTransform, getParser } from '../source';
import { cluster, formatData } from '../source/transform/cluster';
import { extent, tranfrormCoord } from '../util/geo';
import { clone } from '@antv/util';
import { getMap } from '../map/index';
export default class Source extends Base {
getDefaultCfg() {
@ -10,8 +11,8 @@ export default class Source extends Base {
defs: {},
parser: {},
transforms: [],
scales: {
},
scaledefs: {},
scales: {},
options: {}
};
}
@ -19,19 +20,49 @@ export default class Source extends Base {
super(cfg);
const transform = this.get('transforms');
this._transforms = transform || [];
this._initControllers();
const mapType = this.get('mapType') || 'AMap';
this.projectFlat = getMap(mapType).project;
// 数据解析
this._init();
}
_init() {
this._excuteParser();
const isCluster = this.get('isCluster') || false;
isCluster && this._executeCluster();
// 数据转换 统计,聚合,分类
this._executeTrans();
// 坐标转换
this._projectCoords();
}
setData(data, cfg = {}) {
Object.assign(this._attrs, cfg);
const transform = this.get('transforms');
this._transforms = transform || [];
this.set('data', data);
this._init();
}
// 数据更新
updateTransfrom(cfg) {
const { transforms } = cfg;
this._transforms = transforms;
this.data = clone(this.originData);
this._executeTrans();
this._projectCoords();
}
_excuteParser() {
const parser = this.get('parser');
const { type = 'geojson' } = parser;
const data = this.get('data');
this.data = getParser(type)(data, parser);
this.originData = getParser(type)(data, parser);
// this.data = {
// dataArray: clone(this.originData.dataArray)
// }; // TODO 关闭数据备份
this.data = this.originData;
if (this.data !== null) {
this.data.extent = extent(this.data.dataArray);
}
}
/**
* 数据统计
@ -40,36 +71,42 @@ export default class Source extends Base {
const trans = this._transforms;
trans.forEach(tran => {
const { type } = tran;
this.data = getTransform(type)(this.data, tran);
const data = getTransform(type)(this.data, tran);
Object.assign(this.data, data);
});
this._transforms = trans;
}
transform(option) {
const data = getTransform(option.type)(this.data, option);
Object.assign(this.data, data);
}
_executeCluster() {
const clusterCfg = this.get('Cluster') || {};
const zoom = this.get('zoom');
clusterCfg.zoom = Math.floor(zoom);
this.set('cluster', clusterCfg);
const clusterData = cluster(this.data, clusterCfg);
this.data = clusterData.data;
this.pointIndex = clusterData.pointIndex;
}
updateCusterData(zoom, bbox) {
const clusterPoint = this.pointIndex.getClusters(bbox, zoom);
this.data.dataArray = formatData(clusterPoint);
const clusterCfg = this.get('Cluster') || {};
clusterCfg.zoom = Math.floor(zoom);
clusterCfg.bbox = bbox;
this.set('cluster', clusterCfg);
this._projectCoords();
}
_projectCoords() {
this.data.dataArray.forEach(data => {
data.coordinates = this._coordProject(data.coordinates);
});
}
createScale(field) {
const data = this.data.dataArray;
const scales = this.get('scales');
let scale = scales[field];
const scaleController = this.get('scaleController');
if (!scale) {
scale = scaleController.createScale(field, data);
scales[field] = scale;
if (this.data === null) {
return;
}
return scale;
}
_initControllers() {
const defs = this.get('defs');
const mapType = this.get('mapType');
this.projectFlat = getMap(mapType).project;
const scaleController = new Controller.Scale({
defs
this.data.dataArray.forEach(data => {
// data.coordinates = this._coordProject(data.coordinates);
data.coordinates = tranfrormCoord(data.coordinates, this._coorConvert.bind(this));
});
this.set('scaleController', scaleController);
}
_getCoord(geo) {
if (geo.geometry) {
// GeoJSON feature
@ -81,7 +118,6 @@ export default class Source extends Base {
return geo;
}
_coordProject(geo) {
if (Array.isArray(geo[0][0])) {
return geo.map(coor => {
return this._coordProject(coor);
@ -89,22 +125,41 @@ export default class Source extends Base {
}
if (!Array.isArray(geo[0])) {
return this._coorConvert(geo);
}
return geo.map(coor => {
return this._coorConvert(coor);
});
}
_coorConvert(geo) {
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];
// 颜色编码从1开始要素索引从0开始所以颜色转变要素需要减1
const isCluster = this.get('isCluster') || false;
return (data.features && !isCluster)
? data.features[featureId - 1]
: this.data.dataArray[featureId - 1];
}
getSeletFeatureIndex(featureId) {
const data = this.get('data');
if (!data.features) {
return featureId - 1;
}
let featureIndex = 0;
for (let i = 0; i < this.data.dataArray.length; i++) {
const item = this.data.dataArray[i];
if (item._id === featureId) {
break;
}
featureIndex++;
}
return featureIndex;
}
destroy() {
this.data = null;
this.originData = null;
}
}

View File

@ -12,8 +12,13 @@ export { Mesh } from 'three/src/objects/Mesh.js';
export { Texture } from 'three/src/textures/Texture.js';
export { WebGLRenderTarget } from 'three/src/renderers/WebGLRenderTarget.js';
export { PerspectiveCamera } from 'three/src/cameras/PerspectiveCamera.js';
export { OrthographicCamera } from 'three/src/cameras/OrthographicCamera.js';
export { BufferGeometry } from 'three/src/core/BufferGeometry.js';
export { InstancedBufferGeometry } from 'three/src/core/InstancedBufferGeometry';
export { PlaneBufferGeometry } from 'three/src/geometries/PlaneGeometry.js';
export { BoxBufferGeometry } from 'three/src/geometries/BoxGeometry.js';
export { Raycaster } from 'three/src/core/Raycaster.js';
export { UniformsUtils } from 'three/src/renderers/shaders/UniformsUtils.js';
export { Matrix4 } from 'three/src/math/Matrix4.js';
export { Matrix3 } from 'three/src/math/Matrix3.js';
export { Line } from 'three/src/objects/Line.js';
@ -22,6 +27,7 @@ export { Vector3 } from 'three/src/math/Vector3.js';
export { Vector2 } from 'three/src/math/Vector2.js';
export { ShaderMaterial } from 'three/src/materials/ShaderMaterial.js';
export { DataTexture } from 'three/src/textures/DataTexture.js';
export { Color } from 'three/src/math/Color.js';
export {
Float64BufferAttribute,
Float32BufferAttribute,
@ -35,4 +41,5 @@ export {
BufferAttribute
} from 'three/src/core/BufferAttribute.js';
export { InstancedBufferAttribute } from 'three/src/core/InstancedBufferAttribute'
// export * from '../../build/three.js';

38
src/geom/base.js Normal file
View File

@ -0,0 +1,38 @@
export const GeomBase = {
color: 'updateDraw',
size: 'repaint',
filter: 'updateDraw',
layer: '',
pickable: true,
setLayer(layer) {
this.layer = layer;
this.style = layer.get('styleOption');
},
getShape(type) {
return type;
},
draw() {
const shape = this.getShape();
this.Mesh = shape.Mesh();
},
// 更新geometry buffer;
updateDraw() {
},
repaint() {
}
};
export const shapeBae = {
geometryBuffer() {
},
geometry() {},
material() {},
mesh() {
}
};

View File

@ -0,0 +1,101 @@
import BufferBase from '../bufferBase';
import { colorScales } from '../../../attr/colorscales';
import * as THREE from '../../../core/three';
export default class HeatmapBuffer extends BufferBase {
geometryBuffer() {
const data = this.get('data');
const positions = [];
const dirs = [];
const weights = [];
// const indices = [];
// 组织顶点数据
data.forEach(d => {
// const totalIndex = index * 4;
const coord = d.coordinates;
const weight = d.size;
const dir = this._addDir(-1, 1);
const dir1 = this._addDir(1, 1);
const dir2 = this._addDir(-1, -1);
const dir3 = this._addDir(1, -1);
positions.push(...coord, ...coord, ...coord, ...coord, ...coord, ...coord);
dirs.push(...dir, ...dir2, ...dir3, ...dir1, ...dir, ...dir3);
weights.push(weight, weight, weight, weight, weight, weight);
// indices.push(totalIndex, totalIndex + 2, totalIndex + 3, totalIndex, totalIndex + 3, totalIndex + 1);
});
this.attributes = {
vertices: positions,
// indices,
dirs,
weights
};
}
_addVertex(position, dirX, dirY) {
const x = (position[0] * 2) + ((dirX + 1) / 2);
const y = (position[1] * 2) + ((dirY + 1) / 2);
const z = position[2];
return [ x, y, z ];
}
_addDir(dirX, dirY) {
const x = (dirX + 1) / 2;
const y = (dirY + 1) / 2;
return [ x, y ];
}
}
export function createColorRamp(colors) {
const colorImageData = getColorRamp(colors);
const colorTexture = getTexture(colorImageData);
return colorTexture;
}
function getColorRamp(name) {
let colorscale = name;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 1;
canvas.height = 256;
const gradient = ctx.createLinearGradient(0, 0, 0, 256);
let data = null;
if (typeof (colorscale) === 'string') {
colorscale = colorScales[name];
}
if (Object.prototype.toString.call(colorscale) === '[object Object]') {
const min = colorscale.positions[0];
const max = colorscale.positions[colorscale.positions.length - 1];
for (let i = 0; i < colorscale.colors.length; ++i) {
const value = (colorscale.positions[i] - min) / (max - min);
gradient.addColorStop(value, colorscale.colors[i]);
}
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 1, 256);
data = new Uint8ClampedArray(ctx.getImageData(0, 0, 1, 256).data);
}
if (Object.prototype.toString.call(colorscale) === '[object Uint8Array]') {
data = ctx.createImageData(1, 256);
}
return new ImageData(data, 1, 256);
}
function getTexture(image) {
const texture = new THREE.Texture(image);
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
texture.magFilter = THREE.NearestFilter;
texture.minFilter = THREE.NearestFilter;
texture.format = THREE.RGBAFormat;
texture.type = THREE.UnsignedByteType;
texture.needsUpdate = true;
return texture;
}

View File

@ -23,7 +23,7 @@ export default class ImageBuffer extends BufferBase {
const uv = [ 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0 ];
const texture = new THREE.Texture(image);
texture.magFilter = THREE.LinearFilter;
texture.minFilter = THREE.LinearFilter;
texture.minFilter = THREE.LinearMipMapLinearFilter;
texture.needsUpdate = true;
const attributes = {
vertices: new Float32Array(positions),

View File

@ -75,7 +75,7 @@ export default class LineBuffer extends BufferBase {
}
_getMeshLineAttributes() {
const layerData = this.get('layerData');
const { lineType } = this.get('style');
const { dashArray } = this.get('style');
const positions = [];
const pickingIds = [];
const normal = [];
@ -84,10 +84,11 @@ export default class LineBuffer extends BufferBase {
const indexArray = [];
const sizes = [];
const attrDistance = [];
const attrDashArray = [];
layerData.forEach(item => {
const props = item;
const positionCount = positions.length / 3;
const attr = lineShape.Line(item.coordinates, props, positionCount, (lineType !== 'soild'));
const attr = lineShape.Line(item.coordinates, props, positionCount, dashArray);
positions.push(...attr.positions);
normal.push(...attr.normal);
miter.push(...attr.miter);
@ -96,6 +97,7 @@ export default class LineBuffer extends BufferBase {
sizes.push(...attr.sizes);
attrDistance.push(...attr.attrDistance);
pickingIds.push(...attr.pickingIds);
attrDashArray.push(...attr.dashArray);
});
return {
positions,
@ -105,22 +107,26 @@ export default class LineBuffer extends BufferBase {
indexArray,
pickingIds,
sizes,
attrDistance
attrDistance,
attrDashArray
};
}
_toAttributes(bufferStruct) {
const vertCount = bufferStruct.verts.length;
const vertices = new Float32Array(vertCount * 3);
const pickingIds = new Float32Array(vertCount);
const inposs = new Float32Array(vertCount * 4);
const colors = new Float32Array(vertCount * 4);
for (let i = 0; i < vertCount; i++) {
const index = bufferStruct.indexs[i];
const color = bufferStruct.style[index].color;
const id = bufferStruct.style[index].id;
vertices[i * 3] = bufferStruct.verts[i][0];
vertices[i * 3 + 1] = bufferStruct.verts[i][1];
vertices[i * 3 + 2] = bufferStruct.verts[i][2];
colors[i * 4] = color[0];
pickingIds[i] = id;
colors[i * 4 + 1] = color[1];
colors[i * 4 + 2] = color[2];
colors[i * 4 + 3] = color[3];
@ -133,6 +139,7 @@ export default class LineBuffer extends BufferBase {
}
return {
pickingIds,
vertices,
colors,
inposs

View File

@ -0,0 +1,59 @@
import { packUint8ToFloat } from '../../../util/vertex-compress';
import Global from '../../../global';
const { pointShape } = Global;
const LEFT_SHIFT17 = 131072.0;
// const LEFT_SHIFT18 = 262144.0;
// const LEFT_SHIFT19 = 524288.0;
// const LEFT_SHIFT20 = 1048576.0;
const LEFT_SHIFT21 = 2097152.0;
// const LEFT_SHIFT22 = 4194304.0;
const LEFT_SHIFT23 = 8388608.0;
// const LEFT_SHIFT24 = 16777216.0;
export default function circleBuffer(layerData) {
const index = [];
const aPosition = [];
const aPackedData = [];
layerData.forEach(({ size = 0, color, id, coordinates, shape }, i) => {
const shapeIndex = pointShape['2d'].indexOf(shape) || 0;
if (isNaN(size)) {
size = 0;
}
// pack color(vec4) into vec2
const packedColor = [
packUint8ToFloat(color[0] * 255, color[1] * 255),
packUint8ToFloat(color[2] * 255, color[3] * 255)
];
// construct point coords
[
[ -1, -1 ],
[ 1, -1 ],
[ 1, 1 ],
[ -1, 1 ]
].forEach(extrude => {
// vec4(color, color, (4-bit extrude, 4-bit shape, 16-bit size), id)
aPackedData.push(
...packedColor,
(extrude[0] + 1) * LEFT_SHIFT23 + (extrude[1] + 1) * LEFT_SHIFT21
+ shapeIndex * LEFT_SHIFT17
+ size,
id
);
});
// TODO如果使用相对瓦片坐标还可以进一步压缩
aPosition.push(...coordinates, ...coordinates, ...coordinates, ...coordinates);
index.push(...[ 0, 1, 2, 0, 2, 3 ].map(n => n + i * 4));
});
return {
aPosition,
index,
aPackedData
};
}

View File

@ -29,6 +29,12 @@ export default function fillBuffer(layerData) {
throw new Error('Invalid shape type: ' + shape);
}
toPointShapeAttributes(polygon, coordinates, { size, shape, color, id }, attribute);
// toPointShapeAttributes(polygon, null, {}, attribute);
// instanced attributes
// attribute.vertices.push(...coordinates);
// attribute.a_size.push(...size);
// attribute.colors.push(...color);
// attribute.pickingIds.push(id);
});
return attribute;
@ -78,5 +84,8 @@ function toPointShapeAttributes(polygon, geo, style, attribute) {
attribute.colors.push(...color, ...color, ...color);
attribute.pickingIds.push(id, id, id);
// attribute.shapePositions.push(ax, ay, az, bx, by, bz, cx, cy, cz);
// attribute.normals.push(nx, ny, nz, nx, ny, nz, nx, ny, nz);
}
}

View File

@ -2,3 +2,4 @@ export { default as FillBuffer } from './fillBuffer';
export { default as StrokeBuffer } from './strokeBuffer';
export { default as ImageBuffer } from './imageBuffer';
export { default as NormalBuffer } from './normalBuffer';
export { default as CircleBuffer } from './circleBuffer';

View File

@ -13,7 +13,5 @@ export default function NormalBuffer(layerData) {
attributes.sizes.push(size);
});
return attributes;
}

View File

@ -1,246 +0,0 @@
// const SDFCommonWordsKey = '_AMap_sdf_com_words';
// /**
// * SDF 常用字获取/存储/check
// *
// */
// const SDFCommonWords = {
// store() {
// },
// /**
// * 检查一个字符是否在常用字中
// * @param {*} charcode 汉字
// */
// check(charcode) {
// const range = this.range || [];
// const info = this.info || {};
// if (typeof charcode !== 'number') {
// charcode = charcode.substr(0).charCodeAt(0);
// }
// for (let i = 0; i < range.length; i++) {
// const curRange = range[i];
// const [ rangeStart, rangeEnd ] = curRange.split('-');
// if (charcode >= rangeStart && charcode <= rangeEnd) {
// const curInfo = info[curRange] && info[curRange].info || {};
// if (curInfo[charcode]) {
// return true;
// }
// }
// }
// return false;
// },
// /**
// * 获取纹理和位置信息
// * @param list
// * @param cb
// */
// getImagesAndInfo(list, cb) {
// const range = this.range;
// },
// loadCanvas(url, range, done) {
// try {
// const xhr = new XMLHttpRequest();
// xhr.open('GET', url);
// // 直接用 blob 格式 load 图片文件,方便直接转换成 base64
// // 转成 base64 便于存储
// // 使用 canvas 转换 base64 容易有损
// xhr.responseType = 'blob';
// xhr.onerror = function() {
// done({ code: 0 });
// };
// xhr.onload = function() {
// if (xhr.status === 200) {
// const reader = new FileReader();
// reader.onload = () => {
// done(reader.result, range);
// };
// reader.readAsDataURL(xhr.response);
// } else {
// done({ code: 0 });
// }
// };
// xhr.send();
// } catch (err) {
// done({ code: 0 });
// }
// },
// loadImages(urls = []) {
// const deferred = $.Deferred();
// const totalNumbers = urls.length;
// const localInfo = this.info;
// let loadPicNum = 0;
// for (let i = 0; i < urls.length; i++) {
// const { url, range } = urls[i];
// this.loadCanvas(url, range, (base64, range) => {
// // image to base64
// loadPicNum++;
// !localInfo[range] && (localInfo[range] = {});
// localInfo[range].pic = base64;
// this.info = localInfo;
// // todo: temp 暂时用 localstorage 存储,因为数据比较大,最好使用 indexDB
// localStorage.setItem(SDFCommonWordsKey, JSON.stringify(localInfo));
// if (loadPicNum === totalNumbers) {
// deferred.resolve();
// }
// });
// }
// return deferred;
// },
// loadInfo(urls) {
// const deferred = $.Deferred();
// const totalNumbers = urls.length;
// const localInfo = this.info;
// let loadInfoNum = 0;
// for (let i = 0; i < urls.length; i++) {
// const { url, range } = urls[i];
// $.ajax({
// url,
// dataType: 'json',
// success: data => {
// loadInfoNum++;
// !localInfo[range] && (localInfo[range] = {});
// localInfo[range].info = data;
// this.info = localInfo;
// localStorage.setItem(SDFCommonWordsKey, JSON.stringify(localInfo));
// if (loadInfoNum === totalNumbers) {
// deferred.resolve();
// }
// },
// error: () => {
// }
// });
// }
// return deferred;
// },
// getTotalAssets(info, cb) {
// const { range = [], urlPrefix } = info;
// const picUrls = [];
// const infoUrls = [];
// this.range = range;
// for (let i = 0; i < range.length; i++) {
// const curRange = range[i];
// const baseUrl = urlPrefix + curRange;
// const picUrl = baseUrl + '.png';
// const infoUrl = baseUrl + '.json';
// picUrls.push({ range: curRange, url: picUrl });
// infoUrls.push({ range: curRange, url: infoUrl });
// }
// const imageDeferred = this.loadImages(picUrls);
// const infoDeferred = this.loadInfo(infoUrls);
// $.when(imageDeferred, infoDeferred)
// .then(() => {
// // all info load complete
// // console.log("all info load complete", " -- ", 1);
// cb && cb(this.info);
// }, () => {
// // fail
// });
// },
// // 获取数据
// getData(cb) {
// if (!_.isEmpty(this.info)) {
// cb && cb(this.info);
// } else {
// this.getRemoteData(cb);
// }
// },
// /**
// * 从服务获取数据,什么时候强制去取一回数据?过期?
// * @param cb
// */
// getRemoteData(cb) {
// const self = this;
// $.ajax({
// url: '/getcommonwords',
// dataType: 'json',
// success: data => {
// if (data.code == 1) {
// const info = data.data;
// self.getTotalAssets(info, cb);
// }
// }
// });
// },
// destroy() {
// },
// init() {
// let info = localStorage.getItem(SDFCommonWordsKey);
// this.range = [];
// this.info = {};
// if (info) {
// info = JSON.parse(info);
// this.range = Object.keys(info);
// this.info = info;
// }
// this.info = info || {};
// }
// };
// export default SDFCommonWords;

View File

@ -0,0 +1,130 @@
export default function TextBuffer(layerData, fontAtlasManager) {
const characterSet = [];
layerData.forEach(element => {
let text = element.shape || '';
text = text.toString();
for (let j = 0; j < text.length; j++) {
if (characterSet.indexOf(text[j]) === -1) {
characterSet.push(text[j]);
}
}
});
fontAtlasManager.setProps({
characterSet
});
const attr = drawGlyph(layerData, fontAtlasManager);
return attr;
}
function drawGlyph(layerData, fontAtlasManager) {
const attributes = {
originPoints: [],
textSizes: [],
textOffsets: [],
colors: [],
textureElements: [],
pickingIds: []
};
const { texture, fontAtlas, mapping, scale } = fontAtlasManager;
layerData.forEach(function(element) {
const size = element.size;
const pos = element.coordinates;
let text = element.shape || '';
text = text.toString();
const pen = {
x: (-text.length * size) / 2,
y: 0
};
for (let i = 0; i < text.length; i++) {
const metric = mapping[text[i]];
const { x, y, width, height } = metric;
const color = element.color;
const offsetX = pen.x;
const offsetY = pen.y;
attributes.pickingIds.push(
element.id,
element.id,
element.id,
element.id,
element.id,
element.id
);
attributes.textOffsets.push(
// 文字在词语的偏移量
offsetX,
offsetY,
offsetX,
offsetY,
offsetX,
offsetY,
offsetX,
offsetY,
offsetX,
offsetY,
offsetX,
offsetY
);
attributes.originPoints.push(
// 词语的经纬度坐标
pos[0],
pos[1],
0,
pos[0],
pos[1],
0,
pos[0],
pos[1],
0,
pos[0],
pos[1],
0,
pos[0],
pos[1],
0,
pos[0],
pos[1],
0
);
attributes.textSizes.push(
size,
size * scale,
0,
size * scale,
0,
0,
size,
size * scale,
0,
0,
size,
0
);
attributes.colors.push(
...color,
...color,
...color,
...color,
...color,
...color
);
attributes.textureElements.push(
// 文字纹理坐标
x + width,
y,
x,
y,
x,
y + height,
x + width,
y,
x,
y + height,
x + width,
y + height
);
pen.x = pen.x + size;
}
});
attributes.texture = texture;
attributes.fontAtlas = fontAtlas;
return attributes;
}

View File

@ -0,0 +1,227 @@
import TinySDF from '@mapbox/tiny-sdf';
import { buildMapping } from '../../../../util/font-util';
import * as THREE from '../../../../core/three';
import LRUCache from './lru-cache';
export const DEFAULT_CHAR_SET = getDefaultCharacterSet();
export const DEFAULT_FONT_FAMILY = 'sans-serif';
export const DEFAULT_FONT_WEIGHT = 'normal';
export const DEFAULT_FONT_SIZE = 24;
export const DEFAULT_BUFFER = 3;
export const DEFAULT_CUTOFF = 0.25;
export const DEFAULT_RADIUS = 8;
const MAX_CANVAS_WIDTH = 1024;
const BASELINE_SCALE = 0.9;
const HEIGHT_SCALE = 1.2;
const CACHE_LIMIT = 3;
const cache = new LRUCache(CACHE_LIMIT);
const VALID_PROPS = [
'fontFamily',
'fontWeight',
'characterSet',
'fontSize',
'sdf',
'buffer',
'cutoff',
'radius'
];
function getDefaultCharacterSet() {
const charSet = [];
for (let i = 32; i < 128; i++) {
charSet.push(String.fromCharCode(i));
}
return charSet;
}
function setTextStyle(ctx, fontFamily, fontSize, fontWeight) {
ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
ctx.fillStyle = '#000';
ctx.textBaseline = 'baseline';
ctx.textAlign = 'left';
}
function getNewChars(key, characterSet) {
const cachedFontAtlas = cache.get(key);
if (!cachedFontAtlas) {
return characterSet;
}
const newChars = [];
const cachedMapping = cachedFontAtlas.mapping;
let cachedCharSet = Object.keys(cachedMapping);
cachedCharSet = new Set(cachedCharSet);
let charSet = characterSet;
if (charSet instanceof Array) {
charSet = new Set(charSet);
}
charSet.forEach(char => {
if (!cachedCharSet.has(char)) {
newChars.push(char);
}
});
return newChars;
}
function populateAlphaChannel(alphaChannel, imageData) {
// populate distance value from tinySDF to image alpha channel
for (let i = 0; i < alphaChannel.length; i++) {
imageData.data[4 * i + 3] = alphaChannel[i];
}
}
export default class FontAtlasManager {
constructor() {
// font settings
this.props = {
fontFamily: DEFAULT_FONT_FAMILY,
fontWeight: DEFAULT_FONT_WEIGHT,
characterSet: DEFAULT_CHAR_SET,
fontSize: DEFAULT_FONT_SIZE,
buffer: DEFAULT_BUFFER,
// sdf only props
// https://github.com/mapbox/tiny-sdf
sdf: true,
cutoff: DEFAULT_CUTOFF,
radius: DEFAULT_RADIUS
};
// key is used for caching generated fontAtlas
this._key = null;
this._texture = new THREE.Texture();
}
get texture() {
return this._texture;
}
get mapping() {
const data = cache.get(this._key);
return data && data.mapping;
}
get scale() {
return HEIGHT_SCALE;
}
get fontAtlas() {
return this._fontAtlas;
}
setProps(props = {}) {
VALID_PROPS.forEach(prop => {
if (prop in props) {
this.props[prop] = props[prop];
}
});
// update cache key
const oldKey = this._key;
this._key = this._getKey();
const charSet = getNewChars(this._key, this.props.characterSet);
const cachedFontAtlas = cache.get(this._key);
// if a fontAtlas associated with the new settings is cached and
// there are no new chars
if (cachedFontAtlas && charSet.length === 0) {
// update texture with cached fontAtlas
if (this._key !== oldKey) {
this._updateTexture(cachedFontAtlas);
}
return;
}
// update fontAtlas with new settings
const fontAtlas = this._generateFontAtlas(this._key, charSet, cachedFontAtlas);
this._fontAtlas = fontAtlas;
this._updateTexture(fontAtlas);
// update cache
cache.set(this._key, fontAtlas);
}
_updateTexture({ data: canvas }) {
this._texture = new THREE.CanvasTexture(canvas);
this._texture.wrapS = THREE.ClampToEdgeWrapping;
this._texture.wrapT = THREE.ClampToEdgeWrapping;
this._texture.minFilter = THREE.LinearFilter;
this._texture.flipY = false;
this._texture.needUpdate = true;
}
_generateFontAtlas(key, characterSet, cachedFontAtlas) {
const { fontFamily, fontWeight, fontSize, buffer, sdf, radius, cutoff } = this.props;
let canvas = cachedFontAtlas && cachedFontAtlas.data;
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = MAX_CANVAS_WIDTH;
}
const ctx = canvas.getContext('2d');
setTextStyle(ctx, fontFamily, fontSize, fontWeight);
// 1. build mapping
const { mapping, canvasHeight, xOffset, yOffset } = buildMapping(
Object.assign(
{
getFontWidth: char => ctx.measureText(char).width,
fontHeight: fontSize * HEIGHT_SCALE,
buffer,
characterSet,
maxCanvasWidth: MAX_CANVAS_WIDTH
},
cachedFontAtlas && {
mapping: cachedFontAtlas.mapping,
xOffset: cachedFontAtlas.xOffset,
yOffset: cachedFontAtlas.yOffset
}
)
);
// 2. update canvas
// copy old canvas data to new canvas only when height changed
if (canvas.height !== canvasHeight) {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
canvas.height = canvasHeight;
ctx.putImageData(imageData, 0, 0);
}
setTextStyle(ctx, fontFamily, fontSize, fontWeight);
// 3. layout characters
if (sdf) {
const tinySDF = new TinySDF(fontSize, buffer, radius, cutoff, fontFamily, fontWeight);
// used to store distance values from tinySDF
// tinySDF.size equals `fontSize + buffer * 2`
const imageData = ctx.getImageData(0, 0, tinySDF.size, tinySDF.size);
for (const char of characterSet) {
populateAlphaChannel(tinySDF.draw(char), imageData);
ctx.putImageData(imageData, mapping[char].x - buffer, mapping[char].y - buffer);
}
} else {
for (const char of characterSet) {
ctx.fillText(char, mapping[char].x, mapping[char].y + fontSize * BASELINE_SCALE);
}
}
return {
xOffset,
yOffset,
mapping,
data: canvas,
width: canvas.width,
height: canvas.height
};
}
_getKey() {
const { fontFamily, fontWeight, fontSize, buffer, sdf, radius, cutoff } = this.props;
if (sdf) {
return `${fontFamily} ${fontWeight} ${fontSize} ${buffer} ${radius} ${cutoff}`;
}
return `${fontFamily} ${fontWeight} ${fontSize} ${buffer}`;
}
}

View File

@ -0,0 +1,71 @@
/**
* LRU Cache class with limit
*
* Update order for each get/set operation
* Delete oldest when reach given limit
*/
export default class LRUCache {
constructor(limit = 5) {
this.limit = limit;
this.clear();
}
clear() {
this._cache = {};
// access/update order, first item is oldest, last item is newest
this._order = [];
}
get(key) {
const value = this._cache[key];
if (value) {
// update order
this._deleteOrder(key);
this._appendOrder(key);
}
return value;
}
set(key, value) {
if (!this._cache[key]) {
// if reach limit, delete the oldest
if (Object.keys(this._cache).length === this.limit) {
this.delete(this._order[0]);
}
this._cache[key] = value;
this._appendOrder(key);
} else {
// if found in cache, delete the old one, insert new one to the first of list
this.delete(key);
this._cache[key] = value;
this._appendOrder(key);
}
}
delete(key) {
const value = this._cache[key];
if (value) {
this._deleteCache(key);
this._deleteOrder(key);
}
}
_deleteCache(key) {
delete this._cache[key];
}
_deleteOrder(key) {
const index = this._order.findIndex(o => o === key);
if (index >= 0) {
this._order.splice(index, 1);
}
}
_appendOrder(key) {
this._order.push(key);
}
}

View File

@ -1,181 +0,0 @@
import { getJSON } from '../../../util/ajax';
import EventEmitter from 'wolfy87-eventemitter';
import Global from '../../../global';
// const Space = 1;
const metrics = {
buffer: 3,
family: 'ios9',
size: 24
};
export default function TextBuffer(layerData, style) {
EventEmitter.call(this);
const attributes = {
originPoints: [],
textSizes: [],
textOffsets: [],
colors: [],
textureElements: []
};
const { textOffset = [ 0, 0 ] } = style;
const chars = [];
const textChars = {};
layerData.forEach(element => {
let text = element.shape || '';
text = text.toString();
for (let j = 0; j < text.length; j++) {
const code = text.charCodeAt(j);
textChars[text] = 0;
if (chars.indexOf(code) === -1) {
chars.push(text.charCodeAt(j));
}
}
});
loadTextInfo(chars, (chars, texture) => {
layerData.forEach(element => {
const size = element.size;
const pos = layerData.coordinates;
const pen = { x: textOffset[0], y: textOffset[1] };
let text = element.shape || '';
text = text.toString();
for (let i = 0; i < text.length; i++) {
const color = element.color;
drawGlyph(chars, pos, text[i], pen, size, attributes.colors, attributes.textureElements, attributes.originPoints, attributes.textSizes, attributes.textOffsets, color);
}
this.emit('completed', { attributes, texture });
});
});
}
function loadTextInfo(chars, done) {
getJSON({
url: `${Global.sdfHomeUrl}/getsdfdata?chars=${chars.join('|')}`
}, (e, info) => {
loadTextTexture(info.url, texture => {
done(info.info, texture);
});
});
}
function loadTextTexture(url, cb) {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => {
const textTexture = this._creatTexture(img);
cb(textTexture);
};
img.src = url;
}
/**
* 计算每个标注词语的位置
* @param {*} chars 文本信息
* @param {*} pos 文字三维空间坐标
* @param {*} text 字符
* @param {*} pen 字符在词语的偏移量
* @param {*} size 字体大小
* @param {*} colors 颜色
* @param {*} textureElements 纹理坐标
* @param {*} originPoints 初始位置数据
* @param {*} textSizes 文字大小数组
* @param {*} textOffsets 字体偏移量数据
* @param {*} color 文字颜色
*/
function drawGlyph(chars, pos, text, pen, size, colors, textureElements, originPoints, textSizes, textOffsets, color) {
const chr = text.charCodeAt(0);
const metric = chars[chr];
if (!metric) return;
const scale = size / metrics.size;
let width = metric[0];
let height = metric[1];
const posX = metric[5];
const posY = metric[6];
const buffer = metrics.buffer;
if (width > 0 && height > 0) {
width += buffer * 2;
height += buffer * 2;
const originX = 0;
const originY = 0;
const offsetX = pen.x;
const offsetY = pen.y;
originPoints.push(
pos[0] + originX, pos[1] + originY, 0,
pos[0] + originX, pos[1] + originY, 0,
pos[0] + originX, pos[1] + originY, 0,
pos[0] + originX, pos[1] + originY, 0,
pos[0] + originX, pos[1] + originY, 0,
pos[0] + originX, pos[1] + originY, 0,
);
const bx = 0;
const by = metrics.size / 2 + buffer;
textSizes.push(
((bx - buffer + width) * scale), (height - by) * scale,
((bx - buffer) * scale), (height - by) * scale,
((bx - buffer) * scale), -by * scale,
((bx - buffer + width) * scale), (height - by) * scale,
((bx - buffer) * scale), -by * scale,
((bx - buffer + width) * scale), -by * scale,
);
textOffsets.push(
offsetX, offsetY,
offsetX, offsetY,
offsetX, offsetY,
offsetX, offsetY,
offsetX, offsetY,
offsetX, offsetY,
);
colors.push(
...color,
...color,
...color,
...color,
...color,
...color,
);
textureElements.push(
posX + width, posY,
posX, posY,
posX, posY + height,
posX + width, posY,
posX, posY + height,
posX + width, posY + height
);
}
pen.x = pen.x + size * 1.8;
}
// function measureText(text, size) {
// const dimensions = {
// advance: 0
// };
// const metrics = this.metrics;
// const scale = size / metrics.size;
// for (let i = 0; i < text.length; i++) {
// const code = text.charCodeAt(i);
// const horiAdvance = metrics.chars[code][4];
// dimensions.advance += (horiAdvance + Space) * scale;
// }
// return dimensions;
// }
// function creatTexture(image) {
// this.bufferStruct.textSize = [ image.width, image.height ];
// const texture = new THREE.Texture(image);
// texture.minFilter = THREE.LinearFilter;
// texture.magFilter = THREE.ClampToEdgeWrapping;
// texture.needsUpdate = true;
// return texture;
// }

View File

@ -15,17 +15,16 @@ export class RasterBuffer extends BufferBase {
];
const imgPosUv = [ 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0 ];
const size = this.get('size');
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');
const colorImageData = this.getColorRamp(colors);
const colorTexture = this._getTexture(colorImageData);
const colorTexture = this._getTexture(colorImageData); // 颜色纹理
this.bufferStruct.position = positions;
this.bufferStruct.uv = imgPosUv;
this.bufferStruct.u_raster = texture;//
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(width, height, size, this.bufferStruct.u_extent);
const attributes = {
@ -68,16 +67,23 @@ export class RasterBuffer extends BufferBase {
return new ImageData(data, 16, 16);
}
/**
* 颜色纹理
* @param {*} image 颜色图片
* @return {texture} texture
*/
_getTexture(image) {
const texture1 = new THREE.Texture(image);
texture1.magFilter = THREE.LinearFilter;
texture1.minFilter = THREE.LinearFilter;
texture1.format = THREE.RGBAFormat;
texture1.type = THREE.UnsignedByteType;
texture1.generateMipmaps = true;
texture1.needsUpdate = true;
return texture1;
}
_buildTriangles(width, height, size = 1, extent) {
_buildTriangles(width, height, size = 2, extent) {
// const extent = [ 73.482190241, 3.82501784112, 135.106618732, 57.6300459963 ]
const indices = [];
const vertices = [];

View File

@ -1,276 +0,0 @@
import BufferBase from './bufferBase';
import { getJSON } from '../../util/ajax';
import * as THREE from '../../core/three';
import TinySDF from '@mapbox/tiny-sdf';
import Global from '../../global';
const Space = 1;
export default class TextBuffer extends BufferBase {
geometryBuffer() {
this.metrics = {
buffer: 3,
family: 'ios9',
size: 24
};
const layerData = this.get('layerData');
const { textOffset = [ 0, 0 ] } = this.get('style');
const chars = [];
const textChars = {};
layerData.forEach(element => {
let text = element.shape || '';
text = text.toString();
for (let j = 0; j < text.length; j++) {
const code = text.charCodeAt(j);
textChars[text] = 0;
if (chars.indexOf(code) === -1) {
chars.push(text.charCodeAt(j));
}
}
});
const sdfTexture = this._updateSdf(Object.keys(textChars).join(''));
this.sdfTexture = sdfTexture;
this._loadTextInfo(chars);
this.on('SourceLoaded', () => {
const textureElements = [];
const colors = [];
const originPoints = [];
const textSizes = [];
const textOffsets = [];
layerData.forEach(element => {
const size = element.size;
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 || '';
text = text.toString();
for (let i = 0; i < text.length; i++) {
const color = element.color;
this._drawGlyph(pos, text[i], pen, size, colors, textureElements, originPoints, textSizes, textOffsets, color);
}
});
this.bufferStruct.style = layerData;
this.attributes = {
originPoints,
textSizes,
textOffsets,
colors,
textureElements
};
this.emit('completed');
});
}
_loadTextInfo(chars) {
getJSON({
url: `${Global.sdfHomeUrl}/getsdfdata?chars=${chars.join('|')}`
}, (e, info) => {
this.metrics.chars = info.info;
this._loadTextTexture(info.url);
});
}
_loadTextTexture(url) {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => {
this.bufferStruct.textTexture = this._creatTexture(this.sdfTexture.texure);
this.emit('SourceLoaded');
};
img.src = url;
}
/**
* 计算每个标注词语的位置
* @param {*} pos 文字三维空间坐标
* @param {*} text 字符
* @param {*} pen 字符在词语的偏移量
* @param {*} size 字体大小
* @param {*} colors 颜色
* @param {*} textureElements 纹理坐标
* @param {*} originPoints 初始位置数据
* @param {*} textSizes 文字大小数组
* @param {*} textOffsets 字体偏移量数据
* @param {*} color 文字颜色
*/
_drawGlyph(pos, text, pen, size, colors, textureElements, originPoints, textSizes, textOffsets, color) {
const metrics = this.metrics;
const chr = text.charCodeAt(0);
const metric = metrics.chars[chr];
if (!metric) return;
const info = this.sdfTexture.info;
const { x, y } = info[text];
const scale = size / metrics.size;
let width = 24; // metric[0];
let height = 24;// metric[1];
// const horiBearingX = metric[2];
// const horiBearingY = metric[3];
// const horiAdvance = metric[4];
// const posX = metric[5];
// const posY = metric[6];
const posX = x;
const posY = y;
const buffer = metrics.buffer;
if (width > 0 && height > 0) {
width += buffer * 2;
height += buffer * 2;
// Add a quad (= two triangles) per glyph.
// const originX = (horiBearingX - buffer + width / 2) * scale;
// const originY = -(height - horiBearingY) * scale;
const originX = 0;
const originY = 0;
// const offsetWidth = width / 2 * scale / (1.0 - horiBearingX * 1.5 / horiAdvance);
// const offsetHeight = (horiAdvance / 2) * scale;
// const offsetWidth = width/2 * scale;
// const offsetHeight = height / 2 * scale;
// const offsetHeight = height * scale;
const offsetX = pen.x;
const offsetY = pen.y;
originPoints.push(
pos[0] + originX, pos[1] + originY, 0,
pos[0] + originX, pos[1] + originY, 0,
pos[0] + originX, pos[1] + originY, 0,
pos[0] + originX, pos[1] + originY, 0,
pos[0] + originX, pos[1] + originY, 0,
pos[0] + originX, pos[1] + originY, 0,
);
// textSizes.push(
// offsetWidth, offsetHeight,
// -offsetWidth, offsetHeight,
// -offsetWidth, -offsetHeight,
// offsetWidth, offsetHeight,
// -offsetWidth, -offsetHeight,
// offsetWidth, -offsetHeight,
// );
const bx = 0;
const by = metrics.size / 2 + buffer;
textSizes.push(
((bx - buffer + width) * scale), (height - by) * scale,
((bx - buffer) * scale), (height - by) * scale,
((bx - buffer) * scale), -by * scale,
((bx - buffer + width) * scale), (height - by) * scale,
((bx - buffer) * scale), -by * scale,
((bx - buffer + width) * scale), -by * scale,
);
textOffsets.push(
offsetX, offsetY,
offsetX, offsetY,
offsetX, offsetY,
offsetX, offsetY,
offsetX, offsetY,
offsetX, offsetY,
);
colors.push(
...color,
...color,
...color,
...color,
...color,
...color,
);
textureElements.push(
posX + width, posY,
posX, posY,
posX, posY + height,
posX + width, posY,
posX, posY + height,
posX + width, posY + height
);
}
// pen.x = pen.x + (horiAdvance + Space) * scale;
pen.x = pen.x + size * 1.8;
}
_measureText(text, size) {
const dimensions = {
advance: 0
};
const metrics = this.metrics;
const scale = size / metrics.size;
for (let i = 0; i < text.length; i++) {
const code = text.charCodeAt(i);
const horiAdvance = metrics.chars[code][4];
dimensions.advance += (horiAdvance + Space) * scale;
}
return dimensions;
}
_creatTexture(image) {
this.bufferStruct.textSize = [ image.width, image.height ];
const texture = new THREE.Texture(image);
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.ClampToEdgeWrapping;
texture.needsUpdate = true;
return texture;
}
_updateSdf(chars) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const sdfs = {};
const fontSize = 24;
const fontWeight = 100;
const buffer = fontSize / 8;
const radius = fontSize / 3;
const canvasSize = Math.floor(Math.pow(chars.length, 0.5)) * (fontSize + buffer + radius);
canvas.width = canvasSize;
canvas.height = canvasSize;
ctx.clearRect(0, 0, canvas.width, canvas.height);
const sdf = new TinySDF(fontSize, buffer, radius, null, null, fontWeight);
for (let y = 0, i = 0; y + sdf.size <= canvas.height && i < chars.length; y += sdf.size) {
for (let x = 0; x + sdf.size <= canvas.width && i < chars.length; x += sdf.size) {
ctx.putImageData(this._makeRGBAImageData(ctx, sdf.draw(chars[i]), sdf.size), x, y);
sdfs[chars[i]] = { x, y };
i++;
}
}
return {
info: sdfs,
texure: canvas
};
}
_makeRGBAImageData(ctx, alphaChannel, size) {
const imageData = ctx.createImageData(size, size);
const data = imageData.data;
for (let i = 0; i < alphaChannel.length; i++) {
data[4 * i + 0] = alphaChannel[i];
data[4 * i + 1] = alphaChannel[i];
data[4 * i + 2] = alphaChannel[i];
data[4 * i + 3] = 255;
}
return imageData;
}
}

View File

@ -19,18 +19,18 @@ export default function extrudePolygon(points, extrude) {
const flattengeo = earcut.flatten(points);
const positions = [];
let cells = [];
const { dimensions } = flattengeo;
const triangles = earcut(flattengeo.vertices, flattengeo.holes, flattengeo.dimensions);
cells = triangles;
const pointCount = flattengeo.vertices.length / 3;
const pointCount = flattengeo.vertices.length / dimensions;
const { vertices } = flattengeo;
extrude ? full() : flat();
function flat() {
for (let i = 0; i < pointCount; i++) {
positions.push([ vertices[ i * 3 ], vertices[i * 3 + 1 ], 0 ]);
positions.push([ vertices[ i * dimensions ], vertices[i * dimensions + 1 ], 0 ]);
}
}
function full() {
@ -41,10 +41,10 @@ export default function extrudePolygon(points, extrude) {
// 顶部坐标
for (let i = 0; i < pointCount; i++) {
positions.push([ vertices[ i * 3 ], vertices[i * 3 + 1 ], 1 ]);
positions.push([ vertices[ i * dimensions ], vertices[i * dimensions + 1 ], 1 ]);
}
for (let i = 0; i < pointCount; i++) {
positions.push([ vertices[ i * 3 ], vertices[i * 3 + 1 ], 0 ]);
positions.push([ vertices[ i * dimensions ], vertices[i * dimensions + 1 ], 0 ]);
}
for (let i = 0; i < n; i++) {
if (i === (n - 1)) {

View File

@ -1,62 +0,0 @@
const geom = {
point: {
symbol: [ 'circle', 'hexagon', 'triangle', 'diamond' ],
native: {
buffer: '',
geometry: 'PointGeometry',
material: 'PointMaterial'
},
line: {
buffer: 'PointBuffer',
geometry: 'PolygonLine',
material: 'MeshlineMaterial'
},
fill: {
buffer: 'PointBuffer',
geometry: 'PolygonGeometry',
material: 'PolygonMaterial'
},
extrude: {
buffer: 'PointBuffer',
geometry: 'PolygonGeometry',
material: 'PolygonMaterial'
},
extrudeline: {
buffer: 'PointBuffer',
geometry: 'PolygonLine',
material: 'MeshlineMaterial'
},
pointGrid: {
buffer: 'pointGrid',
geometry: 'PolygonLine',
material: 'MeshlineMaterial'
}
},
line: {
shape: [ 'native' ]
},
polygon: {
line: {
buffer: 'polygonLineBuffer',
geometry: 'PolygonLine',
material: 'MeshlineMaterial'
},
fill: {
buffer: 'PolygonBuffer',
geometry: 'PolygonGeometry',
material: 'PolygonMaterial'
},
extrude: {
buffer: 'PolygonBuffer',
geometry: 'PolygonGeometry',
material: 'PolygonMaterial'
},
extrudeline: {
buffer: 'polygonLineBuffer',
geometry: 'PolygonLine',
material: 'MeshlineMaterial'
}
}
};
export default geom;

View File

@ -1,110 +0,0 @@
import { UniformSemantic, DataType, RenderState, BlendFunc } from '@ali/r3-base';
import { Material, RenderTechnique } from '@ali/r3-material';
export function ArclineMaterial() {
const tech = requireGeometryTechnique();
const newMtl = new Material('MeshlineTech_mtl');
newMtl.technique = tech;
newMtl.setValue('segmentNumber', 50.0);
return newMtl;
}
function requireGeometryTechnique() {
// 顶点着色器
const VERT_SHADER = `
precision mediump float;
attribute vec3 a_position;
attribute vec3 a_color;
attribute vec4 a_instance;
uniform mat4 matModelViewProjection;
uniform float segmentNumber;
varying vec3 v_color;
float getSegmentRatio(float index) {
return smoothstep(0.0, 1.0, index / (segmentNumber - 1.0));
}
float paraboloid(vec2 source, vec2 target, float ratio) {
vec2 x = mix(source, target, ratio);
vec2 center = mix(source, target, 0.5);
float dSourceCenter = distance(source, center);
float dXCenter = distance(x, center);
return (dSourceCenter + dXCenter) * (dSourceCenter - dXCenter)*0.6;
}
vec3 getPos(vec2 source, vec2 target, float height, float segmentRatio) {
return vec3(
mix(source, target, segmentRatio),
sqrt(max(0.0, height))
);
}
void main() {
vec2 source = a_instance.rg;
vec2 target = a_instance.ba;
float segmentIndex = a_position.x;
float segmentRatio = getSegmentRatio(segmentIndex);
vec3 c1 = vec3(0.929,0.972,0.917);
vec3 c2 = vec3(0.062,0.325,0.603);
v_color = mix(c1, c2, segmentRatio);
float height = paraboloid(source, target, segmentRatio);
vec3 position = getPos(source,target,height,segmentRatio);
gl_Position = matModelViewProjection * vec4(position, 1.0);
}
`;
// 片元着色器
const FRAG_SHADER = `
precision mediump float;
varying vec3 v_color;
void main() {
gl_FragColor = vec4(v_color,1.0);
}
`;
// Technique 配置信息
const cfg = {
attributes: {
a_position: {
name: 'a_position',
semantic: 'POSITION',
type: DataType.FLOAT_VEC3
},
a_color: {
name: 'a_color',
semantic: 'COLOR',
type: DataType.FLOAT_VEC3
},
a_instance: {
name: 'a_instance',
semantic: 'INSPOS',
type: DataType.FLOAT_VEC4
}
},
uniforms: {
matModelViewProjection: {
name: 'matModelViewProjection',
semantic: UniformSemantic.MODELVIEWPROJECTION,
type: DataType.FLOAT_MAT4
},
segmentNumber: {
name: 'segmentNumber',
type: DataType.FLOAT
}
}
};
// 创建 Technique
const tech = new RenderTechnique('MeshlineTech');
tech.states = {
enable: [ RenderState.BLEND ],
functions: { blendFunc: [ BlendFunc.SRC_ALPHA, BlendFunc.ONE_MINUS_SRC_ALPHA ] }//
};
tech.isValid = true;
tech.uniforms = cfg.uniforms;
tech.attributes = cfg.attributes;
tech.vertexShader = VERT_SHADER;
tech.fragmentShader = FRAG_SHADER;
return tech;
}

View File

@ -0,0 +1,16 @@
import Material from './material';
import { getModule, wrapUniforms } from '../../util/shaderModule';
import merge from '@antv/util/lib/deep-mix';
export default class CircleMaterial extends Material {
constructor(_uniforms, _defines, parameters) {
super(parameters);
const { vs, fs, uniforms } = getModule('circle');
this.uniforms = wrapUniforms(merge(uniforms, _uniforms));
this.defines = _defines;
this.type = 'CircleMaterial';
this.vertexShader = vs;
this.fragmentShader = fs;
this.transparent = true;
}
}

View File

@ -1,8 +1,5 @@
import grid_frag from '../shader/grid_frag.glsl';
import grid_vert from '../shader/grid_vert.glsl';
import Material from './material';
import { getModule } from '../../util/shaderModule';
export default class GridMaterial extends Material {
getDefaultParameters() {
return {
@ -11,7 +8,9 @@ export default class GridMaterial extends Material {
u_time: { value: 0 },
u_xOffset: { value: 0.01 },
u_yOffset: { value: 0.01 },
u_coverage: { value: 0.8 }
u_coverage: { value: 0.8 },
u_activeId: { value: 0 },
u_activeColor: { value: [ 1.0, 0, 0, 1.0 ] }
},
defines: {
@ -21,11 +20,12 @@ export default class GridMaterial extends Material {
constructor(_uniforms, _defines, parameters) {
super(parameters);
const { uniforms, defines } = this.getDefaultParameters();
const { vs, fs } = getModule('grid');
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.vertexShader = vs;
this.fragmentShader = fs;
this.transparent = true;
}
}

View File

@ -0,0 +1,58 @@
import * as THREE from '../../core/three';
import Material from './material';
import { getModule } from '../../util/shaderModule';
export class HeatmapColorizeMaterial extends Material {
getDefaultParameters() {
return {
uniforms: {
u_intensity: { value: 1.0 },
u_texture: { value: null },
u_rampColors: { value: 0 },
u_opacity: { value: 1 }
},
defines: {
}
};
}
constructor(_uniforms, _defines = {}, parameters) {
super(parameters);
const { uniforms, defines } = this.getDefaultParameters();
const { vs, fs } = getModule('heatmap_color');
this.uniforms = Object.assign(uniforms, this.setUniform(_uniforms));
this.type = 'HeatmapColorizeMaterial';
this.defines = Object.assign(defines, _defines);
this.vertexShader = vs;
this.fragmentShader = fs;
this.transparent = true;
}
}
export class HeatmapIntensityMaterial extends Material {
getDefaultParameters() {
return {
uniforms: {
u_intensity: { value: 10.0 },
u_zoom: { value: 4 },
u_radius: { value: 10 }
},
defines: {
}
};
}
constructor(_uniforms, _defines = {}, parameters) {
super(parameters);
const { uniforms, defines } = this.getDefaultParameters();
const { vs, fs } = getModule('heatmap_intensity');
this.uniforms = Object.assign(uniforms, this.setUniform(_uniforms));
this.type = 'heatmap_intensity';
this.defines = Object.assign(defines, _defines);
this.vertexShader = vs;
this.blending = THREE.AdditiveBlending;
this.fragmentShader = fs;
this.depthTest = false;
this.transparent = true;
}
}

View File

@ -1,8 +1,5 @@
import grid_frag from '../shader/hexagon_frag.glsl';
import grid_vert from '../shader/hexagon_vert.glsl';
import Material from './material';
import { getModule } from '../../util/shaderModule';
export default class hexagonMaterial extends Material {
getDefaultParameters() {
return {
@ -11,7 +8,9 @@ export default class hexagonMaterial extends Material {
u_time: { value: 0 },
u_radius: { value: 0.01 },
u_angle: { value: 0.01 },
u_coverage: { value: 0.8 }
u_coverage: { value: 0.8 },
u_activeId: { value: 0 },
u_activeColor: { value: [ 1.0, 0, 0, 1.0 ] }
},
defines: {
@ -21,11 +20,12 @@ export default class hexagonMaterial extends Material {
constructor(_uniforms, _defines, parameters) {
super(parameters);
const { uniforms, defines } = this.getDefaultParameters();
const { vs, fs } = getModule('hexagon');
this.uniforms = Object.assign(uniforms, this.setUniform(_uniforms));
this.type = 'hexagonMaterial';
this.defines = Object.assign(defines, _defines);
this.vertexShader = grid_vert;
this.fragmentShader = grid_frag;
this.vertexShader = vs;
this.fragmentShader = fs;
this.transparent = true;
}
}

Some files were not shown because too many files have changed in this diff Show More