mirror of
				https://github.com/jiawanlong/Cesium-Examples.git
				synced 2025-11-04 01:04:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			2449 lines
		
	
	
		
			75 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			2449 lines
		
	
	
		
			75 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * 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));
 | 
						|
 | 
						|
 |