diff --git a/4190.pdf b/4190.pdf
new file mode 100644
index 0000000..bf045f5
Binary files /dev/null and b/4190.pdf differ
diff --git a/4191.pdf b/4191.pdf
new file mode 100644
index 0000000..a9591b0
Binary files /dev/null and b/4191.pdf differ
diff --git a/9781430216209.jpg b/9781430216209.jpg
new file mode 100644
index 0000000..04aef2a
Binary files /dev/null and b/9781430216209.jpg differ
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..9f45335
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,27 @@
+Freeware License, some rights reserved
+
+Copyright (c) 2009 Jon Udell
+
+Permission is hereby granted, free of charge, to anyone obtaining a copy
+of this software and associated documentation files (the "Software"),
+to work with the Software within the limits of freeware distribution and fair use.
+This includes the rights to use, copy, and modify the Software for personal use.
+Users are also allowed and encouraged to submit corrections and modifications
+to the Software for the benefit of other users.
+
+It is not allowed to reuse, modify, or redistribute the Software for
+commercial use in any way, or for a user’s educational materials such as books
+or blog articles without prior permission from the copyright holder.
+
+The above copyright notice and this permission notice need to be included
+in all copies or substantial portions of the software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS OR APRESS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fce6516
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+#Apress Source Code
+
+This repository accompanies [*Beginning Google Maps Mashups with Mapplets, KML, and GeoRSS*](http://www.apress.com/9781430216209) by Jon Udell (Apress, 2009).
+
+![Cover image](9781430216209.jpg)
+
+Download the files as a zip using the green button, or clone the repository to your machine using Git.
+
+##Releases
+
+Release v1.0 corresponds to the code in the published book, without corrections or updates.
+
+##Contributions
+
+See the file Contributing.md for more information on how you can contribute to this repository.
diff --git a/appendix_a/listing_a_01.js b/appendix_a/listing_a_01.js
new file mode 100644
index 0000000..dbda302
--- /dev/null
+++ b/appendix_a/listing_a_01.js
@@ -0,0 +1,22 @@
+function dms2decimal(deg, min, sec)
+{
+ // dms2decimal: converts degrees/minutes/seconds to decimal degrees
+
+ if (sec == null)
+ sec = 0;
+
+ if (deg >= 0)
+ return deg + (min / 60) + (sec / 3600);
+ else
+ return deg - ((min / 60) + (sec / 3600));
+};
+
+function decimal2dms(degrees)
+{
+ // decimal2dms: converts decimal degrees to degrees/minutes/seconds
+ var minutes = (Math.abs(degrees) - Math.floor(Math.abs(degrees))) * 60;
+ var seconds = Number(((minutes - Math.floor(minutes)) * 60).toFixed(2));
+ var minutes = Math.floor(minutes);
+ var degrees = Math.floor(degrees);
+ return {deg: degrees, min: minutes, sec: seconds};
+};
diff --git a/appendix_a/listing_a_02.php b/appendix_a/listing_a_02.php
new file mode 100644
index 0000000..afcc393
--- /dev/null
+++ b/appendix_a/listing_a_02.php
@@ -0,0 +1,13 @@
+
diff --git a/appendix_b/listing_b_01.js b/appendix_b/listing_b_01.js
new file mode 100644
index 0000000..f1a52ff
--- /dev/null
+++ b/appendix_b/listing_b_01.js
@@ -0,0 +1,10 @@
+if (a == b)
+{
+ // Statements here execute if a is equal to b
+ ...
+}
+else
+{
+ // Statements here execute if a is not equal to b
+ ...
+}
diff --git a/appendix_b/listing_b_02.js b/appendix_b/listing_b_02.js
new file mode 100644
index 0000000..e91d149
--- /dev/null
+++ b/appendix_b/listing_b_02.js
@@ -0,0 +1,15 @@
+if (a == b)
+{
+ // Statements here execute if a is equal to b
+ ...
+}
+else if (a == c)
+{
+ // Statements here execute if a is equal to c
+ ...
+}
+else
+{
+ // Statements here execute if a is not equal to b or c
+ ...
+}
diff --git a/appendix_b/listing_b_03.js b/appendix_b/listing_b_03.js
new file mode 100644
index 0000000..f31a14d
--- /dev/null
+++ b/appendix_b/listing_b_03.js
@@ -0,0 +1,5 @@
+for (a = 0; a < 5; a++)
+{
+ // Statements here will execute five times
+ ...
+}
diff --git a/appendix_b/listing_b_04.js b/appendix_b/listing_b_04.js
new file mode 100644
index 0000000..4f77cb8
--- /dev/null
+++ b/appendix_b/listing_b_04.js
@@ -0,0 +1,5 @@
+for (var a = 0; a < array.length; a++)
+{
+ // Actions can be performed here on array[a]
+ ...
+}
diff --git a/appendix_b/listing_b_05.js b/appendix_b/listing_b_05.js
new file mode 100644
index 0000000..65b57ef
--- /dev/null
+++ b/appendix_b/listing_b_05.js
@@ -0,0 +1,7 @@
+for (var a = array.length ? 1; a >= 0; a--)
+{
+ // Actions can be performed here on array[a], before deletion
+ ...
+
+ array.splice(a, 1);
+}
diff --git a/appendix_b/listing_b_06.js b/appendix_b/listing_b_06.js
new file mode 100644
index 0000000..a823ada
--- /dev/null
+++ b/appendix_b/listing_b_06.js
@@ -0,0 +1,5 @@
+while (a == b)
+{
+ // Statements here will execute until a is no longer equal to b
+ ...
+}
diff --git a/appendix_b/listing_b_07.js b/appendix_b/listing_b_07.js
new file mode 100644
index 0000000..c6494a4
--- /dev/null
+++ b/appendix_b/listing_b_07.js
@@ -0,0 +1,12 @@
+var found = -1;
+for (var a = 0; a < array.length; a++)
+{
+ if (array[a] == 'banana')
+ {
+ found = a;
+ break;
+ }
+}
+
+if (found > -1)
+ alert('banana found at element ' + found);
diff --git a/appendix_b/listing_b_08.js b/appendix_b/listing_b_08.js
new file mode 100644
index 0000000..d432493
--- /dev/null
+++ b/appendix_b/listing_b_08.js
@@ -0,0 +1,16 @@
+switch (a)
+{
+ case b:
+ // Statements here execute if a is equal to b
+ ...
+ break
+
+ case c:
+ // Statements here execute if a is equal to c
+ ...
+ break;
+
+ default:
+ // Statements here execute if a is not equal to b or c
+ ...
+}
diff --git a/appendix_b/listing_b_09.js b/appendix_b/listing_b_09.js
new file mode 100644
index 0000000..1c9a65c
--- /dev/null
+++ b/appendix_b/listing_b_09.js
@@ -0,0 +1,19 @@
+switch (currencyCode)
+{
+ case 'AUD':
+ case 'CAD':
+ case 'USD':
+ var symbol = '$';
+ break;
+
+ case 'GBP':
+ var symbol = '£';
+ break;
+
+ case 'JPY':
+ var symbol = 'Â¥';
+ break;
+
+ default:
+ var symbol = currencyCode;
+}
diff --git a/appendix_b/listing_b_10.js b/appendix_b/listing_b_10.js
new file mode 100644
index 0000000..ed444b5
--- /dev/null
+++ b/appendix_b/listing_b_10.js
@@ -0,0 +1,12 @@
+function doSomething(a)
+{
+ var b;
+ // Actions can be performed here on the parameter, a
+ ...
+
+ return b;
+};
+
+...
+// Later in the code, we can invoke those actions:
+var c = doSomething(d);
diff --git a/appendix_b/listing_b_11.js b/appendix_b/listing_b_11.js
new file mode 100644
index 0000000..6771364
--- /dev/null
+++ b/appendix_b/listing_b_11.js
@@ -0,0 +1,8 @@
+function square (x)
+{
+ var sqr = x * x;
+ return sqr;
+};
+
+var nine = square(3);
+var thirtySix = square(6);
diff --git a/appendix_b/listing_b_12.js b/appendix_b/listing_b_12.js
new file mode 100644
index 0000000..65b8ce1
--- /dev/null
+++ b/appendix_b/listing_b_12.js
@@ -0,0 +1,5 @@
+var x = {length: 6, width: 4, color: 'gray'};
+x.area = function ()
+{
+ return this.length * this.width;
+};
diff --git a/appendix_b/listing_b_13.js b/appendix_b/listing_b_13.js
new file mode 100644
index 0000000..d98c9bd
--- /dev/null
+++ b/appendix_b/listing_b_13.js
@@ -0,0 +1,10 @@
+var a;
+
+function doSomething()
+{
+ var b;
+ var c = a + b;
+ ...
+};
+...
+var d = a + b; // This statement will produce an error!
\ No newline at end of file
diff --git a/appendix_b/listing_b_14.js b/appendix_b/listing_b_14.js
new file mode 100644
index 0000000..bb8127a
--- /dev/null
+++ b/appendix_b/listing_b_14.js
@@ -0,0 +1,6 @@
+GEvent.addListener(map, 'zoomend',
+ function (oldZoom, newZoom)
+ {
+ // Perform actions here, after the map zooms, using the old and new zoom levels
+ ...
+ });
diff --git a/appendix_b/listing_b_15.js b/appendix_b/listing_b_15.js
new file mode 100644
index 0000000..567b421
--- /dev/null
+++ b/appendix_b/listing_b_15.js
@@ -0,0 +1,13 @@
+function addDataPoint(coordinates, name, description, style)
+{
+ var thisMarker = new GMarker(coordinates, options);
+ ...
+ var sidebarRow = document.createElement('div');
+ ...
+ sidebarRow.onclick =
+ function ()
+ {
+ // A click on the sidebar entry triggers a click on its associated marker
+ GEvent.trigger(thisMarker, 'click')
+ };
+};
diff --git a/appendix_b/readme.txt b/appendix_b/readme.txt
new file mode 100644
index 0000000..8e7ed8c
--- /dev/null
+++ b/appendix_b/readme.txt
@@ -0,0 +1,2 @@
+Note: Most of the listings in this chapter are example code snippets to illustrate
+ structure, not complete (nor syntactically valid) JavaScript.
\ No newline at end of file
diff --git a/appendix_c/listing_c_01.js b/appendix_c/listing_c_01.js
new file mode 100644
index 0000000..baa2d1a
--- /dev/null
+++ b/appendix_c/listing_c_01.js
@@ -0,0 +1,21 @@
+var map;
+
+function loadMap()
+{
+ var mapDiv = document.getElementById('map');
+
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ map = new GMap2(mapDiv);
+ map.setCenter(new GLatLng(39.8, -98.5), 4);
+
+ GEvent.addListener(map, 'moveend', mapMoveEnd);
+ }
+};
+
+function mapMoveEnd()
+{
+ GLog.write(map.getCenter().toUrlValue());
+};
diff --git a/appendix_c/listing_c_02.js b/appendix_c/listing_c_02.js
new file mode 100644
index 0000000..a93d327
--- /dev/null
+++ b/appendix_c/listing_c_02.js
@@ -0,0 +1,17 @@
+var map;
+
+GEvent.addDomListener(window, 'load', loadMap);
+GEvent.addDomListener(window, 'unload', GUnload);
+
+function loadMap()
+{
+ var mapDiv = document.getElementById('map');
+
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ map = new GMap2(mapDiv);
+ map.setCenter(new GLatLng(39.8, -98.5), 4);
+ }
+};
diff --git a/appendix_c/listing_c_03.html b/appendix_c/listing_c_03.html
new file mode 100644
index 0000000..7040e94
--- /dev/null
+++ b/appendix_c/listing_c_03.html
@@ -0,0 +1,15 @@
+
+
+
+
+ Better Event Listeners
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/appendix_c/listing_c_04.html b/appendix_c/listing_c_04.html
new file mode 100644
index 0000000..e18a79a
--- /dev/null
+++ b/appendix_c/listing_c_04.html
@@ -0,0 +1,14 @@
+
+
+
+
+ Multiple API Keys
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/appendix_c/listing_c_05.js b/appendix_c/listing_c_05.js
new file mode 100644
index 0000000..0e5b469
--- /dev/null
+++ b/appendix_c/listing_c_05.js
@@ -0,0 +1,12 @@
+// Set Google maps API key
+
+var scriptUrl = 'http://maps.google.com/maps?file=api&v=2.124&key=';
+
+if (location.host == 'www.somedomain.com')
+ // Live server
+ scriptUrl = scriptUrl + 'ABQIAAAA5wG_Upp3E2E2FvbwfSzPchQEMNwiYNXG5Xk21FzWxftqhFg';
+else
+ // Development server
+ scriptUrl = scriptUrl + 'ABQIAAAAN_IKAOv-fz9s2G9ZOihkhRQRiYyMmFOXXgCIe1BMmHtWUkP';
+
+document.write('');
diff --git a/appendix_c/listing_c_06.xml b/appendix_c/listing_c_06.xml
new file mode 100644
index 0000000..5c14298
--- /dev/null
+++ b/appendix_c/listing_c_06.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+ p {
+ font-size: 90%;
+ }
+
+
+
+
+
+ ]]>
+
\ No newline at end of file
diff --git a/async.js b/async.js
new file mode 100644
index 0000000..c5a3e7b
--- /dev/null
+++ b/async.js
@@ -0,0 +1,46 @@
+// GAsync v2 by Michael Geary
+// Commented version and description at:
+// http://mg.to/2007/06/22/write-the-same-code-for-google-mapplets-and-maps-api
+// Free beer and free speech license. Enjoy!
+
+function GAsync( obj ) {
+
+ function callback() {
+ args[nArgs].apply( null, results );
+ }
+
+ function queue( iResult, name, next ) {
+
+ function ready( value ) {
+ results[iResult] = value;
+ if( ! --nCalls )
+ callback();
+ }
+
+ var a = [];
+ if( next.join )
+ a = a.concat(next), ++iArg;
+ if( mapplet ) {
+ a.push( ready );
+ obj[ name+'Async' ].apply( obj, a );
+ }
+ else {
+ results[iResult] = obj[name].apply( obj, a );
+ }
+ }
+
+ var mapplet = ! window.GBrowserIsCompatible;
+ var args = arguments, nArgs = args.length - 1;
+ var results = [], nCalls = 0;
+
+ for( var iArg = 1; iArg < nArgs; ++iArg ) {
+ var name = args[iArg];
+ if( typeof name == 'object' )
+ obj = name;
+ else
+ queue( nCalls++, name, args[iArg+1] );
+ }
+
+ if( ! mapplet )
+ callback();
+}
\ No newline at end of file
diff --git a/chapter_01/listing_01_01.kml b/chapter_01/listing_01_01.kml
new file mode 100644
index 0000000..babdaa8
--- /dev/null
+++ b/chapter_01/listing_01_01.kml
@@ -0,0 +1,10 @@
+
+
+
+ Crater Lake
+ A deep blue lake in the Cascade Mountains.
+
+ -122.1089,42.9413,0
+
+
+
diff --git a/chapter_01/listing_01_02.xml b/chapter_01/listing_01_02.xml
new file mode 100644
index 0000000..ef42e9b
--- /dev/null
+++ b/chapter_01/listing_01_02.xml
@@ -0,0 +1,12 @@
+
+
+ National Park Tour
+ 2008-05-19T19:41:10Z
+
+ Crater Lake
+ A deep blue lake in the Cascade Mountains.
+ 2008-05-19T19:41:10Z
+ 42.9413 -122.1089
+
+
diff --git a/chapter_01/listing_01_03.xml b/chapter_01/listing_01_03.xml
new file mode 100644
index 0000000..c398a0d
--- /dev/null
+++ b/chapter_01/listing_01_03.xml
@@ -0,0 +1,14 @@
+
+
+
+ National Park Tour
+ http://sterlingudell.com/bgmm/
+ Simple RSS feed with geodata
+
+ Crater Lake
+ http://sterlingudell.com/bgmm/chapter_01/listing_01_03.xml
+ A deep blue lake in the Cascade Mountains.
+ 42.9413 -122.1089
+
+
+
diff --git a/chapter_02/listing_02_01.html b/chapter_02/listing_02_01.html
new file mode 100644
index 0000000..da4e26a
--- /dev/null
+++ b/chapter_02/listing_02_01.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Google Maps API Basic Example
+
+
+
+
+
+
diff --git a/chapter_02/listing_02_02.css b/chapter_02/listing_02_02.css
new file mode 100644
index 0000000..977f742
--- /dev/null
+++ b/chapter_02/listing_02_02.css
@@ -0,0 +1,14 @@
+html {
+ height: 100%;
+}
+
+body {
+ height: 100%;
+ margin: 0;
+}
+
+#map {
+ width: 100%;
+ height: 100%;
+}
+
diff --git a/chapter_02/listing_02_03.html b/chapter_02/listing_02_03.html
new file mode 100644
index 0000000..53bc3db
--- /dev/null
+++ b/chapter_02/listing_02_03.html
@@ -0,0 +1,15 @@
+
+
+
+
+ Google Maps API Basic Example
+
+
+
+
+
+
+
+
diff --git a/chapter_02/listing_02_04.js b/chapter_02/listing_02_04.js
new file mode 100644
index 0000000..4a850be
--- /dev/null
+++ b/chapter_02/listing_02_04.js
@@ -0,0 +1,16 @@
+var map;
+
+function loadMap()
+{
+ var mapDiv = document.getElementById('map');
+
+ if (!GBrowserIsCompatible())
+ {
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ }
+ else
+ {
+ map = new GMap2(mapDiv);
+ map.setCenter(new GLatLng(39.9, -105.2), 10);
+ }
+};
diff --git a/chapter_02/listing_02_05.js b/chapter_02/listing_02_05.js
new file mode 100644
index 0000000..c26f909
--- /dev/null
+++ b/chapter_02/listing_02_05.js
@@ -0,0 +1,21 @@
+var map;
+
+function loadMap()
+{
+ var mapDiv = document.getElementById('map');
+
+ if (!GBrowserIsCompatible())
+ {
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ }
+ else
+ {
+ map = new GMap2(mapDiv);
+ var coordinates = new GLatLng(39.9, -105.2);
+ map.setCenter(coordinates, 10);
+
+ var marker = new GMarker(coordinates);
+ marker.bindInfoWindowHtml('
Hello, World!
');
+ map.addOverlay(marker);
+ }
+};
diff --git a/chapter_02/listing_02_06.js b/chapter_02/listing_02_06.js
new file mode 100644
index 0000000..9eb5200
--- /dev/null
+++ b/chapter_02/listing_02_06.js
@@ -0,0 +1,27 @@
+var map;
+
+function loadMap()
+{
+ var mapDiv = document.getElementById('map');
+
+ if (!GBrowserIsCompatible())
+ {
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ }
+ else
+ {
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_PHYSICAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP]});
+ var coordinates = new GLatLng(39.9, -105.2);
+ map.setCenter(coordinates, 10, G_PHYSICAL_MAP);
+
+ var marker = new GMarker(coordinates);
+ marker.bindInfoWindowHtml('
Hello, World!
');
+ map.addOverlay(marker);
+
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+ map.addControl(new GLargeMapControl());
+ }
+};
diff --git a/chapter_03/listing_03_01.js b/chapter_03/listing_03_01.js
new file mode 100644
index 0000000..4eff657
--- /dev/null
+++ b/chapter_03/listing_03_01.js
@@ -0,0 +1,27 @@
+var map;
+var geoXml;
+
+function loadMap()
+{
+ var mapDiv = document.getElementById('map');
+
+ if (!GBrowserIsCompatible())
+ {
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ }
+ else
+ {
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+ var coordinates = new GLatLng(39.8, -98.5);
+ map.setCenter(coordinates, 4);
+
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ geoXml = new GGeoXml('http://www.placeopedia.com/cgi-bin/kml.cgi');
+ map.addOverlay(geoXml);
+ }
+};
diff --git a/chapter_03/listing_03_02.js b/chapter_03/listing_03_02.js
new file mode 100644
index 0000000..a2d5a88
--- /dev/null
+++ b/chapter_03/listing_03_02.js
@@ -0,0 +1,33 @@
+var map;
+var geoXml;
+
+function loadMap()
+{
+ var mapDiv = document.getElementById('map');
+
+ if (!GBrowserIsCompatible())
+ {
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ }
+ else
+ {
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+ var coordinates = new GLatLng(39.8, -98.5);
+ map.setCenter(coordinates, 4);
+
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ geoXml = new GGeoXml(
+ 'http://www.placeopedia.com/cgi-bin/rss.cgi?num_results=5', xmlLoaded);
+ map.addOverlay(geoXml);
+ }
+};
+
+function xmlLoaded()
+{
+ geoXml.gotoDefaultViewport(map);
+};
\ No newline at end of file
diff --git a/chapter_03/listing_03_03.js b/chapter_03/listing_03_03.js
new file mode 100644
index 0000000..d3ec56c
--- /dev/null
+++ b/chapter_03/listing_03_03.js
@@ -0,0 +1,33 @@
+var map;
+var geoXml;
+
+function loadMap()
+{
+ var mapDiv = document.getElementById('map');
+
+ if (!GBrowserIsCompatible())
+ {
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ }
+ else
+ {
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+ var coordinates = new GLatLng(49.186416, -122.842911);
+ map.setCenter(coordinates, 15);
+
+ var marker = new GMarker(coordinates);
+ marker.bindInfoWindowHtml('
Our Office
' +
+ '10100 E Whalley Ring Rd Vancouver, BC');
+ map.addOverlay(marker);
+ GEvent.trigger(marker, 'click');
+
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ geoXml = new GGeoXml('http://bbs.keyhole.com/ubb/download.php?Number=921371');
+ map.addOverlay(geoXml);
+ }
+};
diff --git a/chapter_03/listing_03_04.css b/chapter_03/listing_03_04.css
new file mode 100644
index 0000000..9803c8c
--- /dev/null
+++ b/chapter_03/listing_03_04.css
@@ -0,0 +1,18 @@
+html {
+ height: 100%;
+}
+
+body {
+ height: 100%;
+ margin: 0;
+}
+
+#map {
+ width: 100%;
+ height: 100%;
+}
+
+#map h3 {
+ margin: 0;
+}
+
\ No newline at end of file
diff --git a/chapter_04/delicate_arch.jpg b/chapter_04/delicate_arch.jpg
new file mode 100644
index 0000000..d441b51
Binary files /dev/null and b/chapter_04/delicate_arch.jpg differ
diff --git a/chapter_04/listing_04_01.html b/chapter_04/listing_04_01.html
new file mode 100644
index 0000000..4266256
--- /dev/null
+++ b/chapter_04/listing_04_01.html
@@ -0,0 +1,19 @@
+
+
+
+
+ Building Out Your Map Page
+
+
+
+
+
+
+
+
Arches
+
National Park Visitor Information
+
+
+
\ No newline at end of file
diff --git a/chapter_04/listing_04_02.css b/chapter_04/listing_04_02.css
new file mode 100644
index 0000000..b07cba6
--- /dev/null
+++ b/chapter_04/listing_04_02.css
@@ -0,0 +1,34 @@
+html {
+ height: 100%;
+}
+
+body {
+ height: 100%;
+ margin: 0;
+ font-family: sans-serif;
+ font-size: 90%;
+}
+
+#map {
+ width: 75%;
+ height: 100%;
+}
+
+#sidebar {
+ position: absolute;
+ left: 75%;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ overflow: auto;
+ padding: 1em;
+}
+
+h1 {
+ margin: 0;
+ font-size: 220%;
+}
+h2 {
+ margin: 0;
+ font-size: 125%;
+}
diff --git a/chapter_04/listing_04_03.js b/chapter_04/listing_04_03.js
new file mode 100644
index 0000000..4cdf0d5
--- /dev/null
+++ b/chapter_04/listing_04_03.js
@@ -0,0 +1,30 @@
+// Declare variables for later use
+var map;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport, based on center coordinates and zoom level
+ var coordinates = new GLatLng(38.661, -109.534);
+ map.setCenter(coordinates, 11, G_PHYSICAL_MAP);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+ }
+};
diff --git a/chapter_04/listing_04_04.js b/chapter_04/listing_04_04.js
new file mode 100644
index 0000000..fa70e37
--- /dev/null
+++ b/chapter_04/listing_04_04.js
@@ -0,0 +1,55 @@
+// Declare variables for later use
+var map;
+var whiteIcon, blackIcon;
+var entranceMarker, delicateArchMarker, windowsMarker;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport, based on center coordinates and zoom level
+ var coordinates = new GLatLng(38.661, -109.534);
+ map.setCenter(coordinates, 11, G_PHYSICAL_MAP);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize a new icon, based on the GMaps default but in white
+ whiteIcon = new GIcon(G_DEFAULT_ICON);
+ whiteIcon.image = '../markers/white.png';
+
+ // Add a marker to the map for the park entrance, using the white icon
+ coordinates = new GLatLng(38.6168, -109.61986);
+ entranceMarker = new GMarker(coordinates, {icon: whiteIcon});
+ map.addOverlay(entranceMarker);
+
+ // Another new icon, also based on the GMaps default but this time in black
+ blackIcon = new GIcon(G_DEFAULT_ICON);
+ blackIcon.image = '../markers/black.png';
+
+ // Add two markers to the map for hiking trails, using the black icon
+
+ coordinates = new GLatLng(38.73561, -109.52073);
+ delicateArchMarker = new GMarker(coordinates, {icon: blackIcon});
+ map.addOverlay(delicateArchMarker);
+
+ coordinates = new GLatLng(38.68725, -109.53712);
+ windowsMarker = new GMarker(coordinates, {icon: blackIcon});
+ map.addOverlay(windowsMarker);
+ }
+};
diff --git a/chapter_04/listing_04_05.html b/chapter_04/listing_04_05.html
new file mode 100644
index 0000000..4cf95e2
--- /dev/null
+++ b/chapter_04/listing_04_05.html
@@ -0,0 +1,34 @@
+
+
+
+
+ Building Out Your Map Page
+
+
+
+
+
+
+
+
Arches
+
National Park Visitor Information
+
+
+ Visitor Center
+
+
Hiking Trails
+
+
+
+ Delicate Arch
+
+
+
+ The Windows
+
+
+
+
+
\ No newline at end of file
diff --git a/chapter_04/listing_04_06.css b/chapter_04/listing_04_06.css
new file mode 100644
index 0000000..9232a98
--- /dev/null
+++ b/chapter_04/listing_04_06.css
@@ -0,0 +1,47 @@
+html {
+ height: 100%;
+}
+
+body {
+ height: 100%;
+ margin: 0;
+ font-family: sans-serif;
+ font-size: 90%;
+}
+
+#map {
+ width: 75%;
+ height: 100%;
+}
+
+#sidebar {
+ position: absolute;
+ left: 75%;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ overflow: auto;
+ padding: 1em;
+}
+#sidebar img {
+ vertical-align: middle;
+ border: none;
+}
+
+h1 {
+ margin: 0;
+ font-size: 220%;
+}
+h2 {
+ margin: 0;
+ font-size: 125%;
+}
+
+ul, li {
+ list-style: none;
+ padding: 0;
+ margin-top: 0;
+}
+li {
+ margin-top: 2px;
+}
diff --git a/chapter_04/listing_04_07.js b/chapter_04/listing_04_07.js
new file mode 100644
index 0000000..ed0dd63
--- /dev/null
+++ b/chapter_04/listing_04_07.js
@@ -0,0 +1,62 @@
+// Declare variables for later use
+var map;
+var whiteIcon, blackIcon;
+var entranceMarker, delicateArchMarker, windowsMarker;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport, based on center coordinates and zoom level
+ var coordinates = new GLatLng(38.661, -109.534);
+ map.setCenter(coordinates, 11, G_PHYSICAL_MAP);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize a new icon, based on the GMaps default but in white
+ whiteIcon = new GIcon(G_DEFAULT_ICON);
+ whiteIcon.image = '../markers/white.png';
+
+ // Add a marker to the map for the park entrance, using the white icon
+ coordinates = new GLatLng(38.6168, -109.61986);
+ entranceMarker = new GMarker(coordinates, {icon: whiteIcon});
+ map.addOverlay(entranceMarker);
+ GEvent.addListener(entranceMarker, 'click', entranceClick);
+
+ // Another new icon, also based on the GMaps default but this time in black
+ blackIcon = new GIcon(G_DEFAULT_ICON);
+ blackIcon.image = '../markers/black.png';
+
+ // Add two markers to the map for hiking trails, using the black icon
+
+ coordinates = new GLatLng(38.73561, -109.52073);
+ delicateArchMarker = new GMarker(coordinates, {icon: blackIcon});
+ map.addOverlay(delicateArchMarker);
+
+ coordinates = new GLatLng(38.68725, -109.53712);
+ windowsMarker = new GMarker(coordinates, {icon: blackIcon});
+ map.addOverlay(windowsMarker);
+ }
+};
+
+function entranceClick()
+{
+ // entranceClick: Open a map detail infowindow showing the park entrance area
+ entranceMarker.showMapBlowup({mapType: G_NORMAL_MAP, zoomLevel: 14});
+};
diff --git a/chapter_04/listing_04_08.html b/chapter_04/listing_04_08.html
new file mode 100644
index 0000000..158f44d
--- /dev/null
+++ b/chapter_04/listing_04_08.html
@@ -0,0 +1,80 @@
+
+
+
+
+ Building Out Your Map Page
+
+
+
+
+
+
+
+
Arches
+
National Park Visitor Information
+
+
+ Visitor Center
+
+
Hiking Trails
+
+
+
+ Delicate Arch
+
+
+
+ The Windows
+
+
+
+
+
+
+
Delicate Arch trail
+
+
Distance
3 miles (4.8 km) round trip
+
Time
2 to 3 hours
+
Difficulty
Moderate
+
+
+ One of the classic hikes of any national park, this scramble across
+ exposed slickrock leads to an open sandstone bowl with stunning views of
+ the freestanding Delicate Arch and the La Sal Mountains beyond.
+ Especially fabulous at sunset!
+
+
+ Carry at least 1 quart of water per person. Good balance is a plus as
+ much of the hike is on sloping sandstone.
+
+
+
+
Delicate Arch at Sunset
+
+
+
+
+
The Windows trail
+
+
Distance
1 mile (1.6 km) round trip
+
Time
½ to 1 hour
+
Difficulty
Easy
+
+
+ A fairly short, level walk that takes in several major arches, also
+ providing good views across the singular landscape of the park. The same
+ parking area gives access to the short trail to Double Arch.
+
+
+ Suitable for all abilities. Initial section is wheelchair-accessible.
+
+
+
+
North Window Arch
+
+
+
+
+
\ No newline at end of file
diff --git a/chapter_04/listing_04_09.css b/chapter_04/listing_04_09.css
new file mode 100644
index 0000000..e4c2887
--- /dev/null
+++ b/chapter_04/listing_04_09.css
@@ -0,0 +1,67 @@
+html {
+ height: 100%;
+}
+
+body {
+ height: 100%;
+ margin: 0;
+ font-family: sans-serif;
+ font-size: 90%;
+}
+
+#map {
+ width: 75%;
+ height: 100%;
+}
+
+#sidebar {
+ position: absolute;
+ left: 75%;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ overflow: auto;
+ padding: 1em;
+}
+#sidebar img {
+ vertical-align: middle;
+ border: none;
+}
+
+h1 {
+ margin: 0;
+ font-size: 220%;
+}
+h2 {
+ margin: 0;
+ font-size: 125%;
+}
+
+ul, li {
+ list-style: none;
+ padding: 0;
+ margin-top: 0;
+}
+li {
+ margin-top: 2px;
+}
+
+#infowindow_content {
+ display: none;
+}
+.details_tab {
+ font-size: 80%;
+}
+h3 {
+ margin: 0;
+}
+dt {
+ width: 5em;
+ float: left;
+ clear: left;
+ font-weight: bold;
+ line-height: 1em;
+}
+dd {
+ line-height: 1em;
+}
diff --git a/chapter_04/listing_04_10.js b/chapter_04/listing_04_10.js
new file mode 100644
index 0000000..fd82034
--- /dev/null
+++ b/chapter_04/listing_04_10.js
@@ -0,0 +1,76 @@
+// Declare variables for later use
+var map;
+var whiteIcon, blackIcon;
+var entranceMarker, delicateArchMarker, windowsMarker;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport, based on center coordinates and zoom level
+ var coordinates = new GLatLng(38.661, -109.534);
+ map.setCenter(coordinates, 11, G_PHYSICAL_MAP);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize a new icon, based on the GMaps default but in white
+ whiteIcon = new GIcon(G_DEFAULT_ICON);
+ whiteIcon.image = '../markers/white.png';
+
+ // Add a marker to the map for the park entrance, using the white icon
+ coordinates = new GLatLng(38.6168, -109.61986);
+ entranceMarker = new GMarker(coordinates, {icon: whiteIcon});
+ map.addOverlay(entranceMarker);
+ GEvent.addListener(entranceMarker, 'click', entranceClick);
+
+ // Another new icon, also based on the GMaps default but this time in black
+ blackIcon = new GIcon(G_DEFAULT_ICON);
+ blackIcon.image = '../markers/black.png';
+
+ // Create two infowindow tabs for Delicate Arch using content from the XHTML
+ var tabs = [new GInfoWindowTab('Details',
+ document.getElementById('delicate_tab_details')),
+ new GInfoWindowTab('Photo',
+ document.getElementById('delicate_tab_photo'))];
+
+ // Add a map marker for the Delicate Arch trail using the tabs and black icon
+ coordinates = new GLatLng(38.73561, -109.52073);
+ delicateArchMarker = new GMarker(coordinates, {icon: blackIcon});
+ delicateArchMarker.bindInfoWindowTabs(tabs, {maxWidth: 300});
+ map.addOverlay(delicateArchMarker);
+
+ // Two more infowindow tabs, for the Windows trail...
+ tabs = [new GInfoWindowTab('Details',
+ document.getElementById('windows_tab_details')),
+ new GInfoWindowTab('Photo',
+ document.getElementById('windows_tab_photo'))];
+
+ // ...and a map marker for the Windows trail as well, again using the black icon
+ coordinates = new GLatLng(38.68725, -109.53712);
+ windowsMarker = new GMarker(coordinates, {icon: blackIcon});
+ windowsMarker.bindInfoWindowTabs(tabs, {maxWidth: 300});
+ map.addOverlay(windowsMarker);
+ }
+};
+
+function entranceClick()
+{
+ // entranceClick: Open a map detail infowindow showing the park entrance area
+ entranceMarker.showMapBlowup({mapType: G_NORMAL_MAP, zoomLevel: 14});
+};
diff --git a/chapter_04/listing_04_11.html b/chapter_04/listing_04_11.html
new file mode 100644
index 0000000..a71141c
--- /dev/null
+++ b/chapter_04/listing_04_11.html
@@ -0,0 +1,88 @@
+
+
+
+
+ Building Out Your Map Page
+
+
+
+
+
+
+
+ One of the classic hikes of any national park, this scramble across
+ exposed slickrock leads to an open sandstone bowl with stunning views of
+ the freestanding Delicate Arch and the La Sal Mountains beyond.
+ Especially fabulous at sunset!
+
+
+ Carry at least 1 quart of water per person. Good balance is a plus as
+ much of the hike is on sloping sandstone.
+
+
+
+
Delicate Arch at Sunset
+
+
+
+
+
The Windows trail
+
+
Distance
1 mile (1.6 km) round trip
+
Time
½ to 1 hour
+
Difficulty
Easy
+
+
+ A fairly short, level walk that takes in several major arches, also
+ providing good views across the singular landscape of the park. The same
+ parking area gives access to the short trail to Double Arch.
+
+
+ Suitable for all abilities. Initial section is wheelchair-accessible.
+
+
+
+
North Window Arch
+
+
+
+
+
\ No newline at end of file
diff --git a/chapter_04/listing_04_12.js b/chapter_04/listing_04_12.js
new file mode 100644
index 0000000..20de9e9
--- /dev/null
+++ b/chapter_04/listing_04_12.js
@@ -0,0 +1,91 @@
+// Declare variables for later use
+var map;
+var whiteIcon, blackIcon;
+var entranceMarker, delicateArchMarker, windowsMarker;
+var geoXml;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport, based on center coordinates and zoom level
+ var coordinates = new GLatLng(38.661, -109.534);
+ map.setCenter(coordinates, 11, G_PHYSICAL_MAP);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize a new icon, based on the GMaps default but in white
+ whiteIcon = new GIcon(G_DEFAULT_ICON);
+ whiteIcon.image = '../markers/white.png';
+
+ // Add a marker to the map for the park entrance, using the white icon
+ coordinates = new GLatLng(38.6168, -109.61986);
+ entranceMarker = new GMarker(coordinates, {icon: whiteIcon});
+ map.addOverlay(entranceMarker);
+ GEvent.addListener(entranceMarker, 'click', entranceClick);
+
+ // Another new icon, also based on the GMaps default but this time in black
+ blackIcon = new GIcon(G_DEFAULT_ICON);
+ blackIcon.image = '../markers/black.png';
+
+ // Create two infowindow tabs for Delicate Arch using content from the XHTML
+ var tabs = [new GInfoWindowTab('Details',
+ document.getElementById('delicate_tab_details')),
+ new GInfoWindowTab('Photo',
+ document.getElementById('delicate_tab_photo'))];
+
+ // Add a map marker for the Delicate Arch trail using the tabs and black icon
+ coordinates = new GLatLng(38.73561, -109.52073);
+ delicateArchMarker = new GMarker(coordinates, {icon: blackIcon});
+ delicateArchMarker.bindInfoWindowTabs(tabs, {maxWidth: 300});
+ map.addOverlay(delicateArchMarker);
+
+ // Two more infowindow tabs, for the Windows trail...
+ tabs = [new GInfoWindowTab('Details',
+ document.getElementById('windows_tab_details')),
+ new GInfoWindowTab('Photo',
+ document.getElementById('windows_tab_photo'))];
+
+ // ...and a map marker for the Windows trail as well, again using the black icon
+ coordinates = new GLatLng(38.68725, -109.53712);
+ windowsMarker = new GMarker(coordinates, {icon: blackIcon});
+ windowsMarker.bindInfoWindowTabs(tabs, {maxWidth: 300});
+ map.addOverlay(windowsMarker);
+
+ // Create a geodata overlay for nearby campgrounds and add it to the map
+ geoXml = new GGeoXml(
+ 'http://www.satellitefriendly.com/services/popular_network.kml');
+ map.addOverlay(geoXml);
+ }
+};
+
+function entranceClick()
+{
+ // entranceClick: Open a map detail infowindow showing the park entrance area
+ entranceMarker.showMapBlowup({mapType: G_NORMAL_MAP, zoomLevel: 14});
+};
+
+function toggleCampgrounds()
+{
+ // toggleCampgrounds: Turn the campground geodata overlay on or off
+ if (document.getElementById('show_campgrounds').checked)
+ geoXml.show();
+ else
+ geoXml.hide();
+};
diff --git a/chapter_04/listing_04_13.html b/chapter_04/listing_04_13.html
new file mode 100644
index 0000000..799872b
--- /dev/null
+++ b/chapter_04/listing_04_13.html
@@ -0,0 +1,93 @@
+
+
+
+
+ Building Out Your Map Page
+
+
+
+
+
+
+
+ One of the classic hikes of any national park, this scramble across
+ exposed slickrock leads to an open sandstone bowl with stunning views of
+ the freestanding Delicate Arch and the La Sal Mountains beyond.
+ Especially fabulous at sunset!
+
+
+ Carry at least 1 quart of water per person. Good balance is a plus as
+ much of the hike is on sloping sandstone.
+
+
+
+
Delicate Arch at Sunset
+
+
+
+
+
The Windows trail
+
+
Distance
1 mile (1.6 km) round trip
+
Time
½ to 1 hour
+
Difficulty
Easy
+
+
+ A fairly short, level walk that takes in several major arches, also
+ providing good views across the singular landscape of the park. The same
+ parking area gives access to the short trail to Double Arch.
+
+
+ Suitable for all abilities. Initial section is wheelchair-accessible.
+
+
+
+
North Window Arch
+
+
+
+
+
\ No newline at end of file
diff --git a/chapter_04/listing_04_14.js b/chapter_04/listing_04_14.js
new file mode 100644
index 0000000..a17bbd1
--- /dev/null
+++ b/chapter_04/listing_04_14.js
@@ -0,0 +1,93 @@
+// Declare variables for later use
+var map;
+var whiteIcon, blackIcon;
+var entranceMarker, delicateArchMarker, windowsMarker;
+var geoXml;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object, including the Google Earth maptype
+ map = new GMap2(mapDiv, {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP,
+ G_HYBRID_MAP, G_PHYSICAL_MAP, G_SATELLITE_3D_MAP]});
+ var coordinates = new GLatLng(38.661, -109.534);
+ map.setCenter(coordinates, 11, G_PHYSICAL_MAP);
+
+ // Add the standard map controls, moving the scale control to the upper-right
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl(),
+ new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(6, 31)));
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Also add a local search control to the map
+ map.enableGoogleBar();
+
+ // Initialize a new icon, based on the GMaps default but in white
+ whiteIcon = new GIcon(G_DEFAULT_ICON);
+ whiteIcon.image = '../markers/white.png';
+
+ // Add a marker to the map for the park entrance, using the white icon
+ coordinates = new GLatLng(38.6168, -109.61986);
+ entranceMarker = new GMarker(coordinates, {icon: whiteIcon});
+ map.addOverlay(entranceMarker);
+ GEvent.addListener(entranceMarker, 'click', entranceClick);
+
+ // Another new icon, also based on the GMaps default but this time in black
+ blackIcon = new GIcon(G_DEFAULT_ICON);
+ blackIcon.image = '../markers/black.png';
+
+ // Create two infowindow tabs for Delicate Arch using content from the XHTML
+ var tabs = [new GInfoWindowTab('Details',
+ document.getElementById('delicate_tab_details')),
+ new GInfoWindowTab('Photo',
+ document.getElementById('delicate_tab_photo'))];
+
+ // Add a map marker for the Delicate Arch trail using the tabs and black icon
+ coordinates = new GLatLng(38.73561, -109.52073);
+ delicateArchMarker = new GMarker(coordinates, {icon: blackIcon});
+ delicateArchMarker.bindInfoWindowTabs(tabs, {maxWidth: 300});
+ map.addOverlay(delicateArchMarker);
+
+ // Two more infowindow tabs, for the Windows trail...
+ tabs = [new GInfoWindowTab('Details',
+ document.getElementById('windows_tab_details')),
+ new GInfoWindowTab('Photo',
+ document.getElementById('windows_tab_photo'))];
+
+ // ...and a map marker for the Windows trail as well, again using the black icon
+ coordinates = new GLatLng(38.68725, -109.53712);
+ windowsMarker = new GMarker(coordinates, {icon: blackIcon});
+ windowsMarker.bindInfoWindowTabs(tabs, {maxWidth: 300});
+ map.addOverlay(windowsMarker);
+
+ // Create a geodata overlay for nearby campgrounds and add it to the map
+ geoXml = new GGeoXml(
+ 'http://www.satellitefriendly.com/services/popular_network.kml');
+ map.addOverlay(geoXml);
+ }
+};
+
+function entranceClick()
+{
+ // entranceClick: Open a map detail infowindow showing the park entrance area
+ entranceMarker.showMapBlowup({mapType: G_NORMAL_MAP, zoomLevel: 14});
+};
+
+function toggleCampgrounds()
+{
+ // toggleCampgrounds: Turn the campground geodata overlay on or off
+ if (document.getElementById('show_campgrounds').checked)
+ geoXml.show();
+ else
+ geoXml.hide();
+};
diff --git a/chapter_04/window_arch.jpg b/chapter_04/window_arch.jpg
new file mode 100644
index 0000000..c85e0ff
Binary files /dev/null and b/chapter_04/window_arch.jpg differ
diff --git a/chapter_05/listing_05_01.html b/chapter_05/listing_05_01.html
new file mode 100644
index 0000000..813cc5e
--- /dev/null
+++ b/chapter_05/listing_05_01.html
@@ -0,0 +1,23 @@
+
+
+
+
+ Your Map and the Real World
+
+
+
+
+
+
+
+
Route Finder
+
+ Start Address
+
+
+
+
+
+
\ No newline at end of file
diff --git a/chapter_05/listing_05_02.js b/chapter_05/listing_05_02.js
new file mode 100644
index 0000000..e9015ca
--- /dev/null
+++ b/chapter_05/listing_05_02.js
@@ -0,0 +1,50 @@
+// Declare variables for later use
+var map;
+var geocoder;
+var startMarker;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport, based on center coordinates and zoom level
+ var coordinates = new GLatLng(37.75, -122.44);
+ map.setCenter(coordinates, 12);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize the geocoder object and tie it to the current map view
+ geocoder = new GClientGeocoder();
+ geocoder.setViewport(map.getBounds());
+ }
+};
+
+function geocode()
+{
+ // geocode: Call the Google geocoder with the address supplied by the user
+ var address = document.getElementById('start').value;
+ geocoder.getLatLng(address, afterGeocode);
+};
+
+function afterGeocode(coordinates)
+{
+ // afterGeocode: Callback function for the geocoder, showing the coords on the map
+ startMarker = new GMarker(coordinates);
+ map.addOverlay(startMarker);
+};
diff --git a/chapter_05/listing_05_03.js b/chapter_05/listing_05_03.js
new file mode 100644
index 0000000..1951b92
--- /dev/null
+++ b/chapter_05/listing_05_03.js
@@ -0,0 +1,68 @@
+// Declare variables for later use
+var map;
+var geocoder;
+var startMarker;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport, based on center coordinates and zoom level
+ var coordinates = new GLatLng(37.75, -122.44);
+ map.setCenter(coordinates, 12);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize the geocoder object and tie it to the current map view
+ geocoder = new GClientGeocoder();
+ geocoder.setViewport(map.getBounds());
+ }
+};
+
+function geocode()
+{
+ // geocode: Call the Google geocoder with the address supplied by the user
+ var address = document.getElementById('start').value;
+ geocoder.getLatLng(address, afterGeocode);
+};
+
+function afterGeocode(coordinates)
+{
+ // afterGeocode: Callback function for the geocoder, showing the coords on the map
+ if (coordinates == null)
+ alert('Address not found. Please try again.');
+ else if (!map.getBounds().contains(coordinates))
+ alert('Address not found in map area. Please try again.');
+ else
+ {
+ // Address was found
+ if (startMarker == null)
+ {
+ // This is the first time we've geocoded an address, so create the marker
+ startMarker = new GMarker(coordinates);
+ map.addOverlay(startMarker);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ startMarker.setPoint(coordinates);
+ }
+ }
+};
+
diff --git a/chapter_05/listing_05_04.html b/chapter_05/listing_05_04.html
new file mode 100644
index 0000000..3099288
--- /dev/null
+++ b/chapter_05/listing_05_04.html
@@ -0,0 +1,34 @@
+
+
+
+
+ Your Map and the Real World
+
+
+
+
+
+
+
+
Route Finder
+
+ Start Address
+
+
+
+
+ Destination
+
+
+
+
+
\ No newline at end of file
diff --git a/chapter_05/listing_05_05.js b/chapter_05/listing_05_05.js
new file mode 100644
index 0000000..6b8b4dd
--- /dev/null
+++ b/chapter_05/listing_05_05.js
@@ -0,0 +1,98 @@
+// Declare variables for later use
+var map;
+var geocoder;
+var startMarker;
+var finishMarker;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport, based on center coordinates and zoom level
+ var coordinates = new GLatLng(37.75, -122.44);
+ map.setCenter(coordinates, 12);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize the geocoder object and tie it to the current map view
+ geocoder = new GClientGeocoder();
+ geocoder.setViewport(map.getBounds());
+ }
+};
+
+function geocode()
+{
+ // geocode: Call the Google geocoder with the address supplied by the user
+ var address = document.getElementById('start').value;
+ geocoder.getLatLng(address, afterGeocode);
+};
+
+function afterGeocode(coordinates)
+{
+ // afterGeocode: Callback function for the geocoder, showing the coords on the map
+ if (coordinates == null)
+ alert('Address not found. Please try again.');
+ else if (!map.getBounds().contains(coordinates))
+ alert('Address not found in map area. Please try again.');
+ else
+ {
+ // Address was found
+ if (startMarker == null)
+ {
+ // This is the first time we've geocoded an address, so create the marker
+ startMarker = new GMarker(coordinates);
+ map.addOverlay(startMarker);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ startMarker.setPoint(coordinates);
+ }
+ }
+};
+
+function destinationChange()
+{
+ // destinationChange: Update destination marker from the drop-down list
+
+ // Extract the new destination from the drop-down
+ var finish = document.getElementById('finish');
+ var value = finish.options[finish.selectedIndex].value;
+ if (value != '')
+ {
+ // Valid destination: create a coordinates object from it
+ var coordinates = eval('new GLatLng(' + value + ')');
+
+ if (finishMarker == null)
+ {
+ // This is the first time the user has selected a destination
+ finishMarker = new GMarker(coordinates);
+ map.addOverlay(finishMarker);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ finishMarker.setPoint(coordinates);
+ }
+
+ // Ensure that the destination point is visible on the map
+ if (!map.getBounds().contains(coordinates))
+ map.panTo(coordinates);
+ }
+};
diff --git a/chapter_05/listing_05_06.html b/chapter_05/listing_05_06.html
new file mode 100644
index 0000000..da1e09f
--- /dev/null
+++ b/chapter_05/listing_05_06.html
@@ -0,0 +1,36 @@
+
+
+
+
+ Your Map and the Real World
+
+
+
+
+
+
+
+
Route Finder
+
+ Start Address
+
+
+
+
+ Destination
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/chapter_05/listing_05_07.js b/chapter_05/listing_05_07.js
new file mode 100644
index 0000000..469277c
--- /dev/null
+++ b/chapter_05/listing_05_07.js
@@ -0,0 +1,120 @@
+// Declare variables for later use
+var map;
+var geocoder;
+var startMarker;
+var finishMarker;
+var directions;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport, based on center coordinates and zoom level
+ var coordinates = new GLatLng(37.75, -122.44);
+ map.setCenter(coordinates, 12);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize the geocoder object and tie it to the current map view
+ geocoder = new GClientGeocoder();
+ geocoder.setViewport(map.getBounds());
+
+ // Initialize the driving directions object
+ var panel = document.getElementById('directions');
+ directions = new GDirections(map, panel);
+ }
+};
+
+function geocode()
+{
+ // geocode: Call the Google geocoder with the address supplied by the user
+ var address = document.getElementById('start').value;
+ geocoder.getLatLng(address, afterGeocode);
+};
+
+function afterGeocode(coordinates)
+{
+ // afterGeocode: Callback function for the geocoder, showing the coords on the map
+ if (coordinates == null)
+ alert('Address not found. Please try again.');
+ else if (!map.getBounds().contains(coordinates))
+ alert('Address not found in map area. Please try again.');
+ else
+ {
+ // Address was found
+ if (startMarker == null)
+ {
+ // This is the first time we've geocoded an address, so create the marker
+ startMarker = new GMarker(coordinates);
+ map.addOverlay(startMarker);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ startMarker.setPoint(coordinates);
+ }
+ }
+};
+
+function destinationChange()
+{
+ // destinationChange: Update destination marker from the drop-down list
+
+ // Extract the new destination from the drop-down
+ var finish = document.getElementById('finish');
+ var value = finish.options[finish.selectedIndex].value;
+ if (value != '')
+ {
+ // Valid destination: create a coordinates object from it
+ var coordinates = eval('new GLatLng(' + value + ')');
+
+ if (finishMarker == null)
+ {
+ // This is the first time the user has selected a destination
+ finishMarker = new GMarker(coordinates);
+ map.addOverlay(finishMarker);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ finishMarker.setPoint(coordinates);
+ }
+
+ // Ensure that the destination point is visible on the map
+ if (!map.getBounds().contains(coordinates))
+ map.panTo(coordinates);
+ }
+};
+
+function getDirections()
+{
+ // getDirections: Request driving directions from start to destination
+
+ if ((startMarker == null) || (finishMarker == null))
+ alert('Please select a starting address and destination for directions.');
+ else
+ {
+ // Collect the start and finish points as 'lat,lon' strings
+ var waypoints = [startMarker.getPoint().toUrlValue(),
+ finishMarker.getPoint().toUrlValue()];
+
+ // Load driving directions for those points
+ directions.loadFromWaypoints(waypoints);
+ }
+};
diff --git a/chapter_05/listing_05_08.html b/chapter_05/listing_05_08.html
new file mode 100644
index 0000000..df07f93
--- /dev/null
+++ b/chapter_05/listing_05_08.html
@@ -0,0 +1,41 @@
+
+
+
+
+ Your Map and the Real World
+
+
+
+
+
+
+
+
Route Finder
+
+ Start Address
+
+
+
+
+ Destination
+
+
+
+
+
+ Show Traffic
+
+
+
+
+
\ No newline at end of file
diff --git a/chapter_05/listing_05_09.js b/chapter_05/listing_05_09.js
new file mode 100644
index 0000000..dfcc4c2
--- /dev/null
+++ b/chapter_05/listing_05_09.js
@@ -0,0 +1,134 @@
+// Declare variables for later use
+var map;
+var geocoder;
+var startMarker;
+var finishMarker;
+var directions;
+var traffic;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport, based on center coordinates and zoom level
+ var coordinates = new GLatLng(37.75, -122.44);
+ map.setCenter(coordinates, 12);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize the geocoder object and tie it to the current map view
+ geocoder = new GClientGeocoder();
+ geocoder.setViewport(map.getBounds());
+
+ // Initialize the driving directions object
+ var panel = document.getElementById('directions');
+ directions = new GDirections(map, panel);
+
+ // Initialize the traffic object
+ traffic = new GTrafficOverlay();
+ map.addOverlay(traffic);
+ }
+};
+
+function geocode()
+{
+ // geocode: Call the Google geocoder with the address supplied by the user
+ var address = document.getElementById('start').value;
+ geocoder.getLatLng(address, afterGeocode);
+};
+
+function afterGeocode(coordinates)
+{
+ // afterGeocode: Callback function for the geocoder, showing the coords on the map
+ if (coordinates == null)
+ alert('Address not found. Please try again.');
+ else if (!map.getBounds().contains(coordinates))
+ alert('Address not found in map area. Please try again.');
+ else
+ {
+ // Address was found
+ if (startMarker == null)
+ {
+ // This is the first time we've geocoded an address, so create the marker
+ startMarker = new GMarker(coordinates);
+ map.addOverlay(startMarker);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ startMarker.setPoint(coordinates);
+ }
+ }
+};
+
+function destinationChange()
+{
+ // destinationChange: Update destination marker from the drop-down list
+
+ // Extract the new destination from the drop-down
+ var finish = document.getElementById('finish');
+ var value = finish.options[finish.selectedIndex].value;
+ if (value != '')
+ {
+ // Valid destination: create a coordinates object from it
+ var coordinates = eval('new GLatLng(' + value + ')');
+
+ if (finishMarker == null)
+ {
+ // This is the first time the user has selected a destination
+ finishMarker = new GMarker(coordinates);
+ map.addOverlay(finishMarker);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ finishMarker.setPoint(coordinates);
+ }
+
+ // Ensure that the destination point is visible on the map
+ if (!map.getBounds().contains(coordinates))
+ map.panTo(coordinates);
+ }
+};
+
+function getDirections()
+{
+ // getDirections: Request driving directions from start to destination
+
+ if ((startMarker == null) || (finishMarker == null))
+ alert('Please select a starting address and destination for directions.');
+ else
+ {
+ // Collect the start and finish points as 'lat,lon' strings
+ var waypoints = [startMarker.getPoint().toUrlValue(),
+ finishMarker.getPoint().toUrlValue()];
+
+ // Load driving directions for those points
+ directions.loadFromWaypoints(waypoints);
+ }
+};
+
+function toggleTraffic()
+{
+ // toggleTraffic: Turn the traffic overlay on or off
+ if (document.getElementById('show_traffic').checked)
+ traffic.show();
+ else
+ traffic.hide();
+};
diff --git a/chapter_05/listing_05_10.html b/chapter_05/listing_05_10.html
new file mode 100644
index 0000000..1f4d556
--- /dev/null
+++ b/chapter_05/listing_05_10.html
@@ -0,0 +1,42 @@
+
+
+
+
+ Your Map and the Real World
+
+
+
+
+
+
+
+
Route Finder
+
+ Start Address
+
+
+
+
+ Destination
+
+
+
+
+
+
+ Show Traffic
+
+
+
+
+
\ No newline at end of file
diff --git a/chapter_05/listing_05_11.js b/chapter_05/listing_05_11.js
new file mode 100644
index 0000000..b5d9745
--- /dev/null
+++ b/chapter_05/listing_05_11.js
@@ -0,0 +1,152 @@
+// Declare variables for later use
+var map;
+var geocoder;
+var startMarker;
+var finishMarker;
+var directions;
+var traffic;
+var streetviewClient;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport, based on center coordinates and zoom level
+ var coordinates = new GLatLng(37.75, -122.44);
+ map.setCenter(coordinates, 12);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize the geocoder object and tie it to the current map view
+ geocoder = new GClientGeocoder();
+ geocoder.setViewport(map.getBounds());
+
+ // Initialize the driving directions object
+ var panel = document.getElementById('directions');
+ directions = new GDirections(map, panel);
+
+ // Initialize the traffic object
+ traffic = new GTrafficOverlay();
+ map.addOverlay(traffic);
+
+ // Initialize the Street View controller object
+ streetviewClient = new GStreetviewClient();
+ }
+};
+
+function geocode()
+{
+ // geocode: Call the Google geocoder with the address supplied by the user
+ var address = document.getElementById('start').value;
+ geocoder.getLatLng(address, afterGeocode);
+};
+
+function afterGeocode(coordinates)
+{
+ // afterGeocode: Callback function for the geocoder, showing the coords on the map
+ if (coordinates == null)
+ alert('Address not found. Please try again.');
+ else if (!map.getBounds().contains(coordinates))
+ alert('Address not found in map area. Please try again.');
+ else
+ {
+ // Address was found
+ if (startMarker == null)
+ {
+ // This is the first time we've geocoded an address, so create the marker
+ startMarker = new GMarker(coordinates);
+ map.addOverlay(startMarker);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ startMarker.setPoint(coordinates);
+ }
+ }
+};
+
+function destinationChange()
+{
+ // destinationChange: Update destination marker from the drop-down list
+
+ // Extract the new destination from the drop-down
+ var finish = document.getElementById('finish');
+ var value = finish.options[finish.selectedIndex].value;
+ if (value != '')
+ {
+ // Valid destination: create a coordinates object from it
+ var coordinates = eval('new GLatLng(' + value + ')');
+
+ if (finishMarker == null)
+ {
+ // This is the first time the user has selected a destination
+ finishMarker = new GMarker(coordinates);
+ map.addOverlay(finishMarker);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ finishMarker.setPoint(coordinates);
+ }
+
+ // Ensure that the destination point is visible on the map
+ if (!map.getBounds().contains(coordinates))
+ map.panTo(coordinates);
+ }
+};
+
+function getDirections()
+{
+ // getDirections: Request driving directions from start to destination
+
+ if ((startMarker == null) || (finishMarker == null))
+ alert('Please select a starting address and destination for directions.');
+ else
+ {
+ // Collect the start and finish points as 'lat,lon' strings
+ var waypoints = [startMarker.getPoint().toUrlValue(),
+ finishMarker.getPoint().toUrlValue()];
+
+ // Load driving directions for those points
+ directions.loadFromWaypoints(waypoints);
+ }
+};
+
+function toggleTraffic()
+{
+ // toggleTraffic: Turn the traffic overlay on or off
+ if (document.getElementById('show_traffic').checked)
+ traffic.show();
+ else
+ traffic.hide();
+};
+
+function getView()
+{
+ // getView: Retrieve a Street View panorama for the selected destination
+
+ if (finishMarker == null)
+ alert('Please select a destination.');
+ else
+ {
+ // Retrieve the Street View panorama for the coordinates of the marker
+ var coordinates = finishMarker.getPoint();
+ streetviewClient.getNearestPanorama(coordinates, afterView);
+ }
+};
diff --git a/chapter_05/listing_05_12.js b/chapter_05/listing_05_12.js
new file mode 100644
index 0000000..abdcfc4
--- /dev/null
+++ b/chapter_05/listing_05_12.js
@@ -0,0 +1,177 @@
+// Declare variables for later use
+var map;
+var geocoder;
+var startMarker;
+var finishMarker;
+var directions;
+var traffic;
+var streetviewClient;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport, based on center coordinates and zoom level
+ var coordinates = new GLatLng(37.75, -122.44);
+ map.setCenter(coordinates, 12);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize the geocoder object and tie it to the current map view
+ geocoder = new GClientGeocoder();
+ geocoder.setViewport(map.getBounds());
+
+ // Initialize the driving directions object
+ var panel = document.getElementById('directions');
+ directions = new GDirections(map, panel);
+
+ // Initialize the traffic object
+ traffic = new GTrafficOverlay();
+ map.addOverlay(traffic);
+
+ // Initialize the Street View controller object
+ streetviewClient = new GStreetviewClient();
+ }
+};
+
+function geocode()
+{
+ // geocode: Call the Google geocoder with the address supplied by the user
+ var address = document.getElementById('start').value;
+ geocoder.getLatLng(address, afterGeocode);
+};
+
+function afterGeocode(coordinates)
+{
+ // afterGeocode: Callback function for the geocoder, showing the coords on the map
+ if (coordinates == null)
+ alert('Address not found. Please try again.');
+ else if (!map.getBounds().contains(coordinates))
+ alert('Address not found in map area. Please try again.');
+ else
+ {
+ // Address was found
+ if (startMarker == null)
+ {
+ // This is the first time we've geocoded an address, so create the marker
+ startMarker = new GMarker(coordinates);
+ map.addOverlay(startMarker);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ startMarker.setPoint(coordinates);
+ }
+ }
+};
+
+function destinationChange()
+{
+ // destinationChange: Update destination marker from the drop-down list
+
+ // Extract the new destination from the drop-down
+ var finish = document.getElementById('finish');
+ var value = finish.options[finish.selectedIndex].value;
+ if (value != '')
+ {
+ // Valid destination: create a coordinates object from it
+ var coordinates = eval('new GLatLng(' + value + ')');
+
+ if (finishMarker == null)
+ {
+ // This is the first time the user has selected a destination
+ finishMarker = new GMarker(coordinates);
+ map.addOverlay(finishMarker);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ finishMarker.setPoint(coordinates);
+ }
+
+ // Ensure that the destination point is visible on the map
+ if (!map.getBounds().contains(coordinates))
+ map.panTo(coordinates);
+ }
+};
+
+function getDirections()
+{
+ // getDirections: Request driving directions from start to destination
+
+ if ((startMarker == null) || (finishMarker == null))
+ alert('Please select a starting address and destination for directions.');
+ else
+ {
+ // Collect the start and finish points as 'lat,lon' strings
+ var waypoints = [startMarker.getPoint().toUrlValue(),
+ finishMarker.getPoint().toUrlValue()];
+
+ // Load driving directions for those points
+ directions.loadFromWaypoints(waypoints);
+ }
+};
+
+function toggleTraffic()
+{
+ // toggleTraffic: Turn the traffic overlay on or off
+ if (document.getElementById('show_traffic').checked)
+ traffic.show();
+ else
+ traffic.hide();
+};
+
+function getView()
+{
+ // getView: Retrieve a Street View panorama for the selected destination
+
+ if (finishMarker == null)
+ alert('Please select a destination.');
+ else
+ {
+ // Retrieve the Street View panorama for the coordinates of the marker
+ var coordinates = finishMarker.getPoint();
+ streetviewClient.getNearestPanorama(coordinates, afterView);
+ }
+};
+
+function afterView(streetviewData)
+{
+ // afterView: Callback function for Street View panorama retrieval
+
+ if (streetviewData.code == G_GEO_SUCCESS)
+ {
+ // Create a DHTML element to contain the panorama
+ var streetViewer = document.createElement('div');
+
+ // Create the Street View panorama object
+ var panorama = new GStreetviewPanorama(streetViewer);
+
+ // Extract the precise lat/lon coordinates from the Street View data object
+ var coordinates = new GLatLng(streetviewData.location.lat,
+ streetviewData.location.lng);
+
+ // Tell the panorama object to display view for those coordinates
+ panorama.setLocationAndPOV(coordinates, streetviewData.location.pov);
+
+ // Open an infowindow with the panorama container element
+ streetViewer.className = 'streetViewer';
+ finishMarker.openInfoWindow(streetViewer);
+ }
+};
diff --git a/chapter_05/listing_05_13.css b/chapter_05/listing_05_13.css
new file mode 100644
index 0000000..ee76947
--- /dev/null
+++ b/chapter_05/listing_05_13.css
@@ -0,0 +1,39 @@
+html {
+ height: 100%;
+}
+
+body {
+ height: 100%;
+ margin: 0;
+ font-family: sans-serif;
+ font-size: 90%;
+}
+
+#map {
+ width: 74%;
+ height: 100%;
+}
+
+#sidebar {
+ position: absolute;
+ left: 74%;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ overflow: auto;
+ padding: 1em;
+}
+
+h1 {
+ margin: 0;
+ font-size: 220%;
+}
+h2 {
+ margin: 0;
+ font-size: 125%;
+}
+
+.streetViewer {
+ width: 350px;
+ height: 250px;
+}
diff --git a/chapter_05/listing_05_14.js b/chapter_05/listing_05_14.js
new file mode 100644
index 0000000..b273d22
--- /dev/null
+++ b/chapter_05/listing_05_14.js
@@ -0,0 +1,182 @@
+// Declare variables for later use
+var map;
+var geocoder;
+var startMarker;
+var finishMarker;
+var directions;
+var traffic;
+var streetviewClient;
+var ads;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport, based on center coordinates and zoom level
+ var coordinates = new GLatLng(37.75, -122.44);
+ map.setCenter(coordinates, 12);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize the geocoder object and tie it to the current map view
+ geocoder = new GClientGeocoder();
+ geocoder.setViewport(map.getBounds());
+
+ // Initialize the driving directions object
+ var panel = document.getElementById('directions');
+ directions = new GDirections(map, panel);
+
+ // Initialize the traffic object
+ traffic = new GTrafficOverlay();
+ map.addOverlay(traffic);
+
+ // Initialize the Street View controller object
+ streetviewClient = new GStreetviewClient();
+
+ // Initialize the map advertising object
+ ads = new GAdsManager(map, 'google', {maxAdsOnMap: 10});
+ ads.enable();
+ }
+};
+
+function geocode()
+{
+ // geocode: Call the Google geocoder with the address supplied by the user
+ var address = document.getElementById('start').value;
+ geocoder.getLatLng(address, afterGeocode);
+};
+
+function afterGeocode(coordinates)
+{
+ // afterGeocode: Callback function for the geocoder, showing the coords on the map
+ if (coordinates == null)
+ alert('Address not found. Please try again.');
+ else if (!map.getBounds().contains(coordinates))
+ alert('Address not found in map area. Please try again.');
+ else
+ {
+ // Address was found
+ if (startMarker == null)
+ {
+ // This is the first time we've geocoded an address, so create the marker
+ startMarker = new GMarker(coordinates);
+ map.addOverlay(startMarker);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ startMarker.setPoint(coordinates);
+ }
+ }
+};
+
+function destinationChange()
+{
+ // destinationChange: Update destination marker from the drop-down list
+
+ // Extract the new destination from the drop-down
+ var finish = document.getElementById('finish');
+ var value = finish.options[finish.selectedIndex].value;
+ if (value != '')
+ {
+ // Valid destination: create a coordinates object from it
+ var coordinates = eval('new GLatLng(' + value + ')');
+
+ if (finishMarker == null)
+ {
+ // This is the first time the user has selected a destination
+ finishMarker = new GMarker(coordinates);
+ map.addOverlay(finishMarker);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ finishMarker.setPoint(coordinates);
+ }
+
+ // Ensure that the destination point is visible on the map
+ if (!map.getBounds().contains(coordinates))
+ map.panTo(coordinates);
+ }
+};
+
+function getDirections()
+{
+ // getDirections: Request driving directions from start to destination
+
+ if ((startMarker == null) || (finishMarker == null))
+ alert('Please select a starting address and destination for directions.');
+ else
+ {
+ // Collect the start and finish points as 'lat,lon' strings
+ var waypoints = [startMarker.getPoint().toUrlValue(),
+ finishMarker.getPoint().toUrlValue()];
+
+ // Load driving directions for those points
+ directions.loadFromWaypoints(waypoints);
+ }
+};
+
+function toggleTraffic()
+{
+ // toggleTraffic: Turn the traffic overlay on or off
+ if (document.getElementById('show_traffic').checked)
+ traffic.show();
+ else
+ traffic.hide();
+};
+
+function getView()
+{
+ // getView: Retrieve a Street View panorama for the selected destination
+
+ if (finishMarker == null)
+ alert('Please select a destination.');
+ else
+ {
+ // Retrieve the Street View panorama for the coordinates of the marker
+ var coordinates = finishMarker.getPoint();
+ streetviewClient.getNearestPanorama(coordinates, afterView);
+ }
+};
+
+function afterView(streetviewData)
+{
+ // afterView: Callback function for Street View panorama retrieval
+
+ if (streetviewData.code == G_GEO_SUCCESS)
+ {
+ // Create a DHTML element to contain the panorama
+ var streetViewer = document.createElement('div');
+
+ // Create the Street View panorama object
+ var panorama = new GStreetviewPanorama(streetViewer);
+
+ // Extract the precise lat/lon coordinates from the Street View data object
+ var coordinates = new GLatLng(streetviewData.location.lat,
+ streetviewData.location.lng);
+
+ // Tell the panorama object to display view for those coordinates
+ panorama.setLocationAndPOV(coordinates, streetviewData.location.pov);
+
+ // Open an infowindow with the panorama container element
+ streetViewer.className = 'streetViewer';
+ finishMarker.openInfoWindow(streetViewer);
+ }
+};
diff --git a/chapter_07/listing_07_01.xml b/chapter_07/listing_07_01.xml
new file mode 100644
index 0000000..fdf4c58
--- /dev/null
+++ b/chapter_07/listing_07_01.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ h1 {
+ font-size: 120%;
+ }
+
+
+
New York City
+
Hello, World!
+
+
+ ]]>
+
\ No newline at end of file
diff --git a/chapter_07/listing_07_02.xml b/chapter_07/listing_07_02.xml
new file mode 100644
index 0000000..aa556c8
--- /dev/null
+++ b/chapter_07/listing_07_02.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+ p {
+ font-size: 90%;
+ }
+
+
+
+ The latest 50 Wikipedia entries with locations, from
+ placeopedia.com.
+
+
+
+ ]]>
+
\ No newline at end of file
diff --git a/chapter_07/screenshot.png b/chapter_07/screenshot.png
new file mode 100644
index 0000000..4cc072c
Binary files /dev/null and b/chapter_07/screenshot.png differ
diff --git a/chapter_07/thumbnail.png b/chapter_07/thumbnail.png
new file mode 100644
index 0000000..19ada37
Binary files /dev/null and b/chapter_07/thumbnail.png differ
diff --git a/chapter_08/center_scr.png b/chapter_08/center_scr.png
new file mode 100644
index 0000000..05f6216
Binary files /dev/null and b/chapter_08/center_scr.png differ
diff --git a/chapter_08/center_thm.png b/chapter_08/center_thm.png
new file mode 100644
index 0000000..a2015da
Binary files /dev/null and b/chapter_08/center_thm.png differ
diff --git a/chapter_08/geoname_scr.png b/chapter_08/geoname_scr.png
new file mode 100644
index 0000000..242485c
Binary files /dev/null and b/chapter_08/geoname_scr.png differ
diff --git a/chapter_08/geoname_thm.png b/chapter_08/geoname_thm.png
new file mode 100644
index 0000000..bd42fea
Binary files /dev/null and b/chapter_08/geoname_thm.png differ
diff --git a/chapter_08/listing_08_01.xml b/chapter_08/listing_08_01.xml
new file mode 100644
index 0000000..2401ee7
--- /dev/null
+++ b/chapter_08/listing_08_01.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ p {
+ font-size: 90%;
+ }
+
+
+
+ The latest Wikipedia entries with locations, from
+ placeopedia.com.
+
+
+
+
+
This mapplet is an example from the book:
+
+ ]]>
+
\ No newline at end of file
diff --git a/chapter_08/listing_08_02.js b/chapter_08/listing_08_02.js
new file mode 100644
index 0000000..855ca61
--- /dev/null
+++ b/chapter_08/listing_08_02.js
@@ -0,0 +1,22 @@
+var timeout;
+var clickCoords;
+
+function mapClick(overlay, coordinates)
+{
+ if (overlay != null)
+ // Click wasn't on "empty" map space, so don't go any further
+ return;
+
+ if (timeout == null)
+ {
+ // The first click we've had recently => start a timeout for the lookup
+ clickCoords = coordinates;
+ timeout = setTimeout('placeMarker()', 500);
+ }
+ else
+ {
+ // Second click means it's a double-click, so cancel the lookup timeout
+ clearTimeout(timeout)
+ timeout = null;
+ }
+};
\ No newline at end of file
diff --git a/chapter_08/listing_08_03.js b/chapter_08/listing_08_03.js
new file mode 100644
index 0000000..784f01b
--- /dev/null
+++ b/chapter_08/listing_08_03.js
@@ -0,0 +1,15 @@
+function placeMarker()
+{
+ if (marker == null)
+ {
+ // Marker doesn't exist yet, so create it now
+ marker = new GMarker(clickCoords);
+ map.addOverlay(marker);
+ }
+ else
+ // Move the marker to the new coordinates
+ marker.setPoint(clickCoords);
+
+ // Prepare for the next click
+ timeout = null;
+};
diff --git a/chapter_08/listing_08_04.js b/chapter_08/listing_08_04.js
new file mode 100644
index 0000000..fbeaf0d
--- /dev/null
+++ b/chapter_08/listing_08_04.js
@@ -0,0 +1,20 @@
+function placeMarker()
+{
+ if (marker == null)
+ {
+ // Marker doesn't exist yet, so create it now
+ marker = new GMarker(clickCoords);
+ map.addOverlay(marker);
+ }
+ else
+ // Move the marker to the new coordinates
+ marker.setPoint(clickCoords);
+
+ // Build and retrieve the placename lookup URL from the clicked coordinates
+ var url = 'http://ws.geonames.org/findNearbyPlaceNameJSON?lat=' +
+ clickCoords.lat() + '&lng=' + clickCoords.lng();
+ _IG_FetchContent(url, afterGeoname);
+
+ // Prepare for the next click
+ timeout = null;
+};
diff --git a/chapter_08/listing_08_05.js b/chapter_08/listing_08_05.js
new file mode 100644
index 0000000..17270bd
--- /dev/null
+++ b/chapter_08/listing_08_05.js
@@ -0,0 +1,23 @@
+function afterGeoname(responseText)
+{
+ // Evaluate the JSON response to extract the data from it
+ var responseData = json_parse(responseText);
+
+ if (responseData.geonames.length == 0)
+ {
+ // No place name found; let the user know with a simple message
+ var content = 'No nearby place name found.';
+ }
+ else
+ {
+ // Place name found succesfully; build it into an infowindow
+ var place = responseData.geonames[0];
+ var miles = parseFloat(place.distance) / 1.61;
+ var content = miles.toFixed(2) + ' miles from ' + place.name + ', ' +
+ place.adminName1 + ', ' + place.countryName;
+ }
+
+ // Attach the infowindow content to the marker, and also show it now
+ marker.openInfoWindow(content);
+ marker.bindInfoWindow(content);
+};
diff --git a/chapter_08/listing_08_06.xml b/chapter_08/listing_08_06.xml
new file mode 100644
index 0000000..bfc6a35
--- /dev/null
+++ b/chapter_08/listing_08_06.xml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+ p {
+ font-size: 90%;
+ }
+
+
+
+ Click anywhere on the map to get the name of the nearest populated place.
+ Lookup services provided by GeoNames.org.
+
+
+
+
+
+
This mapplet is an example from the book:
+
+ ]]>
+
diff --git a/chapter_08/listing_08_07.js b/chapter_08/listing_08_07.js
new file mode 100644
index 0000000..c8fad3c
--- /dev/null
+++ b/chapter_08/listing_08_07.js
@@ -0,0 +1,26 @@
+// Declare variables for later use
+var moving = false;
+var lastCenter = new GLatLng(90, 0);
+
+// Initialize the map
+var map = new GMap2();
+
+// Create the center "crosshair" overlay
+var crosshair = new GScreenOverlay(
+ 'http://sterlingudell.com/bgmm/markers/crosshair.png', // image URL
+ new GScreenPoint(0.5, 0.5, 'fraction', 'fraction'), // screen offset
+ new GScreenPoint(11, 12, 'pixel', 'pixel'), // overlay offset
+ new GScreenSize(24, 24, 'pixel', 'pixel') // overlay size
+);
+map.addOverlay(crosshair);
+
+// Attach several events to be called when the map moves
+GEvent.addListener(map, 'movestart', mapMoveStart);
+GEvent.addListener(map, 'moveend', mapMoveEnd);
+GEvent.addListener(map, 'zoomend', mapZoomEnd);
+
+// Initialize the center display
+map.getCenterAsync(afterGetCenter);
+
+// Adjust the height of the sidebar display
+_IG_AdjustIFrameHeight();
diff --git a/chapter_08/listing_08_08.js b/chapter_08/listing_08_08.js
new file mode 100644
index 0000000..5119160
--- /dev/null
+++ b/chapter_08/listing_08_08.js
@@ -0,0 +1,15 @@
+function mapMoveStart()
+{
+ moving = true;
+ map.getCenterAsync(afterGetCenter);
+};
+
+function mapMoveEnd()
+{
+ moving = false;
+};
+
+function mapZoomEnd()
+{
+ map.getCenterAsync(afterGetCenter);
+};
diff --git a/chapter_08/listing_08_09.js b/chapter_08/listing_08_09.js
new file mode 100644
index 0000000..90fc556
--- /dev/null
+++ b/chapter_08/listing_08_09.js
@@ -0,0 +1,41 @@
+function afterGetCenter(coordinates)
+{
+ if (!coordinates.equals(lastCenter))
+ {
+ // Map has moved since the last time we checked
+
+ // Save the new coordinates, so we can tell next time if it's moved
+ lastCenter = coordinates;
+
+ // Reformat the map center latitude as a more readable string
+
+ var latitude = Math.abs(coordinates.lat());
+ latitude = latitude.toFixed(5);
+ latitude = latitude + '? ';
+
+ if (coordinates.lat() > 0)
+ latitude = latitude + 'N';
+ else if (coordinates.lat() < 0)
+ latitude = latitude + 'S';
+
+ // Ditto for longitude
+
+ var longitude = Math.abs(coordinates.lng());
+ longitude = longitude.toFixed(5);
+ longitude = longitude + '? ';
+
+ if (coordinates.lng() > 0)
+ longitude = longitude + 'E';
+ else if (coordinates.lng() < 0)
+ longitude = longitude + 'W';
+
+ var centerDisplay = 'Map center: ' + latitude + ', ' + longitude;
+
+ // Set the mapplet's title to include the formatted center coordinates
+ _IG_SetTitle(centerDisplay);
+ }
+
+ if (moving)
+ // Map is still moving, so carry on updating the center display
+ map.getCenterAsync(afterGetCenter);
+};
diff --git a/chapter_08/listing_08_10.xml b/chapter_08/listing_08_10.xml
new file mode 100644
index 0000000..4271684
--- /dev/null
+++ b/chapter_08/listing_08_10.xml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+ function mapMoveStart()
+ {
+ // mapMoveStart: event handler to initiate the recentering process
+ moving = true;
+ map.getCenterAsync(afterGetCenter);
+ };
+
+ function mapMoveEnd()
+ {
+ // mapMoveEnd: event handler to stop recentering when map stops moving
+ moving = false;
+ };
+
+ function mapZoomEnd()
+ {
+ // mapZoomEnd: also trigger the recentering code when the map is zoomed
+ map.getCenterAsync(afterGetCenter);
+ };
+
+ function afterGetCenter(coordinates)
+ {
+ // afterGetCenter: callback to update title with current map center coords
+
+ if (coordinates.toUrlValue() != lastCenter.toUrlValue())
+ {
+ // Map has moved since the last time we checked
+
+ // Save the new coordinates, so we can tell next time if it's moved
+ lastCenter = coordinates;
+
+ // Reformat the map center latitude as a more readable string
+
+ var latitude = Math.abs(coordinates.lat());
+ latitude = latitude.toFixed(5);
+ latitude = latitude + '� ';
+
+ if (coordinates.lat() > 0)
+ latitude = latitude + 'N';
+ else if (coordinates.lat() < 0)
+ latitude = latitude + 'S';
+
+ // Ditto for longitude
+
+ var longitude = Math.abs(coordinates.lng());
+ longitude = longitude.toFixed(5);
+ longitude = longitude + '� ';
+
+ if (coordinates.lng() > 0)
+ longitude = longitude + 'E';
+ else if (coordinates.lng() < 0)
+ longitude = longitude + 'W';
+
+ var centerDisplay = 'Map center: ' + latitude + ', ' + longitude;
+
+ // Set the mapplet's title to include the formatted center coordinates
+ _IG_SetTitle(centerDisplay);
+ }
+
+ if (moving)
+ // Map is still moving, so carry on updating the center display
+ map.getCenterAsync(afterGetCenter);
+ };
+
+ // END FUNCTION DECLARATIONS - BEGIN MAIN MAPPLET CODE
+
+ // Declare variables for later use
+ var moving = false;
+ var lastCenter = new GLatLng(90, 0);
+ var timeout;
+
+ // Initialize the map
+ var map = new GMap2();
+
+ // Create the center "crosshair" overlay
+ var crosshair = new GScreenOverlay(
+ 'http://sterlingudell.com/bgmm/markers/crosshair.png', // image URL
+ new GScreenPoint(0.5, 0.5, 'fraction', 'fraction'), // screen offset
+ new GScreenPoint(11, 12, 'pixel', 'pixel'), // overlay offset
+ new GScreenSize(24, 24, 'pixel', 'pixel') // overlay size
+ );
+ map.addOverlay(crosshair);
+
+ // Attach several events to be called when the map moves
+ GEvent.addListener(map, 'movestart', mapMoveStart);
+ GEvent.addListener(map, 'moveend', mapMoveEnd);
+ GEvent.addListener(map, 'zoomend', mapZoomEnd);
+
+ // Initialize the center display
+ map.getCenterAsync(afterGetCenter);
+
+ // Adjust the height of the sidebar display
+ _IG_AdjustIFrameHeight();
+
+
+
This mapplet is an example from the book:
+
+ ]]>
+
\ No newline at end of file
diff --git a/chapter_09/brewery_scr.png b/chapter_09/brewery_scr.png
new file mode 100644
index 0000000..192b706
Binary files /dev/null and b/chapter_09/brewery_scr.png differ
diff --git a/chapter_09/brewery_thm.png b/chapter_09/brewery_thm.png
new file mode 100644
index 0000000..37f40e6
Binary files /dev/null and b/chapter_09/brewery_thm.png differ
diff --git a/chapter_09/listing_09_01.js b/chapter_09/listing_09_01.js
new file mode 100644
index 0000000..054381b
--- /dev/null
+++ b/chapter_09/listing_09_01.js
@@ -0,0 +1,7 @@
+var myIcon = new GIcon(G_DEFAULT_ICON);
+myIcon.image = 'http://sterlingudell.com/bgmm/markers/music.png';
+myIcon.iconSize = new GSize(25, 40);
+myIcon.iconAnchor = new GPoint(12, 40);
+myIcon.infoWindowAnchor = new GPoint(12, 1);
+myIcon.shadow = 'http://sterlingudell.com/bgmm/markers/shadow.png';
+myIcon.shadowSize = new GSize(41, 40);
diff --git a/chapter_09/listing_09_02.html b/chapter_09/listing_09_02.html
new file mode 100644
index 0000000..04980ff
--- /dev/null
+++ b/chapter_09/listing_09_02.html
@@ -0,0 +1,35 @@
+
+
+
+
+ Geocoding Revisted
+
+
+
+
+
+
+
+
+ Find Your Location
+
+
+
+
diff --git a/chapter_09/listing_09_03.css b/chapter_09/listing_09_03.css
new file mode 100644
index 0000000..3da2b34
--- /dev/null
+++ b/chapter_09/listing_09_03.css
@@ -0,0 +1,17 @@
+label {
+ float: left;
+ width: 5em;
+}
+
+p {
+ clear: left;
+}
+
+#map {
+ float: right;
+ width: 400px;
+ height: 400px;
+ border: 1px solid;
+ overflow: none;
+}
+
diff --git a/chapter_09/listing_09_04.js b/chapter_09/listing_09_04.js
new file mode 100644
index 0000000..7838c6a
--- /dev/null
+++ b/chapter_09/listing_09_04.js
@@ -0,0 +1,30 @@
+function afterGeocode(coordinates)
+{
+ if (coordinates == null)
+ alert('Address not found. Please try again.');
+ else
+ {
+ // Address was found
+ if (marker == null)
+ {
+ // This is the first time we've geocoded an address, so create the marker
+ var iconOptions = {width: 24, height: 24, primaryColor: "#fffc1b"};
+ var myIcon = MapIconMaker.createMarkerIcon(iconOptions);
+ marker = new GMarker(coordinates, {icon: myIcon, draggable: true});
+ map.addOverlay(marker);
+
+ GEvent.addListener(marker, 'dragend', markerDragEnd);
+ GEvent.addListener(marker, 'dragstart', markerDragStart);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ marker.setPoint(coordinates);
+ }
+
+ map.setCenter(coordinates, 14);
+
+ marker.openInfoWindowHtml('Drag marker to exact location, then click Save.');
+ updatesaveCoordinates();
+ }
+};
diff --git a/chapter_09/listing_09_05.js b/chapter_09/listing_09_05.js
new file mode 100644
index 0000000..1492c37
--- /dev/null
+++ b/chapter_09/listing_09_05.js
@@ -0,0 +1,13 @@
+function markerDragStart()
+{
+ map.closeInfoWindow();
+};
+
+function markerDragEnd()
+{
+ saveCoordinates();
+
+ var content = 'Zoom in' +
+ ' if needed to place marker exactly, or click Save when done.';
+ marker.openInfoWindow(content);
+};
diff --git a/chapter_09/listing_09_06.js b/chapter_09/listing_09_06.js
new file mode 100644
index 0000000..9c8cf60
--- /dev/null
+++ b/chapter_09/listing_09_06.js
@@ -0,0 +1,100 @@
+// Declare variables for later use
+var map;
+var geocoder;
+var marker;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport
+ var coordinates = new GLatLng(39.8, -98.5);
+ map.setCenter(coordinates, 3, G_HYBRID_MAP);
+
+ // Add the standard map controls
+ map.addControl(new GSmallMapControl());
+ map.addControl(new GScaleControl(),
+ new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(6, 18)));
+ map.addControl(new GMapTypeControl(true));
+
+ // Initialize the geocoder object
+ geocoder = new GClientGeocoder();
+ }
+};
+
+function geocode()
+{
+ // geocode: Call the Google geocoder with the address supplied by the user
+ var address = document.getElementById('address').value;
+ geocoder.getLatLng(address, afterGeocode);
+};
+
+function afterGeocode(coordinates)
+{
+ // afterGeocode: Callback function for the geocoder, showing the coords on the map
+ if (coordinates == null)
+ alert('Address not found. Please try again.');
+ else
+ {
+ // Address was found
+ if (marker == null)
+ {
+ // This is the first time we've geocoded an address, so create the marker
+ var iconOptions = {width: 24, height: 24, primaryColor: "#fffc1b"};
+ var myIcon = MapIconMaker.createMarkerIcon(iconOptions);
+ marker = new GMarker(coordinates, {icon: myIcon, draggable: true});
+ map.addOverlay(marker);
+
+ GEvent.addListener(marker, 'dragend', markerDragEnd);
+ GEvent.addListener(marker, 'dragstart', markerDragStart);
+ }
+ else
+ {
+ // The marker already exists, just move it to the new coordinates
+ marker.setPoint(coordinates);
+ }
+
+ map.setCenter(coordinates, 14);
+
+ marker.openInfoWindowHtml('Drag marker to exact location, then click Save.');
+ saveCoordinates();
+ }
+};
+
+function markerDragStart()
+{
+ // markerDragStart: Close the infowindow when the marker is being dragged
+ map.closeInfoWindow();
+};
+
+function markerDragEnd()
+{
+ // markerDragEnd: Update the form coordinates and show more instructions
+
+ saveCoordinates();
+
+ var content = 'Zoom in' +
+ ' if needed to place marker exactly, or click Save when done.';
+ marker.openInfoWindow(content);
+};
+
+function saveCoordinates()
+{
+ // saveCoordinates: Copy the current marker coordinates into the form fields
+ var coordinates = marker.getPoint();
+ document.getElementById('latitude').value = coordinates.lat().toFixed(6);
+ document.getElementById('longitude').value = coordinates.lng().toFixed(6);
+};
+
diff --git a/chapter_09/listing_09_07.html b/chapter_09/listing_09_07.html
new file mode 100644
index 0000000..0cf3385
--- /dev/null
+++ b/chapter_09/listing_09_07.html
@@ -0,0 +1,20 @@
+
+
+
+
+ US State Capitals
+
+
+
+
+
+
+
+
+
US State Capitals
+
+
+
+
diff --git a/chapter_09/listing_09_08.js b/chapter_09/listing_09_08.js
new file mode 100644
index 0000000..feed136
--- /dev/null
+++ b/chapter_09/listing_09_08.js
@@ -0,0 +1,47 @@
+// Declare variables for later use
+var map;
+var geoXml;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport
+ var coordinates = new GLatLng(39.8, -98.5);
+ map.setCenter(coordinates, 4);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize a custom marker icon
+ var starIcon = new GIcon();
+ starIcon.image = '../markers/star.png';
+ starIcon.iconSize = new GSize(17, 17);
+ starIcon.iconAnchor = new GPoint(8, 8);
+ starIcon.infoWindowAnchor = new GPoint(12, 4);
+
+ // Initialize the KML processor
+ var url = 'state_capitals.kml';
+ var options = {sidebarid: 'list',
+ markeroptions: {icon: starIcon}};
+ geoXml = new EGeoXml(map, url, options);
+
+ // Load the KML
+ geoXml.parse();
+ }
+};
diff --git a/chapter_09/listing_09_09.xml b/chapter_09/listing_09_09.xml
new file mode 100644
index 0000000..8ec6d17
--- /dev/null
+++ b/chapter_09/listing_09_09.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+ p {
+ font-size: 90%;
+ }
+
+
+
+
+
+
+
+
This mapplet is an example from the book:
+
+ ]]>
+
\ No newline at end of file
diff --git a/chapter_09/listing_09_10.html b/chapter_09/listing_09_10.html
new file mode 100644
index 0000000..1fdec17
--- /dev/null
+++ b/chapter_09/listing_09_10.html
@@ -0,0 +1,21 @@
+
+
+
+
+ British Breweries
+
+
+
+
+
+
+
+
+
+
Breweries in Great Britain
+
+
+
+
diff --git a/chapter_09/listing_09_11.css b/chapter_09/listing_09_11.css
new file mode 100644
index 0000000..2cf39d6
--- /dev/null
+++ b/chapter_09/listing_09_11.css
@@ -0,0 +1,38 @@
+html {
+ height: 100%;
+}
+
+body {
+ height: 100%;
+ margin: 0;
+ font-family: sans-serif;
+ font-size: 90%;
+}
+
+#map {
+ width: 70%;
+ height: 100%;
+}
+
+#sidebar {
+ position: absolute;
+ left: 70%;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ overflow: auto;
+ padding: 1em;
+}
+
+h1 {
+ margin: 0;
+ font-size: 100%;
+}
+
+ul {
+ padding-left: 1em;
+}
+li {
+ padding-left: 0em;
+}
+
diff --git a/chapter_09/listing_09_12.js b/chapter_09/listing_09_12.js
new file mode 100644
index 0000000..a0ffa67
--- /dev/null
+++ b/chapter_09/listing_09_12.js
@@ -0,0 +1,60 @@
+// Declare variables for later use
+var map;
+var geoXml;
+var data = new Array();
+var markers = new Array();
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport
+ var coordinates = new GLatLng(53.6, -4.3);
+ map.setCenter(coordinates, 6);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize the KML processor
+ var url = 'uk_breweries.kml';
+ var options = {sidebarid: 'list', createmarker: addDataPoint, nozoom: true};
+ geoXml = new EGeoXml(map, url, options);
+
+ // Attach an event handler for after the KML is processed
+ GEvent.addListener(geoXml, 'parsed', xmlParsed);
+
+ // Load the KML
+ geoXml.parse();
+
+ // Attach an event to refresh the marker display whenever the map moves
+ GEvent.addListener(map, 'moveend', mapMoveEnd);
+ }
+};
+
+function addDataPoint(coordinates, name, description)
+{
+ // addDataPoint: save the data for a placemark found by the KML processor
+ var d = data.length;
+ data[d] = {coords: coordinates, title: name, details: description};
+};
+
+function xmlParsed()
+{
+ // xmlParsed: after KML processing, initialize the marker display
+ mapMoveEnd();
+};
\ No newline at end of file
diff --git a/chapter_09/listing_09_13.js b/chapter_09/listing_09_13.js
new file mode 100644
index 0000000..d1e8b60
--- /dev/null
+++ b/chapter_09/listing_09_13.js
@@ -0,0 +1,156 @@
+// Declare variables for later use
+var map;
+var geoXml;
+var data = new Array();
+var markers = new Array();
+var clicked;
+var current;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport
+ var coordinates = new GLatLng(53.6, -4.3);
+ map.setCenter(coordinates, 6);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize the KML processor
+ var url = 'uk_breweries.kml';
+ var options = {createmarker: addDataPoint, nozoom: true};
+ geoXml = new EGeoXml(map, url, options);
+
+ // Attach an event handler for after the KML is processed
+ GEvent.addListener(geoXml, 'parsed', xmlParsed);
+
+ // Load the KML
+ geoXml.parse();
+
+ // Attach an event to refresh the marker display whenever the map moves
+ GEvent.addListener(map, 'moveend', mapMoveEnd);
+ GEvent.addListener(map, 'infowindowopen', mapInfoWindowOpen);
+ GEvent.addListener(map, 'infowindowclose', mapInfoWindowClose);
+ }
+};
+
+function addDataPoint(coordinates, name, description)
+{
+ // addDataPoint: save the data for a placemark found by the KML processor
+ var d = data.length;
+ data[d] = {coords: coordinates, title: name, details: description};
+};
+
+function xmlParsed()
+{
+ // xmlParsed: after KML processing, initialize the marker display
+ mapMoveEnd();
+};
+
+function mapMoveEnd()
+{
+ // mapMoveEnd: refresh the marker display after the map has moved
+
+ // Get the map boundary coordinates
+ var mapBounds = map.getBounds();
+
+ // Don't refresh if the currently-selected marker is still in view
+ if (current != null)
+ {
+ if (mapBounds.contains(current))
+ return;
+ else
+ map.closeInfoWindow();
+ }
+
+ // Prepare to build new sidebar content by starting with a clean slate
+ var sidebarContent = '';
+
+ // Remove previous set of markers from the map and the array
+ for (var m = markers.length - 1; m >= 0; m--)
+ {
+ map.removeOverlay(markers[m]);
+ markers.splice(m, 1);
+ }
+
+ // Create a base icon
+ var numberIcon = new GIcon(G_DEFAULT_ICON);
+
+ // Look for data in the new map area
+ for (var d = 0; d < data.length; d++)
+ {
+ if (mapBounds.contains(data[d].coords))
+ {
+ // Map does contain this data point; create a marker and add it to the map
+ m = markers.length;
+ numberIcon.image =
+ 'http://gmaps-samples.googlecode.com/svn/trunk/markers/orange/marker' +
+ (m + 1) + '.png';
+ markers[m] = new GMarker(data[d].coords, {icon: numberIcon});
+ markers[m].data = data[d];
+ map.addOverlay(markers[m]);
+
+ // Also attach an event handler to show infowindow when marker is clicked
+ GEvent.addListener(markers[m], 'click',
+ new Function('showDetail(' + m + ')'));
+
+ // Create sidebar content for this data point, including click event handler
+ sidebarContent = sidebarContent +
+ '
';
+
+ if (m >= 19)
+ {
+ // We've reached 20 markers, so break out of the loop
+ sidebarContent = sidebarContent +
+ '
zoom in for more...
';
+ break;
+ }
+ }
+ }
+
+ if (markers.length == 0)
+ // No data points found in map boundaries
+ sidebarContent = '
No results found in map area. ' +
+ 'Try zooming out or moving the map.
';
+
+ // Move the new content into the sidebar
+ document.getElementById('list').innerHTML = sidebarContent;
+};
+
+function showDetail(m)
+{
+ // showDetail: open the infowindow for the given map marker
+ current = clicked = markers[m].data.coords;
+ markers[m].openInfoWindow(
+ '
' + markers[m].data.title + '' +
+ '
' + markers[m].data.details + '
');
+};
+
+function mapInfoWindowOpen()
+{
+ // mapInfoWindowOpen: set the variable that keeps track of the selected coords
+ current = clicked;
+};
+
+function mapInfoWindowClose()
+{
+ // mapInfoWindowClose: clear the variable that keeps track of the selected coords
+ current = null;
+};
diff --git a/chapter_09/listing_09_14.xml b/chapter_09/listing_09_14.xml
new file mode 100644
index 0000000..16bc68b
--- /dev/null
+++ b/chapter_09/listing_09_14.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+ ul {
+ font-size: 90%;
+ padding-left: 1em;
+ }
+ li {
+ padding-left: 0em;
+ }
+
+
+
+
+
+
+
+
+
This mapplet is an example from the book:
+
+ ]]>
+
\ No newline at end of file
diff --git a/chapter_09/listing_09_15.js b/chapter_09/listing_09_15.js
new file mode 100644
index 0000000..1bb381f
--- /dev/null
+++ b/chapter_09/listing_09_15.js
@@ -0,0 +1,159 @@
+// Declare variables for later use
+var map;
+var geoXml;
+var data = new Array();
+var markers = new Array();
+var clicked;
+var current;
+
+function loadMap()
+{
+ // loadMap: initialize the API and load the map onto the page
+
+ // Get the map container div
+ var mapDiv = document.getElementById('map');
+
+ // Confirm browser compatibility with the Maps API
+ if (!GBrowserIsCompatible())
+ mapDiv.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
+ else
+ {
+ // Initialize the core map object
+ map = new GMap2(mapDiv,
+ {mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]});
+
+ // Set the starting map viewport
+ var coordinates = new GLatLng(53.6, -4.3);
+ map.setCenter(coordinates, 6);
+
+ // Add the standard map controls
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GScaleControl());
+ map.addControl(new GOverviewMapControl());
+ map.addControl(new GMapTypeControl());
+
+ // Initialize the KML processor
+ var url = 'uk_breweries.kml';
+ var options = {createmarker: addDataPoint, nozoom: true};
+ geoXml = new EGeoXml(map, url, options);
+
+ // Attach an event handler for after the KML is processed
+ GEvent.addListener(geoXml, 'parsed', xmlParsed);
+
+ // Load the KML
+ geoXml.parse();
+
+ // Attach an event to refresh the marker display whenever the map moves
+ GEvent.addListener(map, 'moveend', mapMoveEnd);
+ GEvent.addListener(map, 'infowindowopen', mapInfoWindowOpen);
+ GEvent.addListener(map, 'infowindowclose', mapInfoWindowClose);
+ }
+};
+
+function addDataPoint(coordinates, name, description)
+{
+ // addDataPoint: save the data for a placemark found by the KML processor
+ var d = data.length;
+ data[d] = {coords: coordinates, title: name, details: description};
+};
+
+function xmlParsed()
+{
+ // xmlParsed: after KML processing, initialize the marker display
+ mapMoveEnd();
+};
+
+function mapMoveEnd()
+{
+ // mapMoveEnd: get the new map boundary coordinates for use in marker display
+ GAsync(map, 'getBounds', afterGetBounds);
+};
+
+function afterGetBounds(mapBounds)
+{
+ // afterGetBounds: refresh the marker display
+
+ // Don't refresh if the currently-selected marker is still in view
+ if (current != null)
+ {
+ if (mapBounds.contains(current))
+ return;
+ else
+ map.closeInfoWindow();
+ }
+
+ // Prepare to build new sidebar content by starting with a clean slate
+ var sidebarContent = '';
+
+ // Remove previous set of markers from the map and the array
+ for (var m = markers.length - 1; m >= 0; m--)
+ {
+ map.removeOverlay(markers[m]);
+ markers.splice(m, 1);
+ }
+
+ // Create a base icon
+ var numberIcon = new GIcon(G_DEFAULT_ICON);
+
+ // Look for data in the new map area
+ for (var d = 0; d < data.length; d++)
+ {
+ if (mapBounds.contains(data[d].coords))
+ {
+ // Map does contain this data point; create a marker and add it to the map
+ m = markers.length;
+ numberIcon.image =
+ 'http://gmaps-samples.googlecode.com/svn/trunk/markers/orange/marker' +
+ (m + 1) + '.png';
+ markers[m] = new GMarker(data[d].coords, {icon: numberIcon});
+ markers[m].data = data[d];
+ map.addOverlay(markers[m]);
+
+ // Also attach an event handler to show infowindow when marker is clicked
+ GEvent.addListener(markers[m], 'click',
+ new Function('showDetail(' + m + ')'));
+
+ // Create sidebar content for this data point, including click event handler
+ sidebarContent = sidebarContent +
+ '
';
+
+ if (m >= 19)
+ {
+ // We've reached 20 markers, so break out of the loop
+ sidebarContent = sidebarContent +
+ '
zoom in for more...
';
+ break;
+ }
+ }
+ }
+
+ if (markers.length == 0)
+ // No data points found in map boundaries
+ sidebarContent = '
No results found in map area. ' +
+ 'Try zooming out or moving the map.
';
+
+ // Move the new content into the sidebar
+ document.getElementById('list').innerHTML = sidebarContent;
+};
+
+function showDetail(m)
+{
+ // showDetail: open the infowindow for the given map marker
+ current = clicked = markers[m].data.coords;
+ markers[m].openInfoWindow(
+ '
', options);
+
+ // Add the marker to the recent-entries map
+ recentMap.addOverlay(thisMarker);
+
+ // Also create a list entry (alongside the map) with the icon & name
+ var recentRow = document.createElement('li');
+ recentRow.innerHTML =
+ '' + name;
+ document.getElementById('recent_list').appendChild(recentRow);
+
+ recentRow.onclick =
+ function ()
+ {
+ // A click on the list entry triggers a click on its associated marker
+ GEvent.trigger(thisMarker, 'click')
+ };
+};
+
+function getAnchor(iconUrl)
+{
+ if (iconUrl.indexOf('bus') > -1)
+ return new GPoint(16, 0);
+ else if (iconUrl.indexOf('trailer') > -1)
+ return new GPoint(19, 10);
+ else
+ return new GPoint(16, 6);
+};
+
diff --git a/chapter_11/listing_11_04.js b/chapter_11/listing_11_04.js
new file mode 100644
index 0000000..cc464e7
--- /dev/null
+++ b/chapter_11/listing_11_04.js
@@ -0,0 +1,23 @@
+function mapMoveEnd()
+{
+ // Get the map boundary coordinates
+ var mapBounds = map.getBounds();
+
+ // Parameterize the geodata URL based on those boundaries
+ geoXml.urls = [kmlUrl + 'BBOX=' +
+ mapBounds.getSouthWest().lng().toFixed(6) + ',' +
+ mapBounds.getSouthWest().lat().toFixed(6) + ',' +
+ mapBounds.getNorthEast().lng().toFixed(6) + ',' +
+ mapBounds.getNorthEast().lat().toFixed(6)];
+
+ // Load the KML - new markers will be added when it returns
+ geoXml.parse();
+
+ // Remove markers from display that are no longer visible
+ for (var m = markers.length - 1; m >= 0; m--)
+ if (!mapBounds.contains(markers[m].getPoint()))
+ removeDataPoint(m);
+
+ // Also clear starting location out of the URL
+ location.hash = '#';
+};
diff --git a/chapter_11/listing_11_05.js b/chapter_11/listing_11_05.js
new file mode 100644
index 0000000..8355030
--- /dev/null
+++ b/chapter_11/listing_11_05.js
@@ -0,0 +1,78 @@
+function addDataPoint(coordinates, name, description, style)
+{
+ // Check to see if this placemark is already displayed, and stop if it is
+ for (var m = markers.length - 1; m >= 0; m--)
+ {
+ if (markers[m].getPoint().equals(coordinates))
+ return;
+ }
+
+ // Create and initialize the icon from the style in the KML
+ var myIcon = new GIcon();
+ myIcon.image = geoXml.styles[style].image;
+ myIcon.iconSize = new GSize(32, 32);
+ if (myIcon.image.indexOf('circle') > -1)
+ {
+ // It's a cluster placemark
+ myIcon.shadow = '/images/icons/circle_shadow.png';
+ myIcon.shadowSize = new GSize(40, 40);
+ myIcon.iconAnchor = new GPoint(13, 13);
+ myIcon.infoWindowAnchor = new GPoint(13, 0);
+ }
+ else
+ {
+ // Not a cluster => an individual campground
+ myIcon.shadow = geoXml.styles[style].shadow;
+ myIcon.shadowSize = geoXml.styles[style].shadowSize;
+ myIcon.iconAnchor = new GPoint(16, 28);
+ myIcon.infoWindowAnchor = getAnchor(myIcon.image);
+ }
+
+ // Create a marker for this data point
+ var options = {icon: myIcon, title: name};
+ var thisMarker = new GMarker(coordinates, options);
+ markers.push(thisMarker);
+
+ // Some different handling for clusters and campgrounds
+ if (myIcon.image.indexOf('circle') > -1)
+ {
+ // Cluster
+ thisMarker.isCluster = true;
+
+ GEvent.addListener(thisMarker, 'click',
+ function ()
+ {
+ // Clicking on a cluster zooms the map on its location
+ map.setCenter(coordinates, map.getZoom() + 2);
+ });
+ }
+ else
+ {
+ // Individual campground
+ thisMarker.isCluster = false;
+
+ // Attach infowindow to the marker with content from the KML
+ options = {maxWidth: 350};
+ thisMarker.bindInfoWindowHtml('
' + name + '
' +
+ description + '
', options);
+
+ // Also create a sidebar entry (alongside the map) with the icon, name, & descr
+ var sidebarRow = document.createElement('div');
+ sidebarRow.id = coordinates.toUrlValue();
+ sidebarRow.className = 'sidebar_row';
+ sidebarRow.innerHTML =
+ '
' + name +
+ '
' + description;
+ sidebar.appendChild(sidebarRow);
+
+ sidebarRow.onclick =
+ function ()
+ {
+ // A click on the sidebar entry triggers a click on its associated marker
+ GEvent.trigger(thisMarker, 'click')
+ };
+ }
+
+ // Add the marker to the map
+ map.addOverlay(thisMarker);
+};
diff --git a/chapter_11/listing_11_06.js b/chapter_11/listing_11_06.js
new file mode 100644
index 0000000..55feef7
--- /dev/null
+++ b/chapter_11/listing_11_06.js
@@ -0,0 +1,14 @@
+function removeDataPoint(m)
+{
+ // Remove the marker from the map
+ map.removeOverlay(markers[m]);
+
+ // Find and remove the sidebar entry
+ var id = markers[m].getPoint().toUrlValue();
+ var sidebarRow = document.getElementById(id);
+ if (sidebarRow)
+ sidebar.removeChild(sidebarRow);
+
+ // Remove the marker from our own array
+ markers.splice(m, 1);
+};
diff --git a/chapter_11/listing_11_07.php b/chapter_11/listing_11_07.php
new file mode 100644
index 0000000..0290246
--- /dev/null
+++ b/chapter_11/listing_11_07.php
@@ -0,0 +1,70 @@
+ $viewport[2])
+ $viewport[0] -= 360;
+
+ $north = min(90, (float) $viewport[3]);
+ $south = max(-90, (float) $viewport[1]);
+ $east = min(180, (float) $viewport[2]);
+ $west = max(-180, (float) $viewport[0]);
+
+ // Sort order, also from URL parameter
+ if ($_GET['sort'] == 'coverage')
+ $order_by = 'nCGRating desc, dtmWhen desc';
+ else if ($_GET['sort'] == 'hookups')
+ $order_by = 'nHookups desc, dtmWhen desc';
+ else
+ $order_by = 'nCount desc, dtmWhen desc';
+
+ // Filter, from cookies
+ $filter = '';
+ if (is_numeric($_COOKIE['nCGRating']))
+ $filter .= " and (nCGRating >= $_COOKIE[nCGRating])";
+ if (is_numeric($_COOKIE['nHookups']))
+ $filter .= " and (nHookups >= $_COOKIE[nHookups])";
+ if (is_numeric($_COOKIE['nSatLon']))
+ $filter .= " and (nSatLon = $_COOKIE[nSatLon])";
+
+ // Connect to the database
+ include 'mod_sf_db_connect.php';
+
+ // Execute the database query
+ $query = "select sParkName,
+ Campground.nParkID,
+ Avg(fLatitude) fLatitude,
+ Avg(fLongitude) fLongitude,
+ Round(Avg(nCGRating)) nCGRating,
+ Max(nHookups) nHookups,
+ Count(*) nCount,
+ sTownName,
+ sState,
+ date_format(dtExpire, '%Y-%m-%d') sExpire,
+ sResultsBlurb
+ from Campsite
+ natural join Campground
+ left join Sponsor using (nParkID)
+ where (fLatitude between $south and $north)
+ and (fLongitude between $west and $east)
+ and (bSuspect <> 'Y')
+ $filter
+ group by Campground.nParkID
+ order by $order_by
+ limit 20";
+ $result = mysql_query($query);
+ if (!$result)
+ die("Unable to retrieve data");
+
+ // Build an array holding all the retrieved data to send to the formatting routine
+ $campgrounds = array();
+ while ($row = mysql_fetch_array($result))
+ $campgrounds[] = array('lat' => round($row['fLatitude'], 6),
+ 'lon' => round($row['fLongitude'], 6),
+ 'id' => $row['nParkID'],
+ 'data' => $row);
+
+ // Return the results as KML
+ include 'mod_produce_kml.php';
+?>
diff --git a/chapter_11/listing_11_08.js b/chapter_11/listing_11_08.js
new file mode 100644
index 0000000..0ef85ea
--- /dev/null
+++ b/chapter_11/listing_11_08.js
@@ -0,0 +1,16 @@
+function afterGeocode(response)
+{
+ if (response &&
+ (response.Status.code == 200))
+ {
+ // Address was found - extract the map coordinates from the response
+ var place = response.Placemark[0];
+ var coordinates = new GLatLng(place.Point.coordinates[1],
+ place.Point.coordinates[0]);
+
+ // Move the map there, zooming further in for more accurate results
+ map.setCenter(coordinates, place.AddressDetails.Accuracy + 5);
+ }
+ else
+ alert('Address not found. Please try again.');
+};
diff --git a/chapter_11/listing_11_09.js b/chapter_11/listing_11_09.js
new file mode 100644
index 0000000..0b0750b
--- /dev/null
+++ b/chapter_11/listing_11_09.js
@@ -0,0 +1,22 @@
+// Check for a starting location in URL
+var hash = location.hash.replace('#', '');
+if (hash != '')
+{
+ // Found starting location - parse the coordinates & zoom from it
+ var viewport = startLocation.split(',');
+ var latitude = parseFloat(viewport[0]);
+ var longitude = parseFloat(viewport[1]);
+ var zoom = parseInt(viewport[2]);
+}
+
+// Initialize the core map object
+var options = {backgroundColor: '#D7D5E3',
+ mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]};
+map = new GMap2(mapDiv, options);
+
+if (!isNaN(latitude + longitude + zoom))
+ // Starting location supplied
+ map.setCenter(new GLatLng(latitude, longitude), zoom, G_PHYSICAL_MAP);
+else
+ // Default starting location
+ map.setCenter(new GLatLng(39.8, -98.5), 4, G_PHYSICAL_MAP);
diff --git a/chapter_11/listing_11_10.js b/chapter_11/listing_11_10.js
new file mode 100644
index 0000000..ee36d58
--- /dev/null
+++ b/chapter_11/listing_11_10.js
@@ -0,0 +1,31 @@
+var startLocation;
+
+// Check for a starting location or search address in URL
+var hash = location.hash.replace('#', '');
+if (hash != '')
+ startLocation = hash;
+
+// No starting location in URL - check for a browser cookie
+if (startLocation == null)
+ startLocation = getCookie('lastLocation');
+
+if (startLocation != null)
+{
+ // Found starting location - parse the coordinates & zoom from it
+ var viewport = startLocation.split(',');
+ var latitude = parseFloat(viewport[0]);
+ var longitude = parseFloat(viewport[1]);
+ var zoom = parseInt(viewport[2]);
+}
+
+// Initialize the core map object
+var options = {backgroundColor: '#D7D5E3',
+ mapTypes: [G_NORMAL_MAP, G_SATELLITE_MAP, G_HYBRID_MAP, G_PHYSICAL_MAP]};
+map = new GMap2(mapDiv, options);
+
+if (!isNaN(lat + lon + zoom))
+ // Starting location supplied
+ map.setCenter(new GLatLng(latitude, longitude), zoom, G_PHYSICAL_MAP);
+else
+ // Default starting location
+ map.setCenter(new GLatLng(39.8, -98.5), 4, G_PHYSICAL_MAP);
diff --git a/chapter_11/listing_11_11.php b/chapter_11/listing_11_11.php
new file mode 100644
index 0000000..1af7675
--- /dev/null
+++ b/chapter_11/listing_11_11.php
@@ -0,0 +1,88 @@
+
+
+
+
+ ...
+
+ var campground = {name: '$parkName',
+ iconUrl: '".fsCGIcon($parkRating, $maxHookups)."', color: '$color',
+ latitude: {$totalLatitude}, longitude: {$totalLongitude}};
+
+ var campsites = [".join($sites, ",")."];
+ ";
+ ?>
+
+
+ ...
+
+
\ No newline at end of file
diff --git a/chapter_11/listing_11_12.html b/chapter_11/listing_11_12.html
new file mode 100644
index 0000000..971857a
--- /dev/null
+++ b/chapter_11/listing_11_12.html
@@ -0,0 +1,30 @@
+
+
+
+
+ ...
+
+
+
+ ...
+
+
diff --git a/chapter_11/listing_11_13.js b/chapter_11/listing_11_13.js
new file mode 100644
index 0000000..32e133b
--- /dev/null
+++ b/chapter_11/listing_11_13.js
@@ -0,0 +1,35 @@
+// We'll need the campground boundaries for use in zooming
+campground.bounds = new GLatLngBounds();
+
+// Create a map marker for each campsite to use when zoomed in
+for (var c = 0; c < campsites.length; c++)
+{
+ // Extract the coordinates from JSON and add it to the campground boundaries
+ campsites[c].coordinates = new GLatLng(campsites[c].latitude,
+ campsites[c].longitude);
+ campground.bounds.extend(campsites[c].coordinates);
+
+ // Create campsite icon & marker
+ campsites[c].title = 'Site number ' + campsites[c].number;
+ options = {icon: createIcon(campsites[c].iconUrl), title: campsites[c].title};
+ campsites[c].marker = new GMarker(campsites[c].coordinates, options);
+ map.addOverlay(campsites[c].marker);
+
+ // Immediately hide the marker (our initial zoom is too far out to show it)
+ campsites[c].marker.hide();
+
+ // Attach infowindow to the marker with content from JSON
+ campsites[c].marker.bindInfoWindowHtml(
+ '
' +
+ '
' + campsites[c].title + '
' +
+ '
' +
+ '
' + campsites[c].rating + ' view of satellite at ' +
+ campsites[c].satellite + '°
' +
+ '
' + campsites[c].hookups + '
' +
+ '
' +
+ '
Reported on ' + campsites[c].date + '
' +
+ '
');
+}
+
+// Calculate optimal campground-wide zoom level
+optimalZoom = Math.min(15, map.getBoundsZoomLevel(campground.bounds) - 1);
diff --git a/chapter_11/listing_11_14.js b/chapter_11/listing_11_14.js
new file mode 100644
index 0000000..c77c496
--- /dev/null
+++ b/chapter_11/listing_11_14.js
@@ -0,0 +1,39 @@
+function mapMoveEnd()
+{
+ if (map.getZoom() < optimalZoom - 1)
+ {
+ // Zoomed far enough out to only show single marker for the campground
+
+ // Hide all the individual campsite markers
+ map.closeInfoWindow();
+ for (var c = 0; c < campsites.length; c++)
+ campsites[c].marker.hide()
+
+ // Show (and track) the campground marker
+ campground.marker.show();
+ tracker.enable();
+ }
+ else
+ {
+ // Zoomed far enough in to see individual campsites
+
+ // Hide the campground marker
+ campground.marker.hide();
+
+ // Show all the individual campsite markers
+ siteVisible = false;
+ var bounds = map.getBounds();
+ for (var c = 0; c < campsites.length; c++)
+ {
+ campsites[c].marker.show()
+ if (bounds.contains(campsites[c].coordinates))
+ siteVisible = true;
+ }
+
+ // Only show the marker tracker if no campsite is visible
+ if (siteVisible)
+ tracker.disable();
+ else
+ tracker.enable();
+ }
+};
diff --git a/chapter_11/listing_11_15.js b/chapter_11/listing_11_15.js
new file mode 100644
index 0000000..1ded83d
--- /dev/null
+++ b/chapter_11/listing_11_15.js
@@ -0,0 +1,40 @@
+function getDirections()
+{
+ // getDirections: retrieve driving directions to the campground
+
+ var startAddress = document.getElementById('saddr');
+
+ if (startAddress.value == '')
+ {
+ alert('Please enter a starting point for your driving directions.');
+ startAddress.focus();
+ }
+ else
+ {
+ // Starting point looks good - load driving directions
+ var endpoints = 'from: ' + startAddress.value + ' to:' +
+ campground.coordinates.toUrlValue();
+ directions.load(endpoints);
+ }
+};
+
+function directionsDone()
+{
+ // directionsDone: show the header above the driving directions area
+ var status = directions.getStatus().code;
+ if (status == 200)
+ {
+ document.getElementById('directions_header').style.display = 'block';
+ tracker.disable();
+ }
+};
+
+function directionsError()
+{
+ // directionsError: a problem occurred getting driving directions
+ var status = directions.getStatus().code;
+ if (status == 500)
+ alert('Unable to load driving directions, sorry.');
+ else
+ alert('Unable to locate start point for driving directions.');
+};
diff --git a/chapter_11/listing_11_16.php b/chapter_11/listing_11_16.php
new file mode 100644
index 0000000..19ca34a
--- /dev/null
+++ b/chapter_11/listing_11_16.php
@@ -0,0 +1,60 @@
+
+';
+
+ // Create a sitemap url element for each grid cell
+ for ($lat = $south; $lat < $north; $lat += $lat_grid)
+ for ($lon = $west; $lon < $east; $lon += $lon_grid)
+ {
+ $query = "select 1
+ from Campsite
+ where (fLatitude between $lat and ".($lat + $lat_grid).")
+ and (fLongitude between $lon and ".($lon + $lon_grid).")";
+ $result = mysql_query($query);
+ if (!mysql_num_rows($result))
+ continue;
+
+ $urls = $urls.'
+
+
+ http://www.satellitefriendly.com/services/top_campgrounds.php?BBOX='.
+ $lon.','.$lat.','.($lon + $lon_grid).','.($lat + $lat_grid).'
+
+
+ kml
+
+ ';
+ }
+
+ // Prepare the sitemap XML footer
+ $footer = '
+';
+
+ // Output the final sitemap XML
+ header('Content-type: application/xml');
+ echo $header;
+ echo $urls;
+ echo $footer;
+?>
diff --git a/chapter_11/listing_11_17.xml b/chapter_11/listing_11_17.xml
new file mode 100644
index 0000000..d12b049
--- /dev/null
+++ b/chapter_11/listing_11_17.xml
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+ p, table {
+ font-size: 90%;
+ width: 100%;
+ text-align: left;
+ }
+ h3 {
+ margin: 0;
+ font-size: 12pt;
+ }
+ img {
+ vertical-align: middle;
+ }
+ .coverage {
+ text-align: center;
+ }
+
+
+
+ Campgrounds in North America with coverage for satellite TV and internet.
+ Locations from
+ SatelliteFriendly.com,
+ contributed by users on-site with GPS.
+
+
+
+ View:
+
+
+
+
Legend
+
+
+
+ Multiple campgrounds in area
+
+
+
+
Campsite Hookups
+
Satellite Coverage
+
+
+
+ Full
+
+
+
+
+
+
+
+ Electric
+
+
+
+
+
+
+
+ None
+
+
+
+
+
+
+
+
+ ]]>
+
\ No newline at end of file
diff --git a/contributing.md b/contributing.md
new file mode 100644
index 0000000..f6005ad
--- /dev/null
+++ b/contributing.md
@@ -0,0 +1,14 @@
+# Contributing to Apress Source Code
+
+Copyright for Apress source code belongs to the author(s). However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers.
+
+## How to Contribute
+
+1. Make sure you have a GitHub account.
+2. Fork the repository for the relevant book.
+3. Create a new branch on which to make your change, e.g.
+`git checkout -b my_code_contribution`
+4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted.
+5. Submit a pull request.
+
+Thank you for your contribution!
\ No newline at end of file
diff --git a/egeoxml.js b/egeoxml.js
new file mode 100644
index 0000000..5720272
--- /dev/null
+++ b/egeoxml.js
@@ -0,0 +1,638 @@
+
+/*********************************************************************\
+* *
+* egeoxml.js by Mike Williams *
+* *
+* A Google Maps API Extension *
+* *
+* Renders the contents of a My Maps (or similar) KML file *
+* *
+* Documentation: http://econym.googlepages.com/egeoxml.htm *
+* *
+***********************************************************************
+* *
+* Extended to work with the Mapplets API by Sterling Udell *
+* *
+***********************************************************************
+* *
+* This Javascript is provided by Mike Williams *
+* Blackpool Community Church Javascript Team *
+* http://www.commchurch.freeserve.co.uk/ *
+* http://econym.googlepages.com/index.htm *
+* *
+* This work is licenced under a Creative Commons Licence *
+* http://creativecommons.org/licenses/by/2.0/uk/ *
+* *
+\*********************************************************************/
+
+
+// Version 0.0 17 Apr 2007 - Initial testing, just markers
+// Version 0.1 17 Apr 2007 - Sensible shadows, and a few general improvements
+// Version 0.2 18 Apr 2007 - Polylines (non-clickable, no sidebar)
+// Version 0.3 18 Apr 2007 - Polygons (non-clickable, no sidebar)
+// Version 0.4 18 Apr 2007 - Sidebar entries for polygons
+// Version 0.5 19 Apr 2007 - Accept an array of XML filenames, and add the {sortbyname} option
+// Version 0.6 19 Apr 2007 - Sidebar entries for polylines, get directions and search nearby
+// Version 0.7 20 Apr 2007 - Info Window Styles
+// Version 0.8 21 Apr 2007 - baseicon
+// Version 0.9 21 Apr 2007 - iwoptions and markeroptions
+// Version 1.0 21 Apr 2007 - Launched
+// Version 1.1 25 Apr 2007 - Bugfix - would crash if no options were specified
+// Version 1.2 25 Apr 2007 - If the description begins with "http://" make it into a link.
+// Version 1.3 30 Apr 2007 - printgif, dropbox
+// Version 1.4 14 May 2007 - Elabels
+// Version 1.5 17 May 2007 - Default values for width, fill and outline
+// Version 1.6 21 May 2007 - GGroundOverlay (needs API V2.79+)
+// Version 1.7 22 May 2007 - Better icon positioning for MyMaps icons
+// Version 1.8 31 May 2007 - polyline bugfix
+// Version 1.9 23 Jun 2007 - add .parseString() method
+// Version 2.0 23 Jun 2007 - .parseString() handles an array of strings
+// Version 2.1 25 Jul 2007 - imagescan
+// Version 2.2 10 Aug 2007 - Support new My Maps icons
+// Version 2.3 25 Nov 2007 - Clear variables used by .parse() so that it can be rerun
+// Version 2.4 08 Dec 2007 - polylineoptions and polygonoptions
+// Version 2.5 11 Dec 2007 - EGeoXml.value() trims leading and trailing whitespace
+// S.Udell 13 Dec 2007 - Fix bugs handling KML with empty points, add missing semicolons
+// Version 2.6 08 Feb 2008 - Trailing whitespace wasn't removed in the previous change
+// S.Udell 14 Jul 2008 - Convert for Maps / Mapplets cross-compatibility,
+// removed need for first parmaeter "myvar"
+
+if (typeof GXml == 'undefined')
+{
+ // SCU: Mapplets don't have a native GXml object; this code is from the Maps API
+ GXml = {
+ parse: function (xmlText)
+ {
+ if (typeof ActiveXObject != "undefined" &&
+ typeof GetObject != "undefined")
+ {
+ var b = new ActiveXObject("Microsoft.XMLDOM");
+ b.loadXML(xmlText);
+ return b;
+ };
+
+ if (typeof DOMParser != "undefined")
+ return(new DOMParser).parseFromString(xmlText, "text/xml");
+
+ return x("div",null);
+ },
+
+ value: function (xmlNode)
+ {
+ if (!xmlNode)
+ return "";
+
+ var b="";
+ if (xmlNode.nodeType == 3 ||
+ xmlNode.nodeType == 4 ||
+ xmlNode.nodeType == 2)
+ {
+ b += xmlNode.nodeValue;
+ }
+ else if (xmlNode.nodeType == 1 ||
+ xmlNode.nodeType == 9 ||
+ xmlNode.nodeType == 11)
+ {
+ for (var c = 0; c < xmlNode.childNodes.length; ++c)
+ b += arguments.callee(xmlNode.childNodes[c])
+ }
+ return b;
+ }
+ }
+}
+
+// Constructor
+
+function EGeoXml(map, url, opts) {
+
+ // SCU: create myvar as a new element of a global array (replaces MW's first parm)
+ window.eGeoXml = window.eGeoXml || [];
+ this.myvar = 'window.eGeoXml[' + (window.eGeoXml.push(this) - 1) + ']';
+
+ // store the parameters
+ this.map = map;
+ this.url = url;
+ if (typeof url == "string") {
+ this.urls = [url];
+ } else {
+ this.urls = url;
+ }
+ this.opts = opts || {};
+ // infowindow styles
+ this.titlestyle = this.opts.titlestyle || 'style = "font-family: arial, sans-serif;font-size: medium;font-weight:bold;font-size: 100%;"';
+ this.descstyle = this.opts.descstyle || 'style = "font-family: arial, sans-serif;font-size: small;padding-bottom:.7em;"';
+ this.directionstyle = this.opts.directionstyle || 'style="font-family: arial, sans-serif;font-size: small;padding-left: 1px;padding-top: 1px;padding-right: 4px;"';
+ // sidebar/dropbox functions
+ this.sidebarfn = this.opts.sidebarfn || EGeoXml.addSidebar;
+ this.dropboxfn = this.opts.dropboxfn || EGeoXml.addDropdown;
+ // elabel options
+ this.elabelopacity = this.opts.elabelopacity || 100;
+ // other useful "global" stuff
+ this.bounds = new GLatLngBounds();
+ this.gmarkers = [];
+ this.gpolylines = [];
+ this.gpolygons = [];
+ this.groundoverlays = [];
+ this.side_bar_html = "";
+ this.side_bar_list = [];
+ this.styles = []; // associative array
+ this.iwwidth = this.opts.iwwidth || 250;
+ this.progress = 0;
+ this.lastmarker = {};
+ this.myimages = [];
+ this.imageNum =0;
+ this.isMapplet = !window.GBrowserIsCompatible;
+};
+
+// uses GXml.value, then removes leading and trailing whitespace
+EGeoXml.value = function(e) {
+ a = GXml.value(e);
+ a = a.replace(/^\s*/,"");
+ a = a.replace(/\s*$/,"");
+ return a;
+};
+
+// Create Marker
+
+EGeoXml.prototype.createMarker = function(point,name,desc,style) {
+ var myvar=this.myvar;
+ var iwoptions = this.opts.iwoptions || {};
+ var markeroptions = this.opts.markeroptions || {};
+ var icontype = this.opts.icontype || "style";
+ if ((icontype == "style") &&
+ !!this.styles[style])
+ {
+ var icon = G_DEFAULT_ICON;
+ icon = this.styles[style];
+ }
+ if (!markeroptions.icon && !!icon) {
+ markeroptions.icon = icon;
+ }
+ var m = new GMarker(point, markeroptions);
+
+ // Attempt to preload images
+ if (this.opts.preloadimages) {
+ var text = desc;
+ var pattern = /<\s*img/ig;
+ var result;
+ var pattern2 = /src\s*=\s*[\'\"]/;
+ var pattern3 = /[\'\"]/;
+
+ while ((result = pattern.exec(text)) != null) {
+ var stuff = text.substr(result.index);
+ var result2 = pattern2.exec(stuff);
+ if (result2 != null) {
+ stuff = stuff.substr(result2.index+result2[0].length);
+ var result3 = pattern3.exec(stuff);
+ if (result3 != null) {
+ var imageUrl = stuff.substr(0,result3.index);
+ this.myimages[this.imageNum] = new Image();
+ this.myimages[this.imageNum].src = imageUrl;
+ this.imageNum++;
+ }
+ }
+ }
+ }
+
+
+
+ if (this.opts.elabelclass) {
+ var l = new ELabel(point, name, this.opts.elabelclass, this.opts.elabeloffset, this.elabelopacity, true);
+ this.map.addOverlay(l);
+ }
+
+ var html = "
"
+ + "
"+name+"
"
+ +"
"+desc+"
";
+
+ if (this.opts.directions) {
+ var html1 = html + '
';
+ }
+};
+
+// Dropdown factory method
+EGeoXml.addDropdown = function(myvar,name,type,i,graphic) {
+ return '';
+};
+
+
+// Request to Parse an XML file
+
+EGeoXml.prototype.parse = function() {
+ // clear some variables
+ this.gmarkers = [];
+ this.gpolylines = [];
+ this.gpolygons = [];
+ this.groundoverlays = [];
+ this.side_bar_html = "";
+ this.side_bar_list = [];
+ this.styles = []; // associative array
+ this.lastmarker = {};
+ this.myimages = [];
+ this.imageNum =0;
+ var that = this;
+ this.progress = this.urls.length;
+ for (u=0; u 0) {
+ var href=EGeoXml.value(icons[0].getElementsByTagName("href")[0]);
+ if (!!href) {
+ if (!!that.opts.baseicon) {
+ that.styles["#"+styleID] = new GIcon(that.opts.baseicon,href);
+ } else {
+ that.styles["#"+styleID] = new GIcon(G_DEFAULT_ICON,href);
+ that.styles["#"+styleID].iconSize = new GSize(32,32);
+ that.styles["#"+styleID].shadowSize = new GSize(59,32);
+ that.styles["#"+styleID].dragCrossAnchor = new GPoint(2,8);
+ that.styles["#"+styleID].iconAnchor = new GPoint(16,32);
+ if (that.opts.printgif) {
+ var bits = href.split("/");
+ var gif = bits[bits.length-1];
+ gif = that.opts.printgifpath + gif.replace(/.png/i,".gif");
+ that.styles["#"+styleID].printImage = gif;
+ that.styles["#"+styleID].mozPrintImage = gif;
+ }
+ if (!!that.opts.noshadow) {
+ that.styles["#"+styleID].shadow="";
+ } else {
+ // Try to guess the shadow image
+ if (href.indexOf("/red.png")>-1
+ || href.indexOf("/blue.png")>-1
+ || href.indexOf("/green.png")>-1
+ || href.indexOf("/yellow.png")>-1
+ || href.indexOf("/lightblue.png")>-1
+ || href.indexOf("/purple.png")>-1
+ || href.indexOf("/pink.png")>-1
+ || href.indexOf("/orange.png")>-1
+ || href.indexOf("-dot.png")>-1 ) {
+ that.styles["#"+styleID].shadow="http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png";
+ }
+ else if (href.indexOf("-pushpin.png")>-1) {
+ that.styles["#"+styleID].shadow="http://maps.google.com/mapfiles/ms/micons/pushpin_shadow.png";
+ }
+ else {
+ var shadow = href.replace(".png",".shadow.png");
+ that.styles["#"+styleID].shadow=shadow;
+ }
+ }
+ }
+ }
+ }
+ // is it a LineStyle ?
+ var linestyles=styles[i].getElementsByTagName("LineStyle");
+ if (linestyles.length > 0) {
+ var width = parseInt(GXml.value(linestyles[0].getElementsByTagName("width")[0]));
+ if (width < 1) {width = 5;}
+ var color = EGeoXml.value(linestyles[0].getElementsByTagName("color")[0]);
+ var aa = color.substr(0,2);
+ var bb = color.substr(2,2);
+ var gg = color.substr(4,2);
+ var rr = color.substr(6,2);
+ color = "#" + rr + gg + bb;
+ var opacity = parseInt(aa,16)/256;
+ if (!that.styles["#"+styleID]) {
+ that.styles["#"+styleID] = {};
+ }
+ that.styles["#"+styleID].color=color;
+ that.styles["#"+styleID].width=width;
+ that.styles["#"+styleID].opacity=opacity;
+ }
+ // is it a PolyStyle ?
+ var polystyles=styles[i].getElementsByTagName("PolyStyle");
+ if (polystyles.length > 0) {
+ var fill = parseInt(GXml.value(polystyles[0].getElementsByTagName("fill")[0]));
+ var outline = parseInt(GXml.value(polystyles[0].getElementsByTagName("outline")[0]));
+ var color = EGeoXml.value(polystyles[0].getElementsByTagName("color")[0]);
+
+ if (polystyles[0].getElementsByTagName("fill").length == 0) {fill = 1;}
+ if (polystyles[0].getElementsByTagName("outline").length == 0) {outline = 1;}
+
+ var aa = color.substr(0,2);
+ var bb = color.substr(2,2);
+ var gg = color.substr(4,2);
+ var rr = color.substr(6,2);
+ color = "#" + rr + gg + bb;
+
+ var opacity = parseInt(aa,16)/256;
+ if (!that.styles["#"+styleID]) {
+ that.styles["#"+styleID] = {};
+ }
+ that.styles["#"+styleID].fillcolor=color;
+ that.styles["#"+styleID].fillopacity=opacity;
+ if (!fill) that.styles["#"+styleID].fillopacity = 0;
+ if (!outline) that.styles["#"+styleID].opacity = 0;
+ }
+ }
+
+ // Read through the Placemarks
+ var placemarks = xmlDoc.documentElement.getElementsByTagName("Placemark");
+ for (var i = 0; i < placemarks.length; i++) {
+ var name=EGeoXml.value(placemarks[i].getElementsByTagName("name")[0]);
+ var desc=EGeoXml.value(placemarks[i].getElementsByTagName("description")[0]);
+ if (desc.match(/^http:\/\//i)) {
+ desc = '' + desc + '';
+ }
+ if (desc.match(/^https:\/\//i)) {
+ desc = '' + desc + '';
+ }
+ var style=EGeoXml.value(placemarks[i].getElementsByTagName("styleUrl")[0]);
+ var coords=GXml.value(placemarks[i].getElementsByTagName("coordinates")[0]);
+ coords=coords.replace(/\s+/g," "); // tidy the whitespace
+ coords=coords.replace(/^ /,""); // remove possible leading whitespace
+ coords=coords.replace(/ $/,""); // remove possible trailing whitespace
+ coords=coords.replace(/, /,","); // tidy the commas
+ var path = coords.split(" ");
+
+ // Is this a polyline/polygon?
+ if (path.length > 1) {
+ // Build the list of points
+ var points = [];
+ var pbounds = new GLatLngBounds();
+ for (var p=0; p 0) {
+ // It's not a poly, so I guess it must be a marker
+ var bits = path[0].split(",");
+ if (bits.length >= 2) {
+ var point = new GLatLng(parseFloat(bits[1]),parseFloat(bits[0]));
+ that.bounds.extend(point);
+ // Does the user have their own createmarker function?
+ if (!!that.opts.createmarker) {
+ that.opts.createmarker(point, name, desc, style);
+ } else {
+ that.createMarker(point, name, desc, style);
+ }
+ }
+ }
+ }
+
+ // Scan through the Ground Overlays
+ var grounds = xmlDoc.documentElement.getElementsByTagName("GroundOverlay");
+ for (var i = 0; i < grounds.length; i++) {
+ var url=EGeoXml.value(grounds[i].getElementsByTagName("href")[0]);
+ var north=parseFloat(GXml.value(grounds[i].getElementsByTagName("north")[0]));
+ var south=parseFloat(GXml.value(grounds[i].getElementsByTagName("south")[0]));
+ var east=parseFloat(GXml.value(grounds[i].getElementsByTagName("east")[0]));
+ var west=parseFloat(GXml.value(grounds[i].getElementsByTagName("west")[0]));
+ var sw = new GLatLng(south,west);
+ var ne = new GLatLng(north,east);
+ var ground = new GGroundOverlay(url, new GLatLngBounds(sw,ne));
+ that.bounds.extend(sw);
+ that.bounds.extend(ne);
+ that.groundoverlays.push(ground);
+ that.map.addOverlay(ground);
+ }
+
+ // Is this the last file to be processed?
+ that.progress--;
+ if (that.progress == 0) {
+ // Shall we zoom to the bounds?
+ if (!that.opts.nozoom) {
+ if (that.isMapplet)
+ that.map.getBoundsZoomLevelAsync(that.bounds, function (zoom)
+ {
+ that.map.setCenter(that.bounds.getCenter(), zoom);
+ });
+ else
+ that.map.setCenter(that.bounds.getCenter(),
+ that.map.getBoundsZoomLevel(that.bounds));
+ }
+ // Shall we display the sidebar?
+ if (that.opts.sortbyname) {
+ that.side_bar_list.sort();
+ }
+ if (that.opts.sidebarid) {
+ for (var i=0; i'
+ + ''
+ + that.side_bar_html
+ + '';
+ }
+
+ GEvent.trigger(that,"parsed");
+ }
+};
diff --git a/json_parse.js b/json_parse.js
new file mode 100644
index 0000000..34505f4
--- /dev/null
+++ b/json_parse.js
@@ -0,0 +1,344 @@
+/*
+ http://www.JSON.org/json_parse.js
+ 2008-04-08
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ This file creates a json_parse function.
+
+ json_parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = json_parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+*/
+
+/*members "", "\"", "\/", "\\", at, b, call, charAt, f, fromCharCode,
+ hasOwnProperty, message, n, name, push, r, t, text
+*/
+
+/*global json_parse */
+
+json_parse = function () {
+
+// This is a function that can parse a JSON text, producing a JavaScript
+// data structure. It is a simple, recursive descent parser. It does not use
+// eval or regular expressions, so it can be used as a model for implementing
+// a JSON parser in other languages.
+
+// We are defining the function inside of another function to avoid creating
+// global variables.
+
+ var at, // The index of the current character
+ ch, // The current character
+ escapee = {
+ '"': '"',
+ '\\': '\\',
+ '/': '/',
+ b: '\b',
+ f: '\f',
+ n: '\n',
+ r: '\r',
+ t: '\t'
+ },
+ text,
+
+ error = function (m) {
+
+// Call error when something is wrong.
+
+ throw {
+ name: 'SyntaxError',
+ message: m,
+ at: at,
+ text: text
+ };
+ },
+
+ next = function (c) {
+
+// If a c parameter is provided, verify that it matches the current character.
+
+ if (c && c !== ch) {
+ error("Expected '" + c + "' instead of '" + ch + "'");
+ }
+
+// Get the next character. When there are no more characters,
+// return the empty string.
+
+ ch = text.charAt(at);
+ at += 1;
+ return ch;
+ },
+
+ number = function () {
+
+// Parse a number value.
+
+ var number,
+ string = '';
+
+ if (ch === '-') {
+ string = '-';
+ next('-');
+ }
+ while (ch >= '0' && ch <= '9') {
+ string += ch;
+ next();
+ }
+ if (ch === '.') {
+ string += '.';
+ while (next() && ch >= '0' && ch <= '9') {
+ string += ch;
+ }
+ }
+ if (ch === 'e' || ch === 'E') {
+ string += ch;
+ next();
+ if (ch === '-' || ch === '+') {
+ string += ch;
+ next();
+ }
+ while (ch >= '0' && ch <= '9') {
+ string += ch;
+ next();
+ }
+ }
+ number = +string;
+ if (isNaN(number)) {
+ error("Bad number");
+ } else {
+ return number;
+ }
+ },
+
+ string = function () {
+
+// Parse a string value.
+
+ var hex,
+ i,
+ string = '',
+ uffff;
+
+// When parsing for string values, we must look for " and \ characters.
+
+ if (ch === '"') {
+ while (next()) {
+ if (ch === '"') {
+ next();
+ return string;
+ } else if (ch === '\\') {
+ next();
+ if (ch === 'u') {
+ uffff = 0;
+ for (i = 0; i < 4; i += 1) {
+ hex = parseInt(next(), 16);
+ if (!isFinite(hex)) {
+ break;
+ }
+ uffff = uffff * 16 + hex;
+ }
+ string += String.fromCharCode(uffff);
+ } else if (typeof escapee[ch] === 'string') {
+ string += escapee[ch];
+ } else {
+ break;
+ }
+ } else {
+ string += ch;
+ }
+ }
+ }
+ error("Bad string");
+ },
+
+ white = function () {
+
+// Skip whitespace.
+
+ while (ch && ch <= ' ') {
+ next();
+ }
+ },
+
+ word = function () {
+
+// true, false, or null.
+
+ switch (ch) {
+ case 't':
+ next('t');
+ next('r');
+ next('u');
+ next('e');
+ return true;
+ case 'f':
+ next('f');
+ next('a');
+ next('l');
+ next('s');
+ next('e');
+ return false;
+ case 'n':
+ next('n');
+ next('u');
+ next('l');
+ next('l');
+ return null;
+ }
+ error("Unexpected '" + ch + "'");
+ },
+
+ value, // Place holder for the value function.
+
+ array = function () {
+
+// Parse an array value.
+
+ var array = [];
+
+ if (ch === '[') {
+ next('[');
+ white();
+ if (ch === ']') {
+ next(']');
+ return array; // empty array
+ }
+ while (ch) {
+ array.push(value());
+ white();
+ if (ch === ']') {
+ next(']');
+ return array;
+ }
+ next(',');
+ white();
+ }
+ }
+ error("Bad array");
+ },
+
+ object = function () {
+
+// Parse an object value.
+
+ var key,
+ object = {};
+
+ if (ch === '{') {
+ next('{');
+ white();
+ if (ch === '}') {
+ next('}');
+ return object; // empty object
+ }
+ while (ch) {
+ key = string();
+ white();
+ next(':');
+ object[key] = value();
+ white();
+ if (ch === '}') {
+ next('}');
+ return object;
+ }
+ next(',');
+ white();
+ }
+ }
+ error("Bad object");
+ };
+
+ value = function () {
+
+// Parse a JSON value. It could be an object, an array, a string, a number,
+// or a word.
+
+ white();
+ switch (ch) {
+ case '{':
+ return object();
+ case '[':
+ return array();
+ case '"':
+ return string();
+ case '-':
+ return number();
+ default:
+ return ch >= '0' && ch <= '9' ? number() : word();
+ }
+ };
+
+// Return the json_parse function. It will have access to all of the above
+// functions and variables.
+
+ return function (source, reviver) {
+ var result;
+
+ text = source;
+ at = 0;
+ ch = ' ';
+ result = value();
+ white();
+ if (ch) {
+ error("Syntax error");
+ }
+
+// If there is a reviver function, we recursively walk the new structure,
+// passing each name/value pair to the reviver function for possible
+// transformation, starting with a temporary root object that holds the result
+// in an empty key. If there is not a reviver function, we simply return the
+// result.
+
+ return typeof reviver === 'function' ? function walk(holder, key) {
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }({'': result}, '') : result;
+ };
+}();
\ No newline at end of file
diff --git a/markers/black.png b/markers/black.png
new file mode 100644
index 0000000..dee0818
Binary files /dev/null and b/markers/black.png differ
diff --git a/markers/crosshair.png b/markers/crosshair.png
new file mode 100644
index 0000000..9d394e6
Binary files /dev/null and b/markers/crosshair.png differ
diff --git a/markers/green.png b/markers/green.png
new file mode 100644
index 0000000..635ec06
Binary files /dev/null and b/markers/green.png differ
diff --git a/markers/music.png b/markers/music.png
new file mode 100644
index 0000000..7672343
Binary files /dev/null and b/markers/music.png differ
diff --git a/markers/red.png b/markers/red.png
new file mode 100644
index 0000000..6cc189d
Binary files /dev/null and b/markers/red.png differ
diff --git a/markers/shadow.png b/markers/shadow.png
new file mode 100644
index 0000000..37f7731
Binary files /dev/null and b/markers/shadow.png differ
diff --git a/markers/star.png b/markers/star.png
new file mode 100644
index 0000000..5be7a5e
Binary files /dev/null and b/markers/star.png differ
diff --git a/markers/white.png b/markers/white.png
new file mode 100644
index 0000000..1a6e502
Binary files /dev/null and b/markers/white.png differ
diff --git a/markers/yellow.png b/markers/yellow.png
new file mode 100644
index 0000000..4365777
Binary files /dev/null and b/markers/yellow.png differ