<!DOCTYPE html>
<html ng-app="app">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
    <script>
    document.write('<base href="' + document.location + '" />');
    </script>
    <script data-require="jquery@*" data-semver="2.1.3" src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
    <link data-require="bootstrap-css@3.1.*" data-semver="3.1.1" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
    <link data-require="bootstrap@*" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
    
    <script data-require="bootstrap@*" data-semver="3.3.1" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.4.0/ol.js"></script>
    <!-- <script src="http://openlayers.org/en/v3.1.0/build/ol-debug.js"></script> -->
    <link rel="stylesheet" href="http://openlayers.org/en/v3.4.0/css/ol.css" />
    <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.11/angular.js" data-semver="1.3.11"></script>
    <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.11/angular-sanitize.js" data-semver="1.3.11"></script>
    <script data-require="ui-bootstrap@*" data-semver="0.12.0" src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/2.5.0/ui-bootstrap-tpls.min.js"></script>
    <script src="app.js"></script>
    <script src="highlightFilter.js"></script>
    <script src="multipleFilter.js"></script>
    <script src="mapService.js"></script>
    <script src="MainController.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="//cdn.jsdelivr.net/angular-material-icons/0.3.0/angular-material-icons.min.js"></script>
    <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600" rel="stylesheet" type="text/css" />
  </head>

  <body ng-controller="mainController as mc">
    <div id="map"></div>
    <div class="row-fluid">
      <tabset class="tabset">
        <div>
          <a class="pull-right vcenter" ng-show="mc.search.length>0" ng-click="mc.cancelSearch()">Cancel Search</a>
        </div>
        <tab heading="Search" active="mc.staticTabs.search">
          <div class="search-tab">
            <div class="input-group">
              <span class="input-group-addon">
                <ng-md-icon icon="search" style="fill: #000;" size="18"></ng-md-icon>
              </span>
              <input type="text" class="form-control" ng-model="mc.search" ng-model-options="{ debounce: 1000 }" aria-describedby="inputGroupSuccess1Status" />
              <div class="input-group-addon">
                <span ng-show="mc.features.length>0">
                  <span>{{filtered.length}}</span>
 Result/s</span>
              </div>
            </div>
            <div class="list" ng-show="filtered.length>0">
              <ul>
                <li ng-repeat="f in filtered = (mc.features | multiple: mc.search)" class="feature-result" ng-click="mc.selectFeature(f.name);">
                  <div>
                    <span>{{f.name}}</span>
                  </div>
                </li>
              </ul>
            </div>
          </div>
        </tab>
        <tab heading="Details" active="mc.staticTabs.details">
          <div class="span4 offset4 details-tab">
            <div ng-show="mc.feature">
              <h2>
                <span ng-bind-html="mc.feature.name | highlight:mc.search"></span>
              </h2>
              <p ng-bind-html="mc.feature.description | highlight:mc.search"></p>
            </div>
            <div ng-show="!mc.feature">
              <h2>No Details available.</h2>
              <p>Select a marker on the map to see the details.</p>
            </div>
          </div>
        </tab>
      </tabset>
    </div>
    <div style="display: none;">
      <!-- Popup -->
      <div id="popup"></div>
    </div>
    <!-- move to template -->
    <!-- https://klarsys.github.io/angular-material-icons/ -->
    <div id="svgmarker" ng-show="mc.features>0">
      <ng-md-icon icon="place" style="fill: #7b98bc;" size="64"></ng-md-icon>
    </div>
    <!-- svg shadow filter definition -->
    <svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
      <filter id="blur" y="-2" height="64" x="-10" width="150">
        <feoffset in="SourceAlpha" dx="0" dy="0.25" result="offset2"></feoffset>
        <fegaussianblur in="offset2" stdDeviation="0.5" result="blur2"></fegaussianblur>
        <femerge>
          <femergenode in="blur2"></femergenode>
          <femergenode in="SourceGraphic"></femergenode>
        </femerge>
      </filter>
    </svg>
  </body>

</html>
// Code goes here

/*----------------------------------------*/
/* map styles 
*/
html, body, #map {
  padding: 0;
  margin: 0;
  font-family: "Open Sans", Helvetica, Arial;
}
 
#map {
  width: 100%;
  height: 200px;
}

#map .popover {
  width: 200px;
  z-index: 9999999;
}

/* custom svg marker styles */
#map svg .icon {
  cursor: pointer;
}

