Full Code of kekscom/osmbuildings for AI

master 47dd8c1f4d8c cached
44 files
342.6 KB
108.2k tokens
798 symbols
1 requests
Download .txt
Showing preview only (358K chars total). Download the full file or copy to clipboard to get everything.
Repository: kekscom/osmbuildings
Branch: master
Commit: 47dd8c1f4d8c
Files: 44
Total size: 342.6 KB

Directory structure:
gitextract_r4y828n7/

├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── PERFORMANCE.md
├── README.md
├── build.js
├── dist/
│   ├── OSMBuildings-Leaflet.debug.js
│   ├── OSMBuildings-Leaflet.js
│   ├── OSMBuildings-OpenLayers.debug.js
│   ├── OSMBuildings-OpenLayers.js
│   ├── OSMBuildings.css
│   ├── index-Leaflet-3db.html
│   ├── index-Leaflet.html
│   └── index-OpenLayers.html
├── docs/
│   └── server.md
├── package.json
├── src/
│   ├── Data.js
│   ├── Debug.js
│   ├── GeoJSON.js
│   ├── OSMBuildings.css
│   ├── Request.js
│   ├── adapter.js
│   ├── engines/
│   │   ├── Leaflet.js
│   │   ├── OpenLayers.js
│   │   ├── index-Leaflet.html
│   │   └── index-OpenLayers.html
│   ├── functions.js
│   ├── geometry/
│   │   ├── Cylinder.js
│   │   ├── Extrusion.js
│   │   ├── Pyramid.js
│   │   └── __Dome.js
│   ├── geometry.js
│   ├── layers/
│   │   ├── Buildings.js
│   │   ├── Picking.js
│   │   ├── Shadows.js
│   │   ├── Simplified.js
│   │   └── index.js
│   ├── lib/
│   │   └── getSunPosition.js
│   ├── shortcuts.js
│   └── variables.js
└── tests/
    ├── openlayers-5.3.0/
    │   ├── OSMBuildings-OL5.js
    │   ├── README.md
    │   └── index.js
    └── shadows.html

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.idea/
_misc/
node_modules/


================================================
FILE: CHANGELOG.md
================================================
# Changelog

## 3.0.0 @ 2020-03-20 WIP

- refactoring for ES6
- npm package creation


## 0.2.2b @ 2014-11-25

**Features**

- detection for circular objects

**Fixes**

- several fixes for radius handling
- several click-hit issues fixed


## 0.2.1b @ 2014-09-24

**Features**

- pyramidal buildungs and roofs
- buildings parts are a logical unit now (i.e. OSM relations)
- explicitly injecting OSMBuildings class into global namespace [#66](https://github.com/kekscom/osmbuildings/issues/66)


## 0.2.0b @ 2014-09-04

*From this version on, OSM Buildings is entering beta phase \o/*

**Features**

- Buildings are clickable now, use .click(function(featureId) {...})
- Massive improvements in GeoJSON reading, bigger set of properties and GeometryCollections are supported
- Ambient shadows for buildings added
- Introduced a data service for filtering and caching OSM data, results in massive speedup for loading
- Geometry: cones enabled, also used as an interim replacement for domes
- Tested device accelerated perspective aka Amazon's 'Dynamic Perspective', but disabled again in favor for performance
- Successfully tested with LeafletJS 0.8 and OpenLayers 2.13.1
- Code size reduced from 10.23 to 9.44k (all gzipped)

**Fixes**

- Height scale fixed
- Relation properties precedence fixed
- Simple buildings layer refactored
- Flipped perspective on some latitudes fixed
- Tried requestAnimationFrame, but needed to drop again for IE and iOS


## 0.1.9a @ 2013-10-17

- multipolygon support added
- backend removed, now using web services with GeoJSON or OSM Overpass XAPI
- vector data is subdivided into tiles
- data tiles are cached
- fix for chained method calls
- fix for flat buildings from rendering tall buildings too
- min zoom level decreased to 15
- fix for setStyle() removing shadows
- material color mapping added
- HSLA color support added
- support for W3C named colors added
- CORS-XHR support for MSIE added
- cylindric object rendering added
- API is now documented in GitHub README
- map engine adapters simplified
- minHeight and height units for GeoJSON enabled
- very simple fix for building occlusion
- successful tests with LeafletJS 0.6.4


## 0.1.8a @ 2013-03-10

- on layer removal from map, OSM Buildings is not destroyed anymore
- introduced multiple rendering layers
- improved simplification algorithm, inspired by Vladimir Agafonkin (https://mourner.github.com/simplify-js)
- initial version of objects draw order (farthest first, lower first)
- directional wall shading added
- building shadows added
- shadow date / time dependency added, inspired by Vladimir Agafonkin (https://github.com/mourner/suncalc)
- `min_height` support added (requires backend change)
- color / style table handling improved
- rendering tests added
- successful tests with LeafletJS 0.5.1
- recommendation: reduce building `$heightScale` in backend server config down to 1.2


## 0.1.7a @ 2012-10-10

- adding OpenLayers support, credits to Jérémy Judéaux (https://github.com/Volune)
- aligning Layer naming convention to engines
- fixing some rare cases where layer got removed


## 0.1.6a @ 2012-09-04

- GeoJSON: min zoom removed
- GeoJSON: height property re-enabled
- GeoJSON: multi polygons enabled
- Examples are rebuilt entirely
- Roof colors are re-enbled
- JSHint is now part of the build process


## 0.1.5a

- support for GeoJSON improved
- deep integration with Leaflet in order to avoid jittery movement
- enabled individual building colors
- polygon winding fixed


## 0.1.0a

- GeoJSON support added
- method chaining added
- adding converter PostGIS > MySQL
- data for Frankfurt added
- made either MySQL or PostGIS (Mapnik) fully configurable
- lat/lon order of your coordinates is configurable
- polygon direction is forced to be clockwise
- simpler initialization process


================================================
FILE: LICENSE.md
================================================
Copyright (c) 2020, OSM Buildings, Jan Marsch <mail@osmbuildings.org>
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of
     conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list
     of conditions and the following disclaimer in the documentation and/or other materials
     provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: PERFORMANCE.md
================================================
# Performance

What's done & why in terms of performance.

## IDEAS

- combine canvases
- render items of same style at once
- delete and redraw animated objects
- split off animated shadows
- combine simple buildings in low zoom
- use web workers for data processing
- adapt features dynamically
- pre-calc face direction
- consider using a render queue
- style lookup index


## Projection cache

Looks like this has been a bad idea - removed.
Probably develop the concept into rendering pipelines.
(http://jsperf.com/projcache)


## Readable keys (for compact data structures)

Seemed to be a good idea making these a bit more readable.
Loss expected but turned out to be a slight gain.
http://jsperf.com/readable-keys/2
TODO: Not true anymore. Refactor to objects/properties. DONE


## Combined 2d faces

iPhone4, iOS5: seperate faces are 56% slower
MBA 2010, Chrome: combined faces are 68% slower
Staying with combined so far, probably add system detection.
(http://jsperf.com/canvas-polygon-combiner)


## Typed arrays

Using slice() is not worth it, but it turned out dropping some typed arrays is a good idea.
Especially when these are created on each rendering pass.
(http://jsperf.com/slice-access)


## Canvas anti alias

While looking slick, it eats performance.
Not so on iPhone4, iOS5: it doesn't matter
MBA 2010, Chrome: anti alias is 24% slower
Node: for *any* test, stroking lines eats ~60% performance
(http://jsperf.com/canvas-anti-alias)


## Math round

Using ~~ for a while, it turns out, bit shift << 0 is even faster.
iPad4, iOS6: 12% faster
MBA 2010, Chrome: 25% faster
(http://jsperf.com/math-round-vs-hack/3)


## Public methods vs. closure(d) functions

Huge gain for methods on desktop browsers vs. Safari (mobile).
Will stay a bit until mobile catches up.
Finally, Safari 6 mobile beats it too.
http://jsperf.com/osmb-method-vs-function


## Considerations for further performance improvement

degrade instantly, increase slowly (take average score of a few passes)

### FADE IN
- NO_STROKES
- NO_SHADING
- NO_SHADOWS_SCALE
- NO_SCALE

### MOVE

### STATIC
- NO_STROKES
- NO_SHADING
- NO_FLAT
- NO_SHADOWS


http://jsperf.com/osmb-hidden-canvas4

================================================
FILE: README.md
================================================

<img src="https://osmbuildings.org/logo.svg" width="100" height="88">

OSM Buildings is a JavaScript library for showing building geometry on interactive maps.

Example: https://osmbuildings.org/

**The standalone WebGL version odf OSM Buildings is located here: https://github.com/OSMBuildings/OSMBuildings**

There is also documentation of OSM Buildings Server side:
https://github.com/kekscom/osmbuildings/blob/master/docs/server.md

**Example** https://osmbuildings.org/

It's safe use the [master branch](https://github.com/kekscom/osmbuildings/tree/master/dist/) for production.

For further information visit https://osmbuildings.org, follow [@osmbuildings](https://twitter.com/osmbuildings/) on Twitter or report issues [here on Github](https://github.com/kekscom/osmbuildings/issues/).


## Documentation

### Integration with Leaflet

Link Leaflet and OSM Buildings files in your HTML head section.

~~~ html
<head>
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"/>
  <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
  <script src="OSMBuildings-Leaflet.js"></script>
</head>
~~~

Initialize the map engine and add a map tile layer.<br>
Position is set to Berlin at zoom level 17, I'm using Mapbox tiles here.

~~~ javascript
var map = new L.Map('map').setView([52.52020, 13.37570], 17);
new L.TileLayer('https://{s}.tiles.mapbox.com/v3/<YOUR KEY HERE>/{z}/{x}/{y}.png',
  { attribution: 'Map tiles &copy; <a href="https://mapbox.com">Mapbox</a>', maxZoom: 17 }).addTo(map);
~~~

Add the buildings layer.

~~~ javascript
new OSMBuildings(map).load();
~~~

As a popular alternative, you could pass a <a href="http://www.geojson.org/geojson-spec.html">GeoJSON</a> FeatureCollection object.<br>
Geometry types Polygon, Multipolygon and GeometryCollection are supported.<br>
Make sure the building coordinates are projected in <a href="http://spatialreference.org/ref/epsg/4326/">EPSG:4326</a>.<br>
Height units m, ft, yd, mi are accepted, no given unit defaults to meters.

~~~ javascript
var geoJSON = {
  "type": "FeatureCollection",
  "features": [{
    "type": "Feature",
    "id": 134,
    "geometry": {
      "type": "Polygon",
      "coordinates": [[
        [13.37356, 52.52064],
        [13.37350, 52.51971],
        [13.37664, 52.51973],
        [13.37594, 52.52062],
        [13.37356, 52.52064]
      ]]
    },
    "properties": {
      "wallColor": "rgb(255,0,0)",
      "roofColor": "rgb(255,128,0)",
      "height": 500,
      "minHeight": 0
    }
  }]
};

new OSMBuildings(map).set(geoJSON);
~~~


### Integration with OpenLayers

* NEW: for Integration with OpenLayers 5 see /tests/openlayers-5.3.0 *

Link OpenLayers and OSM Buildings files in your HTML head section.

~~~ html
<head>
  <script src="http://www.openlayers.org/api/OpenLayers.js"></script>
  <script src="OSMBuildings-OpenLayers.js"></script>
</head>
~~~

Initialize the map engine and add a map tile layer.<br>
Position is set to Berlin at zoom level 17.

~~~ javascript
var map = new OpenLayers.Map('map');
map.addControl(new OpenLayers.Control.LayerSwitcher());

var osm = new OpenLayers.Layer.OSM();
map.addLayer(osm);

map.setCenter(
  new OpenLayers.LonLat(13.37570, 52.52020)
    .transform(
      new OpenLayers.Projection('EPSG:4326'),
      map.getProjectionObject()
    ),
  17
);
~~~

Add the buildings layer.

~~~ javascript
new OSMBuildings(map).load();
~~~


## API

### Constructors

<table>
<tr>
<th>Constructor</th>
<th>Description</th>
</tr>

<tr>
<td>new OSMBuildings(map)</td>
<td>Initializes the buildings layer for a given map engine.<br>
Currently Leaflet and OpenLayers are supported.</td>
</tr>
</table>

Constants

<table>
<tr>
<th>Option</th>
<th>Type</th>
<th>Description</th>
</tr>

<tr>
<td>ATTRIBUTION</td>
<td>String</td>
<td>Holds OSM Buildings copyright information.</td>
</tr>

<tr>
<td>VERSION</td>
<td>String</td>
<td>Holds current version information.</td>
</tr>
</table>

Methods

<table>
<tr>
<th>Method</th>
<th>Description</th>
</tr>

<tr>
<td>style({Object})</td>
<td>Set default styles. See below for details.</td>
</tr>

<tr>
<td>date(new Date(2017, 15, 1, 10, 30)))</td>
<td>Set date/time for shadow projection.</td>
</tr>

<tr>
<td>each({Function})</td>
<td>A callback wrapper to override each feature's properties on read. Return false in order to skip a particular feature.<br>
Callback receives a feature object as argument.</td>
</tr>

<tr>
<td>click({Function})</td>
<td>A callback wrapper to handle click events on features.<br>
Callback receives an object { featureId{number,string}, lat{float}, lon{float} } as argument.</td>
</tr>

<tr>
<td>set({GeoJSON FeatureCollection})</td>
<td>Just add GeoJSON data to your map.</td>
</tr>

<tr>
<td>load({Provider})</td>
<td>Without parameter, it loads OpenStreetMap data tiles via an OSM Buildings proxy. This proxy enables transparent data filtering and caching.
Interface of such provider is to be published.</td>
</tr>
</table>

Styles

<table>
<tr>
<th>Option</th>
<th>Type</th>
<th>Description</th>
</tr>

<tr>
<td>color/wallColor</td>
<td>String</td>
<td>Defines the objects default primary color. I.e. #ffcc00, rgb(255,200,200), rgba(255,200,200,0.9)</td>
</tr>

<tr>
<td>roofColor</td>
<td>String</td>
<td>Defines the objects default roof color.</td>
</tr>

<tr>
<td>shadows</td>
<td>Boolean</td>
<td>Enables or disables shadow rendering, default: enabled</td>
</tr>
</table>


## Data

### OSM Tags used

<table>
<tr>
<th>GeoJSON property</th>
<th>OSM Tags</th>
</tr>

<tr>
<td>height</td>
<td>height, building:height, levels, building:levels</td>
</tr>

<tr>
<td>minHeight</td>
<td>min_height, building:min_height, min_level, building:min_level</td>
</tr>

<tr>
<td>color/wallColor</td>
<td>building:color, building:colour</td>
</tr>

<tr>
<td>material</td>
<td>building:material, building:facade:material, building:cladding</td>
</tr>

<tr>
<td>roofColor</td>
<td>roof:color, roof:colour, building:roof:color, building:roof:colour</td>
</tr>

<tr>
<td>roofMaterial</td>
<td>roof:material, building:roof:material</td>
</tr>

<tr>
<td>shape</td>
<td>building:shape[=cylinder,sphere]</td>
</tr>

<tr>
<td>roofShape</td>
<td>roof:shape[=dome]</td>
</tr>

<tr>
<td>roofHeight</td>
<td>roof:height</td>
</tr>
</table>


================================================
FILE: build.js
================================================

const fs = require('fs');
const Terser = require('terser');


//*****************************************************************************

const package = require('./package.json');

const src = `${__dirname}/src`;
const dist = `${__dirname}/dist`;

const code = [
  "src/shortcuts.js",
  "node_modules/qolor/dist/Qolor.debug.js",
  "src/lib/getSunPosition.js",
  "src/GeoJSON.js",
  "src/variables.js",
  "src/geometry.js",
  "src/functions.js",
  "src/Request.js",
  "src/Data.js",
  "src/geometry/Extrusion.js",
  "src/geometry/Cylinder.js",
  "src/geometry/Pyramid.js",
  "src/layers/index.js",
  "src/layers/Buildings.js",
  "src/layers/Simplified.js",
  "src/layers/Shadows.js",
  "src/layers/Picking.js",
  "src/Debug.js",
  "src/adapter.js"
];


//*****************************************************************************

function joinFiles (files) {
  if (!files.push) {
    files = [files];
  }
  return files.map(file => fs.readFileSync(file)).join('\n');
}

function copy (srcFile, distFile) {
  fs.writeFileSync(distFile, fs.readFileSync(srcFile, 'utf8'));
}


//*****************************************************************************

function buildEngine (name, customJS) {
  const commonJS = joinFiles(code);

  let js = commonJS + '\n' + customJS;
  js = js.replace(/\{\{VERSION\}\}/g, package.version);
  js = `const OSMBuildings = (function() {\n${js}\n return OSMBuildings;\n}());`;

  fs.writeFileSync(`${dist}/OSMBuildings-${name}.debug.js`, js);
  fs.writeFileSync(`${dist}/OSMBuildings-${name}.js`, Terser.minify(js).code);
  copy(`${src}/engines/index-${name}.html`, `${dist}/index-${name}.html`);
}


//*****************************************************************************

if (!fs.existsSync(dist)) {
  fs.mkdirSync(dist);
}

buildEngine('Leaflet', fs.readFileSync(`${src}/engines/Leaflet.js`));
buildEngine('OpenLayers', fs.readFileSync(`${src}/engines/OpenLayers.js`));

copy(`${src}/OSMBuildings.css`, `${dist}/OSMBuildings.css`);


================================================
FILE: dist/OSMBuildings-Leaflet.debug.js
================================================
const OSMBuildings = (function() {

const
  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,
  pow = m.pow;


/**
 * @class
 */
class Qolor {

  /**
   * @constructor
   * @param r {Number} 0.0 .. 1.0 red value of a color
   * @param g {Number} 0.0 .. 1.0 green value of a color
   * @param b {Number} 0.0 .. 1.0 blue value of a color
   * @param a {Number} 0.0 .. 1.0 alpha value of a color, default 1
   */
  constructor (r, g, b, a = 1) {
    this.r = this._clamp(r, 1);
    this.g = this._clamp(g, 1);
    this.b = this._clamp(b, 1);
    this.a = this._clamp(a, 1);
  }

  /**
   * @param str {String} can be any color dfinition like: 'red', '#0099ff', 'rgb(64, 128, 255)', 'rgba(64, 128, 255, 0.5)'
   */
  static parse (str) {
    if (typeof str === 'string') {
      str = str.toLowerCase();
      str = Qolor.w3cColors[str] || str;

      let m;

      if ((m = str.match(/^#?(\w{2})(\w{2})(\w{2})$/))) {
        return new Qolor(parseInt(m[1], 16)/255, parseInt(m[2], 16)/255, parseInt(m[3], 16)/255);
      }

      if ((m = str.match(/^#?(\w)(\w)(\w)$/))) {
        return new Qolor(parseInt(m[1]+m[1], 16)/255, parseInt(m[2]+m[2], 16)/255, parseInt(m[3]+m[3], 16)/255);
      }

      if ((m = str.match(/rgba?\((\d+)\D+(\d+)\D+(\d+)(\D+([\d.]+))?\)/))) {
        return new Qolor(
          parseFloat(m[1])/255,
          parseFloat(m[2])/255,
          parseFloat(m[3])/255,
          m[4] ? parseFloat(m[5]) : 1
        );
      }
    }

    return new Qolor();
  }

  static fromHSL (h, s, l, a) {
    const qolor = new Qolor().fromHSL(h, s, l);
    qolor.a = a === undefined ? 1 : a;
    return qolor;
  }

  //***************************************************************************

  _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;
  }

  _clamp(v, max) {
    if (v === undefined) {
      return;
    }
    return Math.min(max, Math.max(0, v || 0));
  }

  //***************************************************************************

  isValid () {
    return this.r !== undefined && this.g !== undefined && this.b !== undefined;
  }

  toHSL () {
    if (!this.isValid()) {
      return;
    }

    const max = Math.max(this.r, this.g, this.b);
    const min = Math.min(this.r, this.g, this.b);
    const range = max - min;
    const l = (max + min)/2;

    // achromatic
    if (!range) {
      return { h: 0, s: 0, l: l };
    }

    const s = l > 0.5 ? range/(2 - max - min) : range/(max + min);

    let h;
    switch (max) {
      case this.r:
        h = (this.g - this.b)/range + (this.g<this.b ? 6 : 0);
        break;
      case this.g:
        h = (this.b - this.r)/range + 2;
        break;
      case this.b:
        h = (this.r - this.g)/range + 4;
        break;
    }
    h *= 60;

    return { h: h, s: s, l: l };
  }

  fromHSL (h, s, l) {
    // h = this._clamp(h, 360),
    // s = this._clamp(s, 1),
    // l = this._clamp(l, 1),

    // achromatic
    if (s === 0) {
      this.r = this.g = this.b = l;
      return this;
    }

    const q = l<0.5 ? l*(1 + s) : l + s - l*s;
    const p = 2*l - q;

    h /= 360;

    this.r = this._hue2rgb(p, q, h + 1/3);
    this.g = this._hue2rgb(p, q, h);
    this.b = this._hue2rgb(p, q, h - 1/3);

    return this;
  }

  toString () {
    if (!this.isValid()) {
      return;
    }

    if (this.a === 1) {
      return '#' + ((1<<24) + (Math.round(this.r*255)<<16) + (Math.round(this.g*255)<<8) + Math.round(this.b*255)).toString(16).slice(1, 7);
    }
    return `rgba(${Math.round(this.r*255)},${Math.round(this.g*255)},${Math.round(this.b*255)},${this.a.toFixed(2)})`;
  }

  toArray () {
    if (!this.isValid) {
      return;
    }
    return [this.r, this.g, this.b];
  }

  hue (h) {
    const hsl = this.toHSL();
    return this.fromHSL(hsl.h+h, hsl.s, hsl.l);
  }

  saturation (s) {
    const hsl = this.toHSL();
    return this.fromHSL(hsl.h, hsl.s*s, hsl.l);
  }

  lightness (l) {
    const hsl = this.toHSL();
    return this.fromHSL(hsl.h, hsl.s, hsl.l*l);
  }

  clone () {
    return new Qolor(this.r, this.g, this.b, this.a);
  }
}

Qolor.w3cColors = {
  aliceblue: '#f0f8ff',
  antiquewhite: '#faebd7',
  aqua: '#00ffff',
  aquamarine: '#7fffd4',
  azure: '#f0ffff',
  beige: '#f5f5dc',
  bisque: '#ffe4c4',
  black: '#000000',
  blanchedalmond: '#ffebcd',
  blue: '#0000ff',
  blueviolet: '#8a2be2',
  brown: '#a52a2a',
  burlywood: '#deb887',
  cadetblue: '#5f9ea0',
  chartreuse: '#7fff00',
  chocolate: '#d2691e',
  coral: '#ff7f50',
  cornflowerblue: '#6495ed',
  cornsilk: '#fff8dc',
  crimson: '#dc143c',
  cyan: '#00ffff',
  darkblue: '#00008b',
  darkcyan: '#008b8b',
  darkgoldenrod: '#b8860b',
  darkgray: '#a9a9a9',
  darkgrey: '#a9a9a9',
  darkgreen: '#006400',
  darkkhaki: '#bdb76b',
  darkmagenta: '#8b008b',
  darkolivegreen: '#556b2f',
  darkorange: '#ff8c00',
  darkorchid: '#9932cc',
  darkred: '#8b0000',
  darksalmon: '#e9967a',
  darkseagreen: '#8fbc8f',
  darkslateblue: '#483d8b',
  darkslategray: '#2f4f4f',
  darkslategrey: '#2f4f4f',
  darkturquoise: '#00ced1',
  darkviolet: '#9400d3',
  deeppink: '#ff1493',
  deepskyblue: '#00bfff',
  dimgray: '#696969',
  dimgrey: '#696969',
  dodgerblue: '#1e90ff',
  firebrick: '#b22222',
  floralwhite: '#fffaf0',
  forestgreen: '#228b22',
  fuchsia: '#ff00ff',
  gainsboro: '#dcdcdc',
  ghostwhite: '#f8f8ff',
  gold: '#ffd700',
  goldenrod: '#daa520',
  gray: '#808080',
  grey: '#808080',
  green: '#008000',
  greenyellow: '#adff2f',
  honeydew: '#f0fff0',
  hotpink: '#ff69b4',
  indianred: '#cd5c5c',
  indigo: '#4b0082',
  ivory: '#fffff0',
  khaki: '#f0e68c',
  lavender: '#e6e6fa',
  lavenderblush: '#fff0f5',
  lawngreen: '#7cfc00',
  lemonchiffon: '#fffacd',
  lightblue: '#add8e6',
  lightcoral: '#f08080',
  lightcyan: '#e0ffff',
  lightgoldenrodyellow: '#fafad2',
  lightgray: '#d3d3d3',
  lightgrey: '#d3d3d3',
  lightgreen: '#90ee90',
  lightpink: '#ffb6c1',
  lightsalmon: '#ffa07a',
  lightseagreen: '#20b2aa',
  lightskyblue: '#87cefa',
  lightslategray: '#778899',
  lightslategrey: '#778899',
  lightsteelblue: '#b0c4de',
  lightyellow: '#ffffe0',
  lime: '#00ff00',
  limegreen: '#32cd32',
  linen: '#faf0e6',
  magenta: '#ff00ff',
  maroon: '#800000',
  mediumaquamarine: '#66cdaa',
  mediumblue: '#0000cd',
  mediumorchid: '#ba55d3',
  mediumpurple: '#9370db',
  mediumseagreen: '#3cb371',
  mediumslateblue: '#7b68ee',
  mediumspringgreen: '#00fa9a',
  mediumturquoise: '#48d1cc',
  mediumvioletred: '#c71585',
  midnightblue: '#191970',
  mintcream: '#f5fffa',
  mistyrose: '#ffe4e1',
  moccasin: '#ffe4b5',
  navajowhite: '#ffdead',
  navy: '#000080',
  oldlace: '#fdf5e6',
  olive: '#808000',
  olivedrab: '#6b8e23',
  orange: '#ffa500',
  orangered: '#ff4500',
  orchid: '#da70d6',
  palegoldenrod: '#eee8aa',
  palegreen: '#98fb98',
  paleturquoise: '#afeeee',
  palevioletred: '#db7093',
  papayawhip: '#ffefd5',
  peachpuff: '#ffdab9',
  peru: '#cd853f',
  pink: '#ffc0cb',
  plum: '#dda0dd',
  powderblue: '#b0e0e6',
  purple: '#800080',
  rebeccapurple: '#663399',
  red: '#ff0000',
  rosybrown: '#bc8f8f',
  royalblue: '#4169e1',
  saddlebrown: '#8b4513',
  salmon: '#fa8072',
  sandybrown: '#f4a460',
  seagreen: '#2e8b57',
  seashell: '#fff5ee',
  sienna: '#a0522d',
  silver: '#c0c0c0',
  skyblue: '#87ceeb',
  slateblue: '#6a5acd',
  slategray: '#708090',
  slategrey: '#708090',
  snow: '#fffafa',
  springgreen: '#00ff7f',
  steelblue: '#4682b4',
  tan: '#d2b48c',
  teal: '#008080',
  thistle: '#d8bfd8',
  tomato: '#ff6347',
  turquoise: '#40e0d0',
  violet: '#ee82ee',
  wheat: '#f5deb3',
  white: '#ffffff',
  whitesmoke: '#f5f5f5',
  yellow: '#ffff00',
  yellowgreen: '#9acd32'
};

if (typeof module !== 'undefined') {
  module.exports = Qolor;
}

// calculations are based on http://aa.quae.nl/en/reken/zonpositie.html
// code credits to Vladimir Agafonkin (@mourner)

function getSunPosition () {

  const m = Math,
    PI = m.PI,
    sin = m.sin,
    cos = m.cos,
    tan = m.tan,
    asin = m.asin,
    atan = m.atan2;

  const 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) {
    const P = rad*102.9372; // perihelion of the Earth
    return M+C+P+PI;
  }

  return function getSunPosition(date, lat, lon) {
    const 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
    };
  };
}


const METERS_PER_LEVEL = 3;

const 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'
};

const 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;
}

const WINDING_CLOCKWISE = 'CW';
const WINDING_COUNTER_CLOCKWISE = 'CCW';

