feat(tile): add image tile layer

This commit is contained in:
thinkinggis 2019-04-18 22:28:17 +08:00
parent 6dd6c5dd01
commit ab11b98e54
15 changed files with 204 additions and 72 deletions

View File

@ -25,50 +25,34 @@
const scene = new L7.Scene({ const scene = new L7.Scene({
id: 'map', id: 'map',
mapStyle: 'dark', // 样式URL mapStyle: 'light', // 样式URL
center: [104.838088,34.075889 ], center: [104.838088,34.075889 ],
pitch: 0, pitch: 0,
zoom: 4.5, hash:true,
zoom: 3,
}); });
window.scene = scene; window.scene = scene;
scene.on('loaded', () => { scene.on('loaded', () => {
// https://gw.alipayobjects.com/os/basement_prod/24883cde-3352-4e53-af52-f6e59d4fe2c8.json scene.ImageTileLayer()
//tile https://gw.alipayobjects.com/os/basement_prod/c400bd4e-5b46-4769-b969-c1f09feaf908.json .source('http://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}')
$.getJSON('https://gw.alipayobjects.com/os/basement_prod/c400bd4e-5b46-4769-b969-c1f09feaf908.json', city => { .render();
city.type = "FeatureCollection";
city.features = city.features.map((item)=>{ $.getJSON('https://gw.alipayobjects.com/os/rmsportal/JToMOWvicvJOISZFCkEI.json', city => {
return { const citylayer = scene.PolygonLayer(
type: "Feature", {
properties:item.tags, zIndex:4
"geometry":{ }
"type": "Polygon", )
"coordinates":item.geometry
}
}
})
console.log(city.features[0]);
const citylayer = scene.PolygonLayer()
.source(city) .source(city)
.color('Code',["#FFF5B8","#FFDC7D","#FFAB5C","#F27049","#D42F31","#730D1C"]) .color('pm2_5_24h',["#FFF5B8","#FFDC7D","#FFAB5C","#F27049","#D42F31","#730D1C"])
.shape('fill') .shape('fill')
.active(true)
.style({ .style({
opacity: 1 opacity: 0.2
}) })
.render(); .render();
console.log(citylayer);
/**
const citylayer2 = scene.PolygonLayer()
.source(city)
.shape('line')
.color('#fff')
.style({
opacity: 1.0
})
.render();
**/
}); });
}); });

View File

@ -79,7 +79,7 @@
"prepublishOnly": "npm run build-lib && npm run dist", "prepublishOnly": "npm run build-lib && npm run dist",
"screenshot": "node ./bin/screenshot.js", "screenshot": "node ./bin/screenshot.js",
"start": "npm run dev", "start": "npm run dev",
"test": "torch --compile-opts ./.torch.compile.opts.js --compile --renderer --recursive test/unit", "test": "torch --compile-opts ./.torch.compile.opts.js --compile --renderer --recursive test/unit",
"test-all": "npm run test && npm run test-bugs", "test-all": "npm run test && npm run test-bugs",
"test-bugs": "torch --compile --renderer --recursive test/bugs", "test-bugs": "torch --compile --renderer --recursive test/bugs",
"test-bugs-live": "torch --compile --interactive --watch --recursive test/bugs", "test-bugs-live": "torch --compile --interactive --watch --recursive test/bugs",

View File

@ -25,9 +25,10 @@ export default class Scene extends Base {
_initEngine(mapContainer) { _initEngine(mapContainer) {
this._engine = new Engine(mapContainer, this); this._engine = new Engine(mapContainer, this);
this.registerMapEvent(); // this.registerMapEvent();
// this.workerPool = new WorkerPool(); // this.workerPool = new WorkerPool();
compileBuiltinModules(); compileBuiltinModules();
this._engine.run();
} }
// 为pickup场景添加 object 对象 // 为pickup场景添加 object 对象
addPickMesh(object) { addPickMesh(object) {

View File

@ -33,7 +33,7 @@ export default class Source extends Base {
// 数据转换 统计,聚合,分类 // 数据转换 统计,聚合,分类
this._executeTrans(); this._executeTrans();
// 坐标转换 // 坐标转换
// this._projectCoords(); this._projectCoords();
} }
setData(data, cfg = {}) { setData(data, cfg = {}) {
Object.assign(this._attrs, cfg); Object.assign(this._attrs, cfg);

View File

@ -9,7 +9,8 @@ export default function ImageMaterial(options) {
}, },
vertexShader: vs, vertexShader: vs,
fragmentShader: fs, fragmentShader: fs,
transparent: true transparent: true,
depthTest: false
}); });
return material; return material;
} }

View File

