diff --git a/packages/maptalks/src/layer/tile/TileLayer.ts b/packages/maptalks/src/layer/tile/TileLayer.ts index 374029aa51..192624656f 100644 --- a/packages/maptalks/src/layer/tile/TileLayer.ts +++ b/packages/maptalks/src/layer/tile/TileLayer.ts @@ -534,12 +534,11 @@ class TileLayer extends Layer { if (!offsets[node.z + 1]) { offsets[node.z + 1] = this._getTileOffset(node.z + 1); } - this._splitNode(node, projectionView, queue, tiles, extent, maxZoom, offsets[node.z + 1], parentRenderer, glRes); - if (this.isParentTile(z, maxZoom, node)) { - parents.push(node); - } + this._splitNode(node, projectionView, queue, tiles, parents, z, extent, maxZoom, offsets[node.z + 1], parentRenderer, glRes); } parents.sort(sortingTiles); + this._debugTile(tiles, '_getPyramidTiles'); + return { tileGrids: [ { @@ -555,11 +554,10 @@ class TileLayer extends Layer { } as TilesType; } - isParentTile(z: number, maxZoom: number, tile: TileNodeType) { - const stackMinZoom = Math.max(this.getMinZoom(), z - this.options['tileStackStartDepth']); - const stackMaxZoom = Math.min(maxZoom, stackMinZoom + this.options['tileStackDepth']); - return tile.z >= stackMinZoom && tile.z < stackMaxZoom; - + isParentTile(currentTileZoom: number, maxZoom: number, tile: TileNodeType) { + const stackMinZoom = Math.max(this.getMinZoom(), currentTileZoom - this.options['tileStackStartDepth']); + const stackMaxZoom = stackMinZoom + this.options['tileStackDepth']; + return tile.z >= stackMinZoom && tile.z < stackMaxZoom && tile.z < maxZoom; } //@internal @@ -568,12 +566,15 @@ class TileLayer extends Layer { projectionView: Matrix4, queue: TileNodeType[], tiles: TileNodeType[], + parents: TileNodeType[], + currentTileZoom: number, gridExtent: PointExtent, maxZoom: number, offset: TileOffsetType, parentRenderer: any, glRes: number ) { + const z = node.z + 1; const sr = this._spatialRef || this.getSpatialReference(); const { idx, idy } = node; @@ -614,11 +615,11 @@ class TileLayer extends Layer { childNode.offset[0] = offset[0]; childNode.offset[1] = offset[1]; const visible = this._isTileVisible(childNode, projectionView, glScale, maxZoom, offset); - if (visible === 1) { + if (visible === TileVisibility.VISIBLE) { hasCurrentIn = true; - } else if (visible === -1) { + } else if (visible === TileVisibility.OUT_OF_FRUSTUM) { continue; - } else if (visible === 0 && z !== maxZoom) { + } else if (visible === TileVisibility.SCREEN_ERROR_TOO_SMALL && z !== maxZoom) { // 任意子瓦片的error低于maxError,则添加父级瓦片,不再遍历子瓦片 tiles.push(node); gridExtent._combine(node.extent2d); @@ -636,6 +637,9 @@ class TileLayer extends Layer { } else { pushIn(queue, children); } + if (this.isParentTile(currentTileZoom, maxZoom, node)) { + parents.push(node); + } } @@ -677,10 +681,10 @@ class TileLayer extends Layer { //@internal _isTileVisible(node: TileNodeType, projectionView: Matrix4, glScale: number, maxZoom: number, offset: TileOffsetType) { if (node.z === 0) { - return 1; + return TileVisibility.VISIBLE; } if (!this._isTileInFrustum(node, projectionView, glScale, offset)/* || this._isTileTooSmall(node, projectionView, glScale, maxZoom, offset)*/) { - return -1; + return TileVisibility.OUT_OF_FRUSTUM; } let maxError = this.options['maxError']; if (isNil(maxError)) { @@ -688,7 +692,7 @@ class TileLayer extends Layer { } const error = this._getScreenSpaceError(node, glScale, maxZoom, offset); - return error >= maxError ? 1 : 0; + return error >= maxError ? TileVisibility.VISIBLE : TileVisibility.SCREEN_ERROR_TOO_SMALL; } // _isTileTooSmall(node, projectionView, glScale, maxZoom, offset) { @@ -1603,6 +1607,32 @@ class TileLayer extends Layer { } return bboxInMask(tileBBOX, this._maskGeoJSON); } + + //@internal + _debugTile(tile: TileNodeType | TileNodeType[], name?: string, debugOn?: boolean) { + if (this.options['debugTile']) { + if (Array.isArray(tile)) { + for (let i = 0; i < tile.length; i++) { + if (!tile[i]) { + continue; + } + this._debugTile(tile[i], name + ' at ' + i, debugOn); + } + return; + } + if (!tile) { + return; + } + const { x, y, z } = this.options['debugTile']; + if (tile.x === x && tile.y === y && tile.z === z) { + console.log(`Debug Tile Found in TileLayer.${name}:`, tile); + if (debugOn) { + // eslint-disable-next-line no-debugger + debugger + } + } + } + } } TileLayer.registerJSONType('TileLayer'); @@ -1723,3 +1753,9 @@ export type TileLayerOptionsType = LayerOptionsType & { currentTilesFirst?: boolean; tileErrorScale?: number; }; + +enum TileVisibility { + OUT_OF_FRUSTUM, + SCREEN_ERROR_TOO_SMALL, + VISIBLE +} diff --git a/packages/maptalks/src/map/Map.Anim.ts b/packages/maptalks/src/map/Map.Anim.ts index 80c783a29a..2e6b1dbc99 100644 --- a/packages/maptalks/src/map/Map.Anim.ts +++ b/packages/maptalks/src/map/Map.Anim.ts @@ -176,7 +176,6 @@ Map.include(/** @lends Map.prototype */{ if (frame.styles['center']) { const center = frame.styles['center']; this._setPrjCenter(projection.project(center)); - this._centerZ = center.z; this.onMoving(this._parseEventFromCoord(this.getCenter())); } else if (frame.styles['prjCenter']) { const center = frame.styles['prjCenter']; diff --git a/packages/maptalks/src/map/Map.Camera.ts b/packages/maptalks/src/map/Map.Camera.ts index 7aa9d3cee5..f7a6ff5a46 100644 --- a/packages/maptalks/src/map/Map.Camera.ts +++ b/packages/maptalks/src/map/Map.Camera.ts @@ -342,6 +342,58 @@ Map.include(/** @lends Map.prototype */{ }, frame); }, + + /** + * Look at the given coordinates. + * @param {Coordinate} coordinates - coordinates to look at + * @returns {Map} this + */ + lookAt(params) { + let { coordinates, bearing, pitch, distance } = params; + coordinates = params.center || coordinates; + if (Array.isArray(coordinates)) { + coordinates = new Coordinate(coordinates as any); + } + if (!coordinates.z && !distance) { + return this.setCenter(coordinates) + } + const glRes = this.getGLRes(); + bearing = isNil(bearing) ? this.getBearing(): bearing; + pitch = isNil(pitch) ? this.getPitch() : pitch; + const radPitch = pitch * RADIAN; + const radBearing = bearing * RADIAN; + if (distance) { + let distZ = distance * Math.sin(radPitch); + let xyDist = Math.sin(radPitch) * distance; + const distX = xyDist * Math.sin(radBearing); + const distY = xyDist * Math.cos(radBearing); + distZ = distZ * this._meterToGLPoint; + xyDist = this.distanceToPointAtRes(distX, distY, glRes); + distance = Math.sqrt(xyDist * xyDist + distZ * distZ); + } + const point = this.coordToPointAtRes(coordinates, glRes); + point.z = 0; + if (coordinates.z) { + point.z = coordinates.z * this._meterToGLPoint; + } + + const cameraToTargetDistance = distance || this.cameraToGroundDistance || this.cameraToCenterDistance; + const xyDistance = Math.sin(radPitch) * cameraToTargetDistance; + const cz = cameraToTargetDistance * Math.cos(radPitch); + const cx = point.x - xyDistance * Math.sin(radBearing); + const cy = point.y - xyDistance * Math.cos(radBearing); + const cameraPosition = [cx, cy, cz + point.z]; + const cameraPoint = new Point(cameraPosition as Vector3); + const cameraCoord = this.pointAtResToCoord(cameraPoint, glRes); + const cameraAltitude = cameraPoint.z / this._meterToGLPoint; + cameraCoord.z = cameraAltitude; + return this.setCameraOrientation({ + position: cameraCoord.toArray(), + bearing, + pitch + }); + }, + /** * Set camera's orientation * @param {Object} options - options @@ -400,6 +452,7 @@ Map.include(/** @lends Map.prototype */{ this._angle = -angle(BEARING as any, SOUTH as any); this._zoomLevel = this.getFitZoomForCamera(coordinate, this._pitch).zoom; this._calcMatrices(); + return this; }, getFitZoomForCamera(cameraPosition: Array | Coordinate, pitch: number) { @@ -426,22 +479,8 @@ Map.include(/** @lends Map.prototype */{ getFitZoomForAltitude(distance: number) { const ratio = this._getFovRatio(); - const scale = distance * ratio * 2 / (this.height || 1) * this.getGLRes(); - const resolutions = this._getResolutions(); - let z = 0; - for (z; z < resolutions.length - 1; z++) { - if (resolutions[z] === scale) { - return z; - } else if (resolutions[z + 1] === scale) { - return z + 1; - } else if (scale < resolutions[z] && scale > resolutions[z + 1]) { - z = (scale - resolutions[z]) / (resolutions[z + 1] - resolutions[z]) + z; - return z - 1; - } else { - continue; - } - } - return z; + const res = distance * ratio * 2 / (this.height || 1) * this.getGLRes(); + return this.getZoomFromRes(res); }, /** @@ -817,7 +856,7 @@ Map.include(/** @lends Map.prototype */{ return function () { const glRes = this.getGLRes(); if (!this._meterToGLPoint) { - this._meterToGLPoint = this.distanceToPointAtRes(100, 0, glRes).x / 100; + this._meterToGLPoint = this.altitudeToPoint(100, glRes) / 100; } const center2D = this._prjToPointAtRes(this._prjCenter, glRes, TEMP_POINT); @@ -835,6 +874,7 @@ Map.include(/** @lends Map.prototype */{ const cameraToCenterDistance = this._getFovZ(); const cameraZenithDistance = this.cameraZenithDistance === undefined ? cameraToCenterDistance : this.cameraZenithDistance; const cameraToGroundDistance = cameraZenithDistance - centerPointZ; + this.cameraToGroundDistance = cameraToGroundDistance; const cz = cameraToGroundDistance * Math.cos(pitch); // and [dist] away from map's center on XY plane to tilt the scene. const dist = Math.sin(pitch) * cameraToGroundDistance; @@ -906,7 +946,7 @@ Map.include(/** @lends Map.prototype */{ //@internal _recenterOnTerrain() { - if (this.centerAltitude === undefined || this._centerZ !== undefined) { + if (this.centerAltitude === undefined) { return; } let queriedAltitude = this._queryTerrainByProjCoord(this._prjCenter); diff --git a/packages/maptalks/src/map/Map.ts b/packages/maptalks/src/map/Map.ts index 01bb4ad228..01045f1f85 100644 --- a/packages/maptalks/src/map/Map.ts +++ b/packages/maptalks/src/map/Map.ts @@ -232,8 +232,6 @@ export class Map extends Handlerable(Eventable(Renderable(Class))) { //@internal _center: Coordinate; //@internal - _centerZ: number; - //@internal _mapViewPoint: Point; isMap: boolean; //@internal @@ -353,7 +351,6 @@ export class Map extends Handlerable(Eventable(Renderable(Class))) { this._zoomLevel = zoom; this._center = center; - this._centerZ = center.z; this.setSpatialReference(opts['spatialReference'] || opts['view']); @@ -608,7 +605,6 @@ export class Map extends Handlerable(Eventable(Renderable(Class))) { const center = projection.unproject(this._prjCenter); center.x = Math.round(center.x * 1E8) / 1E8; center.y = Math.round(center.y * 1E8) / 1E8; - center.z = this._centerZ; if (this.centerAltitude) { center.z = this.centerAltitude; } @@ -642,7 +638,6 @@ export class Map extends Handlerable(Eventable(Renderable(Class))) { this._center = center; return this; } - this._centerZ = center.z; this.onMoveStart(); this._setPrjCenter(pcenter); this.onMoveEnd(this._parseEventFromCoord(this.getCenter())); diff --git a/packages/maptalks/src/renderer/layer/tilelayer/TileLayerRendererable.ts b/packages/maptalks/src/renderer/layer/tilelayer/TileLayerRendererable.ts index 269c507e43..a257536e3f 100644 --- a/packages/maptalks/src/renderer/layer/tilelayer/TileLayerRendererable.ts +++ b/packages/maptalks/src/renderer/layer/tilelayer/TileLayerRendererable.ts @@ -270,6 +270,7 @@ const TileLayerRenderable = function (Base: T) { for (let j = 0, l = allTiles.length; j < l; j++) { const tile = allTiles[j]; + layer._debugTile(tile, '_getTilesInCurrentFrame'); const tileId = tile.id; const isParentTile = !isFirstRender && j < parentCount; //load tile in cache at first if it has. diff --git a/packages/maptalks/test/map/MapCameraSpec.js b/packages/maptalks/test/map/MapCameraSpec.js index b503304824..462c0f0c5b 100644 --- a/packages/maptalks/test/map/MapCameraSpec.js +++ b/packages/maptalks/test/map/MapCameraSpec.js @@ -530,7 +530,49 @@ describe('Map.Camera', function () { expect(map.getPitch()).to.be.eql(0); expect(map.getBearing()).to.be.eql(0); expect(map.getZoom()).to.be.within(14, 15); - }) + }); + + it('map.lookAt', function () { + const view = map.getView(); + const cameraPosition = map.cameraPosition.slice(); + const pitch = map.getPitch(); + const bearing = map.getBearing(); + + map.lookAt(view); + + expect(map.cameraPosition).to.be.closeTo(cameraPosition); + expect(map.getPitch()).to.be.eql(pitch); + expect(map.getBearing()).to.be.eql(bearing); + }); + + it('map.lookAt with distance', function () { + const view = map.getView(); + let distance = map.cameraToCenterDistance; + + // compute meter distance in XYZ against map.cameraToCenterDistance + const glRes = map.getGLRes(); + const radBearing = map.getBearing() * Math.PI / 180; + const radPitch = map.getPitch() * Math.PI / 180; + let distZ = distance * Math.sin(radPitch); + let xyDist = Math.sin(radPitch) * distance; + const distX = xyDist * Math.sin(radBearing); + const distY = xyDist * Math.cos(radBearing); + distZ = distZ / map._meterToGLPoint; + xyDist = map.pointAtResToDistance(distX, distY, glRes); + distance = Math.sqrt(xyDist * xyDist + distZ * distZ); + + const cameraPosition = map.cameraPosition.slice(); + const pitch = map.getPitch(); + const bearing = map.getBearing(); + + view.distance = map.pointAtResToAltitude(distance, map.getGLRes()); + map.lookAt(view); + + expect(map.cameraPosition).to.be.closeTo(cameraPosition); + expect(map.cameraPosition[2]).to.be.eql(cameraPosition[2]); + expect(map.getPitch()).to.be.eql(pitch); + expect(map.getBearing()).to.be.eql(bearing); + }); }); describe('containsPoint when pitching', function () {