| /**
 * Openlayers's Library
 *
 * @author     Aby Dahana <[email protected] >
 * @copyright  (c) Aksara Laboratory <https://aksaracms.com>
 * @license    MIT License
 *
 * This source file is subject to the MIT license that is bundled with this
 * source code in the LICENSE.txt file.
 */
"use strict";
let context,
    map,
    layerVector,
    layerOverlap,
    clickedPoint,
    measurementVector,
    routeWayPoints,
    routeLineString,
    selected,
    selectionBox,
    drawingManager,
    drawingType,
    draggableMarker,
    lngLat,
    colorscheme,
    fill_color,
    stroke_color,
    stroke_width,
    icon_pattern,
    icon_scale,
    geocoder,
    geolocation,
    apply_coordinate,
    apply_latitude,
    apply_longitude,
    apply_address,
    apply_measurement,
    apply_route_from,
    apply_route_to,
    source_url,
    rest_url,
    wms_url,
    wmsTile,
    rasterTile,
    panorama,
    clicked,
    previousCenter = [],
    minZoom = 4,
    maxZoom = 20,
    features = [],
    projection = 'EPSG:4326',
    popup = new ol.Overlay.Popup(),
    highlight = new ol.interaction.Select(),
    highlighted,
    tileSource;
const openlayers = (function() {
    return {
        render: function(_this) {
            /**
             * Render map
             */
            context = _this;
            
            if (context.data('tiles-url') && context.data('tiles-url')) {
                let provider = (new URL(context.data('tiles-url'))),
                    site = provider.hostname.split('.'),
                    domain = site.slice(0).slice(-(site.length === 4 ? 3 : 2)).join('.');
                tileSource = new ol.source.XYZ({
                    url: context.data('tiles-url'),
                    attributions: [
                        '© <a href="https://' + domain + '" target="_blank">' + domain + '</a>',
                        '© <a href="https://openstreetmap.org">OpenStreetMap</a>'
                    ],
                    crossOrigin: 'anonymous'
                });
            } else if (config.default_map_tile) {
                let provider = (new URL(config.default_map_tile ?? '')),
                    site = provider.hostname.split('.'),
                    domain = site.slice(0).slice(-(site.length === 4 ? 3 : 2)).join('.');
                tileSource = new ol.source.XYZ({
                    url: config.default_map_tile ?? 'https://mt{0-3}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}',
                    attributions: [
                        '© <a href="https://' + domain + '" target="_blank">' + domain + '</a>',
                        '© <a href="https://openstreetmap.org">OpenStreetMap</a>'
                    ],
                    crossOrigin: 'anonymous'
                });
            } else {
                tileSource = new ol.source.OSM();
            }
            // Apply coordinate to field with matches attribute
            apply_coordinate = context.data('apply-coordinate-to');
            // Apply latitude to field with matches attribute
            apply_latitude = context.data('apply-latitude-to');
            // Apply longitude to field with matches attribute
            apply_longitude = context.data('apply-longitude-to');
            // Apply address to field witch matches attribute
            apply_address = context.data('apply-address-to');
            // Apply measurement to field with matches attribute
            apply_measurement = context.data('apply-measurement-to');
            // Route start from
            apply_route_from = context.data('route-from') ?? [];
            apply_route_from = (typeof(apply_route_from.lat) !== 'undefined' && typeof(apply_route_from.lng) ? [apply_route_from.lat, apply_route_from.lng] : apply_route_from);
            // Route end to
            apply_route_to = context.data('route-to') ?? [];
            apply_route_to = (typeof(apply_route_to.lat) !== 'undefined' && typeof(apply_route_to.lng) ? [apply_route_to.lng, apply_route_to.lat] : apply_route_to);
            // Use source url geospatial data
            source_url = context.data('source-url');
            // Use rest url (ArcGIS)
            rest_url = context.data('rest-url');
            // Use wms url
            wms_url = context.data('wms-url');
            // Add street view
            panorama = context.data('panorama');
            fill_color = (context.data('fill') ? context.data('fill') : null);
            stroke_color = (context.data('stroke') ? context.data('stroke') : null);
            stroke_width = (context.data('stroke-width') ? context.data('stroke-width') : null);
            icon_pattern = (context.data('icon') ? context.data('icon') : null);
            icon_scale = (context.data('icon-scale') ? context.data('icon-scale') : null);
            lngLat = (context.data('coordinate') ? context.data('coordinate') : (context.data('map-center') ? context.data('map-center') : []));
            lngLat = (lngLat && (typeof lngLat.lng !== 'undefined' && typeof lngLat.lat !== 'undefined' ? [lngLat.lng, lngLat.lat] : (typeof lngLat[0] !== 'undefined' && typeof lngLat[1] !== 'undefined' ? [lngLat[0], lngLat[1]] : [107.0825363, -6.2355892])));
            colorscheme = (typeof lngLat.colorscheme !== 'undefined' ? lngLat.colorscheme : '#ff0000');
            if (! context.attr('id')) {
                context.attr('id', 'maps');
            }
            // Reset previous rendered map
            openlayers.reset();
            // Render new map
            map = new ol.Map({
                renderer: 'webgl',
                interactions: ol.interaction.defaults({
                    mouseWheelZoom: false,
                    dragPan: true
                })
                .extend([
                    new ol.interaction.MouseWheelZoom({
                        condition: function(e) {
                            return (0 != context.data('mousewheel') || ol.events.condition.platformModifierKeyOnly(e));
                        }
                    })
                ]),
                target: context.attr('id'),
                layers: [
                    new ol.layer.Tile({
                        source: tileSource
                    })
                ],
                view: new ol.View({
                    center: ol.proj.fromLonLat(lngLat),
                    zoom: (context.data('zoom') ? parseInt(context.data('zoom')) : 12),
                    minZoom: minZoom,
                    maxZoom: maxZoom
                }),
                loadTilesWhileAnimating: false,
                loadTilesWhileInteracting: false
            });
            let resolution = map.getView().getResolution();
            // Add zoom control to map
            (context.attr('control-fullscreen') ? map.addControl(new ol.control.FullScreen()) : '');
            // Add scale line to map
            (context.attr('control-scaleline') ? map.addControl(new ol.control.ScaleLine()) : '');
            // Add mouse position to map
            (context.attr('control-mouseposition') ? map.addControl(new ol.control.MousePosition({
                coordinateFormat: ol.coordinate.createStringXY(6),
                projection: projection,
                prefix: 'Degrees',
                undefinedHTML: ' '
            })) : '');
            // Add zoom extent control to map
            (context.attr('control-zoom-extent') ? map.addControl(new ol.control.ZoomToExtent({
                extent: map.getView().calculateExtent()
            })) : '');
            $('.ol-zoom-extent').children('button').html('<i class="mdi mdi-home"></i>');
            $('.ol-zoom-extent').on('click', function() {
                map.getView().fit(map.getView().calculateExtent(), {
                    size: map.getSize()
                }), map.getView().setResolution(resolution)
            });
            if (! context.data('drawing-type')) {
                map.on('singleclick', function(event) {
                    if (clicked && context.data('finder-url')) {
                        return false;
                    }
                    // Stop all video and audio player
                    if (typeof mejs !== 'undefined' && typeof mejs.players !== 'undefined') {
                        $.each(mejs.players, function(key, val) {
                            if (typeof val.media.hlsPlayer !== 'undefined') {
                                val.media.hlsPlayer.stopLoad();
                                val.media.hlsPlayer.destroy();
                                val.media.remove();
                            }
                        });
                    }
                    // Add popup overlay
                    map.addOverlay(popup);
                    // Add highlighting interaction
                    map.addInteraction(highlight);
                    if (popup) {
                        // Hide previous popup
                        popup.hide();
                    }
                    if (clickedPoint && clickedPoint.getSource().getFeatures().length) {
                        // Add point to map
                        clickedPoint.getSource().getFeatures()[0].setGeometry(new ol.geom.Point(event.coordinate));
                    } else {
                        // Add point to map using image
                        clickedPoint = new ol.layer.VectorImage({
                            source: new ol.source.Vector({
                                features: [
                                    new ol.Feature({
                                        geometry: new ol.geom.Point(event.coordinate)
                                    })
                                ]
                            }),
                            style: new ol.style.Style({
                                image: new ol.style.Circle({
                                    radius: 5,
                                    stroke: new ol.style.Stroke({
                                        color: '#880000',
                                        width: 1
                                    }),
                                    fill: new ol.style.Fill({
                                        color: '#ff0000'
                                    })
                                })
                            }),
                            zIndex: 100,
                            initial: 'clickedPoint'
                        });
                        map.addLayer(clickedPoint);
                    }
                    let selected = highlight.getFeatures(),
                        coordinate = ol.proj.transform(event.coordinate, map.getView().getProjection(), projection),
                        target = map.forEachFeatureAtPixel(event.pixel, function(point, layer) {
                            return {
                                point: point,
                                layer: layer
                            };
                        });
                    if (typeof target !== 'undefined' && typeof target.point !== 'undefined') {
                        selected.clear();
                        selected.push(target.point);
                    }
                    // Open the popup to find the features on the clicked point
                    popup.show(event.coordinate, `<div class="popup-content"><div class="d-flex justify-content-center"><div class="spinner-border spinner-border-sm" role="status"><span class="visually-hidden sr-only">${phrase('Loading...')}</span></div></div></div>`);
                    if (context.data('finder-url')) {
                        // Attempt to find dataset using finder URL
                        xhr = $.ajax({
                            url: context.data('finder-url'),
                            method: 'POST',
                            data: {
                                objectID: (typeof target !== 'undefined' && typeof target.point !== 'undefined' ? target.point.get('object_id') : 0),
                                coordinate: coordinate
                            },
                            beforeSend: function() {
                                if (xhr) {
                                    xhr.abort();
                                }
                                clicked = true;
                                $('.popup-content').closest('.ol-popup-content').removeAttr('style');
                            }
                        })
                        .done(function(response) {
                            // Check if the specific layer exists on the map
                            const wmsExists = map.getLayers().getArray().includes(wmsTile);
                            clicked = false;
                            if (typeof response.title !== 'undefined' && response.title) {
                                $('.ol-popup-title').text(response.title);
                            }
                            if (typeof response.content !== 'undefined' && response.content) {
                                // Content found from response
                                $('.popup-content, .identification-information').html(response.content);
                                if (typeof response.width === 'number') {
                                    $('.popup-content').closest('.ol-popup-content').attr('style', 'width: ' + response.width + 'px!important');
                                }
                                if ($('[role=videoplayer]').length) {
                                    // Run video player when element's found
                                    reactivate(['videoplayer'])
                                }
                            } else if (! wmsExists) {
                                // No data attribute found, close popup
                                popup.hide();
                            }
                        });
                    } else if (typeof target !== 'undefined' && typeof target.point !== 'undefined' && typeof target.point.getProperties() !== 'undefined') {
                        // Load attributes from property instead
                        let content = '',
                            num = 0;
                        $.each(target.point.getProperties(), function(key, val) {
                            if ('geometry' === key) return;
                            content += `<div class="row text-sm${(num ? ' border-top' : '')}"><div class="col-4 text-muted text-end pr-0">${key}</div><div class="col-8 text-break-word">${val}</div></div>`;
                            num++;
                        });
                        $('.popup-content, .identification-information').html(content);
                    } else {
                        // No data attribute found, close popup
                        popup.hide();
                    }
                    /**
                     * Store clicked coordinat for routing
                     */
                    if (coordinate && $('.route-start').length && ! $('.route-start').val()) {
                        coordinate[0] = coordinate[0].toFixed(6);
                        coordinate[1] = coordinate[1].toFixed(6);
                        // Add route start point
                        $('.route-start').val(coordinate.reverse().join(), projection);
                    } else if (coordinate && $('.route-end').length && ! $('.route-end').val()) {
                        coordinate[0] = coordinate[0].toFixed(6);
                        coordinate[1] = coordinate[1].toFixed(6);
                        // Add route end point
                        $('.route-end').val(coordinate.reverse().join(), projection);
                    }
                });
            }
            map.getView().on('propertychange', function(event) {
                if (typeof geolocation !== 'undefined') {
                    // Stop tracking to prevent map flicker
                    geolocation.setTracking(false);
                }
            });
            map.on('pointermove', function(event) {
                let pixel = map.getEventPixel(event.originalEvent),
                    hit = map.hasFeatureAtPixel(pixel);
                // Set mouse style
                map.getViewport().style.cursor = hit ? 'pointer' : '';
            });
            map.on('moveend', function(event) {
                map.addInteraction(highlight);
                let selected = highlight.getFeatures().getArray();
                if (context.data('clustering-url') && ! selected.length) {
                    // Get the clustering features
                    let coordinate = ol.proj.transform(map.getView().getCenter(), map.getView().getProjection(), projection),
                        latitude = (typeof coordinate[1] !== 'undefined' ? coordinate[1] : 0),
                        longitude = (typeof coordinate[0] !== 'undefined' ? coordinate[0] : 0),
                        distance = 0,
                        checked = [];
                    try {
                        checked = (sessionStorage.getItem('checked_layers') ? JSON.parse(sessionStorage.getItem('checked_layers')) : []);
                    } catch (e) {}
                    // Don't apply the clustering if there's any checked layers
                    if (checked.length) return;
                    if (previousCenter.length) {
                        // Get distance from previous center point
                        let line = new ol.geom.LineString([previousCenter, map.getView().getCenter()]),
                            distance = Math.round(line.getLength() * event.map.getView().getResolution()) / event.map.getView().getResolution();
                    }
                    if (! previousCenter.length || distance > event.map.getView().getResolution()) {
                        // Attempt to get cluster
                        $.ajax({
                            url: context.data('clustering-url'),
                            method: 'POST',
                            data: {
                                lat: latitude,
                                lng: longitude,
                                keyword: context.closest('form').find('input[name=keyword]').val(),
                                marker: checked,
                                distance: Math.round(distance / event.map.getView().getResolution())
                            },
                            beforeSend: function() {
                                context.closest('form').find('button[type=submit]').find('.mdi').removeClass('mdi-magnify').addClass('mdi-loading mdi-spin');
                            }
                        })
                        .done(function(response) {
                            if (response) {
                                openlayers.unzip(response, context.closest('form').find('button[type=submit]').find('.mdi'), null, true);
                            }
                            previousCenter = map.getView().getCenter();
                        })
                        .fail(function() {
                            context.closest('form').find('button[type=submit]').find('.mdi').removeClass('mdi-loading mdi-spin').addClass('mdi-magnify');
                        });
                    }
                }
            });
            // Set coordinate
            (context.data('coordinate') ? openlayers.coordinate((1 == context.data('draggable') ? true : false)) : null);
            // Set geocoder
            (context.data('geocoder') ? openlayers.geocoder(context) : null);
            // Set geolocation
            (context.data('geolocation') ? openlayers.geolocation() : null);
            // Render GeoJSON
            (context.data('geojson') ? openlayers.geojsonString(context.data('geojson'), context.data('drawing-type')) : null);
            // Render from source URL
            (source_url ? openlayers.source(source_url) : null);
            // Render from REST's URL
            (rest_url ? openlayers.rest(rest_url) : null);
            // Apply WMS from URL
            (wms_url ? openlayers.wms(wms_url) : null);
            // Render from REST's URL
            (panorama ? openlayers.panorama() : null);
            // Update map size
            map.updateSize();
            context.find('div').first().css({
                width: map.getSize()[0],
                height: map.getSize()[1]
            });
			
            if ($(apply_latitude).hasClass('reverse_latitude') && $(apply_longitude).hasClass('reverse_longitude')) {
                $('body').off('change.latLng keyup.latLng'),
                $('body').on('change.latLng keyup.latLng', '.reverse_latitude, .reverse_longitude', function(e) {
                    let latitude = $('.reverse_latitude').val();
                    let longitude = $('.reverse_longitude').val();
                    const geojson = new ol.format.GeoJSON();
                        
                    if (! isFinite(latitude) || !isFinite(longitude)) {
                        return;
                    }
                    
                    if (Math.abs(latitude) > 90 || Math.abs(latitude) < -90) {
                        latitude = 0;
                    }
                    
                    if (Math.abs(longitude) > 180 || Math.abs(longitude) < -180) {
                        longitude = 0;
                    }
                    
                    if (latitude && longitude) {
                        if (layerVector) {
                            // Clear prevously drawn feature
                            layerVector.getSource().clear()
                        }
                        
                        if (draggableMarker) {
                            // Push layer to map
                            draggableMarker.getSource().getFeatures()[0].setGeometry(new ol.geom.Point(ol.proj.transform([longitude, latitude], projection, map.getView().getProjection()))),
                            
                            // Fit marker to map
                            map.getView().fit(draggableMarker.getSource().getExtent(), {size: map.getSize()}),
                            
                            // Apply coordinate to input
                            $(apply_coordinate).val(geojson.writeFeatures(draggableMarker.getSource().getFeatures(), {featureProjection: map.getView().getProjection()}))
                        } else {
                            draggableMarker = new ol.layer.VectorImage({
                                source: new ol.source.Vector({
                                    features: [
                                        new ol.Feature({
                                            geometry: new ol.geom.Point(ol.proj.transform([longitude, latitude], projection, map.getView().getProjection()))
                                        })
                                    ]
                                }),
                                style: new ol.style.Style({
                                    image: new ol.style.Icon({
                                        scale: (icon_scale ? icon_scale : 1),
                                        src: (icon_pattern ? icon_pattern : config.asset_url + 'openlayers/resources/icons/marker.png'),
                                        anchor: [0.5, 1]
                                    })
                                }),
                                zIndex: 100,
                                initial: 'draggable'
                            });
                            
                            // Push layer to map
                            map.addLayer(draggableMarker),
                            
                            // Fit marker to map
                            map.getView().fit(draggableMarker.getSource().getExtent(), {size: map.getSize()}),
                            
                            // Apply coordinate to input
                            $(apply_coordinate).val(geojson.writeFeatures(draggableMarker.getSource().getFeatures(), {featureProjection: map.getView().getProjection()}));
                        }
                        
                        const drag = new ol.interaction.Translate({
                            features: new ol.Collection(draggableMarker.getSource().getFeatures())
                        });
                        
                        // Add drag interaction to marker
                        map.addInteraction(drag),
                        
                        // On drag end
                        drag.on('translatestart', function(event) {
                            popup.hide(),
                            
                            map.removeOverlay(popup)
                        }),
                        
                        // On drag end
                        drag.on('translateend', function(event) {
                            if (typeof event.coordinate !== 'undefined') {
                                const coordinate = ol.proj.transform(event.coordinate, map.getView().getProjection(), projection);
                                
                                $(apply_latitude).val((typeof coordinate[1] !== 'undefined' ? parseFloat(coordinate[1]).toFixed(6) : 0)),
                                $(apply_longitude).val((typeof coordinate[0] !== 'undefined' ? parseFloat(coordinate[0]).toFixed(6) : 0)),
                                $(apply_coordinate).val(geojson.writeFeatures(event.features.getArray(), {featureProjection: map.getView().getProjection()}))
                            }
                        });
                        
                        return;
                    }
                })
            }
        },
        reset: function() {
            /**
             * Reset map
             */
            if (map) {
                if (drawingManager) {
                    map.removeInteraction(drawingManager);
                }
                if (highlight) {
                    map.removeInteraction(highlight);
                }
                map.getLayers().forEach(function(layer, index) {
                    if ('draggable' == layer.get('initial')) return;
                    if (layer.getSource()) {
                        layer.getSource().clear();
                    }
                });
                map.updateSize();
            }
            if (popup) {
                popup.hide();
            }
            if (typeof geolocation !== 'undefined') {
                // Stop tracking to prevent map flicker
                geolocation.setTracking(false);
            }
        },
        source: function(source) {
            /**
             * Source extractor
             */
            if (! source) return false;
            
            let extension = source.split('.').pop().toLowerCase();
            if ('zip' == extension.replace(/\?.*/, '') || 'kmz' == extension.replace(/\?.*/, '')) {
                // The source is ZIP or KMZ format
                openlayers.unzip(source);
            } else if ('kml' == extension.replace(/\?.*/, '')) {
                // The source is KML format
                openlayers.keyhole(source);
            } else {
                // The source is GeoJSON format
                openlayers.geojson(source);
            }
        },
        unzip: function(source, spinner, ignore_draggable_marker, ignore_fit) {
            /**
             * Unzipper
             */
            if (typeof source.initial !== 'undefined' && sessionStorage.getItem(source.initial)) {
                // Open stored blob
                openlayers.geojson(sessionStorage.getItem(source.initial), spinner, ignore_draggable_marker, ignore_fit);
            } else if (typeof source.url !== 'undefined') {
                /**
                 * Read the KMZ / ZIP File from the given source parameter
                 */
                try {
                    // Read KMZ (zip) data
                    JSZipUtils.getBinaryContent(source.url, function(err, data) {
                        if (err || data.byteLength < 10) {
                            console.log(err);
                        }
                        // Unzip archive
                        JSZip.loadAsync(data).then(function(zip) {
                            // Read extracted datasource
                            Object.keys(zip.files).forEach(function(filename) {
                                if ('geojson' == filename.split('.').pop().toLowerCase() || 'json' == filename.split('.').pop().toLowerCase()) {
                                    // Create blob file from extracted data
                                    zip.files[filename].async('string').then(function(blob) {
                                        // Write blob file
                                        let blobURL = URL.createObjectURL(new Blob([blob], {
                                            type: 'application/json'
                                        }));
        
                                        openlayers.geojson(blobURL, spinner, ignore_draggable_marker, ignore_fit);
                                    });
                                } else if ('kml' == filename.split('.').pop().toLowerCase()) {
                                    // Create blob file from extracted data
                                    zip.files[filename].async('string').then(function(blob) {
                                        // Write blob file
                                        let blobURL = URL.createObjectURL(new Blob([blob], {
                                            type: 'application/vnd.google-earth.kml+xml'
                                        }));
        
                                        openlayers.keyhole(blobURL, spinner, ignore_fit, true);
                                    });
                                }
                            });
                        });
                    });
                } catch (e) {
                    console.log(e);
                }
            } else if (source.match(/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g)) {
                /**
                 * The source is an url
                 */
                try {
                    // Read kmz (zip) data
                    JSZipUtils.getBinaryContent(source, function(err, data) {
                        if (err || data.byteLength < 10) {
                            // Archive cannot be extracted, show error
                            console.log(err);
                        }
                        // Unzip archive
                        JSZip.loadAsync(data).then(function(zip) {
                            // Read extracted datasource
                            Object.keys(zip.files).forEach(function(filename) {
                                if ('geojson' == filename.split('.').pop().toLowerCase() || 'json' == filename.split('.').pop().toLowerCase()) {
                                    // Create blob file from extracted data
                                    zip.files[filename].async('string').then(function(blob) {
                                        // Write blob file
                                        let blobURL = URL.createObjectURL(new Blob([blob], {
                                            type: 'application/json'
                                        }));
                                        openlayers.geojson(blobURL, spinner, ignore_draggable_marker, ignore_fit);
                                    });
                                } else if ('kml' == filename.split('.').pop().toLowerCase()) {
                                    // Create blob file from extracted data
                                    zip.files[filename].async('string').then(function(blob) {
                                        // Write blob file
                                        let blobURL = URL.createObjectURL(new Blob([blob], {
                                            type: 'application/vnd.google-earth.kml+xml'
                                        }));
                                        openlayers.keyhole(blobURL, spinner, ignore_fit, true);
                                    });
                                }
                            });
                        });
                    });
                } catch (e) {
                    console.log(e);
                }
            }
        },
        geojsonString: function(shapes, type) {
            /**
             * Initialize feature from GeoJSON string
             */
            let geojson = new ol.format.GeoJSON(),
                sourceVector = new ol.source.Vector({});
            if (typeof shapes.features !== 'undefined') {
                sourceVector = new ol.source.Vector({
                    features: geojson.readFeatures(
                        shapes, {
                            featureProjection: map.getView().getProjection()
                        }
                    )
                });
            } else if (typeof shapes.geometry !== 'undefined') {
                sourceVector = new ol.source.Vector({
                    features: geojson.readFeatures({
                        type: 'FeatureCollection',
                        features: [shapes]
                    }, {
                        featureProjection: map.getView().getProjection()
                    })
                });
            } else if (! context.data('draggable') && typeof shapes.lat !== 'undefined' && typeof shapes.lng !== 'undefined') {
                sourceVector = new ol.source.Vector({
                    features: [
                        new ol.Feature({
                            geometry: new ol.geom.Point(ol.proj.transform([shapes.lng, shapes.lat], projection, map.getView().getProjection()))
                        })
                    ]
                })
            }
            layerVector = new ol.layer.VectorImage({
                source: sourceVector,
                style: function(feature, resolution) {
                    let pattern = null,
                        icon_width = 0;
                    if (icon_pattern || feature.get('icon')) {
                        // Apply pattern from icon
                        let canvas = document.createElement('canvas'),
                            canvasContext = canvas.getContext('2d', { willReadFrequently: true }),
                            image = new Image();
                        image.src = (icon_pattern ? icon_pattern : feature.get('icon'));
                        pattern = canvasContext.createPattern(image, 'repeat');
                    }
                    return [new ol.style.Style({
                        image: new ol.style.Icon({
                            scale: (icon_scale ? icon_scale : (feature.get('icon-scale') ? feature.get('icon-scale') : 1)),
                            src: (icon_pattern ? icon_pattern : (feature.get('icon') ? feature.get('icon') : config.base_url + 'assets/openlayers/resources/icons/marker.png')),
                            anchor: [0.5, 1]
                        }),
                        stroke: new ol.style.Stroke({
                            color: (stroke_color ? stroke_color : (feature.get('stroke') ? feature.get('stroke') : hex2rgba('#ff0000', 0))),
                            width: (stroke_width ? stroke_width : (feature.get('stroke-width') ? feature.get('stroke-width') : 2))
                        }),
                        fill: new ol.style.Fill({
                            color: (pattern ? pattern : hex2rgba((fill_color ? fill_color : (feature.get('fill') ? feature.get('fill') : '#ff0000')), (feature.get('fill-opacity') ? feature.get('fill-opacity') : .35)))
                        }),
                        text: new ol.style.Text({
                            text: feature.get('title'),
                            font: '14px Arial, sans-serif',
                            fill: new ol.style.Fill({
                                color: '#000000'
                            }),
                            stroke: new ol.style.Stroke({
                                color: '#ffffff',
                                width: 3
                            })
                        })
                    })];
                }
            });
            // Push layer to map
            map.addLayer(layerVector);
            // Fit map to features
            (layerVector.getSource().getFeatures().length ? map.getView().fit(layerVector.getSource().getExtent(), {
                size: map.getSize()
            }) : null);
            if (type && ['polygon', 'linestring', 'point'].includes(type)) {
                drawingManager = new ol.interaction.Draw({
                    type: (type === 'polygon' ? 'Polygon' : (type === 'linestring' ? 'LineString' : 'Point')),
                    source: layerVector.getSource()
                });
                drawingType = type;
                if ('point' == type) {
                    // Limit drawing to `maxPoints`
                    drawingManager.on('drawstart', (event) => {
                        if (layerVector.getSource().getFeatures().length >= 1) {
                            layerVector.getSource().clear(); // Clear existing points
                        }
                    });
                }
                // Event on drawing end
                drawingManager.on('drawend', function(event) {
                    let drawn = event.feature,
                        features = layerVector.getSource().getFeatures(),
                        coordinate = ol.proj.transform(drawn.getGeometry().flatCoordinates, map.getView().getProjection(), projection),
                        output = features.concat(drawn),
                        measurement = getMeasurement(output);
                    $(apply_coordinate).val(geojson.writeFeatures(output, {
                        featureProjection: map.getView().getProjection()
                    }));
                    $(apply_latitude).val((typeof coordinate[1] !== 'undefined' ? parseFloat(coordinate[1]).toFixed(6) : 0));
                    $(apply_longitude).val((typeof coordinate[0] !== 'undefined' ? parseFloat(coordinate[0]).toFixed(6) : 0));
                    $(apply_measurement).val((measurement.area > 0 ? measurement.area : measurement.distance));
                });
                let drag = new ol.interaction.Translate({
                    layers: [layerVector]
                });
                let modify = new ol.interaction.Modify({
                    source: layerVector.getSource()
                });
                let snap = new ol.interaction.Snap({
                    source: layerVector.getSource()
                });
                let select = new ol.interaction.Select({
                    layers: [layerVector],
                    style: new ol.style.Style({
                        stroke: new ol.style.Stroke({
                            color: (stroke_color ? stroke_color : hex2rgba('#00ff00', 0)),
                            width: (stroke_width ? stroke_width : 2),
                            opacity: .1
                        }),
                        fill: new ol.style.Fill({
                            color: hex2rgba('#00ff00', .35),
                            opacity: .1
                        })
                    })
                });
                // Add drawing manager
                map.addInteraction(drawingManager);
                // Add drag interaction
                map.addInteraction(drag);
                // Add modify interaction
                map.addInteraction(modify);
                // Add snap interaction
                map.addInteraction(snap);
                // Add select interaction
                map.addInteraction(select);
                drag.on('translatestart', function(event) {
                    // On drag start
                    popup.hide();
                    map.removeOverlay(popup);
                });
                drag.on('translateend', function(event) {
                    // On drag end
                    if (typeof event.coordinate !== 'undefined') {
                        const geojson = new ol.format.GeoJSON();
                        let coordinate = ol.proj.transform(event.coordinate, map.getView().getProjection(), projection),
                            features = layerVector.getSource().getFeatures();
    
                        $(apply_coordinate).val(geojson.writeFeatures(features, {
                            featureProjection: map.getView().getProjection()
                        }));
                        $(apply_latitude).val((typeof coordinate[1] !== 'undefined' ? parseFloat(coordinate[1]).toFixed(6) : 0));
                        $(apply_longitude).val((typeof coordinate[0] !== 'undefined' ? parseFloat(coordinate[0]).toFixed(6) : 0));
                    }
                    modify.dispatchEvent({
                        type: 'modifyend'
                    });
                });
                modify.on('modifyend', function(event) {
                    // On modify finish
                    if (typeof event.target !== 'undefined' && typeof event.target.features_ !== 'undefined') {
                        event.features = event.target.features_;
                    }
                    
                    if (typeof event.features === 'undefined') {
                        return false;
                    }
                    let measurement = getMeasurement(event.features.getArray()),
                        coordinate = {lat: 0, lng: 0};
                    event.features.getArray().forEach(function(layer, index) {
                        coordinate = ol.proj.transform(layer.getGeometry().flatCoordinates, map.getView().getProjection(), projection);
                        coordinate = {lat: coordinate[1], lng: coordinate[0]};
                    });
                    // Apply response to targetted input
                    $(apply_coordinate).val(geojson.writeFeatures(event.features.getArray(), {
                        featureProjection: map.getView().getProjection()
                    }));
                    $(apply_latitude).val(parseFloat(coordinate.lat).toFixed(6));
                    $(apply_longitude).val(parseFloat(coordinate.lng).toFixed(6));
                    $(apply_measurement).val((measurement.area > 0 ? measurement.area : measurement.distance));
                    if ('google' == config.openlayers_search_provider) {
                        /**
                         * Use Google Maps API as geocoder
                         */
                        require.js('https://maps.googleapis.com/maps/api/js?key=' + config.openlayers_search_key + '&libraries=places&language=' + config.language, function() {
                            let finder = new google.maps.Geocoder();
    
                            finder.geocode({
                                location: coordinate
                            }, function(response, status) {
                                if (status === 'OK') {
                                    $(apply_address).val(response[0].formatted_address).trigger('input');
                                    $('#gcd-input-query, #autocomplete').val(response[0].formatted_address);
                                } else {
                                    console.log(status);
                                }
                            });
                        })
                    } else {
                        /**
                         * Use Nominatim as geocoder
                         */
                        $.get('https://nominatim.openstreetmap.org/reverse?accept-language=id&format=json&lat=' + coordinate.lat + '&lon=' + coordinate.lng + '&addressdetails=1', function(response) {
                            $(apply_address).val(response.display_name).trigger('input');
                            $('#gcd-input-query, #autocomplete').val(response.display_name);
                        });
                    }
                });
                select.on('select', function(event) {
                    if (event.selected.length) {
                        map.addOverlay(popup);
                        popup.show(event.mapBrowserEvent.coordinate, `<div class="popup-content"><div class="list-group list-group-flush"><a href="javascript:void(0)" class="list-group-item list-group-item-action" onclick="removeFeature()"><i class="mdi mdi-trash-can-outline"></i> ${phrase('Remove feature')}</a></div></div>`);
                        highlighted = event.selected[0];
                    } else {
                        popup.hide();
                        map.removeOverlay(popup);
                    }
                });
            }
        },
        geojson: function(source, spinner, ignore_draggable_marker, ignore_fit, aliases, new_file) {
            /**
             * Render features from geojson
             */
            if (layerOverlap && new_file) {
                // Remove previously rendered layer overlap
                map.removeLayer(layerOverlap);
            } else if (layerVector && ! aliases) {
                // Remove previously rendered layer vector
                map.removeLayer(layerVector);
            }
            // Initialize layer, use blob, url or geojson string
            let vectorSource = new ol.source.Vector({
                url: (typeof source.url !== 'undefined' ? source.url : source),
                format: new ol.format.GeoJSON({
                    extractStyles: true
                }),
                projection: map.getView().getProjection()
            });
            layerVector = new ol.layer.VectorImage({
                source: vectorSource,
                style: function(feature, resolution) {
                    let pattern = null;
                    if (feature.get('icon')) {
                        let canvas = document.createElement('canvas'),
                            canvasContext = canvas.getContext('2d', { willReadFrequently: true }),
                            image = new Image();
                        image.src = feature.get('icon');
                        pattern = canvasContext.createPattern(image, 'repeat');
                    }
                    if (feature.get('selected')) {
                        selected.push(feature);
                    }
                    return [new ol.style.Style({
                        image: new ol.style.Icon({
                            scale: (icon_scale ? icon_scale : (feature.get('icon-scale') ? feature.get('icon-scale') : 1)),
                            src: (icon_pattern ? icon_pattern : (feature.get('icon') ? feature.get('icon') : config.base_url + 'assets/openlayers/resources/icons/marker.png')),
                            anchor: [0.5, 1]
                        }),
                        stroke: new ol.style.Stroke({
                            color: (stroke_color ? stroke_color : (feature.get('stroke') ? feature.get('stroke') : hex2rgba('#ff0000', 0))),
                            width: (stroke_width ? stroke_width : (feature.get('stroke-width') ? feature.get('stroke-width') : 2))
                        }),
                        fill: new ol.style.Fill({
                            color: (pattern ? pattern : hex2rgba((feature.get('fill') ? feature.get('fill') : '#ff0000'), (feature.get('fill-opacity') ? feature.get('fill-opacity') : .35)))
                        }),
                        text: new ol.style.Text({
                            text: (feature.get('layer_type') && $.inArray(feature.get('layer_type'), ['polygon', 'linestring']) !== -1 ? feature.get('label') : null),
                            font: '14px Arial, sans-serif',
                            fill: new ol.style.Fill({
                                color: '#000000'
                            }),
                            stroke: new ol.style.Stroke({
                                color: '#ffffff',
                                width: 3
                            })
                        })
                    })];
                },
                initial: (aliases ? 'layerOverlap' : (typeof source.initial !== 'undefined' ? source.initial : 'layerVector')),
                url: (typeof source.url !== 'undefined' ? source.url : context.data('finder-url'))
            });
            // Push layer to map
            map.addLayer(layerVector);
            if (spinner) {
                spinner.removeClass('mdi-magnify').addClass('mdi-loading mdi-spin')
            }
            // Fit it map to features
            let listener = layerVector.getSource().once('change', function(e) {
                if (layerVector.getSource().getState() === 'ready') {
                    let bounds_selected = ol.extent.createEmpty(),
                        bounds_all = ol.extent.createEmpty(),
                        found_selected = false,
                        found_all = false;
                    for (let i = 0; i < layerVector.getSource().getFeatures().length; i++) {
                        if (typeof layerVector.getZIndex() === 'undefined') {
                            // Add zoom index
                            layerVector.setZIndex(i + 1)
                        }
                        const raster = layerVector.getSource().getFeatures()[i].get('raster');
                        const wms = layerVector.getSource().getFeatures()[i].get('wms');
                        if (raster) {
                            /**
                             * GeoTIFF Raster (experimental)
                             * Export to EPSG:3857 CRS Projection when EPSG:4326 is failed to load
                             */
                            require.js([config.base_url + 'assets/openlayers/231.ol.js'], function() {
                                rasterTile = new ol.layer.WebGLTile({
                                    source: new ol.source.GeoTIFF({
                                        zIndex: parseInt(layerVector.getZIndex(i)) - 1,
                                        sources: [{
                                            url: raster,
                                            nodata: 0
                                        }]
                                    })
                                });
                                rasterTile.getSource().on('change', () => {
                                    if (rasterTile.getSource().getState() === 'ready') {
                                        const projection = rasterTile.getSource().getProjection().getCode();
                                        if (projection == 'EPSG:3857') {
                                            map.addLayer(rasterTile);
                                        } else {
                                            console.log('GeoTIFF Error:', `The projection of GeoTIFF file of "${ layerVector.getSource().getFeatures()[i].get('title') }" layer is not supported. GeoTIFF File Projection: ${ projection }. Required Projection: EPSG:3857.`)
                                        }
                                    }
                                })
                            })
                        } else if (wms) {
                            openlayers.wms(wms)
                        }
                        if (layerVector.getSource().getFeatures()[i].getGeometry()) {
                            if (layerVector.getSource().getFeatures()[i].get('selected')) {
                                found_selected = true;
                                ol.extent.extend(bounds_selected, layerVector.getSource().getFeatures()[i].getGeometry().getExtent());
                            } else {
                                found_all = true;
                                ol.extent.extend(bounds_all, layerVector.getSource().getFeatures()[i].getGeometry().getExtent());
                            }
                        }
                    }
                    
                    if (! ignore_fit) {
                        if (found_selected) {
                            map.getView().fit(bounds_selected, {
                                size: map.getSize()
                            });
                        } else if (found_all && ! ignore_draggable_marker) {
                            map.getView().fit(bounds_all, {
                                size: map.getSize()
                            });
                            if (draggableMarker) {
                                draggableMarker.getSource().getFeatures()[0].setGeometry(new ol.geom.Point(map.getView().getCenter()));
                            }
                        }
                    }
                    // Remove spinner
                    (spinner ? spinner.removeClass('mdi-loading mdi-spin').addClass('mdi-magnify') : '');
                    // Unbind listener
                    ol.Observable.unByKey(listener);
                }
            });
            if (aliases) {
                // Assign vector to layer overlap
                layerOverlap = layerVector;
            }
        },
        keyhole: function(source, spinner, ignore_fit, extract_style) {
            /**
             * Render the keyhole (kml) type of source
             */
            layerOverlap = new ol.layer.VectorImage({
                source: new ol.source.Vector({
                    projection: ol.proj.get(projection),
                    url: source,
                    format: new ol.format.KML({
                        extractStyles: extract_style === true
                    })
                }),
                type: 'xml',
                initial: 'layerOverlap'
            });
            // Push layer to map
            map.addLayer(layerOverlap);
            if (spinner) {
                // Remove spinner
                spinner.removeClass('mdi-magnify').addClass('mdi-loading mdi-spin')
            }
            if (! ignore_fit) {
                // Fit map to features
                layerOverlap.getSource().once('change', function(e) {
                    if (layerOverlap.getSource().getState() === 'ready') {
                        if (layerOverlap.getSource().getExtent().length && $.inArray(Infinity, layerOverlap.getSource().getExtent()) === -1) {
                            map.getView().fit(layerOverlap.getSource().getExtent(), {
                                size: map.getSize()
                            });
                        }
                    }
                })
            }
        },
        rest: function(source) {
            /**
             * Render the features from ArcGIS Rest API
             */
            let esriJSON = new ol.format.EsriJSON();
            let vectorSource = new ol.source.Vector({
                loader: function(extent, resolution, projection) {
                    let srid = projection.getCode().split(/:(?=\d+$)/).pop();
                    $.ajax({
                        url: source + '/query',
                        method: 'POST',
                        data: {
                            f: 'json',
                            returnGeometry: true,
                            spatialRel: 'esriSpatialRelIntersects',
                            geometry: '{"xmin":' + extent[0] + ',"ymin":' + extent[1] + ',"xmax":' + extent[2] + ',"ymax":' + extent[3] + ',"spatialReference":{"wkid":' + srid + '}}',
                            geometryType: 'esriGeometryEnvelope',
                            outFields: '*',
                            inSR: srid,
                            outSR: srid
                        },
                        dataType: 'jsonp',
                        success: function(response, xhr, hehe) {
                            if (response.error) {
                                console.log(response.error.message, response.error.details.join("\n"));
                            } else {
                                let features = esriJSON.readFeatures(response, {
                                    featureProjection: projection
                                });
                                if (features.length > 0) {
                                    vectorSource.addFeatures(features);
                                }
                            }
                        }
                    })
                },
                strategy: ol.loadingstrategy.tile(ol.tilegrid.createXYZ({
                    tileSize: 512
                }))
            });
            layerOverlap = new ol.layer.VectorImage({
                source: vectorSource
            });
            // Push layer to map
            map.addLayer(layerOverlap);
            // Fit map to features
            layerOverlap.getSource().once('change', function(e) {
                if (layerOverlap.getSource().getState() === 'ready') {
                    if (layerOverlap.getSource().getExtent().length && $.inArray(Infinity, layerOverlap.getSource().getExtent()) === -1) {
                        map.getView().fit(layerOverlap.getSource().getExtent(), {
                            size: map.getSize()
                        });
                    }
                }
            });
        },
        wms: function(source) {
            /**
             * Render the features from ArcGIS Rest API
             */
            wmsTile = new ol.layer.Tile({
                initial: 'wms',
                source: new ol.source.TileWMS({
                    url: source,
                    crossOrigin: 'anonymous',
                    projection: map.getView().getProjection(),
                    params: {
                        LAYERS: get_query_string('layers', source),
                        TILED: true
                    },
                    serverType: 'geoserver'
                })
            });
            // Push layer to map
            map.addLayer(wmsTile);
            try {
                map.getView().fit(wmsTile.getSource().getProjection().getExtent(), {
                    size: map.getSize()
                });
            } catch(e) {
                console.log(e)
                // Safe abstraction
            }
            map.on('singleclick', function(event) {
                let shown = false;
                popup.show(event.coordinate, `<div class="popup-content"><div class="d-flex justify-content-center"><div class="spinner-border spinner-border-sm" role="status"><span class="visually-hidden sr-only">${phrase('Loading...')}</span></div></div></div>`);
                map.getLayers().forEach(function(layer, index) {
                    if (shown) return;
                    if ('wms' == layer.get('initial')) {
                        const url = layer.getSource().getFeatureInfoUrl(
                            event.coordinate,
                            map.getView().getResolution(),
                            map.getView().getProjection(), {
                                INFO_FORMAT: 'application/json'
                            }
                        );
        
                        if (url) {
                            let output = '';
        
                            fetch(url)
                            .then((response) => response.text())
                            .then((json) => {
                                try {
                                    const geojson = JSON.parse(json);
                                    const properties = geojson.features[0].properties;
        
                                    Object.entries(properties).forEach(([label, value]) => {
                                        output += `<tr><td>${ label }</td><td>${ value }</td></tr>`;
                                    })
                                } catch(e) {
                                    // Safe abstraction
                                }
        
                                if (output) {
                                    shown = true;
                                    $('.popup-content').html(`
                                        <table class="table table-sm">
                                            <tbody>
                                                ${ output }
                                            </tbody>
                                        </table>
                                    `)
                                } else {
                                    popup.hide();
                                }
                            });
                        } else {
                            popup.hide();
                        }
                    }
                });
            });
        },
        coordinate: function(draggable) {
            /**
             * Create editable marker
             */
            draggableMarker = new ol.layer.VectorImage({
                source: new ol.source.Vector({
                    features: [
                        new ol.Feature({
                            type: 'click',
                            title: `<label class="d-block font-weight-bold text-muted mb-0">${phrase('Default Marker')}</label>`,
                            description: `<p class="mb-0">${phrase('This can be drag on edit mode')}</p>`,
                            geometry: new ol.geom.Point(map.getView().getCenter())
                        })
                    ]
                }),
                style: new ol.style.Style({
                    image: new ol.style.Icon({
                        src: config.base_url + 'assets/openlayers/resources/icons/marker.png',
                        anchor: [0.5, 1]
                    })
                }),
                zIndex: 100,
                initial: 'draggable'
            });
            // Push layer to map
            map.addLayer(draggableMarker);
            if (draggable) {
                let drag = new ol.interaction.Translate({
                    features: new ol.Collection(draggableMarker.getSource().getFeatures())
                });
                // Add drag interaction to marker
                map.addInteraction(drag);
                // Event on marker drag end
                drag.on('translateend', function(event) {
                    // Dragend event
                    if (typeof geolocation !== 'undefined') {
                        geolocation.setTracking(false);
                    }
                    let coordinate = ol.proj.transform(event.coordinate, map.getView().getProjection(), projection);
                    coordinate = {lat: coordinate[1], lng: coordinate[0]};
                    $(apply_latitude).val(parseFloat(coordinate.lat).toFixed(6));
                    $(apply_longitude).val(parseFloat(coordinate.lng).toFixed(6));
                    $(apply_coordinate).val(JSON.stringify(coordinate));
                    if ('google' == config.openlayers_search_provider) {
                        /**
                         * Use Google Maps API as geocoder
                         */
                        require.js('https://maps.googleapis.com/maps/api/js?key=' + config.openlayers_search_key + '&libraries=places&language=' + config.language, function() {
                            let finder = new google.maps.Geocoder();
                            finder.geocode({
                                location: coordinate
                            }, function(response, status) {
                                if (status === 'OK') {
                                    $(apply_address).val(response[0].formatted_address).trigger('input');
                                    $('#gcd-input-query, #autocomplete').val(response[0].formatted_address);
                                } else {
                                    console.log(status);
                                }
                            });
                        })
                    } else {
                        // Getting the address name
                        $.get('https://nominatim.openstreetmap.org/reverse?accept-language=id&format=json&lat=' + coordinate.lat + '&lon=' + coordinate.lng + '&addressdetails=1', function(response) {
                            if (typeof response !== 'undefined') {
                                // Apply to inputs
                                $(apply_address).val(response.display_name).trigger('input');
                                $('#gcd-input-query, #autocomplete').val(response.display_name);
                            }
                        });
                    }
                });
            }
        },
        geocoder: function(context) {
            /**
             * Add geocoder (place search)
             */
            geocoder = new Geocoder(
                'nominatim', {
                    provider: ('openlayers' == config.openlayers_search_provider ? 'osm' : config.openlayers_search_provider), // osm, mapquest, photon, pelias, bing, opencage
                    key: config.openlayers_search_key, // API Key
                    targetType: 'text-input',
                    //countrycodes: 'id',
                    placeholder: phrase('Search Place'),
                    limit: 10,
                    autoComplete: true,
                    featureStyle: null
                }
            );
            // Add geocoder into map controls directories
            map.addControl(geocoder);
            if ('google' == config.openlayers_search_provider) {
                /**
                 * Use Google Maps API as geocoder
                 */
                require.js('https://maps.googleapis.com/maps/api/js?key=' + config.openlayers_search_key + '&libraries=places&language=' + config.language, function() {
                    let autocomplete = new google.maps.places.Autocomplete(
                        (document.getElementById('gcd-input-query')), {
                            componentRestrictions: {
                                country: 'id'
                            },
                            fields: ['formatted_address', 'geometry', 'name'],
                            strictBounds: false,
                            types: ['establishment']
                        }
                    );
                    google.maps.event.addListener(autocomplete, 'place_changed', function() {
                        let place = autocomplete.getPlace();
                        if (typeof place.geometry === 'undefined') {
                            return;
                        }
                        if (typeof geolocation !== 'undefined') {
                            geolocation.setTracking(false);
                        }
                        if (! drawingType) {
                            // Apply to inputs
                            $(apply_coordinate).val(JSON.stringify({
                                lat: place.geometry.location.lat(),
                                lng: place.geometry.location.lng()
                            }));
                        }
                        $(context.data('apply-address-to')).val(place.formatted_address).trigger('change');
                        map.getView().setCenter(ol.proj.transform([place.geometry.location.lng(), place.geometry.location.lat()], projection, map.getView().getProjection()));
                        if (draggableMarker) {
                            draggableMarker.getSource().getFeatures()[0].setGeometry(new ol.geom.Point(ol.proj.transform([place.geometry.location.lng(), place.geometry.location.lat()], projection, map.getView().getProjection())));
                            map.getView().fit(draggableMarker.getSource().getExtent(), {
                                size: map.getSize()
                            });
                        }
                        if (context.data('finder-url')) {
                            xhr = $.ajax({
                                url: context.data('finder-url'),
                                method: 'POST',
                                data: {
                                    lat: place.geometry.location.lat(),
                                    lng: place.geometry.location.lng()
                                },
                                beforeSend: function() {
                                    if (xhr) {
                                        xhr.abort();
                                    }
                                }
                            })
                            .done(function(response) {
                                if (typeof response.url !== 'undefined') {
                                    openlayers.unzip(response, null, true);
                                }
                            });
                        }
                    });
                })
            } else {
                // Create event when address is chosen
                geocoder.on('addresschosen', function(response) {
                    if (typeof geolocation !== 'undefined') {
                        geolocation.setTracking(false);
                    }
                    let coordinate = ol.proj.transform(response.coordinate, map.getView().getProjection(), projection);
                    coordinate = {lat: coordinate[1], lng: coordinate[0]};
                    if (! drawingType) {
                        // Apply to inputs
                        $(apply_coordinate).val(JSON.stringify(coordinate));
                    }
                    $(context.data('apply-address-to')).val(response.address.details.name).trigger('change');
                    $('#gcd-input-query').val(response.address.details.name);
                    map.getView().setCenter(response.coordinate);
                    if (draggableMarker) {
                        draggableMarker.getSource().getFeatures()[0].setGeometry(new ol.geom.Point(response.coordinate));
                        map.getView().fit(draggableMarker.getSource().getExtent(), {
                            size: map.getSize()
                        });
                    }
                    if (context.data('finder-url')) {
                        xhr = $.ajax({
                            url: context.data('finder-url'),
                            method: 'POST',
                            data: {
                                lat: coordinate.lat,
                                lng: coordinate.lng
                            },
                            beforeSend: function() {
                                if (xhr) {
                                    xhr.abort();
                                }
                            }
                        })
                        .done(function(response) {
                            if (typeof response.url !== 'undefined') {
                                openlayers.unzip(response, null, true);
                            }
                        });
                    }
                });
            }
        },
        geolocation: function() {
            /**
             * Add geolocation (user based location)
             */
            if (apply_route_to.length) {
                navigator.geolocation.watchPosition(function(tracking) {
                    // Stop tracking to prevent map flicker
                    openlayers.route([tracking.coords.longitude, tracking.coords.latitude], apply_route_to, 'driving');
                },
                function(error) {
                    if (error.code == error.PERMISSION_DENIED) {
                        alert(phrase('Please enable location to use the directions feature'));
                    }
                });
            }
            geolocation = new ol.Geolocation({
                projection: map.getView().getProjection(),
                tracking: true,
                trackingOptions: {
                    enableHighAccuracy: true,
                    maximumAge: 2000
                }
            });
            // On device position change
            geolocation.on('change', function() {
                map.getView().setCenter(geolocation.getPosition());
                if (draggableMarker) {
                    draggableMarker.getSource().getFeatures()[0].setGeometry(new ol.geom.Point(geolocation.getPosition()));
                    map.getView().fit(draggableMarker.getSource().getExtent(), {
                        size: map.getSize()
                    });
                    if (! drawingType) {
                        let coordinate = ol.proj.transform(geolocation.getPosition(), map.getView().getProjection(), projection);
                        coordinate = {lat: coordinate[1], lng: coordinate[0]};
                        // Apply to inputs
                        $(apply_coordinate).val(JSON.stringify(coordinate));
                    }
                }
                let handleTrack = function(e) {
                    map.getView().setCenter(geolocation.getPosition());
                    if (draggableMarker) {
                        draggableMarker.getSource().getFeatures()[0].setGeometry(new ol.geom.Point(geolocation.getPosition()));
                        map.getView().fit(draggableMarker.getSource().getExtent(), {
                            size: map.getSize()
                        });
                        if (! drawingType) {
                            let coordinate = ol.proj.transform(geolocation.getPosition(), map.getView().getProjection(), projection);
                            coordinate = {lat: coordinate[1], lng: coordinate[0]};
                            // Apply to inputs
                            $(apply_coordinate).val(JSON.stringify(coordinate));
                        }
                    }
                };
                let icon = document.createElement('i'),
                    button = document.createElement('button'),
                    element = document.createElement('div');
                icon.setAttribute('class', 'mdi mdi-crosshairs-gps');
                button.setAttribute('title', phrase('Track Me'));
                button.setAttribute('type', 'button');
                button.appendChild(icon);
                button.addEventListener('click', handleTrack, false);
                element.setAttribute('class', 'ol-track ol-unselectable ol-control');
                element.appendChild(button);
                map.addControl(
                    new ol.control.Control({
                        element: element
                    })
                );
            });
            geolocation.on('error', function(error) {
                console.log(error.message)
            });
        },
        panorama: function() {
            require.css([config.base_url + 'assets/openlayers/ol-street-view/ol-street-view.min.css']);
            require.js([
                config.base_url + 'assets/interactjs/interact.min.js',
                config.base_url + 'assets/openlayers/ol-street-view/ol-street-view.min.js'
            ], function() {
                let streetView = new StreetView({
                    apiKey: config.openlayers_search_key,
                    language: StreetView.Language.EN,
                    transparentButton: false,
                    radius: 50,
                    updatePegmanToClosestPanorama: true,
                    size: StreetView.BtnControlSize.Large,
                    resizable: false,
                    sizeToggler: true,
                    zoomOnInit: 18,
                    minZoom: 13,
                    defaultMapSize: StreetView.MapSize.Compact,
                    autoLoadGoogleMaps: true,
                    i18n: {
                        dragToInit: 'Drag and drop me'
                    }
                });
                
                map.addControl(streetView);
                
                streetView.once('loadLib', function() {
                    let panorama = streetView.showStreetView(map.getView().getCenter());
                    panorama.setPov({
                        heading: 52,
                        pitch: -12,
                        zoom: 1
                    });
                });
                streetView.once('streetViewInit', function() {
                    // Get the panorama instance
                    var panorama = streetView.getStreetViewPanorama();
                    
                    let coordinate = ol.proj.transform(map.getView().getCenter(), map.getView().getProjection(), projection);
                
                    // Use global google maps functions to add the icon
                    var markerPos = new google.maps.LatLng(coordinate[1], coordinate[0]);
                
                    new google.maps.Marker({
                        position: markerPos,
                        map: panorama,
                        icon: 'https://chart.apis.google.com/chart?chst=d_map_pin_icon&chld=star|FF0000',
                        title: 'Star'
                    });
                });
            })
        },
        route: function(start_point, end_point, type) {
            /**
             * Routing machine
             */
            $('.find-route-steps').html('');
            if (! start_point || ! end_point) {
                alert('Please choose the starting point and destination!');
                return;
            }
            if (! $.isArray(start_point)) {
                start_point = start_point.split(',').reverse();
            }
            if (! $.isArray(end_point)) {
                end_point = end_point.split(',').reverse();
            }
            if (routeWayPoints) {
                routeWayPoints.getSource().clear();
            }
            if (routeLineString) {
                routeLineString.getSource().clear();
            }
            routeWayPoints = new ol.layer.VectorImage({
                source: new ol.source.Vector(),
                style: new ol.style.Style({
                    image: new ol.style.Icon({
                        src: config.base_url + 'assets/openlayers/resources/icons/marker.png',
                        anchor: [0.5, 1]
                    })
                }),
                zIndex: 100
            });
            routeLineString = new ol.layer.VectorImage({
                source: new ol.source.Vector(),
                style: new ol.style.Style({
                    stroke: new ol.style.Stroke({
                        width: 5,
                        color: [40, 40, 255, 0.8]
                    })
                }),
                zIndex: 100
            });
            $.ajax({
                url: config.base_url + 'maps/direction',
                method: 'POST',
                data: {
                    profile: type,
                    start_point: start_point.join(),
                    end_point: end_point.join(),
                    partial: true
                },
                beforeSend: function() {
                    popup.hide();
                    $('.find-route-steps').html('');
                    $(`
                        <div class="d-flex justify-content-center loading-result">
                            <div class="spinner-border" role="status">
                                <span class="visually-hidden sr-only">${phrase('Loading...')}</span>
                            </div>
                        </div>
                    `)
                    .appendTo('.find-route-steps');
                }
            })
            .done(function(response) {
                $('.loading-result').remove();
                if (! response) {
                    $('<div class="alert alert-danger">Request take too long, routing cancelled</div>').appendTo('.find-route-steps');
                }
                // Push route to map
                routeLineString.getSource().addFeature(
                    new ol.Feature({
                        type: 'route',
                        geometry: new ol.format.Polyline().readGeometry(response.geometry, {
                            dataProjection: projection,
                            featureProjection: map.getView().getProjection()
                        })
                    })
                );
                $.each(response.steps, function(key, val) {
                    $(`
                        <div class="py-1${(key ? ' border-top' : '')}">
                            <div class="row align-items-center">
                                <div class="col-1 col-md-1">
                                    <span class="routing-icon ${val.icon}"></span>
                                </div>
                                <div class="col-8 col-md-8">
                                    ${val.maneuver}
                                </div>
                                <div class="col-3 col-md-3 text-end">
                                    ${val.distance}
                                </div>
                            </div>
                        </div>
                    `)
                    .appendTo('.find-route-steps');
                    if ((key + 1) >= response.steps.length && typeof mCustomScrollbar !== 'undefined') {
                        if ($('.find-route-results.mCustomScrollbar').length) {
                            $('.find-route-results').mCustomScrollbar('update');
                        } else {
                            $('.find-route-results').mCustomScrollbar({
                                autoHideScrollbar: true,
                                axis: 'y',
                                scrollInertia: 170,
                                mouseWheelPixels: 170,
                                setHeight: $(window).outerHeight(true) - (($('.navbar').length ? $('.navbar').outerHeight(true) : 0) + ($('.filter-header').length ? $('.filter-header').outerHeight(true) : 0)),
                                advanced: {
                                    updateOnContentResize: true
                                }
                            });
                        }
                    }
                });
            });
            // Add point between route to map
            routeWayPoints.getSource().addFeature(
                new ol.Feature({
                    geometry: new ol.geom.Point(ol.proj.fromLonLat(start_point))
                })
            );
            routeWayPoints.getSource().addFeature(
                new ol.Feature({
                    geometry: new ol.geom.Point(ol.proj.fromLonLat(end_point))
                })
            );
            // Push route and waypoints to map
            map.addLayer(routeLineString);
            map.addLayer(routeWayPoints);
            // Fit route to extent
            (routeWayPoints.getSource().getFeatures().length ? map.getView().fit(routeWayPoints.getSource().getExtent(), {
                size: map.getSize()
            }) : null);
        },
        selection: function(appendTo) {
            /**
             * Selection
             */
            map.addInteraction(highlight);
            selected = highlight.getFeatures();
            selectionBox = new ol.interaction.DragBox({
                condition: ol.events.condition.platformModifierKeyOnly
            });
            map.addInteraction(selectionBox);
            selectionBox.on('boxend', function(e) {
                let extent = selectionBox.getGeometry().getExtent(),
                    resolution = map.getView().getResolution(),
                    exists = [];
                map.getLayers().getArray().forEach(function(layer, index) {
                    if (! index) return;
                    layer.getSource().forEachFeatureIntersectingExtent(extent, function(feature) {
                        if (! feature.get('object_id')) {
                            return;
                        }
                        if (! $('#selection-feature-' + feature.get('group_id')).length) {
                            $(`
                                <div id="selection-feature-${feature.get('group_id')}" class="mb-3">
                                    <h6>${feature.get('title')}</h6>
                                    <div class="row align-items-center">
                                        <div class="col-10">
                                            ${feature.get('label')}
                                        </div>
                                        <div class="col-2 text-right text-end">
                                            <span class="badge badge-info bg-info" data-group="${feature.get('group_id')}">1</span>
                                        </div>
                                    </div>
                                </div>
                            `)
                            .appendTo('.selection-information');
                        } else if ($('span[data-group=' + feature.get('group_id') + ']').length) {
                            $('span[data-group=' + feature.get('group_id') + ']').text(parseFloat($('span[data-group=' + feature.get('group_id') + ']').text()) + 1);
                        }
                        selected.push(feature);
                    });
                });
                map.getView().fit(extent, {
                    size: map.getSize()
                });
                map.getView().setResolution(resolution);
            });
            selectionBox.on('boxstart', function(e) {
                selected.clear();
            });
            map.on('click', function() {
                selected.clear();
            });
        },
        identification: function(appendTo) {
            /**
             * Identification
             */
            map.addInteraction(highlight);
            selected = highlight.getFeatures();
            selectionBox = new ol.interaction.DragBox({
                condition: ol.events.condition.platformModifierKeyOnly
            });
            map.addInteraction(selectionBox);
            selectionBox.on('boxend', function(e) {
                let info = [],
                    extent = selectionBox.getGeometry().getExtent(),
                    resolution = map.getView().getResolution(),
                    exist = [],
                    num = 0;
                $(appendTo).html('');
                map.getLayers().getArray().forEach(function(layer, index) {
                    if (! index) return;
                    layer.getSource().forEachFeatureIntersectingExtent(extent, function(feature) {
                        if (! feature.get('object_id')) {
                            return;
                        }
                        let object_id;
                        if (typeof feature.get('object_id') !== 'undefined') {
                            object_id = feature.get('object_id');
                        } else {
                            // Get primary id from description that contain object_id attribute
                            object_id = feature.get('description').toLowerCase() + ' ';
                            object_id = object_id.replace(/(<b[^>]+?>|<b>|<\/b>)/ig, ' ');
                            object_id = object_id.replace(/\s\s+/g, ' ');
                            object_id = object_id.substring(object_id.lastIndexOf('object_id = ') + 11);
                            object_id = object_id.substr(0, object_id.indexOf(' '));
                        }
                        feature.setId(object_id);
                        selected.push(feature);
                        if ($.inArray(object_id, exist) === -1) {
                            exist.push(object_id);
                            $(`
                                <div class="identification-item pt-2 pb-2${($('.identification-information').children().length > 0 ? ' border-top' : '')}" data-title="${feature.get('title')}" data-url="${layer.get('url')}" data-target="${object_id}" style="cursor:pointer">
                                    ${(feature.get('label') ? feature.get('label') : feature.get('title'))}
                                </div>
                            `)
                            .appendTo(appendTo);
                        }
                    });
                    $('body').off('mouseover.identification-item');
                    $('body').on('mouseover.identification-item', '.identification-item', function(e) {
                        if (layer.getSource().getFeatureById($(this).data('target'))) {
                            map.getView().setCenter(ol.extent.getCenter(layer.getSource().getFeatureById($(this).data('target')).getGeometry().getExtent()), {
                                size: map.getSize()
                            });
                        }
                    });
                    $('body').off('mouseout.identification-item');
                    $('body').on('mouseout.identification-item', '.identification-item', function(e) {
                        popup.hide();
                    });
                    $('body').off('click.identification-item touch.identification-item');
                    $('body').on('click.identification-item touch.identification-item', '.identification-item', function(e) {
                        if (layer.getSource().getFeatureById($(this).data('target'))) {
                            map.getView().setCenter(ol.extent.getCenter(layer.getSource().getFeatureById($(this).data('target')).getGeometry().getExtent()), {
                                size: map.getSize()
                            });
                            popup.show(ol.extent.getCenter(layer.getSource().getFeatureById($(this).data('target')).getGeometry().getExtent()), ('<div class="popup-content"></div>'));
                            xhr = $.ajax({
                                url: context.data('finder-url'),
                                method: 'POST',
                                data: {
                                    objectID: $(this).data('target')
                                },
                                beforeSend: function() {
                                    if (xhr) {
                                        xhr.abort();
                                    }
                                    $('.popup-content').html(`
                                        <div class="d-flex justify-content-center">
                                            <div class="spinner-border spinner-border-sm" role="status">
                                                <span class="visually-hidden sr-only">${phrase('Loading...')}</span>
                                            </div>
                                        </div>
                                    `)
                                }
                            })
                            .done(function(response) {
                                $('.popup-title').html(response.title);
                                $('.popup-content').html(response.content);
                            });
                        }
                    })
                });
                map.getView().fit(extent, {
                    size: map.getSize()
                });
            });
            selectionBox.on('boxstart', function(e) {
                selected.clear();
            });
            map.on('click', function() {
                $(appendTo).html('');
                selected.clear();
            });
        },
        measurement: function(type, appendTo) {
            /**
             * Measurement
             */
            $(appendTo).html('');
            if (typeof map !== 'undefined' && typeof drawingManager !== 'undefined') {
                map.removeInteraction(drawingManager);
                drawingManager = null;
            }
            if (measurementVector) {
                measurementVector.getSource().clear();
            }
            if (type === 'area' || type === 'distance') {
                measurementVector = new ol.layer.VectorImage({
                    source: new ol.source.Vector(),
                    style: new ol.style.Style({
                        stroke: new ol.style.Stroke({
                            color: colorscheme,
                            width: 3
                        }),
                        fill: new ol.style.Fill({
                            color: hex2rgba(colorscheme ?? '#ff0000', .15)
                        })
                    })
                });
                drawingManager = new ol.interaction.Draw({
                    type: ('area' == type ? 'Polygon' : 'LineString'),
                    source: measurementVector.getSource()
                });
                // Push layer to map
                map.addLayer(measurementVector);
                // Create drawing tools
                map.addInteraction(drawingManager);
                // Event on drawing end
                drawingManager.on('drawstart', function(event) {
                    clicked = true;
                    measurementVector.getSource().clear();
                });
                // Event on drawing end
                drawingManager.on('drawend', function(event) {
                    clicked = false;
                    let key = (features.length ? parseInt(features.length + 1) : 0),
                        prepare = [];
                    features[key] = event.feature.getGeometry().getCoordinates();
                    $.each(features, function(_key, _val) {
                        if (! _val) return;
                        _val = ('area' == type ? _val[0] : _val);
                        let coord = [];
                        $.each(_val, function(__key, __val) {
                            coord.push(ol.proj.transform(__val, map.getView().getProjection(), projection));
                        });
                        
                        prepare.push({
                            type: 'Feature',
                            geometry: {
                                type: ('area' == type ? 'Polygon' : 'LineString'),
                                coordinates: ('area' == type ? [coord] : coord)
                            },
                            properties: {}
                        });
                    });
                    let meter = (Math.round('area' == type ? event.feature.getGeometry().getArea() : event.feature.getGeometry().getLength())),
                        value = $(appendTo).prev('.form-group').find('select').val(),
                        label = $(appendTo).prev('.form-group').find('select option:selected').html(),
                        result = ('area' == type ? ('ac' == value ? meter / 4047 : ('mi' == value ? meter / 2.59e+6 : ('ha' == value ? meter / 10000 : ('yd' == value ? meter / 1.196 : ('ft' == value ? meter / 10.764 : ('km' == value ? meter / 1000 : meter)))))) : ('mi' == value ? meter / 1609 : ('yd' == value ? meter / 1.094 : ('ft' == value ? meter / 10.764 : ('km' == value ? meter / 1000 : meter)))));
                    $(appendTo).html(`
                        <div class="text-center mt-3">
                            <h6>${phrase('Measurement result')}</h6>
                            <h3>${result.toLocaleString('en')}</h3>
                            <h3>${label}</h3>
                        </div>
                    `);
                });
            } else {
                map.on('singleclick', function(event) {
                    let coordinate = ol.proj.transform(event.coordinate, map.getView().getProjection(), projection),
                        hdms = ol.coordinate.toStringHDMS(coordinate, 4);
                    if (measurementVector) {
                        measurementVector.getSource().clear();
                    }
                    measurementVector = new ol.layer.VectorImage({
                        source: new ol.source.Vector({
                            features: [
                                new ol.Feature({
                                    geometry: new ol.geom.Point(ol.proj.fromLonLat([coordinate[0], coordinate[1]]))
                                })
                            ]
                        }),
                        style: new ol.style.Style({
                            image: new ol.style.Icon({
                                src: config.base_url + 'assets/openlayers/resources/icons/marker.png',
                                anchor: [0.5, 1]
                            })
                        })
                    });
                    // Push layer to map
                    map.addLayer(measurementVector);
                    $(appendTo).html(`
                        <div class="text-center mt-3">
                            <h6 class="mb-3">${phrase('Measurement result')}</h6>
                            <div class="row"
                                <div class="col-6 font-weight-bold">
                                    ${phrase('Latitude')}
                                </div>
                                <div class="col-6 font-weight-bold">
                                    ${phrase('Longitude')}
                                </div>
                            </div>
                            <div class="row form-group">
                                <div class="col-6">
                                    ${coordinate[1].toFixed(6)}
                                </div>
                                <div class="col-6">
                                    ${coordinate[0].toFixed(6)}
                                </div>
                            </div>
                            <div class="form-group">
                                <label class="d-block font-weight-bold">HDMS</label>
                                <p>${hdms}</p>
                            </div>
                        </div>
                    `);
                });
            }
        },
        upload: function(data) {
            /**
             * Upload
             */
            if ('zip' !== data.name.split('.').pop().toLowerCase() && 'kmz' !== data.name.split('.').pop().toLowerCase()) {
                alert('Only KMZ or ZIP file are allowed to upload!');
                return;
            }
            if (map && layerOverlap) {
                map.removeLayer(layerOverlap);
            }
            try {
                // Unzip archive
                JSZip.loadAsync(data).then(function(zip) {
                    let new_file = true;
                    // Read extracted datasource
                    Object.keys(zip.files).forEach(function(filename) {
                        if ('geojson' == filename.split('.').pop().toLowerCase() || 'json' == filename.split('.').pop().toLowerCase()) {
                            // Create blob file from extracted data
                            zip.files[filename].async('string').then(function(blob) {
                                // Write blob file
                                let blobURL = URL.createObjectURL(new Blob([blob], {
                                    type: 'application/json'
                                }));
                                openlayers.geojson(blobURL, null, false, false, true, new_file);
                                new_file = false;
                            });
                        } else if ('kml' == filename.split('.').pop().toLowerCase()) {
                            // Create blob file from extracted data
                            zip.files[filename].async('string').then(function(blob) {
                                // Write blob file
                                let blobURL = URL.createObjectURL(new Blob([blob], {
                                    type: 'application/vnd.google-earth.kml+xml'
                                }));
                                openlayers.keyhole(blobURL, null, false, true);
                            });
                        }
                    });
                });
            } catch (e) {;
                alert('Unsupported geospatial format!')
            }
        },
        download: function(type, pageFormat, dpi) {
            /**
             * Download
             */
            if ('png' == type) {
                let canvas = $('.ol-layer canvas');
                if (canvas && typeof canvas[0] !== 'undefined') {
                    if (navigator.msSaveBlob) {
                        navigator.msSaveBlob(canvas[0].msToBlob(), 'map.png');
                    } else {
                        let link = document.getElementById('image-download');
                        link.href = canvas[0].toDataURL();
                        link.click();
                    }
                }
            } else if ('pdf' == type) {
                let canvas = $('.ol-layer canvas'),
                    center_coordinate = ol.proj.transform(map.getView().getCenter(), map.getView().getProjection(), projection);
                if (canvas && typeof canvas[0] !== 'undefined') {
                    require.js([
                        config.base_url + 'assets/jspdf/jspdf.min.js',
                        config.base_url + 'assets/html2canvas/html2canvas.min.js'
                    ], function() {
                        let unit = map.getView().getProjection().getUnits(),
                            resolution = map.getView().getResolution(),
                            format = ($.inArray(pageFormat, ['a0', 'a1', 'a2', 'a3', 'a4', 'a5']) !== -1 ? pageFormat : 'a4'),
                            doc = new jsPDF({
                                orientation: 'landscape',
                                unit: 'px',
                                format: format,
                                userUnit: dpi,
                                compress: true
                            });
                        doc.html(
                            $(`
                                <div style="position:relative;width:${doc.internal.pageSize.getWidth()}px; height:${doc.internal.pageSize.getHeight()}px;margin:0;padding:15px">
                                    <div style="text-align:center;font-weight:bold" style="margin:0;padding:0">
                                        ${config.app_name}
                                    </div>
                                    <div style="border:1px solid black; width:${(doc.internal.pageSize.getWidth() - 30)}px; height:${(doc.internal.pageSize.getHeight() - 80)}px">
                                        <img src="${canvas[0].toDataURL()}" width="${(doc.internal.pageSize.getWidth() - 32)}" height="${(doc.internal.pageSize.getHeight() - 82)}" />
                                    </div>
                                    <table width="100%">
                                        <tbody>
                                            <tr>
                                                <td width="40%">
                                                    <small style="position:absolute;bottom:15px;font-size:10px">${phrase('Central')}: ${center_coordinate[1].toFixed(4)},${center_coordinate[0].toFixed(4)}</small>
                                                </td>
                                                <td width="40%">
                                                    <small style="position:absolute;bottom:15px;font-size:10px">${phrase('Scale')} ] 1:${Math.floor(resolution * ol.proj.Units.METERS_PER_UNIT[unit] * dpi) + unit} [</small>
                                                </td>
                                                <td width="20%" align="right">
                                                    <img src="${config.app_logo}" style="height:12px;position:absolute;right:15px;bottom:15px" />
                                                </td>
                                            </tr>
                                        </tbody>
                                    </table>
                                </div>
                            `).get(0), {
                            x: 0,
                            y: 0,
                            callback: function(doc) {
                                doc.save('map.pdf');
                            }
                        });
                    });
                }
            }
        }
    }
})();
function getMeasurement(features) {
    let area = 0,
        distance = 0;
    features.forEach(function(feature) {
        let geometry = feature.getGeometry();
        if (geometry instanceof ol.geom.Polygon) {
            area += (Math.round(geometry.getArea() * 100) / 100);
            distance += (Math.round((new ol.geom.LineString(geometry.getLinearRing(0).getCoordinates())).getLength() * 100) / 100);
        } else if (geometry instanceof ol.geom.LineString) {
            distance += (Math.round(geometry.getLength() * 100) / 100);
        }
    });
    return {
        area: area.toFixed(2),
        distance: distance.toFixed(2)
    };
}
function removeFeature() {
    layerVector.getSource().removeFeature(highlighted);
    let geojson = new ol.format.GeoJSON(),
        measurement = getMeasurement(layerVector.getSource().getFeatures());
    $(apply_coordinate).val(geojson.writeFeatures(layerVector.getSource().getFeatures(), {
        featureProjection: map.getView().getProjection()
    }));
    $(apply_measurement).val((measurement.area > 0 ? measurement.area : measurement.distance));
    popup.hide();
    map.removeOverlay(popup);
}
 |