text: /**
   * Cesium - https://github.com/CesiumGS/cesium
   *
   * Copyright 2011-2020 Cesium Contributors
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   * Columbus View (Pat. Pend.)
   *
   * Portions licensed separately.
   * See https://github.com/CesiumGS/cesium/blob/master/LICENSE.md for full licensing details.
   */
  define(['./when-8d13db60', './Check-70bec281', './Math-61ede240', './FeatureDetection-7bd32c34', './createTaskProcessorWorker', './Color-69f1845f', './pbf-9fe59c76'], function (when, Check, _Math, FeatureDetection, createTaskProcessorWorker, Color, pbf) {
  
      // 用来根据Mapbox style标准构建过滤器和定义过滤方法
      function MvtFilter() {
      }
  
      /**
       * 根据根据Mapbox style的过滤器对象构造过滤条件
       * @param filter 输入过滤器
       * @returns 返回过滤条件数组
       */
      MvtFilter.parseLayerFilter = function(filter) {
          if (!when.defined(filter) || !(filter instanceof Array)) {
              return null;
          }
  
          var filterArray = [];
          var condition;
          if (isOperator(filter[0])) {
              condition = parseSingleFilterArray(filter);
              if(when.defined(condition)){
                  filterArray.push(condition);
              }
          }
          else{
              for (var fi = 0; fi < filter.length; fi++) {
                  if (!(filter[fi] instanceof Array)) {
                      continue;
                  }
  
                  if (filter[fi].length !== 3) {
                      for (var fj = 0; fj < filter[fi].length; fj++) {
                          if (filter[fi][fj] instanceof Array && filter[fi][fj].length === 3) {
                              condition = parseSingleFilterArray(filter[fi][fj]);
                              if(when.defined(condition)){
                                  filterArray.push(condition);
                              }
                          }
                      }
                  } else {
                      condition = parseSingleFilterArray(filter[fi]);
                      if(when.defined(condition)){
                          filterArray.push(condition);
                      }
                  }
  
              }
          }
          return filterArray;
      };
  
      /**
       * 根据对象的属性进行过滤条件测试
       * @param filterArray 过滤条件数组
       * @param properties 对象属性
       * @returns 通过过滤条件返回true
       */
      MvtFilter.filterTest = function(properties, filterArray) {
          for(var filterIdx = 0, filterCount = filterArray.length; filterIdx < filterCount; filterIdx++){
              var filter = filterArray[filterIdx];
              if(!compareFunctions[filter.filterOperator](properties, filter.filterFieldName, filter.filterCompareValue)){
                  return false;
              }
          }
          return true;
      };
  
      function parseSingleFilterArray(filterArray){
          var filterCompareValue = null;
          var filterFieldName = null;
          var filterOperator = null;
          if (isOperator(filterArray[0])) {
              filterOperator = filterArray[0];
          }
          else{
              return null;
          }
  
          if(filterArray.length > 1){
              filterFieldName = filterArray[1];
              // TODO: 系统字段暂不处理
              if(filterFieldName[0] === "$" ){
                  return null;
              }
          }
          if(filterArray.length > 2){
              filterCompareValue = filterArray[2];
          }
          return {
              filterOperator : filterOperator,
              filterFieldName : filterFieldName,
              filterCompareValue : filterCompareValue
          };
      }
  
      function isOperator(key) {
          return ["==", "===", ">=", "<=", ">", "<", "!=", "has"].indexOf(key) !== -1;
      }
  
      function equalFunction(properties, fieldName, testValue){
          return properties[fieldName] == testValue;
      }
  
      function greaterFunction(properties, fieldName, testValue){
          return properties[fieldName] > testValue;
      }
  
      function lessFunction(properties, fieldName, testValue){
          return properties[fieldName] < testValue;
      }
  
      function greaterEqualFunction(properties, fieldName, testValue){
          return properties[fieldName] >= testValue;
      }
  
      function lessEqualFunction(properties, fieldName, testValue){
          return properties[fieldName] <= testValue;
      }
  
      function notEqualFunction(properties, fieldName, testValue){
          return properties[fieldName] != testValue;
      }
  
      function hasFunction(properties, fieldName){
          return when.defined(properties[fieldName]);
      }
  
      var compareFunctions = {
          "==" : equalFunction,
          "===" : equalFunction,
          ">" : greaterFunction,
          "<" : lessFunction,
          ">=" : greaterEqualFunction,
          "<=" : lessEqualFunction,
          "!=" : notEqualFunction,
          "has" : hasFunction
      };
  
      function MvtStyle(openlayer, useOffscreen) {
          if(!openlayer){
              throw new Check.DeveloperError('need include ol-debug.js');
          }
          this._useOffscreen = useOffscreen;
          this._openlayer = openlayer;
      }
  
      Object.defineProperties(MvtStyle.prototype, {
          proxy: {
              get: function() {}
          }
      });
  
      MvtStyle.prototype.getStyle = function() {
          var openlayer = this._openlayer;
          var fill = new openlayer.style.Fill({
              color: ""
          });
          fill.setColor("#ffffff");
  
          var stroke = new openlayer.style.Stroke({
              color: "",
              width: 1
          });
          stroke.setWidth(1);
          stroke.setColor("#000000");
  
          var fillAndOutlineStyle = new openlayer.style.Style({
              fill: fill,
              stroke: stroke
          });
  
          return fillAndOutlineStyle;
      };
  
      function parseMapboxColorString(colorString){
          var tempS = colorString.substring(colorString.indexOf("(") + 1, colorString.indexOf(")"));
          tempS = tempS.split(",");
          var resultColor = [];
          resultColor.push(parseFloat(tempS[0]));
          resultColor.push(parseFloat(tempS[1]));
          resultColor.push(parseFloat(tempS[2]));
          resultColor.push(parseFloat(tempS[3]));
          return resultColor;
      }
  
      function colorWithOpacity(color, opacity) {
          if (color && opacity !== undefined) {
              var colorData = {
                  color: [
                      color[0] * 255 / color[3],
                      color[1] * 255 / color[3],
                      color[2] * 255 / color[3],
                      color[3]
                  ],
                  opacity: color[3]
              };
              color = colorData.color;
              color[3] = colorData.opacity * opacity;
              if (color[3] === 0) {
                  color = undefined;
              }
          }
          return color;
      }
  
      MvtStyle.prototype.getStyleByMapboxStyle = function(mapboxStyle) {
          var openlayer = this._openlayer;
          var type = mapboxStyle.type;
          var paint = mapboxStyle.paint;
          var layout = mapboxStyle.layout;
          if(!when.defined(type) || !when.defined(paint)){
              return this.getStyle();
          }
          if(type == "fill"){
              var fillStyle = new openlayer.style.Style({
              });
              var fill = new openlayer.style.Fill({
                  color: "[255,255,255,1]"
              });
              fillStyle.setFill(fill);
              var fillOpcatiy = paint["fill-opacity"];
              if (when.defined(paint["fill-color"])) {
                  var fillColor = parseMapboxColorString(paint["fill-color"]);
                  if(when.defined(fillOpcatiy)){
                      fillColor[3] *= fillOpcatiy;
                  }
                  fill.setColor(fillColor);
              }
              if (when.defined(paint["fill-outline-color"])) {
                  var fillOutlineStroke = new openlayer.style.Stroke({
                      color: "",
                      width: 1
                  });
                  fillOutlineStroke.setColor(paint["fill-outline-color"]);
                  fillStyle.setStroke(fillOutlineStroke);
              }
              if (when.defined(paint["fill-pattern"])) {
                  fillStyle.fillPatternName = paint["fill-pattern"];
              }
              return fillStyle;
          }
          else if(type == "line"){
              var lineStyle = new openlayer.style.Style({
              });
              var lineStroke = new openlayer.style.Stroke({
                  color: "#000000",
                  width: 1
              });
              lineStyle.setStroke(lineStroke);
              var lineOpcatiy = paint["line-opacity"];
              if (when.defined(paint["line-color"])) {
                  var lineColor = parseMapboxColorString(paint["line-color"]);
                  if(when.defined(lineOpcatiy)){
                      lineColor[3] *= lineOpcatiy;
                  }
              }
              if (when.defined(paint["line-width"])) {
                  var lineWidth = paint["line-width"];
                  lineStroke.setWidth(lineWidth);
              }
  
              if (when.defined(paint["line-dasharray"])) {
                  var lineDasharray = paint["line-dasharray"];
                  lineStroke.setLineDash(lineDasharray);
              }
  
              if(when.defined(layout)){
                  if (when.defined(layout["line-cap"])) {
                      var lineCap = layout["line-cap"];
                      lineStroke.setLineCap(lineCap);
                  }
                  if (when.defined(layout["line-join"])) {
                      var lineJoin = layout["line-join"];
                      lineStroke.setLineJoin(lineJoin);
                  }
                  if (when.defined(layout["line-miter-limit"])) {
                      var lineMiterLimit = layout["line-miter-limit"];
                      lineStroke.setMiterLimit(lineMiterLimit);
                  }
              }
  
              lineStroke.setColor(lineColor);
              return lineStyle;
          }
          else if(type == "symbol"){
              var iconStyle = new openlayer.style.Style({
              });
              if(when.defined(layout) && when.defined(layout["icon-image"])){
                  iconStyle.hasIconImage = true;
              }
              if(when.defined(layout) && when.defined(layout["text-field"])){
                  iconStyle.hasTextStyle = true;
              }
              return iconStyle;
          }
          else if(type == "circle"){
              var circleRadius = paint["circle-radius"];
              var circleColor = paint["circle-color"];
              var circleStrokeColor = paint["circle-stroke-color"];
              var circleOpacity = paint["circle-opacity"];
              var circleStrokeOpacity = paint["circle-stroke-opacity"];
              var circleStrokeWidth = paint["circle-stroke-width"];
              var iconImg = new openlayer.style.Circle({
                  radius: circleRadius,
                  stroke: circleStrokeWidth === 0 ? undefined : new openlayer.style.Stroke({
                      width: circleStrokeWidth,
                      color: colorWithOpacity(circleStrokeColor, circleStrokeOpacity)
                  }),
                  fill: new openlayer.style.Fill({
                      color: colorWithOpacity(circleColor, circleOpacity)
                  })
              });
              var circleStyle = new openlayer.style.Style({
              });
              circleStyle.setImage(iconImg);
              return circleStyle;
          }
          else{
              return this.getStyle();
          }
      };
  
      var scratchIDColor = new Color.Color();
      function convertIDtoColor(id, layerID){
          var colorB = Math.floor(id / 65536);
          var d = id - colorB * 65536;
          var colorG = Math.floor(d / 256);
          var colorR = d - colorG * 256;
          var alpha = 1;
          scratchIDColor.red = colorR / 256;
          scratchIDColor.green = colorG / 256;
          scratchIDColor.blue = colorB / 256;
          scratchIDColor.alpha = alpha;
          return scratchIDColor;
      }
  
      MvtStyle.prototype.getIDColorStyle = function(geometryType, id, layerID, lineWidth, radius, lineWidthExpand) {
          var openlayer = this._openlayer;
          var idColor = convertIDtoColor(id);
          var cssColor = idColor.toCssColorString();
          if(geometryType ==  'LineString' || geometryType ==  'LinearRing' || geometryType ==  'MultiLineString'){
              var expandWidth = 4;
              if (when.defined(lineWidth)) {
                  expandWidth = lineWidth * 2 + lineWidthExpand;
              }
              var scratchIDStroke = new openlayer.style.Stroke({
                  color: "",
                  width: expandWidth
              });
              scratchIDStroke.setColor(cssColor);
              return new openlayer.style.Style({
                  stroke: scratchIDStroke
              });
          }
          else if(geometryType == 'Point' || geometryType == 'MultiPoint'){
              var iconImg = new openlayer.style.Circle({
                  radius: (radius - 0.5),
                  fill: new openlayer.style.Fill({
                      color: cssColor
                  })
              });
              var circleStyle = new openlayer.style.Style({
              });
              circleStyle.setImage(iconImg);
              return circleStyle;
          }
          else{
              var scratchIDFill = new openlayer.style.Fill({
                  color: ""
              });
              scratchIDFill.setColor(cssColor);
              var resultStyle = new openlayer.style.Style({
                  fill: scratchIDFill
              });
              if(when.defined(lineWidth)){
                  var scratchIDStroke = new openlayer.style.Stroke({
                      color: "",
                      width: when.defined(lineWidth) ? lineWidth * 2 : 4
                  });
                  scratchIDStroke.setColor(cssColor);
                  resultStyle.setStroke(scratchIDStroke);
              }
              return resultStyle;
          }
      };
  
      var templateRegEx = /^([^]*)\{(.*)\}([^]*)$/;
      function fromTemplate(text, properties) {
          var parts;
          do {
              parts = text.match(templateRegEx);
              if (parts) {
                  const value = properties[parts[2]] || '';
                  text = parts[1] + value + parts[3];
              }
          } while (parts);
          return text;
      }
  
      MvtStyle.prototype.getTextStyle = function(oldStyle, feature, mapboxStyleLayer) {
          var openlayer = this._openlayer;
          var paint = mapboxStyleLayer.paint;
          var layout = mapboxStyleLayer.layout;
          var textField = layout['text-field'];
          var label = fromTemplate(textField, feature.getProperties());
          if(!when.defined(label)){
              return;
          }
          var style = new openlayer.style.Style();
          var text = new openlayer.style.Text();
          style.setText(text);
          var textSize = layout['text-size'];
          var font = when.defaultValue(layout['text-font'], ['Open Sans Regular', 'Arial Unicode MS Regular']);
          var textTransform = layout['text-transform'];
          if (textTransform == 'uppercase') {
              label = label.toUpperCase();
          } else if (textTransform == 'lowercase') {
              label = label.toLowerCase();
          }
          var textMaxWidth = when.defaultValue(layout['text-max-width'], 10);
          //var wrappedLabel = wrapText(label, font, textMaxWidth);
          var wrappedLabel = label;
          text.setText(wrappedLabel);
          text.setFont(font);
          text.setRotation(0);
  
          var textAnchor = when.defaultValue(layout['text-anchor'], 'center');
          var placement = when.defaultValue(layout['symbol-placement'], 'point');
          text.setPlacement(placement);
          if (placement == 'point') {
              var textAlign = 'center';
              if (textAnchor.indexOf('left') !== -1) {
                  textAlign = 'left';
              } else if (textAnchor.indexOf('right') !== -1) {
                  textAlign = 'right';
              }
              text.setTextAlign(textAlign);
          } else {
              text.setTextAlign();
          }
          var textBaseline = 'middle';
          if (textAnchor.indexOf('bottom') == 0) {
              textBaseline = 'bottom';
          } else if (textAnchor.indexOf('top') == 0) {
              textBaseline = 'top';
          }
          text.setTextBaseline(textBaseline);
          var textOffset = when.defaultValue(layout['text-offset'], [0.0, 0.0]);
          var textTranslate = when.defaultValue(layout['text-translate'], [0.0, 0.0]);
          text.setOffsetX(textOffset[0] * textSize + textTranslate[0]);
          text.setOffsetY(textOffset[1] * textSize + textTranslate[1]);
          var opacity = paint['text-opacity'];
          var textColorFill = new openlayer.style.Fill();
          var textColor = paint["text-color"];
          if(when.defined(textColor)){
              textColor = parseMapboxColorString(textColor);
              textColorFill.setColor(textColor);
          }
          text.setFill(textColorFill);
          var textHaloColor = paint["text-halo-color"];
          if (when.defined(textHaloColor)) {
              var textHalo = new openlayer.style.Stroke();
              textHaloColor = parseMapboxColorString(textHaloColor);
              textHalo.setColor(textHaloColor);
              textHalo.setWidth(paint['text-halo-width']);
              text.setStroke(textHalo);
          } else {
              text.setStroke(undefined);
          }
          style.setZIndex(oldStyle.getZIndex());
          style.hasIconImage = oldStyle.hasIconImage;
          style.textSize = textSize;
          return style;
      };
  
      function covertIconAnchor(iconAnchor) {
          var anchorOffset = [0.5, 0.5];
          if (['top-left', 'top-right', 'bottom-left', 'bottom-right'].includes(iconAnchor)) {
              anchorOffset = [0, 0];
          }
          if (iconAnchor === 'left') {
              iconAnchor = 'top-left';
              anchorOffset = [0, 0.5];
          }
          if (iconAnchor === 'right') {
              iconAnchor = 'top-left';
              anchorOffset = [1, 0.5];
          }
          if (iconAnchor === 'bottom') {
              iconAnchor = 'top-left';
              anchorOffset = [0.5, 1];
          }
          if (iconAnchor === 'top') {
              iconAnchor = 'top-left';
              anchorOffset = [0.5, 0];
          }
          return {
              anchorOffset: anchorOffset,
              iconAnchor: iconAnchor
          };
      }
  
      var iconImageCache = {};
  
      MvtStyle.prototype.setIconImageForStyle = function(spriteImageDatas, style, mapboxStyleLayer){
          var openlayer = this._openlayer;
          var paint = mapboxStyleLayer.paint;
          var layout = mapboxStyleLayer.layout;
          var iconSize = when.defaultValue(layout["icon-size"], 1);
          var iconColor = paint['icon-color'];
          var iconTranslate = when.defaultValue(paint['icon-translate'], [0.0, 0.0]);
          var iconTranslateAnchor = when.defaultValue(paint['icon-translate-anchor'], 'map');
          var iconAnchor = when.defaultValue(layout['icon-anchor'], 'center');
          var anchorOffsetAndIconAnchor = covertIconAnchor(iconAnchor);
          var anchorOffset = anchorOffsetAndIconAnchor.anchorOffset;
          var iconOffset = when.defaultValue(layout['iconoffset'], [0.0, 0.0]);
          var iconOpacity = when.defaultValue(layout['icon-opacity'], 1.0);
          var spriteImageName = layout["icon-image"];
          if(!when.defined(spriteImageDatas[spriteImageName])){
              console.log('miss icon-image ' + spriteImageName);
              return;
          }
  
          var icon_cache_key = spriteImageName + '.' + iconSize + '.' + iconTranslate + '.' + iconTranslateAnchor + '.' + iconAnchor + '.' + iconOffset;
          if (when.defined(iconColor)) {
              icon_cache_key += '.' + iconColor;
          }
          var iconImg = iconImageCache[icon_cache_key];
          if(!when.defined(iconImg)){
              var spriteImage = spriteImageDatas[spriteImageName];
              var canvas;
              if(this._useOffscreen){
                  canvas = new OffscreenCanvas(spriteImage.width, spriteImage.height);
              }
              else{
                  canvas = document.createElement('canvas');
                  canvas.width = spriteImage.width;
                  canvas.height = spriteImage.height;
              }
              var ctx = canvas.getContext('2d');
              ctx.putImageData(spriteImage, 0, 0);
              var translateOffset = [iconTranslate[0] / spriteImage.width, iconTranslate[1] / spriteImage.height];
              iconImg = new openlayer.style.Icon({
                  img: canvas,
                  anchorOrigin: anchorOffsetAndIconAnchor.iconAnchor,
                  anchor: [iconOffset[0] + anchorOffset[0] + translateOffset[0], iconOffset[1] + anchorOffset[1] - translateOffset[1]],
                  imgSize: [canvas.width, canvas.height],
                  scale: iconSize
              });
              iconImg.setOpacity(iconOpacity);
              iconImageCache[icon_cache_key] = iconImg;
          }
  
          style.setImage(iconImg);
          //style.setText(undefined);
      };
  
      var VALUE_EXTENT = 4096;
      var replays = ["Default", "Polygon", "LineString", "Image", "Symbol", "Text"];
  
      function MvtRenderer2D(options) {
          this._mvtStyleClass = options.mvtStyle;
          this._openlayer = options.openlayer;
      }
  
      Object.defineProperties(MvtRenderer2D.prototype, {
      });
  
      MvtRenderer2D.prototype.renderFeatures = function(options) {
          var canvas = options.colorCanvas;
          var idCanvas = options.idCanvas;
          var transform = options.transform;
          var layers = options.layers;
          var features = options.features;
          var tileLevel = options.tileLevel;
          var spriteImageCanvas = options.spriteImageCanvas;
          var spriteImageDatas = options.spriteImageDatas;
          var squaredTolerance = options.squaredTolerance;
          var showBillboard = options.showBillboard;
          var renderID = options.renderID;
          var renderColor = options.renderColor;
          var lineWidthExpand = options.lineWidthExpand;
  
          var ol = this._openlayer;
          var ctx = canvas.getContext('2d');
  
          var idFeatures = [];
          var iconImageObjects = [];
          var textObjects = [];
          var style = null;
          var declutterTree = ol.ext.rbush(9);
          var replayGroup = new ol.render.canvas.ReplayGroup(0, [0, 0, VALUE_EXTENT, VALUE_EXTENT], 8, 2, true, declutterTree);
  
          var featureLength = features.length;
          for (var r = 0; r < featureLength; r++) {
              var feature = features[r];
              var sourceLayer = feature.getProperties().layer;
              feature.index = sourceLayer + feature.getId();
              var featureHasStyle = false;
              var layerGroupById = layers[sourceLayer];
              var zIndex = 0;
              for (var layerId in layerGroupById) {
                  var layerById = layerGroupById[layerId];
  
                  var maxzoom = layerById.mapboxStyleLayer.maxzoom;
                  var minzoom = layerById.mapboxStyleLayer.minzoom;
                  if(tileLevel < minzoom || tileLevel > maxzoom){
                      continue;
                  }
  
                  var filterArray = layerById.filterArray;
                  if (!when.defined(filterArray)) {
                      style = this._mvtStyleClass.getStyleByMapboxStyle(layerById.mapboxStyleLayer);
                  }
                  else {
                      var properties = feature.getProperties();
                      if (MvtFilter.filterTest(properties, filterArray)) {
                          style = this._mvtStyleClass.getStyleByMapboxStyle(layerById.mapboxStyleLayer);
                      }
                      else {
                          continue;
                      }
                  }
  
                  if (!when.defined(style)) {
                      continue;
                  }
  
                  this.createFillPatternForStyle(style, spriteImageCanvas, spriteImageDatas, ctx);
  
                  if (when.defined(style.hasTextStyle)) {
                      var textStyle = this._mvtStyleClass.getTextStyle(style, feature, layerById.mapboxStyleLayer);
                      textStyle.setZIndex(zIndex);
                      if(showBillboard){
                          textObjects.push({
                              feature : feature,
                              style : textStyle
                          });
                      }
                      else{
                          if(renderColor){
                              ol.renderer.vector.renderFeature_(replayGroup, feature, textStyle, -1);
                          }
                      }
                  }
  
                  if (when.defined(style.hasIconImage) && !when.defined(style.getImage())) {
                      if(showBillboard){
                          iconImageObjects.push({
                              feature : feature,
                              style : layerById.mapboxStyleLayer
                          });
                          continue;
                      }
                      else{
                          this._mvtStyleClass.setIconImageForStyle(spriteImageDatas, style, layerById.mapboxStyleLayer);
                      }
                  }
  
                  style.setZIndex(zIndex);
                  this.setPickStyleInFeature(feature, style);
                  zIndex++;
  
                  if(renderColor){
                      ol.renderer.vector.renderFeature_(replayGroup, feature, style, -1);
                  }
                  featureHasStyle = true;
              }
              if (featureHasStyle) {
                  idFeatures.push(feature);
              }
          }
  
          if(renderColor){
              replayGroup.finish();
              var declutterReplays = {};
              replayGroup.replay(ctx, transform, 0, {}, replays, declutterReplays);
              if (declutterReplays) {
                  ol.render.canvas.ReplayGroup.replayDeclutter(declutterReplays, ctx, 0.0);
              }
          }
  
          replayGroup = null;
          if(renderID){
              this.renderIDtoTexture(transform, idCanvas, idFeatures, 0, squaredTolerance, lineWidthExpand);
          }
          return {
              idFeatures : idFeatures,
              iconImageObjects : iconImageObjects,
              textObjects : textObjects
          }
      };
  
      MvtRenderer2D.prototype.renderIDtoTexture = function(transform, canvas, features, layerID, tileTolerance, lineWidthExpand) {
          var ol = this._openlayer;
          var ctx = canvas.getContext('2d');
          var declutterTree = ol.ext.rbush(9);
          var replayGroup = new ol.render.canvas.ReplayGroup(0, [0, 0, VALUE_EXTENT, VALUE_EXTENT], 8, 2, true, declutterTree);
          var featureLength = features.length;
          for (var r = 0; r < featureLength; r++) {
              var feature = features[r];
              var id = getFeatureID(feature);
              var idStyle = this._mvtStyleClass.getIDColorStyle(feature.getGeometry().getType(), id, layerID, feature.lineWidth, feature.radius, lineWidthExpand);
              idStyle.setZIndex(feature.zIndex);
              ol.renderer.vector.renderFeature_(replayGroup, feature, idStyle, -1);
          }
          replayGroup.finish();
          var declutterReplays = {};
          replayGroup.replay(ctx, transform, 0, {}, replays, declutterReplays);
          if (declutterReplays) {
              ol.render.canvas.ReplayGroup.replayDeclutter(declutterReplays, ctx, 0.0);
          }
          replayGroup = null;
      };
  
      MvtRenderer2D.prototype.createFillPatternForStyle = function(style, spriteImageCanvas, subSpriteImage, ctx){
          if(!when.defined(style.fillPatternName)){
              return;
          }
          var patternName = style.fillPatternName;
          var patternCanvas = null;
          if (when.defined(spriteImageCanvas[patternName])) {
              patternCanvas = spriteImageCanvas[patternName];
          }
          else {
              var imageData = subSpriteImage[patternName];
              if (!when.defined(imageData)) {
                  console.log('miss sprite ' + patternName);
                  return;
              }
              patternCanvas = document.createElement('canvas');
              patternCanvas.width = imageData.width;
              patternCanvas.height = imageData.height;
              var spriteCtx = patternCanvas.getContext('2d');
              spriteCtx.putImageData(imageData, 0, 0);
              spriteImageCanvas[patternName] = patternCanvas;
          }
          style.fill_.color_ = ctx.createPattern(patternCanvas, 'repeat');
      };
  
      MvtRenderer2D.prototype.setPickStyleInFeature = function(feature, style){
          var ol = this._openlayer;
          feature.zIndex = style.getZIndex();
          if(when.defined(style.getStroke())){
              var styleLineWidth = style.getStroke().getWidth();
              if(when.defined(feature.lineWidth)){
                  feature.lineWidth = Math.max(feature.lineWidth, styleLineWidth);
              }
              else{
                  feature.lineWidth = styleLineWidth;
              }
          }
          if(when.defined(style.getImage())){
              var imageStyle = style.getImage();
              var radius = 1.0;
              if(imageStyle instanceof ol.style.Icon ){
                  var imageSize = imageStyle.getImageSize();
                  radius = Math.max(imageSize[0], imageSize[1]) / 2.0;
                  radius -= 1.0;
              }
              else if(imageStyle instanceof ol.style.Circle ){
                  radius = imageStyle.getRadius();
              }
              if(when.defined(feature.radius)){
                  feature.radius = Math.max(feature.radius, radius);
              }
              else{
                  feature.radius = radius;
              }
          }
      };
  
      function getFeatureID(feature) {
          var id = feature.getId();
          // 只在颜色中记录256*256*256这么大范围的ID，超过这个范围的ID舍去
          var discard = Math.floor(id / 16777216);
          id = id - discard * 16777216;
          return id;
      }
  
      function ol() {
          }
  
          ol.array = {};
  
          /**
           * Performs a binary search on the provided sorted list and returns the index of the item if found. If it can't be found it'll return -1.
           * https://github.com/darkskyapp/binary-search
           *
           * @param {Array.<*>} haystack Items to search through.
           * @param {*} needle The item to look for.
           * @param {Function=} opt_comparator Comparator function.
           * @return {number} The index of the item if found, -1 if not.
           */
          ol.array.binarySearch = function(haystack, needle, opt_comparator) {
              var mid, cmp;
              var comparator = opt_comparator || ol.array.numberSafeCompareFunction;
              var low = 0;
              var high = haystack.length;
              var found = false;
  
              while (low < high) {
                  /* Note that "(low + high) >>> 1" may overflow, and results in a typecast
                   * to double (which gives the wrong results). */
                  mid = low + (high - low >> 1);
                  cmp = +comparator(haystack[mid], needle);
  
                  if (cmp < 0.0) { /* Too low. */
                      low  = mid + 1;
  
                  } else { /* Key found or too high */
                      high = mid;
                      found = !cmp;
                  }
              }
  
              /* Key not found. */
              return found ? low : ~low;
          };
  
  
          /**
           * Compare function for array sort that is safe for numbers.
           * @param {*} a The first object to be compared.
           * @param {*} b The second object to be compared.
           * @return {number} A negative number, zero, or a positive number as the first
           *     argument is less than, equal to, or greater than the second.
           */
          ol.array.numberSafeCompareFunction = function(a, b) {
              return a > b ? 1 : a < b ? -1 : 0;
          };
  
  
          /**
           * Whether the array contains the given object.
           * @param {Array.<*>} arr The array to test for the presence of the element.
           * @param {*} obj The object for which to test.
           * @return {boolean} The object is in the array.
           */
          ol.array.includes = function(arr, obj) {
              return arr.indexOf(obj) >= 0;
          };
  
  
          /**
           * @param {Array.<number>} arr Array.
           * @param {number} target Target.
           * @param {number} direction 0 means return the nearest, > 0
           *    means return the largest nearest, < 0 means return the
           *    smallest nearest.
           * @return {number} Index.
           */
          ol.array.linearFindNearest = function(arr, target, direction) {
              var n = arr.length;
              if (arr[0] <= target) {
                  return 0;
              } else if (target <= arr[n - 1]) {
                  return n - 1;
              } else {
                  var i;
                  if (direction > 0) {
                      for (i = 1; i < n; ++i) {
                          if (arr[i] < target) {
                              return i - 1;
                          }
                      }
                  } else if (direction < 0) {
                      for (i = 1; i < n; ++i) {
                          if (arr[i] <= target) {
                              return i;
                          }
                      }
                  } else {
                      for (i = 1; i < n; ++i) {
                          if (arr[i] == target) {
                              return i;
                          } else if (arr[i] < target) {
                              if (arr[i - 1] - target < target - arr[i]) {
                                  return i - 1;
                              } else {
                                  return i;
                              }
                          }
                      }
                  }
                  return n - 1;
              }
          };
  
  
          /**
           * @param {Array.<*>} arr Array.
           * @param {number} begin Begin index.
           * @param {number} end End index.
           */
          ol.array.reverseSubArray = function(arr, begin, end) {
              while (begin < end) {
                  var tmp = arr[begin];
                  arr[begin] = arr[end];
                  arr[end] = tmp;
                  ++begin;
                  --end;
              }
          };
  
          /**
           * @param {Array.<VALUE>} arr The array to modify.
           * @param {Array.<VALUE>|VALUE} data The elements or arrays of elements
           *     to add to arr.
           * @template VALUE
           */
          ol.array.extend = function(arr, data) {
              var i;
              var extension = Array.isArray(data) ? data : [data];
              var length = extension.length;
              for (i = 0; i < length; i++) {
                  arr[arr.length] = extension[i];
              }
          };
  
  
          /**
           * @param {Array.<VALUE>} arr The array to modify.
           * @param {VALUE} obj The element to remove.
           * @template VALUE
           * @return {boolean} If the element was removed.
           */
          ol.array.remove = function(arr, obj) {
              var i = arr.indexOf(obj);
              var found = i > -1;
              if (found) {
                  arr.splice(i, 1);
              }
              return found;
          };
  
  
          /**
           * @param {Array.<VALUE>} arr The array to search in.
           * @param {function(VALUE, number, ?) : boolean} func The function to compare.
           * @template VALUE
           * @return {VALUE} The element found.
           */
          ol.array.find = function(arr, func) {
              var length = arr.length >>> 0;
              var value;
  
              for (var i = 0; i < length; i++) {
                  value = arr[i];
                  if (func(value, i, arr)) {
                      return value;
                  }
              }
              return null;
          };
  
  
          /**
           * @param {Array|Uint8ClampedArray} arr1 The first array to compare.
           * @param {Array|Uint8ClampedArray} arr2 The second array to compare.
           * @return {boolean} Whether the two arrays are equal.
           */
          ol.array.equals = function(arr1, arr2) {
              var len1 = arr1.length;
              if (len1 !== arr2.length) {
                  return false;
              }
              for (var i = 0; i < len1; i++) {
                  if (arr1[i] !== arr2[i]) {
                      return false;
                  }
              }
              return true;
          };
  
  
          /**
           * @param {Array.<*>} arr The array to sort (modifies original).
           * @param {Function} compareFnc Comparison function.
           */
          ol.array.stableSort = function(arr, compareFnc) {
              var length = arr.length;
              var tmp = Array(arr.length);
              var i;
              for (i = 0; i < length; i++) {
                  tmp[i] = {index: i, value: arr[i]};
              }
              tmp.sort(function(a, b) {
                  return compareFnc(a.value, b.value) || a.index - b.index;
              });
              for (i = 0; i < arr.length; i++) {
                  arr[i] = tmp[i].value;
              }
          };
  
  
          /**
           * @param {Array.<*>} arr The array to search in.
           * @param {Function} func Comparison function.
           * @return {number} Return index.
           */
          ol.array.findIndex = function(arr, func) {
              var index;
              var found = !arr.every(function(el, idx) {
                  index = idx;
                  return !func(el, idx, arr);
              });
              return found ? index : -1;
          };
  
  
          /**
           * @param {Array.<*>} arr The array to test.
           * @param {Function=} opt_func Comparison function.
           * @param {boolean=} opt_strict Strictly sorted (default false).
           * @return {boolean} Return index.
           */
          ol.array.isSorted = function(arr, opt_func, opt_strict) {
              var compare = opt_func || ol.array.numberSafeCompareFunction;
              return arr.every(function(currentVal, index) {
                  if (index === 0) {
                      return true;
                  }
                  var res = compare(arr[index - 1], currentVal);
                  return !(res > 0 || opt_strict && res === 0);
              });
          };
  
  
          ol.ASSUME_TOUCH = false;
          ol.DEFAULT_MAX_ZOOM = 42;
          ol.DEFAULT_MIN_ZOOM = 0;
          ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD = 0.5;
          ol.DEFAULT_TILE_SIZE = 256;
          ol.DEFAULT_WMS_VERSION = '1.3.0';
          ol.ENABLE_CANVAS = true;
          ol.ENABLE_PROJ4JS = true;
          ol.ENABLE_RASTER_REPROJECTION = true;
          ol.ENABLE_WEBGL = true;
          ol.DEBUG_WEBGL = true;
          ol.INITIAL_ATLAS_SIZE = 256;
          ol.MAX_ATLAS_SIZE = -1;
          ol.MOUSEWHEELZOOM_MAXDELTA = 1;
          ol.OVERVIEWMAP_MAX_RATIO = 0.75;
          ol.OVERVIEWMAP_MIN_RATIO = 0.1;
          ol.RASTER_REPROJECTION_MAX_SOURCE_TILES = 100;
          ol.RASTER_REPROJECTION_MAX_SUBDIVISION = 10;
          ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH = 0.25;
          ol.SIMPLIFY_TOLERANCE = 0.5;
          ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024;
          ol.VERSION = '';
  
          ol.inherits = function(childCtor, parentCtor) {
              childCtor.prototype = Object.create(parentCtor.prototype);
              childCtor.prototype.constructor = childCtor;
          };
  
          ol.nullFunction = function() {};
  
          ol.getUid = function(obj) {
              return obj.ol_uid ||
                  (obj.ol_uid = ++ol.uidCounter_);
          };
  
          ol.asserts = {};
          ol.asserts.assert = function(assertion, errorCode) {
          };
  
          ol.has = {};
  
          var ua = typeof navigator !== 'undefined' ?
              navigator.userAgent.toLowerCase() : '';
  
          ol.has.FIREFOX = ua.indexOf('firefox') !== -1;
          ol.has.SAFARI = ua.indexOf('safari') !== -1 && ua.indexOf('chrom') == -1;
          ol.has.WEBKIT = ua.indexOf('webkit') !== -1 && ua.indexOf('edge') == -1;
          ol.has.MAC = ua.indexOf('macintosh') !== -1;
          ol.has.DEVICE_PIXEL_RATIO = 1;
          ol.has.CANVAS_LINE_DASH = true;
  
          ol.structs = {};
  
          /**
           * @enum {string}
           */
          ol.CollectionEventType = {
              ADD: 'add',
              REMOVE: 'remove'
          };
  
          /**
           * @enum {string}
           */
          ol.ObjectEventType = {
              PROPERTYCHANGE: 'propertychange'
          };
  
          ol.events = {};
  
          /**
           * @param {ol.EventsKey} listenerObj Listener object.
           * @return {ol.EventsListenerFunctionType} Bound listener.
           */
          ol.events.bindListener_ = function(listenerObj) {
              var boundListener = function(evt) {
                  var listener = listenerObj.listener;
                  var bindTo = listenerObj.bindTo || listenerObj.target;
                  if (listenerObj.callOnce) {
                      ol.events.unlistenByKey(listenerObj);
                  }
                  return listener.call(bindTo, evt);
              };
              listenerObj.boundListener = boundListener;
              return boundListener;
          };
  
          ol.events.findListener_ = function(listeners, listener, opt_this,
                                             opt_setDeleteIndex) {
              var listenerObj;
              for (var i = 0, ii = listeners.length; i < ii; ++i) {
                  listenerObj = listeners[i];
                  if (listenerObj.listener === listener &&
                      listenerObj.bindTo === opt_this) {
                      if (opt_setDeleteIndex) {
                          listenerObj.deleteIndex = i;
                      }
                      return listenerObj;
                  }
              }
              return undefined;
          };
  
  
          /**
           * @param {ol.EventTargetLike} target Target.
           * @param {string} type Type.
           * @return {Array.<ol.EventsKey>|undefined} Listeners.
           */
          ol.events.getListeners = function(target, type) {
              var listenerMap = target.ol_lm;
              return listenerMap ? listenerMap[type] : undefined;
          };
  
  
          /**
           * Get the lookup of listeners.  If one does not exist on the target, it is
           * created.
           * @param {ol.EventTargetLike} target Target.
           * @return {!Object.<string, Array.<ol.EventsKey>>} Map of
           *     listeners by event type.
           * @private
           */
          ol.events.getListenerMap_ = function(target) {
              var listenerMap = target.ol_lm;
              if (!listenerMap) {
                  listenerMap = target.ol_lm = {};
              }
              return listenerMap;
          };
  
  
          /**
           * Clean up all listener objects of the given type.  All properties on the
           * listener objects will be removed, and if no listeners remain in the listener
           * map, it will be removed from the target.
           * @param {ol.EventTargetLike} target Target.
           * @param {string} type Type.
           * @private
           */
          ol.events.removeListeners_ = function(target, type) {
              var listeners = ol.events.getListeners(target, type);
              if (listeners) {
                  for (var i = 0, ii = listeners.length; i < ii; ++i) {
                      target.removeEventListener(type, listeners[i].boundListener);
                      ol.obj.clear(listeners[i]);
                  }
                  listeners.length = 0;
                  var listenerMap = target.ol_lm;
                  if (listenerMap) {
                      delete listenerMap[type];
                      if (Object.keys(listenerMap).length === 0) {
                          delete target.ol_lm;
                      }
                  }
              }
          };
  
          ol.events.listen = function(target, type, listener, opt_this, opt_once) {
              var listenerMap = ol.events.getListenerMap_(target);
              var listeners = listenerMap[type];
              if (!listeners) {
                  listeners = listenerMap[type] = [];
              }
              var listenerObj = ol.events.findListener_(listeners, listener, opt_this,
                  false);
              if (listenerObj) {
                  if (!opt_once) {
                      // Turn one-off listener into a permanent one.
                      listenerObj.callOnce = false;
                  }
              } else {
                  listenerObj = /** @type {ol.EventsKey} */ ({
                      bindTo: opt_this,
                      callOnce: !!opt_once,
                      listener: listener,
                      target: target,
                      type: type
                  });
                  target.addEventListener(type, ol.events.bindListener_(listenerObj));
                  listeners.push(listenerObj);
              }
  
              return listenerObj;
          };
  
          ol.events.listenOnce = function(target, type, listener, opt_this) {
              return ol.events.listen(target, type, listener, opt_this, true);
          };
  
          ol.events.unlisten = function(target, type, listener, opt_this) {
              var listeners = ol.events.getListeners(target, type);
              if (listeners) {
                  var listenerObj = ol.events.findListener_(listeners, listener, opt_this,
                      true);
                  if (listenerObj) {
                      ol.events.unlistenByKey(listenerObj);
                  }
              }
          };
  
          ol.events.unlistenByKey = function(key) {
              if (key && key.target) {
                  key.target.removeEventListener(key.type, key.boundListener);
                  var listeners = ol.events.getListeners(key.target, key.type);
                  if (listeners) {
                      var i = 'deleteIndex' in key ? key.deleteIndex : listeners.indexOf(key);
                      if (i !== -1) {
                          listeners.splice(i, 1);
                      }
                      if (listeners.length === 0) {
                          ol.events.removeListeners_(key.target, key.type);
                      }
                  }
                  ol.obj.clear(key);
              }
          };
  
          ol.events.unlistenAll = function(target) {
              var listenerMap = ol.events.getListenerMap_(target);
              for (var type in listenerMap) {
                  ol.events.removeListeners_(target, type);
              }
          };
  
          /**
           * Objects that need to clean up after themselves.
           * @constructor
           */
          ol.Disposable = function() {};
  
          ol.Disposable.prototype.disposed_ = false;
  
          ol.Disposable.prototype.dispose = function() {
              if (!this.disposed_) {
                  this.disposed_ = true;
                  this.disposeInternal();
              }
          };
  
          ol.Disposable.prototype.disposeInternal = ol.nullFunction;
  
          ol.events.Event = {};
  
          ol.events.Event = function(type) {
              this.propagationStopped;
              this.type = type;
              this.target = null;
          };
  
          ol.events.Event.prototype.preventDefault =
  
          /**
           * Stop event propagation.
           * @function
           * @override
           * @api
           */
              ol.events.Event.prototype.stopPropagation = function() {
                  this.propagationStopped = true;
              };
  
  
          /**
           * @param {Event|ol.events.Event} evt Event
           */
          ol.events.Event.stopPropagation = function(evt) {
              evt.stopPropagation();
          };
  
  
          /**
           * @param {Event|ol.events.Event} evt Event
           */
          ol.events.Event.preventDefault = function(evt) {
              evt.preventDefault();
          };
  
          ol.events.EventTarget = {};
  
          ol.events.EventTarget = function() {
              ol.Disposable.call(this);
              this.pendingRemovals_ = {};
              this.dispatching_ = {};
              this.listeners_ = {};
  
          };
          ol.inherits(ol.events.EventTarget, ol.Disposable);
  
  
          /**
           * @param {string} type Type.
           * @param {ol.EventsListenerFunctionType} listener Listener.
           */
          ol.events.EventTarget.prototype.addEventListener = function(type, listener) {
              var listeners = this.listeners_[type];
              if (!listeners) {
                  listeners = this.listeners_[type] = [];
              }
              if (listeners.indexOf(listener) === -1) {
                  listeners.push(listener);
              }
          };
  
          ol.events.EventTarget.prototype.dispatchEvent = function(event) {
              var evt = typeof event === 'string' ? new ol.events.Event(event) : event;
              var type = evt.type;
              evt.target = this;
              var listeners = this.listeners_[type];
              var propagate;
              if (listeners) {
                  if (!(type in this.dispatching_)) {
                      this.dispatching_[type] = 0;
                      this.pendingRemovals_[type] = 0;
                  }
                  ++this.dispatching_[type];
                  for (var i = 0, ii = listeners.length; i < ii; ++i) {
                      if (listeners[i].call(this, evt) === false || evt.propagationStopped) {
                          propagate = false;
                          break;
                      }
                  }
                  --this.dispatching_[type];
                  if (this.dispatching_[type] === 0) {
                      var pendingRemovals = this.pendingRemovals_[type];
                      delete this.pendingRemovals_[type];
                      while (pendingRemovals--) {
                          this.removeEventListener(type, ol.nullFunction);
                      }
                      delete this.dispatching_[type];
                  }
                  return propagate;
              }
          };
  
  
          /**
           * @inheritDoc
           */
          ol.events.EventTarget.prototype.disposeInternal = function() {
              ol.events.unlistenAll(this);
          };
  
  
          /**
           * Get the listeners for a specified event type. Listeners are returned in the
           * order that they will be called in.
           *
           * @param {string} type Type.
           * @return {Array.<ol.EventsListenerFunctionType>} Listeners.
           */
          ol.events.EventTarget.prototype.getListeners = function(type) {
              return this.listeners_[type];
          };
  
  
          /**
           * @param {string=} opt_type Type. If not provided,
           *     `true` will be returned if this EventTarget has any listeners.
           * @return {boolean} Has listeners.
           */
          ol.events.EventTarget.prototype.hasListener = function(opt_type) {
              return opt_type ?
                  opt_type in this.listeners_ :
                  Object.keys(this.listeners_).length > 0;
          };
  
  
          /**
           * @param {string} type Type.
           * @param {ol.EventsListenerFunctionType} listener Listener.
           */
          ol.events.EventTarget.prototype.removeEventListener = function(type, listener) {
              var listeners = this.listeners_[type];
              if (listeners) {
                  var index = listeners.indexOf(listener);
                  if (type in this.pendingRemovals_) {
                      // make listener a no-op, and remove later in #dispatchEvent()
                      listeners[index] = ol.nullFunction;
                      ++this.pendingRemovals_[type];
                  } else {
                      listeners.splice(index, 1);
                      if (listeners.length === 0) {
                          delete this.listeners_[type];
                      }
                  }
              }
          };
  
          /**
           * @enum {string}
           * @const
           */
          ol.events.EventType = {
              CHANGE: 'change',
              CLEAR: 'clear',
              CLICK: 'click',
              DBLCLICK: 'dblclick',
              DRAGENTER: 'dragenter',
              DRAGOVER: 'dragover',
              DROP: 'drop',
              ERROR: 'error',
              KEYDOWN: 'keydown',
              KEYPRESS: 'keypress',
              LOAD: 'load',
              MOUSEDOWN: 'mousedown',
              MOUSEMOVE: 'mousemove',
              MOUSEOUT: 'mouseout',
              MOUSEUP: 'mouseup',
              MOUSEWHEEL: 'mousewheel',
              MSPOINTERDOWN: 'MSPointerDown',
              RESIZE: 'resize',
              TOUCHSTART: 'touchstart',
              TOUCHMOVE: 'touchmove',
              TOUCHEND: 'touchend',
              WHEEL: 'wheel'
          };
  
          ol.Observable = function() {
              this.revision_ = 0;
  
          };
          ol.inherits(ol.Observable, ol.events.EventTarget);
  
          ol.Observable.unByKey = function(key) {
              if (Array.isArray(key)) {
                  for (var i = 0, ii = key.length; i < ii; ++i) {
                      ol.events.unlistenByKey(key[i]);
                  }
              } else {
                  ol.events.unlistenByKey(/** @type {ol.EventsKey} */ (key));
              }
          };
  
  
          /**
           * Increases the revision counter and dispatches a 'change' event.
           * @api
           */
          ol.Observable.prototype.changed = function() {
              ++this.revision_;
              //this.dispatchEvent(ol.events.EventType.CHANGE);
          };
  
  
          /**
           * Dispatches an event and calls all listeners listening for events
           * of this type. The event parameter can either be a string or an
           * Object with a `type` property.
           *
           * @param {{type: string,
       *     target: (EventTarget|ol.events.EventTarget|undefined)}|ol.events.Event|
       *     string} event Event object.
           * @function
           * @api
           */
          ol.Observable.prototype.dispatchEvent;
  
  
          /**
           * Get the version number for this object.  Each time the object is modified,
           * its version number will be incremented.
           * @return {number} Revision.
           * @api
           */
          ol.Observable.prototype.getRevision = function() {
              return this.revision_;
          };
  
  
          /**
           * Listen for a certain type of event.
           * @param {string|Array.<string>} type The event type or array of event types.
           * @param {function(?): ?} listener The listener function.
           * @param {Object=} opt_this The object to use as `this` in `listener`.
           * @return {ol.EventsKey|Array.<ol.EventsKey>} Unique key for the listener. If
           *     called with an array of event types as the first argument, the return
           *     will be an array of keys.
           * @api
           */
          ol.Observable.prototype.on = function(type, listener, opt_this) {
              if (Array.isArray(type)) {
                  var len = type.length;
                  var keys = new Array(len);
                  for (var i = 0; i < len; ++i) {
                      keys[i] = ol.events.listen(this, type[i], listener, opt_this);
                  }
                  return keys;
              } else {
                  return ol.events.listen(
                      this, /** @type {string} */ (type), listener, opt_this);
              }
          };
  
  
          /**
           * Listen once for a certain type of event.
           * @param {string|Array.<string>} type The event type or array of event types.
           * @param {function(?): ?} listener The listener function.
           * @param {Object=} opt_this The object to use as `this` in `listener`.
           * @return {ol.EventsKey|Array.<ol.EventsKey>} Unique key for the listener. If
           *     called with an array of event types as the first argument, the return
           *     will be an array of keys.
           * @api
           */
          ol.Observable.prototype.once = function(type, listener, opt_this) {
              if (Array.isArray(type)) {
                  var len = type.length;
                  var keys = new Array(len);
                  for (var i = 0; i < len; ++i) {
                      keys[i] = ol.events.listenOnce(this, type[i], listener, opt_this);
                  }
                  return keys;
              } else {
                  return ol.events.listenOnce(
                      this, /** @type {string} */ (type), listener, opt_this);
              }
          };
  
  
          /**
           * Unlisten for a certain type of event.
           * @param {string|Array.<string>} type The event type or array of event types.
           * @param {function(?): ?} listener The listener function.
           * @param {Object=} opt_this The object which was used as `this` by the
           * `listener`.
           * @api
           */
          ol.Observable.prototype.un = function(type, listener, opt_this) {
              if (Array.isArray(type)) {
                  for (var i = 0, ii = type.length; i < ii; ++i) {
                      ol.events.unlisten(this, type[i], listener, opt_this);
                  }
                  return;
              } else {
                  ol.events.unlisten(this, /** @type {string} */ (type), listener, opt_this);
              }
          };
  
          ol.uidCounter_ = 0;
  
          ol.Object = function(opt_values) {
              ol.Observable.call(this);
  
              // Call ol.getUid to ensure that the order of objects' ids is the same as
              // the order in which they were created.  This also helps to ensure that
              // object properties are always added in the same order, which helps many
              // JavaScript engines generate faster code.
              ol.getUid(this);
  
              /**
               * @private
               * @type {!Object.<string, *>}
               */
              this.values_ = {};
  
              if (opt_values !== undefined) {
                  this.setProperties(opt_values);
              }
          };
          ol.inherits(ol.Object, ol.Observable);
  
  
          /**
           * @private
           * @type {Object.<string, string>}
           */
          ol.Object.changeEventTypeCache_ = {};
  
  
          /**
           * @param {string} key Key name.
           * @return {string} Change name.
           */
          ol.Object.getChangeEventType = function(key) {
              return ol.Object.changeEventTypeCache_.hasOwnProperty(key) ?
                  ol.Object.changeEventTypeCache_[key] :
                  (ol.Object.changeEventTypeCache_[key] = 'change:' + key);
          };
  
  
          /**
           * Gets a value.
           * @param {string} key Key name.
           * @return {*} Value.
           * @api
           */
          ol.Object.prototype.get = function(key) {
              var value;
              if (this.values_.hasOwnProperty(key)) {
                  value = this.values_[key];
              }
              return value;
          };
  
  
          /**
           * Get a list of object property names.
           * @return {Array.<string>} List of property names.
           * @api
           */
          ol.Object.prototype.getKeys = function() {
              return Object.keys(this.values_);
          };
  
  
          /**
           * Get an object of all property names and values.
           * @return {Object.<string, *>} Object.
           * @api
           */
          ol.Object.prototype.getProperties = function() {
              return ol.obj.assign({}, this.values_);
          };
  
  
          /**
           * @param {string} key Key name.
           * @param {*} oldValue Old value.
           */
          ol.Object.prototype.notify = function(key, oldValue) {
              // FIX ME
              return;
          };
  
  
          /**
           * Sets a value.
           * @param {string} key Key name.
           * @param {*} value Value.
           * @param {boolean=} opt_silent Update without triggering an event.
           * @api
           */
          ol.Object.prototype.set = function(key, value, opt_silent) {
              if (opt_silent) {
                  this.values_[key] = value;
              } else {
                  var oldValue = this.values_[key];
                  this.values_[key] = value;
                  if (oldValue !== value) {
                      this.notify(key, oldValue);
                  }
              }
          };
  
  
          /**
           * Sets a collection of key-value pairs.  Note that this changes any existing
           * properties and adds new ones (it does not remove any existing properties).
           * @param {Object.<string, *>} values Values.
           * @param {boolean=} opt_silent Update without triggering an event.
           * @api
           */
          ol.Object.prototype.setProperties = function(values, opt_silent) {
              var key;
              for (key in values) {
                  this.set(key, values[key], opt_silent);
              }
          };
  
  
          /**
           * Unsets a property.
           * @param {string} key Key name.
           * @param {boolean=} opt_silent Unset without triggering an event.
           * @api
           */
          ol.Object.prototype.unset = function(key, opt_silent) {
              if (key in this.values_) {
                  var oldValue = this.values_[key];
                  delete this.values_[key];
                  if (!opt_silent) {
                      this.notify(key, oldValue);
                  }
              }
          };
  
          ol.Object.Event = function(type, key, oldValue) {
              ol.events.Event.call(this, type);
              this.key = key;
              this.oldValue = oldValue;
  
          };
          ol.inherits(ol.Object.Event, ol.events.Event);
  
          ol.functions = {};
          /**
           * Always returns true.
           * @returns {boolean} true.
           */
          ol.functions.TRUE = function() {
              return true;
          };
  
          /**
           * Always returns false.
           * @returns {boolean} false.
           */
          ol.functions.FALSE = function() {
              return false;
          };
  
          ol.math = {};
  
          ol.math.clamp = function(value, min, max) {
              return Math.min(Math.max(value, min), max);
          };
  
          ol.math.cosh = (function() {
              // Wrapped in a iife, to save the overhead of checking for the native
              // implementation on every invocation.
              var cosh;
              if ('cosh' in Math) {
                  // The environment supports the native Math.cosh function, use it…
                  cosh = Math.cosh;
              } else {
                  // … else, use the reference implementation of MDN:
                  cosh = function(x) {
                      var y = Math.exp(x);
                      return (y + 1 / y) / 2;
                  };
              }
              return cosh;
          }());
  
  
          /**
           * @param {number} x X.
           * @return {number} The smallest power of two greater than or equal to x.
           */
          ol.math.roundUpToPowerOfTwo = function(x) {
              ol.asserts.assert(0 < x, 29); // `x` must be greater than `0`
              return Math.pow(2, Math.ceil(Math.log(x) / Math.LN2));
          };
  
          ol.math.squaredSegmentDistance = function(x, y, x1, y1, x2, y2) {
              var dx = x2 - x1;
              var dy = y2 - y1;
              if (dx !== 0 || dy !== 0) {
                  var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy);
                  if (t > 1) {
                      x1 = x2;
                      y1 = y2;
                  } else if (t > 0) {
                      x1 += dx * t;
                      y1 += dy * t;
                  }
              }
              return ol.math.squaredDistance(x, y, x1, y1);
          };
  
          ol.math.squaredDistance = function(x1, y1, x2, y2) {
              var dx = x2 - x1;
              var dy = y2 - y1;
              return dx * dx + dy * dy;
          };
  
          ol.math.solveLinearSystem = function(mat) {
              var n = mat.length;
  
              for (var i = 0; i < n; i++) {
                  // Find max in the i-th column (ignoring i - 1 first rows)
                  var maxRow = i;
                  var maxEl = Math.abs(mat[i][i]);
                  for (var r = i + 1; r < n; r++) {
                      var absValue = Math.abs(mat[r][i]);
                      if (absValue > maxEl) {
                          maxEl = absValue;
                          maxRow = r;
                      }
                  }
  
                  if (maxEl === 0) {
                      return null; // matrix is singular
                  }
  
                  // Swap max row with i-th (current) row
                  var tmp = mat[maxRow];
                  mat[maxRow] = mat[i];
                  mat[i] = tmp;
  
                  // Subtract the i-th row to make all the remaining rows 0 in the i-th column
                  for (var j = i + 1; j < n; j++) {
                      var coef = -mat[j][i] / mat[i][i];
                      for (var k = i; k < n + 1; k++) {
                          if (i == k) {
                              mat[j][k] = 0;
                          } else {
                              mat[j][k] += coef * mat[i][k];
                          }
                      }
                  }
              }
  
              // Solve Ax=b for upper triangular matrix A (mat)
              var x = new Array(n);
              for (var l = n - 1; l >= 0; l--) {
                  x[l] = mat[l][n] / mat[l][l];
                  for (var m = l - 1; m >= 0; m--) {
                      mat[m][n] -= mat[m][l] * x[l];
                  }
              }
              return x;
          };
  
          ol.math.toDegrees = function(angleInRadians) {
              return angleInRadians * 180 / Math.PI;
          };
  
          ol.math.toRadians = function(angleInDegrees) {
              return angleInDegrees * Math.PI / 180;
          };
  
          ol.math.modulo = function(a, b) {
              var r = a % b;
              return r * b < 0 ? r + b : r;
          };
  
          ol.math.lerp = function(a, b, x) {
              return a + x * (b - a);
          };
  
          ol.ImageState = {
              IDLE: 0,
              LOADING: 1,
              LOADED: 2,
              ERROR: 3
          };
  
          ol.color = {};
  
          ol.color.HEX_COLOR_RE_ = /^#(?:[0-9a-f]{3,4}){1,2}$/i;
  
          ol.color.NAMED_COLOR_RE_ = /^([a-z]*)$/i;
  
          ol.color.asArray = function(color) {
              if (Array.isArray(color)) {
                  return color;
              } else {
                  return ol.color.fromString(/** @type {string} */ (color));
              }
          };
  
          ol.color.asString = function(color) {
              if (typeof color === 'string') {
                  return color;
              } else {
                  return ol.color.toString(color);
              }
          };
  
          ol.color.fromNamed = function(color) {
              var el = document.createElement('div');
              el.style.color = color;
              document.body.appendChild(el);
              var rgb = getComputedStyle(el).color;
              document.body.removeChild(el);
              return rgb;
          };
  
          ol.color.fromString = (
              function() {
  
                  // We maintain a small cache of parsed strings.  To provide cheap LRU-like
                  // semantics, whenever the cache grows too large we simply delete an
                  // arbitrary 25% of the entries.
  
                  /**
                   * @const
                   * @type {number}
                   */
                  var MAX_CACHE_SIZE = 1024;
  
                  /**
                   * @type {Object.<string, ol.Color>}
                   */
                  var cache = {};
  
                  /**
                   * @type {number}
                   */
                  var cacheSize = 0;
  
                  return (
                      /**
                       * @param {string} s String.
                       * @return {ol.Color} Color.
                       */
                          function(s) {
                          var color;
                          if (cache.hasOwnProperty(s)) {
                              color = cache[s];
                          } else {
                              if (cacheSize >= MAX_CACHE_SIZE) {
                                  var i = 0;
                                  var key;
                                  for (key in cache) {
                                      if ((i++ & 3) === 0) {
                                          delete cache[key];
                                          --cacheSize;
                                      }
                                  }
                              }
                              color = ol.color.fromStringInternal_(s);
                              cache[s] = color;
                              ++cacheSize;
                          }
                          return color;
                      });
  
              })();
  
          ol.color.fromStringInternal_ = function(s) {
              var r, g, b, a, color, parts;
  
              if (ol.color.NAMED_COLOR_RE_.exec(s)) {
                  s = ol.color.fromNamed(s);
              }
  
              if (ol.color.HEX_COLOR_RE_.exec(s)) { // hex
                  var n = s.length - 1; // number of hex digits
                  var d; // number of digits per channel
                  if (n <= 4) {
                      d = 1;
                  } else {
                      d = 2;
                  }
                  var hasAlpha = n === 4 || n === 8;
                  r = parseInt(s.substr(1 + 0 * d, d), 16);
                  g = parseInt(s.substr(1 + 1 * d, d), 16);
                  b = parseInt(s.substr(1 + 2 * d, d), 16);
                  if (hasAlpha) {
                      a = parseInt(s.substr(1 + 3 * d, d), 16);
                  } else {
                      a = 255;
                  }
                  if (d == 1) {
                      r = (r << 4) + r;
                      g = (g << 4) + g;
                      b = (b << 4) + b;
                      if (hasAlpha) {
                          a = (a << 4) + a;
                      }
                  }
                  color = [r, g, b, a / 255];
              } else if (s.indexOf('rgba(') == 0) { // rgba()
                  parts = s.slice(5, -1).split(',').map(Number);
                  color = ol.color.normalize(parts);
              } else if (s.indexOf('rgb(') == 0) { // rgb()
                  parts = s.slice(4, -1).split(',').map(Number);
                  parts.push(1);
                  color = ol.color.normalize(parts);
              } else {
                  ol.asserts.assert(false, 14); // Invalid color
              }
              return /** @type {ol.Color} */ (color);
          };
  
  
          /**
           * @param {ol.Color} color Color.
           * @param {ol.Color=} opt_color Color.
           * @return {ol.Color} Clamped color.
           */
          ol.color.normalize = function(color, opt_color) {
              var result = opt_color || [];
              result[0] = ol.math.clamp((color[0] + 0.5) | 0, 0, 255);
              result[1] = ol.math.clamp((color[1] + 0.5) | 0, 0, 255);
              result[2] = ol.math.clamp((color[2] + 0.5) | 0, 0, 255);
              result[3] = ol.math.clamp(color[3], 0, 1);
              return result;
          };
  
  
          /**
           * @param {ol.Color} color Color.
           * @return {string} String.
           */
          ol.color.toString = function(color) {
              var r = color[0];
              if (r != (r | 0)) {
                  r = (r + 0.5) | 0;
              }
              var g = color[1];
              if (g != (g | 0)) {
                  g = (g + 0.5) | 0;
              }
              var b = color[2];
              if (b != (b | 0)) {
                  b = (b + 0.5) | 0;
              }
              var a = color[3] === undefined ? 1 : color[3];
              return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
          };
  
          ol.colorlike = {};
  
          ol.colorlike.asColorLike = function(color) {
              if (ol.colorlike.isColorLike(color)) {
                  return /** @type {string|CanvasPattern|CanvasGradient} */ (color);
              } else {
                  return ol.color.asString(/** @type {ol.Color} */ (color));
              }
          };
  
          ol.colorlike.isColorLike = function(color) {
              return (
                  typeof color === 'string' ||
                      color instanceof CanvasPattern ||
                      color instanceof CanvasGradient
                  );
          };
  
          ol.css = {};
          ol.css.CLASS_HIDDEN = 'ol-hidden';
          ol.css.CLASS_SELECTABLE = 'ol-selectable';
          ol.css.CLASS_UNSELECTABLE = 'ol-unselectable';
          ol.css.CLASS_UNSUPPORTED = 'ol-unsupported';
          ol.css.CLASS_CONTROL = 'ol-control';
          ol.css.getFontFamilies = (function() {
              var style;
              var cache = {};
              return function(font) {
                  if (!style) {
                      style = document.createElement('div').style;
                  }
                  if (!(font in cache)) {
                      style.font = font;
                      var family = style.fontFamily;
                      style.font = '';
                      if (!family) {
                          return null;
                      }
                      cache[font] = family.split(/,\s?/);
                  }
                  return cache[font];
              };
          })();
  
          ol.dom = {};
  
          /**
           * Create an html canvas element and returns its 2d context.
           * @param {number=} opt_width Canvas width.
           * @param {number=} opt_height Canvas height.
           * @return {CanvasRenderingContext2D} The context.
           */
          ol.dom.createCanvasContext2D = function(opt_width, opt_height) {
              //var canvas = document.createElement('CANVAS');
              var canvas;
              if (opt_width && opt_height) {
                  canvas = new OffscreenCanvas(opt_width, opt_height);
              }
              else{
                  canvas = new OffscreenCanvas(1, 1);
              }
              return canvas.getContext('2d');
          };
  
  
          /**
           * Get the current computed width for the given element including margin,
           * padding and border.
           * Equivalent to jQuery's `$(el).outerWidth(true)`.
           * @param {!Element} element Element.
           * @return {number} The width.
           */
          ol.dom.outerWidth = function(element) {
              var width = element.offsetWidth;
              var style = getComputedStyle(element);
              width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);
  
              return width;
          };
  
  
          /**
           * Get the current computed height for the given element including margin,
           * padding and border.
           * Equivalent to jQuery's `$(el).outerHeight(true)`.
           * @param {!Element} element Element.
           * @return {number} The height.
           */
          ol.dom.outerHeight = function(element) {
              var height = element.offsetHeight;
              var style = getComputedStyle(element);
              height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);
  
              return height;
          };
  
          /**
           * @param {Node} newNode Node to replace old node
           * @param {Node} oldNode The node to be replaced
           */
          ol.dom.replaceNode = function(newNode, oldNode) {
              var parent = oldNode.parentNode;
              if (parent) {
                  parent.replaceChild(newNode, oldNode);
              }
          };
  
          /**
           * @param {Node} node The node to remove.
           * @returns {Node} The node that was removed or null.
           */
          ol.dom.removeNode = function(node) {
              return node && node.parentNode ? node.parentNode.removeChild(node) : null;
          };
  
          /**
           * @param {Node} node The node to remove the children from.
           */
          ol.dom.removeChildren = function(node) {
              while (node.lastChild) {
                  node.removeChild(node.lastChild);
              }
          };
  
          ol.extent = {};
  
          ol.extent.Corner = {
              BOTTOM_LEFT: 'bottom-left',
              BOTTOM_RIGHT: 'bottom-right',
              TOP_LEFT: 'top-left',
              TOP_RIGHT: 'top-right'
          };
  
          /**
           * Relationship to an extent.
           * @enum {number}
           */
          ol.extent.Relationship = {
              UNKNOWN: 0,
              INTERSECTING: 1,
              ABOVE: 2,
              RIGHT: 4,
              BELOW: 8,
              LEFT: 16
          };
  
          /**
           * Build an extent that includes all given coordinates.
           *
           * @param {Array.<ol.Coordinate>} coordinates Coordinates.
           * @return {ol.Extent} Bounding extent.
           * @api
           */
          ol.extent.boundingExtent = function(coordinates) {
              var extent = ol.extent.createEmpty();
              for (var i = 0, ii = coordinates.length; i < ii; ++i) {
                  ol.extent.extendCoordinate(extent, coordinates[i]);
              }
              return extent;
          };
  
          ol.extent.boundingExtentXYs_ = function(xs, ys, opt_extent) {
              var minX = Math.min.apply(null, xs);
              var minY = Math.min.apply(null, ys);
              var maxX = Math.max.apply(null, xs);
              var maxY = Math.max.apply(null, ys);
              return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent);
          };
  
          ol.extent.buffer = function(extent, value, opt_extent) {
              if (opt_extent) {
                  opt_extent[0] = extent[0] - value;
                  opt_extent[1] = extent[1] - value;
                  opt_extent[2] = extent[2] + value;
                  opt_extent[3] = extent[3] + value;
                  return opt_extent;
              } else {
                  return [
                      extent[0] - value,
                      extent[1] - value,
                      extent[2] + value,
                      extent[3] + value
                  ];
              }
          };
  
          ol.extent.clone = function(extent, opt_extent) {
              if (opt_extent) {
                  opt_extent[0] = extent[0];
                  opt_extent[1] = extent[1];
                  opt_extent[2] = extent[2];
                  opt_extent[3] = extent[3];
                  return opt_extent;
              } else {
                  return extent.slice();
              }
          };
  
          ol.extent.closestSquaredDistanceXY = function(extent, x, y) {
              var dx, dy;
              if (x < extent[0]) {
                  dx = extent[0] - x;
              } else if (extent[2] < x) {
                  dx = x - extent[2];
              } else {
                  dx = 0;
              }
              if (y < extent[1]) {
                  dy = extent[1] - y;
              } else if (extent[3] < y) {
                  dy = y - extent[3];
              } else {
                  dy = 0;
              }
              return dx * dx + dy * dy;
          };
  
  
          /**
           * Check if the passed coordinate is contained or on the edge of the extent.
           *
           * @param {ol.Extent} extent Extent.
           * @param {ol.Coordinate} coordinate Coordinate.
           * @return {boolean} The coordinate is contained in the extent.
           * @api
           */
          ol.extent.containsCoordinate = function(extent, coordinate) {
              return ol.extent.containsXY(extent, coordinate[0], coordinate[1]);
          };
  
  
          /**
           * Check if one extent contains another.
           *
           * An extent is deemed contained if it lies completely within the other extent,
           * including if they share one or more edges.
           *
           * @param {ol.Extent} extent1 Extent 1.
           * @param {ol.Extent} extent2 Extent 2.
           * @return {boolean} The second extent is contained by or on the edge of the
           *     first.
           * @api
           */
          ol.extent.containsExtent = function(extent1, extent2) {
              return extent1[0] <= extent2[0] && extent2[2] <= extent1[2] &&
                  extent1[1] <= extent2[1] && extent2[3] <= extent1[3];
          };
  
  
          /**
           * Check if the passed coordinate is contained or on the edge of the extent.
           *
           * @param {ol.Extent} extent Extent.
           * @param {number} x X coordinate.
           * @param {number} y Y coordinate.
           * @return {boolean} The x, y values are contained in the extent.
           * @api
           */
          ol.extent.containsXY = function(extent, x, y) {
              return extent[0] <= x && x <= extent[2] && extent[1] <= y && y <= extent[3];
          };
  
  
          /**
           * Get the relationship between a coordinate and extent.
           * @param {ol.Extent} extent The extent.
           * @param {ol.Coordinate} coordinate The coordinate.
           * @return {number} The relationship (bitwise compare with
           *     ol.extent.Relationship).
           */
          ol.extent.coordinateRelationship = function(extent, coordinate) {
              var minX = extent[0];
              var minY = extent[1];
              var maxX = extent[2];
              var maxY = extent[3];
              var x = coordinate[0];
              var y = coordinate[1];
              var relationship = ol.extent.Relationship.UNKNOWN;
              if (x < minX) {
                  relationship = relationship | ol.extent.Relationship.LEFT;
              } else if (x > maxX) {
                  relationship = relationship | ol.extent.Relationship.RIGHT;
              }
              if (y < minY) {
                  relationship = relationship | ol.extent.Relationship.BELOW;
              } else if (y > maxY) {
                  relationship = relationship | ol.extent.Relationship.ABOVE;
              }
              if (relationship === ol.extent.Relationship.UNKNOWN) {
                  relationship = ol.extent.Relationship.INTERSECTING;
              }
              return relationship;
          };
  
  
          /**
           * Create an empty extent.
           * @return {ol.Extent} Empty extent.
           * @api
           */
          ol.extent.createEmpty = function() {
              return [Infinity, Infinity, -Infinity, -Infinity];
          };
  
  
          /**
           * Create a new extent or update the provided extent.
           * @param {number} minX Minimum X.
           * @param {number} minY Minimum Y.
           * @param {number} maxX Maximum X.
           * @param {number} maxY Maximum Y.
           * @param {ol.Extent=} opt_extent Destination extent.
           * @return {ol.Extent} Extent.
           */
          ol.extent.createOrUpdate = function(minX, minY, maxX, maxY, opt_extent) {
              if (opt_extent) {
                  opt_extent[0] = minX;
                  opt_extent[1] = minY;
                  opt_extent[2] = maxX;
                  opt_extent[3] = maxY;
                  return opt_extent;
              } else {
                  return [minX, minY, maxX, maxY];
              }
          };
  
  
          /**
           * Create a new empty extent or make the provided one empty.
           * @param {ol.Extent=} opt_extent Extent.
           * @return {ol.Extent} Extent.
           */
          ol.extent.createOrUpdateEmpty = function(opt_extent) {
              return ol.extent.createOrUpdate(
                  Infinity, Infinity, -Infinity, -Infinity, opt_extent);
          };
  
  
          /**
           * @param {ol.Coordinate} coordinate Coordinate.
           * @param {ol.Extent=} opt_extent Extent.
           * @return {ol.Extent} Extent.
           */
          ol.extent.createOrUpdateFromCoordinate = function(coordinate, opt_extent) {
              var x = coordinate[0];
              var y = coordinate[1];
              return ol.extent.createOrUpdate(x, y, x, y, opt_extent);
          };
  
  
          /**
           * @param {Array.<ol.Coordinate>} coordinates Coordinates.
           * @param {ol.Extent=} opt_extent Extent.
           * @return {ol.Extent} Extent.
           */
          ol.extent.createOrUpdateFromCoordinates = function(coordinates, opt_extent) {
              var extent = ol.extent.createOrUpdateEmpty(opt_extent);
              return ol.extent.extendCoordinates(extent, coordinates);
          };
  
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {number} end End.
           * @param {number} stride Stride.
           * @param {ol.Extent=} opt_extent Extent.
           * @return {ol.Extent} Extent.
           */
          ol.extent.createOrUpdateFromFlatCoordinates = function(flatCoordinates, offset, end, stride, opt_extent) {
              var extent = ol.extent.createOrUpdateEmpty(opt_extent);
              return ol.extent.extendFlatCoordinates(
                  extent, flatCoordinates, offset, end, stride);
          };
  
  
          /**
           * @param {Array.<Array.<ol.Coordinate>>} rings Rings.
           * @param {ol.Extent=} opt_extent Extent.
           * @return {ol.Extent} Extent.
           */
          ol.extent.createOrUpdateFromRings = function(rings, opt_extent) {
              var extent = ol.extent.createOrUpdateEmpty(opt_extent);
              return ol.extent.extendRings(extent, rings);
          };
  
  
          /**
           * Determine if two extents are equivalent.
           * @param {ol.Extent} extent1 Extent 1.
           * @param {ol.Extent} extent2 Extent 2.
           * @return {boolean} The two extents are equivalent.
           * @api
           */
          ol.extent.equals = function(extent1, extent2) {
              return extent1[0] == extent2[0] && extent1[2] == extent2[2] &&
                  extent1[1] == extent2[1] && extent1[3] == extent2[3];
          };
  
  
          /**
           * Modify an extent to include another extent.
           * @param {ol.Extent} extent1 The extent to be modified.
           * @param {ol.Extent} extent2 The extent that will be included in the first.
           * @return {ol.Extent} A reference to the first (extended) extent.
           * @api
           */
          ol.extent.extend = function(extent1, extent2) {
              if (extent2[0] < extent1[0]) {
                  extent1[0] = extent2[0];
              }
              if (extent2[2] > extent1[2]) {
                  extent1[2] = extent2[2];
              }
              if (extent2[1] < extent1[1]) {
                  extent1[1] = extent2[1];
              }
              if (extent2[3] > extent1[3]) {
                  extent1[3] = extent2[3];
              }
              return extent1;
          };
  
  
          /**
           * @param {ol.Extent} extent Extent.
           * @param {ol.Coordinate} coordinate Coordinate.
           */
          ol.extent.extendCoordinate = function(extent, coordinate) {
              if (coordinate[0] < extent[0]) {
                  extent[0] = coordinate[0];
              }
              if (coordinate[0] > extent[2]) {
                  extent[2] = coordinate[0];
              }
              if (coordinate[1] < extent[1]) {
                  extent[1] = coordinate[1];
              }
              if (coordinate[1] > extent[3]) {
                  extent[3] = coordinate[1];
              }
          };
  
  
          /**
           * @param {ol.Extent} extent Extent.
           * @param {Array.<ol.Coordinate>} coordinates Coordinates.
           * @return {ol.Extent} Extent.
           */
          ol.extent.extendCoordinates = function(extent, coordinates) {
              var i, ii;
              for (i = 0, ii = coordinates.length; i < ii; ++i) {
                  ol.extent.extendCoordinate(extent, coordinates[i]);
              }
              return extent;
          };
  
  
          /**
           * @param {ol.Extent} extent Extent.
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {number} end End.
           * @param {number} stride Stride.
           * @return {ol.Extent} Extent.
           */
          ol.extent.extendFlatCoordinates = function(extent, flatCoordinates, offset, end, stride) {
              for (; offset < end; offset += stride) {
                  ol.extent.extendXY(
                      extent, flatCoordinates[offset], flatCoordinates[offset + 1]);
              }
              return extent;
          };
  
  
          /**
           * @param {ol.Extent} extent Extent.
           * @param {Array.<Array.<ol.Coordinate>>} rings Rings.
           * @return {ol.Extent} Extent.
           */
          ol.extent.extendRings = function(extent, rings) {
              var i, ii;
              for (i = 0, ii = rings.length; i < ii; ++i) {
                  ol.extent.extendCoordinates(extent, rings[i]);
              }
              return extent;
          };
  
  
          /**
           * @param {ol.Extent} extent Extent.
           * @param {number} x X.
           * @param {number} y Y.
           */
          ol.extent.extendXY = function(extent, x, y) {
              extent[0] = Math.min(extent[0], x);
              extent[1] = Math.min(extent[1], y);
              extent[2] = Math.max(extent[2], x);
              extent[3] = Math.max(extent[3], y);
          };
  
  
          /**
           * This function calls `callback` for each corner of the extent. If the
           * callback returns a truthy value the function returns that value
           * immediately. Otherwise the function returns `false`.
           * @param {ol.Extent} extent Extent.
           * @param {function(this:T, ol.Coordinate): S} callback Callback.
           * @param {T=} opt_this Value to use as `this` when executing `callback`.
           * @return {S|boolean} Value.
           * @template S, T
           */
          ol.extent.forEachCorner = function(extent, callback, opt_this) {
              var val;
              val = callback.call(opt_this, ol.extent.getBottomLeft(extent));
              if (val) {
                  return val;
              }
              val = callback.call(opt_this, ol.extent.getBottomRight(extent));
              if (val) {
                  return val;
              }
              val = callback.call(opt_this, ol.extent.getTopRight(extent));
              if (val) {
                  return val;
              }
              val = callback.call(opt_this, ol.extent.getTopLeft(extent));
              if (val) {
                  return val;
              }
              return false;
          };
  
  
          /**
           * Get the size of an extent.
           * @param {ol.Extent} extent Extent.
           * @return {number} Area.
           * @api
           */
          ol.extent.getArea = function(extent) {
              var area = 0;
              if (!ol.extent.isEmpty(extent)) {
                  area = ol.extent.getWidth(extent) * ol.extent.getHeight(extent);
              }
              return area;
          };
  
  
          /**
           * Get the bottom left coordinate of an extent.
           * @param {ol.Extent} extent Extent.
           * @return {ol.Coordinate} Bottom left coordinate.
           * @api
           */
          ol.extent.getBottomLeft = function(extent) {
              return [extent[0], extent[1]];
          };
  
  
          /**
           * Get the bottom right coordinate of an extent.
           * @param {ol.Extent} extent Extent.
           * @return {ol.Coordinate} Bottom right coordinate.
           * @api
           */
          ol.extent.getBottomRight = function(extent) {
              return [extent[2], extent[1]];
          };
  
  
          /**
           * Get the center coordinate of an extent.
           * @param {ol.Extent} extent Extent.
           * @return {ol.Coordinate} Center.
           * @api
           */
          ol.extent.getCenter = function(extent) {
              return [(extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2];
          };
  
  
          /**
           * Get a corner coordinate of an extent.
           * @param {ol.Extent} extent Extent.
           * @param {ol.extent.Corner} corner Corner.
           * @return {ol.Coordinate} Corner coordinate.
           */
          ol.extent.getCorner = function(extent, corner) {
              var coordinate;
              if (corner === ol.extent.Corner.BOTTOM_LEFT) {
                  coordinate = ol.extent.getBottomLeft(extent);
              } else if (corner === ol.extent.Corner.BOTTOM_RIGHT) {
                  coordinate = ol.extent.getBottomRight(extent);
              } else if (corner === ol.extent.Corner.TOP_LEFT) {
                  coordinate = ol.extent.getTopLeft(extent);
              } else if (corner === ol.extent.Corner.TOP_RIGHT) {
                  coordinate = ol.extent.getTopRight(extent);
              } else {
                  ol.asserts.assert(false, 13); // Invalid corner
              }
              return /** @type {!ol.Coordinate} */ (coordinate);
          };
  
  
          /**
           * @param {ol.Extent} extent1 Extent 1.
           * @param {ol.Extent} extent2 Extent 2.
           * @return {number} Enlarged area.
           */
          ol.extent.getEnlargedArea = function(extent1, extent2) {
              var minX = Math.min(extent1[0], extent2[0]);
              var minY = Math.min(extent1[1], extent2[1]);
              var maxX = Math.max(extent1[2], extent2[2]);
              var maxY = Math.max(extent1[3], extent2[3]);
              return (maxX - minX) * (maxY - minY);
          };
  
  
          /**
           * @param {ol.Coordinate} center Center.
           * @param {number} resolution Resolution.
           * @param {number} rotation Rotation.
           * @param {ol.Size} size Size.
           * @param {ol.Extent=} opt_extent Destination extent.
           * @return {ol.Extent} Extent.
           */
          ol.extent.getForViewAndSize = function(center, resolution, rotation, size, opt_extent) {
              var dx = resolution * size[0] / 2;
              var dy = resolution * size[1] / 2;
              var cosRotation = Math.cos(rotation);
              var sinRotation = Math.sin(rotation);
              var xCos = dx * cosRotation;
              var xSin = dx * sinRotation;
              var yCos = dy * cosRotation;
              var ySin = dy * sinRotation;
              var x = center[0];
              var y = center[1];
              var x0 = x - xCos + ySin;
              var x1 = x - xCos - ySin;
              var x2 = x + xCos - ySin;
              var x3 = x + xCos + ySin;
              var y0 = y - xSin - yCos;
              var y1 = y - xSin + yCos;
              var y2 = y + xSin + yCos;
              var y3 = y + xSin - yCos;
              return ol.extent.createOrUpdate(
                  Math.min(x0, x1, x2, x3), Math.min(y0, y1, y2, y3),
                  Math.max(x0, x1, x2, x3), Math.max(y0, y1, y2, y3),
                  opt_extent);
          };
  
  
          /**
           * Get the height of an extent.
           * @param {ol.Extent} extent Extent.
           * @return {number} Height.
           * @api
           */
          ol.extent.getHeight = function(extent) {
              return extent[3] - extent[1];
          };
  
  
          /**
           * @param {ol.Extent} extent1 Extent 1.
           * @param {ol.Extent} extent2 Extent 2.
           * @return {number} Intersection area.
           */
          ol.extent.getIntersectionArea = function(extent1, extent2) {
              var intersection = ol.extent.getIntersection(extent1, extent2);
              return ol.extent.getArea(intersection);
          };
  
  
          /**
           * Get the intersection of two extents.
           * @param {ol.Extent} extent1 Extent 1.
           * @param {ol.Extent} extent2 Extent 2.
           * @param {ol.Extent=} opt_extent Optional extent to populate with intersection.
           * @return {ol.Extent} Intersecting extent.
           * @api
           */
          ol.extent.getIntersection = function(extent1, extent2, opt_extent) {
              var intersection = opt_extent ? opt_extent : ol.extent.createEmpty();
              if (ol.extent.intersects(extent1, extent2)) {
                  if (extent1[0] > extent2[0]) {
                      intersection[0] = extent1[0];
                  } else {
                      intersection[0] = extent2[0];
                  }
                  if (extent1[1] > extent2[1]) {
                      intersection[1] = extent1[1];
                  } else {
                      intersection[1] = extent2[1];
                  }
                  if (extent1[2] < extent2[2]) {
                      intersection[2] = extent1[2];
                  } else {
                      intersection[2] = extent2[2];
                  }
                  if (extent1[3] < extent2[3]) {
                      intersection[3] = extent1[3];
                  } else {
                      intersection[3] = extent2[3];
                  }
              }
              return intersection;
          };
  
  
          /**
           * @param {ol.Extent} extent Extent.
           * @return {number} Margin.
           */
          ol.extent.getMargin = function(extent) {
              return ol.extent.getWidth(extent) + ol.extent.getHeight(extent);
          };
  
  
          /**
           * Get the size (width, height) of an extent.
           * @param {ol.Extent} extent The extent.
           * @return {ol.Size} The extent size.
           * @api
           */
          ol.extent.getSize = function(extent) {
              return [extent[2] - extent[0], extent[3] - extent[1]];
          };
  
  
          /**
           * Get the top left coordinate of an extent.
           * @param {ol.Extent} extent Extent.
           * @return {ol.Coordinate} Top left coordinate.
           * @api
           */
          ol.extent.getTopLeft = function(extent) {
              return [extent[0], extent[3]];
          };
  
  
          /**
           * Get the top right coordinate of an extent.
           * @param {ol.Extent} extent Extent.
           * @return {ol.Coordinate} Top right coordinate.
           * @api
           */
          ol.extent.getTopRight = function(extent) {
              return [extent[2], extent[3]];
          };
  
  
          /**
           * Get the width of an extent.
           * @param {ol.Extent} extent Extent.
           * @return {number} Width.
           * @api
           */
          ol.extent.getWidth = function(extent) {
              return extent[2] - extent[0];
          };
  
  
          /**
           * Determine if one extent intersects another.
           * @param {ol.Extent} extent1 Extent 1.
           * @param {ol.Extent} extent2 Extent.
           * @return {boolean} The two extents intersect.
           * @api
           */
          ol.extent.intersects = function(extent1, extent2) {
              return extent1[0] <= extent2[2] &&
                  extent1[2] >= extent2[0] &&
                  extent1[1] <= extent2[3] &&
                  extent1[3] >= extent2[1];
          };
  
  
          /**
           * Determine if an extent is empty.
           * @param {ol.Extent} extent Extent.
           * @return {boolean} Is empty.
           * @api
           */
          ol.extent.isEmpty = function(extent) {
              return extent[2] < extent[0] || extent[3] < extent[1];
          };
  
  
          /**
           * @param {ol.Extent} extent Extent.
           * @param {ol.Extent=} opt_extent Extent.
           * @return {ol.Extent} Extent.
           */
          ol.extent.returnOrUpdate = function(extent, opt_extent) {
              if (opt_extent) {
                  opt_extent[0] = extent[0];
                  opt_extent[1] = extent[1];
                  opt_extent[2] = extent[2];
                  opt_extent[3] = extent[3];
                  return opt_extent;
              } else {
                  return extent;
              }
          };
  
  
          /**
           * @param {ol.Extent} extent Extent.
           * @param {number} value Value.
           */
          ol.extent.scaleFromCenter = function(extent, value) {
              var deltaX = ((extent[2] - extent[0]) / 2) * (value - 1);
              var deltaY = ((extent[3] - extent[1]) / 2) * (value - 1);
              extent[0] -= deltaX;
              extent[2] += deltaX;
              extent[1] -= deltaY;
              extent[3] += deltaY;
          };
  
  
          /**
           * Determine if the segment between two coordinates intersects (crosses,
           * touches, or is contained by) the provided extent.
           * @param {ol.Extent} extent The extent.
           * @param {ol.Coordinate} start Segment start coordinate.
           * @param {ol.Coordinate} end Segment end coordinate.
           * @return {boolean} The segment intersects the extent.
           */
          ol.extent.intersectsSegment = function(extent, start, end) {
              var intersects = false;
              var startRel = ol.extent.coordinateRelationship(extent, start);
              var endRel = ol.extent.coordinateRelationship(extent, end);
              if (startRel === ol.extent.Relationship.INTERSECTING ||
                  endRel === ol.extent.Relationship.INTERSECTING) {
                  intersects = true;
              } else {
                  var minX = extent[0];
                  var minY = extent[1];
                  var maxX = extent[2];
                  var maxY = extent[3];
                  var startX = start[0];
                  var startY = start[1];
                  var endX = end[0];
                  var endY = end[1];
                  var slope = (endY - startY) / (endX - startX);
                  var x, y;
                  if (!!(endRel & ol.extent.Relationship.ABOVE) &&
                      !(startRel & ol.extent.Relationship.ABOVE)) {
                      // potentially intersects top
                      x = endX - ((endY - maxY) / slope);
                      intersects = x >= minX && x <= maxX;
                  }
                  if (!intersects && !!(endRel & ol.extent.Relationship.RIGHT) &&
                      !(startRel & ol.extent.Relationship.RIGHT)) {
                      // potentially intersects right
                      y = endY - ((endX - maxX) * slope);
                      intersects = y >= minY && y <= maxY;
                  }
                  if (!intersects && !!(endRel & ol.extent.Relationship.BELOW) &&
                      !(startRel & ol.extent.Relationship.BELOW)) {
                      // potentially intersects bottom
                      x = endX - ((endY - minY) / slope);
                      intersects = x >= minX && x <= maxX;
                  }
                  if (!intersects && !!(endRel & ol.extent.Relationship.LEFT) &&
                      !(startRel & ol.extent.Relationship.LEFT)) {
                      // potentially intersects left
                      y = endY - ((endX - minX) * slope);
                      intersects = y >= minY && y <= maxY;
                  }
  
              }
              return intersects;
          };
  
  
          /**
           * Apply a transform function to the extent.
           * @param {ol.Extent} extent Extent.
           * @param {ol.TransformFunction} transformFn Transform function.  Called with
           * [minX, minY, maxX, maxY] extent coordinates.
           * @param {ol.Extent=} opt_extent Destination extent.
           * @return {ol.Extent} Extent.
           * @api
           */
          ol.extent.applyTransform = function(extent, transformFn, opt_extent) {
              var coordinates = [
                  extent[0], extent[1],
                  extent[0], extent[3],
                  extent[2], extent[1],
                  extent[2], extent[3]
              ];
              transformFn(coordinates, coordinates, 2);
              var xs = [coordinates[0], coordinates[2], coordinates[4], coordinates[6]];
              var ys = [coordinates[1], coordinates[3], coordinates[5], coordinates[7]];
              return ol.extent.boundingExtentXYs_(xs, ys, opt_extent);
          };
  
          ol.obj = {};
  
          /**
           * Polyfill for Object.assign().  Assigns enumerable and own properties from
           * one or more source objects to a target object.
           *
           * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
           * @param {!Object} target The target object.
           * @param {...Object} var_sources The source object(s).
           * @return {!Object} The modified target object.
           */
          ol.obj.assign = (typeof Object.assign === 'function') ? Object.assign : function(target, var_sources) {
              if (target === undefined || target === null) {
                  throw new TypeError('Cannot convert undefined or null to object');
              }
  
              var output = Object(target);
              for (var i = 1, ii = arguments.length; i < ii; ++i) {
                  var source = arguments[i];
                  if (source !== undefined && source !== null) {
                      for (var key in source) {
                          if (source.hasOwnProperty(key)) {
                              output[key] = source[key];
                          }
                      }
                  }
              }
              return output;
          };
  
  
          /**
           * Removes all properties from an object.
           * @param {Object} object The object to clear.
           */
          ol.obj.clear = function(object) {
              for (var property in object) {
                  delete object[property];
              }
          };
  
  
          /**
           * Get an array of property values from an object.
           * @param {Object<K,V>} object The object from which to get the values.
           * @return {!Array<V>} The property values.
           * @template K,V
           */
          ol.obj.getValues = function(object) {
              var values = [];
              for (var property in object) {
                  values.push(object[property]);
              }
              return values;
          };
  
  
          /**
           * Determine if an object has any properties.
           * @param {Object} object The object to check.
           * @return {boolean} The object is empty.
           */
          ol.obj.isEmpty = function(object) {
              var property;
              for (property in object) {
                  return false;
              }
              return !property;
          };
  
          ol.transform = {};
  
          /**
           * @private
           * @type {ol.Transform}
           */
          ol.transform.tmp_ = new Array(6);
  
  
          /**
           * Create an identity transform.
           * @return {!ol.Transform} Identity transform.
           */
          ol.transform.create = function() {
              return [1, 0, 0, 1, 0, 0];
          };
  
  
          /**
           * Resets the given transform to an identity transform.
           * @param {!ol.Transform} transform Transform.
           * @return {!ol.Transform} Transform.
           */
          ol.transform.reset = function(transform) {
              return ol.transform.set(transform, 1, 0, 0, 1, 0, 0);
          };
  
  
          /**
           * Multiply the underlying matrices of two transforms and return the result in
           * the first transform.
           * @param {!ol.Transform} transform1 Transform parameters of matrix 1.
           * @param {!ol.Transform} transform2 Transform parameters of matrix 2.
           * @return {!ol.Transform} transform1 multiplied with transform2.
           */
          ol.transform.multiply = function(transform1, transform2) {
              var a1 = transform1[0];
              var b1 = transform1[1];
              var c1 = transform1[2];
              var d1 = transform1[3];
              var e1 = transform1[4];
              var f1 = transform1[5];
              var a2 = transform2[0];
              var b2 = transform2[1];
              var c2 = transform2[2];
              var d2 = transform2[3];
              var e2 = transform2[4];
              var f2 = transform2[5];
  
              transform1[0] = a1 * a2 + c1 * b2;
              transform1[1] = b1 * a2 + d1 * b2;
              transform1[2] = a1 * c2 + c1 * d2;
              transform1[3] = b1 * c2 + d1 * d2;
              transform1[4] = a1 * e2 + c1 * f2 + e1;
              transform1[5] = b1 * e2 + d1 * f2 + f1;
  
              return transform1;
          };
  
          /**
           * Set the transform components a-f on a given transform.
           * @param {!ol.Transform} transform Transform.
           * @param {number} a The a component of the transform.
           * @param {number} b The b component of the transform.
           * @param {number} c The c component of the transform.
           * @param {number} d The d component of the transform.
           * @param {number} e The e component of the transform.
           * @param {number} f The f component of the transform.
           * @return {!ol.Transform} Matrix with transform applied.
           */
          ol.transform.set = function(transform, a, b, c, d, e, f) {
              transform[0] = a;
              transform[1] = b;
              transform[2] = c;
              transform[3] = d;
              transform[4] = e;
              transform[5] = f;
              return transform;
          };
  
  
          /**
           * Set transform on one matrix from another matrix.
           * @param {!ol.Transform} transform1 Matrix to set transform to.
           * @param {!ol.Transform} transform2 Matrix to set transform from.
           * @return {!ol.Transform} transform1 with transform from transform2 applied.
           */
          ol.transform.setFromArray = function(transform1, transform2) {
              transform1[0] = transform2[0];
              transform1[1] = transform2[1];
              transform1[2] = transform2[2];
              transform1[3] = transform2[3];
              transform1[4] = transform2[4];
              transform1[5] = transform2[5];
              return transform1;
          };
  
  
          /**
           * Transforms the given coordinate with the given transform returning the
           * resulting, transformed coordinate. The coordinate will be modified in-place.
           *
           * @param {ol.Transform} transform The transformation.
           * @param {ol.Coordinate|ol.Pixel} coordinate The coordinate to transform.
           * @return {ol.Coordinate|ol.Pixel} return coordinate so that operations can be
           *     chained together.
           */
          ol.transform.apply = function(transform, coordinate) {
              var x = coordinate[0], y = coordinate[1];
              coordinate[0] = transform[0] * x + transform[2] * y + transform[4];
              coordinate[1] = transform[1] * x + transform[3] * y + transform[5];
              return coordinate;
          };
  
  
          /**
           * Applies rotation to the given transform.
           * @param {!ol.Transform} transform Transform.
           * @param {number} angle Angle in radians.
           * @return {!ol.Transform} The rotated transform.
           */
          ol.transform.rotate = function(transform, angle) {
              var cos = Math.cos(angle);
              var sin = Math.sin(angle);
              return ol.transform.multiply(transform,
                  ol.transform.set(ol.transform.tmp_, cos, sin, -sin, cos, 0, 0));
          };
  
  
          /**
           * Applies scale to a given transform.
           * @param {!ol.Transform} transform Transform.
           * @param {number} x Scale factor x.
           * @param {number} y Scale factor y.
           * @return {!ol.Transform} The scaled transform.
           */
          ol.transform.scale = function(transform, x, y) {
              return ol.transform.multiply(transform,
                  ol.transform.set(ol.transform.tmp_, x, 0, 0, y, 0, 0));
          };
  
  
          /**
           * Applies translation to the given transform.
           * @param {!ol.Transform} transform Transform.
           * @param {number} dx Translation x.
           * @param {number} dy Translation y.
           * @return {!ol.Transform} The translated transform.
           */
          ol.transform.translate = function(transform, dx, dy) {
              return ol.transform.multiply(transform,
                  ol.transform.set(ol.transform.tmp_, 1, 0, 0, 1, dx, dy));
          };
  
  
          /**
           * Creates a composite transform given an initial translation, scale, rotation, and
           * final translation (in that order only, not commutative).
           * @param {!ol.Transform} transform The transform (will be modified in place).
           * @param {number} dx1 Initial translation x.
           * @param {number} dy1 Initial translation y.
           * @param {number} sx Scale factor x.
           * @param {number} sy Scale factor y.
           * @param {number} angle Rotation (in counter-clockwise radians).
           * @param {number} dx2 Final translation x.
           * @param {number} dy2 Final translation y.
           * @return {!ol.Transform} The composite transform.
           */
          ol.transform.compose = function(transform, dx1, dy1, sx, sy, angle, dx2, dy2) {
              var sin = Math.sin(angle);
              var cos = Math.cos(angle);
              transform[0] = sx * cos;
              transform[1] = sy * sin;
              transform[2] = -sx * sin;
              transform[3] = sy * cos;
              transform[4] = dx2 * sx * cos - dy2 * sx * sin + dx1;
              transform[5] = dx2 * sy * sin + dy2 * sy * cos + dy1;
              return transform;
          };
  
  
          /**
           * Invert the given transform.
           * @param {!ol.Transform} transform Transform.
           * @return {!ol.Transform} Inverse of the transform.
           */
          ol.transform.invert = function(transform) {
              var det = ol.transform.determinant(transform);
              //ol.asserts.assert(det !== 0, 32); // Transformation matrix cannot be inverted
  
              var a = transform[0];
              var b = transform[1];
              var c = transform[2];
              var d = transform[3];
              var e = transform[4];
              var f = transform[5];
  
              transform[0] = d / det;
              transform[1] = -b / det;
              transform[2] = -c / det;
              transform[3] = a / det;
              transform[4] = (c * f - d * e) / det;
              transform[5] = -(a * f - b * e) / det;
  
              return transform;
          };
  
  
          /**
           * Returns the determinant of the given matrix.
           * @param {!ol.Transform} mat Matrix.
           * @return {number} Determinant.
           */
          ol.transform.determinant = function(mat) {
              return mat[0] * mat[3] - mat[1] * mat[2];
          };
  
          ol.geom = {};
          ol.geom.flat = {};
          ol.geom.flat.center = {};
          ol.geom.flat.reverse = {};
          ol.geom.flat.orient = {};
  
  
          ol.geom.flat.transform = {};
          ol.geom.flat.transform.transform2D = function(flatCoordinates, offset, end, stride, transform, opt_dest) {
              var dest = opt_dest ? opt_dest : [];
              var i = 0;
              var j;
              for (j = offset; j < end; j += stride) {
                  var x = flatCoordinates[j];
                  var y = flatCoordinates[j + 1];
                  dest[i++] = transform[0] * x + transform[2] * y + transform[4];
                  dest[i++] = transform[1] * x + transform[3] * y + transform[5];
              }
              if (opt_dest && dest.length != i) {
                  dest.length = i;
              }
              return dest;
          };
  
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {number} end End.
           * @param {number} stride Stride.
           * @param {number} angle Angle.
           * @param {Array.<number>} anchor Rotation anchor point.
           * @param {Array.<number>=} opt_dest Destination.
           * @return {Array.<number>} Transformed coordinates.
           */
          ol.geom.flat.transform.rotate = function(flatCoordinates, offset, end, stride, angle, anchor, opt_dest) {
              var dest = opt_dest ? opt_dest : [];
              var cos = Math.cos(angle);
              var sin = Math.sin(angle);
              var anchorX = anchor[0];
              var anchorY = anchor[1];
              var i = 0;
              for (var j = offset; j < end; j += stride) {
                  var deltaX = flatCoordinates[j] - anchorX;
                  var deltaY = flatCoordinates[j + 1] - anchorY;
                  dest[i++] = anchorX + deltaX * cos - deltaY * sin;
                  dest[i++] = anchorY + deltaX * sin + deltaY * cos;
                  for (var k = j + 2; k < j + stride; ++k) {
                      dest[i++] = flatCoordinates[k];
                  }
              }
              if (opt_dest && dest.length != i) {
                  dest.length = i;
              }
              return dest;
          };
  
  
          /**
           * Scale the coordinates.
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {number} end End.
           * @param {number} stride Stride.
           * @param {number} sx Scale factor in the x-direction.
           * @param {number} sy Scale factor in the y-direction.
           * @param {Array.<number>} anchor Scale anchor point.
           * @param {Array.<number>=} opt_dest Destination.
           * @return {Array.<number>} Transformed coordinates.
           */
          ol.geom.flat.transform.scale = function(flatCoordinates, offset, end, stride, sx, sy, anchor, opt_dest) {
              var dest = opt_dest ? opt_dest : [];
              var anchorX = anchor[0];
              var anchorY = anchor[1];
              var i = 0;
              for (var j = offset; j < end; j += stride) {
                  var deltaX = flatCoordinates[j] - anchorX;
                  var deltaY = flatCoordinates[j + 1] - anchorY;
                  dest[i++] = anchorX + sx * deltaX;
                  dest[i++] = anchorY + sy * deltaY;
                  for (var k = j + 2; k < j + stride; ++k) {
                      dest[i++] = flatCoordinates[k];
                  }
              }
              if (opt_dest && dest.length != i) {
                  dest.length = i;
              }
              return dest;
          };
  
          /**
           * The coordinate layout for geometries, indicating whether a 3rd or 4th z ('Z')
           * or measure ('M') coordinate is available. Supported values are `'XY'`,
           * `'XYZ'`, `'XYM'`, `'XYZM'`.
           * @enum {string}
           */
          ol.geom.GeometryLayout = {
              XY: 'XY',
              XYZ: 'XYZ',
              XYM: 'XYM',
              XYZM: 'XYZM'
          };
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {number} end End.
           * @param {number} stride Stride.
           */
          ol.geom.flat.reverse.coordinates = function(flatCoordinates, offset, end, stride) {
              while (offset < end - stride) {
                  var i;
                  for (i = 0; i < stride; ++i) {
                      var tmp = flatCoordinates[offset + i];
                      flatCoordinates[offset + i] = flatCoordinates[end - stride + i];
                      flatCoordinates[end - stride + i] = tmp;
                  }
                  offset += stride;
                  end -= stride;
              }
          };
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {number} end End.
           * @param {number} stride Stride.
           * @return {boolean} Is clockwise.
           */
          ol.geom.flat.orient.linearRingIsClockwise = function(flatCoordinates, offset, end, stride) {
              // http://tinyurl.com/clockwise-method
              // https://github.com/OSGeo/gdal/blob/trunk/gdal/ogr/ogrlinearring.cpp
              var edge = 0;
              var x1 = flatCoordinates[end - stride];
              var y1 = flatCoordinates[end - stride + 1];
              for (; offset < end; offset += stride) {
                  var x2 = flatCoordinates[offset];
                  var y2 = flatCoordinates[offset + 1];
                  edge += (x2 - x1) * (y2 + y1);
                  x1 = x2;
                  y1 = y2;
              }
              return edge > 0;
          };
  
  
          /**
           * Determines if linear rings are oriented.  By default, left-hand orientation
           * is tested (first ring must be clockwise, remaining rings counter-clockwise).
           * To test for right-hand orientation, use the `opt_right` argument.
           *
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {Array.<number>} ends Array of end indexes.
           * @param {number} stride Stride.
           * @param {boolean=} opt_right Test for right-hand orientation
           *     (counter-clockwise exterior ring and clockwise interior rings).
           * @return {boolean} Rings are correctly oriented.
           */
          ol.geom.flat.orient.linearRingsAreOriented = function(flatCoordinates, offset, ends, stride, opt_right) {
              var right = opt_right !== undefined ? opt_right : false;
              var i, ii;
              for (i = 0, ii = ends.length; i < ii; ++i) {
                  var end = ends[i];
                  var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(
                      flatCoordinates, offset, end, stride);
                  if (i === 0) {
                      if ((right && isClockwise) || (!right && !isClockwise)) {
                          return false;
                      }
                  } else {
                      if ((right && !isClockwise) || (!right && isClockwise)) {
                          return false;
                      }
                  }
                  offset = end;
              }
              return true;
          };
  
  
          /**
           * Determines if linear rings are oriented.  By default, left-hand orientation
           * is tested (first ring must be clockwise, remaining rings counter-clockwise).
           * To test for right-hand orientation, use the `opt_right` argument.
           *
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {Array.<Array.<number>>} endss Array of array of end indexes.
           * @param {number} stride Stride.
           * @param {boolean=} opt_right Test for right-hand orientation
           *     (counter-clockwise exterior ring and clockwise interior rings).
           * @return {boolean} Rings are correctly oriented.
           */
          ol.geom.flat.orient.linearRingssAreOriented = function(flatCoordinates, offset, endss, stride, opt_right) {
              var i, ii;
              for (i = 0, ii = endss.length; i < ii; ++i) {
                  if (!ol.geom.flat.orient.linearRingsAreOriented(
                      flatCoordinates, offset, endss[i], stride, opt_right)) {
                      return false;
                  }
              }
              return true;
          };
  
  
          /**
           * Orient coordinates in a flat array of linear rings.  By default, rings
           * are oriented following the left-hand rule (clockwise for exterior and
           * counter-clockwise for interior rings).  To orient according to the
           * right-hand rule, use the `opt_right` argument.
           *
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {Array.<number>} ends Ends.
           * @param {number} stride Stride.
           * @param {boolean=} opt_right Follow the right-hand rule for orientation.
           * @return {number} End.
           */
          ol.geom.flat.orient.orientLinearRings = function(flatCoordinates, offset, ends, stride, opt_right) {
              var right = opt_right !== undefined ? opt_right : false;
              var i, ii;
              for (i = 0, ii = ends.length; i < ii; ++i) {
                  var end = ends[i];
                  var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(
                      flatCoordinates, offset, end, stride);
                  var reverse = i === 0 ?
                      (right && isClockwise) || (!right && !isClockwise) :
                      (right && !isClockwise) || (!right && isClockwise);
                  if (reverse) {
                      ol.geom.flat.reverse.coordinates(flatCoordinates, offset, end, stride);
                  }
                  offset = end;
              }
              return offset;
          };
  
  
          /**
           * Orient coordinates in a flat array of linear rings.  By default, rings
           * are oriented following the left-hand rule (clockwise for exterior and
           * counter-clockwise for interior rings).  To orient according to the
           * right-hand rule, use the `opt_right` argument.
           *
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {Array.<Array.<number>>} endss Array of array of end indexes.
           * @param {number} stride Stride.
           * @param {boolean=} opt_right Follow the right-hand rule for orientation.
           * @return {number} End.
           */
          ol.geom.flat.orient.orientLinearRingss = function(flatCoordinates, offset, endss, stride, opt_right) {
              var i, ii;
              for (i = 0, ii = endss.length; i < ii; ++i) {
                  offset = ol.geom.flat.orient.orientLinearRings(
                      flatCoordinates, offset, endss[i], stride, opt_right);
              }
              return offset;
          };
  
          ol.geom.flat.simplify = {};
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {number} end End.
           * @param {number} stride Stride.
           * @param {number} squaredTolerance Squared tolerance.
           * @param {boolean} highQuality Highest quality.
           * @param {Array.<number>=} opt_simplifiedFlatCoordinates Simplified flat
           *     coordinates.
           * @return {Array.<number>} Simplified line string.
           */
          ol.geom.flat.simplify.lineString = function(flatCoordinates, offset, end,
                                                      stride, squaredTolerance, highQuality, opt_simplifiedFlatCoordinates) {
              var simplifiedFlatCoordinates = opt_simplifiedFlatCoordinates !== undefined ?
                  opt_simplifiedFlatCoordinates : [];
              if (!highQuality) {
                  end = ol.geom.flat.simplify.radialDistance(flatCoordinates, offset, end,
                      stride, squaredTolerance,
                      simplifiedFlatCoordinates, 0);
                  flatCoordinates = simplifiedFlatCoordinates;
                  offset = 0;
                  stride = 2;
              }
              simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker(
                  flatCoordinates, offset, end, stride, squaredTolerance,
                  simplifiedFlatCoordinates, 0);
              return simplifiedFlatCoordinates;
          };
  
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {number} end End.
           * @param {number} stride Stride.
           * @param {number} squaredTolerance Squared tolerance.
           * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
           *     coordinates.
           * @param {number} simplifiedOffset Simplified offset.
           * @return {number} Simplified offset.
           */
          ol.geom.flat.simplify.douglasPeucker = function(flatCoordinates, offset, end,
                                                          stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) {
              var n = (end - offset) / stride;
              if (n < 3) {
                  for (; offset < end; offset += stride) {
                      simplifiedFlatCoordinates[simplifiedOffset++] =
                          flatCoordinates[offset];
                      simplifiedFlatCoordinates[simplifiedOffset++] =
                          flatCoordinates[offset + 1];
                  }
                  return simplifiedOffset;
              }
              /** @type {Array.<number>} */
              var markers = new Array(n);
              markers[0] = 1;
              markers[n - 1] = 1;
              /** @type {Array.<number>} */
              var stack = [offset, end - stride];
              var index = 0;
              var i;
              while (stack.length > 0) {
                  var last = stack.pop();
                  var first = stack.pop();
                  var maxSquaredDistance = 0;
                  var x1 = flatCoordinates[first];
                  var y1 = flatCoordinates[first + 1];
                  var x2 = flatCoordinates[last];
                  var y2 = flatCoordinates[last + 1];
                  for (i = first + stride; i < last; i += stride) {
                      var x = flatCoordinates[i];
                      var y = flatCoordinates[i + 1];
                      var squaredDistance = ol.math.squaredSegmentDistance(
                          x, y, x1, y1, x2, y2);
                      if (squaredDistance > maxSquaredDistance) {
                          index = i;
                          maxSquaredDistance = squaredDistance;
                      }
                  }
                  if (maxSquaredDistance > squaredTolerance) {
                      markers[(index - offset) / stride] = 1;
                      if (first + stride < index) {
                          stack.push(first, index);
                      }
                      if (index + stride < last) {
                          stack.push(index, last);
                      }
                  }
              }
              for (i = 0; i < n; ++i) {
                  if (markers[i]) {
                      simplifiedFlatCoordinates[simplifiedOffset++] =
                          flatCoordinates[offset + i * stride];
                      simplifiedFlatCoordinates[simplifiedOffset++] =
                          flatCoordinates[offset + i * stride + 1];
                  }
              }
              return simplifiedOffset;
          };
  
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {Array.<number>} ends Ends.
           * @param {number} stride Stride.
           * @param {number} squaredTolerance Squared tolerance.
           * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
           *     coordinates.
           * @param {number} simplifiedOffset Simplified offset.
           * @param {Array.<number>} simplifiedEnds Simplified ends.
           * @return {number} Simplified offset.
           */
          ol.geom.flat.simplify.douglasPeuckers = function(flatCoordinates, offset,
                                                           ends, stride, squaredTolerance, simplifiedFlatCoordinates,
                                                           simplifiedOffset, simplifiedEnds) {
              var i, ii;
              for (i = 0, ii = ends.length; i < ii; ++i) {
                  var end = ends[i];
                  simplifiedOffset = ol.geom.flat.simplify.douglasPeucker(
                      flatCoordinates, offset, end, stride, squaredTolerance,
                      simplifiedFlatCoordinates, simplifiedOffset);
                  simplifiedEnds.push(simplifiedOffset);
                  offset = end;
              }
              return simplifiedOffset;
          };
  
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {Array.<Array.<number>>} endss Endss.
           * @param {number} stride Stride.
           * @param {number} squaredTolerance Squared tolerance.
           * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
           *     coordinates.
           * @param {number} simplifiedOffset Simplified offset.
           * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss.
           * @return {number} Simplified offset.
           */
          ol.geom.flat.simplify.douglasPeuckerss = function(
              flatCoordinates, offset, endss, stride, squaredTolerance,
              simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) {
              var i, ii;
              for (i = 0, ii = endss.length; i < ii; ++i) {
                  var ends = endss[i];
                  var simplifiedEnds = [];
                  simplifiedOffset = ol.geom.flat.simplify.douglasPeuckers(
                      flatCoordinates, offset, ends, stride, squaredTolerance,
                      simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds);
                  simplifiedEndss.push(simplifiedEnds);
                  offset = ends[ends.length - 1];
              }
              return simplifiedOffset;
          };
  
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {number} end End.
           * @param {number} stride Stride.
           * @param {number} squaredTolerance Squared tolerance.
           * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
           *     coordinates.
           * @param {number} simplifiedOffset Simplified offset.
           * @return {number} Simplified offset.
           */
          ol.geom.flat.simplify.radialDistance = function(flatCoordinates, offset, end,
                                                          stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) {
              if (end <= offset + stride) {
                  // zero or one point, no simplification possible, so copy and return
                  for (; offset < end; offset += stride) {
                      simplifiedFlatCoordinates[simplifiedOffset++] = flatCoordinates[offset];
                      simplifiedFlatCoordinates[simplifiedOffset++] =
                          flatCoordinates[offset + 1];
                  }
                  return simplifiedOffset;
              }
              var x1 = flatCoordinates[offset];
              var y1 = flatCoordinates[offset + 1];
              // copy first point
              simplifiedFlatCoordinates[simplifiedOffset++] = x1;
              simplifiedFlatCoordinates[simplifiedOffset++] = y1;
              var x2 = x1;
              var y2 = y1;
              for (offset += stride; offset < end; offset += stride) {
                  x2 = flatCoordinates[offset];
                  y2 = flatCoordinates[offset + 1];
                  if (ol.math.squaredDistance(x1, y1, x2, y2) > squaredTolerance) {
                      // copy point at offset
                      simplifiedFlatCoordinates[simplifiedOffset++] = x2;
                      simplifiedFlatCoordinates[simplifiedOffset++] = y2;
                      x1 = x2;
                      y1 = y2;
                  }
              }
              if (x2 != x1 || y2 != y1) {
                  // copy last point
                  simplifiedFlatCoordinates[simplifiedOffset++] = x2;
                  simplifiedFlatCoordinates[simplifiedOffset++] = y2;
              }
              return simplifiedOffset;
          };
  
  
          /**
           * @param {number} value Value.
           * @param {number} tolerance Tolerance.
           * @return {number} Rounded value.
           */
          ol.geom.flat.simplify.snap = function(value, tolerance) {
              return tolerance * Math.round(value / tolerance);
          };
  
  
          /**
           * Simplifies a line string using an algorithm designed by Tim Schaub.
           * Coordinates are snapped to the nearest value in a virtual grid and
           * consecutive duplicate coordinates are discarded.  This effectively preserves
           * topology as the simplification of any subsection of a line string is
           * independent of the rest of the line string.  This means that, for examples,
           * the common edge between two polygons will be simplified to the same line
           * string independently in both polygons.  This implementation uses a single
           * pass over the coordinates and eliminates intermediate collinear points.
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {number} end End.
           * @param {number} stride Stride.
           * @param {number} tolerance Tolerance.
           * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
           *     coordinates.
           * @param {number} simplifiedOffset Simplified offset.
           * @return {number} Simplified offset.
           */
          ol.geom.flat.simplify.quantize = function(flatCoordinates, offset, end, stride,
                                                    tolerance, simplifiedFlatCoordinates, simplifiedOffset) {
              // do nothing if the line is empty
              if (offset == end) {
                  return simplifiedOffset;
              }
              // snap the first coordinate (P1)
              var x1 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance);
              var y1 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance);
              offset += stride;
              // add the first coordinate to the output
              simplifiedFlatCoordinates[simplifiedOffset++] = x1;
              simplifiedFlatCoordinates[simplifiedOffset++] = y1;
              // find the next coordinate that does not snap to the same value as the first
              // coordinate (P2)
              var x2, y2;
              do {
                  x2 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance);
                  y2 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance);
                  offset += stride;
                  if (offset == end) {
                      // all coordinates snap to the same value, the line collapses to a point
                      // push the last snapped value anyway to ensure that the output contains
                      // at least two points
                      // FIXME should we really return at least two points anyway?
                      simplifiedFlatCoordinates[simplifiedOffset++] = x2;
                      simplifiedFlatCoordinates[simplifiedOffset++] = y2;
                      return simplifiedOffset;
                  }
              } while (x2 == x1 && y2 == y1);
              while (offset < end) {
                  var x3, y3;
                  // snap the next coordinate (P3)
                  x3 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance);
                  y3 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance);
                  offset += stride;
                  // skip P3 if it is equal to P2
                  if (x3 == x2 && y3 == y2) {
                      continue;
                  }
                  // calculate the delta between P1 and P2
                  var dx1 = x2 - x1;
                  var dy1 = y2 - y1;
                  // calculate the delta between P3 and P1
                  var dx2 = x3 - x1;
                  var dy2 = y3 - y1;
                  // if P1, P2, and P3 are colinear and P3 is further from P1 than P2 is from
                  // P1 in the same direction then P2 is on the straight line between P1 and
                  // P3
                  if ((dx1 * dy2 == dy1 * dx2) &&
                      ((dx1 < 0 && dx2 < dx1) || dx1 == dx2 || (dx1 > 0 && dx2 > dx1)) &&
                      ((dy1 < 0 && dy2 < dy1) || dy1 == dy2 || (dy1 > 0 && dy2 > dy1))) {
                      // discard P2 and set P2 = P3
                      x2 = x3;
                      y2 = y3;
                      continue;
                  }
                  // either P1, P2, and P3 are not colinear, or they are colinear but P3 is
                  // between P3 and P1 or on the opposite half of the line to P2.  add P2,
                  // and continue with P1 = P2 and P2 = P3
                  simplifiedFlatCoordinates[simplifiedOffset++] = x2;
                  simplifiedFlatCoordinates[simplifiedOffset++] = y2;
                  x1 = x2;
                  y1 = y2;
                  x2 = x3;
                  y2 = y3;
              }
              // add the last point (P2)
              simplifiedFlatCoordinates[simplifiedOffset++] = x2;
              simplifiedFlatCoordinates[simplifiedOffset++] = y2;
              return simplifiedOffset;
          };
  
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {Array.<number>} ends Ends.
           * @param {number} stride Stride.
           * @param {number} tolerance Tolerance.
           * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
           *     coordinates.
           * @param {number} simplifiedOffset Simplified offset.
           * @param {Array.<number>} simplifiedEnds Simplified ends.
           * @return {number} Simplified offset.
           */
          ol.geom.flat.simplify.quantizes = function(
              flatCoordinates, offset, ends, stride,
              tolerance,
              simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds) {
              var i, ii;
              for (i = 0, ii = ends.length; i < ii; ++i) {
                  var end = ends[i];
                  simplifiedOffset = ol.geom.flat.simplify.quantize(
                      flatCoordinates, offset, end, stride,
                      tolerance,
                      simplifiedFlatCoordinates, simplifiedOffset);
                  simplifiedEnds.push(simplifiedOffset);
                  offset = end;
              }
              return simplifiedOffset;
          };
  
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {Array.<Array.<number>>} endss Endss.
           * @param {number} stride Stride.
           * @param {number} tolerance Tolerance.
           * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
           *     coordinates.
           * @param {number} simplifiedOffset Simplified offset.
           * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss.
           * @return {number} Simplified offset.
           */
          ol.geom.flat.simplify.quantizess = function(
              flatCoordinates, offset, endss, stride,
              tolerance,
              simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) {
              var i, ii;
              for (i = 0, ii = endss.length; i < ii; ++i) {
                  var ends = endss[i];
                  var simplifiedEnds = [];
                  simplifiedOffset = ol.geom.flat.simplify.quantizes(
                      flatCoordinates, offset, ends, stride,
                      tolerance,
                      simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds);
                  simplifiedEndss.push(simplifiedEnds);
                  offset = ends[ends.length - 1];
              }
              return simplifiedOffset;
          };
  
          ol.geom.GeometryType = {
              POINT: 'Point',
              LINE_STRING: 'LineString',
              LINEAR_RING: 'LinearRing',
              POLYGON: 'Polygon',
              MULTI_POINT: 'MultiPoint',
              MULTI_LINE_STRING: 'MultiLineString',
              MULTI_POLYGON: 'MultiPolygon',
              GEOMETRY_COLLECTION: 'GeometryCollection',
              CIRCLE: 'Circle'
          };
  
          /**
           * @classdesc
           * Abstract base class; normally only used for creating subclasses and not
           * instantiated in apps.
           * Base class for vector geometries.
           *
           * To get notified of changes to the geometry, register a listener for the
           * generic `change` event on your geometry instance.
           *
           * @constructor
           * @abstract
           * @extends {ol.Object}
           * @api
           */
          ol.geom.Geometry = function() {
  
              ol.Object.call(this);
  
              /**
               * @private
               * @type {ol.Extent}
               */
              this.extent_ = ol.extent.createEmpty();
  
              /**
               * @private
               * @type {number}
               */
              this.extentRevision_ = -1;
  
              /**
               * @protected
               * @type {Object.<string, ol.geom.Geometry>}
               */
              this.simplifiedGeometryCache = {};
  
              /**
               * @protected
               * @type {number}
               */
              this.simplifiedGeometryMaxMinSquaredTolerance = 0;
  
              /**
               * @protected
               * @type {number}
               */
              this.simplifiedGeometryRevision = 0;
  
              /**
               * @private
               * @type {ol.Transform}
               */
              this.tmpTransform_ = ol.transform.create();
  
          };
          ol.inherits(ol.geom.Geometry, ol.Object);
  
  
          /**
           * Make a complete copy of the geometry.
           * @abstract
           * @return {!ol.geom.Geometry} Clone.
           */
          ol.geom.Geometry.prototype.clone = function() {};
  
  
          /**
           * @abstract
           * @param {number} x X.
           * @param {number} y Y.
           * @param {ol.Coordinate} closestPoint Closest point.
           * @param {number} minSquaredDistance Minimum squared distance.
           * @return {number} Minimum squared distance.
           */
          ol.geom.Geometry.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {};
  
  
          /**
           * Return the closest point of the geometry to the passed point as
           * {@link ol.Coordinate coordinate}.
           * @param {ol.Coordinate} point Point.
           * @param {ol.Coordinate=} opt_closestPoint Closest point.
           * @return {ol.Coordinate} Closest point.
           * @api
           */
          ol.geom.Geometry.prototype.getClosestPoint = function(point, opt_closestPoint) {
              var closestPoint = opt_closestPoint ? opt_closestPoint : [NaN, NaN];
              this.closestPointXY(point[0], point[1], closestPoint, Infinity);
              return closestPoint;
          };
  
  
          /**
           * Returns true if this geometry includes the specified coordinate. If the
           * coordinate is on the boundary of the geometry, returns false.
           * @param {ol.Coordinate} coordinate Coordinate.
           * @return {boolean} Contains coordinate.
           * @api
           */
          ol.geom.Geometry.prototype.intersectsCoordinate = function(coordinate) {
              return this.containsXY(coordinate[0], coordinate[1]);
          };
  
  
          /**
           * @abstract
           * @param {ol.Extent} extent Extent.
           * @protected
           * @return {ol.Extent} extent Extent.
           */
          ol.geom.Geometry.prototype.computeExtent = function(extent) {};
  
  
          /**
           * @param {number} x X.
           * @param {number} y Y.
           * @return {boolean} Contains (x, y).
           */
          ol.geom.Geometry.prototype.containsXY = ol.functions.FALSE;
  
  
          /**
           * Get the extent of the geometry.
           * @param {ol.Extent=} opt_extent Extent.
           * @return {ol.Extent} extent Extent.
           * @api
           */
          ol.geom.Geometry.prototype.getExtent = function(opt_extent) {
              if (this.extentRevision_ != this.getRevision()) {
                  this.extent_ = this.computeExtent(this.extent_);
                  this.extentRevision_ = this.getRevision();
              }
              return ol.extent.returnOrUpdate(this.extent_, opt_extent);
          };
  
  
          /**
           * Rotate the geometry around a given coordinate. This modifies the geometry
           * coordinates in place.
           * @abstract
           * @param {number} angle Rotation angle in radians.
           * @param {ol.Coordinate} anchor The rotation center.
           * @api
           */
          ol.geom.Geometry.prototype.rotate = function(angle, anchor) {};
  
  
          /**
           * Scale the geometry (with an optional origin).  This modifies the geometry
           * coordinates in place.
           * @abstract
           * @param {number} sx The scaling factor in the x-direction.
           * @param {number=} opt_sy The scaling factor in the y-direction (defaults to
           *     sx).
           * @param {ol.Coordinate=} opt_anchor The scale origin (defaults to the center
           *     of the geometry extent).
           * @api
           */
          ol.geom.Geometry.prototype.scale = function(sx, opt_sy, opt_anchor) {};
  
  
          /**
           * Create a simplified version of this geometry.  For linestrings, this uses
           * the the {@link
           * https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
           * Douglas Peucker} algorithm.  For polygons, a quantization-based
           * simplification is used to preserve topology.
           * @function
           * @param {number} tolerance The tolerance distance for simplification.
           * @return {ol.geom.Geometry} A new, simplified version of the original
           *     geometry.
           * @api
           */
          ol.geom.Geometry.prototype.simplify = function(tolerance) {
              return this.getSimplifiedGeometry(tolerance * tolerance);
          };
  
  
          /**
           * Create a simplified version of this geometry using the Douglas Peucker
           * algorithm.
           * @see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
           * @abstract
           * @param {number} squaredTolerance Squared tolerance.
           * @return {ol.geom.Geometry} Simplified geometry.
           */
          ol.geom.Geometry.prototype.getSimplifiedGeometry = function(squaredTolerance) {};
  
  
          /**
           * Get the type of this geometry.
           * @abstract
           * @return {ol.geom.GeometryType} Geometry type.
           */
          ol.geom.Geometry.prototype.getType = function() {};
  
  
          /**
           * Apply a transform function to each coordinate of the geometry.
           * The geometry is modified in place.
           * If you do not want the geometry modified in place, first `clone()` it and
           * then use this function on the clone.
           * @abstract
           * @param {ol.TransformFunction} transformFn Transform.
           */
          ol.geom.Geometry.prototype.applyTransform = function(transformFn) {};
  
  
          /**
           * Test if the geometry and the passed extent intersect.
           * @abstract
           * @param {ol.Extent} extent Extent.
           * @return {boolean} `true` if the geometry and the extent intersect.
           */
          ol.geom.Geometry.prototype.intersectsExtent = function(extent) {};
  
  
          /**
           * Translate the geometry.  This modifies the geometry coordinates in place.  If
           * instead you want a new geometry, first `clone()` this geometry.
           * @abstract
           * @param {number} deltaX Delta X.
           * @param {number} deltaY Delta Y.
           */
          ol.geom.Geometry.prototype.translate = function(deltaX, deltaY) {};
  
  
          /**
           * Transform each coordinate of the geometry from one coordinate reference
           * system to another. The geometry is modified in place.
           * For example, a line will be transformed to a line and a circle to a circle.
           * If you do not want the geometry modified in place, first `clone()` it and
           * then use this function on the clone.
           *
           * @param {ol.ProjectionLike} source The current projection.  Can be a
           *     string identifier or a {@link ol.proj.Projection} object.
           * @param {ol.ProjectionLike} destination The desired projection.  Can be a
           *     string identifier or a {@link ol.proj.Projection} object.
           * @return {ol.geom.Geometry} This geometry.  Note that original geometry is
           *     modified in place.
           * @api
           */
          ol.geom.Geometry.prototype.transform = function(source, destination) {
              var tmpTransform = this.tmpTransform_;
              source = ol.proj.get(source);
              var transformFn = source.getUnits() == ol.proj.Units.TILE_PIXELS ?
                  function(inCoordinates, outCoordinates, stride) {
                      var pixelExtent = source.getExtent();
                      var projectedExtent = source.getWorldExtent();
                      var scale = ol.extent.getHeight(projectedExtent) / ol.extent.getHeight(pixelExtent);
                      ol.transform.compose(tmpTransform,
                          projectedExtent[0], projectedExtent[3],
                          scale, -scale, 0,
                          0, 0);
                      ol.geom.flat.transform.transform2D(inCoordinates, 0, inCoordinates.length, stride,
                          tmpTransform, outCoordinates);
                      return ol.proj.getTransform(source, destination)(inCoordinates, outCoordinates, stride);
                  } :
                  ol.proj.getTransform(source, destination);
              this.applyTransform(transformFn);
              return this;
          };
  
  
          /**
           * @classdesc
           * Abstract base class; only used for creating subclasses; do not instantiate
           * in apps, as cannot be rendered.
           *
           * @constructor
           * @abstract
           * @extends {ol.geom.Geometry}
           * @api
           */
          ol.geom.SimpleGeometry = function() {
  
              ol.geom.Geometry.call(this);
  
              /**
               * @protected
               * @type {ol.geom.GeometryLayout}
               */
              this.layout = ol.geom.GeometryLayout.XY;
  
              /**
               * @protected
               * @type {number}
               */
              this.stride = 2;
  
              /**
               * @protected
               * @type {Array.<number>}
               */
              this.flatCoordinates = null;
  
          };
          ol.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry);
  
  
          /**
           * @param {number} stride Stride.
           * @private
           * @return {ol.geom.GeometryLayout} layout Layout.
           */
          ol.geom.SimpleGeometry.getLayoutForStride_ = function(stride) {
              var layout;
              if (stride == 2) {
                  layout = ol.geom.GeometryLayout.XY;
              } else if (stride == 3) {
                  layout = ol.geom.GeometryLayout.XYZ;
              } else if (stride == 4) {
                  layout = ol.geom.GeometryLayout.XYZM;
              }
              return /** @type {ol.geom.GeometryLayout} */ (layout);
          };
  
  
          /**
           * @param {ol.geom.GeometryLayout} layout Layout.
           * @return {number} Stride.
           */
          ol.geom.SimpleGeometry.getStrideForLayout = function(layout) {
              var stride;
              if (layout == ol.geom.GeometryLayout.XY) {
                  stride = 2;
              } else if (layout == ol.geom.GeometryLayout.XYZ || layout == ol.geom.GeometryLayout.XYM) {
                  stride = 3;
              } else if (layout == ol.geom.GeometryLayout.XYZM) {
                  stride = 4;
              }
              return /** @type {number} */ (stride);
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.SimpleGeometry.prototype.containsXY = ol.functions.FALSE;
  
  
          /**
           * @inheritDoc
           */
          ol.geom.SimpleGeometry.prototype.computeExtent = function(extent) {
              return ol.extent.createOrUpdateFromFlatCoordinates(
                  this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
                  extent);
          };
  
  
          /**
           * @abstract
           * @return {Array} Coordinates.
           */
          ol.geom.SimpleGeometry.prototype.getCoordinates = function() {};
  
  
          /**
           * Return the first coordinate of the geometry.
           * @return {ol.Coordinate} First coordinate.
           * @api
           */
          ol.geom.SimpleGeometry.prototype.getFirstCoordinate = function() {
              return this.flatCoordinates.slice(0, this.stride);
          };
  
  
          /**
           * @return {Array.<number>} Flat coordinates.
           */
          ol.geom.SimpleGeometry.prototype.getFlatCoordinates = function() {
              return this.flatCoordinates;
          };
  
  
          /**
           * Return the last coordinate of the geometry.
           * @return {ol.Coordinate} Last point.
           * @api
           */
          ol.geom.SimpleGeometry.prototype.getLastCoordinate = function() {
              return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride);
          };
  
  
          /**
           * Return the {@link ol.geom.GeometryLayout layout} of the geometry.
           * @return {ol.geom.GeometryLayout} Layout.
           * @api
           */
          ol.geom.SimpleGeometry.prototype.getLayout = function() {
              return this.layout;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.SimpleGeometry.prototype.getSimplifiedGeometry = function(squaredTolerance) {
              if (this.simplifiedGeometryRevision != this.getRevision()) {
                  ol.obj.clear(this.simplifiedGeometryCache);
                  this.simplifiedGeometryMaxMinSquaredTolerance = 0;
                  this.simplifiedGeometryRevision = this.getRevision();
              }
              // If squaredTolerance is negative or if we know that simplification will not
              // have any effect then just return this.
              if (squaredTolerance < 0 ||
                  (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 &&
                      squaredTolerance <= this.simplifiedGeometryMaxMinSquaredTolerance)) {
                  return this;
              }
              var key = squaredTolerance.toString();
              if (this.simplifiedGeometryCache.hasOwnProperty(key)) {
                  return this.simplifiedGeometryCache[key];
              } else {
                  var simplifiedGeometry =
                      this.getSimplifiedGeometryInternal(squaredTolerance);
                  var simplifiedFlatCoordinates = simplifiedGeometry.getFlatCoordinates();
                  if (simplifiedFlatCoordinates.length < this.flatCoordinates.length) {
                      this.simplifiedGeometryCache[key] = simplifiedGeometry;
                      return simplifiedGeometry;
                  } else {
                      // Simplification did not actually remove any coordinates.  We now know
                      // that any calls to getSimplifiedGeometry with a squaredTolerance less
                      // than or equal to the current squaredTolerance will also not have any
                      // effect.  This allows us to short circuit simplification (saving CPU
                      // cycles) and prevents the cache of simplified geometries from filling
                      // up with useless identical copies of this geometry (saving memory).
                      this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance;
                      return this;
                  }
              }
          };
  
  
          /**
           * @param {number} squaredTolerance Squared tolerance.
           * @return {ol.geom.SimpleGeometry} Simplified geometry.
           * @protected
           */
          ol.geom.SimpleGeometry.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
              return this;
          };
  
  
          /**
           * @return {number} Stride.
           */
          ol.geom.SimpleGeometry.prototype.getStride = function() {
              return this.stride;
          };
  
  
          /**
           * @param {ol.geom.GeometryLayout} layout Layout.
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @protected
           */
          ol.geom.SimpleGeometry.prototype.setFlatCoordinatesInternal = function(layout, flatCoordinates) {
              this.stride = ol.geom.SimpleGeometry.getStrideForLayout(layout);
              this.layout = layout;
              this.flatCoordinates = flatCoordinates;
          };
  
  
          /**
           * @abstract
           * @param {Array} coordinates Coordinates.
           * @param {ol.geom.GeometryLayout=} opt_layout Layout.
           */
          ol.geom.SimpleGeometry.prototype.setCoordinates = function(coordinates, opt_layout) {};
  
  
          /**
           * @param {ol.geom.GeometryLayout|undefined} layout Layout.
           * @param {Array} coordinates Coordinates.
           * @param {number} nesting Nesting.
           * @protected
           */
          ol.geom.SimpleGeometry.prototype.setLayout = function(layout, coordinates, nesting) {
              /** @type {number} */
              var stride;
              if (layout) {
                  stride = ol.geom.SimpleGeometry.getStrideForLayout(layout);
              } else {
                  var i;
                  for (i = 0; i < nesting; ++i) {
                      if (coordinates.length === 0) {
                          this.layout = ol.geom.GeometryLayout.XY;
                          this.stride = 2;
                          return;
                      } else {
                          coordinates = /** @type {Array} */ (coordinates[0]);
                      }
                  }
                  stride = coordinates.length;
                  layout = ol.geom.SimpleGeometry.getLayoutForStride_(stride);
              }
              this.layout = layout;
              this.stride = stride;
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.SimpleGeometry.prototype.applyTransform = function(transformFn) {
              if (this.flatCoordinates) {
                  transformFn(this.flatCoordinates, this.flatCoordinates, this.stride);
                  this.changed();
              }
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.SimpleGeometry.prototype.rotate = function(angle, anchor) {
              var flatCoordinates = this.getFlatCoordinates();
              if (flatCoordinates) {
                  var stride = this.getStride();
                  ol.geom.flat.transform.rotate(
                      flatCoordinates, 0, flatCoordinates.length,
                      stride, angle, anchor, flatCoordinates);
                  this.changed();
              }
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.SimpleGeometry.prototype.scale = function(sx, opt_sy, opt_anchor) {
              var sy = opt_sy;
              if (sy === undefined) {
                  sy = sx;
              }
              var anchor = opt_anchor;
              if (!anchor) {
                  anchor = ol.extent.getCenter(this.getExtent());
              }
              var flatCoordinates = this.getFlatCoordinates();
              if (flatCoordinates) {
                  var stride = this.getStride();
                  ol.geom.flat.transform.scale(
                      flatCoordinates, 0, flatCoordinates.length,
                      stride, sx, sy, anchor, flatCoordinates);
                  this.changed();
              }
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.SimpleGeometry.prototype.translate = function(deltaX, deltaY) {
              var flatCoordinates = this.getFlatCoordinates();
              if (flatCoordinates) {
                  var stride = this.getStride();
                  ol.geom.flat.transform.translate(
                      flatCoordinates, 0, flatCoordinates.length, stride,
                      deltaX, deltaY, flatCoordinates);
                  this.changed();
              }
          };
  
  
          /**
           * @param {ol.geom.SimpleGeometry} simpleGeometry Simple geometry.
           * @param {ol.Transform} transform Transform.
           * @param {Array.<number>=} opt_dest Destination.
           * @return {Array.<number>} Transformed flat coordinates.
           */
          ol.geom.SimpleGeometry.transform2D = function(simpleGeometry, transform, opt_dest) {
              var flatCoordinates = simpleGeometry.getFlatCoordinates();
              if (!flatCoordinates) {
                  return null;
              } else {
                  var stride = simpleGeometry.getStride();
                  return ol.geom.flat.transform.transform2D(
                      flatCoordinates, 0, flatCoordinates.length, stride,
                      transform, opt_dest);
              }
          };
  
          ol.geom.Polygon = function(coordinates, opt_layout) {
  
              ol.geom.SimpleGeometry.call(this);
  
              /**
               * @type {Array.<number>}
               * @private
               */
              this.ends_ = [];
  
              /**
               * @private
               * @type {number}
               */
              this.flatInteriorPointRevision_ = -1;
  
              /**
               * @private
               * @type {ol.Coordinate}
               */
              this.flatInteriorPoint_ = null;
  
              /**
               * @private
               * @type {number}
               */
              this.maxDelta_ = -1;
  
              /**
               * @private
               * @type {number}
               */
              this.maxDeltaRevision_ = -1;
  
              /**
               * @private
               * @type {number}
               */
              this.orientedRevision_ = -1;
  
              /**
               * @private
               * @type {Array.<number>}
               */
              this.orientedFlatCoordinates_ = null;
  
              this.setCoordinates(coordinates, opt_layout);
  
          };
          ol.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry);
  
  
          /**
           * Append the passed linear ring to this polygon.
           * @param {ol.geom.LinearRing} linearRing Linear ring.
           * @api
           */
          ol.geom.Polygon.prototype.appendLinearRing = function(linearRing) {
              if (!this.flatCoordinates) {
                  this.flatCoordinates = linearRing.getFlatCoordinates().slice();
              } else {
                  ol.array.extend(this.flatCoordinates, linearRing.getFlatCoordinates());
              }
              this.ends_.push(this.flatCoordinates.length);
              this.changed();
          };
  
  
          /**
           * Make a complete copy of the geometry.
           * @return {!ol.geom.Polygon} Clone.
           * @override
           * @api
           */
          ol.geom.Polygon.prototype.clone = function() {
              var polygon = new ol.geom.Polygon(null);
              polygon.setFlatCoordinates(
                  this.layout, this.flatCoordinates.slice(), this.ends_.slice());
              return polygon;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.Polygon.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
              if (minSquaredDistance <
                  ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
                  return minSquaredDistance;
              }
              if (this.maxDeltaRevision_ != this.getRevision()) {
                  this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta(
                      this.flatCoordinates, 0, this.ends_, this.stride, 0));
                  this.maxDeltaRevision_ = this.getRevision();
              }
              return ol.geom.flat.closest.getsClosestPoint(
                  this.flatCoordinates, 0, this.ends_, this.stride,
                  this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.Polygon.prototype.containsXY = function(x, y) {
              return ol.geom.flat.contains.linearRingsContainsXY(
                  this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y);
          };
  
  
          /**
           * Return the area of the polygon on projected plane.
           * @return {number} Area (on projected plane).
           * @api
           */
          ol.geom.Polygon.prototype.getArea = function() {
              return ol.geom.flat.area.linearRings(
                  this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride);
          };
  
  
          /**
           * Get the coordinate array for this geometry.  This array has the structure
           * of a GeoJSON coordinate array for polygons.
           *
           * @param {boolean=} opt_right Orient coordinates according to the right-hand
           *     rule (counter-clockwise for exterior and clockwise for interior rings).
           *     If `false`, coordinates will be oriented according to the left-hand rule
           *     (clockwise for exterior and counter-clockwise for interior rings).
           *     By default, coordinate orientation will depend on how the geometry was
           *     constructed.
           * @return {Array.<Array.<ol.Coordinate>>} Coordinates.
           * @override
           * @api
           */
          ol.geom.Polygon.prototype.getCoordinates = function(opt_right) {
              var flatCoordinates;
              if (opt_right !== undefined) {
                  flatCoordinates = this.getOrientedFlatCoordinates().slice();
                  ol.geom.flat.orient.orientLinearRings(
                      flatCoordinates, 0, this.ends_, this.stride, opt_right);
              } else {
                  flatCoordinates = this.flatCoordinates;
              }
  
              return ol.geom.flat.inflate.coordinatess(
                  flatCoordinates, 0, this.ends_, this.stride);
          };
  
  
          /**
           * @return {Array.<number>} Ends.
           */
          ol.geom.Polygon.prototype.getEnds = function() {
              return this.ends_;
          };
  
  
          /**
           * @return {Array.<number>} Interior point.
           */
          ol.geom.Polygon.prototype.getFlatInteriorPoint = function() {
              if (this.flatInteriorPointRevision_ != this.getRevision()) {
                  var flatCenter = ol.extent.getCenter(this.getExtent());
                  this.flatInteriorPoint_ = ol.geom.flat.interiorpoint.linearRings(
                      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride,
                      flatCenter, 0);
                  this.flatInteriorPointRevision_ = this.getRevision();
              }
              return this.flatInteriorPoint_;
          };
  
  
          /**
           * Return an interior point of the polygon.
           * @return {ol.geom.Point} Interior point as XYM coordinate, where M is the
           * length of the horizontal intersection that the point belongs to.
           * @api
           */
          ol.geom.Polygon.prototype.getInteriorPoint = function() {
              return new ol.geom.Point(this.getFlatInteriorPoint(), ol.geom.GeometryLayout.XYM);
          };
  
  
          /**
           * Return the number of rings of the polygon,  this includes the exterior
           * ring and any interior rings.
           *
           * @return {number} Number of rings.
           * @api
           */
          ol.geom.Polygon.prototype.getLinearRingCount = function() {
              return this.ends_.length;
          };
  
  
          /**
           * Return the Nth linear ring of the polygon geometry. Return `null` if the
           * given index is out of range.
           * The exterior linear ring is available at index `0` and the interior rings
           * at index `1` and beyond.
           *
           * @param {number} index Index.
           * @return {ol.geom.LinearRing} Linear ring.
           * @api
           */
          ol.geom.Polygon.prototype.getLinearRing = function(index) {
              if (index < 0 || this.ends_.length <= index) {
                  return null;
              }
              var linearRing = new ol.geom.LinearRing(null);
              linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice(
                  index === 0 ? 0 : this.ends_[index - 1], this.ends_[index]));
              return linearRing;
          };
  
  
          /**
           * Return the linear rings of the polygon.
           * @return {Array.<ol.geom.LinearRing>} Linear rings.
           * @api
           */
          ol.geom.Polygon.prototype.getLinearRings = function() {
              var layout = this.layout;
              var flatCoordinates = this.flatCoordinates;
              var ends = this.ends_;
              var linearRings = [];
              var offset = 0;
              var i, ii;
              for (i = 0, ii = ends.length; i < ii; ++i) {
                  var end = ends[i];
                  var linearRing = new ol.geom.LinearRing(null);
                  linearRing.setFlatCoordinates(layout, flatCoordinates.slice(offset, end));
                  linearRings.push(linearRing);
                  offset = end;
              }
              return linearRings;
          };
  
  
          /**
           * @return {Array.<number>} Oriented flat coordinates.
           */
          ol.geom.Polygon.prototype.getOrientedFlatCoordinates = function() {
              if (this.orientedRevision_ != this.getRevision()) {
                  var flatCoordinates = this.flatCoordinates;
                  if (ol.geom.flat.orient.linearRingsAreOriented(
                      flatCoordinates, 0, this.ends_, this.stride)) {
                      this.orientedFlatCoordinates_ = flatCoordinates;
                  } else {
                      this.orientedFlatCoordinates_ = flatCoordinates.slice();
                      this.orientedFlatCoordinates_.length =
                          ol.geom.flat.orient.orientLinearRings(
                              this.orientedFlatCoordinates_, 0, this.ends_, this.stride);
                  }
                  this.orientedRevision_ = this.getRevision();
              }
              return this.orientedFlatCoordinates_;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.Polygon.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
              var simplifiedFlatCoordinates = [];
              var simplifiedEnds = [];
              simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizes(
                  this.flatCoordinates, 0, this.ends_, this.stride,
                  Math.sqrt(squaredTolerance),
                  simplifiedFlatCoordinates, 0, simplifiedEnds);
              var simplifiedPolygon = new ol.geom.Polygon(null);
              simplifiedPolygon.setFlatCoordinates(
                  ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds);
              return simplifiedPolygon;
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.Polygon.prototype.getType = function() {
              return ol.geom.GeometryType.POLYGON;
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.Polygon.prototype.intersectsExtent = function(extent) {
              return ol.geom.flat.intersectsextent.linearRings(
                  this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent);
          };
  
  
          /**
           * Set the coordinates of the polygon.
           * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
           * @param {ol.geom.GeometryLayout=} opt_layout Layout.
           * @override
           * @api
           */
          ol.geom.Polygon.prototype.setCoordinates = function(coordinates, opt_layout) {
              if (!coordinates) {
                  this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_);
              } else {
                  this.setLayout(opt_layout, coordinates, 2);
                  if (!this.flatCoordinates) {
                      this.flatCoordinates = [];
                  }
                  var ends = ol.geom.flat.deflate.coordinatess(
                      this.flatCoordinates, 0, coordinates, this.stride, this.ends_);
                  this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1];
                  //this.changed();
              }
          };
  
  
          /**
           * @param {ol.geom.GeometryLayout} layout Layout.
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {Array.<number>} ends Ends.
           */
          ol.geom.Polygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) {
              this.setFlatCoordinatesInternal(layout, flatCoordinates);
              this.ends_ = ends;
              //this.changed();
          };
  
  
          /**
           * Create an approximation of a circle on the surface of a sphere.
           * @param {ol.Sphere} sphere The sphere.
           * @param {ol.Coordinate} center Center (`[lon, lat]` in degrees).
           * @param {number} radius The great-circle distance from the center to
           *     the polygon vertices.
           * @param {number=} opt_n Optional number of vertices for the resulting
           *     polygon. Default is `32`.
           * @return {ol.geom.Polygon} The "circular" polygon.
           * @api
           */
          ol.geom.Polygon.circular = function(sphere, center, radius, opt_n) {
              var n = opt_n ? opt_n : 32;
              /** @type {Array.<number>} */
              var flatCoordinates = [];
              var i;
              for (i = 0; i < n; ++i) {
                  ol.array.extend(
                      flatCoordinates, sphere.offset(center, radius, 2 * Math.PI * i / n));
              }
              flatCoordinates.push(flatCoordinates[0], flatCoordinates[1]);
              var polygon = new ol.geom.Polygon(null);
              polygon.setFlatCoordinates(
                  ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]);
              return polygon;
          };
  
  
          /**
           * Create a polygon from an extent. The layout used is `XY`.
           * @param {ol.Extent} extent The extent.
           * @return {ol.geom.Polygon} The polygon.
           * @api
           */
          ol.geom.Polygon.fromExtent = function(extent) {
              var minX = extent[0];
              var minY = extent[1];
              var maxX = extent[2];
              var maxY = extent[3];
              var flatCoordinates =
                  [minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY];
              var polygon = new ol.geom.Polygon(null);
              polygon.setFlatCoordinates(
                  ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]);
              return polygon;
          };
  
  
          /**
           * Create a regular polygon from a circle.
           * @param {ol.geom.Circle} circle Circle geometry.
           * @param {number=} opt_sides Number of sides of the polygon. Default is 32.
           * @param {number=} opt_angle Start angle for the first vertex of the polygon in
           *     radians. Default is 0.
           * @return {ol.geom.Polygon} Polygon geometry.
           * @api
           */
          ol.geom.Polygon.fromCircle = function(circle, opt_sides, opt_angle) {
              var sides = opt_sides ? opt_sides : 32;
              var stride = circle.getStride();
              var layout = circle.getLayout();
              var polygon = new ol.geom.Polygon(null, layout);
              var arrayLength = stride * (sides + 1);
              var flatCoordinates = new Array(arrayLength);
              for (var i = 0; i < arrayLength; i++) {
                  flatCoordinates[i] = 0;
              }
              var ends = [flatCoordinates.length];
              polygon.setFlatCoordinates(layout, flatCoordinates, ends);
              ol.geom.Polygon.makeRegular(
                  polygon, circle.getCenter(), circle.getRadius(), opt_angle);
              return polygon;
          };
  
  
          /**
           * Modify the coordinates of a polygon to make it a regular polygon.
           * @param {ol.geom.Polygon} polygon Polygon geometry.
           * @param {ol.Coordinate} center Center of the regular polygon.
           * @param {number} radius Radius of the regular polygon.
           * @param {number=} opt_angle Start angle for the first vertex of the polygon in
           *     radians. Default is 0.
           */
          ol.geom.Polygon.makeRegular = function(polygon, center, radius, opt_angle) {
              var flatCoordinates = polygon.getFlatCoordinates();
              var layout = polygon.getLayout();
              var stride = polygon.getStride();
              var ends = polygon.getEnds();
              var sides = flatCoordinates.length / stride - 1;
              var startAngle = opt_angle ? opt_angle : 0;
              var angle, offset;
              for (var i = 0; i <= sides; ++i) {
                  offset = i * stride;
                  angle = startAngle + (ol.math.modulo(i, sides) * 2 * Math.PI / sides);
                  flatCoordinates[offset] = center[0] + (radius * Math.cos(angle));
                  flatCoordinates[offset + 1] = center[1] + (radius * Math.sin(angle));
              }
              polygon.setFlatCoordinates(layout, flatCoordinates, ends);
          };
  
          /**
           * @classdesc
           * Linestring geometry.
           *
           * @constructor
           * @extends {ol.geom.SimpleGeometry}
           * @param {Array.<ol.Coordinate>} coordinates Coordinates.
           * @param {ol.geom.GeometryLayout=} opt_layout Layout.
           * @api
           */
          ol.geom.LineString = function(coordinates, opt_layout) {
  
              ol.geom.SimpleGeometry.call(this);
  
              /**
               * @private
               * @type {ol.Coordinate}
               */
              this.flatMidpoint_ = null;
  
              /**
               * @private
               * @type {number}
               */
              this.flatMidpointRevision_ = -1;
  
              /**
               * @private
               * @type {number}
               */
              this.maxDelta_ = -1;
  
              /**
               * @private
               * @type {number}
               */
              this.maxDeltaRevision_ = -1;
  
              this.setCoordinates(coordinates, opt_layout);
  
          };
          ol.inherits(ol.geom.LineString, ol.geom.SimpleGeometry);
  
  
          /**
           * Append the passed coordinate to the coordinates of the linestring.
           * @param {ol.Coordinate} coordinate Coordinate.
           * @api
           */
          ol.geom.LineString.prototype.appendCoordinate = function(coordinate) {
              if (!this.flatCoordinates) {
                  this.flatCoordinates = coordinate.slice();
              } else {
                  ol.array.extend(this.flatCoordinates, coordinate);
              }
              this.changed();
          };
  
  
          /**
           * Make a complete copy of the geometry.
           * @return {!ol.geom.LineString} Clone.
           * @override
           * @api
           */
          ol.geom.LineString.prototype.clone = function() {
              var lineString = new ol.geom.LineString(null);
              lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
              return lineString;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.LineString.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
              if (minSquaredDistance <
                  ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
                  return minSquaredDistance;
              }
              if (this.maxDeltaRevision_ != this.getRevision()) {
                  this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta(
                      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0));
                  this.maxDeltaRevision_ = this.getRevision();
              }
              return ol.geom.flat.closest.getClosestPoint(
                  this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
                  this.maxDelta_, false, x, y, closestPoint, minSquaredDistance);
          };
  
  
          /**
           * Iterate over each segment, calling the provided callback.
           * If the callback returns a truthy value the function returns that
           * value immediately. Otherwise the function returns `false`.
           *
           * @param {function(this: S, ol.Coordinate, ol.Coordinate): T} callback Function
           *     called for each segment.
           * @param {S=} opt_this The object to be used as the value of 'this'
           *     within callback.
           * @return {T|boolean} Value.
           * @template T,S
           * @api
           */
          ol.geom.LineString.prototype.forEachSegment = function(callback, opt_this) {
              return ol.geom.flat.segments.forEach(this.flatCoordinates, 0,
                  this.flatCoordinates.length, this.stride, callback, opt_this);
          };
  
  
          /**
           * Returns the coordinate at `m` using linear interpolation, or `null` if no
           * such coordinate exists.
           *
           * `opt_extrapolate` controls extrapolation beyond the range of Ms in the
           * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first
           * M will return the first coordinate and Ms greater than the last M will
           * return the last coordinate.
           *
           * @param {number} m M.
           * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`.
           * @return {ol.Coordinate} Coordinate.
           * @api
           */
          ol.geom.LineString.prototype.getCoordinateAtM = function(m, opt_extrapolate) {
              if (this.layout != ol.geom.GeometryLayout.XYM &&
                  this.layout != ol.geom.GeometryLayout.XYZM) {
                  return null;
              }
              var extrapolate = opt_extrapolate !== undefined ? opt_extrapolate : false;
              return ol.geom.flat.interpolate.lineStringCoordinateAtM(this.flatCoordinates, 0,
                  this.flatCoordinates.length, this.stride, m, extrapolate);
          };
  
  
          /**
           * Return the coordinates of the linestring.
           * @return {Array.<ol.Coordinate>} Coordinates.
           * @override
           * @api
           */
          ol.geom.LineString.prototype.getCoordinates = function() {
              return ol.geom.flat.inflate.coordinates(
                  this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
          };
  
  
          /**
           * Return the coordinate at the provided fraction along the linestring.
           * The `fraction` is a number between 0 and 1, where 0 is the start of the
           * linestring and 1 is the end.
           * @param {number} fraction Fraction.
           * @param {ol.Coordinate=} opt_dest Optional coordinate whose values will
           *     be modified. If not provided, a new coordinate will be returned.
           * @return {ol.Coordinate} Coordinate of the interpolated point.
           * @api
           */
          ol.geom.LineString.prototype.getCoordinateAt = function(fraction, opt_dest) {
              return ol.geom.flat.interpolate.lineString(
                  this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
                  fraction, opt_dest);
          };
  
  
          /**
           * Return the length of the linestring on projected plane.
           * @return {number} Length (on projected plane).
           * @api
           */
          ol.geom.LineString.prototype.getLength = function() {
              return ol.geom.flat.length.lineString(
                  this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
          };
  
  
          /**
           * @return {Array.<number>} Flat midpoint.
           */
          ol.geom.LineString.prototype.getFlatMidpoint = function() {
              if (this.flatMidpointRevision_ != this.getRevision()) {
                  this.flatMidpoint_ = this.getCoordinateAt(0.5, this.flatMidpoint_);
                  this.flatMidpointRevision_ = this.getRevision();
              }
              return this.flatMidpoint_;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.LineString.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
              var simplifiedFlatCoordinates = [];
              simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker(
                  this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
                  squaredTolerance, simplifiedFlatCoordinates, 0);
              var simplifiedLineString = new ol.geom.LineString(null);
              simplifiedLineString.setFlatCoordinates(
                  ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates);
              return simplifiedLineString;
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.LineString.prototype.getType = function() {
              return ol.geom.GeometryType.LINE_STRING;
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.LineString.prototype.intersectsExtent = function(extent) {
              return ol.geom.flat.intersectsextent.lineString(
                  this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
                  extent);
          };
  
  
          /**
           * Set the coordinates of the linestring.
           * @param {Array.<ol.Coordinate>} coordinates Coordinates.
           * @param {ol.geom.GeometryLayout=} opt_layout Layout.
           * @override
           * @api
           */
          ol.geom.LineString.prototype.setCoordinates = function(coordinates, opt_layout) {
              if (!coordinates) {
                  this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
              } else {
                  this.setLayout(opt_layout, coordinates, 1);
                  if (!this.flatCoordinates) {
                      this.flatCoordinates = [];
                  }
                  this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
                      this.flatCoordinates, 0, coordinates, this.stride);
                  this.changed();
              }
          };
  
  
          /**
           * @param {ol.geom.GeometryLayout} layout Layout.
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           */
          ol.geom.LineString.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
              this.setFlatCoordinatesInternal(layout, flatCoordinates);
              this.changed();
          };
  
          ol.geom.Point = function(coordinates, opt_layout) {
              ol.geom.SimpleGeometry.call(this);
              this.setCoordinates(coordinates, opt_layout);
          };
          ol.inherits(ol.geom.Point, ol.geom.SimpleGeometry);
  
          ol.geom.Point.prototype.clone = function() {
              var point = new ol.geom.Point(null);
              point.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
              return point;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.Point.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
              var flatCoordinates = this.flatCoordinates;
              var squaredDistance = ol.math.squaredDistance(
                  x, y, flatCoordinates[0], flatCoordinates[1]);
              if (squaredDistance < minSquaredDistance) {
                  var stride = this.stride;
                  var i;
                  for (i = 0; i < stride; ++i) {
                      closestPoint[i] = flatCoordinates[i];
                  }
                  closestPoint.length = stride;
                  return squaredDistance;
              } else {
                  return minSquaredDistance;
              }
          };
  
          ol.geom.Point.prototype.getCoordinates = function() {
              return !this.flatCoordinates ? [] : this.flatCoordinates.slice();
          };
  
          ol.geom.Point.prototype.computeExtent = function(extent) {
              return ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates, extent);
          };
  
          ol.geom.Point.prototype.getType = function() {
              return ol.geom.GeometryType.POINT;
          };
  
          ol.geom.Point.prototype.intersectsExtent = function(extent) {
              return ol.extent.containsXY(extent,
                  this.flatCoordinates[0], this.flatCoordinates[1]);
          };
  
          ol.geom.Point.prototype.setCoordinates = function(coordinates, opt_layout) {
              if (!coordinates) {
                  this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
              } else {
                  this.setLayout(opt_layout, coordinates, 0);
                  if (!this.flatCoordinates) {
                      this.flatCoordinates = [];
                  }
                  this.flatCoordinates.length = ol.geom.flat.deflate.coordinate(
                      this.flatCoordinates, 0, coordinates, this.stride);
                  this.changed();
              }
          };
  
          ol.geom.Point.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
              this.setFlatCoordinatesInternal(layout, flatCoordinates);
              this.changed();
          };
  
          /**
           * @classdesc
           * Multi-linestring geometry.
           *
           * @constructor
           * @extends {ol.geom.SimpleGeometry}
           * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
           * @param {ol.geom.GeometryLayout=} opt_layout Layout.
           * @api
           */
          ol.geom.MultiLineString = function(coordinates, opt_layout) {
  
              ol.geom.SimpleGeometry.call(this);
  
              /**
               * @type {Array.<number>}
               * @private
               */
              this.ends_ = [];
  
              /**
               * @private
               * @type {number}
               */
              this.maxDelta_ = -1;
  
              /**
               * @private
               * @type {number}
               */
              this.maxDeltaRevision_ = -1;
  
              this.setCoordinates(coordinates, opt_layout);
  
          };
          ol.inherits(ol.geom.MultiLineString, ol.geom.SimpleGeometry);
  
  
          /**
           * Append the passed linestring to the multilinestring.
           * @param {ol.geom.LineString} lineString LineString.
           * @api
           */
          ol.geom.MultiLineString.prototype.appendLineString = function(lineString) {
              if (!this.flatCoordinates) {
                  this.flatCoordinates = lineString.getFlatCoordinates().slice();
              } else {
                  ol.array.extend(
                      this.flatCoordinates, lineString.getFlatCoordinates().slice());
              }
              this.ends_.push(this.flatCoordinates.length);
              this.changed();
          };
  
  
          /**
           * Make a complete copy of the geometry.
           * @return {!ol.geom.MultiLineString} Clone.
           * @override
           * @api
           */
          ol.geom.MultiLineString.prototype.clone = function() {
              var multiLineString = new ol.geom.MultiLineString(null);
              multiLineString.setFlatCoordinates(
                  this.layout, this.flatCoordinates.slice(), this.ends_.slice());
              return multiLineString;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.MultiLineString.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
              if (minSquaredDistance <
                  ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
                  return minSquaredDistance;
              }
              if (this.maxDeltaRevision_ != this.getRevision()) {
                  this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta(
                      this.flatCoordinates, 0, this.ends_, this.stride, 0));
                  this.maxDeltaRevision_ = this.getRevision();
              }
              return ol.geom.flat.closest.getsClosestPoint(
                  this.flatCoordinates, 0, this.ends_, this.stride,
                  this.maxDelta_, false, x, y, closestPoint, minSquaredDistance);
          };
  
  
          /**
           * Returns the coordinate at `m` using linear interpolation, or `null` if no
           * such coordinate exists.
           *
           * `opt_extrapolate` controls extrapolation beyond the range of Ms in the
           * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first
           * M will return the first coordinate and Ms greater than the last M will
           * return the last coordinate.
           *
           * `opt_interpolate` controls interpolation between consecutive LineStrings
           * within the MultiLineString. If `opt_interpolate` is `true` the coordinates
           * will be linearly interpolated between the last coordinate of one LineString
           * and the first coordinate of the next LineString.  If `opt_interpolate` is
           * `false` then the function will return `null` for Ms falling between
           * LineStrings.
           *
           * @param {number} m M.
           * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`.
           * @param {boolean=} opt_interpolate Interpolate. Default is `false`.
           * @return {ol.Coordinate} Coordinate.
           * @api
           */
          ol.geom.MultiLineString.prototype.getCoordinateAtM = function(m, opt_extrapolate, opt_interpolate) {
              if ((this.layout != ol.geom.GeometryLayout.XYM &&
                  this.layout != ol.geom.GeometryLayout.XYZM) ||
                  this.flatCoordinates.length === 0) {
                  return null;
              }
              var extrapolate = opt_extrapolate !== undefined ? opt_extrapolate : false;
              var interpolate = opt_interpolate !== undefined ? opt_interpolate : false;
              return ol.geom.flat.interpolate.lineStringsCoordinateAtM(this.flatCoordinates, 0,
                  this.ends_, this.stride, m, extrapolate, interpolate);
          };
  
  
          /**
           * Return the coordinates of the multilinestring.
           * @return {Array.<Array.<ol.Coordinate>>} Coordinates.
           * @override
           * @api
           */
          ol.geom.MultiLineString.prototype.getCoordinates = function() {
              return ol.geom.flat.inflate.coordinatess(
                  this.flatCoordinates, 0, this.ends_, this.stride);
          };
  
  
          /**
           * @return {Array.<number>} Ends.
           */
          ol.geom.MultiLineString.prototype.getEnds = function() {
              return this.ends_;
          };
  
  
          /**
           * Return the linestring at the specified index.
           * @param {number} index Index.
           * @return {ol.geom.LineString} LineString.
           * @api
           */
          ol.geom.MultiLineString.prototype.getLineString = function(index) {
              if (index < 0 || this.ends_.length <= index) {
                  return null;
              }
              var lineString = new ol.geom.LineString(null);
              lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice(
                  index === 0 ? 0 : this.ends_[index - 1], this.ends_[index]));
              return lineString;
          };
  
  
          /**
           * Return the linestrings of this multilinestring.
           * @return {Array.<ol.geom.LineString>} LineStrings.
           * @api
           */
          ol.geom.MultiLineString.prototype.getLineStrings = function() {
              var flatCoordinates = this.flatCoordinates;
              var ends = this.ends_;
              var layout = this.layout;
              /** @type {Array.<ol.geom.LineString>} */
              var lineStrings = [];
              var offset = 0;
              var i, ii;
              for (i = 0, ii = ends.length; i < ii; ++i) {
                  var end = ends[i];
                  var lineString = new ol.geom.LineString(null);
                  lineString.setFlatCoordinates(layout, flatCoordinates.slice(offset, end));
                  lineStrings.push(lineString);
                  offset = end;
              }
              return lineStrings;
          };
  
  
          /**
           * @return {Array.<number>} Flat midpoints.
           */
          ol.geom.MultiLineString.prototype.getFlatMidpoints = function() {
              var midpoints = [];
              var flatCoordinates = this.flatCoordinates;
              var offset = 0;
              var ends = this.ends_;
              var stride = this.stride;
              var i, ii;
              for (i = 0, ii = ends.length; i < ii; ++i) {
                  var end = ends[i];
                  var midpoint = ol.geom.flat.interpolate.lineString(
                      flatCoordinates, offset, end, stride, 0.5);
                  ol.array.extend(midpoints, midpoint);
                  offset = end;
              }
              return midpoints;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.MultiLineString.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
              var simplifiedFlatCoordinates = [];
              var simplifiedEnds = [];
              simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeuckers(
                  this.flatCoordinates, 0, this.ends_, this.stride, squaredTolerance,
                  simplifiedFlatCoordinates, 0, simplifiedEnds);
              var simplifiedMultiLineString = new ol.geom.MultiLineString(null);
              simplifiedMultiLineString.setFlatCoordinates(
                  ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds);
              return simplifiedMultiLineString;
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.MultiLineString.prototype.getType = function() {
              return ol.geom.GeometryType.MULTI_LINE_STRING;
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.MultiLineString.prototype.intersectsExtent = function(extent) {
              return ol.geom.flat.intersectsextent.lineStrings(
                  this.flatCoordinates, 0, this.ends_, this.stride, extent);
          };
  
  
          /**
           * Set the coordinates of the multilinestring.
           * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
           * @param {ol.geom.GeometryLayout=} opt_layout Layout.
           * @override
           * @api
           */
          ol.geom.MultiLineString.prototype.setCoordinates = function(coordinates, opt_layout) {
              if (!coordinates) {
                  this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_);
              } else {
                  this.setLayout(opt_layout, coordinates, 2);
                  if (!this.flatCoordinates) {
                      this.flatCoordinates = [];
                  }
                  var ends = ol.geom.flat.deflate.coordinatess(
                      this.flatCoordinates, 0, coordinates, this.stride, this.ends_);
                  this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1];
                  this.changed();
              }
          };
  
  
          /**
           * @param {ol.geom.GeometryLayout} layout Layout.
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {Array.<number>} ends Ends.
           */
          ol.geom.MultiLineString.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) {
              this.setFlatCoordinatesInternal(layout, flatCoordinates);
              this.ends_ = ends;
              this.changed();
          };
  
  
          /**
           * @param {Array.<ol.geom.LineString>} lineStrings LineStrings.
           */
          ol.geom.MultiLineString.prototype.setLineStrings = function(lineStrings) {
              var layout = this.getLayout();
              var flatCoordinates = [];
              var ends = [];
              var i, ii;
              for (i = 0, ii = lineStrings.length; i < ii; ++i) {
                  var lineString = lineStrings[i];
                  if (i === 0) {
                      layout = lineString.getLayout();
                  }
                  ol.array.extend(flatCoordinates, lineString.getFlatCoordinates());
                  ends.push(flatCoordinates.length);
              }
              this.setFlatCoordinates(layout, flatCoordinates, ends);
          };
  
          /**
           * @classdesc
           * Multi-point geometry.
           *
           * @constructor
           * @extends {ol.geom.SimpleGeometry}
           * @param {Array.<ol.Coordinate>} coordinates Coordinates.
           * @param {ol.geom.GeometryLayout=} opt_layout Layout.
           * @api
           */
          ol.geom.MultiPoint = function(coordinates, opt_layout) {
              ol.geom.SimpleGeometry.call(this);
              this.setCoordinates(coordinates, opt_layout);
          };
          ol.inherits(ol.geom.MultiPoint, ol.geom.SimpleGeometry);
  
  
          /**
           * Append the passed point to this multipoint.
           * @param {ol.geom.Point} point Point.
           * @api
           */
          ol.geom.MultiPoint.prototype.appendPoint = function(point) {
              if (!this.flatCoordinates) {
                  this.flatCoordinates = point.getFlatCoordinates().slice();
              } else {
                  ol.array.extend(this.flatCoordinates, point.getFlatCoordinates());
              }
              this.changed();
          };
  
  
          /**
           * Make a complete copy of the geometry.
           * @return {!ol.geom.MultiPoint} Clone.
           * @override
           * @api
           */
          ol.geom.MultiPoint.prototype.clone = function() {
              var multiPoint = new ol.geom.MultiPoint(null);
              multiPoint.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
              return multiPoint;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.MultiPoint.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
              if (minSquaredDistance <
                  ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
                  return minSquaredDistance;
              }
              var flatCoordinates = this.flatCoordinates;
              var stride = this.stride;
              var i, ii, j;
              for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) {
                  var squaredDistance = ol.math.squaredDistance(
                      x, y, flatCoordinates[i], flatCoordinates[i + 1]);
                  if (squaredDistance < minSquaredDistance) {
                      minSquaredDistance = squaredDistance;
                      for (j = 0; j < stride; ++j) {
                          closestPoint[j] = flatCoordinates[i + j];
                      }
                      closestPoint.length = stride;
                  }
              }
              return minSquaredDistance;
          };
  
  
          /**
           * Return the coordinates of the multipoint.
           * @return {Array.<ol.Coordinate>} Coordinates.
           * @override
           * @api
           */
          ol.geom.MultiPoint.prototype.getCoordinates = function() {
              return ol.geom.flat.inflate.coordinates(
                  this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
          };
  
  
          /**
           * Return the point at the specified index.
           * @param {number} index Index.
           * @return {ol.geom.Point} Point.
           * @api
           */
          ol.geom.MultiPoint.prototype.getPoint = function(index) {
              var n = !this.flatCoordinates ?
                  0 : this.flatCoordinates.length / this.stride;
              if (index < 0 || n <= index) {
                  return null;
              }
              var point = new ol.geom.Point(null);
              point.setFlatCoordinates(this.layout, this.flatCoordinates.slice(
                  index * this.stride, (index + 1) * this.stride));
              return point;
          };
  
  
          /**
           * Return the points of this multipoint.
           * @return {Array.<ol.geom.Point>} Points.
           * @api
           */
          ol.geom.MultiPoint.prototype.getPoints = function() {
              var flatCoordinates = this.flatCoordinates;
              var layout = this.layout;
              var stride = this.stride;
              /** @type {Array.<ol.geom.Point>} */
              var points = [];
              var i, ii;
              for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) {
                  var point = new ol.geom.Point(null);
                  point.setFlatCoordinates(layout, flatCoordinates.slice(i, i + stride));
                  points.push(point);
              }
              return points;
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.MultiPoint.prototype.getType = function() {
              return ol.geom.GeometryType.MULTI_POINT;
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.MultiPoint.prototype.intersectsExtent = function(extent) {
              var flatCoordinates = this.flatCoordinates;
              var stride = this.stride;
              var i, ii, x, y;
              for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) {
                  x = flatCoordinates[i];
                  y = flatCoordinates[i + 1];
                  if (ol.extent.containsXY(extent, x, y)) {
                      return true;
                  }
              }
              return false;
          };
  
  
          /**
           * Set the coordinates of the multipoint.
           * @param {Array.<ol.Coordinate>} coordinates Coordinates.
           * @param {ol.geom.GeometryLayout=} opt_layout Layout.
           * @override
           * @api
           */
          ol.geom.MultiPoint.prototype.setCoordinates = function(coordinates, opt_layout) {
              if (!coordinates) {
                  this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
              } else {
                  this.setLayout(opt_layout, coordinates, 1);
                  if (!this.flatCoordinates) {
                      this.flatCoordinates = [];
                  }
                  this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
                      this.flatCoordinates, 0, coordinates, this.stride);
                  this.changed();
              }
          };
  
  
          /**
           * @param {ol.geom.GeometryLayout} layout Layout.
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           */
          ol.geom.MultiPoint.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
              this.setFlatCoordinatesInternal(layout, flatCoordinates);
              this.changed();
          };
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {Array.<Array.<number>>} endss Endss.
           * @param {number} stride Stride.
           * @return {Array.<number>} Flat centers.
           */
          ol.geom.flat.center.linearRingss = {};
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {Array.<Array.<number>>} endss Endss.
           * @param {number} stride Stride.
           * @return {Array.<number>} Flat centers.
           */
          ol.geom.flat.center.linearRingss = function(flatCoordinates, offset, endss, stride) {
              var flatCenters = [];
              var i, ii;
              var extent = ol.extent.createEmpty();
              for (i = 0, ii = endss.length; i < ii; ++i) {
                  var ends = endss[i];
                  extent = ol.extent.createOrUpdateFromFlatCoordinates(
                      flatCoordinates, offset, ends[0], stride);
                  flatCenters.push((extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2);
                  offset = ends[ends.length - 1];
              }
              return flatCenters;
          };
  
          /**
           * @classdesc
           * Multi-polygon geometry.
           *
           * @constructor
           * @extends {ol.geom.SimpleGeometry}
           * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates.
           * @param {ol.geom.GeometryLayout=} opt_layout Layout.
           * @api
           */
          ol.geom.MultiPolygon = function(coordinates, opt_layout) {
  
              ol.geom.SimpleGeometry.call(this);
  
              /**
               * @type {Array.<Array.<number>>}
               * @private
               */
              this.endss_ = [];
  
              /**
               * @private
               * @type {number}
               */
              this.flatInteriorPointsRevision_ = -1;
  
              /**
               * @private
               * @type {Array.<number>}
               */
              this.flatInteriorPoints_ = null;
  
              /**
               * @private
               * @type {number}
               */
              this.maxDelta_ = -1;
  
              /**
               * @private
               * @type {number}
               */
              this.maxDeltaRevision_ = -1;
  
              /**
               * @private
               * @type {number}
               */
              this.orientedRevision_ = -1;
  
              /**
               * @private
               * @type {Array.<number>}
               */
              this.orientedFlatCoordinates_ = null;
  
              this.setCoordinates(coordinates, opt_layout);
  
          };
          ol.inherits(ol.geom.MultiPolygon, ol.geom.SimpleGeometry);
  
  
          /**
           * Append the passed polygon to this multipolygon.
           * @param {ol.geom.Polygon} polygon Polygon.
           * @api
           */
          ol.geom.MultiPolygon.prototype.appendPolygon = function(polygon) {
              /** @type {Array.<number>} */
              var ends;
              if (!this.flatCoordinates) {
                  this.flatCoordinates = polygon.getFlatCoordinates().slice();
                  ends = polygon.getEnds().slice();
                  this.endss_.push();
              } else {
                  var offset = this.flatCoordinates.length;
                  ol.array.extend(this.flatCoordinates, polygon.getFlatCoordinates());
                  ends = polygon.getEnds().slice();
                  var i, ii;
                  for (i = 0, ii = ends.length; i < ii; ++i) {
                      ends[i] += offset;
                  }
              }
              this.endss_.push(ends);
              this.changed();
          };
  
  
          /**
           * Make a complete copy of the geometry.
           * @return {!ol.geom.MultiPolygon} Clone.
           * @override
           * @api
           */
          ol.geom.MultiPolygon.prototype.clone = function() {
              var multiPolygon = new ol.geom.MultiPolygon(null);
  
              var len = this.endss_.length;
              var newEndss = new Array(len);
              for (var i = 0; i < len; ++i) {
                  newEndss[i] = this.endss_[i].slice();
              }
  
              multiPolygon.setFlatCoordinates(
                  this.layout, this.flatCoordinates.slice(), newEndss);
              return multiPolygon;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.MultiPolygon.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
              if (minSquaredDistance <
                  ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
                  return minSquaredDistance;
              }
              if (this.maxDeltaRevision_ != this.getRevision()) {
                  this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getssMaxSquaredDelta(
                      this.flatCoordinates, 0, this.endss_, this.stride, 0));
                  this.maxDeltaRevision_ = this.getRevision();
              }
              return ol.geom.flat.closest.getssClosestPoint(
                  this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride,
                  this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.MultiPolygon.prototype.containsXY = function(x, y) {
              return ol.geom.flat.contains.linearRingssContainsXY(
                  this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, x, y);
          };
  
  
          /**
           * Return the area of the multipolygon on projected plane.
           * @return {number} Area (on projected plane).
           * @api
           */
          ol.geom.MultiPolygon.prototype.getArea = function() {
              return ol.geom.flat.area.linearRingss(
                  this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride);
          };
  
  
          /**
           * Get the coordinate array for this geometry.  This array has the structure
           * of a GeoJSON coordinate array for multi-polygons.
           *
           * @param {boolean=} opt_right Orient coordinates according to the right-hand
           *     rule (counter-clockwise for exterior and clockwise for interior rings).
           *     If `false`, coordinates will be oriented according to the left-hand rule
           *     (clockwise for exterior and counter-clockwise for interior rings).
           *     By default, coordinate orientation will depend on how the geometry was
           *     constructed.
           * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinates.
           * @override
           * @api
           */
          ol.geom.MultiPolygon.prototype.getCoordinates = function(opt_right) {
              var flatCoordinates;
              if (opt_right !== undefined) {
                  flatCoordinates = this.getOrientedFlatCoordinates().slice();
                  ol.geom.flat.orient.orientLinearRingss(
                      flatCoordinates, 0, this.endss_, this.stride, opt_right);
              } else {
                  flatCoordinates = this.flatCoordinates;
              }
  
              return ol.geom.flat.inflate.coordinatesss(
                  flatCoordinates, 0, this.endss_, this.stride);
          };
  
  
          /**
           * @return {Array.<Array.<number>>} Endss.
           */
          ol.geom.MultiPolygon.prototype.getEndss = function() {
              return this.endss_;
          };
  
  
          /**
           * @return {Array.<number>} Flat interior points.
           */
          ol.geom.MultiPolygon.prototype.getFlatInteriorPoints = function() {
              if (this.flatInteriorPointsRevision_ != this.getRevision()) {
                  var flatCenters = ol.geom.flat.center.linearRingss(
                      this.flatCoordinates, 0, this.endss_, this.stride);
                  this.flatInteriorPoints_ = ol.geom.flat.interiorpoint.linearRingss(
                      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride,
                      flatCenters);
                  this.flatInteriorPointsRevision_ = this.getRevision();
              }
              return this.flatInteriorPoints_;
          };
  
  
          /**
           * Return the interior points as {@link ol.geom.MultiPoint multipoint}.
           * @return {ol.geom.MultiPoint} Interior points as XYM coordinates, where M is
           * the length of the horizontal intersection that the point belongs to.
           * @api
           */
          ol.geom.MultiPolygon.prototype.getInteriorPoints = function() {
              var interiorPoints = new ol.geom.MultiPoint(null);
              interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XYM,
                  this.getFlatInteriorPoints().slice());
              return interiorPoints;
          };
  
  
          /**
           * @return {Array.<number>} Oriented flat coordinates.
           */
          ol.geom.MultiPolygon.prototype.getOrientedFlatCoordinates = function() {
              if (this.orientedRevision_ != this.getRevision()) {
                  var flatCoordinates = this.flatCoordinates;
                  if (ol.geom.flat.orient.linearRingssAreOriented(
                      flatCoordinates, 0, this.endss_, this.stride)) {
                      this.orientedFlatCoordinates_ = flatCoordinates;
                  } else {
                      this.orientedFlatCoordinates_ = flatCoordinates.slice();
                      this.orientedFlatCoordinates_.length =
                          ol.geom.flat.orient.orientLinearRingss(
                              this.orientedFlatCoordinates_, 0, this.endss_, this.stride);
                  }
                  this.orientedRevision_ = this.getRevision();
              }
              return this.orientedFlatCoordinates_;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.geom.MultiPolygon.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
              var simplifiedFlatCoordinates = [];
              var simplifiedEndss = [];
              simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizess(
                  this.flatCoordinates, 0, this.endss_, this.stride,
                  Math.sqrt(squaredTolerance),
                  simplifiedFlatCoordinates, 0, simplifiedEndss);
              var simplifiedMultiPolygon = new ol.geom.MultiPolygon(null);
              simplifiedMultiPolygon.setFlatCoordinates(
                  ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEndss);
              return simplifiedMultiPolygon;
          };
  
  
          /**
           * Return the polygon at the specified index.
           * @param {number} index Index.
           * @return {ol.geom.Polygon} Polygon.
           * @api
           */
          ol.geom.MultiPolygon.prototype.getPolygon = function(index) {
              if (index < 0 || this.endss_.length <= index) {
                  return null;
              }
              var offset;
              if (index === 0) {
                  offset = 0;
              } else {
                  var prevEnds = this.endss_[index - 1];
                  offset = prevEnds[prevEnds.length - 1];
              }
              var ends = this.endss_[index].slice();
              var end = ends[ends.length - 1];
              if (offset !== 0) {
                  var i, ii;
                  for (i = 0, ii = ends.length; i < ii; ++i) {
                      ends[i] -= offset;
                  }
              }
              var polygon = new ol.geom.Polygon(null);
              polygon.setFlatCoordinates(
                  this.layout, this.flatCoordinates.slice(offset, end), ends);
              return polygon;
          };
  
  
          /**
           * Return the polygons of this multipolygon.
           * @return {Array.<ol.geom.Polygon>} Polygons.
           * @api
           */
          ol.geom.MultiPolygon.prototype.getPolygons = function() {
              var layout = this.layout;
              var flatCoordinates = this.flatCoordinates;
              var endss = this.endss_;
              var polygons = [];
              var offset = 0;
              var i, ii, j, jj;
              for (i = 0, ii = endss.length; i < ii; ++i) {
                  var ends = endss[i].slice();
                  var end = ends[ends.length - 1];
                  if (offset !== 0) {
                      for (j = 0, jj = ends.length; j < jj; ++j) {
                          ends[j] -= offset;
                      }
                  }
                  var polygon = new ol.geom.Polygon(null);
                  polygon.setFlatCoordinates(
                      layout, flatCoordinates.slice(offset, end), ends);
                  polygons.push(polygon);
                  offset = end;
              }
              return polygons;
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.MultiPolygon.prototype.getType = function() {
              return ol.geom.GeometryType.MULTI_POLYGON;
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.geom.MultiPolygon.prototype.intersectsExtent = function(extent) {
              return ol.geom.flat.intersectsextent.linearRingss(
                  this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, extent);
          };
  
  
          /**
           * Set the coordinates of the multipolygon.
           * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates.
           * @param {ol.geom.GeometryLayout=} opt_layout Layout.
           * @override
           * @api
           */
          ol.geom.MultiPolygon.prototype.setCoordinates = function(coordinates, opt_layout) {
              if (!coordinates) {
                  this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.endss_);
              } else {
                  this.setLayout(opt_layout, coordinates, 3);
                  if (!this.flatCoordinates) {
                      this.flatCoordinates = [];
                  }
                  var endss = ol.geom.flat.deflate.coordinatesss(
                      this.flatCoordinates, 0, coordinates, this.stride, this.endss_);
                  if (endss.length === 0) {
                      this.flatCoordinates.length = 0;
                  } else {
                      var lastEnds = endss[endss.length - 1];
                      this.flatCoordinates.length = lastEnds.length === 0 ?
                          0 : lastEnds[lastEnds.length - 1];
                  }
                  this.changed();
              }
          };
  
  
          /**
           * @param {ol.geom.GeometryLayout} layout Layout.
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {Array.<Array.<number>>} endss Endss.
           */
          ol.geom.MultiPolygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, endss) {
              this.setFlatCoordinatesInternal(layout, flatCoordinates);
              this.endss_ = endss;
              this.changed();
          };
  
  
          /**
           * @param {Array.<ol.geom.Polygon>} polygons Polygons.
           */
          ol.geom.MultiPolygon.prototype.setPolygons = function(polygons) {
              var layout = this.getLayout();
              var flatCoordinates = [];
              var endss = [];
              var i, ii, ends;
              for (i = 0, ii = polygons.length; i < ii; ++i) {
                  var polygon = polygons[i];
                  if (i === 0) {
                      layout = polygon.getLayout();
                  }
                  var offset = flatCoordinates.length;
                  ends = polygon.getEnds();
                  var j, jj;
                  for (j = 0, jj = ends.length; j < jj; ++j) {
                      ends[j] += offset;
                  }
                  ol.array.extend(flatCoordinates, polygon.getFlatCoordinates());
                  endss.push(ends);
              }
              this.setFlatCoordinates(layout, flatCoordinates, endss);
          };
  
  
          ol.Feature = function(opt_geometryOrProperties) {
  
              ol.Object.call(this);
  
              /**
               * @private
               * @type {number|string|undefined}
               */
              this.id_ = undefined;
  
              /**
               * @type {string}
               * @private
               */
              this.geometryName_ = 'geometry';
  
              /**
               * User provided style.
               * @private
               * @type {ol.style.Style|Array.<ol.style.Style>|
         *     ol.FeatureStyleFunction}
               */
              this.style_ = null;
  
              /**
               * @private
               * @type {ol.FeatureStyleFunction|undefined}
               */
              this.styleFunction_ = undefined;
  
              /**
               * @private
               * @type {?ol.EventsKey}
               */
              this.geometryChangeKey_ = null;
  
      //        ol.events.listen(
      //            this, ol.Object.getChangeEventType(this.geometryName_),
      //            this.handleGeometryChanged_, this);
  
              if (opt_geometryOrProperties !== undefined) {
                  if (opt_geometryOrProperties instanceof ol.geom.Geometry ||
                      !opt_geometryOrProperties) {
                      var geometry = opt_geometryOrProperties;
                      this.setGeometry(geometry);
                  } else {
                      /** @type {Object.<string, *>} */
                      var properties = opt_geometryOrProperties;
                      this.setProperties(properties);
                  }
              }
          };
          ol.inherits(ol.Feature, ol.Object);
  
  
          /**
           * Clone this feature. If the original feature has a geometry it
           * is also cloned. The feature id is not set in the clone.
           * @return {ol.Feature} The clone.
           * @api
           */
          ol.Feature.prototype.clone = function() {
              var clone = new ol.Feature(this.getProperties());
              clone.setGeometryName(this.getGeometryName());
              var geometry = this.getGeometry();
              if (geometry) {
                  clone.setGeometry(geometry.clone());
              }
              var style = this.getStyle();
              if (style) {
                  clone.setStyle(style);
              }
              return clone;
          };
  
  
          /**
           * Get the feature's default geometry.  A feature may have any number of named
           * geometries.  The "default" geometry (the one that is rendered by default) is
           * set when calling {@link ol.Feature#setGeometry}.
           * @return {ol.geom.Geometry|undefined} The default geometry for the feature.
           * @api
           * @observable
           */
          ol.Feature.prototype.getGeometry = function() {
              return /** @type {ol.geom.Geometry|undefined} */ (
                  this.get(this.geometryName_));
          };
  
  
          /**
           * Get the feature identifier.  This is a stable identifier for the feature and
           * is either set when reading data from a remote source or set explicitly by
           * calling {@link ol.Feature#setId}.
           * @return {number|string|undefined} Id.
           * @api
           */
          ol.Feature.prototype.getId = function() {
              return this.id_;
          };
  
  
          /**
           * Get the name of the feature's default geometry.  By default, the default
           * geometry is named `geometry`.
           * @return {string} Get the property name associated with the default geometry
           *     for this feature.
           * @api
           */
          ol.Feature.prototype.getGeometryName = function() {
              return this.geometryName_;
          };
  
  
          /**
           * Get the feature's style. Will return what was provided to the
           * {@link ol.Feature#setStyle} method.
           * @return {ol.style.Style|Array.<ol.style.Style>|
       *     ol.FeatureStyleFunction|ol.StyleFunction} The feature style.
           * @api
           */
          ol.Feature.prototype.getStyle = function() {
              return this.style_;
          };
  
  
          /**
           * Get the feature's style function.
           * @return {ol.FeatureStyleFunction|undefined} Return a function
           * representing the current style of this feature.
           * @api
           */
          ol.Feature.prototype.getStyleFunction = function() {
              return this.styleFunction_;
          };
  
  
          /**
           * @private
           */
          ol.Feature.prototype.handleGeometryChange_ = function() {
              this.changed();
          };
  
  
          /**
           * @private
           */
          ol.Feature.prototype.handleGeometryChanged_ = function() {
              if (this.geometryChangeKey_) {
                  ol.events.unlistenByKey(this.geometryChangeKey_);
                  this.geometryChangeKey_ = null;
              }
              var geometry = this.getGeometry();
              if (geometry) {
                  this.geometryChangeKey_ = ol.events.listen(geometry,
                      ol.events.EventType.CHANGE, this.handleGeometryChange_, this);
              }
              this.changed();
          };
  
  
          /**
           * Set the default geometry for the feature.  This will update the property
           * with the name returned by {@link ol.Feature#getGeometryName}.
           * @param {ol.geom.Geometry|undefined} geometry The new geometry.
           * @api
           * @observable
           */
          ol.Feature.prototype.setGeometry = function(geometry) {
              this.set(this.geometryName_, geometry);
          };
  
  
          /**
           * Set the style for the feature.  This can be a single style object, an array
           * of styles, or a function that takes a resolution and returns an array of
           * styles. If it is `null` the feature has no style (a `null` style).
           * @param {ol.style.Style|Array.<ol.style.Style>|
       *     ol.FeatureStyleFunction|ol.StyleFunction} style Style for this feature.
           * @api
           * @fires ol.events.Event#event:change
           */
          ol.Feature.prototype.setStyle = function(style) {
              this.style_ = style;
              this.styleFunction_ = !style ?
                  undefined : ol.Feature.createStyleFunction(style);
              this.changed();
          };
  
  
          /**
           * Set the feature id.  The feature id is considered stable and may be used when
           * requesting features or comparing identifiers returned from a remote source.
           * The feature id can be used with the {@link ol.source.Vector#getFeatureById}
           * method.
           * @param {number|string|undefined} id The feature id.
           * @api
           * @fires ol.events.Event#event:change
           */
          ol.Feature.prototype.setId = function(id) {
              this.id_ = id;
              this.changed();
          };
  
  
          /**
           * Set the property name to be used when getting the feature's default geometry.
           * When calling {@link ol.Feature#getGeometry}, the value of the property with
           * this name will be returned.
           * @param {string} name The property name of the default geometry.
           * @api
           */
          ol.Feature.prototype.setGeometryName = function(name) {
              ol.events.unlisten(
                  this, ol.Object.getChangeEventType(this.geometryName_),
                  this.handleGeometryChanged_, this);
              this.geometryName_ = name;
              ol.events.listen(
                  this, ol.Object.getChangeEventType(this.geometryName_),
                  this.handleGeometryChanged_, this);
              this.handleGeometryChanged_();
          };
  
  
          /**
           * Convert the provided object into a feature style function.  Functions passed
           * through unchanged.  Arrays of ol.style.Style or single style objects wrapped
           * in a new feature style function.
           * @param {ol.FeatureStyleFunction|!Array.<ol.style.Style>|!ol.style.Style} obj
           *     A feature style function, a single style, or an array of styles.
           * @return {ol.FeatureStyleFunction} A style function.
           */
          ol.Feature.createStyleFunction = function(obj) {
              var styleFunction;
  
              if (typeof obj === 'function') {
                  if (obj.length == 2) {
                      styleFunction = function(resolution) {
                          return /** @type {ol.StyleFunction} */ (obj)(this, resolution);
                      };
                  } else {
                      styleFunction = obj;
                  }
              } else {
                  /**
                   * @type {Array.<ol.style.Style>}
                   */
                  var styles;
                  if (Array.isArray(obj)) {
                      styles = obj;
                  } else {
                      ol.asserts.assert(obj instanceof ol.style.Style,
                          41); // Expected an `ol.style.Style` or an array of `ol.style.Style`
                      styles = [obj];
                  }
                  styleFunction = function() {
                      return styles;
                  };
              }
              return styleFunction;
          };
  
          ol.format = {};
  
          ol.format.Feature = function() {
  
              /**
               * @protected
               * @type {ol.proj.Projection}
               */
              this.defaultDataProjection = null;
  
              /**
               * @protected
               * @type {ol.proj.Projection}
               */
              this.defaultFeatureProjection = null;
  
          };
  
  
          /**
           * Adds the data projection to the read options.
           * @param {Document|Node|Object|string} source Source.
           * @param {olx.format.ReadOptions=} opt_options Options.
           * @return {olx.format.ReadOptions|undefined} Options.
           * @protected
           */
          ol.format.Feature.prototype.getReadOptions = function(source, opt_options) {
              var options;
              if (opt_options) {
                  options = {
                      dataProjection: opt_options.dataProjection ?
                          opt_options.dataProjection : this.readProjection(source),
                      featureProjection: opt_options.featureProjection
                  };
              }
              return this.adaptOptions(options);
          };
  
  
          /**
           * Sets the `defaultDataProjection` on the options, if no `dataProjection`
           * is set.
           * @param {olx.format.WriteOptions|olx.format.ReadOptions|undefined} options
           *     Options.
           * @protected
           * @return {olx.format.WriteOptions|olx.format.ReadOptions|undefined}
           *     Updated options.
           */
          ol.format.Feature.prototype.adaptOptions = function(options) {
              return ol.obj.assign({
                  dataProjection: this.defaultDataProjection,
                  featureProjection: this.defaultFeatureProjection
              }, options);
          };
  
  
          /**
           * Get the extent from the source of the last {@link readFeatures} call.
           * @return {ol.Extent} Tile extent.
           */
          ol.format.Feature.prototype.getLastExtent = function() {
              return null;
          };
  
  
          /**
           * @abstract
           * @return {ol.format.FormatType} Format.
           */
          ol.format.Feature.prototype.getType = function() {};
  
  
          /**
           * Read a single feature from a source.
           *
           * @abstract
           * @param {Document|Node|Object|string} source Source.
           * @param {olx.format.ReadOptions=} opt_options Read options.
           * @return {ol.Feature} Feature.
           */
          ol.format.Feature.prototype.readFeature = function(source, opt_options) {};
  
  
          /**
           * Read all features from a source.
           *
           * @abstract
           * @param {Document|Node|ArrayBuffer|Object|string} source Source.
           * @param {olx.format.ReadOptions=} opt_options Read options.
           * @return {Array.<ol.Feature>} Features.
           */
          ol.format.Feature.prototype.readFeatures = function(source, opt_options) {};
  
  
          /**
           * Read a single geometry from a source.
           *
           * @abstract
           * @param {Document|Node|Object|string} source Source.
           * @param {olx.format.ReadOptions=} opt_options Read options.
           * @return {ol.geom.Geometry} Geometry.
           */
          ol.format.Feature.prototype.readGeometry = function(source, opt_options) {};
  
  
          /**
           * Read the projection from a source.
           *
           * @abstract
           * @param {Document|Node|Object|string} source Source.
           * @return {ol.proj.Projection} Projection.
           */
          ol.format.Feature.prototype.readProjection = function(source) {};
  
  
          /**
           * Encode a feature in this format.
           *
           * @abstract
           * @param {ol.Feature} feature Feature.
           * @param {olx.format.WriteOptions=} opt_options Write options.
           * @return {string} Result.
           */
          ol.format.Feature.prototype.writeFeature = function(feature, opt_options) {};
  
  
          /**
           * Encode an array of features in this format.
           *
           * @abstract
           * @param {Array.<ol.Feature>} features Features.
           * @param {olx.format.WriteOptions=} opt_options Write options.
           * @return {string} Result.
           */
          ol.format.Feature.prototype.writeFeatures = function(features, opt_options) {};
  
  
          /**
           * Write a single geometry in this format.
           *
           * @abstract
           * @param {ol.geom.Geometry} geometry Geometry.
           * @param {olx.format.WriteOptions=} opt_options Write options.
           * @return {string} Result.
           */
          ol.format.Feature.prototype.writeGeometry = function(geometry, opt_options) {};
  
  
          /**
           * @param {ol.geom.Geometry|ol.Extent} geometry Geometry.
           * @param {boolean} write Set to true for writing, false for reading.
           * @param {(olx.format.WriteOptions|olx.format.ReadOptions)=} opt_options
           *     Options.
           * @return {ol.geom.Geometry|ol.Extent} Transformed geometry.
           * @protected
           */
          ol.format.Feature.transformWithOptions = function(
              geometry, write, opt_options) {
  
              /**
               * @type {ol.geom.Geometry|ol.Extent}
               */
              var transformed;
              {
                  transformed = geometry;
              }
              if (write && opt_options && opt_options.decimals !== undefined) {
                  var power = Math.pow(10, opt_options.decimals);
                  // if decimals option on write, round each coordinate appropriately
                  /**
                   * @param {Array.<number>} coordinates Coordinates.
                   * @return {Array.<number>} Transformed coordinates.
                   */
                  var transform = function(coordinates) {
                      for (var i = 0, ii = coordinates.length; i < ii; ++i) {
                          coordinates[i] = Math.round(coordinates[i] * power) / power;
                      }
                      return coordinates;
                  };
                  if (transformed === geometry) {
                      transformed = transformed.clone();
                  }
                  transformed.applyTransform(transform);
              }
              return transformed;
          };
  
  
          ol.format.MVT = function(opt_options) {
  
              ol.format.Feature.call(this);
  
              var options = opt_options ? opt_options : {};
  
              /**
               * @type {ol.proj.Projection}
               */
      //        this.defaultDataProjection = new ol.proj.Projection({
      //            code: 'EPSG:3857',
      //            units: ol.proj.Units.TILE_PIXELS
      //        });
  
              /**
               * @private
               * @type {function((ol.geom.Geometry|Object.<string,*>)=)|
         *     function(ol.geom.GeometryType,Array.<number>,
         *         (Array.<number>|Array.<Array.<number>>),Object.<string,*>,number)}
               */
              this.featureClass_ = options.featureClass ?
                  options.featureClass : ol.render.Feature;
  
              /**
               * @private
               * @type {string|undefined}
               */
              this.geometryName_ = options.geometryName;
  
              /**
               * @private
               * @type {string}
               */
              this.layerName_ = options.layerName ? options.layerName : 'layer';
  
              /**
               * @private
               * @type {Array.<string>}
               */
              this.layers_ = options.layers ? options.layers : null;
  
              /**
               * @private
               * @type {ol.Extent}
               */
              this.extent_ = null;
  
          };
          ol.inherits(ol.format.MVT, ol.format.Feature);
  
          ol.format.MVT.pbfReaders_ = {
              layers: function (tag, layers, pbf) {
                  if (tag === 3) {
                      var layer = {
                          keys: [],
                          values: [],
                          features: []
                      };
                      var end = pbf.readVarint() + pbf.pos;
                      pbf.readFields(ol.format.MVT.pbfReaders_.layer, layer, end);
                      layer.length = layer.features.length;
                      if (layer.length) {
                          layers[layer.name] = layer;
                      }
                  }
              },
              layer: function (tag, layer, pbf) {
                  if (tag === 15) {
                      layer.version = pbf.readVarint();
                  } else if (tag === 1) {
                      layer.name = pbf.readString();
                  } else if (tag === 5) {
                      layer.extent = pbf.readVarint();
                  } else if (tag === 2) {
                      layer.features.push(pbf.pos);
                  } else if (tag === 3) {
                      layer.keys.push(pbf.readString());
                  } else if (tag === 4) {
                      var value = null;
                      var end = pbf.readVarint() + pbf.pos;
                      while (pbf.pos < end) {
                          tag = pbf.readVarint() >> 3;
                          value = tag === 1 ? pbf.readString() :
                              tag === 2 ? pbf.readFloat() :
                                  tag === 3 ? pbf.readDouble() :
                                      tag === 4 ? pbf.readVarint64() :
                                          tag === 5 ? pbf.readVarint() :
                                              tag === 6 ? pbf.readSVarint() :
                                                  tag === 7 ? pbf.readBoolean() : null;
                      }
                      layer.values.push(value);
                  }
              },
              feature: function (tag, feature, pbf) {
                  if (tag == 1) {
                      feature.id = pbf.readVarint();
                  } else if (tag == 2) {
                      var end = pbf.readVarint() + pbf.pos;
                      while (pbf.pos < end) {
                          var key = feature.layer.keys[pbf.readVarint()];
                          var value = feature.layer.values[pbf.readVarint()];
                          feature.properties[key] = value;
                      }
                  } else if (tag == 3) {
                      feature.type = pbf.readVarint();
                  } else if (tag == 4) {
                      feature.geometry = pbf.pos;
                  }
              }
          };
  
          /**
           * Read a raw feature from the pbf offset stored at index `i` in the raw layer.
           * @suppress {missingProperties}
           * @private
           * @param {ol.ext.PBF} pbf PBF.
           * @param {Object} layer Raw layer.
           * @param {number} i Index of the feature in the raw layer's `features` array.
           * @return {Object} Raw feature.
           */
          ol.format.MVT.readRawFeature_ = function (pbf, layer, i) {
              pbf.pos = layer.features[i];
              var end = pbf.readVarint() + pbf.pos;
  
              var feature = {
                  layer: layer,
                  type: 0,
                  properties: {}
              };
              pbf.readFields(ol.format.MVT.pbfReaders_.feature, feature, end);
              return feature;
          };
  
          /**
           * Read the raw geometry from the pbf offset stored in a raw feature's geometry
           * proeprty.
           * @suppress {missingProperties}
           * @private
           * @param {ol.ext.PBF} pbf PBF.
           * @param {Object} feature Raw feature.
           * @param {Array.<number>} flatCoordinates Array to store flat coordinates in.
           * @param {Array.<number>} ends Array to store ends in.
           */
          ol.format.MVT.readRawGeometry_ = function (pbf, feature, flatCoordinates, ends) {
              pbf.pos = feature.geometry;
  
              var end = pbf.readVarint() + pbf.pos;
              var cmd = 1;
              var length = 0;
              var x = 0;
              var y = 0;
              var coordsLen = 0;
              var currentEnd = 0;
  
              while (pbf.pos < end) {
                  if (!length) {
                      var cmdLen = pbf.readVarint();
                      cmd = cmdLen & 0x7;
                      length = cmdLen >> 3;
                  }
  
                  length--;
  
                  if (cmd === 1 || cmd === 2) {
                      x += pbf.readSVarint();
                      y += pbf.readSVarint();
  
                      if (cmd === 1) { // moveTo
                          if (coordsLen > currentEnd) {
                              ends.push(coordsLen);
                              currentEnd = coordsLen;
                          }
                      }
  
                      flatCoordinates.push(x, y);
                      coordsLen += 2;
  
                  } else if (cmd === 7) {
  
                      if (coordsLen > currentEnd) {
                          // close polygon
                          flatCoordinates.push(
                              flatCoordinates[currentEnd], flatCoordinates[currentEnd + 1]);
                          coordsLen += 2;
                      }
  
                  } else {
                      ol.asserts.assert(false, 59); // Invalid command found in the PBF
                  }
              }
  
              if (coordsLen > currentEnd) {
                  ends.push(coordsLen);
                  currentEnd = coordsLen;
              }
  
          };
  
          /**
           * @suppress {missingProperties}
           * @private
           * @param {number} type The raw feature's geometry type
           * @param {number} numEnds Number of ends of the flat coordinates of the
           * geometry.
           * @return {ol.geom.GeometryType} The geometry type.
           */
          ol.format.MVT.getGeometryType_ = function (type, numEnds) {
              /** @type {ol.geom.GeometryType} */
              var geometryType;
              if (type === 1) {
                  geometryType = numEnds === 1 ?
                      ol.geom.GeometryType.POINT : ol.geom.GeometryType.MULTI_POINT;
              } else if (type === 2) {
                  geometryType = numEnds === 1 ?
                      ol.geom.GeometryType.LINE_STRING :
                      ol.geom.GeometryType.MULTI_LINE_STRING;
              } else if (type === 3) {
                  geometryType = ol.geom.GeometryType.POLYGON;
                  // MultiPolygon not relevant for rendering - winding order determines
                  // outer rings of polygons.
              }
              return geometryType;
          };
  
          /**
           * @private
           * @param {ol.ext.PBF} pbf PBF
           * @param {Object} rawFeature Raw Mapbox feature.
           * @param {olx.format.ReadOptions=} opt_options Read options.
           * @return {ol.Feature|ol.render.Feature} Feature.
           */
          ol.format.MVT.prototype.createFeature_ = function (pbf, rawFeature, opt_options) {
              var type = rawFeature.type;
              if (type === 0) {
                  return null;
              }
  
              var feature;
              var id = rawFeature.id;
              var values = rawFeature.properties;
              values[this.layerName_] = rawFeature.layer.name;
  
              var flatCoordinates = [];
              var ends = [];
              ol.format.MVT.readRawGeometry_(pbf, rawFeature, flatCoordinates, ends);
  
              var geometryType = ol.format.MVT.getGeometryType_(type, ends.length);
  
              var geom;
              if (geometryType == ol.geom.GeometryType.POLYGON) {
                  var endss = [];
                  var offset = 0;
                  var prevEndIndex = 0;
                  for (var i = 0, ii = ends.length; i < ii; ++i) {
                      var end = ends[i];
                      if (!ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, offset, end, 2)) {
                          endss.push(ends.slice(prevEndIndex, i + 1));
                          prevEndIndex = i + 1;
                      }
                      offset = end;
                  }
                  if (endss.length > 1) {
                      ends = endss;
                      geom = new ol.geom.MultiPolygon(null);
                  } else {
                      geom = new ol.geom.Polygon(null);
                  }
              } else {
                  geom = geometryType === ol.geom.GeometryType.POINT ? new ol.geom.Point(null) :
                      geometryType === ol.geom.GeometryType.LINE_STRING ? new ol.geom.LineString(null) :
                          geometryType === ol.geom.GeometryType.POLYGON ? new ol.geom.Polygon(null) :
                              geometryType === ol.geom.GeometryType.MULTI_POINT ? new ol.geom.MultiPoint(null) :
                                  geometryType === ol.geom.GeometryType.MULTI_LINE_STRING ? new ol.geom.MultiLineString(null) :
                                      null;
              }
              geom.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates, ends);
              feature = new this.featureClass_();
              if (this.geometryName_) {
                  feature.setGeometryName(this.geometryName_);
              }
              var geometry = ol.format.Feature.transformWithOptions(geom, false, this.adaptOptions(opt_options));
              feature.setGeometry(geometry);
              feature.setId(id);
              feature.setProperties(values);
  
              return feature;
          };
  
          ol.format.MVT.prototype.readFeatures = function (source, opt_options) {
              var layers = this.layers_;
  
              var pbf$1 = new pbf.Protobuf(/** @type {ArrayBuffer} */ (source));
              var pbfLayers = pbf$1.readFields(ol.format.MVT.pbfReaders_.layers, {});
              /** @type {Array.<ol.Feature|ol.render.Feature>} */
              var features = [];
              var pbfLayer;
              for (var name in pbfLayers) {
                  if (layers && layers.indexOf(name) == -1) {
                      continue;
                  }
  
                  if (opt_options !== undefined) {
                      var needSourceLayerNames = opt_options.needSourceLayerNames;
                      if (needSourceLayerNames !== undefined && needSourceLayerNames[name] === undefined) {
                          continue;
                      }
                  }
  
                  pbfLayer = pbfLayers[name];
  
                  var rawFeature;
                  for (var i = 0, ii = pbfLayer.length; i < ii; ++i) {
                      rawFeature = ol.format.MVT.readRawFeature_(pbf$1, pbfLayer, i);
                      features.push(this.createFeature_(pbf$1, rawFeature));
                  }
                  this.extent_ = pbfLayer ? [0, 0, pbfLayer.extent, pbfLayer.extent] : null;
              }
  
              return features;
          };
  
          ol.style = {};
  
          /**
           * Singleton class. Available through {@link ol.style.iconImageCache}.
           * @constructor
           */
          ol.style.IconImageCache = function() {
              this.cache_ = {};
              this.cacheSize_ = 0;
              this.maxCacheSize_ = 32;
          };
  
          ol.style.IconImageCache.getKey = function(src, crossOrigin, color) {
              var colorString = color ? ol.color.asString(color) : 'null';
              return crossOrigin + ':' + src + ':' + colorString;
          };
  
  
          /**
           * FIXME empty description for jsdoc
           */
          ol.style.IconImageCache.prototype.clear = function() {
              this.cache_ = {};
              this.cacheSize_ = 0;
          };
  
  
          /**
           * FIXME empty description for jsdoc
           */
          ol.style.IconImageCache.prototype.expire = function() {
              if (this.cacheSize_ > this.maxCacheSize_) {
                  var i = 0;
                  var key, iconImage;
                  for (key in this.cache_) {
                      iconImage = this.cache_[key];
                      if ((i++ & 3) === 0 && !iconImage.hasListener()) {
                          delete this.cache_[key];
                          --this.cacheSize_;
                      }
                  }
              }
          };
  
          ol.style.IconImageCache.prototype.get = function(src, crossOrigin, color) {
              var key = ol.style.IconImageCache.getKey(src, crossOrigin, color);
              return key in this.cache_ ? this.cache_[key] : null;
          };
  
          ol.style.IconImageCache.prototype.set = function(src, crossOrigin, color, iconImage) {
              var key = ol.style.IconImageCache.getKey(src, crossOrigin, color);
              this.cache_[key] = iconImage;
              ++this.cacheSize_;
          };
  
          ol.style.IconImageCache.prototype.setSize = function(maxCacheSize) {
              this.maxCacheSize_ = maxCacheSize;
              this.expire();
          };
  
          /**
           * The {@link ol.style.IconImageCache} for {@link ol.style.Icon} images.
           * @api
           */
          ol.style.iconImageCache = new ol.style.IconImageCache();
  
          ol.style.Image = function(options) {
  
              /**
               * @private
               * @type {number}
               */
              this.opacity_ = options.opacity;
  
              /**
               * @private
               * @type {boolean}
               */
              this.rotateWithView_ = options.rotateWithView;
  
              /**
               * @private
               * @type {number}
               */
              this.rotation_ = options.rotation;
  
              /**
               * @private
               * @type {number}
               */
              this.scale_ = options.scale;
  
              /**
               * @private
               * @type {boolean}
               */
              this.snapToPixel_ = options.snapToPixel;
  
          };
  
          ol.style.Image.prototype.getOpacity = function() {
              return this.opacity_;
          };
  
          ol.style.Image.prototype.getRotateWithView = function() {
              return this.rotateWithView_;
          };
  
          ol.style.Image.prototype.getRotation = function() {
              return this.rotation_;
          };
  
          ol.style.Image.prototype.getScale = function() {
              return this.scale_;
          };
  
          ol.style.Image.prototype.getSnapToPixel = function() {
              return this.snapToPixel_;
          };
  
          ol.style.Image.prototype.getAnchor = function() {};
  
          ol.style.Image.prototype.getImage = function(pixelRatio) {};
  
          ol.style.Image.prototype.getHitDetectionImage = function(pixelRatio) {};
  
          ol.style.Image.prototype.getImageState = function() {};
  
          ol.style.Image.prototype.getImageSize = function() {};
  
          ol.style.Image.prototype.getHitDetectionImageSize = function() {};
  
          ol.style.Image.prototype.getOrigin = function() {};
  
          ol.style.Image.prototype.getSize = function() {};
  
          ol.style.Image.prototype.setOpacity = function(opacity) {
              this.opacity_ = opacity;
          };
  
          ol.style.Image.prototype.setRotateWithView = function(rotateWithView) {
              this.rotateWithView_ = rotateWithView;
          };
  
          ol.style.Image.prototype.setRotation = function(rotation) {
              this.rotation_ = rotation;
          };
  
          ol.style.Image.prototype.setScale = function(scale) {
              this.scale_ = scale;
          };
  
          ol.style.Image.prototype.setSnapToPixel = function(snapToPixel) {
              this.snapToPixel_ = snapToPixel;
          };
  
          ol.style.Image.prototype.listenImageChange = function(listener, thisArg) {};
  
          ol.style.Image.prototype.load = function() {};
  
          ol.style.Image.prototype.unlistenImageChange = function(listener, thisArg) {};
  
          /**
           * @classdesc
           * Set regular shape style for vector features. The resulting shape will be
           * a regular polygon when `radius` is provided, or a star when `radius1` and
           * `radius2` are provided.
           *
           * @constructor
           * @param {olx.style.RegularShapeOptions} options Options.
           * @extends {ol.style.Image}
           * @api
           */
          ol.style.RegularShape = function(options) {
              /**
               * @private
               * @type {Array.<string>}
               */
              this.checksums_ = null;
  
              /**
               * @private
               * @type {HTMLCanvasElement}
               */
              this.canvas_ = null;
  
              /**
               * @private
               * @type {HTMLCanvasElement}
               */
              this.hitDetectionCanvas_ = null;
  
              /**
               * @private
               * @type {ol.style.Fill}
               */
              this.fill_ = options.fill !== undefined ? options.fill : null;
  
              /**
               * @private
               * @type {Array.<number>}
               */
              this.origin_ = [0, 0];
  
              /**
               * @private
               * @type {number}
               */
              this.points_ = options.points;
  
              /**
               * @protected
               * @type {number}
               */
              this.radius_ = /** @type {number} */ (options.radius !== undefined ?
                  options.radius : options.radius1);
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.radius2_ = options.radius2;
  
              /**
               * @private
               * @type {number}
               */
              this.angle_ = options.angle !== undefined ? options.angle : 0;
  
              /**
               * @private
               * @type {ol.style.Stroke}
               */
              this.stroke_ = options.stroke !== undefined ? options.stroke : null;
  
              /**
               * @private
               * @type {Array.<number>}
               */
              this.anchor_ = null;
  
              /**
               * @private
               * @type {ol.Size}
               */
              this.size_ = null;
  
              /**
               * @private
               * @type {ol.Size}
               */
              this.imageSize_ = null;
  
              /**
               * @private
               * @type {ol.Size}
               */
              this.hitDetectionImageSize_ = null;
  
              /**
               * @protected
               * @type {ol.style.AtlasManager|undefined}
               */
              this.atlasManager_ = options.atlasManager;
  
              this.render_(this.atlasManager_);
  
              /**
               * @type {boolean}
               */
              var snapToPixel = options.snapToPixel !== undefined ?
                  options.snapToPixel : true;
  
              /**
               * @type {boolean}
               */
              var rotateWithView = options.rotateWithView !== undefined ?
                  options.rotateWithView : false;
  
              ol.style.Image.call(this, {
                  opacity: 1,
                  rotateWithView: rotateWithView,
                  rotation: options.rotation !== undefined ? options.rotation : 0,
                  scale: 1,
                  snapToPixel: snapToPixel
              });
          };
          ol.inherits(ol.style.RegularShape, ol.style.Image);
  
          ol.style.RegularShape.prototype.clone = function() {
              var style = new ol.style.RegularShape({
                  fill: this.getFill() ? this.getFill().clone() : undefined,
                  points: this.getPoints(),
                  radius: this.getRadius(),
                  radius2: this.getRadius2(),
                  angle: this.getAngle(),
                  snapToPixel: this.getSnapToPixel(),
                  stroke: this.getStroke() ?  this.getStroke().clone() : undefined,
                  rotation: this.getRotation(),
                  rotateWithView: this.getRotateWithView(),
                  atlasManager: this.atlasManager_
              });
              style.setOpacity(this.getOpacity());
              style.setScale(this.getScale());
              return style;
          };
  
          /**
           * @inheritDoc
           * @api
           */
          ol.style.RegularShape.prototype.getAnchor = function() {
              return this.anchor_;
          };
  
  
          /**
           * Get the angle used in generating the shape.
           * @return {number} Shape's rotation in radians.
           * @api
           */
          ol.style.RegularShape.prototype.getAngle = function() {
              return this.angle_;
          };
  
          /**
           * Get the fill style for the shape.
           * @return {ol.style.Fill} Fill style.
           * @api
           */
          ol.style.RegularShape.prototype.getFill = function() {
              return this.fill_;
          };
  
          /**
           * @inheritDoc
           */
          ol.style.RegularShape.prototype.getHitDetectionImage = function(pixelRatio) {
              return this.hitDetectionCanvas_;
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.style.RegularShape.prototype.getImage = function(pixelRatio) {
              return this.canvas_;
          };
  
          /**
           * @inheritDoc
           */
          ol.style.RegularShape.prototype.getImageSize = function() {
              return this.imageSize_;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.style.RegularShape.prototype.getHitDetectionImageSize = function() {
              return this.hitDetectionImageSize_;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.style.RegularShape.prototype.getImageState = function() {
              return ol.ImageState.LOADED;
          };
  
          /**
           * @inheritDoc
           * @api
           */
          ol.style.RegularShape.prototype.getOrigin = function() {
              return this.origin_;
          };
  
  
          /**
           * Get the number of points for generating the shape.
           * @return {number} Number of points for stars and regular polygons.
           * @api
           */
          ol.style.RegularShape.prototype.getPoints = function() {
              return this.points_;
          };
  
  
          /**
           * Get the (primary) radius for the shape.
           * @return {number} Radius.
           * @api
           */
          ol.style.RegularShape.prototype.getRadius = function() {
              return this.radius_;
          };
  
  
          /**
           * Get the secondary radius for the shape.
           * @return {number|undefined} Radius2.
           * @api
           */
          ol.style.RegularShape.prototype.getRadius2 = function() {
              return this.radius2_;
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.style.RegularShape.prototype.getSize = function() {
              return this.size_;
          };
  
  
          /**
           * Get the stroke style for the shape.
           * @return {ol.style.Stroke} Stroke style.
           * @api
           */
          ol.style.RegularShape.prototype.getStroke = function() {
              return this.stroke_;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.style.RegularShape.prototype.listenImageChange = function(listener, thisArg) {};
  
  
          /**
           * @inheritDoc
           */
          ol.style.RegularShape.prototype.load = function() {};
  
  
          /**
           * @inheritDoc
           */
          ol.style.RegularShape.prototype.unlistenImageChange = function(listener, thisArg) {};
  
  
          /**
           * @protected
           * @param {ol.style.AtlasManager|undefined} atlasManager An atlas manager.
           */
          ol.style.RegularShape.prototype.render_ = function(atlasManager) {
              var imageSize;
              var lineCap = '';
              var lineJoin = '';
              var miterLimit = 0;
              var lineDash = null;
              var lineDashOffset = 0;
              var strokeStyle;
              var strokeWidth = 0;
  
              if (this.stroke_) {
                  strokeStyle = this.stroke_.getColor();
                  if (strokeStyle === null) {
                      strokeStyle = ol.render.canvas.defaultStrokeStyle;
                  }
                  strokeStyle = ol.colorlike.asColorLike(strokeStyle);
                  strokeWidth = this.stroke_.getWidth();
                  if (strokeWidth === undefined) {
                      strokeWidth = ol.render.canvas.defaultLineWidth;
                  }
                  lineDash = this.stroke_.getLineDash();
                  lineDashOffset = this.stroke_.getLineDashOffset();
                  if (!ol.has.CANVAS_LINE_DASH) {
                      lineDash = null;
                      lineDashOffset = 0;
                  }
                  lineJoin = this.stroke_.getLineJoin();
                  if (lineJoin === undefined) {
                      lineJoin = ol.render.canvas.defaultLineJoin;
                  }
                  lineCap = this.stroke_.getLineCap();
                  if (lineCap === undefined) {
                      lineCap = ol.render.canvas.defaultLineCap;
                  }
                  miterLimit = this.stroke_.getMiterLimit();
                  if (miterLimit === undefined) {
                      miterLimit = ol.render.canvas.defaultMiterLimit;
                  }
              }
  
              var size = 2 * (this.radius_ + strokeWidth) + 1;
  
              /** @type {ol.RegularShapeRenderOptions} */
              var renderOptions = {
                  strokeStyle: strokeStyle,
                  strokeWidth: strokeWidth,
                  size: size,
                  lineCap: lineCap,
                  lineDash: lineDash,
                  lineDashOffset: lineDashOffset,
                  lineJoin: lineJoin,
                  miterLimit: miterLimit
              };
  
              if (atlasManager === undefined) {
                  // no atlas manager is used, create a new canvas
                  var context = ol.dom.createCanvasContext2D(size, size);
                  this.canvas_ = context.canvas;
  
                  // canvas.width and height are rounded to the closest integer
                  size = this.canvas_.width;
                  imageSize = size;
  
                  this.draw_(renderOptions, context, 0, 0);
  
                  this.createHitDetectionCanvas_(renderOptions);
              } else {
                  // an atlas manager is used, add the symbol to an atlas
                  size = Math.round(size);
  
                  var hasCustomHitDetectionImage = !this.fill_;
                  var renderHitDetectionCallback;
                  if (hasCustomHitDetectionImage) {
                      // render the hit-detection image into a separate atlas image
                      renderHitDetectionCallback =
                          this.drawHitDetectionCanvas_.bind(this, renderOptions);
                  }
  
                  var id = this.getChecksum();
                  var info = atlasManager.add(
                      id, size, size, this.draw_.bind(this, renderOptions),
                      renderHitDetectionCallback);
  
                  this.canvas_ = info.image;
                  this.origin_ = [info.offsetX, info.offsetY];
                  imageSize = info.image.width;
  
                  if (hasCustomHitDetectionImage) {
                      this.hitDetectionCanvas_ = info.hitImage;
                      this.hitDetectionImageSize_ =
                          [info.hitImage.width, info.hitImage.height];
                  } else {
                      this.hitDetectionCanvas_ = this.canvas_;
                      this.hitDetectionImageSize_ = [imageSize, imageSize];
                  }
              }
  
              this.anchor_ = [size / 2, size / 2];
              this.size_ = [size, size];
              this.imageSize_ = [imageSize, imageSize];
          };
  
  
          /**
           * @private
           * @param {ol.RegularShapeRenderOptions} renderOptions Render options.
           * @param {CanvasRenderingContext2D} context The rendering context.
           * @param {number} x The origin for the symbol (x).
           * @param {number} y The origin for the symbol (y).
           */
          ol.style.RegularShape.prototype.draw_ = function(renderOptions, context, x, y) {
              var i, angle0, radiusC;
              // reset transform
              context.setTransform(1, 0, 0, 1, 0, 0);
  
              // then move to (x, y)
              context.translate(x, y);
  
              context.beginPath();
  
              var points = this.points_;
              if (points === Infinity) {
                  context.arc(
                      renderOptions.size / 2, renderOptions.size / 2,
                      this.radius_, 0, 2 * Math.PI, true);
              } else {
                  var radius2 = (this.radius2_ !== undefined) ? this.radius2_
                      : this.radius_;
                  if (radius2 !== this.radius_) {
                      points = 2 * points;
                  }
                  for (i = 0; i <= points; i++) {
                      angle0 = i * 2 * Math.PI / points - Math.PI / 2 + this.angle_;
                      radiusC = i % 2 === 0 ? this.radius_ : radius2;
                      context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0),
                          renderOptions.size / 2 + radiusC * Math.sin(angle0));
                  }
              }
  
  
              if (this.fill_) {
                  var color = this.fill_.getColor();
                  if (color === null) {
                      color = ol.render.canvas.defaultFillStyle;
                  }
                  context.fillStyle = ol.colorlike.asColorLike(color);
                  context.fill();
              }
              if (this.stroke_) {
                  context.strokeStyle = renderOptions.strokeStyle;
                  context.lineWidth = renderOptions.strokeWidth;
                  if (renderOptions.lineDash) {
                      context.setLineDash(renderOptions.lineDash);
                      context.lineDashOffset = renderOptions.lineDashOffset;
                  }
                  context.lineCap = renderOptions.lineCap;
                  context.lineJoin = renderOptions.lineJoin;
                  context.miterLimit = renderOptions.miterLimit;
                  context.stroke();
              }
              context.closePath();
          };
  
  
          /**
           * @private
           * @param {ol.RegularShapeRenderOptions} renderOptions Render options.
           */
          ol.style.RegularShape.prototype.createHitDetectionCanvas_ = function(renderOptions) {
              this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
              if (this.fill_) {
                  this.hitDetectionCanvas_ = this.canvas_;
                  return;
              }
  
              // if no fill style is set, create an extra hit-detection image with a
              // default fill style
              var context = ol.dom.createCanvasContext2D(renderOptions.size, renderOptions.size);
              this.hitDetectionCanvas_ = context.canvas;
  
              this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
          };
  
  
          /**
           * @private
           * @param {ol.RegularShapeRenderOptions} renderOptions Render options.
           * @param {CanvasRenderingContext2D} context The context.
           * @param {number} x The origin for the symbol (x).
           * @param {number} y The origin for the symbol (y).
           */
          ol.style.RegularShape.prototype.drawHitDetectionCanvas_ = function(renderOptions, context, x, y) {
              // reset transform
              context.setTransform(1, 0, 0, 1, 0, 0);
  
              // then move to (x, y)
              context.translate(x, y);
  
              context.beginPath();
  
              var points = this.points_;
              if (points === Infinity) {
                  context.arc(
                      renderOptions.size / 2, renderOptions.size / 2,
                      this.radius_, 0, 2 * Math.PI, true);
              } else {
                  var radius2 = (this.radius2_ !== undefined) ? this.radius2_
                      : this.radius_;
                  if (radius2 !== this.radius_) {
                      points = 2 * points;
                  }
                  var i, radiusC, angle0;
                  for (i = 0; i <= points; i++) {
                      angle0 = i * 2 * Math.PI / points - Math.PI / 2 + this.angle_;
                      radiusC = i % 2 === 0 ? this.radius_ : radius2;
                      context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0),
                          renderOptions.size / 2 + radiusC * Math.sin(angle0));
                  }
              }
  
              context.fillStyle = ol.render.canvas.defaultFillStyle;
              context.fill();
              if (this.stroke_) {
                  context.strokeStyle = renderOptions.strokeStyle;
                  context.lineWidth = renderOptions.strokeWidth;
                  if (renderOptions.lineDash) {
                      context.setLineDash(renderOptions.lineDash);
                      context.lineDashOffset = renderOptions.lineDashOffset;
                  }
                  context.stroke();
              }
              context.closePath();
          };
  
  
          /**
           * @return {string} The checksum.
           */
          ol.style.RegularShape.prototype.getChecksum = function() {
              var strokeChecksum = this.stroke_ ?
                  this.stroke_.getChecksum() : '-';
              var fillChecksum = this.fill_ ?
                  this.fill_.getChecksum() : '-';
  
              var recalculate = !this.checksums_ ||
                  (strokeChecksum != this.checksums_[1] ||
                      fillChecksum != this.checksums_[2] ||
                      this.radius_ != this.checksums_[3] ||
                      this.radius2_ != this.checksums_[4] ||
                      this.angle_ != this.checksums_[5] ||
                      this.points_ != this.checksums_[6]);
  
              if (recalculate) {
                  var checksum = 'r' + strokeChecksum + fillChecksum +
                      (this.radius_ !== undefined ? this.radius_.toString() : '-') +
                      (this.radius2_ !== undefined ? this.radius2_.toString() : '-') +
                      (this.angle_ !== undefined ? this.angle_.toString() : '-') +
                      (this.points_ !== undefined ? this.points_.toString() : '-');
                  this.checksums_ = [checksum, strokeChecksum, fillChecksum,
                      this.radius_, this.radius2_, this.angle_, this.points_];
              }
  
              return this.checksums_[0];
          };
  
          /**
           * @classdesc
           * Set circle style for vector features.
           *
           * @constructor
           * @param {olx.style.CircleOptions=} opt_options Options.
           * @extends {ol.style.RegularShape}
           * @api
           */
          ol.style.Circle = function(opt_options) {
  
              var options = opt_options || {};
  
              ol.style.RegularShape.call(this, {
                  points: Infinity,
                  fill: options.fill,
                  radius: options.radius,
                  snapToPixel: options.snapToPixel,
                  stroke: options.stroke,
                  atlasManager: options.atlasManager
              });
  
          };
          ol.inherits(ol.style.Circle, ol.style.RegularShape);
  
  
          /**
           * Clones the style.  If an atlasmanager was provided to the original style it will be used in the cloned style, too.
           * @return {ol.style.Circle} The cloned style.
           * @override
           * @api
           */
          ol.style.Circle.prototype.clone = function() {
              var style = new ol.style.Circle({
                  fill: this.getFill() ? this.getFill().clone() : undefined,
                  stroke: this.getStroke() ? this.getStroke().clone() : undefined,
                  radius: this.getRadius(),
                  snapToPixel: this.getSnapToPixel(),
                  atlasManager: this.atlasManager_
              });
              style.setOpacity(this.getOpacity());
              style.setScale(this.getScale());
              return style;
          };
  
  
          /**
           * Set the circle radius.
           *
           * @param {number} radius Circle radius.
           * @api
           */
          ol.style.Circle.prototype.setRadius = function(radius) {
              this.radius_ = radius;
              this.render_(this.atlasManager_);
          };
  
          /**
           * @classdesc
           * Set fill style for vector features.
           *
           * @constructor
           * @param {olx.style.FillOptions=} opt_options Options.
           * @api
           */
          ol.style.Fill = function(opt_options) {
  
              var options = opt_options || {};
  
              /**
               * @private
               * @type {ol.Color|ol.ColorLike}
               */
              this.color_ = options.color !== undefined ? options.color : null;
  
              /**
               * @private
               * @type {string|undefined}
               */
              this.checksum_ = undefined;
          };
  
  
          /**
           * Clones the style. The color is not cloned if it is an {@link ol.ColorLike}.
           * @return {ol.style.Fill} The cloned style.
           * @api
           */
          ol.style.Fill.prototype.clone = function() {
              var color = this.getColor();
              return new ol.style.Fill({
                  color: (color && color.slice) ? color.slice() : color || undefined
              });
          };
  
  
          /**
           * Get the fill color.
           * @return {ol.Color|ol.ColorLike} Color.
           * @api
           */
          ol.style.Fill.prototype.getColor = function() {
              return this.color_;
          };
  
  
          /**
           * Set the color.
           *
           * @param {ol.Color|ol.ColorLike} color Color.
           * @api
           */
          ol.style.Fill.prototype.setColor = function(color) {
              this.color_ = color;
              this.checksum_ = undefined;
          };
  
  
          /**
           * @return {string} The checksum.
           */
          ol.style.Fill.prototype.getChecksum = function() {
              if (this.checksum_ === undefined) {
                  if (
                      this.color_ instanceof CanvasPattern ||
                          this.color_ instanceof CanvasGradient
                      ) {
                      this.checksum_ = ol.getUid(this.color_).toString();
                  } else {
                      this.checksum_ = 'f' + (this.color_ ?
                          ol.color.asString(this.color_) : '-');
                  }
              }
  
              return this.checksum_;
          };
  
          /**
           * @classdesc
           * Set stroke style for vector features.
           * Note that the defaults given are the Canvas defaults, which will be used if
           * option is not defined. The `get` functions return whatever was entered in
           * the options; they will not return the default.
           *
           * @constructor
           * @param {olx.style.StrokeOptions=} opt_options Options.
           * @api
           */
          ol.style.Stroke = function(opt_options) {
  
              var options = opt_options || {};
  
              /**
               * @private
               * @type {ol.Color|ol.ColorLike}
               */
              this.color_ = options.color !== undefined ? options.color : null;
  
              /**
               * @private
               * @type {string|undefined}
               */
              this.lineCap_ = options.lineCap;
  
              /**
               * @private
               * @type {Array.<number>}
               */
              this.lineDash_ = options.lineDash !== undefined ? options.lineDash : null;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.lineDashOffset_ = options.lineDashOffset;
  
              /**
               * @private
               * @type {string|undefined}
               */
              this.lineJoin_ = options.lineJoin;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.miterLimit_ = options.miterLimit;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.width_ = options.width;
  
              /**
               * @private
               * @type {string|undefined}
               */
              this.checksum_ = undefined;
          };
  
  
          /**
           * Clones the style.
           * @return {ol.style.Stroke} The cloned style.
           * @api
           */
          ol.style.Stroke.prototype.clone = function() {
              var color = this.getColor();
              return new ol.style.Stroke({
                  color: (color && color.slice) ? color.slice() : color || undefined,
                  lineCap: this.getLineCap(),
                  lineDash: this.getLineDash() ? this.getLineDash().slice() : undefined,
                  lineDashOffset: this.getLineDashOffset(),
                  lineJoin: this.getLineJoin(),
                  miterLimit: this.getMiterLimit(),
                  width: this.getWidth()
              });
          };
  
  
          /**
           * Get the stroke color.
           * @return {ol.Color|ol.ColorLike} Color.
           * @api
           */
          ol.style.Stroke.prototype.getColor = function() {
              return this.color_;
          };
  
  
          /**
           * Get the line cap type for the stroke.
           * @return {string|undefined} Line cap.
           * @api
           */
          ol.style.Stroke.prototype.getLineCap = function() {
              return this.lineCap_;
          };
  
  
          /**
           * Get the line dash style for the stroke.
           * @return {Array.<number>} Line dash.
           * @api
           */
          ol.style.Stroke.prototype.getLineDash = function() {
              return this.lineDash_;
          };
  
  
          /**
           * Get the line dash offset for the stroke.
           * @return {number|undefined} Line dash offset.
           * @api
           */
          ol.style.Stroke.prototype.getLineDashOffset = function() {
              return this.lineDashOffset_;
          };
  
  
          /**
           * Get the line join type for the stroke.
           * @return {string|undefined} Line join.
           * @api
           */
          ol.style.Stroke.prototype.getLineJoin = function() {
              return this.lineJoin_;
          };
  
  
          /**
           * Get the miter limit for the stroke.
           * @return {number|undefined} Miter limit.
           * @api
           */
          ol.style.Stroke.prototype.getMiterLimit = function() {
              return this.miterLimit_;
          };
  
  
          /**
           * Get the stroke width.
           * @return {number|undefined} Width.
           * @api
           */
          ol.style.Stroke.prototype.getWidth = function() {
              return this.width_;
          };
  
  
          /**
           * Set the color.
           *
           * @param {ol.Color|ol.ColorLike} color Color.
           * @api
           */
          ol.style.Stroke.prototype.setColor = function(color) {
              this.color_ = color;
              this.checksum_ = undefined;
          };
  
  
          /**
           * Set the line cap.
           *
           * @param {string|undefined} lineCap Line cap.
           * @api
           */
          ol.style.Stroke.prototype.setLineCap = function(lineCap) {
              this.lineCap_ = lineCap;
              this.checksum_ = undefined;
          };
  
  
          /**
           * Set the line dash.
           *
           * Please note that Internet Explorer 10 and lower [do not support][mdn] the
           * `setLineDash` method on the `CanvasRenderingContext2D` and therefore this
           * property will have no visual effect in these browsers.
           *
           * [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility
           *
           * @param {Array.<number>} lineDash Line dash.
           * @api
           */
          ol.style.Stroke.prototype.setLineDash = function(lineDash) {
              this.lineDash_ = lineDash;
              this.checksum_ = undefined;
          };
  
  
          /**
           * Set the line dash offset.
           *
           * @param {number|undefined} lineDashOffset Line dash offset.
           * @api
           */
          ol.style.Stroke.prototype.setLineDashOffset = function(lineDashOffset) {
              this.lineDashOffset_ = lineDashOffset;
              this.checksum_ = undefined;
          };
  
  
          /**
           * Set the line join.
           *
           * @param {string|undefined} lineJoin Line join.
           * @api
           */
          ol.style.Stroke.prototype.setLineJoin = function(lineJoin) {
              this.lineJoin_ = lineJoin;
              this.checksum_ = undefined;
          };
  
  
          /**
           * Set the miter limit.
           *
           * @param {number|undefined} miterLimit Miter limit.
           * @api
           */
          ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) {
              this.miterLimit_ = miterLimit;
              this.checksum_ = undefined;
          };
  
  
          /**
           * Set the width.
           *
           * @param {number|undefined} width Width.
           * @api
           */
          ol.style.Stroke.prototype.setWidth = function(width) {
              this.width_ = width;
              this.checksum_ = undefined;
          };
  
  
          /**
           * @return {string} The checksum.
           */
          ol.style.Stroke.prototype.getChecksum = function() {
              if (this.checksum_ === undefined) {
                  this.checksum_ = 's';
                  if (this.color_) {
                      if (typeof this.color_ === 'string') {
                          this.checksum_ += this.color_;
                      } else {
                          this.checksum_ += ol.getUid(this.color_).toString();
                      }
                  } else {
                      this.checksum_ += '-';
                  }
                  this.checksum_ += ',' +
                      (this.lineCap_ !== undefined ?
                          this.lineCap_.toString() : '-') + ',' +
                      (this.lineDash_ ?
                          this.lineDash_.toString() : '-') + ',' +
                      (this.lineDashOffset_ !== undefined ?
                          this.lineDashOffset_ : '-') + ',' +
                      (this.lineJoin_ !== undefined ?
                          this.lineJoin_ : '-') + ',' +
                      (this.miterLimit_ !== undefined ?
                          this.miterLimit_.toString() : '-') + ',' +
                      (this.width_ !== undefined ?
                          this.width_.toString() : '-');
              }
  
              return this.checksum_;
          };
  
          /**
           * Icon anchor units. One of 'fraction', 'pixels'.
           * @enum {string}
           */
          ol.style.IconAnchorUnits = {
              FRACTION: 'fraction',
              PIXELS: 'pixels'
          };
  
          /**
           * @constructor
           * @param {Image|HTMLCanvasElement} image Image.
           * @param {string|undefined} src Src.
           * @param {ol.Size} size Size.
           * @param {?string} crossOrigin Cross origin.
           * @param {ol.ImageState} imageState Image state.
           * @param {ol.Color} color Color.
           * @extends {ol.events.EventTarget}
           */
          ol.style.IconImage = function(image, src, size, crossOrigin, imageState,
                                        color) {
  
              ol.events.EventTarget.call(this);
  
              /**
               * @private
               * @type {Image|HTMLCanvasElement}
               */
              this.hitDetectionImage_ = null;
  
              /**
               * @private
               * @type {Image|HTMLCanvasElement}
               */
              this.image_ = !image ? new Image() : image;
  
              if (crossOrigin !== null) {
                  this.image_.crossOrigin = crossOrigin;
              }
  
              /**
               * @private
               * @type {HTMLCanvasElement}
               */
              this.canvas_ = color ?
              /** @type {HTMLCanvasElement} */ (document.createElement('CANVAS')) :
                  null;
  
              /**
               * @private
               * @type {ol.Color}
               */
              this.color_ = color;
  
              /**
               * @private
               * @type {Array.<ol.EventsKey>}
               */
              this.imageListenerKeys_ = null;
  
              /**
               * @private
               * @type {ol.ImageState}
               */
              this.imageState_ = imageState;
  
              /**
               * @private
               * @type {ol.Size}
               */
              this.size_ = size;
  
              /**
               * @private
               * @type {string|undefined}
               */
              this.src_ = src;
  
              /**
               * @private
               * @type {boolean}
               */
              this.tainting_ = false;
              if (this.imageState_ == ol.ImageState.LOADED) {
                  this.determineTainting_();
              }
  
          };
          ol.inherits(ol.style.IconImage, ol.events.EventTarget);
  
  
          /**
           * @param {Image|HTMLCanvasElement} image Image.
           * @param {string} src Src.
           * @param {ol.Size} size Size.
           * @param {?string} crossOrigin Cross origin.
           * @param {ol.ImageState} imageState Image state.
           * @param {ol.Color} color Color.
           * @return {ol.style.IconImage} Icon image.
           */
          ol.style.IconImage.get = function(image, src, size, crossOrigin, imageState,
                                            color) {
              var iconImageCache = ol.style.iconImageCache;
              var iconImage = iconImageCache.get(src, crossOrigin, color);
              if (!iconImage) {
                  iconImage = new ol.style.IconImage(
                      image, src, size, crossOrigin, imageState, color);
                  iconImageCache.set(src, crossOrigin, color, iconImage);
              }
              return iconImage;
          };
  
  
          /**
           * @private
           */
          ol.style.IconImage.prototype.determineTainting_ = function() {
              var context = ol.dom.createCanvasContext2D(1, 1);
              try {
                  context.drawImage(this.image_, 0, 0);
                  context.getImageData(0, 0, 1, 1);
              } catch (e) {
                  this.tainting_ = true;
              }
          };
  
  
          /**
           * @private
           */
          ol.style.IconImage.prototype.dispatchChangeEvent_ = function() {
              this.dispatchEvent(ol.events.EventType.CHANGE);
          };
  
  
          /**
           * @private
           */
          ol.style.IconImage.prototype.handleImageError_ = function() {
              this.imageState_ = ol.ImageState.ERROR;
              this.unlistenImage_();
              this.dispatchChangeEvent_();
          };
  
  
          /**
           * @private
           */
          ol.style.IconImage.prototype.handleImageLoad_ = function() {
              this.imageState_ = ol.ImageState.LOADED;
              if (this.size_) {
                  this.image_.width = this.size_[0];
                  this.image_.height = this.size_[1];
              }
              this.size_ = [this.image_.width, this.image_.height];
              this.unlistenImage_();
              this.determineTainting_();
              this.replaceColor_();
              this.dispatchChangeEvent_();
          };
  
  
          /**
           * @param {number} pixelRatio Pixel ratio.
           * @return {Image|HTMLCanvasElement} Image or Canvas element.
           */
          ol.style.IconImage.prototype.getImage = function(pixelRatio) {
              return this.canvas_ ? this.canvas_ : this.image_;
          };
  
  
          /**
           * @return {ol.ImageState} Image state.
           */
          ol.style.IconImage.prototype.getImageState = function() {
              return this.imageState_;
          };
  
  
          /**
           * @param {number} pixelRatio Pixel ratio.
           * @return {Image|HTMLCanvasElement} Image element.
           */
          ol.style.IconImage.prototype.getHitDetectionImage = function(pixelRatio) {
              if (!this.hitDetectionImage_) {
                  if (this.tainting_) {
                      var width = this.size_[0];
                      var height = this.size_[1];
                      var context = ol.dom.createCanvasContext2D(width, height);
                      context.fillRect(0, 0, width, height);
                      this.hitDetectionImage_ = context.canvas;
                  } else {
                      this.hitDetectionImage_ = this.image_;
                  }
              }
              return this.hitDetectionImage_;
          };
  
  
          /**
           * @return {ol.Size} Image size.
           */
          ol.style.IconImage.prototype.getSize = function() {
              return this.size_;
          };
  
  
          /**
           * @return {string|undefined} Image src.
           */
          ol.style.IconImage.prototype.getSrc = function() {
              return this.src_;
          };
  
  
          /**
           * Load not yet loaded URI.
           */
          ol.style.IconImage.prototype.load = function() {
              if (this.imageState_ == ol.ImageState.IDLE) {
                  this.imageState_ = ol.ImageState.LOADING;
                  this.imageListenerKeys_ = [
                      ol.events.listenOnce(this.image_, ol.events.EventType.ERROR,
                          this.handleImageError_, this),
                      ol.events.listenOnce(this.image_, ol.events.EventType.LOAD,
                          this.handleImageLoad_, this)
                  ];
                  try {
                      this.image_.src = this.src_;
                  } catch (e) {
                      this.handleImageError_();
                  }
              }
          };
  
  
          /**
           * @private
           */
          ol.style.IconImage.prototype.replaceColor_ = function() {
              if (this.tainting_ || this.color_ === null) {
                  return;
              }
  
              this.canvas_.width = this.image_.width;
              this.canvas_.height = this.image_.height;
  
              var ctx = this.canvas_.getContext('2d');
              ctx.drawImage(this.image_, 0, 0);
  
              var imgData = ctx.getImageData(0, 0, this.image_.width, this.image_.height);
              var data = imgData.data;
              var r = this.color_[0] / 255.0;
              var g = this.color_[1] / 255.0;
              var b = this.color_[2] / 255.0;
  
              for (var i = 0, ii = data.length; i < ii; i += 4) {
                  data[i] *= r;
                  data[i + 1] *= g;
                  data[i + 2] *= b;
              }
              ctx.putImageData(imgData, 0, 0);
          };
  
  
          /**
           * Discards event handlers which listen for load completion or errors.
           *
           * @private
           */
          ol.style.IconImage.prototype.unlistenImage_ = function() {
              this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
              this.imageListenerKeys_ = null;
          };
  
          /**
           * Icon origin. One of 'bottom-left', 'bottom-right', 'top-left', 'top-right'.
           * @enum {string}
           */
          ol.style.IconOrigin = {
              BOTTOM_LEFT: 'bottom-left',
              BOTTOM_RIGHT: 'bottom-right',
              TOP_LEFT: 'top-left',
              TOP_RIGHT: 'top-right'
          };
  
          /**
           * @classdesc
           * Set icon style for vector features.
           *
           * @constructor
           * @param {olx.style.IconOptions=} opt_options Options.
           * @extends {ol.style.Image}
           * @api
           */
          ol.style.Icon = function(opt_options) {
  
              var options = opt_options || {};
  
              /**
               * @private
               * @type {Array.<number>}
               */
              this.anchor_ = options.anchor !== undefined ? options.anchor : [0.5, 0.5];
  
              /**
               * @private
               * @type {Array.<number>}
               */
              this.normalizedAnchor_ = null;
  
              /**
               * @private
               * @type {ol.style.IconOrigin}
               */
              this.anchorOrigin_ = options.anchorOrigin !== undefined ?
                  options.anchorOrigin : ol.style.IconOrigin.TOP_LEFT;
  
              /**
               * @private
               * @type {ol.style.IconAnchorUnits}
               */
              this.anchorXUnits_ = options.anchorXUnits !== undefined ?
                  options.anchorXUnits : ol.style.IconAnchorUnits.FRACTION;
  
              /**
               * @private
               * @type {ol.style.IconAnchorUnits}
               */
              this.anchorYUnits_ = options.anchorYUnits !== undefined ?
                  options.anchorYUnits : ol.style.IconAnchorUnits.FRACTION;
  
              /**
               * @private
               * @type {?string}
               */
              this.crossOrigin_ =
                  options.crossOrigin !== undefined ? options.crossOrigin : null;
  
              /**
               * @type {Image|HTMLCanvasElement}
               */
              var image = options.img !== undefined ? options.img : null;
  
              /**
               * @type {ol.Size}
               */
              var imgSize = options.imgSize !== undefined ? options.imgSize : null;
  
              /**
               * @type {string|undefined}
               */
              var src = options.src;
  
      //        ol.asserts.assert(!(src !== undefined && image),
      //            4); // `image` and `src` cannot be provided at the same time
      //        ol.asserts.assert(!image || (image && imgSize),
      //            5); // `imgSize` must be set when `image` is provided
  
              if ((src === undefined || src.length === 0) && image) {
                  src = image.src || ol.getUid(image).toString();
              }
      //        ol.asserts.assert(src !== undefined && src.length > 0,
      //            6); // A defined and non-empty `src` or `image` must be provided
  
              /**
               * @type {ol.ImageState}
               */
              var imageState = options.src !== undefined ?
                  ol.ImageState.IDLE : ol.ImageState.LOADED;
  
              /**
               * @private
               * @type {ol.Color}
               */
              this.color_ = options.color !== undefined ? ol.color.asArray(options.color) :
                  null;
  
              /**
               * @private
               * @type {ol.style.IconImage}
               */
              this.iconImage_ = ol.style.IconImage.get(
                  image, /** @type {string} */ (src), imgSize, this.crossOrigin_, imageState, this.color_);
  
              /**
               * @private
               * @type {Array.<number>}
               */
              this.offset_ = options.offset !== undefined ? options.offset : [0, 0];
  
              /**
               * @private
               * @type {ol.style.IconOrigin}
               */
              this.offsetOrigin_ = options.offsetOrigin !== undefined ?
                  options.offsetOrigin : ol.style.IconOrigin.TOP_LEFT;
  
              /**
               * @private
               * @type {Array.<number>}
               */
              this.origin_ = null;
  
              /**
               * @private
               * @type {ol.Size}
               */
              this.size_ = options.size !== undefined ? options.size : null;
  
              /**
               * @type {number}
               */
              var opacity = options.opacity !== undefined ? options.opacity : 1;
  
              /**
               * @type {boolean}
               */
              var rotateWithView = options.rotateWithView !== undefined ?
                  options.rotateWithView : false;
  
              /**
               * @type {number}
               */
              var rotation = options.rotation !== undefined ? options.rotation : 0;
  
              /**
               * @type {number}
               */
              var scale = options.scale !== undefined ? options.scale : 1;
  
              /**
               * @type {boolean}
               */
              var snapToPixel = options.snapToPixel !== undefined ?
                  options.snapToPixel : true;
  
              ol.style.Image.call(this, {
                  opacity: opacity,
                  rotation: rotation,
                  scale: scale,
                  snapToPixel: snapToPixel,
                  rotateWithView: rotateWithView
              });
  
          };
          ol.inherits(ol.style.Icon, ol.style.Image);
  
  
          /**
           * Clones the style. The underlying Image/HTMLCanvasElement is not cloned.
           * @return {ol.style.Icon} The cloned style.
           * @api
           */
          ol.style.Icon.prototype.clone = function() {
              return new ol.style.Icon({
                  anchor: this.anchor_.slice(),
                  anchorOrigin: this.anchorOrigin_,
                  anchorXUnits: this.anchorXUnits_,
                  anchorYUnits: this.anchorYUnits_,
                  crossOrigin: this.crossOrigin_,
                  color: (this.color_ && this.color_.slice) ? this.color_.slice() : this.color_ || undefined,
                  src: this.getSrc(),
                  offset: this.offset_.slice(),
                  offsetOrigin: this.offsetOrigin_,
                  size: this.size_ !== null ? this.size_.slice() : undefined,
                  opacity: this.getOpacity(),
                  scale: this.getScale(),
                  snapToPixel: this.getSnapToPixel(),
                  rotation: this.getRotation(),
                  rotateWithView: this.getRotateWithView()
              });
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.style.Icon.prototype.getAnchor = function() {
              if (this.normalizedAnchor_) {
                  return this.normalizedAnchor_;
              }
              var anchor = this.anchor_;
              var size = this.getSize();
              if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION ||
                  this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) {
                  if (!size) {
                      return null;
                  }
                  anchor = this.anchor_.slice();
                  if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION) {
                      anchor[0] *= size[0];
                  }
                  if (this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) {
                      anchor[1] *= size[1];
                  }
              }
  
              if (this.anchorOrigin_ != ol.style.IconOrigin.TOP_LEFT) {
                  if (!size) {
                      return null;
                  }
                  if (anchor === this.anchor_) {
                      anchor = this.anchor_.slice();
                  }
                  if (this.anchorOrigin_ == ol.style.IconOrigin.TOP_RIGHT ||
                      this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
                      anchor[0] = -anchor[0] + size[0];
                  }
                  if (this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT ||
                      this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
                      anchor[1] = -anchor[1] + size[1];
                  }
              }
              this.normalizedAnchor_ = anchor;
              return this.normalizedAnchor_;
          };
  
  
          /**
           * Get the icon color.
           * @return {ol.Color} Color.
           * @api
           */
          ol.style.Icon.prototype.getColor = function() {
              return this.color_;
          };
  
  
          /**
           * Get the image icon.
           * @param {number} pixelRatio Pixel ratio.
           * @return {Image|HTMLCanvasElement} Image or Canvas element.
           * @override
           * @api
           */
          ol.style.Icon.prototype.getImage = function(pixelRatio) {
              return this.iconImage_.getImage(pixelRatio);
          };
  
  
          /**
           * @override
           */
          ol.style.Icon.prototype.getImageSize = function() {
              return this.iconImage_.getSize();
          };
  
  
          /**
           * @override
           */
          ol.style.Icon.prototype.getHitDetectionImageSize = function() {
              return this.getImageSize();
          };
  
  
          /**
           * @override
           */
          ol.style.Icon.prototype.getImageState = function() {
              return this.iconImage_.getImageState();
          };
  
  
          /**
           * @override
           */
          ol.style.Icon.prototype.getHitDetectionImage = function(pixelRatio) {
              return this.iconImage_.getHitDetectionImage(pixelRatio);
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.style.Icon.prototype.getOrigin = function() {
              if (this.origin_) {
                  return this.origin_;
              }
              var offset = this.offset_;
  
              if (this.offsetOrigin_ != ol.style.IconOrigin.TOP_LEFT) {
                  var size = this.getSize();
                  var iconImageSize = this.iconImage_.getSize();
                  if (!size || !iconImageSize) {
                      return null;
                  }
                  offset = offset.slice();
                  if (this.offsetOrigin_ == ol.style.IconOrigin.TOP_RIGHT ||
                      this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
                      offset[0] = iconImageSize[0] - size[0] - offset[0];
                  }
                  if (this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT ||
                      this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
                      offset[1] = iconImageSize[1] - size[1] - offset[1];
                  }
              }
              this.origin_ = offset;
              return this.origin_;
          };
  
  
          /**
           * Get the image URL.
           * @return {string|undefined} Image src.
           * @api
           */
          ol.style.Icon.prototype.getSrc = function() {
              return this.iconImage_.getSrc();
          };
  
  
          /**
           * @inheritDoc
           * @api
           */
          ol.style.Icon.prototype.getSize = function() {
              return !this.size_ ? this.iconImage_.getSize() : this.size_;
          };
  
  
          /**
           * @override
           */
          ol.style.Icon.prototype.listenImageChange = function(listener, thisArg) {
              return ol.events.listen(this.iconImage_, ol.events.EventType.CHANGE,
                  listener, thisArg);
          };
  
  
          /**
           * Load not yet loaded URI.
           * When rendering a feature with an icon style, the vector renderer will
           * automatically call this method. However, you might want to call this
           * method yourself for preloading or other purposes.
           * @override
           * @api
           */
          ol.style.Icon.prototype.load = function() {
              this.iconImage_.load();
          };
  
  
          /**
           * @override
           */
          ol.style.Icon.prototype.unlistenImageChange = function(listener, thisArg) {
              ol.events.unlisten(this.iconImage_, ol.events.EventType.CHANGE,
                  listener, thisArg);
          };
  
          ol.style.Text = function(opt_options) {
              var options = opt_options || {};
              this.font_ = options.font;
              this.rotation_ = options.rotation;
              this.rotateWithView_ = options.rotateWithView;
              this.scale_ = options.scale;
              this.text_ = options.text;
              this.textAlign_ = options.textAlign;
              this.textBaseline_ = options.textBaseline;
              this.fill_ = options.fill !== undefined ? options.fill :
                  new ol.style.Fill({color: ol.style.Text.DEFAULT_FILL_COLOR_});
              this.maxAngle_ = options.maxAngle !== undefined ? options.maxAngle : Math.PI / 4;
              this.placement_ = options.placement !== undefined ? options.placement : ol.style.TextPlacement.POINT;
  
              //TODO Use options.overflow directly after removing @deprecated exceedLength
              var overflow = options.overflow === undefined ? options.exceedLength : options.overflow;
  
              this.overflow_ = overflow !== undefined ? overflow : false;
              this.stroke_ = options.stroke !== undefined ? options.stroke : null;
              this.offsetX_ = options.offsetX !== undefined ? options.offsetX : 0;
              this.offsetY_ = options.offsetY !== undefined ? options.offsetY : 0;
              this.backgroundFill_ = options.backgroundFill ? options.backgroundFill : null;
              this.backgroundStroke_ = options.backgroundStroke ? options.backgroundStroke : null;
              this.padding_ = options.padding === undefined ? null : options.padding;
          };
  
          ol.style.Text.DEFAULT_FILL_COLOR_ = '#333';
          ol.style.Text.prototype.clone = function() {
              return new ol.style.Text({
                  font: this.getFont(),
                  placement: this.getPlacement(),
                  maxAngle: this.getMaxAngle(),
                  overflow: this.getOverflow(),
                  rotation: this.getRotation(),
                  rotateWithView: this.getRotateWithView(),
                  scale: this.getScale(),
                  text: this.getText(),
                  textAlign: this.getTextAlign(),
                  textBaseline: this.getTextBaseline(),
                  fill: this.getFill() ? this.getFill().clone() : undefined,
                  stroke: this.getStroke() ? this.getStroke().clone() : undefined,
                  offsetX: this.getOffsetX(),
                  offsetY: this.getOffsetY()
              });
          };
  
          ol.style.Text.prototype.getOverflow = function() {
              return this.overflow_;
          };
  
          ol.style.Text.prototype.getFont = function() {
              return this.font_;
          };
  
          ol.style.Text.prototype.getMaxAngle = function() {
              return this.maxAngle_;
          };
  
          ol.style.Text.prototype.getPlacement = function() {
              return this.placement_;
          };
  
          ol.style.Text.prototype.getOffsetX = function() {
              return this.offsetX_;
          };
  
          ol.style.Text.prototype.getOffsetY = function() {
              return this.offsetY_;
          };
  
          ol.style.Text.prototype.getFill = function() {
              return this.fill_;
          };
  
          ol.style.Text.prototype.getRotateWithView = function() {
              return this.rotateWithView_;
          };
  
          ol.style.Text.prototype.getRotation = function() {
              return this.rotation_;
          };
  
          ol.style.Text.prototype.getScale = function() {
              return this.scale_;
          };
  
          ol.style.Text.prototype.getStroke = function() {
              return this.stroke_;
          };
  
          ol.style.Text.prototype.getText = function() {
              return this.text_;
          };
  
          ol.style.Text.prototype.getTextAlign = function() {
              return this.textAlign_;
          };
  
          ol.style.Text.prototype.getTextBaseline = function() {
              return this.textBaseline_;
          };
  
          ol.style.Text.prototype.getBackgroundFill = function() {
              return this.backgroundFill_;
          };
  
          ol.style.Text.prototype.getBackgroundStroke = function() {
              return this.backgroundStroke_;
          };
  
          ol.style.Text.prototype.getPadding = function() {
              return this.padding_;
          };
  
          ol.style.Text.prototype.setOverflow = function(overflow) {
              this.overflow_ = overflow;
          };
  
          ol.style.Text.prototype.setFont = function(font) {
              this.font_ = font;
          };
  
          ol.style.Text.prototype.setMaxAngle = function(maxAngle) {
              this.maxAngle_ = maxAngle;
          };
  
          ol.style.Text.prototype.setOffsetX = function(offsetX) {
              this.offsetX_ = offsetX;
          };
  
          ol.style.Text.prototype.setOffsetY = function(offsetY) {
              this.offsetY_ = offsetY;
          };
  
          ol.style.Text.prototype.setPlacement = function(placement) {
              this.placement_ = placement;
          };
  
          ol.style.Text.prototype.setFill = function(fill) {
              this.fill_ = fill;
          };
  
          ol.style.Text.prototype.setRotation = function(rotation) {
              this.rotation_ = rotation;
          };
  
          ol.style.Text.prototype.setScale = function(scale) {
              this.scale_ = scale;
          };
  
          ol.style.Text.prototype.setStroke = function(stroke) {
              this.stroke_ = stroke;
          };
  
          ol.style.Text.prototype.setText = function(text) {
              this.text_ = text;
          };
  
          ol.style.Text.prototype.setTextAlign = function(textAlign) {
              this.textAlign_ = textAlign;
          };
  
          ol.style.Text.prototype.setTextBaseline = function(textBaseline) {
              this.textBaseline_ = textBaseline;
          };
  
          ol.style.Text.prototype.setBackgroundFill = function(fill) {
              this.backgroundFill_ = fill;
          };
  
          ol.style.Text.prototype.setBackgroundStroke = function(stroke) {
              this.backgroundStroke_ = stroke;
          };
  
          ol.style.Text.prototype.setPadding = function(padding) {
              this.padding_ = padding;
          };
  
          ol.style.Style = function(opt_options) {
  
              var options = opt_options || {};
  
              /**
               * @private
               * @type {string|ol.geom.Geometry|ol.StyleGeometryFunction}
               */
              this.geometry_ = null;
  
              /**
               * @private
               * @type {!ol.StyleGeometryFunction}
               */
              this.geometryFunction_ = ol.style.Style.defaultGeometryFunction;
  
              if (options.geometry !== undefined) {
                  this.setGeometry(options.geometry);
              }
  
              /**
               * @private
               * @type {ol.style.Fill}
               */
              this.fill_ = options.fill !== undefined ? options.fill : null;
  
              /**
               * @private
               * @type {ol.style.Image}
               */
              this.image_ = options.image !== undefined ? options.image : null;
  
              /**
               * @private
               * @type {ol.StyleRenderFunction|null}
               */
              this.renderer_ = options.renderer !== undefined ? options.renderer : null;
  
              /**
               * @private
               * @type {ol.style.Stroke}
               */
              this.stroke_ = options.stroke !== undefined ? options.stroke : null;
  
              /**
               * @private
               * @type {ol.style.Text}
               */
              this.text_ = options.text !== undefined ? options.text : null;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.zIndex_ = options.zIndex;
  
          };
  
  
          /**
           * Clones the style.
           * @return {ol.style.Style} The cloned style.
           * @api
           */
          ol.style.Style.prototype.clone = function() {
              var geometry = this.getGeometry();
              if (geometry && geometry.clone) {
                  geometry = geometry.clone();
              }
              return new ol.style.Style({
                  geometry: geometry,
                  fill: this.getFill() ? this.getFill().clone() : undefined,
                  image: this.getImage() ? this.getImage().clone() : undefined,
                  stroke: this.getStroke() ? this.getStroke().clone() : undefined,
                  text: this.getText() ? this.getText().clone() : undefined,
                  zIndex: this.getZIndex()
              });
          };
  
  
          /**
           * Get the custom renderer function that was configured with
           * {@link #setRenderer} or the `renderer` constructor option.
           * @return {ol.StyleRenderFunction|null} Custom renderer function.
           * @api
           */
          ol.style.Style.prototype.getRenderer = function() {
              return this.renderer_;
          };
  
  
          /**
           * Sets a custom renderer function for this style. When set, `fill`, `stroke`
           * and `image` options of the style will be ignored.
           * @param {ol.StyleRenderFunction|null} renderer Custom renderer function.
           * @api
           */
          ol.style.Style.prototype.setRenderer = function(renderer) {
              this.renderer_ = renderer;
          };
  
  
          /**
           * Get the geometry to be rendered.
           * @return {string|ol.geom.Geometry|ol.StyleGeometryFunction}
           * Feature property or geometry or function that returns the geometry that will
           * be rendered with this style.
           * @api
           */
          ol.style.Style.prototype.getGeometry = function() {
              return this.geometry_;
          };
  
  
          /**
           * Get the function used to generate a geometry for rendering.
           * @return {!ol.StyleGeometryFunction} Function that is called with a feature
           * and returns the geometry to render instead of the feature's geometry.
           * @api
           */
          ol.style.Style.prototype.getGeometryFunction = function() {
              return this.geometryFunction_;
          };
  
  
          /**
           * Get the fill style.
           * @return {ol.style.Fill} Fill style.
           * @api
           */
          ol.style.Style.prototype.getFill = function() {
              return this.fill_;
          };
  
  
          /**
           * Set the fill style.
           * @param {ol.style.Fill} fill Fill style.
           * @api
           */
          ol.style.Style.prototype.setFill = function(fill) {
              this.fill_ = fill;
          };
  
  
          /**
           * Get the image style.
           * @return {ol.style.Image} Image style.
           * @api
           */
          ol.style.Style.prototype.getImage = function() {
              return this.image_;
          };
  
  
          /**
           * Set the image style.
           * @param {ol.style.Image} image Image style.
           * @api
           */
          ol.style.Style.prototype.setImage = function(image) {
              this.image_ = image;
          };
  
  
          /**
           * Get the stroke style.
           * @return {ol.style.Stroke} Stroke style.
           * @api
           */
          ol.style.Style.prototype.getStroke = function() {
              return this.stroke_;
          };
  
  
          /**
           * Set the stroke style.
           * @param {ol.style.Stroke} stroke Stroke style.
           * @api
           */
          ol.style.Style.prototype.setStroke = function(stroke) {
              this.stroke_ = stroke;
          };
  
  
          /**
           * Get the text style.
           * @return {ol.style.Text} Text style.
           * @api
           */
          ol.style.Style.prototype.getText = function() {
              return this.text_;
          };
  
  
          /**
           * Set the text style.
           * @param {ol.style.Text} text Text style.
           * @api
           */
          ol.style.Style.prototype.setText = function(text) {
              this.text_ = text;
          };
  
  
          /**
           * Get the z-index for the style.
           * @return {number|undefined} ZIndex.
           * @api
           */
          ol.style.Style.prototype.getZIndex = function() {
              return this.zIndex_;
          };
  
  
          /**
           * Set a geometry that is rendered instead of the feature's geometry.
           *
           * @param {string|ol.geom.Geometry|ol.StyleGeometryFunction} geometry
           *     Feature property or geometry or function returning a geometry to render
           *     for this style.
           * @api
           */
          ol.style.Style.prototype.setGeometry = function(geometry) {
              if (typeof geometry === 'function') {
                  this.geometryFunction_ = geometry;
              } else if (typeof geometry === 'string') {
                  this.geometryFunction_ = function(feature) {
                      return /** @type {ol.geom.Geometry} */ (feature.get(geometry));
                  };
              } else if (!geometry) {
                  this.geometryFunction_ = ol.style.Style.defaultGeometryFunction;
              } else if (geometry !== undefined) {
                  this.geometryFunction_ = function() {
                      return /** @type {ol.geom.Geometry} */ (geometry);
                  };
              }
              this.geometry_ = geometry;
          };
  
  
          /**
           * Set the z-index.
           *
           * @param {number|undefined} zIndex ZIndex.
           * @api
           */
          ol.style.Style.prototype.setZIndex = function(zIndex) {
              this.zIndex_ = zIndex;
          };
  
  
          /**
           * Convert the provided object into a style function.  Functions passed through
           * unchanged.  Arrays of ol.style.Style or single style objects wrapped in a
           * new style function.
           * @param {ol.StyleFunction|Array.<ol.style.Style>|ol.style.Style} obj
           *     A style function, a single style, or an array of styles.
           * @return {ol.StyleFunction} A style function.
           */
          ol.style.Style.createFunction = function(obj) {
              var styleFunction;
  
              if (typeof obj === 'function') {
                  styleFunction = obj;
              } else {
                  /**
                   * @type {Array.<ol.style.Style>}
                   */
                  var styles;
                  if (Array.isArray(obj)) {
                      styles = obj;
                  } else {
                      ol.asserts.assert(obj instanceof ol.style.Style,
                          41); // Expected an `ol.style.Style` or an array of `ol.style.Style`
                      styles = [obj];
                  }
                  styleFunction = function() {
                      return styles;
                  };
              }
              return styleFunction;
          };
  
  
          /**
           * @type {Array.<ol.style.Style>}
           * @private
           */
          ol.style.Style.default_ = null;
  
  
          /**
           * @param {ol.Feature|ol.render.Feature} feature Feature.
           * @param {number} resolution Resolution.
           * @return {Array.<ol.style.Style>} Style.
           */
          ol.style.Style.defaultFunction = function(feature, resolution) {
              // We don't use an immediately-invoked function
              // and a closure so we don't get an error at script evaluation time in
              // browsers that do not support Canvas. (ol.style.Circle does
              // canvas.getContext('2d') at construction time, which will cause an.error
              // in such browsers.)
              if (!ol.style.Style.default_) {
                  var fill = new ol.style.Fill({
                      color: 'rgba(255,255,255,0.4)'
                  });
                  var stroke = new ol.style.Stroke({
                      color: '#3399CC',
                      width: 1.25
                  });
                  ol.style.Style.default_ = [
                      new ol.style.Style({
                          image: new ol.style.Circle({
                              fill: fill,
                              stroke: stroke,
                              radius: 5
                          }),
                          fill: fill,
                          stroke: stroke
                      })
                  ];
              }
              return ol.style.Style.default_;
          };
  
  
          /**
           * Default styles for editing features.
           * @return {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} Styles
           */
          ol.style.Style.createDefaultEditing = function() {
              /** @type {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} */
              var styles = {};
              var white = [255, 255, 255, 1];
              var blue = [0, 153, 255, 1];
              var width = 3;
              styles[ol.geom.GeometryType.POLYGON] = [
                  new ol.style.Style({
                      fill: new ol.style.Fill({
                          color: [255, 255, 255, 0.5]
                      })
                  })
              ];
              styles[ol.geom.GeometryType.MULTI_POLYGON] =
                  styles[ol.geom.GeometryType.POLYGON];
  
              styles[ol.geom.GeometryType.LINE_STRING] = [
                  new ol.style.Style({
                      stroke: new ol.style.Stroke({
                          color: white,
                          width: width + 2
                      })
                  }),
                  new ol.style.Style({
                      stroke: new ol.style.Stroke({
                          color: blue,
                          width: width
                      })
                  })
              ];
              styles[ol.geom.GeometryType.MULTI_LINE_STRING] =
                  styles[ol.geom.GeometryType.LINE_STRING];
  
              styles[ol.geom.GeometryType.CIRCLE] =
                  styles[ol.geom.GeometryType.POLYGON].concat(
                      styles[ol.geom.GeometryType.LINE_STRING]
                  );
  
  
              styles[ol.geom.GeometryType.POINT] = [
                  new ol.style.Style({
                      image: new ol.style.Circle({
                          radius: width * 2,
                          fill: new ol.style.Fill({
                              color: blue
                          }),
                          stroke: new ol.style.Stroke({
                              color: white,
                              width: width / 2
                          })
                      }),
                      zIndex: Infinity
                  })
              ];
              styles[ol.geom.GeometryType.MULTI_POINT] =
                  styles[ol.geom.GeometryType.POINT];
  
              styles[ol.geom.GeometryType.GEOMETRY_COLLECTION] =
                  styles[ol.geom.GeometryType.POLYGON].concat(
                      styles[ol.geom.GeometryType.LINE_STRING],
                      styles[ol.geom.GeometryType.POINT]
                  );
  
              return styles;
          };
  
  
          /**
           * Function that is called with a feature and returns its default geometry.
           * @param {ol.Feature|ol.render.Feature} feature Feature to get the geometry
           *     for.
           * @return {ol.geom.Geometry|ol.render.Feature|undefined} Geometry to render.
           */
          ol.style.Style.defaultGeometryFunction = function(feature) {
              return feature.getGeometry();
          };
  
  
          ol.ext = {};
          ol.ext.rbush = function() {};
  
          (function() {(function (exports) {
  
              var quickselect_1 = quickselect;
              var default_1 = quickselect;
              function quickselect(arr, k, left, right, compare) {
                  quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
              }
              function quickselectStep(arr, k, left, right, compare) {
                  while (right > left) {
                      if (right - left > 600) {
                          var n = right - left + 1;
                          var m = k - left + 1;
                          var z = Math.log(n);
                          var s = 0.5 * Math.exp(2 * z / 3);
                          var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
                          var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
                          var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
                          quickselectStep(arr, k, newLeft, newRight, compare);
                      }
                      var t = arr[k];
                      var i = left;
                      var j = right;
                      swap(arr, left, k);
                      if (compare(arr[right], t) > 0) swap(arr, left, right);
                      while (i < j) {
                          swap(arr, i, j);
                          i++;
                          j--;
                          while (compare(arr[i], t) < 0) i++;
                          while (compare(arr[j], t) > 0) j--;
                      }
                      if (compare(arr[left], t) === 0) swap(arr, left, j);
                      else {
                          j++;
                          swap(arr, j, right);
                      }
                      if (j <= k) left = j + 1;
                      if (k <= j) right = j - 1;
                  }
              }
              function swap(arr, i, j) {
                  var tmp = arr[i];
                  arr[i] = arr[j];
                  arr[j] = tmp;
              }
              function defaultCompare(a, b) {
                  return a < b ? -1 : a > b ? 1 : 0;
              }
              quickselect_1.default = default_1;
  
              var rbush_1 = rbush;
              function rbush(maxEntries, format) {
                  if (!(this instanceof rbush)) return new rbush(maxEntries, format);
                  this._maxEntries = Math.max(4, maxEntries || 9);
                  this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
                  if (format) {
                      this._initFormat(format);
                  }
                  this.clear();
              }
              rbush.prototype = {
                  all: function () {
                      return this._all(this.data, []);
                  },
                  search: function (bbox) {
                      var node = this.data,
                          result = [],
                          toBBox = this.toBBox;
                      if (!intersects(bbox, node)) return result;
                      var nodesToSearch = [],
                          i, len, child, childBBox;
                      while (node) {
                          for (i = 0, len = node.children.length; i < len; i++) {
                              child = node.children[i];
                              childBBox = node.leaf ? toBBox(child) : child;
                              if (intersects(bbox, childBBox)) {
                                  if (node.leaf) result.push(child);
                                  else if (contains(bbox, childBBox)) this._all(child, result);
                                  else nodesToSearch.push(child);
                              }
                          }
                          node = nodesToSearch.pop();
                      }
                      return result;
                  },
                  collides: function (bbox) {
                      var node = this.data,
                          toBBox = this.toBBox;
                      if (!intersects(bbox, node)) return false;
                      var nodesToSearch = [],
                          i, len, child, childBBox;
                      while (node) {
                          for (i = 0, len = node.children.length; i < len; i++) {
                              child = node.children[i];
                              childBBox = node.leaf ? toBBox(child) : child;
                              if (intersects(bbox, childBBox)) {
                                  if (node.leaf || contains(bbox, childBBox)) return true;
                                  nodesToSearch.push(child);
                              }
                          }
                          node = nodesToSearch.pop();
                      }
                      return false;
                  },
                  load: function (data) {
                      if (!(data && data.length)) return this;
                      if (data.length < this._minEntries) {
                          for (var i = 0, len = data.length; i < len; i++) {
                              this.insert(data[i]);
                          }
                          return this;
                      }
                      var node = this._build(data.slice(), 0, data.length - 1, 0);
                      if (!this.data.children.length) {
                          this.data = node;
                      } else if (this.data.height === node.height) {
                          this._splitRoot(this.data, node);
                      } else {
                          if (this.data.height < node.height) {
                              var tmpNode = this.data;
                              this.data = node;
                              node = tmpNode;
                          }
                          this._insert(node, this.data.height - node.height - 1, true);
                      }
                      return this;
                  },
                  insert: function (item) {
                      if (item) this._insert(item, this.data.height - 1);
                      return this;
                  },
                  clear: function () {
                      this.data = createNode([]);
                      return this;
                  },
                  remove: function (item, equalsFn) {
                      if (!item) return this;
                      var node = this.data,
                          bbox = this.toBBox(item),
                          path = [],
                          indexes = [],
                          i, parent, index, goingUp;
                      while (node || path.length) {
                          if (!node) {
                              node = path.pop();
                              parent = path[path.length - 1];
                              i = indexes.pop();
                              goingUp = true;
                          }
                          if (node.leaf) {
                              index = findItem(item, node.children, equalsFn);
                              if (index !== -1) {
                                  node.children.splice(index, 1);
                                  path.push(node);
                                  this._condense(path);
                                  return this;
                              }
                          }
                          if (!goingUp && !node.leaf && contains(node, bbox)) {
                              path.push(node);
                              indexes.push(i);
                              i = 0;
                              parent = node;
                              node = node.children[0];
                          } else if (parent) {
                              i++;
                              node = parent.children[i];
                              goingUp = false;
                          } else node = null;
                      }
                      return this;
                  },
                  toBBox: function (item) { return item; },
                  compareMinX: compareNodeMinX,
                  compareMinY: compareNodeMinY,
                  toJSON: function () { return this.data; },
                  fromJSON: function (data) {
                      this.data = data;
                      return this;
                  },
                  _all: function (node, result) {
                      var nodesToSearch = [];
                      while (node) {
                          if (node.leaf) result.push.apply(result, node.children);
                          else nodesToSearch.push.apply(nodesToSearch, node.children);
                          node = nodesToSearch.pop();
                      }
                      return result;
                  },
                  _build: function (items, left, right, height) {
                      var N = right - left + 1,
                          M = this._maxEntries,
                          node;
                      if (N <= M) {
                          node = createNode(items.slice(left, right + 1));
                          calcBBox(node, this.toBBox);
                          return node;
                      }
                      if (!height) {
                          height = Math.ceil(Math.log(N) / Math.log(M));
                          M = Math.ceil(N / Math.pow(M, height - 1));
                      }
                      node = createNode([]);
                      node.leaf = false;
                      node.height = height;
                      var N2 = Math.ceil(N / M),
                          N1 = N2 * Math.ceil(Math.sqrt(M)),
                          i, j, right2, right3;
                      multiSelect(items, left, right, N1, this.compareMinX);
                      for (i = left; i <= right; i += N1) {
                          right2 = Math.min(i + N1 - 1, right);
                          multiSelect(items, i, right2, N2, this.compareMinY);
                          for (j = i; j <= right2; j += N2) {
                              right3 = Math.min(j + N2 - 1, right2);
                              node.children.push(this._build(items, j, right3, height - 1));
                          }
                      }
                      calcBBox(node, this.toBBox);
                      return node;
                  },
                  _chooseSubtree: function (bbox, node, level, path) {
                      var i, len, child, targetNode, area, enlargement, minArea, minEnlargement;
                      while (true) {
                          path.push(node);
                          if (node.leaf || path.length - 1 === level) break;
                          minArea = minEnlargement = Infinity;
                          for (i = 0, len = node.children.length; i < len; i++) {
                              child = node.children[i];
                              area = bboxArea(child);
                              enlargement = enlargedArea(bbox, child) - area;
                              if (enlargement < minEnlargement) {
                                  minEnlargement = enlargement;
                                  minArea = area < minArea ? area : minArea;
                                  targetNode = child;
                              } else if (enlargement === minEnlargement) {
                                  if (area < minArea) {
                                      minArea = area;
                                      targetNode = child;
                                  }
                              }
                          }
                          node = targetNode || node.children[0];
                      }
                      return node;
                  },
                  _insert: function (item, level, isNode) {
                      var toBBox = this.toBBox,
                          bbox = isNode ? item : toBBox(item),
                          insertPath = [];
                      var node = this._chooseSubtree(bbox, this.data, level, insertPath);
                      node.children.push(item);
                      extend(node, bbox);
                      while (level >= 0) {
                          if (insertPath[level].children.length > this._maxEntries) {
                              this._split(insertPath, level);
                              level--;
                          } else break;
                      }
                      this._adjustParentBBoxes(bbox, insertPath, level);
                  },
                  _split: function (insertPath, level) {
                      var node = insertPath[level],
                          M = node.children.length,
                          m = this._minEntries;
                      this._chooseSplitAxis(node, m, M);
                      var splitIndex = this._chooseSplitIndex(node, m, M);
                      var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
                      newNode.height = node.height;
                      newNode.leaf = node.leaf;
                      calcBBox(node, this.toBBox);
                      calcBBox(newNode, this.toBBox);
                      if (level) insertPath[level - 1].children.push(newNode);
                      else this._splitRoot(node, newNode);
                  },
                  _splitRoot: function (node, newNode) {
                      this.data = createNode([node, newNode]);
                      this.data.height = node.height + 1;
                      this.data.leaf = false;
                      calcBBox(this.data, this.toBBox);
                  },
                  _chooseSplitIndex: function (node, m, M) {
                      var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index;
                      minOverlap = minArea = Infinity;
                      for (i = m; i <= M - m; i++) {
                          bbox1 = distBBox(node, 0, i, this.toBBox);
                          bbox2 = distBBox(node, i, M, this.toBBox);
                          overlap = intersectionArea(bbox1, bbox2);
                          area = bboxArea(bbox1) + bboxArea(bbox2);
                          if (overlap < minOverlap) {
                              minOverlap = overlap;
                              index = i;
                              minArea = area < minArea ? area : minArea;
                          } else if (overlap === minOverlap) {
                              if (area < minArea) {
                                  minArea = area;
                                  index = i;
                              }
                          }
                      }
                      return index;
                  },
                  _chooseSplitAxis: function (node, m, M) {
                      var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX,
                          compareMinY = node.leaf ? this.compareMinY : compareNodeMinY,
                          xMargin = this._allDistMargin(node, m, M, compareMinX),
                          yMargin = this._allDistMargin(node, m, M, compareMinY);
                      if (xMargin < yMargin) node.children.sort(compareMinX);
                  },
                  _allDistMargin: function (node, m, M, compare) {
                      node.children.sort(compare);
                      var toBBox = this.toBBox,
                          leftBBox = distBBox(node, 0, m, toBBox),
                          rightBBox = distBBox(node, M - m, M, toBBox),
                          margin = bboxMargin(leftBBox) + bboxMargin(rightBBox),
                          i, child;
                      for (i = m; i < M - m; i++) {
                          child = node.children[i];
                          extend(leftBBox, node.leaf ? toBBox(child) : child);
                          margin += bboxMargin(leftBBox);
                      }
                      for (i = M - m - 1; i >= m; i--) {
                          child = node.children[i];
                          extend(rightBBox, node.leaf ? toBBox(child) : child);
                          margin += bboxMargin(rightBBox);
                      }
                      return margin;
                  },
                  _adjustParentBBoxes: function (bbox, path, level) {
                      for (var i = level; i >= 0; i--) {
                          extend(path[i], bbox);
                      }
                  },
                  _condense: function (path) {
                      for (var i = path.length - 1, siblings; i >= 0; i--) {
                          if (path[i].children.length === 0) {
                              if (i > 0) {
                                  siblings = path[i - 1].children;
                                  siblings.splice(siblings.indexOf(path[i]), 1);
                              } else this.clear();
                          } else calcBBox(path[i], this.toBBox);
                      }
                  },
                  _initFormat: function (format) {
                      var compareArr = ['return a', ' - b', ';'];
                      this.compareMinX = new Function('a', 'b', compareArr.join(format[0]));
                      this.compareMinY = new Function('a', 'b', compareArr.join(format[1]));
                      this.toBBox = new Function('a',
                          'return {minX: a' + format[0] +
                              ', minY: a' + format[1] +
                              ', maxX: a' + format[2] +
                              ', maxY: a' + format[3] + '};');
                  }
              };
              function findItem(item, items, equalsFn) {
                  if (!equalsFn) return items.indexOf(item);
                  for (var i = 0; i < items.length; i++) {
                      if (equalsFn(item, items[i])) return i;
                  }
                  return -1;
              }
              function calcBBox(node, toBBox) {
                  distBBox(node, 0, node.children.length, toBBox, node);
              }
              function distBBox(node, k, p, toBBox, destNode) {
                  if (!destNode) destNode = createNode(null);
                  destNode.minX = Infinity;
                  destNode.minY = Infinity;
                  destNode.maxX = -Infinity;
                  destNode.maxY = -Infinity;
                  for (var i = k, child; i < p; i++) {
                      child = node.children[i];
                      extend(destNode, node.leaf ? toBBox(child) : child);
                  }
                  return destNode;
              }
              function extend(a, b) {
                  a.minX = Math.min(a.minX, b.minX);
                  a.minY = Math.min(a.minY, b.minY);
                  a.maxX = Math.max(a.maxX, b.maxX);
                  a.maxY = Math.max(a.maxY, b.maxY);
                  return a;
              }
              function compareNodeMinX(a, b) { return a.minX - b.minX; }
              function compareNodeMinY(a, b) { return a.minY - b.minY; }
              function bboxArea(a)   { return (a.maxX - a.minX) * (a.maxY - a.minY); }
              function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
              function enlargedArea(a, b) {
                  return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
                      (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
              }
              function intersectionArea(a, b) {
                  var minX = Math.max(a.minX, b.minX),
                      minY = Math.max(a.minY, b.minY),
                      maxX = Math.min(a.maxX, b.maxX),
                      maxY = Math.min(a.maxY, b.maxY);
                  return Math.max(0, maxX - minX) *
                      Math.max(0, maxY - minY);
              }
              function contains(a, b) {
                  return a.minX <= b.minX &&
                      a.minY <= b.minY &&
                      b.maxX <= a.maxX &&
                      b.maxY <= a.maxY;
              }
              function intersects(a, b) {
                  return b.minX <= a.maxX &&
                      b.minY <= a.maxY &&
                      b.maxX >= a.minX &&
                      b.maxY >= a.minY;
              }
              function createNode(children) {
                  return {
                      children: children,
                      height: 1,
                      leaf: true,
                      minX: Infinity,
                      minY: Infinity,
                      maxX: -Infinity,
                      maxY: -Infinity
                  };
              }
              function multiSelect(arr, left, right, n, compare) {
                  var stack = [left, right],
                      mid;
                  while (stack.length) {
                      right = stack.pop();
                      left = stack.pop();
                      if (right - left <= n) continue;
                      mid = left + Math.ceil((right - left) / n / 2) * n;
                      quickselect_1(arr, mid, left, right, compare);
                      stack.push(left, mid, mid, right);
                  }
              }
  
              exports['default'] = rbush_1;
  
          }((this.rbush = this.rbush || {})));}).call(ol.ext);
          ol.ext.rbush = ol.ext.rbush.default;
  
          ol.render = {};
  
          /**
           * Context for drawing geometries.  A vector context is available on render
           * events and does not need to be constructed directly.
           * @constructor
           * @abstract
           * @struct
           * @api
           */
          ol.render.VectorContext = function() {
          };
  
          ol.render.VectorContext.prototype.drawCustom = function(geometry, feature, renderer) {};
          ol.render.VectorContext.prototype.drawGeometry = function(geometry) {};
          ol.render.VectorContext.prototype.setStyle = function(style) {};
          ol.render.VectorContext.prototype.drawCircle = function(circleGeometry, feature) {};
          ol.render.VectorContext.prototype.drawFeature = function(feature, style) {};
          ol.render.VectorContext.prototype.drawGeometryCollection = function(geometryCollectionGeometry, feature) {};
          ol.render.VectorContext.prototype.drawLineString = function(lineStringGeometry, feature) {};
          ol.render.VectorContext.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) {};
          ol.render.VectorContext.prototype.drawMultiPoint = function(multiPointGeometry, feature) {};
          ol.render.VectorContext.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {};
          ol.render.VectorContext.prototype.drawPoint = function(pointGeometry, feature) {};
          ol.render.VectorContext.prototype.drawPolygon = function(polygonGeometry, feature) {};
          ol.render.VectorContext.prototype.drawText = function(geometry, feature) {};
          ol.render.VectorContext.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {};
          ol.render.VectorContext.prototype.setImageStyle = function(imageStyle, opt_declutterGroup) {};
          ol.render.VectorContext.prototype.setTextStyle = function(textStyle, opt_declutterGroup) {};
  
          ol.render.ReplayGroup = {};
  
          ol.render.ReplayGroup = function() {};
  
          ol.render.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {};
  
          ol.render.ReplayGroup.prototype.isEmpty = function() {};
  
          ol.render.ReplayType = {
              CIRCLE: 'Circle',
              DEFAULT: 'Default',
              IMAGE: 'Image',
              LINE_STRING: 'LineString',
              POLYGON: 'Polygon',
              TEXT: 'Text'
          };
  
          ol.geom.flat.length = {};
  
          ol.geom.flat.length.lineString = function(flatCoordinates, offset, end, stride) {
              var x1 = flatCoordinates[offset];
              var y1 = flatCoordinates[offset + 1];
              var length = 0;
              var i;
              for (i = offset + stride; i < end; i += stride) {
                  var x2 = flatCoordinates[i];
                  var y2 = flatCoordinates[i + 1];
                  length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
                  x1 = x2;
                  y1 = y2;
              }
              return length;
          };
  
          ol.geom.flat.length.linearRing = function(flatCoordinates, offset, end, stride) {
              var perimeter =
                  ol.geom.flat.length.lineString(flatCoordinates, offset, end, stride);
              var dx = flatCoordinates[end - stride] - flatCoordinates[offset];
              var dy = flatCoordinates[end - stride + 1] - flatCoordinates[offset + 1];
              perimeter += Math.sqrt(dx * dx + dy * dy);
              return perimeter;
          };
  
          ol.geom.flat.textpath = {};
  
          ol.geom.flat.textpath.lineString = function(
              flatCoordinates, offset, end, stride, text, measure, startM, maxAngle) {
              var result = [];
  
              // Keep text upright
              var reverse = flatCoordinates[offset] > flatCoordinates[end - stride];
  
              var numChars = text.length;
  
              var x1 = flatCoordinates[offset];
              var y1 = flatCoordinates[offset + 1];
              offset += stride;
              var x2 = flatCoordinates[offset];
              var y2 = flatCoordinates[offset + 1];
              var segmentM = 0;
              var segmentLength = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
  
              var chunk = '';
              var chunkLength = 0;
              var data, index, previousAngle;
              for (var i = 0; i < numChars; ++i) {
                  index = reverse ? numChars - i - 1 : i;
                  var char = text.charAt(index);
                  chunk = reverse ? char + chunk : chunk + char;
                  var charLength = measure(chunk) - chunkLength;
                  chunkLength += charLength;
                  var charM = startM + charLength / 2;
                  while (offset < end - stride && segmentM + segmentLength < charM) {
                      x1 = x2;
                      y1 = y2;
                      offset += stride;
                      x2 = flatCoordinates[offset];
                      y2 = flatCoordinates[offset + 1];
                      segmentM += segmentLength;
                      segmentLength = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
                  }
                  var segmentPos = charM - segmentM;
                  var angle = Math.atan2(y2 - y1, x2 - x1);
                  if (reverse) {
                      angle += angle > 0 ? -Math.PI : Math.PI;
                  }
                  if (previousAngle !== undefined) {
                      var delta = angle - previousAngle;
                      delta += (delta > Math.PI) ? -2 * Math.PI : (delta < -Math.PI) ? 2 * Math.PI : 0;
                      if (Math.abs(delta) > maxAngle) {
                          return null;
                      }
                  }
                  var interpolate = segmentPos / segmentLength;
                  var x = ol.math.lerp(x1, x2, interpolate);
                  var y = ol.math.lerp(y1, y2, interpolate);
                  if (previousAngle == angle) {
                      if (reverse) {
                          data[0] = x;
                          data[1] = y;
                          data[2] = charLength / 2;
                      }
                      data[4] = chunk;
                  } else {
                      chunk = char;
                      chunkLength = charLength;
                      data = [x, y, charLength / 2, angle, chunk];
                      if (reverse) {
                          result.unshift(data);
                      } else {
                          result.push(data);
                      }
                      previousAngle = angle;
                  }
                  startM += charLength;
              }
              return result;
          };
  
  
          ol.structs.LRUCache = function(opt_highWaterMark) {
              ol.events.EventTarget.call(this);
              this.highWaterMark = opt_highWaterMark !== undefined ? opt_highWaterMark : 2048;
              this.count_ = 0;
              this.entries_ = {};
              this.oldest_ = null;
              this.newest_ = null;
          };
  
          ol.inherits(ol.structs.LRUCache, ol.events.EventTarget);
  
          ol.structs.LRUCache.prototype.canExpireCache = function() {
              return this.getCount() > this.highWaterMark;
          };
  
          ol.structs.LRUCache.prototype.clear = function() {
              this.count_ = 0;
              this.entries_ = {};
              this.oldest_ = null;
              this.newest_ = null;
              this.dispatchEvent(ol.events.EventType.CLEAR);
          };
  
          ol.structs.LRUCache.prototype.containsKey = function(key) {
              return this.entries_.hasOwnProperty(key);
          };
  
          ol.structs.LRUCache.prototype.forEach = function(f, opt_this) {
              var entry = this.oldest_;
              while (entry) {
                  f.call(opt_this, entry.value_, entry.key_, this);
                  entry = entry.newer;
              }
          };
  
          ol.structs.LRUCache.prototype.get = function(key) {
              var entry = this.entries_[key];
              ol.asserts.assert(entry !== undefined,
                  15); // Tried to get a value for a key that does not exist in the cache
              if (entry === this.newest_) {
                  return entry.value_;
              } else if (entry === this.oldest_) {
                  this.oldest_ = /** @type {ol.LRUCacheEntry} */ (this.oldest_.newer);
                  this.oldest_.older = null;
              } else {
                  entry.newer.older = entry.older;
                  entry.older.newer = entry.newer;
              }
              entry.newer = null;
              entry.older = this.newest_;
              this.newest_.newer = entry;
              this.newest_ = entry;
              return entry.value_;
          };
  
          ol.structs.LRUCache.prototype.remove = function(key) {
              var entry = this.entries_[key];
              ol.asserts.assert(entry !== undefined, 15); // Tried to get a value for a key that does not exist in the cache
              if (entry === this.newest_) {
                  this.newest_ = /** @type {ol.LRUCacheEntry} */ (entry.older);
                  if (this.newest_) {
                      this.newest_.newer = null;
                  }
              } else if (entry === this.oldest_) {
                  this.oldest_ = /** @type {ol.LRUCacheEntry} */ (entry.newer);
                  if (this.oldest_) {
                      this.oldest_.older = null;
                  }
              } else {
                  entry.newer.older = entry.older;
                  entry.older.newer = entry.newer;
              }
              delete this.entries_[key];
              --this.count_;
              return entry.value_;
          };
  
          ol.structs.LRUCache.prototype.getCount = function() {
              return this.count_;
          };
  
          ol.structs.LRUCache.prototype.getKeys = function() {
              var keys = new Array(this.count_);
              var i = 0;
              var entry;
              for (entry = this.newest_; entry; entry = entry.older) {
                  keys[i++] = entry.key_;
              }
              return keys;
          };
  
          ol.structs.LRUCache.prototype.getValues = function() {
              var values = new Array(this.count_);
              var i = 0;
              var entry;
              for (entry = this.newest_; entry; entry = entry.older) {
                  values[i++] = entry.value_;
              }
              return values;
          };
  
          ol.structs.LRUCache.prototype.peekLast = function() {
              return this.oldest_.value_;
          };
  
          ol.structs.LRUCache.prototype.peekLastKey = function() {
              return this.oldest_.key_;
          };
  
          ol.structs.LRUCache.prototype.peekFirstKey = function() {
              return this.newest_.key_;
          };
  
          ol.structs.LRUCache.prototype.pop = function() {
              var entry = this.oldest_;
              delete this.entries_[entry.key_];
              if (entry.newer) {
                  entry.newer.older = null;
              }
              this.oldest_ = /** @type {ol.LRUCacheEntry} */ (entry.newer);
              if (!this.oldest_) {
                  this.newest_ = null;
              }
              --this.count_;
              return entry.value_;
          };
  
          ol.structs.LRUCache.prototype.replace = function(key, value) {
              this.get(key);  // update `newest_`
              this.entries_[key].value_ = value;
          };
  
          ol.structs.LRUCache.prototype.set = function(key, value) {
              ol.asserts.assert(!(key in this.entries_),
                  16); // Tried to set a value for a key that is used already
              var entry = /** @type {ol.LRUCacheEntry} */ ({
                  key_: key,
                  newer: null,
                  older: this.newest_,
                  value_: value
              });
              if (!this.newest_) {
                  this.oldest_ = entry;
              } else {
                  this.newest_.newer = entry;
              }
              this.newest_ = entry;
              this.entries_[key] = entry;
              ++this.count_;
          };
  
          ol.structs.LRUCache.prototype.prune = function() {
              while (this.canExpireCache()) {
                  this.pop();
              }
          };
  
          ol.render.canvas = {};
          ol.render.canvas.defaultFont = '10px sans-serif';
          ol.render.canvas.defaultFillStyle = [0, 0, 0, 1];
          ol.render.canvas.defaultLineCap = 'round';
          ol.render.canvas.defaultLineDash = [];
          ol.render.canvas.defaultLineDashOffset = 0;
          ol.render.canvas.defaultLineJoin = 'round';
          ol.render.canvas.defaultMiterLimit = 10;
          ol.render.canvas.defaultStrokeStyle = [0, 0, 0, 1];
          ol.render.canvas.defaultTextAlign = 'center';
          ol.render.canvas.defaultTextBaseline = 'middle';
          ol.render.canvas.defaultPadding = [0, 0, 0, 0];
          ol.render.canvas.defaultLineWidth = 1;
          ol.render.canvas.labelCache = new ol.structs.LRUCache();
          ol.render.canvas.checkedFonts_ = {};
          ol.render.canvas.measureContext_ = null;
          ol.render.canvas.textHeights_ = {};
  
          /**
           * Clears the label cache when a font becomes available.
           * @param {string} fontSpec CSS font spec.
           */
          ol.render.canvas.checkFont = (function() {
              var retries = 60;
              var checked = ol.render.canvas.checkedFonts_;
              var labelCache = ol.render.canvas.labelCache;
              var font = '32px monospace';
              var text = 'wmytzilWMYTZIL@#/&?$%10';
              var interval, referenceWidth;
  
              function isAvailable(fontFamily) {
                  var context = ol.render.canvas.getMeasureContext();
                  context.font = font;
                  referenceWidth = context.measureText(text).width;
                  var available = true;
                  if (fontFamily != 'monospace') {
                      context.font = '32px ' + fontFamily + ',monospace';
                      var width = context.measureText(text).width;
                      // If width and referenceWidth are the same, then the 'monospace'
                      // fallback was used instead of the font we wanted, so the font is not
                      // available.
                      available = width != referenceWidth;
                  }
                  return available;
              }
  
              function check() {
                  var done = true;
                  for (var font in checked) {
                      if (checked[font] < retries) {
                          if (isAvailable(font)) {
                              checked[font] = retries;
                              ol.obj.clear(ol.render.canvas.textHeights_);
                              // Make sure that loaded fonts are picked up by Safari
                              ol.render.canvas.measureContext_ = null;
                              labelCache.clear();
                          } else {
                              ++checked[font];
                              done = false;
                          }
                      }
                  }
                  if (done) {
                      window.clearInterval(interval);
                      interval = undefined;
                  }
              }
  
              return function(fontSpec) {
                  var fontFamilies = ol.css.getFontFamilies(fontSpec);
                  if (!fontFamilies) {
                      return;
                  }
                  for (var i = 0, ii = fontFamilies.length; i < ii; ++i) {
                      var fontFamily = fontFamilies[i];
                      if (!(fontFamily in checked)) {
                          checked[fontFamily] = retries;
                          if (!isAvailable(fontFamily)) {
                              checked[fontFamily] = 0;
                              if (interval === undefined) {
                                  interval = window.setInterval(check, 32);
                              }
                          }
                      }
                  }
              };
          })();
  
  
          /**
           * @return {CanvasRenderingContext2D} Measure context.
           */
          ol.render.canvas.getMeasureContext = function() {
              var context = ol.render.canvas.measureContext_;
              if (!context) {
                  context = ol.render.canvas.measureContext_ = ol.dom.createCanvasContext2D(1, 1);
              }
              return context;
          };
  
  
          /**
           * @param {string} font Font to use for measuring.
           * @return {ol.Size} Measurement.
           */
          ol.render.canvas.measureTextHeight = (function() {
              var heights = ol.render.canvas.textHeights_;
              return function(font) {
                  var height = heights[font];
                  if (height == undefined) {
      //                if (!span) {
      //                    span = document.createElement('span');
      //                    span.textContent = 'M';
      //                    span.style.margin = span.style.padding = '0 !important';
      //                    span.style.position = 'absolute !important';
      //                    span.style.left = '-99999px !important';
      //                }
      //                span.style.font = font;
      //                document.body.appendChild(span);
      //                height = heights[font] = span.offsetHeight;
      //                document.body.removeChild(span);
                      height = heights[font] = 19; // FIXME sunyl
                  }
                  return height;
              };
          })();
  
  
          /**
           * @param {string} font Font.
           * @param {string} text Text.
           * @return {number} Width.
           */
          ol.render.canvas.measureTextWidth = function(font, text) {
              var measureContext = ol.render.canvas.getMeasureContext();
              if (font != measureContext.font) {
                  measureContext.font = font;
              }
              return measureContext.measureText(text).width;
          };
  
  
          /**
           * @param {CanvasRenderingContext2D} context Context.
           * @param {number} rotation Rotation.
           * @param {number} offsetX X offset.
           * @param {number} offsetY Y offset.
           */
          ol.render.canvas.rotateAtOffset = function(context, rotation, offsetX, offsetY) {
              if (rotation !== 0) {
                  context.translate(offsetX, offsetY);
                  context.rotate(rotation);
                  context.translate(-offsetX, -offsetY);
              }
          };
  
  
          ol.render.canvas.resetTransform_ = ol.transform.create();
  
  
          /**
           * @param {CanvasRenderingContext2D} context Context.
           * @param {ol.Transform|null} transform Transform.
           * @param {number} opacity Opacity.
           * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image.
           * @param {number} originX Origin X.
           * @param {number} originY Origin Y.
           * @param {number} w Width.
           * @param {number} h Height.
           * @param {number} x X.
           * @param {number} y Y.
           * @param {number} scale Scale.
           */
          ol.render.canvas.drawImage = function(context,
                                                transform, opacity, image, originX, originY, w, h, x, y, scale) {
              var alpha;
              if (opacity != 1) {
                  alpha = context.globalAlpha;
                  context.globalAlpha = alpha * opacity;
              }
              if (transform) {
                  context.setTransform.apply(context, transform);
              }
  
              context.drawImage(image, originX, originY, w, h, x, y, w * scale, h * scale);
  
              if (alpha) {
                  context.globalAlpha = alpha;
              }
              if (transform) {
                  context.setTransform.apply(context, ol.render.canvas.resetTransform_);
              }
          };
  
  
          /**
           * @enum {number}
           */
          ol.render.canvas.Instruction = {
              BEGIN_GEOMETRY: 0,
              BEGIN_PATH: 1,
              CIRCLE: 2,
              CLOSE_PATH: 3,
              CUSTOM: 4,
              DRAW_CHARS: 5,
              DRAW_IMAGE: 6,
              END_GEOMETRY: 7,
              FILL: 8,
              MOVE_TO_LINE_TO: 9,
              SET_FILL_STYLE: 10,
              SET_STROKE_STYLE: 11,
              STROKE: 12
          };
  
          ol.render.replay = {};
  
          ol.render.replay.ORDER = [
              ol.render.ReplayType.POLYGON,
              ol.render.ReplayType.CIRCLE,
              ol.render.ReplayType.LINE_STRING,
              ol.render.ReplayType.IMAGE,
              ol.render.ReplayType.TEXT,
              ol.render.ReplayType.DEFAULT
          ];
  
          ol.render.replay.TEXT_ALIGN = {};
          ol.render.replay.TEXT_ALIGN['left'] = 0;
          ol.render.replay.TEXT_ALIGN['end'] = 0;
          ol.render.replay.TEXT_ALIGN['center'] = 0.5;
          ol.render.replay.TEXT_ALIGN['right'] = 1;
          ol.render.replay.TEXT_ALIGN['start'] = 1;
          ol.render.replay.TEXT_ALIGN['top'] = 0;
          ol.render.replay.TEXT_ALIGN['middle'] = 0.5;
          ol.render.replay.TEXT_ALIGN['hanging'] = 0.2;
          ol.render.replay.TEXT_ALIGN['alphabetic'] = 0.8;
          ol.render.replay.TEXT_ALIGN['ideographic'] = 0.8;
          ol.render.replay.TEXT_ALIGN['bottom'] = 1;
  
  
          ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
              ol.render.VectorContext.call(this);
  
              /**
               * @type {?}
               */
              this.declutterTree = declutterTree;
  
              /**
               * @private
               * @type {ol.Extent}
               */
              this.tmpExtent_ = ol.extent.createEmpty();
  
              /**
               * @protected
               * @type {number}
               */
              this.tolerance = tolerance;
  
              /**
               * @protected
               * @const
               * @type {ol.Extent}
               */
              this.maxExtent = maxExtent;
  
              /**
               * @protected
               * @type {boolean}
               */
              this.overlaps = overlaps;
  
              /**
               * @protected
               * @type {number}
               */
              this.pixelRatio = pixelRatio;
  
              /**
               * @protected
               * @type {number}
               */
              this.maxLineWidth = 0;
  
              /**
               * @protected
               * @const
               * @type {number}
               */
              this.resolution = resolution;
  
              /**
               * @private
               * @type {ol.Coordinate}
               */
              this.fillOrigin_;
  
              /**
               * @private
               * @type {Array.<*>}
               */
              this.beginGeometryInstruction1_ = null;
  
              /**
               * @private
               * @type {Array.<*>}
               */
              this.beginGeometryInstruction2_ = null;
  
              /**
               * @private
               * @type {ol.Extent}
               */
              this.bufferedMaxExtent_ = null;
  
              /**
               * @protected
               * @type {Array.<*>}
               */
              this.instructions = [];
  
              /**
               * @protected
               * @type {Array.<number>}
               */
              this.coordinates = [];
  
              /**
               * @private
               * @type {Object.<number,ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>>}
               */
              this.coordinateCache_ = {};
  
              /**
               * @private
               * @type {!ol.Transform}
               */
              this.renderedTransform_ = ol.transform.create();
  
              /**
               * @protected
               * @type {Array.<*>}
               */
              this.hitDetectionInstructions = [];
  
              /**
               * @private
               * @type {Array.<number>}
               */
              this.pixelCoordinates_ = null;
  
              /**
               * @protected
               * @type {ol.CanvasFillStrokeState}
               */
              this.state = /** @type {ol.CanvasFillStrokeState} */ ({});
  
              /**
               * @private
               * @type {number}
               */
              this.viewRotation_ = 0;
  
              /**
               * @private
               * @type {!ol.Transform}
               */
              this.tmpLocalTransform_ = ol.transform.create();
  
              /**
               * @private
               * @type {!ol.Transform}
               */
              this.resetTransform_ = ol.transform.create();
          };
          ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext);
  
  
          /**
           * @param {CanvasRenderingContext2D} context Context.
           * @param {ol.Coordinate} p1 1st point of the background box.
           * @param {ol.Coordinate} p2 2nd point of the background box.
           * @param {ol.Coordinate} p3 3rd point of the background box.
           * @param {ol.Coordinate} p4 4th point of the background box.
           * @param {Array.<*>} fillInstruction Fill instruction.
           * @param {Array.<*>} strokeInstruction Stroke instruction.
           */
          ol.render.canvas.Replay.prototype.replayTextBackground_ = function(context, p1, p2, p3, p4,
                                                                             fillInstruction, strokeInstruction) {
              context.beginPath();
              context.moveTo.apply(context, p1);
              context.lineTo.apply(context, p2);
              context.lineTo.apply(context, p3);
              context.lineTo.apply(context, p4);
              context.lineTo.apply(context, p1);
              if (fillInstruction) {
                  this.fillOrigin_ = /** @type {Array.<number>} */ (fillInstruction[2]);
                  this.fill_(context);
              }
              if (strokeInstruction) {
                  this.setStrokeStyle_(context, /** @type {Array.<*>} */ (strokeInstruction));
                  context.stroke();
              }
          };
  
  
          /**
           * @param {CanvasRenderingContext2D} context Context.
           * @param {number} x X.
           * @param {number} y Y.
           * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image.
           * @param {number} anchorX Anchor X.
           * @param {number} anchorY Anchor Y.
           * @param {ol.DeclutterGroup} declutterGroup Declutter group.
           * @param {number} height Height.
           * @param {number} opacity Opacity.
           * @param {number} originX Origin X.
           * @param {number} originY Origin Y.
           * @param {number} rotation Rotation.
           * @param {number} scale Scale.
           * @param {boolean} snapToPixel Snap to pixel.
           * @param {number} width Width.
           * @param {Array.<number>} padding Padding.
           * @param {Array.<*>} fillInstruction Fill instruction.
           * @param {Array.<*>} strokeInstruction Stroke instruction.
           */
          ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image,
                                                                    anchorX, anchorY, declutterGroup, height, opacity, originX, originY,
                                                                    rotation, scale, snapToPixel, width, padding, fillInstruction, strokeInstruction) {
              var fillStroke = fillInstruction || strokeInstruction;
              var localTransform = this.tmpLocalTransform_;
              anchorX *= scale;
              anchorY *= scale;
              x -= anchorX;
              y -= anchorY;
              if (snapToPixel) {
                  x = Math.round(x);
                  y = Math.round(y);
              }
  
              var w = (width + originX > image.width) ? image.width - originX : width;
              var h = (height + originY > image.height) ? image.height - originY : height;
              var box = this.tmpExtent_;
              var boxW = padding[3] + w * scale + padding[1];
              var boxH = padding[0] + h * scale + padding[2];
              var boxX = x - padding[3];
              var boxY = y - padding[0];
  
              /** @type {ol.Coordinate} */
              var p1;
              /** @type {ol.Coordinate} */
              var p2;
              /** @type {ol.Coordinate} */
              var p3;
              /** @type {ol.Coordinate} */
              var p4;
              if (fillStroke || rotation !== 0) {
                  p1 = [boxX, boxY];
                  p2 = [boxX + boxW, boxY];
                  p3 = [boxX + boxW, boxY + boxH];
                  p4 = [boxX, boxY + boxH];
              }
  
              var transform = null;
              if (rotation !== 0) {
                  var centerX = x + anchorX;
                  var centerY = y + anchorY;
                  transform = ol.transform.compose(localTransform,
                      centerX, centerY, 1, 1, rotation, -centerX, -centerY);
  
                  ol.extent.createOrUpdateEmpty(box);
                  ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, p1));
                  ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, p2));
                  ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, p3));
                  ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, p4));
              } else {
                  ol.extent.createOrUpdate(boxX, boxY, boxX + boxW, boxY + boxH, box);
              }
              var canvas = context.canvas;
              var intersects = box[0] <= canvas.width && box[2] >= 0 && box[1] <= canvas.height && box[3] >= 0;
              if (declutterGroup) {
                  if (!intersects && declutterGroup[4] == 1) {
                      return;
                  }
                  ol.extent.extend(declutterGroup, box);
                  var declutterArgs = intersects ?
                      [context, transform ? transform.slice(0) : null, opacity, image, originX, originY, w, h, x, y, scale] :
                      null;
                  if (declutterArgs && fillStroke) {
                      declutterArgs.push(fillInstruction, strokeInstruction, p1, p2, p3, p4);
                  }
                  declutterGroup.push(declutterArgs);
              } else if (intersects) {
                  if (fillStroke) {
                      this.replayTextBackground_(context, p1, p2, p3, p4,
                          /** @type {Array.<*>} */ (fillInstruction),
                          /** @type {Array.<*>} */ (strokeInstruction));
                  }
                  ol.render.canvas.drawImage(context, transform, opacity, image, originX, originY, w, h, x, y, scale);
              }
          };
  
  
          /**
           * @protected
           * @param {Array.<number>} dashArray Dash array.
           * @return {Array.<number>} Dash array with pixel ratio applied
           */
          ol.render.canvas.Replay.prototype.applyPixelRatio = function(dashArray) {
              var pixelRatio = this.pixelRatio;
              return pixelRatio == 1 ? dashArray : dashArray.map(function(dash) {
                  return dash * pixelRatio;
              });
          };
  
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {number} end End.
           * @param {number} stride Stride.
           * @param {boolean} closed Last input coordinate equals first.
           * @param {boolean} skipFirst Skip first coordinate.
           * @protected
           * @return {number} My end.
           */
          ol.render.canvas.Replay.prototype.appendFlatCoordinates = function(flatCoordinates, offset, end, stride, closed, skipFirst) {
  
              var myEnd = this.coordinates.length;
              var extent = this.getBufferedMaxExtent();
              if (skipFirst) {
                  offset += stride;
              }
              var lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]];
              var nextCoord = [NaN, NaN];
              var skipped = true;
  
              var i, lastRel, nextRel;
              for (i = offset + stride; i < end; i += stride) {
                  nextCoord[0] = flatCoordinates[i];
                  nextCoord[1] = flatCoordinates[i + 1];
                  nextRel = ol.extent.coordinateRelationship(extent, nextCoord);
                  if (nextRel !== lastRel) {
                      if (skipped) {
                          this.coordinates[myEnd++] = lastCoord[0];
                          this.coordinates[myEnd++] = lastCoord[1];
                      }
                      this.coordinates[myEnd++] = nextCoord[0];
                      this.coordinates[myEnd++] = nextCoord[1];
                      skipped = false;
                  } else if (nextRel === ol.extent.Relationship.INTERSECTING) {
                      this.coordinates[myEnd++] = nextCoord[0];
                      this.coordinates[myEnd++] = nextCoord[1];
                      skipped = false;
                  } else {
                      skipped = true;
                  }
                  lastCoord[0] = nextCoord[0];
                  lastCoord[1] = nextCoord[1];
                  lastRel = nextRel;
              }
  
              // Last coordinate equals first or only one point to append:
              if ((closed && skipped) || i === offset + stride) {
                  this.coordinates[myEnd++] = lastCoord[0];
                  this.coordinates[myEnd++] = lastCoord[1];
              }
              return myEnd;
          };
  
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {Array.<number>} ends Ends.
           * @param {number} stride Stride.
           * @param {Array.<number>} replayEnds Replay ends.
           * @return {number} Offset.
           */
          ol.render.canvas.Replay.prototype.drawCustomCoordinates_ = function(flatCoordinates, offset, ends, stride, replayEnds) {
              for (var i = 0, ii = ends.length; i < ii; ++i) {
                  var end = ends[i];
                  var replayEnd = this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false, false);
                  replayEnds.push(replayEnd);
                  offset = end;
              }
              return offset;
          };
  
  
          /**
           * @inheritDoc.
           */
          ol.render.canvas.Replay.prototype.drawCustom = function(geometry, feature, renderer) {
              this.beginGeometry(geometry, feature);
              var type = geometry.getType();
              var stride = geometry.getStride();
              var replayBegin = this.coordinates.length;
              var flatCoordinates, replayEnd, replayEnds, replayEndss;
              var offset;
              if (type == ol.geom.GeometryType.MULTI_POLYGON) {
                  geometry = /** @type {ol.geom.MultiPolygon} */ (geometry);
                  flatCoordinates = geometry.getOrientedFlatCoordinates();
                  replayEndss = [];
                  var endss = geometry.getEndss();
                  offset = 0;
                  for (var i = 0, ii = endss.length; i < ii; ++i) {
                      var myEnds = [];
                      offset = this.drawCustomCoordinates_(flatCoordinates, offset, endss[i], stride, myEnds);
                      replayEndss.push(myEnds);
                  }
                  this.instructions.push([ol.render.canvas.Instruction.CUSTOM,
                      replayBegin, replayEndss, geometry, renderer, ol.geom.flat.inflate.coordinatesss]);
              } else if (type == ol.geom.GeometryType.POLYGON || type == ol.geom.GeometryType.MULTI_LINE_STRING) {
                  replayEnds = [];
                  flatCoordinates = (type == ol.geom.GeometryType.POLYGON) ?
                  /** @type {ol.geom.Polygon} */ (geometry).getOrientedFlatCoordinates() :
                      geometry.getFlatCoordinates();
                  offset = this.drawCustomCoordinates_(flatCoordinates, 0,
                      /** @type {ol.geom.Polygon|ol.geom.MultiLineString} */ (geometry).getEnds(),
                      stride, replayEnds);
                  this.instructions.push([ol.render.canvas.Instruction.CUSTOM,
                      replayBegin, replayEnds, geometry, renderer, ol.geom.flat.inflate.coordinatess]);
              } else if (type == ol.geom.GeometryType.LINE_STRING || type == ol.geom.GeometryType.MULTI_POINT) {
                  flatCoordinates = geometry.getFlatCoordinates();
                  replayEnd = this.appendFlatCoordinates(
                      flatCoordinates, 0, flatCoordinates.length, stride, false, false);
                  this.instructions.push([ol.render.canvas.Instruction.CUSTOM,
                      replayBegin, replayEnd, geometry, renderer, ol.geom.flat.inflate.coordinates]);
              } else if (type == ol.geom.GeometryType.POINT) {
                  flatCoordinates = geometry.getFlatCoordinates();
                  this.coordinates.push(flatCoordinates[0], flatCoordinates[1]);
                  replayEnd = this.coordinates.length;
                  this.instructions.push([ol.render.canvas.Instruction.CUSTOM,
                      replayBegin, replayEnd, geometry, renderer]);
              }
              this.endGeometry(geometry, feature);
          };
  
  
          /**
           * @protected
           * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
           * @param {ol.Feature|ol.render.Feature} feature Feature.
           */
          ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) {
              this.beginGeometryInstruction1_ =
                  [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0];
              this.instructions.push(this.beginGeometryInstruction1_);
              this.beginGeometryInstruction2_ =
                  [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0];
              this.hitDetectionInstructions.push(this.beginGeometryInstruction2_);
          };
  
  
          /**
           * @private
           * @param {CanvasRenderingContext2D} context Context.
           */
          ol.render.canvas.Replay.prototype.fill_ = function(context) {
              if (this.fillOrigin_) {
                  var origin = ol.transform.apply(this.renderedTransform_, this.fillOrigin_.slice());
                  context.translate(origin[0], origin[1]);
                  context.rotate(this.viewRotation_);
              }
              context.fill();
              if (this.fillOrigin_) {
                  context.setTransform.apply(context, ol.render.canvas.resetTransform_);
              }
          };
  
  
          /**
           * @private
           * @param {CanvasRenderingContext2D} context Context.
           * @param {Array.<*>} instruction Instruction.
           */
          ol.render.canvas.Replay.prototype.setStrokeStyle_ = function(context, instruction) {
              context.strokeStyle = /** @type {ol.ColorLike} */ (instruction[1]);
              context.lineWidth = /** @type {number} */ (instruction[2]);
              context.lineCap = /** @type {string} */ (instruction[3]);
              context.lineJoin = /** @type {string} */ (instruction[4]);
              context.miterLimit = /** @type {number} */ (instruction[5]);
              if (ol.has.CANVAS_LINE_DASH) {
                  context.lineDashOffset = /** @type {number} */ (instruction[7]);
                  context.setLineDash(/** @type {Array.<number>} */ (instruction[6]));
              }
          };
  
  
          /**
           * @param {ol.DeclutterGroup} declutterGroup Declutter group.
           * @param {ol.Feature|ol.render.Feature} feature Feature.
           */
          ol.render.canvas.Replay.prototype.renderDeclutter_ = function(declutterGroup, feature) {
              if (declutterGroup && declutterGroup.length > 5) {
                  var groupCount = declutterGroup[4];
                  if (groupCount == 1 || groupCount == declutterGroup.length - 5) {
                      /** @type {ol.RBushEntry} */
                      var box = {
                          minX: /** @type {number} */ (declutterGroup[0]),
                          minY: /** @type {number} */ (declutterGroup[1]),
                          maxX: /** @type {number} */ (declutterGroup[2]),
                          maxY: /** @type {number} */ (declutterGroup[3]),
                          value: feature
                      };
                      if (!this.declutterTree.collides(box)) {
                          this.declutterTree.insert(box);
                          var drawImage = ol.render.canvas.drawImage;
                          for (var j = 5, jj = declutterGroup.length; j < jj; ++j) {
                              var declutterData = /** @type {Array} */ (declutterGroup[j]);
                              if (declutterData) {
                                  if (declutterData.length > 11) {
                                      this.replayTextBackground_(declutterData[0],
                                          declutterData[13], declutterData[14], declutterData[15], declutterData[16],
                                          declutterData[11], declutterData[12]);
                                  }
                                  drawImage.apply(undefined, declutterData);
                              }
                          }
                      }
                      declutterGroup.length = 5;
                      ol.extent.createOrUpdateEmpty(declutterGroup);
                  }
              }
          };
  
  
          /**
           * @private
           * @param {CanvasRenderingContext2D} context Context.
           * @param {ol.Transform} transform Transform.
           * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
           *     to skip.
           * @param {Array.<*>} instructions Instructions array.
           * @param {function((ol.Feature|ol.render.Feature)): T|undefined}
           *     featureCallback Feature callback.
           * @param {ol.Extent=} opt_hitExtent Only check features that intersect this
           *     extent.
           * @return {T|undefined} Callback result.
           * @template T
           */
          ol.render.canvas.Replay.prototype.replay_ = function(
              context, transform, skippedFeaturesHash,
              instructions, featureCallback, opt_hitExtent) {
              /** @type {Array.<number>} */
              var pixelCoordinates;
              if (this.pixelCoordinates_ && ol.array.equals(transform, this.renderedTransform_)) {
                  pixelCoordinates = this.pixelCoordinates_;
              } else {
                  if (!this.pixelCoordinates_) {
                      this.pixelCoordinates_ = [];
                  }
                  pixelCoordinates = ol.geom.flat.transform.transform2D(
                      this.coordinates, 0, this.coordinates.length, 2,
                      transform, this.pixelCoordinates_);
                  ol.transform.setFromArray(this.renderedTransform_, transform);
              }
              var skipFeatures = !ol.obj.isEmpty(skippedFeaturesHash);
              var i = 0; // instruction index
              var ii = instructions.length; // end of instructions
              var d = 0; // data index
              var dd; // end of per-instruction data
              var anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup, image;
              var pendingFill = 0;
              var pendingStroke = 0;
              var lastFillInstruction = null;
              var lastStrokeInstruction = null;
              var coordinateCache = this.coordinateCache_;
              var viewRotation = this.viewRotation_;
  
              var state = /** @type {olx.render.State} */ ({
                  context: context,
                  pixelRatio: this.pixelRatio,
                  resolution: this.resolution,
                  rotation: viewRotation
              });
  
              // When the batch size gets too big, performance decreases. 200 is a good
              // balance between batch size and number of fill/stroke instructions.
              var batchSize =
                  this.instructions != instructions || this.overlaps ? 0 : 200;
              while (i < ii) {
                  var instruction = instructions[i];
                  var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
                  var /** @type {ol.Feature|ol.render.Feature} */ feature, x, y;
                  switch (type) {
                      case ol.render.canvas.Instruction.BEGIN_GEOMETRY:
                          feature = /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]);
                          if ((skipFeatures &&
                              skippedFeaturesHash[ol.getUid(feature).toString()]) ||
                              !feature.getGeometry()) {
                              i = /** @type {number} */ (instruction[2]);
                          } else if (opt_hitExtent !== undefined && !ol.extent.intersects(
                              opt_hitExtent, feature.getGeometry().getExtent())) {
                              i = /** @type {number} */ (instruction[2]) + 1;
                          } else {
                              ++i;
                          }
                          break;
                      case ol.render.canvas.Instruction.BEGIN_PATH:
                          if (pendingFill > batchSize) {
                              this.fill_(context);
                              pendingFill = 0;
                          }
                          if (pendingStroke > batchSize) {
                              context.stroke();
                              pendingStroke = 0;
                          }
                          if (!pendingFill && !pendingStroke) {
                              context.beginPath();
                              prevX = prevY = NaN;
                          }
                          ++i;
                          break;
                      case ol.render.canvas.Instruction.CIRCLE:
                          d = /** @type {number} */ (instruction[1]);
                          var x1 = pixelCoordinates[d];
                          var y1 = pixelCoordinates[d + 1];
                          var x2 = pixelCoordinates[d + 2];
                          var y2 = pixelCoordinates[d + 3];
                          var dx = x2 - x1;
                          var dy = y2 - y1;
                          var r = Math.sqrt(dx * dx + dy * dy);
                          context.moveTo(x1 + r, y1);
                          context.arc(x1, y1, r, 0, 2 * Math.PI, true);
                          ++i;
                          break;
                      case ol.render.canvas.Instruction.CLOSE_PATH:
                          context.closePath();
                          ++i;
                          break;
                      case ol.render.canvas.Instruction.CUSTOM:
                          d = /** @type {number} */ (instruction[1]);
                          dd = instruction[2];
                          var geometry = /** @type {ol.geom.SimpleGeometry} */ (instruction[3]);
                          var renderer = instruction[4];
                          var fn = instruction.length == 6 ? instruction[5] : undefined;
                          state.geometry = geometry;
                          state.feature = feature;
                          if (!(i in coordinateCache)) {
                              coordinateCache[i] = [];
                          }
                          var coords = coordinateCache[i];
                          if (fn) {
                              fn(pixelCoordinates, d, dd, 2, coords);
                          } else {
                              coords[0] = pixelCoordinates[d];
                              coords[1] = pixelCoordinates[d + 1];
                              coords.length = 2;
                          }
                          renderer(coords, state);
                          ++i;
                          break;
                      case ol.render.canvas.Instruction.DRAW_IMAGE:
                          d = /** @type {number} */ (instruction[1]);
                          dd = /** @type {number} */ (instruction[2]);
                          image =  /** @type {HTMLCanvasElement|HTMLVideoElement|Image} */
                              (instruction[3]);
                          // Remaining arguments in DRAW_IMAGE are in alphabetical order
                          anchorX = /** @type {number} */ (instruction[4]);
                          anchorY = /** @type {number} */ (instruction[5]);
                          declutterGroup = featureCallback ? null : /** @type {ol.DeclutterGroup} */ (instruction[6]);
                          var height = /** @type {number} */ (instruction[7]);
                          var opacity = /** @type {number} */ (instruction[8]);
                          var originX = /** @type {number} */ (instruction[9]);
                          var originY = /** @type {number} */ (instruction[10]);
                          var rotateWithView = /** @type {boolean} */ (instruction[11]);
                          var rotation = /** @type {number} */ (instruction[12]);
                          var scale = /** @type {number} */ (instruction[13]);
                          var snapToPixel = /** @type {boolean} */ (instruction[14]);
                          var width = /** @type {number} */ (instruction[15]);
  
                          var padding, backgroundFill, backgroundStroke;
                          if (instruction.length > 16) {
                              padding = /** @type {Array.<number>} */ (instruction[16]);
                              backgroundFill = /** @type {boolean} */ (instruction[17]);
                              backgroundStroke = /** @type {boolean} */ (instruction[18]);
                          } else {
                              padding = ol.render.canvas.defaultPadding;
                              backgroundFill = backgroundStroke = false;
                          }
  
                          if (rotateWithView) {
                              rotation += viewRotation;
                          }
                          for (; d < dd; d += 2) {
                              this.replayImage_(context,
                                  pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY,
                                  declutterGroup, height, opacity, originX, originY, rotation, scale,
                                  snapToPixel, width, padding,
                                  backgroundFill ? /** @type {Array.<*>} */ (lastFillInstruction) : null,
                                  backgroundStroke ? /** @type {Array.<*>} */ (lastStrokeInstruction) : null);
                          }
                          this.renderDeclutter_(declutterGroup, feature);
                          ++i;
                          break;
                      case ol.render.canvas.Instruction.DRAW_CHARS:
                          var begin = /** @type {number} */ (instruction[1]);
                          var end = /** @type {number} */ (instruction[2]);
                          var baseline = /** @type {number} */ (instruction[3]);
                          declutterGroup = featureCallback ? null : /** @type {ol.DeclutterGroup} */ (instruction[4]);
                          var overflow = /** @type {number} */ (instruction[5]);
                          var fillKey = /** @type {string} */ (instruction[6]);
                          var maxAngle = /** @type {number} */ (instruction[7]);
                          var measure = /** @type {function(string):number} */ (instruction[8]);
                          var offsetY = /** @type {number} */ (instruction[9]);
                          var strokeKey = /** @type {string} */ (instruction[10]);
                          var strokeWidth =  /** @type {number} */ (instruction[11]);
                          var text = /** @type {string} */ (instruction[12]);
                          var textKey = /** @type {string} */ (instruction[13]);
                          var textScale = /** @type {number} */ (instruction[14]);
  
                          var pathLength = ol.geom.flat.length.lineString(pixelCoordinates, begin, end, 2);
                          var textLength = measure(text);
                          if (overflow || textLength <= pathLength) {
                              var textAlign = /** @type {ol.render.canvas.TextReplay} */ (this).textStates[textKey].textAlign;
                              var startM = (pathLength - textLength) * ol.render.replay.TEXT_ALIGN[textAlign];
                              var parts = ol.geom.flat.textpath.lineString(
                                  pixelCoordinates, begin, end, 2, text, measure, startM, maxAngle);
                              if (parts) {
                                  var c, cc, chars, label, part;
                                  if (strokeKey) {
                                      for (c = 0, cc = parts.length; c < cc; ++c) {
                                          part = parts[c]; // x, y, anchorX, rotation, chunk
                                          chars = /** @type {string} */ (part[4]);
                                          label = /** @type {ol.render.canvas.TextReplay} */ (this).getImage(chars, textKey, '', strokeKey);
                                          anchorX = /** @type {number} */ (part[2]) + strokeWidth;
                                          anchorY = baseline * label.height + (0.5 - baseline) * 2 * strokeWidth - offsetY;
                                          this.replayImage_(context,
                                              /** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label,
                                              anchorX, anchorY, declutterGroup, label.height, 1, 0, 0,
                                              /** @type {number} */ (part[3]), textScale, false, label.width,
                                              ol.render.canvas.defaultPadding, null, null);
                                      }
                                  }
                                  if (fillKey) {
                                      for (c = 0, cc = parts.length; c < cc; ++c) {
                                          part = parts[c]; // x, y, anchorX, rotation, chunk
                                          chars = /** @type {string} */ (part[4]);
                                          label = /** @type {ol.render.canvas.TextReplay} */ (this).getImage(chars, textKey, fillKey, '');
                                          anchorX = /** @type {number} */ (part[2]);
                                          anchorY = baseline * label.height - offsetY;
                                          this.replayImage_(context,
                                              /** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label,
                                              anchorX, anchorY, declutterGroup, label.height, 1, 0, 0,
                                              /** @type {number} */ (part[3]), textScale, false, label.width,
                                              ol.render.canvas.defaultPadding, null, null);
                                      }
                                  }
                              }
                          }
                          this.renderDeclutter_(declutterGroup, feature);
                          ++i;
                          break;
                      case ol.render.canvas.Instruction.END_GEOMETRY:
                          if (featureCallback !== undefined) {
                              feature = /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]);
                              var result = featureCallback(feature);
                              if (result) {
                                  return result;
                              }
                          }
                          ++i;
                          break;
                      case ol.render.canvas.Instruction.FILL:
                          if (batchSize) {
                              pendingFill++;
                          } else {
                              this.fill_(context);
                          }
                          ++i;
                          break;
                      case ol.render.canvas.Instruction.MOVE_TO_LINE_TO:
                          d = /** @type {number} */ (instruction[1]);
                          dd = /** @type {number} */ (instruction[2]);
                          x = pixelCoordinates[d];
                          y = pixelCoordinates[d + 1];
                          roundX = (x + 0.5) | 0;
                          roundY = (y + 0.5) | 0;
                          if (roundX !== prevX || roundY !== prevY) {
                              context.moveTo(x, y);
                              prevX = roundX;
                              prevY = roundY;
                          }
                          for (d += 2; d < dd; d += 2) {
                              x = pixelCoordinates[d];
                              y = pixelCoordinates[d + 1];
                              roundX = (x + 0.5) | 0;
                              roundY = (y + 0.5) | 0;
                              if (d == dd - 2 || roundX !== prevX || roundY !== prevY) {
                                  context.lineTo(x, y);
                                  prevX = roundX;
                                  prevY = roundY;
                              }
                          }
                          ++i;
                          break;
                      case ol.render.canvas.Instruction.SET_FILL_STYLE:
                          lastFillInstruction = instruction;
                          this.fillOrigin_ = instruction[2];
  
                          if (pendingFill) {
                              this.fill_(context);
                              pendingFill = 0;
                              if (pendingStroke) {
                                  context.stroke();
                                  pendingStroke = 0;
                              }
                          }
  
                          context.fillStyle = /** @type {ol.ColorLike} */ (instruction[1]);
                          ++i;
                          break;
                      case ol.render.canvas.Instruction.SET_STROKE_STYLE:
                          lastStrokeInstruction = instruction;
                          if (pendingStroke) {
                              context.stroke();
                              pendingStroke = 0;
                          }
                          this.setStrokeStyle_(context, /** @type {Array.<*>} */ (instruction));
                          ++i;
                          break;
                      case ol.render.canvas.Instruction.STROKE:
                          if (batchSize) {
                              pendingStroke++;
                          } else {
                              context.stroke();
                          }
                          ++i;
                          break;
                      default:
                          ++i; // consume the instruction anyway, to avoid an infinite loop
                          break;
                  }
              }
              if (pendingFill) {
                  this.fill_(context);
              }
              if (pendingStroke) {
                  context.stroke();
              }
              return undefined;
          };
  
  
          /**
           * @param {CanvasRenderingContext2D} context Context.
           * @param {ol.Transform} transform Transform.
           * @param {number} viewRotation View rotation.
           * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
           *     to skip.
           */
          ol.render.canvas.Replay.prototype.replay = function(
              context, transform, viewRotation, skippedFeaturesHash) {
              this.viewRotation_ = viewRotation;
              this.replay_(context, transform,
                  skippedFeaturesHash, this.instructions, undefined, undefined);
          };
  
  
          /**
           * @param {CanvasRenderingContext2D} context Context.
           * @param {ol.Transform} transform Transform.
           * @param {number} viewRotation View rotation.
           * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
           *     to skip.
           * @param {function((ol.Feature|ol.render.Feature)): T=} opt_featureCallback
           *     Feature callback.
           * @param {ol.Extent=} opt_hitExtent Only check features that intersect this
           *     extent.
           * @return {T|undefined} Callback result.
           * @template T
           */
          ol.render.canvas.Replay.prototype.replayHitDetection = function(
              context, transform, viewRotation, skippedFeaturesHash,
              opt_featureCallback, opt_hitExtent) {
              this.viewRotation_ = viewRotation;
              return this.replay_(context, transform, skippedFeaturesHash,
                  this.hitDetectionInstructions, opt_featureCallback, opt_hitExtent);
          };
  
  
          /**
           * Reverse the hit detection instructions.
           */
          ol.render.canvas.Replay.prototype.reverseHitDetectionInstructions = function() {
              var hitDetectionInstructions = this.hitDetectionInstructions;
              // step 1 - reverse array
              hitDetectionInstructions.reverse();
              // step 2 - reverse instructions within geometry blocks
              var i;
              var n = hitDetectionInstructions.length;
              var instruction;
              var type;
              var begin = -1;
              for (i = 0; i < n; ++i) {
                  instruction = hitDetectionInstructions[i];
                  type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
                  if (type == ol.render.canvas.Instruction.END_GEOMETRY) {
                      begin = i;
                  } else if (type == ol.render.canvas.Instruction.BEGIN_GEOMETRY) {
                      instruction[2] = i;
                      ol.array.reverseSubArray(this.hitDetectionInstructions, begin, i);
                      begin = -1;
                  }
              }
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.Replay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
              var state = this.state;
              if (fillStyle) {
                  var fillStyleColor = fillStyle.getColor();
                  state.fillStyle = ol.colorlike.asColorLike(fillStyleColor ?
                      fillStyleColor : ol.render.canvas.defaultFillStyle);
              } else {
                  state.fillStyle = undefined;
              }
              if (strokeStyle) {
                  var strokeStyleColor = strokeStyle.getColor();
                  state.strokeStyle = ol.colorlike.asColorLike(strokeStyleColor ?
                      strokeStyleColor : ol.render.canvas.defaultStrokeStyle);
                  var strokeStyleLineCap = strokeStyle.getLineCap();
                  state.lineCap = strokeStyleLineCap !== undefined ?
                      strokeStyleLineCap : ol.render.canvas.defaultLineCap;
                  var strokeStyleLineDash = strokeStyle.getLineDash();
                  state.lineDash = strokeStyleLineDash ?
                      strokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash;
                  var strokeStyleLineDashOffset = strokeStyle.getLineDashOffset();
                  state.lineDashOffset = strokeStyleLineDashOffset ?
                      strokeStyleLineDashOffset : ol.render.canvas.defaultLineDashOffset;
                  var strokeStyleLineJoin = strokeStyle.getLineJoin();
                  state.lineJoin = strokeStyleLineJoin !== undefined ?
                      strokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
                  var strokeStyleWidth = strokeStyle.getWidth();
                  state.lineWidth = strokeStyleWidth !== undefined ?
                      strokeStyleWidth : ol.render.canvas.defaultLineWidth;
                  var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
                  state.miterLimit = strokeStyleMiterLimit !== undefined ?
                      strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
  
                  if (state.lineWidth > this.maxLineWidth) {
                      this.maxLineWidth = state.lineWidth;
                      // invalidate the buffered max extent cache
                      this.bufferedMaxExtent_ = null;
                  }
              } else {
                  state.strokeStyle = undefined;
                  state.lineCap = undefined;
                  state.lineDash = null;
                  state.lineDashOffset = undefined;
                  state.lineJoin = undefined;
                  state.lineWidth = undefined;
                  state.miterLimit = undefined;
              }
          };
  
  
          /**
           * @param {ol.CanvasFillStrokeState} state State.
           * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
           */
          ol.render.canvas.Replay.prototype.applyFill = function(state, geometry) {
              var fillStyle = state.fillStyle;
              var fillInstruction = [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle];
              if (typeof fillStyle !== 'string') {
                  var fillExtent = geometry.getExtent();
                  fillInstruction.push([fillExtent[0], fillExtent[3]]);
              }
              this.instructions.push(fillInstruction);
          };
  
  
          /**
           * @param {ol.CanvasFillStrokeState} state State.
           */
          ol.render.canvas.Replay.prototype.applyStroke = function(state) {
              this.instructions.push([
                  ol.render.canvas.Instruction.SET_STROKE_STYLE,
                  state.strokeStyle, state.lineWidth * this.pixelRatio, state.lineCap,
                  state.lineJoin, state.miterLimit,
                  this.applyPixelRatio(state.lineDash), state.lineDashOffset * this.pixelRatio
              ]);
          };
  
  
          /**
           * @param {ol.CanvasFillStrokeState} state State.
           * @param {function(this:ol.render.canvas.Replay, ol.CanvasFillStrokeState, (ol.geom.Geometry|ol.render.Feature))} applyFill Apply fill.
           * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
           */
          ol.render.canvas.Replay.prototype.updateFillStyle = function(state, applyFill, geometry) {
              var fillStyle = state.fillStyle;
              if (typeof fillStyle !== 'string' || state.currentFillStyle != fillStyle) {
                  applyFill.call(this, state, geometry);
                  state.currentFillStyle = fillStyle;
              }
          };
  
  
          /**
           * @param {ol.CanvasFillStrokeState} state State.
           * @param {function(this:ol.render.canvas.Replay, ol.CanvasFillStrokeState)} applyStroke Apply stroke.
           */
          ol.render.canvas.Replay.prototype.updateStrokeStyle = function(state, applyStroke) {
              var strokeStyle = state.strokeStyle;
              var lineCap = state.lineCap;
              var lineDash = state.lineDash;
              var lineDashOffset = state.lineDashOffset;
              var lineJoin = state.lineJoin;
              var lineWidth = state.lineWidth;
              var miterLimit = state.miterLimit;
              if (state.currentStrokeStyle != strokeStyle ||
                  state.currentLineCap != lineCap ||
                  (lineDash != state.currentLineDash && !ol.array.equals(state.currentLineDash, lineDash)) ||
                  state.currentLineDashOffset != lineDashOffset ||
                  state.currentLineJoin != lineJoin ||
                  state.currentLineWidth != lineWidth ||
                  state.currentMiterLimit != miterLimit) {
                  applyStroke.call(this, state);
                  state.currentStrokeStyle = strokeStyle;
                  state.currentLineCap = lineCap;
                  state.currentLineDash = lineDash;
                  state.currentLineDashOffset = lineDashOffset;
                  state.currentLineJoin = lineJoin;
                  state.currentLineWidth = lineWidth;
                  state.currentMiterLimit = miterLimit;
              }
          };
  
  
          /**
           * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
           * @param {ol.Feature|ol.render.Feature} feature Feature.
           */
          ol.render.canvas.Replay.prototype.endGeometry = function(geometry, feature) {
              this.beginGeometryInstruction1_[2] = this.instructions.length;
              this.beginGeometryInstruction1_ = null;
              this.beginGeometryInstruction2_[2] = this.hitDetectionInstructions.length;
              this.beginGeometryInstruction2_ = null;
              var endGeometryInstruction =
                  [ol.render.canvas.Instruction.END_GEOMETRY, feature];
              this.instructions.push(endGeometryInstruction);
              this.hitDetectionInstructions.push(endGeometryInstruction);
          };
  
  
          /**
           * FIXME empty description for jsdoc
           */
          ol.render.canvas.Replay.prototype.finish = ol.nullFunction;
  
  
          /**
           * Get the buffered rendering extent.  Rendering will be clipped to the extent
           * provided to the constructor.  To account for symbolizers that may intersect
           * this extent, we calculate a buffered extent (e.g. based on stroke width).
           * @return {ol.Extent} The buffered rendering extent.
           * @protected
           */
          ol.render.canvas.Replay.prototype.getBufferedMaxExtent = function() {
              if (!this.bufferedMaxExtent_) {
                  this.bufferedMaxExtent_ = ol.extent.clone(this.maxExtent);
                  if (this.maxLineWidth > 0) {
                      var width = this.resolution * (this.maxLineWidth + 1) / 2;
                      ol.extent.buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_);
                  }
              }
              return this.bufferedMaxExtent_;
          };
  
          /**
           * @constructor
           * @extends {ol.render.canvas.Replay}
           * @param {number} tolerance Tolerance.
           * @param {ol.Extent} maxExtent Maximum extent.
           * @param {number} resolution Resolution.
           * @param {number} pixelRatio Pixel ratio.
           * @param {boolean} overlaps The replay can have overlapping geometries.
           * @param {?} declutterTree Declutter tree.
           * @struct
           */
          ol.render.canvas.ImageReplay = function(
              tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
              ol.render.canvas.Replay.call(this,
                  tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree);
  
              /**
               * @private
               * @type {ol.DeclutterGroup}
               */
              this.declutterGroup_ = null;
  
              /**
               * @private
               * @type {HTMLCanvasElement|HTMLVideoElement|Image}
               */
              this.hitDetectionImage_ = null;
  
              /**
               * @private
               * @type {HTMLCanvasElement|HTMLVideoElement|Image}
               */
              this.image_ = null;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.anchorX_ = undefined;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.anchorY_ = undefined;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.height_ = undefined;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.opacity_ = undefined;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.originX_ = undefined;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.originY_ = undefined;
  
              /**
               * @private
               * @type {boolean|undefined}
               */
              this.rotateWithView_ = undefined;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.rotation_ = undefined;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.scale_ = undefined;
  
              /**
               * @private
               * @type {boolean|undefined}
               */
              this.snapToPixel_ = undefined;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.width_ = undefined;
  
          };
          ol.inherits(ol.render.canvas.ImageReplay, ol.render.canvas.Replay);
  
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {number} end End.
           * @param {number} stride Stride.
           * @private
           * @return {number} My end.
           */
          ol.render.canvas.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) {
              return this.appendFlatCoordinates(
                  flatCoordinates, offset, end, stride, false, false);
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) {
              if (!this.image_) {
                  return;
              }
              this.beginGeometry(pointGeometry, feature);
              var flatCoordinates = pointGeometry.getFlatCoordinates();
              var stride = pointGeometry.getStride();
              var myBegin = this.coordinates.length;
              var myEnd = this.drawCoordinates_(
                  flatCoordinates, 0, flatCoordinates.length, stride);
              this.instructions.push([
                  ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_,
                  // Remaining arguments to DRAW_IMAGE are in alphabetical order
                  this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_,
                  this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
                  this.scale_ * this.pixelRatio, this.snapToPixel_, this.width_
              ]);
              this.hitDetectionInstructions.push([
                  ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd,
                  this.hitDetectionImage_,
                  // Remaining arguments to DRAW_IMAGE are in alphabetical order
                  this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_,
                  this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
                  this.scale_, this.snapToPixel_, this.width_
              ]);
              this.endGeometry(pointGeometry, feature);
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) {
              if (!this.image_) {
                  return;
              }
              this.beginGeometry(multiPointGeometry, feature);
              var flatCoordinates = multiPointGeometry.getFlatCoordinates();
              var stride = multiPointGeometry.getStride();
              var myBegin = this.coordinates.length;
              var myEnd = this.drawCoordinates_(
                  flatCoordinates, 0, flatCoordinates.length, stride);
              this.instructions.push([
                  ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_,
                  // Remaining arguments to DRAW_IMAGE are in alphabetical order
                  this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_,
                  this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
                  this.scale_ * this.pixelRatio, this.snapToPixel_, this.width_
              ]);
              this.hitDetectionInstructions.push([
                  ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd,
                  this.hitDetectionImage_,
                  // Remaining arguments to DRAW_IMAGE are in alphabetical order
                  this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_,
                  this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
                  this.scale_, this.snapToPixel_, this.width_
              ]);
              this.endGeometry(multiPointGeometry, feature);
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.ImageReplay.prototype.finish = function() {
              this.reverseHitDetectionInstructions();
              // FIXME this doesn't really protect us against further calls to draw*Geometry
              this.anchorX_ = undefined;
              this.anchorY_ = undefined;
              this.hitDetectionImage_ = null;
              this.image_ = null;
              this.height_ = undefined;
              this.scale_ = undefined;
              this.opacity_ = undefined;
              this.originX_ = undefined;
              this.originY_ = undefined;
              this.rotateWithView_ = undefined;
              this.rotation_ = undefined;
              this.snapToPixel_ = undefined;
              this.width_ = undefined;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle, declutterGroup) {
              var anchor = imageStyle.getAnchor();
              var size = imageStyle.getSize();
              var hitDetectionImage = imageStyle.getHitDetectionImage(1);
              var image = imageStyle.getImage(1);
              var origin = imageStyle.getOrigin();
              this.anchorX_ = anchor[0];
              this.anchorY_ = anchor[1];
              this.declutterGroup_ = /** @type {ol.DeclutterGroup} */ (declutterGroup);
              this.hitDetectionImage_ = hitDetectionImage;
              this.image_ = image;
              this.height_ = size[1];
              this.opacity_ = imageStyle.getOpacity();
              this.originX_ = origin[0];
              this.originY_ = origin[1];
              this.rotateWithView_ = imageStyle.getRotateWithView();
              this.rotation_ = imageStyle.getRotation();
              this.scale_ = imageStyle.getScale();
              this.snapToPixel_ = imageStyle.getSnapToPixel();
              this.width_ = size[0];
          };
  
          /**
           * @constructor
           * @extends {ol.render.canvas.Replay}
           * @param {number} tolerance Tolerance.
           * @param {ol.Extent} maxExtent Maximum extent.
           * @param {number} resolution Resolution.
           * @param {number} pixelRatio Pixel ratio.
           * @param {boolean} overlaps The replay can have overlapping geometries.
           * @param {?} declutterTree Declutter tree.
           * @struct
           */
          ol.render.canvas.LineStringReplay = function(
              tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
              ol.render.canvas.Replay.call(this,
                  tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree);
          };
          ol.inherits(ol.render.canvas.LineStringReplay, ol.render.canvas.Replay);
  
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {number} end End.
           * @param {number} stride Stride.
           * @private
           * @return {number} end.
           */
          ol.render.canvas.LineStringReplay.prototype.drawFlatCoordinates_ = function(flatCoordinates, offset, end, stride) {
              var myBegin = this.coordinates.length;
              var myEnd = this.appendFlatCoordinates(
                  flatCoordinates, offset, end, stride, false, false);
              var moveToLineToInstruction =
                  [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd];
              this.instructions.push(moveToLineToInstruction);
              this.hitDetectionInstructions.push(moveToLineToInstruction);
              return end;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) {
              var state = this.state;
              var strokeStyle = state.strokeStyle;
              var lineWidth = state.lineWidth;
              if (strokeStyle === undefined || lineWidth === undefined) {
                  return;
              }
              this.updateStrokeStyle(state, this.applyStroke);
              this.beginGeometry(lineStringGeometry, feature);
              this.hitDetectionInstructions.push([
                  ol.render.canvas.Instruction.SET_STROKE_STYLE,
                  state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
                  state.miterLimit, state.lineDash, state.lineDashOffset
              ], [
                  ol.render.canvas.Instruction.BEGIN_PATH
              ]);
              var flatCoordinates = lineStringGeometry.getFlatCoordinates();
              var stride = lineStringGeometry.getStride();
              this.drawFlatCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride);
              this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]);
              this.endGeometry(lineStringGeometry, feature);
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) {
              var state = this.state;
              var strokeStyle = state.strokeStyle;
              var lineWidth = state.lineWidth;
              if (strokeStyle === undefined || lineWidth === undefined) {
                  return;
              }
              this.updateStrokeStyle(state, this.applyStroke);
              this.beginGeometry(multiLineStringGeometry, feature);
              this.hitDetectionInstructions.push([
                  ol.render.canvas.Instruction.SET_STROKE_STYLE,
                  state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
                  state.miterLimit, state.lineDash, state.lineDashOffset
              ], [
                  ol.render.canvas.Instruction.BEGIN_PATH
              ]);
              var ends = multiLineStringGeometry.getEnds();
              var flatCoordinates = multiLineStringGeometry.getFlatCoordinates();
              var stride = multiLineStringGeometry.getStride();
              var offset = 0;
              var i, ii;
              for (i = 0, ii = ends.length; i < ii; ++i) {
                  offset = this.drawFlatCoordinates_(
                      flatCoordinates, offset, ends[i], stride);
              }
              this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]);
              this.endGeometry(multiLineStringGeometry, feature);
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.LineStringReplay.prototype.finish = function() {
              var state = this.state;
              if (state.lastStroke != undefined && state.lastStroke != this.coordinates.length) {
                  this.instructions.push([ol.render.canvas.Instruction.STROKE]);
              }
              this.reverseHitDetectionInstructions();
              this.state = null;
          };
  
  
          /**
           * @inheritDoc.
           */
          ol.render.canvas.LineStringReplay.prototype.applyStroke = function(state) {
              if (state.lastStroke != undefined && state.lastStroke != this.coordinates.length) {
                  this.instructions.push([ol.render.canvas.Instruction.STROKE]);
                  state.lastStroke = this.coordinates.length;
              }
              state.lastStroke = 0;
              ol.render.canvas.Replay.prototype.applyStroke.call(this, state);
              this.instructions.push([ol.render.canvas.Instruction.BEGIN_PATH]);
          };
  
          /**
           * @constructor
           * @extends {ol.render.canvas.Replay}
           * @param {number} tolerance Tolerance.
           * @param {ol.Extent} maxExtent Maximum extent.
           * @param {number} resolution Resolution.
           * @param {number} pixelRatio Pixel ratio.
           * @param {boolean} overlaps The replay can have overlapping geometries.
           * @param {?} declutterTree Declutter tree.
           * @struct
           */
          ol.render.canvas.PolygonReplay = function(
              tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
              ol.render.canvas.Replay.call(this,
                  tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree);
          };
          ol.inherits(ol.render.canvas.PolygonReplay, ol.render.canvas.Replay);
  
  
          /**
           * @param {Array.<number>} flatCoordinates Flat coordinates.
           * @param {number} offset Offset.
           * @param {Array.<number>} ends Ends.
           * @param {number} stride Stride.
           * @private
           * @return {number} End.
           */
          ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ = function(flatCoordinates, offset, ends, stride) {
              var state = this.state;
              var fill = state.fillStyle !== undefined;
              var stroke = state.strokeStyle != undefined;
              var numEnds = ends.length;
              var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH];
              this.instructions.push(beginPathInstruction);
              this.hitDetectionInstructions.push(beginPathInstruction);
              for (var i = 0; i < numEnds; ++i) {
                  var end = ends[i];
                  var myBegin = this.coordinates.length;
                  var myEnd = this.appendFlatCoordinates(
                      flatCoordinates, offset, end, stride, true, !stroke);
                  var moveToLineToInstruction =
                      [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd];
                  this.instructions.push(moveToLineToInstruction);
                  this.hitDetectionInstructions.push(moveToLineToInstruction);
                  if (stroke) {
                      // Performance optimization: only call closePath() when we have a stroke.
                      // Otherwise the ring is closed already (see appendFlatCoordinates above).
                      var closePathInstruction = [ol.render.canvas.Instruction.CLOSE_PATH];
                      this.instructions.push(closePathInstruction);
                      this.hitDetectionInstructions.push(closePathInstruction);
                  }
                  offset = end;
              }
              var fillInstruction = [ol.render.canvas.Instruction.FILL];
              this.hitDetectionInstructions.push(fillInstruction);
              if (fill) {
                  this.instructions.push(fillInstruction);
              }
              if (stroke) {
                  var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
                  this.instructions.push(strokeInstruction);
                  this.hitDetectionInstructions.push(strokeInstruction);
              }
              return offset;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.PolygonReplay.prototype.drawCircle = function(circleGeometry, feature) {
              var state = this.state;
              var fillStyle = state.fillStyle;
              var strokeStyle = state.strokeStyle;
              if (fillStyle === undefined && strokeStyle === undefined) {
                  return;
              }
              this.setFillStrokeStyles_(circleGeometry);
              this.beginGeometry(circleGeometry, feature);
              // always fill the circle for hit detection
              this.hitDetectionInstructions.push([
                  ol.render.canvas.Instruction.SET_FILL_STYLE,
                  ol.color.asString(ol.render.canvas.defaultFillStyle)
              ]);
              if (state.strokeStyle !== undefined) {
                  this.hitDetectionInstructions.push([
                      ol.render.canvas.Instruction.SET_STROKE_STYLE,
                      state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
                      state.miterLimit, state.lineDash, state.lineDashOffset
                  ]);
              }
              var flatCoordinates = circleGeometry.getFlatCoordinates();
              var stride = circleGeometry.getStride();
              var myBegin = this.coordinates.length;
              this.appendFlatCoordinates(
                  flatCoordinates, 0, flatCoordinates.length, stride, false, false);
              var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH];
              var circleInstruction = [ol.render.canvas.Instruction.CIRCLE, myBegin];
              this.instructions.push(beginPathInstruction, circleInstruction);
              this.hitDetectionInstructions.push(beginPathInstruction, circleInstruction);
              var fillInstruction = [ol.render.canvas.Instruction.FILL];
              this.hitDetectionInstructions.push(fillInstruction);
              if (state.fillStyle !== undefined) {
                  this.instructions.push(fillInstruction);
              }
              if (state.strokeStyle !== undefined) {
                  var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
                  this.instructions.push(strokeInstruction);
                  this.hitDetectionInstructions.push(strokeInstruction);
              }
              this.endGeometry(circleGeometry, feature);
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) {
              var state = this.state;
              this.setFillStrokeStyles_(polygonGeometry);
              this.beginGeometry(polygonGeometry, feature);
              // always fill the polygon for hit detection
              this.hitDetectionInstructions.push([
                  ol.render.canvas.Instruction.SET_FILL_STYLE,
                  ol.color.asString(ol.render.canvas.defaultFillStyle)]
              );
              if (state.strokeStyle !== undefined) {
                  this.hitDetectionInstructions.push([
                      ol.render.canvas.Instruction.SET_STROKE_STYLE,
                      state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
                      state.miterLimit, state.lineDash, state.lineDashOffset
                  ]);
              }
              var ends = polygonGeometry.getEnds();
              var flatCoordinates = polygonGeometry.getOrientedFlatCoordinates();
              var stride = polygonGeometry.getStride();
              this.drawFlatCoordinatess_(flatCoordinates, 0, ends, stride);
              this.endGeometry(polygonGeometry, feature);
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {
              var state = this.state;
              var fillStyle = state.fillStyle;
              var strokeStyle = state.strokeStyle;
              if (fillStyle === undefined && strokeStyle === undefined) {
                  return;
              }
              this.setFillStrokeStyles_(multiPolygonGeometry);
              this.beginGeometry(multiPolygonGeometry, feature);
              // always fill the multi-polygon for hit detection
              this.hitDetectionInstructions.push([
                  ol.render.canvas.Instruction.SET_FILL_STYLE,
                  ol.color.asString(ol.render.canvas.defaultFillStyle)
              ]);
              if (state.strokeStyle !== undefined) {
                  this.hitDetectionInstructions.push([
                      ol.render.canvas.Instruction.SET_STROKE_STYLE,
                      state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
                      state.miterLimit, state.lineDash, state.lineDashOffset
                  ]);
              }
              var endss = multiPolygonGeometry.getEndss();
              var flatCoordinates = multiPolygonGeometry.getOrientedFlatCoordinates();
              var stride = multiPolygonGeometry.getStride();
              var offset = 0;
              var i, ii;
              for (i = 0, ii = endss.length; i < ii; ++i) {
                  offset = this.drawFlatCoordinatess_(
                      flatCoordinates, offset, endss[i], stride);
              }
              this.endGeometry(multiPolygonGeometry, feature);
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.PolygonReplay.prototype.finish = function() {
              this.reverseHitDetectionInstructions();
              this.state = null;
              // We want to preserve topology when drawing polygons.  Polygons are
              // simplified using quantization and point elimination. However, we might
              // have received a mix of quantized and non-quantized geometries, so ensure
              // that all are quantized by quantizing all coordinates in the batch.
              var tolerance = this.tolerance;
              if (tolerance !== 0) {
                  var coordinates = this.coordinates;
                  var i, ii;
                  for (i = 0, ii = coordinates.length; i < ii; ++i) {
                      coordinates[i] = ol.geom.flat.simplify.snap(coordinates[i], tolerance);
                  }
              }
          };
  
  
          /**
           * @private
           * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
           */
          ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function(geometry) {
              var state = this.state;
              var fillStyle = state.fillStyle;
              if (fillStyle !== undefined) {
                  this.updateFillStyle(state, this.applyFill, geometry);
              }
              if (state.strokeStyle !== undefined) {
                  this.updateStrokeStyle(state, this.applyStroke);
              }
          };
  
          ol.geom.flat.straightchunk = {};
          ol.geom.flat.straightchunk.lineString = function(maxAngle, flatCoordinates, offset, end, stride) {
              var chunkStart = offset;
              var chunkEnd = offset;
              var chunkM = 0;
              var m = 0;
              var start = offset;
              var acos, i, m12, m23, x1, y1, x12, y12, x23, y23;
              for (i = offset; i < end; i += stride) {
                  var x2 = flatCoordinates[i];
                  var y2 = flatCoordinates[i + 1];
                  if (x1 !== undefined) {
                      x23 = x2 - x1;
                      y23 = y2 - y1;
                      m23 = Math.sqrt(x23 * x23 + y23 * y23);
                      if (x12 !== undefined) {
                          m += m12;
                          acos = Math.acos((x12 * x23 + y12 * y23) / (m12 * m23));
                          if (acos > maxAngle) {
                              if (m > chunkM) {
                                  chunkM = m;
                                  chunkStart = start;
                                  chunkEnd = i;
                              }
                              m = 0;
                              start = i - stride;
                          }
                      }
                      m12 = m23;
                      x12 = x23;
                      y12 = y23;
                  }
                  x1 = x2;
                  y1 = y2;
              }
              m += m23;
              return m > chunkM ? [start, i] : [chunkStart, chunkEnd];
          };
  
          ol.style.TextPlacement = {
              POINT: 'point',
              LINE: 'line'
          };
  
          ol.render.canvas.TextReplay = function(
              tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
              ol.render.canvas.Replay.call(this,
                  tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree);
  
              /**
               * @private
               * @type {ol.DeclutterGroup}
               */
              this.declutterGroup_;
  
              /**
               * @private
               * @type {Array.<HTMLCanvasElement>}
               */
              this.labels_ = null;
  
              /**
               * @private
               * @type {string}
               */
              this.text_ = '';
  
              /**
               * @private
               * @type {number}
               */
              this.textOffsetX_ = 0;
  
              /**
               * @private
               * @type {number}
               */
              this.textOffsetY_ = 0;
  
              /**
               * @private
               * @type {boolean|undefined}
               */
              this.textRotateWithView_ = undefined;
  
              /**
               * @private
               * @type {number}
               */
              this.textRotation_ = 0;
  
              /**
               * @private
               * @type {?ol.CanvasFillState}
               */
              this.textFillState_ = null;
  
              /**
               * @type {Object.<string, ol.CanvasFillState>}
               */
              this.fillStates = {};
  
              /**
               * @private
               * @type {?ol.CanvasStrokeState}
               */
              this.textStrokeState_ = null;
  
              /**
               * @type {Object.<string, ol.CanvasStrokeState>}
               */
              this.strokeStates = {};
  
              /**
               * @private
               * @type {ol.CanvasTextState}
               */
              this.textState_ = /** @type {ol.CanvasTextState} */ ({});
  
              /**
               * @type {Object.<string, ol.CanvasTextState>}
               */
              this.textStates = {};
  
              /**
               * @private
               * @type {string}
               */
              this.textKey_ = '';
  
              /**
               * @private
               * @type {string}
               */
              this.fillKey_ = '';
  
              /**
               * @private
               * @type {string}
               */
              this.strokeKey_ = '';
  
              /**
               * @private
               * @type {Object.<string, Object.<string, number>>}
               */
              this.widths_ = {};
  
              var labelCache = ol.render.canvas.labelCache;
              labelCache.prune();
  
          };
          ol.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay);
  
  
          /**
           * @param {string} font Font to use for measuring.
           * @param {Array.<string>} lines Lines to measure.
           * @param {Array.<number>} widths Array will be populated with the widths of
           * each line.
           * @return {number} Width of the whole text.
           */
          ol.render.canvas.TextReplay.measureTextWidths = function(font, lines, widths) {
              var numLines = lines.length;
              var width = 0;
              var currentWidth, i;
              for (i = 0; i < numLines; ++i) {
                  currentWidth = ol.render.canvas.measureTextWidth(font, lines[i]);
                  width = Math.max(width, currentWidth);
                  widths.push(currentWidth);
              }
              return width;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.TextReplay.prototype.drawText = function(geometry, feature) {
              var fillState = this.textFillState_;
              var strokeState = this.textStrokeState_;
              var textState = this.textState_;
              if (this.text_ === '' || !textState || (!fillState && !strokeState)) {
                  return;
              }
  
              var begin = this.coordinates.length;
  
              var geometryType = geometry.getType();
              var flatCoordinates = null;
              var end = 2;
              var stride = 2;
              var i, ii;
  
              if (textState.placement === ol.style.TextPlacement.LINE) {
                  if (!ol.extent.intersects(this.getBufferedMaxExtent(), geometry.getExtent())) {
                      return;
                  }
                  var ends;
                  flatCoordinates = geometry.getFlatCoordinates();
                  stride = geometry.getStride();
                  if (geometryType == ol.geom.GeometryType.LINE_STRING) {
                      ends = [flatCoordinates.length];
                  } else if (geometryType == ol.geom.GeometryType.MULTI_LINE_STRING) {
                      ends = geometry.getEnds();
                  } else if (geometryType == ol.geom.GeometryType.POLYGON) {
                      ends = geometry.getEnds().slice(0, 1);
                  } else if (geometryType == ol.geom.GeometryType.MULTI_POLYGON) {
                      var endss = geometry.getEndss();
                      ends = [];
                      for (i = 0, ii = endss.length; i < ii; ++i) {
                          ends.push(endss[i][0]);
                      }
                  }
                  this.beginGeometry(geometry, feature);
                  var textAlign = textState.textAlign;
                  var flatOffset = 0;
                  var flatEnd;
                  for (var o = 0, oo = ends.length; o < oo; ++o) {
                      if (textAlign == undefined) {
                          var range = ol.geom.flat.straightchunk.lineString(
                              textState.maxAngle, flatCoordinates, flatOffset, ends[o], stride);
                          flatOffset = range[0];
                          flatEnd = range[1];
                      } else {
                          flatEnd = ends[o];
                      }
                      for (i = flatOffset; i < flatEnd; i += stride) {
                          this.coordinates.push(flatCoordinates[i], flatCoordinates[i + 1]);
                      }
                      end = this.coordinates.length;
                      flatOffset = ends[o];
                      this.drawChars_(begin, end, this.declutterGroup_);
                      begin = end;
                  }
                  this.endGeometry(geometry, feature);
  
              } else {
                  var label = this.getImage(this.text_, this.textKey_, this.fillKey_, this.strokeKey_);
                  var width = label.width / this.pixelRatio;
                  switch (geometryType) {
                      case ol.geom.GeometryType.POINT:
                      case ol.geom.GeometryType.MULTI_POINT:
                          flatCoordinates = geometry.getFlatCoordinates();
                          end = flatCoordinates.length;
                          break;
                      case ol.geom.GeometryType.LINE_STRING:
                          flatCoordinates = /** @type {ol.geom.LineString} */ (geometry).getFlatMidpoint();
                          break;
                      case ol.geom.GeometryType.CIRCLE:
                          flatCoordinates = /** @type {ol.geom.Circle} */ (geometry).getCenter();
                          break;
                      case ol.geom.GeometryType.MULTI_LINE_STRING:
                          flatCoordinates = /** @type {ol.geom.MultiLineString} */ (geometry).getFlatMidpoints();
                          end = flatCoordinates.length;
                          break;
                      case ol.geom.GeometryType.POLYGON:
                          flatCoordinates = /** @type {ol.geom.Polygon} */ (geometry).getFlatInteriorPoint();
                          if (!textState.overflow && flatCoordinates[2] / this.resolution < width) {
                              return;
                          }
                          stride = 3;
                          break;
                      case ol.geom.GeometryType.MULTI_POLYGON:
                          var interiorPoints = /** @type {ol.geom.MultiPolygon} */ (geometry).getFlatInteriorPoints();
                          flatCoordinates = [];
                          for (i = 0, ii = interiorPoints.length; i < ii; i += 3) {
                              if (textState.overflow || interiorPoints[i + 2] / this.resolution >= width) {
                                  flatCoordinates.push(interiorPoints[i], interiorPoints[i + 1]);
                              }
                          }
                          end = flatCoordinates.length;
                          if (end == 0) {
                              return;
                          }
                          break;
                  }
                  end = this.appendFlatCoordinates(flatCoordinates, 0, end, stride, false, false);
                  this.beginGeometry(geometry, feature);
                  if (textState.backgroundFill || textState.backgroundStroke) {
                      this.setFillStrokeStyle(textState.backgroundFill, textState.backgroundStroke);
                      this.updateFillStyle(this.state, this.applyFill, geometry);
                      this.updateStrokeStyle(this.state, this.applyStroke);
                  }
                  this.drawTextImage_(label, begin, end);
                  this.endGeometry(geometry, feature);
              }
          };
  
  
          /**
           * @param {string} text Text.
           * @param {string} textKey Text style key.
           * @param {string} fillKey Fill style key.
           * @param {string} strokeKey Stroke style key.
           * @return {HTMLCanvasElement} Image.
           */
          ol.render.canvas.TextReplay.prototype.getImage = function(text, textKey, fillKey, strokeKey) {
              var label;
              var key = strokeKey + textKey + text + fillKey + this.pixelRatio;
  
              var labelCache = ol.render.canvas.labelCache;
              if (!labelCache.containsKey(key)) {
                  var strokeState = strokeKey ? this.strokeStates[strokeKey] || this.textStrokeState_ : null;
                  var fillState = fillKey ? this.fillStates[fillKey] || this.textFillState_ : null;
                  var textState = this.textStates[textKey] || this.textState_;
                  var pixelRatio = this.pixelRatio;
                  var scale = textState.scale * pixelRatio;
                  var align =  ol.render.replay.TEXT_ALIGN[textState.textAlign || ol.render.canvas.defaultTextAlign];
                  var strokeWidth = strokeKey && strokeState.lineWidth ? strokeState.lineWidth : 0;
  
                  var lines = text.split('\n');
                  var numLines = lines.length;
                  var widths = [];
                  var width = ol.render.canvas.TextReplay.measureTextWidths(textState.font, lines, widths);
                  var lineHeight = ol.render.canvas.measureTextHeight(textState.font);
                  var height = lineHeight * numLines;
                  var renderWidth = (width + strokeWidth);
                  var context = ol.dom.createCanvasContext2D(
                      Math.ceil(renderWidth * scale),
                      Math.ceil((height + strokeWidth) * scale));
                  label = context.canvas;
                  labelCache.set(key, label);
                  if (scale != 1) {
                      context.scale(scale, scale);
                  }
                  context.font = textState.font;
                  if (strokeKey) {
                      context.strokeStyle = strokeState.strokeStyle;
                      context.lineWidth = strokeWidth * (ol.has.SAFARI ? scale : 1);
                      context.lineCap = strokeState.lineCap;
                      context.lineJoin = strokeState.lineJoin;
                      context.miterLimit = strokeState.miterLimit;
                      if (ol.has.CANVAS_LINE_DASH && strokeState.lineDash.length) {
                          context.setLineDash(strokeState.lineDash);
                          context.lineDashOffset = strokeState.lineDashOffset;
                      }
                  }
                  if (fillKey) {
                      context.fillStyle = fillState.fillStyle;
                  }
                  context.textBaseline = 'middle';
                  context.textAlign = 'center';
                  var leftRight = (0.5 - align);
                  var x = align * label.width / scale + leftRight * strokeWidth;
                  var i;
                  if (strokeKey) {
                      for (i = 0; i < numLines; ++i) {
                          context.strokeText(lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight);
                      }
                  }
                  if (fillKey) {
                      for (i = 0; i < numLines; ++i) {
                          context.fillText(lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight);
                      }
                  }
              }
              return labelCache.get(key);
          };
  
  
          /**
           * @private
           * @param {HTMLCanvasElement} label Label.
           * @param {number} begin Begin.
           * @param {number} end End.
           */
          ol.render.canvas.TextReplay.prototype.drawTextImage_ = function(label, begin, end) {
              var textState = this.textState_;
              var strokeState = this.textStrokeState_;
              var pixelRatio = this.pixelRatio;
              var align = ol.render.replay.TEXT_ALIGN[textState.textAlign || ol.render.canvas.defaultTextAlign];
              var baseline = ol.render.replay.TEXT_ALIGN[textState.textBaseline];
              var strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0;
  
              var anchorX = align * label.width / pixelRatio + 2 * (0.5 - align) * strokeWidth;
              var anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth;
              this.instructions.push([ol.render.canvas.Instruction.DRAW_IMAGE, begin, end,
                  label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio,
                  this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_,
                  1, true, label.width,
                  textState.padding == ol.render.canvas.defaultPadding ?
                      ol.render.canvas.defaultPadding : textState.padding.map(function(p) {
                      return p * pixelRatio;
                  }),
                  !!textState.backgroundFill, !!textState.backgroundStroke
              ]);
              this.hitDetectionInstructions.push([ol.render.canvas.Instruction.DRAW_IMAGE, begin, end,
                  label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio,
                  this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_,
                  1 / pixelRatio, true, label.width, textState.padding,
                  !!textState.backgroundFill, !!textState.backgroundStroke
              ]);
          };
  
  
          /**
           * @private
           * @param {number} begin Begin.
           * @param {number} end End.
           * @param {ol.DeclutterGroup} declutterGroup Declutter group.
           */
          ol.render.canvas.TextReplay.prototype.drawChars_ = function(begin, end, declutterGroup) {
              var strokeState = this.textStrokeState_;
              var textState = this.textState_;
              var fillState = this.textFillState_;
  
              var strokeKey = this.strokeKey_;
              if (strokeState) {
                  if (!(strokeKey in this.strokeStates)) {
                      this.strokeStates[strokeKey] = /** @type {ol.CanvasStrokeState} */ ({
                          strokeStyle: strokeState.strokeStyle,
                          lineCap: strokeState.lineCap,
                          lineDashOffset: strokeState.lineDashOffset,
                          lineWidth: strokeState.lineWidth,
                          lineJoin: strokeState.lineJoin,
                          miterLimit: strokeState.miterLimit,
                          lineDash: strokeState.lineDash
                      });
                  }
              }
              var textKey = this.textKey_;
              if (!(this.textKey_ in this.textStates)) {
                  this.textStates[this.textKey_] = /** @type {ol.CanvasTextState} */ ({
                      font: textState.font,
                      textAlign: textState.textAlign || ol.render.canvas.defaultTextAlign,
                      scale: textState.scale
                  });
              }
              var fillKey = this.fillKey_;
              if (fillState) {
                  if (!(fillKey in this.fillStates)) {
                      this.fillStates[fillKey] = /** @type {ol.CanvasFillState} */ ({
                          fillStyle: fillState.fillStyle
                      });
                  }
              }
  
              var pixelRatio = this.pixelRatio;
              var baseline = ol.render.replay.TEXT_ALIGN[textState.textBaseline];
  
              var offsetY = this.textOffsetY_ * pixelRatio;
              var text = this.text_;
              var font = textState.font;
              var textScale = textState.scale;
              var strokeWidth = strokeState ? strokeState.lineWidth * textScale / 2 : 0;
              var widths = this.widths_[font];
              if (!widths) {
                  this.widths_[font] = widths = {};
              }
              this.instructions.push([ol.render.canvas.Instruction.DRAW_CHARS,
                  begin, end, baseline, declutterGroup,
                  textState.overflow, fillKey, textState.maxAngle,
                  function(text) {
                      var width = widths[text];
                      if (!width) {
                          width = widths[text] = ol.render.canvas.measureTextWidth(font, text);
                      }
                      return width * textScale * pixelRatio;
                  },
                  offsetY, strokeKey, strokeWidth * pixelRatio, text, textKey, 1
              ]);
              this.hitDetectionInstructions.push([ol.render.canvas.Instruction.DRAW_CHARS,
                  begin, end, baseline, declutterGroup,
                  textState.overflow, fillKey, textState.maxAngle,
                  function(text) {
                      var width = widths[text];
                      if (!width) {
                          width = widths[text] = ol.render.canvas.measureTextWidth(font, text);
                      }
                      return width * textScale;
                  },
                  offsetY, strokeKey, strokeWidth, text, textKey, 1 / pixelRatio
              ]);
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle, declutterGroup) {
              var textState, fillState, strokeState;
              if (!textStyle) {
                  this.text_ = '';
              } else {
                  this.declutterGroup_ = /** @type {ol.DeclutterGroup} */ (declutterGroup);
  
                  var textFillStyle = textStyle.getFill();
                  if (!textFillStyle) {
                      fillState = this.textFillState_ = null;
                  } else {
                      fillState = this.textFillState_;
                      if (!fillState) {
                          fillState = this.textFillState_ = /** @type {ol.CanvasFillState} */ ({});
                      }
                      fillState.fillStyle = ol.colorlike.asColorLike(
                          textFillStyle.getColor() || ol.render.canvas.defaultFillStyle);
                  }
  
                  var textStrokeStyle = textStyle.getStroke();
                  if (!textStrokeStyle) {
                      strokeState = this.textStrokeState_ = null;
                  } else {
                      strokeState = this.textStrokeState_;
                      if (!strokeState) {
                          strokeState = this.textStrokeState_ = /** @type {ol.CanvasStrokeState} */ ({});
                      }
                      var lineDash = textStrokeStyle.getLineDash();
                      var lineDashOffset = textStrokeStyle.getLineDashOffset();
                      var lineWidth = textStrokeStyle.getWidth();
                      var miterLimit = textStrokeStyle.getMiterLimit();
                      strokeState.lineCap = textStrokeStyle.getLineCap() || ol.render.canvas.defaultLineCap;
                      strokeState.lineDash = lineDash ? lineDash.slice() : ol.render.canvas.defaultLineDash;
                      strokeState.lineDashOffset =
                          lineDashOffset === undefined ? ol.render.canvas.defaultLineDashOffset : lineDashOffset;
                      strokeState.lineJoin = textStrokeStyle.getLineJoin() || ol.render.canvas.defaultLineJoin;
                      strokeState.lineWidth =
                          lineWidth === undefined ? ol.render.canvas.defaultLineWidth : lineWidth;
                      strokeState.miterLimit =
                          miterLimit === undefined ? ol.render.canvas.defaultMiterLimit : miterLimit;
                      strokeState.strokeStyle = ol.colorlike.asColorLike(
                          textStrokeStyle.getColor() || ol.render.canvas.defaultStrokeStyle);
                  }
  
                  textState = this.textState_;
                  var font = textStyle.getFont() || ol.render.canvas.defaultFont;
                  //ol.render.canvas.checkFont(font);  // FIXME sunyl
                  var textScale = textStyle.getScale();
                  textState.overflow = textStyle.getOverflow();
                  textState.font = font;
                  textState.maxAngle = textStyle.getMaxAngle();
                  textState.placement = textStyle.getPlacement();
                  textState.textAlign = textStyle.getTextAlign();
                  textState.textBaseline = textStyle.getTextBaseline() || ol.render.canvas.defaultTextBaseline;
                  textState.backgroundFill = textStyle.getBackgroundFill();
                  textState.backgroundStroke = textStyle.getBackgroundStroke();
                  textState.padding = textStyle.getPadding() || ol.render.canvas.defaultPadding;
                  textState.scale = textScale === undefined ? 1 : textScale;
  
                  var textOffsetX = textStyle.getOffsetX();
                  var textOffsetY = textStyle.getOffsetY();
                  var textRotateWithView = textStyle.getRotateWithView();
                  var textRotation = textStyle.getRotation();
                  this.text_ = textStyle.getText() || '';
                  this.textOffsetX_ = textOffsetX === undefined ? 0 : textOffsetX;
                  this.textOffsetY_ = textOffsetY === undefined ? 0 : textOffsetY;
                  this.textRotateWithView_ = textRotateWithView === undefined ? false : textRotateWithView;
                  this.textRotation_ = textRotation === undefined ? 0 : textRotation;
  
                  this.strokeKey_ = strokeState ?
                      (typeof strokeState.strokeStyle == 'string' ? strokeState.strokeStyle : ol.getUid(strokeState.strokeStyle)) +
                          strokeState.lineCap + strokeState.lineDashOffset + '|' + strokeState.lineWidth +
                          strokeState.lineJoin + strokeState.miterLimit + '[' + strokeState.lineDash.join() + ']' :
                      '';
                  this.textKey_ = textState.font + textState.scale + (textState.textAlign || '?');
                  this.fillKey_ = fillState ?
                      (typeof fillState.fillStyle == 'string' ? fillState.fillStyle : ('|' + ol.getUid(fillState.fillStyle))) :
                      '';
              }
          };
  
          /**
           * @constructor
           * @extends {ol.render.ReplayGroup}
           * @param {number} tolerance Tolerance.
           * @param {ol.Extent} maxExtent Max extent.
           * @param {number} resolution Resolution.
           * @param {number} pixelRatio Pixel ratio.
           * @param {boolean} overlaps The replay group can have overlapping geometries.
           * @param {?} declutterTree Declutter tree
           * for declutter processing in postrender.
           * @param {number=} opt_renderBuffer Optional rendering buffer.
           * @struct
           */
          ol.render.canvas.ReplayGroup = function(
              tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree, opt_renderBuffer) {
              ol.render.ReplayGroup.call(this);
  
              /**
               * Declutter tree.
               * @private
               */
              this.declutterTree_ = declutterTree;
  
              /**
               * @type {ol.DeclutterGroup}
               * @private
               */
              this.declutterGroup_ = null;
  
              /**
               * @private
               * @type {number}
               */
              this.tolerance_ = tolerance;
  
              /**
               * @private
               * @type {ol.Extent}
               */
              this.maxExtent_ = maxExtent;
  
              /**
               * @private
               * @type {boolean}
               */
              this.overlaps_ = overlaps;
  
              /**
               * @private
               * @type {number}
               */
              this.pixelRatio_ = pixelRatio;
  
              /**
               * @private
               * @type {number}
               */
              this.resolution_ = resolution;
  
              /**
               * @private
               * @type {number|undefined}
               */
              this.renderBuffer_ = opt_renderBuffer;
  
              /**
               * @private
               * @type {!Object.<string,
         *        Object.<ol.render.ReplayType, ol.render.canvas.Replay>>}
               */
              this.replaysByZIndex_ = {};
  
              /**
               * @private
               * @type {CanvasRenderingContext2D}
               */
              // FIXME
              this.hitDetectionContext_ = null;
  
              /**
               * @private
               * @type {ol.Transform}
               */
              this.hitDetectionTransform_ = ol.transform.create();
          };
          ol.inherits(ol.render.canvas.ReplayGroup, ol.render.ReplayGroup);
  
  
          /**
           * This cache is used for storing calculated pixel circles for increasing performance.
           * It is a static property to allow each Replaygroup to access it.
           * @type {Object.<number, Array.<Array.<(boolean|undefined)>>>}
           * @private
           */
          ol.render.canvas.ReplayGroup.circleArrayCache_ = {
              0: [[true]]
          };
  
  
          /**
           * This method fills a row in the array from the given coordinate to the
           * middle with `true`.
           * @param {Array.<Array.<(boolean|undefined)>>} array The array that will be altered.
           * @param {number} x X coordinate.
           * @param {number} y Y coordinate.
           * @private
           */
          ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_ = function(array, x, y) {
              var i;
              var radius = Math.floor(array.length / 2);
              if (x >= radius) {
                  for (i = radius; i < x; i++) {
                      array[i][y] = true;
                  }
              } else if (x < radius) {
                  for (i = x + 1; i < radius; i++) {
                      array[i][y] = true;
                  }
              }
          };
  
  
          /**
           * This methods creates a circle inside a fitting array. Points inside the
           * circle are marked by true, points on the outside are undefined.
           * It uses the midpoint circle algorithm.
           * A cache is used to increase performance.
           * @param {number} radius Radius.
           * @returns {Array.<Array.<(boolean|undefined)>>} An array with marked circle points.
           * @private
           */
          ol.render.canvas.ReplayGroup.getCircleArray_ = function(radius) {
              if (ol.render.canvas.ReplayGroup.circleArrayCache_[radius] !== undefined) {
                  return ol.render.canvas.ReplayGroup.circleArrayCache_[radius];
              }
  
              var arraySize = radius * 2 + 1;
              var arr = new Array(arraySize);
              for (var i = 0; i < arraySize; i++) {
                  arr[i] = new Array(arraySize);
              }
  
              var x = radius;
              var y = 0;
              var error = 0;
  
              while (x >= y) {
                  ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + x, radius + y);
                  ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + y, radius + x);
                  ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - y, radius + x);
                  ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - x, radius + y);
                  ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - x, radius - y);
                  ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - y, radius - x);
                  ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + y, radius - x);
                  ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + x, radius - y);
  
                  y++;
                  error += 1 + 2 * y;
                  if (2 * (error - x) + 1 > 0) {
                      x -= 1;
                      error += 1 - 2 * x;
                  }
              }
  
              ol.render.canvas.ReplayGroup.circleArrayCache_[radius] = arr;
              return arr;
          };
  
  
          /**
           * @param {!Object.<string, Array.<*>>} declutterReplays Declutter replays.
           * @param {CanvasRenderingContext2D} context Context.
           * @param {number} rotation Rotation.
           */
          ol.render.canvas.ReplayGroup.replayDeclutter = function(declutterReplays, context, rotation) {
              var zs = Object.keys(declutterReplays).map(Number).sort(ol.array.numberSafeCompareFunction);
              var skippedFeatureUids = {};
              for (var z = 0, zz = zs.length; z < zz; ++z) {
                  var replayData = declutterReplays[zs[z].toString()];
                  for (var i = 0, ii = replayData.length; i < ii;) {
                      var replay = replayData[i++];
                      var transform = replayData[i++];
                      replay.replay(context, transform, rotation, skippedFeatureUids);
                  }
              }
          };
  
  
          /**
           * @param {boolean} group Group with previous replay.
           * @return {ol.DeclutterGroup} Declutter instruction group.
           */
          ol.render.canvas.ReplayGroup.prototype.addDeclutter = function(group) {
              var declutter = null;
              if (this.declutterTree_) {
                  if (group) {
                      declutter = this.declutterGroup_;
                      /** @type {number} */ (declutter[4])++;
                  } else {
                      declutter = this.declutterGroup_ = ol.extent.createEmpty();
                      declutter.push(1);
                  }
              }
              return declutter;
          };
  
  
          /**
           * @param {CanvasRenderingContext2D} context Context.
           * @param {ol.Transform} transform Transform.
           */
          ol.render.canvas.ReplayGroup.prototype.clip = function(context, transform) {
              var flatClipCoords = this.getClipCoords(transform);
              context.beginPath();
              context.moveTo(flatClipCoords[0], flatClipCoords[1]);
              context.lineTo(flatClipCoords[2], flatClipCoords[3]);
              context.lineTo(flatClipCoords[4], flatClipCoords[5]);
              context.lineTo(flatClipCoords[6], flatClipCoords[7]);
              context.clip();
          };
  
  
          /**
           * @param {Array.<ol.render.ReplayType>} replays Replays.
           * @return {boolean} Has replays of the provided types.
           */
          ol.render.canvas.ReplayGroup.prototype.hasReplays = function(replays) {
              for (var zIndex in this.replaysByZIndex_) {
                  var candidates = this.replaysByZIndex_[zIndex];
                  for (var i = 0, ii = replays.length; i < ii; ++i) {
                      if (replays[i] in candidates) {
                          return true;
                      }
                  }
              }
              return false;
          };
  
  
          /**
           * FIXME empty description for jsdoc
           */
          ol.render.canvas.ReplayGroup.prototype.finish = function() {
              var zKey;
              for (zKey in this.replaysByZIndex_) {
                  var replays = this.replaysByZIndex_[zKey];
                  var replayKey;
                  for (replayKey in replays) {
                      replays[replayKey].finish();
                  }
              }
          };
  
  
          /**
           * @param {ol.Coordinate} coordinate Coordinate.
           * @param {number} resolution Resolution.
           * @param {number} rotation Rotation.
           * @param {number} hitTolerance Hit tolerance in pixels.
           * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
           *     to skip.
           * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature
           *     callback.
           * @param {Object.<string, ol.DeclutterGroup>} declutterReplays Declutter
           *     replays.
           * @return {T|undefined} Callback result.
           * @template T
           */
          ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
              coordinate, resolution, rotation, hitTolerance, skippedFeaturesHash, callback, declutterReplays) {
  
              hitTolerance = Math.round(hitTolerance);
              var contextSize = hitTolerance * 2 + 1;
              var transform = ol.transform.compose(this.hitDetectionTransform_,
                  hitTolerance + 0.5, hitTolerance + 0.5,
                  1 / resolution, -1 / resolution,
                  -rotation,
                  -coordinate[0], -coordinate[1]);
              var context = this.hitDetectionContext_;
  
              if (context.canvas.width !== contextSize || context.canvas.height !== contextSize) {
                  context.canvas.width = contextSize;
                  context.canvas.height = contextSize;
              } else {
                  context.clearRect(0, 0, contextSize, contextSize);
              }
  
              /**
               * @type {ol.Extent}
               */
              var hitExtent;
              if (this.renderBuffer_ !== undefined) {
                  hitExtent = ol.extent.createEmpty();
                  ol.extent.extendCoordinate(hitExtent, coordinate);
                  ol.extent.buffer(hitExtent, resolution * (this.renderBuffer_ + hitTolerance), hitExtent);
              }
  
              var mask = ol.render.canvas.ReplayGroup.getCircleArray_(hitTolerance);
              var declutteredFeatures;
              if (this.declutterTree_) {
                  declutteredFeatures = this.declutterTree_.all().map(function(entry) {
                      return entry.value;
                  });
              }
  
              var replayType;
  
              /**
               * @param {ol.Feature|ol.render.Feature} feature Feature.
               * @return {?} Callback result.
               */
              function featureCallback(feature) {
                  var imageData = context.getImageData(0, 0, contextSize, contextSize).data;
                  for (var i = 0; i < contextSize; i++) {
                      for (var j = 0; j < contextSize; j++) {
                          if (mask[i][j]) {
                              if (imageData[(j * contextSize + i) * 4 + 3] > 0) {
                                  var result;
                                  if (!(declutteredFeatures && (replayType == ol.render.ReplayType.IMAGE || replayType == ol.render.ReplayType.TEXT)) ||
                                      declutteredFeatures.indexOf(feature) !== -1) {
                                      result = callback(feature);
                                  }
                                  if (result) {
                                      return result;
                                  } else {
                                      context.clearRect(0, 0, contextSize, contextSize);
                                      return undefined;
                                  }
                              }
                          }
                      }
                  }
              }
  
              /** @type {Array.<number>} */
              var zs = Object.keys(this.replaysByZIndex_).map(Number);
              zs.sort(ol.array.numberSafeCompareFunction);
  
              var i, j, replays, replay, result;
              for (i = zs.length - 1; i >= 0; --i) {
                  var zIndexKey = zs[i].toString();
                  replays = this.replaysByZIndex_[zIndexKey];
                  for (j = ol.render.replay.ORDER.length - 1; j >= 0; --j) {
                      replayType = ol.render.replay.ORDER[j];
                      replay = replays[replayType];
                      if (replay !== undefined) {
                          if (declutterReplays &&
                              (replayType == ol.render.ReplayType.IMAGE || replayType == ol.render.ReplayType.TEXT)) {
                              var declutter = declutterReplays[zIndexKey];
                              if (!declutter) {
                                  declutterReplays[zIndexKey] = [replay, transform.slice(0)];
                              } else {
                                  declutter.push(replay, transform.slice(0));
                              }
                          } else {
                              result = replay.replayHitDetection(context, transform, rotation,
                                  skippedFeaturesHash, featureCallback, hitExtent);
                              if (result) {
                                  return result;
                              }
                          }
                      }
                  }
              }
              return undefined;
          };
  
  
          /**
           * @param {ol.Transform} transform Transform.
           * @return {Array.<number>} Clip coordinates.
           */
          ol.render.canvas.ReplayGroup.prototype.getClipCoords = function(transform) {
              var maxExtent = this.maxExtent_;
              var minX = maxExtent[0];
              var minY = maxExtent[1];
              var maxX = maxExtent[2];
              var maxY = maxExtent[3];
              var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY];
              ol.geom.flat.transform.transform2D(
                  flatClipCoords, 0, 8, 2, transform, flatClipCoords);
              return flatClipCoords;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {
              var zIndexKey = zIndex !== undefined ? zIndex.toString() : '0';
              var replays = this.replaysByZIndex_[zIndexKey];
              if (replays === undefined) {
                  replays = {};
                  this.replaysByZIndex_[zIndexKey] = replays;
              }
              var replay = replays[replayType];
              if (replay === undefined) {
                  var Constructor = ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_[replayType];
                  replay = new Constructor(this.tolerance_, this.maxExtent_,
                      this.resolution_, this.pixelRatio_, this.overlaps_, this.declutterTree_);
                  replays[replayType] = replay;
              }
              return replay;
          };
  
  
          /**
           * @return {Object.<string, Object.<ol.render.ReplayType, ol.render.canvas.Replay>>} Replays.
           */
          ol.render.canvas.ReplayGroup.prototype.getReplays = function() {
              return this.replaysByZIndex_;
          };
  
  
          /**
           * @inheritDoc
           */
          ol.render.canvas.ReplayGroup.prototype.isEmpty = function() {
              return ol.obj.isEmpty(this.replaysByZIndex_);
          };
  
  
          /**
           * @param {CanvasRenderingContext2D} context Context.
           * @param {ol.Transform} transform Transform.
           * @param {number} viewRotation View rotation.
           * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
           *     to skip.
           * @param {Array.<ol.render.ReplayType>=} opt_replayTypes Ordered replay types
           *     to replay. Default is {@link ol.render.replay.ORDER}
           * @param {Object.<string, ol.DeclutterGroup>=} opt_declutterReplays Declutter
           *     replays.
           */
          ol.render.canvas.ReplayGroup.prototype.replay = function(context,
                                                                   transform, viewRotation, skippedFeaturesHash, opt_replayTypes, opt_declutterReplays) {
  
              /** @type {Array.<number>} */
              var zs = Object.keys(this.replaysByZIndex_).map(Number);
              zs.sort(ol.array.numberSafeCompareFunction);
  
              // setup clipping so that the parts of over-simplified geometries are not
              // visible outside the current extent when panning
              context.save();
              this.clip(context, transform);
  
              var replayTypes = opt_replayTypes ? opt_replayTypes : ol.render.replay.ORDER;
              var i, ii, j, jj, replays, replay;
              for (i = 0, ii = zs.length; i < ii; ++i) {
                  var zIndexKey = zs[i].toString();
                  replays = this.replaysByZIndex_[zIndexKey];
                  for (j = 0, jj = replayTypes.length; j < jj; ++j) {
                      var replayType = replayTypes[j];
                      replay = replays[replayType];
                      if (replay !== undefined) {
                          if (opt_declutterReplays &&
                              (replayType == ol.render.ReplayType.IMAGE || replayType == ol.render.ReplayType.TEXT)) {
                              var declutter = opt_declutterReplays[zIndexKey];
                              if (!declutter) {
                                  opt_declutterReplays[zIndexKey] = [replay, transform.slice(0)];
                              } else {
                                  declutter.push(replay, transform.slice(0));
                              }
                          } else {
                              replay.replay(context, transform, viewRotation, skippedFeaturesHash);
                          }
                      }
                  }
              }
  
              context.restore();
          };
  
  
          /**
           * @const
           * @private
           * @type {Object.<ol.render.ReplayType,
       *                function(new: ol.render.canvas.Replay, number, ol.Extent,
       *                number, number, boolean, Array.<ol.DeclutterGroup>)>}
           */
          ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_ = {
              'Circle': ol.render.canvas.PolygonReplay,
              'Default': ol.render.canvas.Replay,
              'Image': ol.render.canvas.ImageReplay,
              'LineString': ol.render.canvas.LineStringReplay,
              'Polygon': ol.render.canvas.PolygonReplay,
              'Text': ol.render.canvas.TextReplay
          };
  
          ol.renderer = {};
          ol.renderer.vector = {};
  
          /**
           * @param {ol.Feature|ol.render.Feature} feature1 Feature 1.
           * @param {ol.Feature|ol.render.Feature} feature2 Feature 2.
           * @return {number} Order.
           */
          ol.renderer.vector.defaultOrder = function(feature1, feature2) {
              return ol.getUid(feature1) - ol.getUid(feature2);
          };
  
  
          /**
           * @param {number} resolution Resolution.
           * @param {number} pixelRatio Pixel ratio.
           * @return {number} Squared pixel tolerance.
           */
          ol.renderer.vector.getSquaredTolerance = function(resolution, pixelRatio) {
              var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio);
              return tolerance * tolerance;
          };
  
  
          /**
           * @param {number} resolution Resolution.
           * @param {number} pixelRatio Pixel ratio.
           * @return {number} Pixel tolerance.
           */
          ol.renderer.vector.getTolerance = function(resolution, pixelRatio) {
              return ol.SIMPLIFY_TOLERANCE * resolution / pixelRatio;
          };
  
  
          /**
           * @param {ol.render.ReplayGroup} replayGroup Replay group.
           * @param {ol.geom.Circle} geometry Geometry.
           * @param {ol.style.Style} style Style.
           * @param {ol.Feature} feature Feature.
           * @private
           */
          ol.renderer.vector.renderCircleGeometry_ = function(replayGroup, geometry, style, feature) {
              var fillStyle = style.getFill();
              var strokeStyle = style.getStroke();
              if (fillStyle || strokeStyle) {
                  var circleReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.CIRCLE);
                  circleReplay.setFillStrokeStyle(fillStyle, strokeStyle);
                  circleReplay.drawCircle(geometry, feature);
              }
              var textStyle = style.getText();
              if (textStyle) {
                  var textReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.TEXT);
                  textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
                  textReplay.drawText(geometry, feature);
              }
          };
  
  
          /**
           * @param {ol.render.ReplayGroup} replayGroup Replay group.
           * @param {ol.Feature|ol.render.Feature} feature Feature.
           * @param {ol.style.Style} style Style.
           * @param {number} squaredTolerance Squared tolerance.
           * @param {function(this: T, ol.events.Event)} listener Listener function.
           * @param {T} thisArg Value to use as `this` when executing `listener`.
           * @return {boolean} `true` if style is loading.
           * @template T
           */
          ol.renderer.vector.renderFeature = function(
              replayGroup, feature, style, squaredTolerance, listener, thisArg) {
              var loading = false;
              var imageStyle, imageState;
              imageStyle = style.getImage();
              if (imageStyle) {
                  imageState = imageStyle.getImageState();
                  if (imageState == ol.ImageState.LOADED ||
                      imageState == ol.ImageState.ERROR) {
                      imageStyle.unlistenImageChange(listener, thisArg);
                  } else {
                      if (imageState == ol.ImageState.IDLE) {
                          imageStyle.load();
                      }
                      imageState = imageStyle.getImageState();
                      imageStyle.listenImageChange(listener, thisArg);
                      loading = true;
                  }
              }
              ol.renderer.vector.renderFeature_(replayGroup, feature, style,
                  squaredTolerance);
  
              return loading;
          };
  
  
          /**
           * @param {ol.render.ReplayGroup} replayGroup Replay group.
           * @param {ol.Feature|ol.render.Feature} feature Feature.
           * @param {ol.style.Style} style Style.
           * @param {number} squaredTolerance Squared tolerance.
           * @private
           */
          ol.renderer.vector.renderFeature_ = function(
              replayGroup, feature, style, squaredTolerance) {
              var geometry = style.getGeometryFunction()(feature);
              if (!geometry) {
                  return;
              }
              var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
              var renderer = style.getRenderer();
              if (renderer) {
                  ol.renderer.vector.renderGeometry_(replayGroup, simplifiedGeometry, style, feature);
              } else {
                  var geometryRenderer =
                      ol.renderer.vector.GEOMETRY_RENDERERS_[simplifiedGeometry.getType()];
                  geometryRenderer(replayGroup, simplifiedGeometry, style, feature);
              }
          };
  
  
          /**
           * @param {ol.render.ReplayGroup} replayGroup Replay group.
           * @param {ol.geom.Geometry} geometry Geometry.
           * @param {ol.style.Style} style Style.
           * @param {ol.Feature|ol.render.Feature} feature Feature.
           * @private
           */
          ol.renderer.vector.renderGeometry_ = function(replayGroup, geometry, style, feature) {
              if (geometry.getType() == ol.geom.GeometryType.GEOMETRY_COLLECTION) {
                  var geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries();
                  for (var i = 0, ii = geometries.length; i < ii; ++i) {
                      ol.renderer.vector.renderGeometry_(replayGroup, geometries[i], style, feature);
                  }
                  return;
              }
              var replay = replayGroup.getReplay(style.getZIndex(), ol.render.ReplayType.DEFAULT);
              replay.drawCustom(/** @type {ol.geom.SimpleGeometry} */ (geometry), feature, style.getRenderer());
          };
  
  
          /**
           * @param {ol.render.ReplayGroup} replayGroup Replay group.
           * @param {ol.geom.GeometryCollection} geometry Geometry.
           * @param {ol.style.Style} style Style.
           * @param {ol.Feature} feature Feature.
           * @private
           */
          ol.renderer.vector.renderGeometryCollectionGeometry_ = function(replayGroup, geometry, style, feature) {
              var geometries = geometry.getGeometriesArray();
              var i, ii;
              for (i = 0, ii = geometries.length; i < ii; ++i) {
                  var geometryRenderer =
                      ol.renderer.vector.GEOMETRY_RENDERERS_[geometries[i].getType()];
                  geometryRenderer(replayGroup, geometries[i], style, feature);
              }
          };
  
  
          /**
           * @param {ol.render.ReplayGroup} replayGroup Replay group.
           * @param {ol.geom.LineString|ol.render.Feature} geometry Geometry.
           * @param {ol.style.Style} style Style.
           * @param {ol.Feature|ol.render.Feature} feature Feature.
           * @private
           */
          ol.renderer.vector.renderLineStringGeometry_ = function(replayGroup, geometry, style, feature) {
              var strokeStyle = style.getStroke();
              if (strokeStyle) {
                  var lineStringReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.LINE_STRING);
                  lineStringReplay.setFillStrokeStyle(null, strokeStyle);
                  lineStringReplay.drawLineString(geometry, feature);
              }
              var textStyle = style.getText();
              if (textStyle) {
                  var textReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.TEXT);
                  textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
                  textReplay.drawText(geometry, feature);
              }
          };
  
  
          /**
           * @param {ol.render.ReplayGroup} replayGroup Replay group.
           * @param {ol.geom.MultiLineString|ol.render.Feature} geometry Geometry.
           * @param {ol.style.Style} style Style.
           * @param {ol.Feature|ol.render.Feature} feature Feature.
           * @private
           */
          ol.renderer.vector.renderMultiLineStringGeometry_ = function(replayGroup, geometry, style, feature) {
              var strokeStyle = style.getStroke();
              if (strokeStyle) {
                  var lineStringReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.LINE_STRING);
                  lineStringReplay.setFillStrokeStyle(null, strokeStyle);
                  lineStringReplay.drawMultiLineString(geometry, feature);
              }
              var textStyle = style.getText();
              if (textStyle) {
                  var textReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.TEXT);
                  textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
                  textReplay.drawText(geometry, feature);
              }
          };
  
  
          /**
           * @param {ol.render.ReplayGroup} replayGroup Replay group.
           * @param {ol.geom.MultiPolygon} geometry Geometry.
           * @param {ol.style.Style} style Style.
           * @param {ol.Feature} feature Feature.
           * @private
           */
          ol.renderer.vector.renderMultiPolygonGeometry_ = function(replayGroup, geometry, style, feature) {
              var fillStyle = style.getFill();
              var strokeStyle = style.getStroke();
              if (strokeStyle || fillStyle) {
                  var polygonReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.POLYGON);
                  polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
                  polygonReplay.drawMultiPolygon(geometry, feature);
              }
              var textStyle = style.getText();
              if (textStyle) {
                  var textReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.TEXT);
                  textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
                  textReplay.drawText(geometry, feature);
              }
          };
  
  
          /**
           * @param {ol.render.ReplayGroup} replayGroup Replay group.
           * @param {ol.geom.Point|ol.render.Feature} geometry Geometry.
           * @param {ol.style.Style} style Style.
           * @param {ol.Feature|ol.render.Feature} feature Feature.
           * @private
           */
          ol.renderer.vector.renderPointGeometry_ = function(replayGroup, geometry, style, feature) {
              var imageStyle = style.getImage();
              if (imageStyle) {
                  if (imageStyle.getImageState() != ol.ImageState.LOADED) {
                      return;
                  }
                  var imageReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.IMAGE);
                  imageReplay.setImageStyle(imageStyle, replayGroup.addDeclutter(false));
                  imageReplay.drawPoint(geometry, feature);
              }
              var textStyle = style.getText();
              if (textStyle) {
                  var textReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.TEXT);
                  textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(!!imageStyle));
                  textReplay.drawText(geometry, feature);
              }
          };
  
  
          /**
           * @param {ol.render.ReplayGroup} replayGroup Replay group.
           * @param {ol.geom.MultiPoint|ol.render.Feature} geometry Geometry.
           * @param {ol.style.Style} style Style.
           * @param {ol.Feature|ol.render.Feature} feature Feature.
           * @private
           */
          ol.renderer.vector.renderMultiPointGeometry_ = function(replayGroup, geometry, style, feature) {
              var imageStyle = style.getImage();
              if (imageStyle) {
                  if (imageStyle.getImageState() != ol.ImageState.LOADED) {
                      return;
                  }
                  var imageReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.IMAGE);
                  imageReplay.setImageStyle(imageStyle, replayGroup.addDeclutter(false));
                  imageReplay.drawMultiPoint(geometry, feature);
              }
              var textStyle = style.getText();
              if (textStyle) {
                  var textReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.TEXT);
                  textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(!!imageStyle));
                  textReplay.drawText(geometry, feature);
              }
          };
  
  
          /**
           * @param {ol.render.ReplayGroup} replayGroup Replay group.
           * @param {ol.geom.Polygon|ol.render.Feature} geometry Geometry.
           * @param {ol.style.Style} style Style.
           * @param {ol.Feature|ol.render.Feature} feature Feature.
           * @private
           */
          ol.renderer.vector.renderPolygonGeometry_ = function(replayGroup, geometry, style, feature) {
              var fillStyle = style.getFill();
              var strokeStyle = style.getStroke();
              if (fillStyle || strokeStyle) {
                  var polygonReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.POLYGON);
                  polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
                  polygonReplay.drawPolygon(geometry, feature);
              }
              var textStyle = style.getText();
              if (textStyle) {
                  var textReplay = replayGroup.getReplay(
                      style.getZIndex(), ol.render.ReplayType.TEXT);
                  textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
                  textReplay.drawText(geometry, feature);
              }
          };
  
  
          /**
           * @const
           * @private
           * @type {Object.<ol.geom.GeometryType,
       *                function(ol.render.ReplayGroup, ol.geom.Geometry,
       *                         ol.style.Style, Object)>}
           */
          ol.renderer.vector.GEOMETRY_RENDERERS_ = {
              'Point': ol.renderer.vector.renderPointGeometry_,
              'LineString': ol.renderer.vector.renderLineStringGeometry_,
              'Polygon': ol.renderer.vector.renderPolygonGeometry_,
              'MultiPoint': ol.renderer.vector.renderMultiPointGeometry_,
              'MultiLineString': ol.renderer.vector.renderMultiLineStringGeometry_,
              'MultiPolygon': ol.renderer.vector.renderMultiPolygonGeometry_,
              'GeometryCollection': ol.renderer.vector.renderGeometryCollectionGeometry_,
              'Circle': ol.renderer.vector.renderCircleGeometry_
          };
  
      var spriteImageCanvas = {};
  
      var mvtStyleClass = new MvtStyle(ol, true);
      var mvtRenderer2D = new MvtRenderer2D({
          mvtStyle: mvtStyleClass,
          openlayer: ol,
          useOffscreen: true
      });
  
      function MVTWorker(parameters, transferableObjects) {
          var canvas = new OffscreenCanvas(parameters.canvasWidth, parameters.canvasWidth);
          var idCanvas = new OffscreenCanvas(parameters.canvasWidth, parameters.canvasWidth);
          var pbfData = parameters.pbfData;
          var layers = parameters.layers;
          var transform = parameters.transform;
          var squaredTolerance = parameters.squaredTolerance;
          var spriteImageDatas = parameters.spriteImageDatas;
          var keepProperties = parameters.keepProperties;
          var tileLevel = parameters.tileLevel;
          var needSourceLayerNames = parameters.needSourceLayerNames;
          var selectEnabled = parameters.selectEnabled;
          var featureProperties = {};
  
          try {
              var mvtParser = new ol.format.MVT({
                  featureClass: ol.Feature
              });
              var features = mvtParser.readFeatures(pbfData, {
                  needSourceLayerNames: needSourceLayerNames
              });
              var renderResult = mvtRenderer2D.renderFeatures({
                  colorCanvas: canvas,
                  idCanvas: idCanvas,
                  transform: transform,
                  layers: layers,
                  features: features,
                  tileLevel: tileLevel,
                  spriteImageCanvas: spriteImageCanvas,
                  spriteImageDatas: spriteImageDatas,
                  squaredTolerance: squaredTolerance,
                  selectEnabled: selectEnabled,
                  showBillboard: false
              });
  
              if (keepProperties) {
                  var featuresToRender = renderResult.idFeatures;
                  var featuresToRenderLength = featuresToRender.length;
                  for (var i = 0; i < featuresToRenderLength; i++) {
                      var feature = featuresToRender[i];
                      var featureID = getFeatureID$1(feature);
                      var propertiesWithoutGeometry = feature.getProperties();
                      if (when.defined(propertiesWithoutGeometry.geometry)) {
                          delete propertiesWithoutGeometry.geometry;
                      }
                      featureProperties[featureID] = propertiesWithoutGeometry;
                  }
              }
          }
          catch (err) {
          }
  
          var imageBitmap = canvas.transferToImageBitmap();
          var idImageBitmap = selectEnabled ? idCanvas.transferToImageBitmap() : null;
          transferableObjects.push(imageBitmap);
          return {
              buffer: imageBitmap,
              idBuffer: idImageBitmap,
              properties: featureProperties
          };
      }
  
      function getFeatureID$1(feature) {
          var id = feature.getId();
          // 只在颜色中记录256*256*256这么大范围的ID，超过这个范围的ID舍去
          var discard = Math.floor(id / 16777216);
          id = id - discard * 16777216;
          return id;
      }
  
      var MVTWorker$1 = createTaskProcessorWorker(MVTWorker);
  
      return MVTWorker$1;
  
  });
  