├── Lesson03-HelloMaps ├── index.html ├── script.js └── style.css ├── Lesson04-MapOptions ├── index.html ├── map-options.js ├── script.js └── style.css ├── Lesson05-MapUtilities ├── Mapster.js ├── index.html ├── map-options.js ├── script.js └── style.css ├── Lesson06-MapEvents ├── Mapster.js ├── index.html ├── map-options.js ├── script.js └── style.css ├── Lesson07-MapMarkers ├── Mapster.js ├── index.html ├── map-options.js ├── mapicons │ ├── _readme_license.txt │ ├── drink.png │ └── farmstand.png ├── script.js └── style.css ├── Lesson08-MarkerEvents ├── Mapster.js ├── index.html ├── map-options.js ├── mapicons │ ├── _readme_license.txt │ ├── drink.png │ └── farmstand.png ├── script.js └── style.css ├── Lesson09-InfoWindows ├── Mapster.js ├── index.html ├── map-options.js ├── mapicons │ ├── _readme_license.txt │ ├── drink.png │ └── farmstand.png ├── script.js └── style.css ├── Lesson10-KeepingTrackOfMarkers ├── Mapster.js ├── index.html ├── map-options.js ├── mapicons │ ├── _readme_license.txt │ ├── drink.png │ └── farmstand.png ├── script.js └── style.css ├── Lesson11-AdvancedMarkerOrganization ├── List.js ├── Mapster.js ├── index.html ├── map-options.js ├── mapicons │ ├── _readme_license.txt │ ├── drink.png │ └── farmstand.png ├── script.js └── style.css ├── Lesson12-MarkerClustering ├── List.js ├── Mapster.js ├── index.html ├── map-options.js ├── mapicons │ ├── _readme_license.txt │ ├── drink.png │ └── farmstand.png ├── markerclusterer.js ├── script.js └── style.css ├── Lesson13-DealingWithMultipleEvents ├── List.js ├── Mapster.js ├── index.html ├── map-options.js ├── mapicons │ ├── _readme_license.txt │ ├── drink.png │ └── farmstand.png ├── markerclusterer.js ├── script.js └── style.css ├── Lesson14-MapWidget ├── List.js ├── Mapster.js ├── index.html ├── jqueryui.mapster.js ├── map-options.js ├── markerclusterer.js ├── script.js └── style.css ├── Lesson15-StreetViewPanorama ├── List.js ├── Mapster.js ├── index.html ├── jqueryui.mapster.js ├── map-options.js ├── markerclusterer.js ├── script.js └── style.css ├── Lesson16-Geocoding ├── List.js ├── Mapster.js ├── index.html ├── jqueryui.mapster.js ├── map-options.js ├── markerclusterer.js ├── script.js └── style.css ├── Lesson17-Geolocation ├── List.js ├── Mapster.js ├── index.html ├── jqueryui.mapster.js ├── map-options.js ├── markerclusterer.js ├── script.js └── style.css ├── Lesson18-StylingMaps ├── List.js ├── Mapster.js ├── index.html ├── jqueryui.mapster.js ├── map-options.js ├── markerclusterer.js ├── script.js └── style.css └── README.md /Lesson03-HelloMaps/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Lesson03-HelloMaps/script.js: -------------------------------------------------------------------------------- 1 | (function(window, google) { 2 | 3 | // map options 4 | var options = { 5 | center: { 6 | lat: 37.791350, 7 | lng: -122.435883 8 | }, 9 | zoom: 10 10 | }, 11 | element = document.getElementById('map-canvas'), 12 | // map 13 | map = new google.maps.Map(element, options); 14 | 15 | }(window, google)); -------------------------------------------------------------------------------- /Lesson03-HelloMaps/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } -------------------------------------------------------------------------------- /Lesson04-MapOptions/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Lesson04-MapOptions/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | maxZoom: 11, 14 | minZoom: 9, 15 | zoomControlOptions: { 16 | position: google.maps.ControlPosition.LEFT_BOTTOM, 17 | style: google.maps.ZoomControlStyle.DEFAULT 18 | }, 19 | panControlOptions: { 20 | position: google.maps.ControlPosition.LEFT_BOTTOM 21 | } 22 | }; 23 | 24 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson04-MapOptions/script.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | // map options 4 | var options = mapster.MAP_OPTIONS, 5 | element = document.getElementById('map-canvas'), 6 | // map 7 | map = new google.maps.Map(element, options); 8 | 9 | }(window, google, window.Mapster)); -------------------------------------------------------------------------------- /Lesson04-MapOptions/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } -------------------------------------------------------------------------------- /Lesson05-MapUtilities/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | } 7 | Mapster.prototype = { 8 | zoom: function(level) { 9 | if (level) { 10 | this.gMap.setZoom(level); 11 | } else { 12 | return this.gMap.getZoom(); 13 | } 14 | } 15 | }; 16 | return Mapster; 17 | }()); 18 | 19 | Mapster.create = function(element, opts) { 20 | return new Mapster(element, opts); 21 | }; 22 | 23 | window.Mapster = Mapster; 24 | 25 | }(window, google)); -------------------------------------------------------------------------------- /Lesson05-MapUtilities/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Lesson05-MapUtilities/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | zoomControlOptions: { 14 | position: google.maps.ControlPosition.LEFT_BOTTOM, 15 | style: google.maps.ZoomControlStyle.DEFAULT 16 | }, 17 | panControlOptions: { 18 | position: google.maps.ControlPosition.LEFT_BOTTOM 19 | } 20 | }; 21 | 22 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson05-MapUtilities/script.js: -------------------------------------------------------------------------------- 1 | (function(window, mapster) { 2 | 3 | // map options 4 | var options = mapster.MAP_OPTIONS, 5 | element = document.getElementById('map-canvas'), 6 | // map 7 | map = mapster.create(element, options); 8 | map.zoom(18); 9 | alert(map.zoom()); 10 | }(window, window.Mapster)); -------------------------------------------------------------------------------- /Lesson05-MapUtilities/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } -------------------------------------------------------------------------------- /Lesson06-MapEvents/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | } 7 | Mapster.prototype = { 8 | zoom: function(level) { 9 | if (level) { 10 | this.gMap.setZoom(level); 11 | } else { 12 | return this.gMap.getZoom(); 13 | } 14 | } 15 | }; 16 | return Mapster; 17 | }()); 18 | 19 | Mapster.create = function(element, opts) { 20 | return new Mapster(element, opts); 21 | }; 22 | 23 | window.Mapster = Mapster; 24 | 25 | }(window, google)); -------------------------------------------------------------------------------- /Lesson06-MapEvents/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Lesson06-MapEvents/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | zoomControlOptions: { 14 | position: google.maps.ControlPosition.LEFT_BOTTOM, 15 | style: google.maps.ZoomControlStyle.DEFAULT 16 | }, 17 | panControlOptions: { 18 | position: google.maps.ControlPosition.LEFT_BOTTOM 19 | } 20 | }; 21 | 22 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson06-MapEvents/script.js: -------------------------------------------------------------------------------- 1 | (function(window, mapster) { 2 | 3 | // map options 4 | var options = mapster.MAP_OPTIONS, 5 | element = document.getElementById('map-canvas'), 6 | // map 7 | map = mapster.create(element, options); 8 | map.zoom(18); 9 | alert(map.zoom()); 10 | }(window, window.Mapster)); -------------------------------------------------------------------------------- /Lesson06-MapEvents/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } -------------------------------------------------------------------------------- /Lesson07-MapMarkers/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | } 7 | Mapster.prototype = { 8 | zoom: function(level) { 9 | if (level) { 10 | this.gMap.setZoom(level); 11 | } else { 12 | return this.gMap.getZoom(); 13 | } 14 | }, 15 | _on: function(event, callback) { 16 | var self = this; 17 | google.maps.event.addListener(this.gMap, event, function(e) { 18 | callback.call(self, e); 19 | }); 20 | }, 21 | addMarker: function(lat, lng, draggable) { 22 | this._createMarker(lat, lng, draggable); 23 | }, 24 | _createMarker: function(lat, lng, draggable) { 25 | var opts = { 26 | position: { 27 | lat: lat, 28 | lng: lng 29 | }, 30 | draggable: draggable, 31 | map: this.gMap 32 | }; 33 | return new google.maps.Marker(opts); 34 | } 35 | }; 36 | return Mapster; 37 | }()); 38 | 39 | Mapster.create = function(element, opts) { 40 | return new Mapster(element, opts); 41 | }; 42 | 43 | window.Mapster = Mapster; 44 | 45 | }(window, google)); -------------------------------------------------------------------------------- /Lesson07-MapMarkers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Lesson07-MapMarkers/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | zoomControlOptions: { 14 | position: google.maps.ControlPosition.LEFT_BOTTOM, 15 | style: google.maps.ZoomControlStyle.DEFAULT 16 | }, 17 | panControlOptions: { 18 | position: google.maps.ControlPosition.LEFT_BOTTOM 19 | } 20 | }; 21 | 22 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson07-MapMarkers/mapicons/_readme_license.txt: -------------------------------------------------------------------------------- 1 | The project "Map Icons Collection" was created by Nicolas Mollet under the Creative Commons Attribution-Share Alike 3.0 Unported license (CC BY SA 3.0 - http://creativecommons.org/licenses/by-sa/3.0/). 2 | 3 | This license lets you remix, tweak, and build upon our work even for commercial reasons, as long as you credit the project and license your new creations under the identical terms. 4 | 5 | Please credit: Maps Icons Collection https://mapicons.mapsmarker.com 6 | Logo available at https://mapicons.mapsmarker.com/wp-content/uploads/2011/03/miclogo-88x31.gif 7 | 8 | Some icons are derived from the project SJJB Map Icons (http://www.sjjb.co.uk/mapicons/) by SJJB Management (http://www.sjjb.co.uk/), licensed under Creative Commons Public Domain Dedication (http://creativecommons.org/licenses/publicdomain/). 9 | 10 | Some icons are derived from the project User Interface Design Framework (http://www.webalys.com/design-interface-application-framework.php) by Webalys (http://www.webalys.com/). -------------------------------------------------------------------------------- /Lesson07-MapMarkers/mapicons/drink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson07-MapMarkers/mapicons/drink.png -------------------------------------------------------------------------------- /Lesson07-MapMarkers/mapicons/farmstand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson07-MapMarkers/mapicons/farmstand.png -------------------------------------------------------------------------------- /Lesson07-MapMarkers/script.js: -------------------------------------------------------------------------------- 1 | (function(window, mapster) { 2 | 3 | // map options 4 | var options = mapster.MAP_OPTIONS, 5 | element = document.getElementById('map-canvas'), 6 | // map 7 | map = mapster.create(element, options); 8 | 9 | map.addMarker(37.791350, -122.435883, true); 10 | 11 | // var marker = new google.maps.Marker({ 12 | // position: { 13 | // lat: 37.791350, 14 | // lng: -122.435883 15 | // }, 16 | // map: map.gMap, 17 | // icon: 'mapicons/drink.png' 18 | // }); 19 | 20 | // var marker2 = new google.maps.Marker({ 21 | // position: { 22 | // lat: 37.781350, 23 | // lng: -122.475883 24 | // }, 25 | // map: map.gMap, 26 | // icon: 'mapicons/farmstand.png' 27 | // }); 28 | 29 | }(window, window.Mapster)); 30 | -------------------------------------------------------------------------------- /Lesson07-MapMarkers/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } -------------------------------------------------------------------------------- /Lesson08-MarkerEvents/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | } 7 | Mapster.prototype = { 8 | zoom: function(level) { 9 | if (level) { 10 | this.gMap.setZoom(level); 11 | } else { 12 | return this.gMap.getZoom(); 13 | } 14 | }, 15 | _on: function(event, callback) { 16 | var self = this; 17 | google.maps.event.addListener(this.gMap, event, function(e) { 18 | callback.call(self, e); 19 | }); 20 | }, 21 | addMarker: function(lat, lng, draggable) { 22 | this._createMarker(lat, lng, draggable); 23 | }, 24 | _createMarker: function(lat, lng, draggable) { 25 | var opts = { 26 | position: { 27 | lat: lat, 28 | lng: lng 29 | }, 30 | draggable: draggable, 31 | map: this.gMap 32 | }; 33 | return new google.maps.Marker(opts); 34 | } 35 | }; 36 | return Mapster; 37 | }()); 38 | 39 | Mapster.create = function(element, opts) { 40 | return new Mapster(element, opts); 41 | }; 42 | 43 | window.Mapster = Mapster; 44 | 45 | }(window, google)); -------------------------------------------------------------------------------- /Lesson08-MarkerEvents/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Lesson08-MarkerEvents/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | zoomControlOptions: { 14 | position: google.maps.ControlPosition.LEFT_BOTTOM, 15 | style: google.maps.ZoomControlStyle.DEFAULT 16 | }, 17 | panControlOptions: { 18 | position: google.maps.ControlPosition.LEFT_BOTTOM 19 | } 20 | }; 21 | 22 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson08-MarkerEvents/mapicons/_readme_license.txt: -------------------------------------------------------------------------------- 1 | The project "Map Icons Collection" was created by Nicolas Mollet under the Creative Commons Attribution-Share Alike 3.0 Unported license (CC BY SA 3.0 - http://creativecommons.org/licenses/by-sa/3.0/). 2 | 3 | This license lets you remix, tweak, and build upon our work even for commercial reasons, as long as you credit the project and license your new creations under the identical terms. 4 | 5 | Please credit: Maps Icons Collection https://mapicons.mapsmarker.com 6 | Logo available at https://mapicons.mapsmarker.com/wp-content/uploads/2011/03/miclogo-88x31.gif 7 | 8 | Some icons are derived from the project SJJB Map Icons (http://www.sjjb.co.uk/mapicons/) by SJJB Management (http://www.sjjb.co.uk/), licensed under Creative Commons Public Domain Dedication (http://creativecommons.org/licenses/publicdomain/). 9 | 10 | Some icons are derived from the project User Interface Design Framework (http://www.webalys.com/design-interface-application-framework.php) by Webalys (http://www.webalys.com/). -------------------------------------------------------------------------------- /Lesson08-MarkerEvents/mapicons/drink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson08-MarkerEvents/mapicons/drink.png -------------------------------------------------------------------------------- /Lesson08-MarkerEvents/mapicons/farmstand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson08-MarkerEvents/mapicons/farmstand.png -------------------------------------------------------------------------------- /Lesson08-MarkerEvents/script.js: -------------------------------------------------------------------------------- 1 | (function(window, mapster) { 2 | 3 | // map options 4 | var options = mapster.MAP_OPTIONS, 5 | element = document.getElementById('map-canvas'), 6 | // map 7 | map = mapster.create(element, options); 8 | 9 | map.addMarker(37.791350, -122.435883, true); 10 | 11 | // var marker = new google.maps.Marker({ 12 | // position: { 13 | // lat: 37.791350, 14 | // lng: -122.435883 15 | // }, 16 | // map: map.gMap, 17 | // icon: 'mapicons/drink.png' 18 | // }); 19 | 20 | // var marker2 = new google.maps.Marker({ 21 | // position: { 22 | // lat: 37.781350, 23 | // lng: -122.475883 24 | // }, 25 | // map: map.gMap, 26 | // icon: 'mapicons/farmstand.png' 27 | // }); 28 | 29 | }(window, window.Mapster)); 30 | -------------------------------------------------------------------------------- /Lesson08-MarkerEvents/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } -------------------------------------------------------------------------------- /Lesson09-InfoWindows/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | } 7 | Mapster.prototype = { 8 | zoom: function(level) { 9 | if (level) { 10 | this.gMap.setZoom(level); 11 | } else { 12 | return this.gMap.getZoom(); 13 | } 14 | }, 15 | _on: function(opts) { 16 | var self = this; 17 | google.maps.event.addListener(opts.obj, opts.event, function(e) { 18 | opts.callback.call(self, e); 19 | }); 20 | }, 21 | addMarker: function(opts) { 22 | var marker; 23 | opts.position = { 24 | lat: opts.lat, 25 | lng: opts.lng 26 | } 27 | marker = this._createMarker(opts); 28 | if (opts.event) { 29 | this._on({ 30 | obj: marker, 31 | event: opts.event.name, 32 | callback: opts.event.callback 33 | }); 34 | } 35 | if (opts.content) { 36 | this._on({ 37 | obj: marker, 38 | event: 'click', 39 | callback: function() { 40 | var infoWindow = new google.maps.InfoWindow({ 41 | content: opts.content 42 | }); 43 | 44 | infoWindow.open(this.gMap, marker); 45 | } 46 | }) 47 | } 48 | return marker; 49 | }, 50 | _createMarker: function(opts) { 51 | opts.map = this.gMap; 52 | return new google.maps.Marker(opts); 53 | } 54 | }; 55 | return Mapster; 56 | }()); 57 | 58 | Mapster.create = function(element, opts) { 59 | return new Mapster(element, opts); 60 | }; 61 | 62 | window.Mapster = Mapster; 63 | 64 | }(window, google)); -------------------------------------------------------------------------------- /Lesson09-InfoWindows/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Lesson09-InfoWindows/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | zoomControlOptions: { 14 | position: google.maps.ControlPosition.LEFT_BOTTOM, 15 | style: google.maps.ZoomControlStyle.DEFAULT 16 | }, 17 | panControlOptions: { 18 | position: google.maps.ControlPosition.LEFT_BOTTOM 19 | } 20 | }; 21 | 22 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson09-InfoWindows/mapicons/_readme_license.txt: -------------------------------------------------------------------------------- 1 | The project "Map Icons Collection" was created by Nicolas Mollet under the Creative Commons Attribution-Share Alike 3.0 Unported license (CC BY SA 3.0 - http://creativecommons.org/licenses/by-sa/3.0/). 2 | 3 | This license lets you remix, tweak, and build upon our work even for commercial reasons, as long as you credit the project and license your new creations under the identical terms. 4 | 5 | Please credit: Maps Icons Collection https://mapicons.mapsmarker.com 6 | Logo available at https://mapicons.mapsmarker.com/wp-content/uploads/2011/03/miclogo-88x31.gif 7 | 8 | Some icons are derived from the project SJJB Map Icons (http://www.sjjb.co.uk/mapicons/) by SJJB Management (http://www.sjjb.co.uk/), licensed under Creative Commons Public Domain Dedication (http://creativecommons.org/licenses/publicdomain/). 9 | 10 | Some icons are derived from the project User Interface Design Framework (http://www.webalys.com/design-interface-application-framework.php) by Webalys (http://www.webalys.com/). -------------------------------------------------------------------------------- /Lesson09-InfoWindows/mapicons/drink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson09-InfoWindows/mapicons/drink.png -------------------------------------------------------------------------------- /Lesson09-InfoWindows/mapicons/farmstand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson09-InfoWindows/mapicons/farmstand.png -------------------------------------------------------------------------------- /Lesson09-InfoWindows/script.js: -------------------------------------------------------------------------------- 1 | (function(window, mapster) { 2 | 3 | // map options 4 | var options = mapster.MAP_OPTIONS, 5 | element = document.getElementById('map-canvas'), 6 | // map 7 | map = mapster.create(element, options); 8 | 9 | var marker = map.addMarker({ 10 | lat: 37.791350, 11 | lng: -122.435883, 12 | content: '
I like food
', 13 | icon: 'mapicons/drink.png' 14 | }); 15 | 16 | var marker2 = map.addMarker({ 17 | lat: 37.781350, 18 | lng: -122.485883, 19 | content: 'I like rice', 20 | icon: 'mapicons/farmstand.png' 21 | }); 22 | 23 | }(window, window.Mapster)); 24 | -------------------------------------------------------------------------------- /Lesson09-InfoWindows/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } -------------------------------------------------------------------------------- /Lesson10-KeepingTrackOfMarkers/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | this.markers = []; 7 | } 8 | Mapster.prototype = { 9 | zoom: function(level) { 10 | if (level) { 11 | this.gMap.setZoom(level); 12 | } else { 13 | return this.gMap.getZoom(); 14 | } 15 | }, 16 | _on: function(opts) { 17 | var self = this; 18 | google.maps.event.addListener(opts.obj, opts.event, function(e) { 19 | opts.callback.call(self, e); 20 | }); 21 | }, 22 | addMarker: function(opts) { 23 | var marker; 24 | opts.position = { 25 | lat: opts.lat, 26 | lng: opts.lng 27 | } 28 | marker = this._createMarker(opts); 29 | this._addMarker(marker); 30 | if (opts.event) { 31 | this._on({ 32 | obj: marker, 33 | event: opts.event.name, 34 | callback: opts.event.callback 35 | }); 36 | } 37 | if (opts.content) { 38 | this._on({ 39 | obj: marker, 40 | event: 'click', 41 | callback: function() { 42 | var infoWindow = new google.maps.InfoWindow({ 43 | content: opts.content 44 | }); 45 | 46 | infoWindow.open(this.gMap, marker); 47 | } 48 | }) 49 | } 50 | return marker; 51 | }, 52 | _addMarker: function(marker) { 53 | this.markers.push(marker); 54 | }, 55 | _removeMarker: function(marker) { 56 | var indexOf = this.markers.indexOf(marker); 57 | if (indexOf !== -1) { 58 | this.markers.splice(indexOf, 1); 59 | marker.setMap(null); 60 | } 61 | }, 62 | findMarkerByLat: function(lat) { 63 | var i = 0; 64 | for(; i < this.markers.length; i++) { 65 | var marker = this.markers[i]; 66 | if (marker.position.lat() === lat) { 67 | return marker; 68 | } 69 | } 70 | }, 71 | _createMarker: function(opts) { 72 | opts.map = this.gMap; 73 | return new google.maps.Marker(opts); 74 | } 75 | }; 76 | return Mapster; 77 | }()); 78 | 79 | Mapster.create = function(element, opts) { 80 | return new Mapster(element, opts); 81 | }; 82 | 83 | window.Mapster = Mapster; 84 | 85 | }(window, google)); -------------------------------------------------------------------------------- /Lesson10-KeepingTrackOfMarkers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Lesson10-KeepingTrackOfMarkers/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | zoomControlOptions: { 14 | position: google.maps.ControlPosition.LEFT_BOTTOM, 15 | style: google.maps.ZoomControlStyle.DEFAULT 16 | }, 17 | panControlOptions: { 18 | position: google.maps.ControlPosition.LEFT_BOTTOM 19 | } 20 | }; 21 | 22 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson10-KeepingTrackOfMarkers/mapicons/_readme_license.txt: -------------------------------------------------------------------------------- 1 | The project "Map Icons Collection" was created by Nicolas Mollet under the Creative Commons Attribution-Share Alike 3.0 Unported license (CC BY SA 3.0 - http://creativecommons.org/licenses/by-sa/3.0/). 2 | 3 | This license lets you remix, tweak, and build upon our work even for commercial reasons, as long as you credit the project and license your new creations under the identical terms. 4 | 5 | Please credit: Maps Icons Collection https://mapicons.mapsmarker.com 6 | Logo available at https://mapicons.mapsmarker.com/wp-content/uploads/2011/03/miclogo-88x31.gif 7 | 8 | Some icons are derived from the project SJJB Map Icons (http://www.sjjb.co.uk/mapicons/) by SJJB Management (http://www.sjjb.co.uk/), licensed under Creative Commons Public Domain Dedication (http://creativecommons.org/licenses/publicdomain/). 9 | 10 | Some icons are derived from the project User Interface Design Framework (http://www.webalys.com/design-interface-application-framework.php) by Webalys (http://www.webalys.com/). -------------------------------------------------------------------------------- /Lesson10-KeepingTrackOfMarkers/mapicons/drink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson10-KeepingTrackOfMarkers/mapicons/drink.png -------------------------------------------------------------------------------- /Lesson10-KeepingTrackOfMarkers/mapicons/farmstand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson10-KeepingTrackOfMarkers/mapicons/farmstand.png -------------------------------------------------------------------------------- /Lesson10-KeepingTrackOfMarkers/script.js: -------------------------------------------------------------------------------- 1 | (function(window, mapster) { 2 | 3 | // map options 4 | var options = mapster.MAP_OPTIONS, 5 | element = document.getElementById('map-canvas'), 6 | // map 7 | map = mapster.create(element, options); 8 | 9 | var marker = map.addMarker({ 10 | lat: 37.791350, 11 | lng: -122.435883, 12 | content: '
I like food
' 13 | }); 14 | 15 | var marker2 = map.addMarker({ 16 | lat: 37.781350, 17 | lng: -122.485883, 18 | content: 'I like rice' 19 | }); 20 | 21 | //map._removeMarker(marker2); 22 | 23 | //console.log(map.markers); 24 | 25 | var found = map.findMarkerByLat(37.781350); 26 | 27 | console.log(found); 28 | 29 | 30 | }(window, window.Mapster)); -------------------------------------------------------------------------------- /Lesson10-KeepingTrackOfMarkers/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } -------------------------------------------------------------------------------- /Lesson11-AdvancedMarkerOrganization/List.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var List = (function() { 3 | function List() { 4 | this.items = []; 5 | } 6 | List.prototype = { 7 | add: function(item) { 8 | this.items.push(item); 9 | }, 10 | remove: function(item) { 11 | var indexOf = this.items.indexOf(item); 12 | if (indexOf !== -1) { 13 | this.items.splice(indexOf, 1); 14 | } 15 | }, 16 | find: function(callback, action) { 17 | var callbackReturn, 18 | items = this.items, 19 | length = items.length 20 | matches = [], 21 | i = 0; 22 | 23 | for(; i < length; i++) { 24 | callbackReturn = callback(items[i], i); 25 | if (callbackReturn) { 26 | matches.push(items[i]); 27 | } 28 | } 29 | 30 | if (action) { 31 | action.call(this, matches); 32 | } 33 | 34 | return matches; 35 | } 36 | }; 37 | return List; 38 | }()); 39 | 40 | List.create = function() { 41 | return new List(); 42 | }; 43 | 44 | window.List = List; 45 | 46 | }(window)); -------------------------------------------------------------------------------- /Lesson11-AdvancedMarkerOrganization/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google, List) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | this.markers = List.create(); 7 | } 8 | Mapster.prototype = { 9 | zoom: function(level) { 10 | if (level) { 11 | this.gMap.setZoom(level); 12 | } else { 13 | return this.gMap.getZoom(); 14 | } 15 | }, 16 | _on: function(opts) { 17 | var self = this; 18 | google.maps.event.addListener(opts.obj, opts.event, function(e) { 19 | opts.callback.call(self, e); 20 | }); 21 | }, 22 | addMarker: function(opts) { 23 | var marker; 24 | opts.position = { 25 | lat: opts.lat, 26 | lng: opts.lng 27 | } 28 | marker = this._createMarker(opts); 29 | this._addMarker(marker); 30 | if (opts.event) { 31 | this._on({ 32 | obj: marker, 33 | event: opts.event.name, 34 | callback: opts.event.callback 35 | }); 36 | } 37 | if (opts.content) { 38 | this._on({ 39 | obj: marker, 40 | event: 'click', 41 | callback: function() { 42 | var infoWindow = new google.maps.InfoWindow({ 43 | content: opts.content 44 | }); 45 | 46 | infoWindow.open(this.gMap, marker); 47 | } 48 | }) 49 | } 50 | return marker; 51 | }, 52 | _addMarker: function(marker) { 53 | this.markers.add(marker); 54 | }, 55 | findBy: function(callback) { 56 | this.markers.find(callback); 57 | }, 58 | removeBy: function(callback) { 59 | this.markers.find(callback, function(markers) { 60 | markers.forEach(function(marker) { 61 | marker.setMap(null); 62 | }); 63 | }); 64 | }, 65 | _createMarker: function(opts) { 66 | opts.map = this.gMap; 67 | return new google.maps.Marker(opts); 68 | } 69 | }; 70 | return Mapster; 71 | }()); 72 | 73 | Mapster.create = function(element, opts) { 74 | return new Mapster(element, opts); 75 | }; 76 | 77 | window.Mapster = Mapster; 78 | 79 | }(window, google, List)); -------------------------------------------------------------------------------- /Lesson11-AdvancedMarkerOrganization/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Lesson11-AdvancedMarkerOrganization/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | zoomControlOptions: { 14 | position: google.maps.ControlPosition.LEFT_BOTTOM, 15 | style: google.maps.ZoomControlStyle.DEFAULT 16 | }, 17 | panControlOptions: { 18 | position: google.maps.ControlPosition.LEFT_BOTTOM 19 | } 20 | }; 21 | 22 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson11-AdvancedMarkerOrganization/mapicons/_readme_license.txt: -------------------------------------------------------------------------------- 1 | The project "Map Icons Collection" was created by Nicolas Mollet under the Creative Commons Attribution-Share Alike 3.0 Unported license (CC BY SA 3.0 - http://creativecommons.org/licenses/by-sa/3.0/). 2 | 3 | This license lets you remix, tweak, and build upon our work even for commercial reasons, as long as you credit the project and license your new creations under the identical terms. 4 | 5 | Please credit: Maps Icons Collection https://mapicons.mapsmarker.com 6 | Logo available at https://mapicons.mapsmarker.com/wp-content/uploads/2011/03/miclogo-88x31.gif 7 | 8 | Some icons are derived from the project SJJB Map Icons (http://www.sjjb.co.uk/mapicons/) by SJJB Management (http://www.sjjb.co.uk/), licensed under Creative Commons Public Domain Dedication (http://creativecommons.org/licenses/publicdomain/). 9 | 10 | Some icons are derived from the project User Interface Design Framework (http://www.webalys.com/design-interface-application-framework.php) by Webalys (http://www.webalys.com/). -------------------------------------------------------------------------------- /Lesson11-AdvancedMarkerOrganization/mapicons/drink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson11-AdvancedMarkerOrganization/mapicons/drink.png -------------------------------------------------------------------------------- /Lesson11-AdvancedMarkerOrganization/mapicons/farmstand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson11-AdvancedMarkerOrganization/mapicons/farmstand.png -------------------------------------------------------------------------------- /Lesson11-AdvancedMarkerOrganization/script.js: -------------------------------------------------------------------------------- 1 | (function(window, mapster) { 2 | 3 | // map options 4 | var options = mapster.MAP_OPTIONS, 5 | element = document.getElementById('map-canvas'), 6 | // map 7 | map = mapster.create(element, options); 8 | 9 | var marker = map.addMarker({ 10 | lat: 37.791350, 11 | lng: -122.435883, 12 | content: '
I like food
', 13 | icon: 'mapicons/drink.png' 14 | }); 15 | 16 | var marker2 = map.addMarker({ 17 | id: 2, 18 | lat: 37.781350, 19 | lng: -122.485883, 20 | content: 'I like rice', 21 | icon: 'mapicons/farmstand.png' 22 | }); 23 | 24 | map.findBy(function(marker) { 25 | return marker.id === 2; 26 | }); 27 | 28 | map.removeBy(function(marker) { 29 | return marker.id === 2; 30 | }); 31 | 32 | 33 | 34 | }(window, window.Mapster)); 35 | -------------------------------------------------------------------------------- /Lesson11-AdvancedMarkerOrganization/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } -------------------------------------------------------------------------------- /Lesson12-MarkerClustering/List.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var List = (function() { 3 | function List() { 4 | this.items = []; 5 | } 6 | List.prototype = { 7 | add: function(item) { 8 | this.items.push(item); 9 | }, 10 | remove: function(item) { 11 | var indexOf = this.items.indexOf(item); 12 | if (indexOf !== -1) { 13 | this.items.splice(indexOf, 1); 14 | } 15 | }, 16 | find: function(callback, action) { 17 | var callbackReturn, 18 | items = this.items, 19 | length = items.length 20 | matches = [], 21 | i = 0; 22 | 23 | for(; i < length; i++) { 24 | callbackReturn = callback(items[i], i); 25 | if (callbackReturn) { 26 | matches.push(items[i]); 27 | } 28 | } 29 | 30 | if (action) { 31 | action.call(this, matches); 32 | } 33 | 34 | return matches; 35 | } 36 | }; 37 | return List; 38 | }()); 39 | 40 | List.create = function() { 41 | return new List(); 42 | }; 43 | 44 | window.List = List; 45 | 46 | }(window)); -------------------------------------------------------------------------------- /Lesson12-MarkerClustering/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google, List) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | this.markers = List.create(); 7 | if (opts.cluster) { 8 | this.markerClusterer = new MarkerClusterer(this.gMap, [], opts.cluster.options); 9 | } 10 | } 11 | Mapster.prototype = { 12 | zoom: function(level) { 13 | if (level) { 14 | this.gMap.setZoom(level); 15 | } else { 16 | return this.gMap.getZoom(); 17 | } 18 | }, 19 | _on: function(opts) { 20 | var self = this; 21 | google.maps.event.addListener(opts.obj, opts.event, function(e) { 22 | opts.callback.call(self, e); 23 | }); 24 | }, 25 | addMarker: function(opts) { 26 | var marker; 27 | opts.position = { 28 | lat: opts.lat, 29 | lng: opts.lng 30 | } 31 | marker = this._createMarker(opts); 32 | if (this.markerClusterer) { 33 | this.markerClusterer.addMarker(marker); 34 | } 35 | this._addMarker(marker); 36 | if (opts.event) { 37 | this._on({ 38 | obj: marker, 39 | event: opts.event.name, 40 | callback: opts.event.callback 41 | }); 42 | } 43 | if (opts.content) { 44 | this._on({ 45 | obj: marker, 46 | event: 'click', 47 | callback: function() { 48 | var infoWindow = new google.maps.InfoWindow({ 49 | content: opts.content 50 | }); 51 | 52 | infoWindow.open(this.gMap, marker); 53 | } 54 | }) 55 | } 56 | return marker; 57 | }, 58 | _addMarker: function(marker) { 59 | this.markers.add(marker); 60 | }, 61 | findBy: function(callback) { 62 | this.markers.find(callback); 63 | }, 64 | removeBy: function(callback) { 65 | var self = this; 66 | self.markers.find(callback, function(markers) { 67 | markers.forEach(function(marker) { 68 | if (self.markerClusterer) { 69 | self.markerClusterer.removeMarker(marker); 70 | } else { 71 | marker.setMap(null); 72 | } 73 | }); 74 | }); 75 | }, 76 | _createMarker: function(opts) { 77 | opts.map = this.gMap; 78 | return new google.maps.Marker(opts); 79 | } 80 | }; 81 | return Mapster; 82 | }()); 83 | 84 | Mapster.create = function(element, opts) { 85 | return new Mapster(element, opts); 86 | }; 87 | 88 | window.Mapster = Mapster; 89 | 90 | }(window, google, List)); -------------------------------------------------------------------------------- /Lesson12-MarkerClustering/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Lesson12-MarkerClustering/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | zoomControlOptions: { 14 | position: google.maps.ControlPosition.LEFT_BOTTOM, 15 | style: google.maps.ZoomControlStyle.DEFAULT 16 | }, 17 | panControlOptions: { 18 | position: google.maps.ControlPosition.LEFT_BOTTOM 19 | }, 20 | cluster: { 21 | options: { 22 | styles: [{ 23 | url: 'http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m2.png', 24 | height: 56, 25 | width: 55, 26 | textColor: '#F00', 27 | textSize: 18 28 | },{ 29 | url: 'http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m1.png', 30 | height: 56, 31 | width: 55 32 | }] 33 | } 34 | } 35 | }; 36 | 37 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson12-MarkerClustering/mapicons/_readme_license.txt: -------------------------------------------------------------------------------- 1 | The project "Map Icons Collection" was created by Nicolas Mollet under the Creative Commons Attribution-Share Alike 3.0 Unported license (CC BY SA 3.0 - http://creativecommons.org/licenses/by-sa/3.0/). 2 | 3 | This license lets you remix, tweak, and build upon our work even for commercial reasons, as long as you credit the project and license your new creations under the identical terms. 4 | 5 | Please credit: Maps Icons Collection https://mapicons.mapsmarker.com 6 | Logo available at https://mapicons.mapsmarker.com/wp-content/uploads/2011/03/miclogo-88x31.gif 7 | 8 | Some icons are derived from the project SJJB Map Icons (http://www.sjjb.co.uk/mapicons/) by SJJB Management (http://www.sjjb.co.uk/), licensed under Creative Commons Public Domain Dedication (http://creativecommons.org/licenses/publicdomain/). 9 | 10 | Some icons are derived from the project User Interface Design Framework (http://www.webalys.com/design-interface-application-framework.php) by Webalys (http://www.webalys.com/). -------------------------------------------------------------------------------- /Lesson12-MarkerClustering/mapicons/drink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson12-MarkerClustering/mapicons/drink.png -------------------------------------------------------------------------------- /Lesson12-MarkerClustering/mapicons/farmstand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson12-MarkerClustering/mapicons/farmstand.png -------------------------------------------------------------------------------- /Lesson12-MarkerClustering/script.js: -------------------------------------------------------------------------------- 1 | (function(window, mapster) { 2 | // DOWNLOAD CLUSTERER: https://code.google.com/p/google-maps-utility-library-v3/wiki/Libraries 3 | 4 | // map options 5 | var options = mapster.MAP_OPTIONS, 6 | element = document.getElementById('map-canvas'), 7 | // map 8 | map = mapster.create(element, options); 9 | 10 | var marker2 = map.addMarker({ 11 | id: 2, 12 | lat: 37.781350, 13 | lng: -122.485883, 14 | content: 'I like rice', 15 | icon: 'mapicons/farmstand.png' 16 | }); 17 | 18 | for (var i = 0; i < 40; i++) { 19 | map.addMarker({ 20 | id: 2, 21 | lat: 37.781350 + Math.random(), 22 | lng: -122.485883 + Math.random(), 23 | content: 'I like rice', 24 | icon: 'mapicons/farmstand.png' 25 | }); 26 | 27 | var marker = map.addMarker({ 28 | lat: 37.791350 + Math.random(), 29 | lng: -122.435883 + Math.random(), 30 | content: '
I like food
', 31 | icon: 'mapicons/drink.png' 32 | }); 33 | } 34 | 35 | map.removeBy(function(marker) { 36 | if (marker.id === 2) { 37 | console.log(marker); 38 | } 39 | return marker.id === 2; 40 | }); 41 | 42 | }(window, window.Mapster)); 43 | -------------------------------------------------------------------------------- /Lesson12-MarkerClustering/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } -------------------------------------------------------------------------------- /Lesson13-DealingWithMultipleEvents/List.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var List = (function() { 3 | function List() { 4 | this.items = []; 5 | } 6 | List.prototype = { 7 | add: function(item) { 8 | this.items.push(item); 9 | }, 10 | remove: function(item) { 11 | var indexOf = this.items.indexOf(item); 12 | if (indexOf !== -1) { 13 | this.items.splice(indexOf, 1); 14 | } 15 | }, 16 | find: function(callback, action) { 17 | var callbackReturn, 18 | items = this.items, 19 | length = items.length 20 | matches = [], 21 | i = 0; 22 | 23 | for(; i < length; i++) { 24 | callbackReturn = callback(items[i], i); 25 | if (callbackReturn) { 26 | matches.push(items[i]); 27 | } 28 | } 29 | 30 | if (action) { 31 | action.call(this, matches); 32 | } 33 | 34 | return matches; 35 | } 36 | }; 37 | return List; 38 | }()); 39 | 40 | List.create = function() { 41 | return new List(); 42 | }; 43 | 44 | window.List = List; 45 | 46 | }(window)); -------------------------------------------------------------------------------- /Lesson13-DealingWithMultipleEvents/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google, List) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | this.markers = List.create(); 7 | if (opts.cluster) { 8 | this.markerClusterer = new MarkerClusterer(this.gMap, [], opts.cluster.options); 9 | } 10 | } 11 | Mapster.prototype = { 12 | zoom: function(level) { 13 | if (level) { 14 | this.gMap.setZoom(level); 15 | } else { 16 | return this.gMap.getZoom(); 17 | } 18 | }, 19 | _on: function(opts) { 20 | var self = this; 21 | google.maps.event.addListener(opts.obj, opts.event, function(e) { 22 | opts.callback.call(self, e, opts.obj); 23 | }); 24 | }, 25 | addMarker: function(opts) { 26 | var marker, 27 | self = this; 28 | 29 | opts.position = { 30 | lat: opts.lat, 31 | lng: opts.lng 32 | } 33 | marker = this._createMarker(opts); 34 | if (this.markerClusterer) { 35 | this.markerClusterer.addMarker(marker); 36 | } 37 | this._addMarker(marker); 38 | if (opts.events) { 39 | this._attachEvents(marker, opts.events); 40 | } 41 | if (opts.content) { 42 | this._on({ 43 | obj: marker, 44 | event: 'click', 45 | callback: function() { 46 | var infoWindow = new google.maps.InfoWindow({ 47 | content: opts.content 48 | }); 49 | 50 | infoWindow.open(this.gMap, marker); 51 | } 52 | }) 53 | } 54 | return marker; 55 | }, 56 | _attachEvents: function(obj, events) { 57 | var self = this; 58 | events.forEach(function(event) { 59 | self._on({ 60 | obj: obj, 61 | event: event.name, 62 | callback: event.callback 63 | }); 64 | }); 65 | }, 66 | _addMarker: function(marker) { 67 | this.markers.add(marker); 68 | }, 69 | findBy: function(callback) { 70 | this.markers.find(callback); 71 | }, 72 | removeBy: function(callback) { 73 | var self = this; 74 | self.markers.find(callback, function(markers) { 75 | markers.forEach(function(marker) { 76 | if (self.markerClusterer) { 77 | self.markerClusterer.removeMarker(marker); 78 | } else { 79 | marker.setMap(null); 80 | } 81 | }); 82 | }); 83 | }, 84 | _createMarker: function(opts) { 85 | opts.map = this.gMap; 86 | return new google.maps.Marker(opts); 87 | } 88 | }; 89 | return Mapster; 90 | }()); 91 | 92 | Mapster.create = function(element, opts) { 93 | return new Mapster(element, opts); 94 | }; 95 | 96 | window.Mapster = Mapster; 97 | 98 | }(window, google, List)); -------------------------------------------------------------------------------- /Lesson13-DealingWithMultipleEvents/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Lesson13-DealingWithMultipleEvents/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | zoomControlOptions: { 14 | position: google.maps.ControlPosition.LEFT_BOTTOM, 15 | style: google.maps.ZoomControlStyle.DEFAULT 16 | }, 17 | panControlOptions: { 18 | position: google.maps.ControlPosition.LEFT_BOTTOM 19 | }, 20 | cluster: { 21 | options: { 22 | styles: [{ 23 | url: 'http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m2.png', 24 | height: 56, 25 | width: 55, 26 | textColor: '#F00', 27 | textSize: 18 28 | },{ 29 | url: 'http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m1.png', 30 | height: 56, 31 | width: 55 32 | }] 33 | } 34 | } 35 | }; 36 | 37 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson13-DealingWithMultipleEvents/mapicons/_readme_license.txt: -------------------------------------------------------------------------------- 1 | The project "Map Icons Collection" was created by Nicolas Mollet under the Creative Commons Attribution-Share Alike 3.0 Unported license (CC BY SA 3.0 - http://creativecommons.org/licenses/by-sa/3.0/). 2 | 3 | This license lets you remix, tweak, and build upon our work even for commercial reasons, as long as you credit the project and license your new creations under the identical terms. 4 | 5 | Please credit: Maps Icons Collection https://mapicons.mapsmarker.com 6 | Logo available at https://mapicons.mapsmarker.com/wp-content/uploads/2011/03/miclogo-88x31.gif 7 | 8 | Some icons are derived from the project SJJB Map Icons (http://www.sjjb.co.uk/mapicons/) by SJJB Management (http://www.sjjb.co.uk/), licensed under Creative Commons Public Domain Dedication (http://creativecommons.org/licenses/publicdomain/). 9 | 10 | Some icons are derived from the project User Interface Design Framework (http://www.webalys.com/design-interface-application-framework.php) by Webalys (http://www.webalys.com/). -------------------------------------------------------------------------------- /Lesson13-DealingWithMultipleEvents/mapicons/drink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson13-DealingWithMultipleEvents/mapicons/drink.png -------------------------------------------------------------------------------- /Lesson13-DealingWithMultipleEvents/mapicons/farmstand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tutsplus/google-maps-api/4c5bb807d45226fb0bb452ab3b95c91f25f6902a/Lesson13-DealingWithMultipleEvents/mapicons/farmstand.png -------------------------------------------------------------------------------- /Lesson13-DealingWithMultipleEvents/script.js: -------------------------------------------------------------------------------- 1 | (function(window, mapster) { 2 | // map options 3 | var options = mapster.MAP_OPTIONS, 4 | element = document.getElementById('map-canvas'), 5 | // map 6 | map = mapster.create(element, options); 7 | 8 | var marker2 = map.addMarker({ 9 | lat: 37.781350, 10 | lng: -122.485883, 11 | draggable: true, 12 | events: [{ 13 | name: 'click', 14 | callback: function(e, marker) { 15 | console.log(e, marker); 16 | } 17 | }, { 18 | name: 'dragend', 19 | callback: function() { 20 | alert('dragged'); 21 | } 22 | }], 23 | icon: 'mapicons/farmstand.png' 24 | }); 25 | 26 | }(window, window.Mapster)); 27 | -------------------------------------------------------------------------------- /Lesson13-DealingWithMultipleEvents/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } -------------------------------------------------------------------------------- /Lesson14-MapWidget/List.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var List = (function() { 3 | function List() { 4 | this.items = []; 5 | } 6 | List.prototype = { 7 | add: function(item) { 8 | this.items.push(item); 9 | }, 10 | remove: function(item) { 11 | var indexOf = this.items.indexOf(item); 12 | if (indexOf !== -1) { 13 | this.items.splice(indexOf, 1); 14 | } 15 | }, 16 | find: function(callback, action) { 17 | var callbackReturn, 18 | items = this.items, 19 | length = items.length 20 | matches = [], 21 | i = 0; 22 | 23 | for(; i < length; i++) { 24 | callbackReturn = callback(items[i], i); 25 | if (callbackReturn) { 26 | matches.push(items[i]); 27 | } 28 | } 29 | 30 | if (action) { 31 | action.call(this, matches); 32 | } 33 | 34 | return matches; 35 | } 36 | }; 37 | return List; 38 | }()); 39 | 40 | List.create = function() { 41 | return new List(); 42 | }; 43 | 44 | window.List = List; 45 | 46 | }(window)); -------------------------------------------------------------------------------- /Lesson14-MapWidget/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google, List) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | this.markers = List.create(); 7 | if (opts.cluster) { 8 | this.markerClusterer = new MarkerClusterer(this.gMap, [], opts.cluster.options); 9 | } 10 | } 11 | Mapster.prototype = { 12 | zoom: function(level) { 13 | if (level) { 14 | this.gMap.setZoom(level); 15 | } else { 16 | return this.gMap.getZoom(); 17 | } 18 | }, 19 | _on: function(opts) { 20 | var self = this; 21 | google.maps.event.addListener(opts.obj, opts.event, function(e) { 22 | opts.callback.call(self, e, opts.obj); 23 | }); 24 | }, 25 | addMarker: function(opts) { 26 | var marker, 27 | self = this; 28 | 29 | opts.position = { 30 | lat: opts.lat, 31 | lng: opts.lng 32 | } 33 | marker = this._createMarker(opts); 34 | if (this.markerClusterer) { 35 | this.markerClusterer.addMarker(marker); 36 | } 37 | this._addMarker(marker); 38 | if (opts.events) { 39 | this._attachEvents(marker, opts.events); 40 | } 41 | if (opts.content) { 42 | this._on({ 43 | obj: marker, 44 | event: 'click', 45 | callback: function() { 46 | var infoWindow = new google.maps.InfoWindow({ 47 | content: opts.content 48 | }); 49 | 50 | infoWindow.open(this.gMap, marker); 51 | } 52 | }) 53 | } 54 | return marker; 55 | }, 56 | _attachEvents: function(obj, events) { 57 | var self = this; 58 | events.forEach(function(event) { 59 | self._on({ 60 | obj: obj, 61 | event: event.name, 62 | callback: event.callback 63 | }); 64 | }); 65 | }, 66 | _addMarker: function(marker) { 67 | this.markers.add(marker); 68 | }, 69 | findBy: function(callback) { 70 | this.markers.find(callback); 71 | }, 72 | removeBy: function(callback) { 73 | var self = this; 74 | self.markers.find(callback, function(markers) { 75 | markers.forEach(function(marker) { 76 | if (self.markerClusterer) { 77 | self.markerClusterer.removeMarker(marker); 78 | } else { 79 | marker.setMap(null); 80 | } 81 | }); 82 | }); 83 | }, 84 | _createMarker: function(opts) { 85 | opts.map = this.gMap; 86 | return new google.maps.Marker(opts); 87 | } 88 | }; 89 | return Mapster; 90 | }()); 91 | 92 | Mapster.create = function(element, opts) { 93 | return new Mapster(element, opts); 94 | }; 95 | 96 | window.Mapster = Mapster; 97 | 98 | }(window, google, List)); -------------------------------------------------------------------------------- /Lesson14-MapWidget/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Lesson14-MapWidget/jqueryui.mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, Mapster) { 2 | 3 | $.widget("mapster.mapster", { 4 | // default options 5 | options: { }, 6 | 7 | // the constructor 8 | _create: function() { 9 | var element = this.element[0], 10 | options = this.options; 11 | this.map = Mapster.create(element, options); 12 | }, 13 | 14 | // called when created, and later when changing options 15 | _refresh: function() { 16 | 17 | }, 18 | 19 | // Add a marker onto the map 20 | addMarker: function( opts ) { 21 | this.map.addMarker(opts); 22 | }, 23 | 24 | findMarkers: function(callback) { 25 | return this.map.findBy(callback); 26 | }, 27 | 28 | removeMarkers: function(callback) { 29 | this.map.removeBy(callback); 30 | }, 31 | 32 | markers: function() { 33 | return this.map.markers.items; 34 | }, 35 | 36 | // events bound via _on are removed automatically 37 | // revert other modifications here 38 | _destroy: function() { 39 | 40 | }, 41 | 42 | // _setOptions is called with a hash of all options that are changing 43 | // always refresh when changing options 44 | _setOptions: function() { 45 | // _super and _superApply handle keeping the right this-context 46 | this._superApply( arguments ); 47 | this._refresh(); 48 | }, 49 | 50 | // _setOption is called for each individual option that is changing 51 | _setOption: function( key, value ) { 52 | this._super( key, value ); 53 | } 54 | }); 55 | 56 | }(window, Mapster)) -------------------------------------------------------------------------------- /Lesson14-MapWidget/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | zoomControlOptions: { 14 | position: google.maps.ControlPosition.LEFT_BOTTOM, 15 | style: google.maps.ZoomControlStyle.DEFAULT 16 | }, 17 | panControlOptions: { 18 | position: google.maps.ControlPosition.LEFT_BOTTOM 19 | }, 20 | cluster: false 21 | }; 22 | 23 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson14-MapWidget/markerclusterer.js: -------------------------------------------------------------------------------- 1 | // ==ClosureCompiler== 2 | // @compilation_level ADVANCED_OPTIMIZATIONS 3 | // @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3_3.js 4 | // ==/ClosureCompiler== 5 | 6 | /** 7 | * @name MarkerClusterer for Google Maps v3 8 | * @version version 1.0.1 9 | * @author Luke Mahe 10 | * @fileoverview 11 | * The library creates and manages per-zoom-level clusters for large amounts of 12 | * markers. 13 | *
14 | * This is a v3 implementation of the 15 | * v2 MarkerClusterer. 17 | */ 18 | 19 | /** 20 | * Licensed under the Apache License, Version 2.0 (the "License"); 21 | * you may not use this file except in compliance with the License. 22 | * You may obtain a copy of the License at 23 | * 24 | * http://www.apache.org/licenses/LICENSE-2.0 25 | * 26 | * Unless required by applicable law or agreed to in writing, software 27 | * distributed under the License is distributed on an "AS IS" BASIS, 28 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29 | * See the License for the specific language governing permissions and 30 | * limitations under the License. 31 | */ 32 | 33 | 34 | /** 35 | * A Marker Clusterer that clusters markers. 36 | * 37 | * @param {google.maps.Map} map The Google map to attach to. 38 | * @param {Array.=} opt_markers Optional markers to add to 39 | * the cluster. 40 | * @param {Object=} opt_options support the following options: 41 | * 'gridSize': (number) The grid size of a cluster in pixels. 42 | * 'maxZoom': (number) The maximum zoom level that a marker can be part of a 43 | * cluster. 44 | * 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a 45 | * cluster is to zoom into it. 46 | * 'averageCenter': (boolean) Wether the center of each cluster should be 47 | * the average of all markers in the cluster. 48 | * 'minimumClusterSize': (number) The minimum number of markers to be in a 49 | * cluster before the markers are hidden and a count 50 | * is shown. 51 | * 'styles': (object) An object that has style properties: 52 | * 'url': (string) The image url. 53 | * 'height': (number) The image height. 54 | * 'width': (number) The image width. 55 | * 'anchor': (Array) The anchor position of the label text. 56 | * 'textColor': (string) The text color. 57 | * 'textSize': (number) The text size. 58 | * 'backgroundPosition': (string) The position of the backgound x, y. 59 | * @constructor 60 | * @extends google.maps.OverlayView 61 | */ 62 | function MarkerClusterer(map, opt_markers, opt_options) { 63 | // MarkerClusterer implements google.maps.OverlayView interface. We use the 64 | // extend function to extend MarkerClusterer with google.maps.OverlayView 65 | // because it might not always be available when the code is defined so we 66 | // look for it at the last possible moment. If it doesn't exist now then 67 | // there is no point going ahead :) 68 | this.extend(MarkerClusterer, google.maps.OverlayView); 69 | this.map_ = map; 70 | 71 | /** 72 | * @type {Array.} 73 | * @private 74 | */ 75 | this.markers_ = []; 76 | 77 | /** 78 | * @type {Array.} 79 | */ 80 | this.clusters_ = []; 81 | 82 | this.sizes = [53, 56, 66, 78, 90]; 83 | 84 | /** 85 | * @private 86 | */ 87 | this.styles_ = []; 88 | 89 | /** 90 | * @type {boolean} 91 | * @private 92 | */ 93 | this.ready_ = false; 94 | 95 | var options = opt_options || {}; 96 | 97 | /** 98 | * @type {number} 99 | * @private 100 | */ 101 | this.gridSize_ = options['gridSize'] || 60; 102 | 103 | /** 104 | * @private 105 | */ 106 | this.minClusterSize_ = options['minimumClusterSize'] || 2; 107 | 108 | 109 | /** 110 | * @type {?number} 111 | * @private 112 | */ 113 | this.maxZoom_ = options['maxZoom'] || null; 114 | 115 | this.styles_ = options['styles'] || []; 116 | 117 | /** 118 | * @type {string} 119 | * @private 120 | */ 121 | this.imagePath_ = options['imagePath'] || 122 | this.MARKER_CLUSTER_IMAGE_PATH_; 123 | 124 | /** 125 | * @type {string} 126 | * @private 127 | */ 128 | this.imageExtension_ = options['imageExtension'] || 129 | this.MARKER_CLUSTER_IMAGE_EXTENSION_; 130 | 131 | /** 132 | * @type {boolean} 133 | * @private 134 | */ 135 | this.zoomOnClick_ = true; 136 | 137 | if (options['zoomOnClick'] != undefined) { 138 | this.zoomOnClick_ = options['zoomOnClick']; 139 | } 140 | 141 | /** 142 | * @type {boolean} 143 | * @private 144 | */ 145 | this.averageCenter_ = false; 146 | 147 | if (options['averageCenter'] != undefined) { 148 | this.averageCenter_ = options['averageCenter']; 149 | } 150 | 151 | this.setupStyles_(); 152 | 153 | this.setMap(map); 154 | 155 | /** 156 | * @type {number} 157 | * @private 158 | */ 159 | this.prevZoom_ = this.map_.getZoom(); 160 | 161 | // Add the map event listeners 162 | var that = this; 163 | google.maps.event.addListener(this.map_, 'zoom_changed', function() { 164 | // Determines map type and prevent illegal zoom levels 165 | var zoom = that.map_.getZoom(); 166 | var minZoom = that.map_.minZoom || 0; 167 | var maxZoom = Math.min(that.map_.maxZoom || 100, 168 | that.map_.mapTypes[that.map_.getMapTypeId()].maxZoom); 169 | zoom = Math.min(Math.max(zoom,minZoom),maxZoom); 170 | 171 | if (that.prevZoom_ != zoom) { 172 | that.prevZoom_ = zoom; 173 | that.resetViewport(); 174 | } 175 | }); 176 | 177 | google.maps.event.addListener(this.map_, 'idle', function() { 178 | that.redraw(); 179 | }); 180 | 181 | // Finally, add the markers 182 | if (opt_markers && (opt_markers.length || Object.keys(opt_markers).length)) { 183 | this.addMarkers(opt_markers, false); 184 | } 185 | } 186 | 187 | 188 | /** 189 | * The marker cluster image path. 190 | * 191 | * @type {string} 192 | * @private 193 | */ 194 | MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ = 195 | 'http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/' + 196 | 'images/m'; 197 | 198 | 199 | /** 200 | * The marker cluster image path. 201 | * 202 | * @type {string} 203 | * @private 204 | */ 205 | MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png'; 206 | 207 | 208 | /** 209 | * Extends a objects prototype by anothers. 210 | * 211 | * @param {Object} obj1 The object to be extended. 212 | * @param {Object} obj2 The object to extend with. 213 | * @return {Object} The new extended object. 214 | * @ignore 215 | */ 216 | MarkerClusterer.prototype.extend = function(obj1, obj2) { 217 | return (function(object) { 218 | for (var property in object.prototype) { 219 | this.prototype[property] = object.prototype[property]; 220 | } 221 | return this; 222 | }).apply(obj1, [obj2]); 223 | }; 224 | 225 | 226 | /** 227 | * Implementaion of the interface method. 228 | * @ignore 229 | */ 230 | MarkerClusterer.prototype.onAdd = function() { 231 | this.setReady_(true); 232 | }; 233 | 234 | /** 235 | * Implementaion of the interface method. 236 | * @ignore 237 | */ 238 | MarkerClusterer.prototype.draw = function() {}; 239 | 240 | /** 241 | * Sets up the styles object. 242 | * 243 | * @private 244 | */ 245 | MarkerClusterer.prototype.setupStyles_ = function() { 246 | if (this.styles_.length) { 247 | return; 248 | } 249 | 250 | for (var i = 0, size; size = this.sizes[i]; i++) { 251 | this.styles_.push({ 252 | url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_, 253 | height: size, 254 | width: size 255 | }); 256 | } 257 | }; 258 | 259 | /** 260 | * Fit the map to the bounds of the markers in the clusterer. 261 | */ 262 | MarkerClusterer.prototype.fitMapToMarkers = function() { 263 | var markers = this.getMarkers(); 264 | var bounds = new google.maps.LatLngBounds(); 265 | for (var i = 0, marker; marker = markers[i]; i++) { 266 | bounds.extend(marker.getPosition()); 267 | } 268 | 269 | this.map_.fitBounds(bounds); 270 | }; 271 | 272 | 273 | /** 274 | * Sets the styles. 275 | * 276 | * @param {Object} styles The style to set. 277 | */ 278 | MarkerClusterer.prototype.setStyles = function(styles) { 279 | this.styles_ = styles; 280 | }; 281 | 282 | 283 | /** 284 | * Gets the styles. 285 | * 286 | * @return {Object} The styles object. 287 | */ 288 | MarkerClusterer.prototype.getStyles = function() { 289 | return this.styles_; 290 | }; 291 | 292 | 293 | /** 294 | * Whether zoom on click is set. 295 | * 296 | * @return {boolean} True if zoomOnClick_ is set. 297 | */ 298 | MarkerClusterer.prototype.isZoomOnClick = function() { 299 | return this.zoomOnClick_; 300 | }; 301 | 302 | /** 303 | * Whether average center is set. 304 | * 305 | * @return {boolean} True if averageCenter_ is set. 306 | */ 307 | MarkerClusterer.prototype.isAverageCenter = function() { 308 | return this.averageCenter_; 309 | }; 310 | 311 | 312 | /** 313 | * Returns the array of markers in the clusterer. 314 | * 315 | * @return {Array.} The markers. 316 | */ 317 | MarkerClusterer.prototype.getMarkers = function() { 318 | return this.markers_; 319 | }; 320 | 321 | 322 | /** 323 | * Returns the number of markers in the clusterer 324 | * 325 | * @return {Number} The number of markers. 326 | */ 327 | MarkerClusterer.prototype.getTotalMarkers = function() { 328 | return this.markers_.length; 329 | }; 330 | 331 | 332 | /** 333 | * Sets the max zoom for the clusterer. 334 | * 335 | * @param {number} maxZoom The max zoom level. 336 | */ 337 | MarkerClusterer.prototype.setMaxZoom = function(maxZoom) { 338 | this.maxZoom_ = maxZoom; 339 | }; 340 | 341 | 342 | /** 343 | * Gets the max zoom for the clusterer. 344 | * 345 | * @return {number} The max zoom level. 346 | */ 347 | MarkerClusterer.prototype.getMaxZoom = function() { 348 | return this.maxZoom_; 349 | }; 350 | 351 | 352 | /** 353 | * The function for calculating the cluster icon image. 354 | * 355 | * @param {Array.} markers The markers in the clusterer. 356 | * @param {number} numStyles The number of styles available. 357 | * @return {Object} A object properties: 'text' (string) and 'index' (number). 358 | * @private 359 | */ 360 | MarkerClusterer.prototype.calculator_ = function(markers, numStyles) { 361 | var index = 0; 362 | var count = markers.length; 363 | var dv = count; 364 | while (dv !== 0) { 365 | dv = parseInt(dv / 10, 10); 366 | index++; 367 | } 368 | 369 | index = Math.min(index, numStyles); 370 | return { 371 | text: count, 372 | index: index 373 | }; 374 | }; 375 | 376 | 377 | /** 378 | * Set the calculator function. 379 | * 380 | * @param {function(Array, number)} calculator The function to set as the 381 | * calculator. The function should return a object properties: 382 | * 'text' (string) and 'index' (number). 383 | * 384 | */ 385 | MarkerClusterer.prototype.setCalculator = function(calculator) { 386 | this.calculator_ = calculator; 387 | }; 388 | 389 | 390 | /** 391 | * Get the calculator function. 392 | * 393 | * @return {function(Array, number)} the calculator function. 394 | */ 395 | MarkerClusterer.prototype.getCalculator = function() { 396 | return this.calculator_; 397 | }; 398 | 399 | 400 | /** 401 | * Add an array of markers to the clusterer. 402 | * 403 | * @param {Array.} markers The markers to add. 404 | * @param {boolean=} opt_nodraw Whether to redraw the clusters. 405 | */ 406 | MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) { 407 | if (markers.length) { 408 | for (var i = 0, marker; marker = markers[i]; i++) { 409 | this.pushMarkerTo_(marker); 410 | } 411 | } else if (Object.keys(markers).length) { 412 | for (var marker in markers) { 413 | this.pushMarkerTo_(markers[marker]); 414 | } 415 | } 416 | if (!opt_nodraw) { 417 | this.redraw(); 418 | } 419 | }; 420 | 421 | 422 | /** 423 | * Pushes a marker to the clusterer. 424 | * 425 | * @param {google.maps.Marker} marker The marker to add. 426 | * @private 427 | */ 428 | MarkerClusterer.prototype.pushMarkerTo_ = function(marker) { 429 | marker.isAdded = false; 430 | if (marker['draggable']) { 431 | // If the marker is draggable add a listener so we update the clusters on 432 | // the drag end. 433 | var that = this; 434 | google.maps.event.addListener(marker, 'dragend', function() { 435 | marker.isAdded = false; 436 | that.repaint(); 437 | }); 438 | } 439 | this.markers_.push(marker); 440 | }; 441 | 442 | 443 | /** 444 | * Adds a marker to the clusterer and redraws if needed. 445 | * 446 | * @param {google.maps.Marker} marker The marker to add. 447 | * @param {boolean=} opt_nodraw Whether to redraw the clusters. 448 | */ 449 | MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) { 450 | this.pushMarkerTo_(marker); 451 | if (!opt_nodraw) { 452 | this.redraw(); 453 | } 454 | }; 455 | 456 | 457 | /** 458 | * Removes a marker and returns true if removed, false if not 459 | * 460 | * @param {google.maps.Marker} marker The marker to remove 461 | * @return {boolean} Whether the marker was removed or not 462 | * @private 463 | */ 464 | MarkerClusterer.prototype.removeMarker_ = function(marker) { 465 | var index = -1; 466 | if (this.markers_.indexOf) { 467 | index = this.markers_.indexOf(marker); 468 | } else { 469 | for (var i = 0, m; m = this.markers_[i]; i++) { 470 | if (m == marker) { 471 | index = i; 472 | break; 473 | } 474 | } 475 | } 476 | 477 | if (index == -1) { 478 | // Marker is not in our list of markers. 479 | return false; 480 | } 481 | 482 | marker.setMap(null); 483 | 484 | this.markers_.splice(index, 1); 485 | 486 | return true; 487 | }; 488 | 489 | 490 | /** 491 | * Remove a marker from the cluster. 492 | * 493 | * @param {google.maps.Marker} marker The marker to remove. 494 | * @param {boolean=} opt_nodraw Optional boolean to force no redraw. 495 | * @return {boolean} True if the marker was removed. 496 | */ 497 | MarkerClusterer.prototype.removeMarker = function(marker, opt_nodraw) { 498 | var removed = this.removeMarker_(marker); 499 | 500 | if (!opt_nodraw && removed) { 501 | this.resetViewport(); 502 | this.redraw(); 503 | return true; 504 | } else { 505 | return false; 506 | } 507 | }; 508 | 509 | 510 | /** 511 | * Removes an array of markers from the cluster. 512 | * 513 | * @param {Array.} markers The markers to remove. 514 | * @param {boolean=} opt_nodraw Optional boolean to force no redraw. 515 | */ 516 | MarkerClusterer.prototype.removeMarkers = function(markers, opt_nodraw) { 517 | var removed = false; 518 | 519 | for (var i = 0, marker; marker = markers[i]; i++) { 520 | var r = this.removeMarker_(marker); 521 | removed = removed || r; 522 | } 523 | 524 | if (!opt_nodraw && removed) { 525 | this.resetViewport(); 526 | this.redraw(); 527 | return true; 528 | } 529 | }; 530 | 531 | 532 | /** 533 | * Sets the clusterer's ready state. 534 | * 535 | * @param {boolean} ready The state. 536 | * @private 537 | */ 538 | MarkerClusterer.prototype.setReady_ = function(ready) { 539 | if (!this.ready_) { 540 | this.ready_ = ready; 541 | this.createClusters_(); 542 | } 543 | }; 544 | 545 | 546 | /** 547 | * Returns the number of clusters in the clusterer. 548 | * 549 | * @return {number} The number of clusters. 550 | */ 551 | MarkerClusterer.prototype.getTotalClusters = function() { 552 | return this.clusters_.length; 553 | }; 554 | 555 | 556 | /** 557 | * Returns the google map that the clusterer is associated with. 558 | * 559 | * @return {google.maps.Map} The map. 560 | */ 561 | MarkerClusterer.prototype.getMap = function() { 562 | return this.map_; 563 | }; 564 | 565 | 566 | /** 567 | * Sets the google map that the clusterer is associated with. 568 | * 569 | * @param {google.maps.Map} map The map. 570 | */ 571 | MarkerClusterer.prototype.setMap = function(map) { 572 | this.map_ = map; 573 | }; 574 | 575 | 576 | /** 577 | * Returns the size of the grid. 578 | * 579 | * @return {number} The grid size. 580 | */ 581 | MarkerClusterer.prototype.getGridSize = function() { 582 | return this.gridSize_; 583 | }; 584 | 585 | 586 | /** 587 | * Sets the size of the grid. 588 | * 589 | * @param {number} size The grid size. 590 | */ 591 | MarkerClusterer.prototype.setGridSize = function(size) { 592 | this.gridSize_ = size; 593 | }; 594 | 595 | 596 | /** 597 | * Returns the min cluster size. 598 | * 599 | * @return {number} The grid size. 600 | */ 601 | MarkerClusterer.prototype.getMinClusterSize = function() { 602 | return this.minClusterSize_; 603 | }; 604 | 605 | /** 606 | * Sets the min cluster size. 607 | * 608 | * @param {number} size The grid size. 609 | */ 610 | MarkerClusterer.prototype.setMinClusterSize = function(size) { 611 | this.minClusterSize_ = size; 612 | }; 613 | 614 | 615 | /** 616 | * Extends a bounds object by the grid size. 617 | * 618 | * @param {google.maps.LatLngBounds} bounds The bounds to extend. 619 | * @return {google.maps.LatLngBounds} The extended bounds. 620 | */ 621 | MarkerClusterer.prototype.getExtendedBounds = function(bounds) { 622 | var projection = this.getProjection(); 623 | 624 | // Turn the bounds into latlng. 625 | var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), 626 | bounds.getNorthEast().lng()); 627 | var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), 628 | bounds.getSouthWest().lng()); 629 | 630 | // Convert the points to pixels and the extend out by the grid size. 631 | var trPix = projection.fromLatLngToDivPixel(tr); 632 | trPix.x += this.gridSize_; 633 | trPix.y -= this.gridSize_; 634 | 635 | var blPix = projection.fromLatLngToDivPixel(bl); 636 | blPix.x -= this.gridSize_; 637 | blPix.y += this.gridSize_; 638 | 639 | // Convert the pixel points back to LatLng 640 | var ne = projection.fromDivPixelToLatLng(trPix); 641 | var sw = projection.fromDivPixelToLatLng(blPix); 642 | 643 | // Extend the bounds to contain the new bounds. 644 | bounds.extend(ne); 645 | bounds.extend(sw); 646 | 647 | return bounds; 648 | }; 649 | 650 | 651 | /** 652 | * Determins if a marker is contained in a bounds. 653 | * 654 | * @param {google.maps.Marker} marker The marker to check. 655 | * @param {google.maps.LatLngBounds} bounds The bounds to check against. 656 | * @return {boolean} True if the marker is in the bounds. 657 | * @private 658 | */ 659 | MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) { 660 | return bounds.contains(marker.getPosition()); 661 | }; 662 | 663 | 664 | /** 665 | * Clears all clusters and markers from the clusterer. 666 | */ 667 | MarkerClusterer.prototype.clearMarkers = function() { 668 | this.resetViewport(true); 669 | 670 | // Set the markers a empty array. 671 | this.markers_ = []; 672 | }; 673 | 674 | 675 | /** 676 | * Clears all existing clusters and recreates them. 677 | * @param {boolean} opt_hide To also hide the marker. 678 | */ 679 | MarkerClusterer.prototype.resetViewport = function(opt_hide) { 680 | // Remove all the clusters 681 | for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { 682 | cluster.remove(); 683 | } 684 | 685 | // Reset the markers to not be added and to be invisible. 686 | for (var i = 0, marker; marker = this.markers_[i]; i++) { 687 | marker.isAdded = false; 688 | if (opt_hide) { 689 | marker.setMap(null); 690 | } 691 | } 692 | 693 | this.clusters_ = []; 694 | }; 695 | 696 | /** 697 | * 698 | */ 699 | MarkerClusterer.prototype.repaint = function() { 700 | var oldClusters = this.clusters_.slice(); 701 | this.clusters_.length = 0; 702 | this.resetViewport(); 703 | this.redraw(); 704 | 705 | // Remove the old clusters. 706 | // Do it in a timeout so the other clusters have been drawn first. 707 | window.setTimeout(function() { 708 | for (var i = 0, cluster; cluster = oldClusters[i]; i++) { 709 | cluster.remove(); 710 | } 711 | }, 0); 712 | }; 713 | 714 | 715 | /** 716 | * Redraws the clusters. 717 | */ 718 | MarkerClusterer.prototype.redraw = function() { 719 | this.createClusters_(); 720 | }; 721 | 722 | 723 | /** 724 | * Calculates the distance between two latlng locations in km. 725 | * @see http://www.movable-type.co.uk/scripts/latlong.html 726 | * 727 | * @param {google.maps.LatLng} p1 The first lat lng point. 728 | * @param {google.maps.LatLng} p2 The second lat lng point. 729 | * @return {number} The distance between the two points in km. 730 | * @private 731 | */ 732 | MarkerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) { 733 | if (!p1 || !p2) { 734 | return 0; 735 | } 736 | 737 | var R = 6371; // Radius of the Earth in km 738 | var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; 739 | var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; 740 | var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + 741 | Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * 742 | Math.sin(dLon / 2) * Math.sin(dLon / 2); 743 | var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 744 | var d = R * c; 745 | return d; 746 | }; 747 | 748 | 749 | /** 750 | * Add a marker to a cluster, or creates a new cluster. 751 | * 752 | * @param {google.maps.Marker} marker The marker to add. 753 | * @private 754 | */ 755 | MarkerClusterer.prototype.addToClosestCluster_ = function(marker) { 756 | var distance = 40000; // Some large number 757 | var clusterToAddTo = null; 758 | var pos = marker.getPosition(); 759 | for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { 760 | var center = cluster.getCenter(); 761 | if (center) { 762 | var d = this.distanceBetweenPoints_(center, marker.getPosition()); 763 | if (d < distance) { 764 | distance = d; 765 | clusterToAddTo = cluster; 766 | } 767 | } 768 | } 769 | 770 | if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) { 771 | clusterToAddTo.addMarker(marker); 772 | } else { 773 | var cluster = new Cluster(this); 774 | cluster.addMarker(marker); 775 | this.clusters_.push(cluster); 776 | } 777 | }; 778 | 779 | 780 | /** 781 | * Creates the clusters. 782 | * 783 | * @private 784 | */ 785 | MarkerClusterer.prototype.createClusters_ = function() { 786 | if (!this.ready_) { 787 | return; 788 | } 789 | 790 | // Get our current map view bounds. 791 | // Create a new bounds object so we don't affect the map. 792 | var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(), 793 | this.map_.getBounds().getNorthEast()); 794 | var bounds = this.getExtendedBounds(mapBounds); 795 | 796 | for (var i = 0, marker; marker = this.markers_[i]; i++) { 797 | if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) { 798 | this.addToClosestCluster_(marker); 799 | } 800 | } 801 | }; 802 | 803 | 804 | /** 805 | * A cluster that contains markers. 806 | * 807 | * @param {MarkerClusterer} markerClusterer The markerclusterer that this 808 | * cluster is associated with. 809 | * @constructor 810 | * @ignore 811 | */ 812 | function Cluster(markerClusterer) { 813 | this.markerClusterer_ = markerClusterer; 814 | this.map_ = markerClusterer.getMap(); 815 | this.gridSize_ = markerClusterer.getGridSize(); 816 | this.minClusterSize_ = markerClusterer.getMinClusterSize(); 817 | this.averageCenter_ = markerClusterer.isAverageCenter(); 818 | this.center_ = null; 819 | this.markers_ = []; 820 | this.bounds_ = null; 821 | this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(), 822 | markerClusterer.getGridSize()); 823 | } 824 | 825 | /** 826 | * Determins if a marker is already added to the cluster. 827 | * 828 | * @param {google.maps.Marker} marker The marker to check. 829 | * @return {boolean} True if the marker is already added. 830 | */ 831 | Cluster.prototype.isMarkerAlreadyAdded = function(marker) { 832 | if (this.markers_.indexOf) { 833 | return this.markers_.indexOf(marker) != -1; 834 | } else { 835 | for (var i = 0, m; m = this.markers_[i]; i++) { 836 | if (m == marker) { 837 | return true; 838 | } 839 | } 840 | } 841 | return false; 842 | }; 843 | 844 | 845 | /** 846 | * Add a marker the cluster. 847 | * 848 | * @param {google.maps.Marker} marker The marker to add. 849 | * @return {boolean} True if the marker was added. 850 | */ 851 | Cluster.prototype.addMarker = function(marker) { 852 | if (this.isMarkerAlreadyAdded(marker)) { 853 | return false; 854 | } 855 | 856 | if (!this.center_) { 857 | this.center_ = marker.getPosition(); 858 | this.calculateBounds_(); 859 | } else { 860 | if (this.averageCenter_) { 861 | var l = this.markers_.length + 1; 862 | var lat = (this.center_.lat() * (l-1) + marker.getPosition().lat()) / l; 863 | var lng = (this.center_.lng() * (l-1) + marker.getPosition().lng()) / l; 864 | this.center_ = new google.maps.LatLng(lat, lng); 865 | this.calculateBounds_(); 866 | } 867 | } 868 | 869 | marker.isAdded = true; 870 | this.markers_.push(marker); 871 | 872 | var len = this.markers_.length; 873 | if (len < this.minClusterSize_ && marker.getMap() != this.map_) { 874 | // Min cluster size not reached so show the marker. 875 | marker.setMap(this.map_); 876 | } 877 | 878 | if (len == this.minClusterSize_) { 879 | // Hide the markers that were showing. 880 | for (var i = 0; i < len; i++) { 881 | this.markers_[i].setMap(null); 882 | } 883 | } 884 | 885 | if (len >= this.minClusterSize_) { 886 | marker.setMap(null); 887 | } 888 | 889 | this.updateIcon(); 890 | return true; 891 | }; 892 | 893 | 894 | /** 895 | * Returns the marker clusterer that the cluster is associated with. 896 | * 897 | * @return {MarkerClusterer} The associated marker clusterer. 898 | */ 899 | Cluster.prototype.getMarkerClusterer = function() { 900 | return this.markerClusterer_; 901 | }; 902 | 903 | 904 | /** 905 | * Returns the bounds of the cluster. 906 | * 907 | * @return {google.maps.LatLngBounds} the cluster bounds. 908 | */ 909 | Cluster.prototype.getBounds = function() { 910 | var bounds = new google.maps.LatLngBounds(this.center_, this.center_); 911 | var markers = this.getMarkers(); 912 | for (var i = 0, marker; marker = markers[i]; i++) { 913 | bounds.extend(marker.getPosition()); 914 | } 915 | return bounds; 916 | }; 917 | 918 | 919 | /** 920 | * Removes the cluster 921 | */ 922 | Cluster.prototype.remove = function() { 923 | this.clusterIcon_.remove(); 924 | this.markers_.length = 0; 925 | delete this.markers_; 926 | }; 927 | 928 | 929 | /** 930 | * Returns the center of the cluster. 931 | * 932 | * @return {number} The cluster center. 933 | */ 934 | Cluster.prototype.getSize = function() { 935 | return this.markers_.length; 936 | }; 937 | 938 | 939 | /** 940 | * Returns the center of the cluster. 941 | * 942 | * @return {Array.} The cluster center. 943 | */ 944 | Cluster.prototype.getMarkers = function() { 945 | return this.markers_; 946 | }; 947 | 948 | 949 | /** 950 | * Returns the center of the cluster. 951 | * 952 | * @return {google.maps.LatLng} The cluster center. 953 | */ 954 | Cluster.prototype.getCenter = function() { 955 | return this.center_; 956 | }; 957 | 958 | 959 | /** 960 | * Calculated the extended bounds of the cluster with the grid. 961 | * 962 | * @private 963 | */ 964 | Cluster.prototype.calculateBounds_ = function() { 965 | var bounds = new google.maps.LatLngBounds(this.center_, this.center_); 966 | this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); 967 | }; 968 | 969 | 970 | /** 971 | * Determines if a marker lies in the clusters bounds. 972 | * 973 | * @param {google.maps.Marker} marker The marker to check. 974 | * @return {boolean} True if the marker lies in the bounds. 975 | */ 976 | Cluster.prototype.isMarkerInClusterBounds = function(marker) { 977 | return this.bounds_.contains(marker.getPosition()); 978 | }; 979 | 980 | 981 | /** 982 | * Returns the map that the cluster is associated with. 983 | * 984 | * @return {google.maps.Map} The map. 985 | */ 986 | Cluster.prototype.getMap = function() { 987 | return this.map_; 988 | }; 989 | 990 | 991 | /** 992 | * Updates the cluster icon 993 | */ 994 | Cluster.prototype.updateIcon = function() { 995 | var zoom = this.map_.getZoom(); 996 | var mz = this.markerClusterer_.getMaxZoom(); 997 | 998 | if (mz && zoom > mz) { 999 | // The zoom is greater than our max zoom so show all the markers in cluster. 1000 | for (var i = 0, marker; marker = this.markers_[i]; i++) { 1001 | marker.setMap(this.map_); 1002 | } 1003 | return; 1004 | } 1005 | 1006 | if (this.markers_.length < this.minClusterSize_) { 1007 | // Min cluster size not yet reached. 1008 | this.clusterIcon_.hide(); 1009 | return; 1010 | } 1011 | 1012 | var numStyles = this.markerClusterer_.getStyles().length; 1013 | var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); 1014 | this.clusterIcon_.setCenter(this.center_); 1015 | this.clusterIcon_.setSums(sums); 1016 | this.clusterIcon_.show(); 1017 | }; 1018 | 1019 | 1020 | /** 1021 | * A cluster icon 1022 | * 1023 | * @param {Cluster} cluster The cluster to be associated with. 1024 | * @param {Object} styles An object that has style properties: 1025 | * 'url': (string) The image url. 1026 | * 'height': (number) The image height. 1027 | * 'width': (number) The image width. 1028 | * 'anchor': (Array) The anchor position of the label text. 1029 | * 'textColor': (string) The text color. 1030 | * 'textSize': (number) The text size. 1031 | * 'backgroundPosition: (string) The background postition x, y. 1032 | * @param {number=} opt_padding Optional padding to apply to the cluster icon. 1033 | * @constructor 1034 | * @extends google.maps.OverlayView 1035 | * @ignore 1036 | */ 1037 | function ClusterIcon(cluster, styles, opt_padding) { 1038 | cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); 1039 | 1040 | this.styles_ = styles; 1041 | this.padding_ = opt_padding || 0; 1042 | this.cluster_ = cluster; 1043 | this.center_ = null; 1044 | this.map_ = cluster.getMap(); 1045 | this.div_ = null; 1046 | this.sums_ = null; 1047 | this.visible_ = false; 1048 | 1049 | this.setMap(this.map_); 1050 | } 1051 | 1052 | 1053 | /** 1054 | * Triggers the clusterclick event and zoom's if the option is set. 1055 | */ 1056 | ClusterIcon.prototype.triggerClusterClick = function() { 1057 | var markerClusterer = this.cluster_.getMarkerClusterer(); 1058 | 1059 | // Trigger the clusterclick event. 1060 | google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_); 1061 | 1062 | if (markerClusterer.isZoomOnClick()) { 1063 | // Zoom into the cluster. 1064 | this.map_.fitBounds(this.cluster_.getBounds()); 1065 | } 1066 | }; 1067 | 1068 | 1069 | /** 1070 | * Adding the cluster icon to the dom. 1071 | * @ignore 1072 | */ 1073 | ClusterIcon.prototype.onAdd = function() { 1074 | this.div_ = document.createElement('DIV'); 1075 | if (this.visible_) { 1076 | var pos = this.getPosFromLatLng_(this.center_); 1077 | this.div_.style.cssText = this.createCss(pos); 1078 | this.div_.innerHTML = this.sums_.text; 1079 | } 1080 | 1081 | var panes = this.getPanes(); 1082 | panes.overlayMouseTarget.appendChild(this.div_); 1083 | 1084 | var that = this; 1085 | google.maps.event.addDomListener(this.div_, 'click', function() { 1086 | that.triggerClusterClick(); 1087 | }); 1088 | }; 1089 | 1090 | 1091 | /** 1092 | * Returns the position to place the div dending on the latlng. 1093 | * 1094 | * @param {google.maps.LatLng} latlng The position in latlng. 1095 | * @return {google.maps.Point} The position in pixels. 1096 | * @private 1097 | */ 1098 | ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) { 1099 | var pos = this.getProjection().fromLatLngToDivPixel(latlng); 1100 | pos.x -= parseInt(this.width_ / 2, 10); 1101 | pos.y -= parseInt(this.height_ / 2, 10); 1102 | return pos; 1103 | }; 1104 | 1105 | 1106 | /** 1107 | * Draw the icon. 1108 | * @ignore 1109 | */ 1110 | ClusterIcon.prototype.draw = function() { 1111 | if (this.visible_) { 1112 | var pos = this.getPosFromLatLng_(this.center_); 1113 | this.div_.style.top = pos.y + 'px'; 1114 | this.div_.style.left = pos.x + 'px'; 1115 | } 1116 | }; 1117 | 1118 | 1119 | /** 1120 | * Hide the icon. 1121 | */ 1122 | ClusterIcon.prototype.hide = function() { 1123 | if (this.div_) { 1124 | this.div_.style.display = 'none'; 1125 | } 1126 | this.visible_ = false; 1127 | }; 1128 | 1129 | 1130 | /** 1131 | * Position and show the icon. 1132 | */ 1133 | ClusterIcon.prototype.show = function() { 1134 | if (this.div_) { 1135 | var pos = this.getPosFromLatLng_(this.center_); 1136 | this.div_.style.cssText = this.createCss(pos); 1137 | this.div_.style.display = ''; 1138 | } 1139 | this.visible_ = true; 1140 | }; 1141 | 1142 | 1143 | /** 1144 | * Remove the icon from the map 1145 | */ 1146 | ClusterIcon.prototype.remove = function() { 1147 | this.setMap(null); 1148 | }; 1149 | 1150 | 1151 | /** 1152 | * Implementation of the onRemove interface. 1153 | * @ignore 1154 | */ 1155 | ClusterIcon.prototype.onRemove = function() { 1156 | if (this.div_ && this.div_.parentNode) { 1157 | this.hide(); 1158 | this.div_.parentNode.removeChild(this.div_); 1159 | this.div_ = null; 1160 | } 1161 | }; 1162 | 1163 | 1164 | /** 1165 | * Set the sums of the icon. 1166 | * 1167 | * @param {Object} sums The sums containing: 1168 | * 'text': (string) The text to display in the icon. 1169 | * 'index': (number) The style index of the icon. 1170 | */ 1171 | ClusterIcon.prototype.setSums = function(sums) { 1172 | this.sums_ = sums; 1173 | this.text_ = sums.text; 1174 | this.index_ = sums.index; 1175 | if (this.div_) { 1176 | this.div_.innerHTML = sums.text; 1177 | } 1178 | 1179 | this.useStyle(); 1180 | }; 1181 | 1182 | 1183 | /** 1184 | * Sets the icon to the the styles. 1185 | */ 1186 | ClusterIcon.prototype.useStyle = function() { 1187 | var index = Math.max(0, this.sums_.index - 1); 1188 | index = Math.min(this.styles_.length - 1, index); 1189 | var style = this.styles_[index]; 1190 | this.url_ = style['url']; 1191 | this.height_ = style['height']; 1192 | this.width_ = style['width']; 1193 | this.textColor_ = style['textColor']; 1194 | this.anchor_ = style['anchor']; 1195 | this.textSize_ = style['textSize']; 1196 | this.backgroundPosition_ = style['backgroundPosition']; 1197 | }; 1198 | 1199 | 1200 | /** 1201 | * Sets the center of the icon. 1202 | * 1203 | * @param {google.maps.LatLng} center The latlng to set as the center. 1204 | */ 1205 | ClusterIcon.prototype.setCenter = function(center) { 1206 | this.center_ = center; 1207 | }; 1208 | 1209 | 1210 | /** 1211 | * Create the css text based on the position of the icon. 1212 | * 1213 | * @param {google.maps.Point} pos The position. 1214 | * @return {string} The css style text. 1215 | */ 1216 | ClusterIcon.prototype.createCss = function(pos) { 1217 | var style = []; 1218 | style.push('background-image:url(' + this.url_ + ');'); 1219 | var backgroundPosition = this.backgroundPosition_ ? this.backgroundPosition_ : '0 0'; 1220 | style.push('background-position:' + backgroundPosition + ';'); 1221 | 1222 | if (typeof this.anchor_ === 'object') { 1223 | if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 && 1224 | this.anchor_[0] < this.height_) { 1225 | style.push('height:' + (this.height_ - this.anchor_[0]) + 1226 | 'px; padding-top:' + this.anchor_[0] + 'px;'); 1227 | } else { 1228 | style.push('height:' + this.height_ + 'px; line-height:' + this.height_ + 1229 | 'px;'); 1230 | } 1231 | if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 && 1232 | this.anchor_[1] < this.width_) { 1233 | style.push('width:' + (this.width_ - this.anchor_[1]) + 1234 | 'px; padding-left:' + this.anchor_[1] + 'px;'); 1235 | } else { 1236 | style.push('width:' + this.width_ + 'px; text-align:center;'); 1237 | } 1238 | } else { 1239 | style.push('height:' + this.height_ + 'px; line-height:' + 1240 | this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;'); 1241 | } 1242 | 1243 | var txtColor = this.textColor_ ? this.textColor_ : 'black'; 1244 | var txtSize = this.textSize_ ? this.textSize_ : 11; 1245 | 1246 | style.push('cursor:pointer; top:' + pos.y + 'px; left:' + 1247 | pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' + 1248 | txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold'); 1249 | return style.join(''); 1250 | }; 1251 | 1252 | 1253 | // Export Symbols for Closure 1254 | // If you are not going to compile with closure then you can remove the 1255 | // code below. 1256 | window['MarkerClusterer'] = MarkerClusterer; 1257 | MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker; 1258 | MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers; 1259 | MarkerClusterer.prototype['clearMarkers'] = 1260 | MarkerClusterer.prototype.clearMarkers; 1261 | MarkerClusterer.prototype['fitMapToMarkers'] = 1262 | MarkerClusterer.prototype.fitMapToMarkers; 1263 | MarkerClusterer.prototype['getCalculator'] = 1264 | MarkerClusterer.prototype.getCalculator; 1265 | MarkerClusterer.prototype['getGridSize'] = 1266 | MarkerClusterer.prototype.getGridSize; 1267 | MarkerClusterer.prototype['getExtendedBounds'] = 1268 | MarkerClusterer.prototype.getExtendedBounds; 1269 | MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap; 1270 | MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers; 1271 | MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom; 1272 | MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles; 1273 | MarkerClusterer.prototype['getTotalClusters'] = 1274 | MarkerClusterer.prototype.getTotalClusters; 1275 | MarkerClusterer.prototype['getTotalMarkers'] = 1276 | MarkerClusterer.prototype.getTotalMarkers; 1277 | MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw; 1278 | MarkerClusterer.prototype['removeMarker'] = 1279 | MarkerClusterer.prototype.removeMarker; 1280 | MarkerClusterer.prototype['removeMarkers'] = 1281 | MarkerClusterer.prototype.removeMarkers; 1282 | MarkerClusterer.prototype['resetViewport'] = 1283 | MarkerClusterer.prototype.resetViewport; 1284 | MarkerClusterer.prototype['repaint'] = 1285 | MarkerClusterer.prototype.repaint; 1286 | MarkerClusterer.prototype['setCalculator'] = 1287 | MarkerClusterer.prototype.setCalculator; 1288 | MarkerClusterer.prototype['setGridSize'] = 1289 | MarkerClusterer.prototype.setGridSize; 1290 | MarkerClusterer.prototype['setMaxZoom'] = 1291 | MarkerClusterer.prototype.setMaxZoom; 1292 | MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd; 1293 | MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw; 1294 | 1295 | Cluster.prototype['getCenter'] = Cluster.prototype.getCenter; 1296 | Cluster.prototype['getSize'] = Cluster.prototype.getSize; 1297 | Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers; 1298 | 1299 | ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd; 1300 | ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw; 1301 | ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove; 1302 | 1303 | Object.keys = Object.keys || function(o) { 1304 | var result = []; 1305 | for(var name in o) { 1306 | if (o.hasOwnProperty(name)) 1307 | result.push(name); 1308 | } 1309 | return result; 1310 | }; -------------------------------------------------------------------------------- /Lesson14-MapWidget/script.js: -------------------------------------------------------------------------------- 1 | (function(window, $) { 2 | 3 | var $mapster = $('#map-canvas').mapster(Mapster.MAP_OPTIONS); 4 | 5 | $mapster.mapster('addMarker', { 6 | lat: 37.791350, 7 | lng: -122.435883 8 | }); 9 | 10 | 11 | }(window, jQuery)); -------------------------------------------------------------------------------- /Lesson14-MapWidget/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } -------------------------------------------------------------------------------- /Lesson15-StreetViewPanorama/List.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var List = (function() { 3 | function List() { 4 | this.items = []; 5 | } 6 | List.prototype = { 7 | add: function(item) { 8 | this.items.push(item); 9 | }, 10 | remove: function(item) { 11 | var indexOf = this.items.indexOf(item); 12 | if (indexOf !== -1) { 13 | this.items.splice(indexOf, 1); 14 | } 15 | }, 16 | find: function(callback, action) { 17 | var callbackReturn, 18 | items = this.items, 19 | length = items.length 20 | matches = [], 21 | i = 0; 22 | 23 | for(; i < length; i++) { 24 | callbackReturn = callback(items[i], i); 25 | if (callbackReturn) { 26 | matches.push(items[i]); 27 | } 28 | } 29 | 30 | if (action) { 31 | action.call(this, matches); 32 | } 33 | 34 | return matches; 35 | } 36 | }; 37 | return List; 38 | }()); 39 | 40 | List.create = function() { 41 | return new List(); 42 | }; 43 | 44 | window.List = List; 45 | 46 | }(window)); -------------------------------------------------------------------------------- /Lesson15-StreetViewPanorama/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google, List) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | this.markers = List.create(); 7 | if (opts.cluster) { 8 | this.markerClusterer = new MarkerClusterer(this.gMap, [], opts.cluster.options); 9 | } 10 | } 11 | Mapster.prototype = { 12 | zoom: function(level) { 13 | if (level) { 14 | this.gMap.setZoom(level); 15 | } else { 16 | return this.gMap.getZoom(); 17 | } 18 | }, 19 | _on: function(opts) { 20 | var self = this; 21 | google.maps.event.addListener(opts.obj, opts.event, function(e) { 22 | opts.callback.call(self, e, opts.obj); 23 | }); 24 | }, 25 | setPano: function(element, opts) { 26 | var panorama = new google.maps.StreetViewPanorama(element, opts); 27 | if (opts.events) { 28 | this._attachEvents(panorama, opts.events); 29 | } 30 | this.gMap.setStreetView(panorama); 31 | }, 32 | addMarker: function(opts) { 33 | var marker, 34 | self = this; 35 | 36 | opts.position = { 37 | lat: opts.lat, 38 | lng: opts.lng 39 | } 40 | marker = this._createMarker(opts); 41 | if (this.markerClusterer) { 42 | this.markerClusterer.addMarker(marker); 43 | } 44 | this._addMarker(marker); 45 | if (opts.events) { 46 | this._attachEvents(marker, opts.events); 47 | } 48 | if (opts.content) { 49 | this._on({ 50 | obj: marker, 51 | event: 'click', 52 | callback: function() { 53 | var infoWindow = new google.maps.InfoWindow({ 54 | content: opts.content 55 | }); 56 | 57 | infoWindow.open(this.gMap, marker); 58 | } 59 | }) 60 | } 61 | return marker; 62 | }, 63 | _attachEvents: function(obj, events) { 64 | var self = this; 65 | events.forEach(function(event) { 66 | self._on({ 67 | obj: obj, 68 | event: event.name, 69 | callback: event.callback 70 | }); 71 | }); 72 | }, 73 | _addMarker: function(marker) { 74 | this.markers.add(marker); 75 | }, 76 | findBy: function(callback) { 77 | this.markers.find(callback); 78 | }, 79 | removeBy: function(callback) { 80 | var self = this; 81 | self.markers.find(callback, function(markers) { 82 | markers.forEach(function(marker) { 83 | if (self.markerClusterer) { 84 | self.markerClusterer.removeMarker(marker); 85 | } else { 86 | marker.setMap(null); 87 | } 88 | }); 89 | }); 90 | }, 91 | _createMarker: function(opts) { 92 | opts.map = this.gMap; 93 | return new google.maps.Marker(opts); 94 | } 95 | }; 96 | return Mapster; 97 | }()); 98 | 99 | Mapster.create = function(element, opts) { 100 | return new Mapster(element, opts); 101 | }; 102 | 103 | window.Mapster = Mapster; 104 | 105 | }(window, google, List)); -------------------------------------------------------------------------------- /Lesson15-StreetViewPanorama/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Lesson15-StreetViewPanorama/jqueryui.mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, Mapster) { 2 | 3 | $.widget("mapster.mapster", { 4 | // default options 5 | options: { }, 6 | 7 | // the constructor 8 | _create: function() { 9 | var element = this.element[0], 10 | options = this.options; 11 | this.map = Mapster.create(element, options); 12 | }, 13 | 14 | // called when created, and later when changing options 15 | _refresh: function() { 16 | 17 | }, 18 | 19 | // Add a marker onto the map 20 | addMarker: function( opts ) { 21 | this.map.addMarker(opts); 22 | }, 23 | 24 | findMarkers: function(callback) { 25 | return this.map.findBy(callback); 26 | }, 27 | 28 | removeMarkers: function(callback) { 29 | this.map.removeBy(callback); 30 | }, 31 | 32 | markers: function() { 33 | return this.map.markers.items; 34 | }, 35 | 36 | setPano: function(selector, opts) { 37 | var elements = $(selector), 38 | self = this; 39 | $.each(elements, function(key, element) { 40 | self.map.setPano(element, opts); 41 | }); 42 | }, 43 | 44 | // events bound via _on are removed automatically 45 | // revert other modifications here 46 | _destroy: function() { 47 | 48 | }, 49 | 50 | // _setOptions is called with a hash of all options that are changing 51 | // always refresh when changing options 52 | _setOptions: function() { 53 | // _super and _superApply handle keeping the right this-context 54 | this._superApply( arguments ); 55 | this._refresh(); 56 | }, 57 | 58 | // _setOption is called for each individual option that is changing 59 | _setOption: function( key, value ) { 60 | this._super( key, value ); 61 | } 62 | }); 63 | 64 | }(window, Mapster)) -------------------------------------------------------------------------------- /Lesson15-StreetViewPanorama/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | zoomControlOptions: { 14 | position: google.maps.ControlPosition.LEFT_BOTTOM, 15 | style: google.maps.ZoomControlStyle.DEFAULT 16 | }, 17 | panControlOptions: { 18 | position: google.maps.ControlPosition.LEFT_BOTTOM 19 | }, 20 | cluster: false 21 | }; 22 | 23 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson15-StreetViewPanorama/script.js: -------------------------------------------------------------------------------- 1 | (function(window, $) { 2 | 3 | var $mapster = $('#map-canvas').mapster(Mapster.MAP_OPTIONS); 4 | 5 | $mapster.mapster('addMarker', { 6 | lat: 37.791350, 7 | lng: -122.435883 8 | }); 9 | 10 | $mapster.mapster('setPano', '#pip-pano', { 11 | position: { 12 | lat: 37.832383, 13 | lng: -122.472971 14 | }, 15 | pov: { 16 | heading: 200, 17 | pitch: 0 18 | }, 19 | events: [{ 20 | name: 'position_changed', 21 | callback: function() { 22 | //alert('changed'); 23 | } 24 | }, { 25 | name: 'links_changed', 26 | callback: function(e, panorama) { 27 | console.log(panorama.getLinks()); 28 | } 29 | }] 30 | }); 31 | 32 | }(window, jQuery)); -------------------------------------------------------------------------------- /Lesson15-StreetViewPanorama/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } 12 | #pip-pano { 13 | height: 200px; 14 | width: 100%; 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | z-index: 1; 19 | } -------------------------------------------------------------------------------- /Lesson16-Geocoding/List.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var List = (function() { 3 | function List() { 4 | this.items = []; 5 | } 6 | List.prototype = { 7 | add: function(item) { 8 | this.items.push(item); 9 | }, 10 | remove: function(item) { 11 | var indexOf = this.items.indexOf(item); 12 | if (indexOf !== -1) { 13 | this.items.splice(indexOf, 1); 14 | } 15 | }, 16 | find: function(callback, action) { 17 | var callbackReturn, 18 | items = this.items, 19 | length = items.length 20 | matches = [], 21 | i = 0; 22 | 23 | for(; i < length; i++) { 24 | callbackReturn = callback(items[i], i); 25 | if (callbackReturn) { 26 | matches.push(items[i]); 27 | } 28 | } 29 | 30 | if (action) { 31 | action.call(this, matches); 32 | } 33 | 34 | return matches; 35 | } 36 | }; 37 | return List; 38 | }()); 39 | 40 | List.create = function() { 41 | return new List(); 42 | }; 43 | 44 | window.List = List; 45 | 46 | }(window)); -------------------------------------------------------------------------------- /Lesson16-Geocoding/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google, List) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | this.markers = List.create(); 7 | if (opts.cluster) { 8 | this.markerClusterer = new MarkerClusterer(this.gMap, [], opts.cluster.options); 9 | } 10 | if (opts.geocoder) { 11 | this.geocoder = new google.maps.Geocoder(); 12 | } 13 | } 14 | Mapster.prototype = { 15 | zoom: function(level) { 16 | if (level) { 17 | this.gMap.setZoom(level); 18 | } else { 19 | return this.gMap.getZoom(); 20 | } 21 | }, 22 | _on: function(opts) { 23 | var self = this; 24 | google.maps.event.addListener(opts.obj, opts.event, function(e) { 25 | opts.callback.call(self, e, opts.obj); 26 | }); 27 | }, 28 | geocode: function(opts) { 29 | this.geocoder.geocode({ 30 | address: opts.address 31 | }, function(results, status) { 32 | if (status === google.maps.GeocoderStatus.OK) { 33 | opts.success.call(this, results, status); 34 | } else { 35 | opts.error.call(this, status); 36 | } 37 | }); 38 | }, 39 | setPano: function(element, opts) { 40 | var panorama = new google.maps.StreetViewPanorama(element, opts); 41 | if (opts.events) { 42 | this._attachEvents(panorama, opts.events); 43 | } 44 | this.gMap.setStreetView(panorama); 45 | }, 46 | addMarker: function(opts) { 47 | var marker, 48 | self = this; 49 | 50 | opts.position = { 51 | lat: opts.lat, 52 | lng: opts.lng 53 | } 54 | marker = this._createMarker(opts); 55 | if (this.markerClusterer) { 56 | this.markerClusterer.addMarker(marker); 57 | } 58 | this._addMarker(marker); 59 | if (opts.events) { 60 | this._attachEvents(marker, opts.events); 61 | } 62 | if (opts.content) { 63 | this._on({ 64 | obj: marker, 65 | event: 'click', 66 | callback: function() { 67 | var infoWindow = new google.maps.InfoWindow({ 68 | content: opts.content 69 | }); 70 | 71 | infoWindow.open(this.gMap, marker); 72 | } 73 | }) 74 | } 75 | return marker; 76 | }, 77 | _attachEvents: function(obj, events) { 78 | var self = this; 79 | events.forEach(function(event) { 80 | self._on({ 81 | obj: obj, 82 | event: event.name, 83 | callback: event.callback 84 | }); 85 | }); 86 | }, 87 | _addMarker: function(marker) { 88 | this.markers.add(marker); 89 | }, 90 | findBy: function(callback) { 91 | this.markers.find(callback); 92 | }, 93 | removeBy: function(callback) { 94 | var self = this; 95 | self.markers.find(callback, function(markers) { 96 | markers.forEach(function(marker) { 97 | if (self.markerClusterer) { 98 | self.markerClusterer.removeMarker(marker); 99 | } else { 100 | marker.setMap(null); 101 | } 102 | }); 103 | }); 104 | }, 105 | _createMarker: function(opts) { 106 | opts.map = this.gMap; 107 | return new google.maps.Marker(opts); 108 | } 109 | }; 110 | return Mapster; 111 | }()); 112 | 113 | Mapster.create = function(element, opts) { 114 | return new Mapster(element, opts); 115 | }; 116 | 117 | window.Mapster = Mapster; 118 | 119 | }(window, google, List)); -------------------------------------------------------------------------------- /Lesson16-Geocoding/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Lesson16-Geocoding/jqueryui.mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, Mapster) { 2 | 3 | $.widget("mapster.mapster", { 4 | // default options 5 | options: { }, 6 | 7 | // the constructor 8 | _create: function() { 9 | var element = this.element[0], 10 | options = this.options; 11 | this.map = Mapster.create(element, options); 12 | }, 13 | 14 | // called when created, and later when changing options 15 | _refresh: function() { 16 | 17 | }, 18 | 19 | // Add a marker onto the map 20 | addMarker: function( opts ) { 21 | var self = this; 22 | if (opts.location) { 23 | this.map.geocode({ 24 | address: opts.location, 25 | success: function(results) { 26 | results.forEach(function(result) { 27 | opts.lat = result.geometry.location.lat(); 28 | opts.lng = result.geometry.location.lng(); 29 | self.map.addMarker(opts); 30 | }); 31 | }, 32 | error: function(status) { 33 | console.error(status) 34 | } 35 | }); 36 | } else { 37 | this.map.addMarker(opts); 38 | } 39 | }, 40 | 41 | findMarkers: function(callback) { 42 | return this.map.findBy(callback); 43 | }, 44 | 45 | removeMarkers: function(callback) { 46 | this.map.removeBy(callback); 47 | }, 48 | 49 | markers: function() { 50 | return this.map.markers.items; 51 | }, 52 | 53 | setPano: function(selector, opts) { 54 | var elements = $(selector), 55 | self = this; 56 | $.each(elements, function(key, element) { 57 | self.map.setPano(element, opts); 58 | }); 59 | }, 60 | 61 | // events bound via _on are removed automatically 62 | // revert other modifications here 63 | _destroy: function() { 64 | 65 | }, 66 | 67 | // _setOptions is called with a hash of all options that are changing 68 | // always refresh when changing options 69 | _setOptions: function() { 70 | // _super and _superApply handle keeping the right this-context 71 | this._superApply( arguments ); 72 | this._refresh(); 73 | }, 74 | 75 | // _setOption is called for each individual option that is changing 76 | _setOption: function( key, value ) { 77 | this._super( key, value ); 78 | } 79 | }); 80 | 81 | }(window, Mapster)) -------------------------------------------------------------------------------- /Lesson16-Geocoding/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | zoomControlOptions: { 14 | position: google.maps.ControlPosition.LEFT_BOTTOM, 15 | style: google.maps.ZoomControlStyle.DEFAULT 16 | }, 17 | panControlOptions: { 18 | position: google.maps.ControlPosition.LEFT_BOTTOM 19 | }, 20 | cluster: false, 21 | geocoder: true 22 | }; 23 | 24 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson16-Geocoding/markerclusterer.js: -------------------------------------------------------------------------------- 1 | // ==ClosureCompiler== 2 | // @compilation_level ADVANCED_OPTIMIZATIONS 3 | // @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3_3.js 4 | // ==/ClosureCompiler== 5 | 6 | /** 7 | * @name MarkerClusterer for Google Maps v3 8 | * @version version 1.0.1 9 | * @author Luke Mahe 10 | * @fileoverview 11 | * The library creates and manages per-zoom-level clusters for large amounts of 12 | * markers. 13 | *
14 | * This is a v3 implementation of the 15 | * v2 MarkerClusterer. 17 | */ 18 | 19 | /** 20 | * Licensed under the Apache License, Version 2.0 (the "License"); 21 | * you may not use this file except in compliance with the License. 22 | * You may obtain a copy of the License at 23 | * 24 | * http://www.apache.org/licenses/LICENSE-2.0 25 | * 26 | * Unless required by applicable law or agreed to in writing, software 27 | * distributed under the License is distributed on an "AS IS" BASIS, 28 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29 | * See the License for the specific language governing permissions and 30 | * limitations under the License. 31 | */ 32 | 33 | 34 | /** 35 | * A Marker Clusterer that clusters markers. 36 | * 37 | * @param {google.maps.Map} map The Google map to attach to. 38 | * @param {Array.=} opt_markers Optional markers to add to 39 | * the cluster. 40 | * @param {Object=} opt_options support the following options: 41 | * 'gridSize': (number) The grid size of a cluster in pixels. 42 | * 'maxZoom': (number) The maximum zoom level that a marker can be part of a 43 | * cluster. 44 | * 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a 45 | * cluster is to zoom into it. 46 | * 'averageCenter': (boolean) Wether the center of each cluster should be 47 | * the average of all markers in the cluster. 48 | * 'minimumClusterSize': (number) The minimum number of markers to be in a 49 | * cluster before the markers are hidden and a count 50 | * is shown. 51 | * 'styles': (object) An object that has style properties: 52 | * 'url': (string) The image url. 53 | * 'height': (number) The image height. 54 | * 'width': (number) The image width. 55 | * 'anchor': (Array) The anchor position of the label text. 56 | * 'textColor': (string) The text color. 57 | * 'textSize': (number) The text size. 58 | * 'backgroundPosition': (string) The position of the backgound x, y. 59 | * @constructor 60 | * @extends google.maps.OverlayView 61 | */ 62 | function MarkerClusterer(map, opt_markers, opt_options) { 63 | // MarkerClusterer implements google.maps.OverlayView interface. We use the 64 | // extend function to extend MarkerClusterer with google.maps.OverlayView 65 | // because it might not always be available when the code is defined so we 66 | // look for it at the last possible moment. If it doesn't exist now then 67 | // there is no point going ahead :) 68 | this.extend(MarkerClusterer, google.maps.OverlayView); 69 | this.map_ = map; 70 | 71 | /** 72 | * @type {Array.} 73 | * @private 74 | */ 75 | this.markers_ = []; 76 | 77 | /** 78 | * @type {Array.} 79 | */ 80 | this.clusters_ = []; 81 | 82 | this.sizes = [53, 56, 66, 78, 90]; 83 | 84 | /** 85 | * @private 86 | */ 87 | this.styles_ = []; 88 | 89 | /** 90 | * @type {boolean} 91 | * @private 92 | */ 93 | this.ready_ = false; 94 | 95 | var options = opt_options || {}; 96 | 97 | /** 98 | * @type {number} 99 | * @private 100 | */ 101 | this.gridSize_ = options['gridSize'] || 60; 102 | 103 | /** 104 | * @private 105 | */ 106 | this.minClusterSize_ = options['minimumClusterSize'] || 2; 107 | 108 | 109 | /** 110 | * @type {?number} 111 | * @private 112 | */ 113 | this.maxZoom_ = options['maxZoom'] || null; 114 | 115 | this.styles_ = options['styles'] || []; 116 | 117 | /** 118 | * @type {string} 119 | * @private 120 | */ 121 | this.imagePath_ = options['imagePath'] || 122 | this.MARKER_CLUSTER_IMAGE_PATH_; 123 | 124 | /** 125 | * @type {string} 126 | * @private 127 | */ 128 | this.imageExtension_ = options['imageExtension'] || 129 | this.MARKER_CLUSTER_IMAGE_EXTENSION_; 130 | 131 | /** 132 | * @type {boolean} 133 | * @private 134 | */ 135 | this.zoomOnClick_ = true; 136 | 137 | if (options['zoomOnClick'] != undefined) { 138 | this.zoomOnClick_ = options['zoomOnClick']; 139 | } 140 | 141 | /** 142 | * @type {boolean} 143 | * @private 144 | */ 145 | this.averageCenter_ = false; 146 | 147 | if (options['averageCenter'] != undefined) { 148 | this.averageCenter_ = options['averageCenter']; 149 | } 150 | 151 | this.setupStyles_(); 152 | 153 | this.setMap(map); 154 | 155 | /** 156 | * @type {number} 157 | * @private 158 | */ 159 | this.prevZoom_ = this.map_.getZoom(); 160 | 161 | // Add the map event listeners 162 | var that = this; 163 | google.maps.event.addListener(this.map_, 'zoom_changed', function() { 164 | // Determines map type and prevent illegal zoom levels 165 | var zoom = that.map_.getZoom(); 166 | var minZoom = that.map_.minZoom || 0; 167 | var maxZoom = Math.min(that.map_.maxZoom || 100, 168 | that.map_.mapTypes[that.map_.getMapTypeId()].maxZoom); 169 | zoom = Math.min(Math.max(zoom,minZoom),maxZoom); 170 | 171 | if (that.prevZoom_ != zoom) { 172 | that.prevZoom_ = zoom; 173 | that.resetViewport(); 174 | } 175 | }); 176 | 177 | google.maps.event.addListener(this.map_, 'idle', function() { 178 | that.redraw(); 179 | }); 180 | 181 | // Finally, add the markers 182 | if (opt_markers && (opt_markers.length || Object.keys(opt_markers).length)) { 183 | this.addMarkers(opt_markers, false); 184 | } 185 | } 186 | 187 | 188 | /** 189 | * The marker cluster image path. 190 | * 191 | * @type {string} 192 | * @private 193 | */ 194 | MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ = 195 | 'http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/' + 196 | 'images/m'; 197 | 198 | 199 | /** 200 | * The marker cluster image path. 201 | * 202 | * @type {string} 203 | * @private 204 | */ 205 | MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png'; 206 | 207 | 208 | /** 209 | * Extends a objects prototype by anothers. 210 | * 211 | * @param {Object} obj1 The object to be extended. 212 | * @param {Object} obj2 The object to extend with. 213 | * @return {Object} The new extended object. 214 | * @ignore 215 | */ 216 | MarkerClusterer.prototype.extend = function(obj1, obj2) { 217 | return (function(object) { 218 | for (var property in object.prototype) { 219 | this.prototype[property] = object.prototype[property]; 220 | } 221 | return this; 222 | }).apply(obj1, [obj2]); 223 | }; 224 | 225 | 226 | /** 227 | * Implementaion of the interface method. 228 | * @ignore 229 | */ 230 | MarkerClusterer.prototype.onAdd = function() { 231 | this.setReady_(true); 232 | }; 233 | 234 | /** 235 | * Implementaion of the interface method. 236 | * @ignore 237 | */ 238 | MarkerClusterer.prototype.draw = function() {}; 239 | 240 | /** 241 | * Sets up the styles object. 242 | * 243 | * @private 244 | */ 245 | MarkerClusterer.prototype.setupStyles_ = function() { 246 | if (this.styles_.length) { 247 | return; 248 | } 249 | 250 | for (var i = 0, size; size = this.sizes[i]; i++) { 251 | this.styles_.push({ 252 | url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_, 253 | height: size, 254 | width: size 255 | }); 256 | } 257 | }; 258 | 259 | /** 260 | * Fit the map to the bounds of the markers in the clusterer. 261 | */ 262 | MarkerClusterer.prototype.fitMapToMarkers = function() { 263 | var markers = this.getMarkers(); 264 | var bounds = new google.maps.LatLngBounds(); 265 | for (var i = 0, marker; marker = markers[i]; i++) { 266 | bounds.extend(marker.getPosition()); 267 | } 268 | 269 | this.map_.fitBounds(bounds); 270 | }; 271 | 272 | 273 | /** 274 | * Sets the styles. 275 | * 276 | * @param {Object} styles The style to set. 277 | */ 278 | MarkerClusterer.prototype.setStyles = function(styles) { 279 | this.styles_ = styles; 280 | }; 281 | 282 | 283 | /** 284 | * Gets the styles. 285 | * 286 | * @return {Object} The styles object. 287 | */ 288 | MarkerClusterer.prototype.getStyles = function() { 289 | return this.styles_; 290 | }; 291 | 292 | 293 | /** 294 | * Whether zoom on click is set. 295 | * 296 | * @return {boolean} True if zoomOnClick_ is set. 297 | */ 298 | MarkerClusterer.prototype.isZoomOnClick = function() { 299 | return this.zoomOnClick_; 300 | }; 301 | 302 | /** 303 | * Whether average center is set. 304 | * 305 | * @return {boolean} True if averageCenter_ is set. 306 | */ 307 | MarkerClusterer.prototype.isAverageCenter = function() { 308 | return this.averageCenter_; 309 | }; 310 | 311 | 312 | /** 313 | * Returns the array of markers in the clusterer. 314 | * 315 | * @return {Array.} The markers. 316 | */ 317 | MarkerClusterer.prototype.getMarkers = function() { 318 | return this.markers_; 319 | }; 320 | 321 | 322 | /** 323 | * Returns the number of markers in the clusterer 324 | * 325 | * @return {Number} The number of markers. 326 | */ 327 | MarkerClusterer.prototype.getTotalMarkers = function() { 328 | return this.markers_.length; 329 | }; 330 | 331 | 332 | /** 333 | * Sets the max zoom for the clusterer. 334 | * 335 | * @param {number} maxZoom The max zoom level. 336 | */ 337 | MarkerClusterer.prototype.setMaxZoom = function(maxZoom) { 338 | this.maxZoom_ = maxZoom; 339 | }; 340 | 341 | 342 | /** 343 | * Gets the max zoom for the clusterer. 344 | * 345 | * @return {number} The max zoom level. 346 | */ 347 | MarkerClusterer.prototype.getMaxZoom = function() { 348 | return this.maxZoom_; 349 | }; 350 | 351 | 352 | /** 353 | * The function for calculating the cluster icon image. 354 | * 355 | * @param {Array.} markers The markers in the clusterer. 356 | * @param {number} numStyles The number of styles available. 357 | * @return {Object} A object properties: 'text' (string) and 'index' (number). 358 | * @private 359 | */ 360 | MarkerClusterer.prototype.calculator_ = function(markers, numStyles) { 361 | var index = 0; 362 | var count = markers.length; 363 | var dv = count; 364 | while (dv !== 0) { 365 | dv = parseInt(dv / 10, 10); 366 | index++; 367 | } 368 | 369 | index = Math.min(index, numStyles); 370 | return { 371 | text: count, 372 | index: index 373 | }; 374 | }; 375 | 376 | 377 | /** 378 | * Set the calculator function. 379 | * 380 | * @param {function(Array, number)} calculator The function to set as the 381 | * calculator. The function should return a object properties: 382 | * 'text' (string) and 'index' (number). 383 | * 384 | */ 385 | MarkerClusterer.prototype.setCalculator = function(calculator) { 386 | this.calculator_ = calculator; 387 | }; 388 | 389 | 390 | /** 391 | * Get the calculator function. 392 | * 393 | * @return {function(Array, number)} the calculator function. 394 | */ 395 | MarkerClusterer.prototype.getCalculator = function() { 396 | return this.calculator_; 397 | }; 398 | 399 | 400 | /** 401 | * Add an array of markers to the clusterer. 402 | * 403 | * @param {Array.} markers The markers to add. 404 | * @param {boolean=} opt_nodraw Whether to redraw the clusters. 405 | */ 406 | MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) { 407 | if (markers.length) { 408 | for (var i = 0, marker; marker = markers[i]; i++) { 409 | this.pushMarkerTo_(marker); 410 | } 411 | } else if (Object.keys(markers).length) { 412 | for (var marker in markers) { 413 | this.pushMarkerTo_(markers[marker]); 414 | } 415 | } 416 | if (!opt_nodraw) { 417 | this.redraw(); 418 | } 419 | }; 420 | 421 | 422 | /** 423 | * Pushes a marker to the clusterer. 424 | * 425 | * @param {google.maps.Marker} marker The marker to add. 426 | * @private 427 | */ 428 | MarkerClusterer.prototype.pushMarkerTo_ = function(marker) { 429 | marker.isAdded = false; 430 | if (marker['draggable']) { 431 | // If the marker is draggable add a listener so we update the clusters on 432 | // the drag end. 433 | var that = this; 434 | google.maps.event.addListener(marker, 'dragend', function() { 435 | marker.isAdded = false; 436 | that.repaint(); 437 | }); 438 | } 439 | this.markers_.push(marker); 440 | }; 441 | 442 | 443 | /** 444 | * Adds a marker to the clusterer and redraws if needed. 445 | * 446 | * @param {google.maps.Marker} marker The marker to add. 447 | * @param {boolean=} opt_nodraw Whether to redraw the clusters. 448 | */ 449 | MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) { 450 | this.pushMarkerTo_(marker); 451 | if (!opt_nodraw) { 452 | this.redraw(); 453 | } 454 | }; 455 | 456 | 457 | /** 458 | * Removes a marker and returns true if removed, false if not 459 | * 460 | * @param {google.maps.Marker} marker The marker to remove 461 | * @return {boolean} Whether the marker was removed or not 462 | * @private 463 | */ 464 | MarkerClusterer.prototype.removeMarker_ = function(marker) { 465 | var index = -1; 466 | if (this.markers_.indexOf) { 467 | index = this.markers_.indexOf(marker); 468 | } else { 469 | for (var i = 0, m; m = this.markers_[i]; i++) { 470 | if (m == marker) { 471 | index = i; 472 | break; 473 | } 474 | } 475 | } 476 | 477 | if (index == -1) { 478 | // Marker is not in our list of markers. 479 | return false; 480 | } 481 | 482 | marker.setMap(null); 483 | 484 | this.markers_.splice(index, 1); 485 | 486 | return true; 487 | }; 488 | 489 | 490 | /** 491 | * Remove a marker from the cluster. 492 | * 493 | * @param {google.maps.Marker} marker The marker to remove. 494 | * @param {boolean=} opt_nodraw Optional boolean to force no redraw. 495 | * @return {boolean} True if the marker was removed. 496 | */ 497 | MarkerClusterer.prototype.removeMarker = function(marker, opt_nodraw) { 498 | var removed = this.removeMarker_(marker); 499 | 500 | if (!opt_nodraw && removed) { 501 | this.resetViewport(); 502 | this.redraw(); 503 | return true; 504 | } else { 505 | return false; 506 | } 507 | }; 508 | 509 | 510 | /** 511 | * Removes an array of markers from the cluster. 512 | * 513 | * @param {Array.} markers The markers to remove. 514 | * @param {boolean=} opt_nodraw Optional boolean to force no redraw. 515 | */ 516 | MarkerClusterer.prototype.removeMarkers = function(markers, opt_nodraw) { 517 | var removed = false; 518 | 519 | for (var i = 0, marker; marker = markers[i]; i++) { 520 | var r = this.removeMarker_(marker); 521 | removed = removed || r; 522 | } 523 | 524 | if (!opt_nodraw && removed) { 525 | this.resetViewport(); 526 | this.redraw(); 527 | return true; 528 | } 529 | }; 530 | 531 | 532 | /** 533 | * Sets the clusterer's ready state. 534 | * 535 | * @param {boolean} ready The state. 536 | * @private 537 | */ 538 | MarkerClusterer.prototype.setReady_ = function(ready) { 539 | if (!this.ready_) { 540 | this.ready_ = ready; 541 | this.createClusters_(); 542 | } 543 | }; 544 | 545 | 546 | /** 547 | * Returns the number of clusters in the clusterer. 548 | * 549 | * @return {number} The number of clusters. 550 | */ 551 | MarkerClusterer.prototype.getTotalClusters = function() { 552 | return this.clusters_.length; 553 | }; 554 | 555 | 556 | /** 557 | * Returns the google map that the clusterer is associated with. 558 | * 559 | * @return {google.maps.Map} The map. 560 | */ 561 | MarkerClusterer.prototype.getMap = function() { 562 | return this.map_; 563 | }; 564 | 565 | 566 | /** 567 | * Sets the google map that the clusterer is associated with. 568 | * 569 | * @param {google.maps.Map} map The map. 570 | */ 571 | MarkerClusterer.prototype.setMap = function(map) { 572 | this.map_ = map; 573 | }; 574 | 575 | 576 | /** 577 | * Returns the size of the grid. 578 | * 579 | * @return {number} The grid size. 580 | */ 581 | MarkerClusterer.prototype.getGridSize = function() { 582 | return this.gridSize_; 583 | }; 584 | 585 | 586 | /** 587 | * Sets the size of the grid. 588 | * 589 | * @param {number} size The grid size. 590 | */ 591 | MarkerClusterer.prototype.setGridSize = function(size) { 592 | this.gridSize_ = size; 593 | }; 594 | 595 | 596 | /** 597 | * Returns the min cluster size. 598 | * 599 | * @return {number} The grid size. 600 | */ 601 | MarkerClusterer.prototype.getMinClusterSize = function() { 602 | return this.minClusterSize_; 603 | }; 604 | 605 | /** 606 | * Sets the min cluster size. 607 | * 608 | * @param {number} size The grid size. 609 | */ 610 | MarkerClusterer.prototype.setMinClusterSize = function(size) { 611 | this.minClusterSize_ = size; 612 | }; 613 | 614 | 615 | /** 616 | * Extends a bounds object by the grid size. 617 | * 618 | * @param {google.maps.LatLngBounds} bounds The bounds to extend. 619 | * @return {google.maps.LatLngBounds} The extended bounds. 620 | */ 621 | MarkerClusterer.prototype.getExtendedBounds = function(bounds) { 622 | var projection = this.getProjection(); 623 | 624 | // Turn the bounds into latlng. 625 | var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), 626 | bounds.getNorthEast().lng()); 627 | var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), 628 | bounds.getSouthWest().lng()); 629 | 630 | // Convert the points to pixels and the extend out by the grid size. 631 | var trPix = projection.fromLatLngToDivPixel(tr); 632 | trPix.x += this.gridSize_; 633 | trPix.y -= this.gridSize_; 634 | 635 | var blPix = projection.fromLatLngToDivPixel(bl); 636 | blPix.x -= this.gridSize_; 637 | blPix.y += this.gridSize_; 638 | 639 | // Convert the pixel points back to LatLng 640 | var ne = projection.fromDivPixelToLatLng(trPix); 641 | var sw = projection.fromDivPixelToLatLng(blPix); 642 | 643 | // Extend the bounds to contain the new bounds. 644 | bounds.extend(ne); 645 | bounds.extend(sw); 646 | 647 | return bounds; 648 | }; 649 | 650 | 651 | /** 652 | * Determins if a marker is contained in a bounds. 653 | * 654 | * @param {google.maps.Marker} marker The marker to check. 655 | * @param {google.maps.LatLngBounds} bounds The bounds to check against. 656 | * @return {boolean} True if the marker is in the bounds. 657 | * @private 658 | */ 659 | MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) { 660 | return bounds.contains(marker.getPosition()); 661 | }; 662 | 663 | 664 | /** 665 | * Clears all clusters and markers from the clusterer. 666 | */ 667 | MarkerClusterer.prototype.clearMarkers = function() { 668 | this.resetViewport(true); 669 | 670 | // Set the markers a empty array. 671 | this.markers_ = []; 672 | }; 673 | 674 | 675 | /** 676 | * Clears all existing clusters and recreates them. 677 | * @param {boolean} opt_hide To also hide the marker. 678 | */ 679 | MarkerClusterer.prototype.resetViewport = function(opt_hide) { 680 | // Remove all the clusters 681 | for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { 682 | cluster.remove(); 683 | } 684 | 685 | // Reset the markers to not be added and to be invisible. 686 | for (var i = 0, marker; marker = this.markers_[i]; i++) { 687 | marker.isAdded = false; 688 | if (opt_hide) { 689 | marker.setMap(null); 690 | } 691 | } 692 | 693 | this.clusters_ = []; 694 | }; 695 | 696 | /** 697 | * 698 | */ 699 | MarkerClusterer.prototype.repaint = function() { 700 | var oldClusters = this.clusters_.slice(); 701 | this.clusters_.length = 0; 702 | this.resetViewport(); 703 | this.redraw(); 704 | 705 | // Remove the old clusters. 706 | // Do it in a timeout so the other clusters have been drawn first. 707 | window.setTimeout(function() { 708 | for (var i = 0, cluster; cluster = oldClusters[i]; i++) { 709 | cluster.remove(); 710 | } 711 | }, 0); 712 | }; 713 | 714 | 715 | /** 716 | * Redraws the clusters. 717 | */ 718 | MarkerClusterer.prototype.redraw = function() { 719 | this.createClusters_(); 720 | }; 721 | 722 | 723 | /** 724 | * Calculates the distance between two latlng locations in km. 725 | * @see http://www.movable-type.co.uk/scripts/latlong.html 726 | * 727 | * @param {google.maps.LatLng} p1 The first lat lng point. 728 | * @param {google.maps.LatLng} p2 The second lat lng point. 729 | * @return {number} The distance between the two points in km. 730 | * @private 731 | */ 732 | MarkerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) { 733 | if (!p1 || !p2) { 734 | return 0; 735 | } 736 | 737 | var R = 6371; // Radius of the Earth in km 738 | var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; 739 | var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; 740 | var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + 741 | Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * 742 | Math.sin(dLon / 2) * Math.sin(dLon / 2); 743 | var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 744 | var d = R * c; 745 | return d; 746 | }; 747 | 748 | 749 | /** 750 | * Add a marker to a cluster, or creates a new cluster. 751 | * 752 | * @param {google.maps.Marker} marker The marker to add. 753 | * @private 754 | */ 755 | MarkerClusterer.prototype.addToClosestCluster_ = function(marker) { 756 | var distance = 40000; // Some large number 757 | var clusterToAddTo = null; 758 | var pos = marker.getPosition(); 759 | for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { 760 | var center = cluster.getCenter(); 761 | if (center) { 762 | var d = this.distanceBetweenPoints_(center, marker.getPosition()); 763 | if (d < distance) { 764 | distance = d; 765 | clusterToAddTo = cluster; 766 | } 767 | } 768 | } 769 | 770 | if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) { 771 | clusterToAddTo.addMarker(marker); 772 | } else { 773 | var cluster = new Cluster(this); 774 | cluster.addMarker(marker); 775 | this.clusters_.push(cluster); 776 | } 777 | }; 778 | 779 | 780 | /** 781 | * Creates the clusters. 782 | * 783 | * @private 784 | */ 785 | MarkerClusterer.prototype.createClusters_ = function() { 786 | if (!this.ready_) { 787 | return; 788 | } 789 | 790 | // Get our current map view bounds. 791 | // Create a new bounds object so we don't affect the map. 792 | var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(), 793 | this.map_.getBounds().getNorthEast()); 794 | var bounds = this.getExtendedBounds(mapBounds); 795 | 796 | for (var i = 0, marker; marker = this.markers_[i]; i++) { 797 | if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) { 798 | this.addToClosestCluster_(marker); 799 | } 800 | } 801 | }; 802 | 803 | 804 | /** 805 | * A cluster that contains markers. 806 | * 807 | * @param {MarkerClusterer} markerClusterer The markerclusterer that this 808 | * cluster is associated with. 809 | * @constructor 810 | * @ignore 811 | */ 812 | function Cluster(markerClusterer) { 813 | this.markerClusterer_ = markerClusterer; 814 | this.map_ = markerClusterer.getMap(); 815 | this.gridSize_ = markerClusterer.getGridSize(); 816 | this.minClusterSize_ = markerClusterer.getMinClusterSize(); 817 | this.averageCenter_ = markerClusterer.isAverageCenter(); 818 | this.center_ = null; 819 | this.markers_ = []; 820 | this.bounds_ = null; 821 | this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(), 822 | markerClusterer.getGridSize()); 823 | } 824 | 825 | /** 826 | * Determins if a marker is already added to the cluster. 827 | * 828 | * @param {google.maps.Marker} marker The marker to check. 829 | * @return {boolean} True if the marker is already added. 830 | */ 831 | Cluster.prototype.isMarkerAlreadyAdded = function(marker) { 832 | if (this.markers_.indexOf) { 833 | return this.markers_.indexOf(marker) != -1; 834 | } else { 835 | for (var i = 0, m; m = this.markers_[i]; i++) { 836 | if (m == marker) { 837 | return true; 838 | } 839 | } 840 | } 841 | return false; 842 | }; 843 | 844 | 845 | /** 846 | * Add a marker the cluster. 847 | * 848 | * @param {google.maps.Marker} marker The marker to add. 849 | * @return {boolean} True if the marker was added. 850 | */ 851 | Cluster.prototype.addMarker = function(marker) { 852 | if (this.isMarkerAlreadyAdded(marker)) { 853 | return false; 854 | } 855 | 856 | if (!this.center_) { 857 | this.center_ = marker.getPosition(); 858 | this.calculateBounds_(); 859 | } else { 860 | if (this.averageCenter_) { 861 | var l = this.markers_.length + 1; 862 | var lat = (this.center_.lat() * (l-1) + marker.getPosition().lat()) / l; 863 | var lng = (this.center_.lng() * (l-1) + marker.getPosition().lng()) / l; 864 | this.center_ = new google.maps.LatLng(lat, lng); 865 | this.calculateBounds_(); 866 | } 867 | } 868 | 869 | marker.isAdded = true; 870 | this.markers_.push(marker); 871 | 872 | var len = this.markers_.length; 873 | if (len < this.minClusterSize_ && marker.getMap() != this.map_) { 874 | // Min cluster size not reached so show the marker. 875 | marker.setMap(this.map_); 876 | } 877 | 878 | if (len == this.minClusterSize_) { 879 | // Hide the markers that were showing. 880 | for (var i = 0; i < len; i++) { 881 | this.markers_[i].setMap(null); 882 | } 883 | } 884 | 885 | if (len >= this.minClusterSize_) { 886 | marker.setMap(null); 887 | } 888 | 889 | this.updateIcon(); 890 | return true; 891 | }; 892 | 893 | 894 | /** 895 | * Returns the marker clusterer that the cluster is associated with. 896 | * 897 | * @return {MarkerClusterer} The associated marker clusterer. 898 | */ 899 | Cluster.prototype.getMarkerClusterer = function() { 900 | return this.markerClusterer_; 901 | }; 902 | 903 | 904 | /** 905 | * Returns the bounds of the cluster. 906 | * 907 | * @return {google.maps.LatLngBounds} the cluster bounds. 908 | */ 909 | Cluster.prototype.getBounds = function() { 910 | var bounds = new google.maps.LatLngBounds(this.center_, this.center_); 911 | var markers = this.getMarkers(); 912 | for (var i = 0, marker; marker = markers[i]; i++) { 913 | bounds.extend(marker.getPosition()); 914 | } 915 | return bounds; 916 | }; 917 | 918 | 919 | /** 920 | * Removes the cluster 921 | */ 922 | Cluster.prototype.remove = function() { 923 | this.clusterIcon_.remove(); 924 | this.markers_.length = 0; 925 | delete this.markers_; 926 | }; 927 | 928 | 929 | /** 930 | * Returns the center of the cluster. 931 | * 932 | * @return {number} The cluster center. 933 | */ 934 | Cluster.prototype.getSize = function() { 935 | return this.markers_.length; 936 | }; 937 | 938 | 939 | /** 940 | * Returns the center of the cluster. 941 | * 942 | * @return {Array.} The cluster center. 943 | */ 944 | Cluster.prototype.getMarkers = function() { 945 | return this.markers_; 946 | }; 947 | 948 | 949 | /** 950 | * Returns the center of the cluster. 951 | * 952 | * @return {google.maps.LatLng} The cluster center. 953 | */ 954 | Cluster.prototype.getCenter = function() { 955 | return this.center_; 956 | }; 957 | 958 | 959 | /** 960 | * Calculated the extended bounds of the cluster with the grid. 961 | * 962 | * @private 963 | */ 964 | Cluster.prototype.calculateBounds_ = function() { 965 | var bounds = new google.maps.LatLngBounds(this.center_, this.center_); 966 | this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); 967 | }; 968 | 969 | 970 | /** 971 | * Determines if a marker lies in the clusters bounds. 972 | * 973 | * @param {google.maps.Marker} marker The marker to check. 974 | * @return {boolean} True if the marker lies in the bounds. 975 | */ 976 | Cluster.prototype.isMarkerInClusterBounds = function(marker) { 977 | return this.bounds_.contains(marker.getPosition()); 978 | }; 979 | 980 | 981 | /** 982 | * Returns the map that the cluster is associated with. 983 | * 984 | * @return {google.maps.Map} The map. 985 | */ 986 | Cluster.prototype.getMap = function() { 987 | return this.map_; 988 | }; 989 | 990 | 991 | /** 992 | * Updates the cluster icon 993 | */ 994 | Cluster.prototype.updateIcon = function() { 995 | var zoom = this.map_.getZoom(); 996 | var mz = this.markerClusterer_.getMaxZoom(); 997 | 998 | if (mz && zoom > mz) { 999 | // The zoom is greater than our max zoom so show all the markers in cluster. 1000 | for (var i = 0, marker; marker = this.markers_[i]; i++) { 1001 | marker.setMap(this.map_); 1002 | } 1003 | return; 1004 | } 1005 | 1006 | if (this.markers_.length < this.minClusterSize_) { 1007 | // Min cluster size not yet reached. 1008 | this.clusterIcon_.hide(); 1009 | return; 1010 | } 1011 | 1012 | var numStyles = this.markerClusterer_.getStyles().length; 1013 | var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); 1014 | this.clusterIcon_.setCenter(this.center_); 1015 | this.clusterIcon_.setSums(sums); 1016 | this.clusterIcon_.show(); 1017 | }; 1018 | 1019 | 1020 | /** 1021 | * A cluster icon 1022 | * 1023 | * @param {Cluster} cluster The cluster to be associated with. 1024 | * @param {Object} styles An object that has style properties: 1025 | * 'url': (string) The image url. 1026 | * 'height': (number) The image height. 1027 | * 'width': (number) The image width. 1028 | * 'anchor': (Array) The anchor position of the label text. 1029 | * 'textColor': (string) The text color. 1030 | * 'textSize': (number) The text size. 1031 | * 'backgroundPosition: (string) The background postition x, y. 1032 | * @param {number=} opt_padding Optional padding to apply to the cluster icon. 1033 | * @constructor 1034 | * @extends google.maps.OverlayView 1035 | * @ignore 1036 | */ 1037 | function ClusterIcon(cluster, styles, opt_padding) { 1038 | cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); 1039 | 1040 | this.styles_ = styles; 1041 | this.padding_ = opt_padding || 0; 1042 | this.cluster_ = cluster; 1043 | this.center_ = null; 1044 | this.map_ = cluster.getMap(); 1045 | this.div_ = null; 1046 | this.sums_ = null; 1047 | this.visible_ = false; 1048 | 1049 | this.setMap(this.map_); 1050 | } 1051 | 1052 | 1053 | /** 1054 | * Triggers the clusterclick event and zoom's if the option is set. 1055 | */ 1056 | ClusterIcon.prototype.triggerClusterClick = function() { 1057 | var markerClusterer = this.cluster_.getMarkerClusterer(); 1058 | 1059 | // Trigger the clusterclick event. 1060 | google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_); 1061 | 1062 | if (markerClusterer.isZoomOnClick()) { 1063 | // Zoom into the cluster. 1064 | this.map_.fitBounds(this.cluster_.getBounds()); 1065 | } 1066 | }; 1067 | 1068 | 1069 | /** 1070 | * Adding the cluster icon to the dom. 1071 | * @ignore 1072 | */ 1073 | ClusterIcon.prototype.onAdd = function() { 1074 | this.div_ = document.createElement('DIV'); 1075 | if (this.visible_) { 1076 | var pos = this.getPosFromLatLng_(this.center_); 1077 | this.div_.style.cssText = this.createCss(pos); 1078 | this.div_.innerHTML = this.sums_.text; 1079 | } 1080 | 1081 | var panes = this.getPanes(); 1082 | panes.overlayMouseTarget.appendChild(this.div_); 1083 | 1084 | var that = this; 1085 | google.maps.event.addDomListener(this.div_, 'click', function() { 1086 | that.triggerClusterClick(); 1087 | }); 1088 | }; 1089 | 1090 | 1091 | /** 1092 | * Returns the position to place the div dending on the latlng. 1093 | * 1094 | * @param {google.maps.LatLng} latlng The position in latlng. 1095 | * @return {google.maps.Point} The position in pixels. 1096 | * @private 1097 | */ 1098 | ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) { 1099 | var pos = this.getProjection().fromLatLngToDivPixel(latlng); 1100 | pos.x -= parseInt(this.width_ / 2, 10); 1101 | pos.y -= parseInt(this.height_ / 2, 10); 1102 | return pos; 1103 | }; 1104 | 1105 | 1106 | /** 1107 | * Draw the icon. 1108 | * @ignore 1109 | */ 1110 | ClusterIcon.prototype.draw = function() { 1111 | if (this.visible_) { 1112 | var pos = this.getPosFromLatLng_(this.center_); 1113 | this.div_.style.top = pos.y + 'px'; 1114 | this.div_.style.left = pos.x + 'px'; 1115 | } 1116 | }; 1117 | 1118 | 1119 | /** 1120 | * Hide the icon. 1121 | */ 1122 | ClusterIcon.prototype.hide = function() { 1123 | if (this.div_) { 1124 | this.div_.style.display = 'none'; 1125 | } 1126 | this.visible_ = false; 1127 | }; 1128 | 1129 | 1130 | /** 1131 | * Position and show the icon. 1132 | */ 1133 | ClusterIcon.prototype.show = function() { 1134 | if (this.div_) { 1135 | var pos = this.getPosFromLatLng_(this.center_); 1136 | this.div_.style.cssText = this.createCss(pos); 1137 | this.div_.style.display = ''; 1138 | } 1139 | this.visible_ = true; 1140 | }; 1141 | 1142 | 1143 | /** 1144 | * Remove the icon from the map 1145 | */ 1146 | ClusterIcon.prototype.remove = function() { 1147 | this.setMap(null); 1148 | }; 1149 | 1150 | 1151 | /** 1152 | * Implementation of the onRemove interface. 1153 | * @ignore 1154 | */ 1155 | ClusterIcon.prototype.onRemove = function() { 1156 | if (this.div_ && this.div_.parentNode) { 1157 | this.hide(); 1158 | this.div_.parentNode.removeChild(this.div_); 1159 | this.div_ = null; 1160 | } 1161 | }; 1162 | 1163 | 1164 | /** 1165 | * Set the sums of the icon. 1166 | * 1167 | * @param {Object} sums The sums containing: 1168 | * 'text': (string) The text to display in the icon. 1169 | * 'index': (number) The style index of the icon. 1170 | */ 1171 | ClusterIcon.prototype.setSums = function(sums) { 1172 | this.sums_ = sums; 1173 | this.text_ = sums.text; 1174 | this.index_ = sums.index; 1175 | if (this.div_) { 1176 | this.div_.innerHTML = sums.text; 1177 | } 1178 | 1179 | this.useStyle(); 1180 | }; 1181 | 1182 | 1183 | /** 1184 | * Sets the icon to the the styles. 1185 | */ 1186 | ClusterIcon.prototype.useStyle = function() { 1187 | var index = Math.max(0, this.sums_.index - 1); 1188 | index = Math.min(this.styles_.length - 1, index); 1189 | var style = this.styles_[index]; 1190 | this.url_ = style['url']; 1191 | this.height_ = style['height']; 1192 | this.width_ = style['width']; 1193 | this.textColor_ = style['textColor']; 1194 | this.anchor_ = style['anchor']; 1195 | this.textSize_ = style['textSize']; 1196 | this.backgroundPosition_ = style['backgroundPosition']; 1197 | }; 1198 | 1199 | 1200 | /** 1201 | * Sets the center of the icon. 1202 | * 1203 | * @param {google.maps.LatLng} center The latlng to set as the center. 1204 | */ 1205 | ClusterIcon.prototype.setCenter = function(center) { 1206 | this.center_ = center; 1207 | }; 1208 | 1209 | 1210 | /** 1211 | * Create the css text based on the position of the icon. 1212 | * 1213 | * @param {google.maps.Point} pos The position. 1214 | * @return {string} The css style text. 1215 | */ 1216 | ClusterIcon.prototype.createCss = function(pos) { 1217 | var style = []; 1218 | style.push('background-image:url(' + this.url_ + ');'); 1219 | var backgroundPosition = this.backgroundPosition_ ? this.backgroundPosition_ : '0 0'; 1220 | style.push('background-position:' + backgroundPosition + ';'); 1221 | 1222 | if (typeof this.anchor_ === 'object') { 1223 | if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 && 1224 | this.anchor_[0] < this.height_) { 1225 | style.push('height:' + (this.height_ - this.anchor_[0]) + 1226 | 'px; padding-top:' + this.anchor_[0] + 'px;'); 1227 | } else { 1228 | style.push('height:' + this.height_ + 'px; line-height:' + this.height_ + 1229 | 'px;'); 1230 | } 1231 | if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 && 1232 | this.anchor_[1] < this.width_) { 1233 | style.push('width:' + (this.width_ - this.anchor_[1]) + 1234 | 'px; padding-left:' + this.anchor_[1] + 'px;'); 1235 | } else { 1236 | style.push('width:' + this.width_ + 'px; text-align:center;'); 1237 | } 1238 | } else { 1239 | style.push('height:' + this.height_ + 'px; line-height:' + 1240 | this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;'); 1241 | } 1242 | 1243 | var txtColor = this.textColor_ ? this.textColor_ : 'black'; 1244 | var txtSize = this.textSize_ ? this.textSize_ : 11; 1245 | 1246 | style.push('cursor:pointer; top:' + pos.y + 'px; left:' + 1247 | pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' + 1248 | txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold'); 1249 | return style.join(''); 1250 | }; 1251 | 1252 | 1253 | // Export Symbols for Closure 1254 | // If you are not going to compile with closure then you can remove the 1255 | // code below. 1256 | window['MarkerClusterer'] = MarkerClusterer; 1257 | MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker; 1258 | MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers; 1259 | MarkerClusterer.prototype['clearMarkers'] = 1260 | MarkerClusterer.prototype.clearMarkers; 1261 | MarkerClusterer.prototype['fitMapToMarkers'] = 1262 | MarkerClusterer.prototype.fitMapToMarkers; 1263 | MarkerClusterer.prototype['getCalculator'] = 1264 | MarkerClusterer.prototype.getCalculator; 1265 | MarkerClusterer.prototype['getGridSize'] = 1266 | MarkerClusterer.prototype.getGridSize; 1267 | MarkerClusterer.prototype['getExtendedBounds'] = 1268 | MarkerClusterer.prototype.getExtendedBounds; 1269 | MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap; 1270 | MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers; 1271 | MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom; 1272 | MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles; 1273 | MarkerClusterer.prototype['getTotalClusters'] = 1274 | MarkerClusterer.prototype.getTotalClusters; 1275 | MarkerClusterer.prototype['getTotalMarkers'] = 1276 | MarkerClusterer.prototype.getTotalMarkers; 1277 | MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw; 1278 | MarkerClusterer.prototype['removeMarker'] = 1279 | MarkerClusterer.prototype.removeMarker; 1280 | MarkerClusterer.prototype['removeMarkers'] = 1281 | MarkerClusterer.prototype.removeMarkers; 1282 | MarkerClusterer.prototype['resetViewport'] = 1283 | MarkerClusterer.prototype.resetViewport; 1284 | MarkerClusterer.prototype['repaint'] = 1285 | MarkerClusterer.prototype.repaint; 1286 | MarkerClusterer.prototype['setCalculator'] = 1287 | MarkerClusterer.prototype.setCalculator; 1288 | MarkerClusterer.prototype['setGridSize'] = 1289 | MarkerClusterer.prototype.setGridSize; 1290 | MarkerClusterer.prototype['setMaxZoom'] = 1291 | MarkerClusterer.prototype.setMaxZoom; 1292 | MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd; 1293 | MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw; 1294 | 1295 | Cluster.prototype['getCenter'] = Cluster.prototype.getCenter; 1296 | Cluster.prototype['getSize'] = Cluster.prototype.getSize; 1297 | Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers; 1298 | 1299 | ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd; 1300 | ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw; 1301 | ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove; 1302 | 1303 | Object.keys = Object.keys || function(o) { 1304 | var result = []; 1305 | for(var name in o) { 1306 | if (o.hasOwnProperty(name)) 1307 | result.push(name); 1308 | } 1309 | return result; 1310 | }; -------------------------------------------------------------------------------- /Lesson16-Geocoding/script.js: -------------------------------------------------------------------------------- 1 | (function(window, $) { 2 | 3 | var $mapster = $('#map-canvas').mapster(Mapster.MAP_OPTIONS), 4 | geocoder = new google.maps.Geocoder(); 5 | 6 | $mapster.mapster('addMarker', { 7 | lat: 37.791350, 8 | lng: -122.435883 9 | }); 10 | 11 | $mapster.mapster('addMarker', { 12 | location: '1111 Main Street' 13 | }); 14 | 15 | 16 | // function geocode(opts) { 17 | // geocoder.geocode({ 18 | // address: opts.address 19 | // }, function(results, status) { 20 | // if (status === google.maps.GeocoderStatus.OK) { 21 | // opts.success.call(this, results, status); 22 | // } else { 23 | // opts.error.call(this, status); 24 | // } 25 | // }); 26 | // } 27 | 28 | // geocode({ 29 | // address: 'Golden Gate Bridge, San Francisco, CA', 30 | // success: function(results) { 31 | // var result = results[0]; 32 | // $mapster.mapster('addMarker', { 33 | // lat: result.geometry.location.lat(), 34 | // lng: result.geometry.location.lng() 35 | // }) 36 | // }, 37 | // error: function(status) { 38 | // console.error(status); 39 | // } 40 | // }) 41 | 42 | }(window, jQuery)); -------------------------------------------------------------------------------- /Lesson16-Geocoding/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } 12 | #pip-pano { 13 | height: 200px; 14 | width: 100%; 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | z-index: 1; 19 | } -------------------------------------------------------------------------------- /Lesson17-Geolocation/List.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var List = (function() { 3 | function List() { 4 | this.items = []; 5 | } 6 | List.prototype = { 7 | add: function(item) { 8 | this.items.push(item); 9 | }, 10 | remove: function(item) { 11 | var indexOf = this.items.indexOf(item); 12 | if (indexOf !== -1) { 13 | this.items.splice(indexOf, 1); 14 | } 15 | }, 16 | find: function(callback, action) { 17 | var callbackReturn, 18 | items = this.items, 19 | length = items.length 20 | matches = [], 21 | i = 0; 22 | 23 | for(; i < length; i++) { 24 | callbackReturn = callback(items[i], i); 25 | if (callbackReturn) { 26 | matches.push(items[i]); 27 | } 28 | } 29 | 30 | if (action) { 31 | action.call(this, matches); 32 | } 33 | 34 | return matches; 35 | } 36 | }; 37 | return List; 38 | }()); 39 | 40 | List.create = function() { 41 | return new List(); 42 | }; 43 | 44 | window.List = List; 45 | 46 | }(window)); -------------------------------------------------------------------------------- /Lesson17-Geolocation/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google, List) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | this.markers = List.create(); 7 | if (opts.cluster) { 8 | this.markerClusterer = new MarkerClusterer(this.gMap, [], opts.cluster.options); 9 | } 10 | if (opts.geocoder) { 11 | this.geocoder = new google.maps.Geocoder(); 12 | } 13 | } 14 | Mapster.prototype = { 15 | zoom: function(level) { 16 | if (level) { 17 | this.gMap.setZoom(level); 18 | } else { 19 | return this.gMap.getZoom(); 20 | } 21 | }, 22 | _on: function(opts) { 23 | var self = this; 24 | google.maps.event.addListener(opts.obj, opts.event, function(e) { 25 | opts.callback.call(self, e, opts.obj); 26 | }); 27 | }, 28 | geocode: function(opts) { 29 | this.geocoder.geocode({ 30 | address: opts.address 31 | }, function(results, status) { 32 | if (status === google.maps.GeocoderStatus.OK) { 33 | opts.success.call(this, results, status); 34 | } else { 35 | opts.error.call(this, status); 36 | } 37 | }); 38 | }, 39 | getCurrentPosition: function(callback) { 40 | if (navigator.geolocation) { 41 | navigator.geolocation.getCurrentPosition(function(position) { 42 | callback.call(this, position)); 43 | }); 44 | } 45 | }, 46 | setPano: function(element, opts) { 47 | var panorama = new google.maps.StreetViewPanorama(element, opts); 48 | if (opts.events) { 49 | this._attachEvents(panorama, opts.events); 50 | } 51 | this.gMap.setStreetView(panorama); 52 | }, 53 | addMarker: function(opts) { 54 | var marker, 55 | self = this; 56 | 57 | opts.position = { 58 | lat: opts.lat, 59 | lng: opts.lng 60 | } 61 | marker = this._createMarker(opts); 62 | if (this.markerClusterer) { 63 | this.markerClusterer.addMarker(marker); 64 | } 65 | this._addMarker(marker); 66 | if (opts.events) { 67 | this._attachEvents(marker, opts.events); 68 | } 69 | if (opts.content) { 70 | this._on({ 71 | obj: marker, 72 | event: 'click', 73 | callback: function() { 74 | var infoWindow = new google.maps.InfoWindow({ 75 | content: opts.content 76 | }); 77 | 78 | infoWindow.open(this.gMap, marker); 79 | } 80 | }) 81 | } 82 | return marker; 83 | }, 84 | _attachEvents: function(obj, events) { 85 | var self = this; 86 | events.forEach(function(event) { 87 | self._on({ 88 | obj: obj, 89 | event: event.name, 90 | callback: event.callback 91 | }); 92 | }); 93 | }, 94 | _addMarker: function(marker) { 95 | this.markers.add(marker); 96 | }, 97 | findBy: function(callback) { 98 | this.markers.find(callback); 99 | }, 100 | removeBy: function(callback) { 101 | var self = this; 102 | self.markers.find(callback, function(markers) { 103 | markers.forEach(function(marker) { 104 | if (self.markerClusterer) { 105 | self.markerClusterer.removeMarker(marker); 106 | } else { 107 | marker.setMap(null); 108 | } 109 | }); 110 | }); 111 | }, 112 | _createMarker: function(opts) { 113 | opts.map = this.gMap; 114 | return new google.maps.Marker(opts); 115 | } 116 | }; 117 | return Mapster; 118 | }()); 119 | 120 | Mapster.create = function(element, opts) { 121 | return new Mapster(element, opts); 122 | }; 123 | 124 | window.Mapster = Mapster; 125 | 126 | }(window, google, List)); 127 | -------------------------------------------------------------------------------- /Lesson17-Geolocation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Lesson17-Geolocation/jqueryui.mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, Mapster) { 2 | 3 | $.widget("mapster.mapster", { 4 | // default options 5 | options: { }, 6 | 7 | // the constructor 8 | _create: function() { 9 | var element = this.element[0], 10 | options = this.options; 11 | this.map = Mapster.create(element, options); 12 | }, 13 | 14 | // called when created, and later when changing options 15 | _refresh: function() { 16 | 17 | }, 18 | 19 | // Add a marker onto the map 20 | addMarker: function( opts ) { 21 | var self = this; 22 | if (opts.location) { 23 | this.map.geocode({ 24 | address: opts.location, 25 | success: function(results) { 26 | results.forEach(function(result) { 27 | opts.lat = result.geometry.location.lat(); 28 | opts.lng = result.geometry.location.lng(); 29 | self.map.addMarker(opts); 30 | }); 31 | }, 32 | error: function(status) { 33 | console.error(status) 34 | } 35 | }); 36 | } else { 37 | this.map.addMarker(opts); 38 | } 39 | }, 40 | 41 | findMarkers: function(callback) { 42 | return this.map.findBy(callback); 43 | }, 44 | 45 | removeMarkers: function(callback) { 46 | this.map.removeBy(callback); 47 | }, 48 | 49 | markers: function() { 50 | return this.map.markers.items; 51 | }, 52 | 53 | getCurrentPosition: function(callback) { 54 | this.map.getCurrentPosition(callback); 55 | } 56 | 57 | setPano: function(selector, opts) { 58 | var elements = $(selector), 59 | self = this; 60 | $.each(elements, function(key, element) { 61 | self.map.setPano(element, opts); 62 | }); 63 | }, 64 | 65 | // events bound via _on are removed automatically 66 | // revert other modifications here 67 | _destroy: function() { 68 | 69 | }, 70 | 71 | // _setOptions is called with a hash of all options that are changing 72 | // always refresh when changing options 73 | _setOptions: function() { 74 | // _super and _superApply handle keeping the right this-context 75 | this._superApply( arguments ); 76 | this._refresh(); 77 | }, 78 | 79 | // _setOption is called for each individual option that is changing 80 | _setOption: function( key, value ) { 81 | this._super( key, value ); 82 | } 83 | }); 84 | 85 | }(window, Mapster)) 86 | -------------------------------------------------------------------------------- /Lesson17-Geolocation/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | mapster.MAP_OPTIONS = { 4 | center: { 5 | lat: 37.791350, 6 | lng: -122.435883 7 | }, 8 | zoom: 10, 9 | disableDefaultUI: false, 10 | scrollwheel: true, 11 | draggable: true, 12 | mapTypeId: google.maps.MapTypeId.ROADMAP, 13 | zoomControlOptions: { 14 | position: google.maps.ControlPosition.LEFT_BOTTOM, 15 | style: google.maps.ZoomControlStyle.DEFAULT 16 | }, 17 | panControlOptions: { 18 | position: google.maps.ControlPosition.LEFT_BOTTOM 19 | }, 20 | cluster: false, 21 | geocoder: true 22 | }; 23 | 24 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson17-Geolocation/script.js: -------------------------------------------------------------------------------- 1 | (function(window, $) { 2 | 3 | var $mapster = $('#map-canvas').mapster(Mapster.MAP_OPTIONS); 4 | 5 | $mapster.mapster('getCurrentPosition', function(position) { 6 | $mapster.mapster('addMarker', { 7 | lat: position.coords.latitude, 8 | lng: position.coords.longitude 9 | }); 10 | }); 11 | 12 | }(window, jQuery)); 13 | -------------------------------------------------------------------------------- /Lesson17-Geolocation/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } 12 | #pip-pano { 13 | height: 200px; 14 | width: 100%; 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | z-index: 1; 19 | } -------------------------------------------------------------------------------- /Lesson18-StylingMaps/List.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var List = (function() { 3 | function List() { 4 | this.items = []; 5 | } 6 | List.prototype = { 7 | add: function(item) { 8 | this.items.push(item); 9 | }, 10 | remove: function(item) { 11 | var indexOf = this.items.indexOf(item); 12 | if (indexOf !== -1) { 13 | this.items.splice(indexOf, 1); 14 | } 15 | }, 16 | find: function(callback, action) { 17 | var callbackReturn, 18 | items = this.items, 19 | length = items.length 20 | matches = [], 21 | i = 0; 22 | 23 | for(; i < length; i++) { 24 | callbackReturn = callback(items[i], i); 25 | if (callbackReturn) { 26 | matches.push(items[i]); 27 | } 28 | } 29 | 30 | if (action) { 31 | action.call(this, matches); 32 | } 33 | 34 | return matches; 35 | } 36 | }; 37 | return List; 38 | }()); 39 | 40 | List.create = function() { 41 | return new List(); 42 | }; 43 | 44 | window.List = List; 45 | 46 | }(window)); -------------------------------------------------------------------------------- /Lesson18-StylingMaps/Mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, google, List) { 2 | 3 | var Mapster = (function() { 4 | function Mapster(element, opts) { 5 | this.gMap = new google.maps.Map(element, opts); 6 | this.markers = List.create(); 7 | if (opts.cluster) { 8 | this.markerClusterer = new MarkerClusterer(this.gMap, [], opts.cluster.options); 9 | } 10 | if (opts.geocoder) { 11 | this.geocoder = new google.maps.Geocoder(); 12 | } 13 | } 14 | Mapster.prototype = { 15 | zoom: function(level) { 16 | if (level) { 17 | this.gMap.setZoom(level); 18 | } else { 19 | return this.gMap.getZoom(); 20 | } 21 | }, 22 | _on: function(opts) { 23 | var self = this; 24 | google.maps.event.addListener(opts.obj, opts.event, function(e) { 25 | opts.callback.call(self, e, opts.obj); 26 | }); 27 | }, 28 | geocode: function(opts) { 29 | this.geocoder.geocode({ 30 | address: opts.address 31 | }, function(results, status) { 32 | if (status === google.maps.GeocoderStatus.OK) { 33 | opts.success.call(this, results, status); 34 | } else { 35 | opts.error.call(this, status); 36 | } 37 | }); 38 | }, 39 | getCurrentPosition: function(callback) { 40 | if (navigator.geolocation) { 41 | navigator.geolocation.getCurrentPosition(function(position) { 42 | callback.call(this, position); 43 | }); 44 | } 45 | }, 46 | setPano: function(element, opts) { 47 | var panorama = new google.maps.StreetViewPanorama(element, opts); 48 | if (opts.events) { 49 | this._attachEvents(panorama, opts.events); 50 | } 51 | this.gMap.setStreetView(panorama); 52 | }, 53 | addMarker: function(opts) { 54 | var marker, 55 | self = this; 56 | 57 | opts.position = { 58 | lat: opts.lat, 59 | lng: opts.lng 60 | } 61 | marker = this._createMarker(opts); 62 | if (this.markerClusterer) { 63 | this.markerClusterer.addMarker(marker); 64 | } 65 | this._addMarker(marker); 66 | if (opts.events) { 67 | this._attachEvents(marker, opts.events); 68 | } 69 | if (opts.content) { 70 | this._on({ 71 | obj: marker, 72 | event: 'click', 73 | callback: function() { 74 | var infoWindow = new google.maps.InfoWindow({ 75 | content: opts.content 76 | }); 77 | 78 | infoWindow.open(this.gMap, marker); 79 | } 80 | }) 81 | } 82 | return marker; 83 | }, 84 | _attachEvents: function(obj, events) { 85 | var self = this; 86 | events.forEach(function(event) { 87 | self._on({ 88 | obj: obj, 89 | event: event.name, 90 | callback: event.callback 91 | }); 92 | }); 93 | }, 94 | _addMarker: function(marker) { 95 | this.markers.add(marker); 96 | }, 97 | findBy: function(callback) { 98 | this.markers.find(callback); 99 | }, 100 | removeBy: function(callback) { 101 | var self = this; 102 | self.markers.find(callback, function(markers) { 103 | markers.forEach(function(marker) { 104 | if (self.markerClusterer) { 105 | self.markerClusterer.removeMarker(marker); 106 | } else { 107 | marker.setMap(null); 108 | } 109 | }); 110 | }); 111 | }, 112 | _createMarker: function(opts) { 113 | opts.map = this.gMap; 114 | return new google.maps.Marker(opts); 115 | } 116 | }; 117 | return Mapster; 118 | }()); 119 | 120 | Mapster.create = function(element, opts) { 121 | return new Mapster(element, opts); 122 | }; 123 | 124 | window.Mapster = Mapster; 125 | 126 | }(window, google, List)); -------------------------------------------------------------------------------- /Lesson18-StylingMaps/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Lesson18-StylingMaps/jqueryui.mapster.js: -------------------------------------------------------------------------------- 1 | (function(window, Mapster) { 2 | 3 | $.widget("mapster.mapster", { 4 | // default options 5 | options: { }, 6 | 7 | // the constructor 8 | _create: function() { 9 | var element = this.element[0], 10 | options = this.options; 11 | this.map = Mapster.create(element, options); 12 | }, 13 | 14 | // called when created, and later when changing options 15 | _refresh: function() { 16 | 17 | }, 18 | 19 | // Add a marker onto the map 20 | addMarker: function( opts ) { 21 | var self = this; 22 | if (opts.location) { 23 | this.map.geocode({ 24 | address: opts.location, 25 | success: function(results) { 26 | results.forEach(function(result) { 27 | opts.lat = result.geometry.location.lat(); 28 | opts.lng = result.geometry.location.lng(); 29 | self.map.addMarker(opts); 30 | }); 31 | }, 32 | error: function(status) { 33 | console.error(status) 34 | } 35 | }); 36 | } else { 37 | this.map.addMarker(opts); 38 | } 39 | }, 40 | 41 | findMarkers: function(callback) { 42 | return this.map.findBy(callback); 43 | }, 44 | 45 | removeMarkers: function(callback) { 46 | this.map.removeBy(callback); 47 | }, 48 | 49 | markers: function() { 50 | return this.map.markers.items; 51 | }, 52 | 53 | getCurrentPosition: function(callback) { 54 | this.map.getCurrentPosition(callback); 55 | }, 56 | 57 | setPano: function(selector, opts) { 58 | var elements = $(selector), 59 | self = this; 60 | $.each(elements, function(key, element) { 61 | self.map.setPano(element, opts); 62 | }); 63 | }, 64 | 65 | // events bound via _on are removed automatically 66 | // revert other modifications here 67 | _destroy: function() { 68 | 69 | }, 70 | 71 | // _setOptions is called with a hash of all options that are changing 72 | // always refresh when changing options 73 | _setOptions: function() { 74 | // _super and _superApply handle keeping the right this-context 75 | this._superApply( arguments ); 76 | this._refresh(); 77 | }, 78 | 79 | // _setOption is called for each individual option that is changing 80 | _setOption: function( key, value ) { 81 | this._super( key, value ); 82 | } 83 | }); 84 | 85 | }(window, Mapster)) -------------------------------------------------------------------------------- /Lesson18-StylingMaps/map-options.js: -------------------------------------------------------------------------------- 1 | (function(window, google, mapster) { 2 | 3 | // remove labels 4 | // water - 3498db 5 | // landscape - 27ae60 6 | // poi - 27ae60 7 | // transit - 27ae60 8 | // highways - 34495e 9 | // main roads - ecf0f1 10 | 11 | var styles = [{ 12 | featureType: 'all', 13 | elementType: 'labels', 14 | stylers: [ 15 | { visibility: 'off' } 16 | ] 17 | }, { 18 | featureType: 'water', 19 | elementType: 'geometry', 20 | stylers: [ 21 | { color: '#3498db' } 22 | ] 23 | }, { 24 | featureType: 'landscape', 25 | elementType: 'geometry', 26 | stylers: [ 27 | { color: '#27ae60' } 28 | ] 29 | }, { 30 | featureType: 'poi', 31 | elementType: 'geometry', 32 | stylers: [ 33 | { color: '#27ae60' } 34 | ] 35 | }, { 36 | featureType: 'transit', 37 | elementType: 'geometry', 38 | stylers: [ 39 | { color: '#27ae60' } 40 | ] 41 | }, { 42 | featureType: 'road.highway', 43 | elementType: 'geometry', 44 | stylers: [ 45 | { color: '#34495e' } 46 | ] 47 | }, { 48 | featureType: 'road.arterial', 49 | elementType: 'geometry', 50 | stylers: [ 51 | { color: '#ecf0f1' } 52 | ] 53 | }]; 54 | 55 | mapster.MAP_OPTIONS = { 56 | center: { 57 | lat: 37.791350, 58 | lng: -122.435883 59 | }, 60 | zoom: 10, 61 | disableDefaultUI: false, 62 | scrollwheel: true, 63 | draggable: true, 64 | mapTypeId: google.maps.MapTypeId.ROADMAP, 65 | zoomControlOptions: { 66 | position: google.maps.ControlPosition.LEFT_BOTTOM, 67 | style: google.maps.ZoomControlStyle.DEFAULT 68 | }, 69 | panControlOptions: { 70 | position: google.maps.ControlPosition.LEFT_BOTTOM 71 | }, 72 | cluster: false, 73 | geocoder: true, 74 | styles: styles 75 | }; 76 | 77 | }(window, google, window.Mapster || (window.Mapster = {}))) -------------------------------------------------------------------------------- /Lesson18-StylingMaps/script.js: -------------------------------------------------------------------------------- 1 | (function(window, $) { 2 | 3 | var $mapster = $('#map-canvas').mapster(Mapster.MAP_OPTIONS); 4 | 5 | // if (navigator.geolocation) { 6 | // navigator.geolocation.getCurrentPosition(function(position) { 7 | // $mapster.mapster('addMarker', { 8 | // lat: position.coords.latitude, 9 | // lng: position.coords.longitude 10 | // }); 11 | // }); 12 | // } 13 | 14 | $mapster.mapster('getCurrentPosition', function(position) { 15 | $mapster.mapster('addMarker', { 16 | lat: position.coords.latitude, 17 | lng: position.coords.longitude 18 | }); 19 | }); 20 | 21 | }(window, jQuery)); -------------------------------------------------------------------------------- /Lesson18-StylingMaps/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | #map-canvas { 10 | height: 100%; 11 | } 12 | #pip-pano { 13 | height: 200px; 14 | width: 100%; 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | z-index: 1; 19 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Tuts+ Course: Google Maps API 2 | #### Instructor: David East 3 | 4 | Get started creating interactive maps using the Google Maps API. In this course you'll learn how to create and configure different types of maps, how to attach events to these maps as well as creating and customizing markers. You'll tap into some of the more powerful services that Google provides, such as geocoding and then even abstract all of this out into your own custom library. By the end of this course, you'll be able to take the library you created and apply it to your own application. 5 | 6 | Source files for the Tuts+ course: [Google Maps API](https://courses.tutsplus.com/courses/custom-interactive-maps-with-the-google-maps-api) 7 | 8 | 9 | ## 3rd-Party Content 10 | 11 | The map marker icons were sourced from the [Maps Icons Collection](https://mapicons.mapsmarker.com) and licensed under Creative Commons [CC BY SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/). 12 | --------------------------------------------------------------------------------