OSMBuildings-SuperMap.js 75 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448
  1. /**
  2. * Copyright (C) 2015 OSM Buildings, Jan Marsch
  3. * A JavaScript library for visualizing building geometry on interactive maps.
  4. * @osmbuildings, http://osmbuildings.org
  5. */
  6. //****** file: prefix.js ******
  7. (function (global) {
  8. 'use strict';
  9. //****** file: shortcuts.js ******
  10. // object access shortcuts
  11. var
  12. m = Math,
  13. exp = m.exp,
  14. log = m.log,
  15. sin = m.sin,
  16. cos = m.cos,
  17. tan = m.tan,
  18. atan = m.atan,
  19. atan2 = m.atan2,
  20. min = m.min,
  21. max = m.max,
  22. sqrt = m.sqrt,
  23. ceil = m.ceil,
  24. floor = m.floor,
  25. round = m.round,
  26. pow = m.pow;
  27. var mapObj;
  28. // polyfills
  29. var
  30. Int32Array = Int32Array || Array,
  31. Uint8Array = Uint8Array || Array;
  32. var IS_IOS = /iP(ad|hone|od)/g.test(navigator.userAgent);
  33. var IS_MSIE = !!~navigator.userAgent.indexOf('Trident');
  34. var requestAnimFrame = (global.requestAnimationFrame && !IS_IOS && !IS_MSIE) ?
  35. global.requestAnimationFrame : function (callback) {
  36. callback();
  37. };
  38. //****** file: Color.debug.js ******
  39. var Color = (function (window) {
  40. var w3cColors = {
  41. aqua: '#00ffff',
  42. black: '#000000',
  43. blue: '#0000ff',
  44. fuchsia: '#ff00ff',
  45. gray: '#808080',
  46. grey: '#808080',
  47. green: '#008000',
  48. lime: '#00ff00',
  49. maroon: '#800000',
  50. navy: '#000080',
  51. olive: '#808000',
  52. orange: '#ffa500',
  53. purple: '#800080',
  54. red: '#ff0000',
  55. silver: '#c0c0c0',
  56. teal: '#008080',
  57. white: '#ffffff',
  58. yellow: '#ffff00'
  59. };
  60. function hue2rgb(p, q, t) {
  61. if (t < 0) t += 1;
  62. if (t > 1) t -= 1;
  63. if (t < 1 / 6) return p + (q - p) * 6 * t;
  64. if (t < 1 / 2) return q;
  65. if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
  66. return p;
  67. }
  68. function clamp(v, max) {
  69. return Math.min(max, Math.max(0, v));
  70. }
  71. var Color = function (h, s, l, a) {
  72. this.H = h;
  73. this.S = s;
  74. this.L = l;
  75. this.A = a;
  76. };
  77. /*
  78. * str can be in any of these:
  79. * #0099ff rgb(64, 128, 255) rgba(64, 128, 255, 0.5)
  80. */
  81. Color.parse = function (str) {
  82. var
  83. r = 0, g = 0, b = 0, a = 1,
  84. m;
  85. str = ('' + str).toLowerCase();
  86. str = w3cColors[str] || str;
  87. if ((m = str.match(/^#(\w{2})(\w{2})(\w{2})$/))) {
  88. r = parseInt(m[1], 16);
  89. g = parseInt(m[2], 16);
  90. b = parseInt(m[3], 16);
  91. } else if ((m = str.match(/rgba?\((\d+)\D+(\d+)\D+(\d+)(\D+([\d.]+))?\)/))) {
  92. r = parseInt(m[1], 10);
  93. g = parseInt(m[2], 10);
  94. b = parseInt(m[3], 10);
  95. a = m[4] ? parseFloat(m[5]) : 1;
  96. } else {
  97. return;
  98. }
  99. return this.fromRGBA(r, g, b, a);
  100. };
  101. Color.fromRGBA = function (r, g, b, a) {
  102. if (typeof r === 'object') {
  103. g = r.g / 255;
  104. b = r.b / 255;
  105. a = r.a;
  106. r = r.r / 255;
  107. } else {
  108. r /= 255;
  109. g /= 255;
  110. b /= 255;
  111. }
  112. var
  113. max = Math.max(r, g, b),
  114. min = Math.min(r, g, b),
  115. h, s, l = (max + min) / 2,
  116. d = max - min;
  117. if (!d) {
  118. h = s = 0; // achromatic
  119. } else {
  120. s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  121. switch (max) {
  122. case r:
  123. h = (g - b) / d + (g < b ? 6 : 0);
  124. break;
  125. case g:
  126. h = (b - r) / d + 2;
  127. break;
  128. case b:
  129. h = (r - g) / d + 4;
  130. break;
  131. }
  132. h *= 60;
  133. }
  134. return new Color(h, s, l, a);
  135. };
  136. Color.prototype = {
  137. toRGBA: function () {
  138. var
  139. h = clamp(this.H, 360),
  140. s = clamp(this.S, 1),
  141. l = clamp(this.L, 1),
  142. rgba = {a: clamp(this.A, 1)};
  143. // achromatic
  144. if (s === 0) {
  145. rgba.r = l;
  146. rgba.g = l;
  147. rgba.b = l;
  148. } else {
  149. var
  150. q = l < 0.5 ? l * (1 + s) : l + s - l * s,
  151. p = 2 * l - q;
  152. h /= 360;
  153. rgba.r = hue2rgb(p, q, h + 1 / 3);
  154. rgba.g = hue2rgb(p, q, h);
  155. rgba.b = hue2rgb(p, q, h - 1 / 3);
  156. }
  157. return {
  158. r: Math.round(rgba.r * 255),
  159. g: Math.round(rgba.g * 255),
  160. b: Math.round(rgba.b * 255),
  161. a: rgba.a
  162. };
  163. },
  164. toString: function () {
  165. var rgba = this.toRGBA();
  166. if (rgba.a === 1) {
  167. return '#' + ((1 << 24) + (rgba.r << 16) + (rgba.g << 8) + rgba.b).toString(16).slice(1, 7);
  168. }
  169. return 'rgba(' + [rgba.r, rgba.g, rgba.b, rgba.a.toFixed(2)].join(',') + ')';
  170. },
  171. hue: function (h) {
  172. return new Color(this.H * h, this.S, this.L, this.A);
  173. },
  174. saturation: function (s) {
  175. return new Color(this.H, this.S * s, this.L, this.A);
  176. },
  177. lightness: function (l) {
  178. return new Color(this.H, this.S, this.L * l, this.A);
  179. },
  180. alpha: function (a) {
  181. return new Color(this.H, this.S, this.L, this.A * a);
  182. }
  183. };
  184. return Color;
  185. }(this));
  186. //****** file: SunPosition.js ******
  187. // calculations are based on http://aa.quae.nl/en/reken/zonpositie.html
  188. // code credits to Vladimir Agafonkin (@mourner)
  189. var getSunPosition = (function () {
  190. var m = Math,
  191. PI = m.PI,
  192. sin = m.sin,
  193. cos = m.cos,
  194. tan = m.tan,
  195. asin = m.asin,
  196. atan = m.atan2;
  197. var rad = PI / 180,
  198. dayMs = 1000 * 60 * 60 * 24,
  199. J1970 = 2440588,
  200. J2000 = 2451545,
  201. e = rad * 23.4397; // obliquity of the Earth
  202. function toJulian(date) {
  203. return date.valueOf() / dayMs - 0.5 + J1970;
  204. }
  205. function toDays(date) {
  206. return toJulian(date) - J2000;
  207. }
  208. function getRightAscension(l, b) {
  209. return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l));
  210. }
  211. function getDeclination(l, b) {
  212. return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l));
  213. }
  214. function getAzimuth(H, phi, dec) {
  215. return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi));
  216. }
  217. function getAltitude(H, phi, dec) {
  218. return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H));
  219. }
  220. function getSiderealTime(d, lw) {
  221. return rad * (280.16 + 360.9856235 * d) - lw;
  222. }
  223. function getSolarMeanAnomaly(d) {
  224. return rad * (357.5291 + 0.98560028 * d);
  225. }
  226. function getEquationOfCenter(M) {
  227. return rad * (1.9148 * sin(M) + 0.0200 * sin(2 * M) + 0.0003 * sin(3 * M));
  228. }
  229. function getEclipticLongitude(M, C) {
  230. var P = rad * 102.9372; // perihelion of the Earth
  231. return M + C + P + PI;
  232. }
  233. return function getSunPosition(date, lat, lon) {
  234. var lw = rad * -lon,
  235. phi = rad * lat,
  236. d = toDays(date),
  237. M = getSolarMeanAnomaly(d),
  238. C = getEquationOfCenter(M),
  239. L = getEclipticLongitude(M, C),
  240. D = getDeclination(L, 0),
  241. A = getRightAscension(L, 0),
  242. t = getSiderealTime(d, lw),
  243. H = t - A;
  244. return {
  245. altitude: getAltitude(H, phi, D),
  246. azimuth: getAzimuth(H, phi, D) - PI / 2 // origin: north
  247. };
  248. };
  249. }());
  250. //****** file: GeoJSON.js ******
  251. var GeoJSON = (function () {
  252. var METERS_PER_LEVEL = 3;
  253. var materialColors = {
  254. brick: '#cc7755',
  255. bronze: '#ffeecc',
  256. canvas: '#fff8f0',
  257. concrete: '#999999',
  258. copper: '#a0e0d0',
  259. glass: '#e8f8f8',
  260. gold: '#ffcc00',
  261. plants: '#009933',
  262. metal: '#aaaaaa',
  263. panel: '#fff8f0',
  264. plaster: '#999999',
  265. roof_tiles: '#f08060',
  266. silver: '#cccccc',
  267. slate: '#666666',
  268. stone: '#996666',
  269. tar_paper: '#333333',
  270. wood: '#deb887'
  271. };
  272. var baseMaterials = {
  273. asphalt: 'tar_paper',
  274. bitumen: 'tar_paper',
  275. block: 'stone',
  276. bricks: 'brick',
  277. glas: 'glass',
  278. glassfront: 'glass',
  279. grass: 'plants',
  280. masonry: 'stone',
  281. granite: 'stone',
  282. panels: 'panel',
  283. paving_stones: 'stone',
  284. plastered: 'plaster',
  285. rooftiles: 'roof_tiles',
  286. roofingfelt: 'tar_paper',
  287. sandstone: 'stone',
  288. sheet: 'canvas',
  289. sheets: 'canvas',
  290. shingle: 'tar_paper',
  291. shingles: 'tar_paper',
  292. slates: 'slate',
  293. steel: 'metal',
  294. tar: 'tar_paper',
  295. tent: 'canvas',
  296. thatch: 'plants',
  297. tile: 'roof_tiles',
  298. tiles: 'roof_tiles'
  299. };
  300. // cardboard
  301. // eternit
  302. // limestone
  303. // straw
  304. function getMaterialColor(str) {
  305. str = str.toLowerCase();
  306. if (str[0] === '#') {
  307. return str;
  308. }
  309. return materialColors[baseMaterials[str] || str] || null;
  310. }
  311. var WINDING_CLOCKWISE = 'CW';
  312. var WINDING_COUNTER_CLOCKWISE = 'CCW';
  313. // detect winding direction: clockwise or counter clockwise
  314. function getWinding(points) {
  315. var x1, y1, x2, y2,
  316. a = 0,
  317. i, il;
  318. for (i = 0, il = points.length - 3; i < il; i += 2) {
  319. x1 = points[i];
  320. y1 = points[i + 1];
  321. x2 = points[i + 2];
  322. y2 = points[i + 3];
  323. a += x1 * y2 - x2 * y1;
  324. }
  325. return (a / 2) > 0 ? WINDING_CLOCKWISE : WINDING_COUNTER_CLOCKWISE;
  326. }
  327. // enforce a polygon winding direcetion. Needed for proper backface culling.
  328. function makeWinding(points, direction) {
  329. var winding = getWinding(points);
  330. if (winding === direction) {
  331. return points;
  332. }
  333. var revPoints = [];
  334. for (var i = points.length - 2; i >= 0; i -= 2) {
  335. revPoints.push(points[i], points[i + 1]);
  336. }
  337. return revPoints;
  338. }
  339. function alignProperties(prop) {
  340. var item = {};
  341. prop = prop || {};
  342. item.height = prop.height || (prop.levels ? prop.levels * METERS_PER_LEVEL : DEFAULT_HEIGHT);
  343. item.minHeight = prop.minHeight || (prop.minLevel ? prop.minLevel * METERS_PER_LEVEL : 0);
  344. var wallColor = prop.material ? getMaterialColor(prop.material) : (prop.wallColor || prop.color);
  345. if (wallColor) {
  346. item.wallColor = wallColor;
  347. }
  348. var roofColor = prop.roofMaterial ? getMaterialColor(prop.roofMaterial) : prop.roofColor;
  349. if (roofColor) {
  350. item.roofColor = roofColor;
  351. }
  352. switch (prop.shape) {
  353. case 'cylinder':
  354. case 'cone':
  355. case 'dome':
  356. case 'sphere':
  357. item.shape = prop.shape;
  358. item.isRotational = true;
  359. break;
  360. case 'pyramid':
  361. item.shape = prop.shape;
  362. break;
  363. }
  364. switch (prop.roofShape) {
  365. case 'cone':
  366. case 'dome':
  367. item.roofShape = prop.roofShape;
  368. item.isRotational = true;
  369. break;
  370. case 'pyramid':
  371. item.roofShape = prop.roofShape;
  372. break;
  373. }
  374. if (item.roofShape && prop.roofHeight) {
  375. item.roofHeight = prop.roofHeight;
  376. item.height = max(0, item.height - item.roofHeight);
  377. } else {
  378. item.roofHeight = 0;
  379. }
  380. return item;
  381. }
  382. function getGeometries(geometry) {
  383. var
  384. i, il, polygon,
  385. geometries = [], sub;
  386. switch (geometry.type) {
  387. case 'GeometryCollection':
  388. geometries = [];
  389. for (i = 0, il = geometry.geometries.length; i < il; i++) {
  390. if ((sub = getGeometries(geometry.geometries[i]))) {
  391. geometries.push.apply(geometries, sub);
  392. }
  393. }
  394. return geometries;
  395. case 'MultiPolygon':
  396. geometries = [];
  397. for (i = 0, il = geometry.coordinates.length; i < il; i++) {
  398. if ((sub = getGeometries({type: 'Polygon', coordinates: geometry.coordinates[i]}))) {
  399. geometries.push.apply(geometries, sub);
  400. }
  401. }
  402. return geometries;
  403. case 'Polygon':
  404. polygon = geometry.coordinates;
  405. break;
  406. default:
  407. return [];
  408. }
  409. var
  410. j, jl,
  411. p, lat = 1, lon = 0,
  412. outer = [], inner = [];
  413. p = polygon[0];
  414. for (i = 0, il = p.length; i < il; i++) {
  415. outer.push(p[i][lat], p[i][lon]);
  416. }
  417. outer = makeWinding(outer, WINDING_CLOCKWISE);
  418. for (i = 0, il = polygon.length - 1; i < il; i++) {
  419. p = polygon[i + 1];
  420. inner[i] = [];
  421. for (j = 0, jl = p.length; j < jl; j++) {
  422. inner[i].push(p[j][lat], p[j][lon]);
  423. }
  424. inner[i] = makeWinding(inner[i], WINDING_COUNTER_CLOCKWISE);
  425. }
  426. return [{
  427. outer: outer,
  428. inner: inner.length ? inner : null
  429. }];
  430. }
  431. function clone(obj) {
  432. var res = {};
  433. for (var p in obj) {
  434. if (obj.hasOwnProperty(p)) {
  435. res[p] = obj[p];
  436. }
  437. }
  438. return res;
  439. }
  440. return {
  441. read: function (geojson) {
  442. if (!geojson || geojson.type !== 'FeatureCollection') {
  443. return [];
  444. }
  445. var
  446. collection = geojson.features,
  447. i, il, j, jl,
  448. res = [],
  449. feature,
  450. geometries,
  451. baseItem, item;
  452. for (i = 0, il = collection.length; i < il; i++) {
  453. feature = collection[i];
  454. if (feature.type !== 'Feature' || onEach(feature) === false) {
  455. continue;
  456. }
  457. baseItem = alignProperties(feature.properties);
  458. geometries = getGeometries(feature.geometry);
  459. for (j = 0, jl = geometries.length; j < jl; j++) {
  460. item = clone(baseItem);
  461. item.footprint = geometries[j].outer;
  462. if (item.isRotational) {
  463. item.radius = getLonDelta(item.footprint);
  464. }
  465. if (geometries[j].inner) {
  466. item.holes = geometries[j].inner;
  467. }
  468. if (feature.id || feature.properties.id) {
  469. item.id = feature.id || feature.properties.id;
  470. }
  471. if (feature.properties.relationId) {
  472. item.relationId = feature.properties.relationId;
  473. }
  474. res.push(item); // TODO: clone base properties!
  475. }
  476. }
  477. return res;
  478. }
  479. };
  480. }());
  481. //****** file: variables.js ******
  482. var
  483. VERSION = '0.2.2b',
  484. ATTRIBUTION = '&copy; <a href="http://osmbuildings.org">OSM Buildings</a>',
  485. DATA_SRC = 'http://{s}.data.osmbuildings.org/0.2/{k}/tile/{z}/{x}/{y}.json', ////////////////geojson 数据
  486. PI = Math.PI,
  487. HALF_PI = PI / 2,
  488. QUARTER_PI = PI / 4,
  489. MAP_TILE_SIZE = 256, // map tile size in pixels
  490. DATA_TILE_SIZE = 0.0075, // data tile size in geo coordinates, smaller: less data to load but more requests
  491. ZOOM, MAP_SIZE,
  492. MIN_ZOOM = 8,
  493. LAT = 'latitude', LON = 'longitude',
  494. TRUE = true, FALSE = false,
  495. WIDTH = 0, HEIGHT = 0,
  496. CENTER_X = 0, CENTER_Y = 0,
  497. ORIGIN_X = 0, ORIGIN_Y = 0,
  498. WALL_COLOR = Color.parse('rgba(200, 190, 180)'),
  499. ALT_COLOR = WALL_COLOR.lightness(0.8),
  500. ROOF_COLOR = WALL_COLOR.lightness(1.2),
  501. WALL_COLOR_STR = '' + WALL_COLOR,
  502. ALT_COLOR_STR = '' + ALT_COLOR,
  503. ROOF_COLOR_STR = '' + ROOF_COLOR,
  504. PIXEL_PER_DEG = 0,
  505. ZOOM_FACTOR = 1,
  506. MAX_HEIGHT, // taller buildings will be cut to this
  507. DEFAULT_HEIGHT = 5,
  508. CAM_X, CAM_Y, CAM_Z = 450,
  509. isZooming;
  510. //****** file: geometry.js ******
  511. function getDistance(p1, p2) {
  512. var
  513. dx = p1.x - p2.x,
  514. dy = p1.y - p2.y;
  515. return dx * dx + dy * dy;
  516. }
  517. //是否可旋转
  518. function isRotational(polygon) {
  519. var length = polygon.length;
  520. if (length < 16) {
  521. return false;
  522. }
  523. var i;
  524. var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
  525. for (i = 0; i < length - 1; i += 2) {
  526. minX = Math.min(minX, polygon[i]);
  527. maxX = Math.max(maxX, polygon[i]);
  528. minY = Math.min(minY, polygon[i + 1]);
  529. maxY = Math.max(maxY, polygon[i + 1]);
  530. }
  531. var
  532. width = maxX - minX,
  533. height = (maxY - minY),
  534. ratio = width / height;
  535. if (ratio < 0.85 || ratio > 1.15) {
  536. return false;
  537. }
  538. var
  539. center = {x: minX + width / 2, y: minY + height / 2},
  540. radius = (width + height) / 4,
  541. sqRadius = radius * radius;
  542. for (i = 0; i < length - 1; i += 2) {
  543. var dist = getDistance({x: polygon[i], y: polygon[i + 1]}, center);
  544. if (dist / sqRadius < 0.8 || dist / sqRadius > 1.2) {
  545. return false;
  546. }
  547. }
  548. return true;
  549. }
  550. function getSquareSegmentDistance(px, py, p1x, p1y, p2x, p2y) {
  551. var
  552. dx = p2x - p1x,
  553. dy = p2y - p1y,
  554. t;
  555. if (dx !== 0 || dy !== 0) {
  556. t = ((px - p1x) * dx + (py - p1y) * dy) / (dx * dx + dy * dy);
  557. if (t > 1) {
  558. p1x = p2x;
  559. p1y = p2y;
  560. } else if (t > 0) {
  561. p1x += dx * t;
  562. p1y += dy * t;
  563. }
  564. }
  565. dx = px - p1x;
  566. dy = py - p1y;
  567. return dx * dx + dy * dy;
  568. }
  569. function simplifyPolygon(buffer) {
  570. var
  571. sqTolerance = 2,
  572. len = buffer.length / 2,
  573. markers = new Uint8Array(len),
  574. first = 0, last = len - 1,
  575. i,
  576. maxSqDist,
  577. sqDist,
  578. index,
  579. firstStack = [], lastStack = [],
  580. newBuffer = [];
  581. markers[first] = markers[last] = 1;
  582. while (last) {
  583. maxSqDist = 0;
  584. for (i = first + 1; i < last; i++) {
  585. sqDist = getSquareSegmentDistance(
  586. buffer[i * 2], buffer[i * 2 + 1],
  587. buffer[first * 2], buffer[first * 2 + 1],
  588. buffer[last * 2], buffer[last * 2 + 1]
  589. );
  590. if (sqDist > maxSqDist) {
  591. index = i;
  592. maxSqDist = sqDist;
  593. }
  594. }
  595. if (maxSqDist > sqTolerance) {
  596. markers[index] = 1;
  597. firstStack.push(first);
  598. lastStack.push(index);
  599. firstStack.push(index);
  600. lastStack.push(last);
  601. }
  602. first = firstStack.pop();
  603. last = lastStack.pop();
  604. }
  605. for (i = 0; i < len; i++) {
  606. if (markers[i]) {
  607. newBuffer.push(buffer[i * 2], buffer[i * 2 + 1]);
  608. }
  609. }
  610. return newBuffer;
  611. }
  612. function getCenter(footprint) {
  613. var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
  614. for (var i = 0, il = footprint.length - 3; i < il; i += 2) {
  615. minX = min(minX, footprint[i]);
  616. maxX = max(maxX, footprint[i]);
  617. minY = min(minY, footprint[i + 1]);
  618. maxY = max(maxY, footprint[i + 1]);
  619. }
  620. return {x: minX + (maxX - minX) / 2 << 0, y: minY + (maxY - minY) / 2 << 0};
  621. }
  622. var EARTH_RADIUS = 6378137;
  623. function getLonDelta(footprint) {
  624. var minLon = 180, maxLon = -180;
  625. for (var i = 0, il = footprint.length; i < il; i += 2) {
  626. minLon = min(minLon, footprint[i + 1]);
  627. maxLon = max(maxLon, footprint[i + 1]);
  628. }
  629. return (maxLon - minLon) / 2;
  630. }
  631. //****** file: functions.js ******
  632. function rad(deg) {
  633. return deg * PI / 180;
  634. }
  635. function deg(rad) {
  636. return rad / PI * 180;
  637. }
  638. function pixelToGeo(x, y) {
  639. var res = {};
  640. x /= MAP_SIZE;
  641. y /= MAP_SIZE;
  642. res[LAT] = y <= 0 ? 90 : y >= 1 ? -90 : deg(2 * atan(exp(PI * (1 - 2 * y))) - HALF_PI);
  643. res[LON] = (x === 1 ? 1 : (x % 1 + 1) % 1) * 360 - 180;
  644. return res;
  645. }
  646. function geoToPixel(lat, lon) {
  647. var latLngProjection = isLatLngProjection();
  648. if (latLngProjection) {
  649. var latitude = ((90 - lat) / 180),
  650. longitude = lon / 360 + 0.5;
  651. return {
  652. x: (longitude * MAP_SIZE) << 0,
  653. y: (latitude * MAP_SIZE / 2) << 0
  654. };
  655. } else {
  656. var latitude = min(1, max(0, 0.5 - (log(tan(QUARTER_PI + HALF_PI * lat / 180)) / PI) / 2)),
  657. longitude = lon / 360 + 0.5;
  658. return {
  659. x: (longitude * MAP_SIZE) << 0,
  660. y: (latitude * MAP_SIZE) << 0
  661. };
  662. }
  663. }
  664. function isLatLngProjection() {
  665. var code = mapObj.getProjection();
  666. if (code.indexOf('3857') > -1) {
  667. return false;
  668. }
  669. if (code.indexOf('4326') > -1||code.indexOf('4490') > -1) {
  670. return true;
  671. }
  672. console.log('Only support EPSG:3857 and EPSG:4326(4490) projection!');
  673. return false;
  674. }
  675. function fromRange(sVal, sMin, sMax, dMin, dMax) {
  676. sVal = min(max(sVal, sMin), sMax);
  677. var rel = (sVal - sMin) / (sMax - sMin),
  678. range = dMax - dMin;
  679. return min(max(dMin + rel * range, dMin), dMax);
  680. }
  681. function isVisible(polygon) {
  682. var
  683. maxX = WIDTH + ORIGIN_X,
  684. maxY = HEIGHT + ORIGIN_Y;
  685. // TODO: checking footprint is sufficient for visibility - NOT VALID FOR SHADOWS!
  686. for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
  687. if (polygon[i] > ORIGIN_X && polygon[i] < maxX && polygon[i + 1] > ORIGIN_Y && polygon[i + 1] < maxY) {
  688. return true;
  689. }
  690. }
  691. return false;
  692. }
  693. //数据请求 原来是在线OSMBuilding OSM与上请求 Builldings Geojson 数据
  694. //****** file: Request.js ******
  695. var Request = (function () {
  696. //var cacheData = {};
  697. //var cacheIndex = [];
  698. //var cacheSize = 0;
  699. //var maxCacheSize = 1024*1024 * 5; // 5MB
  700. //
  701. //function xhr(url, callback) {
  702. // if (cacheData[url]) {
  703. // if (callback) {
  704. // callback(cacheData[url]);
  705. // }
  706. // return;
  707. // }
  708. //
  709. // var req = new XMLHttpRequest();
  710. //
  711. // req.onreadystatechange = function() {
  712. // if (req.readyState !== 4) {
  713. // return;
  714. // }
  715. // if (!req.status || req.status < 200 || req.status > 299) {
  716. // return;
  717. // }
  718. // if (callback && req.responseText) {
  719. // var responseText = req.responseText;
  720. //
  721. // cacheData[url] = responseText;
  722. // cacheIndex.push({ url: url, size: responseText.length });
  723. // cacheSize += responseText.length;
  724. //
  725. // callback(responseText);
  726. //
  727. // while (cacheSize > maxCacheSize) {
  728. // var item = cacheIndex.shift();
  729. // cacheSize -= item.size;
  730. // delete cacheData[item.url];
  731. // }
  732. // }
  733. // };
  734. //
  735. // req.open('GET', url);
  736. // req.send(null);
  737. //
  738. // return req;
  739. //}
  740. //
  741. //return {
  742. // loadJSON: function(url, callback) {
  743. // return xhr(url, function(responseText) {
  744. // var json;
  745. // try {
  746. // json = JSON.parse(responseText);
  747. // } catch(ex) {}
  748. // callback(json);
  749. // });
  750. // }
  751. //};
  752. }());
  753. //****** file: Data.js ******
  754. var Data = {
  755. loadedItems: {}, // maintain a list of cached items in order to avoid duplicates on tile borders
  756. items: [],
  757. getPixelFootprint: function (buffer) {
  758. var footprint = new Int32Array(buffer.length),
  759. px;
  760. for (var i = 0, il = buffer.length - 1; i < il; i += 2) {
  761. px = geoToPixel(buffer[i], buffer[i + 1]);
  762. footprint[i] = px.x;
  763. footprint[i + 1] = px.y;
  764. }
  765. footprint = simplifyPolygon(footprint);
  766. if (footprint.length < 8) { // 3 points & end==start (*2)
  767. return;
  768. }
  769. return footprint;
  770. },
  771. resetItems: function () {
  772. this.items = [];
  773. this.loadedItems = {};
  774. HitAreas.reset();
  775. },
  776. addRenderItems: function (data, allAreNew) {
  777. var item, scaledItem, id;
  778. var geojson = GeoJSON.read(data);
  779. for (var i = 0, il = geojson.length; i < il; i++) {
  780. item = geojson[i];
  781. id = item.id || [item.footprint[0], item.footprint[1], item.height, item.minHeight].join(',');
  782. if (!this.loadedItems[id]) {
  783. if ((scaledItem = this.scale(item))) {
  784. scaledItem.scale = allAreNew ? 0 : 1;
  785. this.items.push(scaledItem);
  786. this.loadedItems[id] = 1;
  787. }
  788. }
  789. }
  790. fadeIn();
  791. },
  792. scale: function (item) {
  793. var
  794. res = {},
  795. // TODO: calculate this on zoom change only
  796. zoomScale = 6 / pow(2, ZOOM - MIN_ZOOM); // TODO: consider using HEIGHT / (global.devicePixelRatio || 1)
  797. if (item.id) {
  798. res.id = item.id;
  799. }
  800. res.height = min(item.height / zoomScale, MAX_HEIGHT);
  801. res.minHeight = isNaN(item.minHeight) ? 0 : item.minHeight / zoomScale;
  802. if (res.minHeight > MAX_HEIGHT) {
  803. return;
  804. }
  805. res.footprint = this.getPixelFootprint(item.footprint);
  806. if (!res.footprint) {
  807. return;
  808. }
  809. res.center = getCenter(res.footprint);
  810. if (item.radius) {
  811. res.radius = item.radius * PIXEL_PER_DEG;
  812. }
  813. if (item.shape) {
  814. res.shape = item.shape;
  815. }
  816. if (item.roofShape) {
  817. res.roofShape = item.roofShape;
  818. }
  819. if ((res.roofShape === 'cone' || res.roofShape === 'dome') && !res.shape && isRotational(res.footprint)) {
  820. res.shape = 'cylinder';
  821. }
  822. if (item.holes) {
  823. res.holes = [];
  824. var innerFootprint;
  825. for (var i = 0, il = item.holes.length; i < il; i++) {
  826. // TODO: simplify
  827. if ((innerFootprint = this.getPixelFootprint(item.holes[i]))) {
  828. res.holes.push(innerFootprint);
  829. }
  830. }
  831. }
  832. var color;
  833. if (item.wallColor) {
  834. if ((color = Color.parse(item.wallColor))) {
  835. color = color.alpha(ZOOM_FACTOR);
  836. res.altColor = '' + color.lightness(0.8);
  837. res.wallColor = '' + color;
  838. }
  839. }
  840. if (item.roofColor) {
  841. if ((color = Color.parse(item.roofColor))) {
  842. res.roofColor = '' + color.alpha(ZOOM_FACTOR);
  843. }
  844. }
  845. if (item.relationId) {
  846. res.relationId = item.relationId;
  847. }
  848. res.hitColor = HitAreas.idToColor(item.relationId || item.id);
  849. res.roofHeight = isNaN(item.roofHeight) ? 0 : item.roofHeight / zoomScale;
  850. if (res.height + res.roofHeight <= res.minHeight) {
  851. return;
  852. }
  853. return res;
  854. },
  855. set: function (data) {
  856. this.isStatic = true;
  857. this.resetItems();
  858. this._staticData = data;
  859. this.addRenderItems(this._staticData, true);
  860. },
  861. load: function (src, key) {
  862. this.src = src || DATA_SRC.replace('{k}', (key || 'anonymous'));
  863. this.update();
  864. },
  865. update: function () {
  866. this.resetItems();
  867. if (ZOOM < MIN_ZOOM) {
  868. return;
  869. }
  870. if (this.isStatic && this._staticData) {
  871. this.addRenderItems(this._staticData);
  872. return;
  873. }
  874. if (!this.src) {
  875. return;
  876. }
  877. var
  878. tileZoom = 13,
  879. tileSize = 256,
  880. zoomedTileSize = ZOOM > tileZoom ? tileSize << (ZOOM - tileZoom) : tileSize >> (tileZoom - ZOOM),
  881. minX = ORIGIN_X / zoomedTileSize << 0,
  882. minY = ORIGIN_Y / zoomedTileSize << 0,
  883. maxX = ceil((ORIGIN_X + WIDTH) / zoomedTileSize),
  884. maxY = ceil((ORIGIN_Y + HEIGHT) / zoomedTileSize),
  885. x, y;
  886. var scope = this;
  887. function callback(json) {
  888. scope.addRenderItems(json);
  889. }
  890. for (y = minY; y <= maxY; y++) {
  891. for (x = minX; x <= maxX; x++) {
  892. this.loadTile(x, y, tileZoom, callback);
  893. }
  894. }
  895. },
  896. loadTile: function (x, y, zoom, callback) {
  897. var s = 'abcd'[(x + y) % 4];
  898. var url = this.src.replace('{s}', s).replace('{x}', x).replace('{y}', y).replace('{z}', zoom);
  899. //return Request.loadJSON(url, callback);
  900. }
  901. };
  902. //块状体
  903. //****** file: Block.js ******
  904. var Block = {
  905. draw: function (context, polygon, innerPolygons, height, minHeight, color, altColor, roofColor) {
  906. var
  907. i, il,
  908. roof = this._extrude(context, polygon, height, minHeight, color, altColor),
  909. innerRoofs = [];
  910. if (innerPolygons) {
  911. for (i = 0, il = innerPolygons.length; i < il; i++) {
  912. innerRoofs[i] = this._extrude(context, innerPolygons[i], height, minHeight, color, altColor);
  913. }
  914. }
  915. context.fillStyle = roofColor;
  916. context.beginPath();
  917. this._ring(context, roof);
  918. if (innerPolygons) {
  919. for (i = 0, il = innerRoofs.length; i < il; i++) {
  920. this._ring(context, innerRoofs[i]);
  921. }
  922. }
  923. context.closePath();
  924. context.stroke();
  925. context.fill();
  926. },
  927. _extrude: function (context, polygon, height, minHeight, color, altColor) {
  928. var
  929. scale = CAM_Z / (CAM_Z - height),
  930. minScale = CAM_Z / (CAM_Z - minHeight),
  931. a = {x: 0, y: 0},
  932. b = {x: 0, y: 0},
  933. _a, _b,
  934. roof = [];
  935. for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
  936. a.x = polygon[i] - ORIGIN_X;
  937. a.y = polygon[i + 1] - ORIGIN_Y;
  938. b.x = polygon[i + 2] - ORIGIN_X;
  939. b.y = polygon[i + 3] - ORIGIN_Y;
  940. _a = Buildings.project(a, scale);
  941. _b = Buildings.project(b, scale);
  942. if (minHeight) {
  943. a = Buildings.project(a, minScale);
  944. b = Buildings.project(b, minScale);
  945. }
  946. // backface culling check
  947. if ((b.x - a.x) * (_a.y - a.y) > (_a.x - a.x) * (b.y - a.y)) {
  948. // depending on direction, set wall shading
  949. if ((a.x < b.x && a.y < b.y) || (a.x > b.x && a.y > b.y)) {
  950. context.fillStyle = altColor;
  951. } else {
  952. context.fillStyle = color;
  953. }
  954. context.beginPath();
  955. this._ring(context, [
  956. b.x, b.y,
  957. a.x, a.y,
  958. _a.x, _a.y,
  959. _b.x, _b.y
  960. ]);
  961. context.closePath();
  962. context.fill();
  963. }
  964. roof[i] = _a.x;
  965. roof[i + 1] = _a.y;
  966. }
  967. return roof;
  968. },
  969. _ring: function (context, polygon) {
  970. context.moveTo(polygon[0], polygon[1]);
  971. for (var i = 2, il = polygon.length - 1; i < il; i += 2) {
  972. context.lineTo(polygon[i], polygon[i + 1]);
  973. }
  974. },
  975. simplified: function (context, polygon, innerPolygons) {
  976. context.beginPath();
  977. this._ringAbs(context, polygon);
  978. if (innerPolygons) {
  979. for (var i = 0, il = innerPolygons.length; i < il; i++) {
  980. this._ringAbs(context, innerPolygons[i]);
  981. }
  982. }
  983. context.closePath();
  984. context.stroke();
  985. context.fill();
  986. },
  987. _ringAbs: function (context, polygon) {
  988. context.moveTo(polygon[0] - ORIGIN_X, polygon[1] - ORIGIN_Y);
  989. for (var i = 2, il = polygon.length - 1; i < il; i += 2) {
  990. context.lineTo(polygon[i] - ORIGIN_X, polygon[i + 1] - ORIGIN_Y);
  991. }
  992. },
  993. shadow: function (context, polygon, innerPolygons, height, minHeight) {
  994. var
  995. mode = null,
  996. a = {x: 0, y: 0},
  997. b = {x: 0, y: 0},
  998. _a, _b;
  999. for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
  1000. a.x = polygon[i] - ORIGIN_X;
  1001. a.y = polygon[i + 1] - ORIGIN_Y;
  1002. b.x = polygon[i + 2] - ORIGIN_X;
  1003. b.y = polygon[i + 3] - ORIGIN_Y;
  1004. _a = Shadows.project(a, height);
  1005. _b = Shadows.project(b, height);
  1006. if (minHeight) {
  1007. a = Shadows.project(a, minHeight);
  1008. b = Shadows.project(b, minHeight);
  1009. }
  1010. // mode 0: floor edges, mode 1: roof edges
  1011. if ((b.x - a.x) * (_a.y - a.y) > (_a.x - a.x) * (b.y - a.y)) {
  1012. if (mode === 1) {
  1013. context.lineTo(a.x, a.y);
  1014. }
  1015. mode = 0;
  1016. if (!i) {
  1017. context.moveTo(a.x, a.y);
  1018. }
  1019. context.lineTo(b.x, b.y);
  1020. } else {
  1021. if (mode === 0) {
  1022. context.lineTo(_a.x, _a.y);
  1023. }
  1024. mode = 1;
  1025. if (!i) {
  1026. context.moveTo(_a.x, _a.y);
  1027. }
  1028. context.lineTo(_b.x, _b.y);
  1029. }
  1030. }
  1031. if (innerPolygons) {
  1032. for (i = 0, il = innerPolygons.length; i < il; i++) {
  1033. this._ringAbs(context, innerPolygons[i]);
  1034. }
  1035. }
  1036. },
  1037. shadowMask: function (context, polygon, innerPolygons) {
  1038. this._ringAbs(context, polygon);
  1039. if (innerPolygons) {
  1040. for (var i = 0, il = innerPolygons.length; i < il; i++) {
  1041. this._ringAbs(context, innerPolygons[i]);
  1042. }
  1043. }
  1044. },
  1045. hitArea: function (context, polygon, innerPolygons, height, minHeight, color) {
  1046. var
  1047. mode = null,
  1048. a = {x: 0, y: 0},
  1049. b = {x: 0, y: 0},
  1050. scale = CAM_Z / (CAM_Z - height),
  1051. minScale = CAM_Z / (CAM_Z - minHeight),
  1052. _a, _b;
  1053. context.fillStyle = color;
  1054. context.beginPath();
  1055. for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
  1056. a.x = polygon[i] - ORIGIN_X;
  1057. a.y = polygon[i + 1] - ORIGIN_Y;
  1058. b.x = polygon[i + 2] - ORIGIN_X;
  1059. b.y = polygon[i + 3] - ORIGIN_Y;
  1060. _a = Buildings.project(a, scale);
  1061. _b = Buildings.project(b, scale);
  1062. if (minHeight) {
  1063. a = Buildings.project(a, minScale);
  1064. b = Buildings.project(b, minScale);
  1065. }
  1066. // mode 0: floor edges, mode 1: roof edges
  1067. if ((b.x - a.x) * (_a.y - a.y) > (_a.x - a.x) * (b.y - a.y)) {
  1068. if (mode === 1) { // mode is initially undefined
  1069. context.lineTo(a.x, a.y);
  1070. }
  1071. mode = 0;
  1072. if (!i) {
  1073. context.moveTo(a.x, a.y);
  1074. }
  1075. context.lineTo(b.x, b.y);
  1076. } else {
  1077. if (mode === 0) { // mode is initially undefined
  1078. context.lineTo(_a.x, _a.y);
  1079. }
  1080. mode = 1;
  1081. if (!i) {
  1082. context.moveTo(_a.x, _a.y);
  1083. }
  1084. context.lineTo(_b.x, _b.y);
  1085. }
  1086. }
  1087. context.closePath();
  1088. context.fill();
  1089. }
  1090. };
  1091. //圆柱体
  1092. //****** file: Cylinder.js ******
  1093. var Cylinder = {
  1094. draw: function (context, center, radius, topRadius, height, minHeight, color, altColor, roofColor) {
  1095. var
  1096. c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y},
  1097. scale = CAM_Z / (CAM_Z - height),
  1098. minScale = CAM_Z / (CAM_Z - minHeight),
  1099. apex = Buildings.project(c, scale),
  1100. a1, a2;
  1101. topRadius *= scale;
  1102. if (minHeight) {
  1103. c = Buildings.project(c, minScale);
  1104. radius = radius * minScale;
  1105. }
  1106. // common tangents for ground and roof circle
  1107. var tangents = this._tangents(c, radius, apex, topRadius);
  1108. // no tangents? top circle is inside bottom circle
  1109. if (!tangents) {
  1110. a1 = 1.5 * PI;
  1111. a2 = 1.5 * PI;
  1112. } else {
  1113. a1 = atan2(tangents[0].y1 - c.y, tangents[0].x1 - c.x);
  1114. a2 = atan2(tangents[1].y1 - c.y, tangents[1].x1 - c.x);
  1115. }
  1116. context.fillStyle = color;
  1117. context.beginPath();
  1118. context.arc(apex.x, apex.y, topRadius, HALF_PI, a1, true);
  1119. context.arc(c.x, c.y, radius, a1, HALF_PI);
  1120. context.closePath();
  1121. context.fill();
  1122. context.fillStyle = altColor;
  1123. context.beginPath();
  1124. context.arc(apex.x, apex.y, topRadius, a2, HALF_PI, true);
  1125. context.arc(c.x, c.y, radius, HALF_PI, a2);
  1126. context.closePath();
  1127. context.fill();
  1128. context.fillStyle = roofColor;
  1129. this._circle(context, apex, topRadius);
  1130. },
  1131. simplified: function (context, center, radius) {
  1132. this._circle(context, {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y}, radius);
  1133. },
  1134. shadow: function (context, center, radius, topRadius, height, minHeight) {
  1135. var
  1136. c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y},
  1137. apex = Shadows.project(c, height),
  1138. p1, p2;
  1139. if (minHeight) {
  1140. c = Shadows.project(c, minHeight);
  1141. }
  1142. // common tangents for ground and roof circle
  1143. var tangents = this._tangents(c, radius, apex, topRadius);
  1144. // TODO: no tangents? roof overlaps everything near cam position
  1145. if (tangents) {
  1146. p1 = atan2(tangents[0].y1 - c.y, tangents[0].x1 - c.x);
  1147. p2 = atan2(tangents[1].y1 - c.y, tangents[1].x1 - c.x);
  1148. context.moveTo(tangents[1].x2, tangents[1].y2);
  1149. context.arc(apex.x, apex.y, topRadius, p2, p1);
  1150. context.arc(c.x, c.y, radius, p1, p2);
  1151. } else {
  1152. context.moveTo(c.x + radius, c.y);
  1153. context.arc(c.x, c.y, radius, 0, 2 * PI);
  1154. }
  1155. },
  1156. shadowMask: function (context, center, radius) {
  1157. var c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y};
  1158. context.moveTo(c.x + radius, c.y);
  1159. context.arc(c.x, c.y, radius, 0, PI * 2);
  1160. },
  1161. hitArea: function (context, center, radius, topRadius, height, minHeight, color) {
  1162. var
  1163. c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y},
  1164. scale = CAM_Z / (CAM_Z - height),
  1165. minScale = CAM_Z / (CAM_Z - minHeight),
  1166. apex = Buildings.project(c, scale),
  1167. p1, p2;
  1168. topRadius *= scale;
  1169. if (minHeight) {
  1170. c = Buildings.project(c, minScale);
  1171. radius = radius * minScale;
  1172. }
  1173. // common tangents for ground and roof circle
  1174. var tangents = this._tangents(c, radius, apex, topRadius);
  1175. context.fillStyle = color;
  1176. context.beginPath();
  1177. // TODO: no tangents? roof overlaps everything near cam position
  1178. if (tangents) {
  1179. p1 = atan2(tangents[0].y1 - c.y, tangents[0].x1 - c.x);
  1180. p2 = atan2(tangents[1].y1 - c.y, tangents[1].x1 - c.x);
  1181. context.moveTo(tangents[1].x2, tangents[1].y2);
  1182. context.arc(apex.x, apex.y, topRadius, p2, p1);
  1183. context.arc(c.x, c.y, radius, p1, p2);
  1184. } else {
  1185. context.moveTo(c.x + radius, c.y);
  1186. context.arc(c.x, c.y, radius, 0, 2 * PI);
  1187. }
  1188. context.closePath();
  1189. context.fill();
  1190. },
  1191. _circle: function (context, center, radius) {
  1192. context.beginPath();
  1193. context.arc(center.x, center.y, radius, 0, PI * 2);
  1194. context.stroke();
  1195. context.fill();
  1196. },
  1197. // http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Tangents_between_two_circles
  1198. _tangents: function (c1, r1, c2, r2) {
  1199. var
  1200. dx = c1.x - c2.x,
  1201. dy = c1.y - c2.y,
  1202. dr = r1 - r2,
  1203. sqdist = (dx * dx) + (dy * dy);
  1204. if (sqdist <= dr * dr) {
  1205. return;
  1206. }
  1207. var dist = sqrt(sqdist),
  1208. vx = -dx / dist,
  1209. vy = -dy / dist,
  1210. c = dr / dist,
  1211. res = [],
  1212. h, nx, ny;
  1213. // Let A, B be the centers, and C, D be points at which the tangent
  1214. // touches first and second circle, and n be the normal vector to it.
  1215. //
  1216. // We have the system:
  1217. // n * n = 1 (n is a unit vector)
  1218. // C = A + r1 * n
  1219. // D = B + r2 * n
  1220. // n * CD = 0 (common orthogonality)
  1221. //
  1222. // n * CD = n * (AB + r2*n - r1*n) = AB*n - (r1 -/+ r2) = 0, <=>
  1223. // AB * n = (r1 -/+ r2), <=>
  1224. // v * n = (r1 -/+ r2) / d, where v = AB/|AB| = AB/d
  1225. // This is a linear equation in unknown vector n.
  1226. // Now we're just intersecting a line with a circle: v*n=c, n*n=1
  1227. h = sqrt(max(0, 1 - c * c));
  1228. for (var sign = 1; sign >= -1; sign -= 2) {
  1229. nx = vx * c - sign * h * vy;
  1230. ny = vy * c + sign * h * vx;
  1231. res.push({
  1232. x1: c1.x + r1 * nx << 0,
  1233. y1: c1.y + r1 * ny << 0,
  1234. x2: c2.x + r2 * nx << 0,
  1235. y2: c2.y + r2 * ny << 0
  1236. });
  1237. }
  1238. return res;
  1239. }
  1240. };
  1241. //椎体
  1242. //****** file: Pyramid.js ******
  1243. var Pyramid = {
  1244. draw: function (context, polygon, center, height, minHeight, color, altColor) {
  1245. var
  1246. c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y},
  1247. scale = CAM_Z / (CAM_Z - height),
  1248. minScale = CAM_Z / (CAM_Z - minHeight),
  1249. apex = Buildings.project(c, scale),
  1250. a = {x: 0, y: 0},
  1251. b = {x: 0, y: 0};
  1252. for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
  1253. a.x = polygon[i] - ORIGIN_X;
  1254. a.y = polygon[i + 1] - ORIGIN_Y;
  1255. b.x = polygon[i + 2] - ORIGIN_X;
  1256. b.y = polygon[i + 3] - ORIGIN_Y;
  1257. if (minHeight) {
  1258. a = Buildings.project(a, minScale);
  1259. b = Buildings.project(b, minScale);
  1260. }
  1261. // backface culling check
  1262. if ((b.x - a.x) * (apex.y - a.y) > (apex.x - a.x) * (b.y - a.y)) {
  1263. // depending on direction, set shading
  1264. if ((a.x < b.x && a.y < b.y) || (a.x > b.x && a.y > b.y)) {
  1265. context.fillStyle = altColor;
  1266. } else {
  1267. context.fillStyle = color;
  1268. }
  1269. context.beginPath();
  1270. this._triangle(context, a, b, apex);
  1271. context.closePath();
  1272. context.fill();
  1273. }
  1274. }
  1275. },
  1276. _triangle: function (context, a, b, c) {
  1277. context.moveTo(a.x, a.y);
  1278. context.lineTo(b.x, b.y);
  1279. context.lineTo(c.x, c.y);
  1280. },
  1281. _ring: function (context, polygon) {
  1282. context.moveTo(polygon[0] - ORIGIN_X, polygon[1] - ORIGIN_Y);
  1283. for (var i = 2, il = polygon.length - 1; i < il; i += 2) {
  1284. context.lineTo(polygon[i] - ORIGIN_X, polygon[i + 1] - ORIGIN_Y);
  1285. }
  1286. },
  1287. shadow: function (context, polygon, center, height, minHeight) {
  1288. var
  1289. a = {x: 0, y: 0},
  1290. b = {x: 0, y: 0},
  1291. c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y},
  1292. apex = Shadows.project(c, height);
  1293. for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
  1294. a.x = polygon[i] - ORIGIN_X;
  1295. a.y = polygon[i + 1] - ORIGIN_Y;
  1296. b.x = polygon[i + 2] - ORIGIN_X;
  1297. b.y = polygon[i + 3] - ORIGIN_Y;
  1298. if (minHeight) {
  1299. a = Shadows.project(a, minHeight);
  1300. b = Shadows.project(b, minHeight);
  1301. }
  1302. // backface culling check
  1303. if ((b.x - a.x) * (apex.y - a.y) > (apex.x - a.x) * (b.y - a.y)) {
  1304. // depending on direction, set shading
  1305. this._triangle(context, a, b, apex);
  1306. }
  1307. }
  1308. },
  1309. shadowMask: function (context, polygon) {
  1310. this._ring(context, polygon);
  1311. },
  1312. hitArea: function (context, polygon, center, height, minHeight, color) {
  1313. var
  1314. c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y},
  1315. scale = CAM_Z / (CAM_Z - height),
  1316. minScale = CAM_Z / (CAM_Z - minHeight),
  1317. apex = Buildings.project(c, scale),
  1318. a = {x: 0, y: 0},
  1319. b = {x: 0, y: 0};
  1320. context.fillStyle = color;
  1321. context.beginPath();
  1322. for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
  1323. a.x = polygon[i] - ORIGIN_X;
  1324. a.y = polygon[i + 1] - ORIGIN_Y;
  1325. b.x = polygon[i + 2] - ORIGIN_X;
  1326. b.y = polygon[i + 3] - ORIGIN_Y;
  1327. if (minHeight) {
  1328. a = Buildings.project(a, minScale);
  1329. b = Buildings.project(b, minScale);
  1330. }
  1331. // backface culling check
  1332. if ((b.x - a.x) * (apex.y - a.y) > (apex.x - a.x) * (b.y - a.y)) {
  1333. this._triangle(context, a, b, apex);
  1334. }
  1335. }
  1336. context.closePath();
  1337. context.fill();
  1338. }
  1339. };
  1340. //****** file: Buildings.js ******
  1341. var Buildings = {
  1342. project: function (p, m) {
  1343. return {
  1344. x: (p.x - CAM_X) * m + CAM_X << 0,
  1345. y: (p.y - CAM_Y) * m + CAM_Y << 0
  1346. };
  1347. },
  1348. render: function () {
  1349. var context = this.context;
  1350. context.clearRect(0, 0, WIDTH, HEIGHT);
  1351. // show on high zoom levels only and avoid rendering during zoom
  1352. if (ZOOM < MIN_ZOOM || isZooming) {
  1353. return;
  1354. }
  1355. var
  1356. item,
  1357. h, mh,
  1358. sortCam = {x: CAM_X + ORIGIN_X, y: CAM_Y + ORIGIN_Y},
  1359. footprint,
  1360. wallColor, altColor, roofColor,
  1361. dataItems = Data.items;
  1362. dataItems.sort(function (a, b) {
  1363. return (a.minHeight - b.minHeight) || getDistance(b.center, sortCam) - getDistance(a.center, sortCam) || (b.height - a.height);
  1364. });
  1365. for (var i = 0, il = dataItems.length; i < il; i++) {
  1366. item = dataItems[i];
  1367. if (Simplified.isSimple(item)) {
  1368. continue;
  1369. }
  1370. footprint = item.footprint;
  1371. if (!isVisible(footprint)) {
  1372. continue;
  1373. }
  1374. // when fading in, use a dynamic height
  1375. h = item.scale < 1 ? item.height * item.scale : item.height;
  1376. mh = 0;
  1377. if (item.minHeight) {
  1378. mh = item.scale < 1 ? item.minHeight * item.scale : item.minHeight;
  1379. }
  1380. wallColor = item.wallColor || WALL_COLOR_STR;
  1381. altColor = item.altColor || ALT_COLOR_STR;
  1382. roofColor = item.roofColor || ROOF_COLOR_STR;
  1383. context.strokeStyle = altColor;
  1384. switch (item.shape) {
  1385. case 'cylinder':
  1386. Cylinder.draw(context, item.center, item.radius, item.radius, h, mh, wallColor, altColor, roofColor);
  1387. break;
  1388. case 'cone':
  1389. Cylinder.draw(context, item.center, item.radius, 0, h, mh, wallColor, altColor);
  1390. break;
  1391. case 'dome':
  1392. Cylinder.draw(context, item.center, item.radius, item.radius / 2, h, mh, wallColor, altColor);
  1393. break;
  1394. case 'sphere':
  1395. Cylinder.draw(context, item.center, item.radius, item.radius, h, mh, wallColor, altColor, roofColor);
  1396. break;
  1397. case 'pyramid':
  1398. Pyramid.draw(context, footprint, item.center, h, mh, wallColor, altColor);
  1399. break;
  1400. default:
  1401. Block.draw(context, footprint, item.holes, h, mh, wallColor, altColor, roofColor);
  1402. }
  1403. switch (item.roofShape) {
  1404. case 'cone':
  1405. Cylinder.draw(context, item.center, item.radius, 0, h + item.roofHeight, h, roofColor, '' + Color.parse(roofColor).lightness(0.9));
  1406. break;
  1407. case 'dome':
  1408. Cylinder.draw(context, item.center, item.radius, item.radius / 2, h + item.roofHeight, h, roofColor, '' + Color.parse(roofColor).lightness(0.9));
  1409. break;
  1410. case 'pyramid':
  1411. Pyramid.draw(context, footprint, item.center, h + item.roofHeight, h, roofColor, Color.parse(roofColor).lightness(0.9));
  1412. break;
  1413. }
  1414. }
  1415. }
  1416. };
  1417. //****** file: Simplified.js ******
  1418. var Simplified = {
  1419. maxZoom: MIN_ZOOM + 2,
  1420. maxHeight: 5,
  1421. isSimple: function (item) {
  1422. return (ZOOM <= this.maxZoom && item.height + item.roofHeight < this.maxHeight);
  1423. },
  1424. render: function () {
  1425. var context = this.context;
  1426. context.clearRect(0, 0, WIDTH, HEIGHT);
  1427. // show on high zoom levels only and avoid rendering during zoom
  1428. if (ZOOM < MIN_ZOOM || isZooming || ZOOM > this.maxZoom) {
  1429. return;
  1430. }
  1431. var
  1432. item,
  1433. footprint,
  1434. dataItems = Data.items;
  1435. for (var i = 0, il = dataItems.length; i < il; i++) {
  1436. item = dataItems[i];
  1437. if (item.height >= this.maxHeight) {
  1438. continue;
  1439. }
  1440. footprint = item.footprint;
  1441. if (!isVisible(footprint)) {
  1442. continue;
  1443. }
  1444. context.strokeStyle = item.altColor || ALT_COLOR_STR;
  1445. context.fillStyle = item.roofColor || ROOF_COLOR_STR;
  1446. switch (item.shape) {
  1447. case 'cylinder':
  1448. case 'cone':
  1449. case 'dome':
  1450. case 'sphere':
  1451. Cylinder.simplified(context, item.center, item.radius);
  1452. break;
  1453. default:
  1454. Block.simplified(context, footprint, item.holes);
  1455. }
  1456. }
  1457. }
  1458. };
  1459. //****** file: Shadows.js ******
  1460. var Shadows = {
  1461. enabled: true,
  1462. color: '#666666',
  1463. blurColor: '#000000',
  1464. blurSize: 15,
  1465. date: new Date(),
  1466. direction: {x: 0, y: 0},
  1467. project: function (p, h) {
  1468. return {
  1469. x: p.x + this.direction.x * h,
  1470. y: p.y + this.direction.y * h
  1471. };
  1472. },
  1473. render: function () {
  1474. var
  1475. context = this.context,
  1476. screenCenter, sun, length, alpha;
  1477. context.clearRect(0, 0, WIDTH, HEIGHT);
  1478. // show on high zoom levels only and avoid rendering during zoom
  1479. if (!this.enabled || ZOOM < MIN_ZOOM || isZooming) {
  1480. return;
  1481. }
  1482. // TODO: calculate this just on demand
  1483. screenCenter = pixelToGeo(CENTER_X + ORIGIN_X, CENTER_Y + ORIGIN_Y);
  1484. sun = getSunPosition(this.date, screenCenter.latitude, screenCenter.longitude);
  1485. if (sun.altitude <= 0) {
  1486. return;
  1487. }
  1488. length = 1 / tan(sun.altitude);
  1489. alpha = length < 5 ? 0.75 : 1 / length * 5;
  1490. this.direction.x = cos(sun.azimuth) * length;
  1491. this.direction.y = sin(sun.azimuth) * length;
  1492. var
  1493. i, il,
  1494. item,
  1495. h, mh,
  1496. footprint,
  1497. dataItems = Data.items;
  1498. context.canvas.style.opacity = alpha / (ZOOM_FACTOR * 2);
  1499. context.shadowColor = this.blurColor;
  1500. context.shadowBlur = this.blurSize * (ZOOM_FACTOR / 2);
  1501. context.fillStyle = this.color;
  1502. context.beginPath();
  1503. for (i = 0, il = dataItems.length; i < il; i++) {
  1504. item = dataItems[i];
  1505. footprint = item.footprint;
  1506. if (!isVisible(footprint)) {
  1507. continue;
  1508. }
  1509. // when fading in, use a dynamic height
  1510. h = item.scale < 1 ? item.height * item.scale : item.height;
  1511. mh = 0;
  1512. if (item.minHeight) {
  1513. mh = item.scale < 1 ? item.minHeight * item.scale : item.minHeight;
  1514. }
  1515. switch (item.shape) {
  1516. case 'cylinder':
  1517. Cylinder.shadow(context, item.center, item.radius, item.radius, h, mh);
  1518. break;
  1519. case 'cone':
  1520. Cylinder.shadow(context, item.center, item.radius, 0, h, mh);
  1521. break;
  1522. case 'dome':
  1523. Cylinder.shadow(context, item.center, item.radius, item.radius / 2, h, mh);
  1524. break;
  1525. case 'sphere':
  1526. Cylinder.shadow(context, item.center, item.radius, item.radius, h, mh);
  1527. break;
  1528. case 'pyramid':
  1529. Pyramid.shadow(context, footprint, item.center, h, mh);
  1530. break;
  1531. default:
  1532. Block.shadow(context, footprint, item.holes, h, mh);
  1533. }
  1534. switch (item.roofShape) {
  1535. case 'cone':
  1536. Cylinder.shadow(context, item.center, item.radius, 0, h + item.roofHeight, h);
  1537. break;
  1538. case 'dome':
  1539. Cylinder.shadow(context, item.center, item.radius, item.radius / 2, h + item.roofHeight, h);
  1540. break;
  1541. case 'pyramid':
  1542. Pyramid.shadow(context, footprint, item.center, h + item.roofHeight, h);
  1543. break;
  1544. }
  1545. }
  1546. context.closePath();
  1547. context.fill();
  1548. context.shadowBlur = null;
  1549. // now draw all the footprints as negative clipping mask
  1550. context.globalCompositeOperation = 'destination-out';
  1551. context.beginPath();
  1552. for (i = 0, il = dataItems.length; i < il; i++) {
  1553. item = dataItems[i];
  1554. footprint = item.footprint;
  1555. if (!isVisible(footprint)) {
  1556. continue;
  1557. }
  1558. // if object is hovered, there is no need to clip it's footprint
  1559. if (item.minHeight) {
  1560. continue;
  1561. }
  1562. switch (item.shape) {
  1563. case 'cylinder':
  1564. case 'cone':
  1565. case 'dome':
  1566. Cylinder.shadowMask(context, item.center, item.radius);
  1567. break;
  1568. default:
  1569. Block.shadowMask(context, footprint, item.holes);
  1570. }
  1571. }
  1572. context.fillStyle = '#00ff00';
  1573. context.fill();
  1574. context.globalCompositeOperation = 'source-over';
  1575. }
  1576. };
  1577. //****** file: HitAreas.js ******
  1578. var HitAreas = {
  1579. _idMapping: [null],
  1580. reset: function () {
  1581. this._idMapping = [null];
  1582. },
  1583. render: function () {
  1584. if (this._timer) {
  1585. return;
  1586. }
  1587. var self = this;
  1588. this._timer = setTimeout(function () {
  1589. self._timer = null;
  1590. self._render();
  1591. }, 500);
  1592. },
  1593. _render: function () {
  1594. var context = this.context;
  1595. context.clearRect(0, 0, WIDTH, HEIGHT);
  1596. // show on high zoom levels only and avoid rendering during zoom
  1597. if (ZOOM < MIN_ZOOM || isZooming) {
  1598. return;
  1599. }
  1600. var
  1601. item,
  1602. h, mh,
  1603. sortCam = {x: CAM_X + ORIGIN_X, y: CAM_Y + ORIGIN_Y},
  1604. footprint,
  1605. color,
  1606. dataItems = Data.items;
  1607. dataItems.sort(function (a, b) {
  1608. return (a.minHeight - b.minHeight) || getDistance(b.center, sortCam) - getDistance(a.center, sortCam) || (b.height - a.height);
  1609. });
  1610. for (var i = 0, il = dataItems.length; i < il; i++) {
  1611. item = dataItems[i];
  1612. if (!(color = item.hitColor)) {
  1613. continue;
  1614. }
  1615. footprint = item.footprint;
  1616. if (!isVisible(footprint)) {
  1617. continue;
  1618. }
  1619. h = item.height;
  1620. mh = 0;
  1621. if (item.minHeight) {
  1622. mh = item.minHeight;
  1623. }
  1624. switch (item.shape) {
  1625. case 'cylinder':
  1626. Cylinder.hitArea(context, item.center, item.radius, item.radius, h, mh, color);
  1627. break;
  1628. case 'cone':
  1629. Cylinder.hitArea(context, item.center, item.radius, 0, h, mh, color);
  1630. break;
  1631. case 'dome':
  1632. Cylinder.hitArea(context, item.center, item.radius, item.radius / 2, h, mh, color);
  1633. break;
  1634. case 'sphere':
  1635. Cylinder.hitArea(context, item.center, item.radius, item.radius, h, mh, color);
  1636. break;
  1637. case 'pyramid':
  1638. Pyramid.hitArea(context, footprint, item.center, h, mh, color);
  1639. break;
  1640. default:
  1641. Block.hitArea(context, footprint, item.holes, h, mh, color);
  1642. }
  1643. switch (item.roofShape) {
  1644. case 'cone':
  1645. Cylinder.hitArea(context, item.center, item.radius, 0, h + item.roofHeight, h, color);
  1646. break;
  1647. case 'dome':
  1648. Cylinder.hitArea(context, item.center, item.radius, item.radius / 2, h + item.roofHeight, h, color);
  1649. break;
  1650. case 'pyramid':
  1651. Pyramid.hitArea(context, footprint, item.center, h + item.roofHeight, h, color);
  1652. break;
  1653. }
  1654. }
  1655. this._imageData = this.context.getImageData(0, 0, WIDTH, HEIGHT).data;
  1656. },
  1657. getIdFromXY: function (x, y) {
  1658. var imageData = this._imageData;
  1659. if (!imageData) {
  1660. return;
  1661. }
  1662. var pos = 4 * ((y | 0) * WIDTH + (x | 0));
  1663. var index = imageData[pos] | (imageData[pos + 1] << 8) | (imageData[pos + 2] << 16);
  1664. return this._idMapping[index];
  1665. },
  1666. idToColor: function (id) {
  1667. var index = this._idMapping.indexOf(id);
  1668. if (index === -1) {
  1669. this._idMapping.push(id);
  1670. index = this._idMapping.length - 1;
  1671. }
  1672. var r = index & 0xff;
  1673. var g = (index >> 8) & 0xff;
  1674. var b = (index >> 16) & 0xff;
  1675. return 'rgb(' + [r, g, b].join(',') + ')';
  1676. }
  1677. };
  1678. //****** file: Debug.js ******
  1679. var Debug = {
  1680. point: function (x, y, color, size) {
  1681. var context = this.context;
  1682. context.fillStyle = color || '#ffcc00';
  1683. context.beginPath();
  1684. context.arc(x, y, size || 3, 0, 2 * PI);
  1685. context.closePath();
  1686. context.fill();
  1687. },
  1688. line: function (ax, ay, bx, by, color) {
  1689. var context = this.context;
  1690. context.strokeStyle = color || '#ffcc00';
  1691. context.beginPath();
  1692. context.moveTo(ax, ay);
  1693. context.lineTo(bx, by);
  1694. context.closePath();
  1695. context.stroke();
  1696. }
  1697. };
  1698. //****** file: Layers.js ******
  1699. var animTimer;
  1700. function fadeIn() {
  1701. if (animTimer) {
  1702. return;
  1703. }
  1704. animTimer = setInterval(function () {
  1705. var dataItems = Data.items,
  1706. isNeeded = false;
  1707. for (var i = 0, il = dataItems.length; i < il; i++) {
  1708. if (dataItems[i].scale < 1) {
  1709. dataItems[i].scale += 0.5 * 0.2; // amount*easing
  1710. if (dataItems[i].scale > 1) {
  1711. dataItems[i].scale = 1;
  1712. }
  1713. isNeeded = true;
  1714. }
  1715. }
  1716. Layers.render();
  1717. if (!isNeeded) {
  1718. clearInterval(animTimer);
  1719. animTimer = null;
  1720. }
  1721. }, 33);
  1722. }
  1723. var Layers = {
  1724. container: document.createElement('DIV'),
  1725. items: [],
  1726. init: function () {
  1727. this.container.style.pointerEvents = 'none';
  1728. this.container.style.position = 'absolute';
  1729. this.container.style.left = 0;
  1730. this.container.style.top = 0;
  1731. // TODO: improve this to .setContext(context)
  1732. Shadows.context = this.createContext(this.container);
  1733. Simplified.context = this.createContext(this.container);
  1734. Buildings.context = this.createContext(this.container);
  1735. HitAreas.context = this.createContext();
  1736. // Debug.context = this.createContext(this.container);
  1737. },
  1738. render: function (quick) {
  1739. requestAnimFrame(function () {
  1740. if (!quick) {
  1741. Shadows.render();
  1742. Simplified.render();
  1743. HitAreas.render();
  1744. }
  1745. Buildings.render();
  1746. });
  1747. },
  1748. createContext: function (container) {
  1749. var canvas = document.createElement('CANVAS');
  1750. canvas.style.transform = 'translate3d(0, 0, 0)'; // turn on hw acceleration
  1751. canvas.style.imageRendering = 'optimizeSpeed';
  1752. canvas.style.position = 'absolute';
  1753. canvas.style.left = 0;
  1754. canvas.style.top = 0;
  1755. var context = canvas.getContext('2d');
  1756. context.lineCap = 'round';
  1757. context.lineJoin = 'round';
  1758. context.lineWidth = 1;
  1759. context.imageSmoothingEnabled = false;
  1760. this.items.push(canvas);
  1761. if (container) {
  1762. container.appendChild(canvas);
  1763. }
  1764. return context;
  1765. },
  1766. appendTo: function (parentNode) {
  1767. parentNode.appendChild(this.container);
  1768. },
  1769. remove: function () {
  1770. this.container.parentNode.removeChild(this.container);
  1771. },
  1772. setSize: function (width, height) {
  1773. for (var i = 0, il = this.items.length; i < il; i++) {
  1774. this.items[i].width = width;
  1775. this.items[i].height = height;
  1776. }
  1777. },
  1778. // usually called after move: container jumps by move delta, cam is reset
  1779. setPosition: function (x, y) {
  1780. this.container.style.left = x + 'px';
  1781. this.container.style.top = y + 'px';
  1782. }
  1783. };
  1784. Layers.init();
  1785. //****** file: adapter.js ******
  1786. function setOrigin(origin) {
  1787. ORIGIN_X = origin.x;
  1788. ORIGIN_Y = origin.y;
  1789. }
  1790. function moveCam(offset) {
  1791. CAM_X = CENTER_X + offset.x;
  1792. CAM_Y = HEIGHT + offset.y;
  1793. Layers.render(true);
  1794. }
  1795. function setSize(size) {
  1796. WIDTH = size.width;
  1797. HEIGHT = size.height;
  1798. CENTER_X = WIDTH / 2 << 0;
  1799. CENTER_Y = HEIGHT / 2 << 0;
  1800. CAM_X = CENTER_X;
  1801. CAM_Y = HEIGHT;
  1802. Layers.setSize(WIDTH, HEIGHT);
  1803. MAX_HEIGHT = CAM_Z - 50;
  1804. }
  1805. function setZoom(z) {
  1806. ZOOM = z;
  1807. if (isLatLngProjection()) {
  1808. MAP_SIZE = 360 / mapObj.resolution;
  1809. } else {
  1810. MAP_SIZE = MAP_TILE_SIZE << ZOOM;
  1811. }
  1812. var center = pixelToGeo(ORIGIN_X + CENTER_X, ORIGIN_Y + CENTER_Y);
  1813. var a = geoToPixel(center.latitude, 0);
  1814. var b = geoToPixel(center.latitude, 1);
  1815. PIXEL_PER_DEG = b.x - a.x;
  1816. ZOOM_FACTOR = pow(0.95, ZOOM - MIN_ZOOM);
  1817. WALL_COLOR_STR = '' + WALL_COLOR.alpha(ZOOM_FACTOR);
  1818. ALT_COLOR_STR = '' + ALT_COLOR.alpha(ZOOM_FACTOR);
  1819. ROOF_COLOR_STR = '' + ROOF_COLOR.alpha(ZOOM_FACTOR);
  1820. }
  1821. function onResize(e) {
  1822. setSize(e);
  1823. Layers.render();
  1824. Data.update();
  1825. }
  1826. function onMoveEnd(e) {
  1827. Layers.render();
  1828. Data.update(); // => fadeIn() => Layers.render()
  1829. }
  1830. function onZoomStart() {
  1831. isZooming = true;
  1832. // effectively clears because of isZooming flag
  1833. // TODO: introduce explicit clear()
  1834. Layers.render();
  1835. }
  1836. function onZoomEnd(e) {
  1837. isZooming = false;
  1838. setZoom(e.zoom);
  1839. Data.update(); // => fadeIn()
  1840. Layers.render();
  1841. }
  1842. //****** file: SuperMap.js ******
  1843. // based on a pull request from Jérémy Judéaux (https://github.com/Volune)
  1844. var parent = SuperMap.Layer.prototype;
  1845. var transformExtent = function (extent) {
  1846. if (isLatLngProjection()) {
  1847. return {
  1848. left: -180,
  1849. bottom: -90,
  1850. right: 180,
  1851. top: 90
  1852. }
  1853. }
  1854. return extent;
  1855. };
  1856. var osmb = function (map) {
  1857. this.offset = {x: 0, y: 0}; // cumulative cam offset during moveBy()
  1858. bindMap(map);
  1859. parent.initialize.call(this, this.name, {projection: 'EPSG:900913'});
  1860. if (map) {
  1861. map.addLayer(this);
  1862. }
  1863. };
  1864. //bind map to global
  1865. function bindMap(map) {
  1866. mapObj = map;
  1867. }
  1868. var proto = osmb.prototype = new SuperMap.Layer();
  1869. proto.name = 'OSM Buildings';
  1870. proto.attribution = ATTRIBUTION;
  1871. proto.isBaseLayer = false;
  1872. proto.alwaysInRange = true;
  1873. proto.addTo = function (map) {
  1874. this.setMap(map);
  1875. return this;
  1876. };
  1877. proto.setOrigin = function () {
  1878. var map = this.map,
  1879. origin = map.getLonLatFromPixel(new SuperMap.Pixel(0, 0)),
  1880. res = map.resolution;
  1881. var ext = transformExtent(this.maxExtent);
  1882. var x = (origin.lon - ext.left) / res << 0,
  1883. y = (ext.top - origin.lat) / res << 0;
  1884. setOrigin({x: x, y: y});
  1885. };
  1886. proto.setMap = function (map) {
  1887. if (!this.map) {
  1888. parent.setMap.call(this, map);
  1889. }
  1890. Layers.appendTo(this.div);
  1891. setSize({width: map.size.w, height: map.size.h});
  1892. setZoom(map.zoom);
  1893. this.setOrigin();
  1894. var layerProjection = this.projection;
  1895. map.events.register('click', map, function (e) {
  1896. var id = HitAreas.getIdFromXY(e.xy.x, e.xy.y);
  1897. if (id) {
  1898. var geo = map.getLonLatFromPixel(e.xy).transform(layerProjection, this.projection);
  1899. onClick({feature: id, lat: geo.lat, lon: geo.lon});
  1900. }
  1901. });
  1902. Data.update();
  1903. };
  1904. proto.removeMap = function (map) {
  1905. Layers.remove();
  1906. parent.removeMap.call(this, map);
  1907. this.map = null;
  1908. };
  1909. proto.onMapResize = function () {
  1910. var map = this.map;
  1911. parent.onMapResize.call(this);
  1912. onResize({width: map.size.w, height: map.size.h});
  1913. };
  1914. proto.moveTo = function (bounds, zoomChanged, isDragging) {
  1915. var
  1916. map = this.map,
  1917. res = parent.moveTo.call(this, bounds, zoomChanged, isDragging);
  1918. if (!isDragging) {
  1919. var
  1920. offsetLeft = parseInt(map.layerContainerDiv.style.left, 10),
  1921. offsetTop = parseInt(map.layerContainerDiv.style.top, 10);
  1922. this.div.style.left = -offsetLeft + 'px';
  1923. this.div.style.top = -offsetTop + 'px';
  1924. }
  1925. this.setOrigin();
  1926. this.offset.x = 0;
  1927. this.offset.y = 0;
  1928. moveCam(this.offset);
  1929. if (zoomChanged) {
  1930. onZoomEnd({zoom: map.zoom});
  1931. } else {
  1932. onMoveEnd();
  1933. }
  1934. return res;
  1935. };
  1936. proto.moveByPx = function (dx, dy) {
  1937. this.offset.x += dx;
  1938. this.offset.y += dy;
  1939. var res = parent.moveByPx.call(this, dx, dy);
  1940. moveCam(this.offset);
  1941. return res;
  1942. };
  1943. //****** file: public.js ******
  1944. proto.style = function (style) {
  1945. style = style || {};
  1946. var color;
  1947. if ((color = style.color || style.wallColor)) {
  1948. WALL_COLOR = Color.parse(color);
  1949. WALL_COLOR_STR = '' + WALL_COLOR.alpha(ZOOM_FACTOR);
  1950. ALT_COLOR = WALL_COLOR.lightness(0.8);
  1951. ALT_COLOR_STR = '' + ALT_COLOR.alpha(ZOOM_FACTOR);
  1952. ROOF_COLOR = WALL_COLOR.lightness(1.2);
  1953. ROOF_COLOR_STR = '' + ROOF_COLOR.alpha(ZOOM_FACTOR);
  1954. }
  1955. if (style.roofColor) {
  1956. ROOF_COLOR = Color.parse(style.roofColor);
  1957. ROOF_COLOR_STR = '' + ROOF_COLOR.alpha(ZOOM_FACTOR);
  1958. }
  1959. if (style.shadows !== undefined) {
  1960. Shadows.enabled = !!style.shadows;
  1961. }
  1962. Layers.render();
  1963. return this;
  1964. };
  1965. proto.date = function (date) {
  1966. Shadows.date = date;
  1967. Shadows.render();
  1968. return this;
  1969. };
  1970. proto.load = function (url) {
  1971. Data.load(url);
  1972. return this;
  1973. };
  1974. proto.set = function (data) {
  1975. Data.set(data);
  1976. return this;
  1977. };
  1978. var onEach = function () {
  1979. };
  1980. proto.each = function (handler) {
  1981. onEach = function (payload) {
  1982. return handler(payload);
  1983. };
  1984. return this;
  1985. };
  1986. var onClick = function () {
  1987. };
  1988. proto.click = function (handler) {
  1989. onClick = function (payload) {
  1990. return handler(payload);
  1991. };
  1992. return this;
  1993. };
  1994. osmb.VERSION = VERSION;
  1995. osmb.ATTRIBUTION = ATTRIBUTION;
  1996. //****** file: suffix.js ******
  1997. global.OSMBuildings = osmb;
  1998. }(this));