diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..e1c5c1b --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["es2015", "react"], + "plugins": ["transform-class-properties", "add-module-exports"] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 55618fe..7b4fa8c 100755 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ /node_modules -/buildOutput -/src/**/*.js -index.html \ No newline at end of file +/buildOutput \ No newline at end of file diff --git a/README.md b/README.md index 58f1d16..bf4f5b4 100755 --- a/README.md +++ b/README.md @@ -1,16 +1,15 @@ -

+

- +

A WebGL virtual globe and map engine

-

- +
## 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.4.4-blue.svg)](https://github.com/iSpring/WebGlobe/releases) -[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/iSpring/WebGlobe) +[![Release](https://img.shields.io/badge/release-0.5.1-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) [![Firefox 4+](https://img.shields.io/badge/Firefox-4+-E77827.svg)](http://caniuse.com/#search=WebGL) @@ -25,27 +24,107 @@ [![Opera Mobile 12+](https://img.shields.io/badge/Opera%20Mobile-12+-E23232.svg)](http://caniuse.com/#search=WebGL) -WebGlobe是基于HTML5原生WebGL实现的轻量级Google Earth三维地图引擎,支持Google地图、微软Bing地图、OpenStreetMap等。 +WebGlobe是基于HTML5原生WebGL实现的轻量级Google Earth三维地图引擎。 -没有使用第三方框架,无需插件,所有支持WebGL的浏览器均可使用。效率高,内存占用少。会持续完善,目标是使其成为三维在线地图服务网站。 +桌面版在线访问地址: https://ispring.github.io/WebGlobe/index.html -Demo: https://ispring.github.io/WebGlobe/index.html +移动版二维码访问(小米系统中的微信、小米默认浏览器在某些情况下存在已知bug): +
+ +
**如果觉得不错,欢迎Star和Fork!** -## Setup dev environment - 1. 项目有两个主要的分支:develop分支和master分支,develop是主分支,开发的代码都提交到该分支;master分支用于release,当develop分支中的代码比较稳定且有重要更新的时候,会将develop分支的代码merge到master分支,然后通过master分支进行发布新版本。 +## Features + 1. 没有使用第三方框架,无需插件,所有支持WebGL的浏览器均可使用。 + + 2. 支持Google、高德、微软Bing、腾讯、360、OpenStreetMap等底图服务。 + + 3. 支持影像图、行政图以及实施交通图。 + + 4. 支持搜索服务,既可以按照POI类型搜索,也可以按照POI名称搜索。 + + 5. 支持路线规划服务,支持自驾车、公交、步行三种出行方式,自驾车和公交出行均提供多种出行方案。 + + 6. 支持移动浏览器并对移动浏览器做了优化,并针对移动端做了一个WebApp,能够实现常用的地图功能,具有实用性。 - 2. 项目采用TypeScript编写,使用Webpack进行编译打包,编译成JavaScript运行,推荐使用[Visual Studio Code](http://code.visualstudio.com/)作为编辑器。 +## Getting Started + 1. 在项目的根目录下执行`npm install`,安装所需模块。执行`npm start`即可进行打包编译,在`buildOutput`目录中,在浏览器中打开`index.html`可访问WebGlobe桌面版,打开`webapp.html`可访问WebGlobe移动版。 - 3. 在项目的根目录下执行npm install,安装所需模块。 + 2. 项目有两个主要的分支:develop分支和master分支,develop是主分支,开发代码提交到该分支,master分支用于发布新版本。 - 4. package.json中定义了npm scripts: + 3. 项目的核心渲染引擎部分使用TypeScript进行开发,移动端WebApp界面采用Babel + React + react-router进行开发,使用Webpack进行构建,推荐使用最新的[Visual Studio Code](http://code.visualstudio.com/)作为编辑器。 + + 4. package.json中定义了`npm scripts`: - npm run clean 用于清除编译打包的结果 - npm run build:dev 对代码进行编译打包,代码没有压缩混淆,用于开发环境 - npm run build:prod 对代码进行编译打包,代码进行了压缩混淆,用于生产环境 - npm start 用于执行build:dev - 5. 开发过程中,在WebGlobe根目录下执行npm start即可进行打包编译 - - 6. 有问题的话欢迎大家提issue或者到 https://gitter.im/iSpring/WebGlobe 进行讨论 + 5. 接入持续集成服务[Travis CI](https://travis-ci.org/iSpring/WebGlobe),保证代码质量。 + + 6. 有问题的话欢迎大家提issue或者到[Gitter](https://gitter.im/iSpring/WebGlobe)中进行讨论。 + + ## Screenshots +**1. WebGlobe移动端主界面** +
+ + + +
+ + +**2. 附近搜索** +
+ + + +
+ + +**3. 搜索结果列表展示** +
+ + + +
+ + +**4. 搜索结果地图展示** +
+ + + +
+ + +**5. 路线规划** +
+ + + +
+ + +**6. 驾车出行路线** +
+ + + +
+ + +**7. 公交出行路线** +
+ + + +
+ + +**8. 步行出行路线** +
+ + + +
\ No newline at end of file diff --git a/images/1.png b/images/1.png new file mode 100644 index 0000000..98330ea Binary files /dev/null and b/images/1.png differ diff --git a/images/2.png b/images/2.png new file mode 100644 index 0000000..8215478 Binary files /dev/null and b/images/2.png differ diff --git a/images/3.png b/images/3.png new file mode 100644 index 0000000..877dbb0 Binary files /dev/null and b/images/3.png differ diff --git a/images/4.png b/images/4.png new file mode 100644 index 0000000..dc65e30 Binary files /dev/null and b/images/4.png differ diff --git a/images/5.png b/images/5.png new file mode 100644 index 0000000..53ee6e3 Binary files /dev/null and b/images/5.png differ diff --git a/images/6.png b/images/6.png new file mode 100644 index 0000000..7b1a4d5 Binary files /dev/null and b/images/6.png differ diff --git a/images/7.png b/images/7.png new file mode 100644 index 0000000..dfec62c Binary files /dev/null and b/images/7.png differ diff --git a/images/8.png b/images/8.png new file mode 100644 index 0000000..f8ac7b8 Binary files /dev/null and b/images/8.png differ diff --git a/images/qrcode.png b/images/qrcode.png new file mode 100644 index 0000000..f42689b Binary files /dev/null and b/images/qrcode.png differ diff --git a/webglobe.png b/images/webglobe.png old mode 100755 new mode 100644 similarity index 100% rename from webglobe.png rename to images/webglobe.png diff --git a/index.css b/index.css deleted file mode 100755 index 6d6d970..0000000 --- a/index.css +++ /dev/null @@ -1,26 +0,0 @@ -* { - margin: 0; - padding: 0; -} - -html, -body { - width: 100%; - height: 100%; -} - -body { - position: relative; - overflow: hidden; - cursor: default; -} - -#canvasId { - cursor: default; -} - -#about-me { - position: absolute; - right: 0; - bottom: 0; -} \ No newline at end of file diff --git a/index.ts b/index.ts deleted file mode 100755 index 526f6b1..0000000 --- a/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Kernel = require("./src/world/Kernel"); -import Globe = require("./src/world/Globe"); - -import "./index.css"; - -(function () { - var canvas = document.getElementById("canvasId"); - var globe = new Globe(canvas); - (window).globe = globe; - (window).kernel = Kernel; -})(); \ No newline at end of file diff --git a/package.json b/package.json index dfaeb8d..b89c494 100755 --- a/package.json +++ b/package.json @@ -1,51 +1,72 @@ { - "name": "webglobe", - "version": "0.4.4", - "description": "A WebGL virtual globe and map engine.", - "main": "require.js", - "scripts": { - "clean": "rimraf buildOutput && rimraf index.html", - "prebuild:dev": "npm run clean", - "build:dev": "cross-env NODE_ENV=development webpack --progress --colors", - "prebuild:prod": "npm run clean", - "build:prod": "cross-env NODE_ENV=production webpack --progress --colors", - "start": "npm run build:dev", - "test": "cross-env NODE_ENV=development webpack --progress --colors --ci" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/iSpring/WebGlobe.git" - }, - "keywords": [ - "WebGL", - "Map", - "Globe", - "Earth", - "3D", - "HTML5" - ], - "author": "iSpring", - "engines": { - "node": ">=6.0.0" - }, - "license": "ISC", - "bugs": { - "url": "https://github.com/iSpring/WebGlobe/issues" - }, - "homepage": "https://github.com/iSpring/WebGlobe#readme", - "devDependencies": { - "chalk": "^1.1.3", - "cross-env": "^3.1.3", - "css-loader": "^0.26.1", - "ejs-loader": "^0.3.0", - "html-webpack-plugin": "^2.26.0", - "rimraf": "^2.5.4", - "style-loader": "^0.13.1", - "ts-loader": "^1.3.3", - "typescript": "^2.0.3", - "webpack": "^1.14.0" - }, - "dependencies": { - "es6-promise": "^4.0.5" - } -} + "name": "webglobe", + "version": "0.5.1", + "description": "A WebGL virtual globe and map engine.", + "main": "require.js", + "scripts": { + "clean": "rimraf buildOutput && rimraf index.html", + "prebuild:dev": "npm run clean", + "build:dev": "cross-env NODE_ENV=development webpack --progress --colors", + "prebuild:prod": "npm run clean", + "build:prod": "cross-env NODE_ENV=production webpack --progress --colors", + "start": "npm run build:dev", + "test": "cross-env NODE_ENV=development webpack --progress --colors --ci" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/iSpring/WebGlobe.git" + }, + "keywords": [ + "WebGL", + "Map", + "Globe", + "Earth", + "3D", + "HTML5" + ], + "author": "iSpring", + "engines": { + "node": ">=6.0.0" + }, + "license": "ISC", + "bugs": { + "url": "https://github.com/iSpring/WebGlobe/issues" + }, + "homepage": "https://github.com/iSpring/WebGlobe#readme", + "devDependencies": { + "@types/es6-promise": "0.0.32", + "@types/react": "^15.0.21", + "@types/react-dom": "^0.14.23", + "babel-core": "^6.24.0", + "babel-loader": "^6.4.1", + "babel-plugin-add-module-exports": "^0.2.1", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-preset-es2015": "^6.24.0", + "babel-preset-react": "^6.23.0", + "chalk": "^1.1.3", + "classnames": "^2.2.5", + "cross-env": "^3.1.3", + "css-loader": "^0.26.1", + "ejs-loader": "^0.3.0", + "extract-text-webpack-plugin": "^1.0.1", + "file-loader": "^0.10.1", + "html-loader": "^0.4.5", + "html-webpack-plugin": "^2.26.0", + "node-sass": "^4.5.0", + "rimraf": "^2.5.4", + "sass-loader": "^4.1.1", + "style-loader": "^0.13.1", + "ts-loader": "^1.3.3", + "typescript": "^2.0.3", + "webpack": "^1.14.0", + "webpack-md5-hash": "0.0.5" + }, + "dependencies": { + "babel-polyfill": "^6.23.0", + "es6-promise": "^4.0.5", + "prop-types": "^15.5.8", + "react": "^15.4.2", + "react-dom": "^15.4.2", + "react-router": "^3.0.3" + } +} \ No newline at end of file diff --git a/images/WebGL.png b/src/core/images/WebGL.png similarity index 100% rename from images/WebGL.png rename to src/core/images/WebGL.png diff --git a/src/core/images/fork-me-on-github.png b/src/core/images/fork-me-on-github.png new file mode 100644 index 0000000..7dc1afc Binary files /dev/null and b/src/core/images/fork-me-on-github.png differ diff --git a/images/iSpring.png b/src/core/images/iSpring.png similarity index 100% rename from images/iSpring.png rename to src/core/images/iSpring.png diff --git a/images/logo.png b/src/core/images/logo.png similarity index 100% rename from images/logo.png rename to src/core/images/logo.png diff --git a/src/core/index.scss b/src/core/index.scss new file mode 100644 index 0000000..0f5fb39 --- /dev/null +++ b/src/core/index.scss @@ -0,0 +1,41 @@ +* { + margin: 0; + padding: 0; +} + +html, +body { + width: 100%; + height: 100%; +} + +body { + position: relative; + overflow: hidden; + cursor: default; +} + +:global { + #canvasId { + cursor: default; + } + #fork-me { + position: absolute; + right: 0; + top: 0; + outline: none !important; + } + #fork-me img { + position: absolute; + top: 0; + right: 0; + left: auto; + bottom: auto; + } + #about-me { + position: absolute; + right: 0; + bottom: 0; + outline: none !important; + } +} \ No newline at end of file diff --git a/src/core/index.ts b/src/core/index.ts new file mode 100644 index 0000000..d222339 --- /dev/null +++ b/src/core/index.ts @@ -0,0 +1,25 @@ +import Kernel from './world/Kernel'; +import Globe, {GlobeOptions} from './world/Globe'; +import './index.scss'; +// declare function require(name: string): any; +// const template = require('./template.html'); +// console.log(template); + +(function () { + var options = new GlobeOptions(); + options.satellite = true; + options.level = 3; + options.lonlat = 'auto'; + var globe = Globe.getInstance(options); + globe.placeAt(document.body); + (window).globe = globe; + (window).kernel = Kernel; + + function resize(){ + globe.resize(window.innerWidth, window.innerHeight); + } + + window.addEventListener("resize", resize, false); + + resize(); +})(); \ No newline at end of file diff --git a/src/core/template.html b/src/core/template.html new file mode 100644 index 0000000..1fd4e8c --- /dev/null +++ b/src/core/template.html @@ -0,0 +1,69 @@ + + + + + WebGlobe + + + + + + + + + + + + + + + + + Fork me on GitHub + + + + + \ No newline at end of file diff --git a/src/world/Camera.ts b/src/core/world/Camera.ts old mode 100755 new mode 100644 similarity index 80% rename from src/world/Camera.ts rename to src/core/world/Camera.ts index 32d0a34..91d7db9 --- a/src/world/Camera.ts +++ b/src/core/world/Camera.ts @@ -1,59 +1,61 @@ -import Kernel = require('./Kernel'); -import Utils = require('./Utils'); -import MathUtils = require('./math/Utils'); -import Vertice = require('./math/Vertice'); -import Vector = require('./math/Vector'); -import Line = require('./math/Line'); -import Plan = require('./math/Plan'); -import TileGrid,{TileGridPosition} from './TileGrid'; -import Matrix = require('./math/Matrix'); -import Object3D = require('./Object3D'); +import Kernel from './Kernel'; +import Utils from './Utils'; +import { EventEmitter } from './Events'; +import MathUtils from './math/Utils'; +import Vertice from './math/Vertice'; +import Vector from './math/Vector'; +import Line from './math/Line'; +import Plan from './math/Plan'; +import TileGrid, { TileGridPosition } from './TileGrid'; +import Matrix from './math/Matrix'; +import Object3D from './Object3D'; +import Extent from './Extent'; -export class CameraCore{ - constructor(private fov: number, private aspect: number, private near: number, private far: number, private floatLevel: number, private matrix: Matrix){ +export class CameraCore { + constructor(private fov: number, private aspect: number, private near: number, private far: number, private floatLevel: number, private matrix: Matrix) { } - getFov(){ + getFov() { return this.fov; } - getAspect(){ + getAspect() { return this.aspect; } - getNear(){ + getNear() { return this.near; } - getFar(){ + getFar() { return this.far; } - getFloatLevel(){ + getFloatLevel() { return this.floatLevel; } - getMatrix(){ + getMatrix() { return this.matrix; } - equals(other: CameraCore): boolean{ - if(!other){ + equals(other: CameraCore): boolean { + if (!other) { return false; } return this.fov === other.getFov() && - this.aspect === other.getAspect() && - this.near === other.getNear() && - this.far === other.getFar() && - this.floatLevel === other.getFloatLevel() && - this.matrix.equals(other.getMatrix()); + this.aspect === other.getAspect() && + this.near === other.getNear() && + this.far === other.getFar() && + this.floatLevel === other.getFloatLevel() && + this.matrix.equals(other.getMatrix()); } } -const realResolutionCache:any = {}; -(function(){ - for(var i = 0; i <= Kernel.MAX_LEVEL; i++){ +const realResolutionCache: any = {}; +(function () { + for (var i = 0; i <= Kernel.MAX_LEVEL; i++) { realResolutionCache[i] = Kernel.MAX_REAL_RESOLUTION / Math.pow(2, i); } })(); @@ -62,7 +64,7 @@ class Camera extends Object3D { private readonly initFov: number; private readonly animationDuration: number = 200;//层级变化的动画周期,毫秒 private readonly nearFactor: number = 0.6; - private readonly maxPitch:number = 40; + private readonly maxPitch: number = 40; private readonly resolutionFactor1: number = Math.pow(2, 0.3752950); private readonly resolutionFactor2: number = Math.pow(2, 1.3752950); @@ -74,7 +76,7 @@ class Camera extends Object3D { // private resolutionInWorld: number = -1;//屏幕1px在实际世界中的距离 private level: number = -1;//当前渲染等级 - private floatLevel: number = -2;//可能是正数,可能是非整数,非整数表示缩放动画过程中的level + private floatLevel: number = -2;//可能是整数,可能是非整数,非整数表示缩放动画过程中的level private lastFloatLevel: number = -3;//上次render()时所用到的this.realLevel private lastMatrix: Matrix;//上次render()时的this.matrix @@ -92,16 +94,21 @@ class Camera extends Object3D { private projMatrixForDraw: Matrix; private projViewMatrixForDraw: Matrix;//实际传递给shader的矩阵是projViewMatrixForDraw,而不是projViewMatrix - private lonlatsOfBoundary:number[][] = null; + /*left top,left middle,left bottom,right top,right middle,right bottom,middle top,middle bottom*/ + //如果没有交点,怎么没有对应的数据,长度就会小于8 + private lonlatsOfBoundary: number[][] = null; private animating: boolean = false; + private eventEmitter: EventEmitter = null; + //this.near一旦初始化之后就不应该再修改 //this.far可以动态计算 //this.aspect在Viewport改变后重新计算 //this.fov可以调整以实现缩放效果 - constructor(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]) { super(); + this.eventEmitter = new EventEmitter(); this.lonlatsOfBoundary = []; this.initFov = this.fov; this.lastMatrix = new Matrix(); @@ -112,19 +119,41 @@ class Camera extends Object3D { this.update(true); } - isEarthFullOverlapScreen(){ + getExtent() { + var extent: Extent = null; + if (this.isEarthFullOverlapScreen()) { + var lons: number[] = []; + var lats: number[] = []; + this.lonlatsOfBoundary.forEach(function (lonlat) { + lons.push(lonlat[0]); + lats.push(lonlat[1]); + }); + var minLon = Math.min(...lons); + var maxLon = Math.max(...lons); + var minLat = Math.min(...lats); + var maxLat = Math.max(...lats); + extent = new Extent(minLon, minLat, maxLon, maxLat); + } + return extent; + } + + getEventEmitter() { + return this.eventEmitter; + } + + isEarthFullOverlapScreen() { return this.lonlatsOfBoundary.length === 8; } - getTileGridsOfBoundary(level:number, filterRepeat: boolean){ - var tileGridsOfBoundary: TileGrid[] = this.lonlatsOfBoundary.map((lonlat)=>{ + getTileGridsOfBoundary(level: number, filterRepeat: boolean): TileGrid[] { + var tileGridsOfBoundary: TileGrid[] = this.lonlatsOfBoundary.map((lonlat) => { return TileGrid.getTileGridByGeo(lonlat[0], lonlat[1], level); }); return filterRepeat ? Utils.filterRepeatArray(tileGridsOfBoundary) : tileGridsOfBoundary; } - toJson():any{ - function matrixToJson(mat: Matrix){ + toJson(): any { + function matrixToJson(mat: Matrix) { return mat ? mat.toJson() : null; } var json = { @@ -150,11 +179,11 @@ class Camera extends Object3D { return json; } - toJsonString(){ + toJsonString() { return JSON.stringify(this.toJson()); } - fromJson(json: any){ + fromJson(json: any) { this.matrix = Matrix.fromJson(json.matrix); this.isZeroPitch = json.isZeroPitch; this.level = json.level; @@ -174,10 +203,9 @@ class Camera extends Object3D { this.projViewMatrixForDraw = Matrix.fromJson(json.projViewMatrixForDraw); this.animating = json.animating; this.update(true); - // Kernel.globe.refresh(true); } - fromJsonString(jsonStr: string){ + fromJsonString(jsonStr: string) { this.fromJson(JSON.parse(jsonStr)); } @@ -252,9 +280,9 @@ class Camera extends Object3D { return far; } - update(force: boolean = false): boolean{ + update(force: boolean = false): boolean { var shouldUpdate = this._updateCore(force); - if(shouldUpdate){ + if (shouldUpdate) { this._updateTileGridsOfBoundary(); } return shouldUpdate; @@ -263,7 +291,7 @@ class Camera extends Object3D { //更新各种矩阵,理论上只在用户交互的时候调用就可以 private _updateCore(force: boolean = false): boolean { var shouldUpdate = force || this._isNeedUpdate(); - if(shouldUpdate){ + if (shouldUpdate) { this._normalUpdate(); this._updateProjViewMatrixForDraw(); } @@ -276,9 +304,9 @@ class Camera extends Object3D { return shouldUpdate; } - private _updateTileGridsOfBoundary(){ - var lonlatsOfBoundary:number[][] = []; - var ndcs:number[][] = [ + private _updateTileGridsOfBoundary() { + var lonlatsOfBoundary: number[][] = []; + var ndcs: number[][] = [ [-1, 1],//left top [-1, 0],//left middle [-1, -1],//left bottom @@ -288,26 +316,26 @@ class Camera extends Object3D { [0, 1],//middle top [0, -1]//middle bottom ]; - ndcs.forEach((ndcXY:number[]) => { + ndcs.forEach((ndcXY: number[]) => { var lonlat = this._getPickLonLatByNDC(ndcXY[0], ndcXY[1]); - if(lonlat && lonlat.length > 0){ + if (lonlat && lonlat.length > 0) { lonlatsOfBoundary.push(lonlat); } }); this.lonlatsOfBoundary = lonlatsOfBoundary; } - getCameraCore(){ + getCameraCore() { return new CameraCore(this.fov, this.aspect, this.near, this.far, this.floatLevel, this.matrix.clone()); } - private _isNeedUpdate(): boolean{ + private _isNeedUpdate(): boolean { return (this.fov !== this.lastFov) || - (this.aspect !== this.lastAspect) || - (this.near !== this.lastNear) || - (this.far !== this.lastFar) || - (this.floatLevel !== this.lastFloatLevel) || - (!this.matrix.equals(this.lastMatrix)); + (this.aspect !== this.lastAspect) || + (this.near !== this.lastNear) || + (this.far !== this.lastFar) || + (this.floatLevel !== this.lastFloatLevel) || + (!this.matrix.equals(this.lastMatrix)); } getProjViewMatrixForDraw(): Matrix { @@ -334,7 +362,7 @@ class Camera extends Object3D { var near = this.near; //计算newFar - var newPosition = this.matrixForDraw.getPosition(); + // var newPosition = this.matrixForDraw.getPosition(); var newFar = this.far; //this._getMinimalFar(newPosition); //根据newFov和newFar重新计算 @@ -419,7 +447,7 @@ class Camera extends Object3D { } //resolution,level - measureXYResolutionAndBestDisplayLevel(): any{ + measureXYResolutionAndBestDisplayLevel(): any { //计算resolution var p = this.matrix.getPosition(); var dir = Vector.fromVertice(p); @@ -427,7 +455,7 @@ class Camera extends Object3D { var pickResult1 = this._getPickCartesianCoordInEarthByLine(line); var p1 = pickResult1[0]; var ndc1 = this._convertVerticeFromWorldToNDC(p1); - var canvasXY1 = MathUtils.convertPointFromNdcToCanvas(ndc1.x, ndc1.y); + var canvasXY1 = MathUtils.convertPointFromNdcToCanvas(this.canvas.width, this.canvas.height, ndc1.x, ndc1.y); var centerX = canvasXY1[0]; var centerY = canvasXY1[1]; @@ -458,15 +486,15 @@ class Camera extends Object3D { } //[resolution,level] - calculateCurrentResolutionAndBestDisplayLevel(){ + calculateCurrentResolutionAndBestDisplayLevel() { var distance2EarthOrigin = this.getDistance2EarthOrigin(); return this._calculateResolutionAndBestDisplayLevelByDistance2EarthOrigin(distance2EarthOrigin); } - //L=>[resolution,level] - private _calculateResolutionAndBestDisplayLevelByDistance2EarthOrigin(distance2EarthOrigin: number){ + //distance2EarthOrigin=>[resolution,level] + private _calculateResolutionAndBestDisplayLevelByDistance2EarthOrigin(distance2EarthOrigin: number) { var α2 = MathUtils.degreeToRadian(this.fov / 2); - var α1 = Math.atan(2 / Kernel.canvas.height * Math.tan(α2)); + var α1 = Math.atan(2 / this.canvas.height * Math.tan(α2)); var δ = Math.asin(distance2EarthOrigin * Math.sin(α1) / Kernel.EARTH_RADIUS); var β = δ - α1; var resolution = β * Kernel.EARTH_RADIUS * this.resolutionFactor2; @@ -475,50 +503,63 @@ class Camera extends Object3D { } //D=>[resolution,level] - private _calculateResolutionAndBestDisplayLevelByDistance2EarthSurface(distance2EarthSurface: number){ + private _calculateResolutionAndBestDisplayLevelByDistance2EarthSurface(distance2EarthSurface: number) { var distance2EarthOrigin = distance2EarthSurface + Kernel.EARTH_RADIUS; return this._calculateResolutionAndBestDisplayLevelByDistance2EarthOrigin(distance2EarthOrigin); } //level=>D - private _calculateDistance2EarthSurfaceByBestDisplayLevel(level: number){ + private _calculateDistance2EarthSurfaceByBestDisplayLevel(level: number) { return this._calculateDistance2EarthOriginByBestDisplayLevel(level) - Kernel.EARTH_RADIUS; } //level=>L - private _calculateDistance2EarthOriginByBestDisplayLevel(level: number){ + private _calculateDistance2EarthOriginByBestDisplayLevel(level: number) { var resolution = this._calculateResolutionByLevel(level); return this._calculateDistance2EarthOriginByResolution(resolution); } - //resolution=>L - private _calculateDistance2EarthOriginByResolution(resolution: number){ + //resolution=>distance2EarthOrigin + private _calculateDistance2EarthOriginByResolution(resolution: number) { resolution /= this.resolutionFactor2; var α2 = MathUtils.degreeToRadian(this.fov / 2); - var α1 = Math.atan(2 / Kernel.canvas.height * Math.tan(α2)); + var α1 = Math.atan(2 / this.canvas.height * Math.tan(α2)); var β = resolution / Kernel.EARTH_RADIUS; var δ = α1 + β; var distance2EarthOrigin = Kernel.EARTH_RADIUS * Math.sin(δ) / Math.sin(α1); return distance2EarthOrigin; } - private _calculateLevelByResolution(resolution: number){ + private _calculateLevelByResolution(resolution: number) { var pow2value = Kernel.MAX_RESOLUTION / resolution; var bestDisplayLevelFloat = MathUtils.log2(pow2value); return bestDisplayLevelFloat; } - private _calculateResolutionByLevel(level: number){ + private _calculateResolutionByLevel(level: number) { return Kernel.MAX_RESOLUTION / Math.pow(2, level); } //屏幕1px在实际世界中的距离 - getResolutionInWorld(): number{ - if(realResolutionCache.hasOwnProperty(this.level)){ + getResolutionInWorld(): number { + if (realResolutionCache.hasOwnProperty(this.level)) { return realResolutionCache[this.level]; - }else{ + } else { return Kernel.MAX_REAL_RESOLUTION / Math.pow(2, this.level); - } + } + } + + getVertice() { + const origin2PositionVector = Vector.fromVertice(this.getPosition()); + origin2PositionVector.setLength(Kernel.EARTH_RADIUS); + const p = origin2PositionVector.getVertice(); + return p; + } + + getLonlat() { + const p = this.getVertice(); + const lonlat = MathUtils.cartesianCoordToGeographic(p); + return lonlat; } getLevel(): number { @@ -529,18 +570,26 @@ class Camera extends Object3D { if (!(Utils.isNonNegativeInteger(level))) { throw "invalid level:" + level; } - if(level < Kernel.MIN_LEVEL){ + if (level < Kernel.MIN_LEVEL) { level = Kernel.MIN_LEVEL; } - if(level > Kernel.MAX_LEVEL){ + if (level > Kernel.MAX_LEVEL) { level = Kernel.MAX_LEVEL; } - if (level !== this.level || force) { + const oldLevel = this.level; + const levelChanged = level !== this.level; + if (levelChanged || force) { //不要在this._updatePositionByLevel()方法中更新this.level,因为这会影响animateToLevel()方法 - var isLevelChanged = this._updatePositionByLevel(level, this.matrix); + this._updatePositionByLevel(level, this.matrix); this.level = level; this.floatLevel = level; } + if(levelChanged){ + Utils.publish('level-change', { + oldLevel: oldLevel, + newLevel: this.level + }); + } } // calculateInitDistanceToOrigin(factor:number = 1){ @@ -550,7 +599,7 @@ class Camera extends Object3D { // return initDistanceToOrigin; // } - private _initCameraPosition(level: number, lon:number, lat:number) { + private _initCameraPosition(level: number, lon: number, lat: number) { var initDistanceToOrigin = this._calculateDistance2EarthOriginByBestDisplayLevel(level); var initPosition = MathUtils.geographicToCartesianCoord(lon, lat, initDistanceToOrigin); var origin = new Vertice(0, 0, 0); @@ -562,7 +611,6 @@ class Camera extends Object3D { //设置观察到的层级,不要在该方法中修改this.level的值 private _updatePositionByLevel(level: number, cameraMatrix: Matrix) { - var globe = Kernel.globe; var intersects = this._getDirectionIntersectPointWithEarth(cameraMatrix); if (intersects.length === 0) { throw "no intersect"; @@ -576,6 +624,9 @@ class Camera extends Object3D { } setDeltaPitch(deltaPitch: number) { + if (this.level < Kernel.MIN_PITCH_LEVEL || !this.isEarthFullOverlapScreen()) { + return; + } var currentPitch = this.getPitch(); var newPitch = currentPitch + deltaPitch; if (newPitch > this.maxPitch) { @@ -610,7 +661,6 @@ class Camera extends Object3D { //刷新 this.isZeroPitch = newPitch === 0; this.matrix = matrix; - // Kernel.globe.refresh(); } //pitch表示Camera视线的倾斜角度,初始值为0,表示视线经过球心,单位为角度,范围是[0, this.maxPitch] @@ -645,7 +695,7 @@ class Camera extends Object3D { var pitch = MathUtils.radianToDegree(radian); - if(pitch >= 90){ + if (pitch >= 90) { throw `Invalid pitch: ${pitch}`; } @@ -697,7 +747,7 @@ class Camera extends Object3D { return length2EarthSurface; } - getDistance2EarthOrigin(): number{ + getDistance2EarthOrigin(): number { var position = this.getPosition(); return Vector.fromVertice(position).getLength(); } @@ -706,7 +756,64 @@ class Camera extends Object3D { return this.animating; } - animateToLevel(newLevel: number, cb?: ()=>void): void { + centerTo(newLon: number, newLat: number, newLevel: number = this.getLevel()) { + if (newLevel !== this.getLevel()) { + const newPosition = this._safelyGetNewPositonByLevel(newLevel); + const newDistance = Vector.fromVertice(newPosition).getLength(); + this._setPositionByLonLatDistance(newLon, newLat, newDistance); + } else { + this._setPositionByLonLatDistance(newLon, newLat); + } + this.setLevel(newLevel, true); + } + + animateTo(newLon: number, newLat: number, newLevel: number = this.getLevel(), duration: number = 1000) { + const promise = new Promise((resolve, reject) => { + if (this.isAnimating()) { + reject("be animating"); + return; + } + const [startLon, startLat] = this.getLonlat(); + const singleSpan = 1000 / 60; + const count = Math.floor(duration / singleSpan); + let start: number = -1; + + const deltaLon = (newLon - startLon) / count; + const deltaLat = (newLat - startLat) / count; + let deltaLevel: number = 0; + let deltaHeight: number = 0; + if (newLevel !== this.level) { + deltaLevel = (newLevel - this.level) / count; + const startDistance = this.getDistance2EarthOrigin(); + const newPosition = this._safelyGetNewPositonByLevel(newLevel); + const newDistance = Vector.fromVertice(newPosition).getLength(); + deltaHeight = (newDistance - startDistance) / count; + } + + this.animating = true; + + const callback = (timestap: number) => { + if (start < 0) { + start = timestap; + } + var a = timestap - start; + if (a >= duration) { + this.animating = false; + this.floatLevel = newLevel; + this.centerTo(newLon, newLat, newLevel); + resolve(); + } else { + this.floatLevel += deltaLevel; + this._setPositionByDeltaLonLatDistance(deltaLon, deltaLat, deltaHeight); + requestAnimationFrame(callback); + } + } + requestAnimationFrame(callback); + }); + return promise; + } + + animateToLevel(newLevel: number, cb?: () => void): void { if (this.isAnimating()) { return; } @@ -714,9 +821,10 @@ class Camera extends Object3D { if (!(Utils.isNonNegativeInteger(newLevel))) { throw "invalid level:" + newLevel; } - var newCameraMatrix = this.matrix.clone(); - this._updatePositionByLevel(newLevel, newCameraMatrix); - var newPosition = newCameraMatrix.getPosition(); + // var newCameraMatrix = this.matrix.clone(); + // this._updatePositionByLevel(newLevel, newCameraMatrix); + // var newPosition = newCameraMatrix.getPosition(); + const newPosition = this._safelyGetNewPositonByLevel(newLevel); var oldPosition = this.getPosition(); var span = this.animationDuration; @@ -739,7 +847,7 @@ class Camera extends Object3D { this.animating = false; this.floatLevel = newLevel; this.setLevel(newLevel); - if(cb){ + if (cb) { cb(); } } else { @@ -752,6 +860,87 @@ class Camera extends Object3D { requestAnimationFrame(callback); } + setExtent(extent: Extent){ + if(extent){ + const [lon, lat, level] = this._calculateLonLatLevelByExtent(extent); + this.centerTo(lon, lat, level); + } + } + + animateToExtent(extent: Extent, duration: number = 1000){ + const [lon, lat, level] = this._calculateLonLatLevelByExtent(extent); + return this.animateTo(lon, lat, level, duration); + } + + private _safelyGetNewPositonByLevel(newLevel: number) { + const newCameraMatrix = this.matrix.clone(); + this._updatePositionByLevel(newLevel, newCameraMatrix); + const newPosition = newCameraMatrix.getPosition(); + return newPosition; + } + + private _setPositionByLonLatDistance(newLon: number, newLat: number, newLengthFromOrigin2Positon?: number) { + const [lon, lat] = this.getLonlat(); + const deltaLon = newLon - lon; + const deltaLat = newLat - lat; + + this._setPositionByDeltaLonLatDistance(deltaLon, deltaLat); + + if (newLengthFromOrigin2Positon > 0) { + this._setPositionByDistanceFromOrigin2Camera(newLengthFromOrigin2Positon); + } + } + + private _setPositionByDeltaLonLatDistance(deltaLon: number, deltaLat: number, deltaHeight?: number) { + const deltaLonRadian = MathUtils.degreeToRadian(deltaLon); + const deltaLatRadian = MathUtils.degreeToRadian(deltaLat); + this.worldRotateY(deltaLonRadian); + const vector1 = Vector.fromVertice(this.getPosition()); + const vector2 = new Vector(0, 1, 0); + const crossAxis = vector1.cross(vector2); + this.worldRotateByVector(deltaLatRadian, crossAxis); + + if (deltaHeight > 0 || deltaHeight < 0) { + const vectorFromOrigin2Position = Vector.fromVertice(this.getPosition()); + const newLength = vectorFromOrigin2Position.getLength() + deltaHeight; + vectorFromOrigin2Position.setLength(newLength); + const newPosition = vectorFromOrigin2Position.getVertice(); + this.setPosition(newPosition); + } + } + + private _setPositionByDistanceFromOrigin2Camera(newLengthFromOrigin2Positon: number) { + const vectorFromOrigin2Position = Vector.fromVertice(this.getPosition()); + vectorFromOrigin2Position.setLength(newLengthFromOrigin2Positon); + const newPosition = vectorFromOrigin2Position.getVertice(); + this.setPosition(newPosition); + } + + private _safelyGetValidLevel(level: number){ + if(level > Kernel.MAX_LEVEL){ + level = Kernel.MAX_LEVEL; + }else if(level < Kernel.MIN_LEVEL){ + level = Kernel.MIN_LEVEL; + } + return level; + } + + private _calculateLonLatLevelByExtent(extent: Extent){ + const centerLon = (extent.getMinLon() + extent.getMaxLon()) / 2; + const centerLat = (extent.getMinLat() + extent.getMaxLat()) / 2; + const deltaLon = extent.getMaxLon() - extent.getMinLon(); + const deltaLonRadian = MathUtils.degreeToRadian(deltaLon); + const deltaLength = Kernel.EARTH_RADIUS * deltaLonRadian; + const resolution = deltaLength / this.canvas.width; + const bestFloatLevel = this._calculateLevelByResolution(resolution); + let level = Math.floor(bestFloatLevel); + // if(bestFloatLevel - level >= 0.9){ + // level += 1; + // } + level = this._safelyGetValidLevel(level); + return [centerLon, centerLat, level]; + } + private _look(cameraPnt: Vertice, targetPnt: Vertice, upDirection: Vector = new Vector(0, 1, 0)): void { var cameraPntCopy = cameraPnt.clone(); var targetPntCopy = targetPnt.clone(); @@ -783,7 +972,7 @@ class Camera extends Object3D { //根据canvasX和canvasY获取拾取向量 private _getPickDirectionByCanvas(canvasX: number, canvasY: number): Vector { - var ndcXY = MathUtils.convertPointFromCanvasToNDC(canvasX, canvasY); + var ndcXY = MathUtils.convertPointFromCanvasToNDC(this.canvas.width, this.canvas.height, canvasX, canvasY); var pickDirection = this._getPickDirectionByNDC(ndcXY[0], ndcXY[1]); return pickDirection; } @@ -831,10 +1020,10 @@ class Camera extends Object3D { return result; } - private _getPickLonLatByNDC(ndcX: number, ndcY: number): number[]{ - var result:number[] = null; + private _getPickLonLatByNDC(ndcX: number, ndcY: number): number[] { + var result: number[] = null; var vertices = this._getPickCartesianCoordInEarthByNDC(ndcX, ndcY); - if(vertices.length > 0){ + if (vertices.length > 0) { result = MathUtils.cartesianCoordToGeographic(vertices[0]); } return result; @@ -1016,9 +1205,9 @@ class Camera extends Object3D { var centerGrid: TileGrid = null; var verticalCenterInfo = this._getVerticalVisibleCenterInfo(); - if(TileGrid.isValidLatitude(verticalCenterInfo.lat)){ + if (TileGrid.isValidLatitude(verticalCenterInfo.lat)) { centerGrid = TileGrid.getTileGridByGeo(verticalCenterInfo.lon, verticalCenterInfo.lat, level); - }else{ + } else { centerGrid = new TileGrid(level, 0, 0); } var handleRowThis = handleRow.bind(this); @@ -1185,11 +1374,11 @@ class Camera extends Object3D { var cross = vector03.cross(vector01); result.clockwise = cross.z > 0; //计算面积 - var topWidth = Math.sqrt(Math.pow(ndcs[1].x - ndcs[2].x, 2) + Math.pow(ndcs[1].y - ndcs[2].y, 2)) * Kernel.canvas.width / 2; - var bottomWidth = Math.sqrt(Math.pow(ndcs[0].x - ndcs[3].x, 2) + Math.pow(ndcs[0].y - ndcs[3].y, 2)) * Kernel.canvas.width / 2; + var topWidth = Math.sqrt(Math.pow(ndcs[1].x - ndcs[2].x, 2) + Math.pow(ndcs[1].y - ndcs[2].y, 2)) * this.canvas.width / 2; + var bottomWidth = Math.sqrt(Math.pow(ndcs[0].x - ndcs[3].x, 2) + Math.pow(ndcs[0].y - ndcs[3].y, 2)) * this.canvas.width / 2; result.width = Math.floor((topWidth + bottomWidth) / 2); - var leftHeight = Math.sqrt(Math.pow(ndcs[0].x - ndcs[1].x, 2) + Math.pow(ndcs[0].y - ndcs[1].y, 2)) * Kernel.canvas.height / 2; - var rightHeight = Math.sqrt(Math.pow(ndcs[2].x - ndcs[3].x, 2) + Math.pow(ndcs[2].y - ndcs[3].y, 2)) * Kernel.canvas.height / 2; + var leftHeight = Math.sqrt(Math.pow(ndcs[0].x - ndcs[1].x, 2) + Math.pow(ndcs[0].y - ndcs[1].y, 2)) * this.canvas.height / 2; + var rightHeight = Math.sqrt(Math.pow(ndcs[2].x - ndcs[3].x, 2) + Math.pow(ndcs[2].y - ndcs[3].y, 2)) * this.canvas.height / 2; result.height = Math.floor((leftHeight + rightHeight) / 2); result.area = result.width * result.height; diff --git a/src/world/Definitions.d.ts b/src/core/world/Definitions.d.ts old mode 100755 new mode 100644 similarity index 68% rename from src/world/Definitions.d.ts rename to src/core/world/Definitions.d.ts index 7726bf8..75ae6e7 --- a/src/world/Definitions.d.ts +++ b/src/core/world/Definitions.d.ts @@ -1,6 +1,6 @@ -import Matrix = require('./math/Matrix'); +import Matrix from './math/Matrix'; import Camera from './Camera'; -import GraphicGroup = require('./GraphicGroup'); +import GraphicGroup from './GraphicGroup'; interface WebGLProgramExtension extends WebGLProgram{ uMVMatrix: WebGLUniformLocation; @@ -31,4 +31,19 @@ export interface Drawable{ draw(camera: Camera): void; shouldDraw(camera: Camera): boolean; destroy(): void; -} \ No newline at end of file +} + +export interface CancelablePromise extends Promise{ + cancel: () => void; +} + +export interface Destroyable{ + destroy: () => void; +} + +// declare module "*.png" { +// const content: any; +// export default content; +// } + +// declare function require(path: string): string; \ No newline at end of file diff --git a/src/world/EventHandler.ts b/src/core/world/EventHandler.ts old mode 100755 new mode 100644 similarity index 73% rename from src/world/EventHandler.ts rename to src/core/world/EventHandler.ts index cc4d066..4d477ba --- a/src/world/EventHandler.ts +++ b/src/core/world/EventHandler.ts @@ -1,10 +1,13 @@ -import Kernel = require("./Kernel"); -import Utils = require("./Utils"); -import MathUtils = require("./math/Utils"); -import Vector = require("./math/Vector"); -import Camera from "./Camera"; +import Kernel from './Kernel'; +import Utils from './Utils'; +import MathUtils from './math/Utils'; +import Vector from './math/Vector'; +import Globe from './Globe'; +import {Destroyable} from './Definitions.d'; -class EventHandler { +type DomEventListener = () => any; + +export default class EventHandler implements Destroyable { private down: boolean = false; private dragGeo: any = null; private previousX: number = -1; @@ -14,40 +17,45 @@ class EventHandler { private lastTime: number = -1; private startTime: number = -1; private endTime: number = -1; + private resizeListener: DomEventListener = null; + private bodyKeydownListener: DomEventListener = null; - constructor(private canvas: HTMLCanvasElement) { + constructor(private globe: Globe) { this.endTime = this.startTime = this.lastTime = this.oldTime = Date.now(); this._bindEvents(); - this._initLayout(); } - private _bindEvents() { - window.addEventListener("resize", this._initLayout.bind(this)); - if (Utils.isMobile()) { - this.canvas.addEventListener("touchstart", this._onTouchStart.bind(this), false); - this.canvas.addEventListener("touchend", this._onTouchEnd.bind(this), false); - this.canvas.addEventListener("touchmove", this._onTouchMove.bind(this), false); - } else { - this.canvas.addEventListener("mousedown", this._onMouseDown.bind(this), false); - this.canvas.addEventListener("mouseup", this._onMouseUp.bind(this), false); - this.canvas.addEventListener("mousemove", this._onMouseMove.bind(this), false); - this.canvas.addEventListener("dblclick", this._onDbClick.bind(this), false); - this.canvas.addEventListener("mousewheel", this._onMouseWheel.bind(this), false); - this.canvas.addEventListener("DOMMouseScroll", this._onMouseWheel.bind(this), false); - document.body.addEventListener("keydown", this._onKeyDown.bind(this), false); + destroy(){ + this.globe = null; + const eventNames = ["touchstart", "touchend", "touchmove", "mousedown", "mouseup", "mousemove", "dblclick", "mousewheel", "DOMMouseScroll"]; + eventNames.forEach((eventName) => { + this.globe.canvas.removeEventListener(eventName); + }); + if(this.bodyKeydownListener){ + document.body.removeEventListener("keydown", this.bodyKeydownListener); } + this.bodyKeydownListener = null; } - private _initLayout() { - this.canvas.width = document.body.clientWidth; - this.canvas.height = document.body.clientHeight; - if (Kernel.globe) { - Kernel.globe.camera.setAspect(this.canvas.width / this.canvas.height); + private _bindEvents() { + if (Utils.isMobile()) { + this.globe.canvas.addEventListener("touchstart", this._onTouchStart.bind(this), false); + this.globe.canvas.addEventListener("touchend", this._onTouchEnd.bind(this), false); + this.globe.canvas.addEventListener("touchmove", this._onTouchMove.bind(this), false); + } else { + 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("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); + this.bodyKeydownListener = this._onKeyDown.bind(this); + document.body.addEventListener("keydown", this.bodyKeydownListener, false); } } moveLonLatToCanvas(lon: number, lat: number, canvasX: number, canvasY: number) { - var pickResult = Kernel.globe.camera.getPickCartesianCoordInEarthByCanvas(canvasX, canvasY); + var pickResult = this.globe.camera.getPickCartesianCoordInEarthByCanvas(canvasX, canvasY); if (pickResult.length > 0) { var newLonLat = MathUtils.cartesianCoordToGeographic(pickResult[0]); var newLon = newLonLat[0]; @@ -66,21 +74,21 @@ class EventHandler { var v2 = Vector.fromVertice(p2); var rotateVector = v1.cross(v2); var rotateRadian = -Vector.getRadianOfTwoVectors(v1, v2); - Kernel.globe.camera.worldRotateByVector(rotateRadian, rotateVector); + this.globe.camera.worldRotateByVector(rotateRadian, rotateVector); } private _handleMouseDownOrTouchStart(offsetX: number, offsetY: number) { this.down = true; this.previousX = offsetX; this.previousY = offsetY; - var pickResult = Kernel.globe.camera.getPickCartesianCoordInEarthByCanvas(this.previousX, this.previousY); + var pickResult = this.globe.camera.getPickCartesianCoordInEarthByCanvas(this.previousX, this.previousY); if (pickResult.length > 0) { this.dragGeo = MathUtils.cartesianCoordToGeographic(pickResult[0]); } } private _handleMouseMoveOrTouchMove(currentX: number, currentY: number) { - var globe = Kernel.globe; + var globe = this.globe; if (!globe || globe.isAnimating() || !this.down) { return; } @@ -96,13 +104,13 @@ class EventHandler { } this.previousX = currentX; this.previousY = currentY; - this.canvas.style.cursor = "pointer"; + this.globe.canvas.style.cursor = "pointer"; } else { //mouse out of Earth this.previousX = -1; this.previousY = -1; this.dragGeo = null; - this.canvas.style.cursor = "default"; + this.globe.canvas.style.cursor = "default"; } } @@ -111,13 +119,14 @@ class EventHandler { this.previousX = -1; this.previousY = -1; this.dragGeo = null; - if (this.canvas) { - this.canvas.style.cursor = "default"; + if (this.globe.canvas) { + this.globe.canvas.style.cursor = "default"; } + Utils.publish("extent-change"); } private _onMouseDown(event: MouseEvent) { - var globe = Kernel.globe; + var globe = this.globe; if (!globe || globe.isAnimating()) { return; } @@ -130,7 +139,7 @@ class EventHandler { if(!this.down){ return; } - if(Kernel.globe.isAnimating()){ + if(this.globe.isAnimating()){ return; } var currentX = event.layerX || event.offsetX; @@ -143,7 +152,7 @@ class EventHandler { } private _onDbClick(event: MouseEvent) { - var globe = Kernel.globe; + var globe = this.globe; if (!globe || globe.isAnimating()) { return; } @@ -165,7 +174,7 @@ class EventHandler { } private _onMouseWheel(event: MouseWheelEvent) { - var globe = Kernel.globe; + var globe = this.globe; if (!globe || globe.isAnimating()) { return; } @@ -183,12 +192,14 @@ class EventHandler { var newLevel = globe.getLevel() + deltaLevel; if (newLevel >= 0) { //globe.setLevel(newLevel); - globe.animateToLevel(newLevel); + globe.animateToLevel(newLevel, function(){ + Utils.publish("extent-change"); + }); } } private _onKeyDown(event: KeyboardEvent) { - var globe = Kernel.globe; + var globe = this.globe; if (!globe || globe.isAnimating()) { return; } @@ -217,7 +228,7 @@ class EventHandler { var time2 = this.endTime - this.lastTime; if (time2 < 300) { this.lastTime = this.oldTime; - Kernel.globe.zoomIn(); + this.globe.zoomIn(); }else { this.lastTime = this.endTime; } @@ -256,7 +267,7 @@ class EventHandler { } private _onTouchStart(event: TouchEvent) { - var globe = Kernel.globe; + var globe = this.globe; if (!globe || globe.isAnimating()) { return; } @@ -290,21 +301,22 @@ class EventHandler { var twoTouchDistance = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); var radio = twoTouchDistance / this.twoTouchDistance; if(radio >= 1.3){ - Kernel.globe.animateIn(()=>{ + this.globe.animateIn(()=>{ this.twoTouchDistance = twoTouchDistance; }); }else if(radio <= 0.7){ - Kernel.globe.animateOut(()=>{ + this.globe.animateOut(()=>{ this.twoTouchDistance = twoTouchDistance; }); } } private _onTouchMove(event: TouchEvent) { + event.preventDefault(); if(!this.down){ return; } - if(Kernel.globe.isAnimating()){ + if(this.globe.isAnimating()){ return; } var touchCount = event.targetTouches.length; @@ -327,7 +339,4 @@ class EventHandler { this._onTouchMulti(); } } - -} - -export = EventHandler; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/core/world/Events.ts b/src/core/world/Events.ts new file mode 100644 index 0000000..e41bf22 --- /dev/null +++ b/src/core/world/Events.ts @@ -0,0 +1,71 @@ +type Listener = (data: any) => void; + +export interface Handle{ + remove(): void; +} + +export class EventEmitter{ + events:{[key:string]:Listener[]} = null; + // onceListeners:Listener[] = null; + + constructor(){ + this.events = {}; + // this.onceListeners = []; + } + + emit(eventName:string, data: any){ + var listeners = this.events[eventName]; + if(listeners){ + listeners.forEach((listener)=>{ + listener(data); + // var index = this.onceListeners.indexOf(listener); + // if(index >= 0){ + // this.onceListeners.splice(index, 1); + // } + }); + } + } + + // once(eventName:string, listener: Listener): Handle{ + // this.onceListeners.push(listener); + // return this.addEventListener(eventName, listener); + // } + + on(eventName:string, listener: Listener): Handle{ + return this.addEventListener(eventName, listener); + } + + addEventListener(eventName:string, listener: Listener): Handle{ + var listeners = this.events[eventName]; + if(listeners){ + listeners.push(listener); + }else{ + this.events[eventName] = [listener]; + } + var handle: any = {}; + handle.remove = () => this.removeEventListener(eventName, listener); + return handle; + } + + removeEventListener(eventName:string, listener: Listener){ + var listeners = this.events[eventName]; + if(listeners){ + var index1 = listeners.indexOf(listener); + if(index1 >= 0){ + listeners.splice(index1, 1); + } + // var index2 = this.onceListeners.indexOf(listener); + // if(index2 >= 0){ + // this.onceListeners.splice(index2, 1); + // } + } + } + + removeAllEventListeners(eventName?:string){ + if(eventName){ + this.events[eventName] = []; + }else{ + this.events = {}; + } + } +}; \ No newline at end of file diff --git a/src/core/world/Extent.ts b/src/core/world/Extent.ts new file mode 100644 index 0000000..345ebd3 --- /dev/null +++ b/src/core/world/Extent.ts @@ -0,0 +1,89 @@ +export default class Extent { + constructor(private minLon: number, private minLat: number, private maxLon: number, private maxLat: number) { + + } + + clone() { + return new Extent(this.minLon, this.minLat, this.maxLon, this.maxLat); + } + + getMinLon() { + return this.minLon; + } + + getMinLat() { + return this.minLat; + } + + getMaxLon() { + return this.maxLon; + } + + getMaxLat() { + return this.maxLat; + } + + toJson() { + return [this.minLon, this.minLat, this.maxLon, this.maxLat]; + } + + static merge(extents: Extent[], union: boolean):Extent{ + var result: Extent = null; + if (extents.length === 1) { + result = extents[0].clone(); + } else if(extents.length > 1){ + var minLons: number[] = []; + var minLats: number[] = []; + var maxLons: number[] = []; + var maxLats: number[] = []; + extents.forEach(function (extent) { + minLons.push(extent.getMinLon()); + minLats.push(extent.getMinLat()); + maxLons.push(extent.getMaxLon()); + maxLats.push(extent.getMaxLat()); + }); + if(union){ + const minLon = Math.min(...minLons); + const minLat = Math.min(...minLats); + const maxLon = Math.max(...maxLons); + const maxLat = Math.max(...maxLats); + result = new Extent(minLon, minLat, maxLon, maxLat); + }else{ + const minLon = Math.max(...minLons); + const minLat = Math.max(...minLats); + const maxLon = Math.min(...maxLons); + const maxLat = Math.min(...maxLats); + if(minLon < maxLon && minLat < maxLat){ + result = new Extent(minLon, minLat, maxLon, maxLat); + } + } + } + return result; + } + + static union(extents: Extent[]): Extent { + return this.merge(extents, true); + } + + static intersect(extents: Extent[]): Extent { + return this.merge(extents, false); + } + + static fromLonlats(lonlats:number[][]){ + if(lonlats.length <= 1){ + return null; + } + const lons:number[] = []; + const lats:number[] = []; + lonlats.forEach((lonlat:number[]) => { + lons.push(lonlat[0]); + lats.push(lonlat[1]); + }); + const minLon = Math.min(...lons); + const minLat = Math.min(...lats); + const maxLon = Math.max(...lons); + const maxLat = Math.max(...lats); + const extent = new Extent(minLon, minLat, maxLon, maxLat); + return extent; + } +}; \ No newline at end of file diff --git a/src/core/world/Globe.ts b/src/core/world/Globe.ts new file mode 100644 index 0000000..cda6a90 --- /dev/null +++ b/src/core/world/Globe.ts @@ -0,0 +1,389 @@ +import Kernel from './Kernel'; +import Utils from './Utils'; +import Renderer from './Renderer'; +import Camera, { CameraCore } from './Camera'; +import Scene from './Scene'; +import ImageUtils from './Image'; +import EventHandler from './EventHandler'; +import TiledLayer from './layers/TiledLayer'; +import { GoogleTiledLayer } from './layers/Google'; +import { AutonaviTiledLayer, AutonaviLabelLayer } from './layers/Autonavi'; +import LabelLayer from './layers/LabelLayer'; +import TrafficLayer from './layers/TrafficLayer'; +// import { QihuTrafficLayer } from './layers/Qihu'; +import Atmosphere from './graphics/Atmosphere'; +import LocationGraphic from './graphics/LocationGraphic'; +import PoiLayer from './layers/PoiLayer'; +import RouteLayer from './layers/RouteLayer'; +import Extent from './Extent'; +import Service,{Location} from './Service'; +import {WebGLRenderingContextExtension} from './Definitions.d'; + +const initLevel:number = Utils.isMobile() ? 11 : 3; + +const initLonlat:number[] = [116.3975, 39.9085]; + +type RenderCallback = () => void; + +export class GlobeOptions{ + pauseRendering: boolean = false; + satellite: boolean = true; + level: number | 'auto' = 'auto'; + lonlat: number[] | 'auto' = 'auto'; + key: string = ""; +} + +export default class Globe { + renderer: Renderer = null; + scene: Scene = null; + camera: Camera = null; + tiledLayer: TiledLayer = null; + labelLayer: LabelLayer = null; + trafficLayer: TrafficLayer = null; + poiLayer: PoiLayer = null; + routeLayer: RouteLayer = null; + locationGraphic: LocationGraphic = null; + debugStopRefreshTiles: boolean = false; + private readonly REFRESH_INTERVAL: number = 150; //Globe自动刷新时间间隔,以毫秒为单位 + private lastRefreshTimestamp: number = -1; + private lastRefreshCameraCore: CameraCore = null; + private eventHandler: EventHandler = null; + private allRefreshCount: number = 0; + private realRefreshCount: number = 0; + // private beforeRenderCallbacks: RenderCallback[] = []; + private afterRenderCallbacks: RenderCallback[] = []; + public gl: WebGLRenderingContextExtension = null; + private static globe: Globe = null; + + static getInstance(options?: GlobeOptions){ + if(!this.globe){ + const canvas = document.createElement("canvas"); + canvas.width = document.documentElement.clientWidth; + canvas.height = document.documentElement.clientHeight; + this.globe = new Globe(canvas, options); + } + return this.globe; + } + + private constructor(public canvas: HTMLCanvasElement, private options?: GlobeOptions) { + if(!this.options){ + this.options = new GlobeOptions(); + } + this.renderer = new Renderer(canvas, this._onBeforeRender.bind(this), this._onAfterRender.bind(this)); + this.gl = this.renderer.gl; + this.scene = new Scene(); + 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.renderer.setScene(this.scene); + this.renderer.setCamera(this.camera); + + if(this.options.satellite){ + //not display well for level 10,11 when style is Default + this._setTiledLayer(new GoogleTiledLayer("Default"), this.options.pauseRendering);//"Default" | "Satellite" | "Road" | "RoadOnly" | "Terrain" | "TerrainOnly"; + // this.labelLayer = new AutonaviLabelLayer(); + // this.labelLayer = new SosoLabelLayer(); + // this.scene.add(this.labelLayer); + }else{ + this._setTiledLayer(new AutonaviTiledLayer(), this.options.pauseRendering); + } + + // this.trafficLayer = new QihuTrafficLayer(); + // this.trafficLayer.visible = false; + // this.scene.add(this.trafficLayer); + var atmosphere = Atmosphere.getInstance(); + this.scene.add(atmosphere); + this.routeLayer = RouteLayer.getInstance(this.camera, this.options.key); + this.scene.add(this.routeLayer); + this.poiLayer = PoiLayer.getInstance(); + this.poiLayer.globe = this; + this.scene.add(this.poiLayer); + this.locationGraphic = LocationGraphic.getInstance(this); + this.scene.add(this.locationGraphic); + + this.eventHandler = new EventHandler(this); + + if(this.options.pauseRendering !== true){ + this.renderer.resumeRendering(); + } + + const locationCallback = (location: any) => { + if(location){ + this.afterRenderCallbacks.push(() => { + this.updateUserLocation(location); + }); + } + }; + + Service.getCurrentPosition(false).then(locationCallback).then(() => { + if(Utils.isMobile()){ + Service.getCurrentPosition(true).then(locationCallback); + } + }); + } + + placeAt(container: HTMLElement){ + if(this.canvas.parentNode){ + if(this.canvas.parentNode !== container){ + container.appendChild(this.canvas); + } + }else{ + container.appendChild(this.canvas); + } + } + + public resize(width: number, height: number){ + this.canvas.width = width; + this.canvas.height = height; + this.camera.setAspect(this.canvas.width / this.canvas.height); + Utils.publish("extent-change"); + } + + private updateUserLocation(location: Location) { + this.locationGraphic.setLonLat(location.lon, location.lat); + + let [lon, lat] = this.camera.getLonlat(); + + if(this.options.lonlat === 'auto'){ + lon = location.lon; + lat = location.lat; + } + + let level = this.getLevel(); + + if(this.options.level === 'auto'){ + level = 8; + if (location.accuracy <= 100) { + level = 16; + } else if (location.accuracy <= 1000) { + level = 13; + } else { + level = 11; + } + } + + this.centerTo(lon, lat, level); + } + + getLonlat(){ + return this.camera.getLonlat(); + } + + isRenderingPaused(){ + return this.renderer.isRenderingPaused(); + } + + pauseRendering(){ + this.renderer.pauseRendering(); + } + + resumeRendering(){ + this.renderer.resumeRendering(); + this.refresh(true); + } + + private _setTiledLayer(tiledLayer: TiledLayer, dontRefresh: boolean = false) { + //在更换切片图层的类型时清空缓存的图片 + ImageUtils.clear(); + if (this.tiledLayer) { + var b = this.scene.remove(this.tiledLayer); + if (!b) { + console.error("this.scene.remove(this.tiledLayer)失败"); + } + this.scene.tiledLayer = null; + } + tiledLayer.globe = this; + this.tiledLayer = tiledLayer; + this.scene.add(this.tiledLayer, true); + if(!dontRefresh){ + this.refresh(true); + } + } + + showLabelLayer() { + if (this.labelLayer) { + this.labelLayer.visible = true; + } + } + + hideLabelLayer() { + if (this.labelLayer) { + this.labelLayer.visible = false; + } + } + + showTrafficLayer() { + if (this.trafficLayer) { + this.trafficLayer.visible = true; + } + } + + hideTrafficLayer() { + if (this.trafficLayer) { + this.trafficLayer.visible = false; + } + } + + getLevel() { + return this.camera.getLevel(); + } + + zoomIn() { + this.setLevel(this.getLevel() + 1); + } + + setLevel(level: number) { + if (this.camera) { + this.camera.setLevel(level); + } + } + + centerTo(lon: number, lat: number, level:number = this.getLevel()){ + return this.camera.centerTo(lon, lat, level); + } + + animateTo(newLon: number, newLat: number, newLevel: number = this.getLevel(), duration: number = 1000){ + return this.camera.animateTo(newLon, newLat, newLevel, duration); + } + + setExtent(extent: Extent){ + return this.camera.setExtent(extent); + } + + animateToExtent(extent: Extent, duration: number = 1000){ + return this.camera.animateToExtent(extent, duration); + } + + isAnimating(): boolean { + return this.camera.isAnimating(); + } + + animateToLevel(level: number, cb?: () => void) { + if (!this.isAnimating()) { + if (level < Kernel.MIN_LEVEL) { + level = Kernel.MIN_LEVEL; + } + if (level > Kernel.MAX_LEVEL) { + level = Kernel.MAX_LEVEL; + } + if (level !== this.getLevel()) { + this.camera.animateToLevel(level, cb); + } + } + } + + animateOut(cb?: () => void) { + this.animateToLevel(this.getLevel() - 1, cb); + } + + animateIn(cb?: () => void) { + this.animateToLevel(this.getLevel() + 1, cb); + } + + private _onBeforeRender(renderer: Renderer) { + // this.beforeRenderCallbacks.forEach((callback) => callback()); + this.refresh(); + } + + private _onAfterRender(render: Renderer) { + this.afterRenderCallbacks.forEach((callback) => callback()); + this.afterRenderCallbacks = []; + } + + logRefreshInfo() { + console.log(this.realRefreshCount, this.allRefreshCount, this.realRefreshCount / this.allRefreshCount); + } + + refresh(force: boolean = false) { + this.allRefreshCount++; + var timestamp = Date.now(); + + //先更新camera中的各种矩阵 + this.camera.update(force); + + if (!this.tiledLayer || !this.scene || !this.camera) { + return; + } + + if (this.debugStopRefreshTiles) { + return; + } + + var newCameraCore = this.camera.getCameraCore(); + // var isNeedRefresh = force || !newCameraCore.equals(this.cameraCore); + var isNeedRefresh = false; + if (force) { + isNeedRefresh = true; + } else { + if(this.isRenderingPaused()){ + //when rendering paused, we don't need to refresh + isNeedRefresh = false; + }else{ + if (newCameraCore.equals(this.lastRefreshCameraCore)) { + isNeedRefresh = false; + } else { + isNeedRefresh = timestamp - this.lastRefreshTimestamp >= this.REFRESH_INTERVAL; + } + } + } + + this.tiledLayer.updateSubLayerCount(); + + if (isNeedRefresh) { + this.realRefreshCount++; + this.lastRefreshTimestamp = timestamp; + this.lastRefreshCameraCore = newCameraCore; + this.tiledLayer.refresh(); + } + + this.tiledLayer.updateTileVisibility(); + + if(!this.isRenderingPaused()){ + var a = !!(this.labelLayer && this.labelLayer.visible); + var b = !!(this.trafficLayer && this.trafficLayer.visible); + if (a || b) { + var lastLevelTileGrids = this.tiledLayer.getLastLevelVisibleTileGrids(); + if (a) { + this.labelLayer.updateTiles(this.getLevel(), lastLevelTileGrids); + } + if (b) { + this.trafficLayer.updateTiles(this.getLevel(), lastLevelTileGrids); + } + } + } + } + + getExtent(){ + const extents:Extent[] = []; + //layerExtent is null when rendering paused + var layerExtent = this.tiledLayer.getExtent(); + if(layerExtent){ + extents.push(layerExtent); + } + + var cameraExtent = this.camera.getExtent(); + if(cameraExtent){ + extents.push(cameraExtent); + } + + if(extents.length === 0){ + return null; + }else if(extents.length === 1){ + return extents[0]; + }else{ + return Extent.intersect(extents); + } + } + + test(){ + this.debugStopRefreshTiles = true; + this.labelLayer.hideAllTiles(); + this.tiledLayer.children.forEach((subLayer) => subLayer.hideAllTiles()); + var subLayer = this.tiledLayer.children[this.tiledLayer.children.length-1]; + subLayer.visible = true; + subLayer.children[0].visible = true; + return subLayer; + } + +}; \ No newline at end of file diff --git a/src/world/GraphicGroup.ts b/src/core/world/GraphicGroup.ts old mode 100755 new mode 100644 similarity index 91% rename from src/world/GraphicGroup.ts rename to src/core/world/GraphicGroup.ts index 85c94d8..9b6ebb5 --- a/src/world/GraphicGroup.ts +++ b/src/core/world/GraphicGroup.ts @@ -1,9 +1,8 @@ -import Kernel = require('./Kernel'); +import Kernel from './Kernel'; import {Drawable} from './Definitions.d'; -import Graphic = require('./graphics/Graphic'); import Camera from "./Camera"; -class GraphicGroup implements Drawable { +export default class GraphicGroup implements Drawable { id: number; parent: GraphicGroup; children: T[]; @@ -80,6 +79,4 @@ class GraphicGroup implements Drawable { } }); } -} - -export = GraphicGroup; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/Image.ts b/src/core/world/Image.ts old mode 100755 new mode 100644 similarity index 96% rename from src/world/Image.ts rename to src/core/world/Image.ts index 7f43443..ab09cb0 --- a/src/world/Image.ts +++ b/src/core/world/Image.ts @@ -34,4 +34,4 @@ const ImageUtils = { } }; -export = ImageUtils; \ No newline at end of file +export default ImageUtils; \ No newline at end of file diff --git a/src/core/world/Kernel.ts b/src/core/world/Kernel.ts new file mode 100644 index 0000000..85b385d --- /dev/null +++ b/src/core/world/Kernel.ts @@ -0,0 +1,25 @@ +import {WebGLRenderingContextExtension} from './Definitions'; + +const REAL_EARTH_RADIUS = 6378137;//meters +const EARTH_RADIUS = 500; +const SCALE_FACTOR = EARTH_RADIUS / REAL_EARTH_RADIUS; +const MAX_PROJECTED_COORD = Math.PI * EARTH_RADIUS; +const MAX_REAL_RESOLUTION = 156543.03392800014; +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 SCALE_FACTOR: number = SCALE_FACTOR; + static readonly REAL_EARTH_RADIUS: number = REAL_EARTH_RADIUS; + static readonly EARTH_RADIUS: number = EARTH_RADIUS; + static readonly MAX_RESOLUTION: number = MAX_RESOLUTION; + static readonly MAX_REAL_RESOLUTION: number = MAX_REAL_RESOLUTION; + static readonly MAX_PROJECTED_COORD: number = MAX_PROJECTED_COORD; + static readonly BASE_LEVEL: number = 6; //渲染的基准层级,从该层级开始segment为1 + static readonly MAX_LEVEL: number = 18; + static readonly MIN_LEVEL: number = 2; + static readonly MIN_PITCH_LEVEL: number = 8; + static readonly proxy: string = ""; +}; \ No newline at end of file diff --git a/src/core/world/Locator.ts b/src/core/world/Locator.ts new file mode 100644 index 0000000..030a24c --- /dev/null +++ b/src/core/world/Locator.ts @@ -0,0 +1,58 @@ +import Utils from './Utils'; + +//http://lbs.qq.com/tool/component-geolocation.html + +export class LocationData { + module: string;//geolocation + type: string;//h5,ip + adcode: string;//110105 + nation: string;//中国 + province: string;//北京市 + city: string;//北京市 + district: string;//朝阳区 + addr: string;//朝阳区崔各庄乡顺白路何各庄村公交站西南 + lat: number; + lng: number; + accuracy: number;//25 +} + +const targetOrigin: string = 'https://apis.map.qq.com'; + +var iframe = document.createElement("iframe"); + +class Locator { + public static getLocation() { + iframe.contentWindow.postMessage('getLocation', targetOrigin); + } + + public static getRobustLocation() { + iframe.contentWindow.postMessage('getLocation.robust', targetOrigin); + } + + public static watchPosition() { + iframe.contentWindow.postMessage('watchPosition', targetOrigin); + } + + public static clearWatch() { + iframe.contentWindow.postMessage('clearWatch', targetOrigin); + } +} + +(function () { + window.addEventListener('message', function (event) { + var data: LocationData = event.data; + if (data && data.module === 'geolocation') { + Utils.publish('location', event.data); + } + }, false); + + iframe.setAttribute("width", "0"); + iframe.setAttribute("height", "0"); + iframe.setAttribute("frameborder", "0"); + iframe.setAttribute("scrolling", "no"); + iframe.style.display = "none"; + iframe.setAttribute("src", `${targetOrigin}/tools/geolocation?key=YLZBZ-XDPKU-LWMV6-2WNPB-PL5W5-H6BGL&referer=WebGlobe`); + document.body.appendChild(iframe); +})(); + +export default Locator; \ No newline at end of file diff --git a/src/world/Object3D.ts b/src/core/world/Object3D.ts old mode 100755 new mode 100644 similarity index 92% rename from src/world/Object3D.ts rename to src/core/world/Object3D.ts index fdbc463..a223eb5 --- a/src/world/Object3D.ts +++ b/src/core/world/Object3D.ts @@ -1,9 +1,8 @@ -import Kernel = require('./Kernel'); -import Matrix = require('./math/Matrix'); -import Vertice = require('./math/Vertice'); -import Vector = require('./math/Vector'); +import Matrix from './math/Matrix'; +import Vertice from './math/Vertice'; +import Vector from './math/Vector'; -class Object3D { +export default class Object3D { protected matrix: Matrix; constructor() { @@ -116,6 +115,4 @@ class Object3D { directionZ.normalize(); return directionZ; } -} - -export = Object3D; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/Program.ts b/src/core/world/Program.ts old mode 100755 new mode 100644 similarity index 97% rename from src/world/Program.ts rename to src/core/world/Program.ts index c6fd193..8204eb3 --- a/src/world/Program.ts +++ b/src/core/world/Program.ts @@ -1,7 +1,6 @@ -import Kernel = require("./Kernel"); -import Graphic = require("./graphics/Graphic"); +import Kernel from './Kernel'; -class Program{ +export default class Program{ ready: boolean = false; activeInfosObject: any; program: WebGLProgram; @@ -183,6 +182,4 @@ class Program{ return shader; } -} - -export = Program; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/core/world/Renderer.ts b/src/core/world/Renderer.ts new file mode 100644 index 0000000..484dd7c --- /dev/null +++ b/src/core/world/Renderer.ts @@ -0,0 +1,130 @@ +import Kernel from './Kernel'; +import Scene from './Scene'; +import Camera from './Camera'; +import { WebGLRenderingContextExtension } from "./Definitions"; + +export default class Renderer { + scene: Scene = null; + camera: Camera = null; + renderingPaused: boolean = true; + gl: WebGLRenderingContextExtension = null; + + constructor( + private canvas: HTMLCanvasElement, + private onBeforeRender?: (renderer: Renderer) => void, + private onAfterRender?: (renderer: Renderer) => void) { + + this.gl = this._getWebGLContext(this.canvas); + + Kernel.gl = this.gl; + + if(!this.gl){ + console.debug("浏览器不支持WebGL或将WebGL禁用!"); + } + + const gl = this.gl; + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.clearColor(0, 0, 0, 1); + + gl.enable(gl.DEPTH_TEST); + gl.depthFunc(gl.LEQUAL); + gl.depthMask(true);//允许写入深度 + + gl.enable(gl.CULL_FACE); //一定要启用裁剪,否则显示不出立体感 + gl.frontFace(gl.CCW);//指定逆时针方向为正面 + gl.cullFace(gl.BACK); //裁剪掉背面 + + //gl.enable(gl.TEXTURE_2D);//WebGL: INVALID_ENUM: enable: invalid capability + } + + private _getWebGLContext(canvas: HTMLCanvasElement) { + var gl: WebGLRenderingContextExtension = null; + + try { + var contextList: string[] = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"]; + for (var i = 0; i < contextList.length; i++) { + gl = canvas.getContext(contextList[i], { + antialias: true + }) as WebGLRenderingContextExtension; + if (gl) { + break; + } + } + } catch (e) { + console.error(e); + } + return gl; + } + + render(scene: Scene, camera: Camera) { + var gl = this.gl; + gl.viewport(0, 0, this.canvas.width, this.canvas.height); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.clearColor(0, 0, 0, 1); + // gl.enable(gl.DEPTH_TEST); + // gl.depthFunc(gl.LEQUAL); + // gl.depthMask(true); + + //如果refresh方法出现异常而且没有捕捉,那么就会导致无法继续设置setTimeout,从而无法进一步更新切片 + + try{ + camera.update(); + }catch(e){ + console.error(e); + } + + try{ + if (this.onBeforeRender) { + this.onBeforeRender(this); + } + }catch(e){ + console.error(e); + } + + try{ + if(!this.renderingPaused){ + scene.draw(camera); + } + }catch(e){ + console.error(e); + } + + try{ + if (this.onAfterRender) { + this.onAfterRender(this); + } + }catch(e){ + console.error(e); + } + } + + setScene(scene: Scene) { + this.scene = scene; + } + + setCamera(camera: Camera) { + this.camera = camera; + } + + private _tick() { + if (this.scene && this.camera) { + this.render(this.scene, this.camera); + } + + window.requestAnimationFrame(this._tick.bind(this)); + } + + isRenderingPaused(){ + return this.renderingPaused; + } + + pauseRendering(){ + this.renderingPaused = true; + } + + resumeRendering(){ + this.renderingPaused = false; + this._tick(); + } +}; \ No newline at end of file diff --git a/src/core/world/Scene.ts b/src/core/world/Scene.ts new file mode 100644 index 0000000..c0d570f --- /dev/null +++ b/src/core/world/Scene.ts @@ -0,0 +1,7 @@ +import {Drawable} from './Definitions.d'; +import GraphicGroup from './GraphicGroup'; +import TiledLayer from './layers/TiledLayer'; + +export default class Scene extends GraphicGroup{ + tiledLayer: TiledLayer; +}; \ No newline at end of file diff --git a/src/core/world/Service.ts b/src/core/world/Service.ts new file mode 100644 index 0000000..f906ee8 --- /dev/null +++ b/src/core/world/Service.ts @@ -0,0 +1,568 @@ +import Extent from './Extent'; +import MathUtils from './math/Utils'; + +export interface Location { + lon: number; + lat: number; + accuracy: number; + city: ""//北京市 +} + +type RouteType = "bus" | "snsnav"; + +//Type: 表示按类型查询,qt=rn +//POI: 表示按兴趣点的名称查询,qt=poi +//Auto: 通过检索poiTypes自动判断类型 +type StrictSearchType = 'Type' | 'POI'; +export type SearchType = StrictSearchType | 'Auto'; + +//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 { + + private static jsonp(url: string, callback: (response: any) => void, charset: string = "", callbackParameterName: string = "cb", callbackPrefix: string = "QQ"): () => void { + //callback名称要以大写的QQ开头,否则容易挂掉 + var callbackName = `${callbackPrefix}_webglobe_callback_` + Math.random().toString().substring(2); + if (url.indexOf('?') < 0) { + url += '?'; + } else { + url += '&'; + } + url += `${callbackParameterName}=window.${callbackName}`; + var scriptElement = document.createElement("script"); + scriptElement.setAttribute("src", url); + if (charset) { + scriptElement.setAttribute("charset", charset);//UTF-8,GBK + } + scriptElement.setAttribute("async", "true"); + scriptElement.setAttribute("defer", "true"); + document.body.appendChild(scriptElement); + var canceled = false; + + function clear() { + delete (window)[callbackName]; + scriptElement.src = ""; + if (scriptElement.parentNode) { + scriptElement.parentNode.removeChild(scriptElement); + } + scriptElement = null; + } + + (window)[callbackName] = function (response: any) { + if (!canceled) { + callback(response); + } + clear(); + } + + return function () { + canceled = true; + clear(); + }; + } + + //--------------------------------------------------location------------------------------------------------------- + + private static cityLocation: Location = null; + + private static location: Location = null; + + private static getCityLocation() { + const promise = new Promise((resolve) => { + if (this.cityLocation) { + resolve(this.cityLocation); + } else { + const url = "//apis.map.qq.com/jsapi?qt=gc&output=jsonp"; + Service.jsonp(url, (response: any) => { + console.log(`定位:`, response); + const detail = response.detail; + if (response.detail) { + this.cityLocation = { + lon: parseFloat(detail.pointx), + lat: parseFloat(detail.pointy), + accuracy: Infinity, + city: detail.cname + } as Location; + resolve(this.cityLocation); + } else { + resolve(null); + } + }); + } + }); + return promise; + } + + static getCurrentPosition(highAccuracy: boolean = false) { + const promise = new Promise((resolve) => { + this.getCityLocation().then((cityLocation: Location) => { + if (highAccuracy) { + navigator.geolocation.getCurrentPosition((response: Position) => { + const location = { + lon: response.coords.longitude, + lat: response.coords.latitude, + accuracy: response.coords.accuracy, + city: cityLocation.city + } as Location; + this.location = location; + resolve(this.location); + }, (err) => { + console.error(err); + if (this.location) { + resolve(this.location); + } else { + resolve(cityLocation); + } + }, { + enableHighAccuracy: true + }); + } else { + if (this.location) { + resolve(this.location); + } else { + resolve(cityLocation); + } + } + }); + }); + return promise; + } + + //--------------------------------------------------search------------------------------------------------------- + //http://lbs.qq.com/webservice_v1/guide-appendix.html + private static poiClasses: any = { + '美食': [ + '西餐', '烧烤', '火锅', '海鲜', '素食', '清真', '自助餐', '面包甜点', '冷饮店', '小吃快餐', { + '中餐厅': ['鲁菜', '粤菜', '湘菜', '川菜', '浙江菜', '安徽菜', '东北菜', '北京菜'], + '日韩菜': ['日本料理', '韩国料理'], + '东南亚菜': ['泰国菜'] + }, '美食畅饮', '甜点饮品'//美食畅饮,甜点饮品 + ], + '购物': ['综合商场', '便利店', '超市', '数码家电', '花鸟鱼虫', '家具家居建材', '农贸市场', '小商品市场', '旧货市场', '体育户外', '服饰鞋包', '图书音像', '眼镜店', '母婴儿童', '珠宝饰品', '化妆品', '礼品', '摄影器材', '拍卖典当行', '古玩字画', '自行车专卖', '烟酒专卖', '文化用品'], + '生活服务': ['旅行社', '报刊亭', '自来水营业厅', '电力营业厅', '摄影冲印', '洗衣店', '招聘求职', '彩票', '中介机构', '宠物服务', '废品收购站', '福利院养老院', '美容美发', { + '票务代售': ['飞机票代售', '火车票代售', '汽车票代售', '公交及IC卡'], + '邮局速递': ['邮局', '速递'], + '通讯服务': ['中国电信营业厅', '中国网通营业厅', '中国移动营业厅', '中国联通营业厅', '中国铁通营业厅'], + '家政': ['月嫂保姆', '保洁钟点工', '开锁', '送水', '家电维修', '搬家'] + }], + '娱乐休闲': ['洗浴推拿足疗', 'KTV', '酒吧', '咖啡厅', '夜总会', '电影院', '剧场音乐厅', '度假疗养', '网吧', { + '户外活动': ['游乐场', '垂钓园', '采摘园'], + '游戏棋牌': ['游戏厅', '棋牌室'] + }], + '汽车': ['停车场', '汽车销售', '汽车维修', '汽车养护', '洗车场', { + '加油站': ['中石化', '中石油', '其它加油加气站'], + '摩托车': ['摩托车服务相关', '销售', '维修', '其它摩托车'] + }], + '医疗保健': ['综合医院', '诊所', '急救中心', '药房药店'], + '酒店宾馆': ['酒店宾馆', '星级酒店', '经济型酒店', '旅馆招待所', '青年旅社', '快捷酒店'],//'快捷酒店' + '旅游景点': [], + '文化场馆': ['博物馆', '展览馆', '科技馆', '图书馆', '美术馆', '会展中心'], + '教育学校': ['大学', '中学', '小学', '幼儿园', '培训', '职业技术学校', '成人教育'], + '银行金融': ['银行', '自动提款机', '保险公司', '证券公司', 'ATM'],//ATM + '基础设施': ['其它基础设施',{ + '交通设施': ['公交车站', '地铁站', '火车站', '长途汽车站', '公交线路', '地铁线路'], + '公共设施': ['公共厕所', '公用电话', '紧急避难场所'], + '道路附属': ['收费站', '服务区'] + }], + '房产小区': ['商务楼宇', { + '住宅区': ['住宅小区', '别墅' ,'宿舍', '社区中心'] + }] + }; + + private static poiTypes: string[] = []; + + private static createPoiTypes(){ + this._createPoiTypes(this.poiClasses, this.poiTypes); + return this.poiTypes; + } + + public static _createPoiTypes(obj: any, types: string[]){ + for(let key in obj){ + if(obj.hasOwnProperty(key)){ + types.push(key); + let values = obj[key]; + if(values && values.length > 0){ + values.forEach((value: any) => { + if(typeof value === 'string'){ + types.push(value); + }else if(typeof value === 'object'){ + this._createPoiTypes(value, types); + } + }); + } + } + } + } + + private static _getStrictSearchType(keyword: string): StrictSearchType{ + let searchType: StrictSearchType = 'POI'; + for(let i = 0; i < this.poiTypes.length; i++){ + let poiType = this.poiTypes[i]; + if(poiType.indexOf(keyword) >= 0){ + searchType = 'Type'; + return searchType; + } + //keyword: 洗浴足疗 => poiType: 洗浴推拿足疗 + //keyword: 公交站 => poiType: 公交车站 + let looselyContain: boolean = true; + for(let j = 0; j < keyword.length; j++){ + let char = keyword.charAt(j); + if(poiType.indexOf(char) < 0){ + looselyContain = false; + break; + } + } + if(looselyContain){ + searchType = 'Type'; + return searchType; + } + } + return searchType; + } + + private static _getUrlBySearchType(url: string, searchType: StrictSearchType){ + if(searchType === 'Type'){ + url += '&qt=rn'; + }else if(searchType === 'POI'){ + url += '&qt=poi'; + } + return url; + } + + //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 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; + } + + static searchByBuffer(keyword: string, lon: number, lat: number, radius: number, searchType: SearchType = 'Auto', pageCapacity: number = 50, pageIndex: number = 0) { + if(searchType === 'Auto'){ + searchType = this._getStrictSearchType(keyword); + return this._rawSearchByBuffer(searchType, keyword, lon, lat, radius, pageCapacity, pageIndex).then((response: any) => { + let poiCount = 0; + if(response && response.detail && response.detail.pois && response.detail.pois.length > 0){ + poiCount = response.detail.pois.length; + } + if(poiCount === 0){ + if(searchType === 'Type'){ + return this._rawSearchByBuffer('POI', keyword, lon, lat, radius, pageCapacity, pageIndex); + }else if(searchType === 'POI'){ + return this._rawSearchByBuffer('Type', keyword, lon, lat, radius, pageCapacity, pageIndex); + } + } + return response; + }); + }else{ + return this._rawSearchByBuffer(searchType, keyword, lon, lat, radius, pageCapacity, pageIndex); + } + } + + private static _rawSearchByBuffer(searchType: StrictSearchType, keyword: string, lon: number, lat: number, radius: number, pageCapacity: number = 50, pageIndex: number = 0) { + const promise = new Promise((resolve) => { + //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`; + url = this._getUrlBySearchType(url, searchType); + Service.jsonp(url, function (response: any) { + if(response){ + response.location = [lon, lat]; + } + resolve(response); + }); + }); + return promise; + } + + static searchByCity(keyword: string, city: string, searchType: SearchType = 'Auto', pageCapacity: number = 50, pageIndex: number = 0) { + if(searchType === 'Auto'){ + searchType = this._getStrictSearchType(keyword); + return this._rawSearchByCity(searchType, keyword, city, pageCapacity, pageIndex).then((response: any) => { + let poiCount = 0; + if(response && response.detail && response.detail.pois && response.detail.pois.length > 0){ + poiCount = response.detail.pois.length; + } + if(poiCount === 0){ + if(searchType === 'Type'){ + return this._rawSearchByCity('POI', keyword, city, pageCapacity, pageIndex); + }else if(searchType === 'POI'){ + return this._rawSearchByCity('Type', keyword, city, pageCapacity, pageIndex); + } + } + return response; + }); + }else{ + return this._rawSearchByCity(searchType, keyword, city, pageCapacity, pageIndex); + } + } + + 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) => { + 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) => { + if(response){ + if(this.location){ + response.location = [this.location.lon, this.location.lat]; + }else if(this.cityLocation){ + response.location = [this.cityLocation.lon, this.cityLocation.lat]; + }else{ + response.location = null; + } + } + resolve(response); + }); + }); + return promise; + } + + static searchNearby(keyword: string, radius: number, searchType: SearchType = 'Auto', highAccuracy: boolean = false, pageCapacity: number = 50, pageIndex: number = 0) { + return this.getCurrentPosition(highAccuracy).then((location: Location) => { + return this.searchByBuffer(keyword, location.lon, location.lat, radius, searchType, pageCapacity, pageIndex); + }); + } + + static searchByCurrentCity(keyword: string, searchType: SearchType = 'Auto', pageCapacity: number = 50, pageIndex: number = 0) { + return this.getCityLocation().then((cityLocation: Location) => { + return this.searchByCity(keyword, cityLocation.city, searchType, pageCapacity, pageIndex); + }); + } + + //--------------------------------------------------routing------------------------------------------------------- + + 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 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+ + // xhr.responseType = 'json'; + xhr.open("GET", url, true); + xhr.onload = (event: any) => { + // const response = event.target.response; + const response = this._handleDrivingResult(event.target.responseText); + resolve(response); + }; + xhr.onerror = (err: any) => { + reject(err); + }; + xhr.onabort = (err: any) => { + reject(err); + }; + xhr.send(); + }); + return promise; + } + + private static _handleDrivingResult(responseText: string) { + const response: any = JSON.parse(responseText); + if (response.route) { + response.route.type = 'driving'; + if (response.route.paths && response.route.paths.length > 0) { + response.route.paths.forEach((path: any) => { + if (path.steps) { + path.steps.forEach((step: any) => this._parseStepPolyline(step)); + } + }); + } + } + return response; + } + + 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) => { + //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(); + xhr.open("GET", url, true); + xhr.onload = (event: any) => { + const response = this._handleBusResult(event.target.responseText); + resolve(response); + }; + xhr.onerror = (err: any) => { + reject(err); + }; + xhr.onabort = (err: any) => { + reject(err); + }; + xhr.send(); + }); + return promise; + } + + private static _handleBusResult(responseText: string) { + const response = JSON.parse(responseText); + if (response.route) { + response.route.type = 'bus'; + if (response.route.transits && response.route.transits.length > 0) { + response.route.transits.forEach((transit: any) => { + let walking_distance:number = 0; + transit.segments.forEach((segment: any) => { + if (segment.walking && segment.walking.steps && segment.walking.steps.length > 0) { + segment.walking.lonlats = []; + segment.walking.steps.forEach((step: any) => { + this._parseStepPolyline(step); + segment.walking.lonlats.push(...step.lonlats); + if(step.distance){ + const stepDistance = parseFloat(step.distance); + if(!isNaN(stepDistance)){ + walking_distance += stepDistance; + } + } + }); + segment.walking.firstLonlat = segment.walking.lonlats[0]; + segment.walking.lastLonlat = segment.walking.lonlats[segment.walking.lonlats.length - 1]; + } + if (segment.bus && segment.bus.buslines && segment.bus.buslines.length > 0) { + segment.bus.lonlats = []; + segment.bus.buslines.forEach((step: any) => { + this._parseStepPolyline(step); + segment.bus.lonlats.push(...step.lonlats); + step.busName = step.name; + const idx = step.name.indexOf("("); + if (idx >= 0) { + step.busName = step.name.slice(0, idx); + } + }); + if (segment.bus.lonlats.length > 0) { + segment.bus.firstLonlat = segment.bus.lonlats[0]; + segment.bus.lastLonlat = segment.bus.lonlats[segment.bus.lonlats.length - 1]; + } + } + if(segment.railway && segment.railway.departure_stop && segment.railway.arrival_stop){ + let location1 = segment.railway.departure_stop.location; + let location2 = segment.railway.arrival_stop.location; + segment.railway.lonlats = []; + if(location1 && location2){ + let splits1 = location1.split(" "); + let lon1 = parseFloat(splits1[0]); + let lat1 = parseFloat(splits1[1]); + let splits2 = location2.split(" "); + let lon2 = parseFloat(splits2[0]); + let lat2 = parseFloat(splits2[1]); + if(!isNaN(lon1) && !isNaN(lat1) && !isNaN(lon2) && !isNaN(lat2)){ + segment.railway.lonlats = [[lon1, lat1], [lon2, lat2]]; + } + } + } + }); + const originalWalkingDistance = parseFloat(transit.walking_distance); + if(isNaN(originalWalkingDistance)){ + transit.walking_distance = walking_distance; + }else{ + transit.walking_distance = originalWalkingDistance; + } + }); + } + } + return response; + } + + static routeByWalking(fromLon: number, fromLat: number, toLon: number, toLat: number, key: string) { + const promise = new Promise((resolve, reject) => { + //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(); + xhr.open("GET", url, true); + xhr.onload = (event: any) => { + const response = this._handleWalkingResult(event.target.responseText); + resolve(response); + }; + xhr.onerror = (err: any) => { + reject(err); + }; + xhr.onabort = (err: any) => { + reject(err); + }; + xhr.send(); + }); + return promise; + } + + private static _handleWalkingResult(responseText: string) { + const response = JSON.parse(responseText); + if (response.route) { + response.route.type = 'walking'; + if (response.route.paths && response.route.paths.length > 0) { + response.route.paths.forEach((path: any) => { + if (path && path.steps && path.steps.length > 0) { + path.steps.forEach((step: any) => { + this._parseStepPolyline(step); + }); + } + }); + } + } + return response; + } + + private static _parseStepPolyline(step: any) { + //polyline: "117.002052,39.403416;116.998672,39.404453" + const strLonLats: string[] = step.polyline.split(";"); + const lonlats: number[][] = strLonLats.map((strLonlat: string) => { + const splits = strLonlat.split(","); + const lon = parseFloat(splits[0]); + const lat = parseFloat(splits[1]); + return [lon, lat]; + }); + step.firstLonlat = lonlats[0]; + step.lastLonlat = lonlats[lonlats.length - 1]; + step.lonlats = lonlats; + } + + static decodeQQPolyline(polyline: number[]) { + for (var i = 2; i < polyline.length; i++) { + polyline[i] = polyline[i - 2] + polyline[i] / 1000000; + } + return polyline; + } + + static qqRouteByDriving(fromLon: number, fromLat: number, toLon: number, toLat: number, key: string, policy?: string) { + //policy: LEAST_TIME,LEAST_FEE,REAL_TRAFFIC + //http://apis.map.qq.com/ws/direction/v1/driving/?from=39.915285,116.403857&to=39.915285,116.803857&waypoints=39.111,116.112;39.112,116.113&output=json&callback=cb&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77 + let url = `//apis.map.qq.com/ws/direction/v1/driving/?from=${fromLat},${fromLon}&to=${toLat},${toLon}&output=jsonp&key=${key}`; + if (policy) { + url += `&policy=${policy}`; + } + const promise = new Promise((resolve) => { + Service.jsonp(url, (response: any) => { + response.result.routes.forEach((route: any) => { + Service.decodeQQPolyline(route.polyline); + }); + resolve(response); + }); + }); + return promise; + } + + + static qqRoute(routeType: RouteType, fromLon: number, fromLat: number, toLon: number, toLat: number) { + //RouteType: bus,snsnav + //http://lbs.qq.com/guides/direction.html + //http://lbs.qq.com/javascript_v2/case-run.html#sample-directions-route + //http://apis.map.qq.com/jsapi?c=北京&qt=snsnav&start=1$$$$12947295.571620844, 4863618.782990928$$&dest=1$$$$12968287.08799973, 4863630.841508553$$&cond=3&key=TKUBZ-D24AF-GJ4JY-JDVM2-IBYKK-KEBCU&mt=2&s=2&fm=0&output=jsonp&pf=jsapi&ref=jsapi&cb=qq.maps._svcb3.driving_service_0 + const fromX: number = MathUtils.degreeLonToWebMercatorX(fromLon, true); + const fromY: number = MathUtils.degreeLatToWebMercatorY(fromLat, true); + 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) => { + Service.jsonp(url, (response: any) => { + console.log(response); + resolve(response); + }, "GBK"); + }); + return promise; + } + +}; + +(Service as any).createPoiTypes(); + +export default Service; \ No newline at end of file diff --git a/src/world/TileGrid.ts b/src/core/world/TileGrid.ts old mode 100755 new mode 100644 similarity index 97% rename from src/world/TileGrid.ts rename to src/core/world/TileGrid.ts index 5f729b3..3720a8e --- a/src/world/TileGrid.ts +++ b/src/core/world/TileGrid.ts @@ -1,6 +1,5 @@ -import Kernel = require('./Kernel'); -import Utils = require('./Utils'); -import MathUtils = require('./math/Utils'); +import Kernel from './Kernel'; +import MathUtils from './math/Utils'; export enum TileGridPosition{ LEFT_TOP, @@ -19,7 +18,7 @@ class TileGrid { private Egeo:any = null;//{minLon,minLat,maxLon,maxLat} - private maxSize: number = 0; + // private maxSize: number = 0; constructor(public level: number, public row: number, public column: number) { // this.maxSize = Math.pow(2, this.level); diff --git a/src/world/Utils.ts b/src/core/world/Utils.ts old mode 100755 new mode 100644 similarity index 75% rename from src/world/Utils.ts rename to src/core/world/Utils.ts index 5c7e04f..c23785a --- a/src/world/Utils.ts +++ b/src/core/world/Utils.ts @@ -1,13 +1,13 @@ -import Kernel = require('./Kernel'); +import Kernel from './Kernel'; type ArrayVoidCallback = (value: any, index: number, arr: Array) => void; type ArrayBooleanCallback = (value: any, index: number, arr: any[]) => boolean; type ArrayAnyCallback = (value: any, index: number, arr: any[]) => any; type TopicCallback = (data:any) => void; -const topic:{[key:string]:TopicCallback[]} = {}; +const topic:{[key:string]:TopicCallback[]} = {}; -class Utils { +export default class Utils { static isNumber(v: any): boolean { return typeof v === "number"; @@ -103,34 +103,6 @@ class Utils { return simplifyArray; } - static jsonp(url: string, callback: (response: any) => void, callbackParameterName: string = "cb"): () => void { - var callbackName = `webglobe_callback_` + Math.random().toString().substring(2); - if (url.indexOf('?') < 0) { - url += '?'; - } else { - url += '&'; - } - url += `${callbackParameterName}=window.${callbackName}`; - var scriptElement = document.createElement("script"); - scriptElement.setAttribute("src", url); - scriptElement.setAttribute("async", "true"); - document.body.appendChild(scriptElement); - var canceled = false; - (window)[callbackName] = function (response: any) { - if (!canceled) { - callback(response); - } - delete (window)[callbackName]; - scriptElement.src = ""; - if (scriptElement.parentNode) { - scriptElement.parentNode.removeChild(scriptElement); - } - } - return function () { - canceled = true; - }; - } - static isMobile(): boolean { return !!window.navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone|IEMobile|Opera Mini/i); } @@ -149,7 +121,7 @@ class Utils { topic[topicName].push(callback); } - static publish(topicName: string, data: any){ + static publish(topicName: string, data?: any){ var callbacks = topic[topicName]; if(callbacks && callbacks.length > 0){ callbacks.forEach(function(callback: TopicCallback){ @@ -157,6 +129,4 @@ class Utils { }); } } -}; - -export = Utils; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/VertexBufferObject.ts b/src/core/world/VertexBufferObject.ts old mode 100755 new mode 100644 similarity index 73% rename from src/world/VertexBufferObject.ts rename to src/core/world/VertexBufferObject.ts index 0e507a3..5882d74 --- a/src/world/VertexBufferObject.ts +++ b/src/core/world/VertexBufferObject.ts @@ -1,8 +1,8 @@ -import Kernel = require("./Kernel"); +import Kernel from './Kernel'; const maxBufferSize:number = 200; const buffers:WebGLBuffer[] = []; -class VertexBufferObject{ +export default class VertexBufferObject{ buffer: WebGLBuffer; constructor(public target: number){ @@ -31,10 +31,10 @@ class VertexBufferObject{ var gl = Kernel.gl; - if(this.target === gl.ARRAY_BUFFER){ - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), usage); - }else if(this.target === gl.ELEMENT_ARRAY_BUFFER){ - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(data), usage); + if(this.target === Kernel.gl.ARRAY_BUFFER){ + gl.bufferData(Kernel.gl.ARRAY_BUFFER, new Float32Array(data), usage); + }else if(this.target === Kernel.gl.ELEMENT_ARRAY_BUFFER){ + gl.bufferData(Kernel.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(data), usage); } } @@ -49,6 +49,4 @@ class VertexBufferObject{ } this.buffer = null; } -} - -export = VertexBufferObject; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/geometries/Atmosphere.ts b/src/core/world/geometries/Atmosphere.ts old mode 100755 new mode 100644 similarity index 84% rename from src/world/geometries/Atmosphere.ts rename to src/core/world/geometries/Atmosphere.ts index b87029c..3e7813b --- a/src/world/geometries/Atmosphere.ts +++ b/src/core/world/geometries/Atmosphere.ts @@ -1,11 +1,11 @@ -import Kernel = require("../Kernel"); -import MeshVertice = require("./MeshVertice"); -import Triangle = require("./Triangle"); -import Mesh = require("./Mesh"); -import Vertice = require("../math/Vertice"); -import Matrix = require("../math/Matrix"); +import Kernel from '../Kernel'; +import MeshVertice from './MeshVertice'; +import Triangle from './Triangle'; +import Mesh from './Mesh'; +import Vertice from '../math/Vertice'; +import Matrix from '../math/Matrix'; -class Atmosphere extends Mesh { +export default class Atmosphere extends Mesh { private readonly segment: number = 360; private readonly radius1: number = Kernel.EARTH_RADIUS * 0.99; private readonly radius2: number = Kernel.EARTH_RADIUS * 1.01; @@ -65,6 +65,4 @@ class Atmosphere extends Mesh { this.vertices.push(...meshVertices1, ...meshVertices2); } -} - -export = Atmosphere; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/geometries/Box.ts b/src/core/world/geometries/Box.ts old mode 100755 new mode 100644 similarity index 95% rename from src/world/geometries/Box.ts rename to src/core/world/geometries/Box.ts index 19063ee..4c207fd --- a/src/world/geometries/Box.ts +++ b/src/core/world/geometries/Box.ts @@ -1,8 +1,8 @@ -import Vertice = require("./MeshVertice"); -import Triangle = require("./Triangle"); -import Mesh = require("./Mesh"); +import Vertice from './MeshVertice'; +import Triangle from './Triangle'; +import Mesh from './Mesh'; -class Box extends Mesh { +export default class Box extends Mesh { constructor(public length: number, public width: number, public height: number) { super(); this.buildTriangles(); @@ -140,6 +140,4 @@ class Box extends Mesh { return result; } -} - -export = Box; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/core/world/geometries/Geometry.ts b/src/core/world/geometries/Geometry.ts new file mode 100644 index 0000000..495654e --- /dev/null +++ b/src/core/world/geometries/Geometry.ts @@ -0,0 +1,5 @@ +interface Geometry{ + destroy(): void +} + +export default Geometry; \ No newline at end of file diff --git a/src/world/geometries/Marker.ts b/src/core/world/geometries/Marker.ts old mode 100755 new mode 100644 similarity index 65% rename from src/world/geometries/Marker.ts rename to src/core/world/geometries/Marker.ts index 1366213..fea2f38 --- a/src/world/geometries/Marker.ts +++ b/src/core/world/geometries/Marker.ts @@ -1,8 +1,8 @@ -import Kernel = require("../Kernel"); -import Geometry = require('./Geometry'); -import VertexBufferObject = require("../VertexBufferObject"); +import Kernel from '../Kernel'; +import Geometry from './Geometry'; +import VertexBufferObject from '../VertexBufferObject'; -class Marker implements Geometry{ +export default class Marker implements Geometry{ vbo: VertexBufferObject; @@ -17,6 +17,4 @@ class Marker implements Geometry{ this.vbo.destroy(); this.vbo = null; } -} - -export = Marker; \ No newline at end of file +} \ No newline at end of file diff --git a/src/world/geometries/Mesh.ts b/src/core/world/geometries/Mesh.ts old mode 100755 new mode 100644 similarity index 77% rename from src/world/geometries/Mesh.ts rename to src/core/world/geometries/Mesh.ts index e0e90df..6b395f1 --- a/src/world/geometries/Mesh.ts +++ b/src/core/world/geometries/Mesh.ts @@ -1,17 +1,39 @@ -import Kernel = require("../Kernel"); -import Vertice = require("./MeshVertice"); -import Triangle = require("./Triangle"); -import Object3D = require("../Object3D"); -import VertexBufferObject = require("../VertexBufferObject"); - -class Mesh extends Object3D { - vertices: Vertice[]; - triangles: Triangle[]; - vbo: VertexBufferObject; - ibo: VertexBufferObject; - nbo: VertexBufferObject; - uvbo: VertexBufferObject; - cbo: VertexBufferObject; +import Kernel from '../Kernel'; +import Vertice from './MeshVertice'; +import Triangle from './Triangle'; +import Object3D from '../Object3D'; +import VertexBufferObject from '../VertexBufferObject'; + +export default class Mesh extends Object3D { + vertices: Vertice[] = null; + triangles: Triangle[] = null; + vbo: VertexBufferObject = null; + ibo: VertexBufferObject = null; + nbo: VertexBufferObject = null; + uvbo: VertexBufferObject = null; + cbo: VertexBufferObject = null; + + static buildPlane(vLeftTop: Vertice, vLeftBottom: Vertice, vRightTop: Vertice, vRightBottom: Vertice) { + /*对于一个面从外面向里面看的绘制顺序 + * 0 2 + * + * 1 3*/ + //0,1,2; 2,1,3 + + //triangles + + var tri0 = new Triangle(vLeftTop, vLeftBottom, vRightTop); + + var tri1 = new Triangle(vRightTop, vLeftBottom, vRightBottom); + + return [tri0, tri1]; + } + + constructor(){ + super(); + this.vertices = []; + this.triangles = []; + } //set vertices and triangles buildTriangles(){ @@ -148,6 +170,4 @@ class Mesh extends Object3D { this.vertices = []; this.triangles = []; } -} - -export = Mesh; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/geometries/MeshVertice.ts b/src/core/world/geometries/MeshVertice.ts old mode 100755 new mode 100644 similarity index 83% rename from src/world/geometries/MeshVertice.ts rename to src/core/world/geometries/MeshVertice.ts index b531e83..30d2dff --- a/src/world/geometries/MeshVertice.ts +++ b/src/core/world/geometries/MeshVertice.ts @@ -1,4 +1,4 @@ -class MeshVertice{ +export default class MeshVertice{ p:number[]; n:number[]; uv:number[]; @@ -13,6 +13,4 @@ this.n = args.n;//[x,y,z] this.c = args.c;//[r,g,b] } -} - -export = MeshVertice; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/core/world/geometries/TileGeometry.ts b/src/core/world/geometries/TileGeometry.ts new file mode 100644 index 0000000..105b5f1 --- /dev/null +++ b/src/core/world/geometries/TileGeometry.ts @@ -0,0 +1,9 @@ +import Vertice from './MeshVertice'; +import Triangle from './Triangle'; +import Mesh from './Mesh'; + +export default class TileGeometry extends Mesh { + constructor(public vertices: Vertice[], public triangles: Triangle[]) { + super(); + } +}; \ No newline at end of file diff --git a/src/world/geometries/Triangle.ts b/src/core/world/geometries/Triangle.ts old mode 100755 new mode 100644 similarity index 81% rename from src/world/geometries/Triangle.ts rename to src/core/world/geometries/Triangle.ts index 2b1d830..2eaa663 --- a/src/world/geometries/Triangle.ts +++ b/src/core/world/geometries/Triangle.ts @@ -1,7 +1,6 @@ -import Vertice = require("./MeshVertice"); - -class Triangle{ +import Vertice from './MeshVertice'; +export default class Triangle{ constructor(public v1: Vertice, public v2: Vertice, public v3: Vertice){} setColor(c: number[]){ @@ -11,6 +10,4 @@ class Triangle{ static assembleQuad(leftTop: Vertice, leftBottom: Vertice, rightTop: Vertice, rightBottom: Vertice): Triangle[]{ return [new Triangle(leftTop, leftBottom, rightTop), new Triangle(rightTop, leftBottom, rightBottom)]; } -} - -export = Triangle; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/graphics/Atmosphere.ts b/src/core/world/graphics/Atmosphere.ts old mode 100755 new mode 100644 similarity index 67% rename from src/world/graphics/Atmosphere.ts rename to src/core/world/graphics/Atmosphere.ts index 7f0a004..70e05ae --- a/src/world/graphics/Atmosphere.ts +++ b/src/core/world/graphics/Atmosphere.ts @@ -1,19 +1,21 @@ -import Kernel = require("../Kernel"); -import MeshGraphic = require('./MeshGraphic'); -import AtmosphereGeometry = require("../geometries/Atmosphere"); -import MeshTextureMaterial = require('../materials/MeshTextureMaterial'); -import Camera from "../Camera"; -import Vector = require("../math/Vector"); - -class Atmosphere extends MeshGraphic { +declare function require(name: string): any; +import Kernel from '../Kernel'; +import MeshTextureGraphic from './MeshTextureGraphic'; +import AtmosphereGeometry from '../geometries/Atmosphere'; +import MeshTextureMaterial from '../materials/MeshTextureMaterial'; +import Camera from '../Camera'; +import Vector from '../math/Vector'; +// import atmosphereImgUrl = require("../images/atmosphere.png"); +const atmosphereImgUrl = require("../images/atmosphere.png"); + +export default class Atmosphere extends MeshTextureGraphic { private constructor(public geometry: AtmosphereGeometry, public material: MeshTextureMaterial){ super(geometry, material); } static getInstance(): Atmosphere{ var geometry = new AtmosphereGeometry(); - var imageUrl = "/WebGlobe/src/world/images/atmosphere.png"; - var material = new MeshTextureMaterial(imageUrl, false); + var material = new MeshTextureMaterial(atmosphereImgUrl, false); return new Atmosphere(geometry, material); } @@ -23,10 +25,10 @@ class Atmosphere extends MeshGraphic { onDraw(camera: Camera){ var gl = Kernel.gl; - gl.disable(gl.DEPTH_TEST); + gl.disable(Kernel.gl.DEPTH_TEST); gl.depthMask(false); - gl.enable(gl.BLEND); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + gl.enable(Kernel.gl.BLEND); + gl.blendFunc(Kernel.gl.SRC_ALPHA, Kernel.gl.ONE_MINUS_SRC_ALPHA); this.geometry.getMatrix().setUnitMatrix(); @@ -49,10 +51,8 @@ class Atmosphere extends MeshGraphic { super.onDraw(camera); - gl.enable(gl.DEPTH_TEST); + gl.enable(Kernel.gl.DEPTH_TEST); gl.depthMask(true); - gl.disable(gl.BLEND); + gl.disable(Kernel.gl.BLEND); } -} - -export = Atmosphere; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/graphics/Graphic.ts b/src/core/world/graphics/Graphic.ts old mode 100755 new mode 100644 similarity index 82% rename from src/world/graphics/Graphic.ts rename to src/core/world/graphics/Graphic.ts index c1bd603..90ccd81 --- a/src/world/graphics/Graphic.ts +++ b/src/core/world/graphics/Graphic.ts @@ -1,11 +1,10 @@ -import Kernel = require('../Kernel'); +import Kernel from '../Kernel'; import {Drawable} from '../Definitions.d'; -import Geometry = require('../geometries/Geometry'); -import Material = require('../materials/Material'); -import Program = require('../Program'); +import Geometry from '../geometries/Geometry'; +import Material from '../materials/Material'; +import Program from '../Program'; import Camera from '../Camera'; -import GraphicGroup = require('../GraphicGroup'); - +import GraphicGroup from '../GraphicGroup'; abstract class Graphic implements Drawable{ id: number; @@ -55,4 +54,4 @@ abstract class Graphic implements Drawable{ } } -export = Graphic; \ No newline at end of file +export default Graphic; \ No newline at end of file diff --git a/src/core/world/graphics/LocationGraphic.ts b/src/core/world/graphics/LocationGraphic.ts new file mode 100644 index 0000000..6509523 --- /dev/null +++ b/src/core/world/graphics/LocationGraphic.ts @@ -0,0 +1,35 @@ +declare function require(name: string): any; +import Kernel from '../Kernel'; +import Utils from '../Utils'; +import MultiPointsGraphic from '../graphics/MultiPointsGraphic'; +import MarkerTextureMaterial from '../materials/MarkerTextureMaterial'; +import Service from '../Service'; +import Globe from '../Globe'; +const locationImageUrl = require("../images/location.png"); + +export default class LocationGraphic extends MultiPointsGraphic { + private constructor(material: MarkerTextureMaterial, private globe: Globe) { + super(material); + // Utils.subscribe("location", () => { + // console.log(location); + // }); + } + + setLonLat(lon: number, lat: number){ + this.setLonlats([[lon, lat]]); + } + + destroy(){ + this.globe = null; + super.destroy(); + } + + isReady(){ + return this.globe && this.globe.camera.isEarthFullOverlapScreen() && super.isReady(); + } + + static getInstance(globe: Globe): LocationGraphic { + var material = new MarkerTextureMaterial(locationImageUrl, 24); + return new LocationGraphic(material, globe); + } +}; \ No newline at end of file diff --git a/src/core/world/graphics/MeshColorGraphic.ts b/src/core/world/graphics/MeshColorGraphic.ts new file mode 100644 index 0000000..72a237d --- /dev/null +++ b/src/core/world/graphics/MeshColorGraphic.ts @@ -0,0 +1,103 @@ +import Kernel from '../Kernel'; +import Program from '../Program'; +import Graphic from './Graphic'; +import Mesh from '../geometries/Mesh'; +import MeshVertice from '../geometries/MeshVertice'; +import MeshColorMaterial from '../materials/MeshColorMaterial'; +import Camera from '../Camera'; + +const vs = +` +attribute vec3 aPosition; +attribute vec3 aColor; +varying vec4 vColor; +uniform mat4 uPMVMatrix; + +void main() +{ + gl_Position = uPMVMatrix * vec4(aPosition,1.0); + vColor = vec4(aColor,1.0); +} +`; + +const fs = +` +precision mediump float; +varying vec4 vColor; + +void main() +{ + gl_FragColor = vColor; +} +`; + +export default class MeshColorGraphic extends Graphic { + constructor(public geometry: Mesh, public material: MeshColorMaterial){ + super(geometry, material); + this.setGeometry(geometry); + } + + setGeometry(geometry: Mesh){ + if(this.geometry){ + this.geometry.destroy(); + } + this.geometry = geometry; + if(this.geometry){ + this.geometry.vertices.forEach((vertice: MeshVertice) => { + vertice.c = this.material.color; + }); + this.geometry.calculateVBO(); + this.geometry.calculateIBO(); + this.geometry.calculateCBO(); + } + } + + isGeometryReady():boolean{ + return !!this.geometry.vbo && !!this.geometry.ibo && !!this.geometry.cbo; + } + + isReady():boolean{ + return this.isGeometryReady() && super.isReady(); + } + + createProgram(): Program{ + return Program.getProgram(vs, fs); + } + + protected updateShaderUniforms(camera: Camera){ + //uPMVMatrix + var gl = Kernel.gl; + var pmvMatrix = camera.getProjViewMatrixForDraw().multiplyMatrix(this.geometry.getMatrix()); + var locPMVMatrix = this.program.getUniformLocation('uPMVMatrix'); + gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.getFloat32Array()); + } + + protected onDraw(camera: Camera) { + var gl = Kernel.gl; + + this.updateShaderUniforms(camera); + + //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); + + //aColor + var locColor = this.program.getAttribLocation('aColor'); + this.program.enableVertexAttribArray('aColor'); + this.geometry.cbo.bind(); + gl.vertexAttribPointer(locColor, 3, Kernel.gl.FLOAT, false, 0, 0); + + //设置索引,但不用往shader中赋值 + this.geometry.ibo.bind(); + + //绘图 + var count = this.geometry.triangles.length * 3; + gl.drawElements(Kernel.gl.TRIANGLES, count, Kernel.gl.UNSIGNED_SHORT, 0); + + //释放当前绑定对象 + // gl.bindBuffer(gl.ARRAY_BUFFER, null); + // gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); + } +}; \ No newline at end of file diff --git a/src/world/graphics/MeshGraphic.ts b/src/core/world/graphics/MeshTextureGraphic.ts old mode 100755 new mode 100644 similarity index 79% rename from src/world/graphics/MeshGraphic.ts rename to src/core/world/graphics/MeshTextureGraphic.ts index 9400c74..1bce039 --- a/src/world/graphics/MeshGraphic.ts +++ b/src/core/world/graphics/MeshTextureGraphic.ts @@ -1,8 +1,8 @@ -import Kernel = require("../Kernel"); -import Program = require("../Program"); -import Graphic = require("./Graphic"); -import Mesh = require("../geometries/Mesh"); -import MeshTextureMaterial = require("../materials/MeshTextureMaterial"); +import Kernel from '../Kernel'; +import Program from '../Program'; +import Graphic from './Graphic'; +import Mesh from '../geometries/Mesh'; +import MeshTextureMaterial from '../materials/MeshTextureMaterial'; import Camera from "../Camera"; const vs = @@ -31,7 +31,7 @@ void main() } `; -class MeshGraphic extends Graphic { +export default class MeshTextureGraphic extends Graphic { constructor(public geometry: Mesh, public material: MeshTextureMaterial){ super(geometry, material); this.geometry.calculateVBO(); @@ -63,7 +63,7 @@ class MeshGraphic extends Graphic { gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.getFloat32Array()); //uSampler - gl.activeTexture(gl.TEXTURE0); + gl.activeTexture(Kernel.gl.TEXTURE0); var locSampler = this.program.getUniformLocation('uSampler'); gl.uniform1i(locSampler, 0); } @@ -77,29 +77,27 @@ class MeshGraphic extends Graphic { var locPosition = this.program.getAttribLocation('aPosition'); this.program.enableVertexAttribArray('aPosition'); this.geometry.vbo.bind(); - gl.vertexAttribPointer(locPosition, 3, gl.FLOAT, false, 0, 0); + gl.vertexAttribPointer(locPosition, 3, Kernel.gl.FLOAT, false, 0, 0); //set aUV var locUV = this.program.getAttribLocation('aUV'); this.program.enableVertexAttribArray('aUV'); this.geometry.uvbo.bind(); - gl.vertexAttribPointer(locUV, 2, gl.FLOAT, false, 0, 0); + gl.vertexAttribPointer(locUV, 2, Kernel.gl.FLOAT, false, 0, 0); //set uSampler - gl.bindTexture(gl.TEXTURE_2D, this.material.texture); + gl.bindTexture(Kernel.gl.TEXTURE_2D, this.material.texture); //设置索引,但不用往shader中赋值 this.geometry.ibo.bind(); //绘图 var count = this.geometry.triangles.length * 3; - gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, 0); + gl.drawElements(Kernel.gl.TRIANGLES, count, Kernel.gl.UNSIGNED_SHORT, 0); //释放当前绑定对象 // gl.bindBuffer(gl.ARRAY_BUFFER, null); // gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); // gl.bindTexture(gl.TEXTURE_2D, null); } -} - -export = MeshGraphic; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/core/world/graphics/MultiPointsGraphic.ts b/src/core/world/graphics/MultiPointsGraphic.ts new file mode 100644 index 0000000..f8b0f4a --- /dev/null +++ b/src/core/world/graphics/MultiPointsGraphic.ts @@ -0,0 +1,126 @@ +import Kernel from '../Kernel'; +import Utils from '../Utils'; +import Camera from '../Camera'; +import MathUtils from '../math/Utils'; +import Program from '../Program'; +import Graphic from '../graphics/Graphic'; +import MarkerTextureMaterial from '../materials/MarkerTextureMaterial'; +import VertexBufferObject from '../VertexBufferObject'; +import Vertice from '../math/Vertice'; + +const vs = +` +attribute vec3 aPosition; +uniform mat4 uPMVMatrix; +uniform float uSize; + +void main(void) { + gl_Position = uPMVMatrix * vec4(aPosition, 1.0); + gl_PointSize = uSize; +} +`; + +//http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0 +//gl_FragColor = texture2D(uSampler, vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y)); + +//https://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/discard.php +//highp mediump +const fs = +` +precision mediump float; +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; +} +`; + +export default class MultiPointsGraphic extends Graphic { + private vbo: VertexBufferObject = null; + private vertices: Vertice[] = null; + + protected 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); + } + + isReady(): boolean { + return !!(this.vertices.length > 0 && this.material && this.material.isReady()); + } + + onDraw(camera: Camera) { + var gl = Kernel.gl; + + gl.disable(Kernel.gl.DEPTH_TEST); + gl.depthMask(false); + gl.enable(Kernel.gl.BLEND); + gl.blendFunc(Kernel.gl.SRC_ALPHA, Kernel.gl.ONE_MINUS_SRC_ALPHA); + + //aPosition + var locPosition = this.program.getAttribLocation('aPosition'); + this.program.enableVertexAttribArray('aPosition'); + this.vbo.bind(); + var vertices: number[] = []; + this.vertices.map(function (vertice) { + vertices.push(vertice.x, vertice.y, vertice.z); + }); + this.vbo.bufferData(vertices, Kernel.gl.DYNAMIC_DRAW, true); + gl.vertexAttribPointer(locPosition, 3, Kernel.gl.FLOAT, false, 0, 0); + + //uPMVMatrix + var pmvMatrix = camera.getProjViewMatrixForDraw(); + var locPMVMatrix = this.program.getUniformLocation('uPMVMatrix'); + gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.getFloat32Array()); + + //uSize + var locSize = this.program.getUniformLocation('uSize'); + gl.uniform1f(locSize, this.material.size); + + //set uSampler + var locSampler = this.program.getUniformLocation('uSampler'); + gl.activeTexture(Kernel.gl.TEXTURE0); + gl.bindTexture(Kernel.gl.TEXTURE_2D, this.material.texture); + gl.uniform1i(locSampler, 0); + + //绘图,vertices.length / 3表示所绘点的个数 + gl.drawArrays(Kernel.gl.POINTS, 0, vertices.length / 3); + + //释放当前绑定对象 + gl.enable(Kernel.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); + } + + setLonlats(lonlats:number[][]){ + //不要调用this.clear(),否则会触发调用子类的clear方法 + MultiPointsGraphic.prototype.clear.apply(this); + this.vertices = []; + lonlats.forEach((lonlat) => { + var p = MathUtils.geographicToCartesianCoord(lonlat[0], lonlat[1], Kernel.EARTH_RADIUS + 0.001); + this.vertices.push(p); + }); + } + + clear() { + this.vertices = []; + } +}; \ No newline at end of file diff --git a/src/world/graphics/Tile.ts b/src/core/world/graphics/Tile.ts old mode 100755 new mode 100644 similarity index 89% rename from src/world/graphics/Tile.ts rename to src/core/world/graphics/Tile.ts index 5d84208..7dec0ca --- a/src/world/graphics/Tile.ts +++ b/src/core/world/graphics/Tile.ts @@ -1,13 +1,13 @@ -import Kernel = require('../Kernel'); -import Extent = require('../Extent'); +import Kernel from '../Kernel'; +import Extent from '../Extent'; import Camera from '../Camera'; -import MathUtils = require('../math/Utils'); -import MeshGraphic = require('../graphics/MeshGraphic'); -import TileMaterial = require('../materials/TileMaterial'); -import TileGeometry = require('../geometries/TileGeometry'); -import Vertice = require('../geometries/MeshVertice'); -import Triangle = require('../geometries/Triangle'); -import SubTiledLayer = require('../layers/SubTiledLayer'); +import MathUtils from '../math/Utils'; +import MeshTextureGraphic from '../graphics/MeshTextureGraphic'; +import TileMaterial from '../materials/TileMaterial'; +import TileGeometry from '../geometries/TileGeometry'; +import Vertice from '../geometries/MeshVertice'; +import Triangle from '../geometries/Triangle'; +import SubTiledLayer from '../layers/SubTiledLayer'; import TileGrid from '../TileGrid'; class TileInfo { @@ -73,7 +73,7 @@ class TileInfo { var deltaX = (this.maxX - this.minX) / this.segment; var deltaY = (this.maxY - this.minY) / this.segment; var deltaTextureCoord = 1.0 / this.segment; - var changeElevation = 0;//this.type === Enum.TERRAIN_TILE && this.elevationInfo; + //var changeElevation = 0;//this.type === Enum.TERRAIN_TILE && this.elevationInfo; //level不同设置的半径也不同 var levelDeltaR = 0;//this.level * 2; //对WebMercator投影进行等间距划分格网 @@ -142,7 +142,7 @@ class TileInfo { } } -class Tile extends MeshGraphic { +export default class Tile extends MeshTextureGraphic { subTiledLayer: SubTiledLayer; private constructor(public geometry: TileGeometry, public material: TileMaterial, public tileInfo: TileInfo) { @@ -160,7 +160,7 @@ class Tile extends MeshGraphic { getExtent(){ var tileInfo = this.tileInfo; var tileGrid = new TileGrid(tileInfo.level, tileInfo.row, tileInfo.column); - return new Extent(this.tileInfo.minLon, this.tileInfo.minLat, this.tileInfo.maxLon, this.tileInfo.maxLat, tileGrid); + return new Extent(this.tileInfo.minLon, this.tileInfo.minLat, this.tileInfo.maxLon, this.tileInfo.maxLat); } shouldDraw(camera: Camera){ @@ -171,6 +171,4 @@ class Tile extends MeshGraphic { super.destroy(); this.subTiledLayer = null; } -} - -export = Tile; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/images/atmosphere.png b/src/core/world/images/atmosphere.png similarity index 100% rename from src/world/images/atmosphere.png rename to src/core/world/images/atmosphere.png diff --git a/src/core/world/images/blue8.png b/src/core/world/images/blue8.png new file mode 100644 index 0000000..03db8e4 Binary files /dev/null and b/src/core/world/images/blue8.png differ diff --git a/src/core/world/images/green.png b/src/core/world/images/green.png new file mode 100644 index 0000000..617d1a9 Binary files /dev/null and b/src/core/world/images/green.png differ diff --git a/src/core/world/images/grey.png b/src/core/world/images/grey.png new file mode 100644 index 0000000..7ded3de Binary files /dev/null and b/src/core/world/images/grey.png differ diff --git a/src/core/world/images/iconspng.png b/src/core/world/images/iconspng.png new file mode 100644 index 0000000..3a83e72 Binary files /dev/null and b/src/core/world/images/iconspng.png differ diff --git a/src/core/world/images/location.png b/src/core/world/images/location.png new file mode 100644 index 0000000..514a4ca Binary files /dev/null and b/src/core/world/images/location.png differ diff --git a/src/world/images/location.png b/src/core/world/images/location54.png similarity index 100% rename from src/world/images/location.png rename to src/core/world/images/location54.png diff --git a/src/core/world/images/multi-poi.png b/src/core/world/images/multi-poi.png new file mode 100644 index 0000000..c6b9c43 Binary files /dev/null and b/src/core/world/images/multi-poi.png differ diff --git a/src/core/world/images/orange.png b/src/core/world/images/orange.png new file mode 100644 index 0000000..06e14e1 Binary files /dev/null and b/src/core/world/images/orange.png differ diff --git a/src/world/images/pin.png b/src/core/world/images/pin.png similarity index 100% rename from src/world/images/pin.png rename to src/core/world/images/pin.png diff --git a/src/world/images/poi.png b/src/core/world/images/poi.png similarity index 100% rename from src/world/images/poi.png rename to src/core/world/images/poi.png diff --git a/src/core/world/images/red.png b/src/core/world/images/red.png new file mode 100644 index 0000000..260d268 Binary files /dev/null and b/src/core/world/images/red.png differ diff --git a/src/core/world/images/red8.png b/src/core/world/images/red8.png new file mode 100644 index 0000000..3037d60 Binary files /dev/null and b/src/core/world/images/red8.png differ diff --git a/src/core/world/images/yellow.png b/src/core/world/images/yellow.png new file mode 100644 index 0000000..31d7bba Binary files /dev/null and b/src/core/world/images/yellow.png differ diff --git a/src/world/layers/ArcGISTiledLayer.ts b/src/core/world/layers/ArcGIS.ts old mode 100755 new mode 100644 similarity index 58% rename from src/world/layers/ArcGISTiledLayer.ts rename to src/core/world/layers/ArcGIS.ts index 333aac8..04857e1 --- a/src/world/layers/ArcGISTiledLayer.ts +++ b/src/core/world/layers/ArcGIS.ts @@ -1,7 +1,6 @@ -import Kernel = require("../Kernel"); -import TiledLayer = require("./TiledLayer"); +import TiledLayer from './TiledLayer'; -class ArcGISTiledLayer extends TiledLayer{ +export class ArcGISTiledLayer extends TiledLayer{ constructor(public url: string){ super(); } @@ -10,6 +9,4 @@ class ArcGISTiledLayer extends TiledLayer{ var url = `${this.url}/tile/${level}/${row}/${column}`; return this.wrapUrlWithProxy(url); } -} - -export = ArcGISTiledLayer; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/layers/Autonavi.ts b/src/core/world/layers/Autonavi.ts old mode 100755 new mode 100644 similarity index 95% rename from src/world/layers/Autonavi.ts rename to src/core/world/layers/Autonavi.ts index 6febf5e..2bb2fa8 --- a/src/world/layers/Autonavi.ts +++ b/src/core/world/layers/Autonavi.ts @@ -1,5 +1,4 @@ -import Kernel = require('../Kernel'); -import TiledLayer = require('./TiledLayer'); +import TiledLayer from './TiledLayer'; import LabelLayer from './LabelLayer'; //http://gaode.com diff --git a/src/world/layers/BingTiledLayer.ts b/src/core/world/layers/Bing.ts old mode 100755 new mode 100644 similarity index 88% rename from src/world/layers/BingTiledLayer.ts rename to src/core/world/layers/Bing.ts index 266bfda..56264af --- a/src/world/layers/BingTiledLayer.ts +++ b/src/core/world/layers/Bing.ts @@ -1,8 +1,8 @@ -import MathUtils = require('../math/Utils'); -import TiledLayer = require('./TiledLayer'); +import MathUtils from '../math/Utils'; +import TiledLayer from './TiledLayer'; //Bing地图 -class BingTiledLayer extends TiledLayer{ +export class BingTiledLayer extends TiledLayer{ getTileUrl(level: number, row: number, column: number): string { var url = ""; @@ -42,6 +42,4 @@ class BingTiledLayer extends TiledLayer{ return url; } -} - -export = BingTiledLayer; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/layers/BlendTiledLayer.ts b/src/core/world/layers/BlendTiledLayer.ts old mode 100755 new mode 100644 similarity index 52% rename from src/world/layers/BlendTiledLayer.ts rename to src/core/world/layers/BlendTiledLayer.ts index f11da3d..5c4dba4 --- a/src/world/layers/BlendTiledLayer.ts +++ b/src/core/world/layers/BlendTiledLayer.ts @@ -1,10 +1,9 @@ -import TiledLayer = require("./TiledLayer"); -import NokiaTiledLayer = require("./NokiaTiledLayer"); -import {GoogleTiledLayer} from "./Google"; -import OsmTiledLayer = require("./OsmTiledLayer"); - -class BlendTiledLayer extends TiledLayer { +import TiledLayer from './TiledLayer'; +import {NokiaTiledLayer} from './Nokia'; +import {GoogleTiledLayer} from './Google'; +import {OsmTiledLayer} from './OpenStreetMap'; +export default class BlendTiledLayer extends TiledLayer { getTileUrl(level: number, row: number, column: number): string { var array:any[] = [NokiaTiledLayer, GoogleTiledLayer, OsmTiledLayer]; var sum = level + row + column; @@ -12,7 +11,4 @@ class BlendTiledLayer extends TiledLayer { var url = array[idx].prototype.getTileUrl.apply(this, arguments); return url; } - -} - -export = BlendTiledLayer; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/layers/Google.ts b/src/core/world/layers/Google.ts old mode 100755 new mode 100644 similarity index 97% rename from src/world/layers/Google.ts rename to src/core/world/layers/Google.ts index 03bff9f..06337b2 --- a/src/world/layers/Google.ts +++ b/src/core/world/layers/Google.ts @@ -1,4 +1,4 @@ -import TiledLayer = require('./TiledLayer'); +import TiledLayer from './TiledLayer'; import LabelLayer from './LabelLayer'; //http://www.google.cn/maps @@ -81,7 +81,6 @@ export class GoogleLabelLayer extends LabelLayer { getTileUrl(level: number, row: number, column: number): string { - if (this.idx === undefined) { this.idx = 0; } diff --git a/src/world/layers/LabelLayer.ts b/src/core/world/layers/LabelLayer.ts old mode 100755 new mode 100644 similarity index 90% rename from src/world/layers/LabelLayer.ts rename to src/core/world/layers/LabelLayer.ts index 237486a..478d07f --- a/src/world/layers/LabelLayer.ts +++ b/src/core/world/layers/LabelLayer.ts @@ -1,8 +1,8 @@ +import Kernel from '../Kernel'; import Camera from '../Camera'; import TileGrid from '../TileGrid'; -import Kernel = require('../Kernel'); -import Tile = require("../graphics/Tile"); -import SubTiledLayer = require('./SubTiledLayer'); +import Tile from '../graphics/Tile'; +import SubTiledLayer from './SubTiledLayer'; abstract class LabelLayer extends SubTiledLayer { diff --git a/src/world/layers/NokiaTiledLayer.ts b/src/core/world/layers/Nokia.ts old mode 100755 new mode 100644 similarity index 82% rename from src/world/layers/NokiaTiledLayer.ts rename to src/core/world/layers/Nokia.ts index 0cec5db..5dec379 --- a/src/world/layers/NokiaTiledLayer.ts +++ b/src/core/world/layers/Nokia.ts @@ -1,7 +1,6 @@ -import TiledLayer = require('./TiledLayer'); - -class NokiaTiledLayer extends TiledLayer{ +import TiledLayer from './TiledLayer'; +export class NokiaTiledLayer extends TiledLayer{ getTileUrl(level: number, row: number, column: number): string { var sum = level + row + column; var idx = 1 + sum % 4; //1,2,3,4 @@ -9,7 +8,4 @@ class NokiaTiledLayer extends TiledLayer{ var url = `//${idx}.base.maps.api.here.com/maptile/2.1/maptile/f1f4a211b3/normal.day/${level}/${column}/${row}/512/png8?app_id=xWVIueSv6JL0aJ5xqTxb&app_code=djPZyynKsbTjIUDOBcHZ2g&lg=eng&ppi=72&pview=DEF`; return url; } - -} - -export = NokiaTiledLayer; \ No newline at end of file +} \ No newline at end of file diff --git a/src/world/layers/OsmTiledLayer.ts b/src/core/world/layers/OpenStreetMap.ts old mode 100755 new mode 100644 similarity index 90% rename from src/world/layers/OsmTiledLayer.ts rename to src/core/world/layers/OpenStreetMap.ts index b2d6d80..88f40fa --- a/src/world/layers/OsmTiledLayer.ts +++ b/src/core/world/layers/OpenStreetMap.ts @@ -1,10 +1,10 @@ -import TiledLayer = require('./TiledLayer'); +import TiledLayer from './TiledLayer'; //http://www.openstreetmap.org/ type Style = "Default" | "Cycle" | "Transport" | "Humanitarian"; -export default class OsmTiledLayer extends TiledLayer { +export class OsmTiledLayer extends TiledLayer { private idx:number = 0; diff --git a/src/core/world/layers/PoiLayer.ts b/src/core/world/layers/PoiLayer.ts new file mode 100644 index 0000000..a015ece --- /dev/null +++ b/src/core/world/layers/PoiLayer.ts @@ -0,0 +1,120 @@ +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 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 { + private keyword: string = null; + private searchExtentMode: boolean = false; + private pois: Poi[] = null; + public globe: Globe = null; + + private constructor(public material: MarkerTextureMaterial) { + super(material); + this.pois = []; + Utils.subscribe("extent-change", () => { + if (this.searchExtentMode && this.keyword) { + this.search(this.keyword); + } + }); + } + + static getInstance(): PoiLayer { + var material = new MarkerTextureMaterial(poiImgUrl, 16); + return new PoiLayer(material); + } + + isReady(){ + return this.globe && this.globe.camera.isEarthFullOverlapScreen() && super.isReady(); + } + + destroy() { + this.globe = null; + super.destroy(); + } + + clear() { + super.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 _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]); + } + } + } + + search(keyword: string) { + this.searchExtentMode = true; + this.clear(); + this.keyword = keyword; + var level = this.globe.getLevel(); + if (level >= 10) { + var extent = this.globe.getExtent(); + if(extent){ + Service.searchByExtent(keyword, level, extent).then((response: any) => { + this._showPois(response); + }); + } + } + } + + searchNearby(keyword: string, radius: number, searchType: SearchType = 'Auto', pageCapacity: number = 50, pageIndex: number = 0) { + this.searchExtentMode = false; + return Service.searchNearby(keyword, radius, searchType, false, pageCapacity, pageIndex).then((response: any) => { + this._showPois(response); + return response; + }); + } + + searchByCurrentCity(keyword: string, searchType: SearchType = 'Auto', pageCapacity: number = 50, pageIndex: number = 0){ + return Service.searchByCurrentCity(keyword, searchType, pageCapacity, pageIndex).then((response: any) => { + if(response){ + if(!response.location){ + response.location = this.globe.getLonlat(); + } + } + this._showPois(response); + return response; + }); + } +}; \ No newline at end of file diff --git a/src/world/layers/Qihu.ts b/src/core/world/layers/Qihu.ts old mode 100755 new mode 100644 similarity index 95% rename from src/world/layers/Qihu.ts rename to src/core/world/layers/Qihu.ts index e969a49..7341af9 --- a/src/world/layers/Qihu.ts +++ b/src/core/world/layers/Qihu.ts @@ -1,4 +1,4 @@ -import TiledLayer = require('./TiledLayer'); +import TiledLayer from './TiledLayer'; import TrafficLayer from './TrafficLayer'; //http://ditu.so.com/ diff --git a/src/core/world/layers/RouteLayer.ts b/src/core/world/layers/RouteLayer.ts new file mode 100644 index 0000000..00f352b --- /dev/null +++ b/src/core/world/layers/RouteLayer.ts @@ -0,0 +1,473 @@ +import Kernel from '../Kernel'; +import Utils from '../Utils'; +import MathUtils from '../math/Utils'; +import Vertice from '../math/Vertice'; +import Vector from '../math/Vector'; +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 GraphicGroup from '../GraphicGroup'; +import { Drawable } from '../Definitions.d'; +import Camera from '../Camera'; +import Service from '../Service'; +import Extent from '../Extent'; + +interface RoutePoint { + lonlat: number[]; + vertice: Vertice; + start2EndVector: Vector; + v1: MeshVertice; + v3: MeshVertice; + B12: boolean; + B34: boolean; +} + +class RouteGraphic extends MeshColorGraphic { + private readonly inflexionPointAngle = 70;//认为两条道路出现近乎垂直情况时候的夹角 + + constructor(private lonlats: number[][], private pixelWidth: number, resolution: number, material: MeshColorMaterial) { + super(null, material); + this.updateGeometry(resolution); + } + + updateGeometry(resolution: number) { + const geometry = this._getRouteGeometryByLonlats(this.lonlats, resolution, this.pixelWidth); + this.setGeometry(geometry); + } + + private _getRouteGeometryByLonlats(lonlats: number[][], resolution: number, pixelWidth: number) { + const mesh = new Mesh(); + + const points: RoutePoint[] = lonlats.map((lonlat: number[]) => { + return { + lonlat: lonlat, + vertice: MathUtils.geographicToCartesianCoord(lonlat[0], lonlat[1]), + start2EndVector: null, + v1: null, + 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[] = [];//所有起始拐点的索引 + points.forEach((point: RoutePoint, index) => { + if (index > 0 && index < points.length - 1) { + const prevPoint = points[index - 1]; + const nextPoint = points[index + 1]; + const vector1 = Vector.verticeMinusVertice(point.vertice, prevPoint.vertice); + const vector2 = Vector.verticeMinusVertice(nextPoint.vertice, point.vertice); + const radian = Vector.getRadianOfTwoVectors(vector1, vector2); + const angle = MathUtils.radianToDegree(radian); + if (angle > this.inflexionPointAngle) { + //point就是B点,即道路的拐点 + point.B12 = true; + point.B34 = false; + B12Indexes.push(index); + // console.log("拐点:", point); + } + } + }); + + //逆向遍历B12Indexes,在所有起始拐点B12后面插入额外的终止拐点B34 + for (let i = B12Indexes.length - 1; i >= 0; i--) { + const B12Index = B12Indexes[i]; + const B12 = points[B12Index]; + const B34 = { + ...B12, + B12: false, + 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 { + const endPoint = points[index + 1]; + startPoint.start2EndVector = Vector.verticeMinusVertice(endPoint.vertice, startPoint.vertice); + } + } else { + const prevPoint = points[index - 1]; + startPoint.start2EndVector = prevPoint.start2EndVector; + } + }); + + let startIndex: number = 0; + + points.forEach((point: RoutePoint, index: number) => { + const result = this._getRouteVertices(startIndex, point.vertice, point.start2EndVector, resolution, pixelWidth); + startIndex = result.startIndex; + point.v1 = result.v1; + point.v3 = result.v3; + mesh.vertices.push(point.v1, point.v3); + if (index !== 0) { + const prevPoint = points[index - 1]; + const v1 = prevPoint.v1; + const v3 = prevPoint.v3; + const v0 = point.v1; + const v2 = point.v3; + const triangles = Mesh.buildPlane(v0, v1, v2, v3); + mesh.triangles.push(...triangles); + } + }); + + return mesh; + } + + private _getRouteVertices(startIndex: number, startVertice: Vertice, start2EndVector: Vector, resolution: number, pixelWidth: number) { + /*对于一个面从外面向里面看的绘制顺序 + * 0 end 2 + * + * 1 start 3*/ + const origin2StartVector = Vector.fromVertice(startVertice); + // const start2EndVector = Vector.verticeMinusVertice(endVertice, startVertice); + const end2StartVector = start2EndVector.getOpposite(); + + // const [resolution,bestLevel] = this.camera.calculateCurrentResolutionAndBestDisplayLevel(); + // const { + // resolutionX, + // bestDisplayLevelFloatX, + // resolutionY, + // bestDisplayLevelFloatY + // } = this.camera.measureXYResolutionAndBestDisplayLevel(); + // const resolution = (resolutionX + resolutionY) / 2; + const offset = resolution * pixelWidth / 2; + + const start2LeftBottomVector = origin2StartVector.cross(start2EndVector).setLength(offset); + const p1 = Vector.verticePlusVector(startVertice, start2LeftBottomVector).getArray(); + const v1 = new MeshVertice({ + i: startIndex++, + p: p1 + }); + + const start2RightBottomVector = origin2StartVector.cross(end2StartVector).setLength(offset); + const p3 = Vector.verticePlusVector(startVertice, start2RightBottomVector).getArray(); + const v3 = new MeshVertice({ + i: startIndex++, + p: p3 + }); + + // const end2LeftTopVector = origin2EndVector.cross(start2EndVector).setLength(offset); + // const p0 = Vector.verticePlusVector(endVertice, end2LeftTopVector).getArray(); + // const v0 = new MeshVertice({ + // i: startIndex++, + // p: p0 + // }); + + // const end2RightTopVector = origin2EndVector.cross(end2StartVector).setLength(offset); + // const p2 = Vector.verticePlusVector(endVertice, end2RightTopVector).getArray(); + // const v2 = new MeshVertice({ + // i: startIndex++, + // p: p2 + // }); + + return { + startIndex: startIndex, + v1: v1, + v3: v3 + }; + } +} + +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; + //将1米的作为连接两个经纬度点的最小阈值的平方 + private deltaLonlatSquareThreshold: number = Math.pow(1 / (2 * Math.PI * Kernel.REAL_EARTH_RADIUS) * 360, 2); + + private constructor(private camera: Camera, private key: string) { + super(); + // this.test(); + Utils.subscribe('level-change', () => { + if (this.children.length > 0) { + const resolution = this._getResolution(); + this.children.forEach((graphic: Drawable) => { + if (graphic instanceof RouteGraphic) { + graphic.updateGeometry(resolution); + } + }); + } + }); + } + + test(pixelWidth: number = 5, segments: number = 100, rgb: number[] = [0, 255, 0]) { + // this.clear(); + // const startLonLat: number[] = [116, 40];//[116, -40]; + // const endLonLat: number[] = [116, 25];// [116, 40]; + const resolution = this._getResolution(); + // this._addRouteByLonlat(startLonLat, endLonLat, resolution, pixelWidth, segments, rgb); + this._addRouteByLonlats([[90, 0], [120, 0], [120, 40]], resolution, this.pixelWidth, rgb); + } + + private _addRouteByLonlat(startLonLat: number[], endLonLat: number[], resolution: number, pixelWidth: number, segments: number, rgb: number[]) { + const lonlats = this._getLonlatsBySegments(startLonLat, endLonLat, segments); + return this._addRouteByLonlats(lonlats, resolution, pixelWidth, rgb); + } + + private _addRouteByLonlats(lonlats: number[][], resolution: number, pixelWidth: number, rgb: number[]) { + if (lonlats.length >= 2) { + let validLonlats: number[][] = lonlats; + + // lonlats.forEach((lonlat:number[], index) => { + // if(index === 0){ + // validLonlats.push(lonlat); + // }else{ + // const lastLonlat = validLonlats[validLonlats.length - 1]; + // const deltaLon = lonlat[0] - lastLonlat[0]; + // const deltaLat = lonlat[1] - lastLonlat[1]; + // const square = deltaLon * deltaLon + deltaLat * deltaLat; + // // if(square > this.deltaLonlatSquareThreshold){ + // // validLonlats.push(lonlat); + // // }else{ + // // const realDistance = MathUtils.getRealArcDistanceBetweenLonLats(lastLonlat[0], lastLonlat[1], lonlat[0], lonlat[1]); + // // console.log(`距离太短:${realDistance}`); + // // } + // validLonlats.push(lonlat); + // const realDistance = MathUtils.getRealArcDistanceBetweenLonLats(lastLonlat[0], lastLonlat[1], lonlat[0], lonlat[1]); + // console.log(`距离:${realDistance}`); + // } + // }); + + if (validLonlats.length >= 2) { + const graphic = new RouteGraphic(validLonlats, pixelWidth, resolution, new MeshColorMaterial(rgb)); + this.add(graphic); + return graphic; + } + } + return null; + } + + private _getResolution() { + const { + resolutionX, + bestDisplayLevelFloatX, + resolutionY, + bestDisplayLevelFloatY + } = this.camera.measureXYResolutionAndBestDisplayLevel(); + return (resolutionX + resolutionY) / 2; + } + + private _getLonlatsBySegments(startLonLat: number[], endLonLat: number[], segments: number) { + const deltaLon: number = (endLonLat[0] - startLonLat[0]) / segments; + const deltaLat: number = (endLonLat[1] - startLonLat[1]) / segments; + const lonlats: number[][] = []; + for (let i = 0; i < segments; i++) { + let lonlat: number[] = [startLonLat[0] + deltaLon * i, startLonLat[1] + deltaLat * i]; + lonlats.push(lonlat); + } + lonlats.push(endLonLat); + return lonlats; + } + + showPath(pathIndex: number) { + if (this.route) { + if (this.route.type === 'driving') { + this._showDrivingPath(pathIndex); + } else if (this.route.type === 'bus') { + this._showBusPath(pathIndex); + } + } + } + + routeByDriving(fromLon: number, fromLat: number, toLon: number, toLat: number, strategy: number = 5) { + 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.route = response.route; + this.showPath(0); + } + return response; + }); + } + + private _showDrivingPath(pathIndex: number) { + if (this.route && this.route.paths && this.route.paths.length > 0) { + const path: any = this.route.paths[pathIndex]; + if (path && path.steps && path.steps.length > 0) { + this.clear(); + const lonlats: 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); + lonlatsSegments.push(joinLonlats); + lonlats.push(...joinLonlats); + } + // this._addRouteByLonlats(step.lonlats, resolution, this.pixelWidth, this.greenColor); + lonlatsSegments.push(step.lonlats); + lonlats.push(...step.lonlats); + }); + + const extent = Extent.fromLonlats(lonlats); + + 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); + }); + }, 0); + } + } + } + } + + routeByBus(fromLon: number, fromLat: number, toLon: number, toLat: number, startCity: string, endCity: string, strategy: number = 0) { + 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.route = response.route; + this.showPath(0); + } + return response; + }); + } + + private _showBusPath(pathIndex: number) { + if (this.route && this.route.transits && this.route.transits.length > 0) { + const transit = this.route.transits[pathIndex]; + if (transit && transit.segments && transit.segments.length > 0) { + this.clear(); + const lonlats: 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; + 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; + 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; + lonlatsSegments.push(segment.railway.lonlats); + lonlats.push(...segment.railway.lonlats); + } + }); + + const extent = Extent.fromLonlats(lonlats); + + if(extent){ + this.camera.setExtent(extent); + + setTimeout(() => { + const resolution = this._getResolution(); + lonlatsSegments.forEach((lonlats: number[][]) => { + this._addRouteByLonlats(lonlats, resolution, this.pixelWidth, (lonlats as any).color); + }); + }, 0); + } + } + } + } + + 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){ + this.route = response.route; + this._showWalkingPath(0); + } + return response; + }); + } + + 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){ + this.clear(); + const lonlats: number[][] = []; + + path.steps.forEach((step: any) => { + lonlats.push(...step.lonlats); + }); + + const extent = Extent.fromLonlats(lonlats); + + if(extent){ + this.camera.setExtent(extent); + + setTimeout(() => { + const resolution = this._getResolution(); + this._addRouteByLonlats(lonlats, resolution, this.pixelWidth, this.blueColor); + }, 0); + } + } + } + } + + private _clearAll() { + this.route = null; + this.clear(); + } + + protected onDraw(camera: Camera) { + const gl = Kernel.gl; + + gl.disable(Kernel.gl.DEPTH_TEST); + gl.depthMask(false); + + super.onDraw(camera); + + gl.enable(Kernel.gl.DEPTH_TEST); + gl.depthMask(true); + } + + destroy() { + this.camera = null; + super.destroy(); + } + + test2(str: string) { + const splits = str.split(";"); + const splits1 = splits[0].split(","); + const splits2 = splits[1].split(","); + var lon1 = parseFloat(splits1[0]); + var lat1 = parseFloat(splits1[1]); + var lon2 = parseFloat(splits2[0]); + var lat2 = parseFloat(splits2[1]); + var s = Math.pow((lon1 - lon2), 2) + Math.pow((lat1 - lat2), 2); + const distance = MathUtils.getRealArcDistanceBetweenLonLats(lon1, lat1, lon2, lat2); + return { + lon1: lon1, + lat1: lat1, + lon2: lon2, + lat2: lat2, + s: s, + distance: distance + }; + } + + static getInstance(camera: Camera, key: string) { + return new RouteLayer(camera, key); + } +}; \ No newline at end of file diff --git a/src/world/layers/Soso.ts b/src/core/world/layers/Soso.ts old mode 100755 new mode 100644 similarity index 59% rename from src/world/layers/Soso.ts rename to src/core/world/layers/Soso.ts index 725c54c..c2ac301 --- a/src/world/layers/Soso.ts +++ b/src/core/world/layers/Soso.ts @@ -1,11 +1,12 @@ -import Utils = require('../Utils'); -import TiledLayer = require('./TiledLayer'); +import Utils from '../Utils'; +import TiledLayer from './TiledLayer'; import TrafficLayer from './TrafficLayer'; +import LabelLayer from './LabelLayer'; export class SosoTiledLayer extends TiledLayer { getTileUrl(level: number, row: number, column: number): string { - if(level >= 10){ + if (level >= 10) { return this._getPoliticalUrl(level, row, column); } @@ -14,7 +15,7 @@ export class SosoTiledLayer extends TiledLayer { } //地形图 - private _getDemUrl(level: number, row: number, column: number): string{ + private _getDemUrl(level: number, row: number, column: number): string { //http://p0.map.gtimg.com/demTiles/4/0/0/11_9.jpg var tileCount = Math.pow(2, level); var a = column; @@ -47,7 +48,7 @@ export class SosoTiledLayer extends TiledLayer { private _getPoliticalUrl(level: number, row: number, column: number): string { //["http://rt0.map.gtimg.com/tile", "http://rt1.map.gtimg.com/tile", "http://rt2.map.gtimg.com/tile", "http://rt3.map.gtimg.com/tile"] row = Math.pow(2, level) - row - 1; - var index:number = (level + row + column) % 4; + var index: number = (level + row + column) % 4; //http://rt2.map.gtimg.com/tile?z=4&x=11&y=9&type=vector&styleid=3&version=112 var url = `//rt${index}.map.gtimg.com/tile?z=${level}&x=${column}&y=${row}&type=vector&styleid=3&version=112`; //need proxy @@ -57,28 +58,53 @@ export class SosoTiledLayer extends TiledLayer { export class SosoTrafficLayer extends TrafficLayer { - private idx: number = 1; - private readonly domains: string[] = ["rtt2", "rtt2a", "rtt2b", "rtt2c"]; - protected minLevel: number = 11; + private idx: number = 1; + private readonly domains: string[] = ["rtt2", "rtt2a", "rtt2b", "rtt2c"]; + protected minLevel: number = 11; - getTileUrl(level: number, row: number, column: number): string { + getTileUrl(level: number, row: number, column: number): string { + + if (this.idx === undefined) { + this.idx = 0; + } + + row = Math.pow(2, level) - row - 1; - if (this.idx === undefined) { - this.idx = 0; - } + //http://rtt2.map.qq.com/rtt/?z=11&x=1687&y=1270&timeKey148454126 + var timestamp = Math.floor(Date.now() / 10000); + var url = `//${this.domains[this.idx]}.map.qq.com/rtt/?z=${level}&x=${column}&y=${row}&timeKey${timestamp}`; - row = Math.pow(2, level) - row - 1; + this.idx++; - //http://rtt2.map.qq.com/rtt/?z=11&x=1687&y=1270&timeKey148454126 - var timestamp = Math.floor(Date.now() / 10000); - var url = `//${this.domains[this.idx]}.map.qq.com/rtt/?z=${level}&x=${column}&y=${row}&timeKey${timestamp}`; + if (this.idx >= 4) { + this.idx = 0; + } - this.idx++; + return Utils.wrapUrlWithProxy(url); + } +}; - if (this.idx >= 4) { - this.idx = 0; - } +export class SosoLabelLayer extends LabelLayer { + private idx: number = 0; + + getTileUrl(level: number, row: number, column: number): string { - return Utils.wrapUrlWithProxy(url); + if (this.idx === undefined) { + this.idx = 0; } + + row = Math.pow(2, level) - row - 1; + + //http://rt0.map.gtimg.com/tile?z=12&x=3373&y=2543&type=vector&styleid=3&version=224 + const url = `//rt${this.idx}.map.gtimg.com/tile?z=${level}&x=${column}&y=${row}&type=vector&styleid=3&version=224`; + + this.idx++; + + if (this.idx >= 4) { + this.idx = 0; + } + + //需要使用代理 + return url; + } }; \ No newline at end of file diff --git a/src/world/layers/SubTiledLayer.ts b/src/core/world/layers/SubTiledLayer.ts old mode 100755 new mode 100644 similarity index 91% rename from src/world/layers/SubTiledLayer.ts rename to src/core/world/layers/SubTiledLayer.ts index 485bd01..f8f53ca --- a/src/world/layers/SubTiledLayer.ts +++ b/src/core/world/layers/SubTiledLayer.ts @@ -1,13 +1,9 @@ -import Kernel = require('../Kernel'); -import Utils = require('../Utils'); -import Extent = require('../Extent'); -import MathUtils = require('../math/Utils'); +import Extent from '../Extent'; import TileGrid from '../TileGrid'; -import GraphicGroup = require('../GraphicGroup'); -import Tile = require('../graphics/Tile'); -import TiledLayer = require('./TiledLayer'); +import GraphicGroup from '../GraphicGroup'; +import Tile from '../graphics/Tile'; -class SubTiledLayer extends GraphicGroup { +export default class SubTiledLayer extends GraphicGroup { constructor(private level: number) { super(); @@ -157,6 +153,4 @@ class SubTiledLayer extends GraphicGroup { return tile.visible && tile.isReady(); }).length : 0; } -} - -export = SubTiledLayer; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/layers/TiledLayer.ts b/src/core/world/layers/TiledLayer.ts old mode 100755 new mode 100644 similarity index 68% rename from src/world/layers/TiledLayer.ts rename to src/core/world/layers/TiledLayer.ts index 9b54644..5574829 --- a/src/world/layers/TiledLayer.ts +++ b/src/core/world/layers/TiledLayer.ts @@ -1,49 +1,67 @@ +import Kernel from '../Kernel'; import Camera from '../Camera'; -import Utils = require('../Utils'); +import Globe from '../Globe'; +import Utils from '../Utils'; import TileGrid from '../TileGrid'; -import Kernel = require('../Kernel'); -import Extent = require('../Extent'); -import Tile = require("../graphics/Tile"); -import GraphicGroup = require('../GraphicGroup'); -import SubTiledLayer = require('./SubTiledLayer'); +import Tile from '../graphics/Tile'; +import GraphicGroup from '../GraphicGroup'; +import SubTiledLayer from './SubTiledLayer'; abstract class TiledLayer extends GraphicGroup { - readonly imageRequestOptimizeDeltaLevel = 2; + private readonly imageRequestOptimizeDeltaLevel = 2; + public globe: Globe = null; constructor(protected style: string = "") { super(); //添加第0级的子图层 - var subLayer0 = new SubTiledLayer(0); + const subLayer0 = new SubTiledLayer(0); this.add(subLayer0); - //要对level为1的图层进行特殊处理,在创建level为1时就创建其中的全部的四个tile - var subLayer1 = new SubTiledLayer(1); + //添加第1级的子图层要 + const subLayer1 = new SubTiledLayer(1); this.add(subLayer1); + } + + destroy(){ + this.globe = null; + super.destroy(); + } - for (var m = 0; m <= 1; m++) { - for (var n = 0; n <= 1; n++) { - var args = { - level: 1, - row: m, - column: n, - url: "" - }; - args.url = this.getTileUrl(args.level, args.row, args.column); - var tile = Tile.getInstance(args.level, args.row, args.column, args.url); - subLayer1.add(tile); + private _checkSubLayer1(){ + const subLayer1 = this.children[1]; + if(subLayer1 && subLayer1.getLevel() === 1){ + if(subLayer1.children.length !== 4){ + //对level为1的图层进行特殊处理,创建其中的全部的四个tile + subLayer1.children = []; + for (let m = 0; m <= 1; m++) { + for (let n = 0; n <= 1; n++) { + let args = { + level: 1, + row: m, + column: n, + url: "" + }; + args.url = this.getTileUrl(args.level, args.row, args.column); + let tile = Tile.getInstance(args.level, args.row, args.column, args.url); + subLayer1.add(tile); + } + } } } } refresh() { - var globe = Kernel.globe; - var camera = globe.camera; - var level = globe.getLevel(); + if(!this.globe){ + return; + } + this._checkSubLayer1(); + var camera = this.globe.camera; + var level = this.globe.getLevel(); var options = { threshold: 1 }; - var pitch = camera.getPitch(); + // var pitch = camera.getPitch(); options.threshold = 1;// options.threshold = Math.min(90 / (90 - pitch), 1.5); //最大级别的level所对应的可见TileGrids var lastLevelTileGrids = camera.getVisibleTilesByLevel(level, options); @@ -56,7 +74,7 @@ abstract class TiledLayer extends GraphicGroup { for (subLevel = level; subLevel >= 2; subLevel--) { levelsTileGrids[subLevel] = parentTileGrids;//此行代码表示第subLevel层级的可见切片 - parentTileGrids = parentTileGrids.map(function (item) { + parentTileGrids = parentTileGrids.map(function (item: TileGrid) { return item.getParent(); }); parentTileGrids = Utils.filterRepeatArray(parentTileGrids); @@ -72,7 +90,10 @@ abstract class TiledLayer extends GraphicGroup { //根据传入的level更新SubTiledLayer的数量 updateSubLayerCount() { - var level: number = Kernel.globe.getLevel(); + if(!this.globe){ + return; + } + var level: number = this.globe.getLevel(); var subLayerCount = this.children.length; var deltaLevel = level + 1 - subLayerCount; var i: number, subLayer: SubTiledLayer; @@ -115,8 +136,10 @@ abstract class TiledLayer extends GraphicGroup { } updateTileVisibility() { - var globe = Kernel.globe; - var level = globe.getLevel(); + if(!this.globe){ + return; + } + var level = this.globe.getLevel(); this.children.forEach((subTiledLayer) => { subTiledLayer.showAllTiles(); @@ -124,14 +147,14 @@ abstract class TiledLayer extends GraphicGroup { var ancesorLevel = level - this.imageRequestOptimizeDeltaLevel - 1; if(ancesorLevel >= 1){ - var camera = Kernel.globe.camera; + var camera = this.globe.camera; var tileGrids = camera.getTileGridsOfBoundary(ancesorLevel, false); if(tileGrids.length === 8){ tileGrids = Utils.filterRepeatArray(tileGrids); for(var i: number = 0; i <= ancesorLevel; i++){ this.children[i].hideAllTiles(); } - tileGrids.forEach((tileGrid) => { + tileGrids.forEach((tileGrid: TileGrid) => { var tile = this._getReadyTile(tileGrid); if(tile){ tile.setVisible(true); @@ -143,12 +166,16 @@ abstract class TiledLayer extends GraphicGroup { } onDraw(camera: Camera) { + var gl = this.globe && this.globe.gl; + if(!gl){ + return; + } var program = Tile.findProgram(); if (!program) { return; } program.use(); - var gl = Kernel.gl; + //设置uniform变量的值 //uPMVMatrix @@ -157,31 +184,23 @@ abstract class TiledLayer extends GraphicGroup { gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.getFloat32Array()); //uSampler - gl.activeTexture(gl.TEXTURE0); + gl.activeTexture(Kernel.gl.TEXTURE0); var locSampler = program.getUniformLocation('uSampler'); gl.uniform1i(locSampler, 0); //此处将深度测试设置为ALWAYS是为了解决两个不同层级的切片在拖动时一起渲染会导致屏闪的问题 - gl.depthFunc(gl.ALWAYS); + gl.depthFunc(Kernel.gl.ALWAYS); super.onDraw(camera); //将深度测试恢复成LEQUAL - gl.depthFunc(gl.LEQUAL); + gl.depthFunc(Kernel.gl.LEQUAL); } - getExtent(level?: number) { - var extents = this.getExtents(level); - return Extent.union(extents); - } - - getExtents(level?: number): Extent[] { - if (!(level >= 0 && level <= (this.children.length - 1))) { - level = this.children.length - 1 - 3; - } - var subTiledLayer = this.children[level]; - if (subTiledLayer) { - return subTiledLayer.getExtents(); + getExtent() { + if(this.globe.isRenderingPaused()){ + return null; } - return []; + var subTiledLayer = this.children[this.children.length - 1]; + return subTiledLayer.getExtent(); } protected wrapUrlWithProxy(url: string): string { @@ -212,7 +231,6 @@ abstract class TiledLayer extends GraphicGroup { }); console.table(result); } -} - +}; -export = TiledLayer; \ No newline at end of file +export default TiledLayer; \ No newline at end of file diff --git a/src/world/layers/TrafficLayer.ts b/src/core/world/layers/TrafficLayer.ts similarity index 100% rename from src/world/layers/TrafficLayer.ts rename to src/core/world/layers/TrafficLayer.ts diff --git a/src/world/materials/PoiMaterial.ts b/src/core/world/materials/MarkerTextureMaterial.ts old mode 100755 new mode 100644 similarity index 52% rename from src/world/materials/PoiMaterial.ts rename to src/core/world/materials/MarkerTextureMaterial.ts index 2b8ee20..b1dc85d --- a/src/world/materials/PoiMaterial.ts +++ b/src/core/world/materials/MarkerTextureMaterial.ts @@ -1,12 +1,9 @@ -import MeshTextureMaterial = require("./MeshTextureMaterial"); +import MeshTextureMaterial from './MeshTextureMaterial'; type ImageType = string | HTMLImageElement; -class PoiMaterial extends MeshTextureMaterial{ - +export default class MarkerTextureMaterial extends MeshTextureMaterial{ constructor(imageOrUrl?: ImageType, public size:number = 16){ super(imageOrUrl, false); } -} - -export = PoiMaterial; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/materials/Material.ts b/src/core/world/materials/Material.ts old mode 100755 new mode 100644 similarity index 79% rename from src/world/materials/Material.ts rename to src/core/world/materials/Material.ts index 9c7357c..be9cff4 --- a/src/world/materials/Material.ts +++ b/src/core/world/materials/Material.ts @@ -3,4 +3,4 @@ abstract destroy(): void } -export = Material; \ No newline at end of file +export default Material; \ No newline at end of file diff --git a/src/core/world/materials/MeshColorMaterial.ts b/src/core/world/materials/MeshColorMaterial.ts new file mode 100644 index 0000000..842a061 --- /dev/null +++ b/src/core/world/materials/MeshColorMaterial.ts @@ -0,0 +1,64 @@ +import Material from './Material'; + +// export default class MeshColorMaterial extends Material { +// type: string = ""; +// ready: boolean = false; +// singleColor: number[]; +// triangleColors: number[][]; +// verticeColors: number[][]; + +// constructor() { +// super(); +// this.reset(); +// } + +// isReady(){ +// return this.ready; +// } + +// reset() { +// this.type = ''; +// this.singleColor = null; +// this.triangleColors = []; +// this.verticeColors = []; +// this.ready = false; +// } + +// setSingleColor(color: number[]) { +// this.type = 'single'; +// this.singleColor = color; +// this.ready = true; +// } + +// setTriangleColor(colors: number[][]) { +// this.type = 'triangle'; +// this.triangleColors = colors; +// this.ready = true; +// }; + +// setVerticeColor(colors: number[][]) { +// this.type = 'vertice'; +// this.verticeColors = colors; +// this.ready = true; +// } + +// destroy() { +// this.reset(); +// } +// }; + +export default class MeshColorMaterial extends Material{ + public color: number[] = null;//rgb,0-1 + + constructor(rgb255: number[]){ + super(); + this.color = [rgb255[0] / 255, rgb255[1] / 255, rgb255[2] / 255]; + } + + isReady(){ + return this.color && this.color.length > 0; + } + + destroy(){ + } +}; \ No newline at end of file diff --git a/src/world/materials/MeshTextureMaterial.ts b/src/core/world/materials/MeshTextureMaterial.ts old mode 100755 new mode 100644 similarity index 96% rename from src/world/materials/MeshTextureMaterial.ts rename to src/core/world/materials/MeshTextureMaterial.ts index f28d953..2c8c3a3 --- a/src/world/materials/MeshTextureMaterial.ts +++ b/src/core/world/materials/MeshTextureMaterial.ts @@ -1,11 +1,10 @@ -import Kernel = require("../Kernel"); -import MathUtils = require("../math/Utils"); -import Material = require("./Material"); -import ImageUtils = require('../Image'); +import Kernel from '../Kernel'; +import Material from './Material'; +import ImageUtils from '../Image'; type ImageType = HTMLImageElement | string; -class MeshTextureMaterial extends Material { +export default class MeshTextureMaterial extends Material { texture: WebGLTexture; image: HTMLImageElement; private ready: boolean = false; @@ -144,6 +143,4 @@ class MeshTextureMaterial extends Material { this.texture = null; this.deleted = true; } -} - -export = MeshTextureMaterial; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/materials/TileMaterial.ts b/src/core/world/materials/TileMaterial.ts old mode 100755 new mode 100644 similarity index 65% rename from src/world/materials/TileMaterial.ts rename to src/core/world/materials/TileMaterial.ts index 3e740b5..43f7b9c --- a/src/world/materials/TileMaterial.ts +++ b/src/core/world/materials/TileMaterial.ts @@ -1,9 +1,9 @@ -import MeshTextureMaterial= require('./MeshTextureMaterial'); -import ImageUtils = require('../Image'); +import MeshTextureMaterial from './MeshTextureMaterial'; +import ImageUtils from '../Image'; type ImageType = HTMLImageElement | string; -class TileMaterial extends MeshTextureMaterial{ +export default class TileMaterial extends MeshTextureMaterial{ level: number; constructor(level: number, imageOrUrl: ImageType){ @@ -17,6 +17,4 @@ class TileMaterial extends MeshTextureMaterial{ } super.onLoad(); } -} - -export = TileMaterial; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/math/Line.ts b/src/core/world/math/Line.ts old mode 100755 new mode 100644 similarity index 85% rename from src/world/math/Line.ts rename to src/core/world/math/Line.ts index 720313e..08ea84a --- a/src/world/math/Line.ts +++ b/src/core/world/math/Line.ts @@ -1,7 +1,7 @@ -import Vertice = require('./Vertice'); -import Vector = require('./Vector'); +import Vertice from './Vertice'; +import Vector from './Vector'; -class Line{ +export default class Line{ public vertice: Vertice; public vector: Vector; @@ -26,6 +26,4 @@ class Line{ var lineCopy = new Line(this.vertice, this.vector); return lineCopy; } -} - -export = Line; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/math/Matrix.ts b/src/core/world/math/Matrix.ts old mode 100755 new mode 100644 similarity index 98% rename from src/world/math/Matrix.ts rename to src/core/world/math/Matrix.ts index acd2d9b..d46a583 --- a/src/world/math/Matrix.ts +++ b/src/core/world/math/Matrix.ts @@ -1,9 +1,8 @@ -import Utils = require('../Utils'); -import Vertice = require('./Vertice'); -import Vector = require('./Vector'); - -class Matrix{ +import Utils from '../Utils'; +import Vertice from './Vertice'; +import Vector from './Vector'; +export default class Matrix{ private elements: Float64Array; constructor(m11 = 1, m12 = 0, m13 = 0, m14 = 0, @@ -30,7 +29,7 @@ class Matrix{ toJson(){ //TypedArray不会被序列化成数组,而是序列化成对象,不要用map,可能会返回TypedArray var elements:number[] = []; - Utils.forEach(this.elements, function(ele:number, i:number){ + Utils.forEach(this.elements, function(ele:number){ elements.push(ele); }); return { @@ -495,6 +494,4 @@ class Matrix{ this.worldRotateByVector(radian, worldVector); this.setPosition(transVertice); } - }; - - export = Matrix; \ No newline at end of file + }; \ No newline at end of file diff --git a/src/world/math/Plan.ts b/src/core/world/math/Plan.ts old mode 100755 new mode 100644 similarity index 87% rename from src/world/math/Plan.ts rename to src/core/world/math/Plan.ts index b98ccae..5615413 --- a/src/world/math/Plan.ts +++ b/src/core/world/math/Plan.ts @@ -1,4 +1,4 @@ -class Plan{ +export default class Plan{ constructor(public A: number, public B: number, public C: number, public D: number){ } @@ -6,6 +6,4 @@ class Plan{ var planCopy = new Plan(this.A, this.B, this.C, this.D); return planCopy; } -} - -export = Plan; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/math/Ray.ts b/src/core/world/math/Ray.ts old mode 100755 new mode 100644 similarity index 80% rename from src/world/math/Ray.ts rename to src/core/world/math/Ray.ts index e4cf2c7..62c6a2f --- a/src/world/math/Ray.ts +++ b/src/core/world/math/Ray.ts @@ -1,7 +1,7 @@ -import Vertice = require('./Vertice'); -import Vector = require('./Vector'); +import Vertice from './Vertice'; +import Vector from './Vector'; -class Ray{ +export default class Ray{ public vertice: Vertice; public vector: Vector; /** @@ -31,10 +31,4 @@ class Ray{ var rayCopy = new Ray(this.vertice, this.vector); return rayCopy; } - - rotateVertice(vertice: Vertice): Vertice { - return null; - } -} - -export = Ray; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/math/Utils.ts b/src/core/world/math/Utils.ts old mode 100755 new mode 100644 similarity index 89% rename from src/world/math/Utils.ts rename to src/core/world/math/Utils.ts index 7b89921..da70248 --- a/src/world/math/Utils.ts +++ b/src/core/world/math/Utils.ts @@ -1,9 +1,9 @@ -import Kernel = require('../Kernel'); -import Utils = require('../Utils'); -import Vertice = require('./Vertice'); -import Vector = require('./Vector'); -import Line = require('./Line'); -import Plan = require('./Plan'); +import Kernel from '../Kernel'; +import Utils from '../Utils'; +import Vertice from './Vertice'; +import Vector from './Vector'; +import Line from './Line'; +import Plan from './Plan'; //Math.log2() method is defined in ES6 if(!(Math).log2){ @@ -22,7 +22,7 @@ const pow2Cache: any = {}; const ONE_RADIAN_EQUAL_DEGREE:number = 57.29577951308232;//=>180/Math.PI const ONE_DEGREE_EQUAL_RADIAN:number = 0.017453292519943295;//=>Math.PI/180 -class MathUtils { +export default class MathUtils { static getRealValueInWorld(virtualValue: number){ return virtualValue / Kernel.SCALE_FACTOR; } @@ -330,28 +330,28 @@ class MathUtils { /////////////////////////////////////////////////////////////////////////////////////////// //点变换: Canvas->NDC - static convertPointFromCanvasToNDC(canvasX: number, canvasY: number): number[]{ + static convertPointFromCanvasToNDC(canvasWidth: number, canvasHeight: number, canvasX: number, canvasY: number): number[]{ if(!(Utils.isNumber(canvasX))){ throw "invalid canvasX"; } if(!(Utils.isNumber(canvasY))){ throw "invalid canvasY"; } - var ndcX = 2 * canvasX / Kernel.canvas.width - 1; - var ndcY = 1 - 2 * canvasY / Kernel.canvas.height; + var ndcX = 2 * canvasX / canvasWidth - 1; + var ndcY = 1 - 2 * canvasY / canvasHeight; return [ndcX, ndcY]; } //点变换: NDC->Canvas - static convertPointFromNdcToCanvas(ndcX: number, ndcY: number): number[]{ + static convertPointFromNdcToCanvas(canvasWidth: number, canvasHeight: number, ndcX: number, ndcY: number): number[]{ if(!(Utils.isNumber(ndcX))){ throw "invalid ndcX"; } if(!(Utils.isNumber(ndcY))){ throw "invalid ndcY"; } - var canvasX = (1 + ndcX) * Kernel.canvas.width / 2.0; - var canvasY = (1 - ndcY) * Kernel.canvas.height / 2.0; + var canvasX = (1 + ndcX) * canvasWidth / 2.0; + var canvasY = (1 - ndcY) * canvasHeight / 2.0; return [canvasX, canvasY]; } @@ -450,12 +450,37 @@ class MathUtils { return radian * ONE_RADIAN_EQUAL_DEGREE; } + // static webMercatorDistanceBetweenLonlats(lon1: number, lat1: number, lon2: number, lat2: number){ + // var xy1 = this.degreeGeographicToWebMercator(lon1, lat1); + // var xy2 = this.degreeGeographicToWebMercator(lon2, lat2); + // var deltaX = xy1[0] - xy2[0]; + // var deltaY = xy1[1] - xy2[1]; + // var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + // return distance; + // } + + 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); + var Δφ = φ2 - φ1; + var Δλ = this.degreeToRadian(lon2 - lon1); + var a = Math.sin(Δφ / 2); + var b = Math.sin(Δλ / 2); + + var c = a * a + Math.cos(φ1) * Math.cos(φ2) * b * b; + var d = 2 * Math.atan2(Math.sqrt(c), Math.sqrt(1 - c)); + + var distance = Kernel.REAL_EARTH_RADIUS * d; + return distance; + } + /** * 将投影坐标x转换为以弧度表示的经度 * @param x 投影坐标x * @return {Number} 返回的经度信息以弧度表示 */ - static webMercatorXToRadianLog(x: number){ + static webMercatorXToRadianLon(x: number){ return x / Kernel.EARTH_RADIUS; } @@ -464,8 +489,8 @@ class MathUtils { * @param x 投影坐标x * @return {*} 返回的经度信息以角度表示 */ - static webMercatorXToDegreeLog(x: number): number{ - var radianLog = this.webMercatorXToRadianLog(x); + static webMercatorXToDegreeLon(x: number): number{ + var radianLog = this.webMercatorXToRadianLon(x); return this.radianToDegree(radianLog); } @@ -502,7 +527,7 @@ class MathUtils { * @return {Array} 返回的经纬度信息以弧度表示 */ static webMercatorToRadianGeographic(x: number, y: number): number[]{ - var radianLog = this.webMercatorXToRadianLog(x); + var radianLog = this.webMercatorXToRadianLon(x); var radianLat = this.webMercatorYToRadianLat(y); return [radianLog,radianLat]; } @@ -514,7 +539,7 @@ class MathUtils { * @return {Array} 返回的经纬度信息以角度表示 */ static webMercatorToDegreeGeographic(x: number, y: number): number[]{ - var degreeLog = this.webMercatorXToDegreeLog(x); + var degreeLog = this.webMercatorXToDegreeLon(x); var degreeLat = this.webMercatorYToDegreeLat(y); return [degreeLog,degreeLat]; } @@ -524,11 +549,15 @@ class MathUtils { * @param radianLog 以弧度表示的经度 * @return {*} 投影坐标x */ - static radianLogToWebMercatorX(radianLog: number): number{ + 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"; } - return Kernel.EARTH_RADIUS * radianLog; + if(real){ + return Kernel.REAL_EARTH_RADIUS * radianLog; + }else{ + return Kernel.EARTH_RADIUS * radianLog; + } } /** @@ -536,12 +565,12 @@ class MathUtils { * @param degreeLog 以角度表示的经度 * @return {*} 投影坐标x */ - static degreeLogToWebMercatorX(degreeLog: number): number{ + 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); - return this.radianLogToWebMercatorX(radianLog); + return this.radianLonToWebMercatorX(radianLog, real); } /** @@ -549,15 +578,18 @@ class MathUtils { * @param radianLat 以弧度表示的纬度 * @return {Number} 投影坐标y */ - static radianLatToWebMercatorY(radianLat: number): number{ + 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); - var y = Kernel.EARTH_RADIUS * c; - return y; + if(real){ + return Kernel.REAL_EARTH_RADIUS * c; + }else{ + return Kernel.EARTH_RADIUS * c; + } } /** @@ -565,12 +597,12 @@ class MathUtils { * @param degreeLat 以角度表示的纬度 * @return {Number} 投影坐标y */ - static degreeLatToWebMercatorY(degreeLat: number): number{ + 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); - return this.radianLatToWebMercatorY(radianLat); + return this.radianLatToWebMercatorY(radianLat, real); } /** @@ -580,7 +612,7 @@ class MathUtils { * @return {Array} 投影坐标x、y */ static radianGeographicToWebMercator(radianLog: number, radianLat: number): number[]{ - var x = this.radianLogToWebMercatorX(radianLog); + var x = this.radianLonToWebMercatorX(radianLog); var y = this.radianLatToWebMercatorY(radianLat); return [x, y]; } @@ -592,7 +624,7 @@ class MathUtils { * @return {Array} */ static degreeGeographicToWebMercator(degreeLog: number, degreeLat: number): number[]{ - var x = this.degreeLogToWebMercatorX(degreeLog); + var x = this.degreeLonToWebMercatorX(degreeLog); var y = this.degreeLatToWebMercatorY(degreeLat); return [x, y]; } @@ -740,6 +772,4 @@ class MathUtils { return ns; } -}; - -export = MathUtils; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/math/Vector.ts b/src/core/world/math/Vector.ts old mode 100755 new mode 100644 similarity index 98% rename from src/world/math/Vector.ts rename to src/core/world/math/Vector.ts index ab701f7..45cc2db --- a/src/world/math/Vector.ts +++ b/src/core/world/math/Vector.ts @@ -1,6 +1,6 @@ -import Vertice = require('./Vertice'); +import Vertice from './Vertice'; -class Vector{ +export default class Vector{ constructor(public x = 0, public y = 0, public z = 0){} static fromVertice(vertice: Vertice): Vector{ @@ -168,6 +168,4 @@ class Vector{ var newVector = newVertice.getVector(); return newVector; }*/ -} - -export = Vector; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/world/math/Vertice.ts b/src/core/world/math/Vertice.ts old mode 100755 new mode 100644 similarity index 89% rename from src/world/math/Vertice.ts rename to src/core/world/math/Vertice.ts index d242dbb..fcac8db --- a/src/world/math/Vertice.ts +++ b/src/core/world/math/Vertice.ts @@ -1,4 +1,4 @@ -class Vertice{ +export default class Vertice{ constructor(public x = 0, public y = 0, public z = 0){} getArray(): number[] { @@ -12,6 +12,4 @@ class Vertice{ getOpposite(): Vertice { return new Vertice(-this.x, -this.y, -this.z); } -} - -export = Vertice; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/webapp/common/Utils.js b/src/webapp/common/Utils.js new file mode 100644 index 0000000..17c116c --- /dev/null +++ b/src/webapp/common/Utils.js @@ -0,0 +1,9 @@ +export const handleStyles = (styles) => { + if(styles && styles.locals){ + for(let localName in styles.locals){ + if(styles.locals.hasOwnProperty(localName)){ + styles[localName] = styles.locals[localName]; + } + } + } +}; \ No newline at end of file diff --git a/src/webapp/common/loading/index.js b/src/webapp/common/loading/index.js new file mode 100644 index 0000000..669fcd5 --- /dev/null +++ b/src/webapp/common/loading/index.js @@ -0,0 +1,24 @@ +import styles from './index.scss'; +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); + +const loading = { + show(){ + this.hide(); + document.body.appendChild(loadingDom); + }, + + hide(){ + if(loadingDom.parentNode){ + loadingDom.parentNode.removeChild(loadingDom); + } + } +}; + +export default loading; \ No newline at end of file diff --git a/src/webapp/common/loading/index.scss b/src/webapp/common/loading/index.scss new file mode 100644 index 0000000..e81b3e5 --- /dev/null +++ b/src/webapp/common/loading/index.scss @@ -0,0 +1,31 @@ +$icon-size: 36px; + +@keyframes loading-keyframes{ + from{ + transform: rotate(0deg); + } + to{ + transform: rotate(360deg); + } +} + +#loading{ + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + + .icon{ + position: absolute; + 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; + } +} \ No newline at end of file diff --git a/src/webapp/common/loading/loading.png b/src/webapp/common/loading/loading.png new file mode 100644 index 0000000..fb79d7f Binary files /dev/null and b/src/webapp/common/loading/loading.png differ diff --git a/src/webapp/components/Map/index.jsx b/src/webapp/components/Map/index.jsx new file mode 100644 index 0000000..ec90c27 --- /dev/null +++ b/src/webapp/components/Map/index.jsx @@ -0,0 +1,78 @@ +import React, {Component} from 'react'; +import ReactDOM from 'react-dom'; +import styles from './index.scss'; +import Globe from 'world/Globe'; + +export const globe = Globe.getInstance({ + pauseRendering: true, + satellite: false, + lonlat: "auto", + level: "auto", + key: "db146b37ef8d9f34473828f12e1e85ad" +}); + +//延迟一秒加载globe中的切片 +setTimeout(function(){ + globe.resumeRendering(); + let __mounted = false; + const parent = globe.canvas.parentNode; + if(parent){ + __mounted = !!parent.__mounted; + } + if(!__mounted){ + globe.pauseRendering(); + } +}, 1000); + +window.globe = globe; + +function safelyResizeByParent(){ + try{ + const parent = globe.canvas.parentNode; + if(parent){ + const w = parent.clientWidth; + const h = parent.clientHeight; + if(w > 0 && h > 0){ + globe.resize(w, h); + } + } + }catch(e){ + console.error(e); + } +} + +window.addEventListener("resize", safelyResizeByParent, false); + +export default class Map extends Component{ + + constructor(props){ + super(props); + this.state = {}; + } + + render(){ + return
; + } + + resizeGlobe(){ + safelyResizeByParent(); + } + + componentDidMount(){ + const domNode = ReactDOM.findDOMNode(this); + domNode.__mounted = true; + globe.placeAt(domNode); + this.resizeGlobe(); + globe.resumeRendering(); + } + + componentWillUnmount(){ + const domNode = ReactDOM.findDOMNode(this); + domNode.__mounted = false; + const canvas = globe && globe.canvas; + if(canvas && canvas.parentNode === domNode){ + domNode.removeChild(canvas); + globe.pauseRendering(); + } + } +}; \ No newline at end of file diff --git a/src/webapp/components/Map/index.scss b/src/webapp/components/Map/index.scss new file mode 100644 index 0000000..1cfb74d --- /dev/null +++ b/src/webapp/components/Map/index.scss @@ -0,0 +1,5 @@ +.map{ + width: 100%; + height: 100%; + overflow: hidden; +} \ No newline at end of file diff --git a/src/webapp/components/RouteComponent/index.jsx b/src/webapp/components/RouteComponent/index.jsx new file mode 100644 index 0000000..8a4e45f --- /dev/null +++ b/src/webapp/components/RouteComponent/index.jsx @@ -0,0 +1,84 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import ReactDOM from 'react-dom'; +import {hashHistory} from 'react-router'; +import loading from 'webapp/common/loading'; +import styles from './index.scss'; + +let currentLocation = null; +let previousLocation = null; + +hashHistory.listen((location) => { + //action: REPLACE or PUSH => init new route component => componentDidMount() => async POP + //router.goBack() => POP + if(location.action === "POP"){ + previousLocation = currentLocation; + currentLocation = location; + } +}); + +export default class RouteComponent extends Component { + static contextTypes = { + router: PropTypes.object + }; + + goBack(){ + this.context.router.goBack(); + } + + getPreviousLocation(){ + const realLocation = this.context.router.getCurrentLocation(); + if(realLocation && realLocation.action === 'POP' && realLocation.pathname !== currentLocation.pathname){ + // In this case, location is changed, but hashHistory doesn't emit event now because it emit event async. + // Don't change previousLocation and currentLocation here because we can do it in next hashHistory event. + // previousLocation = currentLocation; + // currentLocation = realLocation; + return currentLocation; + } + return previousLocation; + } + + componentDidMount(){ + this._isMounted = true; + const domNode = ReactDOM.findDOMNode(this); + const className = styles["route-component"]; + if(!domNode.classList.contains(className)){ + domNode.classList.add(className); + } + } + + componentWillUnmount(){ + this._isMounted = false; + } + + hasBeenMounted(){ + return this._isMounted; + } + + wrapPromise(promise){ + loading.show(); + this.setState({ + loading: true + }); + const p = new Promise((resolve, reject) => { + promise.then((response) => { + loading.hide(); + if(this.hasBeenMounted()){ + this.setState({ + loading: false + }); + resolve(response); + } + }, (err) => { + loading.hide(); + if(this.hasBeenMounted()){ + this.setState({ + loading: false + }); + reject(err); + } + }); + }); + return p; + } +}; \ No newline at end of file diff --git a/src/webapp/components/RouteComponent/index.scss b/src/webapp/components/RouteComponent/index.scss new file mode 100644 index 0000000..1df153c --- /dev/null +++ b/src/webapp/components/RouteComponent/index.scss @@ -0,0 +1,6 @@ +.route-component{ + position: relative; + box-sizing: border-box; + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/src/webapp/components/Search/index.jsx b/src/webapp/components/Search/index.jsx new file mode 100644 index 0000000..ef32e6a --- /dev/null +++ b/src/webapp/components/Search/index.jsx @@ -0,0 +1,132 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import styles from './index.scss'; + +export default class Search extends Component { + static propTypes = { + className: PropTypes.string, + readOnly: PropTypes.bool, + placeholder: PropTypes.string, + showVoice: PropTypes.bool, + showMapList: PropTypes.bool, + showCancel: PropTypes.bool, + // showMap: PropTypes.bool, + onMap: PropTypes.func, + onList: PropTypes.func, + onCancel: PropTypes.func, + onFocus: PropTypes.func, + onSearch: PropTypes.func + }; + + static defaultProps = { + placeholder: "", + readOnly: false, + showVoice: false, + showMapList: false, + showCancel: false + // showMap: false + }; + + constructor(props) { + super(props); + this.state = { + keyword: "", + showMap: false//当前显示地图内容还是列表内容,false表示显示列表内容,但是label是"地图",表示可以切换到地图页面 + }; + } + + onLeftAction(){ + this.setState((prevState, props) => ({ + showMap: !prevState.showMap + }), () => { + if(this.state.showMap){ + if(this.props.onMap){ + this.props.onMap(); + } + }else{ + if(this.props.onList){ + this.props.onList(); + } + } + }); + } + + onRightAction(){ + if(this.props.onCancel){ + setTimeout(() => { + this.props.onCancel(); + }, 0); + // this.props.onCancel(); + } + } + + onKeywordDivClick(){ + if(this.props.onFocus){ + this.props.onFocus(); + } + } + + onKeywordInputFocus(e){ + // if(this.props.readOnly){ + // // e.preventDefault(); + // this.keywordInput.blur(); + // } + if(this.props.onFocus){ + this.props.onFocus(); + } + } + + onKeywordInputBlur(){ + } + + isFocused(){ + return !!(this.keywordInput && this.keywordInput === document.activeElement); + } + + onKeywordInputPress(e){ + if(e.key === "Enter"){ + if(this.props.onSearch && this.keywordInput && this.keywordInput.value){ + this.props.onSearch(this.keywordInput.value); + } + } + } + + onClickSearchIcon(){ + if(this.props.onSearch){ + if(this.keywordInput && this.keywordInput.value){ + this.props.onSearch(this.keywordInput.value); + }else if(this.keywordDiv && this.keywordDiv.textContent){ + this.props.onSearch(this.keywordDiv.textContent); + } + } + } + + render() { + const a = classNames(styles["search-section"], this.props.className, { + [styles["hide-left-action"]]: !this.props.showMapList, + [styles["hide-right-action"]]: !this.props.showCancel + }); + + return ( +
+ { + this.props.showMapList ?
this.onLeftAction()}>{this.state.showMap ? "列表" : "地图" }
: false + } +
+ { + this.props.readOnly ? ( +
{this.keywordDiv=input}} className={styles.keyword} onClick={() => this.onKeywordDivClick()}>{this.props.placeholder}
+ ) : ( + {this.keywordInput=input}} type="text" className={styles.keyword} readOnly={this.props.readOnly} placeholder={this.props.placeholder} onFocus={(e) => this.onKeywordInputFocus(e)} onBlur={() => this.onKeywordInputBlur()} onKeyPress={(e)=>{this.onKeywordInputPress(e)}} /> + ) + } + this.onClickSearchIcon()}> +
+ { + this.props.showCancel &&
this.onRightAction()}>取消
+ } +
+ ); + } +}; \ No newline at end of file diff --git a/src/webapp/components/Search/index.scss b/src/webapp/components/Search/index.scss new file mode 100644 index 0000000..349853d --- /dev/null +++ b/src/webapp/components/Search/index.scss @@ -0,0 +1,63 @@ +@import "../../css/variables"; +$padding-offset: 15px; + +.search-section{ + display: flex; + flex-flow: row nowrap; + align-items: center; + height: 30px; + padding: 8px 0; + + &.hide-left-action{ + padding-left: $padding-offset; + } + + &.hide-right-action{ + padding-right: $padding-offset; + } + + .input-container{ + position: relative; + width: 100%; + height: 100%; + background-color: #ededed; + border-radius: 4px; + + > *{ + background: transparent; + } + + .keyword{ + flex: auto; + box-sizing: border-box; + display: block; + width: 100%; + font-size: 15px; + border: none; + line-height: 22px; + padding-left: 10px; + padding-top: 4px; + padding-bottom: 4px; + color: black !important; + } + + div.keyword{ + color: #888 !important; + } + + i{ + position: absolute; + right: 11px; + top: 7px; + color: #888; + font-size: 16px; + } + } + + .left-action,.right-action{ + flex: none; + padding: 0 $padding-offset; + color: $blue-color; + font-size: 16px; + } +} \ No newline at end of file diff --git a/src/webapp/components/TrafficTypes/index.jsx b/src/webapp/components/TrafficTypes/index.jsx new file mode 100644 index 0000000..7fffc38 --- /dev/null +++ b/src/webapp/components/TrafficTypes/index.jsx @@ -0,0 +1,66 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import styles from './index.scss'; + +export default class TrafficTypes extends Component { + + static propTypes = { + type: PropTypes.string, + onTrafficTypeChange: PropTypes.func, + onCancel: PropTypes.func + } + + static defaultProps = { + type: 'driving'//bus,walking + } + + constructor(props) { + super(props); + this.lastType = this.props.type; + } + + onClickTrafficType(trafficType) { + if (trafficType !== this.lastType) { + if (this.props.onTrafficTypeChange) { + this.props.onTrafficTypeChange(trafficType); + } + } + this.lastType = trafficType; + } + + getTrafficType() { + return this.state.type; + } + + onCancel() { + if (this.props.onCancel) { + this.props.onCancel(); + } + } + + render() { + const busClassName = classNames("icon-bus", styles["traffic-type"], { + selected: this.props.type === 'bus' + }); + const driveClassName = classNames("icon-cab", styles["traffic-type"], { + selected: this.props.type === 'driving' + }); + const walkClassName = classNames("icon-male", styles["traffic-type"], { + selected: this.props.type === 'walking' + }); + + return ( +
+
+
+ this.onClickTrafficType('bus')}> + this.onClickTrafficType('driving')}> + this.onClickTrafficType('walking')}> +
+
{ this.onCancel(); }}>取消
+
+
+ ); + } +}; \ No newline at end of file diff --git a/src/webapp/components/TrafficTypes/index.scss b/src/webapp/components/TrafficTypes/index.scss new file mode 100644 index 0000000..a626a03 --- /dev/null +++ b/src/webapp/components/TrafficTypes/index.scss @@ -0,0 +1,38 @@ +@import "../../css/variables"; +$header-height: 46px; + +header{ + position: relative; + box-sizing: border-box; + height: $header-height; + line-height: $header-height; + border-bottom: 1px solid #b4b4b4; + + .traffic-types{ + width: 50%; + height: 100%; + margin: 0 auto; + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + align-items: center; + + .traffic-type{ + font-size: 24px; + color: #54585E; + &:global(.selected){ + color: $blue-color; + } + } + } + + .cancel{ + position: absolute; + color: $blue-color; + font-size: 16px; + height: 100%; + top: 0; + bottom: 0; + right: 15px; + } +} \ No newline at end of file diff --git a/src/webapp/css/_variables.scss b/src/webapp/css/_variables.scss new file mode 100644 index 0000000..eeaf31f --- /dev/null +++ b/src/webapp/css/_variables.scss @@ -0,0 +1 @@ +$blue-color: #0079ff; \ No newline at end of file diff --git a/src/webapp/css/common.scss b/src/webapp/css/common.scss new file mode 100644 index 0000000..c34f72b --- /dev/null +++ b/src/webapp/css/common.scss @@ -0,0 +1,51 @@ +html, body, input{ + margin: 0; + padding: 0; +} + +html, body,:global(#root){ + width: 100%; + height: 100%; +} + +html{ + overflow: hidden; +} + +body{ + position: fixed; + height: 100%; + left: 0; + right: 0; + top: 0; + bottom: 0; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +input[type=text]{ + border: 0; + outline: none; + &::-ms-clear{ + display: none; + } +} + +:global{ + *[data-reactroot]{ + position: relative; + box-sizing: border-box; + width: 100%; + height: 100%; + } + + .ellipsis{ + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .hidden{ + display: none !important; + } +} \ No newline at end of file diff --git a/src/webapp/fonts/fontello.eot b/src/webapp/fonts/fontello.eot new file mode 100644 index 0000000..a7fcdcf Binary files /dev/null and b/src/webapp/fonts/fontello.eot differ diff --git a/src/webapp/fonts/fontello.scss b/src/webapp/fonts/fontello.scss new file mode 100644 index 0000000..3cda34e --- /dev/null +++ b/src/webapp/fonts/fontello.scss @@ -0,0 +1,89 @@ +@font-face { + font-family: 'fontello'; + src: url('./fontello.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + +:global { + [class^="icon-"], + [class*=" icon-"] { + display: inline-block; + line-height: 1em; + } + [class^="icon-"]:before, + [class*=" icon-"]:before { + font-family: "fontello"; + font-style: normal; + font-weight: normal; + display: inline-block; + text-decoration: inherit; + text-align: center; + font-variant: normal; + text-transform: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + /* Uncomment for 3D effect */ + /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ + } + + .icon-search:before { + content: '\e800'; + } + + .icon-location:before { + content: '\e801'; + } + + .icon-road:before { + content: '\e802'; + } + + .icon-glass:before { + content: '\e803'; + } + + .icon-basket:before { + content: '\e804'; + } + + .icon-food:before { + content: '\f0f5'; + } + + .icon-angle-left:before { + content: '\f104'; + } + + .icon-angle-right:before { + content: '\f105'; + } + + .icon-circle-empty:before { + content: '\f10c'; + } + + .icon-male:before { + content: '\f183'; + } + + .icon-cab:before { + content: '\f1b9'; + } + + .icon-bus:before { + content: '\f207'; + } + + .icon-bed:before { + content: '\f236'; + } + + .icon-subway:before { + content: '\f239'; + } + + .icon-map-o:before { + content: '\f278'; + } +} \ No newline at end of file diff --git a/src/webapp/fonts/fontello.svg b/src/webapp/fonts/fontello.svg new file mode 100644 index 0000000..fd41701 --- /dev/null +++ b/src/webapp/fonts/fontello.svg @@ -0,0 +1,40 @@ + + + +Copyright (C) 2017 by original authors @ fontello.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/webapp/fonts/fontello.ttf b/src/webapp/fonts/fontello.ttf new file mode 100644 index 0000000..56e4363 Binary files /dev/null and b/src/webapp/fonts/fontello.ttf differ diff --git a/src/webapp/fonts/fontello.woff b/src/webapp/fonts/fontello.woff new file mode 100644 index 0000000..8bd67e2 Binary files /dev/null and b/src/webapp/fonts/fontello.woff differ diff --git a/src/webapp/fonts/fontello.woff2 b/src/webapp/fonts/fontello.woff2 new file mode 100644 index 0000000..e08641f Binary files /dev/null and b/src/webapp/fonts/fontello.woff2 differ diff --git a/src/webapp/index.jsx b/src/webapp/index.jsx new file mode 100644 index 0000000..6662a99 --- /dev/null +++ b/src/webapp/index.jsx @@ -0,0 +1,48 @@ +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 IndexIndex from './routes/index/Index'; +import MapBase from './routes/map/Base'; +import NearbySearch from './routes/nearby/Search'; +import NearbyResult from './routes/nearby/Result'; +import NavSearch from './routes/nav/Search'; +import NavPaths from './routes/nav/Paths'; +import NotFound from './routes/404'; + +const rootDiv = document.getElementById("root"); + +ReactDOM.render(( + + + + + + + + + + + + + + + + + + + + +), rootDiv);*/ + +import rootRoute from './routes/route'; + +const rootDiv = document.getElementById("root"); + +ReactDOM.render(( + + +), rootDiv); \ No newline at end of file diff --git a/src/webapp/routes/404/index.jsx b/src/webapp/routes/404/index.jsx new file mode 100644 index 0000000..f791cfe --- /dev/null +++ b/src/webapp/routes/404/index.jsx @@ -0,0 +1,18 @@ +import React, {Component} from 'react'; +import styles from './index.scss'; + +export default class App extends Component{ + + constructor(props){ + super(props); + this.state = {}; + } + + render(){ + return( +
+
404
+
+ ); + } +}; \ No newline at end of file diff --git a/src/webapp/routes/404/index.scss b/src/webapp/routes/404/index.scss new file mode 100644 index 0000000..c47919e --- /dev/null +++ b/src/webapp/routes/404/index.scss @@ -0,0 +1,4 @@ +.status-404{ + font-size: 8em; + text-align: center; +} \ No newline at end of file diff --git a/src/webapp/routes/404/route.js b/src/webapp/routes/404/route.js new file mode 100644 index 0000000..ab2d11a --- /dev/null +++ b/src/webapp/routes/404/route.js @@ -0,0 +1,6 @@ +const route = { + path: '*', + component: require('./index') +}; + +export default route; \ No newline at end of file diff --git a/src/webapp/routes/index/Index/index.jsx b/src/webapp/routes/index/Index/index.jsx new file mode 100644 index 0000000..f0c8ab0 --- /dev/null +++ b/src/webapp/routes/index/Index/index.jsx @@ -0,0 +1,78 @@ +import React from 'react'; +import {Link} from 'react-router'; +import classNames from 'classnames'; +import styles from './index.scss'; +import RouteComponent from 'webapp/components/RouteComponent'; +import Search from 'webapp/components/Search'; +// import {handleStyles} from 'webapp/common/Utils'; +// handleStyles(styles); + +export default class App extends RouteComponent{ + + constructor(props){ + super(props); + this.state = {}; + } + + onSearchFocus(){ + const path = `/map/base`; + this.context.router.push(path); + } + + render(){ + const faMapTo = "icon-map-o"; + const faMapMarker = "icon-location"; + const faRoad = "icon-road"; + const faCutlery = "icon-food"; + const faBed = "icon-bed"; + const faBus = "icon-bus"; + const faSubway = "icon-subway"; + + return( +
+
WebGlobe
+ this.onSearchFocus()} placeholder="搜索地点、公交、城市" /> +
+ + + 地图 + + + + 附近 + + + + 路线 + +
+
+ +
+ +
+ 美食 + + +
+ +
+ 酒店 + + +
+ +
+ 公交 + + +
+ +
+ 地铁 + +
+
+ ); + } +}; \ No newline at end of file diff --git a/src/webapp/routes/index/Index/index.scss b/src/webapp/routes/index/Index/index.scss new file mode 100644 index 0000000..4769f23 --- /dev/null +++ b/src/webapp/routes/index/Index/index.scss @@ -0,0 +1,99 @@ +.root{ + box-shadow: border-box; + padding: 0 30px; + overflow: hidden; + height: auto !important; +} + +.title{ + font-size: 30px; + text-align: center; + padding: 30px 0; + color: gray; +} + +.search{ + padding: 8px 0; +} + +.link1-container{ + padding: 10px 0; +} + +.link1,.link2{ + box-sizing: border-box; + display: inline-block; + text-align: center; + text-decoration: none; +} + +.link1{ + width: 33.33333%; + line-height: 28px; + font-size: 15px; + color: gray; + &:nth-child(2){ + &:after{ + border-left: 1px solid rgba(128,128,128,0.3); + border-right: 1px solid rgba(128,128,128,0.3); + } + } + + span{ + margin-left: 5px; + } +} + +.link2-container{ + display: flex; + flex-direction: row; + justify-content: space-between; + flex-wrap: nowrap; + border-top: 1px solid rgba(128,128,128,0.3); + padding-top: 20px; +} + +.link2{ + div{ + width: 48px; + height: 48px; + border-radius: 50%; + background-color: #f3a61f; + margin: 0 auto; + color: white; + font-size: 23px; + & *{ + display: inline-block; + vertical-align: middle; + } + + &:after{ + display: inline-block; + width: 0; + height: 100%; + content: ""; + vertical-align: middle; + } + } + + span{ + font-size: 12px; + color: gray; + } + + .food{ + background: #f73838; + } + + .hotel{ + background: #f3a61f; + } + + .traffic{ + background: #61b51d; + } + + .subway{ + background: #0079ff; + } +} \ No newline at end of file diff --git a/src/webapp/routes/index/Index/route.js b/src/webapp/routes/index/Index/route.js new file mode 100644 index 0000000..b6f5304 --- /dev/null +++ b/src/webapp/routes/index/Index/route.js @@ -0,0 +1,7 @@ +const route = { + path: 'index', + component: require('./index'), + childRoutes: [] +}; + +export default route; \ No newline at end of file diff --git a/src/webapp/routes/index/route.js b/src/webapp/routes/index/route.js new file mode 100644 index 0000000..44aa8fd --- /dev/null +++ b/src/webapp/routes/index/route.js @@ -0,0 +1,8 @@ +const route = { + path: 'index', + childRoutes: [ + require('./Index/route') + ] +}; + +export default route; \ No newline at end of file diff --git a/src/webapp/routes/map/Base/index.jsx b/src/webapp/routes/map/Base/index.jsx new file mode 100644 index 0000000..e97a633 --- /dev/null +++ b/src/webapp/routes/map/Base/index.jsx @@ -0,0 +1,50 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Search from 'webapp/components/Search'; +import MapComponent, {globe} from 'webapp/components/Map'; +import RouteComponent from 'webapp/components/RouteComponent'; +import styles from './index.scss'; + +export default class MapBase extends RouteComponent{ + constructor(props){ + super(props); + this.state = {}; + } + + onSearch(keyword){ + const path = `/nearby/result?keyword=${keyword}`; + this.context.router.push(path); + } + + onCancel(){ + // basiclly fix flash issue for Xiaomi browser + this.mapContainer.style.display = 'none'; + setTimeout(() => { + const path = '/index/index'; + this.context.router.push(path); + }, 50); + + // this.mapContainer.style.display = 'none'; + + // const searchDom = ReactDOM.findDOMNode(this.search); + // if(searchDom){ + // searchDom.style.display = 'none'; + // } + // globe.pauseRendering(); + // setTimeout(() => { + // const path = '/index/index'; + // this.context.router.push(path); + // }, 50); + } + + render(){ + return ( +
+ this.search = input} placeholder="搜索地点、公交、城市" showCancel={true} onSearch={(keyword) => this.onSearch(keyword)} onCancel={() => this.onCancel()} /> +
this.mapContainer = input} className={styles["map-container"]}> + +
+
+ ); + } +}; \ No newline at end of file diff --git a/src/webapp/routes/map/Base/index.scss b/src/webapp/routes/map/Base/index.scss new file mode 100644 index 0000000..dd5c8fb --- /dev/null +++ b/src/webapp/routes/map/Base/index.scss @@ -0,0 +1,10 @@ + +.map-container{ + box-sizing: border-box; + position: absolute; + left: 0; + width: 100%; + top: 46px; + bottom: 0; + border-top: 1px solid #ededed; +} \ No newline at end of file diff --git a/src/webapp/routes/map/Base/route.js b/src/webapp/routes/map/Base/route.js new file mode 100644 index 0000000..e98d8a0 --- /dev/null +++ b/src/webapp/routes/map/Base/route.js @@ -0,0 +1,7 @@ +const route = { + path: 'base', + component: require('./index'), + childRoutes: [] +}; + +export default route; \ No newline at end of file diff --git a/src/webapp/routes/map/route.js b/src/webapp/routes/map/route.js new file mode 100644 index 0000000..fed7e44 --- /dev/null +++ b/src/webapp/routes/map/route.js @@ -0,0 +1,8 @@ +const route = { + path: 'map', + childRoutes: [ + require('./Base/route') + ] +}; + +export default route; \ No newline at end of file diff --git a/src/webapp/routes/nav/Paths/index.jsx b/src/webapp/routes/nav/Paths/index.jsx new file mode 100644 index 0000000..25c721a --- /dev/null +++ b/src/webapp/routes/nav/Paths/index.jsx @@ -0,0 +1,328 @@ +import React from 'react'; +import classNames from 'classnames'; +import TrafficTypes from 'webapp/components/TrafficTypes'; +import RouteComponent from 'webapp/components/RouteComponent'; +import MapComponent, { globe } from 'webapp/components/Map'; +import styles from './index.scss'; + +export default class Paths extends RouteComponent { + constructor(props) { + super(props); + this.state = { + type: 'driving', + route: null, + selectedPathIndex: 0 + }; + if (this.props.location.state) { + if (this.props.location.state.type) { + this.state.type = this.props.location.state.type; + } + } + } + + onSelectPath(pathIndex) { + globe.routeLayer.showPath(pathIndex); + this.setState({ + selectedPathIndex: pathIndex + }); + } + + onCancel(){ + globe.routeLayer.clear(); + this.goBack(); + } + + route(type) { + if (!type) { + return; + } + const state = this.props.location.state || {}; + const fromPoi = state.fromPoi; + const toPoi = state.toPoi; + + if (fromPoi && toPoi) { + const fromLon = fromPoi.pointx; + const fromLat = fromPoi.pointy; + const toLon = toPoi.pointx; + const toLat = toPoi.pointy; + + let promise = null; + + if (type === 'driving') { + promise = globe.routeLayer.routeByDriving(fromLon, fromLat, toLon, toLat, 10); + } else if (type === 'bus') { + const startCity = fromPoi.POI_PATH[0].cname; + const endCity = toPoi.POI_PATH[0].cname; + promise = globe.routeLayer.routeByBus(fromLon, fromLat, toLon, toLat, startCity, endCity, 0); + } else if (type === 'walking') { + promise = globe.routeLayer.routeByWalking(fromLon, fromLat, toLon, toLat); + } + + if (promise) { + this.wrapPromise(promise).then((response) => { + console.log(response); + if (response.route) { + this.setState({ + type: type, + route: response.route, + selectedPathIndex: 0 + }, () => { + this.mapComponent.resizeGlobe(); + }); + } + }, (err) => console.error(err)); + } + } + } + + componentDidMount() { + super.componentDidMount(); + this.route(this.state.type); + } + + onTrafficTypeChange(trafficType) { + this.setState({ + type: trafficType, + route: null, + selectedPathIndex: 0 + }, () => { + this.route(trafficType); + }); + } + + render() { + const route = this.state.route; + if (route) { + let selectedPathIndex = this.state.selectedPathIndex; + if (route.type === 'driving') { + return this.renderDriving(route, selectedPathIndex); + } else if (route.type === 'bus') { + return this.renderBus(route, selectedPathIndex); + } else if (route.type === 'walking') { + return this.renderWalking(route, selectedPathIndex); + } + } + return this.renderNoRoute(); + } + + renderDriving(route, selectedPathIndex) { + if (route && route.paths && route.paths.length > 0) { + if (route.paths.length === 1) { + const path = route.paths[0]; + return ( +
+ {this.renderHeaderMap()} +
+
{this.getPathDetail(route, path, 0, true, true)}
+
+
+ ); + } else { + return ( +
+ {this.renderHeaderMap()} +
+
+ { + route.paths.map((path, index) => { + const [timeSummary, distanceSummary] = this.getTimeDistanceSummary(path); + const tabClassName = selectedPathIndex === index ? classNames(styles.tab, styles.selected) : styles.tab; + return ( +
this.onSelectPath(index)}> +
{timeSummary}
+
{distanceSummary}
+
+ ); + }) + } +
+
+ { + route.paths.map((path, index) => { + return this.getPathDetail(route, path, index, index === selectedPathIndex, false); + }) + } +
+
+
+ ); + } + } else { + return this.renderNoRoute(); + } + } + + renderBus(route, selectedPathIndex) { + if (route && route.transits && route.transits.length > 0) { + return ( +
+ {this.renderHeaderMap()} +
+
+ { + route.transits.map((transit, index) => { + const [timeSummary, distanceSummary] = this.getTimeDistanceSummary(transit); + const tabClassName = selectedPathIndex === index ? classNames(styles.tab, styles.selected) : styles.tab; + return ( +
this.onSelectPath(index)}> +
{timeSummary}
+
{distanceSummary}
+
+ ); + }) + } +
+
+ { + route.transits.map((transit, index) => { + return this.getPathDetail(route, transit, index, index === selectedPathIndex, false); + }) + } +
+
+
+ ); + } else { + return this.renderNoRoute(); + } + } + + renderWalking(route, selectedPathIndex) { + if (route && route.paths && route.paths.length > 0) { + const path = route.paths[0]; + return ( +
+ {this.renderHeaderMap()} +
+
{this.getPathDetail(route, path, 0, true, false)}
+
+
+ ); + } else { + return this.renderNoRoute(); + } + } + + renderNoRoute() { + return ( +
+ {this.renderHeaderMap()} +
+ ); + } + + renderHeaderMap() { + return [ + this.onTrafficTypeChange(e)} onCancel={() => this.onCancel()} />, +
+ this.mapComponent = input} /> +
+ ]; + } + + getPathDetail(route, path, pathIndex, selected, showSummary1) { + const pathDetailClassName = selected ? classNames(styles["path-detail"], styles.selected) : styles["path-detail"] + + return ( +
+ { + (() => { + if (route.type === 'driving') { + let drivingChildren = []; + if (showSummary1) { + const [timeSummary, distanceSummary] = this.getTimeDistanceSummary(path); + drivingChildren.push(
{`${timeSummary} ${distanceSummary}`}
); + } + drivingChildren = drivingChildren.concat([ +
{path.steps.map((step) => step.road).filter((road) => !!road).join(" -> ")}
, +
红绿灯{path.traffic_lights}个 {route.taxi_cost && `打车${parseFloat(route.taxi_cost).toFixed(1)}元`}
+ ]); + return drivingChildren; + } else if (route.type === 'bus') { + const busNames = []; + const transit = path; + if (transit.segments && transit.segments.length > 0) { + transit.segments.forEach((segment) => { + if(segment){ + if (segment.bus && segment.bus.buslines && segment.bus.buslines.length > 0) { + segment.bus.buslines.forEach((busline) => { + if (busline && busline.busName) { + if (busNames.indexOf(busline.busName) < 0) { + busNames.push(busline.busName); + } + } + }); + } + if(segment.railway){ + let { + departure_stop, + arrival_stop + } = segment.railway; + if(departure_stop && departure_stop.name){ + if(departure_stop.name.slice(-1) === '站'){ + busNames.push(departure_stop.name); + }else{ + busNames.push(`${departure_stop.name}站`); + } + } + if(arrival_stop && arrival_stop.name){ + if(arrival_stop.name.slice(-1) === '站'){ + busNames.push(arrival_stop.name); + }else{ + busNames.push(`${arrival_stop.name}站`); + } + } + } + } + }); + } + return [ +
{busNames.join(" -> ")}
, +
花费{parseFloat(transit.cost).toFixed(1)}元 步行{this.getDistanceSummary(transit.walking_distance)}
+ ]; + } else if (route.type === 'walking') { + const [timeSummary, distanceSummary] = this.getTimeDistanceSummary(path); + return [ +
{`${timeSummary} ${distanceSummary}`}
, +
{path.steps.map((step) => step.road).filter((road) => typeof road === 'string' && !!road).join(" -> ")}
, +
详情
+ ]; + } + return false; + })() + } +
+ ); + } + + getTimeDistanceSummary(path) { + let { + distance, + duration + } = path; + + return [this.getTimeSummary(duration), this.getDistanceSummary(distance)]; + } + + getTimeSummary(seconds) { + seconds = parseFloat(seconds); + let allMinutes = Math.round(seconds / 60); + if(allMinutes < 60){ + return `${allMinutes}分钟`; + }else{ + let hours = Math.floor(allMinutes / 60); + let minutes = allMinutes - hours * 60; + if(minutes > 0){ + return `${hours}小时${minutes}分钟`; + }else{ + return `${hours}小时`; + } + } + } + + getDistanceSummary(distance) { + distance = parseFloat(distance); + let distanceSummary = distance >= 1000 ? `${(distance / 1000).toFixed(1)}公里` : `${distance}米`; + return distanceSummary; + } +}; \ No newline at end of file diff --git a/src/webapp/routes/nav/Paths/index.scss b/src/webapp/routes/nav/Paths/index.scss new file mode 100644 index 0000000..703d3f5 --- /dev/null +++ b/src/webapp/routes/nav/Paths/index.scss @@ -0,0 +1,139 @@ +@import "../../../css/variables"; +$header-height: 46px; + +$footer-single-path-detail-height: 77px; +$footer-single-path-height: $footer-single-path-detail-height; + +$footer-multiple-path-tabs-height: 45px; +$footer-multiple-path-detail-height: 60px; +$footer-multiple-path-height: $footer-multiple-path-tabs-height + $footer-multiple-path-detail-height; + + +.map-container{ + position: absolute; + left: 0; + top: $header-height; + width: 100%; +} + +.footer{ + position: absolute; + left: 0; + bottom: 0; + width: 100%; + + .tabs{ + box-sizing: border-box; + height: $footer-multiple-path-tabs-height; + text-align: center; + font-size: 12px; + display: flex; + flex-flow: row nowrap; + + .tab{ + box-sizing: border-box; + height: 100%; + flex: 1 1 auto; + background: #efefef; + border-left: 1px solid #dfdfdf; + border-top: 3px solid transparent; + &.selected{ + background: white; + color: $blue-color; + border-top-color: $blue-color; + } + + .time{ + margin: 2px 0; + } + } + } + + .path-details{ + box-sizing: border-box; + position: relative; + overflow: hidden; + + .path-detail{ + box-sizing: border-box; + position: absolute; + left: 100%; + top: 0; + width: 100%; + height: 100%; + padding: 0 9px; + + &.selected{ + left: 0; + } + + .summary1{ + font-weight: bold; + font-size: 14px; + color: #333; + margin: 4px 0; + } + + .summary2{ + font-size: 14px; + color: #333; + } + + .summary3{ + margin-top: 5px; + font-size: 13px; + color: #999; + } + + .detail-btn{ + color: $blue-color; + } + } + } +} + +.no-path{ + .map-container{ + bottom: 0; + } + + .footer{ + height: 0; + + .path-details{ + height: 0; + } + } +} + +.single-path{ + .map-container{ + bottom: $footer-single-path-height; + } + + .footer{ + height: $footer-single-path-height; + + .path-details{ + height: $footer-single-path-detail-height; + } + } +} + +.multiple-path{ + .map-container{ + bottom: $footer-multiple-path-height; + } + + .footer{ + height: $footer-multiple-path-height; + + .path-details{ + height: $footer-multiple-path-detail-height; + + .summary2{ + margin-top: 9px; + } + } + } +} \ No newline at end of file diff --git a/src/webapp/routes/nav/Paths/route.js b/src/webapp/routes/nav/Paths/route.js new file mode 100644 index 0000000..a439185 --- /dev/null +++ b/src/webapp/routes/nav/Paths/route.js @@ -0,0 +1,7 @@ +const route = { + path: 'paths', + component: require('./index'), + childRoutes: [] +}; + +export default route; \ No newline at end of file diff --git a/src/webapp/routes/nav/Search/index.jsx b/src/webapp/routes/nav/Search/index.jsx new file mode 100644 index 0000000..492e7bd --- /dev/null +++ b/src/webapp/routes/nav/Search/index.jsx @@ -0,0 +1,192 @@ +import React from 'react'; +import TrafficTypes from 'webapp/components/TrafficTypes'; +import RouteComponent from 'webapp/components/RouteComponent'; +import classNames from 'classnames'; +import styles from './index.scss'; +import Kernel from 'world/Kernel'; +import Service from 'world/Service'; +import { globe } from 'webapp/components/Map'; + +export default class Nav extends RouteComponent { + + constructor(props) { + super(props); + this.fromPoi = null; + this.toPoi = null; + this.pageCapacity = 10; + this.searchDistance = Kernel.REAL_EARTH_RADIUS; + this.isFromLastFocused = true; + this.state = { + type: 'driving',//bus,walking + fromPois: [], + toPois: [], + routes: [] + }; + } + + onCancel() { + this.goBack(); + } + + onKeyPress(e) { + const isFrom = e.target === this.fromInput; + const keyword = e.target.value; + if (e.key === "Enter") { + if(keyword){ + this.searchPois(isFrom, keyword); + } + } + } + + onFromFocus() { + this.isFromLastFocused = true; + this.setState({ + toPois: [] + }); + } + + onToFocus() { + this.isFromLastFocused = false; + this.setState({ + fromPois: [] + }); + } + + onClickFromSearchIcon(){ + this.fromInput.focus(); + const keyword = this.fromInput.value; + if(keyword){ + this.searchPois(true, keyword); + } + } + + onClickToSearchIcon(){ + this.toInput.focus(); + const keyword = this.toInput.value; + if(keyword){ + this.searchPois(false, keyword); + } + } + + onClickSearchIcon(isFrom, keyword) { + if(keyword){ + this.searchPois(isFrom, keyword); + } + } + + searchPois(isFrom, keyword) { + if (!keyword) { + return; + } + const promise = Service.searchNearby(keyword, this.searchDistance, 'Auto', this.pageCapacity); + this.wrapPromise(promise).then((response) => { + let pois = null; + if (response.detail) { + pois = response.detail.pois; + } + if (!pois) { + pois = []; + } + if (isFrom) { + this.setState({ + fromPois: pois + }); + } else { + this.setState({ + toPois: pois + }); + } + }); + } + + onClickPoi(poi, isFromPoi) { + if (isFromPoi) { + this.fromPoi = poi; + this.fromInput.value = poi.name || ""; + this.setState({ + fromPois: [] + }); + if (this.toPoi) { + this.route(this.fromPoi, this.toPoi); + } else { + this.toInput.focus(); + } + } else { + this.toPoi = poi; + this.toInput.value = poi.name || ""; + this.setState({ + toPois: [] + }) + if (this.fromPoi) { + this.route(this.fromPoi, this.toPoi); + } else { + this.fromInput.focus(); + } + } + } + + onTrafficTypeChange(trafficType) { + this.setState({ + type: trafficType + }); + if (this.fromPoi && this.toPoi) { + this.route(this.fromPoi, this.toPoi); + } + } + + route(fromPoi, toPoi) { + if (fromPoi && toPoi) { + this.props.router.push({ + pathname: '/nav/paths', + state: { + type: this.state.type, + fromPoi: fromPoi, + toPoi: toPoi + } + }); + } + } + + 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; + + return ( +
+ 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.toInput = input; }} onFocus={(e) => this.onToFocus(e)} onKeyPress={(e) => this.onKeyPress(e)} /> + this.onClickToSearchIcon()}> +
+
+ {/*
+ +
*/} +
+
+ { + pois.map((poi) => { + return ( +
{ this.onClickPoi(poi, this.isFromLastFocused); }}> +
{poi.name}
+
{poi.addr}
+
+ ); + }) + } +
+
+ ); + } +}; \ No newline at end of file diff --git a/src/webapp/routes/nav/Search/index.scss b/src/webapp/routes/nav/Search/index.scss new file mode 100644 index 0000000..f487641 --- /dev/null +++ b/src/webapp/routes/nav/Search/index.scss @@ -0,0 +1,102 @@ +@import "../../../css/variables"; +$header-height: 46px; +$input-container-height: $header-height; +$input-height: 26px; +// $exchange-size: 60px; +$exchange-size: 0; + +.search-section{ + position: relative; + box-sizing: border-box; + height: $input-container-height * 2; + padding-left: 15px; + border-bottom: 1px solid #d3d3d3; + overflow: hidden; + + .inputs-container{ + position: absolute; + left: 15px; + right: $exchange-size; + top: 0; + height: 100%; + + .input-container{ + position: relative; + box-sizing: border-box; + height: 50%; + padding: 0 16px; + + .from-icon,.to-icon{ + position: absolute; + left: 0; + top: 0; + height: $input-container-height; + line-height: $input-container-height; + } + .from-icon{ + color: $blue-color; + font-size: 16px; + } + .to-icon{ + color: #fe5f5f; + font-size: 13px; + } + input{ + width: 100%; + height: $input-height; + line-height: $input-height; + color: #333; + font-size: 15px; + margin-top: ($input-container-height - $input-height) / 2; + } + :global(.icon-search){ + position: absolute; + font-size: 16px; + right: 15px; + top: 15px; + color: #888; + } + &.from{ + border-bottom: 1px solid #d3d3d3; + } + } + } + + .exchange{ + position: absolute; + top: 0; + right: 0; + width: $exchange-size; + height: 100%; + text-align: center; + + i{ + color: gray; + font-size: 24px; + line-height: $input-container-height * 2; + } + } +} + +.pois{ + position: absolute; + left: 0; + width: 100%; + top: $header-height * 3; + bottom: 0; + overflow-y: auto; + + .poi{ + padding: 15px; + .name{ + color: #333; + font-size: 15px; + } + .address{ + color: gray; + font-size: 12px; + margin-top: 12px; + } + border-bottom: 1px solid #d3d3d3; + } +} diff --git a/src/webapp/routes/nav/Search/route.js b/src/webapp/routes/nav/Search/route.js new file mode 100644 index 0000000..13d2c90 --- /dev/null +++ b/src/webapp/routes/nav/Search/route.js @@ -0,0 +1,7 @@ +const route = { + path: 'search', + component: require('./index'), + childRoutes: [] +}; + +export default route; \ No newline at end of file diff --git a/src/webapp/routes/nav/route.js b/src/webapp/routes/nav/route.js new file mode 100644 index 0000000..0075bbe --- /dev/null +++ b/src/webapp/routes/nav/route.js @@ -0,0 +1,9 @@ +const route = { + path: 'nav', + childRoutes: [ + require('./Paths/route'), + require('./Search/route') + ] +}; + +export default route; \ No newline at end of file diff --git a/src/webapp/routes/nearby/Result/index.jsx b/src/webapp/routes/nearby/Result/index.jsx new file mode 100644 index 0000000..856b89c --- /dev/null +++ b/src/webapp/routes/nearby/Result/index.jsx @@ -0,0 +1,220 @@ +import React, { Component } from 'react'; +import ReactDOM from 'react-dom'; +import classNames from 'classnames'; +import styles from './index.scss'; +import RouteComponent from 'webapp/components/RouteComponent'; +import Search from 'webapp/components/Search'; +import Map, { globe } from 'webapp/components/Map'; +import Kernel from 'world/Kernel'; +import MathUtils from 'world/math/Utils'; + +export default class Result extends RouteComponent { + + constructor(props) { + super(props); + this.pageCapacity = 10; + this.distance = Kernel.REAL_EARTH_RADIUS; + this.nameClassNames = classNames(styles.name, "ellipsis"); + this.addressClassNames = classNames(styles.address, "ellipsis"); + this.roadIcon = "icon-road"; + this.leftIcon = "icon-angle-left"; + this.rightIcon = "icon-angle-right"; + this.state = { + loading: false, + total: 0, + pageIndex: 0, + location: null, + pois: [], + list: true + }; + } + + componentDidMount() { + super.componentDidMount(); + this.domNode.style.opacity = 1; + this.search(0); + } + + onMap() { + this.setState({ + list: false + }); + } + + onList() { + this.setState({ + list: true + }); + } + + onClickPoi(poi){ + if(typeof poi.pointx === 'number' && typeof poi.pointy === 'number'){ + globe.centerTo(poi.pointx, poi.pointy); + } + this.setState({ + list: false + }); + } + + onFocus(){ + this.onCancel(); + } + + onCancel() { + // normal code + // globe.poiLayer.clear(); + // this.goBack(); + + //fix for Xiaomi browser + this.domNode.style.opacity = 0; + // console.log(this.domNode.clientWidth); + setTimeout(() => { + globe.poiLayer.clear(); + this.goBack(); + }, 50); + } + + onPrevPage() { + this.search(this.state.pageIndex - 1); + } + + onNextPage() { + this.search(this.state.pageIndex + 1); + } + + isFromMapBaseRoute(){ + let fromMapBase = false; + const prevLocation = this.getPreviousLocation(); + if(prevLocation && prevLocation.pathname === "/map/base"){ + fromMapBase = true; + } + return fromMapBase; + } + + search(pageIndex) { + const distance = this.distance; + const { + query: { + keyword + } + } = this.props.location; + if (this.hasBeenMounted() && keyword) { + // const fromMapBase = this.isFromMapBaseRoute(); + // console.log(`fromMapBase: ${fromMapBase}`); + // const promise = fromMapBase ? globe.poiLayer.searchByCurrentCity(keyword, 'Auto', this.pageCapacity, pageIndex) : globe.poiLayer.searchNearby(keyword, distance, 'Auto', this.pageCapacity, pageIndex); + const promise = globe.poiLayer.searchNearby(keyword, distance, 'Auto', this.pageCapacity, pageIndex); + this.wrapPromise(promise).then((response) => { + if (response) { + this.setState({ + total: response.info.total, + pageIndex: pageIndex, + location: response.location, + pois: response.detail.pois + }); + } + }); + } + } + + render() { + const { + loading, + total, + pageIndex, + location, + pois + } = this.state; + + let { + query: { + keyword + } + } = this.props.location; + keyword = keyword || ""; + + const totalPageCount = Math.ceil(total / this.pageCapacity); + const showPrevPage = pageIndex > 0; + const showNextPage = pageIndex < totalPageCount - 1; + + return ( +
this.domNode = input}> + this.onMap()} onList={() => this.onList()} onCancel={() => this.onCancel()} onFocus={() => this.onFocus()} /> + { + this.state.list ? ( +
+ { + !loading && total === 0 && ( +
{keyword ? `未在附近找到与${keyword}相关的地点` : `请输入要搜索的兴趣点`}
+ ) + } + { + total > 0 && ( +
+ { + this.state.pois.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); + distanceLabel = distance > 1000 ? `${(distance/1000).toFixed(1)}公里` : `${Math.floor(distance)}米`; + } + + return ( +
this.onClickPoi(poi)}> +
{index + 1}
+
+
{poi.name}
+
{poi.addr}
+
+ { + distanceLabel && ( +
+ +
{distanceLabel}
+
+ ) + } +
+ ); + }) + } +
+ ) + } + { + total > 0 && ( +
+ { + showPrevPage && ( +
this.onPrevPage()}> + + 上一页 +
+ ) + } +
+ {pageIndex + 1} / {totalPageCount} +
+ { + showNextPage && ( +
this.onNextPage()}> + 下一页 + +
+ ) + } +
+ ) + } +
+ ) : ( +
+ +
+ ) + } +
+ ); + } +}; \ No newline at end of file diff --git a/src/webapp/routes/nearby/Result/index.scss b/src/webapp/routes/nearby/Result/index.scss new file mode 100644 index 0000000..f87d98c --- /dev/null +++ b/src/webapp/routes/nearby/Result/index.scss @@ -0,0 +1,147 @@ +@import "../../../css/variables"; +$index-size: 16px; +$info-left: 40px; +$distance-width: 60px; +$index-left: ($info-left - $index-size) / 2; +$main-color: gray; +$header-height: 0; +$footer-height: 48px; +$line: 1px solid #d3d3d3; + +.map, .list{ + position: absolute; + left: 0; + right: 0; + top: 45px; + bottom: 0; +} + +.list { + .header{ + + } + + .not-found, .pois{ + position: absolute; + left: 0; + right: 0; + } + + .not-found{ + top: 50%; + margin-top: -16px; + padding: 0 30px; + text-align: center; + color: $main-color; + font-size: 12px; + } + + .pois { + top: $header-height; + bottom: $footer-height; + overflow-y: auto; + .poi { + position: relative; + height: 64px; + box-sizing: border-box; + >* { + position: absolute; + } + .index { + left: $index-left; + top: 10px; + width: $index-size; + height: $index-size; + border-radius: 50%; + background: red; + color: white; + text-align: center; + line-height: 16px; + font-size: 12px; + } + .info, + .distance { + top: 0; + height: 100%; + box-sizing: border-box; + border-bottom: $line; + >* { + margin-top: 8px; + } + } + &:last-child { + .info, + .distance { + border-bottom: 0; + } + } + .info { + left: $info-left; + right: $distance-width; + .name { + font-size: 15px; + color: #333; + } + .address { + font-size: 12px; + color: $main-color; + } + } + .distance { + right: 0; + width: $distance-width; + text-align: center; + box-sizing: border-box; + padding-top: 3px; + i { + color: $blue-color; + } + div { + color: $main-color; + font-size: 12px; + text-align: center; + } + } + } + } + + .footer { + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: $footer-height; + border-top: $line; + >* { + line-height: $footer-height; + } + .prev-page, + .next-page { + position: absolute; + top: 0; + bottom: 0; + color: $blue-color; + font-size: 14px; + i { + font-size: 14px; + } + } + .prev-page { + left: 12px; + span { + margin-left: 5px; + } + } + .next-page { + right: 12px; + span { + margin-right: 5px; + } + } + .current-page { + text-align: center; + color: $main-color; + font-size: 12px; + } + } +} \ No newline at end of file diff --git a/src/webapp/routes/nearby/Result/route.js b/src/webapp/routes/nearby/Result/route.js new file mode 100644 index 0000000..2a2fd65 --- /dev/null +++ b/src/webapp/routes/nearby/Result/route.js @@ -0,0 +1,7 @@ +const route = { + path: 'result', + component: require('./index'), + childRoutes: [] +}; + +export default route; \ No newline at end of file diff --git a/src/webapp/routes/nearby/Search/index.jsx b/src/webapp/routes/nearby/Search/index.jsx new file mode 100644 index 0000000..30567d4 --- /dev/null +++ b/src/webapp/routes/nearby/Search/index.jsx @@ -0,0 +1,135 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import styles from './index.scss'; +import RouteComponent from 'webapp/components/RouteComponent'; +import Search from 'webapp/components/Search'; + +export default class Nearby extends RouteComponent{ + + constructor(props){ + super(props); + this.state = {}; + this.structure = [ + [ + [{ + type: "big", + value: "美食畅饮", + className: styles.food, + fontIcon: "icon-food" + }, "甜点饮品"], + [ + "快餐", "火锅", "咖啡厅" + ], + [ + "中餐", "川菜", "西餐" + ] + ], + [ + [{ + type: "big", + value: "酒店宾馆", + className: styles.hotel, + fontIcon: "icon-bed" + }], + [ + "快捷酒店", "星级酒店" + ], + [ + "旅馆", "青年旅社" + ] + ], + [ + [{ + type: "big", + value: "娱乐休闲", + className: styles.recreation, + fontIcon: "icon-glass" + }, "酒吧"], + [ + "电影院", "景点", "KTV" + ], + [ + "网吧", "购物", "洗浴足疗" + ] + ], + [ + [{ + type: "big", + value: "交通设施", + className: styles.traffic, + fontIcon: "icon-bus" + }], + [ + "加油站", "地铁站" + ], + [ + "公交站", "停车场" + ] + ], + [ + [{ + type: "big", + value: "生活服务", + className: styles.life, + fontIcon: "icon-basket" + }, "美容美发"], + [ + "ATM", "公厕", "药店" + ], + [ + "银行", "超市", "医院" + ] + ] + ]; + } + + onSearch(keyword){ + const path = `/nearby/result?keyword=${keyword}`; + this.context.router.push(path); + } + + onCancel(){ + this.props.router.goBack(); + } + + render(){ + return ( +
+ this.onCancel()} onSearch={(keyword) => this.onSearch(keyword)} /> +
+ { + this.structure.map((card, index) => { + return ( +
+ { + card.map((column, index) => { + return ( +
+ { + column.map((item, index) => { + if(item.type === 'big'){ + return ( +
{this.onSearch(item.value)}} key={`item-${index}`}> + +
{item.value}
+
+ ); + }else{ + return
{this.onSearch(item)}} key={`item-${item}`}>{item}
; + } + }) + } +
+ ) + }) + } +
+ ) + }) + } +
+
+ ); + } +}; \ No newline at end of file diff --git a/src/webapp/routes/nearby/Search/index.scss b/src/webapp/routes/nearby/Search/index.scss new file mode 100644 index 0000000..96d07c3 --- /dev/null +++ b/src/webapp/routes/nearby/Search/index.scss @@ -0,0 +1,74 @@ +$small-item-height: 36px; +$item-spacing: 2px; +$big-item-height: $small-item-height * 2 + $item-spacing; + +.root{ + position: relative; + height: 100%; +} + +.cards{ + position: absolute; + left: 0; + right: 0; + top: 40px; + bottom: 0; + overflow-y: auto; + text-align: center; + margin: 0 15px; + + .card{ + display: flex; + flex-direction: row; + flex-wrap: nowrap; + margin-top: 18px; + + .column{ + flex: 1 1 33.333333%; + padding: 0 $item-spacing/2; + + .big-item, .small-item{ + font-size: 12px; + border-radius: 2px; + margin-bottom: $item-spacing; + } + + .big-item{ + height: $big-item-height; + color: white; + + &.food{ + background: #f73838; + } + + &.hotel{ + background: #f3a61f; + } + + &.recreation{ + background: #61b51d; + } + + &.traffic{ + background: #0079ff; + } + + &.life{ + background: #9584dd; + } + + .icon{ + font-size: 24px; + margin: 13px; + } + } + + .small-item{ + height: $small-item-height; + line-height: $small-item-height; + background: #ebebeb; + color: #333; + } + } + } +} \ No newline at end of file diff --git a/src/webapp/routes/nearby/Search/route.js b/src/webapp/routes/nearby/Search/route.js new file mode 100644 index 0000000..13d2c90 --- /dev/null +++ b/src/webapp/routes/nearby/Search/route.js @@ -0,0 +1,7 @@ +const route = { + path: 'search', + component: require('./index'), + childRoutes: [] +}; + +export default route; \ No newline at end of file diff --git a/src/webapp/routes/nearby/route.js b/src/webapp/routes/nearby/route.js new file mode 100644 index 0000000..56a812e --- /dev/null +++ b/src/webapp/routes/nearby/route.js @@ -0,0 +1,9 @@ +const route = { + path: 'nearby', + childRoutes: [ + require('./Result/route'), + require('./Search/route') + ] +}; + +export default route; \ No newline at end of file diff --git a/src/webapp/routes/route.js b/src/webapp/routes/route.js new file mode 100644 index 0000000..186ec51 --- /dev/null +++ b/src/webapp/routes/route.js @@ -0,0 +1,26 @@ +const route = { + childRoutes: [{ + path: '/', + indexRoute: { onEnter: (nextState, replace) => replace('/index/index') }, + childRoutes: [ + require('./index/route'), + require('./map/route'), + require('./nav/route'), + require('./nearby/route'), + require('./404/route') + ] + // getChildRoutes(partialNextState, callback){ + // require.ensure([], function(){ + // callback(null, [ + // require('./index/route'), + // require('./map/route'), + // require('./nav/route'), + // require('./nearby/route'), + // require('./404/route') + // ]) + // }, 'root-childroutes') + // } + }] +}; + +export default route; \ No newline at end of file diff --git a/template.html b/src/webapp/template.html old mode 100755 new mode 100644 similarity index 65% rename from template.html rename to src/webapp/template.html index 836977f..a3bb228 --- a/template.html +++ b/src/webapp/template.html @@ -6,7 +6,7 @@ - + @@ -15,10 +15,8 @@ - - - - - + +
+ \ No newline at end of file diff --git a/src/world/Extent.ts b/src/world/Extent.ts deleted file mode 100755 index 6148548..0000000 --- a/src/world/Extent.ts +++ /dev/null @@ -1,37 +0,0 @@ -import TileGrid from './TileGrid'; - -class Extent{ - constructor(private minLon: number, private minLat: number, private maxLon: number, private maxLat: number, private tileGrid: TileGrid){ - - } - - getMinLon(){ - return this.minLon; - } - - getMinLat(){ - return this.minLat; - } - - getMaxLon(){ - return this.maxLon; - } - - getMaxLat(){ - return this.maxLat; - } - - getTileGrid(){ - return this.tileGrid; - } - - toJson(){ - return [this.minLon, this.minLat, this.maxLon, this.maxLat]; - } - - static union(extents: Extent[]): Extent{ - return null; - } -} - -export = Extent; \ No newline at end of file diff --git a/src/world/Globe.ts b/src/world/Globe.ts deleted file mode 100755 index b5664eb..0000000 --- a/src/world/Globe.ts +++ /dev/null @@ -1,270 +0,0 @@ -import Kernel = require("./Kernel"); -import Utils = require("./Utils"); -import LocationService, { LocationData } from "./LocationService"; -import Renderer = require("./Renderer"); -import Camera, { CameraCore } from "./Camera"; -import Scene = require("./Scene"); -import ImageUtils = require("./Image"); -import EventHandler = require("./EventHandler"); -import TiledLayer = require("./layers/TiledLayer"); -import { GoogleTiledLayer, GoogleLabelLayer } from "./layers/Google"; -import { AutonaviTiledLayer, AutonaviLabelLayer } from "./layers/Autonavi"; -import LabelLayer from "./layers/LabelLayer"; -import TrafficLayer from "./layers/TrafficLayer"; -import { QihuTrafficLayer } from "./layers/Qihu"; -import Atmosphere = require("./graphics/Atmosphere"); -import PoiLayer = require("./layers/PoiLayer"); - -const initLevel = Utils.isMobile() ? 11 : 3; - -type RenderCallback = () => void; - -class Globe { - renderer: Renderer = null; - scene: Scene = null; - camera: Camera = null; - tiledLayer: TiledLayer = null; - labelLayer: LabelLayer = null; - trafficLayer: TrafficLayer = null; - poiLayer: PoiLayer = null; - debugStopRefreshTiles: boolean = false; - private readonly REFRESH_INTERVAL: number = 150; //Globe自动刷新时间间隔,以毫秒为单位 - private lastRefreshTimestamp: number = -1; - private lastRefreshCameraCore: CameraCore = null; - private eventHandler: EventHandler = null; - private allRefreshCount: number = 0; - private realRefreshCount: number = 0; - private beforeRenderCallbacks: RenderCallback[] = []; - private afterRenderCallbacks: RenderCallback[] = []; - - constructor(private canvas: HTMLCanvasElement, level: number = initLevel, lonlat: number[] = [116.3975, 39.9085]) { - Kernel.globe = this; - Kernel.canvas = canvas; - this.renderer = new Renderer(canvas, this._onBeforeRender.bind(this), this._onAfterRender.bind(this)); - this.scene = new Scene(); - var radio = canvas.width / canvas.height; - this.camera = new Camera(30, radio, 1, Kernel.EARTH_RADIUS * 2, level, lonlat); - this.renderer.setScene(this.scene); - this.renderer.setCamera(this.camera); - - this.labelLayer = new AutonaviLabelLayer(); - // this.labelLayer = new GoogleLabelLayer(); - this.scene.add(this.labelLayer); - // this.trafficLayer = new QihuTrafficLayer(); - // this.trafficLayer.visible = false; - // this.scene.add(this.trafficLayer); - var atmosphere = Atmosphere.getInstance(); - this.scene.add(atmosphere); - this.poiLayer = PoiLayer.getInstance(); - this.scene.add(this.poiLayer); - - this.renderer.setIfAutoRefresh(true); - this.eventHandler = new EventHandler(canvas); - - var tiledLayer = new GoogleTiledLayer("Satellite"); - // var tiledLayer = new AutonaviTiledLayer("Satellite"); - this.setTiledLayer(tiledLayer); - - // if(Utils.isMobile() && window.navigator.geolocation){ - // window.navigator.geolocation.getCurrentPosition((position: Position) => { - // // var str = `accuracy:${position.coords.accuracy},heading:${position.coords.heading},speed:${position.coords.speed}`; - // // alert(str); - // var lon = position.coords.longitude; - // var lat = position.coords.latitude; - // this.showLocation(lon, lat); - // }); - // } - - Utils.subscribe('location', (data: LocationData) => { - console.info(data); - this.afterRenderCallbacks.push(() => { - this.showLocation(data); - }); - }); - LocationService.getRobustLocation(); - LocationService.getLocation(); - // LocationService.watchPosition(); - } - - showLocation(locationData: LocationData) { - var lon = locationData.lng; - var lat = locationData.lat; - // this.poiLayer.clear(); - // this.poiLayer.addPoi(lon, lat, "", "", "", ""); - this.eventHandler.moveLonLatToCanvas(lon, lat, this.canvas.width / 2, this.canvas.height / 2); - var accuracy = locationData.accuracy; - var level: number = 8; - if (accuracy <= 100) { - level = 16; - } else if (accuracy <= 1000) { - level = 13; - } else { - level = 11; - } - this.setLevel(level); - } - - setTiledLayer(tiledLayer: TiledLayer) { - //在更换切片图层的类型时清空缓存的图片 - ImageUtils.clear(); - if (this.tiledLayer) { - var b = this.scene.remove(this.tiledLayer); - if (!b) { - console.error("this.scene.remove(this.tiledLayer)失败"); - } - this.scene.tiledLayer = null; - } - this.tiledLayer = tiledLayer; - this.scene.add(this.tiledLayer, true); - this.refresh(true); - } - - showLabelLayer() { - if (this.labelLayer) { - this.labelLayer.visible = true; - } - } - - hideLabelLayer() { - if (this.labelLayer) { - this.labelLayer.visible = false; - } - } - - showTrafficLayer() { - if (this.trafficLayer) { - this.trafficLayer.visible = true; - } - } - - hideTrafficLayer() { - if (this.trafficLayer) { - this.trafficLayer.visible = false; - } - } - - getLevel() { - return this.camera.getLevel(); - } - - zoomIn() { - this.setLevel(this.getLevel() + 1); - } - - setLevel(level: number) { - if (this.camera) { - this.camera.setLevel(level); - } - } - - isAnimating(): boolean { - return this.camera.isAnimating(); - } - - animateToLevel(level: number, cb?: () => void) { - if (!this.isAnimating()) { - if (level < Kernel.MIN_LEVEL) { - level = Kernel.MIN_LEVEL; - } - if (level > Kernel.MAX_LEVEL) { - level = Kernel.MAX_LEVEL; - } - if (level !== this.getLevel()) { - this.camera.animateToLevel(level, cb); - } - } - } - - animateOut(cb?: () => void) { - this.animateToLevel(this.getLevel() - 1, cb); - } - - animateIn(cb?: () => void) { - this.animateToLevel(this.getLevel() + 1, cb); - } - - private _onBeforeRender(renderer: Renderer) { - // this.beforeRenderCallbacks.forEach((callback) => callback()); - this.refresh(); - } - - private _onAfterRender(render: Renderer) { - this.afterRenderCallbacks.forEach((callback) => callback()); - this.afterRenderCallbacks = []; - } - - // private _tick() { - // try { - // //如果refresh方法出现异常而且没有捕捉,那么就会导致无法继续设置setTimeout,从而无法进一步更新切片 - // this.refresh(); - // } catch (e) { - // console.error(e); - // } - // setTimeout(() => { - // this._tick(); - // }, this.REFRESH_INTERVAL); - // } - - logRefreshInfo() { - console.log(this.realRefreshCount, this.allRefreshCount, this.realRefreshCount / this.allRefreshCount); - } - - refresh(force: boolean = false) { - this.allRefreshCount++; - var timestamp = Date.now(); - - //先更新camera中的各种矩阵 - this.camera.update(force); - - if (!this.tiledLayer || !this.scene || !this.camera) { - return; - } - - if (this.debugStopRefreshTiles) { - return; - } - - var newCameraCore = this.camera.getCameraCore(); - // var isNeedRefresh = force || !newCameraCore.equals(this.cameraCore); - var isNeedRefresh = false; - if (force) { - isNeedRefresh = true; - } else { - if (newCameraCore.equals(this.lastRefreshCameraCore)) { - isNeedRefresh = false; - } else { - isNeedRefresh = timestamp - this.lastRefreshTimestamp >= this.REFRESH_INTERVAL; - } - } - - this.tiledLayer.updateSubLayerCount(); - - if (isNeedRefresh) { - this.realRefreshCount++; - this.lastRefreshTimestamp = timestamp; - this.lastRefreshCameraCore = newCameraCore; - this.tiledLayer.refresh(); - } - - this.tiledLayer.updateTileVisibility(); - - var a = !!(this.labelLayer && this.labelLayer.visible); - var b = !!(this.trafficLayer && this.trafficLayer.visible); - if (a || b) { - var lastLevelTileGrids = this.tiledLayer.getLastLevelVisibleTileGrids(); - if (a) { - this.labelLayer.updateTiles(this.getLevel(), lastLevelTileGrids); - } - if (b) { - this.trafficLayer.updateTiles(this.getLevel(), lastLevelTileGrids); - } - } - } - - getExtents(level?: number) { - return this.tiledLayer.getExtents(level); - } - -} - -export = Globe; \ No newline at end of file diff --git a/src/world/Kernel.ts b/src/world/Kernel.ts deleted file mode 100755 index 29363cc..0000000 --- a/src/world/Kernel.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {WebGLRenderingContextExtension} from './Definitions'; -import Globe = require("./Globe"); -import Renderer = require("./Renderer"); - -const REAL_EARTH_RADIUS = 6378137; -const EARTH_RADIUS = 500; -const SCALE_FACTOR = EARTH_RADIUS / REAL_EARTH_RADIUS; -const MAX_PROJECTED_COORD = Math.PI * EARTH_RADIUS; -const MAX_REAL_RESOLUTION = 156543.03392800014; -const MAX_RESOLUTION = MAX_REAL_RESOLUTION * SCALE_FACTOR; - -class Kernel{ - static gl:WebGLRenderingContextExtension = null; - static canvas:HTMLCanvasElement = null; - static globe:Globe = null; - static idCounter:number = 0; //Object3D对象的唯一标识 - static readonly version:string = "0.4.4"; - static readonly SCALE_FACTOR:number = SCALE_FACTOR; - static readonly EARTH_RADIUS:number = EARTH_RADIUS; - static readonly MAX_RESOLUTION:number = MAX_RESOLUTION; - static readonly MAX_REAL_RESOLUTION: number = MAX_REAL_RESOLUTION; - static readonly MAX_PROJECTED_COORD:number = MAX_PROJECTED_COORD; - static readonly BASE_LEVEL:number = 6; //渲染的基准层级,从该层级开始segment为1 - static readonly MAX_LEVEL:number = 18; - static readonly MIN_LEVEL:number = 2; - static readonly proxy:string = ""; -} - -export = Kernel; \ No newline at end of file diff --git a/src/world/LocationService.ts b/src/world/LocationService.ts deleted file mode 100755 index 833fb7c..0000000 --- a/src/world/LocationService.ts +++ /dev/null @@ -1,60 +0,0 @@ -import Utils = require('./Utils'); - -//http://lbs.qq.com/tool/component-geolocation.html - -export class LocationData{ - module: string;//geolocation - type: string;//h5,ip - adcode: string;//110105 - nation: string;//中国 - province: string;//北京市 - city: string;//北京市 - district: string;//朝阳区 - addr: string;//朝阳区崔各庄乡顺白路何各庄村公交站西南 - lat: number; - lng: number; - accuracy: number;//25 -} - -const targetOrigin:string = 'https://apis.map.qq.com'; - -var iframe = document.createElement("iframe"); - -class LocationService{ - private static init(){ - window.addEventListener('message', function(event){ - var data:LocationData = event.data; - if(data && data.module === 'geolocation'){ - Utils.publish('location', event.data); - } - }, false); - - iframe.setAttribute("width", "0"); - iframe.setAttribute("height", "0"); - iframe.setAttribute("frameborder", "0"); - iframe.setAttribute("scrolling", "no"); - iframe.style.display = "none"; - iframe.setAttribute("src", `${targetOrigin}/tools/geolocation?key=YLZBZ-XDPKU-LWMV6-2WNPB-PL5W5-H6BGL&referer=WebGlobe`); - document.body.appendChild(iframe); - } - - public static getLocation(){ - iframe.contentWindow.postMessage('getLocation', targetOrigin); - } - - public static getRobustLocation(){ - iframe.contentWindow.postMessage('getLocation.robust', targetOrigin); - } - - public static watchPosition(){ - iframe.contentWindow.postMessage('watchPosition', targetOrigin); - } - - public static clearWatch(){ - iframe.contentWindow.postMessage('clearWatch', targetOrigin); - } -} - -(LocationService as any).init(); - -export default LocationService; \ No newline at end of file diff --git a/src/world/Renderer.ts b/src/world/Renderer.ts deleted file mode 100755 index 09370e3..0000000 --- a/src/world/Renderer.ts +++ /dev/null @@ -1,101 +0,0 @@ -import Kernel = require("./Kernel"); -import Scene = require("./Scene"); -import Camera from "./Camera"; -import { WebGLRenderingContextExtension, WebGLProgramExtension } from "./Definitions"; - -class Renderer { - scene: Scene = null; - camera: Camera = null; - autoRefresh: boolean = false; - - constructor( - private canvas: HTMLCanvasElement, - private onBeforeRender?: (renderer: Renderer) => void, - private onAfterRender?: (renderer: Renderer) => void) { - - var gl: WebGLRenderingContextExtension; - - function initWebGL(canvas: HTMLCanvasElement) { - try { - var contextList = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"]; - for (var i = 0; i < contextList.length; i++) { - gl = canvas.getContext(contextList[i], { - antialias: true - }) as WebGLRenderingContextExtension; - if (gl) { - Kernel.gl = gl; - (window).gl = gl; - Kernel.canvas = canvas; - break; - } - } - } catch (e) { } - } - - initWebGL(canvas); - - if (!gl) { - console.debug("浏览器不支持WebGL或将WebGL禁用!"); - return; - } - - Kernel.gl.clear(Kernel.gl.COLOR_BUFFER_BIT | Kernel.gl.DEPTH_BUFFER_BIT); - gl.clearColor(0, 0, 0, 1); - - gl.enable(gl.DEPTH_TEST); - gl.depthFunc(gl.LEQUAL); - gl.depthMask(true);//允许写入深度 - - gl.enable(gl.CULL_FACE); //一定要启用裁剪,否则显示不出立体感 - gl.frontFace(gl.CCW);//指定逆时针方向为正面 - gl.cullFace(gl.BACK); //裁剪掉背面 - - //gl.enable(gl.TEXTURE_2D);//WebGL: INVALID_ENUM: enable: invalid capability - } - - render(scene: Scene, camera: Camera) { - var gl = Kernel.gl; - var canvas = Kernel.canvas; - gl.viewport(0, 0, canvas.width, canvas.height); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - gl.clearColor(0, 0, 0, 1); - // gl.enable(gl.DEPTH_TEST); - // gl.depthFunc(gl.LEQUAL); - // gl.depthMask(true); - camera.update(); - if(this.onBeforeRender){ - this.onBeforeRender(this); - } - scene.draw(camera); - if(this.onAfterRender){ - this.onAfterRender(this); - } - } - - setScene(scene: Scene) { - this.scene = scene; - } - - setCamera(camera: Camera) { - this.camera = camera; - } - - private _tick() { - if (this.scene && this.camera) { - this.render(this.scene, this.camera); - } - - if (this.autoRefresh) { - window.requestAnimationFrame(this._tick.bind(this)); - } - } - - setIfAutoRefresh(auto: boolean) { - this.autoRefresh = auto; - if (this.autoRefresh) { - this._tick(); - } - } -} - -export = Renderer; \ No newline at end of file diff --git a/src/world/Scene.ts b/src/world/Scene.ts deleted file mode 100755 index 5ce025b..0000000 --- a/src/world/Scene.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {Drawable} from './Definitions.d'; -import GraphicGroup = require('./GraphicGroup'); -import TiledLayer = require("./layers/TiledLayer"); - -class Scene extends GraphicGroup{ - tiledLayer: TiledLayer; -} - -export = Scene; \ No newline at end of file diff --git a/src/world/geometries/Geometry.ts b/src/world/geometries/Geometry.ts deleted file mode 100755 index d17e4a7..0000000 --- a/src/world/geometries/Geometry.ts +++ /dev/null @@ -1,5 +0,0 @@ -interface Geometry{ - destroy():void -} - -export = Geometry; \ No newline at end of file diff --git a/src/world/geometries/TileGeometry.ts b/src/world/geometries/TileGeometry.ts deleted file mode 100755 index c67e35e..0000000 --- a/src/world/geometries/TileGeometry.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Vertice = require("./MeshVertice"); -import Triangle = require("./Triangle"); -import Mesh = require("./Mesh"); - -class TileGeometry extends Mesh { - constructor(public vertices: Vertice[], public triangles: Triangle[]) { - super(); - } -} - -export = TileGeometry; \ No newline at end of file diff --git a/src/world/graphics/Locator.ts b/src/world/graphics/Locator.ts deleted file mode 100755 index fd317cc..0000000 --- a/src/world/graphics/Locator.ts +++ /dev/null @@ -1,5 +0,0 @@ -class Locator{ - -} - -export default Locator; \ No newline at end of file diff --git a/src/world/layers/PoiLayer.ts b/src/world/layers/PoiLayer.ts deleted file mode 100755 index e31d789..0000000 --- a/src/world/layers/PoiLayer.ts +++ /dev/null @@ -1,163 +0,0 @@ -import Kernel = require('../Kernel'); -import Utils = require('../Utils'); -import Extent = require('../Extent'); -import Camera from '../Camera'; -import MathUtils = require('../math/Utils'); -import Program = require('../Program'); -import Graphic = require('../graphics/Graphic'); -import PoiMaterial = require('../materials/PoiMaterial'); -import VertexBufferObject = require('../VertexBufferObject'); - -class Poi { - constructor( - public x: number, - public y: number, - public z: number, - public uuid: string, - public name: string, - public address: string, - public phone: string) { } -} - -const vs = - ` -attribute vec3 aPosition; -uniform mat4 uPMVMatrix; -uniform float uSize; - -void main(void) { - gl_Position = uPMVMatrix * vec4(aPosition, 1.0); - gl_PointSize = uSize; -} -`; - -//http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0 -//gl_FragColor = texture2D(uSampler, vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y)); - -//https://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/discard.php -//highp mediump -const fs = - ` -precision mediump float; -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; -} -`; - -class PoiLayer extends Graphic { - private keyword: string = null; - private pois: Poi[] = null; - private vbo: VertexBufferObject = null; - - private constructor(public material: PoiMaterial) { - super(null, material); - this.pois = []; - this.vbo = new VertexBufferObject(Kernel.gl.ARRAY_BUFFER); - // this._addPoi(116.408540, 39.902350, "3161565500563468633", "首都大酒店", "北京市东城区前门东大街3号", ""); - } - - static getInstance() { - var url = "/WebGlobe/src/world/images/poi.png"; - var material = new PoiMaterial(url, 24); - return new PoiLayer(material); - } - - createProgram() { - return Program.getProgram(vs, fs); - } - - isReady(): boolean { - return !!(this.pois.length > 0 && this.material && this.material.isReady()); - } - - onDraw(camera: Camera) { - var gl = Kernel.gl; - - gl.enable(gl.BLEND); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - - //aPosition - var locPosition = this.program.getAttribLocation('aPosition'); - this.program.enableVertexAttribArray('aPosition'); - this.vbo.bind(); - var vertices: number[] = []; - this.pois.map(function (poi) { - vertices.push(poi.x, poi.y, poi.z); - }); - this.vbo.bufferData(vertices, gl.DYNAMIC_DRAW, true); - gl.vertexAttribPointer(locPosition, 3, gl.FLOAT, false, 0, 0); - - //uPMVMatrix - var pmvMatrix = camera.getProjViewMatrixForDraw(); - var locPMVMatrix = this.program.getUniformLocation('uPMVMatrix'); - gl.uniformMatrix4fv(locPMVMatrix, false, pmvMatrix.getFloat32Array()); - - //uSize - var locSize = this.program.getUniformLocation('uSize'); - gl.uniform1f(locSize, this.material.size); - - //set uSampler - var locSampler = this.program.getUniformLocation('uSampler'); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this.material.texture); - gl.uniform1i(locSampler, 0); - - //绘图,vertices.length / 3表示所绘点的个数 - gl.drawArrays(gl.POINTS, 0, vertices.length / 3); - - //释放当前绑定对象 - gl.disable(gl.BLEND); - // gl.bindBuffer(gl.ARRAY_BUFFER, null); - // gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); - // gl.bindTexture(gl.TEXTURE_2D, null); - } - - 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; - } - - addPoi(lon: number, lat: number, uuid: string, name: string, address: string, phone: string){ - return this._addPoi(lon, lat, uuid, name, address, phone); - } - - static search(wd: string, level: number, minLon: number, minLat: number, maxLon: number, maxLat: number, callback: (response: any) => void, pageCapacity: number = 50, pageIndex: number = 0) { - var url = `//apis.map.qq.com/jsapi?qt=syn&wd=${wd}&pn=${pageIndex}&rn=${pageCapacity}&output=jsonp&b=${minLon},${minLat},${maxLon},${maxLat}&l=${level}&c=000000`; - Utils.jsonp(url, callback); - } - - search(keyword: string) { - this.clear(); - this.keyword = keyword; - var globe = Kernel.globe; - var level = globe.getLevel(); - var extents = globe.getExtents(level); - extents.forEach((extent: Extent) => { - PoiLayer.search(keyword, level, extent.getMinLon(), extent.getMinLat(), extent.getMaxLon(), extent.getMaxLat(), (response) => { - console.log(`${keyword} response:`, response); - var data = response.detail.pois || []; - data.forEach((item: any) => { - var lon = parseFloat(item.pointx); - var lat = parseFloat(item.pointy); - this._addPoi(lon, lat, item.uid, item.name, item.addr, item.phone); - }) - }); - }); - } -} - -export = PoiLayer; \ No newline at end of file diff --git a/src/world/materials/MeshColorMaterial.ts b/src/world/materials/MeshColorMaterial.ts deleted file mode 100755 index ec08650..0000000 --- a/src/world/materials/MeshColorMaterial.ts +++ /dev/null @@ -1,54 +0,0 @@ -import Material = require("./Material"); - -class MeshColorMaterial extends Material { - type: string = ""; - ready: boolean = false; - singleColor: number[]; - triangleColors: number[][]; - verticeColors: number[][]; - - constructor() { - super(); - this.reset(); - } - - isReady(){ - return this.ready; - } - - getType(){ - return "MeshColorMaterial"; - } - - reset() { - this.type = ''; - this.singleColor = null; - this.triangleColors = []; - this.verticeColors = []; - this.ready = false; - } - - setSingleColor(color: number[]) { - this.type = 'single'; - this.singleColor = color; - this.ready = true; - } - - setTriangleColor(colors: number[][]) { - this.type = 'triangle'; - this.triangleColors = colors; - this.ready = true; - }; - - setVerticeColor(colors: number[][]) { - this.type = 'vertice'; - this.verticeColors = colors; - this.ready = true; - } - - destroy() { - this.reset(); - } -} - -export = MeshColorMaterial; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 56a4420..191ab5a 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,15 @@ { "compilerOptions": { - "module": "amd", + "module": "commonjs", "target": "es5", "noImplicitAny": true, "removeComments": true, + "noUnusedLocals": false, + "noUnusedParameters": false, "preserveConstEnums": true, - "outDir": "buildOutput/amd" + "outDir": "buildOutput", + "jsx": "preserve" }, - "include": [ - "src/**/*.ts" - ], "exclude": [ "node_modules" ] diff --git a/versions.md b/versions.md new file mode 100644 index 0000000..30b4741 --- /dev/null +++ b/versions.md @@ -0,0 +1,253 @@ + - 0.0.4.4 优化了判断切片是否可见的算法,使用经纬度判断法(isTileVisible2)取代了老式的计算方法(isTileVisible) + + - 0.0.4.5 继续优化isTileVisible算法,许多参数从函数外面传递进来,比如geoExtent和projViewMatrix,循环传递使用,减少了生成这两个变量所调用函数的次数;引入了World.Globe,将一些处理逻辑放到了Globe中,比如讲renderer中的updateGlobe方法迁移到Globe中的refresh方法,尽量只对二次开发人员暴露World.Globe的接口,更合理一些,从而将版本从0.0.4.4升级到0.0.4.5 + + - 0.0.4.6 不再通过ajax获取vertexShader和fragmentShader的文本内容,而是将其以字符串的形式直接放入到World.js文件中(World.ShaderContent),从而减少了对于其他GLSL文件的依赖,并因此将World.js的版本升级到0.0.4.6 + + - 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.1 优化了各种TiledLayer的getImageUrl的算法 + + - 0.0.5.2 + + - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE); + + - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE); + + - 通过执行以上命令消除了两个切片之间的白线问题 + + - 0.0.5.3 从0.0.5.2升级到0.0.5.3:globe中增加了calculateCameraMoveInfo方法,在大比例尺下移动视图也比较正常 + + - 0.0.5.4 升级到0.0.5.4:在World.Object3D的destroy方法中释放了显卡中的各种buffer和texture资源 + + - 0.0.5.5 在render方法中计算了viewMatrix并赋值给camera作为其中的一个属性,这样就只调用一次getViweMatrix方法,不用在draw(camera)中再多次计算 + + - 0.0.5.6 在创建level为1的subTiledLayer的时候就全部创建其下的四个切片,并且从不删除该subTiledLayer;将自动更新时间设置为750毫秒 + + - 0.0.5.7 添加了World.BingTiledLayer,支持微软Bing地图渲染(有三种风格:a、r、h) + + - 0.0.5.8 添加了World.SosoTiledLayer,支持soso地图 + + - 0.0.5.9 添加了World.TiandituTiledLayer,支持天地图 + + - 0.0.6.0 通过添加代理页proxy.jsp可以加载ArcGIS的切片地图,包括ArcGISOnline以及捷泰地图,或其他ArcGIS Server发布的地图服务 + + - 0.0.6.1 通过代理方式支持高德地图 + + - 0.0.6.2 用bind方式代替闭包实现World.TextureMaterial.prototype.setImageUrl中handleLoadedTexture操作texture + + - 0.0.6.3 在Matrix和Object3D中加入对worldScale和localScale的支持 + + - 0.0.6.4 在Matrix和Object3D中加入对localRotateByVector的支持 + + - 0.0.6.5 修复了在层级比较大的情况下切片不能完全正常匹配的问题,解决办法是取消掉了对切片范围的一像素扩容 + + - 0.0.6.6 将World.Scene继承自World.Object3DComponents,减少冗余代码,结构也更合理,也因此调整了World.js中各代码块的位置 + + - 0.0.6.7 在拖动鼠标时,鼠标拖动点与经纬度进行了绑定,实现定点拖动 + + - 0.0.6.8 将CanvasEventHandler.js中的事件代码移植到World.Event中,以后在html页面中只需要引入World.js一个js文件即可 + + - 0.0.6.9 修改了camera中的isLonVisible方法,将其精简完善,从而解决了在伦敦中央经线附近在14级时一片漆黑的问题 + + - 0.0.7.0 修改了无法删除canvas的onMouseMove事件的问题 + + - 0.0.7.1 修改了将视图缩放到中国区域范围的时候,camera.getGeoExtent()获取的经纬度不准确的问题,原因是在计算由于地球自身视线遮挡的经度范围时,只是简单的在中心经度上加减偏移量,对于如果中心点的纬度为0时是正确的,只要中心点纬度不为0,这种简单的加减经度偏移量是不准确的 + + - 0.0.7.2 完善了各种坐标系之间坐标的换算 + + - 0.0.7.3 基本完成camera.getExtentInfo的编写,准备用其替换camera.getGeoExtent方法,并添加了camera.isGeoVisibleInCanvas方法 + + - 0.0.7.4 继续晚上camera.getExtentInfo方法,在沿着经线上下偏移纬度时不准确,比如65+40=105>90,导致其超出[-90,90]的范围,现在会确保其范围是正确的,但是有可能把范围算大了 + + - 0.0.7.5 完成camera.getExtentInfo方法的重写,并对老旧代码删除,用新代码替换老代码 + + - 0.0.7.6 以前为了优化效率,在很多地方设置了SubTiledLayer的isvisible和Tile的isvisible都为false,但是体验效果却不好,经常会拿level1来作为保底图层;现在只要camera变化了就将所有SubTiledLayer和Tile的isvisible都设置为true,体验效果好,只有在camera位置没有发生变化,且level最大的那级图层的所有切片都已加载的情况下(即SubTiledLayer.checkIfLoaded为true)时才会将无需显示的那些SubTiledLayer的isvisible属性置为false + + - 0.0.7.7 将MAX_LEVEL、CURRENT_LEVEL、BELOW_LEVEL、OLD_POSITION属性从World中迁移如World.Globe中,更合理 + + - 0.0.7.8 完善了onMouseMove事件代码,提供与Google New Map一样的体验 + + - 0.0.7.9 修改完善了getPickDirectionByNDC方法,ndcZ传入0.1而非0.5,0.5在某些情况下会导致计算的w为0 + + - 0.0.8.0 将ndcZ最终改为0.499,没发现问题;并且在camera.isTileVisible方法内通过判断切片斜对角线的长度是否均小于0.1来决定不可见或可见,这对于倾斜视角下很有用,会剔除掉那些面积很小的切片,直接用面积比较不如用斜对角线长度比较实用,并因此在任意时刻都渲染全部的切片,去掉了checkIfLoaded的优化代码 + + - 0.0.8.1 打算重写算法,添加了两个重要的方法camera.getVisibleTilesByLevel和getTileVisibleInfo,直接从第level级获取可见的切片,无需从第0级依次遍历 + + - 0.0.8.2 重写了算法,不再根据经纬度依次从第1级开始判断,直接通过camera.getVisibleTilesByLevel获取第level级别的可见切片,即使缩放到20级,内存也在170M左右,而且很流畅,删除了以前的老代码,实现重大升级 + + - 0.0.8.3 加入World.Elevation代码,用于请求高程数据,开始改造Object3D和Tile的相关方法,使其能根据传入的vertice等信息动态更改buffer + + - 0.0.8.4 实现最最基础的三维地形图 + + - 0.0.8.5 上一版本的World.Elevation.getElevationByCache有问题,现在已修改完善,正确计算切片高程的起点索引值 + + - 0.0.8.8 重写了Tile.handleTile方法,将老的方法命名为handTile2,但没有删除,新的handleTile方法从左上角至右下角遍历,并且多个三角形公用同一个顶点 + + - 0.1.0 将Globe.js拆分成多个AMD模块,AMD代码放在js目录下;并将AMD代码用TypeScript重新实现,放到ts目录下,并对原有AMD的代码进行微调:解除了Vertice与Vector之间的相互引用,解除了Math与TileGrid之间的相互引用;通过gulp分别实现了对AMD和TypeScript代码进行编译压缩,通过requirejs均可加载相应bundle代码 + + - 0.1.1 在js和ts中均修复了在两极地区抖动的问题:world/Event#moveGeo() + + - 0.2 引入World.js中的许多类的设计理念,包括Program、GraphicGroup、Graphic、Geometry、Material、VertexBufferObject等,现在SubTiledLayer和TiledLayer都继承自GraphicGroup,并且让Tile继承自于Graphic + + - 0.2.1 修复了在leve 15及以上的情况下,拖动地图导致地图空白的问题,见issue#9 + + - 0.2.2 删除高程相关代码 + + - 0.2.3 将world/geometries/Geomtry重命名为world/geometries/Mesh,并在package.json中添加了多个npm scripts,对应gulp中定义的tasks + + - 0.2.4 之前EARTH_RADIUS的值为6378137,特别大,导致了深度值计算有问题,从而使得深度测试不能正常进行。将EARTH_RADIUS改成14000,使得深度测试可以正常进行。并加入了Poi和PoiLayer这两个类。 + + - 0.3.0 + - 将EARTH_RADIUS从14000改为500,在0-10级的时候,通过改变Camera的position实现缩放,在10级之后通过改变fov实现缩放。 + - Camera中所有的计算(比如计算视野中的可见切片)都是基于matrix、viewMatrix、projMatrix和projViewMatrix。 + - 但是实际传递给shader用于绘图的是projViewMatrixForDraw。Camera.getPickCartesianCoordInEarthByCanvas()方法也是基于projViewMatrixForDraw系列矩阵的。 + - 该版本提高了深度值的精度,基本解决了z值精度问题。在update()方法中会计算projViewMatrixForDraw系列矩阵。 + + - 0.3.1 使得Globe.animateToLevel()可以在0.3.0的版本上运行,解决办法是引入了camera.animationLevel,其值是非整数。 + + - 0.3.2 优化了Camera.update()方法,只有发生用户交互的情况下才实际进行计算。 + + - 0.3.3 优化了Globe.refresh()方法,只有发生用户交互的情况下才重新计算可见切片。 + + - 0.3.4 增加了Atmosphere效果。 + + - 0.3.5 优化了切片加载的算法: + - 当MeshTextureMaterial执行destroy的时候,取消掉原有的图片请求; + - 当缩放的时候,不会请求父级别以及父父级别的切片。比如缩放到第15级的时候,不再请求第13和14级的切片,但是会请求12级及以前的切片,减少了图片请求的数量。 + + - 0.3.6 + - 使得PoiLayer能基本搜索POI并将其显示; + - 为IE11等浏览器添加ES6方法的polyfill; + - 在Matrix中用Float64Array存储数据,并同时提供getFloat32Array()方法以便给Shader传递Float32Array类型的数据(Shader不支持Float64Array)。 + + - 0.3.7 删除Poi类,并让PoiLayer继承自Graphic,每次OnDraw()的时候将所有poi点的坐标动态赋值给buffer,大幅减少了绘图命令的调用,提高了FPS。 + + - 0.3.8 在TiledLayer中为program中的uniform变量设置值,减少了WebGL命令的调用次数。 + + - 0.3.9 用webpack代替gulp作为打包工具 + + - 0.3.10 每次渲染之前都会检查Camera是否发生变化,如果变化则更新切片列表,并且保证Canvas八个角点都有保底的可见切片,不再从level1全部渲染切片,减少WebGL绘图命令 + + - 0.3.11 修复了0.3.10里面的一个bug + + - 0.3.12 使得GoogleTiledLayer可以使用,并且为GoogleTiledLayer和OsmTiledLayer增加了多种Style + + - 0.3.13 添加LabelLayer和AutonaviLabelLayer,叠加GoogleTiledLayer,取得了较好的效果 + + - 0.3.14 在移动端浏览器上面可以通过手势缩放 + + - 0.3.15 添加了TrafficLayer、SosoTrafficLayer、QihuTrafficLayer,可以查看实时交通 + + - 0.3.16 使得QihuTrafficLayer可以运行 + + - 0.3.17 添加Location模块,监听位置变化 + + - 0.3.18 在Camera中增加了resolution相关的方法,并可以通过Camera.getBestDisplayLevel()方法获取当前摄像机位置下最优的显示层级的切片 + + - 0.4.0 + - 去除了level和lastLevel的概念,只保留一个概念:renderingLevel,renderingLevel即相当于之前的lastLevel,表示最后渲染切片的级别; + - 增加了calculateDistance2EarthOriginByResolution()和calculateResolutionAndBestDisplayLevelByDistance2EarthOrigin()等相关方法,并依据这些方法完成Camera位置与renderingLevel之间的映射 + + - 0.4.1 将renderingLevel重命名为level,将set/getRenderingLevel()重命名为set/getLevel() + + - 0.4.2 重命名Camera中的相关方法 + + - 0.4.3 向Camera添加isEarthFullOverlapScreen()等方法 + + - 0.4.4 默认显示当前位置 + + - 0.4.5 添加Travis CI服务,在每次push代码的时候进行测试,在README.md中添加了各种Badge + + - 0.4.6 增加了GlobeOptions类,为Globe构造函数传递参数 + + - 0.4.7 修复Camera中setDeltaPitch的bug + + - 0.4.8 使用Webpack加载图片、分离JavaScript与CSS、基于文件内容生成hash + + - 0.4.9 当level>=10时可以进行POI搜索,并且当地图范围变化时会重新动态搜索 + + - 0.4.10 开发模式下,启用SourceMap + + - 0.4.11 + - 分离core和webapp目录,基本搭建起了webapp的路由机制,基本完成首页界面 + - 将所有服务都放入到Service类中,并添加searchNearby接口 + - 将core中的import require改成import from + + - 0.4.12 使用更加快速的城市级别的定位服务,明显缩短定位时间 + + - 0.4.13 + - 添加MultiPointsGraphic类,并让PoiLayer继承自该类 + - 通过配置tsconfig.json删除了很多无用的import和变量 + + - 0.4.14 + - versions.txt -> versions.md + - 基本完成附近搜索功能,将搜索结果分页显示 + + - 0.4.15 + - 阻止隐藏地址栏 + - 处理文本框和软键盘 + - 对nearby/Search和nearby/Result的UI做了增强 + + - 0.4.16 从Kernel中删除了globe和canvas属性 + + - 0.4.17 + - 设置Globe的构造函数为私有方法,只能通过静态方法getInstance获取其单例 + - 将Globe包装成React组件 + + - 0.4.18 + - 优化定位逻辑 + - 将附近搜索的结果在地图中显示 + + - 0.4.19 + - 使用WebGLRenderingContext引用WebGL静态常量 + - 在绘制PoiLayer时禁用深度测试 + + - 0.4.20 + - 在Service中添加searchByCurrentCity接口 + - 为Globe添加pause和resume方法,在React组件中调用 + + - 0.4.21 + - 添加RouteComponent,所有routes目录下的Component均继承自它,异步操作会显示loading + - 对附近搜索无结果的情况进行友好显示 + + - 0.4.22 + - 继续完善index/Nav页面 + - 添加了路线规划相关的服务 + +- 0.4.23 + - 完善Service.gaodeRouteByDriving()方法 + - MeshGraphic => MeshTextureGraphic + - 添加MeshColorGraphic + +- 0.4.24 RouteLayer基本可以实现固定宽度的Polyline + +- 0.4.25 更新webpack.config.js + +- 0.4.26 Camera中添加centerTo和animateTo方法 + +- 0.4.27 Camera中添加setExtent和animateToExtent方法 + +- 0.4.28 通过判断道路拐点解决"箭头道路"的问题 + +- 0.4.29 可以在nav/Paths页面中直接切换出行方式重新进行路线规划 + +- 0.4.30 在RouteComponent中添加getPreviousLocation()方法,并在nearyby/Result中使用 + +- 0.4.31 在公交导航中支持火车出行 + +- 0.4.32 通过http://fontello.com/ 自定义FontAwesome + +- 0.4.33 Globe构造函数支持pauseRendering参数,可以实现切片延迟加载 + +- 0.4.34 searchByBuffer和searchByCity支持SearchType参数,可以智能判断类型,并且可以在查询无结果的情况下改变SearchType再次查询,优化查询体验 + +- 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.5.0 为搜索框添加搜索图标,方便搜索 + +- 0.5.1 更新README.md,发布新版本 \ No newline at end of file diff --git a/versions.txt b/versions.txt deleted file mode 100755 index 8d25ece..0000000 --- a/versions.txt +++ /dev/null @@ -1,154 +0,0 @@ -0.0.4.4 优化了判断切片是否可见的算法,使用经纬度判断法(isTileVisible2)取代了老式的计算方法(isTileVisible) - -0.0.4.5 继续优化isTileVisible算法,许多参数从函数外面传递进来,比如geoExtent和projViewMatrix,循环传递使用,减少了生成这两个变量所调用函数的次数;引入了World.Globe,将一些处理逻辑放到了Globe中,比如讲renderer中的updateGlobe方法迁移到Globe中的refresh方法,尽量只对二次开发人员暴露World.Globe的接口,更合理一些,从而将版本从0.0.4.4升级到0.0.4.5 - -0.0.4.6 不再通过ajax获取vertexShader和fragmentShader的文本内容,而是将其以字符串的形式直接放入到World.js文件中(World.ShaderContent),从而减少了对于其他GLSL文件的依赖,并因此将World.js的版本升级到0.0.4.6 - -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.1 优化了各种TiledLayer的getImageUrl的算法 - -0.0.5.2 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE); - 通过执行以上命令消除了两个切片之间的白线问题 - -0.0.5.3 从0.0.5.2升级到0.0.5.3:globe中增加了calculateCameraMoveInfo方法,在大比例尺下移动视图也比较正常 - -0.0.5.4 升级到0.0.5.4:在World.Object3D的destroy方法中释放了显卡中的各种buffer和texture资源 - -0.0.5.5 在render方法中计算了viewMatrix并赋值给camera作为其中的一个属性,这样就只调用一次getViweMatrix方法,不用在draw(camera)中再多次计算 - -0.0.5.6 在创建level为1的subTiledLayer的时候就全部创建其下的四个切片,并且从不删除该subTiledLayer;将自动更新时间设置为750毫秒 - -0.0.5.7 添加了World.BingTiledLayer,支持微软Bing地图渲染(有三种风格:a、r、h) - -0.0.5.8 添加了World.SosoTiledLayer,支持soso地图 - -0.0.5.9 添加了World.TiandituTiledLayer,支持天地图 - -0.0.6.0 通过添加代理页proxy.jsp可以加载ArcGIS的切片地图,包括ArcGISOnline以及捷泰地图,或其他ArcGIS Server发布的地图服务 - -0.0.6.1 通过代理方式支持高德地图 - -0.0.6.2 用bind方式代替闭包实现World.TextureMaterial.prototype.setImageUrl中handleLoadedTexture操作texture - -0.0.6.3 在Matrix和Object3D中加入对worldScale和localScale的支持 - -0.0.6.4 在Matrix和Object3D中加入对localRotateByVector的支持 - -0.0.6.5 修复了在层级比较大的情况下切片不能完全正常匹配的问题,解决办法是取消掉了对切片范围的一像素扩容 - -0.0.6.6 将World.Scene继承自World.Object3DComponents,减少冗余代码,结构也更合理,也因此调整了World.js中各代码块的位置 - -0.0.6.7 在拖动鼠标时,鼠标拖动点与经纬度进行了绑定,实现定点拖动 - -0.0.6.8 将CanvasEventHandler.js中的事件代码移植到World.Event中,以后在html页面中只需要引入World.js一个js文件即可 - -0.0.6.9 修改了camera中的isLonVisible方法,将其精简完善,从而解决了在伦敦中央经线附近在14级时一片漆黑的问题 - -0.0.7.0 修改了无法删除canvas的onMouseMove事件的问题 - -0.0.7.1 修改了将视图缩放到中国区域范围的时候,camera.getGeoExtent()获取的经纬度不准确的问题,原因是在计算由于地球自身视线遮挡的经度范围时,只是简单的在中心经度上加减偏移量,对于如果中心点的纬度为0时是正确的,只要中心点纬度不为0,这种简单的加减经度偏移量是不准确的 - -0.0.7.2 完善了各种坐标系之间坐标的换算 - -0.0.7.3 基本完成camera.getExtentInfo的编写,准备用其替换camera.getGeoExtent方法,并添加了camera.isGeoVisibleInCanvas方法 - -0.0.7.4 继续晚上camera.getExtentInfo方法,在沿着经线上下偏移纬度时不准确,比如65+40=105>90,导致其超出[-90,90]的范围,现在会确保其范围是正确的,但是有可能把范围算大了 - -0.0.7.5 完成camera.getExtentInfo方法的重写,并对老旧代码删除,用新代码替换老代码 - -0.0.7.6 以前为了优化效率,在很多地方设置了SubTiledLayer的isvisible和Tile的isvisible都为false,但是体验效果却不好,经常会拿level1来作为保底图层;现在只要camera变化了就将所有SubTiledLayer和Tile的isvisible都设置为true,体验效果好,只有在camera位置没有发生变化,且level最大的那级图层的所有切片都已加载的情况下(即SubTiledLayer.checkIfLoaded为true)时才会将无需显示的那些SubTiledLayer的isvisible属性置为false - -0.0.7.7 将MAX_LEVEL、CURRENT_LEVEL、BELOW_LEVEL、OLD_POSITION属性从World中迁移如World.Globe中,更合理 - -0.0.7.8 完善了onMouseMove事件代码,提供与Google New Map一样的体验 - -0.0.7.9 修改完善了getPickDirectionByNDC方法,ndcZ传入0.1而非0.5,0.5在某些情况下会导致计算的w为0 - -0.0.8.0 将ndcZ最终改为0.499,没发现问题;并且在camera.isTileVisible方法内通过判断切片斜对角线的长度是否均小于0.1来决定不可见或可见,这对于倾斜视角下很有用,会剔除掉那些面积很小的切片,直接用面积比较不如用斜对角线长度比较实用,并因此在任意时刻都渲染全部的切片,去掉了checkIfLoaded的优化代码 - -0.0.8.1 打算重写算法,添加了两个重要的方法camera.getVisibleTilesByLevel和getTileVisibleInfo,直接从第level级获取可见的切片,无需从第0级依次遍历 - -0.0.8.2 重写了算法,不再根据经纬度依次从第1级开始判断,直接通过camera.getVisibleTilesByLevel获取第level级别的可见切片,即使缩放到20级,内存也在170M左右,而且很流畅,删除了以前的老代码,实现重大升级 - -0.0.8.3 加入World.Elevation代码,用于请求高程数据,开始改造Object3D和Tile的相关方法,使其能根据传入的vertice等信息动态更改buffer - -0.0.8.4 实现最最基础的三维地形图 - -0.0.8.5 上一版本的World.Elevation.getElevationByCache有问题,现在已修改完善,正确计算切片高程的起点索引值 - -0.0.8.8 重写了Tile.handleTile方法,将老的方法命名为handTile2,但没有删除,新的handleTile方法从左上角至右下角遍历,并且多个三角形公用同一个顶点 - -0.1.0 将Globe.js拆分成多个AMD模块,AMD代码放在js目录下;并将AMD代码用TypeScript重新实现,放到ts目录下,并对原有AMD的代码进行微调:解除了Vertice与Vector之间的相互引用,解除了Math与TileGrid之间的相互引用;通过gulp分别实现了对AMD和TypeScript代码进行编译压缩,通过requirejs均可加载相应bundle代码 - -0.1.1 在js和ts中均修复了在两极地区抖动的问题:world/Event#moveGeo() - -0.2 引入World.js中的许多类的设计理念,包括Program、GraphicGroup、Graphic、Geometry、Material、VertexBufferObject等,现在SubTiledLayer和TiledLayer都继承自GraphicGroup,并且让Tile继承自于Graphic - -0.2.1 修复了在leve 15及以上的情况下,拖动地图导致地图空白的问题,见issue#9 - -0.2.2 删除高程相关代码 - -0.2.3 将world/geometries/Geomtry重命名为world/geometries/Mesh,并在package.json中添加了多个npm scripts,对应gulp中定义的tasks - -0.2.4 之前EARTH_RADIUS的值为6378137,特别大,导致了深度值计算有问题,从而使得深度测试不能正常进行。将EARTH_RADIUS改成14000,使得深度测试可以正常进行。并加入了Poi和PoiLayer这两个类。 - -0.3.0 将EARTH_RADIUS从14000改为500,在0-10级的时候,通过改变Camera的position实现缩放,在10级之后通过改变fov实现缩放。 - Camera中所有的计算(比如计算视野中的可见切片)都是基于matrix、viewMatrix、projMatrix和projViewMatrix。 - 但是实际传递给shader用于绘图的是projViewMatrixForDraw。Camera.getPickCartesianCoordInEarthByCanvas()方法也是基于projViewMatrixForDraw系列矩阵的。 - 该版本提高了深度值的精度,基本解决了z值精度问题。在update()方法中会计算projViewMatrixForDraw系列矩阵。 - -0.3.1 使得Globe.animateToLevel()可以在0.3.0的版本上运行,解决办法是引入了camera.animationLevel,其值是非整数。 - -0.3.2 优化了Camera.update()方法,只有发生用户交互的情况下才实际进行计算。 - -0.3.3 优化了Globe.refresh()方法,只有发生用户交互的情况下才重新计算可见切片。 - -0.3.4 增加了Atmosphere效果。 - -0.3.5 优化了切片加载的算法: - 1. 当MeshTextureMaterial执行destroy的时候,取消掉原有的图片请求; - 2. 当缩放的时候,不会请求父级别以及父父级别的切片。比如缩放到第15级的时候,不再请求第13和14级的切片,但是会请求12级及以前的切片,减少了图片请求的数量。 - -0.3.6 使得PoiLayer能基本搜索POI并将其显示; - 为IE11等浏览器添加ES6方法的polyfill; - 在Matrix中用Float64Array存储数据,并同时提供getFloat32Array()方法以便给Shader传递Float32Array类型的数据(Shader不支持Float64Array)。 - -0.3.7 删除Poi类,并让PoiLayer继承自Graphic,每次OnDraw()的时候将所有poi点的坐标动态赋值给buffer,大幅减少了绘图命令的调用,提高了FPS。 - -0.3.8 在TiledLayer中为program中的uniform变量设置值,减少了WebGL命令的调用次数。 - -0.3.9 用webpack代替gulp作为打包工具 - -0.3.10 每次渲染之前都会检查Camera是否发生变化,如果变化则更新切片列表,并且保证Canvas八个角点都有保底的可见切片,不再从level1全部渲染切片,减少WebGL绘图命令 - -0.3.11 修复了0.3.10里面的一个bug - -0.3.12 使得GoogleTiledLayer可以使用,并且为GoogleTiledLayer和OsmTiledLayer增加了多种Style - -0.3.13 添加LabelLayer和AutonaviLabelLayer,叠加GoogleTiledLayer,取得了较好的效果 - -0.3.14 在移动端浏览器上面可以通过手势缩放 - -0.3.15 添加了TrafficLayer、SosoTrafficLayer、QihuTrafficLayer,可以查看实时交通 - -0.3.16 使得QihuTrafficLayer可以运行 - -0.3.17 添加Location模块,监听位置变化 - -0.3.18 在Camera中增加了resolution相关的方法,并可以通过Camera.getBestDisplayLevel()方法获取当前摄像机位置下最优的显示层级的切片 - -0.4.0 1. 去除了level和lastLevel的概念,只保留一个概念:renderingLevel,renderingLevel即相当于之前的lastLevel,表示最后渲染切片的级别; - 2. 增加了calculateDistance2EarthOriginByResolution()和calculateResolutionAndBestDisplayLevelByDistance2EarthOrigin()等相关方法, - 并依据这些方法完成Camera位置与renderingLevel之间的映射 - -0.4.1 将renderingLevel重命名为level,将set/getRenderingLevel()重命名为set/getLevel() - -0.4.2 重命名Camera中的相关方法 - -0.4.3 向Camera添加isEarthFullOverlapScreen()等方法 - -0.4.4 默认显示当前位置 \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 4d4b742..69b7e74 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,48 +1,129 @@ var path = require('path'); var chalk = require('chalk'); var webpack = require('webpack'); + +var ExtractTextWebpackPlugin = require("extract-text-webpack-plugin"); +//https://github.com/webpack-contrib/extract-text-webpack-plugin/blob/webpack-1/README.md +//[name] the name of the chunk +//[contenthash] a hash of the content of the extracted file +var extractPlugin = new ExtractTextWebpackPlugin("[name].[contenthash].css"); + +var WebpackMd5Hash = require('webpack-md5-hash'); +var webpackMd5HashPlugin = new WebpackMd5Hash(); + var HtmlWebpackPlugin = require('html-webpack-plugin'); +//https://github.com/jantimon/html-webpack-plugin/issues/133 +var indexHtmlWebpackPlugin = new HtmlWebpackPlugin({ + filename: './index.html', + template: '!!html!./src/core/template.html', + hash: false, + inject: 'body', + // chunks: ["runtime", "globe", "index"] + chunks: ["index"] +}); + +var webappHtmlWebpackPlugin = new HtmlWebpackPlugin({ + filename: './webapp.html', + template: '!!ejs!./src/webapp/template.html', + hash: false, + inject: 'body', + // chunks: ["runtime", "webapp", "globe"] + chunks: ["webapp"] +}); + +// var commonsChunkPlugin = new webpack.optimize.CommonsChunkPlugin({ +// // name: "globe", +// // chunks: ["globe"] +// names: ["globe", "runtime"] +// }); -//https://github.com/webpack/webpack/issues/708 +var buildFolder = "buildOutput"; + +var PRODUCTION = process.env.NODE_ENV === 'production'; + +var es6Promise = "./node_modules/es6-promise/dist/es6-promise.auto.min.js"; module.exports = { - entry: path.resolve(__dirname, "./index.ts"), + entry: { + // globe: "./src/core/world/Globe.ts", + index: [es6Promise, "./src/core/index.ts"], + webapp: [es6Promise, "./src/webapp/index.jsx"] + }, + output: { - path: path.resolve(__dirname, "buildOutput"), - filename: "bundle.js" + path: path.resolve(__dirname, buildFolder), + filename: "[name].[chunkhash].js", + // publicPath: buildFolder + "/", + devtoolModuleFilenameTemplate: 'webpack:///[absolute-resource-path]' }, + + // devtool: PRODUCTION ? 'hidden-source-map' : 'cheap-module-eval-source-map' + devtool: PRODUCTION ? false : 'source-map', + resolve: { - extensions: ["", ".webpack.js", ".web.js", ".js", ".ts", ".tsx"] + extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js", ".jsx", ".scss", ".png"], + alias: { + core: path.resolve(__dirname, "./src/core"), + world: path.resolve(__dirname, "./src/core/world"), + webapp: path.resolve(__dirname, "./src/webapp") + } }, + module: { - loaders: [ - { test: /\.tsx?$/, loader: "ts-loader" }, - { test: /\.css$/, loader: "style!css"} + loaders: [{ + test: /\.tsx?$/, + loader: "ts-loader" + }, + { + test: /\.jsx?$/, + loader: "babel-loader" + }, + { + test: /\.scss$/, + loader: extractPlugin.extract("css?modules&localIdentName=[name]__[local]___[hash:base64:5]!sass") + }, + { + test: /\.(png|jpeg|jpg)$/, + loader: "file-loader" + }, + { + test: /\.(otf|ttf|eot|woff|woff2).*/, + loader: "file-loader" + }, + { + test: /\.html$/, + loader: "html-loader", + query: { + attrs: ['img:src'] + } + } ] }, + plugins: [ - new HtmlWebpackPlugin({ - filename: '../index.html', - template: '!!ejs!./template.html', - hash: true, - inject: 'body' - }) + // commonsChunkPlugin, + extractPlugin, + webpackMd5HashPlugin, + indexHtmlWebpackPlugin, + webappHtmlWebpackPlugin ] }; -if(process.argv.indexOf("--ci") >= 0){ +if (process.argv.indexOf("--ci") >= 0) { + //https://github.com/webpack/webpack/issues/708 module.exports.plugins.push( - function(){ - this.plugin("done", function(stats){ + function() { + this.plugin("done", function(stats) { var errors = stats.compilation.errors; - if(errors && errors.length > 0){ + if (errors && errors.length > 0) { console.log(""); console.log(chalk.red("----------------------------------------------------------------")); - errors.forEach(function(err){ + errors.forEach(function(err) { var msg = chalk.red(`ERROR in ${err.module.userRequest},`); - msg += chalk.blue(`(${err.location.line},${err.location.character}),`); - msg += chalk.red(err.rawMessage); + // msg += chalk.blue(`(${err.location.line},${err.location.character}),`); + msg += chalk.red(err.message); console.log(msg); + // console.log(err); }); console.log(chalk.red("----------------------------------------------------------------")); process.exit(1); @@ -52,6 +133,15 @@ if(process.argv.indexOf("--ci") >= 0){ ); } -if(process.env.NODE_ENV === 'production'){ - module.exports.plugins.push(new webpack.optimize.UglifyJsPlugin()); +if (PRODUCTION) { + module.exports.plugins.push( + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('production') + } + }), + new webpack.optimize.UglifyJsPlugin({ + sourceMap: false + }) + ); } \ No newline at end of file