File: public/assets/local/js/openlayers.min.js

Recommend this page to a friend!
  Classes of Aby Dahana  >  Aksara  >  public/assets/local/js/openlayers.min.js  >  Download  
File: public/assets/local/js/openlayers.min.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Aksara
A CodeIgniter based API and CRUD generator
Author: By
Last change: Update of public/assets/local/js/openlayers.min.js
Date: 2 months ago
Size: 43,142 bytes
 

Contents

Class file image Download
/**
 * OpenLayers Map Module
 * This module will initialize map from the given parameter
 *
 * @author			Aby Dahana
 * @profile			abydahana.github.io
 *
 * Property of Aksara Laboratory
 * www.aksaracms.com
 */

"use strict";

/**
 * default variables
 */
var _this,
	map,
	layerVector,
	layerOverlap,
	measurementVector,
	selection,
	selectionBox,
	drawingManager,
	drawingType,
	draggableMarker,
	lngLat,
	disable_default_marker,
	colorscheme,
	fill_color,
	stroke_color,
	icon_pattern,
	geocoder,
	geolocation,
	apply_coordinate,
	apply_address,
	apply_measure_area,
	apply_measure_distance,
	geojson,
	c,
	originalHandleEvent,
	highlighted,
	dragging,
	maxZoom											= 20,
	features										= [],
	popup											= new ol.Overlay.Popup(),
	highlight										= new ol.interaction.Select(),
	tileSource										= new ol.source.XYZ
	({
		/* Google Maps will be used, comment the url and attributions to rollback the default */
		url: 'https://mt{0-3}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}',
		attributions:
		[
			' <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors.',
			'Tiles by <a href="https://developers.google.com/maps/terms" target="_blank"> Google</a>'
		],
		crossOrigin: 'anonymous'
	});