// detect winding direction: clockwise or counter clockwise
function getWinding (points) {
  let x1, y1, x2, y2,
    a = 0;
  for (let 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) {
  let winding = getWinding(points);
  if (winding === direction) {
    return points;
  }
  let revPoints = [];
  for (let i = points.length-2; i >= 0; i -= 2) {
    revPoints.push(points[i], points[i+1]);
  }
  return revPoints;
}

function alignProperties(prop) {
  const 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);

  const wallColor = prop.material ? getMaterialColor(prop.material) : (prop.wallColor || prop.color);
  if (wallColor) {
    item.wallColor = wallColor;
  }

  const 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) {
  let
    polygon,
    geometries = [], sub;

  switch (geometry.type) {
    case 'GeometryCollection':
      geometries = [];
      for (let 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 (let 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 [];
  }

  let
    p, lat = 1, lon = 0,
    outer = [], inner = [];

  p = polygon[0];
  for (let i = 0, il = p.length; i < il; i++) {
    outer.push(p[i][lat], p[i][lon]);
  }
  outer = makeWinding(outer, WINDING_CLOCKWISE);

  for (let i = 0, il = polygon.length-1; i < il; i++) {
    p = polygon[i+1];
    inner[i] = [];
    for (let 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) {
  let res = {};
  for (const p in obj) {
    if (obj.hasOwnProperty(p)) {
      res[p] = obj[p];
    }
  }
  return res;
}

class GeoJSON {

  static read (geojson) {
    if (!geojson || geojson.type !== 'FeatureCollection') {
      return [];
    }

    const collection = geojson.features;
    const res = [];

    for (let i = 0, il = collection.length; i < il; i++) {
      const feature = collection[i];

      if (feature.type !== 'Feature' || onEach(feature) === false) {
        continue;
      }

      const baseItem = alignProperties(feature.properties);
      const geometries = getGeometries(feature.geometry);

      for (let j = 0, jl = geometries.length; j < jl; j++) {
        const 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;
  }
}

let
  VERSION      = '0.3.2',
  ATTRIBUTION  = '&copy; <a href="https://osmbuildings.org">OSM Buildings</a>',

  DATA_SRC = 'https://{s}.data.osmbuildings.org/0.2/{k}/tile/{z}/{x}/{y}.json',

  PI         = Math.PI,
  HALF_PI    = PI/2,
  QUARTER_PI = PI/4,

  MAP_TILE_SIZE  = 256,    // map tile size in pixels
  ZOOM, MAP_SIZE,

  MIN_ZOOM = 15,

  LAT = 'latitude', LON = 'longitude',

  WIDTH = 0, HEIGHT = 0,
  CENTER_X = 0, CENTER_Y = 0,
  ORIGIN_X = 0, ORIGIN_Y = 0,

  WALL_COLOR = Qolor.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,

  MAX_HEIGHT, // taller buildings will be cut to this
  DEFAULT_HEIGHT = 5,

  CAM_X, CAM_Y, CAM_Z = 450,

  IS_ZOOMING;

function onEach () {}

function onClick () {}


function getDistance (p1, p2) {
  const
    dx = p1.x-p2.x,
    dy = p1.y-p2.y;
  return dx*dx + dy*dy;
}

function isRotational (polygon) {
  const length = polygon.length;
  if (length < 16) {
    return false;
  }

  let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
  for (let 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]);
  }

  const
    width = maxX-minX,
    height = (maxY-minY),
    ratio = width/height;

  if (ratio < 0.85 || ratio > 1.15) {
    return false;
  }

  const
    center = { x:minX+width/2, y:minY+height/2 },
    radius = (width+height)/4,
    sqRadius = radius*radius;

  for (let i = 0; i < length-1; i+=2) {
    const 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) {
  let
    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) {
  let
    sqTolerance = 2,
    len = buffer.length/2,
    markers = new Uint8Array(len),

    first = 0, last = len-1,

    maxSqDist,
    sqDist,
    index,
    firstStack = [], lastStack  = [],
    newBuffer  = [];

  markers[first] = markers[last] = 1;

  while (last) {
    maxSqDist = 0;
    for (let 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 (let i = 0; i < len; i++) {
    if (markers[i]) {
      newBuffer.push(buffer[i*2], buffer[i*2 + 1]);
    }
  }

  return newBuffer;
}

function getCenter (footprint) {
  let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
  for (let 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 };
}

let EARTH_RADIUS = 6378137;

function getLonDelta (footprint) {
  let minLon = 180, maxLon = -180;
  for (let 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;
}


function rad (deg) {
  return deg * PI / 180;
}

function deg (rad) {
  return rad / PI * 180;
}

function pixelToGeo (x, y) {
  const 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) {
  const
    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 fromRange (sVal, sMin, sMax, dMin, dMax) {
  sVal = min(max(sVal, sMin), sMax);
  const rel = (sVal-sMin) / (sMax-sMin),
    range = dMax-dMin;
  return min(max(dMin + rel*range, dMin), dMax);
}

function isVisible (polygon) {
  const
    maxX = WIDTH+ORIGIN_X,
    maxY = HEIGHT+ORIGIN_Y;

  // TODO: checking footprint is sufficient for visibility - NOT VALID FOR SHADOWS!
  for (let 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;
}


let cacheData = {};
let cacheIndex = [];
let cacheSize = 0;
let maxCacheSize = 1024*1024 * 5; // 5MB

function xhr (url, callback) {
  if (cacheData[url]) {
    if (callback) {
      callback(cacheData[url]);
    }
    return;
  }

  const 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) {
      const responseText = req.responseText;

      cacheData[url] = responseText;
      cacheIndex.push({ url: url, size: responseText.length });
      cacheSize += responseText.length;

      callback(responseText);

      while (cacheSize > maxCacheSize) {
        let item = cacheIndex.shift();
        cacheSize -= item.size;
        delete cacheData[item.url];
      }
    }
  };

  req.open('GET', url);
  req.send(null);

  return req;
}

class Request {

  static loadJSON (url, callback) {
    return xhr(url, responseText => {
      let json;
      try {
        json = JSON.parse(responseText);
      } catch(ex) {}

      callback(json);
    });
  }
}


class Data {

  static getPixelFootprint (buffer) {
    let footprint = new Int32Array(buffer.length),
      px;

    for (let 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;
  }

  static resetItems () {
    this.items = [];
    this.cache = {};
    Picking.reset();
  }

  static addRenderItems (data, allAreNew) {
    let item, scaledItem, id;
    let geojson = GeoJSON.read(data);
    for (let 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.cache[id]) {
        if ((scaledItem = this.scaleItem(item))) {
          scaledItem.scale = allAreNew ? 0 : 1;
          this.items.push(scaledItem);
          this.cache[id] = 1;
        }
      }
    }
    fadeIn();
  }

  static scalePolygon (buffer, factor) {
    return buffer.map(coord => coord*factor);
  }

  static scale (factor) {
    Data.items = Data.items.map(item => {
      // item.height = Math.min(item.height*factor, MAX_HEIGHT); // TODO: should be filtered by renderer

      item.height *= factor;
      item.minHeight *= factor;

      item.footprint = Data.scalePolygon(item.footprint, factor);
      item.center.x *= factor;
      item.center.y *= factor;

      if (item.radius) {
        item.radius *= factor;
      }

      if (item.holes) {
        for (let i = 0, il = item.holes.length; i < il; i++) {
          item.holes[i] = Data.scalePolygon(item.holes[i], factor);
        }
      }

      item.roofHeight *= factor;

      return item;
    });
  }

  static scaleItem (item) {
    let
      res = {},
      // TODO: calculate this on zoom change only
      zoomScale = 6 / pow(2, ZOOM-MIN_ZOOM); // TODO: consider using HEIGHT / (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 = [];
      let innerFootprint;
      for (let i = 0, il = item.holes.length; i < il; i++) {
        // TODO: simplify
        if ((innerFootprint = this.getPixelFootprint(item.holes[i]))) {
          res.holes.push(innerFootprint);
        }
      }
    }

    let color;

    if (item.wallColor) {
      if ((color = Qolor.parse(item.wallColor))) {
        res.altColor  = ''+ color.lightness(0.8);
        res.wallColor = ''+ color;
      }
    }

    if (item.roofColor) {
      if ((color = Qolor.parse(item.roofColor))) {
        res.roofColor = ''+ color;
      }
    }

    if (item.relationId) {
      res.relationId = item.relationId;
    }
    res.hitColor = Picking.idToColor(item.relationId || item.id);

    res.roofHeight = isNaN(item.roofHeight) ? 0 : item.roofHeight/zoomScale;

    if (res.height+res.roofHeight <= res.minHeight) {
      return;
    }

    return res;
  }

  static set (data) {
    this.resetItems();
    this._staticData = data;
    this.addRenderItems(this._staticData, true);
  }

  static load (src, key) {
    this.src = src || DATA_SRC.replace('{k}', (key || 'anonymous'));
    this.update();
  }

  static update () {
    this.resetItems();

    if (ZOOM < MIN_ZOOM) {
      return;
    }

    if (this._staticData) {
      this.addRenderItems(this._staticData);
    }

    if (this.src) {
      let
        tileZoom = 16,
        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;

      let 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);
        }
      }
    }
  }

  static loadTile (x, y, zoom, callback) {
    let s = 'abcd'[(x+y) % 4];
    let url = this.src.replace('{s}', s).replace('{x}', x).replace('{y}', y).replace('{z}', zoom);
    return Request.loadJSON(url, callback);
  }
}

Data.cache = {}; // maintain a list of cached items in order to avoid duplicates on tile borders
Data.items = [];

class Extrusion {

  static draw (context, polygon, innerPolygons, height, minHeight, color, altColor, roofColor) {
    let
      roof = this._extrude(context, polygon, height, minHeight, color, altColor),
      innerRoofs = [];

    if (innerPolygons) {
      for (let 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 (let i = 0, il = innerRoofs.length; i < il; i++) {
        this._ring(context, innerRoofs[i]);
      }
    }
    context.closePath();
    context.fill();
  }

  static _extrude (context, polygon, height, minHeight, color, altColor) {
    let
      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 (let 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;
  }

  static _ring (context, polygon) {
    context.moveTo(polygon[0], polygon[1]);
    for (let i = 2, il = polygon.length-1; i < il; i += 2) {
      context.lineTo(polygon[i], polygon[i+1]);
    }
  }

  static simplified (context, polygon, innerPolygons) {
    context.beginPath();
    this._ringAbs(context, polygon);
    if (innerPolygons) {
      for (let i = 0, il = innerPolygons.length; i < il; i++) {
        this._ringAbs(context, innerPolygons[i]);
      }
    }
    context.closePath();
    context.fill();
  }

  static _ringAbs (context, polygon) {
    context.moveTo(polygon[0]-ORIGIN_X, polygon[1]-ORIGIN_Y);
    for (let i = 2, il = polygon.length-1; i < il; i += 2) {
      context.lineTo(polygon[i]-ORIGIN_X, polygon[i+1]-ORIGIN_Y);
    }
  }

  static shadow (context, polygon, innerPolygons, height, minHeight) {
    let
      mode = null,
      a = { x:0, y:0 },
      b = { x:0, y:0 },
      _a, _b;

    for (let 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 (let i = 0, il = innerPolygons.length; i < il; i++) {
        this._ringAbs(context, innerPolygons[i]);
      }
    }
  }

  static hitArea (context, polygon, innerPolygons, height, minHeight, color) {
    let
      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 (let 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();
  }
}

class Cylinder {

  static draw (context, center, radius, topRadius, height, minHeight, color, altColor, roofColor) {
    let
      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
    let 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);
  }

  static simplified (context, center, radius) {
    this._circle(context, { x:center.x-ORIGIN_X, y:center.y-ORIGIN_Y }, radius);
  }

  static shadow (context, center, radius, topRadius, height, minHeight) {
    let
      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
    let 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);
    }
  }

  static hitArea (context, center, radius, topRadius, height, minHeight, color) {
    let
      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
    let 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();
  }

  static _circle (context, center, radius) {
    context.beginPath();
    context.arc(center.x, center.y, radius, 0, PI*2);
    context.fill();
  }

    // http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Tangents_between_two_circles
  static _tangents (c1, r1, c2, r2) {
    let
      dx = c1.x-c2.x,
      dy = c1.y-c2.y,
      dr = r1-r2,
      sqdist = (dx*dx) + (dy*dy);

    if (sqdist <= dr*dr) {
      return;
    }

    let 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 (let 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;
  }
}

class Pyramid {

  static draw (context, polygon, center, height, minHeight, color, altColor) {
    let
      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 (let 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();
      }
    }
  }

  static _triangle (context, a, b, c) {
    context.moveTo(a.x, a.y);
    context.lineTo(b.x, b.y);
    context.lineTo(c.x, c.y);
  }

  static _ring (context, polygon) {
    context.moveTo(polygon[0]-ORIGIN_X, polygon[1]-ORIGIN_Y);
    for (let i = 2, il = polygon.length-1; i < il; i += 2) {
      context.lineTo(polygon[i]-ORIGIN_X, polygon[i+1]-ORIGIN_Y);
    }
  }

  static shadow (context, polygon, center, height, minHeight) {
    let
      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 (let 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);
      }
    }
  }

  static hitArea (context, polygon, center, height, minHeight, color) {
    let
      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 (let 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();
  }
}

let animTimer;

function fadeIn() {
  if (animTimer) {
    return;
  }

  animTimer = setInterval(t => {
    let dataItems = Data.items,
      isNeeded = false;

    for (let 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);
}

class Layers {

  static init () {
    Layers.container.className = 'osmb-container';

    // TODO: improve this
    Shadows.init(Layers.createContext(Layers.container));
    Simplified.init(Layers.createContext(Layers.container));
    Buildings.init(Layers.createContext(Layers.container));
    Picking.init(Layers.createContext());
  }

  static clear () {
    Shadows.clear();
    Simplified.clear();
    Buildings.clear();
    Picking.clear();
  }

  static setOpacity (opacity) {
    Shadows.setOpacity(opacity);
    Simplified.setOpacity(opacity);
    Buildings.setOpacity(opacity);
    Picking.setOpacity(opacity);
  }

  static render (quick) {
    // show on high zoom levels only
    if (ZOOM < MIN_ZOOM) {
      Layers.clear();
      return;
    }

    // don't render during zoom
    if (IS_ZOOMING) {
      return;
    }

    requestAnimationFrame(f => {
      if (!quick) {
        Shadows.render();
        Simplified.render();
        //HitAreas.render(); // TODO: do this on demand
      }
      Buildings.render();
    });
  }

  static createContext (container) {
    let canvas = document.createElement('CANVAS');
    canvas.className = 'osmb-layer';

    let context = canvas.getContext('2d');
    context.lineCap   = 'round';
    context.lineJoin  = 'round';
    context.lineWidth = 1;
    context.imageSmoothingEnabled = false;

    Layers.items.push(canvas);
    if (container) {
      container.appendChild(canvas);
    }

    return context;
  }

  static appendTo (parentNode) {
    parentNode.appendChild(Layers.container);
  }

  static remove () {
    Layers.container.parentNode.removeChild(Layers.container);
  }

  static setSize (width, height) {
    Layers.items.forEach(canvas => {
      canvas.width  = width;
      canvas.height = height;
    });
  }

  // usually called after move: container jumps by move delta, cam is reset
  static setPosition (x, y) {
    Layers.container.style.left = x +'px';
    Layers.container.style.top  = y +'px';
  }
}

Layers.container = document.createElement('DIV');
Layers.items = [];

class Buildings {

  static init (context) {
    this.context = context;
  }

  static clear () {
    this.context.clearRect(0, 0, WIDTH, HEIGHT);
  }

  static setOpacity (opacity) {
    this.context.canvas.style.opacity = opacity;
  }

  static project (p, m) {
    return {
      x: (p.x-CAM_X) * m + CAM_X <<0,
      y: (p.y-CAM_Y) * m + CAM_Y <<0
    };
  }

  static render () {
    this.clear();
    
    let
      context = this.context,
      item,
      h, mh,
      sortCam = { x:CAM_X+ORIGIN_X, y:CAM_Y+ORIGIN_Y },
      footprint,
      wallColor, altColor, roofColor,
      dataItems = Data.items;

    dataItems.sort((a, b) => {
      return (a.minHeight-b.minHeight) || getDistance(b.center, sortCam) - getDistance(a.center, sortCam) || (b.height-a.height);
    });

    for (let 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:         Extrusion.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, ''+ Qolor.parse(roofColor).lightness(0.9));             break;
        case 'dome':    Cylinder.draw(context, item.center, item.radius, item.radius/2, h+item.roofHeight, h, roofColor, ''+ Qolor.parse(roofColor).lightness(0.9)); break;
        case 'pyramid': Pyramid.draw(context, footprint, item.center, h+item.roofHeight, h, roofColor, Qolor.parse(roofColor).lightness(0.9));                       break;
      }
    }
  }
}

class Simplified {

  static init (context) {
    this.context = context;
  }

  static clear () {
    this.context.clearRect(0, 0, WIDTH, HEIGHT);
  }

  static setOpacity (opacity) {
    this.context.canvas.style.opacity = opacity;
  }

  static isSimple (item) {
    return (ZOOM <= Simplified.MAX_ZOOM && item.height+item.roofHeight < Simplified.MAX_HEIGHT);
  }

  static render () {
    this.clear();
    
    let context = this.context;

    // show on high zoom levels only and avoid rendering during zoom
    if (ZOOM > Simplified.MAX_ZOOM) {
      return;
    }

    let
      item,
      footprint,
      dataItems = Data.items;

    for (let i = 0, il = dataItems.length; i < il; i++) {
      item = dataItems[i];

      if (item.height >= Simplified.MAX_HEIGHT) {
        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: Extrusion.simplified(context, footprint, item.holes);
      }
    }
  }
}

Simplified.MAX_ZOOM = 16; // max zoom where buildings could render simplified
Simplified.MAX_HEIGHT = 5; // max building height in order to be simple

class Shadows {

  static init (context) {
    this.context = context;
  }

  static clear () {
    this.context.clearRect(0, 0, WIDTH, HEIGHT);
  }

  static setOpacity (opacity) {
    this.opacity = opacity;
  }

  static project (p, h) {
    return {
      x: p.x + this.direction.x*h,
      y: p.y + this.direction.y*h
    };
  }

  static render () {
    this.clear();
    
    let
      context = this.context,
      screenCenter,
      sun, length, alpha;

    // 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;

    let
      i, il,
      item,
      h, mh,
      footprint,
      dataItems = Data.items;

    context.canvas.style.opacity = alpha / (this.opacity * 2);
    context.shadowColor = this.blurColor;
    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:         Extrusion.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();
  }
}

Shadows.color = '#666666';
Shadows.blurColor = '#000000';
Shadows.date = new Date();
Shadows.direction = { x:0, y:0 };
Shadows.opacity = 1;



class Picking {

  static init (context) {
    this.context = context;
  }

  static setOpacity (opacity) {}

  static clear () {}

  static reset () {
    this._idMapping = [null];
  }

  static render () {
    if (this._timer) {
      return;
    }
    let self = this;
    this._timer = setTimeout(t => {
      self._timer = null;
      self._render();
    }, 500);
  }

  static _render () {
    this.clear();
    
    let
      context = this.context,
      item,
      h, mh,
      sortCam = { x:CAM_X+ORIGIN_X, y:CAM_Y+ORIGIN_Y },
      footprint,
      color,
      dataItems = Data.items;

    dataItems.sort((a, b) => {
      return (a.minHeight-b.minHeight) || getDistance(b.center, sortCam) - getDistance(a.center, sortCam) || (b.height-a.height);
    });

    for (let 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:         Extrusion.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;
      }
    }

    // otherwise fails on size 0
    if (WIDTH && HEIGHT) {
      this._imageData = this.context.getImageData(0, 0, WIDTH, HEIGHT).data;
    }
  }

  static getIdFromXY (x, y) {
    let imageData = this._imageData;
    if (!imageData) {
      return;
    }
    let pos = 4*((y|0) * WIDTH + (x|0));
    let index = imageData[pos] | (imageData[pos+1]<<8) | (imageData[pos+2]<<16);
    return this._idMapping[index];
  }

  static idToColor (id) {
    let index = this._idMapping.indexOf(id);
    if (index === -1) {
      this._idMapping.push(id);
      index = this._idMapping.length-1;
    }
    let r =  index       & 0xff;
    let g = (index >>8)  & 0xff;
    let b = (index >>16) & 0xff;
    return 'rgb('+ [r, g, b].join(',') +')';
  }
}

Picking._idMapping = [null];


class Debug {

  static point (x, y, color, size) {
    const context = this.context;
    context.fillStyle = color || '#ffcc00';
    context.beginPath();
    context.arc(x, y, size || 3, 0, 2*PI);
    context.closePath();
    context.fill();
  }

  static line (ax, ay, bx, by, color) {
    const context = this.context;
    context.strokeStyle = color || '#ffcc00';
    context.beginPath();
    context.moveTo(ax, ay);
    context.lineTo(bx, by);
    context.closePath();
    context.stroke();
  }
}


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;
  MAP_SIZE = MAP_TILE_SIZE <<ZOOM;

  const center = pixelToGeo(ORIGIN_X+CENTER_X, ORIGIN_Y+CENTER_Y);
  const a = geoToPixel(center.latitude, 0);
  const b = geoToPixel(center.latitude, 1);
  PIXEL_PER_DEG = b.x-a.x;

  Layers.setOpacity(Math.pow(0.95, ZOOM-MIN_ZOOM));

  WALL_COLOR_STR = ''+ WALL_COLOR;
  ALT_COLOR_STR  = ''+ ALT_COLOR;
  ROOF_COLOR_STR = ''+ ROOF_COLOR;
}

function onResize (e) {
  setSize(e);
  Layers.render();
  Data.update();
}

function onMoveEnd (e) {
  Layers.render();
  Data.update(); // => fadeIn() => Layers.render()
}

function onZoomStart () {
  IS_ZOOMING = true;
}

function onZoomEnd (e) {
  IS_ZOOMING = false;
  const factor = Math.pow(2, e.zoom-ZOOM);

  setZoom(e.zoom);
  // Layers.render(); // TODO: requestAnimationFrame() causes flickering because layers are already cleared

  // show on high zoom levels only
  if (ZOOM <= MIN_ZOOM) {
    Layers.clear();
    return;
  }

  Data.scale(factor);

  Shadows.render();
  Simplified.render();
  Buildings.render();

  Data.update(); // => fadeIn()
}


class OSMBuildings extends L.Layer {

  constructor (map) {
    super(map);

    this.offset = {x: 0, y: 0};
    Layers.init();
    if (map) {
      map.addLayer(this);
    }
  }

  addTo (map) {
    map.addLayer(this);
    return this;
  }

  onAdd (map) {
    this.map = map;
    Layers.appendTo(map._panes.overlayPane);

    let
      off = this.getOffset(),
      po = map.getPixelOrigin();
    setSize({width: map._size.x, height: map._size.y});
    setOrigin({x: po.x - off.x, y: po.y - off.y});
    setZoom(map._zoom);

    Layers.setPosition(-off.x, -off.y);

    map.on({
      move: this.onMove,
      moveend: this.onMoveEnd,
      zoomstart: this.onZoomStart,
      zoomend: this.onZoomEnd,
      resize: this.onResize,
      viewreset: this.onViewReset,
      click: this.onClick
    }, this);

    if (map.options.zoomAnimation) {
      map.on('zoomanim', this.onZoom, this);
    }

    if (map.attributionControl) {
      map.attributionControl.addAttribution(ATTRIBUTION);
    }

    Data.update();
  }

  onRemove () {
    let map = this.map;
    if (map.attributionControl) {
      map.attributionControl.removeAttribution(ATTRIBUTION);
    }

    map.off({
      move: this.onMove,
      moveend: this.onMoveEnd,
      zoomstart: this.onZoomStart,
      zoomend: this.onZoomEnd,
      resize: this.onResize,
      viewreset: this.onViewReset,
      click: this.onClick
    }, this);

    if (map.options.zoomAnimation) {
      map.off('zoomanim', this.onZoom, this);
    }
    Layers.remove();
    map = null;
  }

  onMove (e) {
    let off = this.getOffset();
    moveCam({x: this.offset.x - off.x, y: this.offset.y - off.y});
  }

  onMoveEnd (e) {
    if (this.noMoveEnd) { // moveend is also fired after zoom
      this.noMoveEnd = false;
      return;
    }

    let
      map = this.map,
      off = this.getOffset(),
      po = map.getPixelOrigin();

    this.offset = off;
    Layers.setPosition(-off.x, -off.y);
    moveCam({x: 0, y: 0});

    setSize({width: map._size.x, height: map._size.y}); // in case this is triggered by resize
    setOrigin({x: po.x - off.x, y: po.y - off.y});
    onMoveEnd(e);
  }

  onZoomStart (e) {
    onZoomStart(e);
  }

  onZoom (e) {
    let center = this.map.latLngToContainerPoint(e.center);
    let scale = Math.pow(2, e.zoom - ZOOM);

    let dx = WIDTH / 2 - center.x;
    let dy = HEIGHT / 2 - center.y;

    let x = WIDTH / 2;
    let y = HEIGHT / 2;

    if (e.zoom > ZOOM) {
      x -= dx * scale;
      y -= dy * scale;
    } else {
      x += dx;
      y += dy;
    }

    Layers.container.classList.add('zoom-animation');
    Layers.container.style.transformOrigin = x + 'px ' + y + 'px';
    Layers.container.style.transform = 'translate3d(0, 0, 0) scale(' + scale + ')';
  }

  onZoomEnd (e) {
    Layers.clear();
    Layers.container.classList.remove('zoom-animation');
    Layers.container.style.transform = 'translate3d(0, 0, 0) scale(1)';

    let
      map = this.map,
      off = this.getOffset(),
      po = map.getPixelOrigin();

    setOrigin({x: po.x - off.x, y: po.y - off.y});
    onZoomEnd({zoom: map._zoom});
    this.noMoveEnd = true;
  }

  onResize () {
  }

  onViewReset () {
    let off = this.getOffset();

    this.offset = off;
    Layers.setPosition(-off.x, -off.y);
    moveCam({x: 0, y: 0});
  }

  onClick (e) {
    let id = Picking.getIdFromXY(e.containerPoint.x, e.containerPoint.y);
    if (id) {
      onClick({feature: id, lat: e.latlng.lat, lon: e.latlng.lng});
    }
  }

  getOffset () {
    return L.DomUtil.getPosition(this.map._mapPane);
  }

  //*** COMMON PUBLIC METHODS ***

  style (style) {
    style = style || {};
    let color;
    if ((color = style.color || style.wallColor)) {
      WALL_COLOR = Qolor.parse(color);
      WALL_COLOR_STR = '' + WALL_COLOR;

      ALT_COLOR = WALL_COLOR.lightness(0.8);
      ALT_COLOR_STR = '' + ALT_COLOR;

      ROOF_COLOR = WALL_COLOR.lightness(1.2);
      ROOF_COLOR_STR = '' + ROOF_COLOR;
    }

    if (style.roofColor) {
      ROOF_COLOR = Qolor.parse(style.roofColor);
      ROOF_COLOR_STR = '' + ROOF_COLOR;
    }

    Layers.render();

    return this;
  }

  date (date) {
    Shadows.date = date;
    Shadows.render();
    return this;
  }

  load (url) {
    Data.load(url);
    return this;
  }

  set (data) {
    Data.set(data);
    return this;
  }

  each (handler) {
    onEach = function (payload) {
      return handler(payload);
    };
    return this;
  }

  click (handler) {
    onClick = function (payload) {
      return handler(payload);
    };
    return this;
  }
}

OSMBuildings.VERSION = VERSION;
OSMBuildings.ATTRIBUTION = ATTRIBUTION;

 return OSMBuildings;
}());

