// -*- mode: javascript; indent-tabs-mode: nil; c-basic-offset: 8 -*-
"use strict";

// Base layers configuration
//			"url" : "https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png"
//			"url" : "http://{a-c}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png"
//			"url" : "http://{a-c}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png"
//			"url" : "https://korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}"
//			"url" : "https://korona.geog.uni-heidelberg.de/tiles/asterh/x={x}&y={y}&z={z}"
//			"url" : "https://{a-c}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png"
//			"url" : "http://{a-c}.tilessputnik.ru/tiles/kmt2/{z}/{x}/{y}.png"
//			"url" : "https://{a-c}.tile.openstreetmap.se/hydda/full/{z}/{x}/{y}.png"
//			"url" : "https://{a-c}.tile.openstreetmap.se/osm/{z}/{x}/{y}.png"

function createBaseLayers() {
    let layers = new ol.Collection();
    let layers_group = new ol.layer.Group({
        layers: layers,
    });

    let world = new ol.Collection();
    let us = new ol.Collection();
    let europe = new ol.Collection();

    const tileTransition = onMobile ? 0 : 0;

    if (loStore['customTiles'] != undefined) {
        custom_layers.push(new ol.layer.Tile({
            source: new ol.source.OSM({
                "url" : loStore['customTiles'],
                maxZoom: 20,
                transition: tileTransition,
            }),
            name: 'custom_tiles',
            title: 'Custom tiles',
            type: 'base',
        }));
    }
    /*
    world.push(new ol.layer.Tile({
        source: new ol.source.OSM({
            "url" : "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png"
        }),
        name: 'wikimedia',
        title: 'OpenStreetMap Wikimedia',
        type: 'base',
    }));
    */

    if (offlineMapDetail > 0) {
        world.push(new ol.layer.Tile({
            source: new ol.source.OSM({
                "url" : "osm_tiles_offline/{z}/{x}/{y}.png",
                attributionsCollapsible: false,
                maxZoom: offlineMapDetail,
                transition: tileTransition,
            }),
            name: 'osm_tiles_offline',
            title: 'OpenStreetMap offline',
            type: 'base',
        }));
    }

    world.push(new ol.layer.Tile({
        source: new ol.source.OSM({
            maxZoom: 17,
            attributionsCollapsible: false,
            transition: tileTransition,
        }),
        name: 'osm',
        title: 'OpenStreetMap',
        type: 'base',
    }));

    let basemap_id = "rastertiles/voyager";
    world.push(new ol.layer.Tile({
        source: new ol.source.OSM({
            "url" : "https://{a-d}.basemaps.cartocdn.com/"+ basemap_id + "/{z}/{x}/{y}.png",
            "attributions" : 'Powered by <a href="https://carto.com">CARTO.com</a>'
            + ' using data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.',
            attributionsCollapsible: false,
            maxZoom: 15,
            transition: tileTransition,
        }),
        name: "carto_" + basemap_id,
        title: 'CARTO.com English',
        type: 'base',
    }));

    world.push(new ol.layer.Tile({
        source: new ol.source.OSM({
            "url" : "https://{a-d}.tile.openstreetmap.de/{z}/{x}/{y}.png",
            attributionsCollapsible: false,
            maxZoom: 17,
            transition: tileTransition,
        }),
        name: 'osm_de',
        title: 'OpenStreetMap DE',
        type: 'base',
    }));

    if (offlineMapDetailOFM > 0) {
        world.push(new ol.layer.VectorTile({
            type: 'base',
            name: 'OpenFreeMapOfflineBright',
            title: 'OpenFreeMap Offl. Bright',
            onVisible: (layer) => {
                if (!layer.get('styleApplied')) {
                    ol.mapboxStyle.applyStyle(layer, "./openfreemap_offline/bright");
                    ol.mapboxStyle.applyBackground(layer, "./openfreemap_offline/bright");
                    layer.set('styleApplied', true);
                }
            },
        }));
        world.push(new ol.layer.VectorTile({
            type: 'base',
            name: 'OpenFreeMapOfflineLiberty',
            title: 'OpenFreeMap Offl. Lib.',
            onVisible: (layer) => {
                if (!layer.get('styleApplied')) {
                    ol.mapboxStyle.applyStyle(layer, "./openfreemap_offline/liberty");
                    ol.mapboxStyle.applyBackground(layer, "./openfreemap_offline/liberty");
                    layer.set('styleApplied', true);
                }
            },
        }));
        world.push(new ol.layer.VectorTile({
            type: 'base',
            name: 'OpenFreeMapOfflinePositron',
            title: 'OpenFreeMap Offl. Pos.',
            onVisible: (layer) => {
                if (!layer.get('styleApplied')) {
                    ol.mapboxStyle.applyStyle(layer, "./openfreemap_offline/positron");
                    ol.mapboxStyle.applyBackground(layer, "./openfreemap_offline/positron");
                    layer.set('styleApplied', true);
                }
            },
        }));
        world.push(new ol.layer.VectorTile({
            type: 'base',
            name: 'OpenFreeMapOfflineDark',
            title: 'OpenFreeMap Offl. Dark',
            onVisible: (layer) => {
                if (!layer.get('styleApplied')) {
                    ol.mapboxStyle.applyStyle(layer, "./openfreemap_offline/dark");
                    ol.mapboxStyle.applyBackground(layer, "./openfreemap_offline/dark");
                    layer.set('styleApplied', true);
                }
            },
        }));
        world.push(new ol.layer.VectorTile({
            type: 'base',
            name: 'OpenFreeMapOfflineFiord',
            title: 'OpenFreeMap Offl. Fiord',
            onVisible: (layer) => {
                if (!layer.get('styleApplied')) {
                    ol.mapboxStyle.applyStyle(layer, "./openfreemap_offline/fiord");
                    ol.mapboxStyle.applyBackground(layer, "./openfreemap_offline/fiord");
                    layer.set('styleApplied', true);
                }
            },
        }));
    }
    if (1) {
        world.push(new ol.layer.VectorTile({
            type: 'base',
            name: 'OpenFreeMapBright',
            title: 'OpenFreeMap Bright',
            onVisible: (layer) => {
                if (!layer.get('styleApplied')) {
                    ol.mapboxStyle.applyStyle(layer, "https://tiles.openfreemap.org/styles/bright");
                    ol.mapboxStyle.applyBackground(layer, "https://tiles.openfreemap.org/styles/bright");
                    layer.set('styleApplied', true);
                }
            },
        }));
    }
    if (1) {
        world.push(new ol.layer.VectorTile({
            type: 'base',
            name: 'OpenFreeMapLiberty',
            title: 'OpenFreeMap Liberty',
            onVisible: (layer) => {
                if (!layer.get('styleApplied')) {
                    ol.mapboxStyle.applyStyle(layer, "https://tiles.openfreemap.org/styles/liberty");
                    ol.mapboxStyle.applyBackground(layer, "https://tiles.openfreemap.org/styles/liberty");
                    layer.set('styleApplied', true);
                }
            },
        }));
    }
    if (1) {
        world.push(new ol.layer.VectorTile({
            type: 'base',
            name: 'OpenFreeMapPositron',
            title: 'OpenFreeMap Positron',
            onVisible: (layer) => {
                if (!layer.get('styleApplied')) {
                    ol.mapboxStyle.applyStyle(layer, "https://tiles.openfreemap.org/styles/positron");
                    ol.mapboxStyle.applyBackground(layer, "https://tiles.openfreemap.org/styles/positron");
                    layer.set('styleApplied', true);
                }
            },
        }));
    }
    if (1) {
        world.push(new ol.layer.VectorTile({
            type: 'base',
            name: 'OpenFreeMapDark',
            title: 'OpenFreeMap Dark',
            onVisible: (layer) => {
                if (!layer.get('styleApplied')) {
                    ol.mapboxStyle.applyStyle(layer, "https://tiles.openfreemap.org/styles/dark");
                    ol.mapboxStyle.applyBackground(layer, "https://tiles.openfreemap.org/styles/dark");
                    layer.set('styleApplied', true);
                }
            },
        }));
    }
    if (1) {
        world.push(new ol.layer.VectorTile({
            type: 'base',
            name: 'OpenFreeMapFiord',
            title: 'OpenFreeMap Fiord',
            onVisible: (layer) => {
                if (!layer.get('styleApplied')) {
                    ol.mapboxStyle.applyStyle(layer, "https://tiles.openfreemap.org/styles/fiord");
                    ol.mapboxStyle.applyBackground(layer, "https://tiles.openfreemap.org/styles/fiord");
                    layer.set('styleApplied', true);
                }
            },
        }));
    }


    if (1) {
        world.push(new ol.layer.Tile({
            source: new ol.source.XYZ({
                url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
                attributions: 'Powered by <a href="https://www.esri.com">Esri.com</a>' +
                '— Sources: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',
                attributionsCollapsible: false,
                maxZoom: 17,
                transition: tileTransition,
            }),
            name: 'esri',
            title: 'ESRI.com Sat.',
            type: 'base',
        }));
        world.push(new ol.layer.Tile({
            source: new ol.source.XYZ({
                url: "https://services.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}",
                attributions: 'Powered by <a href="https://www.esri.com">Esri.com</a>' +
                '— Sources: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',
                attributionsCollapsible: false,
                maxZoom: 16,
                transition: tileTransition,
            }),
            name: 'esri_gray',
            title: 'ESRI.com Gray',
            type: 'base',
        }));
        world.push(new ol.layer.Tile({
            source: new ol.source.XYZ({
                url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}",
                attributions: 'Powered by <a href="https://www.esri.com">Esri.com</a>' +
                '— Sources: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',
                attributionsCollapsible: false,
                maxZoom: 17,
                transition: tileTransition,
            }),
            name: 'esri_streets',
            title: 'ESRI.com Streets',
            type: 'base',
        }));
    }

    // testing ...
    if (0) {
        let english_map = new ol.layer.VectorTile({
            declutter: true,
            type: 'base',
            name: 'english_map',
            title: 'English Map',
        });
        // ol-mapbox-style plugin packed in with ol ... (kinda ugly)
        //ol.applyStyle(english_map, "https://tiles.adsb.co/api/maps/basic/style.json");
        world.push(english_map);
    }

    if (0) {
        let vtlayer = new ol.layer.VectorTile({
            source: new ol.source.VectorTile({
                url: "http://test02.dev.adsf.com/tiles/{z}/{x}/{y}.pbf",
                format: new ol.format.MVT(),
                maxZoom: 9,
                transition: tileTransition,
            }),
            name: 'vtlayer',
            title: 'TEST VECTOR',
            type: 'base',
            renderMode: 'image',
        });

        jQuery.ajax({
            url: 'osm-liberty/style.json',
            dataType: 'json',
            layer: vtlayer,
            cache: false,
        }).done(function(glStyle) {
            ol.mbApplyStyle(this.layer, glStyle, 'openmaptiles');
        });

        world.push(vtlayer);
    }

    /* seems defunct
    world.push(new ol.layer.Tile({
        source: new ol.source.OSM({
            url: 'https://gibs-{a-c}.earthdata.nasa.gov/wmts/epsg3857/best/BlueMarble_ShadedRelief_Bathymetry/default/EPSG3857_500m/{z}/{y}/{x}.jpeg',
            attributions: '<a href="https://terra.nasa.gov/about/terra-instruments/modis">MODIS Terra</a> ' +
            'Provided by NASA\'s Global Imagery Browse Services (GIBS), part of NASA\'s Earth Observing System Data and Information System (EOSDIS)',
            maxZoom: 8,
            transition: tileTransition,
        }),
        name: 'gibs_relief',
        title: 'GIBS Relief',
        type: 'base',
    }));
    */

    const date = new Date(Date.now() - 86400 * 1000);
    const yesterday = date.getUTCFullYear() + '-' + (date.getUTCMonth() + 1).toString().padStart(2, '0') + '-' + date.getUTCDate().toString().padStart(2, '0');
    world.push(new ol.layer.Tile({
        source: new ol.source.OSM({
            url: 'https://gibs-{a-c}.earthdata.nasa.gov/wmts/epsg3857/best/' +
            'MODIS_Terra_CorrectedReflectance_TrueColor/default/' +
            yesterday + '/' +
            'GoogleMapsCompatible_Level9/{z}/{y}/{x}.jpg',
            attributions: '<a href="https://terra.nasa.gov/about/terra-instruments/modis">MODIS Terra</a> ' +
            yesterday + ' Provided by NASA\'s Global Imagery Browse Services (GIBS), part of NASA\'s Earth Observing System Data and Information System (EOSDIS)',
            maxZoom: 9,
            transition: tileTransition,
        }),
        name: 'gibs',
        title: 'GIBS Clouds ' + yesterday,
        type: 'base',
    }));
    // carto.com basemaps, see the following URLs for details on them:
    // http://basemaps.cartocdn.com
    // https://github.com/CartoDB/cartodb/wiki/BaseMaps-available

    let basemaps = [ "dark_all", "dark_nolabels",
        "light_all", "light_nolabels"
    ]

    if (1) {
        for (let i in basemaps) {
            let basemap_id = basemaps[i];

            world.push(new ol.layer.Tile({
                source: new ol.source.OSM({
                    "url" : "https://{a-d}.basemaps.cartocdn.com/"+ basemap_id + "/{z}/{x}/{y}.png",
                    "attributions" : 'Powered by <a href="https://carto.com">CARTO.com</a>'
                    + ' using data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.',
                    attributionsCollapsible: false,
                    maxZoom: 15,
                    transition: tileTransition,
                }),
                name: "carto_" + basemap_id,
                title: 'CARTO.com ' + basemap_id,
                type: 'base',
            }));
        }
    }

    if (loStore['bingKey'] != undefined)
        BingMapsAPIKey = loStore['bingKey'];

    if (BingMapsAPIKey) {
        world.push(new ol.layer.Tile({
            source: new ol.source.BingMaps({
                key: BingMapsAPIKey,
                imagerySet: 'Aerial',
                transition: tileTransition,
            }),
            name: 'bing_aerial',
            title: 'Bing Aerial',
            type: 'base',
        }));
        world.push(new ol.layer.Tile({
            source: new ol.source.BingMaps({
                key: BingMapsAPIKey,
                imagerySet: 'RoadOnDemand',
                transition: tileTransition,
            }),
            name: 'bing_roads',
            title: 'Bing Roads',
            type: 'base',
        }));
    }

    if (loStore['mapboxKey'] != undefined)
        MapboxAPIKey = loStore['mapboxKey'];

    if (MapboxAPIKey) {
        world.push(new ol.mapboxStyle.MapboxVectorLayer({
            styleUrl: 'mapbox://styles/mapbox/streets-v10',
            accessToken: MapboxAPIKey,
            properties: {
                name: 'mapbox_streets',
                title: 'Mapbox Streets',
                type: 'base',
            },
        }));
        world.push(new ol.mapboxStyle.MapboxVectorLayer({
            styleUrl: 'mapbox://styles/mapbox/light-v11',
            accessToken: MapboxAPIKey,
            properties: {
                name: 'mapbox_light',
                title: 'Mapbox Light',
                type: 'base',
            },
        }));
        world.push(new ol.mapboxStyle.MapboxVectorLayer({
            styleUrl: 'mapbox://styles/mapbox/dark-v11',
            accessToken: MapboxAPIKey,
            properties: {
                name: 'mapbox_dark',
                title: 'Mapbox Dark',
                type: 'base',
            },
        }));
        world.push(new ol.mapboxStyle.MapboxVectorLayer({
            styleUrl: 'mapbox://styles/mapbox/outdoors-v10',
            accessToken: MapboxAPIKey,
            properties: {
                name: 'mapbox_outdoors',
                title: 'Mapbox Outdoors',
                type: 'base',
            },
        }));
    }

    if (1) {
        us.push(new ol.layer.Tile({
            source: new ol.source.XYZ({
                url: "https://tiles.arcgis.com/tiles/ssFJjBXIUyZDrSYZ/arcgis/rest/services/VFR_Sectional/MapServer/tile/{z}/{y}/{x}",
                attributions: 'Tiles courtesy of <a href="http://tiles.arcgis.com/">arcgis.com</a>',
                attributionsCollapsible: false,
                minZoom: 8,
                maxZoom: 12,
                transition: tileTransition,
            }),
            name: 'VFR_Sectional',
            title: 'VFR Sectional Chart',
            type: 'base'
        }));

        us.push(new ol.layer.Tile({
            source: new ol.source.XYZ({
                url: "https://tiles.arcgis.com/tiles/ssFJjBXIUyZDrSYZ/arcgis/rest/services/VFR_Terminal/MapServer/tile/{z}/{y}/{x}",
                attributions: 'Tiles courtesy of <a href="http://tiles.arcgis.com/">arcgis.com</a>',
                attributionsCollapsible: false,
                minZoom: 10,
                maxZoom: 12,
                transition: tileTransition,
            }),
            name: 'VFR_Terminal',
            title: 'VFR Terminal Chart',
            type: 'base'
        }));

        us.push(new ol.layer.Tile({
            source: new ol.source.XYZ({
                url: "https://tiles.arcgis.com/tiles/ssFJjBXIUyZDrSYZ/arcgis/rest/services/IFR_AreaLow/MapServer/tile/{z}/{y}/{x}",
                attributions: 'Tiles courtesy of <a href="http://tiles.arcgis.com/">arcgis.com</a>',
                attributionsCollapsible: false,
                minZoom: 8,
                maxZoom: 11,
                transition: tileTransition,
            }),
            name: 'IFR_AreaLow',
            title: 'IFR Enroute Chart Low',
            type: 'base'
        }));

        us.push(new ol.layer.Tile({
            source: new ol.source.XYZ({
                url: "https://tiles.arcgis.com/tiles/ssFJjBXIUyZDrSYZ/arcgis/rest/services/IFR_High/MapServer/tile/{z}/{y}/{x}",
                attributions: 'Tiles courtesy of <a href="http://tiles.arcgis.com/">arcgis.com</a>',
                attributionsCollapsible: false,
                minZoom: 7,
                maxZoom: 11,
                transition: tileTransition,
            }),
            name: 'IFR_High',
            title: 'IFR Enroute Chart High',
            type: 'base'
        }));
    }