@ -5,6 +5,8 @@ import LineLayer from './lineLayer';
import ImageLayer from './imageLayer'; import ImageLayer from './imageLayer';
import RasterLayer from './rasterLayer'; import RasterLayer from './rasterLayer';
import HeatmapLayer from './heatmapLayer'; import HeatmapLayer from './heatmapLayer';
import TileLayer from './tile/tileLayer';
import ImageTileLayer from './tile/imageTileLayer';
registerLayer('PolygonLayer', PolygonLayer); registerLayer('PolygonLayer', PolygonLayer);
registerLayer('PointLayer', PointLayer); registerLayer('PointLayer', PointLayer);
@ -12,6 +14,8 @@ registerLayer('LineLayer', LineLayer);
registerLayer('ImageLayer', ImageLayer); registerLayer('ImageLayer', ImageLayer);
registerLayer('RasterLayer', RasterLayer); registerLayer('RasterLayer', RasterLayer);
registerLayer('HeatmapLayer', HeatmapLayer); registerLayer('HeatmapLayer', HeatmapLayer);
registerLayer('TileLayer', TileLayer);
registerLayer('ImageTileLayer', ImageTileLayer);
export { LAYER_MAP } from './factory'; export { LAYER_MAP } from './factory';
export { registerLayer }; export { registerLayer };

View File

@ -1,13 +1,13 @@
import * as THREE from '../../../core/three'; import * as THREE from '../../../core/three';
import ImageMaterial from '../../../geom/material/imageMaterial'; import ImageMaterial from '../../../geom/material/imageMaterial';
export default function DrawImage(attributes, style) { export default function DrawImage(attributes, style) {
this.geometry = new THREE.BufferGeometry(); const geometry = new THREE.BufferGeometry();
this.geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3));
this.geometry.addAttribute('uv', new THREE.Float32BufferAttribute(attributes.uvs, 2)); geometry.addAttribute('uv', new THREE.Float32BufferAttribute(attributes.uvs, 2));
const { opacity } = style; const { opacity } = style;
const material = new ImageMaterial({ const material = new ImageMaterial({
u_texture: attributes.texture, u_texture: attributes.texture,
u_opacity: opacity u_opacity: opacity
}); });
return new THREE.Mesh(this.geometry, material); return new THREE.Mesh(geometry, material);
} }

View File

