text: /*
   *
   * Copyright (c) 1999-2021 Luciad All Rights Reserved.
   *
   * Luciad grants you ("Licensee") a non-exclusive, royalty free, license to use,
   * modify and redistribute this software in source and binary code form,
   * provided that i) this copyright notice and license appear on all copies of
   * the software; and ii) Licensee does not utilize the software in a manner
   * which is disparaging to Luciad.
   *
   * This software is provided "AS IS," without a warranty of any kind. ALL
   * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
   * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
   * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. LUCIAD AND ITS LICENSORS SHALL NOT BE
   * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
   * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL LUCIAD OR ITS
   * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
   * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
   * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
   * OR INABILITY TO USE SOFTWARE, EVEN IF LUCIAD HAS BEEN ADVISED OF THE
   * POSSIBILITY OF SUCH DAMAGES.
   */
  import {FeatureModel} from "@luciad/ria/model/feature/FeatureModel";
  import {UrlStore} from "@luciad/ria/model/store/UrlStore";
  import {RasterDataType} from "@luciad/ria/model/tileset/RasterDataType";
  import {RasterSamplingMode} from "@luciad/ria/model/tileset/RasterSamplingMode";
  import {BingMapsTileSetModel} from "@luciad/ria/model/tileset/BingMapsTileSetModel";
  import {FusionTileSetModel} from "@luciad/ria/model/tileset/FusionTileSetModel";
  import {UrlTileSetModel} from "@luciad/ria/model/tileset/UrlTileSetModel";
  import {getReference} from "@luciad/ria/reference/ReferenceProvider";
  import {LonLatPointFormat} from "@luciad/ria/shape/format/LonLatPointFormat";
  import {createBounds} from "@luciad/ria/shape/ShapeFactory";
  import {EditController} from "@luciad/ria/view/controller/EditController";
  import {FeatureLayer} from "@luciad/ria/view/feature/FeatureLayer";
  import {FeaturePainter} from "@luciad/ria/view/feature/FeaturePainter";
  import {GridLayer} from "@luciad/ria/view/grid/GridLayer";
  import {LonLatGrid} from "@luciad/ria/view/grid/LonLatGrid";
  import {LayerType} from "@luciad/ria/view/LayerType";
  import {PinEndPosition} from "@luciad/ria/view/style/PinEndPosition";
  import {RasterTileSetLayer} from "@luciad/ria/view/tileset/RasterTileSetLayer";
  import {isCluster} from "@luciad/ria/view/feature/transformation/ClusteringTransformer";
  import {request} from "./request";
  import {URLUtil} from "./URLUtil";
  import {IconFactory} from "./IconFactory";
  
  //#snippet references
  var EPSG4326 = getReference("EPSG:4326");
  //#endsnippet references
  //#snippet createModel
  var CRS84 = getReference("CRS:84");
  //#endsnippet createModel
  
  // Tests whether the public demo LuciadFusion server is reachable
  var publicLuciadFusionServerPromise = null;
  
  function isPublicLuciadFusionServerReachable() {
    return publicLuciadFusionServerPromise || (
      publicLuciadFusionServerPromise = request(
        URLUtil.toSampleServices(
          "lts?SERVICE=LTS&VERSION=1.0.0&REQUEST=getTile&COVERAGE_ID=4ceea49c-3e7c-4e2d-973d-c608fb2fb07e&LEVEL=0&ROW=0&COLUMN=0"
        )
      )
    );
  }
  
  function formatResourceData(data) {
  
    //Inspect the response
    var resourceSet;
    var resource = data;
    if (Object.prototype.toString.call(data.resourceSets) === '[object Array]' && data.resourceSets.length > 0) {
      resourceSet = data.resourceSets[0];
      if (Object.prototype.toString.call(resourceSet.resources) === '[object Array]' &&
        resourceSet.resources.length > 0) {
        resource = resourceSet.resources[0];
      }
    }
  
    resource.brandLogoUri = resource.brandLogoUri || data.brandLogoUri;
    return resource;
  }
  
  function throwBingProxyError() {
    throw new Error("Something went wrong while contacting the Bing Proxy. Did you configure it with a Bing Maps key?");
  }
  
  function createBingLayer(model) {
    return new RasterTileSetLayer(model, {
      layerType: LayerType.BASE
    });
  }
  
  function createFusionBackgroundLayer() {
    var tileSetReference = getReference("EPSG:4326");
  
    var imageryParameters = {
      reference: EPSG4326,
      level0Columns: 4,
      level0Rows: 2,
      levelCount: 24,
      bounds: createBounds(EPSG4326, [-180, 360, -90, 180]),
      url: URLUtil.toSampleServices("lts"),
      coverageId: "4ceea49c-3e7c-4e2d-973d-c608fb2fb07e",
      tileWidth: 256,
      tileHeight: 256
    };
  
    return isPublicLuciadFusionServerReachable().then(function() {
      return new RasterTileSetLayer(new FusionTileSetModel(imageryParameters), {
        label: "Aerial imagery",
        layerType: LayerType.BASE
      });
    });
  }
  
  function createFusionElevationLayer() {
    var elevationParameters = {
      reference: EPSG4326,
      level0Columns: 4,
      level0Rows: 2,
      levelCount: 24,
      bounds: createBounds(EPSG4326, [-180, 360, -90, 180]),
      url: URLUtil.toSampleServices("lts"),
      coverageId: "e8f28a35-0e8c-4210-b2e8-e5d4333824ec",
      tileWidth: 32,
      tileHeight: 32,
      dataType: RasterDataType.ELEVATION,
      samplingMode: RasterSamplingMode.POINT
    };
  
    return isPublicLuciadFusionServerReachable().then(function() {
      return new RasterTileSetLayer(new FusionTileSetModel(elevationParameters), {
        label: "Elevation"
      });
    });
  }
  
  function createFallbackBackgroundLayer() {
    return request(URLUtil.toDataRoot("boston/meta.json"))
        .then(response => response.json())
        .then(function(data) {
          data.reference = getReference(data.reference);
          data.bounds = createBounds(data.reference, data.bounds);
          data.baseURL = URLUtil.toDataRoot("boston/{z}_{x}_{y}.png");
          return new UrlTileSetModel(data);
        }).then(function(model) {
          return new RasterTileSetLayer(model, {
            label: "Earth Image",
            layerType: LayerType.BASE
          });
        });
  }
  
  function createBingModel(options) {
    options = options || {};
    var proxyUri = options.proxyUri || "/sampleservices/bingproxy/";
    var type = options.type || "Aerial";
  
    //return a promise for the bing layer.
    return request(proxyUri + type)
      .then(response => response.json())
      .then(formatResourceData)
      .then(function(resource) {
        return new BingMapsTileSetModel(resource);
      })
      .catch(throwBingProxyError);
  }
  
  export var LayerConfigUtil = {
    createBingLayer: function(options) {
      return createBingModel(options).then(createBingLayer);
    },
  
    createBackgroundLayer: createFallbackBackgroundLayer,
    createFusionBackgroundLayer: createFusionBackgroundLayer,
    createFallbackBackgroundLayer: createFallbackBackgroundLayer,
    createFusionElevationLayer: createFusionElevationLayer,
  
    addBackgroundRaster: function(map) {
      createFallbackBackgroundLayer().then(function(tileSetLayer) {
        map.layerTree.addChild(tileSetLayer, "bottom");
      });
    },
  
    createCitiesPainter: function(options) {
      options = options || {};
      //#snippet memoization
      var citiesPainter = new FeaturePainter();
      citiesPainter.cache = {};
      citiesPainter.paintBody = function(geoCanvas, feature, shape, layer, map, state) {
        let dimension = this.getIconSize(feature);
        let hash = dimension.toString() + state.selected + state.hovered;
        var icon = this.cache[hash];
        if (!icon) {
          let stroke = "rgba(255,255,255,1.0)";
          let fill = "rgba(255,0,0,0.5)";
          let strokeWidth = 2;
          let width = dimension;
          let height = dimension;
          if (state.selected) {
            stroke = "rgba(0,0,255,1.0)";
            fill = "rgba(255,0,255,0.7)";
            strokeWidth = 3;
          }
          if (state.hovered) {
            width = height = dimension * 1.33;
          }
          icon = IconFactory.circle({
            stroke,
            fill,
            strokeWidth,
            width,
            height
          });
          this.cache[hash] = icon;
        }
        //#endsnippet memoization
        geoCanvas.drawIcon(shape.focusPoint, {
          width: icon.width + "px",
          height: icon.height + "px",
          image: icon
        });
        //#snippet memoization
      };
      //#endsnippet memoization
  
      var emptyStyle = {
        offset: 20,
        pin: {
          endPosition: PinEndPosition.MIDDLE,
          width: 1
        }
      };
      var htmlTemplateNormal = '<div style="background-color: rgb(40,40,40); padding: 5px; border-radius: 10px; color: rgb(238,233,233)">$city, $state</div>';
      var htmlTemplateSelect = '<div style="background-color: rgb(80,10,10); padding: 5px; border-radius: 10px; color: rgb(238,233,233)">$city, $state</div>';
      citiesPainter.paintLabel = function(labelCanvas, feature, shape, layer, map, state) {
        var html;
        if (state.selected || options.labelsAlwaysVisible) {
          html = state.selected ? htmlTemplateSelect : htmlTemplateNormal;
          html = html.replace('$city', feature.properties.CITY);
          html = html.replace('$state', feature.properties.STATE);
          labelCanvas.drawLabel(html, shape, emptyStyle);
        }
      };
  
      citiesPainter.getIconSize = function(feature) {
        return Math.round(Math.sqrt(Math.round(feature.properties.TOT_POP / 10000) * 10000 / Math.PI) / 28);
      };
      return citiesPainter;
    },
  
    createWorldLayer: function() {
      // world layer
      var smallWorldStore = new UrlStore({
        target: URLUtil.toDataRoot("world.json")
      });
      var smallWorldModel = new FeatureModel(smallWorldStore, {
        reference: EPSG4326
      });
  
      return new FeatureLayer(smallWorldModel, {
        label: "World Countries",
        layerType: LayerType.STATIC,
        selectable: true
      });
    },
  
    addWorldLayer: function(map) {
      var layer = this.createWorldLayer();
      map.layerTree.addChild(layer);
      return layer;
    },
    addCitiesLayer: function(map, painter, options) {
  
      /************************** CITIES Layer *****************************/
      var citiesStore = new UrlStore({
        target: URLUtil.toDataRoot("cities.json")
      });
  
      var citiesModel = new FeatureModel(citiesStore, {
        reference: CRS84
      });
  
      var citiesPainter;
      if (painter) {
        citiesPainter = painter;
      } else {
        citiesPainter = this.createCitiesPainter(options);
      }
  
      var citiesLayer = new FeatureLayer(citiesModel, {
        label: "USA Cities",
        layerType: LayerType.STATIC,
        painter: citiesPainter,
        selectable: true
      });
      map.layerTree.addChild(citiesLayer);
      return citiesLayer;
    },
    //#snippet createLonLatGridLayer
    createLonLatGridLayer: function() {
      //Define scale ranges and create a grid
      var settings = [{
        scale: 40000.0E-9,
        deltaLon: 1 / 60,
        deltaLat: 1 / 60
      }, {
        scale: 20000.0E-9,
        deltaLon: 1 / 30,
        deltaLat: 1 / 30
      }, {
        scale: 10000.0E-9,
        deltaLon: 1 / 10,
        deltaLat: 1 / 10
      }, {
        scale: 5000.0E-9,
        deltaLon: 1 / 2,
        deltaLat: 1 / 2
      }, {
        scale: 1000.0E-9,
        deltaLon: 1,
        deltaLat: 1
      }, {
        scale: 200.0E-9,
        deltaLon: 5,
        deltaLat: 5
      }, {
        scale: 20.0E-9,
        deltaLon: 10,
        deltaLat: 10
      }, {
        scale: 9.0E-9,
        deltaLon: 20,
        deltaLat: 20
      }, {
        scale: 5.0E-9,
        deltaLon: 30,
        deltaLat: 30
      }, {
        scale: 0,
        deltaLon: 45,
        deltaLat: 45
      }];
      var grid = new LonLatGrid(settings);
  
      //Set the default styling for grid lines and labels
      grid.fallbackStyle = {
        labelFormat: new LonLatPointFormat({
          pattern: "lat(+DM),lon(+DM)"
        }),
        originLabelFormat: new LonLatPointFormat({
          pattern: "lat(+D),lon(+D)"
        }),
        originLineStyle: {
          color: "rgba(230, 20, 20, 0.6)",
          width: 2
        },
        lineStyle: {
          color: "rgba(210,210,210,0.6)",
          width: 1
        },
        originLabelStyle: {
          fill: "rgba(210,210,210,0.8)",
          halo: "rgba(230, 20, 20, 0.8)",
          haloWidth: 3,
          font: "12px sans-serif"
        },
        labelStyle: {
          fill: "rgb(220,220,220)",
          halo: "rgb(102,102,102)",
          haloWidth: 3,
          font: "12px sans-serif"
        }
      };
  
      //Use simplified labels when zoomed out a lot.
      var degreesOnlyFormat = new LonLatPointFormat({
        pattern: "lat(+D),lon(+D)"
      });
      grid.setStyle(grid.scales.indexOf(0), {
        labelFormat: degreesOnlyFormat
      });
      grid.setStyle(grid.scales.indexOf(5.0E-9), {
        labelFormat: degreesOnlyFormat
      });
      grid.setStyle(grid.scales.indexOf(9.0E-9), {
        labelFormat: degreesOnlyFormat
      });
      grid.setStyle(grid.scales.indexOf(20.0E-9), {
        labelFormat: degreesOnlyFormat
      });
      grid.setStyle(grid.scales.indexOf(200.0E-9), {
        labelFormat: degreesOnlyFormat
      });
      return new GridLayer(grid, {
        label: "Grid"
      });
    },
    //#endsnippet createLonLatGridLayer
    addLonLatGridLayer: function(map) {
      var grid = this.createLonLatGridLayer();
      map.layerTree.addChild(grid, "top");
      return grid;
    },
  
    createContextMenu: function(contextMenu, map, contextMenuInfo) {
      if (contextMenuInfo.objects.length > 0) {
        //we do not want to allow users to edit or delete clusters
        for (var i = 0; i < contextMenuInfo.objects.length; i++) {
          var feature = contextMenuInfo.objects[i];
          if (isCluster(feature)) {
            return;
          }
        }
        if (contextMenuInfo.layer.editable && contextMenuInfo.layer.visibleInTree) {
          contextMenu.addItem({
            id: "EDIT_ID",
            label: "Edit",
            action: function() {
              map.controller = new EditController(contextMenuInfo.layer, contextMenuInfo.objects[0], {
                finishOnSingleClick: true
              });
            }
          });
          contextMenu.addItem({
            id: "DELETE_ID",
            label: "Delete",
            action: function() {
              map.selectObjects([]);
              contextMenuInfo.layer.workingSet.remove(contextMenuInfo.objects[0].id);
            }
          });
        }
      }
    }
  };
  