<html lang="en">
	<head>
		<link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" type="text/css">
		<style>
			.map {
				height: 100%;
				width: 100%;
			}

			.modal {
				display: none; /* Hidden by default */
				position: fixed; /* Stay in place */
				z-index: 1; /* Sit on top */
				left: 0;
				top: 0;
				width: 100%; /* Full width */
				height: 100%; /* Full height */
				overflow: auto; /* Enable scroll if needed */
				background-color: rgb(0,0,0); /* Fallback color */
				background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
			}

			/* Modal Content/Box */
			.modal-content {
				background-color: #fefefe;
				margin: 15% auto; /* 15% from the top and centered */
				padding: 20px;
				border: 1px solid #888;
				width: 550px; /* Could be more or less, depending on screen size */
			}

			/* The Close Button */
			.close {
				color: #aaa;
				float: right;
				font-size: 28px;
				font-weight: bold;
			}

			.close:hover,
			.close:focus {
				color: black;
				text-decoration: none;
				cursor: pointer;
			}
			
		</style>
		<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js">
		<title>OpenLayers example</title>
	</head>
	<body>
		<div id="map" class="map"></div>
		<form>
			<label>cluster distance</label>
			<input id="distance" type="range" min="0" max="100" step="1" value="40"/>
		</form>
		<!-- The Modal -->
		<div id="myModal" class="modal">

		<!-- Modal content -->
		<div class="modal-content">
			<span class="close">×</span>
			<h2 id="modalTitle" ></h2>
			<img id="modalImage" src="" />
			<p><a id="modalUrl" href="">Click to find out more</a></p>
		</div>
		<script type="text/javascript">
			
			var jsonData =
			{
				"odessa": {
					"title": "Odessa Restaurant, Kyiv",
					"long": 30.5191,
					"lat": 50.4227,
					"imgSrc": "https://media-cdn.tripadvisor.com/media/photo-s/04/49/ca/08/odessa-restaurant.jpg",
					"url": "https://odessarest.com.ua"
				},
				"example": {
					"title": "Example marker",
					"long": 31.5191,
					"lat": 51.4227,
					"imgSrc": "https://images-na.ssl-images-amazon.com/images/I/61PcbNuRRfL._SX425_.jpg",
					"url": "https://mobiletechtracker.co.uk/"
				}
			};
			
			var modal = document.getElementById("myModal");
			var span = document.getElementsByClassName("close")[0];
			
			span.onclick = function() {
				closeModal();
			}

			// When the user clicks anywhere outside of the modal, close it
			window.onclick = function(event) {
				if (event.target == modal)
					closeModal();
			}

			function closeModal() {
				modal.style.display = "none";
			}

			function createStyle(src, img) {
				return new ol.style.Style({
					image: new ol.style.Icon(({
						anchor: [0.5, 0.96],
						crossOrigin: 'anonymous',
						src: src,
						img: img,
						imgSize: img ? [img.width, img.height] : undefined
					}))
				});
			}
			
			var iconFeatures = [];
			
			setIconFeatures();
			
			function setIconFeatures() {
				for(var key in jsonData) {			
					var jsonItem = jsonData[key];
				
					var iconFeature = new ol.Feature(new ol.geom.Point(ol.proj.fromLonLat([jsonItem.long, jsonItem.lat])));
					iconFeature.setId(key);
					iconFeature.set('style', createStyle('https://openlayers.org/en/latest/examples/data/icon.png', undefined));
					iconFeatures.push(iconFeature);
				}
			}
	  
			var distance = document.getElementById('distance');
			var source = new ol.source.Vector({features: iconFeatures});
	  
			var unclusteredLayer = new ol.layer.Vector({
				source: source,
				style: function(feature) {
					return feature.get('style');
				},
				maxResolution: 2000
			});
	  
			var clusterSource = new ol.source.Cluster({
				distance: parseInt(distance.value, 10),
				source: source
			});
	  
			var styleCache = {};
			
			var clusters = new ol.layer.Vector({
				source: clusterSource,
				style: function(feature) {
					var size = feature.get('features').length;
					var style = styleCache[size];
					if (!style) {
						style = new ol.style.Style({
							image: new ol.style.Circle({
								radius: 10,
								stroke: new ol.style.Stroke({
									color: '#fff'
								}),
								fill: new ol.style.Fill({
									color: '#3399CC'
								})
							}),
							text: new ol.style.Text({
								text: size.toString(),
								fill: new ol.style.Fill({
									color: '#fff'
								})
							})
						});
						styleCache[size] = style;
					}
					return style;
				},
				minResolution: 2001
			});
	  
			var raster = new ol.layer.Tile({
				source: new ol.source.OSM()
			});
	  
			var map = new ol.Map({
				target: 'map',
				layers: [raster, clusters, unclusteredLayer],
				view: new ol.View({
					center: ol.proj.fromLonLat([30.5191, 50.4227]),
					zoom: 6
				})
			});

			distance.addEventListener('input', function() {
				clusterSource.setDistance(parseInt(distance.value, 10));
			});

			map.on('click', function(event) {
				map.forEachFeatureAtPixel(event.pixel, function(feature,layer) {
					var key = feature.getId();		
					if (key in jsonData) {
						var jsonItem = jsonData[key];
						
						document.getElementById("modalTitle").innerText = jsonItem.title;
						document.getElementById("modalImage").src = jsonItem.imgSrc;
						document.getElementById("modalUrl").href = jsonItem.url;
					
						modal.style.display = "block";
					}
					else if (map.getView().getZoom() < 7) {
						map.getView().fit(feature.getGeometry(), {padding: [170, 50, 30, 150], minResolution: 1000});
					}
				});
			});

			map.on('pointermove', function(evt) {
				map.getTargetElement().style.cursor =
					map.hasFeatureAtPixel(evt.pixel) ? 'pointer' : '';
			});
			
		</script>
	</body>
</html>