@ -1,6 +1,6 @@
import * as THREE from '../../../core/three'; import * as THREE from '../../../core/three';
// import PolygonMaterial from '../../../geom/material/polygonMaterial'; import PolygonMaterial from '../../../geom/material/polygonMaterial';
import TileMaterial from '../../../geom/material/tile/polygon'; // import TileMaterial from '../../../geom/material/tile/polygon';
export default function DrawPolygonFill(attributes, style) { export default function DrawPolygonFill(attributes, style) {
const { opacity, activeColor } = style; const { opacity, activeColor } = style;
@ -15,7 +15,7 @@ export default function DrawPolygonFill(attributes, style) {
// }, { // }, {
// SHAPE: false // SHAPE: false
// }); // });
const material = new TileMaterial({ const material = new PolygonMaterial({
u_opacity: opacity, u_opacity: opacity,
u_activeColor: activeColor u_activeColor: activeColor
}, { }, {

View File

@ -7,7 +7,7 @@ export default class ImageTile extends Tile {
// Making this asynchronous really speeds up the LOD framerate // Making this asynchronous really speeds up the LOD framerate
setTimeout(() => { setTimeout(() => {
if (!this._mesh) { if (!this._mesh) {
this._mesh = this._createMesh(); // this._mesh = this._createMesh();
this._requestTile(); this._requestTile();
} }
}, 0); }, 0);
@ -20,7 +20,6 @@ export default class ImageTile extends Tile {
}; };
const url = this._getTileURL(urlParams); const url = this._getTileURL(urlParams);
const image = document.createElement('img'); const image = document.createElement('img');
image.addEventListener('load', () => { image.addEventListener('load', () => {
@ -40,11 +39,11 @@ export default class ImageTile extends Tile {
this._image = image; this._image = image;
} }
_getBufferData(images) { _getBufferData(images) {
const NW = this._tileBounds.getTopLeft().to; const NW = this._tileBounds.getTopLeft();
const SE = this._tileBounds.getBottomRight(); const SE = this._tileBounds.getBottomRight();
const coordinate = [[ NW.x, NW.y ], [ SE.x, SE.y ]]; const coordinates = [[ NW.x, NW.y, 0 ], [ SE.x, SE.y, 0 ]];
return [{ return [{
coordinate, coordinates,
images images
}]; }];
} }
@ -56,10 +55,10 @@ export default class ImageTile extends Tile {
const buffer = new ImageBuffer({ const buffer = new ImageBuffer({
layerData: this._layerData layerData: this._layerData
}); });
buffer.attributes.texture = buffer.texture;
const style = this.get('styleOptions'); const style = this.layer.get('styleOptions');
const mesh = DrawImage(buffer.attributes, style); const mesh = DrawImage(buffer.attributes, style);
this.Object3D.push(mesh); this.Object3D.add(mesh);
return this.Object3D; return this.Object3D;
} }
_abortRequest() { _abortRequest() {

View File

@ -0,0 +1,9 @@
import TileLayer from './tileLayer';
import ImageTile from './imageTile';
export default class ImageTileLayer extends TileLayer {
_createTile(key, layer) {
return new ImageTile(key, this.url, layer);
}
}

View File

@ -1,13 +1,13 @@
import * as THREE from '../../core/three'; import * as THREE from '../../core/three';
import { toLngLatBounds, toBounds } from '@antv/geo-coord'; import { toLngLatBounds, toBounds } from '@antv/geo-coord';
import { sphericalMercator } from '@antv/geo-coord/lib/geo/projection/spherical-mercator';
const r2d = 180 / Math.PI; const r2d = 180 / Math.PI;
const tileURLRegex = /\{([zxy])\}/g; const tileURLRegex = /\{([zxy])\}/g;
export class Tile { export default class Tile {
constructor(layer, z, x, y) { constructor(key, url, layer) {
this.layer = layer; this.layer = layer;
this._tile = [ x, y, z ]; this._tile = key.split('_').map(v => v * 1);
this._path = url;
this._tileLnglatBounds = this._tileLnglatBounds(this._tile); this._tileLnglatBounds = this._tileLnglatBounds(this._tile);
this._tileBounds = this._tileBounds(this._tileLnglatBounds); this._tileBounds = this._tileBounds(this._tileLnglatBounds);
@ -16,6 +16,7 @@ export class Tile {
this._centerLnglat = this._tileLnglatBounds.getCenter(); this._centerLnglat = this._tileLnglatBounds.getCenter();
this.Object3D = new THREE.Object3D(); this.Object3D = new THREE.Object3D();
this.requestTileAsync();
} }
@ -33,16 +34,19 @@ export class Tile {
} }
// 经纬度范围转瓦片范围 // 经纬度范围转瓦片范围
_tileBounds(lnglatBound) { _tileBounds(lnglatBound) {
const nw = sphericalMercator.project(lnglatBound.getTopLeft()); const ne = this.layer.scene.project([ lnglatBound.getNorthWest().lng, lnglatBound.getNorthEast().lat ]);
const se = sphericalMercator.project(lnglatBound.getBottomRight()); const sw = this.layer.scene.project([ lnglatBound.getSouthEast().lng, lnglatBound.getSouthWest().lat ]);
return toBounds(sw, ne);
return toBounds(nw, se);
} }
getMesh() {
return this.Object3D;
}
// Get tile bounds in WGS84 coordinates // Get tile bounds in WGS84 coordinates
_tileLnglatBounds(tile) { _tileLnglatBounds(tile) {
const e = this._tile2lon(tile[0] + 1, tile[2]); const e = this._tile2lng(tile[0] + 1, tile[2]);
const w = this._tile2lon(tile[0], tile[2]); const w = this._tile2lng(tile[0], tile[2]);
const s = this._tile2lat(tile[1] + 1, tile[2]); const s = this._tile2lat(tile[1] + 1, tile[2]);
const n = this._tile2lat(tile[1], tile[2]); const n = this._tile2lat(tile[1], tile[2]);
return toLngLatBounds([ w, n ], [ e, s ]); return toLngLatBounds([ w, n ], [ e, s ]);

View File

@ -1,21 +1,16 @@
import LRUCache from '../../util/lru-cache'; import LRUCache from '../../util/lru-cache';
export default class TileCache { export default class TileCache {
constructor(limit = 50) { constructor(limit = 500) {
this._cache = new LRUCache(limit); this._cache = new LRUCache(limit);
} }
getTile(z, x, y) { getTile(key) {
const key = this._generateKey(z, x, y);
return this._cache.get(key); return this._cache.get(key);
} }
setTile(tile, z, x, y) { setTile(tile, key) {
const key = this._generateKey(z, x, y);
this._cache.set(key, tile); this._cache.set(key, tile);
} }
_generateKey(z, x, y) {
return [ z, x, y ].join('_');
}
destory() { destory() {
this._cache.clear(); this._cache.clear();
} }

View File

@ -1,8 +1,117 @@
import Layer from '../core/layer'; import Layer from '../../core/layer';
export class TileLayer extends Layer { import * as THREE from '../../core/three';
import TileCache from './tileCache';
import { toLngLat } from '@antv/geo-coord';
import { epsg3857 } from '@antv/geo-coord/lib/geo/crs/crs-epsg3857';
export default class TileLayer extends Layer {
constructor(scene, cfg) {
super(scene, cfg);
this._tileCache = new TileCache();
this._crs = epsg3857;
this._tiles = new THREE.Object3D();
this._tileKeys = [];
this.tileList = [];
}
source(url) {
this.url = url;
return this;
}
render() {
this._initMapEvent();
this.draw();
}
draw() { draw() {
this._object3D.add(this._tiles);
this._calculateLOD();
} }
drawTile() { drawTile() {
} }
zoomchange(ev) {
super.zoomchange(ev);
this._calculateLOD();
}
dragend(ev) {
super.dragend(ev);
this._calculateLOD();
}
_calculateLOD() {
const viewPort = this.scene.getBounds().toBounds();
const SE = viewPort.getSouthEast();
const NW = viewPort.getNorthWest();
const zoom = Math.round(this.scene.getZoom()) - 1;
const NWPonint = this._crs.lngLatToPoint(toLngLat(NW.lng, NW.lat), zoom);
const SEPonint = this._crs.lngLatToPoint(toLngLat(SE.lng, SE.lat), zoom);
const minXY = NWPonint.divideBy(256).round();
const maxXY = SEPonint.divideBy(256).round();
// console.log(NW.lng, NW.lat, SE.lng, SE.lat, NWPonint, SEPonint);
let updateTileList = [];
this.tileList = [];
const halfx = Math.floor((maxXY.x - minXY.x) / 2) + 1;
const halfy = Math.floor((maxXY.y - minXY.y) / 2) + 1;
for (let i = minXY.x - halfx; i < maxXY.x + halfx; i++) {
for (let j = minXY.y - halfy; j < maxXY.y + halfy; j++) {
const key = [ i, j, zoom ].join('_');
this.tileList.push(key);
if (this._tileKeys.indexOf(key) === -1) {
updateTileList.push(key);
}
}
}
// 过滤掉已经存在的
// tileList = tileList.filter(tile => {
// })
updateTileList = updateTileList.sort((a, b) => {
const tile1 = a.split('_');
const tile2 = b.split('_');
const d1 = Math.pow((tile1[0] - halfx), 2) + Math.pow((tile1[1] - halfy));
const d2 = Math.pow((tile2[0] - halfy), 2) + Math.pow((tile2[1] - halfy));
return d1 - d2;
});
updateTileList.forEach(key => {
this._requestTile(key, this);
});
this._removeOutTiles();
}
_requestTile(key, layer) {
let tile = this._tileCache.getTile(key);
if (!tile) {
tile = this._createTile(key, layer);
const mesh = tile.getMesh();
mesh.name = key;
this._tileCache.setTile(tile, key);
this._tileKeys.push(key);
// this.scene._engine.update();
}
this._tiles.add(tile.getMesh());
this._tileKeys.push(key);
}
// 移除视野外的tile
_removeOutTiles() {
for (let i = this._tiles.children.length - 1; i >= 0; i--) {
const tile = this._tiles.children[i];
const key = tile.name;
if (this.tileList.indexOf(key) === -1) {
this._tiles.remove(tile);
}
this._tileKeys = [].concat(this.tileList);
}
}
_removeTiles() {
if (!this._tiles || !this._tiles.children) {
return;
}
for (let i = this._tiles.children.length - 1; i >= 0; i--) {
this._tiles.remove(this._tiles.children[i]);
}
}
_destroyTile() {
}
desttroy() {
}
} }

View File

@ -103,6 +103,7 @@ export default class GaodeMap extends Base {
} }
mixMap(scene) { mixMap(scene) {
const map = this.map; const map = this.map;
scene.project = GaodeMap.project;
scene.getZoom = () => { scene.getZoom = () => {
return map.getZoom(); return map.getZoom();
}; };

View File

@ -0,0 +1,25 @@
import { expect } from 'chai';
import {Scene} from '../../../../src/core/scene';
import TileLayer from '../../../../src/layer/tile/tileLayer';
describe('tile layer', function() {
const amapscript = document.createElement('script');
amapscript.type = 'text/javascript';
amapscript.src = 'https://webapi.amap.com/maps?v=1.4.8&key=15cd8a57710d40c9b7c0e3cc120f1200&plugin=Map3D';
document.body.appendChild(amapscript);
const div = document.createElement('div');
div.id = 'map';
div.style.cssText = 'width:500px;height:500px;position:absolute';
document.body.appendChild(div);
const scene = new Scene({
id: 'map',
mapStyle: 'light', // 样式URL
center: [ 120.19382669582967, 30.258134 ],
pitch: 0,
zoom: 2,
maxZoom: 20,
minZoom: 0
});
// const TileLayer = new TileLayer(null, {});
});