var openlayers										= (function()
{
	return {
		/**
		 * render the map
		 */
		render: function(_this_)
		{
			this.reset();
			
			/* keep the "this" context */
			_this									= _this_,
			apply_coordinate						= _this.attr('data-apply-coordinate-to'),
			apply_address							= _this.attr('data-apply-address-to'),
			apply_measure_area						= _this.attr('data-apply-measure-area-to'),
			apply_measure_distance					= _this.attr('data-apply-measure-distance-to'),
			disable_default_marker					= (1 == _this.attr('data-disable-default-marker') ? true : false),
			
			fill_color								= (_this.attr('data-fill') ? _this.attr('data-fill') : null),
			stroke_color							= (_this.attr('data-stroke') ? _this.attr('data-stroke') : null),
			icon_pattern							= (_this.attr('data-icon') ? _this.attr('data-icon') : null),
			
			leave_page								= false,
			
			/* set the coordinate from data-coordinate attribute*/
			lngLat									= (_this.attr('data-coordinate') ? JSON.parse(_this.attr('data-coordinate')) : ''),
			lngLat									= (lngLat && typeof lngLat.lng !== 'undefined' && typeof lngLat.lat !== 'undefined' ? [lngLat.lng, lngLat.lat] : [107.0680127, -6.2299611]),
			colorscheme								= (typeof lngLat.colorscheme !== 'undefined' ? lngLat.colorscheme : '#ff0000');
			
			if(!_this.attr('id'))
			{
				_this.attr('id', 'maps')
			}
			
			if(map)
			{
				map.dispose()
			}
			
			/* define and render map */
			map										= new ol.Map
			({
				interactions: ol.interaction.defaults
				({
					mouseWheelZoom: false,
					dragPan: true
				})
				.extend
				([
					new ol.interaction.MouseWheelZoom
					({
						condition: function(e)
						{
							return (0 != _this.attr('data-mousewheel') || ol.events.condition.platformModifierKeyOnly(e));
						}
					})
				]),
				target: _this.attr('id'),
				layers:
				[
					new ol.layer.Tile
					({
						/*source: new ol.source.OSM()*/
						source: tileSource
					})
				],
				view: new ol.View
				({
					center: ol.proj.fromLonLat(lngLat),
					zoom: (_this.attr('data-zoom') ? parseInt(_this.attr('data-zoom')) : 12),
					maxZoom: maxZoom
				}),
				loadTilesWhileAnimating: false,
				loadTilesWhileInteracting: false
			});
			
			var resolution							= map.getView().getResolution();
			
			/* add fullscreen control */
			(_this.attr('control-fullscreen') && $(window).outerWidth() > 1024 ? map.addControl(new ol.control.FullScreen()) : ''),
			
			/* add scaleline control */
			(_this.attr('control-scaleline') ? map.addControl(new ol.control.ScaleLine()) : ''),
			
			/* add mouseposition control */
			(_this.attr('control-mouseposition') ? map.addControl(new ol.control.MousePosition({coordinateFormat: ol.coordinate.createStringXY(5), projection: 'EPSG:4326', prefix: 'Degrees', undefinedHTML: '&nbsp;'})) : ''),
			
			/* add zoom extent control */
			(_this.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)}),
			
			_this.on('touchmove', function(e)
			{
				if(e.touches.length < 2)
				{
					(!_this.find('.dragPan').length ? _this.append('<div class="dragPan d-flex align-items-center justify-content-center" style="position:absolute; top:0; right:0; bottom:0; left:0; background:rgba(0,0,0,.5); font-weight:bold; text-align:center"><div class="d-flex align-items-center"><div class="text-light">' + (phrase.use_two_fingers_to_move_the_map ? phrase.use_two_fingers_to_move_the_map : 'Use two fingers to move the map') + '</div></div></div>') : null)
				}
				else
				{
					_this.find('.dragPan').remove()
				}
			});
			
			if(_this.attr('data-drawing-manager'))
			{
			}
			else
			{
				/* add default popup */
				map.addOverlay(popup),
				
				/* add highlight interaction */
				map.addInteraction(highlight)
			}
			
			/* on property change */
			map.getView().on('propertychange', function(event)
			{
				if(typeof geolocation !== 'undefined')
				{
					/* stop tracking to prevent map flicker */
					geolocation.setTracking(false)
				}
			}),
			
			/* on pointermove */
			map.on('pointermove', function(e)
			{
				var pixel							= map.getEventPixel(e.originalEvent),
					hit								= map.hasFeatureAtPixel(pixel);
					
				map.getViewport().style.cursor = hit ? 'pointer' : '';
			}),
			
			/* update map size */
			map.updateSize(),
			
			(_this.attr('data-coordinate') ? this.coordinate((1 == _this.attr('data-draggable') ? true : false)) : null),
			
			(_this.attr('data-geocoder') ? this.geocoder(_this) : null),
			
			(_this.attr('data-geolocation') ? this.geolocation() : null),
			
			(_this.attr('data-geojson') ? this.geojsonString(JSON.parse(_this.attr('data-geojson')), _this.attr('data-drawing-type')) : null)
		},
		
		/**
		 * reset layers
		 */
		reset: function()
		{
			$('.dragPan').remove();
			
			if(map)
			{
				if(drawingManager)
				{
					map.removeInteraction(drawingManager)
				}
				
				if(highlight)
				{
					map.removeInteraction(highlight)
				}
				
				map.getLayers().forEach(function(layer, index)
				{
					if('draggable' == layer.get('initial')) return;
					
					layer.getSource().clear()
				})
			}
			
			if(popup)
			{
				popup.hide()
			}
			
			if(typeof geolocation !== 'undefined')
			{
				/* stop tracking to prevent map flicker */
				geolocation.setTracking(false)
			}
		},
		
		unzip: function(source, spinner)
		{
			if(localStorage.getItem(source.initial))
			{
				if(source.type == 'geojson')
				{
					openlayers.geojson(JSON.parse(localStorage.getItem(source.initial)), spinner)
				}
			}
			else
			{
				/* read kmz (zip) data */
				JSZipUtils.getBinaryContent(source.url, function(err, data)
				{
					if(err)
					{
						/* 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())
							{
								/* create blob file from extracted data */
								zip.files[filename].async('string').then(function(blob)
								{
									/* write blob file */
									source.blob	= URL.createObjectURL(new Blob([blob], {type: 'geojson'}));
									
									localStorage.setItem(source.initial, JSON.stringify({initial: source.initial, blob: source.blob, url: source.url})),
									
									openlayers.geojson(source, spinner)
								})
							}
						})
					})
				})
			}
		},
		
		/**
		 * initialize polygon
		 */
		geojsonString: function(shapes, type)
		{
			var geojson								= new ol.format.GeoJSON();
			
			if(typeof shapes.features !== 'undefined' && shapes.features.length)
			{
				var sourceVector					= new ol.source.Vector
				({
					features: geojson.readFeatures
					(
						shapes,
						{
							featureProjection: 'EPSG:3857'
						}
					)
				});
			}
			else
			{
				var sourceVector					= new ol.source.Vector();
			}
			
			layerVector								= new ol.layer.Vector
			({
				source: sourceVector,
				style: function(feature, resolution)
				{
					var pattern						= null;
					
					if(icon_pattern || feature.get('icon'))
					{
						var canvas					= document.createElement('canvas');
						var context					= canvas.getContext('2d');
						var image					= new Image();
						
						image.src					= (icon_pattern ? icon_pattern : feature.get('icon'));
						pattern						= context.createPattern(image, 'repeat');
					}
					
					return [new ol.style.Style
					({
						image: new ol.style.Icon
						({
							scale: (feature.get('icon-scale') ? feature.get('icon-scale') : 1),
							src: (icon_pattern ? icon_pattern : (feature.get('icon') ? feature.get('icon') : config.asset_url + 'openlayers/resources/icons/marker.png'))
						}),
						stroke: new ol.style.Stroke
						({
							color: (stroke_color ? stroke_color : (feature.get('stroke') ? feature.get('stroke') : hex_to_rgba('#ffffff', 0))),
							width: (feature.get('stroke-width') ? feature.get('stroke-width') : 1)
						}),
						fill: new ol.style.Fill
						({
							color: (pattern ? pattern : hex_to_rgba((fill_color ? fill_color : feature.get('fill')), (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)
			{
				drawingManager						= new ol.interaction.Draw
				({
					type: (type == 'polygon' ? 'Polygon' : (type == 'linestring' ? 'LineString' : 'Point')),
					source: layerVector.getSource()
				});
				drawingType							= type;
				
				/* event on drawing end */
				drawingManager.on('drawend', function(event)
				{
					var drawn						= event.feature,
						features					= layerVector.getSource().getFeatures(),
						output						= features.concat(drawn),
						measure						= getMeasure(output);
					
					$(apply_coordinate).val(geojson.writeFeatures(output, {featureProjection: 'EPSG:3857'})),
					
					$(apply_measure_area).val(measure.area),
					
					$(apply_measure_distance).val(measure.distance)
				});
				
				var drag							= new ol.interaction.Translate
				({
					layers: [layerVector]
				});
				
				var modify							= new ol.interaction.Modify
				({
					source: layerVector.getSource()
				});
				
				var snap							= new ol.interaction.Snap
				({
					source: layerVector.getSource()
				});
				
				var highlight						= new ol.interaction.Select
				({
					layers: [layerVector]
				});
				
				/* add drawing manager */
				map.addInteraction(drawingManager),
				
				/* add feature drag interaction */
				map.addInteraction(drag),
				
				/* add feature modify interaction */
				map.addInteraction(modify),
				
				/* add feature snap interaction */
				map.addInteraction(snap),
				
				/* add feature snap interaction */
				map.addInteraction(highlight),
				
				/* on drag end */
				drag.on('translatestart', function(event)
				{
					popup.hide(),
					
					map.removeOverlay(popup)
				}),
				
				/* on drag end */
				drag.on('translateend', function(event)
				{
					modify.dispatchEvent
					({
						type: 'modifyend'
					})
				}),
				
				/* on modify end */
				modify.on('modifyend', function(event)
				{
					if(typeof event.target !== 'undefined' && typeof event.target.features_ !== 'undefined')
					{
						event.features				= event.target.features_;
					}
					
					var measure						= getMeasure(event.features.getArray());
					
					$(apply_coordinate).val(geojson.writeFeatures(event.features.getArray(), {featureProjection: 'EPSG:3857'})),
					
					$(apply_measure_area).val(measure.area),
					
					$(apply_measure_distance).val(measure.distance)
				}),
				
				highlight.on('select', function(event)
				{
					if(event.selected.length)
					{
						map.addOverlay(popup),
						
						popup.show(event.mapBrowserEvent.coordinate, ('<div class="popup-placeholder"><div class="pt-2 pr-3 pb-2 pl-3 border-bottom"><label class="font-weight-bold d-block mb-0"><i class="mdi mdi-menu" style="width:30px; display:inline-block"></i> ' + (phrase.options ? phrase.options : 'Options') + '</label></div><div class="popup-content"><div class="list-group list-group-flush"><a href="javascript:void(0)" class="list-group-item list-group-item-action p-3" onclick="removeFeature()"><i class="mdi mdi-trash-can-outline"></i> ' + (phrase.remove_feature ? phrase.remove_feature : 'Remove feature') + '</a></div></div></div>'))
						
						highlighted					= event.selected[0];
					}
					else
					{
						popup.hide(),
						
						map.removeOverlay(popup)
					}
				})
			}
		},
		
		/**
		 * render from GeoJSON
		 */
		geojson: function(source, spinner)
		{
			/* add spinner indicator */
			(spinner ? spinner.removeClass('mdi-magnify').addClass('mdi-loading mdi-spin') : ''),
			$('.preloader').remove(),
			$('<div class="absolute top right bottom left preloader"></div>').appendTo(_this);
			
			/* initialize layer */
			layerVector								= new ol.layer.Vector
			({
				source: new ol.source.Vector
				({
					url: (typeof source.blob !== 'undefined' ? source.blob : source.url),
					format: new ol.format.GeoJSON
					({
						extractStyles: true
					}),
					projection: ol.proj.get('EPSG:3857')
				}),
				style: function(feature, resolution)
				{
					var pattern						= null;
					
					if(feature.get('icon'))
					{
						var canvas					= document.createElement('canvas');
						var context					= canvas.getContext('2d');
						var image					= new Image();
						
						image.src					= feature.get('icon');
						pattern						= context.createPattern(image, 'repeat');
					}
					
					return [new ol.style.Style
					({
						image: new ol.style.Icon
						({
							scale: (feature.get('icon-scale') ? feature.get('icon-scale') : 1),
							src: (feature.get('icon') ? feature.get('icon') : config.asset_url + 'openlayers/resources/icons/marker.png')
						}),
						stroke: new ol.style.Stroke
						({
							color: (feature.get('stroke') ? feature.get('stroke') : hex_to_rgba('#ffffff', 0)),
							width: (feature.get('stroke-width') ? feature.get('stroke-width') : 1)
						}),
						fill: new ol.style.Fill
						({
							color: (pattern ? pattern : hex_to_rgba(feature.get('fill'), (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: source.initial,
				url: source.url
			});
			
			/* push layer to map */
			map.addLayer(layerVector),
			
			/* fit map to features */
			layerVector.getSource().once('change',function(e)
			{
				if(layerVector.getSource().getState() === 'ready')
				{ 
					var bounds						= ol.extent.createEmpty(),
						found						= false;
					
					for(var i = 0; i < layerVector.getSource().getFeatures().length; i++)
					{
						found						= true;
						
						ol.extent.extend(bounds, layerVector.getSource().getFeatures()[i].getGeometry().getExtent())
					}
					
					if(found)
					{
						map.getView().fit(bounds, {size: map.getSize()})
					}
				}
			}),
			
			/* on map click event */
			map.on('singleclick', function(event)
			{
				var selected						= highlight.getFeatures();
				
				if(c) return false;
				
				var clicked							= map.forEachFeatureAtPixel(event.pixel, function(point, layer)
				{
					return {
						point: point,
						layer: layer
					};
				});
				
				if(clicked && typeof clicked.point !== 'undefined')
				{
					if(typeof clicked.point.get('object_id') === 'undefined' || !clicked.layer) return;
					
					selected.clear(),
					
					selected.push(clicked.point),
					
					popup.show(event.coordinate, ('<div class="popup-placeholder"><div class="pt-2 pr-3 pb-2 pl-3 border-bottom"><label class="font-weight-bold d-block mb-0">' + (clicked.point.get('title') ? clicked.point.get('title') : clicked.layer.get('title')) + '</label></div><div class="popup-content p-3"></div></div>')),
					
					$.ajax
					({
						url: clicked.layer.get('url'),
						method: 'POST',
						data:
						{
							targetFeature: clicked.point.get('object_id')
						},
						beforeSend: function()
						{
							c						= true;
							
							$('.popup-content').html('<div class="d-flex justify-content-center"><div class="spinner-border" role="status"><span class="sr-only">' + (phrase.loading ? phrase.loading : 'Loading...') + '</span></div></div>')
						}
					})
					.done(function(response)
					{
						c							= false;
						
						$('.popup-content, .identification-information').html((typeof response.content!== 'undefined' ? response.content : ''))
					})
				}
				else
				{
					selected.clear(),
					popup.hide()
				}
			});
			
			/* add event listener to remove spinner */
			var listener							= layerVector.getSource().once('change', function(e)
			{
				if(layerVector.getSource().getState() == 'ready')
				{
					/* remove spinner */
					(spinner ? spinner.removeClass('mdi-loading mdi-spin').addClass('mdi-magnify') : ''),
					$('.preloader').remove(),
					
					/* unbind listener */
					ol.Observable.unByKey(listener)
				}
			});
		},
		
		/**
		 * create draggable marker
		 */
		coordinate: function(draggable)
		{
			draggableMarker							= new ol.layer.Vector
			({
				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 ? phrase.default_marker : 'Default Marker') + '</label>',
							description: '<p class="mb-0">' + (phrase.this_can_be_drag_on_edit_mode ? phrase.this_can_be_drag_on_edit_mode : '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
					({
						anchor: [.5, 1],
						scale: .4,
						src: config.asset_url + 'openlayers/resources/icons/marker.png'
					})
				}),
				initial: 'draggable'
			});
			
			/* push layer to map */
			map.addLayer(draggableMarker);
			
			if(draggable)
			{
				var 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)
					}
					
					var coordinate					= ol.proj.transform(event.coordinate, 'EPSG:3857', 'EPSG:4326');
					var coordinate					=
					{
						lat: coordinate[1],
						lng: coordinate[0]
					};
					
					if('google' == config.openlayers_search_provider)
					{
						var finder					= new google.maps.Geocoder();
						
						finder.geocode({location: coordinate}, function(results, status)
						{
							if(status === 'OK')
							{
								$(apply_address).val(results[0].formatted_address).trigger('change'),
								$('#gcd-input-query').val(results[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)
						{
							/* apply to inputs */
							$(apply_address).val(response.display_name).trigger('change'),
							
							$('#gcd-input-query').val(response.display_name)
						})
					}
					
					$(apply_coordinate).val(JSON.stringify(coordinate))
				})
			}
		},
		
		/**
		 * add geocoder (place search)
		 */
		geocoder: function(_this)
		{
			geocoder								= new Geocoder
			(
				'nominatim',
				{
					provider: ('openlayers' == config.openlayers_search_provider ? 'osm' : config.openlayers_search_provider), /* available provider: osm, mapquest (require key), photon, pelias(require key), bing(require key) and opencage(require key) */
					key: config.openlayers_search_key, /* api key */
					targetType: 'text-input',
					countrycodes: 'id',
					placeholder: (phrase.search_place ? phrase.search_place : 'Search Place'),
					limit: 10,
					autoComplete: true,
					featureStyle: null
				}
			);
			
			/* add geocoder into map controls directories */
			map.addControl(geocoder);
			
			if('google' == config.openlayers_search_provider)
			{
				var autocomplete					= new google.maps.places.Autocomplete
				(
					(document.getElementById('gcd-input-query')),
					{
						types: ['geocode']
					}
				);
				
				google.maps.event.addListener(autocomplete, 'place_changed', function()
				{
					var 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()}))
					}
					
					$(_this.attr('data-apply-address-to')).val(place.formatted_address).trigger('change'),
					
					map.getView().setCenter(ol.proj.transform([place.geometry.location.lng(), place.geometry.location.lat()], 'EPSG:4326', 'EPSG:3857'));
					
					if(draggableMarker)
					{
						draggableMarker.getSource().getFeatures()[0].setGeometry(new ol.geom.Point(ol.proj.transform([place.geometry.location.lng(), place.geometry.location.lat()], 'EPSG:4326', 'EPSG:3857'))),
					
						map.getView().fit(draggableMarker.getSource().getExtent(), map.getSize())
					}
				})
			}
			else
			{
				/* create event when address is chosen */
				geocoder.on('addresschosen', function(response)
				{
					if(typeof geolocation !== 'undefined')
					{
						geolocation.setTracking(false)
					}
					
					var coordinate					= ol.proj.transform(response.coordinate, 'EPSG:3857', 'EPSG:4326');
					var coordinate					=
					{
						lat: coordinate[1],
						lng: coordinate[0]
					};
					
					if(!drawingType)
					{
						/* apply to inputs */
						$(apply_coordinate).val(JSON.stringify(coordinate))
					}
					
					$(_this.attr('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(), map.getSize())
					}
				})
			}
		},
		
		/**
		 * add geolocation (user based location)
		 */
		geolocation: function()
		{
			if(navigator.geolocation)
			{
				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(), map.getSize());
						
						if(!drawingType)
						{
							var coordinate			= ol.proj.transform(geolocation.getPosition(), 'EPSG:3857', 'EPSG:4326');
							var coordinate			=
							{
								lat: coordinate[1],
								lng: coordinate[0]
							};
							
							/* apply to inputs */
							$(apply_coordinate).val(JSON.stringify(coordinate))
						}
					}
					
				});
				
				var 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(), map.getSize());
						
						if(!drawingType)
						{
							var coordinate			= ol.proj.transform(geolocation.getPosition(), 'EPSG:3857', 'EPSG:4326');
							var coordinate			=
							{
								lat: coordinate[1],
								lng: coordinate[0]
							};
							
							/* apply to inputs */
							$(apply_coordinate).val(JSON.stringify(coordinate))
						}
					}
				};
				
				var icon							= document.createElement('i');
				var button							= document.createElement('button');
				var element							= document.createElement('div');
				
				icon.setAttribute('class', 'mdi mdi-crosshairs-gps'),
				button.setAttribute('title', (phrase.track_me ? phrase.track_me : '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
					})
				)
			};
		},
		
		/**
		 * pulsate effect
		 */
		pulsate: function pulsate(feature, style, duration)
		{
			var start								= new Date().getTime(),
				key									= map.on('postcompose', function(event)
			{
				var vectorContext					= event.vectorContext,
					frameState						= event.frameState,
					flashGeom						= feature.getGeometry().clone(),
					elapsed							= frameState.time - start,
					elapsedRatio					= elapsed / duration,
					radius							= ol.easing.easeOut(elapsedRatio) * 35 + 6,
					opacity							= ol.easing.easeOut(1 - elapsedRatio),
					fillOpacity						= ol.easing.easeOut(0.3 - elapsedRatio);
					
				vectorContext.setStyle(new ol.style.Style
				({
					image: new ol.style.Circle
					({
						radius: radius,
						fill: new ol.style.Fill
						({
							color: 'rgba(255, 0, 0, ' + (fillOpacity) + ')'
						}),
						stroke: new ol.style.Stroke
						({
							color: 'rgba(255, 0, 0, ' + fillOpacity + ')',
							width: 1 + opacity
						})
					})
				})),
				
				vectorContext.drawGeometry(flashGeom),
				
				vectorContext.setStyle(style),
				
				vectorContext.drawGeometry(feature.getGeometry());
				
				if(elapsed > duration)
				{
					ol.Observable.unByKey(key),
					openlayers.pulsate(feature, style, duration)
				}
				
				map.render()
			})
		},
		
		/**
		 * selection
		 */
		selection: function(appendTo)
		{
			var selected							= highlight.getFeatures(),
				selectionBox						= new ol.interaction.DragBox
				({
					condition: ol.events.condition.platformModifierKeyOnly
				});
			
			map.addInteraction(selectionBox),
			
			selectionBox.on('boxend', function(e)
			{
				var 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)
					{
						var group					= feature.get('group_id');
						
						if(typeof exists[group] !== 'undefined')
						{
							exists[group]++;
						}
						else
						{
							exists[group]			= 1;
						}
						
						selected.push(feature);
					})
				}),
				
				map.getView().fit(extent, {size: map.getSize()}),
				
				map.getView().setResolution(resolution),
				
				$.each(exists, function(key, val)
				{
					if(val)
					{
						$(appendTo).find('[data-initial=' + key + ']').find('.badge').addClass('badge-info').text(val.toLocaleString('en'));
						$(appendTo).find('[data-initial=' + key + ']').find('input[type=checkbox]').prop('checked', true),
						$(appendTo).find('[data-initial=' + key + ']').find('label').removeClass('text-muted')
					}
				})
			}),
			
			selectionBox.on('boxstart', function(e)
			{
				selected.clear(),
				$(appendTo).find('.badge').text(''),
				$(appendTo).find('input[type=checkbox]').prop('checked', false),
				$(appendTo).find('label').addClass('text-muted')
			}),
			
			map.on('click', function()
			{
				selected.clear(),
				$(appendTo).find('.badge').text(''),
				$(appendTo).find('input[type=checkbox]').prop('checked', false),
				$(appendTo).find('label').addClass('text-muted')
			})
		},
		
		/**
		 * identification
		 */
		identification: function(appendTo)
		{
			var selected							= highlight.getFeatures(),
				selectionBox						= new ol.interaction.DragBox
				({
					condition: ol.events.condition.platformModifierKeyOnly
				});
			
			map.addInteraction(selectionBox),
			
			selectionBox.on('boxend', function(e)
			{
				var 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(typeof feature.get('object_id') !== 'undefined')
						{
							var object_id			= feature.get('object_id');
						}
						else
						{
							/* get primary id from description that contain object_id attribute */
							var 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).attr('data-target')))
						{
							popup.show(layer.getSource().getFeatureById($(this).attr('data-target')).getGeometry().getExtent(), ('<div class="popup-placeholder"><div class="pt-2 pr-3 pb-2 pl-3 border-bottom"><label class="font-weight-bold d-block mb-0 popup-title">' + $(this).attr('data-title') + '</label></div><div class="popup-content p-3"><label class="d-block text-muted text-center pt-3 pb-3">' + (phrase.click_to_get_detail ? phrase.click_to_get_detail : 'Click to get detail') + '</label></div></div>'))
						}
					}),
					
					$('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).attr('data-target')))
						{
							map.getView().fit(layer.getSource().getFeatureById($(this).attr('data-target')).getGeometry().getExtent(), {size: map.getSize(), zoom: maxZoom}),
							
							$.ajax
							({
								url: $(this).attr('data-url'),
								method: 'POST',
								data:
								{
									targetFeature: $(this).attr('data-target'),
									coordinate: ol.proj.transform(event.coordinate, 'EPSG:3857', 'EPSG:4326')
								},
								beforeSend: function()
								{
									$('.popup-content').html('<div class="d-flex justify-content-center"><div class="spinner-border" role="status"><span class="sr-only">' + (phrase.loading ? phrase.loading : 'Loading...') + '</span></div></div>')
								}
							})
							.done(function(response)
							{
								$('.popup-title').html(response.title),
								$('.popup-content').html(response.content)
							})
						}
					})
				}),
				
				map.getView().fit(extent, {size: map.getSize()}),
				
				map.getView().setResolution(resolution)
			}),
			
			selectionBox.on('boxstart', function(e)
			{
				selected.clear()
			}),
			
			map.on('click', function()
			{
				$(appendTo).html(''),
				selected.clear()
			})
		},
		
		/**
		 * measurement
		 */
		measurement: function(type, appendTo)
		{
			$(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.Vector
				({
					source: new ol.source.Vector(),
					style: new ol.style.Style
					({
						stroke: new ol.style.Stroke
						({
							color: colorscheme,
							width: 1
						}),
						fill: new ol.style.Fill
						({
							color: hex_to_rgba(colorscheme, .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)
				{
					measurementVector.getSource().clear()
				}),
				
				/* event on drawing end */
				drawingManager.on('drawend', function(event)
				{
					var key							= (features.length ? parseInt(features.length + 1) : 0),
						prepare						= [];
					features[key]					= event.feature.getGeometry().getCoordinates();
					
					$.each(features, function(_key, _val)
					{
						if(!_val) return;
						
						var coord					= [],
							_val					= ('area' == type ? _val[0] : _val);
						$.each(_val, function(__key, __val)
						{
							coord.push(ol.proj.transform(__val, 'EPSG:3857', 'EPSG:4326'))
						}),
						prepare.push
						({
							type: 'Feature',
							geometry:
							{
								type: ('area' == type ? 'Polygon' : 'LineString'),
								coordinates: ('area' == type ? [coord] : coord)
							},
							properties:
							{
							}
						})
					});
					
					var 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 ? phrase.measurement_result : 'Measurement result') +
							'</h6>' +
							'<h3>' +
								result.toLocaleString('en') +
							'</h3>' +
							'<h3>' +
								label +
							'</h3>' +
						'</div>'
					)
				})
			}
			else
			{
				map.on('singleclick', function(event)
				{
					var coordinate					= ol.proj.transform(event.coordinate, 'EPSG:3857', 'EPSG:4326'),
						hdms						= ol.coordinate.toStringHDMS(coordinate, 5);
					
					if(measurementVector)
					{
						measurementVector.getSource().clear()
					}
					
					measurementVector				= new ol.layer.Vector
					({
						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
							({
								anchor: [.5, 1],
								scale: .5,
								src: config.asset_url + 'openlayers/resources/icons/marker.png'
							})
						})
					});
					
					/* push layer to map */
					map.addLayer(measurementVector)
					
					$(appendTo).html
					(
						'<div class="text-center mt-3">' +
							'<h6 class="mb-3">' +
								(phrase.measurement_result ? phrase.measurement_result : 'Measurement result') +
							'</h6>' +
							'<div class="row">' +
								'<div class="col-6 font-weight-bold">' +
									(phrase.latitude ? phrase.latitude : 'Latitude') +
								'</div>' +
								'<div class="col-6 font-weight-bold">' +
									(phrase.longitude ? phrase.longitude : 'Longitude') +
								'</div>' +
							'</div>' +
							'<div class="row form-group">' +
								'<div class="col-6">' +
									coordinate[1].toString().substring(0, 12) +
								'</div>' +
								'<div class="col-6">' +
									coordinate[0].toString().substring(0, 12) +
								'</div>' +
							'</div>' +
							'<div class="form-group">' +
								'<label class="d-block font-weight-bold">HDMS</label>' +
								'<p>' +
									hdms +
								'</p>' +
							'</div>' +
						'</div>'
					)
				})
			}
		},
		
		/**
		 * upload
		 */
		upload: function(data, title)
		{
			if('kmz' !== data.name.split('.').pop().toLowerCase())
			{
				alert('Only KMZ file are allowed!');
				return;
			}
			
			if(map && layerOverlap)
			{
				map.removeLayer(layerOverlap)
			}
			
			/* unzip archive */
			JSZip.loadAsync(data).then(function(zip)
			{
				/* read extracted datasource */
				Object.keys(zip.files).forEach(function(filename)
				{
					/* check if the extracted data is a valid KML format */
					if('kml' !== filename.split('.').pop().toLowerCase()) return;
					
					/* create blob file from extracted data */
					zip.files[filename].async('string').then(function(blob)
					{
						/* write blob file */
						var blob					= new Blob([blob], {type: 'kml'}),
							blobURL					= URL.createObjectURL(blob);
						
						/* initialize layer */
						layerOverlap				= new ol.layer.Vector
						({
							source: new ol.source.Vector
							({
								projection: ol.proj.get('EPSG:4326'),
								url: blobURL,
								format: new ol.format.KML
								({
									extractStyles: true
								})
							}),
							type: 'xml',
							title: title
						});
						
						/* push layer to map */
						map.addLayer(layerOverlap),
						
						/* fit map to features */
						layerOverlap.getSource().once('change',function(e)
						{
							if(layerOverlap.getSource().getState() === 'ready')
							{ 
								var bounds			= ol.extent.createEmpty();
								
								for(var i = 0; i < layerOverlap.getSource().getFeatures().length; i++)
								{
									ol.extent.extend(bounds, layerOverlap.getSource().getFeatures()[i].getGeometry().getExtent())
								}
								console.log(bounds);
								if(typeof bounds[0] !== 'undefined' && bounds[0] !== Infinity)
								{
									map.getView().fit(bounds, {size: map.getSize()})
								}
							}
						})
					})
				})
			})
		},
		
		/**
		 * download
		 */
		download: function(type, size, resolution)
		{
			if('png' == type)
			{
				//map.once('rendercomplete', function()
				//{
					var canvas						= $('.ol-layer canvas');
					
					if(canvas && typeof canvas[0] !== 'undefined')
					{
						if(navigator.msSaveBlob)
						{
							navigator.msSaveBlob(canvas[0].msToBlob(), 'map.png')
						}
						else
						{
							var link				= document.getElementById('image-download');
							link.href				= canvas[0].toDataURL();
							
							link.click()
						}
					}
				//})
			}
			else if('pdf' == type)
			{
				//map.once('rendercomplete', function()
				//{
					var canvas						= $('.ol-layer canvas');
					
					if(canvas && typeof canvas[0] !== 'undefined')
					{
						require.js(['https://rawgit.com/MrRio/jsPDF/master/dist/jspdf.min.js'], function()
						{
							var resolution			= ($.inArray(resolution, ['a0', 'a1', 'a2', 'a3', 'a4', 'a5']) !== -1 ? resolution : 'a4'),
								doc					= new jsPDF('l', 'mm', resolution);
							
							doc.addImage(canvas[0].toDataURL(), 'PNG', 0, 0, doc.internal.pageSize.getWidth(), doc.internal.pageSize.getHeight())
							doc.save('map.pdf')
						})
					}
				//})
			}
		}
	}
})();

function getMeasure(features)
{
	var area										= 0,
		distance									= 0;
	
	features.forEach(function(feature)
	{
		var 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);
	
	var geojson										= new ol.format.GeoJSON(),
		measure										= getMeasure(layerVector.getSource().getFeatures());
	
	$(apply_coordinate).val(geojson.writeFeatures(layerVector.getSource().getFeatures(), {featureProjection: 'EPSG:3857'})),
	
	$(apply_measure_area).val(measure.area),
	
	$(apply_measure_distance).val(measure.distance),
	
	popup.hide(),
	
	map.removeOverlay(popup)
}

For more information send a message to info at phpclasses dot org.