================================================
FILE: dist/OSMBuildings-Leaflet.js
================================================
const OSMBuildings=function(){const e=Math,t=e.exp,i=e.log,r=e.sin,a=e.cos,o=e.tan,s=e.atan,n=e.atan2,l=e.min,c=e.max,h=e.sqrt,f=e.ceil,d=e.pow;class u{constructor(e,t,i,r=1){this.r=this._clamp(e,1),this.g=this._clamp(t,1),this.b=this._clamp(i,1),this.a=this._clamp(r,1)}static parse(e){if("string"==typeof e){let t;if(e=e.toLowerCase(),t=(e=u.w3cColors[e]||e).match(/^#?(\w{2})(\w{2})(\w{2})$/))return new u(parseInt(t[1],16)/255,parseInt(t[2],16)/255,parseInt(t[3],16)/255);if(t=e.match(/^#?(\w)(\w)(\w)$/))return new u(parseInt(t[1]+t[1],16)/255,parseInt(t[2]+t[2],16)/255,parseInt(t[3]+t[3],16)/255);if(t=e.match(/rgba?\((\d+)\D+(\d+)\D+(\d+)(\D+([\d.]+))?\)/))return new u(parseFloat(t[1])/255,parseFloat(t[2])/255,parseFloat(t[3])/255,t[4]?parseFloat(t[5]):1)}return new u}static fromHSL(e,t,i,r){const a=(new u).fromHSL(e,t,i);return a.a=void 0===r?1:r,a}_hue2rgb(e,t,i){return i<0&&(i+=1),i>1&&(i-=1),i<1/6?e+6*(t-e)*i:i<.5?t:i<2/3?e+(t-e)*(2/3-i)*6:e}_clamp(e,t){if(void 0!==e)return Math.min(t,Math.max(0,e||0))}isValid(){return void 0!==this.r&&void 0!==this.g&&void 0!==this.b}toHSL(){if(!this.isValid())return;const e=Math.max(this.r,this.g,this.b),t=Math.min(this.r,this.g,this.b),i=e-t,r=(e+t)/2;if(!i)return{h:0,s:0,l:r};const a=r>.5?i/(2-e-t):i/(e+t);let o;switch(e){case this.r:o=(this.g-this.b)/i+(this.g<this.b?6:0);break;case this.g:o=(this.b-this.r)/i+2;break;case this.b:o=(this.r-this.g)/i+4}return o*=60,{h:o,s:a,l:r}}fromHSL(e,t,i){if(0===t)return this.r=this.g=this.b=i,this;const r=i<.5?i*(1+t):i+t-i*t,a=2*i-r;return e/=360,this.r=this._hue2rgb(a,r,e+1/3),this.g=this._hue2rgb(a,r,e),this.b=this._hue2rgb(a,r,e-1/3),this}toString(){if(this.isValid())return 1===this.a?"#"+((1<<24)+(Math.round(255*this.r)<<16)+(Math.round(255*this.g)<<8)+Math.round(255*this.b)).toString(16).slice(1,7):`rgba(${Math.round(255*this.r)},${Math.round(255*this.g)},${Math.round(255*this.b)},${this.a.toFixed(2)})`}toArray(){if(this.isValid)return[this.r,this.g,this.b]}hue(e){const t=this.toHSL();return this.fromHSL(t.h+e,t.s,t.l)}saturation(e){const t=this.toHSL();return this.fromHSL(t.h,t.s*e,t.l)}lightness(e){const t=this.toHSL();return this.fromHSL(t.h,t.s,t.l*e)}clone(){return new u(this.r,this.g,this.b,this.a)}}function g(){const e=Math,t=e.PI,i=e.sin,r=e.cos,a=e.tan,o=e.asin,s=e.atan2,n=t/180,l=23.4397*n;function c(e,t,o){return s(i(e),r(e)*i(t)-a(o)*r(t))}function h(e,t,a){return o(i(t)*i(a)+r(t)*r(a)*r(e))}return function(e,f,d){const u=n*-d,g=n*f,p=function(e){return function(e){return e.valueOf()/864e5-.5+2440588}(e)-2451545}(e),y=function(e){return n*(357.5291+.98560028*e)}(p),m=function(e,i){return e+i+102.9372*n+t}(y,function(e){return n*(1.9148*i(e)+.02*i(2*e)+3e-4*i(3*e))}(y)),x=(k=m,o(i(_=0)*r(l)+r(_)*i(l)*i(k))),b=function(e,t){return s(i(e)*r(l)-a(t)*i(l),r(e))}(m,0),w=function(e,t){return n*(280.16+360.9856235*e)-t}(p,u)-b;var k,_;return{altitude:h(w,g,x),azimuth:c(w,g,x)-t/2}}}u.w3cColors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},"undefined"!=typeof module&&(module.exports=u);const p={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"},y={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"};function m(e){return"#"===(e=e.toLowerCase())[0]?e:p[y[e]||e]||null}function x(e,t){if(function(e){let t,i,r,a,o=0;for(let s=0,n=e.length-3;s<n;s+=2)t=e[s],i=e[s+1],r=e[s+2],a=e[s+3],o+=t*a-r*i;return o/2>0?"CW":"CCW"}(e)===t)return e;let i=[];for(let t=e.length-2;t>=0;t-=2)i.push(e[t],e[t+1]);return i}function b(e){const t={};e=e||{},t.height=e.height||(e.levels?3*e.levels:G),t.minHeight=e.minHeight||(e.minLevel?3*e.minLevel:0);const i=e.material?m(e.material):e.wallColor||e.color;i&&(t.wallColor=i);const r=e.roofMaterial?m(e.roofMaterial):e.roofColor;switch(r&&(t.roofColor=r),e.shape){case"cylinder":case"cone":case"dome":case"sphere":t.shape=e.shape,t.isRotational=!0;break;case"pyramid":t.shape=e.shape}switch(e.roofShape){case"cone":case"dome":t.roofShape=e.roofShape,t.isRotational=!0;break;case"pyramid":t.roofShape=e.roofShape}return t.roofShape&&e.roofHeight?(t.roofHeight=e.roofHeight,t.height=c(0,t.height-t.roofHeight)):t.roofHeight=0,t}function w(e){let t,i,r=[];switch(e.type){case"GeometryCollection":r=[];for(let t=0,a=e.geometries.length;t<a;t++)(i=w(e.geometries[t]))&&r.push.apply(r,i);return r;case"MultiPolygon":r=[];for(let t=0,a=e.coordinates.length;t<a;t++)(i=w({type:"Polygon",coordinates:e.coordinates[t]}))&&r.push.apply(r,i);return r;case"Polygon":t=e.coordinates;break;default:return[]}let a,o=[],s=[];a=t[0];for(let e=0,t=a.length;e<t;e++)o.push(a[e][1],a[e][0]);o=x(o,"CW");for(let e=0,i=t.length-1;e<i;e++){a=t[e+1],s[e]=[];for(let t=0,i=a.length;t<i;t++)s[e].push(a[t][1],a[t][0]);s[e]=x(s[e],"CCW")}return[{outer:o,inner:s.length?s:null}]}function k(e){let t={};for(const i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t}let _,v,H,C,S,M,P='&copy; <a href="https://osmbuildings.org">OSM Buildings</a>',T=Math.PI,z=T/2,I=T/4,O=0,A=0,j=0,R=0,E=0,D=0,F=u.parse("rgba(200, 190, 180)"),N=F.lightness(.8),q=F.lightness(1.2),Z=""+F,V=""+N,X=""+q,$=0,G=5;function W(){}function J(){}function B(e,t){const i=e.x-t.x,r=e.y-t.y;return i*i+r*r}function U(e,t,i,r,a,o){let s,n=a-i,l=o-r;return 0===n&&0===l||(s=((e-i)*n+(t-r)*l)/(n*n+l*l),s>1?(i=a,r=o):s>0&&(i+=n*s,r+=l*s)),n=e-i,l=t-r,n*n+l*l}function Y(e){let t=180,i=-180;for(let r=0,a=e.length;r<a;r+=2)t=l(t,e[r+1]),i=c(i,e[r+1]);return(i-t)/2}function K(e,i){const r={};return e/=v,i/=v,r.latitude=i<=0?90:i>=1?-90:function(e){return e/T*180}(2*s(t(T*(1-2*i)))-z),r.longitude=360*(1===e?1:(e%1+1)%1)-180,r}function Q(e,t){const r=l(1,c(0,.5-i(o(I+z*e/180))/T/2));return{x:(t/360+.5)*v<<0,y:r*v<<0}}function ee(e){const t=O+E,i=A+D;for(let r=0,a=e.length-3;r<a;r+=2)if(e[r]>E&&e[r]<t&&e[r+1]>D&&e[r+1]<i)return!0;return!1}let te,ie={},re=[],ae=0;class oe{static getPixelFootprint(e){let t,i=new Int32Array(e.length);for(let r=0,a=e.length-1;r<a;r+=2)t=Q(e[r],e[r+1]),i[r]=t.x,i[r+1]=t.y;if(i=function(e){let t,i,r,a=e.length/2,o=new Uint8Array(a),s=0,n=a-1,l=[],c=[],h=[];for(o[s]=o[n]=1;n;){t=0;for(let a=s+1;a<n;a++)i=U(e[2*a],e[2*a+1],e[2*s],e[2*s+1],e[2*n],e[2*n+1]),i>t&&(r=a,t=i);t>2&&(o[r]=1,l.push(s),c.push(r),l.push(r),c.push(n)),s=l.pop(),n=c.pop()}for(let t=0;t<a;t++)o[t]&&h.push(e[2*t],e[2*t+1]);return h}(i),!(i.length<8))return i}static resetItems(){this.items=[],this.cache={},ue.reset()}static addRenderItems(e,t){let i,r,a,o=class{static read(e){if(!e||"FeatureCollection"!==e.type)return[];const t=e.features,i=[];for(let e=0,r=t.length;e<r;e++){const r=t[e];if("Feature"!==r.type||!1===W(r))continue;const a=b(r.properties),o=w(r.geometry);for(let e=0,t=o.length;e<t;e++){const t=k(a);t.footprint=o[e].outer,t.isRotational&&(t.radius=Y(t.footprint)),o[e].inner&&(t.holes=o[e].inner),(r.id||r.properties.id)&&(t.id=r.id||r.properties.id),r.properties.relationId&&(t.relationId=r.properties.relationId),i.push(t)}}return i}}.read(e);for(let e=0,s=o.length;e<s;e++)i=o[e],a=i.id||[i.footprint[0],i.footprint[1],i.height,i.minHeight].join(","),this.cache[a]||(r=this.scaleItem(i))&&(r.scale=t?0:1,this.items.push(r),this.cache[a]=1);!function(){if(te)return;te=setInterval(e=>{let t=oe.items,i=!1;for(let e=0,r=t.length;e<r;e++)t[e].scale<1&&(t[e].scale+=.1,t[e].scale>1&&(t[e].scale=1),i=!0);ce.render(),i||(clearInterval(te),te=null)},33)}()}static scalePolygon(e,t){return e.map(e=>e*t)}static scale(e){oe.items=oe.items.map(t=>{if(t.height*=e,t.minHeight*=e,t.footprint=oe.scalePolygon(t.footprint,e),t.center.x*=e,t.center.y*=e,t.radius&&(t.radius*=e),t.holes)for(let i=0,r=t.holes.length;i<r;i++)t.holes[i]=oe.scalePolygon(t.holes[i],e);return t.roofHeight*=e,t})}static scaleItem(e){let t,i={},r=6/d(2,_-15);if(e.id&&(i.id=e.id),i.height=l(e.height/r,H),i.minHeight=isNaN(e.minHeight)?0:e.minHeight/r,!(i.minHeight>H)&&(i.footprint=this.getPixelFootprint(e.footprint),i.footprint)){if(i.center=function(e){let t=1/0,i=-1/0,r=1/0,a=-1/0;for(let o=0,s=e.length-3;o<s;o+=2)t=l(t,e[o]),i=c(i,e[o]),r=l(r,e[o+1]),a=c(a,e[o+1]);return{x:t+(i-t)/2<<0,y:r+(a-r)/2<<0}}(i.footprint),e.radius&&(i.radius=e.radius*$),e.shape&&(i.shape=e.shape),e.roofShape&&(i.roofShape=e.roofShape),"cone"!==i.roofShape&&"dome"!==i.roofShape||i.shape||!function(e){const t=e.length;if(t<16)return!1;let i=1/0,r=-1/0,a=1/0,o=-1/0;for(let s=0;s<t-1;s+=2)i=Math.min(i,e[s]),r=Math.max(r,e[s]),a=Math.min(a,e[s+1]),o=Math.max(o,e[s+1]);const s=r-i,n=o-a,l=s/n;if(l<.85||l>1.15)return!1;const c={x:i+s/2,y:a+n/2},h=(s+n)/4,f=h*h;for(let i=0;i<t-1;i+=2){const t=B({x:e[i],y:e[i+1]},c);if(t/f<.8||t/f>1.2)return!1}return!0}(i.footprint)||(i.shape="cylinder"),e.holes){let t;i.holes=[];for(let r=0,a=e.holes.length;r<a;r++)(t=this.getPixelFootprint(e.holes[r]))&&i.holes.push(t)}return e.wallColor&&(t=u.parse(e.wallColor))&&(i.altColor=""+t.lightness(.8),i.wallColor=""+t),e.roofColor&&(t=u.parse(e.roofColor))&&(i.roofColor=""+t),e.relationId&&(i.relationId=e.relationId),i.hitColor=ue.idToColor(e.relationId||e.id),i.roofHeight=isNaN(e.roofHeight)?0:e.roofHeight/r,i.height+i.roofHeight<=i.minHeight?void 0:i}}static set(e){this.resetItems(),this._staticData=e,this.addRenderItems(this._staticData,!0)}static load(e,t){this.src=e||"https://{s}.data.osmbuildings.org/0.2/{k}/tile/{z}/{x}/{y}.json".replace("{k}",t||"anonymous"),this.update()}static update(){if(this.resetItems(),!(_<15)&&(this._staticData&&this.addRenderItems(this._staticData),this.src)){let t,i,r=16,a=256,o=_>r?a<<_-r:a>>r-_,s=E/o<<0,n=D/o<<0,l=f((E+O)/o),c=f((D+A)/o),h=this;function e(e){h.addRenderItems(e)}for(i=n;i<=c;i++)for(t=s;t<=l;t++)this.loadTile(t,i,r,e)}}static loadTile(e,t,i,r){let a="abcd"[(e+t)%4],o=this.src.replace("{s}",a).replace("{x}",e).replace("{y}",t).replace("{z}",i);return class{static loadJSON(e,t){return function(e,t){if(ie[e])return void(t&&t(ie[e]));const i=new XMLHttpRequest;return i.onreadystatechange=function(){if(4===i.readyState&&!(!i.status||i.status<200||i.status>299)&&t&&i.responseText){const r=i.responseText;for(ie[e]=r,re.push({url:e,size:r.length}),ae+=r.length,t(r);ae>5242880;){let e=re.shift();ae-=e.size,delete ie[e.url]}}},i.open("GET",e),i.send(null),i}(e,e=>{let i;try{i=JSON.parse(e)}catch(e){}t(i)})}}.loadJSON(o,r)}}oe.cache={},oe.items=[];class se{static draw(e,t,i,r,a,o,s,n){let l=this._extrude(e,t,r,a,o,s),c=[];if(i)for(let t=0,n=i.length;t<n;t++)c[t]=this._extrude(e,i[t],r,a,o,s);if(e.fillStyle=n,e.beginPath(),this._ring(e,l),i)for(let t=0,i=c.length;t<i;t++)this._ring(e,c[t]);e.closePath(),e.fill()}static _extrude(e,t,i,r,a,o){let s,n,l=450/(450-i),c=450/(450-r),h={x:0,y:0},f={x:0,y:0},d=[];for(let i=0,u=t.length-3;i<u;i+=2)h.x=t[i]-E,h.y=t[i+1]-D,f.x=t[i+2]-E,f.y=t[i+3]-D,s=he.project(h,l),n=he.project(f,l),r&&(h=he.project(h,c),f=he.project(f,c)),(f.x-h.x)*(s.y-h.y)>(s.x-h.x)*(f.y-h.y)&&(h.x<f.x&&h.y<f.y||h.x>f.x&&h.y>f.y?e.fillStyle=o:e.fillStyle=a,e.beginPath(),this._ring(e,[f.x,f.y,h.x,h.y,s.x,s.y,n.x,n.y]),e.closePath(),e.fill()),d[i]=s.x,d[i+1]=s.y;return d}static _ring(e,t){e.moveTo(t[0],t[1]);for(let i=2,r=t.length-1;i<r;i+=2)e.lineTo(t[i],t[i+1])}static simplified(e,t,i){if(e.beginPath(),this._ringAbs(e,t),i)for(let t=0,r=i.length;t<r;t++)this._ringAbs(e,i[t]);e.closePath(),e.fill()}static _ringAbs(e,t){e.moveTo(t[0]-E,t[1]-D);for(let i=2,r=t.length-1;i<r;i+=2)e.lineTo(t[i]-E,t[i+1]-D)}static shadow(e,t,i,r,a){let o,s,n=null,l={x:0,y:0},c={x:0,y:0};for(let i=0,h=t.length-3;i<h;i+=2)l.x=t[i]-E,l.y=t[i+1]-D,c.x=t[i+2]-E,c.y=t[i+3]-D,o=de.project(l,r),s=de.project(c,r),a&&(l=de.project(l,a),c=de.project(c,a)),(c.x-l.x)*(o.y-l.y)>(o.x-l.x)*(c.y-l.y)?(1===n&&e.lineTo(l.x,l.y),n=0,i||e.moveTo(l.x,l.y),e.lineTo(c.x,c.y)):(0===n&&e.lineTo(o.x,o.y),n=1,i||e.moveTo(o.x,o.y),e.lineTo(s.x,s.y));if(i)for(let t=0,r=i.length;t<r;t++)this._ringAbs(e,i[t])}static hitArea(e,t,i,r,a,o){let s,n,l=null,c={x:0,y:0},h={x:0,y:0},f=450/(450-r),d=450/(450-a);e.fillStyle=o,e.beginPath();for(let i=0,r=t.length-3;i<r;i+=2)c.x=t[i]-E,c.y=t[i+1]-D,h.x=t[i+2]-E,h.y=t[i+3]-D,s=he.project(c,f),n=he.project(h,f),a&&(c=he.project(c,d),h=he.project(h,d)),(h.x-c.x)*(s.y-c.y)>(s.x-c.x)*(h.y-c.y)?(1===l&&e.lineTo(c.x,c.y),l=0,i||e.moveTo(c.x,c.y),e.lineTo(h.x,h.y)):(0===l&&e.lineTo(s.x,s.y),l=1,i||e.moveTo(s.x,s.y),e.lineTo(n.x,n.y));e.closePath(),e.fill()}}class ne{static draw(e,t,i,r,a,o,s,l,c){let h,f,d={x:t.x-E,y:t.y-D},u=450/(450-a),g=450/(450-o),p=he.project(d,u);r*=u,o&&(d=he.project(d,g),i*=g);let y=this._tangents(d,i,p,r);y?(h=n(y[0].y1-d.y,y[0].x1-d.x),f=n(y[1].y1-d.y,y[1].x1-d.x)):(h=1.5*T,f=1.5*T),e.fillStyle=s,e.beginPath(),e.arc(p.x,p.y,r,z,h,!0),e.arc(d.x,d.y,i,h,z),e.closePath(),e.fill(),e.fillStyle=l,e.beginPath(),e.arc(p.x,p.y,r,f,z,!0),e.arc(d.x,d.y,i,z,f),e.closePath(),e.fill(),e.fillStyle=c,this._circle(e,p,r)}static simplified(e,t,i){this._circle(e,{x:t.x-E,y:t.y-D},i)}static shadow(e,t,i,r,a,o){let s,l,c={x:t.x-E,y:t.y-D},h=de.project(c,a);o&&(c=de.project(c,o));let f=this._tangents(c,i,h,r);f?(s=n(f[0].y1-c.y,f[0].x1-c.x),l=n(f[1].y1-c.y,f[1].x1-c.x),e.moveTo(f[1].x2,f[1].y2),e.arc(h.x,h.y,r,l,s),e.arc(c.x,c.y,i,s,l)):(e.moveTo(c.x+i,c.y),e.arc(c.x,c.y,i,0,2*T))}static hitArea(e,t,i,r,a,o,s){let l,c,h={x:t.x-E,y:t.y-D},f=450/(450-a),d=450/(450-o),u=he.project(h,f);r*=f,o&&(h=he.project(h,d),i*=d);let g=this._tangents(h,i,u,r);e.fillStyle=s,e.beginPath(),g?(l=n(g[0].y1-h.y,g[0].x1-h.x),c=n(g[1].y1-h.y,g[1].x1-h.x),e.moveTo(g[1].x2,g[1].y2),e.arc(u.x,u.y,r,c,l),e.arc(h.x,h.y,i,l,c)):(e.moveTo(h.x+i,h.y),e.arc(h.x,h.y,i,0,2*T)),e.closePath(),e.fill()}static _circle(e,t,i){e.beginPath(),e.arc(t.x,t.y,i,0,2*T),e.fill()}static _tangents(e,t,i,r){let a=e.x-i.x,o=e.y-i.y,s=t-r,n=a*a+o*o;if(n<=s*s)return;let l,f,d,u=h(n),g=-a/u,p=-o/u,y=s/u,m=[];l=h(c(0,1-y*y));for(let a=1;a>=-1;a-=2)f=g*y-a*l*p,d=p*y+a*l*g,m.push({x1:e.x+t*f<<0,y1:e.y+t*d<<0,x2:i.x+r*f<<0,y2:i.y+r*d<<0});return m}}class le{static draw(e,t,i,r,a,o,s){let n={x:i.x-E,y:i.y-D},l=450/(450-r),c=450/(450-a),h=he.project(n,l),f={x:0,y:0},d={x:0,y:0};for(let i=0,r=t.length-3;i<r;i+=2)f.x=t[i]-E,f.y=t[i+1]-D,d.x=t[i+2]-E,d.y=t[i+3]-D,a&&(f=he.project(f,c),d=he.project(d,c)),(d.x-f.x)*(h.y-f.y)>(h.x-f.x)*(d.y-f.y)&&(f.x<d.x&&f.y<d.y||f.x>d.x&&f.y>d.y?e.fillStyle=s:e.fillStyle=o,e.beginPath(),this._triangle(e,f,d,h),e.closePath(),e.fill())}static _triangle(e,t,i,r){e.moveTo(t.x,t.y),e.lineTo(i.x,i.y),e.lineTo(r.x,r.y)}static _ring(e,t){e.moveTo(t[0]-E,t[1]-D);for(let i=2,r=t.length-1;i<r;i+=2)e.lineTo(t[i]-E,t[i+1]-D)}static shadow(e,t,i,r,a){let o={x:0,y:0},s={x:0,y:0},n={x:i.x-E,y:i.y-D},l=de.project(n,r);for(let i=0,r=t.length-3;i<r;i+=2)o.x=t[i]-E,o.y=t[i+1]-D,s.x=t[i+2]-E,s.y=t[i+3]-D,a&&(o=de.project(o,a),s=de.project(s,a)),(s.x-o.x)*(l.y-o.y)>(l.x-o.x)*(s.y-o.y)&&this._triangle(e,o,s,l)}static hitArea(e,t,i,r,a,o){let s={x:i.x-E,y:i.y-D},n=450/(450-r),l=450/(450-a),c=he.project(s,n),h={x:0,y:0},f={x:0,y:0};e.fillStyle=o,e.beginPath();for(let i=0,r=t.length-3;i<r;i+=2)h.x=t[i]-E,h.y=t[i+1]-D,f.x=t[i+2]-E,f.y=t[i+3]-D,a&&(h=he.project(h,l),f=he.project(f,l)),(f.x-h.x)*(c.y-h.y)>(c.x-h.x)*(f.y-h.y)&&this._triangle(e,h,f,c);e.closePath(),e.fill()}}class ce{static init(){ce.container.className="osmb-container",de.init(ce.createContext(ce.container)),fe.init(ce.createContext(ce.container)),he.init(ce.createContext(ce.container)),ue.init(ce.createContext())}static clear(){de.clear(),fe.clear(),he.clear(),ue.clear()}static setOpacity(e){de.setOpacity(e),fe.setOpacity(e),he.setOpacity(e),ue.setOpacity(e)}static render(e){_<15?ce.clear():M||requestAnimationFrame(t=>{e||(de.render(),fe.render()),he.render()})}static createContext(e){let t=document.createElement("CANVAS");t.className="osmb-layer";let i=t.getContext("2d");return i.lineCap="round",i.lineJoin="round",i.lineWidth=1,i.imageSmoothingEnabled=!1,ce.items.push(t),e&&e.appendChild(t),i}static appendTo(e){e.appendChild(ce.container)}static remove(){ce.container.parentNode.removeChild(ce.container)}static setSize(e,t){ce.items.forEach(i=>{i.width=e,i.height=t})}static setPosition(e,t){ce.container.style.left=e+"px",ce.container.style.top=t+"px"}}ce.container=document.createElement("DIV"),ce.items=[];class he{static init(e){this.context=e}static clear(){this.context.clearRect(0,0,O,A)}static setOpacity(e){this.context.canvas.style.opacity=e}static project(e,t){return{x:(e.x-C)*t+C<<0,y:(e.y-S)*t+S<<0}}static render(){this.clear();let e,t,i,r,a,o,s,n=this.context,l={x:C+E,y:S+D},c=oe.items;c.sort((e,t)=>e.minHeight-t.minHeight||B(t.center,l)-B(e.center,l)||t.height-e.height);for(let l=0,h=c.length;l<h;l++)if(e=c[l],!fe.isSimple(e)&&(r=e.footprint,ee(r))){switch(t=e.scale<1?e.height*e.scale:e.height,i=0,e.minHeight&&(i=e.scale<1?e.minHeight*e.scale:e.minHeight),a=e.wallColor||Z,o=e.altColor||V,s=e.roofColor||X,n.strokeStyle=o,e.shape){case"cylinder":ne.draw(n,e.center,e.radius,e.radius,t,i,a,o,s);break;case"cone":ne.draw(n,e.center,e.radius,0,t,i,a,o);break;case"dome":ne.draw(n,e.center,e.radius,e.radius/2,t,i,a,o);break;case"sphere":ne.draw(n,e.center,e.radius,e.radius,t,i,a,o,s);break;case"pyramid":le.draw(n,r,e.center,t,i,a,o);break;default:se.draw(n,r,e.holes,t,i,a,o,s)}switch(e.roofShape){case"cone":ne.draw(n,e.center,e.radius,0,t+e.roofHeight,t,s,""+u.parse(s).lightness(.9));break;case"dome":ne.draw(n,e.center,e.radius,e.radius/2,t+e.roofHeight,t,s,""+u.parse(s).lightness(.9));break;case"pyramid":le.draw(n,r,e.center,t+e.roofHeight,t,s,u.parse(s).lightness(.9))}}}}class fe{static init(e){this.context=e}static clear(){this.context.clearRect(0,0,O,A)}static setOpacity(e){this.context.canvas.style.opacity=e}static isSimple(e){return _<=fe.MAX_ZOOM&&e.height+e.roofHeight<fe.MAX_HEIGHT}static render(){this.clear();let e=this.context;if(_>fe.MAX_ZOOM)return;let t,i,r=oe.items;for(let a=0,o=r.length;a<o;a++)if(t=r[a],!(t.height>=fe.MAX_HEIGHT)&&(i=t.footprint,ee(i)))switch(e.strokeStyle=t.altColor||V,e.fillStyle=t.roofColor||X,t.shape){case"cylinder":case"cone":case"dome":case"sphere":ne.simplified(e,t.center,t.radius);break;default:se.simplified(e,i,t.holes)}}}fe.MAX_ZOOM=16,fe.MAX_HEIGHT=5;class de{static init(e){this.context=e}static clear(){this.context.clearRect(0,0,O,A)}static setOpacity(e){this.opacity=e}static project(e,t){return{x:e.x+this.direction.x*t,y:e.y+this.direction.y*t}}static render(){this.clear();let e,t,i,s,n=this.context;if(e=K(j+E,R+D),t=g(this.date,e.latitude,e.longitude),t.altitude<=0)return;i=1/o(t.altitude),s=i<5?.75:1/i*5,this.direction.x=a(t.azimuth)*i,this.direction.y=r(t.azimuth)*i;let l,c,h,f,d,u,p=oe.items;for(n.canvas.style.opacity=s/(2*this.opacity),n.shadowColor=this.blurColor,n.fillStyle=this.color,n.beginPath(),l=0,c=p.length;l<c;l++)if(h=p[l],u=h.footprint,ee(u)){switch(f=h.scale<1?h.height*h.scale:h.height,d=0,h.minHeight&&(d=h.scale<1?h.minHeight*h.scale:h.minHeight),h.shape){case"cylinder":ne.shadow(n,h.center,h.radius,h.radius,f,d);break;case"cone":ne.shadow(n,h.center,h.radius,0,f,d);break;case"dome":ne.shadow(n,h.center,h.radius,h.radius/2,f,d);break;case"sphere":ne.shadow(n,h.center,h.radius,h.radius,f,d);break;case"pyramid":le.shadow(n,u,h.center,f,d);break;default:se.shadow(n,u,h.holes,f,d)}switch(h.roofShape){case"cone":ne.shadow(n,h.center,h.radius,0,f+h.roofHeight,f);break;case"dome":ne.shadow(n,h.center,h.radius,h.radius/2,f+h.roofHeight,f);break;case"pyramid":le.shadow(n,u,h.center,f+h.roofHeight,f)}}n.closePath(),n.fill()}}de.color="#666666",de.blurColor="#000000",de.date=new Date,de.direction={x:0,y:0},de.opacity=1;class ue{static init(e){this.context=e}static setOpacity(e){}static clear(){}static reset(){this._idMapping=[null]}static render(){if(this._timer)return;let e=this;this._timer=setTimeout(t=>{e._timer=null,e._render()},500)}static _render(){this.clear();let e,t,i,r,a,o=this.context,s={x:C+E,y:S+D},n=oe.items;n.sort((e,t)=>e.minHeight-t.minHeight||B(t.center,s)-B(e.center,s)||t.height-e.height);for(let s=0,l=n.length;s<l;s++)if(e=n[s],(a=e.hitColor)&&(r=e.footprint,ee(r))){switch(t=e.height,i=0,e.minHeight&&(i=e.minHeight),e.shape){case"cylinder":ne.hitArea(o,e.center,e.radius,e.radius,t,i,a);break;case"cone":ne.hitArea(o,e.center,e.radius,0,t,i,a);break;case"dome":ne.hitArea(o,e.center,e.radius,e.radius/2,t,i,a);break;case"sphere":ne.hitArea(o,e.center,e.radius,e.radius,t,i,a);break;case"pyramid":le.hitArea(o,r,e.center,t,i,a);break;default:se.hitArea(o,r,e.holes,t,i,a)}switch(e.roofShape){case"cone":ne.hitArea(o,e.center,e.radius,0,t+e.roofHeight,t,a);break;case"dome":ne.hitArea(o,e.center,e.radius,e.radius/2,t+e.roofHeight,t,a);break;case"pyramid":le.hitArea(o,r,e.center,t+e.roofHeight,t,a)}}O&&A&&(this._imageData=this.context.getImageData(0,0,O,A).data)}static getIdFromXY(e,t){let i=this._imageData;if(!i)return;let r=4*((0|t)*O+(0|e)),a=i[r]|i[r+1]<<8|i[r+2]<<16;return this._idMapping[a]}static idToColor(e){let t=this._idMapping.indexOf(e);return-1===t&&(this._idMapping.push(e),t=this._idMapping.length-1),"rgb("+[255&t,t>>8&255,t>>16&255].join(",")+")"}}ue._idMapping=[null];function ge(e){E=e.x,D=e.y}function pe(e){C=j+e.x,S=A+e.y,ce.render(!0)}function ye(e){O=e.width,A=e.height,j=O/2<<0,R=A/2<<0,C=j,S=A,ce.setSize(O,A),H=400}function me(e){_=e,v=256<<_;const t=K(E+j,D+R),i=Q(t.latitude,0),r=Q(t.latitude,1);$=r.x-i.x,ce.setOpacity(Math.pow(.95,_-15)),Z=""+F,V=""+N,X=""+q}class xe extends L.Layer{constructor(e){super(e),this.offset={x:0,y:0},ce.init(),e&&e.addLayer(this)}addTo(e){return e.addLayer(this),this}onAdd(e){this.map=e,ce.appendTo(e._panes.overlayPane);let t=this.getOffset(),i=e.getPixelOrigin();ye({width:e._size.x,height:e._size.y}),ge({x:i.x-t.x,y:i.y-t.y}),me(e._zoom),ce.setPosition(-t.x,-t.y),e.on({move:this.onMove,moveend:this.onMoveEnd,zoomstart:this.onZoomStart,zoomend:this.onZoomEnd,resize:this.onResize,viewreset:this.onViewReset,click:this.onClick},this),e.options.zoomAnimation&&e.on("zoomanim",this.onZoom,this),e.attributionControl&&e.attributionControl.addAttribution(P),oe.update()}onRemove(){let e=this.map;e.attributionControl&&e.attributionControl.removeAttribution(P),e.off({move:this.onMove,moveend:this.onMoveEnd,zoomstart:this.onZoomStart,zoomend:this.onZoomEnd,resize:this.onResize,viewreset:this.onViewReset,click:this.onClick},this),e.options.zoomAnimation&&e.off("zoomanim",this.onZoom,this),ce.remove(),e=null}onMove(e){let t=this.getOffset();pe({x:this.offset.x-t.x,y:this.offset.y-t.y})}onMoveEnd(e){if(this.noMoveEnd)return void(this.noMoveEnd=!1);let t=this.map,i=this.getOffset(),r=t.getPixelOrigin();this.offset=i,ce.setPosition(-i.x,-i.y),pe({x:0,y:0}),ye({width:t._size.x,height:t._size.y}),ge({x:r.x-i.x,y:r.y-i.y}),ce.render(),oe.update()}onZoomStart(e){M=!0}onZoom(e){let t=this.map.latLngToContainerPoint(e.center),i=Math.pow(2,e.zoom-_),r=O/2-t.x,a=A/2-t.y,o=O/2,s=A/2;e.zoom>_?(o-=r*i,s-=a*i):(o+=r,s+=a),ce.container.classList.add("zoom-animation"),ce.container.style.transformOrigin=o+"px "+s+"px",ce.container.style.transform="translate3d(0, 0, 0) scale("+i+")"}onZoomEnd(e){ce.clear(),ce.container.classList.remove("zoom-animation"),ce.container.style.transform="translate3d(0, 0, 0) scale(1)";let t=this.map,i=this.getOffset(),r=t.getPixelOrigin();ge({x:r.x-i.x,y:r.y-i.y}),function(e){M=!1;const t=Math.pow(2,e.zoom-_);me(e.zoom),_<=15?ce.clear():(oe.scale(t),de.render(),fe.render(),he.render(),oe.update())}({zoom:t._zoom}),this.noMoveEnd=!0}onResize(){}onViewReset(){let e=this.getOffset();this.offset=e,ce.setPosition(-e.x,-e.y),pe({x:0,y:0})}onClick(e){let t=ue.getIdFromXY(e.containerPoint.x,e.containerPoint.y);t&&J({feature:t,lat:e.latlng.lat,lon:e.latlng.lng})}getOffset(){return L.DomUtil.getPosition(this.map._mapPane)}style(e){let t;return(t=(e=e||{}).color||e.wallColor)&&(F=u.parse(t),Z=""+F,N=F.lightness(.8),V=""+N,q=F.lightness(1.2),X=""+q),e.roofColor&&(q=u.parse(e.roofColor),X=""+q),ce.render(),this}date(e){return de.date=e,de.render(),this}load(e){return oe.load(e),this}set(e){return oe.set(e),this}each(e){return W=function(t){return e(t)},this}click(e){return J=function(t){return e(t)},this}}return xe.VERSION="0.3.2",xe.ATTRIBUTION=P,xe}();

================================================
FILE: dist/OSMBuildings-OpenLayers.debug.js
================================================
const OSMBuildings = (function() {

const
  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,
  pow = m.pow;


/**
 * @class
 */
class Qolor {

  /**
   * @constructor
   * @param r {Number} 0.0 .. 1.0 red value of a color
   * @param g {Number} 0.0 .. 1.0 green value of a color
   * @param b {Number} 0.0 .. 1.0 blue value of a color
   * @param a {Number} 0.0 .. 1.0 alpha value of a color, default 1
   */
  constructor (r, g, b, a = 1) {
    this.r = this._clamp(r, 1);
    this.g = this._clamp(g, 1);
    this.b = this._clamp(b, 1);
    this.a = this._clamp(a, 1);
  }

  /**
   * @param str {String} can be any color dfinition like: 'red', '#0099ff', 'rgb(64, 128, 255)', 'rgba(64, 128, 255, 0.5)'
   */
  static parse (str) {
    if (typeof str === 'string') {
      str = str.toLowerCase();
      str = Qolor.w3cColors[str] || str;

      let m;

      if ((m = str.match(/^#?(\w{2})(\w{2})(\w{2})$/))) {
        return new Qolor(parseInt(m[1], 16)/255, parseInt(m[2], 16)/255, parseInt(m[3], 16)/255);
      }

      if ((m = str.match(/^#?(\w)(\w)(\w)$/))) {
        return new Qolor(parseInt(m[1]+m[1], 16)/255, parseInt(m[2]+m[2], 16)/255, parseInt(m[3]+m[3], 16)/255);
      }

      if ((m = str.match(/rgba?\((\d+)\D+(\d+)\D+(\d+)(\D+([\d.]+))?\)/))) {
        return new Qolor(
          parseFloat(m[1])/255,
          parseFloat(m[2])/255,
          parseFloat(m[3])/255,
          m[4] ? parseFloat(m[5]) : 1
        );
      }
    }

    return new Qolor();
  }

  static fromHSL (h, s, l, a) {
    const qolor = new Qolor().fromHSL(h, s, l);
    qolor.a = a === undefined ? 1 : a;
    return qolor;
  }

  //***************************************************************************

  _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;
  }

  _clamp(v, max) {
    if (v === undefined) {
      return;
    }
    return Math.min(max, Math.max(0, v || 0));
  }

  //***************************************************************************

  isValid () {
    return this.r !== undefined && this.g !== undefined && this.b !== undefined;
  }

  toHSL () {
    if (!this.isValid()) {
      return;
    }

    const max = Math.max(this.r, this.g, this.b);
    const min = Math.min(this.r, this.g, this.b);
    const range = max - min;
    const l = (max + min)/2;

    // achromatic
    if (!range) {
      return { h: 0, s: 0, l: l };
    }

    const s = l > 0.5 ? range/(2 - max - min) : range/(max + min);

    let h;
    switch (max) {
      case this.r:
        h = (this.g - this.b)/range + (this.g<this.b ? 6 : 0);
        break;
      case this.g:
        h = (this.b - this.r)/range + 2;
        break;
      case this.b:
        h = (this.r - this.g)/range + 4;
        break;
    }
    h *= 60;

    return { h: h, s: s, l: l };
  }

  fromHSL (h, s, l) {
    // h = this._clamp(h, 360),
    // s = this._clamp(s, 1),
    // l = this._clamp(l, 1),

    // achromatic
    if (s === 0) {
      this.r = this.g = this.b = l;
      return this;
    }

    const q = l<0.5 ? l*(1 + s) : l + s - l*s;
    const p = 2*l - q;

    h /= 360;

    this.r = this._hue2rgb(p, q, h + 1/3);
    this.g = this._hue2rgb(p, q, h);
    this.b = this._hue2rgb(p, q, h - 1/3);

    return this;
  }

  toString () {
    if (!this.isValid()) {
      return;
    }

    if (this.a === 1) {
      return '#' + ((1<<24) + (Math.round(this.r*255)<<16) + (Math.round(this.g*255)<<8) + Math.round(this.b*255)).toString(16).slice(1, 7);
    }
    return `rgba(${Math.round(this.r*255)},${Math.round(this.g*255)},${Math.round(this.b*255)},${this.a.toFixed(2)})`;
  }

  toArray () {
    if (!this.isValid) {
      return;
    }
    return [this.r, this.g, this.b];
  }

  hue (h) {
    const hsl = this.toHSL();
    return this.fromHSL(hsl.h+h, hsl.s, hsl.l);
  }

  saturation (s) {
    const hsl = this.toHSL();
    return this.fromHSL(hsl.h, hsl.s*s, hsl.l);
  }

  lightness (l) {
    const hsl = this.toHSL();
    return this.fromHSL(hsl.h, hsl.s, hsl.l*l);
  }

  clone () {
    return new Qolor(this.r, this.g, this.b, this.a);
  }
}

Qolor.w3cColors = {
  aliceblue: '#f0f8ff',
  antiquewhite: '#faebd7',
  aqua: '#00ffff',
  aquamarine: '#7fffd4',
  azure: '#f0ffff',
  beige: '#f5f5dc',
  bisque: '#ffe4c4',
  black: '#000000',
  blanchedalmond: '#ffebcd',
  blue: '#0000ff',
  blueviolet: '#8a2be2',
  brown: '#a52a2a',
  burlywood: '#deb887',
  cadetblue: '#5f9ea0',
  chartreuse: '#7fff00',
  chocolate: '#d2691e',
  coral: '#ff7f50',
  cornflowerblue: '#6495ed',
  cornsilk: '#fff8dc',
  crimson: '#dc143c',
  cyan: '#00ffff',
  darkblue: '#00008b',
  darkcyan: '#008b8b',
  darkgoldenrod: '#b8860b',
  darkgray: '#a9a9a9',
  darkgrey: '#a9a9a9',
  darkgreen: '#006400',
  darkkhaki: '#bdb76b',
  darkmagenta: '#8b008b',
  darkolivegreen: '#556b2f',
  darkorange: '#ff8c00',
  darkorchid: '#9932cc',
  darkred: '#8b0000',
  darksalmon: '#e9967a',
  darkseagreen: '#8fbc8f',
  darkslateblue: '#483d8b',
  darkslategray: '#2f4f4f',
  darkslategrey: '#2f4f4f',
  darkturquoise: '#00ced1',
  darkviolet: '#9400d3',
  deeppink: '#ff1493',
  deepskyblue: '#00bfff',
  dimgray: '#696969',
  dimgrey: '#696969',
  dodgerblue: '#1e90ff',
  firebrick: '#b22222',
  floralwhite: '#fffaf0',
  forestgreen: '#228b22',
  fuchsia: '#ff00ff',
  gainsboro: '#dcdcdc',
  ghostwhite: '#f8f8ff',
  gold: '#ffd700',
  goldenrod: '#daa520',
  gray: '#808080',
  grey: '#808080',
  green: '#008000',
  greenyellow: '#adff2f',
  honeydew: '#f0fff0',
  hotpink: '#ff69b4',
  indianred: '#cd5c5c',
  indigo: '#4b0082',
  ivory: '#fffff0',
  khaki: '#f0e68c',
  lavender: '#e6e6fa',
  lavenderblush: '#fff0f5',
  lawngreen: '#7cfc00',
  lemonchiffon: '#fffacd',
  lightblue: '#add8e6',
  lightcoral: '#f08080',
  lightcyan: '#e0ffff',
  lightgoldenrodyellow: '#fafad2',
  lightgray: '#d3d3d3',
  lightgrey: '#d3d3d3',
  lightgreen: '#90ee90',
  lightpink: '#ffb6c1',
  lightsalmon: '#ffa07a',
  lightseagreen: '#20b2aa',
  lightskyblue: '#87cefa',
  lightslategray: '#778899',
  lightslategrey: '#778899',
  lightsteelblue: '#b0c4de',
  lightyellow: '#ffffe0',
  lime: '#00ff00',
  limegreen: '#32cd32',
  linen: '#faf0e6',
  magenta: '#ff00ff',
  maroon: '#800000',
  mediumaquamarine: '#66cdaa',
  mediumblue: '#0000cd',
  mediumorchid: '#ba55d3',
  mediumpurple: '#9370db',
  mediumseagreen: '#3cb371',
  mediumslateblue: '#7b68ee',
  mediumspringgreen: '#00fa9a',
  mediumturquoise: '#48d1cc',
  mediumvioletred: '#c71585',
  midnightblue: '#191970',
  mintcream: '#f5fffa',
  mistyrose: '#ffe4e1',
  moccasin: '#ffe4b5',
  navajowhite: '#ffdead',
  navy: '#000080',
  oldlace: '#fdf5e6',
  olive: '#808000',
  olivedrab: '#6b8e23',
  orange: '#ffa500',
  orangered: '#ff4500',
  orchid: '#da70d6',
  palegoldenrod: '#eee8aa',
  palegreen: '#98fb98',
  paleturquoise: '#afeeee',
  palevioletred: '#db7093',
  papayawhip: '#ffefd5',
  peachpuff: '#ffdab9',
  peru: '#cd853f',
  pink: '#ffc0cb',
  plum: '#dda0dd',
  powderblue: '#b0e0e6',
  purple: '#800080',
  rebeccapurple: '#663399',
  red: '#ff0000',
  rosybrown: '#bc8f8f',
  royalblue: '#4169e1',
  saddlebrown: '#8b4513',
  salmon: '#fa8072',
  sandybrown: '#f4a460',
  seagreen: '#2e8b57',
  seashell: '#fff5ee',
  sienna: '#a0522d',
  silver: '#c0c0c0',
  skyblue: '#87ceeb',
  slateblue: '#6a5acd',
  slategray: '#708090',
  slategrey: '#708090',
  snow: '#fffafa',
  springgreen: '#00ff7f',
  steelblue: '#4682b4',
  tan: '#d2b48c',
  teal: '#008080',
  thistle: '#d8bfd8',
  tomato: '#ff6347',
  turquoise: '#40e0d0',
  violet: '#ee82ee',
  wheat: '#f5deb3',
  white: '#ffffff',
  whitesmoke: '#f5f5f5',
  yellow: '#ffff00',
  yellowgreen: '#9acd32'
};

if (typeof module !== 'undefined') {
  module.exports = Qolor;
}

// calculations are based on http://aa.quae.nl/en/reken/zonpositie.html
// code credits to Vladimir Agafonkin (@mourner)

function getSunPosition () {

  const m = Math,
    PI = m.PI,
    sin = m.sin,
    cos = m.cos,
    tan = m.tan,
    asin = m.asin,
    atan = m.atan2;

  const 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) {
    const P = rad*102.9372; // perihelion of the Earth
    return M+C+P+PI;
  }

  return function getSunPosition(date, lat, lon) {
    const 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
    };
  };
}


const METERS_PER_LEVEL = 3;

const 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'
};

const 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;
}

const WINDING_CLOCKWISE = 'CW';
const WINDING_COUNTER_CLOCKWISE = 'CCW';

// detect winding direction: clockwise or counter clockwise
function getWinding (points) {
  let x1, y1, x2, y2,
    a = 0;
  for (let 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) {
  let winding = getWinding(points);
  if (winding === direction) {
    return points;
  }
  let revPoints = [];
  for (let i = points.length-2; i >= 0; i -= 2) {
    revPoints.push(points[i], points[i+1]);
  }
  return revPoints;
}

function alignProperties(prop) {
  const 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);

  const wallColor = prop.material ? getMaterialColor(prop.material) : (prop.wallColor || prop.color);
  if (wallColor) {
    item.wallColor = wallColor;
  }

  const 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) {
  let
    polygon,
    geometries = [], sub;

  switch (geometry.type) {
    case 'GeometryCollection':
      geometries = [];
      for (let 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 (let 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 [];
  }

  let
    p, lat = 1, lon = 0,
    outer = [], inner = [];

  p = polygon[0];
  for (let i = 0, il = p.length; i < il; i++) {
    outer.push(p[i][lat], p[i][lon]);
  }
  outer = makeWinding(outer, WINDING_CLOCKWISE);

  for (let i = 0, il = polygon.length-1; i < il; i++) {
    p = polygon[i+1];
    inner[i] = [];
    for (let 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) {
  let res = {};
  for (const p in obj) {
    if (obj.hasOwnProperty(p)) {
      res[p] = obj[p];
    }
  }
  return res;
}

class GeoJSON {

  static read (geojson) {
    if (!geojson || geojson.type !== 'FeatureCollection') {
      return [];
    }

    const collection = geojson.features;
    const res = [];

    for (let i = 0, il = collection.length; i < il; i++) {
      const feature = collection[i];

      if (feature.type !== 'Feature' || onEach(feature) === false) {
        continue;
      }

      const baseItem = alignProperties(feature.properties);
      const geometries = getGeometries(feature.geometry);

      for (let j = 0, jl = geometries.length; j < jl; j++) {
        const 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;
  }
}

let
  VERSION      = '0.3.2',
  ATTRIBUTION  = '&copy; <a href="https://osmbuildings.org">OSM Buildings</a>',

  DATA_SRC = 'https://{s}.data.osmbuildings.org/0.2/{k}/tile/{z}/{x}/{y}.json',

  PI         = Math.PI,
  HALF_PI    = PI/2,
  QUARTER_PI = PI/4,

  MAP_TILE_SIZE  = 256,    // map tile size in pixels
  ZOOM, MAP_SIZE,

  MIN_ZOOM = 15,

  LAT = 'latitude', LON = 'longitude',

  WIDTH = 0, HEIGHT = 0,
  CENTER_X = 0, CENTER_Y = 0,
  ORIGIN_X = 0, ORIGIN_Y = 0,

  WALL_COLOR = Qolor.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,

  MAX_HEIGHT, // taller buildings will be cut to this
  DEFAULT_HEIGHT = 5,

  CAM_X, CAM_Y, CAM_Z = 450,

  IS_ZOOMING;

function onEach () {}

function onClick () {}


function getDistance (p1, p2) {
  const
    dx = p1.x-p2.x,
    dy = p1.y-p2.y;
  return dx*dx + dy*dy;
}

function isRotational (polygon) {
  const length = polygon.length;
  if (length < 16) {
    return false;
  }

  let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
  for (let 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]);
  }

  const
    width = maxX-minX,
    height = (maxY-minY),
    ratio = width/height;

  if (ratio < 0.85 || ratio > 1.15) {
    return false;
  }

  const
    center = { x:minX+width/2, y:minY+height/2 },
    radius = (width+height)/4,
    sqRadius = radius*radius;

  for (let i = 0; i < length-1; i+=2) {
    const 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) {
  let
    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) {
  let
    sqTolerance = 2,
    len = buffer.length/2,
    markers = new Uint8Array(len),

    first = 0, last = len-1,

    maxSqDist,
    sqDist,
    index,
    firstStack = [], lastStack  = [],
    newBuffer  = [];

  markers[first] = markers[last] = 1;

  while (last) {
    maxSqDist = 0;
    for (let 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 (let i = 0; i < len; i++) {
    if (markers[i]) {
      newBuffer.push(buffer[i*2], buffer[i*2 + 1]);
    }
  }

  return newBuffer;
}

function getCenter (footprint) {
  let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
  for (let 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 };
}

let EARTH_RADIUS = 6378137;

function getLonDelta (footprint) {
  let minLon = 180, maxLon = -180;
  for (let 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;
}


function rad (deg) {
  return deg * PI / 180;
}

function deg (rad) {
  return rad / PI * 180;
}

function pixelToGeo (x, y) {
  const 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) {
  const
    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 fromRange (sVal, sMin, sMax, dMin, dMax) {
  sVal = min(max(sVal, sMin), sMax);
  const rel = (sVal-sMin) / (sMax-sMin),
    range = dMax-dMin;
  return min(max(dMin + rel*range, dMin), dMax);
}

function isVisible (polygon) {
  const
    maxX = WIDTH+ORIGIN_X,
    maxY = HEIGHT+ORIGIN_Y;

  // TODO: checking footprint is sufficient for visibility - NOT VALID FOR SHADOWS!
  for (let 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;
}


let cacheData = {};
let cacheIndex = [];
let cacheSize = 0;
let maxCacheSize = 1024*1024 * 5; // 5MB

function xhr (url, callback) {
  if (cacheData[url]) {
    if (callback) {
      callback(cacheData[url]);
    }
    return;
  }

  const 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) {
      const responseText = req.responseText;

      cacheData[url] = responseText;
      cacheIndex.push({ url: url, size: responseText.length });
      cacheSize += responseText.length;

      callback(responseText);

      while (cacheSize > maxCacheSize) {
        let item = cacheIndex.shift();
        cacheSize -= item.size;
        delete cacheData[item.url];
      }
    }
  };

  req.open('GET', url);
  req.send(null);

  return req;
}

class Request {

  static loadJSON (url, callback) {
    return xhr(url, responseText => {
      let json;
      try {
        json = JSON.parse(responseText);
      } catch(ex) {}

      callback(json);
    });
  }
}


class Data {

  static getPixelFootprint (buffer) {
    let footprint = new Int32Array(buffer.length),
      px;

    for (let 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;
  }

  static resetItems () {
    this.items = [];
    this.cache = {};
    Picking.reset();
  }

  static addRenderItems (data, allAreNew) {
    let item, scaledItem, id;
    let geojson = GeoJSON.read(data);
    for (let 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.cache[id]) {
        if ((scaledItem = this.scaleItem(item))) {
          scaledItem.scale = allAreNew ? 0 : 1;
          this.items.push(scaledItem);
          this.cache[id] = 1;
        }
      }
    }
    fadeIn();
  }

  static scalePolygon (buffer, factor) {
    return buffer.map(coord => coord*factor);
  }

  static scale (factor) {
    Data.items = Data.items.map(item => {
      // item.height = Math.min(item.height*factor, MAX_HEIGHT); // TODO: should be filtered by renderer

      item.height *= factor;
      item.minHeight *= factor;

      item.footprint = Data.scalePolygon(item.footprint, factor);
      item.center.x *= factor;
      item.center.y *= factor;

      if (item.radius) {
        item.radius *= factor;
      }

      if (item.holes) {
        for (let i = 0, il = item.holes.length; i < il; i++) {
          item.holes[i] = Data.scalePolygon(item.holes[i], factor);
        }
      }

      item.roofHeight *= factor;

      return item;
    });
  }

  static scaleItem (item) {
    let
      res = {},
      // TODO: calculate this on zoom change only
      zoomScale = 6 / pow(2, ZOOM-MIN_ZOOM); // TODO: consider using HEIGHT / (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 = [];
      let innerFootprint;
      for (let i = 0, il = item.holes.length; i < il; i++) {
        // TODO: simplify
        if ((innerFootprint = this.getPixelFootprint(item.holes[i]))) {
          res.holes.push(innerFootprint);
        }
      }
    }

    let color;

    if (item.wallColor) {
      if ((color = Qolor.parse(item.wallColor))) {
        res.altColor  = ''+ color.lightness(0.8);
        res.wallColor = ''+ color;
      }
    }

    if (item.roofColor) {
      if ((color = Qolor.parse(item.roofColor))) {
        res.roofColor = ''+ color;
      }
    }

    if (item.relationId) {
      res.relationId = item.relationId;
    }
    res.hitColor = Picking.idToColor(item.relationId || item.id);

    res.roofHeight = isNaN(item.roofHeight) ? 0 : item.roofHeight/zoomScale;

    if (res.height+res.roofHeight <= res.minHeight) {
      return;
    }

    return res;
  }

  static set (data) {
    this.resetItems();
    this._staticData = data;
    this.addRenderItems(this._staticData, true);
  }

  static load (src, key) {
    this.src = src || DATA_SRC.replace('{k}', (key || 'anonymous'));
    this.update();
  }

  static update () {
    this.resetItems();

    if (ZOOM < MIN_ZOOM) {
      return;
    }

    if (this._staticData) {
      this.addRenderItems(this._staticData);
    }

    if (this.src) {
      let
        tileZoom = 16,
        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;

      let 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);
        }
      }
    }
  }

  static loadTile (x, y, zoom, callback) {
    let s = 'abcd'[(x+y) % 4];
    let url = this.src.replace('{s}', s).replace('{x}', x).replace('{y}', y).replace('{z}', zoom);
    return Request.loadJSON(url, callback);
  }
}

Data.cache = {}; // maintain a list of cached items in order to avoid duplicates on tile borders
Data.items = [];

class Extrusion {

  static draw (context, polygon, innerPolygons, height, minHeight, color, altColor, roofColor) {
    let
      roof = this._extrude(context, polygon, height, minHeight, color, altColor),
      innerRoofs = [];

    if (innerPolygons) {
      for (let 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 (let i = 0, il = innerRoofs.length; i < il; i++) {
        this._ring(context, innerRoofs[i]);
      }
    }
    context.closePath();
    context.fill();
  }

  static _extrude (context, polygon, height, minHeight, color, altColor) {
    let
      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 (let 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;
  }

  static _ring (context, polygon) {
    context.moveTo(polygon[0], polygon[1]);
    for (let i = 2, il = polygon.length-1; i < il; i += 2) {
      context.lineTo(polygon[i], polygon[i+1]);
    }
  }

  static simplified (context, polygon, innerPolygons) {
    context.beginPath();
    this._ringAbs(context, polygon);
    if (innerPolygons) {
      for (let i = 0, il = innerPolygons.length; i < il; i++) {
        this._ringAbs(context, innerPolygons[i]);
      }
    }
    context.closePath();
    context.fill();
  }

  static _ringAbs (context, polygon) {
    context.moveTo(polygon[0]-ORIGIN_X, polygon[1]-ORIGIN_Y);
    for (let i = 2, il = polygon.length-1; i < il; i += 2) {
      context.lineTo(polygon[i]-ORIGIN_X, polygon[i+1]-ORIGIN_Y);
    }
  }

  static shadow (context, polygon, innerPolygons, height, minHeight) {
    let
      mode = null,
      a = { x:0, y:0 },
      b = { x:0, y:0 },
      _a, _b;

    for (let 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 (let i = 0, il = innerPolygons.length; i < il; i++) {
        this._ringAbs(context, innerPolygons[i]);
      }
    }
  }

  static hitArea (context, polygon, innerPolygons, height, minHeight, color) {
    let
      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 (let 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();
  }
}

class Cylinder {

  static draw (context, center, radius, topRadius, height, minHeight, color, altColor, roofColor) {
    let
      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
    let 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);
  }

  static simplified (context, center, radius) {
    this._circle(context, { x:center.x-ORIGIN_X, y:center.y-ORIGIN_Y }, radius);
  }

  static shadow (context, center, radius, topRadius, height, minHeight) {
    let
      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
    let 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);
    }
  }

  static hitArea (context, center, radius, topRadius, height, minHeight, color) {
    let
      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
    let 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();
  }

  static _circle (context, center, radius) {
    context.beginPath();
    context.arc(center.x, center.y, radius, 0, PI*2);
    context.fill();
  }

    // http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Tangents_between_two_circles
  static _tangents (c1, r1, c2, r2) {
    let
      dx = c1.x-c2.x,
      dy = c1.y-c2.y,
      dr = r1-r2,
      sqdist = (dx*dx) + (dy*dy);

    if (sqdist <= dr*dr) {
      return;
    }

    let 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 (let 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;
  }
}

class Pyramid {

  static draw (context, polygon, center, height, minHeight, color, altColor) {
    let
      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 (let 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();
      }
    }
  }

  static _triangle (context, a, b, c) {
    context.moveTo(a.x, a.y);
    context.lineTo(b.x, b.y);
    context.lineTo(c.x, c.y);
  }

  static _ring (context, polygon) {
    context.moveTo(polygon[0]-ORIGIN_X, polygon[1]-ORIGIN_Y);
    for (let i = 2, il = polygon.length-1; i < il; i += 2) {
      context.lineTo(polygon[i]-ORIGIN_X, polygon[i+1]-ORIGIN_Y);
    }
  }

  static shadow (context, polygon, center, height, minHeight) {
    let
      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 (let 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);
      }
    }
  }

  static hitArea (context, polygon, center, height, minHeight, color) {
    let
      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 (let 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();
  }
}

let animTimer;

function fadeIn() {
  if (animTimer) {
    return;
  }

  animTimer = setInterval(t => {
    let dataItems = Data.items,
      isNeeded = false;

    for (let 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);
}

class Layers {

  static init () {
    Layers.container.className = 'osmb-container';

    // TODO: improve this
    Shadows.init(Layers.createContext(Layers.container));
    Simplified.init(Layers.createContext(Layers.container));
    Buildings.init(Layers.createContext(Layers.container));
    Picking.init(Layers.createContext());
  }

  static clear () {
    Shadows.clear();
    Simplified.clear();
    Buildings.clear();
    Picking.clear();
  }

  static setOpacity (opacity) {
    Shadows.setOpacity(opacity);
    Simplified.setOpacity(opacity);
    Buildings.setOpacity(opacity);
    Picking.setOpacity(opacity);
  }

  static render (quick) {
    // show on high zoom levels only
    if (ZOOM < MIN_ZOOM) {
      Layers.clear();
      return;
    }

    // don't render during zoom
    if (IS_ZOOMING) {
      return;
    }

    requestAnimationFrame(f => {
      if (!quick) {
        Shadows.render();
        Simplified.render();
        //HitAreas.render(); // TODO: do this on demand
      }
      Buildings.render();
    });
  }

  static createContext (container) {
    let canvas = document.createElement('CANVAS');
    canvas.className = 'osmb-layer';

    let context = canvas.getContext('2d');
    context.lineCap   = 'round';
    context.lineJoin  = 'round';
    context.lineWidth = 1;
    context.imageSmoothingEnabled = false;

    Layers.items.push(canvas);
    if (container) {
      container.appendChild(canvas);
    }

    return context;
  }

  static appendTo (parentNode) {
    parentNode.appendChild(Layers.container);
  }

  static remove () {
    Layers.container.parentNode.removeChild(Layers.container);
  }

  static setSize (width, height) {
    Layers.items.forEach(canvas => {
      canvas.width  = width;
      canvas.height = height;
    });
  }

  // usually called after move: container jumps by move delta, cam is reset
  static setPosition (x, y) {
    Layers.container.style.left = x +'px';
    Layers.container.style.top  = y +'px';
  }
}

Layers.container = document.createElement('DIV');
Layers.items = [];

class Buildings {

  static init (context) {
    this.context = context;
  }

  static clear () {
    this.context.clearRect(0, 0, WIDTH, HEIGHT);
  }

  static setOpacity (opacity) {
    this.context.canvas.style.opacity = opacity;
  }

  static project (p, m) {
    return {
      x: (p.x-CAM_X) * m + CAM_X <<0,
      y: (p.y-CAM_Y) * m + CAM_Y <<0
    };
  }

  static render () {
    this.clear();
    
    let
      context = this.context,
      item,
      h, mh,
      sortCam = { x:CAM_X+ORIGIN_X, y:CAM_Y+ORIGIN_Y },
      footprint,
      wallColor, altColor, roofColor,
      dataItems = Data.items;

    dataItems.sort((a, b) => {
      return (a.minHeight-b.minHeight) || getDistance(b.center, sortCam) - getDistance(a.center, sortCam) || (b.height-a.height);
    });

    for (let 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:         Extrusion.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, ''+ Qolor.parse(roofColor).lightness(0.9));             break;
        case 'dome':    Cylinder.draw(context, item.center, item.radius, item.radius/2, h+item.roofHeight, h, roofColor, ''+ Qolor.parse(roofColor).lightness(0.9)); break;
        case 'pyramid': Pyramid.draw(context, footprint, item.center, h+item.roofHeight, h, roofColor, Qolor.parse(roofColor).lightness(0.9));                       break;
      }
    }
  }
}

class Simplified {

  static init (context) {
    this.context = context;
  }

  static clear () {
    this.context.clearRect(0, 0, WIDTH, HEIGHT);
  }

  static setOpacity (opacity) {
    this.context.canvas.style.opacity = opacity;
  }

  static isSimple (item) {
    return (ZOOM <= Simplified.MAX_ZOOM && item.height+item.roofHeight < Simplified.MAX_HEIGHT);
  }

  static render () {
    this.clear();
    
    let context = this.context;

    // show on high zoom levels only and avoid rendering during zoom
    if (ZOOM > Simplified.MAX_ZOOM) {
      return;
    }

    let
      item,
      footprint,
      dataItems = Data.items;

    for (let i = 0, il = dataItems.length; i < il; i++) {
      item = dataItems[i];

      if (item.height >= Simplified.MAX_HEIGHT) {
        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: Extrusion.simplified(context, footprint, item.holes);
      }
    }
  }
}

Simplified.MAX_ZOOM = 16; // max zoom where buildings could render simplified
Simplified.MAX_HEIGHT = 5; // max building height in order to be simple

class Shadows {

  static init (context) {
    this.context = context;
  }

  static clear () {
    this.context.clearRect(0, 0, WIDTH, HEIGHT);
  }

  static setOpacity (opacity) {
    this.opacity = opacity;
  }

  static project (p, h) {
    return {
      x: p.x + this.direction.x*h,
      y: p.y + this.direction.y*h
    };
  }

  static render () {
    this.clear();
    
    let
      context = this.context,
      screenCenter,
      sun, length, alpha;

    // 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;

    let
      i, il,
      item,
      h, mh,
      footprint,
      dataItems = Data.items;

    context.canvas.style.opacity = alpha / (this.opacity * 2);
    context.shadowColor = this.blurColor;
    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:         Extrusion.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();
  }
}

Shadows.color = '#666666';
Shadows.blurColor = '#000000';
Shadows.date = new Date();
Shadows.direction = { x:0, y:0 };
Shadows.opacity = 1;



class Picking {

  static init (context) {
    this.context = context;
  }

  static setOpacity (opacity) {}

  static clear () {}

  static reset () {
    this._idMapping = [null];
  }

  static render () {
    if (this._timer) {
      return;
    }
    let self = this;
    this._timer = setTimeout(t => {
      self._timer = null;
      self._render();
    }, 500);
  }

  static _render () {
    this.clear();
    
    let
      context = this.context,
      item,
      h, mh,
      sortCam = { x:CAM_X+ORIGIN_X, y:CAM_Y+ORIGIN_Y },
      footprint,
      color,
      dataItems = Data.items;

    dataItems.sort((a, b) => {
      return (a.minHeight-b.minHeight) || getDistance(b.center, sortCam) - getDistance(a.center, sortCam) || (b.height-a.height);
    });

    for (let 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:         Extrusion.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;
      }
    }

    // otherwise fails on size 0
    if (WIDTH && HEIGHT) {
      this._imageData = this.context.getImageData(0, 0, WIDTH, HEIGHT).data;
    }
  }

  static getIdFromXY (x, y) {
    let imageData = this._imageData;
    if (!imageData) {
      return;
    }
    let pos = 4*((y|0) * WIDTH + (x|0));
    let index = imageData[pos] | (imageData[pos+1]<<8) | (imageData[pos+2]<<16);
    return this._idMapping[index];
  }

  static idToColor (id) {
    let index = this._idMapping.indexOf(id);
    if (index === -1) {
      this._idMapping.push(id);
      index = this._idMapping.length-1;
    }
    let r =  index       & 0xff;
    let g = (index >>8)  & 0xff;
    let b = (index >>16) & 0xff;
    return 'rgb('+ [r, g, b].join(',') +')';
  }
}

Picking._idMapping = [null];


class Debug {

  static point (x, y, color, size) {
    const context = this.context;
    context.fillStyle = color || '#ffcc00';
    context.beginPath();
    context.arc(x, y, size || 3, 0, 2*PI);
    context.closePath();
    context.fill();
  }

  static line (ax, ay, bx, by, color) {
    const context = this.context;
    context.strokeStyle = color || '#ffcc00';
    context.beginPath();
    context.moveTo(ax, ay);
    context.lineTo(bx, by);
    context.closePath();
    context.stroke();
  }
}


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;
  MAP_SIZE = MAP_TILE_SIZE <<ZOOM;

  const center = pixelToGeo(ORIGIN_X+CENTER_X, ORIGIN_Y+CENTER_Y);
  const a = geoToPixel(center.latitude, 0);
  const b = geoToPixel(center.latitude, 1);
  PIXEL_PER_DEG = b.x-a.x;

  Layers.setOpacity(Math.pow(0.95, ZOOM-MIN_ZOOM));

  WALL_COLOR_STR = ''+ WALL_COLOR;
  ALT_COLOR_STR  = ''+ ALT_COLOR;
  ROOF_COLOR_STR = ''+ ROOF_COLOR;
}

function onResize (e) {
  setSize(e);
  Layers.render();
  Data.update();
}

function onMoveEnd (e) {
  Layers.render();
  Data.update(); // => fadeIn() => Layers.render()
}

function onZoomStart () {
  IS_ZOOMING = true;
}

function onZoomEnd (e) {
  IS_ZOOMING = false;
  const factor = Math.pow(2, e.zoom-ZOOM);

  setZoom(e.zoom);
  // Layers.render(); // TODO: requestAnimationFrame() causes flickering because layers are already cleared

  // show on high zoom levels only
  if (ZOOM <= MIN_ZOOM) {
    Layers.clear();
    return;
  }

  Data.scale(factor);

  Shadows.render();
  Simplified.render();
  Buildings.render();

  Data.update(); // => fadeIn()
}

// based on a pull request from Jérémy Judéaux (https://github.com/Volune)

class OSMBuildings extends ol.layer.Layer {

  constructor (map) {
    super(OSMBuildings.name, {projection: 'EPSG:900913'});

    this.offset = {x: 0, y: 0}; // cumulative cam offset during moveBy()

    Layers.init();
    if (map) {
      map.addLayer(this);
    }
  }

  addTo (map) {
    this.setMap(map);
    return this;
  }

  setOrigin () {
    let map = this.map,
      origin = map.getLonLatFromPixel(new OpenLayers.Pixel(0, 0)),
      res = map.resolution,
      ext = this.maxExtent,
      x = (origin.lon - ext.left) / res << 0,
      y = (ext.top - origin.lat) / res << 0;
    setOrigin({x: x, y: y});
  }

  setMap (map) {
    if (!this.map) {
      super.setMap.call(this, map);
    }
    Layers.appendTo(this.div);
    setSize({width: map.size.w, height: map.size.h});
    setZoom(map.zoom);
    this.setOrigin();

    let layerProjection = this.projection;
    map.events.register('click', map, e => {
      let id = Picking.getIdFromXY(e.xy.x, e.xy.y);
      if (id) {
        let geo = map.getLonLatFromPixel(e.xy).transform(layerProjection, this.projection);
        onClick({feature: id, lat: geo.lat, lon: geo.lon});
      }
    });

    Data.update();
  }

  removeMap (map) {
    Layers.remove();
    super.removeMap.call(this, map);
    this.map = null;
  }

  onMapResize () {
    let map = this.map;
    super.onMapResize.call(this);
    onResize({width: map.size.w, height: map.size.h});
  }

  moveTo (bounds, zoomChanged, isDragging) {
    let
      map = this.map,
      res = super.moveTo.call(this, bounds, zoomChanged, isDragging);

    if (!isDragging) {
      let
        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;
  }

  moveByPx (dx, dy) {
    this.offset.x += dx;
    this.offset.y += dy;
    let res = super.moveByPx.call(this, dx, dy);
    moveCam(this.offset);
    return res;
  }
}

OSMBuildings.name = 'OSM Buildings';
OSMBuildings.attribution = ATTRIBUTION;
OSMBuildings.isBaseLayer = false;
OSMBuildings.alwaysInRange = true;

 return OSMBuildings;
}());

================================================
FILE: dist/OSMBuildings-OpenLayers.js
================================================
const OSMBuildings=function(){const e=Math,t=e.exp,i=e.log,r=e.sin,a=e.cos,s=e.tan,o=e.atan,n=e.atan2,l=e.min,c=e.max,h=e.sqrt,f=e.ceil,d=e.pow;class u{constructor(e,t,i,r=1){this.r=this._clamp(e,1),this.g=this._clamp(t,1),this.b=this._clamp(i,1),this.a=this._clamp(r,1)}static parse(e){if("string"==typeof e){let t;if(e=e.toLowerCase(),t=(e=u.w3cColors[e]||e).match(/^#?(\w{2})(\w{2})(\w{2})$/))return new u(parseInt(t[1],16)/255,parseInt(t[2],16)/255,parseInt(t[3],16)/255);if(t=e.match(/^#?(\w)(\w)(\w)$/))return new u(parseInt(t[1]+t[1],16)/255,parseInt(t[2]+t[2],16)/255,parseInt(t[3]+t[3],16)/255);if(t=e.match(/rgba?\((\d+)\D+(\d+)\D+(\d+)(\D+([\d.]+))?\)/))return new u(parseFloat(t[1])/255,parseFloat(t[2])/255,parseFloat(t[3])/255,t[4]?parseFloat(t[5]):1)}return new u}static fromHSL(e,t,i,r){const a=(new u).fromHSL(e,t,i);return a.a=void 0===r?1:r,a}_hue2rgb(e,t,i){return i<0&&(i+=1),i>1&&(i-=1),i<1/6?e+6*(t-e)*i:i<.5?t:i<2/3?e+(t-e)*(2/3-i)*6:e}_clamp(e,t){if(void 0!==e)return Math.min(t,Math.max(0,e||0))}isValid(){return void 0!==this.r&&void 0!==this.g&&void 0!==this.b}toHSL(){if(!this.isValid())return;const e=Math.max(this.r,this.g,this.b),t=Math.min(this.r,this.g,this.b),i=e-t,r=(e+t)/2;if(!i)return{h:0,s:0,l:r};const a=r>.5?i/(2-e-t):i/(e+t);let s;switch(e){case this.r:s=(this.g-this.b)/i+(this.g<this.b?6:0);break;case this.g:s=(this.b-this.r)/i+2;break;case this.b:s=(this.r-this.g)/i+4}return s*=60,{h:s,s:a,l:r}}fromHSL(e,t,i){if(0===t)return this.r=this.g=this.b=i,this;const r=i<.5?i*(1+t):i+t-i*t,a=2*i-r;return e/=360,this.r=this._hue2rgb(a,r,e+1/3),this.g=this._hue2rgb(a,r,e),this.b=this._hue2rgb(a,r,e-1/3),this}toString(){if(this.isValid())return 1===this.a?"#"+((1<<24)+(Math.round(255*this.r)<<16)+(Math.round(255*this.g)<<8)+Math.round(255*this.b)).toString(16).slice(1,7):`rgba(${Math.round(255*this.r)},${Math.round(255*this.g)},${Math.round(255*this.b)},${this.a.toFixed(2)})`}toArray(){if(this.isValid)return[this.r,this.g,this.b]}hue(e){const t=this.toHSL();return this.fromHSL(t.h+e,t.s,t.l)}saturation(e){const t=this.toHSL();return this.fromHSL(t.h,t.s*e,t.l)}lightness(e){const t=this.toHSL();return this.fromHSL(t.h,t.s,t.l*e)}clone(){return new u(this.r,this.g,this.b,this.a)}}function p(){const e=Math,t=e.PI,i=e.sin,r=e.cos,a=e.tan,s=e.asin,o=e.atan2,n=t/180,l=23.4397*n;function c(e,t,s){return o(i(e),r(e)*i(t)-a(s)*r(t))}function h(e,t,a){return s(i(t)*i(a)+r(t)*r(a)*r(e))}return function(e,f,d){const u=n*-d,p=n*f,g=function(e){return function(e){return e.valueOf()/864e5-.5+2440588}(e)-2451545}(e),y=function(e){return n*(357.5291+.98560028*e)}(g),m=function(e,i){return e+i+102.9372*n+t}(y,function(e){return n*(1.9148*i(e)+.02*i(2*e)+3e-4*i(3*e))}(y)),x=(k=m,s(i(_=0)*r(l)+r(_)*i(l)*i(k))),b=function(e,t){return o(i(e)*r(l)-a(t)*i(l),r(e))}(m,0),w=function(e,t){return n*(280.16+360.9856235*e)-t}(g,u)-b;var k,_;return{altitude:h(w,p,x),azimuth:c(w,p,x)-t/2}}}u.w3cColors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},"undefined"!=typeof module&&(module.exports=u);const g={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"},y={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"};function m(e){return"#"===(e=e.toLowerCase())[0]?e:g[y[e]||e]||null}function x(e,t){if(function(e){let t,i,r,a,s=0;for(let o=0,n=e.length-3;o<n;o+=2)t=e[o],i=e[o+1],r=e[o+2],a=e[o+3],s+=t*a-r*i;return s/2>0?"CW":"CCW"}(e)===t)return e;let i=[];for(let t=e.length-2;t>=0;t-=2)i.push(e[t],e[t+1]);return i}function b(e){const t={};e=e||{},t.height=e.height||(e.levels?3*e.levels:G),t.minHeight=e.minHeight||(e.minLevel?3*e.minLevel:0);const i=e.material?m(e.material):e.wallColor||e.color;i&&(t.wallColor=i);const r=e.roofMaterial?m(e.roofMaterial):e.roofColor;switch(r&&(t.roofColor=r),e.shape){case"cylinder":case"cone":case"dome":case"sphere":t.shape=e.shape,t.isRotational=!0;break;case"pyramid":t.shape=e.shape}switch(e.roofShape){case"cone":case"dome":t.roofShape=e.roofShape,t.isRotational=!0;break;case"pyramid":t.roofShape=e.roofShape}return t.roofShape&&e.roofHeight?(t.roofHeight=e.roofHeight,t.height=c(0,t.height-t.roofHeight)):t.roofHeight=0,t}function w(e){let t,i,r=[];switch(e.type){case"GeometryCollection":r=[];for(let t=0,a=e.geometries.length;t<a;t++)(i=w(e.geometries[t]))&&r.push.apply(r,i);return r;case"MultiPolygon":r=[];for(let t=0,a=e.coordinates.length;t<a;t++)(i=w({type:"Polygon",coordinates:e.coordinates[t]}))&&r.push.apply(r,i);return r;case"Polygon":t=e.coordinates;break;default:return[]}let a,s=[],o=[];a=t[0];for(let e=0,t=a.length;e<t;e++)s.push(a[e][1],a[e][0]);s=x(s,"CW");for(let e=0,i=t.length-1;e<i;e++){a=t[e+1],o[e]=[];for(let t=0,i=a.length;t<i;t++)o[e].push(a[t][1],a[t][0]);o[e]=x(o[e],"CCW")}return[{outer:s,inner:o.length?o:null}]}function k(e){let t={};for(const i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t}let _,v,H,S,C,M,T=Math.PI,P=T/2,I=T/4,j=0,O=0,A=0,L=0,z=0,F=0,D=u.parse("rgba(200, 190, 180)"),R=D.lightness(.8),q=D.lightness(1.2),N=""+D,E=""+R,X=""+q,B=0,G=5;function V(e,t){const i=e.x-t.x,r=e.y-t.y;return i*i+r*r}function $(e,t,i,r,a,s){let o,n=a-i,l=s-r;return 0===n&&0===l||(o=((e-i)*n+(t-r)*l)/(n*n+l*l),o>1?(i=a,r=s):o>0&&(i+=n*o,r+=l*o)),n=e-i,l=t-r,n*n+l*l}function W(e){let t=180,i=-180;for(let r=0,a=e.length;r<a;r+=2)t=l(t,e[r+1]),i=c(i,e[r+1]);return(i-t)/2}function J(e,i){const r={};return e/=v,i/=v,r.latitude=i<=0?90:i>=1?-90:function(e){return e/T*180}(2*o(t(T*(1-2*i)))-P),r.longitude=360*(1===e?1:(e%1+1)%1)-180,r}function Z(e,t){const r=l(1,c(0,.5-i(s(I+P*e/180))/T/2));return{x:(t/360+.5)*v<<0,y:r*v<<0}}function Y(e){const t=j+z,i=O+F;for(let r=0,a=e.length-3;r<a;r+=2)if(e[r]>z&&e[r]<t&&e[r+1]>F&&e[r+1]<i)return!0;return!1}let U,K={},Q=[],ee=0;class te{static getPixelFootprint(e){let t,i=new Int32Array(e.length);for(let r=0,a=e.length-1;r<a;r+=2)t=Z(e[r],e[r+1]),i[r]=t.x,i[r+1]=t.y;if(i=function(e){let t,i,r,a=e.length/2,s=new Uint8Array(a),o=0,n=a-1,l=[],c=[],h=[];for(s[o]=s[n]=1;n;){t=0;for(let a=o+1;a<n;a++)i=$(e[2*a],e[2*a+1],e[2*o],e[2*o+1],e[2*n],e[2*n+1]),i>t&&(r=a,t=i);t>2&&(s[r]=1,l.push(o),c.push(r),l.push(r),c.push(n)),o=l.pop(),n=c.pop()}for(let t=0;t<a;t++)s[t]&&h.push(e[2*t],e[2*t+1]);return h}(i),!(i.length<8))return i}static resetItems(){this.items=[],this.cache={},ce.reset()}static addRenderItems(e,t){let i,r,a,s=class{static read(e){if(!e||"FeatureCollection"!==e.type)return[];const t=e.features,i=[];for(let e=0,r=t.length;e<r;e++){const r=t[e];if("Feature"!==r.type)continue;const a=b(r.properties),s=w(r.geometry);for(let e=0,t=s.length;e<t;e++){const t=k(a);t.footprint=s[e].outer,t.isRotational&&(t.radius=W(t.footprint)),s[e].inner&&(t.holes=s[e].inner),(r.id||r.properties.id)&&(t.id=r.id||r.properties.id),r.properties.relationId&&(t.relationId=r.properties.relationId),i.push(t)}}return i}}.read(e);for(let e=0,o=s.length;e<o;e++)i=s[e],a=i.id||[i.footprint[0],i.footprint[1],i.height,i.minHeight].join(","),this.cache[a]||(r=this.scaleItem(i))&&(r.scale=t?0:1,this.items.push(r),this.cache[a]=1);!function(){if(U)return;U=setInterval(e=>{let t=te.items,i=!1;for(let e=0,r=t.length;e<r;e++)t[e].scale<1&&(t[e].scale+=.1,t[e].scale>1&&(t[e].scale=1),i=!0);se.render(),i||(clearInterval(U),U=null)},33)}()}static scalePolygon(e,t){return e.map(e=>e*t)}static scale(e){te.items=te.items.map(t=>{if(t.height*=e,t.minHeight*=e,t.footprint=te.scalePolygon(t.footprint,e),t.center.x*=e,t.center.y*=e,t.radius&&(t.radius*=e),t.holes)for(let i=0,r=t.holes.length;i<r;i++)t.holes[i]=te.scalePolygon(t.holes[i],e);return t.roofHeight*=e,t})}static scaleItem(e){let t,i={},r=6/d(2,_-15);if(e.id&&(i.id=e.id),i.height=l(e.height/r,H),i.minHeight=isNaN(e.minHeight)?0:e.minHeight/r,!(i.minHeight>H)&&(i.footprint=this.getPixelFootprint(e.footprint),i.footprint)){if(i.center=function(e){let t=1/0,i=-1/0,r=1/0,a=-1/0;for(let s=0,o=e.length-3;s<o;s+=2)t=l(t,e[s]),i=c(i,e[s]),r=l(r,e[s+1]),a=c(a,e[s+1]);return{x:t+(i-t)/2<<0,y:r+(a-r)/2<<0}}(i.footprint),e.radius&&(i.radius=e.radius*B),e.shape&&(i.shape=e.shape),e.roofShape&&(i.roofShape=e.roofShape),"cone"!==i.roofShape&&"dome"!==i.roofShape||i.shape||!function(e){const t=e.length;if(t<16)return!1;let i=1/0,r=-1/0,a=1/0,s=-1/0;for(let o=0;o<t-1;o+=2)i=Math.min(i,e[o]),r=Math.max(r,e[o]),a=Math.min(a,e[o+1]),s=Math.max(s,e[o+1]);const o=r-i,n=s-a,l=o/n;if(l<.85||l>1.15)return!1;const c={x:i+o/2,y:a+n/2},h=(o+n)/4,f=h*h;for(let i=0;i<t-1;i+=2){const t=V({x:e[i],y:e[i+1]},c);if(t/f<.8||t/f>1.2)return!1}return!0}(i.footprint)||(i.shape="cylinder"),e.holes){let t;i.holes=[];for(let r=0,a=e.holes.length;r<a;r++)(t=this.getPixelFootprint(e.holes[r]))&&i.holes.push(t)}return e.wallColor&&(t=u.parse(e.wallColor))&&(i.altColor=""+t.lightness(.8),i.wallColor=""+t),e.roofColor&&(t=u.parse(e.roofColor))&&(i.roofColor=""+t),e.relationId&&(i.relationId=e.relationId),i.hitColor=ce.idToColor(e.relationId||e.id),i.roofHeight=isNaN(e.roofHeight)?0:e.roofHeight/r,i.height+i.roofHeight<=i.minHeight?void 0:i}}static set(e){this.resetItems(),this._staticData=e,this.addRenderItems(this._staticData,!0)}static load(e,t){this.src=e||"https://{s}.data.osmbuildings.org/0.2/{k}/tile/{z}/{x}/{y}.json".replace("{k}",t||"anonymous"),this.update()}static update(){if(this.resetItems(),!(_<15)&&(this._staticData&&this.addRenderItems(this._staticData),this.src)){let t,i,r=16,a=256,s=_>r?a<<_-r:a>>r-_,o=z/s<<0,n=F/s<<0,l=f((z+j)/s),c=f((F+O)/s),h=this;function e(e){h.addRenderItems(e)}for(i=n;i<=c;i++)for(t=o;t<=l;t++)this.loadTile(t,i,r,e)}}static loadTile(e,t,i,r){let a="abcd"[(e+t)%4],s=this.src.replace("{s}",a).replace("{x}",e).replace("{y}",t).replace("{z}",i);return class{static loadJSON(e,t){return function(e,t){if(K[e])return void(t&&t(K[e]));const i=new XMLHttpRequest;return i.onreadystatechange=function(){if(4===i.readyState&&!(!i.status||i.status<200||i.status>299)&&t&&i.responseText){const r=i.responseText;for(K[e]=r,Q.push({url:e,size:r.length}),ee+=r.length,t(r);ee>5242880;){let e=Q.shift();ee-=e.size,delete K[e.url]}}},i.open("GET",e),i.send(null),i}(e,e=>{let i;try{i=JSON.parse(e)}catch(e){}t(i)})}}.loadJSON(s,r)}}te.cache={},te.items=[];class ie{static draw(e,t,i,r,a,s,o,n){let l=this._extrude(e,t,r,a,s,o),c=[];if(i)for(let t=0,n=i.length;t<n;t++)c[t]=this._extrude(e,i[t],r,a,s,o);if(e.fillStyle=n,e.beginPath(),this._ring(e,l),i)for(let t=0,i=c.length;t<i;t++)this._ring(e,c[t]);e.closePath(),e.fill()}static _extrude(e,t,i,r,a,s){let o,n,l=450/(450-i),c=450/(450-r),h={x:0,y:0},f={x:0,y:0},d=[];for(let i=0,u=t.length-3;i<u;i+=2)h.x=t[i]-z,h.y=t[i+1]-F,f.x=t[i+2]-z,f.y=t[i+3]-F,o=oe.project(h,l),n=oe.project(f,l),r&&(h=oe.project(h,c),f=oe.project(f,c)),(f.x-h.x)*(o.y-h.y)>(o.x-h.x)*(f.y-h.y)&&(h.x<f.x&&h.y<f.y||h.x>f.x&&h.y>f.y?e.fillStyle=s:e.fillStyle=a,e.beginPath(),this._ring(e,[f.x,f.y,h.x,h.y,o.x,o.y,n.x,n.y]),e.closePath(),e.fill()),d[i]=o.x,d[i+1]=o.y;return d}static _ring(e,t){e.moveTo(t[0],t[1]);for(let i=2,r=t.length-1;i<r;i+=2)e.lineTo(t[i],t[i+1])}static simplified(e,t,i){if(e.beginPath(),this._ringAbs(e,t),i)for(let t=0,r=i.length;t<r;t++)this._ringAbs(e,i[t]);e.closePath(),e.fill()}static _ringAbs(e,t){e.moveTo(t[0]-z,t[1]-F);for(let i=2,r=t.length-1;i<r;i+=2)e.lineTo(t[i]-z,t[i+1]-F)}static shadow(e,t,i,r,a){let s,o,n=null,l={x:0,y:0},c={x:0,y:0};for(let i=0,h=t.length-3;i<h;i+=2)l.x=t[i]-z,l.y=t[i+1]-F,c.x=t[i+2]-z,c.y=t[i+3]-F,s=le.project(l,r),o=le.project(c,r),a&&(l=le.project(l,a),c=le.project(c,a)),(c.x-l.x)*(s.y-l.y)>(s.x-l.x)*(c.y-l.y)?(1===n&&e.lineTo(l.x,l.y),n=0,i||e.moveTo(l.x,l.y),e.lineTo(c.x,c.y)):(0===n&&e.lineTo(s.x,s.y),n=1,i||e.moveTo(s.x,s.y),e.lineTo(o.x,o.y));if(i)for(let t=0,r=i.length;t<r;t++)this._ringAbs(e,i[t])}static hitArea(e,t,i,r,a,s){let o,n,l=null,c={x:0,y:0},h={x:0,y:0},f=450/(450-r),d=450/(450-a);e.fillStyle=s,e.beginPath();for(let i=0,r=t.length-3;i<r;i+=2)c.x=t[i]-z,c.y=t[i+1]-F,h.x=t[i+2]-z,h.y=t[i+3]-F,o=oe.project(c,f),n=oe.project(h,f),a&&(c=oe.project(c,d),h=oe.project(h,d)),(h.x-c.x)*(o.y-c.y)>(o.x-c.x)*(h.y-c.y)?(1===l&&e.lineTo(c.x,c.y),l=0,i||e.moveTo(c.x,c.y),e.lineTo(h.x,h.y)):(0===l&&e.lineTo(o.x,o.y),l=1,i||e.moveTo(o.x,o.y),e.lineTo(n.x,n.y));e.closePath(),e.fill()}}class re{static draw(e,t,i,r,a,s,o,l,c){let h,f,d={x:t.x-z,y:t.y-F},u=450/(450-a),p=450/(450-s),g=oe.project(d,u);r*=u,s&&(d=oe.project(d,p),i*=p);let y=this._tangents(d,i,g,r);y?(h=n(y[0].y1-d.y,y[0].x1-d.x),f=n(y[1].y1-d.y,y[1].x1-d.x)):(h=1.5*T,f=1.5*T),e.fillStyle=o,e.beginPath(),e.arc(g.x,g.y,r,P,h,!0),e.arc(d.x,d.y,i,h,P),e.closePath(),e.fill(),e.fillStyle=l,e.beginPath(),e.arc(g.x,g.y,r,f,P,!0),e.arc(d.x,d.y,i,P,f),e.closePath(),e.fill(),e.fillStyle=c,this._circle(e,g,r)}static simplified(e,t,i){this._circle(e,{x:t.x-z,y:t.y-F},i)}static shadow(e,t,i,r,a,s){let o,l,c={x:t.x-z,y:t.y-F},h=le.project(c,a);s&&(c=le.project(c,s));let f=this._tangents(c,i,h,r);f?(o=n(f[0].y1-c.y,f[0].x1-c.x),l=n(f[1].y1-c.y,f[1].x1-c.x),e.moveTo(f[1].x2,f[1].y2),e.arc(h.x,h.y,r,l,o),e.arc(c.x,c.y,i,o,l)):(e.moveTo(c.x+i,c.y),e.arc(c.x,c.y,i,0,2*T))}static hitArea(e,t,i,r,a,s,o){let l,c,h={x:t.x-z,y:t.y-F},f=450/(450-a),d=450/(450-s),u=oe.project(h,f);r*=f,s&&(h=oe.project(h,d),i*=d);let p=this._tangents(h,i,u,r);e.fillStyle=o,e.beginPath(),p?(l=n(p[0].y1-h.y,p[0].x1-h.x),c=n(p[1].y1-h.y,p[1].x1-h.x),e.moveTo(p[1].x2,p[1].y2),e.arc(u.x,u.y,r,c,l),e.arc(h.x,h.y,i,l,c)):(e.moveTo(h.x+i,h.y),e.arc(h.x,h.y,i,0,2*T)),e.closePath(),e.fill()}static _circle(e,t,i){e.beginPath(),e.arc(t.x,t.y,i,0,2*T),e.fill()}static _tangents(e,t,i,r){let a=e.x-i.x,s=e.y-i.y,o=t-r,n=a*a+s*s;if(n<=o*o)return;let l,f,d,u=h(n),p=-a/u,g=-s/u,y=o/u,m=[];l=h(c(0,1-y*y));for(let a=1;a>=-1;a-=2)f=p*y-a*l*g,d=g*y+a*l*p,m.push({x1:e.x+t*f<<0,y1:e.y+t*d<<0,x2:i.x+r*f<<0,y2:i.y+r*d<<0});return m}}class ae{static draw(e,t,i,r,a,s,o){let n={x:i.x-z,y:i.y-F},l=450/(450-r),c=450/(450-a),h=oe.project(n,l),f={x:0,y:0},d={x:0,y:0};for(let i=0,r=t.length-3;i<r;i+=2)f.x=t[i]-z,f.y=t[i+1]-F,d.x=t[i+2]-z,d.y=t[i+3]-F,a&&(f=oe.project(f,c),d=oe.project(d,c)),(d.x-f.x)*(h.y-f.y)>(h.x-f.x)*(d.y-f.y)&&(f.x<d.x&&f.y<d.y||f.x>d.x&&f.y>d.y?e.fillStyle=o:e.fillStyle=s,e.beginPath(),this._triangle(e,f,d,h),e.closePath(),e.fill())}static _triangle(e,t,i,r){e.moveTo(t.x,t.y),e.lineTo(i.x,i.y),e.lineTo(r.x,r.y)}static _ring(e,t){e.moveTo(t[0]-z,t[1]-F);for(let i=2,r=t.length-1;i<r;i+=2)e.lineTo(t[i]-z,t[i+1]-F)}static shadow(e,t,i,r,a){let s={x:0,y:0},o={x:0,y:0},n={x:i.x-z,y:i.y-F},l=le.project(n,r);for(let i=0,r=t.length-3;i<r;i+=2)s.x=t[i]-z,s.y=t[i+1]-F,o.x=t[i+2]-z,o.y=t[i+3]-F,a&&(s=le.project(s,a),o=le.project(o,a)),(o.x-s.x)*(l.y-s.y)>(l.x-s.x)*(o.y-s.y)&&this._triangle(e,s,o,l)}static hitArea(e,t,i,r,a,s){let o={x:i.x-z,y:i.y-F},n=450/(450-r),l=450/(450-a),c=oe.project(o,n),h={x:0,y:0},f={x:0,y:0};e.fillStyle=s,e.beginPath();for(let i=0,r=t.length-3;i<r;i+=2)h.x=t[i]-z,h.y=t[i+1]-F,f.x=t[i+2]-z,f.y=t[i+3]-F,a&&(h=oe.project(h,l),f=oe.project(f,l)),(f.x-h.x)*(c.y-h.y)>(c.x-h.x)*(f.y-h.y)&&this._triangle(e,h,f,c);e.closePath(),e.fill()}}class se{static init(){se.container.className="osmb-container",le.init(se.createContext(se.container)),ne.init(se.createContext(se.container)),oe.init(se.createContext(se.container)),ce.init(se.createContext())}static clear(){le.clear(),ne.clear(),oe.clear(),ce.clear()}static setOpacity(e){le.setOpacity(e),ne.setOpacity(e),oe.setOpacity(e),ce.setOpacity(e)}static render(e){_<15?se.clear():M||requestAnimationFrame(t=>{e||(le.render(),ne.render()),oe.render()})}static createContext(e){let t=document.createElement("CANVAS");t.className="osmb-layer";let i=t.getContext("2d");return i.lineCap="round",i.lineJoin="round",i.lineWidth=1,i.imageSmoothingEnabled=!1,se.items.push(t),e&&e.appendChild(t),i}static appendTo(e){e.appendChild(se.container)}static remove(){se.container.parentNode.removeChild(se.container)}static setSize(e,t){se.items.forEach(i=>{i.width=e,i.height=t})}static setPosition(e,t){se.container.style.left=e+"px",se.container.style.top=t+"px"}}se.container=document.createElement("DIV"),se.items=[];class oe{static init(e){this.context=e}static clear(){this.context.clearRect(0,0,j,O)}static setOpacity(e){this.context.canvas.style.opacity=e}static project(e,t){return{x:(e.x-S)*t+S<<0,y:(e.y-C)*t+C<<0}}static render(){this.clear();let e,t,i,r,a,s,o,n=this.context,l={x:S+z,y:C+F},c=te.items;c.sort((e,t)=>e.minHeight-t.minHeight||V(t.center,l)-V(e.center,l)||t.height-e.height);for(let l=0,h=c.length;l<h;l++)if(e=c[l],!ne.isSimple(e)&&(r=e.footprint,Y(r))){switch(t=e.scale<1?e.height*e.scale:e.height,i=0,e.minHeight&&(i=e.scale<1?e.minHeight*e.scale:e.minHeight),a=e.wallColor||N,s=e.altColor||E,o=e.roofColor||X,n.strokeStyle=s,e.shape){case"cylinder":re.draw(n,e.center,e.radius,e.radius,t,i,a,s,o);break;case"cone":re.draw(n,e.center,e.radius,0,t,i,a,s);break;case"dome":re.draw(n,e.center,e.radius,e.radius/2,t,i,a,s);break;case"sphere":re.draw(n,e.center,e.radius,e.radius,t,i,a,s,o);break;case"pyramid":ae.draw(n,r,e.center,t,i,a,s);break;default:ie.draw(n,r,e.holes,t,i,a,s,o)}switch(e.roofShape){case"cone":re.draw(n,e.center,e.radius,0,t+e.roofHeight,t,o,""+u.parse(o).lightness(.9));break;case"dome":re.draw(n,e.center,e.radius,e.radius/2,t+e.roofHeight,t,o,""+u.parse(o).lightness(.9));break;case"pyramid":ae.draw(n,r,e.center,t+e.roofHeight,t,o,u.parse(o).lightness(.9))}}}}class ne{static init(e){this.context=e}static clear(){this.context.clearRect(0,0,j,O)}static setOpacity(e){this.context.canvas.style.opacity=e}static isSimple(e){return _<=ne.MAX_ZOOM&&e.height+e.roofHeight<ne.MAX_HEIGHT}static render(){this.clear();let e=this.context;if(_>ne.MAX_ZOOM)return;let t,i,r=te.items;for(let a=0,s=r.length;a<s;a++)if(t=r[a],!(t.height>=ne.MAX_HEIGHT)&&(i=t.footprint,Y(i)))switch(e.strokeStyle=t.altColor||E,e.fillStyle=t.roofColor||X,t.shape){case"cylinder":case"cone":case"dome":case"sphere":re.simplified(e,t.center,t.radius);break;default:ie.simplified(e,i,t.holes)}}}ne.MAX_ZOOM=16,ne.MAX_HEIGHT=5;class le{static init(e){this.context=e}static clear(){this.context.clearRect(0,0,j,O)}static setOpacity(e){this.opacity=e}static project(e,t){return{x:e.x+this.direction.x*t,y:e.y+this.direction.y*t}}static render(){this.clear();let e,t,i,o,n=this.context;if(e=J(A+z,L+F),t=p(this.date,e.latitude,e.longitude),t.altitude<=0)return;i=1/s(t.altitude),o=i<5?.75:1/i*5,this.direction.x=a(t.azimuth)*i,this.direction.y=r(t.azimuth)*i;let l,c,h,f,d,u,g=te.items;for(n.canvas.style.opacity=o/(2*this.opacity),n.shadowColor=this.blurColor,n.fillStyle=this.color,n.beginPath(),l=0,c=g.length;l<c;l++)if(h=g[l],u=h.footprint,Y(u)){switch(f=h.scale<1?h.height*h.scale:h.height,d=0,h.minHeight&&(d=h.scale<1?h.minHeight*h.scale:h.minHeight),h.shape){case"cylinder":re.shadow(n,h.center,h.radius,h.radius,f,d);break;case"cone":re.shadow(n,h.center,h.radius,0,f,d);break;case"dome":re.shadow(n,h.center,h.radius,h.radius/2,f,d);break;case"sphere":re.shadow(n,h.center,h.radius,h.radius,f,d);break;case"pyramid":ae.shadow(n,u,h.center,f,d);break;default:ie.shadow(n,u,h.holes,f,d)}switch(h.roofShape){case"cone":re.shadow(n,h.center,h.radius,0,f+h.roofHeight,f);break;case"dome":re.shadow(n,h.center,h.radius,h.radius/2,f+h.roofHeight,f);break;case"pyramid":ae.shadow(n,u,h.center,f+h.roofHeight,f)}}n.closePath(),n.fill()}}le.color="#666666",le.blurColor="#000000",le.date=new Date,le.direction={x:0,y:0},le.opacity=1;class ce{static init(e){this.context=e}static setOpacity(e){}static clear(){}static reset(){this._idMapping=[null]}static render(){if(this._timer)return;let e=this;this._timer=setTimeout(t=>{e._timer=null,e._render()},500)}static _render(){this.clear();let e,t,i,r,a,s=this.context,o={x:S+z,y:C+F},n=te.items;n.sort((e,t)=>e.minHeight-t.minHeight||V(t.center,o)-V(e.center,o)||t.height-e.height);for(let o=0,l=n.length;o<l;o++)if(e=n[o],(a=e.hitColor)&&(r=e.footprint,Y(r))){switch(t=e.height,i=0,e.minHeight&&(i=e.minHeight),e.shape){case"cylinder":re.hitArea(s,e.center,e.radius,e.radius,t,i,a);break;case"cone":re.hitArea(s,e.center,e.radius,0,t,i,a);break;case"dome":re.hitArea(s,e.center,e.radius,e.radius/2,t,i,a);break;case"sphere":re.hitArea(s,e.center,e.radius,e.radius,t,i,a);break;case"pyramid":ae.hitArea(s,r,e.center,t,i,a);break;default:ie.hitArea(s,r,e.holes,t,i,a)}switch(e.roofShape){case"cone":re.hitArea(s,e.center,e.radius,0,t+e.roofHeight,t,a);break;case"dome":re.hitArea(s,e.center,e.radius,e.radius/2,t+e.roofHeight,t,a);break;case"pyramid":ae.hitArea(s,r,e.center,t+e.roofHeight,t,a)}}j&&O&&(this._imageData=this.context.getImageData(0,0,j,O).data)}static getIdFromXY(e,t){let i=this._imageData;if(!i)return;let r=4*((0|t)*j+(0|e)),a=i[r]|i[r+1]<<8|i[r+2]<<16;return this._idMapping[a]}static idToColor(e){let t=this._idMapping.indexOf(e);return-1===t&&(this._idMapping.push(e),t=this._idMapping.length-1),"rgb("+[255&t,t>>8&255,t>>16&255].join(",")+")"}}ce._idMapping=[null];function he(e){S=A+e.x,C=O+e.y,se.render(!0)}function fe(e){j=e.width,O=e.height,A=j/2<<0,L=O/2<<0,S=A,C=O,se.setSize(j,O),H=400}function de(e){_=e,v=256<<_;const t=J(z+A,F+L),i=Z(t.latitude,0),r=Z(t.latitude,1);B=r.x-i.x,se.setOpacity(Math.pow(.95,_-15)),N=""+D,E=""+R,X=""+q}class ue extends ol.layer.Layer{constructor(e){super(ue.name,{projection:"EPSG:900913"}),this.offset={x:0,y:0},se.init(),e&&e.addLayer(this)}addTo(e){return this.setMap(e),this}setOrigin(){let e=this.map,t=e.getLonLatFromPixel(new OpenLayers.Pixel(0,0)),i=e.resolution,r=this.maxExtent;!function(e){z=e.x,F=e.y}({x:(t.lon-r.left)/i<<0,y:(r.top-t.lat)/i<<0})}setMap(e){this.map||super.setMap.call(this,e),se.appendTo(this.div),fe({width:e.size.w,height:e.size.h}),de(e.zoom),this.setOrigin();let t=this.projection;e.events.register("click",e,i=>{let r=ce.getIdFromXY(i.xy.x,i.xy.y);if(r){let r=e.getLonLatFromPixel(i.xy).transform(t,this.projection);r.lat,r.lon}}),te.update()}removeMap(e){se.remove(),super.removeMap.call(this,e),this.map=null}onMapResize(){let e=this.map;super.onMapResize.call(this),fe({width:e.size.w,height:e.size.h}),se.render(),te.update()}moveTo(e,t,i){let r=this.map,a=super.moveTo.call(this,e,t,i);if(!i){let e=parseInt(r.layerContainerDiv.style.left,10),t=parseInt(r.layerContainerDiv.style.top,10);this.div.style.left=-e+"px",this.div.style.top=-t+"px"}return this.setOrigin(),this.offset.x=0,this.offset.y=0,he(this.offset),t?function(e){M=!1;const t=Math.pow(2,e.zoom-_);de(e.zoom),_<=15?se.clear():(te.scale(t),le.render(),ne.render(),oe.render(),te.update())}({zoom:r.zoom}):(se.render(),te.update()),a}moveByPx(e,t){this.offset.x+=e,this.offset.y+=t;let i=super.moveByPx.call(this,e,t);return he(this.offset),i}}return ue.name="OSM Buildings",ue.attribution='&copy; <a href="https://osmbuildings.org">OSM Buildings</a>',ue.isBaseLayer=!1,ue.alwaysInRange=!0,ue}();

================================================
FILE: dist/OSMBuildings.css
================================================
.osmb-container {
  transform: translate3d(0, 0, 0);
  pointerEvents: none;
  position: absolute;
  left: 0;
  top: 0;
}

.osmb-container.zoom-animation {
  transition-duration: 250ms;
  transition-property: transform;
  transform-origin: 50% 50%;
}

.osmb-layer {
  transform: translate3d(0, 0, 0);
  image-rendering: optimizeSpeed;
  position: absolute;
  left: 0;
  top: 0;
}


================================================
FILE: dist/index-Leaflet-3db.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>OSM Buildings for Leaflet</title>
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"/>
  <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
  <link rel="stylesheet" href="OSMBuildings.css">
  <script src="OSMBuildings-Leaflet.debug.js"></script>
  <style>
    html, body {
      border: 0;
      margin: 0;
      padding: 0;
      width: 100%;
      height: 100%;
      overflow: hidden;
    }

    #map {
      height: 100%;
    }
  </style>
</head>

<body>
<div id="map"></div>

<script>
  const map = new L.Map('map').setView([40.711516, -74.011695], 16); // NYC

  // base map source
  new L.TileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(map);

  const osmb = new OSMBuildings();
  osmb.addTo(map);

  // osmb.load('https://{s}-data.3dbuildings.com/tile/{z}/{x}/{y}.json?token=xsthpfc');
  osmb.load();

</script>
</body>
</html>

================================================
FILE: dist/index-Leaflet.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>OSM Buildings Classic for Leaflet</title>
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"/>
  <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
  <link rel="stylesheet" href="OSMBuildings.css">
  <script src="OSMBuildings-Leaflet.debug.js"></script>
  <style>
    html, body {
      border: 0;
      margin: 0;
      padding: 0;
      width: 100%;
      height: 100%;
      overflow: hidden;
    }

    #map {
      height: 100%;
    }
  </style>
</head>

<body>
<div id="map"></div>

<script>
  // const map = new L.Map('map', { minZoom: 16 }).setView([52.52179, 13.39503], 18); // Berlin
  const map = new L.Map('map').setView([40.711516, -74.011695], 16); // NYC

  // new L.TileLayer('https://{s}.tiles.mapbox.com/v3/osmbuildings.kbpalbpk/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(map);
  new L.TileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(map);

  const osmb = new OSMBuildings();

  L.control.layers({}, { Buildings: osmb }).addTo(map);

  osmb.addTo(map)
    .date(new Date(2017, 5, 15, 17, 30))
    .load()
    .click(function(e) {
      console.log('feature clicked:', e);
    });

  // add GeoJSON
  map.setView([52.52179, 13.39503], 18); // Berlin Bodemuseum
  const data = {
    "type": "FeatureCollection",
    "features": [
      {
        "type": "Feature",
        "properties": { "height": 50, "color": "#ffcc00" },
        "geometry": {
          "type": "Polygon",
          "coordinates": [
            [
              [
                13.39631974697113,
                52.52184840804295
              ],
              [
                13.39496523141861,
                52.521166220963536
              ],
              [
                13.395150303840637,
                52.52101770514734
              ],
              [
                13.396652340888977,
                52.52174559105107
              ],
              [
                13.39631974697113,
                52.52184840804295
              ]
            ]
          ]
        }
      }
    ]
  };
  osmb.set(data);
</script>
</body>
</html>

================================================
FILE: dist/index-OpenLayers.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>OSM Buildings Classic for OpenLayers</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.2.1/css/ol.css"/>
  <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.2.1/build/ol.js"></script>
  <link rel="stylesheet" href="OSMBuildings.css">
  <script src="OSMBuildings-OpenLayers.debug.js"></script>
  <style>
    html, body {
      border: 0;
      margin: 0;
      padding: 0;
      width: 100%;
      height: 100%;
      overflow: hidden;
    }

    #map {
      height: 100%;
    }
  </style>
</head>

<body>
<div id="map"></div>

<script>
  const map = new ol.Map({
    target: 'map',
    layers: [
      new ol.layer.Tile({
        source: new ol.source.OSM()
      })
    ],
    view: new ol.View({
      center: ol.proj.fromLonLat([52.52179, 13.39503]), // Berlin
      // center: ol.proj.fromLonLat([40.711516, -74.011695]), // NYC
      zoom: 16
    })
  });

  const osmb = new OSMBuildings();

  osmb.addTo(map)
    .date(new Date(2017, 5, 15, 17, 30))
    .load()
    .click(function(e) {
      console.log('feature clicked:', e);
    });

  // // GeoJSON
  // // map.setView([52.52179, 13.39503], 18); // Berlin Bodemuseum
  // // const data = {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":1,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.388652,52.520222],[13.388758,52.52021],[13.389204,52.520184],[13.389735,52.520162],[13.390013,52.52015],[13.390153,52.520155],[13.391974,52.520274],[13.392177,52.52029],[13.392477,52.520319],[13.392728,52.520355],[13.393062,52.520414],[13.393416,52.520505],[13.393754,52.520615],[13.394054,52.52073],[13.394226,52.520811],[13.394393,52.520678],[13.394217,52.520597],[13.393905,52.520471],[13.39355,52.52036],[13.393176,52.520268],[13.392811,52.520205],[13.392527,52.520169],[13.392209,52.52014],[13.392006,52.520122],[13.39025,52.520002],[13.390123,52.519993],[13.389317,52.519947],[13.38872,52.5199],[13.388693,52.520002],[13.388652,52.520222]]]]}},{"type":"Feature","properties":{"id":2,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.392946,52.521681],[13.393053,52.521847],[13.393228,52.52174],[13.393363,52.521645],[13.393499,52.521549],[13.393439,52.521523],[13.394023,52.521095],[13.39398,52.521074],[13.394229,52.520907],[13.394066,52.520818],[13.3938,52.521001],[13.393842,52.521027],[13.393242,52.521434],[13.393285,52.52146],[13.392946,52.521681]]]]}},{"type":"Feature","properties":{"id":3,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.393432,52.521863],[13.39371,52.522017],[13.393874,52.521918],[13.393605,52.521756],[13.393432,52.521863]]]]}},{"type":"Feature","properties":{"id":4,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.394871,52.520148],[13.395216,52.52032],[13.395544,52.520072],[13.395374,52.519995],[13.395111,52.519962],[13.394871,52.520148]],[[13.395008,52.520144],[13.395124,52.520049],[13.395292,52.520078],[13.395128,52.520203],[13.395008,52.520144]]]]}},{"type":"Feature","properties":{"id":5,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.393699,52.5202],[13.393927,52.520312],[13.394052,52.520218],[13.393866,52.520126],[13.393746,52.520074],[13.393699,52.5202]]]]}},{"type":"Feature","properties":{"id":6,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.393746,52.520074],[13.393866,52.520126],[13.393871,52.520081],[13.393889,52.520073],[13.393872,52.520056],[13.393903,52.519975],[13.393926,52.519965],[13.394501,52.52004],[13.394479,52.520103],[13.394162,52.520083],[13.394151,52.520148],[13.394319,52.520157],[13.394526,52.520168],[13.394555,52.520146],[13.394623,52.520095],[13.394679,52.520052],[13.394803,52.519958],[13.393834,52.519838],[13.39382,52.519876],[13.393795,52.519942],[13.393746,52.520074]]]]}},{"type":"Feature","properties":{"id":7,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.393867,52.522186],[13.394056,52.522516],[13.394296,52.522483],[13.394093,52.522153],[13.393867,52.522186]]]]}},{"type":"Feature","properties":{"id":9,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.393927,52.520312],[13.394078,52.520388],[13.394206,52.520294],[13.394157,52.52027],[13.394052,52.520218],[13.393927,52.520312]]]]}},{"type":"Feature","properties":{"id":10,"height":15},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.393996,52.521925],[13.394018,52.521993],[13.394044,52.522027],[13.394071,52.522054],[13.394154,52.522098],[13.394256,52.522118],[13.394314,52.522121],[13.394502,52.5221],[13.395601,52.52198],[13.395597,52.521963],[13.396059,52.521915],[13.396104,52.521876],[13.396091,52.521831],[13.396035,52.521803],[13.396074,52.521771],[13.394912,52.5212],[13.394077,52.521797],[13.394017,52.521856],[13.393996,52.521925]],[[13.394702,52.521541],[13.394743,52.521509],[13.394794,52.521506],[13.394905,52.521429],[13.394904,52.521409],[13.394881,52.521396],[13.39493,52.521361],[13.395228,52.52151],[13.394873,52.521627],[13.394702,52.521541]],[[13.395524,52.521723],[13.395602,52.521715],[13.395599,52.521701],[13.395618,52.521686],[13.395741,52.521749],[13.395765,52.521837],[13.395565,52.521854],[13.395524,52.521723]],[[13.395005,52.521779],[13.395327,52.521666],[13.395382,52.521847],[13.39504,52.521885],[13.395005,52.521779]],[[13.394604,52.521879],[13.394829,52.5218],[13.394869,52.521931],[13.394676,52.521955],[13.394604,52.521879]],[[13.394447,52.521727],[13.394577,52.521638],[13.394742,52.521724],[13.394526,52.521798],[13.394447,52.521727]]]]}},{"type":"Feature","properties":{"id":11,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.394078,52.520388],[13.394369,52.520531],[13.394497,52.520438],[13.394299,52.52034],[13.394206,52.520294],[13.394078,52.520388]]]]}},{"type":"Feature","properties":{"id":12,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.394157,52.52027],[13.394206,52.520294],[13.394299,52.52034],[13.39443,52.52024],[13.394515,52.520176],[13.394526,52.520168],[13.394319,52.520157],[13.394157,52.52027]]]]}},{"type":"Feature","properties":{"id":13,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.394226,52.520811],[13.394532,52.520957],[13.394655,52.521015],[13.394965,52.521163],[13.395059,52.521209],[13.395088,52.521188],[13.395121,52.521163],[13.395168,52.521128],[13.395212,52.521095],[13.395247,52.52107],[13.395151,52.521027],[13.394841,52.520884],[13.394711,52.520824],[13.394393,52.520678],[13.394226,52.520811]]]]}},{"type":"Feature","properties":{"id":14,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.394369,52.520531],[13.394712,52.5207],[13.39488,52.520574],[13.394708,52.520488],[13.394665,52.520521],[13.394497,52.520438],[13.394369,52.520531]]]]}},{"type":"Feature","properties":{"id":16,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.39443,52.52024],[13.394793,52.520423],[13.394708,52.520488],[13.39488,52.520574],[13.395053,52.520443],[13.394515,52.520176],[13.39443,52.52024]]]]}},{"type":"Feature","properties":{"id":17,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.394515,52.520176],[13.395053,52.520443],[13.395216,52.52032],[13.394871,52.520148],[13.394803,52.520114],[13.394735,52.52008],[13.394679,52.520052],[13.394623,52.520095],[13.395003,52.520284],[13.39495,52.520323],[13.39476,52.520228],[13.394744,52.52024],[13.394555,52.520146],[13.394526,52.520168],[13.394515,52.520176]]]]}},{"type":"Feature","properties":{"id":19,"height":null},"geometry":{"type":"MultiPolygon","coordinates":[[[[13.394585,52.521048],[13.394595,52.521053],[13.394614,52.52104],[13.394604,52.521034],[13.394585,52.521048]]]]}},{"type":"Feature","properties":{"id":20,"height":null},"geometry":{"type":"MultiPoly
Download .txt
gitextract_r4y828n7/

├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── PERFORMANCE.md
├── README.md
├── build.js
├── dist/
│   ├── OSMBuildings-Leaflet.debug.js
│   ├── OSMBuildings-Leaflet.js
│   ├── OSMBuildings-OpenLayers.debug.js
│   ├── OSMBuildings-OpenLayers.js
│   ├── OSMBuildings.css
│   ├── index-Leaflet-3db.html
│   ├── index-Leaflet.html
│   └── index-OpenLayers.html
├── docs/
│   └── server.md
├── package.json
├── src/
│   ├── Data.js
│   ├── Debug.js
│   ├── GeoJSON.js
│   ├── OSMBuildings.css
│   ├── Request.js
│   ├── adapter.js
│   ├── engines/
│   │   ├── Leaflet.js
│   │   ├── OpenLayers.js
│   │   ├── index-Leaflet.html
│   │   └── index-OpenLayers.html
│   ├── functions.js
│   ├── geometry/
│   │   ├── Cylinder.js
│   │   ├── Extrusion.js
│   │   ├── Pyramid.js
│   │   └── __Dome.js
│   ├── geometry.js
│   ├── layers/
│   │   ├── Buildings.js
│   │   ├── Picking.js
│   │   ├── Shadows.js
│   │   ├── Simplified.js
│   │   └── index.js
│   ├── lib/
│   │   └── getSunPosition.js
│   ├── shortcuts.js
│   └── variables.js
└── tests/
    ├── openlayers-5.3.0/
    │   ├── OSMBuildings-OL5.js
    │   ├── README.md
    │   └── index.js
    └── shadows.html
Download .txt
SYMBOL INDEX (798 symbols across 26 files)

FILE: build.js
  function joinFiles (line 38) | function joinFiles (files) {
  function copy (line 45) | function copy (srcFile, distFile) {
  function buildEngine (line 52) | function buildEngine (name, customJS) {

FILE: dist/OSMBuildings-Leaflet.debug.js
  class Qolor (line 22) | class Qolor {
    method constructor (line 31) | constructor (r, g, b, a = 1) {
    method parse (line 41) | static parse (str) {
    method fromHSL (line 69) | static fromHSL (h, s, l, a) {
    method _hue2rgb (line 77) | _hue2rgb(p, q, t) {
    method _clamp (line 86) | _clamp(v, max) {
    method isValid (line 95) | isValid () {
    method toHSL (line 99) | toHSL () {
    method fromHSL (line 133) | fromHSL (h, s, l) {
    method toString (line 156) | toString () {
    method toArray (line 167) | toArray () {
    method hue (line 174) | hue (h) {
    method saturation (line 179) | saturation (s) {
    method lightness (line 184) | lightness (l) {
    method clone (line 189) | clone () {
  function getSunPosition (line 352) | function getSunPosition () {
  function getMaterialColor (line 475) | function getMaterialColor (str) {
  function getWinding (line 487) | function getWinding (points) {
  function makeWinding (line 501) | function makeWinding (points, direction) {
  function alignProperties (line 513) | function alignProperties(prop) {
  function getGeometries (line 567) | function getGeometries (geometry) {
  function clone (line 623) | function clone (obj) {
  class GeoJSON (line 633) | class GeoJSON {
    method read (line 635) | static read (geojson) {
  function onEach (line 717) | function onEach () {}
  function onClick (line 719) | function onClick () {}
  function getDistance (line 722) | function getDistance (p1, p2) {
  function isRotational (line 729) | function isRotational (polygon) {
  function getSquareSegmentDistance (line 767) | function getSquareSegmentDistance (px, py, p1x, p1y, p2x, p2y) {
  function simplifyPolygon (line 787) | function simplifyPolygon (buffer) {
  function getCenter (line 840) | function getCenter (footprint) {
  function getLonDelta (line 853) | function getLonDelta (footprint) {
  function rad (line 863) | function rad (deg) {
  function deg (line 867) | function deg (rad) {
  function pixelToGeo (line 871) | function pixelToGeo (x, y) {
  function geoToPixel (line 880) | function geoToPixel (lat, lon) {
  function fromRange (line 890) | function fromRange (sVal, sMin, sMax, dMin, dMax) {
  function isVisible (line 897) | function isVisible (polygon) {
  function xhr (line 917) | function xhr (url, callback) {
  class Request (line 957) | class Request {
    method loadJSON (line 959) | static loadJSON (url, callback) {
  class Data (line 972) | class Data {
    method getPixelFootprint (line 974) | static getPixelFootprint (buffer) {
    method resetItems (line 992) | static resetItems () {
    method addRenderItems (line 998) | static addRenderItems (data, allAreNew) {
    method scalePolygon (line 1015) | static scalePolygon (buffer, factor) {
    method scale (line 1019) | static scale (factor) {
    method scaleItem (line 1046) | static scaleItem (item) {
    method set (line 1122) | static set (data) {
    method load (line 1128) | static load (src, key) {
    method update (line 1133) | static update () {
    method loadTile (line 1169) | static loadTile (x, y, zoom, callback) {
  class Extrusion (line 1179) | class Extrusion {
    method draw (line 1181) | static draw (context, polygon, innerPolygons, height, minHeight, color...
    method _extrude (line 1205) | static _extrude (context, polygon, height, minHeight, color, altColor) {
    method _ring (line 1255) | static _ring (context, polygon) {
    method simplified (line 1262) | static simplified (context, polygon, innerPolygons) {
    method _ringAbs (line 1274) | static _ringAbs (context, polygon) {
    method shadow (line 1281) | static shadow (context, polygon, innerPolygons, height, minHeight) {
    method hitArea (line 1331) | static hitArea (context, polygon, innerPolygons, height, minHeight, co...
  class Cylinder (line 1384) | class Cylinder {
    method draw (line 1386) | static draw (context, center, radius, topRadius, height, minHeight, co...
    method simplified (line 1431) | static simplified (context, center, radius) {
    method shadow (line 1435) | static shadow (context, center, radius, topRadius, height, minHeight) {
    method hitArea (line 1461) | static hitArea (context, center, radius, topRadius, height, minHeight,...
    method _circle (line 1498) | static _circle (context, center, radius) {
    method _tangents (line 1505) | static _tangents (c1, r1, c2, r2) {
  class Pyramid (line 1554) | class Pyramid {
    method draw (line 1556) | static draw (context, polygon, center, height, minHeight, color, altCo...
    method _triangle (line 1593) | static _triangle (context, a, b, c) {
    method _ring (line 1599) | static _ring (context, polygon) {
    method shadow (line 1606) | static shadow (context, polygon, center, height, minHeight) {
    method hitArea (line 1632) | static hitArea (context, polygon, center, height, minHeight, color) {
  function fadeIn (line 1668) | function fadeIn() {
  class Layers (line 1696) | class Layers {
    method init (line 1698) | static init () {
    method clear (line 1708) | static clear () {
    method setOpacity (line 1715) | static setOpacity (opacity) {
    method render (line 1722) | static render (quick) {
    method createContext (line 1744) | static createContext (container) {
    method appendTo (line 1762) | static appendTo (parentNode) {
    method remove (line 1766) | static remove () {
    method setSize (line 1770) | static setSize (width, height) {
    method setPosition (line 1778) | static setPosition (x, y) {
  class Buildings (line 1787) | class Buildings {
    method init (line 1789) | static init (context) {
    method clear (line 1793) | static clear () {
    method setOpacity (line 1797) | static setOpacity (opacity) {
    method project (line 1801) | static project (p, m) {
    method render (line 1808) | static render () {
  class Simplified (line 1868) | class Simplified {
    method init (line 1870) | static init (context) {
    method clear (line 1874) | static clear () {
    method setOpacity (line 1878) | static setOpacity (opacity) {
    method isSimple (line 1882) | static isSimple (item) {
    method render (line 1886) | static render () {
  class Shadows (line 1931) | class Shadows {
    method init (line 1933) | static init (context) {
    method clear (line 1937) | static clear () {
    method setOpacity (line 1941) | static setOpacity (opacity) {
    method project (line 1945) | static project (p, h) {
    method render (line 1952) | static render () {
  class Picking (line 2032) | class Picking {
    method init (line 2034) | static init (context) {
    method setOpacity (line 2038) | static setOpacity (opacity) {}
    method clear (line 2040) | static clear () {}
    method reset (line 2042) | static reset () {
    method render (line 2046) | static render () {
    method _render (line 2057) | static _render () {
    method getIdFromXY (line 2115) | static getIdFromXY (x, y) {
    method idToColor (line 2125) | static idToColor (id) {
  class Debug (line 2141) | class Debug {
    method point (line 2143) | static point (x, y, color, size) {
    method line (line 2152) | static line (ax, ay, bx, by, color) {
  function setOrigin (line 2164) | function setOrigin (origin) {
  function moveCam (line 2169) | function moveCam (offset) {
  function setSize (line 2175) | function setSize (size) {
  function setZoom (line 2188) | function setZoom (z) {
  function onResize (line 2204) | function onResize (e) {
  function onMoveEnd (line 2210) | function onMoveEnd (e) {
  function onZoomStart (line 2215) | function onZoomStart () {
  function onZoomEnd (line 2219) | function onZoomEnd (e) {
  class OSMBuildings (line 2242) | class OSMBuildings extends L.Layer {
    method constructor (line 2244) | constructor (map) {
    method addTo (line 2254) | addTo (map) {
    method onAdd (line 2259) | onAdd (map) {
    method onRemove (line 2293) | onRemove () {
    method onMove (line 2316) | onMove (e) {
    method onMoveEnd (line 2321) | onMoveEnd (e) {
    method onZoomStart (line 2341) | onZoomStart (e) {
    method onZoom (line 2345) | onZoom (e) {
    method onZoomEnd (line 2368) | onZoomEnd (e) {
    method onResize (line 2383) | onResize () {
    method onViewReset (line 2386) | onViewReset () {
    method onClick (line 2394) | onClick (e) {
    method getOffset (line 2401) | getOffset () {
    method style (line 2407) | style (style) {
    method date (line 2431) | date (date) {
    method load (line 2437) | load (url) {
    method set (line 2442) | set (data) {
    method each (line 2447) | each (handler) {
    method click (line 2454) | click (handler) {

FILE: dist/OSMBuildings-Leaflet.js
  class u (line 1) | class u{constructor(e,t,i,r=1){this.r=this._clamp(e,1),this.g=this._clam...
    method constructor (line 1) | constructor(e,t,i,r=1){this.r=this._clamp(e,1),this.g=this._clamp(t,1)...
    method parse (line 1) | static parse(e){if("string"==typeof e){let t;if(e=e.toLowerCase(),t=(e...
    method fromHSL (line 1) | static fromHSL(e,t,i,r){const a=(new u).fromHSL(e,t,i);return a.a=void...
    method _hue2rgb (line 1) | _hue2rgb(e,t,i){return i<0&&(i+=1),i>1&&(i-=1),i<1/6?e+6*(t-e)*i:i<.5?...
    method _clamp (line 1) | _clamp(e,t){if(void 0!==e)return Math.min(t,Math.max(0,e||0))}
    method isValid (line 1) | isValid(){return void 0!==this.r&&void 0!==this.g&&void 0!==this.b}
    method toHSL (line 1) | toHSL(){if(!this.isValid())return;const e=Math.max(this.r,this.g,this....
    method fromHSL (line 1) | fromHSL(e,t,i){if(0===t)return this.r=this.g=this.b=i,this;const r=i<....
    method toString (line 1) | toString(){if(this.isValid())return 1===this.a?"#"+((1<<24)+(Math.roun...
    method toArray (line 1) | toArray(){if(this.isValid)return[this.r,this.g,this.b]}
    method hue (line 1) | hue(e){const t=this.toHSL();return this.fromHSL(t.h+e,t.s,t.l)}
    method saturation (line 1) | saturation(e){const t=this.toHSL();return this.fromHSL(t.h,t.s*e,t.l)}
    method lightness (line 1) | lightness(e){const t=this.toHSL();return this.fromHSL(t.h,t.s,t.l*e)}
    method clone (line 1) | clone(){return new u(this.r,this.g,this.b,this.a)}
  function g (line 1) | function g(){const e=Math,t=e.PI,i=e.sin,r=e.cos,a=e.tan,o=e.asin,s=e.at...
  function m (line 1) | function m(e){return"#"===(e=e.toLowerCase())[0]?e:p[y[e]||e]||null}
  function x (line 1) | function x(e,t){if(function(e){let t,i,r,a,o=0;for(let s=0,n=e.length-3;...
  function b (line 1) | function b(e){const t={};e=e||{},t.height=e.height||(e.levels?3*e.levels...
  function w (line 1) | function w(e){let t,i,r=[];switch(e.type){case"GeometryCollection":r=[];...
  function k (line 1) | function k(e){let t={};for(const i in e)e.hasOwnProperty(i)&&(t[i]=e[i])...
  function W (line 1) | function W(){}
  function J (line 1) | function J(){}
  function B (line 1) | function B(e,t){const i=e.x-t.x,r=e.y-t.y;return i*i+r*r}
  function U (line 1) | function U(e,t,i,r,a,o){let s,n=a-i,l=o-r;return 0===n&&0===l||(s=((e-i)...
  function Y (line 1) | function Y(e){let t=180,i=-180;for(let r=0,a=e.length;r<a;r+=2)t=l(t,e[r...
  function K (line 1) | function K(e,i){const r={};return e/=v,i/=v,r.latitude=i<=0?90:i>=1?-90:...
  function Q (line 1) | function Q(e,t){const r=l(1,c(0,.5-i(o(I+z*e/180))/T/2));return{x:(t/360...
  function ee (line 1) | function ee(e){const t=O+E,i=A+D;for(let r=0,a=e.length-3;r<a;r+=2)if(e[...
  class oe (line 1) | class oe{static getPixelFootprint(e){let t,i=new Int32Array(e.length);fo...
    method getPixelFootprint (line 1) | static getPixelFootprint(e){let t,i=new Int32Array(e.length);for(let r...
    method resetItems (line 1) | static resetItems(){this.items=[],this.cache={},ue.reset()}
    method addRenderItems (line 1) | static addRenderItems(e,t){let i,r,a,o=class{static read(e){if(!e||"Fe...
    method scalePolygon (line 1) | static scalePolygon(e,t){return e.map(e=>e*t)}
    method scale (line 1) | static scale(e){oe.items=oe.items.map(t=>{if(t.height*=e,t.minHeight*=...
    method scaleItem (line 1) | static scaleItem(e){let t,i={},r=6/d(2,_-15);if(e.id&&(i.id=e.id),i.he...
    method set (line 1) | static set(e){this.resetItems(),this._staticData=e,this.addRenderItems...
    method load (line 1) | static load(e,t){this.src=e||"https://{s}.data.osmbuildings.org/0.2/{k...
    method update (line 1) | static update(){if(this.resetItems(),!(_<15)&&(this._staticData&&this....
    method loadTile (line 1) | static loadTile(e,t,i,r){let a="abcd"[(e+t)%4],o=this.src.replace("{s}...
  class se (line 1) | class se{static draw(e,t,i,r,a,o,s,n){let l=this._extrude(e,t,r,a,o,s),c...
    method draw (line 1) | static draw(e,t,i,r,a,o,s,n){let l=this._extrude(e,t,r,a,o,s),c=[];if(...
    method _extrude (line 1) | static _extrude(e,t,i,r,a,o){let s,n,l=450/(450-i),c=450/(450-r),h={x:...
    method _ring (line 1) | static _ring(e,t){e.moveTo(t[0],t[1]);for(let i=2,r=t.length-1;i<r;i+=...
    method simplified (line 1) | static simplified(e,t,i){if(e.beginPath(),this._ringAbs(e,t),i)for(let...
    method _ringAbs (line 1) | static _ringAbs(e,t){e.moveTo(t[0]-E,t[1]-D);for(let i=2,r=t.length-1;...
    method shadow (line 1) | static shadow(e,t,i,r,a){let o,s,n=null,l={x:0,y:0},c={x:0,y:0};for(le...
    method hitArea (line 1) | static hitArea(e,t,i,r,a,o){let s,n,l=null,c={x:0,y:0},h={x:0,y:0},f=4...
  class ne (line 1) | class ne{static draw(e,t,i,r,a,o,s,l,c){let h,f,d={x:t.x-E,y:t.y-D},u=45...
    method draw (line 1) | static draw(e,t,i,r,a,o,s,l,c){let h,f,d={x:t.x-E,y:t.y-D},u=450/(450-...
    method simplified (line 1) | static simplified(e,t,i){this._circle(e,{x:t.x-E,y:t.y-D},i)}
    method shadow (line 1) | static shadow(e,t,i,r,a,o){let s,l,c={x:t.x-E,y:t.y-D},h=de.project(c,...
    method hitArea (line 1) | static hitArea(e,t,i,r,a,o,s){let l,c,h={x:t.x-E,y:t.y-D},f=450/(450-a...
    method _circle (line 1) | static _circle(e,t,i){e.beginPath(),e.arc(t.x,t.y,i,0,2*T),e.fill()}
    method _tangents (line 1) | static _tangents(e,t,i,r){let a=e.x-i.x,o=e.y-i.y,s=t-r,n=a*a+o*o;if(n...
  class le (line 1) | class le{static draw(e,t,i,r,a,o,s){let n={x:i.x-E,y:i.y-D},l=450/(450-r...
    method draw (line 1) | static draw(e,t,i,r,a,o,s){let n={x:i.x-E,y:i.y-D},l=450/(450-r),c=450...
    method _triangle (line 1) | static _triangle(e,t,i,r){e.moveTo(t.x,t.y),e.lineTo(i.x,i.y),e.lineTo...
    method _ring (line 1) | static _ring(e,t){e.moveTo(t[0]-E,t[1]-D);for(let i=2,r=t.length-1;i<r...
    method shadow (line 1) | static shadow(e,t,i,r,a){let o={x:0,y:0},s={x:0,y:0},n={x:i.x-E,y:i.y-...
    method hitArea (line 1) | static hitArea(e,t,i,r,a,o){let s={x:i.x-E,y:i.y-D},n=450/(450-r),l=45...
  class ce (line 1) | class ce{static init(){ce.container.className="osmb-container",de.init(c...
    method init (line 1) | static init(){ce.container.className="osmb-container",de.init(ce.creat...
    method clear (line 1) | static clear(){de.clear(),fe.clear(),he.clear(),ue.clear()}
    method setOpacity (line 1) | static setOpacity(e){de.setOpacity(e),fe.setOpacity(e),he.setOpacity(e...
    method render (line 1) | static render(e){_<15?ce.clear():M||requestAnimationFrame(t=>{e||(de.r...
    method createContext (line 1) | static createContext(e){let t=document.createElement("CANVAS");t.class...
    method appendTo (line 1) | static appendTo(e){e.appendChild(ce.container)}
    method remove (line 1) | static remove(){ce.container.parentNode.removeChild(ce.container)}
    method setSize (line 1) | static setSize(e,t){ce.items.forEach(i=>{i.width=e,i.height=t})}
    method setPosition (line 1) | static setPosition(e,t){ce.container.style.left=e+"px",ce.container.st...
  class he (line 1) | class he{static init(e){this.context=e}static clear(){this.context.clear...
    method init (line 1) | static init(e){this.context=e}
    method clear (line 1) | static clear(){this.context.clearRect(0,0,O,A)}
    method setOpacity (line 1) | static setOpacity(e){this.context.canvas.style.opacity=e}
    method project (line 1) | static project(e,t){return{x:(e.x-C)*t+C<<0,y:(e.y-S)*t+S<<0}}
    method render (line 1) | static render(){this.clear();let e,t,i,r,a,o,s,n=this.context,l={x:C+E...
  class fe (line 1) | class fe{static init(e){this.context=e}static clear(){this.context.clear...
    method init (line 1) | static init(e){this.context=e}
    method clear (line 1) | static clear(){this.context.clearRect(0,0,O,A)}
    method setOpacity (line 1) | static setOpacity(e){this.context.canvas.style.opacity=e}
    method isSimple (line 1) | static isSimple(e){return _<=fe.MAX_ZOOM&&e.height+e.roofHeight<fe.MAX...
    method render (line 1) | static render(){this.clear();let e=this.context;if(_>fe.MAX_ZOOM)retur...
  class de (line 1) | class de{static init(e){this.context=e}static clear(){this.context.clear...
    method init (line 1) | static init(e){this.context=e}
    method clear (line 1) | static clear(){this.context.clearRect(0,0,O,A)}
    method setOpacity (line 1) | static setOpacity(e){this.opacity=e}
    method project (line 1) | static project(e,t){return{x:e.x+this.direction.x*t,y:e.y+this.directi...
    method render (line 1) | static render(){this.clear();let e,t,i,s,n=this.context;if(e=K(j+E,R+D...
  class ue (line 1) | class ue{static init(e){this.context=e}static setOpacity(e){}static clea...
    method init (line 1) | static init(e){this.context=e}
    method setOpacity (line 1) | static setOpacity(e){}
    method clear (line 1) | static clear(){}
    method reset (line 1) | static reset(){this._idMapping=[null]}
    method render (line 1) | static render(){if(this._timer)return;let e=this;this._timer=setTimeou...
    method _render (line 1) | static _render(){this.clear();let e,t,i,r,a,o=this.context,s={x:C+E,y:...
    method getIdFromXY (line 1) | static getIdFromXY(e,t){let i=this._imageData;if(!i)return;let r=4*((0...
    method idToColor (line 1) | static idToColor(e){let t=this._idMapping.indexOf(e);return-1===t&&(th...
  function ge (line 1) | function ge(e){E=e.x,D=e.y}
  function pe (line 1) | function pe(e){C=j+e.x,S=A+e.y,ce.render(!0)}
  function ye (line 1) | function ye(e){O=e.width,A=e.height,j=O/2<<0,R=A/2<<0,C=j,S=A,ce.setSize...
  function me (line 1) | function me(e){_=e,v=256<<_;const t=K(E+j,D+R),i=Q(t.latitude,0),r=Q(t.l...
  class xe (line 1) | class xe extends L.Layer{constructor(e){super(e),this.offset={x:0,y:0},c...
    method constructor (line 1) | constructor(e){super(e),this.offset={x:0,y:0},ce.init(),e&&e.addLayer(...
    method addTo (line 1) | addTo(e){return e.addLayer(this),this}
    method onAdd (line 1) | onAdd(e){this.map=e,ce.appendTo(e._panes.overlayPane);let t=this.getOf...
    method onRemove (line 1) | onRemove(){let e=this.map;e.attributionControl&&e.attributionControl.r...
    method onMove (line 1) | onMove(e){let t=this.getOffset();pe({x:this.offset.x-t.x,y:this.offset...
    method onMoveEnd (line 1) | onMoveEnd(e){if(this.noMoveEnd)return void(this.noMoveEnd=!1);let t=th...
    method onZoomStart (line 1) | onZoomStart(e){M=!0}
    method onZoom (line 1) | onZoom(e){let t=this.map.latLngToContainerPoint(e.center),i=Math.pow(2...
    method onZoomEnd (line 1) | onZoomEnd(e){ce.clear(),ce.container.classList.remove("zoom-animation"...
    method onResize (line 1) | onResize(){}
    method onViewReset (line 1) | onViewReset(){let e=this.getOffset();this.offset=e,ce.setPosition(-e.x...
    method onClick (line 1) | onClick(e){let t=ue.getIdFromXY(e.containerPoint.x,e.containerPoint.y)...
    method getOffset (line 1) | getOffset(){return L.DomUtil.getPosition(this.map._mapPane)}
    method style (line 1) | style(e){let t;return(t=(e=e||{}).color||e.wallColor)&&(F=u.parse(t),Z...
    method date (line 1) | date(e){return de.date=e,de.render(),this}
    method load (line 1) | load(e){return oe.load(e),this}
    method set (line 1) | set(e){return oe.set(e),this}
    method each (line 1) | each(e){return W=function(t){return e(t)},this}
    method click (line 1) | click(e){return J=function(t){return e(t)},this}

FILE: dist/OSMBuildings-OpenLayers.debug.js
  class Qolor (line 22) | class Qolor {
    method constructor (line 31) | constructor (r, g, b, a = 1) {
    method parse (line 41) | static parse (str) {
    method fromHSL (line 69) | static fromHSL (h, s, l, a) {
    method _hue2rgb (line 77) | _hue2rgb(p, q, t) {
    method _clamp (line 86) | _clamp(v, max) {
    method isValid (line 95) | isValid () {
    method toHSL (line 99) | toHSL () {
    method fromHSL (line 133) | fromHSL (h, s, l) {
    method toString (line 156) | toString () {
    method toArray (line 167) | toArray () {
    method hue (line 174) | hue (h) {
    method saturation (line 179) | saturation (s) {
    method lightness (line 184) | lightness (l) {
    method clone (line 189) | clone () {
  function getSunPosition (line 352) | function getSunPosition () {
  function getMaterialColor (line 475) | function getMaterialColor (str) {
  function getWinding (line 487) | function getWinding (points) {
  function makeWinding (line 501) | function makeWinding (points, direction) {
  function alignProperties (line 513) | function alignProperties(prop) {
  function getGeometries (line 567) | function getGeometries (geometry) {
  function clone (line 623) | function clone (obj) {
  class GeoJSON (line 633) | class GeoJSON {
    method read (line 635) | static read (geojson) {
  function onEach (line 717) | function onEach () {}
  function onClick (line 719) | function onClick () {}
  function getDistance (line 722) | function getDistance (p1, p2) {
  function isRotational (line 729) | function isRotational (polygon) {
  function getSquareSegmentDistance (line 767) | function getSquareSegmentDistance (px, py, p1x, p1y, p2x, p2y) {
  function simplifyPolygon (line 787) | function simplifyPolygon (buffer) {
  function getCenter (line 840) | function getCenter (footprint) {
  function getLonDelta (line 853) | function getLonDelta (footprint) {
  function rad (line 863) | function rad (deg) {
  function deg (line 867) | function deg (rad) {
  function pixelToGeo (line 871) | function pixelToGeo (x, y) {
  function geoToPixel (line 880) | function geoToPixel (lat, lon) {
  function fromRange (line 890) | function fromRange (sVal, sMin, sMax, dMin, dMax) {
  function isVisible (line 897) | function isVisible (polygon) {
  function xhr (line 917) | function xhr (url, callback) {
  class Request (line 957) | class Request {
    method loadJSON (line 959) | static loadJSON (url, callback) {
  class Data (line 972) | class Data {
    method getPixelFootprint (line 974) | static getPixelFootprint (buffer) {
    method resetItems (line 992) | static resetItems () {
    method addRenderItems (line 998) | static addRenderItems (data, allAreNew) {
    method scalePolygon (line 1015) | static scalePolygon (buffer, factor) {
    method scale (line 1019) | static scale (factor) {
    method scaleItem (line 1046) | static scaleItem (item) {
    method set (line 1122) | static set (data) {
    method load (line 1128) | static load (src, key) {
    method update (line 1133) | static update () {
    method loadTile (line 1169) | static loadTile (x, y, zoom, callback) {
  class Extrusion (line 1179) | class Extrusion {
    method draw (line 1181) | static draw (context, polygon, innerPolygons, height, minHeight, color...
    method _extrude (line 1205) | static _extrude (context, polygon, height, minHeight, color, altColor) {
    method _ring (line 1255) | static _ring (context, polygon) {
    method simplified (line 1262) | static simplified (context, polygon, innerPolygons) {
    method _ringAbs (line 1274) | static _ringAbs (context, polygon) {
    method shadow (line 1281) | static shadow (context, polygon, innerPolygons, height, minHeight) {
    method hitArea (line 1331) | static hitArea (context, polygon, innerPolygons, height, minHeight, co...
  class Cylinder (line 1384) | class Cylinder {
    method draw (line 1386) | static draw (context, center, radius, topRadius, height, minHeight, co...
    method simplified (line 1431) | static simplified (context, center, radius) {
    method shadow (line 1435) | static shadow (context, center, radius, topRadius, height, minHeight) {
    method hitArea (line 1461) | static hitArea (context, center, radius, topRadius, height, minHeight,...
    method _circle (line 1498) | static _circle (context, center, radius) {
    method _tangents (line 1505) | static _tangents (c1, r1, c2, r2) {
  class Pyramid (line 1554) | class Pyramid {
    method draw (line 1556) | static draw (context, polygon, center, height, minHeight, color, altCo...
    method _triangle (line 1593) | static _triangle (context, a, b, c) {
    method _ring (line 1599) | static _ring (context, polygon) {
    method shadow (line 1606) | static shadow (context, polygon, center, height, minHeight) {
    method hitArea (line 1632) | static hitArea (context, polygon, center, height, minHeight, color) {
  function fadeIn (line 1668) | function fadeIn() {
  class Layers (line 1696) | class Layers {
    method init (line 1698) | static init () {
    method clear (line 1708) | static clear () {
    method setOpacity (line 1715) | static setOpacity (opacity) {
    method render (line 1722) | static render (quick) {
    method createContext (line 1744) | static createContext (container) {
    method appendTo (line 1762) | static appendTo (parentNode) {
    method remove (line 1766) | static remove () {
    method setSize (line 1770) | static setSize (width, height) {
    method setPosition (line 1778) | static setPosition (x, y) {
  class Buildings (line 1787) | class Buildings {
    method init (line 1789) | static init (context) {
    method clear (line 1793) | static clear () {
    method setOpacity (line 1797) | static setOpacity (opacity) {
    method project (line 1801) | static project (p, m) {
    method render (line 1808) | static render () {
  class Simplified (line 1868) | class Simplified {
    method init (line 1870) | static init (context) {
    method clear (line 1874) | static clear () {
    method setOpacity (line 1878) | static setOpacity (opacity) {
    method isSimple (line 1882) | static isSimple (item) {
    method render (line 1886) | static render () {
  class Shadows (line 1931) | class Shadows {
    method init (line 1933) | static init (context) {
    method clear (line 1937) | static clear () {
    method setOpacity (line 1941) | static setOpacity (opacity) {
    method project (line 1945) | static project (p, h) {
    method render (line 1952) | static render () {
  class Picking (line 2032) | class Picking {
    method init (line 2034) | static init (context) {
    method setOpacity (line 2038) | static setOpacity (opacity) {}
    method clear (line 2040) | static clear () {}
    method reset (line 2042) | static reset () {
    method render (line 2046) | static render () {
    method _render (line 2057) | static _render () {
    method getIdFromXY (line 2115) | static getIdFromXY (x, y) {
    method idToColor (line 2125) | static idToColor (id) {
  class Debug (line 2141) | class Debug {
    method point (line 2143) | static point (x, y, color, size) {
    method line (line 2152) | static line (ax, ay, bx, by, color) {
  function setOrigin (line 2164) | function setOrigin (origin) {
  function moveCam (line 2169) | function moveCam (offset) {
  function setSize (line 2175) | function setSize (size) {
  function setZoom (line 2188) | function setZoom (z) {
  function onResize (line 2204) | function onResize (e) {
  function onMoveEnd (line 2210) | function onMoveEnd (e) {
  function onZoomStart (line 2215) | function onZoomStart () {
  function onZoomEnd (line 2219) | function onZoomEnd (e) {
  class OSMBuildings (line 2243) | class OSMBuildings extends ol.layer.Layer {
    method constructor (line 2245) | constructor (map) {
    method addTo (line 2256) | addTo (map) {
    method setOrigin (line 2261) | setOrigin () {
    method setMap (line 2271) | setMap (map) {
    method removeMap (line 2292) | removeMap (map) {
    method onMapResize (line 2298) | onMapResize () {
    method moveTo (line 2304) | moveTo (bounds, zoomChanged, isDragging) {
    method moveByPx (line 2332) | moveByPx (dx, dy) {

FILE: dist/OSMBuildings-OpenLayers.js
  class u (line 1) | class u{constructor(e,t,i,r=1){this.r=this._clamp(e,1),this.g=this._clam...
    method constructor (line 1) | constructor(e,t,i,r=1){this.r=this._clamp(e,1),this.g=this._clamp(t,1)...
    method parse (line 1) | static parse(e){if("string"==typeof e){let t;if(e=e.toLowerCase(),t=(e...
    method fromHSL (line 1) | static fromHSL(e,t,i,r){const a=(new u).fromHSL(e,t,i);return a.a=void...
    method _hue2rgb (line 1) | _hue2rgb(e,t,i){return i<0&&(i+=1),i>1&&(i-=1),i<1/6?e+6*(t-e)*i:i<.5?...
    method _clamp (line 1) | _clamp(e,t){if(void 0!==e)return Math.min(t,Math.max(0,e||0))}
    method isValid (line 1) | isValid(){return void 0!==this.r&&void 0!==this.g&&void 0!==this.b}
    method toHSL (line 1) | toHSL(){if(!this.isValid())return;const e=Math.max(this.r,this.g,this....
    method fromHSL (line 1) | fromHSL(e,t,i){if(0===t)return this.r=this.g=this.b=i,this;const r=i<....
    method toString (line 1) | toString(){if(this.isValid())return 1===this.a?"#"+((1<<24)+(Math.roun...
    method toArray (line 1) | toArray(){if(this.isValid)return[this.r,this.g,this.b]}
    method hue (line 1) | hue(e){const t=this.toHSL();return this.fromHSL(t.h+e,t.s,t.l)}
    method saturation (line 1) | saturation(e){const t=this.toHSL();return this.fromHSL(t.h,t.s*e,t.l)}
    method lightness (line 1) | lightness(e){const t=this.toHSL();return this.fromHSL(t.h,t.s,t.l*e)}
    method clone (line 1) | clone(){return new u(this.r,this.g,this.b,this.a)}
  function p (line 1) | function p(){const e=Math,t=e.PI,i=e.sin,r=e.cos,a=e.tan,s=e.asin,o=e.at...
  function m (line 1) | function m(e){return"#"===(e=e.toLowerCase())[0]?e:g[y[e]||e]||null}
  function x (line 1) | function x(e,t){if(function(e){let t,i,r,a,s=0;for(let o=0,n=e.length-3;...
  function b (line 1) | function b(e){const t={};e=e||{},t.height=e.height||(e.levels?3*e.levels...
  function w (line 1) | function w(e){let t,i,r=[];switch(e.type){case"GeometryCollection":r=[];...
  function k (line 1) | function k(e){let t={};for(const i in e)e.hasOwnProperty(i)&&(t[i]=e[i])...
  function V (line 1) | function V(e,t){const i=e.x-t.x,r=e.y-t.y;return i*i+r*r}
  function $ (line 1) | function $(e,t,i,r,a,s){let o,n=a-i,l=s-r;return 0===n&&0===l||(o=((e-i)...
  function W (line 1) | function W(e){let t=180,i=-180;for(let r=0,a=e.length;r<a;r+=2)t=l(t,e[r...
  function J (line 1) | function J(e,i){const r={};return e/=v,i/=v,r.latitude=i<=0?90:i>=1?-90:...
  function Z (line 1) | function Z(e,t){const r=l(1,c(0,.5-i(s(I+P*e/180))/T/2));return{x:(t/360...
  function Y (line 1) | function Y(e){const t=j+z,i=O+F;for(let r=0,a=e.length-3;r<a;r+=2)if(e[r...
  class te (line 1) | class te{static getPixelFootprint(e){let t,i=new Int32Array(e.length);fo...
    method getPixelFootprint (line 1) | static getPixelFootprint(e){let t,i=new Int32Array(e.length);for(let r...
    method resetItems (line 1) | static resetItems(){this.items=[],this.cache={},ce.reset()}
    method addRenderItems (line 1) | static addRenderItems(e,t){let i,r,a,s=class{static read(e){if(!e||"Fe...
    method scalePolygon (line 1) | static scalePolygon(e,t){return e.map(e=>e*t)}
    method scale (line 1) | static scale(e){te.items=te.items.map(t=>{if(t.height*=e,t.minHeight*=...
    method scaleItem (line 1) | static scaleItem(e){let t,i={},r=6/d(2,_-15);if(e.id&&(i.id=e.id),i.he...
    method set (line 1) | static set(e){this.resetItems(),this._staticData=e,this.addRenderItems...
    method load (line 1) | static load(e,t){this.src=e||"https://{s}.data.osmbuildings.org/0.2/{k...
    method update (line 1) | static update(){if(this.resetItems(),!(_<15)&&(this._staticData&&this....
    method loadTile (line 1) | static loadTile(e,t,i,r){let a="abcd"[(e+t)%4],s=this.src.replace("{s}...
  class ie (line 1) | class ie{static draw(e,t,i,r,a,s,o,n){let l=this._extrude(e,t,r,a,s,o),c...
    method draw (line 1) | static draw(e,t,i,r,a,s,o,n){let l=this._extrude(e,t,r,a,s,o),c=[];if(...
    method _extrude (line 1) | static _extrude(e,t,i,r,a,s){let o,n,l=450/(450-i),c=450/(450-r),h={x:...
    method _ring (line 1) | static _ring(e,t){e.moveTo(t[0],t[1]);for(let i=2,r=t.length-1;i<r;i+=...
    method simplified (line 1) | static simplified(e,t,i){if(e.beginPath(),this._ringAbs(e,t),i)for(let...
    method _ringAbs (line 1) | static _ringAbs(e,t){e.moveTo(t[0]-z,t[1]-F);for(let i=2,r=t.length-1;...
    method shadow (line 1) | static shadow(e,t,i,r,a){let s,o,n=null,l={x:0,y:0},c={x:0,y:0};for(le...
    method hitArea (line 1) | static hitArea(e,t,i,r,a,s){let o,n,l=null,c={x:0,y:0},h={x:0,y:0},f=4...
  class re (line 1) | class re{static draw(e,t,i,r,a,s,o,l,c){let h,f,d={x:t.x-z,y:t.y-F},u=45...
    method draw (line 1) | static draw(e,t,i,r,a,s,o,l,c){let h,f,d={x:t.x-z,y:t.y-F},u=450/(450-...
    method simplified (line 1) | static simplified(e,t,i){this._circle(e,{x:t.x-z,y:t.y-F},i)}
    method shadow (line 1) | static shadow(e,t,i,r,a,s){let o,l,c={x:t.x-z,y:t.y-F},h=le.project(c,...
    method hitArea (line 1) | static hitArea(e,t,i,r,a,s,o){let l,c,h={x:t.x-z,y:t.y-F},f=450/(450-a...
    method _circle (line 1) | static _circle(e,t,i){e.beginPath(),e.arc(t.x,t.y,i,0,2*T),e.fill()}
    method _tangents (line 1) | static _tangents(e,t,i,r){let a=e.x-i.x,s=e.y-i.y,o=t-r,n=a*a+s*s;if(n...
  class ae (line 1) | class ae{static draw(e,t,i,r,a,s,o){let n={x:i.x-z,y:i.y-F},l=450/(450-r...
    method draw (line 1) | static draw(e,t,i,r,a,s,o){let n={x:i.x-z,y:i.y-F},l=450/(450-r),c=450...
    method _triangle (line 1) | static _triangle(e,t,i,r){e.moveTo(t.x,t.y),e.lineTo(i.x,i.y),e.lineTo...
    method _ring (line 1) | static _ring(e,t){e.moveTo(t[0]-z,t[1]-F);for(let i=2,r=t.length-1;i<r...
    method shadow (line 1) | static shadow(e,t,i,r,a){let s={x:0,y:0},o={x:0,y:0},n={x:i.x-z,y:i.y-...
    method hitArea (line 1) | static hitArea(e,t,i,r,a,s){let o={x:i.x-z,y:i.y-F},n=450/(450-r),l=45...
  class se (line 1) | class se{static init(){se.container.className="osmb-container",le.init(s...
    method init (line 1) | static init(){se.container.className="osmb-container",le.init(se.creat...
    method clear (line 1) | static clear(){le.clear(),ne.clear(),oe.clear(),ce.clear()}
    method setOpacity (line 1) | static setOpacity(e){le.setOpacity(e),ne.setOpacity(e),oe.setOpacity(e...
    method render (line 1) | static render(e){_<15?se.clear():M||requestAnimationFrame(t=>{e||(le.r...
    method createContext (line 1) | static createContext(e){let t=document.createElement("CANVAS");t.class...
    method appendTo (line 1) | static appendTo(e){e.appendChild(se.container)}
    method remove (line 1) | static remove(){se.container.parentNode.removeChild(se.container)}
    method setSize (line 1) | static setSize(e,t){se.items.forEach(i=>{i.width=e,i.height=t})}
    method setPosition (line 1) | static setPosition(e,t){se.container.style.left=e+"px",se.container.st...
  class oe (line 1) | class oe{static init(e){this.context=e}static clear(){this.context.clear...
    method init (line 1) | static init(e){this.context=e}
    method clear (line 1) | static clear(){this.context.clearRect(0,0,j,O)}
    method setOpacity (line 1) | static setOpacity(e){this.context.canvas.style.opacity=e}
    method project (line 1) | static project(e,t){return{x:(e.x-S)*t+S<<0,y:(e.y-C)*t+C<<0}}
    method render (line 1) | static render(){this.clear();let e,t,i,r,a,s,o,n=this.context,l={x:S+z...
  class ne (line 1) | class ne{static init(e){this.context=e}static clear(){this.context.clear...
    method init (line 1) | static init(e){this.context=e}
    method clear (line 1) | static clear(){this.context.clearRect(0,0,j,O)}
    method setOpacity (line 1) | static setOpacity(e){this.context.canvas.style.opacity=e}
    method isSimple (line 1) | static isSimple(e){return _<=ne.MAX_ZOOM&&e.height+e.roofHeight<ne.MAX...
    method render (line 1) | static render(){this.clear();let e=this.context;if(_>ne.MAX_ZOOM)retur...
  class le (line 1) | class le{static init(e){this.context=e}static clear(){this.context.clear...
    method init (line 1) | static init(e){this.context=e}
    method clear (line 1) | static clear(){this.context.clearRect(0,0,j,O)}
    method setOpacity (line 1) | static setOpacity(e){this.opacity=e}
    method project (line 1) | static project(e,t){return{x:e.x+this.direction.x*t,y:e.y+this.directi...
    method render (line 1) | static render(){this.clear();let e,t,i,o,n=this.context;if(e=J(A+z,L+F...
  class ce (line 1) | class ce{static init(e){this.context=e}static setOpacity(e){}static clea...
    method init (line 1) | static init(e){this.context=e}
    method setOpacity (line 1) | static setOpacity(e){}
    method clear (line 1) | static clear(){}
    method reset (line 1) | static reset(){this._idMapping=[null]}
    method render (line 1) | static render(){if(this._timer)return;let e=this;this._timer=setTimeou...
    method _render (line 1) | static _render(){this.clear();let e,t,i,r,a,s=this.context,o={x:S+z,y:...
    method getIdFromXY (line 1) | static getIdFromXY(e,t){let i=this._imageData;if(!i)return;let r=4*((0...
    method idToColor (line 1) | static idToColor(e){let t=this._idMapping.indexOf(e);return-1===t&&(th...
  function he (line 1) | function he(e){S=A+e.x,C=O+e.y,se.render(!0)}
  function fe (line 1) | function fe(e){j=e.width,O=e.height,A=j/2<<0,L=O/2<<0,S=A,C=O,se.setSize...
  function de (line 1) | function de(e){_=e,v=256<<_;const t=J(z+A,F+L),i=Z(t.latitude,0),r=Z(t.l...
  class ue (line 1) | class ue extends ol.layer.Layer{constructor(e){super(ue.name,{projection...
    method constructor (line 1) | constructor(e){super(ue.name,{projection:"EPSG:900913"}),this.offset={...
    method addTo (line 1) | addTo(e){return this.setMap(e),this}
    method setOrigin (line 1) | setOrigin(){let e=this.map,t=e.getLonLatFromPixel(new OpenLayers.Pixel...
    method setMap (line 1) | setMap(e){this.map||super.setMap.call(this,e),se.appendTo(this.div),fe...
    method removeMap (line 1) | removeMap(e){se.remove(),super.removeMap.call(this,e),this.map=null}
    method onMapResize (line 1) | onMapResize(){let e=this.map;super.onMapResize.call(this),fe({width:e....
    method moveTo (line 1) | moveTo(e,t,i){let r=this.map,a=super.moveTo.call(this,e,t,i);if(!i){le...
    method moveByPx (line 1) | moveByPx(e,t){this.offset.x+=e,this.offset.y+=t;let i=super.moveByPx.c...

FILE: src/Data.js
  class Data (line 2) | class Data {
    method getPixelFootprint (line 4) | static getPixelFootprint (buffer) {
    method resetItems (line 22) | static resetItems () {
    method addRenderItems (line 28) | static addRenderItems (data, allAreNew) {
    method scalePolygon (line 45) | static scalePolygon (buffer, factor) {
    method scale (line 49) | static scale (factor) {
    method scaleItem (line 76) | static scaleItem (item) {
    method set (line 152) | static set (data) {
    method load (line 158) | static load (src, key) {
    method update (line 163) | static update () {
    method loadTile (line 199) | static loadTile (x, y, zoom, callback) {

FILE: src/Debug.js
  class Debug (line 2) | class Debug {
    method point (line 4) | static point (x, y, color, size) {
    method line (line 13) | static line (ax, ay, bx, by, color) {

FILE: src/GeoJSON.js
  constant METERS_PER_LEVEL (line 2) | const METERS_PER_LEVEL = 3;
  function getMaterialColor (line 57) | function getMaterialColor (str) {
  constant WINDING_CLOCKWISE (line 65) | const WINDING_CLOCKWISE = 'CW';
  constant WINDING_COUNTER_CLOCKWISE (line 66) | const WINDING_COUNTER_CLOCKWISE = 'CCW';
  function getWinding (line 69) | function getWinding (points) {
  function makeWinding (line 83) | function makeWinding (points, direction) {
  function alignProperties (line 95) | function alignProperties(prop) {
  function getGeometries (line 149) | function getGeometries (geometry) {
  function clone (line 205) | function clone (obj) {
  class GeoJSON (line 215) | class GeoJSON {
    method read (line 217) | static read (geojson) {

FILE: src/Request.js
  function xhr (line 7) | function xhr (url, callback) {
  class Request (line 47) | class Request {
    method loadJSON (line 49) | static loadJSON (url, callback) {

FILE: src/adapter.js
  function setOrigin (line 2) | function setOrigin (origin) {
  function moveCam (line 7) | function moveCam (offset) {
  function setSize (line 13) | function setSize (size) {
  function setZoom (line 26) | function setZoom (z) {
  function onResize (line 42) | function onResize (e) {
  function onMoveEnd (line 48) | function onMoveEnd (e) {
  function onZoomStart (line 53) | function onZoomStart () {
  function onZoomEnd (line 57) | function onZoomEnd (e) {

FILE: src/engines/Leaflet.js
  class OSMBuildings (line 2) | class OSMBuildings extends L.Layer {
    method constructor (line 4) | constructor (map) {
    method addTo (line 14) | addTo (map) {
    method onAdd (line 19) | onAdd (map) {
    method onRemove (line 53) | onRemove () {
    method onMove (line 76) | onMove (e) {
    method onMoveEnd (line 81) | onMoveEnd (e) {
    method onZoomStart (line 101) | onZoomStart (e) {
    method onZoom (line 105) | onZoom (e) {
    method onZoomEnd (line 128) | onZoomEnd (e) {
    method onResize (line 143) | onResize () {
    method onViewReset (line 146) | onViewReset () {
    method onClick (line 154) | onClick (e) {
    method getOffset (line 161) | getOffset () {
    method style (line 167) | style (style) {
    method date (line 191) | date (date) {
    method load (line 197) | load (url) {
    method set (line 202) | set (data) {
    method each (line 207) | each (handler) {
    method click (line 214) | click (handler) {

FILE: src/engines/OpenLayers.js
  class OSMBuildings (line 3) | class OSMBuildings extends ol.layer.Layer {
    method constructor (line 5) | constructor (map) {
    method addTo (line 16) | addTo (map) {
    method setOrigin (line 21) | setOrigin () {
    method setMap (line 31) | setMap (map) {
    method removeMap (line 52) | removeMap (map) {
    method onMapResize (line 58) | onMapResize () {
    method moveTo (line 64) | moveTo (bounds, zoomChanged, isDragging) {
    method moveByPx (line 92) | moveByPx (dx, dy) {

FILE: src/functions.js
  function rad (line 2) | function rad (deg) {
  function deg (line 6) | function deg (rad) {
  function pixelToGeo (line 10) | function pixelToGeo (x, y) {
  function geoToPixel (line 19) | function geoToPixel (lat, lon) {
  function fromRange (line 29) | function fromRange (sVal, sMin, sMax, dMin, dMax) {
  function isVisible (line 36) | function isVisible (polygon) {

FILE: src/geometry.js
  function getDistance (line 2) | function getDistance (p1, p2) {
  function isRotational (line 9) | function isRotational (polygon) {
  function getSquareSegmentDistance (line 47) | function getSquareSegmentDistance (px, py, p1x, p1y, p2x, p2y) {
  function simplifyPolygon (line 67) | function simplifyPolygon (buffer) {
  function getCenter (line 120) | function getCenter (footprint) {
  constant EARTH_RADIUS (line 131) | let EARTH_RADIUS = 6378137;
  function getLonDelta (line 133) | function getLonDelta (footprint) {

FILE: src/geometry/Cylinder.js
  class Cylinder (line 1) | class Cylinder {
    method draw (line 3) | static draw (context, center, radius, topRadius, height, minHeight, co...
    method simplified (line 48) | static simplified (context, center, radius) {
    method shadow (line 52) | static shadow (context, center, radius, topRadius, height, minHeight) {
    method hitArea (line 78) | static hitArea (context, center, radius, topRadius, height, minHeight,...
    method _circle (line 115) | static _circle (context, center, radius) {
    method _tangents (line 122) | static _tangents (c1, r1, c2, r2) {

FILE: src/geometry/Extrusion.js
  class Extrusion (line 1) | class Extrusion {
    method draw (line 3) | static draw (context, polygon, innerPolygons, height, minHeight, color...
    method _extrude (line 27) | static _extrude (context, polygon, height, minHeight, color, altColor) {
    method _ring (line 77) | static _ring (context, polygon) {
    method simplified (line 84) | static simplified (context, polygon, innerPolygons) {
    method _ringAbs (line 96) | static _ringAbs (context, polygon) {
    method shadow (line 103) | static shadow (context, polygon, innerPolygons, height, minHeight) {
    method hitArea (line 153) | static hitArea (context, polygon, innerPolygons, height, minHeight, co...

FILE: src/geometry/Pyramid.js
  class Pyramid (line 1) | class Pyramid {
    method draw (line 3) | static draw (context, polygon, center, height, minHeight, color, altCo...
    method _triangle (line 40) | static _triangle (context, a, b, c) {
    method _ring (line 46) | static _ring (context, polygon) {
    method shadow (line 53) | static shadow (context, polygon, center, height, minHeight) {
    method hitArea (line 79) | static hitArea (context, polygon, center, height, minHeight, color) {

FILE: src/geometry/__Dome.js
  function rotation (line 1) | function rotation (p, c, a) {
  constant KAPPA (line 11) | let KAPPA = 0.5522847498;
  function dome (line 12) | function dome (c, r, h, minHeight) {
  function drawMeridian (line 93) | function drawMeridian (c, r, _h, hfK, apex, angle) {
  function drawHalfMeridian (line 98) | function drawHalfMeridian (c, r, _h, hfK, apex, angle) {
  function getEllipseTangent (line 107) | function getEllipseTangent (a, b, x, y) {

FILE: src/layers/Buildings.js
  class Buildings (line 1) | class Buildings {
    method init (line 3) | static init (context) {
    method clear (line 7) | static clear () {
    method setOpacity (line 11) | static setOpacity (opacity) {
    method project (line 15) | static project (p, m) {
    method render (line 22) | static render () {

FILE: src/layers/Picking.js
  class Picking (line 2) | class Picking {
    method init (line 4) | static init (context) {
    method setOpacity (line 8) | static setOpacity (opacity) {}
    method clear (line 10) | static clear () {}
    method reset (line 12) | static reset () {
    method render (line 16) | static render () {
    method _render (line 27) | static _render () {
    method getIdFromXY (line 85) | static getIdFromXY (x, y) {
    method idToColor (line 95) | static idToColor (id) {

FILE: src/layers/Shadows.js
  class Shadows (line 1) | class Shadows {
    method init (line 3) | static init (context) {
    method clear (line 7) | static clear () {
    method setOpacity (line 11) | static setOpacity (opacity) {
    method project (line 15) | static project (p, h) {
    method render (line 22) | static render () {

FILE: src/layers/Simplified.js
  class Simplified (line 1) | class Simplified {
    method init (line 3) | static init (context) {
    method clear (line 7) | static clear () {
    method setOpacity (line 11) | static setOpacity (opacity) {
    method isSimple (line 15) | static isSimple (item) {
    method render (line 19) | static render () {

FILE: src/layers/index.js
  function fadeIn (line 3) | function fadeIn() {
  class Layers (line 31) | class Layers {
    method init (line 33) | static init () {
    method clear (line 43) | static clear () {
    method setOpacity (line 50) | static setOpacity (opacity) {
    method render (line 57) | static render (quick) {
    method createContext (line 79) | static createContext (container) {
    method appendTo (line 97) | static appendTo (parentNode) {
    method remove (line 101) | static remove () {
    method setSize (line 105) | static setSize (width, height) {
    method setPosition (line 113) | static setPosition (x, y) {

FILE: src/lib/getSunPosition.js
  function getSunPosition (line 4) | function getSunPosition () {

FILE: src/variables.js
  function onEach (line 39) | function onEach () {}
  function onClick (line 41) | function onClick () {}

FILE: tests/openlayers-5.3.0/OSMBuildings-OL5.js
  class Block (line 13) | class Block {
    method constructor (line 14) | constructor() {}
    method draw (line 16) | draw(
    method _extrude (line 65) | _extrude(context, polygon, height, minHeight, color, altColor) {
    method _ring (line 116) | _ring(context, polygon) {
    method simplified (line 123) | simplified(context, polygon, innerPolygons) {
    method _ringAbs (line 136) | _ringAbs(context, polygon) {
    method shadow (line 143) | shadow(context, polygon, innerPolygons, height, minHeight) {
    method shadowMask (line 199) | shadowMask(context, polygon, innerPolygons) {
    method hitArea (line 208) | hitArea(context, polygon, innerPolygons, height, minHeight, color) {
  class Buildings (line 270) | class Buildings {
    method constructor (line 271) | constructor() {
    method setData (line 274) | setData(data) {
    method project (line 278) | project(p, m) {
    method render (line 285) | render() {
    method setContext (line 458) | setContext(context) {
  class Color (line 464) | class Color {
    method constructor (line 465) | constructor(h, s, l, a) {
    method hue2rgb (line 472) | hue2rgb(p, q, t) {
    method clamp (line 481) | clamp(v, max) {
    method parse (line 489) | static parse(str) {
    method toRGBA (line 538) | toRGBA() {
    method fromRGBA (line 569) | static fromRGBA(r, g, b, a) {
    method toString (line 609) | toString() {
    method hue (line 625) | hue(h) {
    method saturation (line 629) | saturation(s) {
    method lightness (line 633) | lightness(l) {
    method alpha (line 637) | alpha(a) {
  class Cylinder (line 643) | class Cylinder {
    method constructor (line 644) | constructor() {}
    method draw (line 645) | draw(
    method simplified (line 703) | simplified(context, center, radius) {
    method shadow (line 713) | shadow(context, center, radius, topRadius, height, minHeight) {
    method shadowMask (line 742) | shadowMask(context, center, radius) {
    method hitArea (line 751) | hitArea(context, center, radius, topRadius, height, minHeight, color) {
    method _circle (line 791) | _circle(context, center, radius) {
    method _tangents (line 799) | _tangents(c1, r1, c2, r2) {
  class Data (line 850) | class Data {
    method constructor (line 851) | constructor() {
    method getPixelFootprint (line 867) | getPixelFootprint(buffer) {
    method getItems (line 886) | getItems() {
    method resetItems (line 890) | resetItems() {
    method fadeIn (line 896) | fadeIn() {
    method addRenderItems (line 930) | addRenderItems(data, allAreNew) {
    method scale (line 955) | scale(item) {
    method set (line 1036) | set(data) {
    method load (line 1052) | load(src, key) {
    method update (line 1057) | update() {
    method loadTile (line 1099) | loadTile(x, y, zoom, callback) {
  class Debug (line 1111) | class Debug {
    method constructor (line 1112) | constructor() {}
    method point (line 1114) | point(x, y, color, size) {
    method line (line 1123) | line(ax, ay, bx, by, color) {
  class Functions (line 1135) | class Functions {
    method rad (line 1136) | static rad(deg) {
    method deg (line 1140) | static deg(rad) {
    method pixelToGeo (line 1144) | static pixelToGeo(x, y) {
    method geoToPixel (line 1158) | static geoToPixel(lat, lon) {
    method fromRange (line 1170) | static fromRange(sVal, sMin, sMax, dMin, dMax) {
    method isVisible (line 1177) | static isVisible(polygon) {
  class GeoJSON (line 1197) | class GeoJSON {
    method constructor (line 1198) | constructor() {
    method getMaterialColor (line 1258) | getMaterialColor(str) {
    method getWinding (line 1267) | getWinding(points) {
    method makeWinding (line 1286) | makeWinding(points, direction) {
    method alignProperties (line 1298) | alignProperties(prop) {
    method getGeometries (line 1359) | getGeometries(geometry) {
    method clone (line 1427) | clone(obj) {
    method read (line 1437) | read(geojson) {
  class Geometry (line 1491) | class Geometry {
    method getDistance (line 1492) | static getDistance(p1, p2) {
    method isRotational (line 1498) | static isRotational(polygon) {
    method getSquareSegmentDistance (line 1545) | static getSquareSegmentDistance(px, py, p1x, p1y, p2x, p2y) {
    method simplifyPolygon (line 1564) | static simplifyPolygon(buffer) {
    method getCenter (line 1620) | static getCenter(footprint) {
    method getLonDelta (line 1637) | static getLonDelta(footprint) {
  class HitAreas (line 1649) | class HitAreas {
    method constructor (line 1651) | constructor() {
    method setData (line 1657) | setData(data) {
    method reset (line 1661) | reset() {
    method render (line 1665) | render() {
    method _render (line 1676) | _render() {
    method getIdFromXY (line 1814) | getIdFromXY(x, y) {
    method idToColor (line 1825) | idToColor(id) {
    method setContext (line 1837) | setContext(context) {
  class Layers (line 1843) | class Layers {
    method constructor (line 1844) | constructor() {
    method render (line 1862) | render(quick) {
    method createContext (line 1874) | createContext(container) {
    method appendTo (line 1896) | appendTo(parentNode) {
    method remove (line 1900) | remove() {
    method setSize (line 1904) | setSize(width, height) {
    method setPosition (line 1912) | setPosition(x, y) {
  class Pyramid (line 1919) | class Pyramid {
    method constructor (line 1920) | constructor() {}
    method draw (line 1922) | draw(context, polygon, center, height, minHeight, color, altColor) {
    method _triangle (line 1967) | _triangle(context, a, b, c) {
    method _ring (line 1973) | _ring(context, polygon) {
    method shadow (line 1980) | shadow(context, polygon, center, height, minHeight) {
    method shadowMask (line 2014) | shadowMask(context, polygon) {
    method hitArea (line 2018) | hitArea(context, polygon, center, height, minHeight, color) {
  class Request (line 2061) | class Request {
    method constructor (line 2062) | constructor() {
    method xhr (line 2069) | xhr(url, callback) {
    method loadJSON (line 2113) | loadJSON(url, callback) {
  class Shadows (line 2125) | class Shadows {
    method constructor (line 2126) | constructor() {
    method project (line 2141) | project(p, h) {
    method setData (line 2148) | setData(data) {
    method render (line 2152) | render() {
    method setContext (line 2334) | setContext(context) {
  class Simplified (line 2340) | class Simplified {
    method constructor (line 2341) | constructor() {
    method setData (line 2346) | setData(data) {
    method init (line 2350) | init() {
    method isSimple (line 2355) | isSimple(item) {
    method render (line 2361) | render() {
    method setContext (line 2405) | setContext(context) {
  class SunPosition (line 2414) | class SunPosition {
    method constructor (line 2415) | constructor() {
    method init (line 2419) | init() {
    method toJulian (line 2434) | toJulian(date) {
    method toDays (line 2437) | toDays(date) {
    method getRightAscension (line 2440) | getRightAscension(l, b) {
    method getDeclination (line 2446) | getDeclination(l, b) {
    method getAzimuth (line 2452) | getAzimuth(H, phi, dec) {
    method getAltitude (line 2458) | getAltitude(H, phi, dec) {
    method getSiderealTime (line 2464) | getSiderealTime(d, lw) {
    method getSolarMeanAnomaly (line 2467) | getSolarMeanAnomaly(d) {
    method getEquationOfCenter (line 2470) | getEquationOfCenter(M) {
    method getEclipticLongitude (line 2476) | getEclipticLongitude(M, C) {
    method getSunPosition (line 2481) | getSunPosition(date, lat, lon) {
  class OSMBuildings (line 2501) | class OSMBuildings extends VectorLayer {
    method constructor (line 2502) | constructor(map) {
    method setGlobalOrigin (line 2524) | setGlobalOrigin(origin) {
    method moveCam (line 2529) | moveCam(offset) {
    method setSize (line 2535) | setSize(size) {
    method setZoom (line 2548) | setZoom(z) {
    method onResize (line 2564) | onResize(e) {
    method onMoveEnd (line 2570) | onMoveEnd(e) {
    method onZoomStart (line 2575) | onZoomStart() {
    method onZoomEnd (line 2582) | onZoomEnd(e) {
    method setOrigin (line 2589) | setOrigin() {
    method setMap (line 2607) | setMap(map) {
    method set (line 2690) | set(dataToSet) {
    method getDataItems (line 2695) | getDataItems(){
Condensed preview — 44 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (367K chars).
[
  {
    "path": ".gitignore",
    "chars": 28,
    "preview": ".idea/\n_misc/\nnode_modules/\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 3813,
    "preview": "# Changelog\n\n## 3.0.0 @ 2020-03-20 WIP\n\n- refactoring for ES6\n- npm package creation\n\n\n## 0.2.2b @ 2014-11-25\n\n**Feature"
  },
  {
    "path": "LICENSE.md",
    "chars": 1347,
    "preview": "Copyright (c) 2020, OSM Buildings, Jan Marsch <mail@osmbuildings.org>\nAll rights reserved.\n\nRedistribution and use in so"
  },
  {
    "path": "PERFORMANCE.md",
    "chars": 2175,
    "preview": "# Performance\n\nWhat's done & why in terms of performance.\n\n## IDEAS\n\n- combine canvases\n- render items of same style at "
  },
  {
    "path": "README.md",
    "chars": 6267,
    "preview": "\n<img src=\"https://osmbuildings.org/logo.svg\" width=\"100\" height=\"88\">\n\nOSM Buildings is a JavaScript library for showin"
  },
  {
    "path": "build.js",
    "chars": 1985,
    "preview": "\nconst fs = require('fs');\nconst Terser = require('terser');\n\n\n//*******************************************************"
  },
  {
    "path": "dist/OSMBuildings-Leaflet.debug.js",
    "chars": 59870,
    "preview": "const OSMBuildings = (function() {\n\nconst\n  m = Math,\n  exp = m.exp,\n  log = m.log,\n  sin = m.sin,\n  cos = m.cos,\n  tan "
  },
  {
    "path": "dist/OSMBuildings-Leaflet.js",
    "chars": 27500,
    "preview": "const OSMBuildings=function(){const e=Math,t=e.exp,i=e.log,r=e.sin,a=e.cos,o=e.tan,s=e.atan,n=e.atan2,l=e.min,c=e.max,h="
  },
  {
    "path": "dist/OSMBuildings-OpenLayers.debug.js",
    "chars": 57684,
    "preview": "const OSMBuildings = (function() {\n\nconst\n  m = Math,\n  exp = m.exp,\n  log = m.log,\n  sin = m.sin,\n  cos = m.cos,\n  tan "
  },
  {
    "path": "dist/OSMBuildings-OpenLayers.js",
    "chars": 26190,
    "preview": "const OSMBuildings=function(){const e=Math,t=e.exp,i=e.log,r=e.sin,a=e.cos,s=e.tan,o=e.atan,n=e.atan2,l=e.min,c=e.max,h="
  },
  {
    "path": "dist/OSMBuildings.css",
    "chars": 379,
    "preview": ".osmb-container {\n  transform: translate3d(0, 0, 0);\n  pointerEvents: none;\n  position: absolute;\n  left: 0;\n  top: 0;\n}"
  },
  {
    "path": "dist/index-Leaflet-3db.html",
    "chars": 964,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>OSM Buildings for Leaflet</title>\n  <link rel=\"stylesheet\" href=\"https://unpkg.co"
  },
  {
    "path": "dist/index-Leaflet.html",
    "chars": 2170,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>OSM Buildings Classic for Leaflet</title>\n  <link rel=\"stylesheet\" href=\"https://"
  },
  {
    "path": "dist/index-OpenLayers.html",
    "chars": 8170,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>OSM Buildings Classic for OpenLayers</title>\n  <link rel=\"stylesheet\" href=\"https"
  },
  {
    "path": "docs/server.md",
    "chars": 2058,
    "preview": "# OSM Buildings Server #\n\nCode for OSM Buildings data service ist not public.\nHowever, you may use our service for *free"
  },
  {
    "path": "package.json",
    "chars": 465,
    "preview": "{\n  \"name\": \"osmbuildings-classic\",\n  \"description\": \"OSM Buildings Classic\",\n  \"version\": \"3.0.1\",\n  \"homepage\": \"https"
  },
  {
    "path": "src/Data.js",
    "chars": 5138,
    "preview": "\nclass Data {\n\n  static getPixelFootprint (buffer) {\n    let footprint = new Int32Array(buffer.length),\n      px;\n\n    f"
  },
  {
    "path": "src/Debug.js",
    "chars": 503,
    "preview": "\nclass Debug {\n\n  static point (x, y, color, size) {\n    const context = this.context;\n    context.fillStyle = color || "
  },
  {
    "path": "src/GeoJSON.js",
    "chars": 5864,
    "preview": "\nconst METERS_PER_LEVEL = 3;\n\nconst materialColors = {\n  brick:'#cc7755',\n  bronze:'#ffeecc',\n  canvas:'#fff8f0',\n  conc"
  },
  {
    "path": "src/OSMBuildings.css",
    "chars": 379,
    "preview": ".osmb-container {\n  transform: translate3d(0, 0, 0);\n  pointerEvents: none;\n  position: absolute;\n  left: 0;\n  top: 0;\n}"
  },
  {
    "path": "src/Request.js",
    "chars": 1151,
    "preview": "\nlet cacheData = {};\nlet cacheIndex = [];\nlet cacheSize = 0;\nlet maxCacheSize = 1024*1024 * 5; // 5MB\n\nfunction xhr (url"
  },
  {
    "path": "src/adapter.js",
    "chars": 1503,
    "preview": "\nfunction setOrigin (origin) {\n  ORIGIN_X = origin.x;\n  ORIGIN_Y = origin.y;\n}\n\nfunction moveCam (offset) {\n  CAM_X = CE"
  },
  {
    "path": "src/engines/Leaflet.js",
    "chars": 4630,
    "preview": "\nclass OSMBuildings extends L.Layer {\n\n  constructor (map) {\n    super(map);\n\n    this.offset = {x: 0, y: 0};\n    Layers"
  },
  {
    "path": "src/engines/OpenLayers.js",
    "chars": 2444,
    "preview": "// based on a pull request from Jérémy Judéaux (https://github.com/Volune)\n\nclass OSMBuildings extends ol.layer.Layer {\n"
  },
  {
    "path": "src/engines/index-Leaflet.html",
    "chars": 2170,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>OSM Buildings Classic for Leaflet</title>\n  <link rel=\"stylesheet\" href=\"https://"
  },
  {
    "path": "src/engines/index-OpenLayers.html",
    "chars": 8170,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>OSM Buildings Classic for OpenLayers</title>\n  <link rel=\"stylesheet\" href=\"https"
  },
  {
    "path": "src/functions.js",
    "chars": 1179,
    "preview": "\nfunction rad (deg) {\n  return deg * PI / 180;\n}\n\nfunction deg (rad) {\n  return rad / PI * 180;\n}\n\nfunction pixelToGeo ("
  },
  {
    "path": "src/geometry/Cylinder.js",
    "chars": 4885,
    "preview": "class Cylinder {\n\n  static draw (context, center, radius, topRadius, height, minHeight, color, altColor, roofColor) {\n  "
  },
  {
    "path": "src/geometry/Extrusion.js",
    "chars": 5331,
    "preview": "class Extrusion {\n\n  static draw (context, polygon, innerPolygons, height, minHeight, color, altColor, roofColor) {\n    "
  },
  {
    "path": "src/geometry/Pyramid.js",
    "chars": 3131,
    "preview": "class Pyramid {\n\n  static draw (context, polygon, center, height, minHeight, color, altColor) {\n    let\n      c = { x:ce"
  },
  {
    "path": "src/geometry/__Dome.js",
    "chars": 3034,
    "preview": "function rotation (p, c, a) {\n  let ms = sin(a), mc = cos(a);\n  p.x -= c.x;\n  p.y -= c.y;\n  return {\n    x: p.x* mc + p."
  },
  {
    "path": "src/geometry.js",
    "chars": 3074,
    "preview": "\nfunction getDistance (p1, p2) {\n  const\n    dx = p1.x-p2.x,\n    dy = p1.y-p2.y;\n  return dx*dx + dy*dy;\n}\n\nfunction isR"
  },
  {
    "path": "src/layers/Buildings.js",
    "chars": 2850,
    "preview": "class Buildings {\n\n  static init (context) {\n    this.context = context;\n  }\n\n  static clear () {\n    this.context.clear"
  },
  {
    "path": "src/layers/Picking.js",
    "chars": 3022,
    "preview": "\nclass Picking {\n\n  static init (context) {\n    this.context = context;\n  }\n\n  static setOpacity (opacity) {}\n\n  static "
  },
  {
    "path": "src/layers/Shadows.js",
    "chars": 2819,
    "preview": "class Shadows {\n\n  static init (context) {\n    this.context = context;\n  }\n\n  static clear () {\n    this.context.clearRe"
  },
  {
    "path": "src/layers/Simplified.js",
    "chars": 1449,
    "preview": "class Simplified {\n\n  static init (context) {\n    this.context = context;\n  }\n\n  static clear () {\n    this.context.clea"
  },
  {
    "path": "src/layers/index.js",
    "chars": 2610,
    "preview": "let animTimer;\n\nfunction fadeIn() {\n  if (animTimer) {\n    return;\n  }\n\n  animTimer = setInterval(t => {\n    let dataIte"
  },
  {
    "path": "src/lib/getSunPosition.js",
    "chars": 1832,
    "preview": "// calculations are based on http://aa.quae.nl/en/reken/zonpositie.html\n// code credits to Vladimir Agafonkin (@mourner)"
  },
  {
    "path": "src/shortcuts.js",
    "chars": 209,
    "preview": "\nconst\n  m = Math,\n  exp = m.exp,\n  log = m.log,\n  sin = m.sin,\n  cos = m.cos,\n  tan = m.tan,\n  atan = m.atan,\n  atan2 ="
  },
  {
    "path": "src/variables.js",
    "chars": 907,
    "preview": "let\n  VERSION      = '0.3.2',\n  ATTRIBUTION  = '&copy; <a href=\"https://osmbuildings.org\">OSM Buildings</a>',\n\n  DATA_SR"
  },
  {
    "path": "tests/openlayers-5.3.0/OSMBuildings-OL5.js",
    "chars": 77402,
    "preview": "/**\n * Copyright (C) 2019 OSM Buildings, Jan Marsch\n * A JavaScript library for visualizing building geometry on interac"
  },
  {
    "path": "tests/openlayers-5.3.0/README.md",
    "chars": 762,
    "preview": "# OpenLayers 5 OSM Buildings Support\n\n1. Install dependencies from package.json.\n```bash\nnpm install\n```\n2. Run local se"
  },
  {
    "path": "tests/openlayers-5.3.0/index.js",
    "chars": 1088,
    "preview": "// Map\nimport Map from 'ol/Map.js';\nimport View from 'ol/View.js';\n// Layers\nimport { Tile as TileLayer} from 'ol/layer."
  },
  {
    "path": "tests/shadows.html",
    "chars": 2235,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n<title>OSM Buildings - Shadows</title>\n<meta http-equiv=\"content-type\" content=\"text/html;"
  }
]

About this extraction

This page contains the full source code of the kekscom/osmbuildings GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 44 files (342.6 KB), approximately 108.2k tokens, and a symbol index with 798 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!