123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448 |
- /**
- * Copyright (C) 2015 OSM Buildings, Jan Marsch
- * A JavaScript library for visualizing building geometry on interactive maps.
- * @osmbuildings, http://osmbuildings.org
- */
- //****** file: prefix.js ******
- (function (global) {
- 'use strict';
- //****** file: shortcuts.js ******
- // object access shortcuts
- var
- m = Math,
- exp = m.exp,
- log = m.log,
- sin = m.sin,
- cos = m.cos,
- tan = m.tan,
- atan = m.atan,
- atan2 = m.atan2,
- min = m.min,
- max = m.max,
- sqrt = m.sqrt,
- ceil = m.ceil,
- floor = m.floor,
- round = m.round,
- pow = m.pow;
- var mapObj;
- // polyfills
- var
- Int32Array = Int32Array || Array,
- Uint8Array = Uint8Array || Array;
- var IS_IOS = /iP(ad|hone|od)/g.test(navigator.userAgent);
- var IS_MSIE = !!~navigator.userAgent.indexOf('Trident');
- var requestAnimFrame = (global.requestAnimationFrame && !IS_IOS && !IS_MSIE) ?
- global.requestAnimationFrame : function (callback) {
- callback();
- };
- //****** file: Color.debug.js ******
- var Color = (function (window) {
- var w3cColors = {
- aqua: '#00ffff',
- black: '#000000',
- blue: '#0000ff',
- fuchsia: '#ff00ff',
- gray: '#808080',
- grey: '#808080',
- green: '#008000',
- lime: '#00ff00',
- maroon: '#800000',
- navy: '#000080',
- olive: '#808000',
- orange: '#ffa500',
- purple: '#800080',
- red: '#ff0000',
- silver: '#c0c0c0',
- teal: '#008080',
- white: '#ffffff',
- yellow: '#ffff00'
- };
- function hue2rgb(p, q, t) {
- if (t < 0) t += 1;
- if (t > 1) t -= 1;
- if (t < 1 / 6) return p + (q - p) * 6 * t;
- if (t < 1 / 2) return q;
- if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
- return p;
- }
- function clamp(v, max) {
- return Math.min(max, Math.max(0, v));
- }
- var Color = function (h, s, l, a) {
- this.H = h;
- this.S = s;
- this.L = l;
- this.A = a;
- };
- /*
- * str can be in any of these:
- * #0099ff rgb(64, 128, 255) rgba(64, 128, 255, 0.5)
- */
- Color.parse = function (str) {
- var
- r = 0, g = 0, b = 0, a = 1,
- m;
- str = ('' + str).toLowerCase();
- str = w3cColors[str] || str;
- if ((m = str.match(/^#(\w{2})(\w{2})(\w{2})$/))) {
- r = parseInt(m[1], 16);
- g = parseInt(m[2], 16);
- b = parseInt(m[3], 16);
- } else if ((m = str.match(/rgba?\((\d+)\D+(\d+)\D+(\d+)(\D+([\d.]+))?\)/))) {
- r = parseInt(m[1], 10);
- g = parseInt(m[2], 10);
- b = parseInt(m[3], 10);
- a = m[4] ? parseFloat(m[5]) : 1;
- } else {
- return;
- }
- return this.fromRGBA(r, g, b, a);
- };
- Color.fromRGBA = function (r, g, b, a) {
- if (typeof r === 'object') {
- g = r.g / 255;
- b = r.b / 255;
- a = r.a;
- r = r.r / 255;
- } else {
- r /= 255;
- g /= 255;
- b /= 255;
- }
- var
- max = Math.max(r, g, b),
- min = Math.min(r, g, b),
- h, s, l = (max + min) / 2,
- d = max - min;
- if (!d) {
- h = s = 0; // achromatic
- } else {
- s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
- switch (max) {
- case r:
- h = (g - b) / d + (g < b ? 6 : 0);
- break;
- case g:
- h = (b - r) / d + 2;
- break;
- case b:
- h = (r - g) / d + 4;
- break;
- }
- h *= 60;
- }
- return new Color(h, s, l, a);
- };
- Color.prototype = {
- toRGBA: function () {
- var
- h = clamp(this.H, 360),
- s = clamp(this.S, 1),
- l = clamp(this.L, 1),
- rgba = {a: clamp(this.A, 1)};
- // achromatic
- if (s === 0) {
- rgba.r = l;
- rgba.g = l;
- rgba.b = l;
- } else {
- var
- q = l < 0.5 ? l * (1 + s) : l + s - l * s,
- p = 2 * l - q;
- h /= 360;
- rgba.r = hue2rgb(p, q, h + 1 / 3);
- rgba.g = hue2rgb(p, q, h);
- rgba.b = hue2rgb(p, q, h - 1 / 3);
- }
- return {
- r: Math.round(rgba.r * 255),
- g: Math.round(rgba.g * 255),
- b: Math.round(rgba.b * 255),
- a: rgba.a
- };
- },
- toString: function () {
- var rgba = this.toRGBA();
- if (rgba.a === 1) {
- return '#' + ((1 << 24) + (rgba.r << 16) + (rgba.g << 8) + rgba.b).toString(16).slice(1, 7);
- }
- return 'rgba(' + [rgba.r, rgba.g, rgba.b, rgba.a.toFixed(2)].join(',') + ')';
- },
- hue: function (h) {
- return new Color(this.H * h, this.S, this.L, this.A);
- },
- saturation: function (s) {
- return new Color(this.H, this.S * s, this.L, this.A);
- },
- lightness: function (l) {
- return new Color(this.H, this.S, this.L * l, this.A);
- },
- alpha: function (a) {
- return new Color(this.H, this.S, this.L, this.A * a);
- }
- };
- return Color;
- }(this));
- //****** file: SunPosition.js ******
- // calculations are based on http://aa.quae.nl/en/reken/zonpositie.html
- // code credits to Vladimir Agafonkin (@mourner)
- var getSunPosition = (function () {
- var m = Math,
- PI = m.PI,
- sin = m.sin,
- cos = m.cos,
- tan = m.tan,
- asin = m.asin,
- atan = m.atan2;
- var rad = PI / 180,
- dayMs = 1000 * 60 * 60 * 24,
- J1970 = 2440588,
- J2000 = 2451545,
- e = rad * 23.4397; // obliquity of the Earth
- function toJulian(date) {
- return date.valueOf() / dayMs - 0.5 + J1970;
- }
- function toDays(date) {
- return toJulian(date) - J2000;
- }
- function getRightAscension(l, b) {
- return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l));
- }
- function getDeclination(l, b) {
- return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l));
- }
- function getAzimuth(H, phi, dec) {
- return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi));
- }
- function getAltitude(H, phi, dec) {
- return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H));
- }
- function getSiderealTime(d, lw) {
- return rad * (280.16 + 360.9856235 * d) - lw;
- }
- function getSolarMeanAnomaly(d) {
- return rad * (357.5291 + 0.98560028 * d);
- }
- function getEquationOfCenter(M) {
- return rad * (1.9148 * sin(M) + 0.0200 * sin(2 * M) + 0.0003 * sin(3 * M));
- }
- function getEclipticLongitude(M, C) {
- var P = rad * 102.9372; // perihelion of the Earth
- return M + C + P + PI;
- }
- return function getSunPosition(date, lat, lon) {
- var lw = rad * -lon,
- phi = rad * lat,
- d = toDays(date),
- M = getSolarMeanAnomaly(d),
- C = getEquationOfCenter(M),
- L = getEclipticLongitude(M, C),
- D = getDeclination(L, 0),
- A = getRightAscension(L, 0),
- t = getSiderealTime(d, lw),
- H = t - A;
- return {
- altitude: getAltitude(H, phi, D),
- azimuth: getAzimuth(H, phi, D) - PI / 2 // origin: north
- };
- };
- }());
- //****** file: GeoJSON.js ******
- var GeoJSON = (function () {
- var METERS_PER_LEVEL = 3;
- var materialColors = {
- brick: '#cc7755',
- bronze: '#ffeecc',
- canvas: '#fff8f0',
- concrete: '#999999',
- copper: '#a0e0d0',
- glass: '#e8f8f8',
- gold: '#ffcc00',
- plants: '#009933',
- metal: '#aaaaaa',
- panel: '#fff8f0',
- plaster: '#999999',
- roof_tiles: '#f08060',
- silver: '#cccccc',
- slate: '#666666',
- stone: '#996666',
- tar_paper: '#333333',
- wood: '#deb887'
- };
- var baseMaterials = {
- asphalt: 'tar_paper',
- bitumen: 'tar_paper',
- block: 'stone',
- bricks: 'brick',
- glas: 'glass',
- glassfront: 'glass',
- grass: 'plants',
- masonry: 'stone',
- granite: 'stone',
- panels: 'panel',
- paving_stones: 'stone',
- plastered: 'plaster',
- rooftiles: 'roof_tiles',
- roofingfelt: 'tar_paper',
- sandstone: 'stone',
- sheet: 'canvas',
- sheets: 'canvas',
- shingle: 'tar_paper',
- shingles: 'tar_paper',
- slates: 'slate',
- steel: 'metal',
- tar: 'tar_paper',
- tent: 'canvas',
- thatch: 'plants',
- tile: 'roof_tiles',
- tiles: 'roof_tiles'
- };
- // cardboard
- // eternit
- // limestone
- // straw
- function getMaterialColor(str) {
- str = str.toLowerCase();
- if (str[0] === '#') {
- return str;
- }
- return materialColors[baseMaterials[str] || str] || null;
- }
- var WINDING_CLOCKWISE = 'CW';
- var WINDING_COUNTER_CLOCKWISE = 'CCW';
- // detect winding direction: clockwise or counter clockwise
- function getWinding(points) {
- var x1, y1, x2, y2,
- a = 0,
- i, il;
- for (i = 0, il = points.length - 3; i < il; i += 2) {
- x1 = points[i];
- y1 = points[i + 1];
- x2 = points[i + 2];
- y2 = points[i + 3];
- a += x1 * y2 - x2 * y1;
- }
- return (a / 2) > 0 ? WINDING_CLOCKWISE : WINDING_COUNTER_CLOCKWISE;
- }
- // enforce a polygon winding direcetion. Needed for proper backface culling.
- function makeWinding(points, direction) {
- var winding = getWinding(points);
- if (winding === direction) {
- return points;
- }
- var revPoints = [];
- for (var i = points.length - 2; i >= 0; i -= 2) {
- revPoints.push(points[i], points[i + 1]);
- }
- return revPoints;
- }
- function alignProperties(prop) {
- var item = {};
- prop = prop || {};
- item.height = prop.height || (prop.levels ? prop.levels * METERS_PER_LEVEL : DEFAULT_HEIGHT);
- item.minHeight = prop.minHeight || (prop.minLevel ? prop.minLevel * METERS_PER_LEVEL : 0);
- var wallColor = prop.material ? getMaterialColor(prop.material) : (prop.wallColor || prop.color);
- if (wallColor) {
- item.wallColor = wallColor;
- }
- var roofColor = prop.roofMaterial ? getMaterialColor(prop.roofMaterial) : prop.roofColor;
- if (roofColor) {
- item.roofColor = roofColor;
- }
- switch (prop.shape) {
- case 'cylinder':
- case 'cone':
- case 'dome':
- case 'sphere':
- item.shape = prop.shape;
- item.isRotational = true;
- break;
- case 'pyramid':
- item.shape = prop.shape;
- break;
- }
- switch (prop.roofShape) {
- case 'cone':
- case 'dome':
- item.roofShape = prop.roofShape;
- item.isRotational = true;
- break;
- case 'pyramid':
- item.roofShape = prop.roofShape;
- break;
- }
- if (item.roofShape && prop.roofHeight) {
- item.roofHeight = prop.roofHeight;
- item.height = max(0, item.height - item.roofHeight);
- } else {
- item.roofHeight = 0;
- }
- return item;
- }
- function getGeometries(geometry) {
- var
- i, il, polygon,
- geometries = [], sub;
- switch (geometry.type) {
- case 'GeometryCollection':
- geometries = [];
- for (i = 0, il = geometry.geometries.length; i < il; i++) {
- if ((sub = getGeometries(geometry.geometries[i]))) {
- geometries.push.apply(geometries, sub);
- }
- }
- return geometries;
- case 'MultiPolygon':
- geometries = [];
- for (i = 0, il = geometry.coordinates.length; i < il; i++) {
- if ((sub = getGeometries({type: 'Polygon', coordinates: geometry.coordinates[i]}))) {
- geometries.push.apply(geometries, sub);
- }
- }
- return geometries;
- case 'Polygon':
- polygon = geometry.coordinates;
- break;
- default:
- return [];
- }
- var
- j, jl,
- p, lat = 1, lon = 0,
- outer = [], inner = [];
- p = polygon[0];
- for (i = 0, il = p.length; i < il; i++) {
- outer.push(p[i][lat], p[i][lon]);
- }
- outer = makeWinding(outer, WINDING_CLOCKWISE);
- for (i = 0, il = polygon.length - 1; i < il; i++) {
- p = polygon[i + 1];
- inner[i] = [];
- for (j = 0, jl = p.length; j < jl; j++) {
- inner[i].push(p[j][lat], p[j][lon]);
- }
- inner[i] = makeWinding(inner[i], WINDING_COUNTER_CLOCKWISE);
- }
- return [{
- outer: outer,
- inner: inner.length ? inner : null
- }];
- }
- function clone(obj) {
- var res = {};
- for (var p in obj) {
- if (obj.hasOwnProperty(p)) {
- res[p] = obj[p];
- }
- }
- return res;
- }
- return {
- read: function (geojson) {
- if (!geojson || geojson.type !== 'FeatureCollection') {
- return [];
- }
- var
- collection = geojson.features,
- i, il, j, jl,
- res = [],
- feature,
- geometries,
- baseItem, item;
- for (i = 0, il = collection.length; i < il; i++) {
- feature = collection[i];
- if (feature.type !== 'Feature' || onEach(feature) === false) {
- continue;
- }
- baseItem = alignProperties(feature.properties);
- geometries = getGeometries(feature.geometry);
- for (j = 0, jl = geometries.length; j < jl; j++) {
- item = clone(baseItem);
- item.footprint = geometries[j].outer;
- if (item.isRotational) {
- item.radius = getLonDelta(item.footprint);
- }
- if (geometries[j].inner) {
- item.holes = geometries[j].inner;
- }
- if (feature.id || feature.properties.id) {
- item.id = feature.id || feature.properties.id;
- }
- if (feature.properties.relationId) {
- item.relationId = feature.properties.relationId;
- }
- res.push(item); // TODO: clone base properties!
- }
- }
- return res;
- }
- };
- }());
- //****** file: variables.js ******
- var
- VERSION = '0.2.2b',
- ATTRIBUTION = '© <a href="http://osmbuildings.org">OSM Buildings</a>',
- DATA_SRC = 'http://{s}.data.osmbuildings.org/0.2/{k}/tile/{z}/{x}/{y}.json', ////////////////geojson 数据
- PI = Math.PI,
- HALF_PI = PI / 2,
- QUARTER_PI = PI / 4,
- MAP_TILE_SIZE = 256, // map tile size in pixels
- DATA_TILE_SIZE = 0.0075, // data tile size in geo coordinates, smaller: less data to load but more requests
- ZOOM, MAP_SIZE,
- MIN_ZOOM = 8,
- LAT = 'latitude', LON = 'longitude',
- TRUE = true, FALSE = false,
- WIDTH = 0, HEIGHT = 0,
- CENTER_X = 0, CENTER_Y = 0,
- ORIGIN_X = 0, ORIGIN_Y = 0,
- WALL_COLOR = Color.parse('rgba(200, 190, 180)'),
- ALT_COLOR = WALL_COLOR.lightness(0.8),
- ROOF_COLOR = WALL_COLOR.lightness(1.2),
- WALL_COLOR_STR = '' + WALL_COLOR,
- ALT_COLOR_STR = '' + ALT_COLOR,
- ROOF_COLOR_STR = '' + ROOF_COLOR,
- PIXEL_PER_DEG = 0,
- ZOOM_FACTOR = 1,
- MAX_HEIGHT, // taller buildings will be cut to this
- DEFAULT_HEIGHT = 5,
- CAM_X, CAM_Y, CAM_Z = 450,
- isZooming;
- //****** file: geometry.js ******
- function getDistance(p1, p2) {
- var
- dx = p1.x - p2.x,
- dy = p1.y - p2.y;
- return dx * dx + dy * dy;
- }
- //是否可旋转
- function isRotational(polygon) {
- var length = polygon.length;
- if (length < 16) {
- return false;
- }
- var i;
- var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
- for (i = 0; i < length - 1; i += 2) {
- minX = Math.min(minX, polygon[i]);
- maxX = Math.max(maxX, polygon[i]);
- minY = Math.min(minY, polygon[i + 1]);
- maxY = Math.max(maxY, polygon[i + 1]);
- }
- var
- width = maxX - minX,
- height = (maxY - minY),
- ratio = width / height;
- if (ratio < 0.85 || ratio > 1.15) {
- return false;
- }
- var
- center = {x: minX + width / 2, y: minY + height / 2},
- radius = (width + height) / 4,
- sqRadius = radius * radius;
- for (i = 0; i < length - 1; i += 2) {
- var dist = getDistance({x: polygon[i], y: polygon[i + 1]}, center);
- if (dist / sqRadius < 0.8 || dist / sqRadius > 1.2) {
- return false;
- }
- }
- return true;
- }
- function getSquareSegmentDistance(px, py, p1x, p1y, p2x, p2y) {
- var
- dx = p2x - p1x,
- dy = p2y - p1y,
- t;
- if (dx !== 0 || dy !== 0) {
- t = ((px - p1x) * dx + (py - p1y) * dy) / (dx * dx + dy * dy);
- if (t > 1) {
- p1x = p2x;
- p1y = p2y;
- } else if (t > 0) {
- p1x += dx * t;
- p1y += dy * t;
- }
- }
- dx = px - p1x;
- dy = py - p1y;
- return dx * dx + dy * dy;
- }
- function simplifyPolygon(buffer) {
- var
- sqTolerance = 2,
- len = buffer.length / 2,
- markers = new Uint8Array(len),
- first = 0, last = len - 1,
- i,
- maxSqDist,
- sqDist,
- index,
- firstStack = [], lastStack = [],
- newBuffer = [];
- markers[first] = markers[last] = 1;
- while (last) {
- maxSqDist = 0;
- for (i = first + 1; i < last; i++) {
- sqDist = getSquareSegmentDistance(
- buffer[i * 2], buffer[i * 2 + 1],
- buffer[first * 2], buffer[first * 2 + 1],
- buffer[last * 2], buffer[last * 2 + 1]
- );
- if (sqDist > maxSqDist) {
- index = i;
- maxSqDist = sqDist;
- }
- }
- if (maxSqDist > sqTolerance) {
- markers[index] = 1;
- firstStack.push(first);
- lastStack.push(index);
- firstStack.push(index);
- lastStack.push(last);
- }
- first = firstStack.pop();
- last = lastStack.pop();
- }
- for (i = 0; i < len; i++) {
- if (markers[i]) {
- newBuffer.push(buffer[i * 2], buffer[i * 2 + 1]);
- }
- }
- return newBuffer;
- }
- function getCenter(footprint) {
- var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
- for (var i = 0, il = footprint.length - 3; i < il; i += 2) {
- minX = min(minX, footprint[i]);
- maxX = max(maxX, footprint[i]);
- minY = min(minY, footprint[i + 1]);
- maxY = max(maxY, footprint[i + 1]);
- }
- return {x: minX + (maxX - minX) / 2 << 0, y: minY + (maxY - minY) / 2 << 0};
- }
- var EARTH_RADIUS = 6378137;
- function getLonDelta(footprint) {
- var minLon = 180, maxLon = -180;
- for (var i = 0, il = footprint.length; i < il; i += 2) {
- minLon = min(minLon, footprint[i + 1]);
- maxLon = max(maxLon, footprint[i + 1]);
- }
- return (maxLon - minLon) / 2;
- }
- //****** file: functions.js ******
- function rad(deg) {
- return deg * PI / 180;
- }
- function deg(rad) {
- return rad / PI * 180;
- }
- function pixelToGeo(x, y) {
- var res = {};
- x /= MAP_SIZE;
- y /= MAP_SIZE;
- res[LAT] = y <= 0 ? 90 : y >= 1 ? -90 : deg(2 * atan(exp(PI * (1 - 2 * y))) - HALF_PI);
- res[LON] = (x === 1 ? 1 : (x % 1 + 1) % 1) * 360 - 180;
- return res;
- }
- function geoToPixel(lat, lon) {
- var latLngProjection = isLatLngProjection();
- if (latLngProjection) {
- var latitude = ((90 - lat) / 180),
- longitude = lon / 360 + 0.5;
- return {
- x: (longitude * MAP_SIZE) << 0,
- y: (latitude * MAP_SIZE / 2) << 0
- };
- } else {
- var latitude = min(1, max(0, 0.5 - (log(tan(QUARTER_PI + HALF_PI * lat / 180)) / PI) / 2)),
- longitude = lon / 360 + 0.5;
- return {
- x: (longitude * MAP_SIZE) << 0,
- y: (latitude * MAP_SIZE) << 0
- };
- }
- }
- function isLatLngProjection() {
- var code = mapObj.getProjection();
- if (code.indexOf('3857') > -1) {
- return false;
- }
- if (code.indexOf('4326') > -1||code.indexOf('4490') > -1) {
- return true;
- }
- console.log('Only support EPSG:3857 and EPSG:4326(4490) projection!');
- return false;
- }
- function fromRange(sVal, sMin, sMax, dMin, dMax) {
- sVal = min(max(sVal, sMin), sMax);
- var rel = (sVal - sMin) / (sMax - sMin),
- range = dMax - dMin;
- return min(max(dMin + rel * range, dMin), dMax);
- }
- function isVisible(polygon) {
- var
- maxX = WIDTH + ORIGIN_X,
- maxY = HEIGHT + ORIGIN_Y;
- // TODO: checking footprint is sufficient for visibility - NOT VALID FOR SHADOWS!
- for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
- if (polygon[i] > ORIGIN_X && polygon[i] < maxX && polygon[i + 1] > ORIGIN_Y && polygon[i + 1] < maxY) {
- return true;
- }
- }
- return false;
- }
- //数据请求 原来是在线OSMBuilding OSM与上请求 Builldings Geojson 数据
- //****** file: Request.js ******
- var Request = (function () {
- //var cacheData = {};
- //var cacheIndex = [];
- //var cacheSize = 0;
- //var maxCacheSize = 1024*1024 * 5; // 5MB
- //
- //function xhr(url, callback) {
- // if (cacheData[url]) {
- // if (callback) {
- // callback(cacheData[url]);
- // }
- // return;
- // }
- //
- // var req = new XMLHttpRequest();
- //
- // req.onreadystatechange = function() {
- // if (req.readyState !== 4) {
- // return;
- // }
- // if (!req.status || req.status < 200 || req.status > 299) {
- // return;
- // }
- // if (callback && req.responseText) {
- // var responseText = req.responseText;
- //
- // cacheData[url] = responseText;
- // cacheIndex.push({ url: url, size: responseText.length });
- // cacheSize += responseText.length;
- //
- // callback(responseText);
- //
- // while (cacheSize > maxCacheSize) {
- // var item = cacheIndex.shift();
- // cacheSize -= item.size;
- // delete cacheData[item.url];
- // }
- // }
- // };
- //
- // req.open('GET', url);
- // req.send(null);
- //
- // return req;
- //}
- //
- //return {
- // loadJSON: function(url, callback) {
- // return xhr(url, function(responseText) {
- // var json;
- // try {
- // json = JSON.parse(responseText);
- // } catch(ex) {}
- // callback(json);
- // });
- // }
- //};
- }());
- //****** file: Data.js ******
- var Data = {
- loadedItems: {}, // maintain a list of cached items in order to avoid duplicates on tile borders
- items: [],
- getPixelFootprint: function (buffer) {
- var footprint = new Int32Array(buffer.length),
- px;
- for (var i = 0, il = buffer.length - 1; i < il; i += 2) {
- px = geoToPixel(buffer[i], buffer[i + 1]);
- footprint[i] = px.x;
- footprint[i + 1] = px.y;
- }
- footprint = simplifyPolygon(footprint);
- if (footprint.length < 8) { // 3 points & end==start (*2)
- return;
- }
- return footprint;
- },
- resetItems: function () {
- this.items = [];
- this.loadedItems = {};
- HitAreas.reset();
- },
- addRenderItems: function (data, allAreNew) {
- var item, scaledItem, id;
- var geojson = GeoJSON.read(data);
- for (var i = 0, il = geojson.length; i < il; i++) {
- item = geojson[i];
- id = item.id || [item.footprint[0], item.footprint[1], item.height, item.minHeight].join(',');
- if (!this.loadedItems[id]) {
- if ((scaledItem = this.scale(item))) {
- scaledItem.scale = allAreNew ? 0 : 1;
- this.items.push(scaledItem);
- this.loadedItems[id] = 1;
- }
- }
- }
- fadeIn();
- },
- scale: function (item) {
- var
- res = {},
- // TODO: calculate this on zoom change only
- zoomScale = 6 / pow(2, ZOOM - MIN_ZOOM); // TODO: consider using HEIGHT / (global.devicePixelRatio || 1)
- if (item.id) {
- res.id = item.id;
- }
- res.height = min(item.height / zoomScale, MAX_HEIGHT);
- res.minHeight = isNaN(item.minHeight) ? 0 : item.minHeight / zoomScale;
- if (res.minHeight > MAX_HEIGHT) {
- return;
- }
- res.footprint = this.getPixelFootprint(item.footprint);
- if (!res.footprint) {
- return;
- }
- res.center = getCenter(res.footprint);
- if (item.radius) {
- res.radius = item.radius * PIXEL_PER_DEG;
- }
- if (item.shape) {
- res.shape = item.shape;
- }
- if (item.roofShape) {
- res.roofShape = item.roofShape;
- }
- if ((res.roofShape === 'cone' || res.roofShape === 'dome') && !res.shape && isRotational(res.footprint)) {
- res.shape = 'cylinder';
- }
- if (item.holes) {
- res.holes = [];
- var innerFootprint;
- for (var i = 0, il = item.holes.length; i < il; i++) {
- // TODO: simplify
- if ((innerFootprint = this.getPixelFootprint(item.holes[i]))) {
- res.holes.push(innerFootprint);
- }
- }
- }
- var color;
- if (item.wallColor) {
- if ((color = Color.parse(item.wallColor))) {
- color = color.alpha(ZOOM_FACTOR);
- res.altColor = '' + color.lightness(0.8);
- res.wallColor = '' + color;
- }
- }
- if (item.roofColor) {
- if ((color = Color.parse(item.roofColor))) {
- res.roofColor = '' + color.alpha(ZOOM_FACTOR);
- }
- }
- if (item.relationId) {
- res.relationId = item.relationId;
- }
- res.hitColor = HitAreas.idToColor(item.relationId || item.id);
- res.roofHeight = isNaN(item.roofHeight) ? 0 : item.roofHeight / zoomScale;
- if (res.height + res.roofHeight <= res.minHeight) {
- return;
- }
- return res;
- },
- set: function (data) {
- this.isStatic = true;
- this.resetItems();
- this._staticData = data;
- this.addRenderItems(this._staticData, true);
- },
- load: function (src, key) {
- this.src = src || DATA_SRC.replace('{k}', (key || 'anonymous'));
- this.update();
- },
- update: function () {
- this.resetItems();
- if (ZOOM < MIN_ZOOM) {
- return;
- }
- if (this.isStatic && this._staticData) {
- this.addRenderItems(this._staticData);
- return;
- }
- if (!this.src) {
- return;
- }
- var
- tileZoom = 13,
- tileSize = 256,
- zoomedTileSize = ZOOM > tileZoom ? tileSize << (ZOOM - tileZoom) : tileSize >> (tileZoom - ZOOM),
- minX = ORIGIN_X / zoomedTileSize << 0,
- minY = ORIGIN_Y / zoomedTileSize << 0,
- maxX = ceil((ORIGIN_X + WIDTH) / zoomedTileSize),
- maxY = ceil((ORIGIN_Y + HEIGHT) / zoomedTileSize),
- x, y;
- var scope = this;
- function callback(json) {
- scope.addRenderItems(json);
- }
- for (y = minY; y <= maxY; y++) {
- for (x = minX; x <= maxX; x++) {
- this.loadTile(x, y, tileZoom, callback);
- }
- }
- },
- loadTile: function (x, y, zoom, callback) {
- var s = 'abcd'[(x + y) % 4];
- var url = this.src.replace('{s}', s).replace('{x}', x).replace('{y}', y).replace('{z}', zoom);
- //return Request.loadJSON(url, callback);
- }
- };
- //块状体
- //****** file: Block.js ******
- var Block = {
- draw: function (context, polygon, innerPolygons, height, minHeight, color, altColor, roofColor) {
- var
- i, il,
- roof = this._extrude(context, polygon, height, minHeight, color, altColor),
- innerRoofs = [];
- if (innerPolygons) {
- for (i = 0, il = innerPolygons.length; i < il; i++) {
- innerRoofs[i] = this._extrude(context, innerPolygons[i], height, minHeight, color, altColor);
- }
- }
- context.fillStyle = roofColor;
- context.beginPath();
- this._ring(context, roof);
- if (innerPolygons) {
- for (i = 0, il = innerRoofs.length; i < il; i++) {
- this._ring(context, innerRoofs[i]);
- }
- }
- context.closePath();
- context.stroke();
- context.fill();
- },
- _extrude: function (context, polygon, height, minHeight, color, altColor) {
- var
- scale = CAM_Z / (CAM_Z - height),
- minScale = CAM_Z / (CAM_Z - minHeight),
- a = {x: 0, y: 0},
- b = {x: 0, y: 0},
- _a, _b,
- roof = [];
- for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
- a.x = polygon[i] - ORIGIN_X;
- a.y = polygon[i + 1] - ORIGIN_Y;
- b.x = polygon[i + 2] - ORIGIN_X;
- b.y = polygon[i + 3] - ORIGIN_Y;
- _a = Buildings.project(a, scale);
- _b = Buildings.project(b, scale);
- if (minHeight) {
- a = Buildings.project(a, minScale);
- b = Buildings.project(b, minScale);
- }
- // backface culling check
- if ((b.x - a.x) * (_a.y - a.y) > (_a.x - a.x) * (b.y - a.y)) {
- // depending on direction, set wall shading
- if ((a.x < b.x && a.y < b.y) || (a.x > b.x && a.y > b.y)) {
- context.fillStyle = altColor;
- } else {
- context.fillStyle = color;
- }
- context.beginPath();
- this._ring(context, [
- b.x, b.y,
- a.x, a.y,
- _a.x, _a.y,
- _b.x, _b.y
- ]);
- context.closePath();
- context.fill();
- }
- roof[i] = _a.x;
- roof[i + 1] = _a.y;
- }
- return roof;
- },
- _ring: function (context, polygon) {
- context.moveTo(polygon[0], polygon[1]);
- for (var i = 2, il = polygon.length - 1; i < il; i += 2) {
- context.lineTo(polygon[i], polygon[i + 1]);
- }
- },
- simplified: function (context, polygon, innerPolygons) {
- context.beginPath();
- this._ringAbs(context, polygon);
- if (innerPolygons) {
- for (var i = 0, il = innerPolygons.length; i < il; i++) {
- this._ringAbs(context, innerPolygons[i]);
- }
- }
- context.closePath();
- context.stroke();
- context.fill();
- },
- _ringAbs: function (context, polygon) {
- context.moveTo(polygon[0] - ORIGIN_X, polygon[1] - ORIGIN_Y);
- for (var i = 2, il = polygon.length - 1; i < il; i += 2) {
- context.lineTo(polygon[i] - ORIGIN_X, polygon[i + 1] - ORIGIN_Y);
- }
- },
- shadow: function (context, polygon, innerPolygons, height, minHeight) {
- var
- mode = null,
- a = {x: 0, y: 0},
- b = {x: 0, y: 0},
- _a, _b;
- for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
- a.x = polygon[i] - ORIGIN_X;
- a.y = polygon[i + 1] - ORIGIN_Y;
- b.x = polygon[i + 2] - ORIGIN_X;
- b.y = polygon[i + 3] - ORIGIN_Y;
- _a = Shadows.project(a, height);
- _b = Shadows.project(b, height);
- if (minHeight) {
- a = Shadows.project(a, minHeight);
- b = Shadows.project(b, minHeight);
- }
- // mode 0: floor edges, mode 1: roof edges
- if ((b.x - a.x) * (_a.y - a.y) > (_a.x - a.x) * (b.y - a.y)) {
- if (mode === 1) {
- context.lineTo(a.x, a.y);
- }
- mode = 0;
- if (!i) {
- context.moveTo(a.x, a.y);
- }
- context.lineTo(b.x, b.y);
- } else {
- if (mode === 0) {
- context.lineTo(_a.x, _a.y);
- }
- mode = 1;
- if (!i) {
- context.moveTo(_a.x, _a.y);
- }
- context.lineTo(_b.x, _b.y);
- }
- }
- if (innerPolygons) {
- for (i = 0, il = innerPolygons.length; i < il; i++) {
- this._ringAbs(context, innerPolygons[i]);
- }
- }
- },
- shadowMask: function (context, polygon, innerPolygons) {
- this._ringAbs(context, polygon);
- if (innerPolygons) {
- for (var i = 0, il = innerPolygons.length; i < il; i++) {
- this._ringAbs(context, innerPolygons[i]);
- }
- }
- },
- hitArea: function (context, polygon, innerPolygons, height, minHeight, color) {
- var
- mode = null,
- a = {x: 0, y: 0},
- b = {x: 0, y: 0},
- scale = CAM_Z / (CAM_Z - height),
- minScale = CAM_Z / (CAM_Z - minHeight),
- _a, _b;
- context.fillStyle = color;
- context.beginPath();
- for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
- a.x = polygon[i] - ORIGIN_X;
- a.y = polygon[i + 1] - ORIGIN_Y;
- b.x = polygon[i + 2] - ORIGIN_X;
- b.y = polygon[i + 3] - ORIGIN_Y;
- _a = Buildings.project(a, scale);
- _b = Buildings.project(b, scale);
- if (minHeight) {
- a = Buildings.project(a, minScale);
- b = Buildings.project(b, minScale);
- }
- // mode 0: floor edges, mode 1: roof edges
- if ((b.x - a.x) * (_a.y - a.y) > (_a.x - a.x) * (b.y - a.y)) {
- if (mode === 1) { // mode is initially undefined
- context.lineTo(a.x, a.y);
- }
- mode = 0;
- if (!i) {
- context.moveTo(a.x, a.y);
- }
- context.lineTo(b.x, b.y);
- } else {
- if (mode === 0) { // mode is initially undefined
- context.lineTo(_a.x, _a.y);
- }
- mode = 1;
- if (!i) {
- context.moveTo(_a.x, _a.y);
- }
- context.lineTo(_b.x, _b.y);
- }
- }
- context.closePath();
- context.fill();
- }
- };
- //圆柱体
- //****** file: Cylinder.js ******
- var Cylinder = {
- draw: function (context, center, radius, topRadius, height, minHeight, color, altColor, roofColor) {
- var
- c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y},
- scale = CAM_Z / (CAM_Z - height),
- minScale = CAM_Z / (CAM_Z - minHeight),
- apex = Buildings.project(c, scale),
- a1, a2;
- topRadius *= scale;
- if (minHeight) {
- c = Buildings.project(c, minScale);
- radius = radius * minScale;
- }
- // common tangents for ground and roof circle
- var tangents = this._tangents(c, radius, apex, topRadius);
- // no tangents? top circle is inside bottom circle
- if (!tangents) {
- a1 = 1.5 * PI;
- a2 = 1.5 * PI;
- } else {
- a1 = atan2(tangents[0].y1 - c.y, tangents[0].x1 - c.x);
- a2 = atan2(tangents[1].y1 - c.y, tangents[1].x1 - c.x);
- }
- context.fillStyle = color;
- context.beginPath();
- context.arc(apex.x, apex.y, topRadius, HALF_PI, a1, true);
- context.arc(c.x, c.y, radius, a1, HALF_PI);
- context.closePath();
- context.fill();
- context.fillStyle = altColor;
- context.beginPath();
- context.arc(apex.x, apex.y, topRadius, a2, HALF_PI, true);
- context.arc(c.x, c.y, radius, HALF_PI, a2);
- context.closePath();
- context.fill();
- context.fillStyle = roofColor;
- this._circle(context, apex, topRadius);
- },
- simplified: function (context, center, radius) {
- this._circle(context, {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y}, radius);
- },
- shadow: function (context, center, radius, topRadius, height, minHeight) {
- var
- c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y},
- apex = Shadows.project(c, height),
- p1, p2;
- if (minHeight) {
- c = Shadows.project(c, minHeight);
- }
- // common tangents for ground and roof circle
- var tangents = this._tangents(c, radius, apex, topRadius);
- // TODO: no tangents? roof overlaps everything near cam position
- if (tangents) {
- p1 = atan2(tangents[0].y1 - c.y, tangents[0].x1 - c.x);
- p2 = atan2(tangents[1].y1 - c.y, tangents[1].x1 - c.x);
- context.moveTo(tangents[1].x2, tangents[1].y2);
- context.arc(apex.x, apex.y, topRadius, p2, p1);
- context.arc(c.x, c.y, radius, p1, p2);
- } else {
- context.moveTo(c.x + radius, c.y);
- context.arc(c.x, c.y, radius, 0, 2 * PI);
- }
- },
- shadowMask: function (context, center, radius) {
- var c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y};
- context.moveTo(c.x + radius, c.y);
- context.arc(c.x, c.y, radius, 0, PI * 2);
- },
- hitArea: function (context, center, radius, topRadius, height, minHeight, color) {
- var
- c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y},
- scale = CAM_Z / (CAM_Z - height),
- minScale = CAM_Z / (CAM_Z - minHeight),
- apex = Buildings.project(c, scale),
- p1, p2;
- topRadius *= scale;
- if (minHeight) {
- c = Buildings.project(c, minScale);
- radius = radius * minScale;
- }
- // common tangents for ground and roof circle
- var tangents = this._tangents(c, radius, apex, topRadius);
- context.fillStyle = color;
- context.beginPath();
- // TODO: no tangents? roof overlaps everything near cam position
- if (tangents) {
- p1 = atan2(tangents[0].y1 - c.y, tangents[0].x1 - c.x);
- p2 = atan2(tangents[1].y1 - c.y, tangents[1].x1 - c.x);
- context.moveTo(tangents[1].x2, tangents[1].y2);
- context.arc(apex.x, apex.y, topRadius, p2, p1);
- context.arc(c.x, c.y, radius, p1, p2);
- } else {
- context.moveTo(c.x + radius, c.y);
- context.arc(c.x, c.y, radius, 0, 2 * PI);
- }
- context.closePath();
- context.fill();
- },
- _circle: function (context, center, radius) {
- context.beginPath();
- context.arc(center.x, center.y, radius, 0, PI * 2);
- context.stroke();
- context.fill();
- },
- // http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Tangents_between_two_circles
- _tangents: function (c1, r1, c2, r2) {
- var
- dx = c1.x - c2.x,
- dy = c1.y - c2.y,
- dr = r1 - r2,
- sqdist = (dx * dx) + (dy * dy);
- if (sqdist <= dr * dr) {
- return;
- }
- var dist = sqrt(sqdist),
- vx = -dx / dist,
- vy = -dy / dist,
- c = dr / dist,
- res = [],
- h, nx, ny;
- // Let A, B be the centers, and C, D be points at which the tangent
- // touches first and second circle, and n be the normal vector to it.
- //
- // We have the system:
- // n * n = 1 (n is a unit vector)
- // C = A + r1 * n
- // D = B + r2 * n
- // n * CD = 0 (common orthogonality)
- //
- // n * CD = n * (AB + r2*n - r1*n) = AB*n - (r1 -/+ r2) = 0, <=>
- // AB * n = (r1 -/+ r2), <=>
- // v * n = (r1 -/+ r2) / d, where v = AB/|AB| = AB/d
- // This is a linear equation in unknown vector n.
- // Now we're just intersecting a line with a circle: v*n=c, n*n=1
- h = sqrt(max(0, 1 - c * c));
- for (var sign = 1; sign >= -1; sign -= 2) {
- nx = vx * c - sign * h * vy;
- ny = vy * c + sign * h * vx;
- res.push({
- x1: c1.x + r1 * nx << 0,
- y1: c1.y + r1 * ny << 0,
- x2: c2.x + r2 * nx << 0,
- y2: c2.y + r2 * ny << 0
- });
- }
- return res;
- }
- };
- //椎体
- //****** file: Pyramid.js ******
- var Pyramid = {
- draw: function (context, polygon, center, height, minHeight, color, altColor) {
- var
- c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y},
- scale = CAM_Z / (CAM_Z - height),
- minScale = CAM_Z / (CAM_Z - minHeight),
- apex = Buildings.project(c, scale),
- a = {x: 0, y: 0},
- b = {x: 0, y: 0};
- for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
- a.x = polygon[i] - ORIGIN_X;
- a.y = polygon[i + 1] - ORIGIN_Y;
- b.x = polygon[i + 2] - ORIGIN_X;
- b.y = polygon[i + 3] - ORIGIN_Y;
- if (minHeight) {
- a = Buildings.project(a, minScale);
- b = Buildings.project(b, minScale);
- }
- // backface culling check
- if ((b.x - a.x) * (apex.y - a.y) > (apex.x - a.x) * (b.y - a.y)) {
- // depending on direction, set shading
- if ((a.x < b.x && a.y < b.y) || (a.x > b.x && a.y > b.y)) {
- context.fillStyle = altColor;
- } else {
- context.fillStyle = color;
- }
- context.beginPath();
- this._triangle(context, a, b, apex);
- context.closePath();
- context.fill();
- }
- }
- },
- _triangle: function (context, a, b, c) {
- context.moveTo(a.x, a.y);
- context.lineTo(b.x, b.y);
- context.lineTo(c.x, c.y);
- },
- _ring: function (context, polygon) {
- context.moveTo(polygon[0] - ORIGIN_X, polygon[1] - ORIGIN_Y);
- for (var i = 2, il = polygon.length - 1; i < il; i += 2) {
- context.lineTo(polygon[i] - ORIGIN_X, polygon[i + 1] - ORIGIN_Y);
- }
- },
- shadow: function (context, polygon, center, height, minHeight) {
- var
- a = {x: 0, y: 0},
- b = {x: 0, y: 0},
- c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y},
- apex = Shadows.project(c, height);
- for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
- a.x = polygon[i] - ORIGIN_X;
- a.y = polygon[i + 1] - ORIGIN_Y;
- b.x = polygon[i + 2] - ORIGIN_X;
- b.y = polygon[i + 3] - ORIGIN_Y;
- if (minHeight) {
- a = Shadows.project(a, minHeight);
- b = Shadows.project(b, minHeight);
- }
- // backface culling check
- if ((b.x - a.x) * (apex.y - a.y) > (apex.x - a.x) * (b.y - a.y)) {
- // depending on direction, set shading
- this._triangle(context, a, b, apex);
- }
- }
- },
- shadowMask: function (context, polygon) {
- this._ring(context, polygon);
- },
- hitArea: function (context, polygon, center, height, minHeight, color) {
- var
- c = {x: center.x - ORIGIN_X, y: center.y - ORIGIN_Y},
- scale = CAM_Z / (CAM_Z - height),
- minScale = CAM_Z / (CAM_Z - minHeight),
- apex = Buildings.project(c, scale),
- a = {x: 0, y: 0},
- b = {x: 0, y: 0};
- context.fillStyle = color;
- context.beginPath();
- for (var i = 0, il = polygon.length - 3; i < il; i += 2) {
- a.x = polygon[i] - ORIGIN_X;
- a.y = polygon[i + 1] - ORIGIN_Y;
- b.x = polygon[i + 2] - ORIGIN_X;
- b.y = polygon[i + 3] - ORIGIN_Y;
- if (minHeight) {
- a = Buildings.project(a, minScale);
- b = Buildings.project(b, minScale);
- }
- // backface culling check
- if ((b.x - a.x) * (apex.y - a.y) > (apex.x - a.x) * (b.y - a.y)) {
- this._triangle(context, a, b, apex);
- }
- }
- context.closePath();
- context.fill();
- }
- };
- //****** file: Buildings.js ******
- var Buildings = {
- project: function (p, m) {
- return {
- x: (p.x - CAM_X) * m + CAM_X << 0,
- y: (p.y - CAM_Y) * m + CAM_Y << 0
- };
- },
- render: function () {
- var context = this.context;
- context.clearRect(0, 0, WIDTH, HEIGHT);
- // show on high zoom levels only and avoid rendering during zoom
- if (ZOOM < MIN_ZOOM || isZooming) {
- return;
- }
- var
- item,
- h, mh,
- sortCam = {x: CAM_X + ORIGIN_X, y: CAM_Y + ORIGIN_Y},
- footprint,
- wallColor, altColor, roofColor,
- dataItems = Data.items;
- dataItems.sort(function (a, b) {
- return (a.minHeight - b.minHeight) || getDistance(b.center, sortCam) - getDistance(a.center, sortCam) || (b.height - a.height);
- });
- for (var i = 0, il = dataItems.length; i < il; i++) {
- item = dataItems[i];
- if (Simplified.isSimple(item)) {
- continue;
- }
- footprint = item.footprint;
- if (!isVisible(footprint)) {
- continue;
- }
- // when fading in, use a dynamic height
- h = item.scale < 1 ? item.height * item.scale : item.height;
- mh = 0;
- if (item.minHeight) {
- mh = item.scale < 1 ? item.minHeight * item.scale : item.minHeight;
- }
- wallColor = item.wallColor || WALL_COLOR_STR;
- altColor = item.altColor || ALT_COLOR_STR;
- roofColor = item.roofColor || ROOF_COLOR_STR;
- context.strokeStyle = altColor;
- switch (item.shape) {
- case 'cylinder':
- Cylinder.draw(context, item.center, item.radius, item.radius, h, mh, wallColor, altColor, roofColor);
- break;
- case 'cone':
- Cylinder.draw(context, item.center, item.radius, 0, h, mh, wallColor, altColor);
- break;
- case 'dome':
- Cylinder.draw(context, item.center, item.radius, item.radius / 2, h, mh, wallColor, altColor);
- break;
- case 'sphere':
- Cylinder.draw(context, item.center, item.radius, item.radius, h, mh, wallColor, altColor, roofColor);
- break;
- case 'pyramid':
- Pyramid.draw(context, footprint, item.center, h, mh, wallColor, altColor);
- break;
- default:
- Block.draw(context, footprint, item.holes, h, mh, wallColor, altColor, roofColor);
- }
- switch (item.roofShape) {
- case 'cone':
- Cylinder.draw(context, item.center, item.radius, 0, h + item.roofHeight, h, roofColor, '' + Color.parse(roofColor).lightness(0.9));
- break;
- case 'dome':
- Cylinder.draw(context, item.center, item.radius, item.radius / 2, h + item.roofHeight, h, roofColor, '' + Color.parse(roofColor).lightness(0.9));
- break;
- case 'pyramid':
- Pyramid.draw(context, footprint, item.center, h + item.roofHeight, h, roofColor, Color.parse(roofColor).lightness(0.9));
- break;
- }
- }
- }
- };
- //****** file: Simplified.js ******
- var Simplified = {
- maxZoom: MIN_ZOOM + 2,
- maxHeight: 5,
- isSimple: function (item) {
- return (ZOOM <= this.maxZoom && item.height + item.roofHeight < this.maxHeight);
- },
- render: function () {
- var context = this.context;
- context.clearRect(0, 0, WIDTH, HEIGHT);
- // show on high zoom levels only and avoid rendering during zoom
- if (ZOOM < MIN_ZOOM || isZooming || ZOOM > this.maxZoom) {
- return;
- }
- var
- item,
- footprint,
- dataItems = Data.items;
- for (var i = 0, il = dataItems.length; i < il; i++) {
- item = dataItems[i];
- if (item.height >= this.maxHeight) {
- continue;
- }
- footprint = item.footprint;
- if (!isVisible(footprint)) {
- continue;
- }
- context.strokeStyle = item.altColor || ALT_COLOR_STR;
- context.fillStyle = item.roofColor || ROOF_COLOR_STR;
- switch (item.shape) {
- case 'cylinder':
- case 'cone':
- case 'dome':
- case 'sphere':
- Cylinder.simplified(context, item.center, item.radius);
- break;
- default:
- Block.simplified(context, footprint, item.holes);
- }
- }
- }
- };
- //****** file: Shadows.js ******
- var Shadows = {
- enabled: true,
- color: '#666666',
- blurColor: '#000000',
- blurSize: 15,
- date: new Date(),
- direction: {x: 0, y: 0},
- project: function (p, h) {
- return {
- x: p.x + this.direction.x * h,
- y: p.y + this.direction.y * h
- };
- },
- render: function () {
- var
- context = this.context,
- screenCenter, sun, length, alpha;
- context.clearRect(0, 0, WIDTH, HEIGHT);
- // show on high zoom levels only and avoid rendering during zoom
- if (!this.enabled || ZOOM < MIN_ZOOM || isZooming) {
- return;
- }
- // TODO: calculate this just on demand
- screenCenter = pixelToGeo(CENTER_X + ORIGIN_X, CENTER_Y + ORIGIN_Y);
- sun = getSunPosition(this.date, screenCenter.latitude, screenCenter.longitude);
- if (sun.altitude <= 0) {
- return;
- }
- length = 1 / tan(sun.altitude);
- alpha = length < 5 ? 0.75 : 1 / length * 5;
- this.direction.x = cos(sun.azimuth) * length;
- this.direction.y = sin(sun.azimuth) * length;
- var
- i, il,
- item,
- h, mh,
- footprint,
- dataItems = Data.items;
- context.canvas.style.opacity = alpha / (ZOOM_FACTOR * 2);
- context.shadowColor = this.blurColor;
- context.shadowBlur = this.blurSize * (ZOOM_FACTOR / 2);
- context.fillStyle = this.color;
- context.beginPath();
- for (i = 0, il = dataItems.length; i < il; i++) {
- item = dataItems[i];
- footprint = item.footprint;
- if (!isVisible(footprint)) {
- continue;
- }
- // when fading in, use a dynamic height
- h = item.scale < 1 ? item.height * item.scale : item.height;
- mh = 0;
- if (item.minHeight) {
- mh = item.scale < 1 ? item.minHeight * item.scale : item.minHeight;
- }
- switch (item.shape) {
- case 'cylinder':
- Cylinder.shadow(context, item.center, item.radius, item.radius, h, mh);
- break;
- case 'cone':
- Cylinder.shadow(context, item.center, item.radius, 0, h, mh);
- break;
- case 'dome':
- Cylinder.shadow(context, item.center, item.radius, item.radius / 2, h, mh);
- break;
- case 'sphere':
- Cylinder.shadow(context, item.center, item.radius, item.radius, h, mh);
- break;
- case 'pyramid':
- Pyramid.shadow(context, footprint, item.center, h, mh);
- break;
- default:
- Block.shadow(context, footprint, item.holes, h, mh);
- }
- switch (item.roofShape) {
- case 'cone':
- Cylinder.shadow(context, item.center, item.radius, 0, h + item.roofHeight, h);
- break;
- case 'dome':
- Cylinder.shadow(context, item.center, item.radius, item.radius / 2, h + item.roofHeight, h);
- break;
- case 'pyramid':
- Pyramid.shadow(context, footprint, item.center, h + item.roofHeight, h);
- break;
- }
- }
- context.closePath();
- context.fill();
- context.shadowBlur = null;
- // now draw all the footprints as negative clipping mask
- context.globalCompositeOperation = 'destination-out';
- context.beginPath();
- for (i = 0, il = dataItems.length; i < il; i++) {
- item = dataItems[i];
- footprint = item.footprint;
- if (!isVisible(footprint)) {
- continue;
- }
- // if object is hovered, there is no need to clip it's footprint
- if (item.minHeight) {
- continue;
- }
- switch (item.shape) {
- case 'cylinder':
- case 'cone':
- case 'dome':
- Cylinder.shadowMask(context, item.center, item.radius);
- break;
- default:
- Block.shadowMask(context, footprint, item.holes);
- }
- }
- context.fillStyle = '#00ff00';
- context.fill();
- context.globalCompositeOperation = 'source-over';
- }
- };
- //****** file: HitAreas.js ******
- var HitAreas = {
- _idMapping: [null],
- reset: function () {
- this._idMapping = [null];
- },
- render: function () {
- if (this._timer) {
- return;
- }
- var self = this;
- this._timer = setTimeout(function () {
- self._timer = null;
- self._render();
- }, 500);
- },
- _render: function () {
- var context = this.context;
- context.clearRect(0, 0, WIDTH, HEIGHT);
- // show on high zoom levels only and avoid rendering during zoom
- if (ZOOM < MIN_ZOOM || isZooming) {
- return;
- }
- var
- item,
- h, mh,
- sortCam = {x: CAM_X + ORIGIN_X, y: CAM_Y + ORIGIN_Y},
- footprint,
- color,
- dataItems = Data.items;
- dataItems.sort(function (a, b) {
- return (a.minHeight - b.minHeight) || getDistance(b.center, sortCam) - getDistance(a.center, sortCam) || (b.height - a.height);
- });
- for (var i = 0, il = dataItems.length; i < il; i++) {
- item = dataItems[i];
- if (!(color = item.hitColor)) {
- continue;
- }
- footprint = item.footprint;
- if (!isVisible(footprint)) {
- continue;
- }
- h = item.height;
- mh = 0;
- if (item.minHeight) {
- mh = item.minHeight;
- }
- switch (item.shape) {
- case 'cylinder':
- Cylinder.hitArea(context, item.center, item.radius, item.radius, h, mh, color);
- break;
- case 'cone':
- Cylinder.hitArea(context, item.center, item.radius, 0, h, mh, color);
- break;
- case 'dome':
- Cylinder.hitArea(context, item.center, item.radius, item.radius / 2, h, mh, color);
- break;
- case 'sphere':
- Cylinder.hitArea(context, item.center, item.radius, item.radius, h, mh, color);
- break;
- case 'pyramid':
- Pyramid.hitArea(context, footprint, item.center, h, mh, color);
- break;
- default:
- Block.hitArea(context, footprint, item.holes, h, mh, color);
- }
- switch (item.roofShape) {
- case 'cone':
- Cylinder.hitArea(context, item.center, item.radius, 0, h + item.roofHeight, h, color);
- break;
- case 'dome':
- Cylinder.hitArea(context, item.center, item.radius, item.radius / 2, h + item.roofHeight, h, color);
- break;
- case 'pyramid':
- Pyramid.hitArea(context, footprint, item.center, h + item.roofHeight, h, color);
- break;
- }
- }
- this._imageData = this.context.getImageData(0, 0, WIDTH, HEIGHT).data;
- },
- getIdFromXY: function (x, y) {
- var imageData = this._imageData;
- if (!imageData) {
- return;
- }
- var pos = 4 * ((y | 0) * WIDTH + (x | 0));
- var index = imageData[pos] | (imageData[pos + 1] << 8) | (imageData[pos + 2] << 16);
- return this._idMapping[index];
- },
- idToColor: function (id) {
- var index = this._idMapping.indexOf(id);
- if (index === -1) {
- this._idMapping.push(id);
- index = this._idMapping.length - 1;
- }
- var r = index & 0xff;
- var g = (index >> 8) & 0xff;
- var b = (index >> 16) & 0xff;
- return 'rgb(' + [r, g, b].join(',') + ')';
- }
- };
- //****** file: Debug.js ******
- var Debug = {
- point: function (x, y, color, size) {
- var context = this.context;
- context.fillStyle = color || '#ffcc00';
- context.beginPath();
- context.arc(x, y, size || 3, 0, 2 * PI);
- context.closePath();
- context.fill();
- },
- line: function (ax, ay, bx, by, color) {
- var context = this.context;
- context.strokeStyle = color || '#ffcc00';
- context.beginPath();
- context.moveTo(ax, ay);
- context.lineTo(bx, by);
- context.closePath();
- context.stroke();
- }
- };
- //****** file: Layers.js ******
- var animTimer;
- function fadeIn() {
- if (animTimer) {
- return;
- }
- animTimer = setInterval(function () {
- var dataItems = Data.items,
- isNeeded = false;
- for (var i = 0, il = dataItems.length; i < il; i++) {
- if (dataItems[i].scale < 1) {
- dataItems[i].scale += 0.5 * 0.2; // amount*easing
- if (dataItems[i].scale > 1) {
- dataItems[i].scale = 1;
- }
- isNeeded = true;
- }
- }
- Layers.render();
- if (!isNeeded) {
- clearInterval(animTimer);
- animTimer = null;
- }
- }, 33);
- }
- var Layers = {
- container: document.createElement('DIV'),
- items: [],
- init: function () {
- this.container.style.pointerEvents = 'none';
- this.container.style.position = 'absolute';
- this.container.style.left = 0;
- this.container.style.top = 0;
- // TODO: improve this to .setContext(context)
- Shadows.context = this.createContext(this.container);
- Simplified.context = this.createContext(this.container);
- Buildings.context = this.createContext(this.container);
- HitAreas.context = this.createContext();
- // Debug.context = this.createContext(this.container);
- },
- render: function (quick) {
- requestAnimFrame(function () {
- if (!quick) {
- Shadows.render();
- Simplified.render();
- HitAreas.render();
- }
- Buildings.render();
- });
- },
- createContext: function (container) {
- var canvas = document.createElement('CANVAS');
- canvas.style.transform = 'translate3d(0, 0, 0)'; // turn on hw acceleration
- canvas.style.imageRendering = 'optimizeSpeed';
- canvas.style.position = 'absolute';
- canvas.style.left = 0;
- canvas.style.top = 0;
- var context = canvas.getContext('2d');
- context.lineCap = 'round';
- context.lineJoin = 'round';
- context.lineWidth = 1;
- context.imageSmoothingEnabled = false;
- this.items.push(canvas);
- if (container) {
- container.appendChild(canvas);
- }
- return context;
- },
- appendTo: function (parentNode) {
- parentNode.appendChild(this.container);
- },
- remove: function () {
- this.container.parentNode.removeChild(this.container);
- },
- setSize: function (width, height) {
- for (var i = 0, il = this.items.length; i < il; i++) {
- this.items[i].width = width;
- this.items[i].height = height;
- }
- },
- // usually called after move: container jumps by move delta, cam is reset
- setPosition: function (x, y) {
- this.container.style.left = x + 'px';
- this.container.style.top = y + 'px';
- }
- };
- Layers.init();
- //****** file: adapter.js ******
- function setOrigin(origin) {
- ORIGIN_X = origin.x;
- ORIGIN_Y = origin.y;
- }
- function moveCam(offset) {
- CAM_X = CENTER_X + offset.x;
- CAM_Y = HEIGHT + offset.y;
- Layers.render(true);
- }
- function setSize(size) {
- WIDTH = size.width;
- HEIGHT = size.height;
- CENTER_X = WIDTH / 2 << 0;
- CENTER_Y = HEIGHT / 2 << 0;
- CAM_X = CENTER_X;
- CAM_Y = HEIGHT;
- Layers.setSize(WIDTH, HEIGHT);
- MAX_HEIGHT = CAM_Z - 50;
- }
- function setZoom(z) {
- ZOOM = z;
- if (isLatLngProjection()) {
- MAP_SIZE = 360 / mapObj.resolution;
- } else {
- MAP_SIZE = MAP_TILE_SIZE << ZOOM;
- }
- var center = pixelToGeo(ORIGIN_X + CENTER_X, ORIGIN_Y + CENTER_Y);
- var a = geoToPixel(center.latitude, 0);
- var b = geoToPixel(center.latitude, 1);
- PIXEL_PER_DEG = b.x - a.x;
- ZOOM_FACTOR = pow(0.95, ZOOM - MIN_ZOOM);
- WALL_COLOR_STR = '' + WALL_COLOR.alpha(ZOOM_FACTOR);
- ALT_COLOR_STR = '' + ALT_COLOR.alpha(ZOOM_FACTOR);
- ROOF_COLOR_STR = '' + ROOF_COLOR.alpha(ZOOM_FACTOR);
- }
- function onResize(e) {
- setSize(e);
- Layers.render();
- Data.update();
- }
- function onMoveEnd(e) {
- Layers.render();
- Data.update(); // => fadeIn() => Layers.render()
- }
- function onZoomStart() {
- isZooming = true;
- // effectively clears because of isZooming flag
- // TODO: introduce explicit clear()
- Layers.render();
- }
- function onZoomEnd(e) {
- isZooming = false;
- setZoom(e.zoom);
- Data.update(); // => fadeIn()
- Layers.render();
- }
- //****** file: SuperMap.js ******
- // based on a pull request from Jérémy Judéaux (https://github.com/Volune)
- var parent = SuperMap.Layer.prototype;
- var transformExtent = function (extent) {
- if (isLatLngProjection()) {
- return {
- left: -180,
- bottom: -90,
- right: 180,
- top: 90
- }
- }
- return extent;
- };
- var osmb = function (map) {
- this.offset = {x: 0, y: 0}; // cumulative cam offset during moveBy()
- bindMap(map);
- parent.initialize.call(this, this.name, {projection: 'EPSG:900913'});
- if (map) {
- map.addLayer(this);
- }
- };
- //bind map to global
- function bindMap(map) {
- mapObj = map;
- }
- var proto = osmb.prototype = new SuperMap.Layer();
- proto.name = 'OSM Buildings';
- proto.attribution = ATTRIBUTION;
- proto.isBaseLayer = false;
- proto.alwaysInRange = true;
- proto.addTo = function (map) {
- this.setMap(map);
- return this;
- };
- proto.setOrigin = function () {
- var map = this.map,
- origin = map.getLonLatFromPixel(new SuperMap.Pixel(0, 0)),
- res = map.resolution;
- var ext = transformExtent(this.maxExtent);
- var x = (origin.lon - ext.left) / res << 0,
- y = (ext.top - origin.lat) / res << 0;
- setOrigin({x: x, y: y});
- };
- proto.setMap = function (map) {
- if (!this.map) {
- parent.setMap.call(this, map);
- }
- Layers.appendTo(this.div);
- setSize({width: map.size.w, height: map.size.h});
- setZoom(map.zoom);
- this.setOrigin();
- var layerProjection = this.projection;
- map.events.register('click', map, function (e) {
- var id = HitAreas.getIdFromXY(e.xy.x, e.xy.y);
- if (id) {
- var geo = map.getLonLatFromPixel(e.xy).transform(layerProjection, this.projection);
- onClick({feature: id, lat: geo.lat, lon: geo.lon});
- }
- });
- Data.update();
- };
- proto.removeMap = function (map) {
- Layers.remove();
- parent.removeMap.call(this, map);
- this.map = null;
- };
- proto.onMapResize = function () {
- var map = this.map;
- parent.onMapResize.call(this);
- onResize({width: map.size.w, height: map.size.h});
- };
- proto.moveTo = function (bounds, zoomChanged, isDragging) {
- var
- map = this.map,
- res = parent.moveTo.call(this, bounds, zoomChanged, isDragging);
- if (!isDragging) {
- var
- offsetLeft = parseInt(map.layerContainerDiv.style.left, 10),
- offsetTop = parseInt(map.layerContainerDiv.style.top, 10);
- this.div.style.left = -offsetLeft + 'px';
- this.div.style.top = -offsetTop + 'px';
- }
- this.setOrigin();
- this.offset.x = 0;
- this.offset.y = 0;
- moveCam(this.offset);
- if (zoomChanged) {
- onZoomEnd({zoom: map.zoom});
- } else {
- onMoveEnd();
- }
- return res;
- };
- proto.moveByPx = function (dx, dy) {
- this.offset.x += dx;
- this.offset.y += dy;
- var res = parent.moveByPx.call(this, dx, dy);
- moveCam(this.offset);
- return res;
- };
- //****** file: public.js ******
- proto.style = function (style) {
- style = style || {};
- var color;
- if ((color = style.color || style.wallColor)) {
- WALL_COLOR = Color.parse(color);
- WALL_COLOR_STR = '' + WALL_COLOR.alpha(ZOOM_FACTOR);
- ALT_COLOR = WALL_COLOR.lightness(0.8);
- ALT_COLOR_STR = '' + ALT_COLOR.alpha(ZOOM_FACTOR);
- ROOF_COLOR = WALL_COLOR.lightness(1.2);
- ROOF_COLOR_STR = '' + ROOF_COLOR.alpha(ZOOM_FACTOR);
- }
- if (style.roofColor) {
- ROOF_COLOR = Color.parse(style.roofColor);
- ROOF_COLOR_STR = '' + ROOF_COLOR.alpha(ZOOM_FACTOR);
- }
- if (style.shadows !== undefined) {
- Shadows.enabled = !!style.shadows;
- }
- Layers.render();
- return this;
- };
- proto.date = function (date) {
- Shadows.date = date;
- Shadows.render();
- return this;
- };
- proto.load = function (url) {
- Data.load(url);
- return this;
- };
- proto.set = function (data) {
- Data.set(data);
- return this;
- };
- var onEach = function () {
- };
- proto.each = function (handler) {
- onEach = function (payload) {
- return handler(payload);
- };
- return this;
- };
- var onClick = function () {
- };
- proto.click = function (handler) {
- onClick = function (payload) {
- return handler(payload);
- };
- return this;
- };
- osmb.VERSION = VERSION;
- osmb.ATTRIBUTION = ATTRIBUTION;
- //****** file: suffix.js ******
- global.OSMBuildings = osmb;
- }(this));
|