/*     if (ChartBundleLayers) {

        let chartbundleTypesDirect = {
            sec: "Sectional Charts",
            enrh: "IFR Enroute High Charts",

            tac: "Terminal Area Charts",
            hel: "Helicopter Charts",
            enrl: "IFR Enroute Low Charts",
            enra: "IFR Area Charts",
            secgrids: "Sect. w/ SAR grid",
        };

        for (let type in chartbundleTypesDirect) {
            us.push(new ol.layer.Tile({
                source: new ol.source.TileWMS({
                    url: 'https://wms.chartbundle.com/wms',
                    params: {LAYERS: type},
                    projection: 'EPSG:3857',
                    attributions: 'Tiles courtesy of <a href="http://www.chartbundle.com/">ChartBundle</a>',
                    attributionsCollapsible: false,
                    maxZoom: 12, // doesn't work for WMS
                    transition: tileTransition,
                }),
                name: 'chartbundle_' + type,
                title: chartbundleTypesDirect[type],
                type: 'base',
                group: 'chartbundle'}));
        }
    } */

    world.push(new ol.layer.Tile({
        source: new ol.source.XYZ({
            "url" : "https://map.adsbexchange.com/mapproxy/tiles/1.0.0/openaip/ul_grid/{z}/{x}/{y}.png",
            "attributions" : "openAIP.net",
            attributionsCollapsible: false,
            maxZoom: 12,
            transition: tileTransition,
        }),
        name: 'openaip',
        title: 'openAIP TMS',
        type: 'overlay',
        opacity: openAIPOpacity,
        visible: false,
        zIndex: 99,
        maxZoom: 13,
    }));

    if (true) {
        let tfrLayer = new ol.layer.Vector({
            style: {
                'stroke-width': 2,
                'stroke-color': 'red',
                'fill-color': [255, 0, 0, 0.08],
                'text-value': [
                    'concat',
                    ['get', 'NOTAM_KEY'],
                    '\n',
                    ['get', 'LEGAL']
                ],
                'text-font': '14px sans-serif',
                'text-stroke-width': 2,
                'text-stroke-color': 'black'
            },
            name: 'tfrs',
            title: 'TFRs',
            type: 'overlay',
            opacity: tfrOpacity,
            visible: false,
            zIndex: 99,
        });

        us.push(tfrLayer);

        let refreshTFR = function() {
            let source = new ol.source.Vector({
                url: 'https://raw.githubusercontent.com/wiedehopf/tar1090-aux/master/tfrs.geojson',
                format: new ol.format.GeoJSON(),
                attributions: 'TFRs via FAA hosted on github: <a href="https://github.com/wiedehopf/tar1090-aux" target="_blank">tar1090-aux</a>.'
            });
            tfrLayer.setSource(source);
        };

        refreshTFR();
        window.setInterval(refreshTFR, 5 * 60 * 1000);
    }

    us.push(new ol.layer.Vector({
        type: 'overlay',
        title: 'Special Use Airspace',
        name: 'sua',
        zIndex: 99,
        visible: false,
        source: new ol.source.Vector({
            url: 'https://opendata.arcgis.com/datasets/dd0d1b726e504137ab3c41b21835d05b_0.geojson',
            transition: tileTransition,
            format: new ol.format.GeoJSON({
                defaultDataProjection: 'EPSG:4326',
                projection: 'EPSG:3857'
            })
        }),
        style: function style(feature) {
            let type = feature.getProperties().TYPE_CODE;
            if (type == "P" || type == "R" || type == "W") {
                return new ol.style.Style({
                    stroke: new ol.style.Stroke({
                        color: 'rgba(72, 149, 239, 1)',
                        width: 2
                    }),
                    fill: new ol.style.Fill({
                        color: 'rgba(72, 149, 239, 0.3)',
                    })
                })
            } else if (type == "A" || type == "MOA") {
                return new ol.style.Style({
                    stroke: new ol.style.Stroke({
                        color: 'rgba(133, 45, 69, 1)',
                        width: 2
                    }),
                    fill: new ol.style.Fill({
                        color : 'rgba(133, 45, 69, 0.3)'
                    })
                });
            }
        }
    }));

    // nexrad and noaa stuff
    const bottomLeft = ol.proj.fromLonLat([-171.0,9.0]);
    const topRight = ol.proj.fromLonLat([-51.0,69.0]);
    const naExtent = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]];

    if (true) {

        let nexrad = new ol.layer.Tile({
            name: 'nexrad',
            title: 'NEXRAD',
            type: 'overlay',
            opacity: nexradOpacity,
            visible: false,
            zIndex: 99,
            //extent: naExtent,
            // this somehow seems to cause webgl errors.
            // workaround by just not using this, it's not important
        });

        let refreshNexrad = function() {
            // re-build the source to force a refresh of the nexrad tiles
            let now = new Date().getTime();
            let nexradSource = new ol.source.XYZ({
                url : 'https://mesonet{1-3}.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-900913/{z}/{x}/{y}.png?_=' + now,
                attributions: 'NEXRAD courtesy of <a href="https://mesonet.agron.iastate.edu/">IEM</a>',
                attributionsCollapsible: false,
                maxZoom: 8,
            });
            nexrad.setSource(nexradSource);
        };

        refreshNexrad();
        window.setInterval(refreshNexrad, 2 * 60 * 1000);

        us.push(nexrad);
    }
    if (true) {

        let noaaSatSource = new ol.source.ImageWMS({
            attributions: ['NOAA'],
            attributionsCollapsible: false,
            url: 'https://nowcoast.noaa.gov/geoserver/satellite/wms',
            params: {'LAYERS': 'global_longwave_imagery_mosaic'},
            projection: 'EPSG:3857',
            resolutions: [156543.03392804097, 78271.51696402048, 39135.75848201024, 19567.87924100512, 9783.93962050256, 4891.96981025128, 2445.98490512564, 1222.99245256282],
            ratio: 1,
            transition: tileTransition,
        });

        let noaaSat = new ol.layer.Image({
            title: 'NOAA Infrared Sat',
            name: 'noaa_sat',
            zIndex: 99,
            type: 'overlay',
            visible: false,
            source: noaaSatSource,
            opacity: noaaInfraredOpacity,
            extent: naExtent,
        });

        let refreshNoaaSat = function () {
            noaaSatSource.refresh();
        }

        // Refresh sat layer every 15 minutes
        refreshNoaaSat();
        window.setInterval(refreshNoaaSat, 15 * 60 * 1000);

        us.push(noaaSat);
    }
    if (true) {
        let noaaRadarSource = new ol.source.ImageWMS({
            attributions: ['NOAA'],
            attributionsCollapsible: false,
            url: 'https://nowcoast.noaa.gov/geoserver/weather_radar/wms',
            params: {'LAYERS': 'base_reflectivity_mosaic'},
            projection: 'EPSG:3857',
            resolutions: [156543.03392804097, 78271.51696402048, 39135.75848201024, 19567.87924100512, 9783.93962050256, 4891.96981025128, 2445.98490512564, 1222.99245256282],
            ratio: 1,
            transition: tileTransition,
        });

        let noaaRadar = new ol.layer.Image({
            title: 'NOAA Radar',
            name: 'noaa_radar',
            zIndex: 99,
            type: 'overlay',
            visible: false,
            source: noaaRadarSource,
            opacity: noaaRadarOpacity,
            extent: naExtent,
        });

        us.push(noaaRadar);
    }

    if (enableDWD) {
        const bottomLeft = ol.proj.fromLonLat([1.9,46.2]);
        const topRight = ol.proj.fromLonLat([16.0,55.0]);
        const dwdExtent = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]];

        let dwdSource = new ol.source.TileWMS({
            url: 'https://maps.dwd.de/geoserver/wms',
            params: {LAYERS: dwdLayers, validtime: (new Date()).getTime()},
            projection: 'EPSG:3857',
            attributions: 'Deutscher Wetterdienst (DWD)',
            attributionsCollapsible: false,
            tileGrid: ol.tilegrid.createXYZ({
                extent: ol.tilegrid.extentFromProjection('EPSG:3857'),
                maxResolution: 156543.03392804097,
                maxZoom: 8,
                minZoom: 0,
                tileSize: 512,
            }),
            transition: tileTransition,
        });

        let dwd = new ol.layer.Tile({
            source: dwdSource,
            name: 'radolan',
            title: 'DWD RADOLAN',
            type: 'overlay',
            opacity: dwdRadolanOpacity,
            visible: false,
            zIndex: 99,
            //extent: dwdExtent,
            // extent somehow bugged
        });

        let dwdValidtime = "";

        let refreshDwd = function () {
            let ms = Date.now();
            let validtime = (ms - ms % (5 * 60 * 1000)) / 1000;
            if (validtime != dwdValidtime) {
                //console.log(`dwd validtime ${zuluTime(new Date(validtime * 1000))}`);
                dwd.getSource().updateParams({validtime: validtime});
                dwdValidtime = validtime;
            }
        };
        refreshDwd();
        window.setInterval(refreshDwd, 15 * 1000);

        europe.push(dwd);
    }

    if (true) {
        g.getRainviewerLayers = async function(key) {
            const response = await fetch("https://api.rainviewer.com/public/weather-maps.json", {credentials: "omit",});
            const jsonData = await response.json();
            return jsonData[key];
        }

        const rainviewerRadar = new ol.layer.Tile({
            name: 'rainviewer_radar',
            title: 'RainViewer Radar',
            type: 'overlay',
            opacity: rainViewerRadarOpacity,
            visible: false,
            zIndex: 99,
        });
        g.refreshRainviewerRadar = async function() {
            const latestLayer = await g.getRainviewerLayers('radar');
            const rainviewerRadarSource = new ol.source.XYZ({
                url: 'https://tilecache.rainviewer.com/v2/radar/' + latestLayer.past[latestLayer.past.length - 1].time + '/512/{z}/{x}/{y}/6/1_1.png',
                attributions: '<a href="https://www.rainviewer.com/api.html" target="_blank">RainViewer.com</a>',
                attributionsCollapsible: false,
                maxZoom: 7,
            });
            rainviewerRadar.setSource(rainviewerRadarSource);
        };

        rainviewerRadar.on('change:visible', function(evt) {
            if (evt.target.getVisible()) {
                g.refreshRainviewerRadar();
                g.refreshRainviewerRadarInterval = window.setInterval(g.refreshRainviewerRadar, 2 * 60 * 1000);
            } else {
                clearInterval(g.refreshRainviewerRadarInterval);
            }
        });

        world.push(rainviewerRadar);
    }

    let createGeoJsonLayer = function (title, name, url, fill, stroke, showLabel = true) {
        return new ol.layer.Vector({
            type: 'overlay',
            title: title,
            name: name,
            zIndex: 99,
            visible: false,
            source: new ol.source.Vector({
                url: url,
                transition: tileTransition,
                format: new ol.format.GeoJSON({
                    defaultDataProjection :'EPSG:4326',
                    projection: 'EPSG:3857'
                })
            }),
            style: function style(feature) {
                return new ol.style.Style({
                    fill: new ol.style.Fill({
                        color : fill
                    }),
                    stroke: new ol.style.Stroke({
                        color: stroke,
                        width: 1
                    }),
                    text: new ol.style.Text({
                        text: showLabel ? feature.get("name") : "",
                        overflow: OLMap.getView().getZoom() > 5,
                        scale: 1.25,
                        fill: new ol.style.Fill({
                            color: '#000000'
                        }),
                        stroke: new ol.style.Stroke({
                            color: '#FFFFFF',
                            width: 2
                        })
                    })
                });
            }
        });
    };

    // Taken from https://www.ais.pansa.pl/mil/pliki/EP_ENR_2_4_en.pdf
    europe.push(createGeoJsonLayer('PL AWACS Orbits', 'plawacsorbits', 'geojson/PL_Mil_AWACS_Orbits.geojson', 'rgba(252, 186, 3, 0.3)', 'rgba(252, 186, 3, 1)', false));

    // Taken from https://english.defensie.nl/binaries/defence/documenten/publications/2022/12/14/milaip-01-23-part-1-gen-part-2-enr/MILAIP_01_2023split_GEN_ENR.pdf
    europe.push(createGeoJsonLayer('NL AWACS Orbits', 'nlawacsorbits', 'geojson/NL_Mil_AWACS_Orbits.geojson', 'rgba(252, 186, 3, 0.3)', 'rgba(252, 186, 3, 1)', false));

    // Taken from https://github.com/olithissen/AwacsOrbitsDE
    europe.push(createGeoJsonLayer('DE AWACS Orbits', 'deawacsorbits', 'geojson/DE_Mil_AWACS_Orbits.geojson', 'rgba(252, 186, 3, 0.3)', 'rgba(252, 186, 3, 1)', false));

    // Taken from https://github.com/alkissack/Dump1090-OpenLayers3-html
    europe.push(createGeoJsonLayer('UK Radar Corridors', 'ukradarcorridors', 'geojson/UK_Mil_RC.geojson', 'rgba(22, 171, 22, 0.3)', 'rgba(22, 171, 22, 1)'));
    europe.push(createGeoJsonLayer('UK A2A Refueling', 'uka2arefueling', 'geojson/UK_Mil_AAR_Zones.geojson', 'rgba(52, 50, 168, 0.3)', 'rgba(52, 50, 168, 1)'));
    europe.push(createGeoJsonLayer('UK AWACS Orbits', 'ukawacsorbits', 'geojson/UK_Mil_AWACS_Orbits.geojson', 'rgba(252, 186, 3, 0.3)', 'rgba(252, 186, 3, 1)', false));

    us.push(createGeoJsonLayer('US A2A Refueling', 'usa2arefueling', 'geojson/US_A2A_refueling.geojson', 'rgba(52, 50, 168, 0.3)', 'rgba(52, 50, 168, 1)'));

    us.push(createGeoJsonLayer('US ARTCC Boundaries', 'usartccboundaries', 'geojson/US_ARTCC_boundaries.geojson', 'rgba(255, 0, 255, 0.3)', 'rgba(255, 0, 255, 1)', false));

    if (uk_advisory) {
        europe.push(createGeoJsonLayer('uka_airports', 'uka_airports', 'geojson/uk_advisory/airports.geojson', 'rgba(255, 255, 255, 1)', 'rgba(255, 255, 255, 1)'));
        europe.push(createGeoJsonLayer('uka_airspaces', 'uka_airspaces',
            'https://raw.githubusercontent.com/wiedehopf/tar1090-aux/master/uk_advisory/airspaces.geojson',
            'rgba(0, 0, 0, 0.1)', 'rgba(0, 30, 255, 0.2)'));
        //europe.push(createGeoJsonLayer('hotspots', 'hotspots', 'geojson/uk_advisory/hotspots.geojson', 'rgba(255, 255, 255, 1)', 'rgba(255, 255, 255, 1)'));
        //europe.push(createGeoJsonLayer('navaids', 'navaids', 'geojson/uk_advisory/navaids.geojson', 'rgba(0, 0, 0, 1)', 'rgba(0, 0, 0, 1)'));
        europe.push(createGeoJsonLayer('uka_runways', 'uka_runways', 'geojson/uk_advisory/runways.geojson', 'rgba(0, 0, 0, 0.5)', 'rgba(0, 0, 0, 0.5)'));
        europe.push(createGeoJsonLayer('uka_shoreham', 'uka_shoreham', 'geojson/uk_advisory/shoreham.geojson', 'rgba(0, 0, 0, 0.5)', 'rgba(0, 0, 0, 0.5)'));
    }

    if (l3harris) {
        let files = ['IFT_NAV_Routes.geojson','IFT_Training_Areas.geojson','USAFA_Training_Areas.geojson'];
        for (let i in files) {
            let name = files[i].split('.')[0];
            us.push(createGeoJsonLayer(name, 'ift' + i, 'geojson/IFT/' + files[i], 'rgba(52, 50, 168, 0.3)', 'rgba(52, 50, 168, 1)'));
        }
    }

    if (usp.has('aiscatcher_server')) { aiscatcher_server = usp.get('aiscatcher_server'); }
    if (aiscatcher_server == 'disable' || heatmap || replay ) {
        aiscatcher_server = "";
    }
    if (aiscatcher_server) {

        g.aiscatcher_source = new ol.source.Vector({
            format: new ol.format.GeoJSON(),
        });

        const aiscatcher_mapping = {
            0: { size: [20, 20], offset: [120, 20], comment: 'CLASS_OTHER' },
            1: { size: [20, 20], offset: [120, 20], comment: 'CLASS_UNKNOWN' },
            2: { size: [20, 20], offset: [0, 20], comment: 'CLASS_CARGO' },
            3: { size: [20, 20], offset: [20, 20], comment: 'CLASS_B' },
            4: { size: [20, 20], offset: [40, 20], comment: 'CLASS_PASSENGER' },
            5: { size: [20, 20], offset: [60, 20], comment: 'CLASS_SPECIAL' },
            6: { size: [20, 20], offset: [80, 20], comment: 'CLASS_TANKER' },
            7: { size: [20, 20], offset: [100, 20], comment: 'CLASS_HIGHSPEED' },
            8: { size: [20, 20], offset: [140, 20], comment: 'CLASS_FISHING' },
            9: { size: [25, 25], offset: [0, 60], comment: 'CLASS_PLANE' },
            10: { size: [25, 25], offset: [0, 85], comment: 'CLASS_HELICOPTER' },
            11: { size: [20, 20], offset: [20, 40], comment: 'CLASS_STATION' },
            12: { size: [20, 20], offset: [0, 40], comment: 'CLASS_ATON' },
            13: { size: [20, 20], offset: [40, 40], comment: 'CLASS_SARTEPIRB' }
        };

        g.aiscatcherLayer = new ol.layer.Vector({
            type: 'overlay',
            title: "aiscatcher",
            name: "aiscatcher",
            zIndex: 99,
            source: g.aiscatcher_source,

            style: function (feature) {
                const cog = feature.get('cog');
                const rotation = (cog || 0) * (Math.PI / 180);
                const shipclass = feature.get('shipclass');
                const speed = feature.get('speed');

                const ofs = aiscatcher_mapping[shipclass].offset;
                const size = aiscatcher_mapping[shipclass].size;

                let o;
                if (speed && speed > 0.5) {
                    o = [ofs[0], 0];
                } else {
                    o = ofs;
                }

                return new ol.style.Style({
                    image: new ol.style.Icon({
                        src: aiscatcher_server + '/icons.png',
                        anchor: [0.5, 0.5],
                        rotation: rotation,
                        size: size,
                        offset: o
                    })
                });
            }
        });
        world.push(g.aiscatcherLayer);
    }

    layers.push(new ol.layer.Group({
        name: 'custom',
        title: 'Custom',
        layers: custom_layers,
    }));

    if (europe.getLength() > 0) {
        layers.push(new ol.layer.Group({
            name: 'europe',
            title: 'Europe',
            layers: new ol.Collection(europe.getArray().reverse()),
            fold: 'open',
        }));
    }

    if (us.getLength() > 0) {
        layers.push(new ol.layer.Group({
            name: 'us',
            title: 'US',
            layers: new ol.Collection(us.getArray().reverse()),
            fold: 'open',
        }));
    }

    if (world.getLength() > 0) {
        layers.push(new ol.layer.Group({
            name: 'world',
            title: 'Worldwide',
            layers: new ol.Collection(world.getArray().reverse()),
            //fold: 'open',
        }));
    }




    return layers_group;
}
