From ab11b98e54262a6ab1e6e7abe05172d04ed07e83 Mon Sep 17 00:00:00 2001 From: thinkinggis Date: Thu, 18 Apr 2019 22:28:17 +0800 Subject: [PATCH] feat(tile): add image tile layer --- demos/tile.html | 50 ++++------- package.json | 2 +- src/core/scene.js | 3 +- src/core/source.js | 2 +- src/geom/material/imageMaterial.js | 3 +- src/layer/index.js | 4 + src/layer/render/image/drawImage.js | 8 +- src/layer/render/polygon/drawFill.js | 6 +- src/layer/tile/imageTile.js | 15 ++-- src/layer/tile/imageTileLayer.js | 9 ++ src/layer/tile/tile.js | 24 ++--- src/layer/tile/tileCache.js | 11 +-- src/layer/tile/tileLayer.js | 113 +++++++++++++++++++++++- src/map/AMap.js | 1 + test/unit/layer/tile/tile-layer-spec.js | 25 ++++++ 15 files changed, 204 insertions(+), 72 deletions(-) create mode 100644 src/layer/tile/imageTileLayer.js create mode 100644 test/unit/layer/tile/tile-layer-spec.js diff --git a/demos/tile.html b/demos/tile.html index f0867c42a7..bfc4552833 100644 --- a/demos/tile.html +++ b/demos/tile.html @@ -25,50 +25,34 @@ const scene = new L7.Scene({ id: 'map', - mapStyle: 'dark', // 样式URL + mapStyle: 'light', // 样式URL center: [104.838088,34.075889 ], pitch: 0, - zoom: 4.5, + hash:true, + zoom: 3, }); window.scene = scene; scene.on('loaded', () => { - // https://gw.alipayobjects.com/os/basement_prod/24883cde-3352-4e53-af52-f6e59d4fe2c8.json - //tile https://gw.alipayobjects.com/os/basement_prod/c400bd4e-5b46-4769-b969-c1f09feaf908.json - $.getJSON('https://gw.alipayobjects.com/os/basement_prod/c400bd4e-5b46-4769-b969-c1f09feaf908.json', city => { - city.type = "FeatureCollection"; - city.features = city.features.map((item)=>{ - return { - type: "Feature", - properties:item.tags, - "geometry":{ - "type": "Polygon", - "coordinates":item.geometry - } - } - }) - - console.log(city.features[0]); - const citylayer = scene.PolygonLayer() + scene.ImageTileLayer() + .source('http://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}') + .render(); + + $.getJSON('https://gw.alipayobjects.com/os/rmsportal/JToMOWvicvJOISZFCkEI.json', city => { + const citylayer = scene.PolygonLayer( + { + zIndex:4 + } + ) .source(city) - .color('Code',["#FFF5B8","#FFDC7D","#FFAB5C","#F27049","#D42F31","#730D1C"]) + .color('pm2_5_24h',["#FFF5B8","#FFDC7D","#FFAB5C","#F27049","#D42F31","#730D1C"]) .shape('fill') - .active(true) .style({ - opacity: 1 + opacity: 0.2 }) .render(); - console.log(citylayer); - /** - const citylayer2 = scene.PolygonLayer() - .source(city) - .shape('line') - .color('#fff') - .style({ - opacity: 1.0 - }) - .render(); - **/ + + }); }); diff --git a/package.json b/package.json index 125dcb8168..ae5f234a50 100755 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "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", + "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", diff --git a/src/core/scene.js b/src/core/scene.js index 0789516806..637cf59690 100644 --- a/src/core/scene.js +++ b/src/core/scene.js @@ -25,9 +25,10 @@ export default class Scene extends Base { _initEngine(mapContainer) { this._engine = new Engine(mapContainer, this); - this.registerMapEvent(); + // this.registerMapEvent(); // this.workerPool = new WorkerPool(); compileBuiltinModules(); + this._engine.run(); } // 为pickup场景添加 object 对象 addPickMesh(object) { diff --git a/src/core/source.js b/src/core/source.js index 60f667c755..765636ea5d 100644 --- a/src/core/source.js +++ b/src/core/source.js @@ -33,7 +33,7 @@ export default class Source extends Base { // 数据转换 统计,聚合,分类 this._executeTrans(); // 坐标转换 - // this._projectCoords(); + this._projectCoords(); } setData(data, cfg = {}) { Object.assign(this._attrs, cfg); diff --git a/src/geom/material/imageMaterial.js b/src/geom/material/imageMaterial.js index d193576d7b..ecf950f498 100644 --- a/src/geom/material/imageMaterial.js +++ b/src/geom/material/imageMaterial.js @@ -9,7 +9,8 @@ export default function ImageMaterial(options) { }, vertexShader: vs, fragmentShader: fs, - transparent: true + transparent: true, + depthTest: false }); return material; } diff --git a/src/layer/index.js b/src/layer/index.js index 02e72edceb..8b33d8534e 100644 --- a/src/layer/index.js +++ b/src/layer/index.js @@ -5,6 +5,8 @@ import LineLayer from './lineLayer'; import ImageLayer from './imageLayer'; import RasterLayer from './rasterLayer'; import HeatmapLayer from './heatmapLayer'; +import TileLayer from './tile/tileLayer'; +import ImageTileLayer from './tile/imageTileLayer'; registerLayer('PolygonLayer', PolygonLayer); registerLayer('PointLayer', PointLayer); @@ -12,6 +14,8 @@ registerLayer('LineLayer', LineLayer); registerLayer('ImageLayer', ImageLayer); registerLayer('RasterLayer', RasterLayer); registerLayer('HeatmapLayer', HeatmapLayer); +registerLayer('TileLayer', TileLayer); +registerLayer('ImageTileLayer', ImageTileLayer); export { LAYER_MAP } from './factory'; export { registerLayer }; diff --git a/src/layer/render/image/drawImage.js b/src/layer/render/image/drawImage.js index 54a49d75a3..93330df82f 100644 --- a/src/layer/render/image/drawImage.js +++ b/src/layer/render/image/drawImage.js @@ -1,13 +1,13 @@ import * as THREE from '../../../core/three'; import ImageMaterial from '../../../geom/material/imageMaterial'; export default function DrawImage(attributes, style) { - this.geometry = new THREE.BufferGeometry(); - this.geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); - this.geometry.addAttribute('uv', new THREE.Float32BufferAttribute(attributes.uvs, 2)); + const geometry = new THREE.BufferGeometry(); + geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); + geometry.addAttribute('uv', new THREE.Float32BufferAttribute(attributes.uvs, 2)); const { opacity } = style; const material = new ImageMaterial({ u_texture: attributes.texture, u_opacity: opacity }); - return new THREE.Mesh(this.geometry, material); + return new THREE.Mesh(geometry, material); } diff --git a/src/layer/render/polygon/drawFill.js b/src/layer/render/polygon/drawFill.js index b273686929..d9981cbdbb 100644 --- a/src/layer/render/polygon/drawFill.js +++ b/src/layer/render/polygon/drawFill.js @@ -1,6 +1,6 @@ import * as THREE from '../../../core/three'; -// import PolygonMaterial from '../../../geom/material/polygonMaterial'; -import TileMaterial from '../../../geom/material/tile/polygon'; +import PolygonMaterial from '../../../geom/material/polygonMaterial'; +// import TileMaterial from '../../../geom/material/tile/polygon'; export default function DrawPolygonFill(attributes, style) { const { opacity, activeColor } = style; @@ -15,7 +15,7 @@ export default function DrawPolygonFill(attributes, style) { // }, { // SHAPE: false // }); - const material = new TileMaterial({ + const material = new PolygonMaterial({ u_opacity: opacity, u_activeColor: activeColor }, { diff --git a/src/layer/tile/imageTile.js b/src/layer/tile/imageTile.js index 3a3a8f6b46..d30e336385 100644 --- a/src/layer/tile/imageTile.js +++ b/src/layer/tile/imageTile.js @@ -7,7 +7,7 @@ export default class ImageTile extends Tile { // Making this asynchronous really speeds up the LOD framerate setTimeout(() => { if (!this._mesh) { - this._mesh = this._createMesh(); + // this._mesh = this._createMesh(); this._requestTile(); } }, 0); @@ -20,7 +20,6 @@ export default class ImageTile extends Tile { }; const url = this._getTileURL(urlParams); - const image = document.createElement('img'); image.addEventListener('load', () => { @@ -40,11 +39,11 @@ export default class ImageTile extends Tile { this._image = image; } _getBufferData(images) { - const NW = this._tileBounds.getTopLeft().to; + const NW = this._tileBounds.getTopLeft(); 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 [{ - coordinate, + coordinates, images }]; } @@ -56,10 +55,10 @@ export default class ImageTile extends Tile { const buffer = new ImageBuffer({ layerData: this._layerData }); - - const style = this.get('styleOptions'); + buffer.attributes.texture = buffer.texture; + const style = this.layer.get('styleOptions'); const mesh = DrawImage(buffer.attributes, style); - this.Object3D.push(mesh); + this.Object3D.add(mesh); return this.Object3D; } _abortRequest() { diff --git a/src/layer/tile/imageTileLayer.js b/src/layer/tile/imageTileLayer.js new file mode 100644 index 0000000000..5f39e39bf7 --- /dev/null +++ b/src/layer/tile/imageTileLayer.js @@ -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); + } + +} diff --git a/src/layer/tile/tile.js b/src/layer/tile/tile.js index 4502a5bb70..d96c8a69bf 100644 --- a/src/layer/tile/tile.js +++ b/src/layer/tile/tile.js @@ -1,13 +1,13 @@ import * as THREE from '../../core/three'; import { toLngLatBounds, toBounds } from '@antv/geo-coord'; -import { sphericalMercator } from '@antv/geo-coord/lib/geo/projection/spherical-mercator'; const r2d = 180 / Math.PI; const tileURLRegex = /\{([zxy])\}/g; -export class Tile { - constructor(layer, z, x, y) { +export default class Tile { + constructor(key, url, 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._tileBounds = this._tileBounds(this._tileLnglatBounds); @@ -16,6 +16,7 @@ export class Tile { this._centerLnglat = this._tileLnglatBounds.getCenter(); this.Object3D = new THREE.Object3D(); + this.requestTileAsync(); } @@ -33,16 +34,19 @@ export class Tile { } // 经纬度范围转瓦片范围 _tileBounds(lnglatBound) { - const nw = sphericalMercator.project(lnglatBound.getTopLeft()); - const se = sphericalMercator.project(lnglatBound.getBottomRight()); - - return toBounds(nw, se); + const ne = this.layer.scene.project([ lnglatBound.getNorthWest().lng, lnglatBound.getNorthEast().lat ]); + const sw = this.layer.scene.project([ lnglatBound.getSouthEast().lng, lnglatBound.getSouthWest().lat ]); + return toBounds(sw, ne); } + getMesh() { + return this.Object3D; + } + // Get tile bounds in WGS84 coordinates _tileLnglatBounds(tile) { - const e = this._tile2lon(tile[0] + 1, tile[2]); - const w = this._tile2lon(tile[0], tile[2]); + const e = this._tile2lng(tile[0] + 1, tile[2]); + const w = this._tile2lng(tile[0], tile[2]); const s = this._tile2lat(tile[1] + 1, tile[2]); const n = this._tile2lat(tile[1], tile[2]); return toLngLatBounds([ w, n ], [ e, s ]); diff --git a/src/layer/tile/tileCache.js b/src/layer/tile/tileCache.js index cd42e03cd1..a96260d16d 100644 --- a/src/layer/tile/tileCache.js +++ b/src/layer/tile/tileCache.js @@ -1,21 +1,16 @@ import LRUCache from '../../util/lru-cache'; export default class TileCache { - constructor(limit = 50) { + constructor(limit = 500) { this._cache = new LRUCache(limit); } - getTile(z, x, y) { - const key = this._generateKey(z, x, y); + getTile(key) { return this._cache.get(key); } - setTile(tile, z, x, y) { - const key = this._generateKey(z, x, y); + setTile(tile, key) { this._cache.set(key, tile); } - _generateKey(z, x, y) { - return [ z, x, y ].join('_'); - } destory() { this._cache.clear(); } diff --git a/src/layer/tile/tileLayer.js b/src/layer/tile/tileLayer.js index c3a6732ce1..dde0de7787 100644 --- a/src/layer/tile/tileLayer.js +++ b/src/layer/tile/tileLayer.js @@ -1,8 +1,117 @@ -import Layer from '../core/layer'; -export class TileLayer extends Layer { +import Layer from '../../core/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() { + this._object3D.add(this._tiles); + this._calculateLOD(); } 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() { + } } diff --git a/src/map/AMap.js b/src/map/AMap.js index 8ac662f838..d5bf7aae89 100644 --- a/src/map/AMap.js +++ b/src/map/AMap.js @@ -103,6 +103,7 @@ export default class GaodeMap extends Base { } mixMap(scene) { const map = this.map; + scene.project = GaodeMap.project; scene.getZoom = () => { return map.getZoom(); }; diff --git a/test/unit/layer/tile/tile-layer-spec.js b/test/unit/layer/tile/tile-layer-spec.js new file mode 100644 index 0000000000..b492aa577f --- /dev/null +++ b/test/unit/layer/tile/tile-layer-spec.js @@ -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, {}); +});