diff --git a/README.md b/README.md index bf4f5b4..771c054 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- +

A WebGL virtual globe and map engine

@@ -8,7 +8,7 @@ ## WebGlobe [![Build Status](https://travis-ci.org/iSpring/WebGlobe.svg?branch=develop)](https://travis-ci.org/iSpring/WebGlobe) -[![Release](https://img.shields.io/badge/release-0.5.1-blue.svg)](https://github.com/iSpring/WebGlobe/releases) +[![Release](https://img.shields.io/badge/release-0.6.0-blue.svg)](https://github.com/iSpring/WebGlobe/releases) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iSpring/WebGlobe) [![Chrome 8+](https://img.shields.io/badge/Chrome-8+-1DA362.svg)](http://caniuse.com/#search=WebGL) @@ -30,7 +30,7 @@ WebGlobe是基于HTML5原生WebGL实现的轻量级Google Earth三维地图引 移动版二维码访问(小米系统中的微信、小米默认浏览器在某些情况下存在已知bug):
- +
**如果觉得不错,欢迎Star和Fork!** @@ -69,7 +69,7 @@ WebGlobe是基于HTML5原生WebGL实现的轻量级Google Earth三维地图引 **1. WebGlobe移动端主界面**
- +
@@ -77,7 +77,7 @@ WebGlobe是基于HTML5原生WebGL实现的轻量级Google Earth三维地图引 **2. 附近搜索**
- +
@@ -85,7 +85,7 @@ WebGlobe是基于HTML5原生WebGL实现的轻量级Google Earth三维地图引 **3. 搜索结果列表展示**
- +
@@ -93,7 +93,7 @@ WebGlobe是基于HTML5原生WebGL实现的轻量级Google Earth三维地图引 **4. 搜索结果地图展示**
- +
@@ -101,7 +101,7 @@ WebGlobe是基于HTML5原生WebGL实现的轻量级Google Earth三维地图引 **5. 路线规划**
- +
@@ -109,7 +109,7 @@ WebGlobe是基于HTML5原生WebGL实现的轻量级Google Earth三维地图引 **6. 驾车出行路线**
- +
@@ -117,7 +117,7 @@ WebGlobe是基于HTML5原生WebGL实现的轻量级Google Earth三维地图引 **7. 公交出行路线**
- +
@@ -125,6 +125,6 @@ WebGlobe是基于HTML5原生WebGL实现的轻量级Google Earth三维地图引 **8. 步行出行路线**
- +
\ No newline at end of file diff --git a/images/3.png b/images/3.png index 877dbb0..76f8d67 100644 Binary files a/images/3.png and b/images/3.png differ diff --git a/images/4.png b/images/4.png index dc65e30..f98a3cb 100644 Binary files a/images/4.png and b/images/4.png differ diff --git a/images/5.png b/images/5.png index 53ee6e3..82b668a 100644 Binary files a/images/5.png and b/images/5.png differ diff --git a/images/6.png b/images/6.png index 7b1a4d5..392076d 100644 Binary files a/images/6.png and b/images/6.png differ diff --git a/images/7.png b/images/7.png index dfec62c..5db679c 100644 Binary files a/images/7.png and b/images/7.png differ diff --git a/images/8.png b/images/8.png index f8ac7b8..24ff27d 100644 Binary files a/images/8.png and b/images/8.png differ diff --git a/package.json b/package.json index b89c494..cb33a21 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webglobe", - "version": "0.5.1", + "version": "0.6.0", "description": "A WebGL virtual globe and map engine.", "main": "require.js", "scripts": { diff --git a/src/core/world/Camera.ts b/src/core/world/Camera.ts index 91d7db9..871134a 100644 --- a/src/core/world/Camera.ts +++ b/src/core/world/Camera.ts @@ -1,3 +1,4 @@ +declare const Promise: any; import Kernel from './Kernel'; import Utils from './Utils'; import { EventEmitter } from './Events'; @@ -65,8 +66,14 @@ class Camera extends Object3D { private readonly animationDuration: number = 200;//层级变化的动画周期,毫秒 private readonly nearFactor: number = 0.6; private readonly maxPitch: number = 40; - private readonly resolutionFactor1: number = Math.pow(2, 0.3752950); - private readonly resolutionFactor2: number = Math.pow(2, 1.3752950); + //resolutionFactor1的值为1时量算出的分辨率就是实际分辨率,但是图片不是256大小显示 + //为了确保图片以256显示,需要将resolutionFactor1设置为Math.pow(2, 0.3752950) + //getResolution()和getResolutionInWorld()方法用于让其他类调用获取实际的分辨率,需要除以resolutionFactor1以便获取真实值 + // private resolutionFactor1: number = Math.pow(2, 0.3752950); + private resolutionFactor1: number; + //resolutionFactor2用于矫正计算出的分辨率与实际分辨率之间的差别 + // private resolutionFactor2: number = this.resolutionFactor1 * 2; + private resolutionFactor2: number; //旋转的时候,绕着视线与地球交点进行旋转 //定义抬头时,旋转角为正值 @@ -106,8 +113,22 @@ class Camera extends Object3D { //this.far可以动态计算 //this.aspect在Viewport改变后重新计算 //this.fov可以调整以实现缩放效果 - constructor(private canvas: HTMLCanvasElement, private fov: number = 45, private aspect: number = 1, private near: number = 1, private far: number = 100, level: number = 3, lonlat: number[] = [0, 0]) { + constructor( + private canvas: HTMLCanvasElement, + private fov: number = 45, + private aspect: number = 1, + private near: number = 1, + private far: number = 100, + level: number = 3, + lonlat: number[] = [0, 0], + resolutionFactor: number = Math.pow(2, 0.3752950)) { + super(); + if(!(resolutionFactor > 0)){ + resolutionFactor = Math.pow(2, 0.3752950); + } + this.resolutionFactor1 = resolutionFactor; + this.resolutionFactor2 = this.resolutionFactor1 * 2; this.eventEmitter = new EventEmitter(); this.lonlatsOfBoundary = []; this.initFov = this.fov; @@ -446,8 +467,34 @@ class Camera extends Object3D { return newFov; } + //返回x和y综合的平均分辨率 + //for public use + getResolution(): number { + const { + resolutionX, + bestDisplayLevelFloatX, + resolutionY, + bestDisplayLevelFloatY + } = this.measureXYResolutionAndBestDisplayLevel(); + return (resolutionX + resolutionY) / 2 / this.resolutionFactor1; + } + + //for public use + getResolutionInWorld(): number { + return this.getResolution() / Kernel.SCALE_FACTOR; + } + + //屏幕1px在实际世界中的距离,for test + private getResolutionInWorld2(): number { + if (realResolutionCache.hasOwnProperty(this.level)) { + return realResolutionCache[this.level]; + } else { + return Kernel.MAX_REAL_RESOLUTION / Math.pow(2, this.level); + } + } + //resolution,level - measureXYResolutionAndBestDisplayLevel(): any { + private measureXYResolutionAndBestDisplayLevel(): any { //计算resolution var p = this.matrix.getPosition(); var dir = Vector.fromVertice(p); @@ -486,7 +533,7 @@ class Camera extends Object3D { } //[resolution,level] - calculateCurrentResolutionAndBestDisplayLevel() { + private calculateCurrentResolutionAndBestDisplayLevel() { var distance2EarthOrigin = this.getDistance2EarthOrigin(); return this._calculateResolutionAndBestDisplayLevelByDistance2EarthOrigin(distance2EarthOrigin); } @@ -540,15 +587,6 @@ class Camera extends Object3D { return Kernel.MAX_RESOLUTION / Math.pow(2, level); } - //屏幕1px在实际世界中的距离 - getResolutionInWorld(): number { - if (realResolutionCache.hasOwnProperty(this.level)) { - return realResolutionCache[this.level]; - } else { - return Kernel.MAX_REAL_RESOLUTION / Math.pow(2, this.level); - } - } - getVertice() { const origin2PositionVector = Vector.fromVertice(this.getPosition()); origin2PositionVector.setLength(Kernel.EARTH_RADIUS); @@ -584,7 +622,7 @@ class Camera extends Object3D { this.level = level; this.floatLevel = level; } - if(levelChanged){ + if (levelChanged) { Utils.publish('level-change', { oldLevel: oldLevel, newLevel: this.level @@ -702,9 +740,48 @@ class Camera extends Object3D { return pitch; } - //计算拾取射线与地球的交点,以笛卡尔空间直角坐标系坐标数组的形式返回 - //该方法需要projViewMatrixForDraw系列矩阵进行计算 - getPickCartesianCoordInEarthByCanvas(canvasX: number, canvasY: number): Vertice[] { + // _doWorkByMatrixForDraw(cb: any) { + // this._updateCore(); + + // //暂存projViewMatrix系列矩阵 + // var matrix = this.matrix; + // var viewMatrix = this.viewMatrix; + // var projMatrix = this.projMatrix; + // var projViewMatrix = this.projViewMatrix; + + // //将projViewMatrix系列矩阵赋值为projViewMatrixForDraw系列矩阵 + // this.matrix = this.matrixForDraw; + // this.viewMatrix = this.viewMatrixForDraw; + // this.projMatrix = this.projMatrixForDraw; + // this.projViewMatrix = this.projViewMatrixForDraw; + + // //基于projViewMatrixForDraw系列矩阵进行计算,应该没有误差 + // // var pickDirection = this._getPickDirectionByCanvas(canvasX, canvasY); + // // var p = this.getPosition(); + // // var line = new Line(p, pickDirection); + // // var result = this._getPickCartesianCoordInEarthByLine(line); + // cb(); + + // //还原projViewMatrix系列矩阵 + // this.matrix = matrix; + // this.viewMatrix = viewMatrix; + // this.projMatrix = projMatrix; + // this.projViewMatrix = projViewMatrix; + // } + + /** + * 该方法需要projViewMatrixForDraw系列矩阵进行计算 + * 返回拾取的射线相关信息,result.line表示拾取的直线,result.vertices表示拾取的点,即射线与地球的交点 + * @param canvasX + * @param canvasY + * @param verticesResult 如果为true,那么会计算result.vertices + * return {line, vertices} + */ + getPickInfoByCanvas(canvasX: number, canvasY: number, verticesResult: boolean = false):any { + const result: any = { + line: null, + vertices: [] + }; this._updateCore(); //暂存projViewMatrix系列矩阵 @@ -722,8 +799,10 @@ class Camera extends Object3D { //基于projViewMatrixForDraw系列矩阵进行计算,应该没有误差 var pickDirection = this._getPickDirectionByCanvas(canvasX, canvasY); var p = this.getPosition(); - var line = new Line(p, pickDirection); - var result = this._getPickCartesianCoordInEarthByLine(line); + result.line = new Line(p, pickDirection); + if(verticesResult){ + result.vertices = this._getPickCartesianCoordInEarthByLine(result.line); + } //还原projViewMatrix系列矩阵 this.matrix = matrix; @@ -734,6 +813,13 @@ class Camera extends Object3D { return result; } + //计算拾取射线与地球的交点,以笛卡尔空间直角坐标系坐标数组的形式返回 + //该方法需要projViewMatrixForDraw系列矩阵进行计算 + getPickCartesianCoordInEarthByCanvas(canvasX: number, canvasY: number): Vertice[] { + const pickInfo = this.getPickInfoByCanvas(canvasX, canvasY, true); + return pickInfo.vertices; + } + getLightDirection(): Vector { var dirVertice = this.matrix.getVectorZ(); var direction = new Vector(-dirVertice.x, -dirVertice.y, -dirVertice.z); @@ -768,7 +854,7 @@ class Camera extends Object3D { } animateTo(newLon: number, newLat: number, newLevel: number = this.getLevel(), duration: number = 1000) { - const promise = new Promise((resolve, reject) => { + const promise = new Promise((resolve:any, reject:any) => { if (this.isAnimating()) { reject("be animating"); return; @@ -860,14 +946,14 @@ class Camera extends Object3D { requestAnimationFrame(callback); } - setExtent(extent: Extent){ - if(extent){ + setExtent(extent: Extent) { + if (extent) { const [lon, lat, level] = this._calculateLonLatLevelByExtent(extent); - this.centerTo(lon, lat, level); + this.centerTo(lon, lat, level); } } - animateToExtent(extent: Extent, duration: number = 1000){ + animateToExtent(extent: Extent, duration: number = 1000) { const [lon, lat, level] = this._calculateLonLatLevelByExtent(extent); return this.animateTo(lon, lat, level, duration); } @@ -916,16 +1002,16 @@ class Camera extends Object3D { this.setPosition(newPosition); } - private _safelyGetValidLevel(level: number){ - if(level > Kernel.MAX_LEVEL){ + private _safelyGetValidLevel(level: number) { + if (level > Kernel.MAX_LEVEL) { level = Kernel.MAX_LEVEL; - }else if(level < Kernel.MIN_LEVEL){ + } else if (level < Kernel.MIN_LEVEL) { level = Kernel.MIN_LEVEL; } return level; } - private _calculateLonLatLevelByExtent(extent: Extent){ + private _calculateLonLatLevelByExtent(extent: Extent) { const centerLon = (extent.getMinLon() + extent.getMaxLon()) / 2; const centerLat = (extent.getMinLat() + extent.getMaxLat()) / 2; const deltaLon = extent.getMaxLon() - extent.getMinLon(); diff --git a/src/core/world/Definitions.d.ts b/src/core/world/Definitions.d.ts index 75ae6e7..c4f7513 100644 --- a/src/core/world/Definitions.d.ts +++ b/src/core/world/Definitions.d.ts @@ -1,3 +1,5 @@ +declare const Promise: any; +import Line from './math/Line'; import Matrix from './math/Matrix'; import Camera from './Camera'; import GraphicGroup from './GraphicGroup'; @@ -33,6 +35,13 @@ export interface Drawable{ destroy(): void; } +export interface Pickable{ + ifIntersectLocalLine(localLine: Line): boolean; + ifIntersectWorldLine(worldLine: Line): boolean; +} + +export type PickListener = (target: Pickable) => void; + export interface CancelablePromise extends Promise{ cancel: () => void; } @@ -41,6 +50,10 @@ export interface Destroyable{ destroy: () => void; } +export interface Attributes{ + [key: string]: any +} + // declare module "*.png" { // const content: any; // export default content; diff --git a/src/core/world/EventHandler.ts b/src/core/world/EventHandler.ts index 4d477ba..0ed6e42 100644 --- a/src/core/world/EventHandler.ts +++ b/src/core/world/EventHandler.ts @@ -10,7 +10,7 @@ type DomEventListener = () => any; export default class EventHandler implements Destroyable { private down: boolean = false; private dragGeo: any = null; - private previousX: number = -1; + private previousX: number = -1;//对于PC浏览器存储offsetX,对于移动浏览器存储pageX private previousY: number = -1; private twoTouchDistance: number = -1; private oldTime: number = -1; @@ -46,6 +46,7 @@ export default class EventHandler implements Destroyable { this.globe.canvas.addEventListener("mousedown", this._onMouseDown.bind(this), false); this.globe.canvas.addEventListener("mouseup", this._onMouseUp.bind(this), false); this.globe.canvas.addEventListener("mousemove", this._onMouseMove.bind(this), false); + this.globe.canvas.addEventListener("click", this._onClick.bind(this), false); this.globe.canvas.addEventListener("dblclick", this._onDbClick.bind(this), false); this.globe.canvas.addEventListener("mousewheel", this._onMouseWheel.bind(this), false); this.globe.canvas.addEventListener("DOMMouseScroll", this._onMouseWheel.bind(this), false); @@ -77,6 +78,11 @@ export default class EventHandler implements Destroyable { this.globe.camera.worldRotateByVector(rotateRadian, rotateVector); } + //单击时进行拾取 + private _handleSingleClick(canvasX: number, canvasY: number){ + this.globe.pick(canvasX, canvasY); + } + private _handleMouseDownOrTouchStart(offsetX: number, offsetY: number) { this.down = true; this.previousX = offsetX; @@ -151,6 +157,12 @@ export default class EventHandler implements Destroyable { this._handleMouseUpOrTouchEnd(); } + private _onClick(event: MouseEvent){ + var absoluteX = event.layerX || event.offsetX; + var absoluteY = event.layerY || event.offsetY; + this._handleSingleClick(absoluteX, absoluteY); + } + private _onDbClick(event: MouseEvent) { var globe = this.globe; if (!globe || globe.isAnimating()) { @@ -219,18 +231,27 @@ export default class EventHandler implements Destroyable { //-------------------------------------------------------------------------------------- - private _onTouchZero(){ + private _onTouchZero(event: TouchEvent){ this.twoTouchDistance = -1; + const previousX = this.previousX; + const previousY = this.previousY; this._handleMouseUpOrTouchEnd(); this.endTime = Date.now(); var time = this.endTime - this.startTime; if (time <= 200) { var time2 = this.endTime - this.lastTime; if (time2 < 300) { + //相当于双击 this.lastTime = this.oldTime; this.globe.zoomIn(); }else { + //相当于单击 this.lastTime = this.endTime; + //此时event.targetTouches为空数组 + const {left, top} = this.globe.canvas.getBoundingClientRect(); + const canvasX = previousX - left; + const canvasY = previousY - top; + this._handleSingleClick(canvasX, canvasY); } } } @@ -274,7 +295,7 @@ export default class EventHandler implements Destroyable { var touchCount = event.targetTouches.length; if (touchCount === 0) { - this._onTouchZero(); + this._onTouchZero(event); }else if(touchCount === 1){ this._onTouchOne(event); }else if(touchCount === 2){ @@ -330,7 +351,7 @@ export default class EventHandler implements Destroyable { private _onTouchEnd(event: TouchEvent) { var touchCount = event.targetTouches.length; if (touchCount === 0) { - this._onTouchZero(); + this._onTouchZero(event); }else if(touchCount === 1){ this._onTouchOne(event); }else if(touchCount === 2){ diff --git a/src/core/world/Globe.ts b/src/core/world/Globe.ts index cda6a90..3959641 100644 --- a/src/core/world/Globe.ts +++ b/src/core/world/Globe.ts @@ -31,6 +31,7 @@ export class GlobeOptions{ level: number | 'auto' = 'auto'; lonlat: number[] | 'auto' = 'auto'; key: string = ""; + resolutionFactor: number; } export default class Globe { @@ -75,7 +76,7 @@ export default class Globe { var radio = canvas.width / canvas.height; let level = this.options.level >= 0 ? (this.options.level as number) : initLevel; let lonlat = (this.options.lonlat && this.options.lonlat.length === 2) ? (this.options.lonlat as number[]) : initLonlat; - this.camera = new Camera(canvas, 30, radio, 1, Kernel.EARTH_RADIUS * 2, level, lonlat); + this.camera = new Camera(canvas, 30, radio, 1, Kernel.EARTH_RADIUS * 2, level, lonlat, options.resolutionFactor); this.renderer.setScene(this.scene); this.renderer.setCamera(this.camera); @@ -376,6 +377,12 @@ export default class Globe { } } + pick(canvasX: number, canvasY: number){ + const pickInfo = this.camera.getPickInfoByCanvas(canvasX, canvasY, false); + const line = pickInfo.line; + this.scene.pickByWorldLine(line); + } + test(){ this.debugStopRefreshTiles = true; this.labelLayer.hideAllTiles(); diff --git a/src/core/world/GraphicGroup.ts b/src/core/world/GraphicGroup.ts index 9b6ebb5..82145d6 100644 --- a/src/core/world/GraphicGroup.ts +++ b/src/core/world/GraphicGroup.ts @@ -1,8 +1,9 @@ import Kernel from './Kernel'; -import {Drawable} from './Definitions.d'; -import Camera from "./Camera"; +import {Drawable, Pickable, PickListener} from './Definitions.d'; +import Camera from './Camera'; +import Line from './math/Line'; -export default class GraphicGroup implements Drawable { +class GraphicGroup implements Drawable { id: number; parent: GraphicGroup; children: T[]; @@ -23,15 +24,21 @@ export default class GraphicGroup implements Drawable { } remove(g: T): boolean { - var result = false; - var findResult = this.findGraphicById(g.id); - if (findResult) { - g.destroy(); - this.children.splice(findResult.index, 1); - g = null; - result = true; + // var result = false; + // var findResult = this.findChildById(g.id); + // if (findResult) { + // g.destroy(); + // this.children.splice(findResult.index, 1); + // g = null; + // result = true; + // } + // return result; + const index = this.findChildIndex(g); + if(index >= 0){ + this.children.splice(index, 1); + return true; } - return result; + return false; } clear() { @@ -48,8 +55,19 @@ export default class GraphicGroup implements Drawable { this.clear(); } - findGraphicById(graphicId: number) { - var i = 0, length = this.children.length, g: Drawable = null; + findChildIndex(child: T){ + const count = this.children.length; + for(let i = 0; i < count; i++){ + const g = this.children[i]; + if(child === g){ + return i; + } + } + return -1; + } + + findChildById(graphicId: number) { + var i = 0, length = this.children.length, g: T = null; for (; i < length; i++) { g = this.children[i]; if (g.id === graphicId) { @@ -66,12 +84,24 @@ export default class GraphicGroup implements Drawable { return this.visible && this.children.length > 0; } + moveChildToLastPosition(child: T){ + const index = this.findChildIndex(child); + this.children.splice(index, 1); + this.children.push(child); + } + draw(camera: Camera) { if (this.shouldDraw()) { + this.onBeforeDraw(); this.onDraw(camera); + this.onAfterDraw(); } } + protected onBeforeDraw(){ + + } + protected onDraw(camera: Camera) { this.children.forEach(function (g: Drawable) { if (g.shouldDraw(camera)) { @@ -79,4 +109,60 @@ export default class GraphicGroup implements Drawable { } }); } -}; \ No newline at end of file + + protected onAfterDraw(){ + + } +}; + + +//通过T extends Drawable & Pickable让T同时继承自多个接口 +export class PickableGraphicGroup extends GraphicGroup{ + private pickListener: PickListener = null; + + pickByLocalLine(localLine: Line, emitListener: boolean = false): T{ + const count = this.children.length; + for(let i = count - 1; i >= 0; i--){ + const child = this.children[i]; + if(child.ifIntersectLocalLine(localLine)){ + if(emitListener){ + this.onPick(child); + } + return child; + } + } + return null; + } + + pickByWorldLine(worldLine: Line, emitListener: boolean = false): T{ + const count = this.children.length; + for(let i = count - 1; i >= 0; i--){ + const child = this.children[i]; + if(child.ifIntersectWorldLine(worldLine)){ + if(emitListener){ + this.onPick(child); + } + return child; + } + } + return null; + } + + private onPick(target: T){ + //将选中的child放到最后的位置以便于最后渲染,这样防止其他child对其遮挡 + this.moveChildToLastPosition(target); + if(this.pickListener){ + this.pickListener(target); + } + } + + hasPickListener(){ + return !!this.pickListener; + } + + setPickListener(listener: PickListener){ + this.pickListener = listener; + } +} + +export default GraphicGroup; \ No newline at end of file diff --git a/src/core/world/Kernel.ts b/src/core/world/Kernel.ts index 85b385d..e1513db 100644 --- a/src/core/world/Kernel.ts +++ b/src/core/world/Kernel.ts @@ -10,7 +10,7 @@ const MAX_RESOLUTION = MAX_REAL_RESOLUTION * SCALE_FACTOR; export default class Kernel{ static gl: WebGLRenderingContextExtension = null; static idCounter: number = 0; - static readonly version: string = "0.5.1"; + static readonly version: string = "0.6.0"; static readonly SCALE_FACTOR: number = SCALE_FACTOR; static readonly REAL_EARTH_RADIUS: number = REAL_EARTH_RADIUS; static readonly EARTH_RADIUS: number = EARTH_RADIUS; diff --git a/src/core/world/Object3D.ts b/src/core/world/Object3D.ts index a223eb5..0c8b5e1 100644 --- a/src/core/world/Object3D.ts +++ b/src/core/world/Object3D.ts @@ -9,6 +9,10 @@ export default class Object3D { this.matrix = new Matrix(); } + setMatrix(matrix: Matrix){ + this.matrix = matrix; + } + getMatrix(): Matrix{ return this.matrix; } diff --git a/src/core/world/Scene.ts b/src/core/world/Scene.ts index c0d570f..4e40df4 100644 --- a/src/core/world/Scene.ts +++ b/src/core/world/Scene.ts @@ -1,7 +1,23 @@ -import {Drawable} from './Definitions.d'; -import GraphicGroup from './GraphicGroup'; +import {Drawable, Pickable} from './Definitions.d'; +import GraphicGroup,{PickableGraphicGroup} from './GraphicGroup'; import TiledLayer from './layers/TiledLayer'; +import Line from './math/Line'; export default class Scene extends GraphicGroup{ tiledLayer: TiledLayer; + + pickByWorldLine(worldLine: Line){ + const count = this.children.length; + for(let i = count - 1; i >= 0; i--){ + let graphicGroup = this.children[i]; + if(graphicGroup instanceof PickableGraphicGroup){ + let pickableGraphicGroup = graphicGroup as PickableGraphicGroup; + const target = pickableGraphicGroup.pickByWorldLine(worldLine, true); + if(target){ + return target; + } + } + } + return null; + } }; \ No newline at end of file diff --git a/src/core/world/Service.ts b/src/core/world/Service.ts index f906ee8..dfe7d6c 100644 --- a/src/core/world/Service.ts +++ b/src/core/world/Service.ts @@ -1,3 +1,4 @@ +declare const Promise: any; import Extent from './Extent'; import MathUtils from './math/Utils'; @@ -16,6 +17,8 @@ type RouteType = "bus" | "snsnav"; type StrictSearchType = 'Type' | 'POI'; export type SearchType = StrictSearchType | 'Auto'; +type AnyCallbackType = (v: any) => any; + //http://apis.map.qq.com/jsapi?qt=rn&wd=北京南站&pn=0&rn=10&px=117.200983&py=39.084158&r=3000&output=json class Service { @@ -68,7 +71,7 @@ class Service { private static location: Location = null; private static getCityLocation() { - const promise = new Promise((resolve) => { + const p = new Promise((resolve: AnyCallbackType) => { if (this.cityLocation) { resolve(this.cityLocation); } else { @@ -90,11 +93,11 @@ class Service { }); } }); - return promise; + return p; } static getCurrentPosition(highAccuracy: boolean = false) { - const promise = new Promise((resolve) => { + const p = new Promise((resolve: AnyCallbackType) => { this.getCityLocation().then((cityLocation: Location) => { if (highAccuracy) { navigator.geolocation.getCurrentPosition((response: Position) => { @@ -125,7 +128,7 @@ class Service { } }); }); - return promise; + return p; } //--------------------------------------------------search------------------------------------------------------- @@ -231,13 +234,13 @@ class Service { //http://lbs.qq.com/javascript_v2/case-run.html#service-searchservice static searchByExtent(keyword: string, level: number, { minLon, minLat, maxLon, maxLat }: Extent, pageCapacity: number = 50, pageIndex: number = 0) { - const promise = new Promise((resolve) => { + const p = new Promise((resolve: AnyCallbackType) => { const url = `//apis.map.qq.com/jsapi?qt=syn&wd=${keyword}&pn=${pageIndex}&rn=${pageCapacity}&output=jsonp&b=${minLon},${minLat},${maxLon},${maxLat}&l=${level}&c=000000`; Service.jsonp(url, function (response: any) { resolve(response); }); }); - return promise; + return p; } static searchByBuffer(keyword: string, lon: number, lat: number, radius: number, searchType: SearchType = 'Auto', pageCapacity: number = 50, pageIndex: number = 0) { @@ -263,7 +266,7 @@ class Service { } private static _rawSearchByBuffer(searchType: StrictSearchType, keyword: string, lon: number, lat: number, radius: number, pageCapacity: number = 50, pageIndex: number = 0) { - const promise = new Promise((resolve) => { + const p = new Promise((resolve: AnyCallbackType) => { //http://apis.map.qq.com/jsapi?qt=rn&wd=酒店&pn=0&rn=5&px=116.397128&py=39.916527&r=2000&output=jsonp&cb=webglobe_jsonp_1 //http://apis.map.qq.com/jsapi?qt=poi&wd=杨村一中&pn=0&rn=5&px=116.397128&py=39.916527&r=2000&output=jsonp&cb=webglobe_jsonp_1 let url = `//apis.map.qq.com/jsapi?wd=${keyword}&pn=${pageIndex}&rn=${pageCapacity}&px=${lon}&py=${lat}&r=${radius}&output=jsonp`; @@ -275,7 +278,7 @@ class Service { resolve(response); }); }); - return promise; + return p; } static searchByCity(keyword: string, city: string, searchType: SearchType = 'Auto', pageCapacity: number = 50, pageIndex: number = 0) { @@ -302,7 +305,7 @@ class Service { private static _rawSearchByCity(searchType: StrictSearchType, keyword: string, city: string, pageCapacity: number = 50, pageIndex: number = 0) { //http://apis.map.qq.com/jsapi?qt=poi&wd=杨村一中&pn=0&rn=5&c=北京&output=json&cb=callbackname - const promise = new Promise((resolve) => { + const p = new Promise((resolve: AnyCallbackType) => { let url = `//apis.map.qq.com/jsapi?wd=${keyword}&pn=${pageIndex}&rn=${pageCapacity}&c=${city}&output=jsonp`; url = this._getUrlBySearchType(url, searchType); Service.jsonp(url, (response: any) => { @@ -318,7 +321,7 @@ class Service { resolve(response); }); }); - return promise; + return p; } static searchNearby(keyword: string, radius: number, searchType: SearchType = 'Auto', highAccuracy: boolean = false, pageCapacity: number = 50, pageIndex: number = 0) { @@ -338,7 +341,7 @@ class Service { static routeByDriving(fromLon: number, fromLat: number, toLon: number, toLat: number, key: string, strategy: number = 5) { //http://lbs.gaode.com/api/webservice/guide/api/direction/#driving //http://restapi.amap.com/v3/direction/driving?origin=117.00216,39.40365&destination=117.01633,39.37265&extensions=all&output=json&key=db146b37ef8d9f34473828f12e1e85ad&strategy=10 - const promise = new Promise((resolve, reject) => { + const p = new Promise((resolve: AnyCallbackType, reject: AnyCallbackType) => { const url = `//restapi.amap.com/v3/direction/driving?origin=${fromLon},${fromLat}&destination=${toLon},${toLat}&extensions=all&output=json&key=${key}&strategy=${strategy}`; const xhr = new XMLHttpRequest(); //IE12+ @@ -357,7 +360,7 @@ class Service { }; xhr.send(); }); - return promise; + return p; } private static _handleDrivingResult(responseText: string) { @@ -376,7 +379,7 @@ class Service { } static routeByBus(fromLon: number, fromLat: number, toLon: number, toLat: number, startCity: string, endCity: string, key: string, strategy: number = 0) { - const promise = new Promise((resolve, reject) => { + const p = new Promise((resolve: AnyCallbackType, reject: AnyCallbackType) => { //http://restapi.amap.com/v3/direction/transit/integrated?origin=117.00216,39.40365&destination=117.01633,39.37265&city=武清区&cityd=武清区&output=json&key=db146b37ef8d9f34473828f12e1e85ad const url = `//restapi.amap.com/v3/direction/transit/integrated?origin=${fromLon},${fromLat}&destination=${toLon},${toLat}&city=${startCity}&cityd=${endCity}&output=json&key=${key}`; const xhr = new XMLHttpRequest(); @@ -393,7 +396,7 @@ class Service { }; xhr.send(); }); - return promise; + return p; } private static _handleBusResult(responseText: string) { @@ -465,7 +468,7 @@ class Service { } static routeByWalking(fromLon: number, fromLat: number, toLon: number, toLat: number, key: string) { - const promise = new Promise((resolve, reject) => { + const p = new Promise((resolve: AnyCallbackType, reject: AnyCallbackType) => { //http://restapi.amap.com/v3/direction/walking?origin=116.434307,39.90909&destination=116.434446,39.90816&key=db146b37ef8d9f34473828f12e1e85ad const url = `//restapi.amap.com/v3/direction/walking?origin=${fromLon},${fromLat}&destination=${toLon},${toLat}&key=${key}`; const xhr = new XMLHttpRequest(); @@ -482,7 +485,7 @@ class Service { }; xhr.send(); }); - return promise; + return p; } private static _handleWalkingResult(responseText: string) { @@ -530,7 +533,7 @@ class Service { if (policy) { url += `&policy=${policy}`; } - const promise = new Promise((resolve) => { + const p = new Promise((resolve: AnyCallbackType) => { Service.jsonp(url, (response: any) => { response.result.routes.forEach((route: any) => { Service.decodeQQPolyline(route.polyline); @@ -538,7 +541,7 @@ class Service { resolve(response); }); }); - return promise; + return p; } @@ -552,13 +555,13 @@ class Service { const toX: number = MathUtils.degreeLonToWebMercatorX(toLon, true); const toY: number = MathUtils.degreeLatToWebMercatorY(toLat, true); const url = `//apis.map.qq.com/jsapi?qt=${routeType}&start=1$$$$${fromX}, ${fromY}$$&dest=1$$$$${toX}, ${toY}$$&cond=3&mt=2&s=2&fm=0&output=jsonp&pf=jsapi&ref=jsapi`; - const promise = new Promise((resolve) => { + const p = new Promise((resolve: AnyCallbackType) => { Service.jsonp(url, (response: any) => { console.log(response); resolve(response); }, "GBK"); }); - return promise; + return p; } }; diff --git a/src/core/world/Utils.ts b/src/core/world/Utils.ts index c23785a..60ef89d 100644 --- a/src/core/world/Utils.ts +++ b/src/core/world/Utils.ts @@ -107,6 +107,11 @@ export default class Utils { return !!window.navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone|IEMobile|Opera Mini/i); } + static isWindows(): boolean { + const platform = window.navigator.platform; + return platform.toLowerCase().indexOf('win') === 0; + } + static wrapUrlWithProxy(url: string): string{ if (Kernel.proxy) { return Kernel.proxy + "?" + url; diff --git a/src/core/world/geometries/Line.ts b/src/core/world/geometries/Line.ts new file mode 100644 index 0000000..288d874 --- /dev/null +++ b/src/core/world/geometries/Line.ts @@ -0,0 +1,39 @@ +import Kernel from '../Kernel'; +import Object3D from '../Object3D'; +import Vertice from '../math/Vertice'; +import VertexBufferObject from '../VertexBufferObject'; + +export default class Line extends Object3D{ + public vbo: VertexBufferObject = null; + private verticeCount: number = 0; + + constructor(vertices: Vertice[]){ + super(); + const data:number[] = []; + this.verticeCount = vertices.length; + for(var i = 0; i < this.verticeCount; i++){ + const vertice = vertices[i]; + data.push(vertice.x); + data.push(vertice.y); + data.push(vertice.z); + } + this.vbo = new VertexBufferObject(Kernel.gl.ARRAY_BUFFER); + this.vbo.bind(); + this.vbo.bufferData(data, Kernel.gl.STATIC_DRAW, true); + } + + isReady(){ + return this.verticeCount >= 2; + } + + getVerticeCount(){ + return this.verticeCount; + } + + destroy(){ + if(this.vbo){ + this.vbo.destroy(); + } + this.vbo = null; + } +} \ No newline at end of file diff --git a/src/core/world/geometries/Mesh.ts b/src/core/world/geometries/Mesh.ts index 6b395f1..b090d8e 100644 --- a/src/core/world/geometries/Mesh.ts +++ b/src/core/world/geometries/Mesh.ts @@ -1,19 +1,29 @@ import Kernel from '../Kernel'; -import Vertice from './MeshVertice'; +import MeshVertice from './MeshVertice'; import Triangle from './Triangle'; import Object3D from '../Object3D'; import VertexBufferObject from '../VertexBufferObject'; +import Vertice from '../math/Vertice'; +import Ray from '../math/Ray'; +import Line from '../math/Line'; +import MathUtils from '../math/Utils'; + +interface Box{ + center: Vertice;//中心点,模型坐标系中的中心点 + radius: number;//半径,模型坐标系中的半径 +} export default class Mesh extends Object3D { - vertices: Vertice[] = null; + vertices: MeshVertice[] = null; triangles: Triangle[] = null; vbo: VertexBufferObject = null; ibo: VertexBufferObject = null; nbo: VertexBufferObject = null; uvbo: VertexBufferObject = null; cbo: VertexBufferObject = null; + box: Box = null;//local box - static buildPlane(vLeftTop: Vertice, vLeftBottom: Vertice, vRightTop: Vertice, vRightBottom: Vertice) { + static buildPlane(vLeftTop: MeshVertice, vLeftBottom: MeshVertice, vRightTop: MeshVertice, vRightBottom: MeshVertice) { /*对于一个面从外面向里面看的绘制顺序 * 0 2 * @@ -29,12 +39,111 @@ export default class Mesh extends Object3D { return [tri0, tri1]; } + static buildMesh(vLeftTop: MeshVertice, vLeftBottom: MeshVertice, vRightTop: MeshVertice, vRightBottom: MeshVertice){ + const mesh = new Mesh(); + mesh.vertices = [vLeftTop, vLeftBottom, vRightTop, vRightBottom]; + mesh.triangles = this.buildPlane(vLeftTop, vLeftBottom, vRightTop, vRightBottom); + return mesh; + } + constructor(){ super(); this.vertices = []; this.triangles = []; } + updateBox(force: boolean = false){ + const triCount = this.triangles.length; + const verCount = this.vertices.length; + if(triCount === 0 || verCount <= 3){ + this.box = null; + return; + } + if(!this.box || force){ + let maxX = -Infinity; + let maxY = -Infinity; + let maxZ = -Infinity; + let minX = Infinity; + let minY = Infinity; + let minZ = Infinity; + for(let i = 0; i < verCount; i++){ + const vertice = this.vertices[i]; + const [x, y, z] = vertice.p; + if(x > maxX){ + maxX = x; + } + if(y > maxY){ + maxY = y; + } + if(z > maxZ){ + maxZ = z; + } + if(x < minX){ + minX = x; + } + if(y < minY){ + minY = y; + } + if(z < minZ){ + minZ = z; + } + } + const deltaX = maxX - minX; + const deltaY = maxY - minY; + const deltaZ = maxZ - minZ; + const radius = Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ); + const center = new Vertice( + (maxX + minX) / 2, + (maxY + minY) / 2, + (maxZ + minZ) / 2 + ); + this.box = { + center: center, + radius: radius + }; + } + } + + /** + * 判断mesh是否与直线相交,其中直线为世界坐标系中的坐标 + * @param worldLine + */ + ifIntersectWorldLine(worldLine: Line){ + const localLine = MathUtils.convertWorldLineToLocalLine(worldLine, this.matrix); + return this.ifIntersectLocalLine(localLine); + } + + /** + * 判断mesh是否与直线相交,其中直线为模型坐标系中的坐标 + * @param localLine + */ + ifIntersectLocalLine(localLine: Line){ + this.updateBox(false); + if(!this.box){ + return false; + } + + //首先判断射线是否与box相交,如果与box不想交,则肯定不想交 + const distance = MathUtils.getLengthFromVerticeToLine(this.box.center, localLine); + if(distance > this.box.radius){ + return false; + } + + //在射线与box相交的前提下再判断射线是否与Mesh具体相交 + const count = this.triangles.length; + for(let i = 0; i < count; i++){ + const tri = this.triangles[i]; + const v1 = new Vertice(tri.v1.p[0], tri.v1.p[1], tri.v1.p[2]); + const v2 = new Vertice(tri.v2.p[0], tri.v2.p[1], tri.v2.p[2]); + const v3 = new Vertice(tri.v3.p[0], tri.v3.p[1], tri.v3.p[2]); + const isIntersected = MathUtils.intersectTriangle(localLine.vertice, localLine.vector, v1, v2, v3); + if(isIntersected){ + return true; + } + } + return false; + } + //set vertices and triangles buildTriangles(){ this.vertices = []; @@ -43,7 +152,7 @@ export default class Mesh extends Object3D { calculateVBO(force:boolean = false) { if (!this.vbo || force) { - var vboData:number[] = [], vertex:Vertice; + var vboData:number[] = [], vertex:MeshVertice; for (var i = 0, length = this.vertices.length; i < length; i++) { vertex = this.vertices[i]; @@ -85,7 +194,7 @@ export default class Mesh extends Object3D { calculateNBO(force:boolean = false) { if (!this.nbo || force) { - var nboData:number[] = [], vertex:Vertice; + var nboData:number[] = [], vertex:MeshVertice; for (var i = 0, length = this.vertices.length; i < length; i++) { vertex = this.vertices[i]; @@ -106,7 +215,7 @@ export default class Mesh extends Object3D { calculateUVBO(force:boolean = false) { if (!this.uvbo || force) { - var uvboData:number[] = [], vertex:Vertice; + var uvboData:number[] = [], vertex:MeshVertice; for (var i = 0, length = this.vertices.length; i < length; i++) { vertex = this.vertices[i]; @@ -126,7 +235,7 @@ export default class Mesh extends Object3D { calculateCBO(force:boolean = false) { if (!this.cbo || force) { - var cboData:number[] = [], vertex:Vertice; + var cboData:number[] = [], vertex:MeshVertice; for (var i = 0, length = this.vertices.length; i < length; i++) { vertex = this.vertices[i]; diff --git a/src/core/world/geometries/MeshVertice.ts b/src/core/world/geometries/MeshVertice.ts index 30d2dff..bc8e79b 100644 --- a/src/core/world/geometries/MeshVertice.ts +++ b/src/core/world/geometries/MeshVertice.ts @@ -13,4 +13,14 @@ this.n = args.n;//[x,y,z] this.c = args.c;//[r,g,b] } + + clone(){ + var args: any = {}; + args.i = this.i; + args.p = [...this.p]; + args.uv = [...this.uv]; + args.n = [...this.n]; + args.c = [...this.c]; + return new MeshVertice(args); + } }; \ No newline at end of file diff --git a/src/core/world/graphics/Graphic.ts b/src/core/world/graphics/Graphic.ts index 90ccd81..7c27ee6 100644 --- a/src/core/world/graphics/Graphic.ts +++ b/src/core/world/graphics/Graphic.ts @@ -1,5 +1,5 @@ import Kernel from '../Kernel'; -import {Drawable} from '../Definitions.d'; +import {Drawable, Attributes} from '../Definitions.d'; import Geometry from '../geometries/Geometry'; import Material from '../materials/Material'; import Program from '../Program'; @@ -12,7 +12,7 @@ abstract class Graphic implements Drawable{ parent: GraphicGroup; program: Program; - constructor(public geometry: Geometry = null, public material: Material = null){ + constructor(public geometry: Geometry = null, public material: Material = null, public attributes: Attributes = null){ this.id = ++Kernel.idCounter; this.parent = null; this.program = this.createProgram(); diff --git a/src/core/world/graphics/LineGraphic.ts b/src/core/world/graphics/LineGraphic.ts new file mode 100644 index 0000000..94b8ab2 --- /dev/null +++ b/src/core/world/graphics/LineGraphic.ts @@ -0,0 +1,71 @@ +import Kernel from '../Kernel'; +import Program from '../Program'; +import Graphic from './Graphic'; +import Line from '../geometries/Line'; +import ColorMaterial from '../materials/ColorMaterial'; +import Camera from '../Camera'; + +const vs = +` +attribute vec3 aPosition; +uniform vec3 uColor; +varying vec4 vColor; +uniform mat4 uPMVMatrix; + +void main(void) +{ + gl_Position = uPMVMatrix * vec4(aPosition, 1.0); + vColor = vec4(uColor,1.0); +} +`; + +const fs = +` +#ifdef GL_ES +precision highp float; +#endif + +varying vec4 vColor; + +void main(void) { + gl_FragColor = vColor; +} +`; + + +export default class LineGraphic extends Graphic{ + constructor(public geometry: Line, public material: ColorMaterial, public lineWidth: number = 1){ + super(geometry, material); + } + + isReady(){ + return this.geometry.isReady(); + } + + createProgram(): Program{ + return Program.getProgram(vs, fs); + } + + protected onDraw(camera: Camera){ + const gl = Kernel.gl; + + //uPMVMatrix + var pmvMatrix = camera.getProjViewMatrixForDraw();//.multiplyMatrix(this.geometry.getMatrix()); + var locPMVMatrix = this.program.getUniformLocation('uPMVMatrix'); + gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.getFloat32Array()); + + //aPosition + var locPosition = this.program.getAttribLocation('aPosition'); + this.program.enableVertexAttribArray('aPosition'); + this.geometry.vbo.bind(); + gl.vertexAttribPointer(locPosition, 3, Kernel.gl.FLOAT, false, 0, 0); + + //uColor + var locColor = this.program.getUniformLocation('uColor'); + gl.uniform3fv(locColor, this.material.color); + + //绘图 + gl.lineWidth(this.lineWidth); + gl.drawArrays(gl.LINE_STRIP, 0, this.geometry.getVerticeCount()); + } +} \ No newline at end of file diff --git a/src/core/world/graphics/LocationGraphic.ts b/src/core/world/graphics/LocationGraphic.ts index 6509523..2e409ad 100644 --- a/src/core/world/graphics/LocationGraphic.ts +++ b/src/core/world/graphics/LocationGraphic.ts @@ -5,7 +5,7 @@ import MultiPointsGraphic from '../graphics/MultiPointsGraphic'; import MarkerTextureMaterial from '../materials/MarkerTextureMaterial'; import Service from '../Service'; import Globe from '../Globe'; -const locationImageUrl = require("../images/location.png"); +const locationImageUrl = require('../images/location.png'); export default class LocationGraphic extends MultiPointsGraphic { private constructor(material: MarkerTextureMaterial, private globe: Globe) { diff --git a/src/core/world/graphics/MeshColorGraphic.ts b/src/core/world/graphics/MeshColorGraphic.ts index 72a237d..3503013 100644 --- a/src/core/world/graphics/MeshColorGraphic.ts +++ b/src/core/world/graphics/MeshColorGraphic.ts @@ -3,7 +3,7 @@ import Program from '../Program'; import Graphic from './Graphic'; import Mesh from '../geometries/Mesh'; import MeshVertice from '../geometries/MeshVertice'; -import MeshColorMaterial from '../materials/MeshColorMaterial'; +import ColorMaterial from '../materials/ColorMaterial'; import Camera from '../Camera'; const vs = @@ -32,7 +32,7 @@ void main() `; export default class MeshColorGraphic extends Graphic { - constructor(public geometry: Mesh, public material: MeshColorMaterial){ + constructor(public geometry: Mesh, public material: ColorMaterial){ super(geometry, material); this.setGeometry(geometry); } diff --git a/src/core/world/graphics/MeshTextureGraphic.ts b/src/core/world/graphics/MeshTextureGraphic.ts index 1bce039..322e164 100644 --- a/src/core/world/graphics/MeshTextureGraphic.ts +++ b/src/core/world/graphics/MeshTextureGraphic.ts @@ -3,7 +3,9 @@ import Program from '../Program'; import Graphic from './Graphic'; import Mesh from '../geometries/Mesh'; import MeshTextureMaterial from '../materials/MeshTextureMaterial'; -import Camera from "../Camera"; +import Camera from '../Camera'; +import Line from '../math/Line'; +import {Drawable, Pickable, Attributes} from '../Definitions.d'; const vs = ` @@ -31,9 +33,9 @@ void main() } `; -export default class MeshTextureGraphic extends Graphic { - constructor(public geometry: Mesh, public material: MeshTextureMaterial){ - super(geometry, material); +export default class MeshTextureGraphic extends Graphic implements Pickable { + constructor(public geometry: Mesh, public material: MeshTextureMaterial, public attributes: Attributes = null){ + super(geometry, material, attributes); this.geometry.calculateVBO(); this.geometry.calculateIBO(); this.geometry.calculateUVBO(); @@ -100,4 +102,18 @@ export default class MeshTextureGraphic extends Graphic { // gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); // gl.bindTexture(gl.TEXTURE_2D, null); } + + ifIntersectLocalLine(localLine: Line):boolean { + if(this.geometry){ + return this.geometry.ifIntersectLocalLine(localLine); + } + return false; + } + + ifIntersectWorldLine(worldLine: Line): boolean{ + if(this.geometry){ + return this.geometry.ifIntersectWorldLine(worldLine); + } + return false; + } }; \ No newline at end of file diff --git a/src/core/world/graphics/MultiPointsGraphic.ts b/src/core/world/graphics/MultiPointsGraphic.ts index f8b0f4a..400287a 100644 --- a/src/core/world/graphics/MultiPointsGraphic.ts +++ b/src/core/world/graphics/MultiPointsGraphic.ts @@ -33,10 +33,10 @@ uniform sampler2D uSampler; void main() { vec4 color = texture2D(uSampler, vec2(gl_PointCoord.x, gl_PointCoord.y)); - if(color.a == 0.0){ - discard; - } - gl_FragColor = color; + if(color.a == 0.0){ + discard; + } + gl_FragColor = color; } `; @@ -44,18 +44,12 @@ export default class MultiPointsGraphic extends Graphic { private vbo: VertexBufferObject = null; private vertices: Vertice[] = null; - protected constructor(public material: MarkerTextureMaterial) { + constructor(public material: MarkerTextureMaterial) { super(null, material); this.vbo = new VertexBufferObject(Kernel.gl.ARRAY_BUFFER); this.vertices = []; - // this._addPoi(116.408540, 39.902350, "3161565500563468633", "首都大酒店", "北京市东城区前门东大街3号", ""); } - // static getInstance(iconUrl: string, iconSize: number = 16) { - // var material = new MarkerTextureMaterial(iconUrl, iconSize); - // return new MultiPointsGraphic(material); - // } - createProgram() { return Program.getProgram(vs, fs); } @@ -65,7 +59,7 @@ export default class MultiPointsGraphic extends Graphic { } onDraw(camera: Camera) { - var gl = Kernel.gl; + const gl = Kernel.gl; gl.disable(Kernel.gl.DEPTH_TEST); gl.depthMask(false); @@ -94,20 +88,20 @@ export default class MultiPointsGraphic extends Graphic { //set uSampler var locSampler = this.program.getUniformLocation('uSampler'); - gl.activeTexture(Kernel.gl.TEXTURE0); - gl.bindTexture(Kernel.gl.TEXTURE_2D, this.material.texture); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.material.texture); gl.uniform1i(locSampler, 0); //绘图,vertices.length / 3表示所绘点的个数 - gl.drawArrays(Kernel.gl.POINTS, 0, vertices.length / 3); + gl.drawArrays(gl.POINTS, 0, vertices.length / 3); //释放当前绑定对象 - gl.enable(Kernel.gl.DEPTH_TEST); + gl.enable(gl.DEPTH_TEST); gl.depthMask(true); - gl.disable(Kernel.gl.BLEND); - // gl.bindBuffer(Kernel.gl.ARRAY_BUFFER, null); - // gl.bindBuffer(Kernel.gl.ELEMENT_ARRAY_BUFFER, null); - // gl.bindTexture(Kernel.gl.TEXTURE_2D, null); + gl.disable(gl.BLEND); + // gl.bindBuffer(gl.ARRAY_BUFFER, null); + // gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); + // gl.bindTexture(gl.TEXTURE_2D, null); } setLonlats(lonlats:number[][]){ diff --git a/src/core/world/images/1.png b/src/core/world/images/1.png new file mode 100644 index 0000000..2d59e91 Binary files /dev/null and b/src/core/world/images/1.png differ diff --git a/src/core/world/images/end.png b/src/core/world/images/end.png new file mode 100644 index 0000000..2bdb49a Binary files /dev/null and b/src/core/world/images/end.png differ diff --git a/src/core/world/images/end1.png b/src/core/world/images/end1.png new file mode 100644 index 0000000..175184b Binary files /dev/null and b/src/core/world/images/end1.png differ diff --git a/src/core/world/images/end2.png b/src/core/world/images/end2.png new file mode 100644 index 0000000..c569b02 Binary files /dev/null and b/src/core/world/images/end2.png differ diff --git a/src/core/world/images/icons.png b/src/core/world/images/icons.png new file mode 100644 index 0000000..50b9a25 Binary files /dev/null and b/src/core/world/images/icons.png differ diff --git a/src/core/world/images/start.png b/src/core/world/images/start.png new file mode 100644 index 0000000..61792fa Binary files /dev/null and b/src/core/world/images/start.png differ diff --git a/src/core/world/images/start1.png b/src/core/world/images/start1.png new file mode 100644 index 0000000..273dfd7 Binary files /dev/null and b/src/core/world/images/start1.png differ diff --git a/src/core/world/images/start2.png b/src/core/world/images/start2.png new file mode 100644 index 0000000..0a62702 Binary files /dev/null and b/src/core/world/images/start2.png differ diff --git a/src/core/world/layers/PoiLayer.ts b/src/core/world/layers/PoiLayer.ts index a015ece..3250e5e 100644 --- a/src/core/world/layers/PoiLayer.ts +++ b/src/core/world/layers/PoiLayer.ts @@ -2,47 +2,175 @@ declare function require(name: string): any; import Kernel from '../Kernel'; import Utils from '../Utils'; import MathUtils from '../math/Utils'; -import MultiPointsGraphic from '../graphics/MultiPointsGraphic'; -import MarkerTextureMaterial from '../materials/MarkerTextureMaterial'; +import Vector from '../math/Vector'; +import MeshVertice from '../geometries/MeshVertice'; +import Mesh from '../geometries/Mesh'; +import Graphic from '../graphics/Graphic'; +import MeshTextureGraphic from '../graphics/MeshTextureGraphic'; +import GraphicGroup,{PickableGraphicGroup} from '../GraphicGroup'; +import MeshTextureMaterial from '../materials/MeshTextureMaterial'; import Service, { Location, SearchType } from '../Service'; import Globe from '../Globe'; import Extent from '../Extent'; -const poiImgUrl = require("../images/red.png"); - -class Poi { - constructor( - public x: number, - public y: number, - public z: number, - public uuid: string, - public name: string, - public address: string, - public phone: string) { } -} - -export default class PoiLayer extends MultiPointsGraphic { +const poiImgUrl = require('../images/icons.png'); + +type HighlightListener = (graphic: MeshTextureGraphic) => void; +type UnHighlightListener = () => void; + +export default class PoiLayer extends PickableGraphicGroup{ private keyword: string = null; private searchExtentMode: boolean = false; - private pois: Poi[] = null; public globe: Globe = null; + private currentHighLightPoi: MeshTextureGraphic = null; + private highlightListener: HighlightListener = null; + private unHighlightListener: UnHighlightListener = null; + + //icons.png的尺寸是874X524 + private readonly iconsWidth = 874; + private readonly iconsHeight = 524; + + //icons划分为6行10列 + private readonly iconsRow = 6; + private readonly iconsColumn = 10; + private readonly MAX_POI_COUNT = this.iconsColumn;//一次最多显示10个poi + + //每个小图标有效范围大小为50X70 + private readonly validPinIconWidth = 50; + private readonly validPinIconHeight = 70; - private constructor(public material: MarkerTextureMaterial) { - super(material); - this.pois = []; - Utils.subscribe("extent-change", () => { + //使用icons.png中的第三行(0 based)作为normal material + private readonly normalMaterialRow = 3; + //使用icons.png中的第一行(0 based)作为highlight material + private readonly highLightMaterialRow = 1; + + //需要计算 + //每个图标占据的范围(包括周围的透明) + private readonly pinIconWidth = this.iconsWidth / this.iconsColumn; + private readonly pinIconHeight = this.iconsHeight / this.iconsRow; + //每个小图标(包括周围的透明)所占用的U空间和V空间 + private pinIconDeltaU = this.pinIconWidth / this.iconsWidth; + private pinIconDeltaV = this.pinIconHeight / this.iconsHeight; + //每个小图标的有效部分所占用的U空间和V空间 + private validPinIconDeltaU = this.validPinIconWidth / this.iconsWidth; + private validPinIconDeltaV = this.validPinIconHeight / this.iconsHeight; + //计算小图标有效区域左上角到整个小图标左上角的UV偏移量 + private validPinIconOffsetU = (this.pinIconDeltaU - this.validPinIconDeltaU) / 2; + private validPinIconOffsetV = (this.pinIconDeltaV - this.validPinIconDeltaV) / 2; + + //在屏幕上绘制的时候POI的像素大小,宽高比需要和validPinIconHeight/validPinIconWidth保持一致 + private readonly poiPixelWidth = 30; + private readonly poiPixelHeight = 42; + + private constructor() { + super(); + Utils.subscribe('extent-change', () => { if (this.searchExtentMode && this.keyword) { this.search(this.keyword); } }); + + Utils.subscribe('level-change', () => { + if (this.children.length > 0) { + const resolution = this.globe.camera.getResolution(); + this.children.forEach((graphic: MeshTextureGraphic) => { + const scale = resolution / (graphic.geometry as any).resolution; + graphic.geometry.localScale(scale, scale, scale); + (graphic.geometry as any).resolution = resolution; + }); + } + }); + this.setPickListener((target: MeshTextureGraphic) => { + if(this.currentHighLightPoi !== target){ + this.unHighlightPoi(); + this.highlightPoi(target); + } + }); } static getInstance(): PoiLayer { - var material = new MarkerTextureMaterial(poiImgUrl, 16); - return new PoiLayer(material); + return new PoiLayer(); } - isReady(){ - return this.globe && this.globe.camera.isEarthFullOverlapScreen() && super.isReady(); + /** + * 传入行列号,返回该图标的四个角点的UV值 + * @param row 0 based + * @param column 0 based + */ + private _getUV(row: number, column: number):number[][]{ + const smallV = this.pinIconDeltaV * row + this.validPinIconOffsetV; + const bigV = smallV + this.validPinIconDeltaV; + const smallU = this.pinIconDeltaU * column + this.validPinIconOffsetU; + const bigU = smallU + this.validPinIconDeltaU; + const v0:number[] = [smallU, smallV];//左上 + const v1:number[] = [smallU, bigV];//左下 + const v2:number[] = [bigU, smallV];//右上 + const v3:number[] = [bigU, bigV];//右下 + return [v0, v1, v2, v3]; + } + + getHighlightPoi(){ + return this.currentHighLightPoi; + } + + highlightPoi(target: MeshTextureGraphic){ + if(this.currentHighLightPoi === target){ + return; + } + this.unHighlightPoi(); + this.currentHighLightPoi = target; + this._updateMaterial(this.currentHighLightPoi, this.highLightMaterialRow); + this.moveChildToLastPosition(this.currentHighLightPoi); + if(this.highlightListener){ + this.highlightListener(this.currentHighLightPoi); + } + } + + unHighlightPoi(){ + if(this.currentHighLightPoi){ + this._updateMaterial(this.currentHighLightPoi, this.normalMaterialRow); + this.currentHighLightPoi = null; + if(this.unHighlightListener){ + this.unHighlightListener(); + } + } + } + + setHighlightListener(listener: HighlightListener){ + this.highlightListener = listener; + } + + setUnHighlightListener(listener: UnHighlightListener){ + this.unHighlightListener = listener; + } + + private _updateMaterial(target: MeshTextureGraphic, row: number){ + const columnIndex = (target as any).columnIndex; + const uv = this._getUV(row, columnIndex); + const [vLeftTop, vLeftBottom, vRightTop, vRightBottom] = target.geometry.vertices; + vLeftTop.uv = uv[0]; + vLeftBottom.uv = uv[1]; + vRightTop.uv = uv[2]; + vRightBottom.uv = uv[3]; + target.geometry.calculateUVBO(true); + } + + shouldDraw() { + return this.globe && this.globe.camera.isEarthFullOverlapScreen() && super.shouldDraw(); + } + + onBeforeDraw(){ + const gl = Kernel.gl; + gl.disable(gl.DEPTH_TEST); + // gl.depthMask(false); + gl.enable(Kernel.gl.BLEND); + gl.blendFunc(Kernel.gl.SRC_ALPHA, Kernel.gl.ONE_MINUS_SRC_ALPHA); + } + + onAfterDraw(){ + const gl = Kernel.gl; + gl.enable(gl.DEPTH_TEST); + // gl.depthMask(true); + gl.disable(Kernel.gl.BLEND); } destroy() { @@ -50,47 +178,114 @@ export default class PoiLayer extends MultiPointsGraphic { super.destroy(); } - clear() { + clear(){ + this.currentHighLightPoi = null; super.clear(); + } + + clearAll() { + this.clear(); this.keyword = null; - this.pois = []; } - private _addPoi(lon: number, lat: number, uuid: string, name: string, address: string, phone: string) { - var p = MathUtils.geographicToCartesianCoord(lon, lat, Kernel.EARTH_RADIUS + 0.001); - var poi = new Poi(p.x, p.y, p.z, uuid, name, address, phone); - this.pois.push(poi); - return poi; + private _addPoi(lon: number, lat: number, resolution: number, item: any, index: number) { + const localMatrix = this.globe.camera.getMatrix().clone(); + const pCenterBottom = MathUtils.geographicToCartesianCoord(lon, lat, Kernel.EARTH_RADIUS + 0.001); + localMatrix.setPosition(pCenterBottom); + + const localXAxisVector = new Vector(1, 0, 0); + const halfWidth = resolution * this.poiPixelWidth / 2; + localXAxisVector.setLength(halfWidth); + const v3 = localXAxisVector.getVertice(); + const v1 = localXAxisVector.getOpposite().getVertice(); + + const localUp = new Vector(0, 1, 0); + const height = resolution * this.poiPixelHeight; + localUp.setLength(height); + const v0 = Vector.verticePlusVector(v1, localUp); + const v2 = Vector.verticePlusVector(v3, localUp); + + const uv = this._getUV(this.normalMaterialRow, index); + + //左上角 + const meshV0 = new MeshVertice({ + i: 0, + p: v0.getArray(), + uv: uv[0] + }); + //左下角 + const meshV1 = new MeshVertice({ + i: 1, + p: v1.getArray(), + uv: uv[1] + }); + //右上角 + const meshV2 = new MeshVertice({ + i: 2, + p: v2.getArray(), + uv: uv[2] + }); + //右下角 + const meshV3 = new MeshVertice({ + i: 3, + p: v3.getArray(), + uv: uv[3] + }); + + const mesh = Mesh.buildMesh(meshV0, meshV1, meshV2, meshV3); + mesh.setMatrix(localMatrix); + const material = new MeshTextureMaterial(poiImgUrl); + const graphic = new MeshTextureGraphic(mesh, material, item); + (graphic.geometry as any).resolution = resolution; + (graphic as any).columnIndex = index; + + this.add(graphic); + return graphic; } private _showPois(searchResponse: any) { - var data = searchResponse.detail.pois || []; - if(data.length > 0){ - var lonlats: number[][] = data.map((item: any) => { - var lon = parseFloat(item.pointx); - var lat = parseFloat(item.pointy); - this._addPoi(lon, lat, item.uid, item.name, item.addr, item.phone); - return [lon, lat]; - }); - this.setLonlats(lonlats); - if(lonlats.length > 1){ - const extent = Extent.fromLonlats(lonlats); - this.globe.setExtent(extent); - }else{ - const lonlat = lonlats[0]; - this.globe.centerTo(lonlat[0], lonlat[1]); - } + this.clear(); + let pois: any[] = searchResponse.detail.pois || []; + if (pois.length === 0) { + return; + } + + if(pois.length > this.MAX_POI_COUNT){ + pois = pois.slice(0, this.MAX_POI_COUNT); } + + const lonlats: number[][] = pois.map((item: any) => { + var lon = parseFloat(item.pointx); + var lat = parseFloat(item.pointy); + return [lon, lat]; + }); + + if (lonlats.length > 1) { + const extent = Extent.fromLonlats(lonlats); + this.globe.setExtent(extent); + } else { + const lonlat = lonlats[0]; + this.globe.centerTo(lonlat[0], lonlat[1]); + } + + const resolution = this.globe.camera.getResolution(); + + //添加graphics + searchResponse.detail.graphics = pois.map((item: any, index: number) => { + var lon = parseFloat(item.pointx); + var lat = parseFloat(item.pointy); + return this._addPoi(lon, lat, resolution, item, index); + }); } search(keyword: string) { this.searchExtentMode = true; - this.clear(); + this.clearAll(); this.keyword = keyword; var level = this.globe.getLevel(); if (level >= 10) { var extent = this.globe.getExtent(); - if(extent){ + if (extent) { Service.searchByExtent(keyword, level, extent).then((response: any) => { this._showPois(response); }); @@ -99,6 +294,8 @@ export default class PoiLayer extends MultiPointsGraphic { } searchNearby(keyword: string, radius: number, searchType: SearchType = 'Auto', pageCapacity: number = 50, pageIndex: number = 0) { + this.clearAll(); + this.keyword = keyword; this.searchExtentMode = false; return Service.searchNearby(keyword, radius, searchType, false, pageCapacity, pageIndex).then((response: any) => { this._showPois(response); @@ -106,10 +303,12 @@ export default class PoiLayer extends MultiPointsGraphic { }); } - searchByCurrentCity(keyword: string, searchType: SearchType = 'Auto', pageCapacity: number = 50, pageIndex: number = 0){ + searchByCurrentCity(keyword: string, searchType: SearchType = 'Auto', pageCapacity: number = 50, pageIndex: number = 0) { + this.clearAll(); + this.keyword = keyword; return Service.searchByCurrentCity(keyword, searchType, pageCapacity, pageIndex).then((response: any) => { - if(response){ - if(!response.location){ + if (response) { + if (!response.location) { response.location = this.globe.getLonlat(); } } diff --git a/src/core/world/layers/RouteLayer.ts b/src/core/world/layers/RouteLayer.ts index 00f352b..44f47a2 100644 --- a/src/core/world/layers/RouteLayer.ts +++ b/src/core/world/layers/RouteLayer.ts @@ -1,3 +1,4 @@ +declare function require(name: string): any; import Kernel from '../Kernel'; import Utils from '../Utils'; import MathUtils from '../math/Utils'; @@ -7,12 +8,24 @@ import MeshVertice from '../geometries/MeshVertice'; import Triangle from '../geometries/Triangle'; import Mesh from '../geometries/Mesh'; import MeshColorGraphic from '../graphics/MeshColorGraphic'; -import MeshColorMaterial from '../materials/MeshColorMaterial'; +import ColorMaterial from '../materials/ColorMaterial'; +import MarkerTextureMaterial from '../materials/MarkerTextureMaterial'; +import Graphic from '../graphics/Graphic'; +import MultiPointsGraphic from '../graphics/MultiPointsGraphic'; +// import Line from '../geometries/Line'; +// import LineGraphic from '../graphics/LineGraphic'; import GraphicGroup from '../GraphicGroup'; import { Drawable } from '../Definitions.d'; import Camera from '../Camera'; import Service from '../Service'; import Extent from '../Extent'; +import Program from '../Program'; + +const startPointImageUrl = require('../images/start.png'); +const startPointImageSize = 80; + +const endPointImageUrl = require('../images/end.png'); +const endPointImageSize = 80; interface RoutePoint { lonlat: number[]; @@ -20,23 +33,110 @@ interface RoutePoint { start2EndVector: Vector; v1: MeshVertice; v3: MeshVertice; - B12: boolean; - B34: boolean; + // B12: boolean; + // B34: boolean; } -class RouteGraphic extends MeshColorGraphic { - private readonly inflexionPointAngle = 70;//认为两条道路出现近乎垂直情况时候的夹角 +type RouteType = 'driving' | 'bus' | 'walking'; + +// const isWindows = Utils.isWindows(); + +class MeshRouteGraphic extends MeshColorGraphic { + private readonly inflexionPointAngle = 30;//认为两条道路出现近乎垂直情况时候的夹角 - constructor(private lonlats: number[][], private pixelWidth: number, resolution: number, material: MeshColorMaterial) { + constructor(private originalLonlats: number[][], private pixelWidth: number, resolution: number, material: ColorMaterial) { super(null, material); + this._removeDuplicatePoints(this.originalLonlats); this.updateGeometry(resolution); } updateGeometry(resolution: number) { - const geometry = this._getRouteGeometryByLonlats(this.lonlats, resolution, this.pixelWidth); + var lonlats = this._handleCurveJoin(this.originalLonlats, resolution, this.pixelWidth); + const geometry = this._getRouteGeometryByLonlats(lonlats, resolution, this.pixelWidth); this.setGeometry(geometry); } + private _removeDuplicatePoints(lonlats: number[][]){ + let cursor: number = 0; + while(cursor <= (lonlats.length - 2)){ + let currentLonlat = lonlats[cursor]; + let nextLonlat = lonlats[cursor + 1]; + if(currentLonlat[0] === nextLonlat[0] && currentLonlat[1] === nextLonlat[1]){ + //删除重复点,不移动游标 + lonlats.splice(cursor + 1, 1); + }else{ + //移动游标 + cursor += 1; + } + } + } + + /** + * + * @param _lonlats 传入经纬度坐标数组,如果两条线段之间夹角大于一定角度,就认为是拐点,针对拐点生成贝塞尔曲线点,该方法不会修原数组 + */ + private _handleCurveJoin(_lonlats: number[][], resolution: number, pixelWidth: number){ + //把所有的经纬度转换为WebMercator坐标,方便进行对拐点进行曲线拟合 + // var xyArr = lonlats.map((lonlat: number[]) => { + // MathUtils.degreeGeographicToWebMercator(lonlat[0], lonlat[1]); + // }); + + var lonlats = [].concat(_lonlats); + + var cursor: number = 1; + + //在用p0、p1、p2生成贝塞尔曲线点的时候,offsetLonlat是理想的p0p1(p1p2)的距离,单位是角度 + const offsetLonlat = MathUtils.radianToDegree(resolution * pixelWidth / Kernel.EARTH_RADIUS); + + //每次都要动态判断lonlats.length,因为lonlats的内容可能是在不断增加的 + while(cursor <= (lonlats.length - 2)){ + const currentLonlat = lonlats[cursor]; + const prevLonlat = lonlats[cursor - 1]; + const nextLonlat = lonlats[cursor + 1]; + const currentPoint = new Vertice(currentLonlat[0], currentLonlat[1], 0); + const prevPoint = new Vertice(prevLonlat[0], prevLonlat[1], 0); + const nextPoint = new Vertice(nextLonlat[0], nextLonlat[1], 0); + //prevPoint -> currentPoint + const vector1 = Vector.verticeMinusVertice(currentPoint, prevPoint); + //currentPoint -> nextPoint + const vector2 = Vector.verticeMinusVertice(nextPoint, currentPoint); + //有可能nextPoint和currentPoint是相同的点,这样计算出的vector2长度为0,计算出的夹角为0 + const radian = Vector.getRadianOfTwoVectors(vector1, vector2); + const angle = MathUtils.radianToDegree(radian); + if(angle > this.inflexionPointAngle){ + //currentLonlat是拐点,需要对拐点进行处理 + let p0: number[] = null;//经纬度 + let p1: number[] = currentLonlat;//经纬度 + let p2: number[] = null;//经纬度 + //保证p0到p1的距离与p1到p2的距离相等,都只占Math.min(length1, length2)的N分之一 + let length1 = vector1.getLength(); + let length2 = vector2.getLength(); + //length1和length2都是以米为单位的 + // let length1 = MathUtils.getRealArcDistanceBetweenLonLats(currentLonlat[0], currentLonlat[1], prevLonlat[0], prevLonlat[1]); + // let length2 = MathUtils.getRealArcDistanceBetweenLonLats(currentLonlat[0], currentLonlat[1], nextLonlat[0], nextLonlat[1]); + let minLength = Math.min(length1, length2); + //deltaLength单位是角度 + let deltaLength = Math.min(offsetLonlat, minLength / 2); + //计算p0 + let p0Vertice = Vector.verticePlusVector(currentPoint, vector1.getOpposite().setLength(deltaLength)); + p0 = [p0Vertice.x, p0Vertice.y]; + //计算p2 + let p2Vertice = Vector.verticePlusVector(currentPoint, vector2.clone().setLength(deltaLength)); + p2 = [p2Vertice.x, p2Vertice.y]; + //curveLonlats包含了p0和p2 + const curveLonlats = MathUtils.quad(p0, p1, p2, 10); + //删除拐点currentLonlat,加入数组curveLonlats + lonlats.splice(cursor, 1, ...curveLonlats); + //让游标指向下一个点 + cursor += curveLonlats.length; + }else{ + //让游标指向下一个点 + cursor++; + } + } + return lonlats; + } + private _getRouteGeometryByLonlats(lonlats: number[][], resolution: number, pixelWidth: number) { const mesh = new Mesh(); @@ -46,16 +146,16 @@ class RouteGraphic extends MeshColorGraphic { vertice: MathUtils.geographicToCartesianCoord(lonlat[0], lonlat[1]), start2EndVector: null, v1: null, - v3: null, - B12: false,//起始拐点 - B34: false//终止拐点,需要额外插入 + v3: null + // B12: false,//起始拐点 + // B34: false//终止拐点,需要额外插入 } as RoutePoint; }); //检查路口拐点的情况,如果不做处理,那么就会形成箭头形状的道路, //比如道路AB和道路BC是垂直的,AB方向在B点生成的是B1和B2,BC在B点生成的是B3和B4,对于这种情况,我们人为再插入一个B点 //这样就相当于绘制了AB12、B12B34、B34C - const B12Indexes: number[] = [];//所有起始拐点的索引 + /*const B12Indexes: number[] = [];//所有起始拐点的索引 points.forEach((point: RoutePoint, index) => { if (index > 0 && index < points.length - 1) { const prevPoint = points[index - 1]; @@ -69,13 +169,13 @@ class RouteGraphic extends MeshColorGraphic { point.B12 = true; point.B34 = false; B12Indexes.push(index); - // console.log("拐点:", point); + console.log("拐点:", point); } } - }); + });*/ //逆向遍历B12Indexes,在所有起始拐点B12后面插入额外的终止拐点B34 - for (let i = B12Indexes.length - 1; i >= 0; i--) { + /*for (let i = B12Indexes.length - 1; i >= 0; i--) { const B12Index = B12Indexes[i]; const B12 = points[B12Index]; const B34 = { @@ -84,18 +184,18 @@ class RouteGraphic extends MeshColorGraphic { B34: true } as RoutePoint; points.splice(B12Index + 1, 0, B34); - } + }*/ points.forEach((startPoint: RoutePoint, index: number) => { if (index !== points.length - 1) { - if (startPoint.B12) { - //B12为true时,B12向量与AB相同 - const prevPoint = points[index - 1]; - startPoint.start2EndVector = prevPoint.start2EndVector; - } else { + // if (startPoint.B12) { + // //B12为true时,B12向量与AB相同 + // const prevPoint = points[index - 1]; + // startPoint.start2EndVector = prevPoint.start2EndVector; + // } else { const endPoint = points[index + 1]; startPoint.start2EndVector = Vector.verticeMinusVertice(endPoint.vertice, startPoint.vertice); - } + // } } else { const prevPoint = points[index - 1]; startPoint.start2EndVector = prevPoint.start2EndVector; @@ -179,13 +279,14 @@ class RouteGraphic extends MeshColorGraphic { } } -type RouteType = 'driving' | 'bus' | 'walking'; - export default class RouteLayer extends GraphicGroup{ private pixelWidth: number = 5; - private greenColor: number[] = [7, 215, 108]; - private blueColor: number[] = [67, 140, 237]; - private route: any = null; + private busColor: number[] = [82, 153, 255]; + private drivingColor: number[] = [0, 189, 0]; + private walkingColor: number[] = [76, 221, 38]; + private startLonlat: number[] = null;//起点 + private endLonlat: number[] = null;//终点 + private route: any = null;//路径查询结果 //将1米的作为连接两个经纬度点的最小阈值的平方 private deltaLonlatSquareThreshold: number = Math.pow(1 / (2 * Math.PI * Kernel.REAL_EARTH_RADIUS) * 360, 2); @@ -194,9 +295,9 @@ export default class RouteLayer extends GraphicGroup{ // this.test(); Utils.subscribe('level-change', () => { if (this.children.length > 0) { - const resolution = this._getResolution(); + const resolution = this.camera.getResolution(); this.children.forEach((graphic: Drawable) => { - if (graphic instanceof RouteGraphic) { + if (graphic instanceof MeshRouteGraphic) { graphic.updateGeometry(resolution); } }); @@ -208,7 +309,7 @@ export default class RouteLayer extends GraphicGroup{ // this.clear(); // const startLonLat: number[] = [116, 40];//[116, -40]; // const endLonLat: number[] = [116, 25];// [116, 40]; - const resolution = this._getResolution(); + const resolution = this.camera.getResolution(); // this._addRouteByLonlat(startLonLat, endLonLat, resolution, pixelWidth, segments, rgb); this._addRouteByLonlats([[90, 0], [120, 0], [120, 40]], resolution, this.pixelWidth, rgb); } @@ -219,6 +320,7 @@ export default class RouteLayer extends GraphicGroup{ } private _addRouteByLonlats(lonlats: number[][], resolution: number, pixelWidth: number, rgb: number[]) { + var graphic: Graphic = null; if (lonlats.length >= 2) { let validLonlats: number[][] = lonlats; @@ -243,22 +345,22 @@ export default class RouteLayer extends GraphicGroup{ // }); if (validLonlats.length >= 2) { - const graphic = new RouteGraphic(validLonlats, pixelWidth, resolution, new MeshColorMaterial(rgb)); + // if(isWindows){ + // graphic = new MeshRouteGraphic(validLonlats, pixelWidth, resolution, new ColorMaterial(rgb)); + // }else{ + // const vertices = validLonlats.map((lonlat) => { + // return MathUtils.geographicToCartesianCoord(lonlat[0], lonlat[1]); + // }); + // const line = new Line(vertices); + // graphic = new LineGraphic(line, new ColorMaterial(rgb), pixelWidth); + // } + // this.add(graphic); + // return graphic; + graphic = new MeshRouteGraphic(validLonlats, pixelWidth, resolution, new ColorMaterial(rgb)); this.add(graphic); - return graphic; } } - return null; - } - - private _getResolution() { - const { - resolutionX, - bestDisplayLevelFloatX, - resolutionY, - bestDisplayLevelFloatY - } = this.camera.measureXYResolutionAndBestDisplayLevel(); - return (resolutionX + resolutionY) / 2; + return graphic; } private _getLonlatsBySegments(startLonLat: number[], endLonLat: number[], segments: number) { @@ -279,6 +381,8 @@ export default class RouteLayer extends GraphicGroup{ this._showDrivingPath(pathIndex); } else if (this.route.type === 'bus') { this._showBusPath(pathIndex); + } else if( this.route.type === 'walking'){ + this._showWalkingPath(pathIndex); } } } @@ -287,6 +391,8 @@ export default class RouteLayer extends GraphicGroup{ return Service.routeByDriving(fromLon, fromLat, toLon, toLat, this.key, strategy).then((response: any) => { this._clearAll(); if (response.route && response.route.paths && response.route.paths.length > 0) { + this.startLonlat = [fromLon, fromLat]; + this.endLonlat = [toLon, toLat]; this.route = response.route; this.showPath(0); } @@ -300,32 +406,36 @@ export default class RouteLayer extends GraphicGroup{ if (path && path.steps && path.steps.length > 0) { this.clear(); const lonlats: number[][] = []; - const lonlatsSegments:number[][][] = []; - + // const lonlatsSegments: number[][][] = []; + path.steps.forEach((step: any, index: number, steps: any[]) => { + /* if (index !== 0) { let prevStep = steps[index - 1]; const joinLonlats: number[][] = [prevStep.lastLonlat, step.firstLonlat]; - // this._addRouteByLonlats(joinLonlats, resolution, this.pixelWidth, this.greenColor); + // this._addRouteByLonlats(joinLonlats, resolution, this.pixelWidth, this.drivingColor); lonlatsSegments.push(joinLonlats); lonlats.push(...joinLonlats); } - // this._addRouteByLonlats(step.lonlats, resolution, this.pixelWidth, this.greenColor); + // this._addRouteByLonlats(step.lonlats, resolution, this.pixelWidth, this.drivingColor); lonlatsSegments.push(step.lonlats); + */ lonlats.push(...step.lonlats); }); const extent = Extent.fromLonlats(lonlats); - if(extent){ + if (extent) { this.camera.setExtent(extent); - + setTimeout(() => { //It's better to show path after extent changed because we can use the new resolution. - const resolution = this._getResolution(); - lonlatsSegments.forEach((lonlats:number[][]) => { - this._addRouteByLonlats(lonlats, resolution, this.pixelWidth, this.greenColor); - }); + const resolution = this.camera.getResolution(); + /*lonlatsSegments.forEach((lonlats: number[][]) => { + this._addRouteByLonlats(lonlats, resolution, this.pixelWidth, this.drivingColor); + });*/ + this._addRouteByLonlats(lonlats, resolution, this.pixelWidth, this.drivingColor); + this._showStartEndPoints(); }, 0); } } @@ -336,6 +446,8 @@ export default class RouteLayer extends GraphicGroup{ return Service.routeByBus(fromLon, fromLat, toLon, toLat, startCity, endCity, this.key, strategy).then((response: any) => { this._clearAll(); if (response.route && response.route.transits && response.route.transits.length > 0) { + this.startLonlat = [fromLon, fromLat]; + this.endLonlat = [toLon, toLat]; this.route = response.route; this.showPath(0); } @@ -349,25 +461,25 @@ export default class RouteLayer extends GraphicGroup{ if (transit && transit.segments && transit.segments.length > 0) { this.clear(); const lonlats: number[][] = []; - const lonlatsSegments:number[][][] = []; + const lonlatsSegments: number[][][] = []; transit.segments.forEach((segment: any) => { if (segment.walking && segment.walking.lonlats && segment.walking.lonlats.length > 0) { - // this._addRouteByLonlats(segment.walking.lonlats, resolution, this.pixelWidth, this.greenColor); - segment.walking.lonlats.color = this.greenColor; + // this._addRouteByLonlats(segment.walking.lonlats, resolution, this.pixelWidth, this.walkingColor); + segment.walking.lonlats.color = this.walkingColor; lonlatsSegments.push(segment.walking.lonlats); lonlats.push(...segment.walking.lonlats); } if (segment.bus && segment.bus.lonlats && segment.bus.lonlats.length > 0) { - // this._addRouteByLonlats(segment.bus.lonlats, resolution, this.pixelWidth, this.blueColor); - segment.bus.lonlats.color = this.blueColor; + // this._addRouteByLonlats(segment.bus.lonlats, resolution, this.pixelWidth, this.busColor); + segment.bus.lonlats.color = this.busColor; lonlatsSegments.push(segment.bus.lonlats); lonlats.push(...segment.bus.lonlats); } - if(segment.railway && segment.railway.lonlats && segment.railway.lonlats.length > 0){ - segment.railway.lonlats.color = this.blueColor; + if (segment.railway && segment.railway.lonlats && segment.railway.lonlats.length > 0) { + segment.railway.lonlats.color = this.busColor; lonlatsSegments.push(segment.railway.lonlats); lonlats.push(...segment.railway.lonlats); } @@ -375,24 +487,27 @@ export default class RouteLayer extends GraphicGroup{ const extent = Extent.fromLonlats(lonlats); - if(extent){ + if (extent) { this.camera.setExtent(extent); setTimeout(() => { - const resolution = this._getResolution(); + const resolution = this.camera.getResolution(); lonlatsSegments.forEach((lonlats: number[][]) => { this._addRouteByLonlats(lonlats, resolution, this.pixelWidth, (lonlats as any).color); }); + this._showStartEndPoints(); }, 0); } } } } - routeByWalking(fromLon: number, fromLat: number, toLon: number, toLat: number){ + routeByWalking(fromLon: number, fromLat: number, toLon: number, toLat: number) { return Service.routeByWalking(fromLon, fromLat, toLon, toLat, this.key).then((response: any) => { this._clearAll(); - if(response.route && response.route.paths && response.route.paths.length > 0){ + if (response.route && response.route.paths && response.route.paths.length > 0) { + this.startLonlat = [fromLon, fromLat]; + this.endLonlat = [toLon, toLat]; this.route = response.route; this._showWalkingPath(0); } @@ -400,32 +515,51 @@ export default class RouteLayer extends GraphicGroup{ }); } - private _showWalkingPath(pathIndex: number){ - if(this.route && this.route.paths && this.route.paths.length > 0){ + private _showWalkingPath(pathIndex: number) { + if (this.route && this.route.paths && this.route.paths.length > 0) { const path = this.route.paths[pathIndex]; - if(path && path.steps && path.steps.length > 0){ + if (path && path.steps && path.steps.length > 0) { this.clear(); const lonlats: number[][] = []; - + path.steps.forEach((step: any) => { lonlats.push(...step.lonlats); }); const extent = Extent.fromLonlats(lonlats); - if(extent){ + if (extent) { this.camera.setExtent(extent); setTimeout(() => { - const resolution = this._getResolution(); - this._addRouteByLonlats(lonlats, resolution, this.pixelWidth, this.blueColor); + const resolution = this.camera.getResolution(); + this._addRouteByLonlats(lonlats, resolution, this.pixelWidth, this.walkingColor); + this._showStartEndPoints(); }, 0); } } } } + private _showStartEndPoints(){ + if(this.startLonlat){ + let material = new MarkerTextureMaterial(startPointImageUrl, startPointImageSize); + let startPointGraphic = new MultiPointsGraphic(material); + startPointGraphic.setLonlats([this.startLonlat]); + this.add(startPointGraphic); + } + + if(this.endLonlat){ + let material = new MarkerTextureMaterial(endPointImageUrl, endPointImageSize); + let endPointGraphic = new MultiPointsGraphic(material); + endPointGraphic.setLonlats([this.endLonlat]); + this.add(endPointGraphic); + } + } + private _clearAll() { + this.startLonlat = null; + this.endLonlat = null; this.route = null; this.clear(); } diff --git a/src/core/world/materials/MeshColorMaterial.ts b/src/core/world/materials/ColorMaterial.ts similarity index 95% rename from src/core/world/materials/MeshColorMaterial.ts rename to src/core/world/materials/ColorMaterial.ts index 842a061..5a5ece0 100644 --- a/src/core/world/materials/MeshColorMaterial.ts +++ b/src/core/world/materials/ColorMaterial.ts @@ -47,7 +47,7 @@ // } // }; -export default class MeshColorMaterial extends Material{ +export default class ColorMaterial extends Material{ public color: number[] = null;//rgb,0-1 constructor(rgb255: number[]){ diff --git a/src/core/world/math/Matrix.ts b/src/core/world/math/Matrix.ts index d46a583..8162052 100644 --- a/src/core/world/math/Matrix.ts +++ b/src/core/world/math/Matrix.ts @@ -354,7 +354,8 @@ export default class Matrix{ scaleX = (scaleX !== undefined) ? scaleX : 1; scaleY = (scaleY !== undefined) ? scaleY : 1; scaleZ = (scaleZ !== undefined) ? scaleZ : 1; - var m = new Matrix(scaleX, 0, 0, 0, + var m = new Matrix( + scaleX, 0, 0, 0, 0, scaleY, 0, 0, 0, 0, scaleZ, 0, 0, 0, 0, 1); @@ -372,7 +373,8 @@ export default class Matrix{ worldRotateX(radian: number): void { var c = Math.cos(radian); var s = Math.sin(radian); - var m = new Matrix(1, 0, 0, 0, + var m = new Matrix( + 1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1); @@ -383,8 +385,10 @@ export default class Matrix{ worldRotateY(radian: number): void { var c = Math.cos(radian); var s = Math.sin(radian); - var m = new Matrix(c, 0, s, 0, - 0, 1, 0, 0, -s, 0, c, 0, + var m = new Matrix( + c, 0, s, 0, + 0, 1, 0, 0, + -s, 0, c, 0, 0, 0, 0, 1); var result = m.multiplyMatrix(this); this.setMatrixByOther(result); @@ -393,7 +397,8 @@ export default class Matrix{ worldRotateZ(radian: number) { var c = Math.cos(radian); var s = Math.sin(radian); - var m = new Matrix(c, -s, 0, 0, + var m = new Matrix( + c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); @@ -450,7 +455,8 @@ export default class Matrix{ var m43 = 0.0; //M(3,2) var m44 = 1.0; //M(3,3) - var mat = new Matrix(m11, m12, m13, m14, + var mat = new Matrix( + m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); diff --git a/src/core/world/math/Ray.ts b/src/core/world/math/Ray.ts index 62c6a2f..01d2fe7 100644 --- a/src/core/world/math/Ray.ts +++ b/src/core/world/math/Ray.ts @@ -6,8 +6,8 @@ export default class Ray{ public vector: Vector; /** * 射线 - * @param position 射线起点 World.Vertice类型 - * @param direction 射线方向 World.Vector类型 + * @param position 射线起点 + * @param direction 射线方向 * @constructor */ constructor(position: Vertice, direction: Vector){ diff --git a/src/core/world/math/Utils.ts b/src/core/world/math/Utils.ts index da70248..81b0173 100644 --- a/src/core/world/math/Utils.ts +++ b/src/core/world/math/Utils.ts @@ -4,9 +4,10 @@ import Vertice from './Vertice'; import Vector from './Vector'; import Line from './Line'; import Plan from './Plan'; +import Matrix from './Matrix'; //Math.log2() method is defined in ES6 -if(!(Math).log2){ +if (!(Math).log2) { (Math).log2 = (value: number) => (Math.log(value) / Math.log(2)); } @@ -19,11 +20,11 @@ const pow2Cache: any = {}; } })(pow2Cache); -const ONE_RADIAN_EQUAL_DEGREE:number = 57.29577951308232;//=>180/Math.PI -const ONE_DEGREE_EQUAL_RADIAN:number = 0.017453292519943295;//=>Math.PI/180 +const ONE_RADIAN_EQUAL_DEGREE: number = 57.29577951308232;//=>180/Math.PI +const ONE_DEGREE_EQUAL_RADIAN: number = 0.017453292519943295;//=>Math.PI/180 export default class MathUtils { - static getRealValueInWorld(virtualValue: number){ + static getRealValueInWorld(virtualValue: number) { return virtualValue / Kernel.SCALE_FACTOR; } @@ -36,36 +37,36 @@ export default class MathUtils { } } - static log2(value: number){ + static log2(value: number) { return (Math).log2(value); } - static izZero(value: any) : boolean { - if(!Utils.isNumber(value)){ + static izZero(value: any): boolean { + if (!Utils.isNumber(value)) { throw "invalid value"; } return Math.abs(value) < 0.000001; } static isPowerOfTwo(value: number) { - return ( value & ( value - 1 ) ) === 0 && value !== 0; + return (value & (value - 1)) === 0 && value !== 0; } - static asinSafely(value: number){ - if(value > 1){ + static asinSafely(value: number) { + if (value > 1) { value = 1; } - if(value < -1){ + if (value < -1) { value = -1; } return Math.asin(value); } - static acosSafely(value: number){ - if(value > 1){ + static acosSafely(value: number) { + if (value > 1) { value = 1; } - if(value < -1){ + if (value < -1) { value = -1; } return Math.acos(value); @@ -77,12 +78,12 @@ export default class MathUtils { * @param strNum 字符串形式的要转换的数据 * @returns {number} 整数的十进制数据 */ - static numerationSystemTo10(numSys: number, strNum: string) : number{ + static numerationSystemTo10(numSys: number, strNum: string): number { var sum = 0; - for(var i=0;i 0){ + else if (delta > 0) { var sqrtDelta = Math.sqrt(delta); - var k1 = (-2*t+sqrtDelta)/(2*A); - var x1 = k1*a+x0; - var y1 = k1*b+y0; - var z1 = k1*c+z0; - var p1 = new Vertice(x1,y1,z1); + var k1 = (-2 * t + sqrtDelta) / (2 * A); + var x1 = k1 * a + x0; + var y1 = k1 * b + y0; + var z1 = k1 * c + z0; + var p1 = new Vertice(x1, y1, z1); result.push(p1); - var k2 = (-2*t-sqrtDelta)/(2*A); - var x2 = k2*a+x0; - var y2 = k2*b+y0; - var z2 = k2*c+z0; - var p2 = new Vertice(x2,y2,z2); + var k2 = (-2 * t - sqrtDelta) / (2 * A); + var x2 = k2 * a + x0; + var y2 = k2 * b + y0; + var z2 = k2 * c + z0; + var p2 = new Vertice(x2, y2, z2); result.push(p2); } } @@ -313,7 +314,7 @@ export default class MathUtils { * @param direction 向量V * @return {Object} World.Plan 返回平面表达式中Ax+By+Cz+D=0的A、B、C、D的信息 */ - static getCrossPlaneByLine(vertice: Vertice, direction: Vector): Plan{ + static getCrossPlaneByLine(vertice: Vertice, direction: Vector): Plan { var verticeCopy = vertice.clone(); var directionCopy = direction.clone(); directionCopy.normalize(); @@ -323,18 +324,18 @@ export default class MathUtils { var x0 = verticeCopy.x; var y0 = verticeCopy.y; var z0 = verticeCopy.z; - var d = -(a*x0+b*y0+c*z0); - var plan = new Plan(a,b,c,d); + var d = -(a * x0 + b * y0 + c * z0); + var plan = new Plan(a, b, c, d); return plan; } /////////////////////////////////////////////////////////////////////////////////////////// //点变换: Canvas->NDC - static convertPointFromCanvasToNDC(canvasWidth: number, canvasHeight: number, canvasX: number, canvasY: number): number[]{ - if(!(Utils.isNumber(canvasX))){ + static convertPointFromCanvasToNDC(canvasWidth: number, canvasHeight: number, canvasX: number, canvasY: number): number[] { + if (!(Utils.isNumber(canvasX))) { throw "invalid canvasX"; } - if(!(Utils.isNumber(canvasY))){ + if (!(Utils.isNumber(canvasY))) { throw "invalid canvasY"; } var ndcX = 2 * canvasX / canvasWidth - 1; @@ -343,11 +344,11 @@ export default class MathUtils { } //点变换: NDC->Canvas - static convertPointFromNdcToCanvas(canvasWidth: number, canvasHeight: number, ndcX: number, ndcY: number): number[]{ - if(!(Utils.isNumber(ndcX))){ + static convertPointFromNdcToCanvas(canvasWidth: number, canvasHeight: number, ndcX: number, ndcY: number): number[] { + if (!(Utils.isNumber(ndcX))) { throw "invalid ndcX"; } - if(!(Utils.isNumber(ndcY))){ + if (!(Utils.isNumber(ndcY))) { throw "invalid ndcY"; } var canvasX = (1 + ndcX) * canvasWidth / 2.0; @@ -361,11 +362,11 @@ export default class MathUtils { * @r optional 可选的地球半径 * @p 笛卡尔坐标系中的坐标 */ - static geographicToCartesianCoord(lon:number, lat: number, r: number = Kernel.EARTH_RADIUS): Vertice{ - if(!(lon >= -(180 + 0.001) && lon <= (180 + 0.001))){ + static geographicToCartesianCoord(lon: number, lat: number, r: number = Kernel.EARTH_RADIUS): Vertice { + if (!(lon >= -(180 + 0.001) && lon <= (180 + 0.001))) { throw "invalid lon"; } - if(!(lat >= -(90 + 0.001) && lat <= (90 + 0.001))){ + if (!(lat >= -(90 + 0.001) && lat <= (90 + 0.001))) { throw "invalid lat"; } var radianLon = this.degreeToRadian(lon); @@ -374,9 +375,9 @@ export default class MathUtils { var cos1 = Math.cos(radianLon); var sin2 = Math.sin(radianLat); var cos2 = Math.cos(radianLat); - var x = r * sin1 *cos2; + var x = r * sin1 * cos2; var y = r * sin2; - var z = r *cos1 * cos2; + var z = r * cos1 * cos2; return new Vertice(x, y, z); } @@ -385,7 +386,7 @@ export default class MathUtils { * @param vertice * @return {Array} */ - static cartesianCoordToGeographic(vertice: Vertice): number[]{ + static cartesianCoordToGeographic(vertice: Vertice): number[] { var verticeCopy = vertice.clone(); var x = verticeCopy.x; var y = verticeCopy.y; @@ -394,35 +395,35 @@ export default class MathUtils { var radianLat = this.asinSafely(sin2); var cos2 = Math.cos(radianLat); var sin1 = x / (Kernel.EARTH_RADIUS * cos2); - if(sin1 > 1){ + if (sin1 > 1) { sin1 = 1; } - if(sin1 < -1){ + if (sin1 < -1) { sin1 = -1; } var cos1 = z / (Kernel.EARTH_RADIUS * cos2); - if(cos1 > 1){ + if (cos1 > 1) { cos1 = 1; } - if(cos1 < -1){ + if (cos1 < -1) { cos1 = -1; } var radianLog = this.asinSafely(sin1); - if(sin1 >= 0){ + if (sin1 >= 0) { //经度在[0,π] - if(cos1 >= 0){ + if (cos1 >= 0) { //经度在[0, π/2]之间 radianLog = radianLog; - }else{ + } else { //经度在[π/2, π]之间 radianLog = Math.PI - radianLog; } - }else{ + } else { //经度在[-π, 0]之间 - if(cos1 >= 0){ + if (cos1 >= 0) { //经度在[-π/2, 0]之间 radianLog = radianLog; - }else{ + } else { //经度在[-π,-π/2]之间 radianLog = -radianLog - Math.PI; } @@ -437,7 +438,7 @@ export default class MathUtils { * @param degree * @return {*} */ - static degreeToRadian(degree: number){ + static degreeToRadian(degree: number) { return degree * ONE_DEGREE_EQUAL_RADIAN; } @@ -446,7 +447,7 @@ export default class MathUtils { * @param radian * @return {*} */ - static radianToDegree(radian: number){ + static radianToDegree(radian: number) { return radian * ONE_RADIAN_EQUAL_DEGREE; } @@ -459,7 +460,7 @@ export default class MathUtils { // return distance; // } - static getRealArcDistanceBetweenLonLats(lon1: number, lat1: number, lon2: number, lat2: number){ + static getRealArcDistanceBetweenLonLats(lon1: number, lat1: number, lon2: number, lat2: number) { //http://www.movable-type.co.uk/scripts/latlong.html var φ1 = this.degreeToRadian(lat1); var φ2 = this.degreeToRadian(lat2); @@ -480,7 +481,7 @@ export default class MathUtils { * @param x 投影坐标x * @return {Number} 返回的经度信息以弧度表示 */ - static webMercatorXToRadianLon(x: number){ + static webMercatorXToRadianLon(x: number) { return x / Kernel.EARTH_RADIUS; } @@ -489,7 +490,7 @@ export default class MathUtils { * @param x 投影坐标x * @return {*} 返回的经度信息以角度表示 */ - static webMercatorXToDegreeLon(x: number): number{ + static webMercatorXToDegreeLon(x: number): number { var radianLog = this.webMercatorXToRadianLon(x); return this.radianToDegree(radianLog); } @@ -499,14 +500,14 @@ export default class MathUtils { * @param y 投影坐标y * @return {Number} 返回的纬度信息以弧度表示 */ - static webMercatorYToRadianLat(y: number): number{ - if(!(Utils.isNumber(y))){ + static webMercatorYToRadianLat(y: number): number { + if (!(Utils.isNumber(y))) { throw "invalid y"; } var a = y / Kernel.EARTH_RADIUS; var b = Math.pow(Math.E, a); var c = Math.atan(b); - var radianLat = 2 * c - Math.PI/2; + var radianLat = 2 * c - Math.PI / 2; return radianLat; } @@ -515,7 +516,7 @@ export default class MathUtils { * @param y 投影坐标y * @return {*} 返回的纬度信息以角度表示 */ - static webMercatorYToDegreeLat(y: number): number{ + static webMercatorYToDegreeLat(y: number): number { var radianLat = this.webMercatorYToRadianLat(y); return this.radianToDegree(radianLat); } @@ -526,10 +527,10 @@ export default class MathUtils { * @param y 投影坐标y * @return {Array} 返回的经纬度信息以弧度表示 */ - static webMercatorToRadianGeographic(x: number, y: number): number[]{ + static webMercatorToRadianGeographic(x: number, y: number): number[] { var radianLog = this.webMercatorXToRadianLon(x); var radianLat = this.webMercatorYToRadianLat(y); - return [radianLog,radianLat]; + return [radianLog, radianLat]; } /** @@ -538,10 +539,10 @@ export default class MathUtils { * @param y 投影坐标y * @return {Array} 返回的经纬度信息以角度表示 */ - static webMercatorToDegreeGeographic(x: number, y: number): number[]{ + static webMercatorToDegreeGeographic(x: number, y: number): number[] { var degreeLog = this.webMercatorXToDegreeLon(x); var degreeLat = this.webMercatorYToDegreeLat(y); - return [degreeLog,degreeLat]; + return [degreeLog, degreeLat]; } /** @@ -549,13 +550,13 @@ export default class MathUtils { * @param radianLog 以弧度表示的经度 * @return {*} 投影坐标x */ - static radianLonToWebMercatorX(radianLog: number, real: boolean = false): number{ - if(!(Utils.isNumber(radianLog) && radianLog <= (Math.PI + 0.001) && radianLog >= -(Math.PI + 0.001))){ + static radianLonToWebMercatorX(radianLog: number, real: boolean = false): number { + if (!(Utils.isNumber(radianLog) && radianLog <= (Math.PI + 0.001) && radianLog >= -(Math.PI + 0.001))) { throw "invalid radianLog"; } - if(real){ + if (real) { return Kernel.REAL_EARTH_RADIUS * radianLog; - }else{ + } else { return Kernel.EARTH_RADIUS * radianLog; } } @@ -565,8 +566,8 @@ export default class MathUtils { * @param degreeLog 以角度表示的经度 * @return {*} 投影坐标x */ - static degreeLonToWebMercatorX(degreeLog: number, real: boolean = false): number{ - if(!(Utils.isNumber(degreeLog) && degreeLog <= (180 + 0.001) && degreeLog >= -(180 + 0.001))){ + static degreeLonToWebMercatorX(degreeLog: number, real: boolean = false): number { + if (!(Utils.isNumber(degreeLog) && degreeLog <= (180 + 0.001) && degreeLog >= -(180 + 0.001))) { throw "invalid degreeLog"; } var radianLog = this.degreeToRadian(degreeLog); @@ -578,16 +579,16 @@ export default class MathUtils { * @param radianLat 以弧度表示的纬度 * @return {Number} 投影坐标y */ - static radianLatToWebMercatorY(radianLat: number, real: boolean = false): number{ - if(!(radianLat <= (Math.PI / 2 + 0.001) && radianLat >= -(Math.PI / 2 + 0.001))){ + static radianLatToWebMercatorY(radianLat: number, real: boolean = false): number { + if (!(radianLat <= (Math.PI / 2 + 0.001) && radianLat >= -(Math.PI / 2 + 0.001))) { throw "invalid radianLat"; } var a = Math.PI / 4 + radianLat / 2; var b = Math.tan(a); var c = Math.log(b); - if(real){ + if (real) { return Kernel.REAL_EARTH_RADIUS * c; - }else{ + } else { return Kernel.EARTH_RADIUS * c; } } @@ -597,8 +598,8 @@ export default class MathUtils { * @param degreeLat 以角度表示的纬度 * @return {Number} 投影坐标y */ - static degreeLatToWebMercatorY(degreeLat: number, real: boolean = false): number{ - if(!(degreeLat <= (90 + 0.001) && degreeLat >= -(90 + 0.001))){ + static degreeLatToWebMercatorY(degreeLat: number, real: boolean = false): number { + if (!(degreeLat <= (90 + 0.001) && degreeLat >= -(90 + 0.001))) { throw "invalid degreeLat"; } var radianLat = this.degreeToRadian(degreeLat); @@ -611,7 +612,7 @@ export default class MathUtils { * @param radianLat 以弧度表示的纬度 * @return {Array} 投影坐标x、y */ - static radianGeographicToWebMercator(radianLog: number, radianLat: number): number[]{ + static radianGeographicToWebMercator(radianLog: number, radianLat: number): number[] { var x = this.radianLonToWebMercatorX(radianLog); var y = this.radianLatToWebMercatorY(radianLat); return [x, y]; @@ -623,14 +624,14 @@ export default class MathUtils { * @param degreeLat 以角度表示的纬度 * @return {Array} */ - static degreeGeographicToWebMercator(degreeLog: number, degreeLat: number): number[]{ + static degreeGeographicToWebMercator(degreeLog: number, degreeLat: number): number[] { var x = this.degreeLonToWebMercatorX(degreeLog); var y = this.degreeLatToWebMercatorY(degreeLat); return [x, y]; } //根据切片的level、row、column计算该切片所覆盖的投影区域的范围 - static getTileWebMercatorEnvelopeByGrid(level: number, row: number, column: number): any{ + static getTileWebMercatorEnvelopeByGrid(level: number, row: number, column: number): any { var k = Kernel.MAX_PROJECTED_COORD; var size = 2 * k / Math.pow(2, level); var minX = -k + column * size; @@ -638,16 +639,16 @@ export default class MathUtils { var maxY = k - row * size; var minY = maxY - size; var Eproj = { - "minX":minX, - "minY":minY, - "maxX":maxX, - "maxY":maxY + "minX": minX, + "minY": minY, + "maxX": maxX, + "maxY": maxY }; return Eproj; } //根据切片的level、row、column计算该切片所覆盖的经纬度区域的范围,以经纬度表示返回结果 - static getTileGeographicEnvelopByGrid(level: number, row: number, column: number): any{ + static getTileGeographicEnvelopByGrid(level: number, row: number, column: number): any { var Eproj = this.getTileWebMercatorEnvelopeByGrid(level, row, column); var pMin = this.webMercatorToDegreeGeographic(Eproj.minX, Eproj.minY); var pMax = this.webMercatorToDegreeGeographic(Eproj.maxX, Eproj.maxY); @@ -661,8 +662,8 @@ export default class MathUtils { } //根据切片的level、row、column计算该切片所覆盖的笛卡尔空间直角坐标系的范围,以x、y、z表示返回结果 - static getTileCartesianEnvelopByGrid(level: number, row: number, column: number): Object{ - var Egeo = this.getTileGeographicEnvelopByGrid(level,row,column); + static getTileCartesianEnvelopByGrid(level: number, row: number, column: number): Object { + var Egeo = this.getTileGeographicEnvelopByGrid(level, row, column); var minLon = Egeo.minLon; var minLat = Egeo.minLat; var maxLon = Egeo.maxLon; @@ -691,85 +692,235 @@ export default class MathUtils { * @param column * @return {Array} */ - static getGeographicTileCenter(level: number, row: number, column: number): number[]{ + static getGeographicTileCenter(level: number, row: number, column: number): number[] { var Egeo = this.getTileGeographicEnvelopByGrid(level, row, column); var minLon = Egeo.minLon; var minLat = Egeo.minLat; var maxLon = Egeo.maxLon; var maxLat = Egeo.maxLat; - var centerLon = (minLon+maxLon)/2;//切片的经度中心 - var centerLat = (minLat+maxLat)/2;//切片的纬度中心 + var centerLon = (minLon + maxLon) / 2;//切片的经度中心 + var centerLat = (minLat + maxLat) / 2;//切片的纬度中心 var lonlatTileCenter = [centerLon, centerLat]; return lonlatTileCenter; } - static getCartesianTileCenter(level: number, row: number, column: number): Vertice{ + static getCartesianTileCenter(level: number, row: number, column: number): Vertice { var lonLat = this.getGeographicTileCenter(level, row, column); var vertice = this.geographicToCartesianCoord(lonLat[0], lonLat[1]); return vertice; } + /** + * http://www.cnblogs.com/jay-dong/archive/2012/09/26/2704188.html + * 根据二阶贝塞尔曲线生成指定数量的曲线上的点,返回结果包含p0和p2 + * @param p0 长度为2,x和y,贝塞尔曲线的起点 + * @param p1 长度为2,x和y,贝塞尔曲线的控制点 + * @param p2 长度为2,x和y,贝塞尔曲线的终点 + * @param count 生成的点的数量 + */ + static quad(p0: number[], p1: number[], p2: number[], count: number) { + const points: number[][] = []; + for (var i = 0; i < count; i++) { + if (i === 0) { + points.push(p0); + } else if (i === count - 1) { + points.push(p2) + } else { + const t = i / count; + const a = (1 - t) * (1 - t); + const b = 2 * t * (1 - t); + const c = t * t; + const x = a * p0[0] + b * p1[0] + c * p2[0]; + const y = a * p0[1] + b * p1[1] + c * p2[1]; + points.push([x, y]); + } + } + return points; + } + /** * 计算TRIANGLES的平均法向量 * @param vs 传入的顶点坐标数组 array * @param ind 传入的顶点的索引数组 array * @return {Array} 返回每个顶点的平均法向量的数组 */ - static calculateNormals(vs: number[], ind: number[]): number[]{ + static calculateNormals(vs: number[], ind: number[]): number[] { var x = 0; var y = 1; var z = 2; - var ns:number[] = []; + var ns: number[] = []; //对于每个vertex,初始化normal x, normal y, normal z - for(var i = 0;i < vs.length; i = i + 3){ - ns[i + x]=0.0; - ns[i + y]=0.0; - ns[i + z]=0.0; + for (var i = 0; i < vs.length; i = i + 3) { + ns[i + x] = 0.0; + ns[i + y] = 0.0; + ns[i + z] = 0.0; } //用三元组vertices计算向量,所以i = i+3,i表示索引 - for(var i=0;i=0, v >= 0,u+v<=1。 + * 三角形内一点p = (1 - u - v)*v0 + u*v1 + v*v2 + * http://www.cnblogs.com/graphics/archive/2010/08/09/1795348.html + * @param orig 射线起点 + * @param dir 射线方向 + * @param v0 三角形第一个顶点 + * @param v1 三角形第二个顶点 + * @param v2 三角形第三个顶点 + */ + static intersectTriangle(orig: Vertice, dir: Vector, v0: Vertice, v1: Vertice, v2: Vertice) { + var t: number, u: number, v: number; + + var E1 = Vector.verticeMinusVertice(v1, v0); + + var E2 = Vector.verticeMinusVertice(v2, v0); + + var P = dir.cross(E2); + + // determinant + var det = E1.dot(P); + + // keep det > 0, modify T accordingly + var T: Vector = null; + if(det > 0){ + T = Vector.verticeMinusVertice(orig, v0); + }else{ + T = Vector.verticeMinusVertice(v0, orig); + det = -det; + } + + // If determinant is near zero, ray lies in plane of triangle + if(det < 0.0001){ + return false; + } + + // Calculate u and make sure u <= 1 + u = T.dot(P); + if(u < 0 || u > det){ + return false; + } + + var Q = T.cross(E1); + + // Calculate v and make sure u + v <= 1 + v = dir.dot(Q); + if(v < 0 || (u + v) > det){ + return false; + } + + // Calculate t, scale parameters, ray intersects triangle + // 如果要求出具体的交点可以使用下面的代码 + // t = E2.dot(Q); + + // var fInvDet = 1 / det; + // t *= fInvDet; + // u *= fInvDet; + // v *= fInvDet; + // // p = (1 - u - v)*v0 + u*v1 + v*v2 + // var s = 1- u - v; + // //intersect就是射线与三角形的交点 + // var intersect = new Vertice(); + // intersect.x = s * v0.x + u * v1.x + v * v2.x; + // intersect.y = s * v0.y + u * v1.y + v * v2.y; + // intersect.z = s * v0.z + u * v1.z + v * v2.z; + + return true; + } + + /** + * 将世界坐标系中的坐标转换为模型坐标系中的坐标 + * @param worldVertice 世界坐标系中的坐标 + * @param localMatrix 模型矩阵 + * @param inverseLocalMatrix 可选参数,模型矩阵的逆矩阵,如果传递该参数可以减少不必要的逆矩阵计算 + */ + static convertWorldVerticeToLocalVertice(worldVertice: Vertice, localMatrix: Matrix, inverseLocalMatrix?: Matrix){ + if(!inverseLocalMatrix){ + inverseLocalMatrix = localMatrix.getInverseMatrix(); + } + const inputColumn = worldVertice.getArray(); + inputColumn.push(1); + const column = inverseLocalMatrix.multiplyColumn(inputColumn); + const localVertice = new Vertice(column[0], column[1], column[2]); + return localVertice; + } + + /** + * 将世界坐标系中的向量转换为模型坐标系中的向量 + * @param worldVector 世界坐标系中的向量 + * @param localMatrix 模型矩阵 + * @param inverseLocalMatrix 可选参数,模型矩阵的逆矩阵,如果传递该参数可以减少不必要的逆矩阵计算 + */ + static convertWorldVectorToLocalVector(worldVector: Vector, localMatrix: Matrix, inverseLocalMatrix?: Matrix){ + if(!inverseLocalMatrix){ + inverseLocalMatrix = localMatrix.getInverseMatrix(); + } + const worldVertice1 = new Vertice(0, 0, 0); + const worldVertice2 = worldVector.getVertice(); + const localVertice1 = this.convertWorldVerticeToLocalVertice(worldVertice1, localMatrix, inverseLocalMatrix); + const localVertice2 = this.convertWorldVerticeToLocalVertice(worldVertice2, localMatrix, inverseLocalMatrix); + const localVector = Vector.verticeMinusVertice(localVertice2, localVertice1); + localVector.normalize(); + return localVector; + } + + /** + * 将世界坐标系中的直线转换为模型坐标系中的直线 + * @param worldLine 世界坐标系中的直线 + * @param localMatrix 模型矩阵 + * @param inverseLocalMatrix 可选参数,模型矩阵的逆矩阵,如果传递该参数可以减少不必要的逆矩阵计算 + */ + static convertWorldLineToLocalLine(worldLine: Line, localMatrix: Matrix, inverseLocalMatrix?: Matrix){ + if(!inverseLocalMatrix){ + inverseLocalMatrix = localMatrix.getInverseMatrix(); + } + const localVertice = this.convertWorldVerticeToLocalVertice(worldLine.vertice, localMatrix, inverseLocalMatrix); + const localVector = this.convertWorldVectorToLocalVector(worldLine.vector, localMatrix, inverseLocalMatrix); + const localLine = new Line(localVertice, localVector); + return localLine; + } }; \ No newline at end of file diff --git a/src/core/world/math/Vector.ts b/src/core/world/math/Vector.ts index 45cc2db..62bdd75 100644 --- a/src/core/world/math/Vector.ts +++ b/src/core/world/math/Vector.ts @@ -16,6 +16,9 @@ export default class Vector{ } static getRadianOfTwoVectors(vector1: Vector, vector2: Vector): number{ + if(vector1.isZeroLength() || vector2.isZeroLength()){ + return 0; + } var v1 = vector1.clone().normalize(); var v2 = vector2.clone().normalize(); var dotValue = v1.dot(v2); @@ -50,6 +53,10 @@ export default class Vector{ return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); } + isZeroLength(){ + return this.x === 0 && this.y === 0 && this.z === 0; + } + normalize(): Vector { var length = this.getLength(); if(Math.abs(length) >= 0.000001) { diff --git a/src/webapp/common/loading/index.js b/src/webapp/common/loading/index.js index 669fcd5..dcf2b4f 100644 --- a/src/webapp/common/loading/index.js +++ b/src/webapp/common/loading/index.js @@ -3,10 +3,11 @@ import loadingIcon from './loading.png'; const loadingDom = document.createElement("div"); loadingDom.id = styles.loading; -const iconDom = document.createElement("div"); -iconDom.className = styles.icon; -iconDom.style.backgroundImage = `url(${loadingIcon})`; -loadingDom.appendChild(iconDom); +loadingDom.innerHTML = `
加载中...
`; +// const iconDom = document.createElement("div"); +// iconDom.className = styles.icon; +// iconDom.style.backgroundImage = `url(${loadingIcon})`; +// loadingDom.appendChild(iconDom); const loading = { show(){ diff --git a/src/webapp/common/loading/index.scss b/src/webapp/common/loading/index.scss index e81b3e5..9a6f070 100644 --- a/src/webapp/common/loading/index.scss +++ b/src/webapp/common/loading/index.scss @@ -1,4 +1,8 @@ -$icon-size: 36px; +$content-width: 156px; +$content-height: 70px; +$icon-size: 32px; +$icon-left: 20px; +$text-left: 20px; @keyframes loading-keyframes{ from{ @@ -16,16 +20,34 @@ top: 0; bottom: 0; - .icon{ + .content{ position: absolute; + width: $content-width; + height: $content-height; left: 50%; top: 50%; - width: $icon-size; - height: $icon-size; - margin-left: -$icon-size/2; - margin-top: -$icon-size/2; - background-position: center; - background-repeat: no-repeat; - animation: loading-keyframes 0.9s linear infinite; + margin-left: -$content-width / 2; + margin-top: -$content-height / 2; + background: #000; + opacity: 0.4; + color: white; + border-radius: 5px; + + .icon{ + position: absolute; + width: $icon-size; + height: $icon-size; + top: ($content-height - $icon-size) / 2; + left: $icon-left; + background-position: center; + background-repeat: no-repeat; + animation: loading-keyframes 0.9s linear infinite; + } + + .text{ + display: inline-block; + line-height: $content-height; + margin-left: $icon-left + $icon-size + $text-left; + } } } \ No newline at end of file diff --git a/src/webapp/common/loading/loading.gif b/src/webapp/common/loading/loading.gif new file mode 100644 index 0000000..4b524cd Binary files /dev/null and b/src/webapp/common/loading/loading.gif differ diff --git a/src/webapp/components/Map/index.jsx b/src/webapp/components/Map/index.jsx index ec90c27..3f1dfc4 100644 --- a/src/webapp/components/Map/index.jsx +++ b/src/webapp/components/Map/index.jsx @@ -8,7 +8,8 @@ export const globe = Globe.getInstance({ satellite: false, lonlat: "auto", level: "auto", - key: "db146b37ef8d9f34473828f12e1e85ad" + key: "db146b37ef8d9f34473828f12e1e85ad", + resolutionFactor: 1.2 }); //延迟一秒加载globe中的切片 diff --git a/src/webapp/components/RouteComponent/index.jsx b/src/webapp/components/RouteComponent/index.jsx index 8a4e45f..d25b415 100644 --- a/src/webapp/components/RouteComponent/index.jsx +++ b/src/webapp/components/RouteComponent/index.jsx @@ -55,14 +55,18 @@ export default class RouteComponent extends Component { return this._isMounted; } - wrapPromise(promise){ - loading.show(); + wrapPromise(promise, disableLoading){ + if(!disableLoading){ + loading.show(); + } this.setState({ loading: true }); const p = new Promise((resolve, reject) => { promise.then((response) => { - loading.hide(); + if(!disableLoading){ + loading.hide(); + } if(this.hasBeenMounted()){ this.setState({ loading: false @@ -70,7 +74,9 @@ export default class RouteComponent extends Component { resolve(response); } }, (err) => { - loading.hide(); + if(!disableLoading){ + loading.hide(); + } if(this.hasBeenMounted()){ this.setState({ loading: false diff --git a/src/webapp/index.jsx b/src/webapp/index.jsx index 6662a99..1491741 100644 --- a/src/webapp/index.jsx +++ b/src/webapp/index.jsx @@ -1,10 +1,8 @@ -import React from 'react'; + +/* +import React from 'react'; import ReactDOM from 'react-dom'; -import { Router, hashHistory } from 'react-router'; -import './css/common.scss'; -import './fonts/fontello.scss'; - -/*import {Router, Route, Link, Redirect, hashHistory} from 'react-router'; +import {Router, Route, Link, Redirect, hashHistory} from 'react-router'; import IndexIndex from './routes/index/Index'; import MapBase from './routes/map/Base'; import NearbySearch from './routes/nearby/Search'; @@ -12,6 +10,8 @@ import NearbyResult from './routes/nearby/Result'; import NavSearch from './routes/nav/Search'; import NavPaths from './routes/nav/Paths'; import NotFound from './routes/404'; +import './css/common.scss'; +import './fonts/fontello.scss'; const rootDiv = document.getElementById("root"); @@ -36,8 +36,15 @@ ReactDOM.render(( -), rootDiv);*/ +), rootDiv); +*/ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Router, hashHistory } from 'react-router'; +import './css/common.scss'; +import './fonts/fontello.scss'; import rootRoute from './routes/route'; const rootDiv = document.getElementById("root"); diff --git a/src/webapp/routes/nav/Paths/index.jsx b/src/webapp/routes/nav/Paths/index.jsx index 25c721a..06a7d2b 100644 --- a/src/webapp/routes/nav/Paths/index.jsx +++ b/src/webapp/routes/nav/Paths/index.jsx @@ -231,7 +231,7 @@ export default class Paths extends RouteComponent { let drivingChildren = []; if (showSummary1) { const [timeSummary, distanceSummary] = this.getTimeDistanceSummary(path); - drivingChildren.push(
{`${timeSummary} ${distanceSummary}`}
); + drivingChildren.push(
{`${timeSummary} ${distanceSummary}`}
); } drivingChildren = drivingChildren.concat([
{path.steps.map((step) => step.road).filter((road) => !!road).join(" -> ")}
, diff --git a/src/webapp/routes/nav/Search/index.jsx b/src/webapp/routes/nav/Search/index.jsx index 492e7bd..1082749 100644 --- a/src/webapp/routes/nav/Search/index.jsx +++ b/src/webapp/routes/nav/Search/index.jsx @@ -16,10 +16,13 @@ export default class Nav extends RouteComponent { this.pageCapacity = 10; this.searchDistance = Kernel.REAL_EARTH_RADIUS; this.isFromLastFocused = true; + this.currentLocationText = '当前位置'; + this.tipPoisCache = {};//{keyword: tipPois} this.state = { type: 'driving',//bus,walking - fromPois: [], - toPois: [], + from: Service.location ? this.currentLocationText : '', + to: '', + tipPois: [], routes: [] }; } @@ -29,11 +32,16 @@ export default class Nav extends RouteComponent { } onKeyPress(e) { - const isFrom = e.target === this.fromInput; const keyword = e.target.value; if (e.key === "Enter") { if(keyword){ - this.searchPois(isFrom, keyword); + this.searchPois(keyword, this.isFromLastFocused, (pois) => { + if(e.target.value === keyword){ + this.setState({ + tipPois: pois + }); + } + }); } } } @@ -41,60 +49,66 @@ export default class Nav extends RouteComponent { onFromFocus() { this.isFromLastFocused = true; this.setState({ - toPois: [] + tipPois: [] }); } onToFocus() { this.isFromLastFocused = false; this.setState({ - fromPois: [] + tipPois: [] }); } - onClickFromSearchIcon(){ - this.fromInput.focus(); - const keyword = this.fromInput.value; - if(keyword){ - this.searchPois(true, keyword); - } + onFromChange(){ + this.setState({ + from: this.fromInput.value + }); + this.onInputChange(this.fromInput); } - onClickToSearchIcon(){ - this.toInput.focus(); - const keyword = this.toInput.value; - if(keyword){ - this.searchPois(false, keyword); - } + onToChange(){ + this.setState({ + to: this.toInput.value + }); + this.onInputChange(this.toInput); } - onClickSearchIcon(isFrom, keyword) { - if(keyword){ - this.searchPois(isFrom, keyword); + onInputChange(input){ + const keyword = input.value.trim(); + if(keyword && keyword.indexOf(this.currentLocationText) < 0){ + this.searchPois(keyword, this.isFromLastFocused, (pois) => { + if(input.value === keyword){ + this.setState({ + tipPois: pois + }); + } + }); + }else{ + this.setState({ + tipPois: [] + }); } } - searchPois(isFrom, keyword) { - if (!keyword) { + //isFromLastFocused maybe boolean or stirng 'ignored' + searchPois(keyword, isFromLastFocused, cb) { + let pois = this.tipPoisCache[keyword]; + if(pois){ + cb(pois); return; } const promise = Service.searchNearby(keyword, this.searchDistance, 'Auto', this.pageCapacity); - this.wrapPromise(promise).then((response) => { - let pois = null; + this.wrapPromise(promise, true).then((response) => { if (response.detail) { pois = response.detail.pois; } if (!pois) { pois = []; } - if (isFrom) { - this.setState({ - fromPois: pois - }); - } else { - this.setState({ - toPois: pois - }); + this.tipPoisCache[keyword] = pois; + if(isFromLastFocused === 'ignored' || isFromLastFocused === this.isFromLastFocused){ + cb(pois); } }); } @@ -104,8 +118,9 @@ export default class Nav extends RouteComponent { this.fromPoi = poi; this.fromInput.value = poi.name || ""; this.setState({ - fromPois: [] + tipPois: [] }); + if (this.toPoi) { this.route(this.fromPoi, this.toPoi); } else { @@ -115,8 +130,9 @@ export default class Nav extends RouteComponent { this.toPoi = poi; this.toInput.value = poi.name || ""; this.setState({ - toPois: [] - }) + tipPois: [] + }); + if (this.fromPoi) { this.route(this.fromPoi, this.toPoi); } else { @@ -125,6 +141,27 @@ export default class Nav extends RouteComponent { } } + onClickSearchBtn(){ + if(this.fromPoi && this.toPoi){ + this.route(this.fromPoi, this.toPoi); + }else{ + const from = this.fromInput.value.trim(); + const to = this.toInput.value.trim(); + if(from && to){ + const p1 = this.getMockPoiByKeyword(this.fromPoi, from); + const p2 = this.getMockPoiByKeyword(this.toPoi, to); + const p = Promise.all([p1, p2]); + this.wrapPromise(p).then((results) => { + const fromPoi = results[0]; + const toPoi = results[1]; + if(fromPoi && toPoi){ + this.route(fromPoi, toPoi); + } + }); + } + } + } + onTrafficTypeChange(trafficType) { this.setState({ type: trafficType @@ -147,32 +184,74 @@ export default class Nav extends RouteComponent { } } + getMockPoiByKeyword(poi, keyword){ + return new Promise((resolve, reject) => { + if(poi){ + resolve(poi); + }else{ + if(keyword === this.currentLocationText){ + this.mockPoiByCurrentLocation().then((mockPoi) => resolve(mockPoi)) + .catch((e) => { + console.error(e); + reject(e); + }); + }else{ + this.searchPois(keyword, 'ignored', (pois) => { + resolve(pois[0]); + }); + } + } + }); + } + + mockPoiByCurrentLocation(){ + return Service.getCurrentPosition(false).then((location) => { + return { + pointx: location.lon, + pointy: location.lat, + POI_PATH: [{ + cname: location.city + }] + }; + }); + } + render() { const fromClassName = classNames("icon-location", styles["from-icon"]); const toClassName = classNames("icon-circle-empty", styles["to-icon"]); - // const exchangeArrowClassName = classNames(fontStyles.fa, fontStyles["fa-arrows-v"]); const addressClassName = classNames(styles.address, "ellipsis"); - const pois = this.isFromLastFocused ? this.state.fromPois : this.state.toPois; + const pois = this.state.tipPois; return (
- this.trafficTypes = input} onTrafficTypeChange={e => this.onTrafficTypeChange(e)} onCancel={() => this.onCancel()} /> + this.trafficTypes = input} + onTrafficTypeChange={e => this.onTrafficTypeChange(e)} + onCancel={() => this.onCancel()} />
- { this.fromInput = input; }} onFocus={(e) => this.onFromFocus(e)} onKeyPress={(e) => this.onKeyPress(e)} /> - this.onClickFromSearchIcon()}> + { this.fromInput = input; }} + onFocus={(e) => this.onFromFocus(e)} + onKeyPress={(e) => this.onKeyPress(e)} + onChange={(e) => this.onFromChange(e)} />
- { this.toInput = input; }} onFocus={(e) => this.onToFocus(e)} onKeyPress={(e) => this.onKeyPress(e)} /> - this.onClickToSearchIcon()}> + { this.toInput = input; }} + onFocus={(e) => this.onToFocus(e)} + onKeyPress={(e) => this.onKeyPress(e)} + onChange={(e) => this.onToChange(e)} />
- {/*
- -
*/} +
this.onClickSearchBtn()}>搜索
{ diff --git a/src/webapp/routes/nav/Search/index.scss b/src/webapp/routes/nav/Search/index.scss index f487641..f06d599 100644 --- a/src/webapp/routes/nav/Search/index.scss +++ b/src/webapp/routes/nav/Search/index.scss @@ -16,7 +16,7 @@ $exchange-size: 0; .inputs-container{ position: absolute; left: 15px; - right: $exchange-size; + right: 70px; top: 0; height: 100%; @@ -76,6 +76,17 @@ $exchange-size: 0; line-height: $input-container-height * 2; } } + + .search-btn{ + position: absolute; + top: 29px; + right: 8px; + border: 1px solid $blue-color; + padding: 4px 8px; + border-radius: 20px; + font-size: 15px; + color: $blue-color; + } } .pois{ diff --git a/src/webapp/routes/nearby/Result/index.jsx b/src/webapp/routes/nearby/Result/index.jsx index 856b89c..c0570c6 100644 --- a/src/webapp/routes/nearby/Result/index.jsx +++ b/src/webapp/routes/nearby/Result/index.jsx @@ -24,7 +24,9 @@ export default class Result extends RouteComponent { total: 0, pageIndex: 0, location: null, - pois: [], + // pois: [], + graphics: [], + highlightPoi: null, list: true }; } @@ -33,6 +35,22 @@ export default class Result extends RouteComponent { super.componentDidMount(); this.domNode.style.opacity = 1; this.search(0); + globe.poiLayer.setHighlightListener((graphic) => { + this.setState({ + highlightPoi: graphic + }); + }); + globe.poiLayer.setUnHighlightListener(() => { + this.setState({ + highlightPoi: null + }); + }); + } + + componentWillUnmount(){ + super.componentWillUnmount(); + globe.poiLayer.setHighlightListener(null); + globe.poiLayer.setUnHighlightListener(null); } onMap() { @@ -47,9 +65,13 @@ export default class Result extends RouteComponent { }); } - onClickPoi(poi){ - if(typeof poi.pointx === 'number' && typeof poi.pointy === 'number'){ - globe.centerTo(poi.pointx, poi.pointy); + onClickPoi(graphic, index){ + // const graphic = globe.poiLayer.children[index]; + if(graphic){ + globe.poiLayer.highlightPoi(graphic); + } + if(typeof graphic.attributes.pointx === 'number' && typeof graphic.attributes.pointy === 'number'){ + globe.centerTo(graphic.attributes.pointx, graphic.attributes.pointy); } this.setState({ list: false @@ -109,7 +131,8 @@ export default class Result extends RouteComponent { total: response.info.total, pageIndex: pageIndex, location: response.location, - pois: response.detail.pois + // pois: response.detail.pois + graphics: response.detail.graphics }); } }); @@ -122,7 +145,9 @@ export default class Result extends RouteComponent { total, pageIndex, location, - pois + // pois + graphics, + highlightPoi } = this.state; let { @@ -151,21 +176,21 @@ export default class Result extends RouteComponent { total > 0 && (
{ - this.state.pois.map((poi, index) => { + graphics.map((poi, index) => { let distanceLabel = false; if(location && location.length === 2){ const [lon, lat] = location; - let distance = MathUtils.getRealArcDistanceBetweenLonLats(lon, lat, poi.pointx, poi.pointy); + let distance = MathUtils.getRealArcDistanceBetweenLonLats(lon, lat, poi.attributes.pointx, poi.attributes.pointy); distanceLabel = distance > 1000 ? `${(distance/1000).toFixed(1)}公里` : `${Math.floor(distance)}米`; } return ( -
this.onClickPoi(poi)}> +
this.onClickPoi(poi, index)}>
{index + 1}
-
{poi.name}
-
{poi.addr}
+
{poi.attributes.name}
+
{poi.attributes.addr}
{ distanceLabel && ( @@ -211,6 +236,14 @@ export default class Result extends RouteComponent { ) : (
+ { + highlightPoi && ( +
+
{highlightPoi.attributes.name}
+
{highlightPoi.attributes.addr}
+
+ ) + }
) } diff --git a/src/webapp/routes/nearby/Result/index.scss b/src/webapp/routes/nearby/Result/index.scss index f87d98c..495f183 100644 --- a/src/webapp/routes/nearby/Result/index.scss +++ b/src/webapp/routes/nearby/Result/index.scss @@ -144,4 +144,27 @@ $line: 1px solid #d3d3d3; font-size: 12px; } } +} + +.map{ + .infowindow{ + position: absolute; + left: 10px; + right: 10px; + bottom: 10px; + height: auto; + background: white; + border-radius: 5px; + border: 1px solid #ccc; + padding: 8px; + .name { + font-size: 15px; + color: #333; + } + .address { + font-size: 12px; + color: $main-color; + margin-top: 8px; + } + } } \ No newline at end of file diff --git a/versions.md b/versions.md index 30b4741..008fe99 100644 --- a/versions.md +++ b/versions.md @@ -6,7 +6,7 @@ - 0.0.4.7 新增了World.TiledLayer类型,取消掉了World.NokiaTile等类型,图层都继承自World.TiledLayer,并重写其中的getImageUrl方法,所有的切片都放到World.TiledLayer中进行管理,结构化明确,更加面向对象,因此将版本升级到0.0.4.7 -- 0.0.5.0 从0.0.4.7直接升级到0.0.5.0,在TiledLayer的基础上又增加了SubTiledLayer类,并在此基础上优化了诸多算法,包括通过添加isvisible判断是否渲染,完善了camera的getCurrentGeoExtent算法,结构更合理,访问速度更快,故升级到0.0.5.0 + - 0.0.5.0 从0.0.4.7直接升级到0.0.5.0,在TiledLayer的基础上又增加了SubTiledLayer类,并在此基础上优化了诸多算法,包括通过添加isvisible判断是否渲染,完善了camera的getCurrentGeoExtent算法,结构更合理,访问速度更快,故升级到0.0.5.0 - 0.0.5.1 优化了各种TiledLayer的getImageUrl的算法 @@ -222,32 +222,46 @@ - MeshGraphic => MeshTextureGraphic - 添加MeshColorGraphic -- 0.4.24 RouteLayer基本可以实现固定宽度的Polyline + - 0.4.24 RouteLayer基本可以实现固定宽度的Polyline -- 0.4.25 更新webpack.config.js + - 0.4.25 更新webpack.config.js -- 0.4.26 Camera中添加centerTo和animateTo方法 + - 0.4.26 Camera中添加centerTo和animateTo方法 -- 0.4.27 Camera中添加setExtent和animateToExtent方法 + - 0.4.27 Camera中添加setExtent和animateToExtent方法 -- 0.4.28 通过判断道路拐点解决"箭头道路"的问题 + - 0.4.28 通过判断道路拐点解决"箭头道路"的问题 -- 0.4.29 可以在nav/Paths页面中直接切换出行方式重新进行路线规划 + - 0.4.29 可以在nav/Paths页面中直接切换出行方式重新进行路线规划 -- 0.4.30 在RouteComponent中添加getPreviousLocation()方法,并在nearyby/Result中使用 + - 0.4.30 在RouteComponent中添加getPreviousLocation()方法,并在nearyby/Result中使用 -- 0.4.31 在公交导航中支持火车出行 + - 0.4.31 在公交导航中支持火车出行 -- 0.4.32 通过http://fontello.com/ 自定义FontAwesome + - 0.4.32 通过http://fontello.com/ 自定义FontAwesome -- 0.4.33 Globe构造函数支持pauseRendering参数,可以实现切片延迟加载 + - 0.4.33 Globe构造函数支持pauseRendering参数,可以实现切片延迟加载 -- 0.4.34 searchByBuffer和searchByCity支持SearchType参数,可以智能判断类型,并且可以在查询无结果的情况下改变SearchType再次查询,优化查询体验 + - 0.4.34 searchByBuffer和searchByCity支持SearchType参数,可以智能判断类型,并且可以在查询无结果的情况下改变SearchType再次查询,优化查询体验 -- 0.4.35 iOS系统中的浏览器不能访问类WebGLRenderingContext的静态常量,将所有用到WebGLRenderingContext的地方重新改成Kernel.gl的形式,用实例常量访问即可修复iOS中无法渲染的严重bug + - 0.4.35 iOS系统中的浏览器不能访问类WebGLRenderingContext的静态常量,将所有用到WebGLRenderingContext的地方重新改成Kernel.gl的形式,用实例常量访问即可修复iOS中无法渲染的严重bug -- 0.4.36 更新webpack.config.js文件,在生产环境中配置new webpack.DefinePlugin,将NODE_ENV配置为production,使得压缩打包后的React体积减小88KB,参见https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build + - 0.4.36 更新webpack.config.js文件,在生产环境中配置new webpack.DefinePlugin,将NODE_ENV配置为production,使得压缩打包后的React体积减小88KB,参见https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build -- 0.5.0 为搜索框添加搜索图标,方便搜索 + - 0.5.0 为搜索框添加搜索图标,方便搜索 -- 0.5.1 更新README.md,发布新版本 \ No newline at end of file + - 0.5.1 更新README.md,发布新版本 + + - 0.5.5 修复了Camera中计算出的实际分辨率总是真实值的1.3倍的bug + + - 0.5.6 可以点击拾取PoiLayer + + - 0.5.7 Graphic支持attributes属性,优化PoiLayer拾取逻辑,修复Service.ts中Promise导致TypeScript报错的问题 + + - 0.5.8 重新修改了resolutionFactor1和resolutionFactor2的值,确保图片是256大小显示,并且确保getResolution()和getResolutionInWorld()方法用于让其他类调用获取实际的分辨率 + + - 0.5.9 + - 使得Globe支持resolutionFactor参数,并且在webapp.html中设置值为1.2 + - 优化了webapp.html搜索体验,支持“当前位置” + + - 0.6.0 更新README.md和图片,发布新版本 \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 69b7e74..689bdd0 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -83,7 +83,7 @@ module.exports = { loader: extractPlugin.extract("css?modules&localIdentName=[name]__[local]___[hash:base64:5]!sass") }, { - test: /\.(png|jpeg|jpg)$/, + test: /\.(png|jpeg|jpg|gif)$/, loader: "file-loader" }, { @@ -119,7 +119,8 @@ if (process.argv.indexOf("--ci") >= 0) { console.log(""); console.log(chalk.red("----------------------------------------------------------------")); errors.forEach(function(err) { - var msg = chalk.red(`ERROR in ${err.module.userRequest},`); + var msg = ''; + // var msg = chalk.red(`ERROR in ${err.module.userRequest},`); // msg += chalk.blue(`(${err.location.line},${err.location.character}),`); msg += chalk.red(err.message); console.log(msg); @@ -135,6 +136,7 @@ if (process.argv.indexOf("--ci") >= 0) { if (PRODUCTION) { module.exports.plugins.push( + //add DefinePlugin for production to save 88KB for React build new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production')