#map svg .icon.selected {
  fill: #dd1c77;
}

/* custom zoom to extent control */
#map .ol-control, .ol-scale-line {
  z-index: 9999999;
}


/*----------------------------------------*/
/* tabs 
*/

/* remove bootstrap rounded tab corners */
.tabset .nav-tabs > li > a {
  -webkit-border-radius: 0;
  -moz-border-radius: 0;
  border-radius: 0;
}

.tabset .details-tab .highlighted {
  background: yellow;
  /* background: #dd1c77;
  color: white; */
}

.tabset .search-tab, .tabset .details-tab {
  margin: 15px;
}

/* search tab */
.tabset .search-tab ul {
  list-style-type: none;
  padding: 0px;
}

.tabset .search-tab .input-group .input-group-addon {
  padding-bottom: 4px;
}

.tabset .list {
  margin-top: 2px;
  max-height: 150px;
  width: 100%;
  overflow-y: auto;
  background-color: #fff;
  border: 1px solid #eee;
}

.tabset .feature-result {
  padding: 10px;
  cursor: pointer;
}

.tabset .feature-result:hover {
  background-color: #dd1c77;
  border: 1px solid #ddd;
  color: #fff;
}

/* cancel search link */
.tabset .vcenter {
  padding: 10px 15px;
  cursor: pointer;
}
/**
 * Main app module
 */
;angular
  .module('app', [
    'ngMdIcons', 
    'ui.bootstrap', 
    'ngSanitize'
  ]);
  
