GoogleMapV3Helper.php 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572
  1. <?php
  2. /**
  3. * This is a CakePHP helper that helps users to integrate google map v3
  4. * into their application by only writing php code. this helper depends on jQuery
  5. *
  6. * @author Rajib Ahmed
  7. * @version 0.10.12
  8. *
  9. * enhanced/modified by Mark Scherer
  10. */
  11. App::uses('AppHelper', 'View/Helper');
  12. /**
  13. * PHP5 / CakePHP 2.x
  14. *
  15. * @author Mark Scherer
  16. * @link http://www.dereuromark.de/2010/12/21/googlemapsv3-cakephp-helper/
  17. * @package tools plugin
  18. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  19. *
  20. * fixed brackets, spacesToTabs, indends, some improvements, supports multiple maps now.
  21. * now capable of resetting itself (full or partly) for multiple maps on a single view
  22. *
  23. * CodeAPI: http://code.google.com/intl/de-DE/apis/maps/documentation/javascript/basics.html
  24. * Icons/Images: http://gmapicons.googlepages.com/home
  25. *
  26. * v1.2: Cake2.x
  27. * 2011-10-12 ms
  28. * v1.3: E_STRICT compliant methods (url now mapUrl, link now mapLink)
  29. * 2012-08-31 ms
  30. */
  31. class GoogleMapV3Helper extends AppHelper {
  32. public static $MAP_COUNT = 0;
  33. public static $MARKER_COUNT = 0;
  34. public static $ICON_COUNT = 0;
  35. public static $INFO_WINDOW_COUNT = 0;
  36. public static $INFO_CONTENT_COUNT = 0;
  37. const API = 'maps.google.com/maps/api/js?';
  38. const STATIC_API = 'maps.google.com/maps/api/staticmap?';
  39. const TYPE_ROADMAP = 'R';
  40. const TYPE_HYBRID = 'H';
  41. const TYPE_SATELLITE = 'S';
  42. const TYPE_TERRAIN = 'T';
  43. public $types = array(
  44. self::TYPE_ROADMAP => 'ROADMAP',
  45. self::TYPE_HYBRID => 'HYBRID',
  46. self::TYPE_SATELLITE => 'SATELLITE',
  47. self::TYPE_TERRAIN => 'TERRAIN'
  48. );
  49. /**
  50. * Cakephp builtin helper
  51. *
  52. * @var array
  53. */
  54. public $helpers = array('Html', 'Js');
  55. /**
  56. * Google maker config instance variable
  57. *
  58. * @var array
  59. */
  60. public $markers = array();
  61. public $infoWindows = array();
  62. public $infoContents = array();
  63. public $icons = array();
  64. public $matching = array();
  65. //public $iconMatching = array();
  66. public $map = '';
  67. protected $_mapIds = array(); # remember already used ones (valid xhtml contains ids not more than once)
  68. /**
  69. * Default settings
  70. *
  71. * @var array
  72. */
  73. protected $_defaultOptions = array(
  74. 'zoom' =>null, # global, both map and staticMap
  75. 'lat' => null, # global, both map and staticMap
  76. 'lng' => null, # global, both map and staticMap
  77. 'type' => self::TYPE_ROADMAP,
  78. 'map'=>array(
  79. 'api' => null,
  80. 'streetViewControl' => false,
  81. 'navigationControl' => true,
  82. 'mapTypeControl' => true,
  83. 'scaleControl' => true,
  84. 'scrollwheel' => false,
  85. 'keyboardShortcuts' => true,
  86. //'zoom' =>5, # deprecated as default value, uses global one if missing
  87. //'type' =>'R', # deprecated as default value, uses global one if missing
  88. //'lat' => 51, # deprecated as default value, uses global one if missing
  89. //'lng' => 11, # deprecated as default value, uses global one if missing
  90. 'typeOptions' => array(),
  91. 'navOptions' => array(),
  92. 'scaleOptions' => array(),
  93. 'defaultLat' => 51, # only last fallback, use Configure::write('Google.lat', ...); to define own one
  94. 'defaultLng' => 11, # only last fallback, use Configure::write('Google.lng', ...); to define own one
  95. 'defaultZoom' => 5,
  96. ),
  97. 'staticMap' => array(
  98. 'size' => '300x300',
  99. //'type' =>'R', # deprecated as default value, uses global one if missing
  100. //'zoom' => 12 # deprecated as default value, uses global one if missing
  101. //'lat' => 51, # deprecated as default value, uses global one if missing
  102. //'lng' => 11, # deprecated as default value, uses global one if missing
  103. 'format' => 'png',
  104. 'mobile' => false,
  105. //'shadow' => true # for icons
  106. ),
  107. 'geolocate' => false,
  108. 'sensor' => false,
  109. 'language' => null,
  110. 'region' => null,
  111. 'showMarker' => true,
  112. //'showInfoWindow' => true,
  113. 'infoWindow' => array(
  114. 'content'=>'',
  115. 'useMultiple'=>false, # Using single infowindow object for all
  116. 'maxWidth'=>300,
  117. 'lat'=>null,
  118. 'lng'=>null,
  119. 'pixelOffset' => 0,
  120. 'zIndex' => 200,
  121. 'disableAutoPan' => false
  122. ),
  123. 'marker'=>array(
  124. //'autoCenter' => true,
  125. 'icon' => null, # => default (red marker) //http://google-maps-icons.googlecode.com/files/home.png
  126. 'title' => null,
  127. 'shadow' => null,
  128. 'shape' => null,
  129. 'zIndex' => null,
  130. 'draggable' => false,
  131. 'cursor' => null,
  132. 'directions' => false # add form with directions
  133. ),
  134. 'div'=>array(
  135. 'id'=>'map_canvas',
  136. 'width' => '100%',
  137. 'height' => '400px',
  138. 'class' => 'map',
  139. 'escape' => true
  140. ),
  141. 'event'=>array(
  142. ),
  143. 'animation' => array(
  144. //TODO
  145. ),
  146. 'callbacks' => array(
  147. 'geolocate' => null //TODO
  148. ),
  149. 'plugins' => array(
  150. 'keydragzoom' => false, # http://google-maps-utility-library-v3.googlecode.com/svn/tags/keydragzoom/
  151. 'markermanager' => false, # http://google-maps-utility-library-v3.googlecode.com/svn/tags/markermanager/
  152. 'markercluster' => false, # http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclusterer/
  153. ),
  154. 'autoCenter' => false, # try to fit all markers in (careful, all zooms values are omitted)
  155. 'autoScript' => false, # let the helper include the necessary js script links
  156. 'inline' => false, # for scripts
  157. 'localImages' => false,
  158. 'https' => null # auto detect
  159. );
  160. protected $_currentOptions =array();
  161. protected $_apiIncluded = false;
  162. protected $_gearsIncluded = false;
  163. protected $_located = false;
  164. public function __construct($View = null, $settings = array()) {
  165. parent::__construct($View, $settings);
  166. # read constum config settings
  167. $google = (array)Configure::read('Google');
  168. if (!empty($google['api'])) {
  169. $this->_defaultOptions['map']['api'] = $google['api'];
  170. }
  171. if (!empty($google['zoom'])) {
  172. $this->_defaultOptions['map']['zoom'] = $google['zoom'];
  173. }
  174. if (!empty($google['lat'])) {
  175. $this->_defaultOptions['map']['lat'] = $google['lat'];
  176. }
  177. if (!empty($google['lng'])) {
  178. $this->_defaultOptions['map']['lng'] = $google['lng'];
  179. }
  180. if (!empty($google['type'])) {
  181. $this->_defaultOptions['map']['type'] = $google['type'];
  182. }
  183. if (!empty($google['size'])) {
  184. $this->_defaultOptions['div']['width'] = $google['size']['width'];
  185. $this->_defaultOptions['div']['height'] = $google['size']['height'];
  186. }
  187. if (!empty($google['staticSize'])) {
  188. $this->_defaultOptions['staticMap']['size'] = $google['staticSize'];
  189. }
  190. # the following are convience defaults - if not available the map lat/lng/zoom defaults will be used
  191. if (!empty($google['staticZoom'])) {
  192. $this->_defaultOptions['staticMap']['zoom'] = $google['staticZoom'];
  193. }
  194. if (!empty($google['staticLat'])) {
  195. $this->_defaultOptions['staticMap']['lat'] = $google['staticLat'];
  196. }
  197. if (!empty($google['staticLng'])) {
  198. $this->_defaultOptions['staticMap']['lng'] = $google['staticLng'];
  199. }
  200. if (isset($google['localImages'])) {
  201. if ($google['localImages'] === true) {
  202. //$google['localImages'] = IMAGES.'google_map'.DS;
  203. $google['localImages'] = Router::url('/img/google_map/', true);
  204. }
  205. $this->_defaultOptions['localImages'] = $google['localImages'];
  206. }
  207. $this->_currentOptions = $this->_defaultOptions;
  208. }
  209. /** Google Maps JS **/
  210. /**
  211. * JS maps.google API url
  212. * Like:
  213. * http://maps.google.com/maps/api/js?sensor=true
  214. * Adds Key - more variables could be added after it with "&key=value&..."
  215. * - region
  216. * @param bool $sensor
  217. * @param string $language (iso2: en, de, ja, ...)
  218. * @param string $append (more key-value-pairs to append)
  219. * @return string $fullUrl
  220. * 2009-03-09 ms
  221. */
  222. public function apiUrl($sensor = false, $api = null, $language = null, $append = null) {
  223. $url = $this->_protocol() . self::API;
  224. $url .= 'sensor=' . ($sensor ? 'true' : 'false');
  225. if (!empty($language)) {
  226. $url .= '&language='.$language;
  227. }
  228. /*
  229. if (!empty($this->key)) {
  230. $url .= '&key='.$this->key;
  231. }
  232. */
  233. if (!empty($api)) {
  234. $this->_currentOptions['map']['api'] = $api;
  235. }
  236. if (!empty($this->_currentOptions['map']['api'])) {
  237. $url .= '&v='.$this->_currentOptions['map']['api'];
  238. }
  239. if (!empty($append)) {
  240. $url .= $append;
  241. }
  242. $this->_apiIncluded = true;
  243. return $url;
  244. }
  245. //deprecated
  246. public function gearsUrl() {
  247. $this->_gearsIncluded = true;
  248. $url = $this->_protocol() . 'code.google.com/apis/gears/gears_init.js';
  249. return $url;
  250. }
  251. /**
  252. * @return string $currentMapObject
  253. * 2010-12-18 ms
  254. */
  255. public function name() {
  256. return 'map'.self::$MAP_COUNT;
  257. }
  258. /**
  259. * @return string $currentContainerId
  260. * 2010-12-18 ms
  261. */
  262. public function id() {
  263. return $this->_currentOptions['div']['id'];
  264. }
  265. /**
  266. * make it possible to include multiple maps per page
  267. * resets markers, infoWindows etc
  268. * @param full: true=optionsAsWell
  269. * @return void
  270. * 2010-12-18 ms
  271. */
  272. public function reset($full = true) {
  273. //self::$MAP_COUNT
  274. self::$MARKER_COUNT = self::$INFO_WINDOW_COUNT = 0;
  275. $this->markers = $this->infoWindows = array();
  276. if ($full) {
  277. $this->_currentOptions = $this->_defaultOptions;
  278. }
  279. }
  280. /**
  281. * set the controls of current map
  282. * @param array $controls:
  283. * - zoom, scale, overview: TRUE/FALSE
  284. *
  285. * - map: FALSE, small, large
  286. * - type: FALSE, normal, menu, hierarchical
  287. * TIP: faster/shorter by using only the first character (e.g. "H" for "hierarchical")
  288. *
  289. * 2011-03-15 ms
  290. */
  291. public function setControls($options = array()) {
  292. if (!empty($options['streetView'])) {
  293. $this->_currentOptions['map']['streetViewControl'] = $options['streetView'];
  294. }
  295. if (!empty($options['zoom'])) {
  296. $this->_currentOptions['map']['scaleControl'] = $options['zoom'];
  297. }
  298. if (isset($options['scrollwheel'])) {
  299. $this->_currentOptions['map']['scrollwheel'] = $options['scrollwheel'];
  300. }
  301. if (isset($options['keyboardShortcuts'])) {
  302. $this->_currentOptions['map']['keyboardShortcuts'] = $options['keyboardShortcuts'];
  303. }
  304. /*
  305. if (!empty($options['map'])) {
  306. if ($options['map'] === 'l' || $options['map'] === 'large') {
  307. $this->setMapControl('GLargeMapControl()');
  308. } else {
  309. $this->setMapControl('GSmallMapControl()');
  310. }
  311. }
  312. */
  313. if (!empty($options['type'])) {
  314. /*
  315. if ($options['type'] === 'm' || $options['type'] === 'menu') {
  316. $this->setMapControl('GMenuMapTypeControl()');
  317. } elseif ($options['type'] === 'h' || $options['type'] === 'hierarchical') {
  318. $this->setMapControl('GHierarchicalMapTypeControl()');
  319. } else {
  320. $this->setMapControl('GMapTypeControl()');
  321. }
  322. */
  323. $this->_currentOptions['map']['type'] = $options['type'];
  324. }
  325. }
  326. /**
  327. * This the initialization point of the script
  328. * Returns the div container you can echo on the website
  329. *
  330. * @param array $options associative array of settings are passed
  331. * @return string $divContainer
  332. * 2010-12-20 ms
  333. */
  334. public function map($options = array()) {
  335. $this->reset();
  336. $this->_currentOptions = Set::merge($this->_currentOptions, $options);
  337. $this->_currentOptions['map'] = array_merge($this->_currentOptions['map'], array('zoom'=>$this->_currentOptions['zoom'], 'lat' => $this->_currentOptions['lat'], 'lng' => $this->_currentOptions['lng'], 'type' => $this->_currentOptions['type']), $options);
  338. if (!$this->_currentOptions['map']['lat'] || !$this->_currentOptions['map']['lng']) {
  339. $this->_currentOptions['map']['lat'] = $this->_currentOptions['map']['defaultLat'];
  340. $this->_currentOptions['map']['lng'] = $this->_currentOptions['map']['defaultLng'];
  341. $this->_currentOptions['map']['zoom'] = $this->_currentOptions['map']['defaultZoom'];
  342. } elseif (!$this->_currentOptions['map']['zoom']) {
  343. $this->_currentOptions['map']['zoom'] = $this->_currentOptions['map']['defaultZoom'];
  344. }
  345. $result = '';
  346. # autoinclude js?
  347. if (!empty($this->_currentOptions['autoScript']) && !$this->_apiIncluded) {
  348. $res = $this->Html->script($this->apiUrl(), array('inline'=>$this->_currentOptions['inline']));
  349. if ($this->_currentOptions['inline']) {
  350. $result .= $res . PHP_EOL;
  351. }
  352. # usually already included
  353. //http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js
  354. }
  355. # still not very common: http://code.google.com/intl/de-DE/apis/maps/documentation/javascript/basics.html
  356. if (false && !empty($this->_currentOptions['autoScript']) && !$this->_gearsIncluded) {
  357. $res = $this->Html->script($this->gearsUrl(), array('inline'=>$this->_currentOptions['inline']));
  358. if ($this->_currentOptions['inline']) {
  359. $result .= $res . PHP_EOL;
  360. }
  361. }
  362. $map = "
  363. var initialLocation = ".$this->_initialLocation().";
  364. var browserSupportFlag = new Boolean();
  365. var myOptions = ".$this->_mapOptions().";
  366. // deprecated
  367. gMarkers".self::$MAP_COUNT." = new Array();
  368. gInfoWindows".self::$MAP_COUNT." = new Array();
  369. gWindowContents".self::$MAP_COUNT." = new Array();
  370. ";
  371. #rename "map_canvas" to "map_canvas1", ... if multiple maps on one page
  372. while (in_array($this->_currentOptions['div']['id'], $this->_mapIds)) {
  373. $this->_currentOptions['div']['id'] .= '-1'; //TODO: improve
  374. }
  375. $this->_mapIds[] = $this->_currentOptions['div']['id'];
  376. $map .= "
  377. var ".$this->name()." = new google.maps.Map(document.getElementById(\"".$this->_currentOptions['div']['id']."\"), myOptions);
  378. ";
  379. $this->map = $map;
  380. $this->_currentOptions['div']['style'] = '';
  381. if (is_numeric($this->_currentOptions['div']['width'])) {
  382. $this->_currentOptions['div']['width'] .= 'px';
  383. }
  384. if (is_numeric($this->_currentOptions['div']['height'])) {
  385. $this->_currentOptions['div']['height'] .= 'px';
  386. }
  387. $this->_currentOptions['div']['style'] .= 'width: '.$this->_currentOptions['div']['width'].';';
  388. $this->_currentOptions['div']['style'] .= 'height: '.$this->_currentOptions['div']['height'].';';
  389. unset($this->_currentOptions['div']['width']); unset($this->_currentOptions['div']['height']);
  390. $defaultText = isset($this->_currentOptions['content']) ? $this->_currentOptions['content'] : __('Map cannot be displayed!');
  391. $result .= $this->Html->tag('div', $defaultText, $this->_currentOptions['div']);
  392. return $result;
  393. }
  394. public function _initialLocation() {
  395. if ($this->_currentOptions['map']['lat'] && $this->_currentOptions['map']['lng']) {
  396. return "new google.maps.LatLng(".$this->_currentOptions['map']['lat'].", ".$this->_currentOptions['map']['lng'].")";
  397. }
  398. $this->_currentOptions['autoCenter'] = true;
  399. return 'false';
  400. }
  401. /**
  402. * @param array $options
  403. * - lat, lng, title, content, icon, directions
  404. * @return int $markerCount or false on failure
  405. * 2010-12-18 ms
  406. */
  407. public function addMarker($options) {
  408. if (empty($options)) {
  409. return false;
  410. }
  411. if (!isset($options['lat']) || !isset($options['lng'])) {
  412. return false;
  413. };
  414. if (!preg_match("/[-+]?\b[0-9]*\.?[0-9]+\b/", $options['lat']) || !preg_match("/[-+]?\b[0-9]*\.?[0-9]+\b/", $options['lng'])) {
  415. return false;
  416. }
  417. $defaults = $this->_currentOptions['marker'];
  418. if (isset($options['icon']) && is_array($options['icon'])) {
  419. $defaults = array_merge($defaults, $options['icon']);
  420. unset($options['icon']);
  421. }
  422. $options = array_merge($defaults, $options);
  423. $params = array();
  424. $params['map'] = $this->name();
  425. if (isset($options['title'])) {
  426. $params['title'] = json_encode($options['title']);
  427. }
  428. if (isset($options['icon'])) {
  429. $params['icon'] = $options['icon'];
  430. if (is_int($params['icon'])) {
  431. $params['icon'] = 'gIcons'.self::$MAP_COUNT.'['.$params['icon'].']';
  432. } else {
  433. $params['icon'] = json_encode($params['icon']);
  434. }
  435. }
  436. if (isset($options['shadow'])) {
  437. $params['shadow'] = $options['shadow'];
  438. if (is_int($params['shadow'])) {
  439. $params['shadow'] = 'gIcons'.self::$MAP_COUNT.'['.$params['shadow'].']';
  440. }
  441. }
  442. if (isset($options['shape'])) {
  443. $params['shape'] = $options['shape'];
  444. }
  445. if (isset($options['zIndex'])) {
  446. $params['zIndex'] = $options['zIndex'];
  447. }
  448. $marker = "
  449. var x".self::$MARKER_COUNT." = new google.maps.Marker({
  450. position: new google.maps.LatLng(".$options['lat'].",".$options['lng']."),
  451. ".$this->_toObjectParams($params, false, false)."
  452. });
  453. gMarkers".self::$MAP_COUNT.".push(
  454. x".self::$MARKER_COUNT."
  455. );
  456. ";
  457. $this->map.= $marker;
  458. if (!empty($options['directions'])) {
  459. $options['content'] .= $this->_directions($options['directions'], $options);
  460. }
  461. if (!empty($options['content']) && $this->_currentOptions['infoWindow']['useMultiple']) {
  462. $x = $this->addInfoWindow(array('content'=>$options['content']));
  463. $this->setContentInfoWindow($options['content'], $x);
  464. /*
  465. $marker .= "
  466. var window".self::$MARKER_COUNT." = new google.maps.InfoWindow({ content: '".$options['content']."',
  467. size: new google.maps.Size(50,50)
  468. });
  469. google.maps.event.addListener(x".self::$MARKER_COUNT.", 'click', function() {
  470. / ".$this->name().".setZoom(7); /
  471. infowindow.setContent(gWindows[".self::$MARKER_COUNT."]);
  472. infowindow.setPosition(event.latLng);
  473. infowindow.open(map);
  474. });
  475. ";
  476. */
  477. $this->addEvent($x);
  478. } elseif (!empty($options['content'])) {
  479. if (!isset($this->_currentOptions['marker']['infoWindow'])) {
  480. $this->_currentOptions['marker']['infoWindow'] = $this->addInfoWindow();
  481. }
  482. $x = $this->addInfoContent($options['content']);
  483. $event = "
  484. gInfoWindows".self::$MAP_COUNT."[".$this->_currentOptions['marker']['infoWindow']."].setContent(gWindowContents".self::$MAP_COUNT."[".self::$MARKER_COUNT."]);
  485. gInfoWindows".self::$MAP_COUNT."[".$this->_currentOptions['marker']['infoWindow']."].open(".$this->name().", gMarkers".self::$MAP_COUNT."[".self::$MARKER_COUNT."]);
  486. ";
  487. $this->addCustomEvent(self::$MARKER_COUNT, $event);
  488. }
  489. # custom matching event?
  490. if (isset($options['id'])) {
  491. $this->matching[$options['id']] = self::$MARKER_COUNT;
  492. }
  493. /*
  494. //$this->mapMarkers[$id] = ;
  495. //$function = 'function() { '.$id.'.'.$call.'("'.$content.'");}';
  496. $function = 'function() { mapMarkers[\''.$id.'\'].'.$call.'(mapWindows[\''.$id.'\']);}';
  497. $this->addListener($id, $function, isset($options['action'])?$options['action']:null);
  498. //"gInfoWindows".self::$MAP_COUNT.".setContent(gWindowContents1[1]);
  499. //"gInfoWindows".self::$MAP_COUNT.".open(map1, gMarkers1[1]);
  500. */
  501. return self::$MARKER_COUNT++;
  502. }
  503. /**
  504. * build directions form (type get) for directions inside infoWindows
  505. * @param mixed $directions
  506. * - bool TRUE for autoDirections (using lat/lng)
  507. * @param array $options
  508. * - options array of marker for autoDirections etc (optional)
  509. * 2011-03-22 ms
  510. */
  511. public function _directions($directions, $markerOptions = array()) {
  512. $options = array(
  513. 'from' => null,
  514. 'to' => null,
  515. 'label' => __('Enter your address'),
  516. 'submit' => __('Get directions'),
  517. 'escape' => true,
  518. 'zoom' => null, # auto
  519. );
  520. if ($directions === true) {
  521. $options['to'] = $markerOptions['lat'].','.$markerOptions['lng'];
  522. } elseif (is_array($directions)) {
  523. $options = array_merge($options, $directions);
  524. }
  525. if (empty($options['to']) && empty($options['from'])) {
  526. return '';
  527. }
  528. $form = '<form action="http://maps.google.com/maps" method="get" target="_blank">';
  529. $form .= $options['escape'] ? h($options['label']) : $options['label'];
  530. if (!empty($options['from'])) {
  531. $form .= '<input type="hidden" name="saddr" value="'.$options['from'].'" />';
  532. } else {
  533. $form .= '<input type="text" name="saddr" />';
  534. }
  535. if (!empty($options['to'])) {
  536. $form .= '<input type="hidden" name="daddr" value="'.$options['to'].'" />';
  537. } else {
  538. $form .= '<input type="text" name="daddr" />';
  539. }
  540. if (isset($options['zoom'])) {
  541. $form .= '<input type="hidden" name="z" value="'.$options['zoom'].'" />';
  542. }
  543. $form .= '<input type="submit" value="'.$options['submit'].'" />';
  544. $form .= '</form>';
  545. return '<div class="directions">'.$form.'</div>';
  546. }
  547. public function addInfoContent($con) {
  548. $this->infoContents[self::$MARKER_COUNT] = $this->escapeString($con);
  549. $event = "
  550. gWindowContents".self::$MAP_COUNT.".push(".$this->escapeString($con).");
  551. ";
  552. $this->addCustom($event);
  553. //TODO: own count?
  554. return self::$MARKER_COUNT;
  555. }
  556. public $setIcons = array(
  557. 'color' => 'http://www.google.com/mapfiles/marker%s.png',
  558. 'alpha' => 'http://www.google.com/mapfiles/marker%s%s.png',
  559. 'numeric' => 'http://google-maps-icons.googlecode.com/files/%s%s.png',
  560. 'special' => 'http://google-maps-icons.googlecode.com/files/%s.png'
  561. );
  562. /**
  563. * get a custom icon set
  564. * @param color: green, red, purple, ... or some special ones like "home", ...
  565. * @param char: A...Z or 0...20/100 (defaults to none)
  566. * @param size: s, m, l (defaults to medium)
  567. * NOTE: for special ones only first parameter counts!
  568. * @return array: array(icon, shadow, shape, ...)
  569. * 2011-03-14 ms
  570. */
  571. public function iconSet($color, $char = null, $size = 'm') {
  572. $colors = array('red', 'green', 'yellow', 'blue', 'purple', 'white', 'black');
  573. if (!in_array($color, $colors)) {
  574. $color = 'red';
  575. }
  576. if (!empty($this->_currentOptions['localImages'])) {
  577. $this->setIcons['color'] = $this->_currentOptions['localImages'].'marker%s.png';
  578. $this->setIcons['alpha'] = $this->_currentOptions['localImages'].'marker%s%s.png';
  579. $this->setIcons['numeric'] = $this->_currentOptions['localImages'].'%s%s.png';
  580. $this->setIcons['special'] = $this->_currentOptions['localImages'].'%s.png';
  581. }
  582. if (!empty($char)) {
  583. if ($color === 'red') {
  584. $color = '';
  585. } else {
  586. $color = '_'.$color;
  587. }
  588. $url = sprintf($this->setIcons['alpha'], $color, $char);
  589. } else {
  590. if ($color === 'red') {
  591. $color = '';
  592. } else {
  593. $color = '_'.$color;
  594. }
  595. $url = sprintf($this->setIcons['color'], $color);
  596. }
  597. /*
  598. var iconImage = new google.maps.MarkerImage('images/' + images[0] + '.png',
  599. new google.maps.Size(iconData[images[0]].width, iconData[images[0]].height),
  600. new google.maps.Point(0,0),
  601. new google.maps.Point(0, 32)
  602. );
  603. var iconShadow = new google.maps.MarkerImage('images/' + images[1] + '.png',
  604. new google.maps.Size(iconData[images[1]].width, iconData[images[1]].height),
  605. new google.maps.Point(0,0),
  606. new google.maps.Point(0, 32)
  607. );
  608. var iconShape = {
  609. coord: [1, 1, 1, 32, 32, 32, 32, 1],
  610. type: 'poly'
  611. };
  612. */
  613. $shadow = 'http://www.google.com/mapfiles/shadow50.png';
  614. $res = array('url'=>$url, 'icon'=>$this->icon($url, array('size'=>array('width'=>20, 'height'=>34))), 'shadow'=>$this->icon($shadow, array('size'=>array('width'=>37, 'height'=>34), 'shadow'=>array('width'=>10, 'height'=>34))));
  615. //$this->icons[$ICON_COUNT] = $res;
  616. //$ICON_COUNT++;
  617. return $res;
  618. }
  619. /**
  620. * @param string $imageUrl (http://...)
  621. * @param string $shadowImageUrl (http://...)
  622. * @param array $imageOptions
  623. * @param array $shadowImageOptions
  624. * custom icon: http://thydzik.com/thydzikGoogleMap/markerlink.php?text=?&color=FFFFFF
  625. * custom icons: http://code.google.com/p/google-maps-icons/wiki/NumericIcons#Lettered_Balloons_from_A_to_Z,_in_10_Colors
  626. * custom shadows: http://www.cycloloco.com/shadowmaker/shadowmaker.htm
  627. * 2011-03-13 ms
  628. */
  629. public function addIcon($image, $shadow = null, $imageOptions = array(), $shadowOptions = array()) {
  630. $res = array('url'=>$image);
  631. $res['icon'] = $this->icon($image, $imageOptions);
  632. if ($shadow) {
  633. $last = $this->_iconRemember[$res['icon']];
  634. if (!isset($shadowOptions['anchor'])) {
  635. $shadowOptions['anchor'] = array();
  636. }
  637. $shadowOptions['anchor'] = array_merge($shadowOptions['anchor'], $last['options']['anchor']);
  638. $res['shadow'] = $this->icon($shadow, $shadowOptions);
  639. }
  640. return $res;
  641. }
  642. protected $_iconRemember = array();
  643. /**
  644. * generate icon object
  645. * @param url (required)
  646. * @param options (optional):
  647. * - size: array(width=>x, height=>y)
  648. * - origin: array(width=>x, height=>y)
  649. * - anchor: array(width=>x, height=>y)
  650. */
  651. public function icon($url, $options = array()) {
  652. // The shadow image is larger in the horizontal dimension
  653. // while the position and offset are the same as for the main image.
  654. if (empty($options['size'])) {
  655. if ($data = @getimagesize($url)) {
  656. $options['size']['width'] = $data[0];
  657. $options['size']['height'] = $data[1];
  658. } else {
  659. $options['size']['width'] = $options['size']['height'] = 0;
  660. }
  661. }
  662. if (empty($options['anchor'])) {
  663. $options['anchor']['width'] = intval($options['size']['width']/2);
  664. $options['anchor']['height'] = $options['size']['height'];
  665. }
  666. if (empty($options['origin'])) {
  667. $options['origin']['width'] = $options['origin']['height'] = 0;
  668. }
  669. if (isset($options['shadow'])) {
  670. $options['anchor'] = $options['shadow'];
  671. }
  672. //pr(returns($options));
  673. $icon = 'new google.maps.MarkerImage(\''.$url.'\',
  674. new google.maps.Size('.$options['size']['width'].', '.$options['size']['height'].'),
  675. new google.maps.Point('.$options['origin']['width'].', '.$options['origin']['height'].'),
  676. new google.maps.Point('.$options['anchor']['width'].', '.$options['anchor']['height'].')
  677. )';
  678. $this->icons[self::$ICON_COUNT] = $icon;
  679. $this->_iconRemember[self::$ICON_COUNT] = array('url'=>$url, 'options'=>$options, 'id'=>self::$ICON_COUNT);
  680. //$this->map .= $code;
  681. return self::$ICON_COUNT++;
  682. }
  683. /**
  684. * @param array $options
  685. * - lat, lng, content, maxWidth, pixelOffset, zIndex
  686. * @return int $windowCount
  687. * 2010-12-18 ms
  688. */
  689. public function addInfoWindow($options=array()) {
  690. $options = $this->_currentOptions['infoWindow'];
  691. $options = array_merge($options, $options);
  692. if (!empty($options['lat']) && !empty($options['lng'])) {
  693. $position = "new google.maps.LatLng(".$options['lat'].", ".$options['lng'].")";
  694. } else {
  695. $position = " ".$this->name().".getCenter()";
  696. }
  697. $windows = "
  698. gInfoWindows".self::$MAP_COUNT.".push( new google.maps.InfoWindow({
  699. position: {$position},
  700. content: ".$this->escapeString($options['content']).",
  701. maxWidth: {$options['maxWidth']},
  702. pixelOffset: {$options['pixelOffset']}
  703. /*zIndex: {$options['zIndex']},*/
  704. }));
  705. ";
  706. $this->map .= $windows;
  707. return self::$INFO_WINDOW_COUNT++;
  708. }
  709. /**
  710. * @param int $marker
  711. * @param int $infoWindow
  712. * @return void
  713. * 2010-12-18 ms
  714. */
  715. public function addEvent($marker, $infoWindow) {
  716. $this->map .= "
  717. google.maps.event.addListener(gMarkers[{$marker}], 'click', function() {
  718. gInfoWindows".self::$MAP_COUNT."[$infoWindow].open(".$this->name().", this);
  719. });
  720. ";
  721. }
  722. /**
  723. * @param int $marker
  724. * @param string $event (js)
  725. * @return void
  726. * 2010-12-18 ms
  727. */
  728. public function addCustomEvent($marker, $event) {
  729. $this->map .= "
  730. google.maps.event.addListener(gMarkers".self::$MAP_COUNT."[{$marker}], 'click', function() {
  731. $event
  732. });
  733. ";
  734. }
  735. /**
  736. * @param string $custom (js)
  737. * @return void
  738. * 2010-12-18 ms
  739. */
  740. public function addCustom($js) {
  741. $this->map .= $js;
  742. }
  743. /**
  744. * @param string $content (html/text)
  745. * @param int $infoWindowCount
  746. * @return void
  747. * 2010-12-18 ms
  748. */
  749. public function setContentInfoWindow($con, $index) {
  750. $this->map .= "
  751. gInfoWindows".self::$MAP_COUNT."[$index].setContent(".$this->escapeString($con).");";
  752. }
  753. /**
  754. * json encode string
  755. *
  756. * @param mixed $content
  757. * @return json
  758. */
  759. public function escapeString($content) {
  760. return json_encode($content);
  761. }
  762. /**
  763. * This method returns the javascript for the current map container
  764. * Just echo it below the map container
  765. * @return string
  766. * 2010-12-18 ms
  767. */
  768. public function script() {
  769. $script='<script type="text/javascript">
  770. '.$this->_arrayToObject('matching', $this->matching, false, true).'
  771. '.$this->_arrayToObject('gIcons'.self::$MAP_COUNT, $this->icons, false, false).'
  772. jQuery(document).ready(function() {
  773. ';
  774. $script .= $this->map;
  775. if ($this->_currentOptions['geolocate']) {
  776. $script .= $this->_geolocate();
  777. }
  778. if ($this->_currentOptions['showMarker'] && !empty($this->markers) && is_array($this->markers)) {
  779. $script .= implode($this->markers, " ");
  780. }
  781. if ($this->_currentOptions['autoCenter']) {
  782. $script .= $this->_autoCenter();
  783. }
  784. $script .= '
  785. });
  786. </script>';
  787. self::$MAP_COUNT++;
  788. return $script;
  789. }
  790. /**
  791. * set a custom geolocate callback
  792. * @param string $customJs
  793. * false: no callback at all
  794. * @return void
  795. * 2011-03-16 ms
  796. */
  797. public function geolocateCallback($js) {
  798. if ($js === false) {
  799. $this->_currentOptions['callbacks']['geolocate'] = false;
  800. return;
  801. }
  802. $this->_currentOptions['callbacks']['geolocate'] = $js;
  803. }
  804. /**
  805. * experimental - works in cutting edge browsers like chrome10
  806. * 2011-03-16 ms
  807. */
  808. protected function _geolocate() {
  809. return '
  810. // Try W3C Geolocation (Preferred)
  811. if (navigator.geolocation) {
  812. browserSupportFlag = true;
  813. navigator.geolocation.getCurrentPosition(function(position) {
  814. geolocationCallback(position.coords.latitude, position.coords.longitude);
  815. }, function() {
  816. handleNoGeolocation(browserSupportFlag);
  817. });
  818. // Try Google Gears Geolocation
  819. } else if (google.gears) {
  820. browserSupportFlag = true;
  821. var geo = google.gears.factory.create(\'beta.geolocation\');
  822. geo.getCurrentPosition(function(position) {
  823. geolocationCallback(position.latitude, position.longitude);
  824. }, function() {
  825. handleNoGeoLocation(browserSupportFlag);
  826. });
  827. // Browser doesn\'t support Geolocation
  828. } else {
  829. browserSupportFlag = false;
  830. handleNoGeolocation(browserSupportFlag);
  831. }
  832. function geolocationCallback(lat, lng) {
  833. '.$this->_geolocationCallback().'
  834. }
  835. function handleNoGeolocation(errorFlag) {
  836. if (errorFlag == true) {
  837. //alert("Geolocation service failed.");
  838. } else {
  839. //alert("Your browser doesn\'t support geolocation. We\'ve placed you in Siberia.");
  840. }
  841. //'.$this->name().'.setCenter(initialLocation);
  842. }
  843. ';
  844. }
  845. protected function _geolocationCallback() {
  846. if (($js = $this->_currentOptions['callbacks']['geolocate']) === false) {
  847. return '';
  848. }
  849. if ($js === null) {
  850. $js = 'initialLocation = new google.maps.LatLng(lat, lng);
  851. '.$this->name().'.setCenter(initialLocation);
  852. ';
  853. }
  854. return $js;
  855. }
  856. /**
  857. * auto center map
  858. * careful: with only one marker this can result in too high zoom values!
  859. * @return string $autoCenterCommands
  860. * 2010-12-17 ms
  861. */
  862. protected function _autoCenter() {
  863. return '
  864. var bounds = new google.maps.LatLngBounds();
  865. $.each(gMarkers'.self::$MAP_COUNT.',function (index, marker) { bounds.extend(marker.position);});
  866. '.$this->name().'.fitBounds(bounds);
  867. ';
  868. }
  869. /**
  870. * @return json like js string
  871. * 2010-12-17 ms
  872. */
  873. protected function _mapOptions() {
  874. $options = array_merge($this->_currentOptions, $this->_currentOptions['map']);
  875. $mapOptions = array_intersect_key($options, array(
  876. 'streetViewControl' => null,
  877. 'navigationControl' => null,
  878. 'mapTypeControl' => null,
  879. 'scaleControl' => null,
  880. 'scrollwheel' => null,
  881. 'zoom' => null,
  882. 'keyboardShortcuts' => null
  883. ));
  884. $res = array();
  885. foreach ($mapOptions as $key => $mapOption) {
  886. $res[] = $key.': '.$this->Js->value($mapOption);
  887. }
  888. if (empty($options['autoCenter'])) {
  889. $res[] = 'center: initialLocation';
  890. }
  891. if (!empty($options['navOptions'])) {
  892. $res[] = 'navigationControlOptions: '.$this->_controlOptions('nav', $options['navOptions']);
  893. }
  894. if (!empty($options['typeOptions'])) {
  895. $res[] = 'mapTypeControlOptions: '.$this->_controlOptions('type', $options['typeOptions']);
  896. }
  897. if (!empty($options['scaleOptions'])) {
  898. $res[] = 'scaleControlOptions: '.$this->_controlOptions('scale', $options['scaleOptions']);
  899. }
  900. if (array_key_exists($options['type'], $this->types)) {
  901. $type = $this->types[$options['type']];
  902. } else {
  903. $type = $options['type'];
  904. }
  905. $res[] = 'mapTypeId: google.maps.MapTypeId.'.$type;
  906. return '{'.implode(', ', $res).'}';
  907. }
  908. /**
  909. * @return json like js string
  910. * 2010-12-17 ms
  911. */
  912. protected function _controlOptions($type, $options) {
  913. $mapping = array(
  914. 'nav' => 'NavigationControlStyle',
  915. 'type' => 'MapTypeControlStyle',
  916. 'scale' => ''
  917. );
  918. $res = array();
  919. if (!empty($options['style']) && ($m = $mapping[$type])) {
  920. $res[] = 'style: google.maps.'.$m.'.'.$options['style'];
  921. }
  922. if (!empty($options['pos'])) {
  923. $res[] = 'position: google.maps.ControlPosition.'.$options['pos'];
  924. }
  925. return '{'.implode(', ', $res).'}';
  926. }
  927. /** Google Maps Link **/
  928. /**
  929. * returns a maps.google link
  930. *
  931. * @param string $linkTitle
  932. * @param array $mapOptions
  933. * @param array $linkOptions
  934. * @return string Html link
  935. * 2011-03-12 ms
  936. */
  937. public function mapLink($title, $mapOptions = array(), $linkOptions = array()) {
  938. return $this->Html->link($title, $this->mapUrl($mapOptions), $linkOptions);
  939. }
  940. /**
  941. * returns a maps.google url
  942. *
  943. * @param array options:
  944. * - from: necessary (address or lat,lng)
  945. * - to: 1x necessary (address or lat,lng - can be an array of multiple destinations: array('dest1', 'dest2'))
  946. * - zoom: optional (defaults to none)
  947. * @return string link: http://...
  948. * 2010-12-18 ms
  949. */
  950. public function mapUrl($options = array()) {
  951. $url = $this->_protocol() . 'maps.google.com/maps?';
  952. $urlArray = array();
  953. if (!empty($options['from'])) {
  954. $urlArray[] = 'saddr=' . urlencode($options['from']);
  955. }
  956. if (!empty($options['to']) && is_array($options['to'])) {
  957. $to = array_shift($options['to']);
  958. foreach ($options['to'] as $key => $value) {
  959. $to .= '+to:' . $value;
  960. }
  961. $urlArray[] = 'daddr=' . urlencode($to);
  962. } elseif (!empty($options['to'])) {
  963. $urlArray[] = 'daddr=' . urlencode($options['to']);
  964. }
  965. if (!empty($options['zoom'])) {
  966. $urlArray[] = 'z=' . (int)$options['zoom'];
  967. }
  968. //$urlArray[] = 'f=d';
  969. //$urlArray[] = 'hl=de';
  970. //$urlArray[] = 'ie=UTF8';
  971. return $url . (implode('&', $urlArray));
  972. }
  973. /** STATIC MAP **/
  974. /** http://maps.google.com/staticmap?center=40.714728,-73.998672&zoom=14&size=512x512&maptype=mobile&markers=40.702147,-74.015794,blues%7C40.711614,-74.012318,greeng%7C40.718217,-73.998284,redc&mobile=true&sensor=false **/
  975. /**
  976. * Create a plain image map
  977. * @link http://code.google.com/intl/de-DE/apis/maps/documentation/staticmaps
  978. * @param options:
  979. * - string $size [necessary: VALxVAL, e.g. 500x400 - max 640x640]
  980. * - string $center: x,y or address [necessary, if no markers are given; else tries to take defaults if available] or TRUE/FALSE
  981. * - int $zoom [optional; if no markers are given, default value is used; if set to "auto" and ]*
  982. * - array $markers [optional, @see staticPaths() method]
  983. * - string $type [optional: roadmap/hybrid, ...; default:roadmap]
  984. * - string $mobile TRUE/FALSE
  985. * - string $visible: $area (x|y|...)
  986. * - array $paths [optional, @see staticPaths() method]
  987. * - string $language [optional]
  988. * @param array $attributes: html attributes for the image
  989. * - title
  990. * - alt (defaults to 'Map')
  991. * - url (tip: you can pass $this->link(...) and it will create a link to maps.google.com)
  992. * @return string $imageTag
  993. * 2010-12-18 ms
  994. */
  995. public function staticMap($options = array(), $attributes = array()) {
  996. $defaultAttributes = array('alt' => __('Map'));
  997. return $this->Html->image($this->staticMapUrl($options), array_merge($defaultAttributes, $attributes));
  998. }
  999. /**
  1000. * Create a link to a plain image map
  1001. * @param string $linkTitle
  1002. * @param array $mapOptions
  1003. * @param array $linkOptions
  1004. * @return string Html link
  1005. * 2011-03-12 ms
  1006. */
  1007. public function staticMapLink($title, $mapOptions = array(), $linkOptions = array()) {
  1008. return $this->Html->link($title, $this->staticMapUrl($mapOptions), $linkOptions);
  1009. }
  1010. /**
  1011. * Create an url to a plain image map
  1012. * @param options
  1013. * - see staticMap() for details
  1014. * @return string $urlOfImage: http://...
  1015. * 2010-12-18 ms
  1016. */
  1017. public function staticMapUrl($options = array()) {
  1018. $map = $this->_protocol() . self::STATIC_API;
  1019. /*
  1020. $params = array(
  1021. 'sensor' => 'false',
  1022. 'mobile' => 'false',
  1023. 'format' => 'png',
  1024. //'center' => false
  1025. );
  1026. if (!empty($options['sensor'])) {
  1027. $params['sensor'] = 'true';
  1028. }
  1029. if (!empty($options['mobile'])) {
  1030. $params['mobile'] = 'true';
  1031. }
  1032. */
  1033. $defaults = array_merge($this->_defaultOptions, $this->_defaultOptions['staticMap']);
  1034. $mapOptions = array_merge($defaults, $options);
  1035. $params = array_intersect_key($mapOptions, array(
  1036. 'sensor' => null,
  1037. 'mobile' => null,
  1038. 'format' => null,
  1039. 'size' => null,
  1040. //'zoom' => null,
  1041. //'lat' => null,
  1042. //'lng' => null,
  1043. //'visible' => null,
  1044. //'type' => null,
  1045. ));
  1046. # do we want zoom to auto-correct itself?
  1047. if (!isset($options['zoom']) && !empty($mapOptions['markers'])|| !empty($mapOptions['paths']) || !empty($mapOptions['visible'])) {
  1048. $options['zoom'] = 'auto';
  1049. }
  1050. # a position on the map that is supposed to stay visible at all cost
  1051. if (!empty($mapOptions['visible'])) {
  1052. $params['visible'] = urlencode($mapOptions['visible']);
  1053. }
  1054. # center and zoom are not necccessary if path, visible or markers are given
  1055. if (!isset($options['center']) || $options['center'] === false) {
  1056. # dont use it
  1057. } elseif ($options['center'] === true && $mapOptions['lat'] !== null && $mapOptions['lng'] !== null) {
  1058. $params['center'] = urlencode((string)$mapOptions['lat'] . ',' . (string)$mapOptions['lng']);
  1059. } elseif (!empty($options['center'])) {
  1060. $params['center'] = urlencode($options['center']);
  1061. } /*else {
  1062. # try to read from markers array???
  1063. if (isset($options['markers']) && count($options['markers']) == 1) {
  1064. //pr ($options['markers']);
  1065. }
  1066. }*/
  1067. if (!isset($options['zoom']) || $options['zoom'] === false) {
  1068. # dont use it
  1069. } else {
  1070. if ($options['zoom'] === 'auto') {
  1071. if (!empty($options['markers']) && strpos($options['zoom'],'|') !== false) {
  1072. # let google find the best zoom value itself
  1073. } else {
  1074. # do something here?
  1075. }
  1076. } else {
  1077. $params['zoom'] = $options['zoom'];
  1078. }
  1079. }
  1080. if (array_key_exists($mapOptions['type'], $this->types)) {
  1081. $params['maptype'] = $this->types[$mapOptions['type']];
  1082. } else {
  1083. $params['maptype'] = $mapOptions['type'];
  1084. }
  1085. //unset($options['type']);
  1086. $params['maptype'] = strtolower($params['maptype']);
  1087. # old: {latitude},{longitude},{color}{alpha-character}
  1088. # new: @see staticMarkers()
  1089. if (!empty($options['markers'])) {
  1090. $params['markers'] = $options['markers'];
  1091. }
  1092. if (!empty($options['paths'])) {
  1093. $params['path'] = $options['paths'];
  1094. }
  1095. # valXval
  1096. if (!empty($options['size'])) {
  1097. $params['size'] = $options['size'];
  1098. }
  1099. $pieces = array();
  1100. foreach ($params as $key => $value) {
  1101. if (is_array($value)) {
  1102. $value = implode('&'.$key.'=', $value);
  1103. } elseif ($value === true) {
  1104. $value = 'true';
  1105. } elseif ($value === false) {
  1106. $value = 'false';
  1107. } elseif ($value === null) {
  1108. continue;
  1109. }
  1110. $pieces[] = $key.'='.$value;
  1111. //$map .= $key.'='.$value.'&';
  1112. }
  1113. return $map . (implode('&', $pieces));
  1114. }
  1115. /**
  1116. * prepare paths for staticMap
  1117. * @param array $pathElementArrays
  1118. * - elements: [required] (multiple array(lat=>x, lng=>y) or just a address strings)
  1119. * - color: red/blue/green (optional, default blue)
  1120. * - weight: numeric (optional, default: 5)
  1121. * @return string $paths: e.g: color:0x0000FF80|weight:5|37.40303,-122.08334|37.39471,-122.07201|37.40589,-122.06171{|...}
  1122. * 2010-12-18 ms
  1123. */
  1124. public function staticPaths($pos = array()) {
  1125. $defaults = array(
  1126. 'color' => 'blue',
  1127. 'weight' => 5 # pixel
  1128. );
  1129. # not a 2-level array? make it one
  1130. if (!isset($pos[0])) {
  1131. $pos = array($pos);
  1132. }
  1133. $res = array();
  1134. foreach ($pos as $p) {
  1135. $options = array_merge($defaults, $p);
  1136. $markers = $options['path'];
  1137. unset($options['path']);
  1138. # prepare color
  1139. if (!empty($options['color'])) {
  1140. $options['color'] = $this->_prepColor($options['color']);
  1141. }
  1142. $path = array();
  1143. foreach ($options as $key => $value) {
  1144. $path[] = $key.':'.urlencode($value);
  1145. }
  1146. foreach ($markers as $key => $pos) {
  1147. if (is_array($pos)) {
  1148. # lat/lng?
  1149. $pos = $pos['lat'].','.$pos['lng'];
  1150. }
  1151. $path[] = $pos;
  1152. }
  1153. $res[] = implode('|', $path);
  1154. }
  1155. return $res;
  1156. }
  1157. /**
  1158. * prepare markers for staticMap
  1159. * @param array $markerArrays
  1160. * - lat: xx.xxxxxx (necessary)
  1161. * - lng: xx.xxxxxx (necessary)
  1162. * - address: (instead of lat/lng)
  1163. * - color: red/blue/green (optional, default blue)
  1164. * - label: a-z or numbers (optional, default: s)
  1165. * - icon: custom icon (png, gif, jpg - max 64x64 - max 5 different icons per image)
  1166. * - shadow: TRUE/FALSE
  1167. * @param style (global) (overridden by custom marker styles)
  1168. * - color
  1169. * - label
  1170. * - icon
  1171. * - shadow
  1172. * @return array $markers: color:green|label:Z|48,11|Berlin
  1173. *
  1174. * NEW: size:mid|color:red|label:E|37.400465,-122.073003|37.437328,-122.159928&markers=size:small|color:blue|37.369110,-122.096034
  1175. * OLD: 40.702147,-74.015794,blueS|40.711614,-74.012318,greenG{|...}
  1176. * 2010-12-18 ms
  1177. */
  1178. public function staticMarkers($pos = array(), $style = array()) {
  1179. $markers = array();
  1180. $verbose = false;
  1181. $defaults = array(
  1182. 'shadow' => 'true',
  1183. 'color' => 'blue',
  1184. 'label' => '',
  1185. 'address' => '',
  1186. 'size' => ''
  1187. );
  1188. # not a 2-level array? make it one
  1189. if (!isset($pos[0])) {
  1190. $pos = array($pos);
  1191. }
  1192. # new in statitV2: separate styles! right now just merged
  1193. foreach ($pos as $p) {
  1194. $p = array_merge($defaults, $style, $p);
  1195. # adress or lat/lng?
  1196. if (!empty($p['lat']) && !empty($p['lng'])) {
  1197. $p['address'] = $p['lat'].','.$p['lng'];
  1198. } else {
  1199. $p['address'] = $p['address'];
  1200. }
  1201. $p['address'] = urlencode($p['address']);
  1202. $values = array();
  1203. # prepare color
  1204. if (!empty($p['color'])) {
  1205. $p['color'] = $this->_prepColor($p['color']);
  1206. $values[] = 'color:'.$p['color'];
  1207. }
  1208. # label? A-Z0-9
  1209. if (!empty($p['label'])) {
  1210. $values[] = 'label:'.strtoupper($p['label']);
  1211. }
  1212. if (!empty($p['size'])) {
  1213. $values[] = 'size:'.$p['size'];
  1214. }
  1215. if (!empty($p['shadow'])) {
  1216. $values[] = 'shadow:'.$p['shadow'];
  1217. }
  1218. if (!empty($p['icon'])) {
  1219. $values[] = 'icon:'.urlencode($p['icon']);
  1220. }
  1221. $values[] = $p['address'];
  1222. //TODO: icons
  1223. $markers[] = implode('|', $values);
  1224. }
  1225. //TODO: shortcut? only possible if no custom params!
  1226. if ($verbose) {
  1227. }
  1228. // long: markers=styles1|address1&markers=styles2|address2&...
  1229. // short: markers=styles,address1|address2|address3|...
  1230. return $markers;
  1231. }
  1232. /**
  1233. * Ensure that we stay on the appropriate protocol
  1234. *
  1235. * @return string protocol base (including ://)
  1236. */
  1237. protected function _protocol() {
  1238. if (($https = $this->_currentOptions['https']) === null) {
  1239. $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on';
  1240. }
  1241. return ($https ? 'https' : 'http') . '://';
  1242. }
  1243. /**
  1244. * # to 0x
  1245. * or # added
  1246. * @param string $color: FFFFFF, #FFFFFF, 0xFFFFFF or blue
  1247. * @return string $color
  1248. * 2010-12-20 ms
  1249. */
  1250. protected function _prepColor($color) {
  1251. if (strpos($color, '#') !== false) {
  1252. return str_replace('#', '0x', $color);
  1253. } elseif (is_numeric($color)) {
  1254. return '0x'.$color;
  1255. }
  1256. return $color;
  1257. }
  1258. /** TODOS/EXP **/
  1259. /*
  1260. TODOS:
  1261. - animations
  1262. marker.setAnimation(google.maps.Animation.BOUNCE);
  1263. - geocoding (+ reverse)
  1264. - directions
  1265. - overlays
  1266. - fluster (for clustering?)
  1267. or
  1268. - markerManager (many markers)
  1269. - infoBox
  1270. http://google-maps-utility-library-v3.googlecode.com/svn/tags/infobox/
  1271. - ...
  1272. */
  1273. public function geocoder() {
  1274. $js = 'var geocoder = new google.maps.Geocoder();';
  1275. //TODO
  1276. }
  1277. /**
  1278. * managing lots of markers!
  1279. * @link http://google-maps-utility-library-v3.googlecode.com/svn/tags/markermanager/1.0/docs/examples.html
  1280. * @param options
  1281. * -
  1282. * @return void
  1283. * 2010-12-18 ms
  1284. */
  1285. public function setManager() {
  1286. $js .= '
  1287. var mgr'.self::$MAP_COUNT.' = new MarkerManager('.$this->name().');
  1288. ';
  1289. }
  1290. public function addManagerMarker($marker, $options) {
  1291. $js = 'mgr'.self::$MAP_COUNT.'.addMarker('.$marker.');';
  1292. }
  1293. /**
  1294. * clustering for lots of markers!
  1295. * @link ?
  1296. * @param options
  1297. * -
  1298. * based on Fluster2 0.1.1
  1299. * @return void
  1300. */
  1301. public function setCluster($options) {
  1302. $js = self::$flusterScript;
  1303. $js .= '
  1304. var fluster'.self::$MAP_COUNT.' = new Fluster2('.$this->name().');
  1305. ';
  1306. # styles
  1307. 'fluster'.self::$MAP_COUNT.'.styles = {}';
  1308. $this->map .= $js;
  1309. }
  1310. public function addClusterMarker($marker, $options) {
  1311. $js = 'fluster'.self::$MAP_COUNT.'.addMarker('.$marker.');';
  1312. }
  1313. public function initCluster() {
  1314. $this->map .= 'fluster'.self::$MAP_COUNT.'.initialize();';
  1315. }
  1316. public static $flusterScript = '
  1317. function Fluster2(_map,_debug) {var map=_map;var projection=new Fluster2ProjectionOverlay(map);var me=this;var clusters=new Object();var markersLeft=new Object();this.debugEnabled=_debug;this.gridSize=60;this.markers=new Array();this.currentZoomLevel=-1;this.styles={0:{image:\'http://gmaps-utility-library.googlecode.com/svn/trunk/markerclusterer/1.0/images/m1.png\',textColor:\'#FFFFFF\',width:53,height:52},10:{image:\'http://gmaps-utility-library.googlecode.com/svn/trunk/markerclusterer/1.0/images/m2.png\',textColor:\'#FFFFFF\',width:56,height:55},20:{image:\'http://gmaps-utility-library.googlecode.com/svn/trunk/markerclusterer/1.0/images/m3.png\',textColor:\'#FFFFFF\',width:66,height:65}};var zoomChangedTimeout=null;function createClusters() {var zoom=map.getZoom();if (clusters[zoom]) {me.debug(\'Clusters for zoom level \'+zoom+\' already initialized.\')} else {var clustersThisZoomLevel=new Array();var clusterCount=0;var markerCount=me.markers.length;for (var i=0;i<markerCount;i++) {var marker=me.markers[i];var markerPosition=marker.getPosition();var done=false;for (var j=clusterCount-1;j>=0;j--) {var cluster=clustersThisZoomLevel[j];if (cluster.contains(markerPosition)) {cluster.addMarker(marker);done=true;break}}if (!done) {var cluster=new Fluster2Cluster(me,marker);clustersThisZoomLevel.push(cluster);clusterCount++}}clusters[zoom]=clustersThisZoomLevel;me.debug(\'Initialized \'+clusters[zoom].length+\' clusters for zoom level \'+zoom+\'.\')}if (clusters[me.currentZoomLevel]) {for (var i=0;i<clusters[me.currentZoomLevel].length;i++) {clusters[me.currentZoomLevel][i].hide()}}me.currentZoomLevel=zoom;showClustersInBounds()}function showClustersInBounds() {var mapBounds=map.getBounds();for (var i=0;i<clusters[me.currentZoomLevel].length;i++) {var cluster=clusters[me.currentZoomLevel][i];if (mapBounds.contains(cluster.getPosition())) {cluster.show()}}}this.zoomChanged=function() {window.clearInterval(zoomChangedTimeout);zoomChangedTimeout=window.setTimeout(createClusters,500)};this.getMap=function() {return map};this.getProjection=function() {return projection.getP()};this.debug=function(message) {if (me.debugEnabled) {console.log(\'Fluster2: \'+message)}};this.addMarker=function(_marker) {me.markers.push(_marker)};this.getStyles=function() {return me.styles};this.initialize=function() {google.maps.event.addListener(map,\'zoom_changed\',this.zoomChanged);google.maps.event.addListener(map,\'dragend\',showClustersInBounds);window.setTimeout(createClusters,1000)}}
  1318. function Fluster2Cluster(_fluster,_marker) {var markerPosition=_marker.getPosition();this.fluster=_fluster;this.markers=[];this.bounds=null;this.marker=null;this.lngSum=0;this.latSum=0;this.center=markerPosition;this.map=this.fluster.getMap();var me=this;var projection=_fluster.getProjection();var gridSize=_fluster.gridSize;var position=projection.fromLatLngToDivPixel(markerPosition);var positionSW=new google.maps.Point(position.x-gridSize,position.y+gridSize);var positionNE=new google.maps.Point(position.x+gridSize,position.y-gridSize);this.bounds=new google.maps.LatLngBounds(projection.fromDivPixelToLatLng(positionSW),projection.fromDivPixelToLatLng(positionNE));this.addMarker=function(_marker) {this.markers.push(_marker)};this.show=function() {if (this.markers.length==1) {this.markers[0].setMap(me.map)}else if (this.markers.length>1) {for (var i=0;i<this.markers.length;i++) {this.markers[i].setMap(null)}if (this.marker==null) {this.marker=new Fluster2ClusterMarker(this.fluster,this);if (this.fluster.debugEnabled) {google.maps.event.addListener(this.marker,\'mouseover\',me.debugShowMarkers);google.maps.event.addListener(this.marker,\'mouseout\',me.debugHideMarkers)}}this.marker.show()}};this.hide=function() {if (this.marker!=null) {this.marker.hide()}};this.debugShowMarkers=function() {for (var i=0;i<me.markers.length;i++) {me.markers[i].setVisible(true)}};this.debugHideMarkers=function() {for (var i=0;i<me.markers.length;i++) {me.markers[i].setVisible(false)}};this.getMarkerCount=function() {return this.markers.length};this.contains=function(_position) {return me.bounds.contains(_position)};this.getPosition=function() {return this.center};this.getBounds=function() {return this.bounds};this.getMarkerBounds=function() {var bounds=new google.maps.LatLngBounds(me.markers[0].getPosition(),me.markers[0].getPosition());for (var i=1;i<me.markers.length;i++) {bounds.extend(me.markers[i].getPosition())}return bounds};this.addMarker(_marker)}
  1319. function Fluster2ClusterMarker(_fluster,_cluster) {this.fluster=_fluster;this.cluster=_cluster;this.position=this.cluster.getPosition();this.markerCount=this.cluster.getMarkerCount();this.map=this.fluster.getMap();this.style=null;this.div=null;var styles=this.fluster.getStyles();for (var i in styles) {if (this.markerCount>i) {this.style=styles[i]} else {break}}google.maps.OverlayView.call(this);this.setMap(this.map);this.draw()};Fluster2ClusterMarker.prototype=new google.maps.OverlayView();Fluster2ClusterMarker.prototype.draw=function() {if (this.div==null) {var me=this;this.div=document.createElement(\'div\');this.div.style.position=\'absolute\';this.div.style.width=this.style.width+\'px\';this.div.style.height=this.style.height+\'px\';this.div.style.lineHeight=this.style.height+\'px\';this.div.style.background=\'transparent url("\'+this.style.image+\'") 50% 50% no-repeat\';this.div.style.color=this.style.textColor;this.div.style.textAlign=\'center\';this.div.style.fontFamily=\'Arial, Helvetica\';this.div.style.fontSize=\'11px\';this.div.style.fontWeight=\'bold\';this.div.innerHTML=this.markerCount;this.div.style.cursor=\'pointer\';google.maps.event.addDomListener(this.div,\'click\',function() {me.map.fitBounds(me.cluster.getMarkerBounds())});this.getPanes().overlayLayer.appendChild(this.div)}var position=this.getProjection().fromLatLngToDivPixel(this.position);this.div.style.left=(position.x-parseInt(this.style.width/2))+\'px\';this.div.style.top=(position.y-parseInt(this.style.height/2))+\'px\'};Fluster2ClusterMarker.prototype.hide=function() {this.div.style.display=\'none\'};Fluster2ClusterMarker.prototype.show=function() {this.div.style.display=\'block\'};
  1320. function Fluster2ProjectionOverlay(map) {google.maps.OverlayView.call(this);this.setMap(map);this.getP=function() {return this.getProjection()}}Fluster2ProjectionOverlay.prototype=new google.maps.OverlayView();Fluster2ProjectionOverlay.prototype.draw=function() {};
  1321. \'';
  1322. /** CALCULATING STUFF **/
  1323. /**
  1324. * Calculates Distance between two points array('lat'=>x,'lng'=>y)
  1325. * DB:
  1326. '6371.04 * ACOS( COS( PI()/2 - RADIANS(90 - Retailer.lat)) * ' .
  1327. 'COS( PI()/2 - RADIANS(90 - '. $data['Location']['lat'] .')) * ' .
  1328. 'COS( RADIANS(Retailer.lng) - RADIANS('. $data['Location']['lng'] .')) + ' .
  1329. 'SIN( PI()/2 - RADIANS(90 - Retailer.lat)) * ' .
  1330. 'SIN( PI()/2 - RADIANS(90 - '. $data['Location']['lat'] . '))) ' .
  1331. 'AS distance'
  1332. * @param array pointX
  1333. * @param array pointY
  1334. * @return int distance: in km
  1335. * DEPRECATED - use GeocodeLib::distance() instead!
  1336. * 2009-03-06 ms
  1337. */
  1338. public function distance($pointX, $pointY) {
  1339. /*
  1340. $res = 6371.04 * ACOS( COS( PI()/2 - rad2deg(90 - $pointX['lat'])) *
  1341. COS( PI()/2 - rad2deg(90 - $pointY['lat'])) *
  1342. COS( rad2deg($pointX['lng']) - rad2deg($pointY['lng'])) +
  1343. SIN( PI()/2 - rad2deg(90 - $pointX['lat'])) *
  1344. SIN( PI()/2 - rad2deg(90 - $pointY['lat'])));
  1345. $res = 6371.04 * acos(sin($pointY['lat'])*sin($pointX['lat'])+cos($pointY['lat'])*cos($pointX['lat'])*cos($pointY['lng'] - $pointX['lng']));
  1346. */
  1347. # seems to be the only working one (although slightly incorrect...)
  1348. $res = 69.09 * rad2deg(acos(sin(deg2rad($pointX['lat'])) * sin(deg2rad($pointY['lat'])) + cos(deg2rad($pointX['lat'])) * cos(deg2rad($pointY['lat'])) * cos(deg2rad($pointX['lng'] - $pointY['lng']))));
  1349. # Miles to KM
  1350. $res *= 1.609344;
  1351. return ceil($res);
  1352. }
  1353. protected function _arrayToObject($name, $array, $asString = true, $keyAsString = false) {
  1354. $res = 'var '.$name.' = {'.PHP_EOL;
  1355. $res .= $this->_toObjectParams($array, $asString, $keyAsString);
  1356. $res .= '};';
  1357. return $res;
  1358. }
  1359. protected function _toObjectParams($array, $asString = true, $keyAsString = false) {
  1360. $pieces = array();
  1361. foreach ($array as $key => $value) {
  1362. $e = ($asString && strpos($value, 'new ') !== 0 ? '\'' : '');
  1363. $ke = ($keyAsString ? '\'' : '');
  1364. $pieces[] = $ke.$key.$ke.': '.$e.$value.$e;
  1365. }
  1366. return implode(','.PHP_EOL, $pieces);
  1367. }
  1368. }