/*
 
                     Tb.          Tb.                                
                     :$$b.        $$$b.                              
                     :$$$$b.      :$$$$b.                            
                     :$$$$$$b     :$$$$$$b                           
                      $$$$$$$b     $$$$$$$b                          
                      $$$$$$$$b    :$$$$$$$b                         
                      :$$$$$$$$b---^$$$$$$$$b                        
                      :$$$$$$$$$b        ""^Tb                       
                       $$$$$$$$$$b    __...__`.                      
                       $$$$$$$$$$$b.g$$$$$$$$$pb                     
                       $$$$$$$$$$$$$$$$$$$$$$$$$b                    
                       $$$$$$$$$$$$$$$$$$$$$$$$$$b                   
                       :$$$$$$$$$$$$$$$$$$$$$$$$$$;                  
                       :$$$$$$$$$$$$$^T$$$$$$$$$$P;                  
                       :$$$$$$$$$$$$$b  "^T$$$$P' :                  
                       :$$$$$$$$$$$$$$b._.g$$$$$p.db                 
                       :$$$$$$$$$$$$$$$$$$$$$$$$$$$$;                
                       :$$$$$$$$"""^^T$$$$$$$$$$$$P^;                
                       :$$$$$$$$       ""^^T$$$P^'  ;                
                       :$$$$$$$$    .'       `"     ;                
                       $$$$$$$$;   /                :                
                       $$$$$$$$;           .----,   :                
                       $$$$$$$$;         ,"          ;               
                       $$$$$$$$$p.                   |               
                      :$$$$$$$$$$$$p.                :               
                      :$$$$$$$$$$$$$$$p.            .'               
                      :$$$$$$$$$$$$$$$$$$p...___..-"                 
                      $$$$$$$$$$$$$$$$$$$$$$$$$;            "To the Bat Mobile, Robin!"         
   .db.          bug  $$$$$$$$$$$$$$$$$$$$$$$$$$                     
  d$$$$bp.            $$$$$$$$$$$$$$$$$$$$$$$$$$;                    
 d$$$$$$$$$$pp..__..gg$$$$$$$$$$$$$$$$$$$$$$$$$$$                    
d$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$p._            .gp. 
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$p._.ggp._.d$$$$b
 
*/
(function() {
'use strict';

/**
 * Highlight filter
 */
angular
  .module('app')
  .filter('highlight', [
    '$sce', 
    filter
  ]);

/**
 * Creates a filter that wraps each search term occurrence in a span element with the 'highlighted' css class
 *
 * @param {$sceProvider} $sce
 * @returns {returnedFunction} highlight filter
 * 
 * See [$sce]{@link https://docs.angularjs.org/api/ng/service/$sce}
 * Credits [higlight filter]{@link http://stackoverflow.com/questions/15519713/highlighting-a-filtered-result-in-angularjs/27798600#27798600}
 */
function filter($sce) {
  return highlight;

  /**
   * Highlight filter
   * @param  {String} inputText - filter expression
   * @param  {String} searchTerms - search 
   */
  function highlight(inputText, searchTerms) {
    if (!searchTerms) return inputText;
    // split search terms by space character
    var terms = searchTerms.split(' ') || [searchTerms]; 
    
    terms.forEach(function(item){
      // avoid messing with HTML tags
      // needs a clever regular expression that skips HTML tags matches altogether
      if (inputText && inputText.indexOf("<")===-1)
        inputText = inputText.replace(new RegExp('(' + item + ')', 'gi'),
          '<span class="highlighted">$1</span>')
    });

    return $sce.trustAsHtml(inputText);
  }
}

})();
(function() {
'use strict';

/**
 * Multiple terms search filter
 */
angular
  .module('app')
  .filter('multiple', [
    '$rootScope', 
    filter
  ]);

/**
 * Creates a filter that takes into account multiple search terms
 *
 * @param {$rootScope} $rootScope
 * @returns {Function} multiple filter
 *
 * Credits [multiple filter]{@link http://stackoverflow.com/questions/23504757/angular-js-filter-by-logical-and-using-multiple-terms}
 */
function filter($rootScope) {
  return multiple;

  /**
   * Multiple filter
   * @param  {String} items - filter expression
   * @param  {String} searchTerms - search 
   */
  function multiple(items, searchTerms) {
    // return all items if searchTerms is empty
    if (!searchTerms) {
      triggerHideFeatures([], $rootScope);
      return items; 
    }

    var terms = searchTerms.split(' '),
      matchingItems = [],
      passTest;

    items.forEach(function(item){ 
      passTest = true;
      terms.forEach(function(term){ 
        // we check the default KML properties
        passTest = passTest && ( 
            (item.name.toLowerCase().indexOf(term.toLowerCase()) > -1) ||
            (item.description.toLowerCase().indexOf(term.toLowerCase()) > -1)
          );
      });
      // Add item to return array only if passTest is true,
      // all search terms were found in item
      if (passTest) { matchingItems.push(item); }
    });
    
    triggerHideFeatures(matchingItems, $rootScope);

    return matchingItems;
  }

  /**
   * Notifies the application with the matching features
   * @param  {Array} matchingItems - filtered features
   * @param  {Object} $rootScope - $rootScope 
   */
  function triggerHideFeatures(matchingItems, $rootScope){
    var featuresArray = matchingItems.map(function(feature){
      return feature.name;
    })
    $rootScope.$broadcast("global.hide-features", featuresArray);
  }
}

})();
(function() {
'use strict';

/**
 * Map Service
 */
angular
  .module('app')
  .factory('mapService', service);

function service(){
  // check openlayers is available on service instantiation
  // this can be handled with Require later on
  if (!ol) return {};

  var map = {}, //convenience reference
    defaults = {
      zoom: 15,
      startLocation: [0,40],
      extractStylesKml: false,
      popupOffset: [0,0],
      featurePropertiesMap: ['name', 'description', 'address', 'phoneNumber', 'styleUrl'],
      onFeatureSelected: function(feature) { console.log("feature selected", feature);}
    },
    zIndex = 9999, 
    popup, 
    selectedFeature,
    myZoomToExtentControl;
  
  // public API
  var ms = {
    map: map, // ol.Map
    init: init,
    getFeatures: getFeatures,
    selectFeature: selectFeature,
    hideFeatures: hideFeatures,
    unselectFeature: unselectFeature
  };
  
  return ms;
  
  ///////////////////////////////////////////////////////////
  // helper functions

  function olMapFeatures() {
    var featuresArray = map  //ol.Map
      .getLayers()  //ol.Collection
      .getArray()[1]  //ol.layer.Vector
        .getSource()  //ol.source.KML
          .getFeatures()  //ol.Feature
    return featuresArray;
  }

  function getFeatures() {
    var f = []; 
    olMapFeatures()
      .forEach(function(olFeature, i) {
        var feature = {id: olFeature.getId()};
        f.push(mapFeatureProperties(feature, olFeature));
      });
    return f;
  }

  function unselectFeature(zoom) {
    var undefined;
    selectedFeature = undefined;
    $("#map path").each(function(index, item){
        item.setAttribute("class", "icon");
      });
    if (zoom) 
      zoomToExtent();
  }
  
  function selectFeature(name, pan){
    var feature;
    if (!name) return;
    var target = $("#map path[feature='" + escape(name) + "']")[0];
    
    //search for feature
    olMapFeatures()
      .forEach(function(item, i) {
        var f = item.get('name');
        if (name==f)
          feature = item;
      });
    selectedFeature = feature;
    
    if (feature) {
      unselectFeature();
      target.setAttribute("class", "icon selected");
      
      //put on top
      $(target.parentNode.parentNode)
        .parent().parent()
        .css('z-index', ++zIndex);
    }

    //display feature details and pan
    if (pan && feature) {
      onFeatureSelected(feature);
      panToFeature(feature, map.getView().getZoom());
    
      var element = angular.element('#popup');
      $(element).popover('destroy');
      //show popup for feature or hide any previous one
      if (feature) {
        setTimeout(function(){
          var coord = feature.getGeometry().getCoordinates();
          var title = feature.get('name');
          popup.setPosition(coord);
          $(element).popover({
            'title': title, 
            'placement': 'top',
            'animation': false,
            'html': true
            //'content': feature.get('description')
          });
          $(element).popover('show'); 
        }, 1000);
      }
    }
    return feature;
  }

  function hideFeatures(features, search){
    //hide any popups
    var element = angular.element('#popup');
    $(element).popover('destroy');
    	  
    if (!features || features.length===0) {
      if (search && search.length>0)
        //search with no results: filters all
         $("#map path.icon").hide();
      else
        //reset after having results
         $("#map path.icon").show();
      return;
    }

	  features.forEach(function(item){
	    $("#map path[feature!='" + escape(item) + "'].icon").hide();
	  });
	  features.forEach(function(item){
	    $("#map path[feature='" + escape(item) + "'].icon").show();
	  });
  }
  

  
  function mapFeatureProperties(feature, olFeature) {
    if (!olFeature) return feature;
    if (!feature) feature = {};
    defaults.featurePropertiesMap.forEach(function(key){
        feature[key] = olFeature.get(key);
    });
    return feature;
  }
  
  function onFeatureSelected(olFeature) {
    if (!olFeature) return;
    var feature = mapFeatureProperties({}, olFeature);
    if(defaults.onFeatureSelected)
      defaults.onFeatureSelected(feature);
  }
  

  
  // Creates an overlays in the given coordinates
  function createSVGOverlay(position, feature) {
      if (defaults.extractStylesKml) return;
      
      var elem = document.createElement('div');
      var svg = angular.element('#svgmarker ng-md-icon').clone();

      //change path attributes
      var path = svg.find('path');
      path.attr('class', 'icon');
      path.attr('filter', 'url(#blur)');
      path.attr('feature', escape(feature.get('name')) );
      
      var filter = document.createElement('filter');
      var fe = document.createElement('feGaussianBlur');
      filter.setAttribute('id', 'blur');
      fe.setAttribute('stdDeviation', 3);
      filter.appendChild(fe);
      svg.find('svg')[0].appendChild(filter);
      
      elem.appendChild(svg[0]);
      
      return new ol.Overlay({
        offset: [-2, 12],
        element: elem,
        position: position,
        positioning: 'bottom-center',
        stopEvent: false,
      });
  }
  
  function renderSVGFeatures(){
    if (defaults.extractStylesKml) return;
    
    //wait till directive renders svg element
    setTimeout(function() {
      olMapFeatures()
        .forEach(function(item, i, arr){
          var hidden = item.get('hidden');
          if (!hidden) {
            var coordinates = item.getGeometry().getCoordinates();
            var overlay = createSVGOverlay(coordinates, item);
            map.addOverlay(overlay);
          }
        });      
    }, 0);
  }
  
  function popupSetup() {
    var element = angular.element('#popup');
    
    // Add popup showing the position the user clicked
    popup = new ol.Overlay({
      element: element,
      stopEvent: true,
			offset: defaults.popupOffset
    });
    map.addOverlay(popup);
    
    var displayPopup = function(evt){
      var element = popup.getElement();
      var coordinate = evt.coordinate;
      var hdms = ol.coordinate.toStringHDMS(ol.proj.transform(
          coordinate, 'EPSG:3857', 'EPSG:4326'));
    
      $(element).popover('destroy');
      popup.setPosition(coordinate);
      // the keys are quoted to prevent renaming in ADVANCED mode.
      $(element).popover({
        'placement': 'top',
        'animation': false,
        'html': true,
        'content': '<p>The location you clicked was:</p><code>' + hdms + '</code>'
      });
      $(element).popover('show');
    }
    
		// display popup on click
		map.on('click', function(evt) {
		  if (defaults.extractStylesKml) {
		    // Regular rendered feature find on click coordinates
  			var feature = map.forEachFeatureAtPixel(evt.pixel,
  				function(feature, layer) {
  					return feature;
  			});
		  } else {
        // SVG marker. Search at element attributes
  		  if (!feature && evt.originalEvent.target && evt.originalEvent.target.nodeName == "path") {
          var target = evt.originalEvent.target;
          var featureId = unescape(target.getAttribute('feature'));
          feature = selectFeature(featureId, false);
  		  };
		  };
		  
		  //trigger onFeatureSelected event
		  selectedFeature = feature;
		  onFeatureSelected(feature);
			
			$(element).popover('destroy');
			//show popup for feature or hide any previous one
			if (feature) {
				
				setTimeout(function(){
					var coord = feature.getGeometry().getCoordinates();
  				popup.setPosition(coord);
  				var title = feature.get('name');
  				$(element).popover({
  				  'title': title, 
  				  'placement': 'top',
            'animation': false,
            'html': true
  				  //'content': feature.get('description')
  				});
  				$(element).popover('show');			  
				}, 1000);
			}
			
			if (feature) {
			  panToFeature(feature, map.getView().getZoom());
			} 
		});
		
  }
  
  function panToFeature(feature, zoom) {
		var lonLat = feature.getGeometry().getCoordinates()
		
		var olPixel = map.getPixelFromCoordinate(lonLat);
		olPixel[1] -= 40;
		lonLat = map.getCoordinateFromPixel(olPixel);
		
		if (map.getView().getZoom() < zoom) 
			map.getView().setZoom(zoom);
		
		var animation = ol.animation.pan({
		  duration: 1000,
			easing: eval(ol.easing.inAndOut),
			source: map.getView().getCenter()
		});
			
		// Add animation to the render pipeline
		map.beforeRender(animation);
		// Change center location
		map.getView().setCenter(lonLat);
	};
  
  function init(config){
    var config = angular.extend(defaults, config);

    createMyZoomToExtentControl();
    
    // map initialisation
    map = new ol.Map({
      target: 'map',
      layers: [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        })
      ],
      view: new ol.View({
        center: ol.proj.transform(config.startLocation, 'EPSG:4326', 'EPSG:3857'),
        zoom: config.zoom
      }),
		  controls: ol.control.defaults().extend([
		    new myZoomToExtentControl({tipLabel: "Fit to extent"}),
				new ol.control.ScaleLine()
			])
    });
    
    popupSetup();
    loadKML();
    zoomToExtent();
  }

  function createMyZoomToExtentControl(){
    /**
     * @constructor
     * @extends {ol.control.Control}
     * @param {Object} opt_options - Control options.
     */
    myZoomToExtentControl = function (opt_options) {

      var options = opt_options || {};

      var button = document.createElement('button');
      button.id = 'zoom-to-extent';
      button.setAttribute("title","Zoom to Extent");

      var span = document.createElement('span');
      
      //span.setAttribute("class", "glyphicon glyphicon-record");
      span.innerHTML = 'E';
      button.appendChild(span);

      var this_ = this;
      var handler = function(e) {
        e.preventDefault(); //cancel click event
        zoomToExtent();
        document.getElementById("zoom-to-extent").disabled = true;
        setTimeout(function() {
          document.getElementById("zoom-to-extent").disabled = false;
        }, 1);
      };

      button.addEventListener('click', handler, true);
      button.addEventListener('touchstart', handler, true);

      var element = document.createElement('div');
      element.className = 'zoom-to-extent ol-zoom-extent ol-unselectable ol-control';
      element.appendChild(button);

      ol.control.Control.call(this, {
        element: element,
        target: options.target
      });

    };
    ol.inherits(myZoomToExtentControl, ol.control.ZoomToExtent);
  }
  
  function zoomToExtent() {
		var bounds = ol.extent.createEmpty();
		
		olMapFeatures()
      .forEach(function(item, i, arr){
        var ext = ol.extent.createEmpty();
        ext = item.getGeometry().getExtent();
        bounds = ol.extent.extend(bounds, ext);
      });
            
		if (bounds) {
		  // increase bounds using a tenth of the 
			// maximum distance between coordinates
			var incX = Math.abs(bounds[2] - bounds[0]);
			var incY = Math.abs(bounds[3] - bounds[1]);
			var buffer = (incX>incY)? incX: incY;
			var bounds10 = ol.extent.createEmpty();
			ol.extent.buffer(bounds, buffer/5, bounds10);
			
			var animation = ol.animation.pan({
				easing: eval(ol.easing.inAndOut),
				source: map.getView().getCenter()
			});
			map.beforeRender(animation);
			
			map.getView().fitExtent(bounds10, map.getSize());
		}	  
	};
  
  function loadKML(){
    var kml = '<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://earth.google.com/kml/2.2"><Document><name><![CDATA[Coworking spaces]]></name><description><![CDATA[]]></description><Style id="style1"><IconStyle><Icon><href>http://geoklubb.se/foursquare-lists-kml-export/img/geoklubb-pin.png</href></Icon></IconStyle></Style><Placemark><name><![CDATA[Ziferblat]]></name><description><![CDATA[<a href="http://london.ziferblat.net">Venue URL</a><br />Phone: 07984 693440<br />]]></description><styleUrl>#style1</styleUrl><Point><coordinates>-0.078374147415161,51.526985282303,0</coordinates></Point></Placemark><Placemark><name><![CDATA[Campus London]]></name><description><![CDATA[<a href="http://campuslondon.com">Venue URL</a><br />]]></description><styleUrl>#style1</styleUrl><Point><coordinates>-0.085487365722656,51.522703131223,0</coordinates></Point></Placemark><Placemark><name><![CDATA[Rainmaking Loft]]></name><description><![CDATA[<a href="http://www.rainmakingloft.com">Venue URL</a><br />]]></description><styleUrl>#style1</styleUrl><Point><coordinates>-0.073642730712891,51.50764732314,0</coordinates></Point></Placemark><Placemark><name><![CDATA[TechHub]]></name><description><![CDATA[Phone: 020 7490 0764<br />]]></description><styleUrl>#style1</styleUrl><Point><coordinates>-0.087708234786987,51.525032831563,0</coordinates></Point></Placemark><Placemark><name><![CDATA[Innovation Warehouse London]]></name><description><![CDATA[<a href="http://innovationwarehouse.org">Venue URL</a><br />Phone: 020 7248 0199<br />]]></description><styleUrl>#style1</styleUrl><Point><coordinates>-0.10269641876221,51.518998061413,0</coordinates></Point></Placemark><Placemark><name><![CDATA[Hub Westminster]]></name><description><![CDATA[<a href="http://westminster.impacthub.net">Venue URL</a><br />Phone: 020 7148 6720<br />]]></description><styleUrl>#style1</styleUrl><Point><coordinates>-0.13124592579464,51.507792367419,0</coordinates></Point></Placemark></Document></kml>';
    var kmlSource = new ol.source.KML({
        projection: 'EPSG:3857',
        text: kml,
        //url: 'source.kml',
        extractStyles: defaults.extractStylesKml
    });
    
    var vectorLayer = new ol.layer.Vector({
        source: kmlSource,
        style: kmlStyle
    });
    
    function kmlStyle(feature, resolution){
      // use default styles if using kml icons
      if (!defaults.extractStylesKml) return [];
      
      return [new ol.style.Style({
        image: new ol.style.Circle({
          radius: 5,
          fill: new ol.style.Fill({
            color: 'rgba(123, 152, 188, 0)'
          }),
          stroke: new ol.style.Stroke({
            color: 'rgba(123, 152, 188, 0)',
            width: 1
          })
        })
      })];
    }
    
    // Add vectory layer to map
    map.addLayer(vectorLayer);
    
    //render custom markers
    renderSVGFeatures();
  }
}


})();
(function() {
'use strict';

/**
 * Main Controller
 */
angular
  .module('app')
  .controller('mainController', Controller);

Controller.$inject = [
    'mapService', 
    '$timeout', 
    '$rootScope'
];

function Controller(mapService, $timeout, $rootScope) {
  var vm = this;

  // map initialisation
  mapService.init({
      extractStylesKml: false,
      popupOffset: [-4,-43],
      featurePropertiesMap: ['name', 'description'], //override default mapping
      onFeatureSelected: onFeatureSelected //override default event handler
  });

  vm.staticTabs = { search: true, details: false };
  vm.features = mapService.getFeatures();
  vm.selectFeature = selectFeature;
  vm.hideFeatures = hideFeatures;
  vm.cancelSearch = cancelSearch;


  ///////////////////////////////////////////////////////////
  // map to view interactions

  /**
   * Event handler triggered when a feature is selected
   *
   * @param {Object} feature - feature selected. 
   * 
   * Feature properties are defined by config.featurePropertiesMap.
   */
  function onFeatureSelected(feature) {
    console.log("feature selected", feature);
    // safely run after digest cycle
    // needed to handle list selection 
    $timeout(function(){
      vm.feature = feature;
      selectTab("details");
    });
  }

  /**
   * Activates tab
   *
   * @param {String} key - tab id 
   */
  function selectTab(key){
    if (vm.staticTabs.hasOwnProperty(key))
      vm.staticTabs[key] = true;
  }

  ///////////////////////////////////////////////////////////
  // view to map interactions
  
  // subscribe to event
  $rootScope.$on("global.hide-features", vm.hideFeatures);

  /**
   * Selects a single feature on the map
   *
   * @param {String} id - feature id 
   */
  function selectFeature(id){
    mapService.selectFeature(id, true);
  }  
  
  /**
   * Hides features on the map
   *
   * @param {Event} event       - event object
   * @param {Array} features    - feature ids that should be shown
   */
  function hideFeatures(event, features){
    mapService.hideFeatures(features, vm.search);
  };

  /**
   * Cancels search and zoom to extent
   */
  function cancelSearch(){
    var undefined, 
      zoomToExtent = true;
      
    selectTab("search");
    vm.search = "";
    vm.feature = undefined;
    mapService.unselectFeature(zoomToExtent);
  };
}

})();
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.2">
   <Document>
      <name><![CDATA[Coworking spaces]]></name>
      <description />
      <Style id="style1">
         <IconStyle>
            <Icon>
               <href>http://geoklubb.se/foursquare-lists-kml-export/img/geoklubb-pin.png</href>
            </Icon>
         </IconStyle>
      </Style>
      <Placemark>
         <name><![CDATA[Ziferblat]]></name>
         <description><![CDATA[<a href="http://london.ziferblat.net">Venue URL</a><br />Phone: 07984 693440<br />]]></description>
         <styleUrl>#style1</styleUrl>
         <Point>
            <coordinates>-0.078374147415161,51.526985282303,0</coordinates>
         </Point>
      </Placemark>
      <Placemark>
         <name><![CDATA[Campus London]]></name>
         <description><![CDATA[<a href="http://campuslondon.com">Venue URL</a><br />]]></description>
         <styleUrl>#style1</styleUrl>
         <Point>
            <coordinates>-0.085487365722656,51.522703131223,0</coordinates>
         </Point>
      </Placemark>
      <Placemark>
         <name><![CDATA[Rainmaking Loft]]></name>
         <description><![CDATA[<a href="http://www.rainmakingloft.com">Venue URL</a><br />]]></description>
         <styleUrl>#style1</styleUrl>
         <Point>
            <coordinates>-0.073642730712891,51.50764732314,0</coordinates>
         </Point>
      </Placemark>
      <Placemark>
         <name><![CDATA[TechHub]]></name>
         <description><![CDATA[Phone: 020 7490 0764<br />]]></description>
         <styleUrl>#style1</styleUrl>
         <Point>
            <coordinates>-0.087708234786987,51.525032831563,0</coordinates>
         </Point>
      </Placemark>
      <Placemark>
         <name><![CDATA[Innovation Warehouse London]]></name>
         <description><![CDATA[<a href="http://innovationwarehouse.org">Venue URL</a><br />Phone: 020 7248 0199<br />]]></description>
         <styleUrl>#style1</styleUrl>
         <Point>
            <coordinates>-0.10269641876221,51.518998061413,0</coordinates>
         </Point>
      </Placemark>
      <Placemark>
         <name><![CDATA[Hub Westminster]]></name>
         <description><![CDATA[<a href="http://westminster.impacthub.net">Venue URL</a><br />Phone: 020 7148 6720<br />]]></description>
         <styleUrl>#style1</styleUrl>
         <Point>
            <coordinates>-0.13124592579464,51.507792367419,0</coordinates>
         </Point>
      </Placemark>
   </Document>
</kml>