Index: trunk/extensions/Maps/Maps.i18n.magic.php |
— | — | @@ -13,6 +13,7 @@ |
14 | 14 | 'display_map' => array( 0, 'display_map' ), |
15 | 15 | 'display_point' => array( 0, 'display_point' ), |
16 | 16 | 'display_points' => array( 0, 'display_points' ), |
| 17 | + 'display_line' => array( 0, 'display_line' ), |
17 | 18 | 'geocode' => array( 0, 'geocode' ), |
18 | 19 | 'geodistance' => array( 0, 'geodistance' ), |
19 | 20 | 'finddestination' => array( 0, 'finddestination' ), |
Index: trunk/extensions/Maps/Maps.settings.php |
— | — | @@ -31,7 +31,8 @@ |
32 | 32 | 'defaultServices', |
33 | 33 | array( |
34 | 34 | 'display_point' => 'googlemaps3', |
35 | | - 'display_map' => 'googlemaps3' |
| 35 | + 'display_map' => 'googlemaps3', |
| 36 | + 'display_line' => 'googlemaps3' |
36 | 37 | ) |
37 | 38 | ), |
38 | 39 | |
Index: trunk/extensions/Maps/Maps.php |
— | — | @@ -80,6 +80,7 @@ |
81 | 81 | $wgAutoloadClasses['MapsLayerPage'] = $incDir . 'Maps_LayerPage.php'; |
82 | 82 | $wgAutoloadClasses['MapsLayers'] = $incDir . 'Maps_Layers.php'; |
83 | 83 | $wgAutoloadClasses['MapsLocation'] = $incDir . 'Maps_Location.php'; |
| 84 | +$wgAutoloadClasses['MapsLine'] = $incDir . 'Maps_Line.php'; |
84 | 85 | $wgAutoloadClasses['iMappingService'] = $incDir . 'iMappingService.php'; |
85 | 86 | $wgAutoloadClasses['MapsMappingServices'] = $incDir . 'Maps_MappingServices.php'; |
86 | 87 | $wgAutoloadClasses['MapsMappingService'] = $incDir . 'Maps_MappingService.php'; |
— | — | @@ -93,12 +94,14 @@ |
94 | 95 | $wgAutoloadClasses['CriterionIsLocation'] = $criDir . 'CriterionIsLocation.php'; |
95 | 96 | $wgAutoloadClasses['CriterionMapDimension'] = $criDir . 'CriterionMapDimension.php'; |
96 | 97 | $wgAutoloadClasses['CriterionMapLayer'] = $criDir . 'CriterionMapLayer.php'; |
| 98 | +$wgAutoloadClasses['CriterionLine'] = $criDir . 'CriterionLine.php'; |
97 | 99 | unset( $criDir ); |
98 | 100 | |
99 | 101 | // Autoload the "includes/features/" classes. |
100 | 102 | $ftDir = $incDir . '/features/'; |
101 | 103 | $wgAutoloadClasses['MapsBaseMap'] = $ftDir . 'Maps_BaseMap.php'; |
102 | | -$wgAutoloadClasses['MapsBasePointMap'] = $ftDir . 'Maps_BasePointMap.php'; |
| 104 | +$wgAutoloadClasses['MapsBasePointMap'] = $ftDir . 'Maps_BasePointMap.php'; |
| 105 | +$wgAutoloadClasses['MapsBasePointLineMap'] = $ftDir . 'Maps_BasePointLineMap.php'; |
103 | 106 | unset( $ftDir ); |
104 | 107 | |
105 | 108 | // Autoload the "includes/geocoders/" classes. |
— | — | @@ -122,6 +125,7 @@ |
123 | 126 | $wgAutoloadClasses['MapsParamLocation'] = $manDir . 'Maps_ParamLocation.php'; |
124 | 127 | $wgAutoloadClasses['MapsParamService'] = $manDir . 'Maps_ParamService.php'; |
125 | 128 | $wgAutoloadClasses['MapsParamZoom'] = $manDir . 'Maps_ParamZoom.php'; |
| 129 | +$wgAutoloadClasses['MapsParamLine'] = $manDir . 'Maps_ParamLine.php'; |
126 | 130 | unset( $manDir ); |
127 | 131 | |
128 | 132 | // Autoload the "includes/parserHooks/" classes. |
— | — | @@ -129,6 +133,7 @@ |
130 | 134 | $wgAutoloadClasses['MapsCoordinates'] = $phDir . 'Maps_Coordinates.php'; |
131 | 135 | $wgAutoloadClasses['MapsDisplayMap'] = $phDir . 'Maps_DisplayMap.php'; |
132 | 136 | $wgAutoloadClasses['MapsDisplayPoint'] = $phDir . 'Maps_DisplayPoint.php'; |
| 137 | +$wgAutoloadClasses['MapsDisplayLine'] = $phDir . 'Maps_DisplayLine.php'; |
133 | 138 | $wgAutoloadClasses['MapsDistance'] = $phDir . 'Maps_Distance.php'; |
134 | 139 | $wgAutoloadClasses['MapsFinddestination'] = $phDir . 'Maps_Finddestination.php'; |
135 | 140 | $wgAutoloadClasses['MapsGeocode'] = $phDir . 'Maps_Geocode.php'; |
— | — | @@ -165,6 +170,7 @@ |
166 | 171 | |
167 | 172 | $egMapsFeatures['pf'][] = 'MapsDisplayMap::initialize'; |
168 | 173 | $egMapsFeatures['pf'][] = 'MapsDisplayPoint::initialize'; |
| 174 | +$egMapsFeatures['pf'][] = 'MapsDisplayLine::initialize'; |
169 | 175 | |
170 | 176 | # Parser hooks |
171 | 177 | |
— | — | @@ -174,6 +180,8 @@ |
175 | 181 | $wgHooks['ParserFirstCallInit'][] = 'MapsDisplayMap::staticInit'; |
176 | 182 | # Required for #display_point. |
177 | 183 | $wgHooks['ParserFirstCallInit'][] = 'MapsDisplayPoint::staticInit'; |
| 184 | + # Required for #display_line. |
| 185 | + $wgHooks['ParserFirstCallInit'][] = 'MapsDisplayLine::staticInit'; |
178 | 186 | # Required for #distance. |
179 | 187 | $wgHooks['ParserFirstCallInit'][] = 'MapsDistance::staticInit'; |
180 | 188 | # Required for #finddestination. |
Index: trunk/extensions/Maps/Maps_Settings.php |
— | — | @@ -44,7 +44,8 @@ |
45 | 45 | # for, since it's used as a fallback mechanism. |
46 | 46 | $egMapsDefaultServices = array( |
47 | 47 | 'display_point' => $egMapsDefaultService, |
48 | | - 'display_map' => $egMapsDefaultService |
| 48 | + 'display_map' => $egMapsDefaultService, |
| 49 | + 'display_line' => $egMapsDefaultService |
49 | 50 | ); |
50 | 51 | |
51 | 52 | |
Index: trunk/extensions/Maps/includes/Maps_Line.php |
— | — | @@ -0,0 +1,185 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * Class that holds metadata on lines made up by locations on map. |
| 5 | + * |
| 6 | + * @since 0.7.2 |
| 7 | + * |
| 8 | + * @file Maps_Line.php |
| 9 | + * @ingroup Maps |
| 10 | + * |
| 11 | + * @licence GNU GPL v3 |
| 12 | + * @author Kim Eik < kim@heldig.org > |
| 13 | + */ |
| 14 | +class MapsLine{ |
| 15 | + |
| 16 | + |
| 17 | + /** |
| 18 | + * @var |
| 19 | + */ |
| 20 | + protected $lineCoords; |
| 21 | + |
| 22 | + /** |
| 23 | + * @var |
| 24 | + */ |
| 25 | + protected $title; |
| 26 | + |
| 27 | + /** |
| 28 | + * @var |
| 29 | + */ |
| 30 | + protected $text; |
| 31 | + |
| 32 | + /** |
| 33 | + * @var |
| 34 | + */ |
| 35 | + protected $strokeColor; |
| 36 | + /** |
| 37 | + * @var |
| 38 | + */ |
| 39 | + protected $strokeOpacity; |
| 40 | + /** |
| 41 | + * @var |
| 42 | + */ |
| 43 | + protected $strokeWeight; |
| 44 | + |
| 45 | + /** |
| 46 | + * |
| 47 | + */ |
| 48 | + function __construct($coords) |
| 49 | + { |
| 50 | + $this->setLineCoords($coords); |
| 51 | + } |
| 52 | + |
| 53 | + /** |
| 54 | + * @param \text $text |
| 55 | + */ |
| 56 | + public function setText($text) |
| 57 | + { |
| 58 | + $this->text = $text; |
| 59 | + } |
| 60 | + |
| 61 | + /** |
| 62 | + * @return \text |
| 63 | + */ |
| 64 | + public function getText() |
| 65 | + { |
| 66 | + return $this->text; |
| 67 | + } |
| 68 | + |
| 69 | + /** |
| 70 | + * @param \title $title |
| 71 | + */ |
| 72 | + public function setTitle($title) |
| 73 | + { |
| 74 | + $this->title = $title; |
| 75 | + } |
| 76 | + |
| 77 | + /** |
| 78 | + * @return \title |
| 79 | + */ |
| 80 | + public function getTitle() |
| 81 | + { |
| 82 | + return $this->title; |
| 83 | + } |
| 84 | + |
| 85 | + protected function setLineCoords($lineCoords) |
| 86 | + { |
| 87 | + foreach($lineCoords as $lineCoord){ |
| 88 | + $this->lineCoords[] = new MapsLocation($lineCoord); |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + protected function getLineCoords() |
| 93 | + { |
| 94 | + return $this->lineCoords; |
| 95 | + } |
| 96 | + |
| 97 | + public function getJSONObject( $defText = '', $defTitle = '') { |
| 98 | + $posArray = array(); |
| 99 | + foreach ($this->lineCoords as $mapLocation){ |
| 100 | + $posArray[] = array( |
| 101 | + 'lat' => $mapLocation->getLatitude(), |
| 102 | + 'lon' => $mapLocation->getLongitude() |
| 103 | + ); |
| 104 | + } |
| 105 | + |
| 106 | + return array( |
| 107 | + 'pos' => $posArray, |
| 108 | + 'text' => $this->hasText() ? $this->getText() : $defText, |
| 109 | + 'title' => $this->hasTitle() ? $this->getTitle() : $defTitle, |
| 110 | + 'strokeColor' => $this->hasStrokeColor() ? $this->getStrokeColor() : '#FF0000', |
| 111 | + 'strokeOpacity' => $this->hasStrokeOpacity() ? $this->getStrokeOpacity() : '1', |
| 112 | + 'strokeWeight' => $this->hasStrokeWeight() ? $this->getStrokeWeight() : '2' |
| 113 | + ); |
| 114 | + } |
| 115 | + |
| 116 | + |
| 117 | + /** |
| 118 | + * @param $strokeColor |
| 119 | + */ |
| 120 | + public function setStrokeColor($strokeColor) |
| 121 | + { |
| 122 | + $this->strokeColor = $strokeColor; |
| 123 | + } |
| 124 | + |
| 125 | + /** |
| 126 | + * @return |
| 127 | + */ |
| 128 | + public function getStrokeColor() |
| 129 | + { |
| 130 | + return $this->strokeColor; |
| 131 | + } |
| 132 | + |
| 133 | + /** |
| 134 | + * @param $strokeOpacity |
| 135 | + */ |
| 136 | + public function setStrokeOpacity($strokeOpacity) |
| 137 | + { |
| 138 | + $this->strokeOpacity = $strokeOpacity; |
| 139 | + } |
| 140 | + |
| 141 | + /** |
| 142 | + * @return |
| 143 | + */ |
| 144 | + public function getStrokeOpacity() |
| 145 | + { |
| 146 | + return $this->strokeOpacity; |
| 147 | + } |
| 148 | + |
| 149 | + /** |
| 150 | + * @param $strokeWeight |
| 151 | + */ |
| 152 | + public function setStrokeWeight($strokeWeight) |
| 153 | + { |
| 154 | + $this->strokeWeight = $strokeWeight; |
| 155 | + } |
| 156 | + |
| 157 | + /** |
| 158 | + * @return |
| 159 | + */ |
| 160 | + public function getStrokeWeight() |
| 161 | + { |
| 162 | + return $this->strokeWeight; |
| 163 | + } |
| 164 | + |
| 165 | + |
| 166 | + public function hasText(){ |
| 167 | + return $this->text !== ''; |
| 168 | + } |
| 169 | + |
| 170 | + public function hasTitle(){ |
| 171 | + return $this->title !== ''; |
| 172 | + } |
| 173 | + |
| 174 | + public function hasStrokeColor(){ |
| 175 | + return $this->strokeColor !== ''; |
| 176 | + } |
| 177 | + |
| 178 | + public function hasStrokeOpacity(){ |
| 179 | + return $this->strokeOpacity !== ''; |
| 180 | + } |
| 181 | + |
| 182 | + |
| 183 | + public function hasStrokeWeight(){ |
| 184 | + return $this->strokeWeight !== ''; |
| 185 | + } |
| 186 | +} |
Index: trunk/extensions/Maps/includes/parserHooks/Maps_DisplayLine.php |
— | — | @@ -0,0 +1,100 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Class for the 'display_line' parser hooks. |
| 6 | + * |
| 7 | + * @since 0.7 |
| 8 | + * |
| 9 | + * @file Maps_DisplayLine.php |
| 10 | + * @ingroup Maps |
| 11 | + * |
| 12 | + * @author Kim Eik |
| 13 | + */ |
| 14 | +class MapsDisplayLine extends MapsDisplayPoint { |
| 15 | + |
| 16 | + /** |
| 17 | + * No LSB in pre-5.3 PHP *sigh*. |
| 18 | + * This is to be refactored as soon as php >=5.3 becomes acceptable. |
| 19 | + */ |
| 20 | + public static function staticInit( Parser &$parser ) { |
| 21 | + $instance = new self; |
| 22 | + return $instance->init( $parser ); |
| 23 | + } |
| 24 | + |
| 25 | + |
| 26 | + /** |
| 27 | + * Gets the name of the parser hook. |
| 28 | + * |
| 29 | + * @since 0.4 |
| 30 | + * |
| 31 | + * @return string or array of string |
| 32 | + */ |
| 33 | + protected function getName(){ |
| 34 | + return 'display_line'; |
| 35 | + } |
| 36 | + |
| 37 | + /** |
| 38 | + * @param array $parameters |
| 39 | + * @return mixed |
| 40 | + */ |
| 41 | + public function render( array $parameters ) { |
| 42 | + // Get the instance of the service class. |
| 43 | + $service = MapsMappingServices::getServiceInstance($parameters['mappingservice']); |
| 44 | + |
| 45 | + // Get an instance of the class handling the current parser hook and service. |
| 46 | + $mapClass = $service->getFeatureInstance( $this->getName() ); |
| 47 | + |
| 48 | + return $mapClass->renderMap( $parameters, $this->parser ); |
| 49 | + } |
| 50 | + |
| 51 | + /** |
| 52 | + * Returns an array containing the parameter info. |
| 53 | + * @see ParserHook::getParameterInfo |
| 54 | + * |
| 55 | + * @since 0.7 |
| 56 | + * |
| 57 | + * @return array |
| 58 | + */ |
| 59 | + protected function getParameterInfo( $type ) { |
| 60 | + global $egMapsDefaultServices; |
| 61 | + |
| 62 | + $params = parent::getParameterInfo($type); |
| 63 | + |
| 64 | + $params['mappingservice']->setDefault( $egMapsDefaultServices[$this->getName()] ); |
| 65 | + $params['mappingservice']->addManipulations( new MapsParamService( $this->getName() ) ); |
| 66 | + |
| 67 | + $params['lines'] = new ListParameter( 'lines', ';' ); |
| 68 | + $params['lines']->setDefault(array()); |
| 69 | + $params['lines']->addCriteria(new CriterionLine()); |
| 70 | + $params['lines']->addManipulations( new MapsParamLine() ); |
| 71 | + |
| 72 | + $params['copycoords'] = new Parameter( |
| 73 | + 'copycoords', |
| 74 | + Parameter::TYPE_BOOLEAN |
| 75 | + ); |
| 76 | + $params['copycoords']->setDefault(false); |
| 77 | + $params['copycoords']->setDoManipulationOfDefault( false ); |
| 78 | + |
| 79 | + |
| 80 | + $params['markercluster'] = new Parameter( |
| 81 | + 'markercluster', |
| 82 | + Parameter::TYPE_BOOLEAN |
| 83 | + ); |
| 84 | + $params['markercluster']->setDefault(false); |
| 85 | + $params['markercluster']->setDoManipulationOfDefault( false ); |
| 86 | + |
| 87 | + return $params; |
| 88 | + } |
| 89 | + |
| 90 | + /** |
| 91 | + * Returns the list of default parameters. |
| 92 | + * @see ParserHook::getDefaultParameters |
| 93 | + * |
| 94 | + * @since 0.7 |
| 95 | + * |
| 96 | + * @return array |
| 97 | + */ |
| 98 | + protected function getDefaultParameters( $type ) { |
| 99 | + return array( 'coordinates','lines' ); |
| 100 | + } |
| 101 | +} |
\ No newline at end of file |
Index: trunk/extensions/Maps/includes/criteria/CriterionLine.php |
— | — | @@ -0,0 +1,84 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Parameter criterion stating that the value must be a set of coordinates or an address. |
| 6 | + * |
| 7 | + * @since 0.7 |
| 8 | + * |
| 9 | + * @file CriterionLine.php |
| 10 | + * @ingroup Maps |
| 11 | + * @ingroup Criteria |
| 12 | + * |
| 13 | + * @author Kim Eik |
| 14 | + */ |
| 15 | + |
| 16 | +class CriterionLine extends ItemParameterCriterion |
| 17 | +{ |
| 18 | + |
| 19 | + /** |
| 20 | + * Returns true if the parameter value contains atleast 1 comma |
| 21 | + * meaning that there are atleast two enpoints on which to draw a line. |
| 22 | + * |
| 23 | + * @param string $value |
| 24 | + * @param Parameter $parameter |
| 25 | + * @param array $parameters |
| 26 | + * |
| 27 | + * @since 0.4 |
| 28 | + * |
| 29 | + * @return boolean |
| 30 | + */ |
| 31 | + protected function doValidation($value, Parameter $parameter, array $parameters) |
| 32 | + { |
| 33 | + //need atleast two points to create a line |
| 34 | + $valid = strpos($value, ':') != false; |
| 35 | + if (!$valid) { |
| 36 | + return $valid; |
| 37 | + } |
| 38 | + |
| 39 | + //setup geocode deps |
| 40 | + $canGeoCode = MapsGeocoders::canGeocode(); |
| 41 | + if ($canGeoCode) { |
| 42 | + $geoService = $parameter->hasDependency('geoservice') ? $parameters['geoservice']->getValue() : ''; |
| 43 | + $mappingService = $parameter->hasDependency('mappingservice') ? $parameters['mappingservice']->getValue() : false; |
| 44 | + } |
| 45 | + |
| 46 | + //strip away line parameters and check for valid locations |
| 47 | + $parts = preg_split('/[:]/', $value); |
| 48 | + foreach ($parts as $part) { |
| 49 | + $toIndex = strpos($part, '|'); |
| 50 | + if ($toIndex != false) { |
| 51 | + $part = substr($part, 0, $toIndex); |
| 52 | + } |
| 53 | + |
| 54 | + if($canGeoCode){ |
| 55 | + $valid = MapsGeocoders::isLocation( |
| 56 | + $part, |
| 57 | + $geoService, |
| 58 | + $mappingService |
| 59 | + ); |
| 60 | + } else { |
| 61 | + $valid = MapsCoordinateParser::areCoordinates($part); |
| 62 | + } |
| 63 | + |
| 64 | + if(!$valid){ |
| 65 | + break; |
| 66 | + } |
| 67 | + } |
| 68 | + return $valid; |
| 69 | + } |
| 70 | + |
| 71 | + /** |
| 72 | + * Gets an internationalized error message to construct a ValidationError with |
| 73 | + * when the criteria validation failed. (for non-list values) |
| 74 | + * |
| 75 | + * @param Parameter $parameter |
| 76 | + * |
| 77 | + * @since 0.4 |
| 78 | + * |
| 79 | + * @return string |
| 80 | + */ |
| 81 | + protected function getItemErrorMessage(Parameter $parameter) |
| 82 | + { |
| 83 | + return wfMsgExt('validation-error-invalid-line-param', 'parsemag', $parameter->getOriginalName()); |
| 84 | + } |
| 85 | +} |
\ No newline at end of file |
Index: trunk/extensions/Maps/includes/features/Maps_BasePointLineMap.php |
— | — | @@ -0,0 +1,30 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * |
| 6 | + * @file Maps_BasePointLineMap.php |
| 7 | + * @ingroup Maps |
| 8 | + * |
| 9 | + * @author Kim Eik |
| 10 | + */ |
| 11 | +class MapsBasePointLineMap extends MapsBasePointMap{ |
| 12 | + |
| 13 | + protected function handleMarkerData(array &$params, Parser $parser) |
| 14 | + { |
| 15 | + parent::handleMarkerData($params, $parser); |
| 16 | + |
| 17 | + $parserClone = clone $parser; |
| 18 | + |
| 19 | + foreach($params['lines'] as &$line){ |
| 20 | + |
| 21 | + $line['title'] = $parserClone->parse( $line['title'], $parserClone->getTitle(), new ParserOptions() )->getText(); |
| 22 | + $line['text'] = $parserClone->parse( $line['text'], $parserClone->getTitle(), new ParserOptions() )->getText(); |
| 23 | + |
| 24 | + $hasTitleAndtext = $line['title'] !== '' && $line['text'] !== ''; |
| 25 | + $line['text'] = ( $hasTitleAndtext ? '<b>' . $line['title'] . '</b><hr />' : $line['title'] ) . $line['text']; |
| 26 | + $line['title'] = strip_tags( $line['title'] ); |
| 27 | + } |
| 28 | + } |
| 29 | + |
| 30 | + |
| 31 | +} |
Index: trunk/extensions/Maps/includes/services/GoogleMaps3/jquery.googlemap.js |
— | — | @@ -10,6 +10,7 @@ |
11 | 11 | var _this = this; |
12 | 12 | |
13 | 13 | this.map = null; |
| 14 | + this.markercluster = null; |
14 | 15 | this.options = options; |
15 | 16 | |
16 | 17 | /** |
— | — | @@ -20,6 +21,13 @@ |
21 | 22 | this.markers = []; |
22 | 23 | |
23 | 24 | /** |
| 25 | + * All Polylines currently on the map, |
| 26 | + * @type {Array} |
| 27 | + * @private |
| 28 | + */ |
| 29 | + this.lines = []; |
| 30 | + |
| 31 | + /** |
24 | 32 | * Creates a new marker with the provided data, |
25 | 33 | * adds it to the map, and returns it. |
26 | 34 | * @param {Object} markerData Contains the fields lat, lon, title, text and icon |
— | — | @@ -133,6 +141,55 @@ |
134 | 142 | } |
135 | 143 | }; |
136 | 144 | |
| 145 | + this.addLine = function(properties){ |
| 146 | + var paths = new google.maps.MVCArray(); |
| 147 | + for(var x = 0; x < properties.pos.length; x++){ |
| 148 | + paths.push(new google.maps.LatLng( properties.pos[x].lat , properties.pos[x].lon )); |
| 149 | + } |
| 150 | + |
| 151 | + var line = new google.maps.Polyline({ |
| 152 | + map:this.map, |
| 153 | + path:paths, |
| 154 | + strokeColor:properties.strokeColor, |
| 155 | + strokeOpacity:properties.strokeOpacity, |
| 156 | + strokeWeight:properties.strokeWeight |
| 157 | + }); |
| 158 | + this.lines.push(line); |
| 159 | + |
| 160 | + google.maps.event.addListener(line,"click", function(event){ |
| 161 | + if (this.openWindow != undefined) { |
| 162 | + this.openWindow.close(); |
| 163 | + } |
| 164 | + this.openWindow = new google.maps.InfoWindow(); |
| 165 | + this.openWindow.content = properties.text; |
| 166 | + this.openWindow.position = event.latLng; |
| 167 | + this.openWindow.closeclick = function() { |
| 168 | + line.openWindow = undefined; |
| 169 | + }; |
| 170 | + this.openWindow.open(_this.map); |
| 171 | + }); |
| 172 | + }; |
| 173 | + |
| 174 | + this.removeLine = function(line){ |
| 175 | + line.setMap( null ); |
| 176 | + |
| 177 | + for ( var i = this.line.length - 1; i >= 0; i-- ) { |
| 178 | + if ( this.line[i] === line ) { |
| 179 | + delete this.line[i]; |
| 180 | + break; |
| 181 | + } |
| 182 | + } |
| 183 | + |
| 184 | + delete line; |
| 185 | + }; |
| 186 | + |
| 187 | + this.removeLines = function(){ |
| 188 | + for ( var i = this.lines.length - 1; i >= 0; i-- ) { |
| 189 | + this.lines[i].setMap( null ); |
| 190 | + } |
| 191 | + this.lines = []; |
| 192 | + } |
| 193 | + |
137 | 194 | this.setup = function() { |
138 | 195 | var showEarth = $.inArray( 'earth', options.types ) !== -1; |
139 | 196 | |
— | — | @@ -311,8 +368,48 @@ |
312 | 369 | _this.resizable(); |
313 | 370 | } ); |
314 | 371 | } |
| 372 | + |
| 373 | + /** |
| 374 | + * used in display_line functionality |
| 375 | + * draws paths between markers |
| 376 | + */ |
| 377 | + if(options.lines){ |
| 378 | + for ( var i = 0; i < options.lines.length; i++ ) { |
| 379 | + this.addLine(options.lines[i]); |
| 380 | + } |
| 381 | + } |
| 382 | + |
| 383 | + /** |
| 384 | + * used in display_line functionality |
| 385 | + * allows the copy to clipboard of coordinates |
| 386 | + */ |
| 387 | + if(options.copycoords){ |
| 388 | + function addRightClickListener(object){ |
| 389 | + google.maps.event.addListener( object, 'rightclick', function(event) { |
| 390 | + prompt("CTRL+C, ENTER",event.latLng.Sa+','+event.latLng.Ta); |
| 391 | + }); |
| 392 | + } |
| 393 | + |
| 394 | + for(var x = 0; x < this.markers.length; x++){ |
| 395 | + addRightClickListener(this.markers[x]); |
| 396 | + } |
| 397 | + |
| 398 | + for(var x = 0; x < this.lines.length; x++){ |
| 399 | + addRightClickListener(this.lines[x]); |
| 400 | + } |
| 401 | + |
| 402 | + addRightClickListener(this.map); |
| 403 | + } |
| 404 | + |
| 405 | + /** |
| 406 | + * used in display_line functionality |
| 407 | + * allows grouping of markers |
| 408 | + */ |
| 409 | + if(options.markercluster){ |
| 410 | + this.markercluster = new MarkerClusterer(this.map,this.markers); |
| 411 | + } |
| 412 | + |
315 | 413 | }; |
316 | | - |
317 | 414 | this.setup(); |
318 | 415 | |
319 | 416 | return this; |
Index: trunk/extensions/Maps/includes/services/GoogleMaps3/markerclusterer.js |
— | — | @@ -0,0 +1,1596 @@ |
| 2 | +/*jslint browser: true, confusion: true, sloppy: true, vars: true, nomen: false, plusplus: false, indent: 2 */ |
| 3 | +/*global window,google */ |
| 4 | + |
| 5 | +/** |
| 6 | + * @name MarkerClustererPlus for Google Maps V3 |
| 7 | + * @version 2.0.9 [February 20, 2012] |
| 8 | + * @author Gary Little |
| 9 | + * @fileoverview |
| 10 | + * The library creates and manages per-zoom-level clusters for large amounts of markers. |
| 11 | + * <p> |
| 12 | + * This is an enhanced V3 implementation of the |
| 13 | + * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/" |
| 14 | + * >V2 MarkerClusterer</a> by Xiaoxi Wu. It is based on the |
| 15 | + * <a href="http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclusterer/" |
| 16 | + * >V3 MarkerClusterer</a> port by Luke Mahe. MarkerClustererPlus was created by Gary Little. |
| 17 | + * <p> |
| 18 | + * v2.0 release: MarkerClustererPlus v2.0 is backward compatible with MarkerClusterer v1.0. It |
| 19 | + * adds support for the <code>ignoreHidden</code>, <code>title</code>, <code>printable</code>, |
| 20 | + * <code>batchSizeIE</code>, and <code>calculator</code> properties as well as support for |
| 21 | + * four more events. It also allows greater control over the styling of the text that appears |
| 22 | + * on the cluster marker. The documentation has been significantly improved and the overall |
| 23 | + * code has been simplified and polished. Very large numbers of markers can now be managed |
| 24 | + * without causing Javascript timeout errors on Internet Explorer. Note that the name of the |
| 25 | + * <code>clusterclick</code> event has been deprecated. The new name is <code>click</code>, |
| 26 | + * so please change your application code now. |
| 27 | + */ |
| 28 | + |
| 29 | +/** |
| 30 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 31 | + * you may not use this file except in compliance with the License. |
| 32 | + * You may obtain a copy of the License at |
| 33 | + * |
| 34 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 35 | + * |
| 36 | + * Unless required by applicable law or agreed to in writing, software |
| 37 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 38 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 39 | + * See the License for the specific language governing permissions and |
| 40 | + * limitations under the License. |
| 41 | + */ |
| 42 | + |
| 43 | + |
| 44 | +/** |
| 45 | + * @name ClusterIconStyle |
| 46 | + * @class This class represents the object for values in the <code>styles</code> array passed |
| 47 | + * to the {@link MarkerClusterer} constructor. The element in this array that is used to |
| 48 | + * style the cluster icon is determined by calling the <code>calculator</code> function. |
| 49 | + * |
| 50 | + * @property {string} url The URL of the cluster icon image file. Required. |
| 51 | + * @property {number} height The height (in pixels) of the cluster icon. Required. |
| 52 | + * @property {number} width The width (in pixels) of the cluster icon. Required. |
| 53 | + * @property {Array} [anchor] The anchor position (in pixels) of the label text to be shown on |
| 54 | + * the cluster icon, relative to the top left corner of the icon. |
| 55 | + * The format is <code>[yoffset, xoffset]</code>. The <code>yoffset</code> must be positive |
| 56 | + * and less than <code>height</code> and the <code>xoffset</code> must be positive and less |
| 57 | + * than <code>width</code>. The default is to anchor the label text so that it is centered |
| 58 | + * on the icon. |
| 59 | + * @property {Array} [anchorIcon] The anchor position (in pixels) of the cluster icon. This is the |
| 60 | + * spot on the cluster icon that is to be aligned with the cluster position. The format is |
| 61 | + * <code>[yoffset, xoffset]</code> where <code>yoffset</code> increases as you go down and |
| 62 | + * <code>xoffset</code> increases to the right. The default anchor position is the center of the |
| 63 | + * cluster icon. |
| 64 | + * @property {string} [textColor="black"] The color of the label text shown on the |
| 65 | + * cluster icon. |
| 66 | + * @property {number} [textSize=11] The size (in pixels) of the label text shown on the |
| 67 | + * cluster icon. |
| 68 | + * @property {number} [textDecoration="none"] The value of the CSS <code>text-decoration</code> |
| 69 | + * property for the label text shown on the cluster icon. |
| 70 | + * @property {number} [fontWeight="bold"] The value of the CSS <code>font-weight</code> |
| 71 | + * property for the label text shown on the cluster icon. |
| 72 | + * @property {number} [fontStyle="normal"] The value of the CSS <code>font-style</code> |
| 73 | + * property for the label text shown on the cluster icon. |
| 74 | + * @property {number} [fontFamily="Arial,sans-serif"] The value of the CSS <code>font-family</code> |
| 75 | + * property for the label text shown on the cluster icon. |
| 76 | + * @property {string} [backgroundPosition="0 0"] The position of the cluster icon image |
| 77 | + * within the image defined by <code>url</code>. The format is <code>"xpos ypos"</code> |
| 78 | + * (the same format as for the CSS <code>background-position</code> property). You must set |
| 79 | + * this property appropriately when the image defined by <code>url</code> represents a sprite |
| 80 | + * containing multiple images. |
| 81 | + */ |
| 82 | +/** |
| 83 | + * @name ClusterIconInfo |
| 84 | + * @class This class is an object containing general information about a cluster icon. This is |
| 85 | + * the object that a <code>calculator</code> function returns. |
| 86 | + * |
| 87 | + * @property {string} text The text of the label to be shown on the cluster icon. |
| 88 | + * @property {number} index The index plus 1 of the element in the <code>styles</code> |
| 89 | + * array to be used to style the cluster icon. |
| 90 | + */ |
| 91 | +/** |
| 92 | + * A cluster icon. |
| 93 | + * |
| 94 | + * @constructor |
| 95 | + * @extends google.maps.OverlayView |
| 96 | + * @param {Cluster} cluster The cluster with which the icon is to be associated. |
| 97 | + * @param {Array} [styles] An array of {@link ClusterIconStyle} defining the cluster icons |
| 98 | + * to use for various cluster sizes. |
| 99 | + * @private |
| 100 | + */ |
| 101 | +function ClusterIcon(cluster, styles) { |
| 102 | + cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); |
| 103 | + |
| 104 | + this.cluster_ = cluster; |
| 105 | + this.styles_ = styles; |
| 106 | + this.center_ = null; |
| 107 | + this.div_ = null; |
| 108 | + this.sums_ = null; |
| 109 | + this.visible_ = false; |
| 110 | + |
| 111 | + this.setMap(cluster.getMap()); // Note: this causes onAdd to be called |
| 112 | +} |
| 113 | + |
| 114 | + |
| 115 | +/** |
| 116 | + * Adds the icon to the DOM. |
| 117 | + */ |
| 118 | +ClusterIcon.prototype.onAdd = function () { |
| 119 | + var cClusterIcon = this; |
| 120 | + var cMouseDownInCluster; |
| 121 | + var cDraggingMapByCluster; |
| 122 | + |
| 123 | + this.div_ = document.createElement("div"); |
| 124 | + if (this.visible_) { |
| 125 | + this.show(); |
| 126 | + } |
| 127 | + |
| 128 | + this.getPanes().overlayMouseTarget.appendChild(this.div_); |
| 129 | + |
| 130 | + // Fix for Issue 157 |
| 131 | + google.maps.event.addListener(this.getMap(), "bounds_changed", function () { |
| 132 | + cDraggingMapByCluster = cMouseDownInCluster; |
| 133 | + }); |
| 134 | + |
| 135 | + google.maps.event.addDomListener(this.div_, "mousedown", function () { |
| 136 | + cMouseDownInCluster = true; |
| 137 | + cDraggingMapByCluster = false; |
| 138 | + }); |
| 139 | + |
| 140 | + google.maps.event.addDomListener(this.div_, "click", function (e) { |
| 141 | + cMouseDownInCluster = false; |
| 142 | + if (!cDraggingMapByCluster) { |
| 143 | + var mz; |
| 144 | + var mc = cClusterIcon.cluster_.getMarkerClusterer(); |
| 145 | + /** |
| 146 | + * This event is fired when a cluster marker is clicked. |
| 147 | + * @name MarkerClusterer#click |
| 148 | + * @param {Cluster} c The cluster that was clicked. |
| 149 | + * @event |
| 150 | + */ |
| 151 | + google.maps.event.trigger(mc, "click", cClusterIcon.cluster_); |
| 152 | + google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name |
| 153 | + |
| 154 | + // The default click handler follows. Disable it by setting |
| 155 | + // the zoomOnClick property to false. |
| 156 | + if (mc.getZoomOnClick()) { |
| 157 | + // Zoom into the cluster. |
| 158 | + mz = mc.getMaxZoom(); |
| 159 | + mc.getMap().fitBounds(cClusterIcon.cluster_.getBounds()); |
| 160 | + // Don't zoom beyond the max zoom level |
| 161 | + if (mz !== null && (mc.getMap().getZoom() > mz)) { |
| 162 | + mc.getMap().setZoom(mz + 1); |
| 163 | + } |
| 164 | + } |
| 165 | + |
| 166 | + // Prevent event propagation to the map: |
| 167 | + e.cancelBubble = true; |
| 168 | + if (e.stopPropagation) { |
| 169 | + e.stopPropagation(); |
| 170 | + } |
| 171 | + } |
| 172 | + }); |
| 173 | + |
| 174 | + google.maps.event.addDomListener(this.div_, "mouseover", function () { |
| 175 | + var mc = cClusterIcon.cluster_.getMarkerClusterer(); |
| 176 | + /** |
| 177 | + * This event is fired when the mouse moves over a cluster marker. |
| 178 | + * @name MarkerClusterer#mouseover |
| 179 | + * @param {Cluster} c The cluster that the mouse moved over. |
| 180 | + * @event |
| 181 | + */ |
| 182 | + google.maps.event.trigger(mc, "mouseover", cClusterIcon.cluster_); |
| 183 | + }); |
| 184 | + |
| 185 | + google.maps.event.addDomListener(this.div_, "mouseout", function () { |
| 186 | + var mc = cClusterIcon.cluster_.getMarkerClusterer(); |
| 187 | + /** |
| 188 | + * This event is fired when the mouse moves out of a cluster marker. |
| 189 | + * @name MarkerClusterer#mouseout |
| 190 | + * @param {Cluster} c The cluster that the mouse moved out of. |
| 191 | + * @event |
| 192 | + */ |
| 193 | + google.maps.event.trigger(mc, "mouseout", cClusterIcon.cluster_); |
| 194 | + }); |
| 195 | +}; |
| 196 | + |
| 197 | + |
| 198 | +/** |
| 199 | + * Removes the icon from the DOM. |
| 200 | + */ |
| 201 | +ClusterIcon.prototype.onRemove = function () { |
| 202 | + if (this.div_ && this.div_.parentNode) { |
| 203 | + this.hide(); |
| 204 | + google.maps.event.clearInstanceListeners(this.div_); |
| 205 | + this.div_.parentNode.removeChild(this.div_); |
| 206 | + this.div_ = null; |
| 207 | + } |
| 208 | +}; |
| 209 | + |
| 210 | + |
| 211 | +/** |
| 212 | + * Draws the icon. |
| 213 | + */ |
| 214 | +ClusterIcon.prototype.draw = function () { |
| 215 | + if (this.visible_) { |
| 216 | + var pos = this.getPosFromLatLng_(this.center_); |
| 217 | + this.div_.style.top = pos.y + "px"; |
| 218 | + this.div_.style.left = pos.x + "px"; |
| 219 | + } |
| 220 | +}; |
| 221 | + |
| 222 | + |
| 223 | +/** |
| 224 | + * Hides the icon. |
| 225 | + */ |
| 226 | +ClusterIcon.prototype.hide = function () { |
| 227 | + if (this.div_) { |
| 228 | + this.div_.style.display = "none"; |
| 229 | + } |
| 230 | + this.visible_ = false; |
| 231 | +}; |
| 232 | + |
| 233 | + |
| 234 | +/** |
| 235 | + * Positions and shows the icon. |
| 236 | + */ |
| 237 | +ClusterIcon.prototype.show = function () { |
| 238 | + if (this.div_) { |
| 239 | + var pos = this.getPosFromLatLng_(this.center_); |
| 240 | + this.div_.style.cssText = this.createCss(pos); |
| 241 | + if (this.cluster_.printable_) { |
| 242 | + // (Would like to use "width: inherit;" below, but doesn't work with MSIE) |
| 243 | + this.div_.innerHTML = "<img src='" + this.url_ + "'><div style='position: absolute; top: 0px; left: 0px; width: " + this.width_ + "px;'>" + this.sums_.text + "</div>"; |
| 244 | + } else { |
| 245 | + this.div_.innerHTML = this.sums_.text; |
| 246 | + } |
| 247 | + this.div_.title = this.cluster_.getMarkerClusterer().getTitle(); |
| 248 | + this.div_.style.display = ""; |
| 249 | + } |
| 250 | + this.visible_ = true; |
| 251 | +}; |
| 252 | + |
| 253 | + |
| 254 | +/** |
| 255 | + * Sets the icon styles to the appropriate element in the styles array. |
| 256 | + * |
| 257 | + * @param {ClusterIconInfo} sums The icon label text and styles index. |
| 258 | + */ |
| 259 | +ClusterIcon.prototype.useStyle = function (sums) { |
| 260 | + this.sums_ = sums; |
| 261 | + var index = Math.max(0, sums.index - 1); |
| 262 | + index = Math.min(this.styles_.length - 1, index); |
| 263 | + var style = this.styles_[index]; |
| 264 | + this.url_ = style.url; |
| 265 | + this.height_ = style.height; |
| 266 | + this.width_ = style.width; |
| 267 | + this.anchor_ = style.anchor; |
| 268 | + this.anchorIcon_ = style.anchorIcon || [parseInt(this.height_ / 2, 10), parseInt(this.width_ / 2, 10)]; |
| 269 | + this.textColor_ = style.textColor || "black"; |
| 270 | + this.textSize_ = style.textSize || 11; |
| 271 | + this.textDecoration_ = style.textDecoration || "none"; |
| 272 | + this.fontWeight_ = style.fontWeight || "bold"; |
| 273 | + this.fontStyle_ = style.fontStyle || "normal"; |
| 274 | + this.fontFamily_ = style.fontFamily || "Arial,sans-serif"; |
| 275 | + this.backgroundPosition_ = style.backgroundPosition || "0 0"; |
| 276 | +}; |
| 277 | + |
| 278 | + |
| 279 | +/** |
| 280 | + * Sets the position at which to center the icon. |
| 281 | + * |
| 282 | + * @param {google.maps.LatLng} center The latlng to set as the center. |
| 283 | + */ |
| 284 | +ClusterIcon.prototype.setCenter = function (center) { |
| 285 | + this.center_ = center; |
| 286 | +}; |
| 287 | + |
| 288 | + |
| 289 | +/** |
| 290 | + * Creates the cssText style parameter based on the position of the icon. |
| 291 | + * |
| 292 | + * @param {google.maps.Point} pos The position of the icon. |
| 293 | + * @return {string} The CSS style text. |
| 294 | + */ |
| 295 | +ClusterIcon.prototype.createCss = function (pos) { |
| 296 | + var style = []; |
| 297 | + if (!this.cluster_.printable_) { |
| 298 | + style.push('background-image:url(' + this.url_ + ');'); |
| 299 | + style.push('background-position:' + this.backgroundPosition_ + ';'); |
| 300 | + } |
| 301 | + |
| 302 | + if (typeof this.anchor_ === 'object') { |
| 303 | + if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 && |
| 304 | + this.anchor_[0] < this.height_) { |
| 305 | + style.push('height:' + (this.height_ - this.anchor_[0]) + |
| 306 | + 'px; padding-top:' + this.anchor_[0] + 'px;'); |
| 307 | + } else { |
| 308 | + style.push('height:' + this.height_ + 'px; line-height:' + this.height_ + |
| 309 | + 'px;'); |
| 310 | + } |
| 311 | + if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 && |
| 312 | + this.anchor_[1] < this.width_) { |
| 313 | + style.push('width:' + (this.width_ - this.anchor_[1]) + |
| 314 | + 'px; padding-left:' + this.anchor_[1] + 'px;'); |
| 315 | + } else { |
| 316 | + style.push('width:' + this.width_ + 'px; text-align:center;'); |
| 317 | + } |
| 318 | + } else { |
| 319 | + style.push('height:' + this.height_ + 'px; line-height:' + |
| 320 | + this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;'); |
| 321 | + } |
| 322 | + |
| 323 | + style.push('cursor:pointer; top:' + pos.y + 'px; left:' + |
| 324 | + pos.x + 'px; color:' + this.textColor_ + '; position:absolute; font-size:' + |
| 325 | + this.textSize_ + 'px; font-family:' + this.fontFamily_ + '; font-weight:' + |
| 326 | + this.fontWeight_ + '; font-style:' + this.fontStyle_ + '; text-decoration:' + |
| 327 | + this.textDecoration_ + ';'); |
| 328 | + |
| 329 | + return style.join(""); |
| 330 | +}; |
| 331 | + |
| 332 | + |
| 333 | +/** |
| 334 | + * Returns the position at which to place the DIV depending on the latlng. |
| 335 | + * |
| 336 | + * @param {google.maps.LatLng} latlng The position in latlng. |
| 337 | + * @return {google.maps.Point} The position in pixels. |
| 338 | + */ |
| 339 | +ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) { |
| 340 | + var pos = this.getProjection().fromLatLngToDivPixel(latlng); |
| 341 | + pos.x -= this.anchorIcon_[1]; |
| 342 | + pos.y -= this.anchorIcon_[0]; |
| 343 | + return pos; |
| 344 | +}; |
| 345 | + |
| 346 | + |
| 347 | +/** |
| 348 | + * Creates a single cluster that manages a group of proximate markers. |
| 349 | + * Used internally, do not call this constructor directly. |
| 350 | + * @constructor |
| 351 | + * @param {MarkerClusterer} mc The <code>MarkerClusterer</code> object with which this |
| 352 | + * cluster is associated. |
| 353 | + */ |
| 354 | +function Cluster(mc) { |
| 355 | + this.markerClusterer_ = mc; |
| 356 | + this.map_ = mc.getMap(); |
| 357 | + this.gridSize_ = mc.getGridSize(); |
| 358 | + this.minClusterSize_ = mc.getMinimumClusterSize(); |
| 359 | + this.averageCenter_ = mc.getAverageCenter(); |
| 360 | + this.printable_ = mc.getPrintable(); |
| 361 | + this.markers_ = []; |
| 362 | + this.center_ = null; |
| 363 | + this.bounds_ = null; |
| 364 | + this.clusterIcon_ = new ClusterIcon(this, mc.getStyles()); |
| 365 | +} |
| 366 | + |
| 367 | + |
| 368 | +/** |
| 369 | + * Returns the number of markers managed by the cluster. You can call this from |
| 370 | + * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler |
| 371 | + * for the <code>MarkerClusterer</code> object. |
| 372 | + * |
| 373 | + * @return {number} The number of markers in the cluster. |
| 374 | + */ |
| 375 | +Cluster.prototype.getSize = function () { |
| 376 | + return this.markers_.length; |
| 377 | +}; |
| 378 | + |
| 379 | + |
| 380 | +/** |
| 381 | + * Returns the array of markers managed by the cluster. You can call this from |
| 382 | + * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler |
| 383 | + * for the <code>MarkerClusterer</code> object. |
| 384 | + * |
| 385 | + * @return {Array} The array of markers in the cluster. |
| 386 | + */ |
| 387 | +Cluster.prototype.getMarkers = function () { |
| 388 | + return this.markers_; |
| 389 | +}; |
| 390 | + |
| 391 | + |
| 392 | +/** |
| 393 | + * Returns the center of the cluster. You can call this from |
| 394 | + * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler |
| 395 | + * for the <code>MarkerClusterer</code> object. |
| 396 | + * |
| 397 | + * @return {google.maps.LatLng} The center of the cluster. |
| 398 | + */ |
| 399 | +Cluster.prototype.getCenter = function () { |
| 400 | + return this.center_; |
| 401 | +}; |
| 402 | + |
| 403 | + |
| 404 | +/** |
| 405 | + * Returns the map with which the cluster is associated. |
| 406 | + * |
| 407 | + * @return {google.maps.Map} The map. |
| 408 | + * @ignore |
| 409 | + */ |
| 410 | +Cluster.prototype.getMap = function () { |
| 411 | + return this.map_; |
| 412 | +}; |
| 413 | + |
| 414 | + |
| 415 | +/** |
| 416 | + * Returns the <code>MarkerClusterer</code> object with which the cluster is associated. |
| 417 | + * |
| 418 | + * @return {MarkerClusterer} The associated marker clusterer. |
| 419 | + * @ignore |
| 420 | + */ |
| 421 | +Cluster.prototype.getMarkerClusterer = function () { |
| 422 | + return this.markerClusterer_; |
| 423 | +}; |
| 424 | + |
| 425 | + |
| 426 | +/** |
| 427 | + * Returns the bounds of the cluster. |
| 428 | + * |
| 429 | + * @return {google.maps.LatLngBounds} the cluster bounds. |
| 430 | + * @ignore |
| 431 | + */ |
| 432 | +Cluster.prototype.getBounds = function () { |
| 433 | + var i; |
| 434 | + var bounds = new google.maps.LatLngBounds(this.center_, this.center_); |
| 435 | + var markers = this.getMarkers(); |
| 436 | + for (i = 0; i < markers.length; i++) { |
| 437 | + bounds.extend(markers[i].getPosition()); |
| 438 | + } |
| 439 | + return bounds; |
| 440 | +}; |
| 441 | + |
| 442 | + |
| 443 | +/** |
| 444 | + * Removes the cluster from the map. |
| 445 | + * |
| 446 | + * @ignore |
| 447 | + */ |
| 448 | +Cluster.prototype.remove = function () { |
| 449 | + this.clusterIcon_.setMap(null); |
| 450 | + this.markers_ = []; |
| 451 | + delete this.markers_; |
| 452 | +}; |
| 453 | + |
| 454 | + |
| 455 | +/** |
| 456 | + * Adds a marker to the cluster. |
| 457 | + * |
| 458 | + * @param {google.maps.Marker} marker The marker to be added. |
| 459 | + * @return {boolean} True if the marker was added. |
| 460 | + * @ignore |
| 461 | + */ |
| 462 | +Cluster.prototype.addMarker = function (marker) { |
| 463 | + var i; |
| 464 | + var mCount; |
| 465 | + var mz; |
| 466 | + |
| 467 | + if (this.isMarkerAlreadyAdded_(marker)) { |
| 468 | + return false; |
| 469 | + } |
| 470 | + |
| 471 | + if (!this.center_) { |
| 472 | + this.center_ = marker.getPosition(); |
| 473 | + this.calculateBounds_(); |
| 474 | + } else { |
| 475 | + if (this.averageCenter_) { |
| 476 | + var l = this.markers_.length + 1; |
| 477 | + var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l; |
| 478 | + var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l; |
| 479 | + this.center_ = new google.maps.LatLng(lat, lng); |
| 480 | + this.calculateBounds_(); |
| 481 | + } |
| 482 | + } |
| 483 | + |
| 484 | + marker.isAdded = true; |
| 485 | + this.markers_.push(marker); |
| 486 | + |
| 487 | + mCount = this.markers_.length; |
| 488 | + mz = this.markerClusterer_.getMaxZoom(); |
| 489 | + if (mz !== null && this.map_.getZoom() > mz) { |
| 490 | + // Zoomed in past max zoom, so show the marker. |
| 491 | + if (marker.getMap() !== this.map_) { |
| 492 | + marker.setMap(this.map_); |
| 493 | + } |
| 494 | + } else if (mCount < this.minClusterSize_) { |
| 495 | + // Min cluster size not reached so show the marker. |
| 496 | + if (marker.getMap() !== this.map_) { |
| 497 | + marker.setMap(this.map_); |
| 498 | + } |
| 499 | + } else if (mCount === this.minClusterSize_) { |
| 500 | + // Hide the markers that were showing. |
| 501 | + for (i = 0; i < mCount; i++) { |
| 502 | + this.markers_[i].setMap(null); |
| 503 | + } |
| 504 | + } else { |
| 505 | + marker.setMap(null); |
| 506 | + } |
| 507 | + |
| 508 | + this.updateIcon_(); |
| 509 | + return true; |
| 510 | +}; |
| 511 | + |
| 512 | + |
| 513 | +/** |
| 514 | + * Determines if a marker lies within the cluster's bounds. |
| 515 | + * |
| 516 | + * @param {google.maps.Marker} marker The marker to check. |
| 517 | + * @return {boolean} True if the marker lies in the bounds. |
| 518 | + * @ignore |
| 519 | + */ |
| 520 | +Cluster.prototype.isMarkerInClusterBounds = function (marker) { |
| 521 | + return this.bounds_.contains(marker.getPosition()); |
| 522 | +}; |
| 523 | + |
| 524 | + |
| 525 | +/** |
| 526 | + * Calculates the extended bounds of the cluster with the grid. |
| 527 | + */ |
| 528 | +Cluster.prototype.calculateBounds_ = function () { |
| 529 | + var bounds = new google.maps.LatLngBounds(this.center_, this.center_); |
| 530 | + this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); |
| 531 | +}; |
| 532 | + |
| 533 | + |
| 534 | +/** |
| 535 | + * Updates the cluster icon. |
| 536 | + */ |
| 537 | +Cluster.prototype.updateIcon_ = function () { |
| 538 | + var mCount = this.markers_.length; |
| 539 | + var mz = this.markerClusterer_.getMaxZoom(); |
| 540 | + |
| 541 | + if (mz !== null && this.map_.getZoom() > mz) { |
| 542 | + this.clusterIcon_.hide(); |
| 543 | + return; |
| 544 | + } |
| 545 | + |
| 546 | + if (mCount < this.minClusterSize_) { |
| 547 | + // Min cluster size not yet reached. |
| 548 | + this.clusterIcon_.hide(); |
| 549 | + return; |
| 550 | + } |
| 551 | + |
| 552 | + var numStyles = this.markerClusterer_.getStyles().length; |
| 553 | + var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); |
| 554 | + this.clusterIcon_.setCenter(this.center_); |
| 555 | + this.clusterIcon_.useStyle(sums); |
| 556 | + this.clusterIcon_.show(); |
| 557 | +}; |
| 558 | + |
| 559 | + |
| 560 | +/** |
| 561 | + * Determines if a marker has already been added to the cluster. |
| 562 | + * |
| 563 | + * @param {google.maps.Marker} marker The marker to check. |
| 564 | + * @return {boolean} True if the marker has already been added. |
| 565 | + */ |
| 566 | +Cluster.prototype.isMarkerAlreadyAdded_ = function (marker) { |
| 567 | + var i; |
| 568 | + if (this.markers_.indexOf) { |
| 569 | + return this.markers_.indexOf(marker) !== -1; |
| 570 | + } else { |
| 571 | + for (i = 0; i < this.markers_.length; i++) { |
| 572 | + if (marker === this.markers_[i]) { |
| 573 | + return true; |
| 574 | + } |
| 575 | + } |
| 576 | + } |
| 577 | + return false; |
| 578 | +}; |
| 579 | + |
| 580 | + |
| 581 | +/** |
| 582 | + * @name MarkerClustererOptions |
| 583 | + * @class This class represents the optional parameter passed to |
| 584 | + * the {@link MarkerClusterer} constructor. |
| 585 | + * @property {number} [gridSize=60] The grid size of a cluster in pixels. The grid is a square. |
| 586 | + * @property {number} [maxZoom=null] The maximum zoom level at which clustering is enabled or |
| 587 | + * <code>null</code> if clustering is to be enabled at all zoom levels. |
| 588 | + * @property {boolean} [zoomOnClick=true] Whether to zoom the map when a cluster marker is |
| 589 | + * clicked. You may want to set this to <code>false</code> if you have installed a handler |
| 590 | + * for the <code>click</code> event and it deals with zooming on its own. |
| 591 | + * @property {boolean} [averageCenter=false] Whether the position of a cluster marker should be |
| 592 | + * the average position of all markers in the cluster. If set to <code>false</code>, the |
| 593 | + * cluster marker is positioned at the location of the first marker added to the cluster. |
| 594 | + * @property {number} [minimumClusterSize=2] The minimum number of markers needed in a cluster |
| 595 | + * before the markers are hidden and a cluster marker appears. |
| 596 | + * @property {boolean} [ignoreHidden=false] Whether to ignore hidden markers in clusters. You |
| 597 | + * may want to set this to <code>true</code> to ensure that hidden markers are not included |
| 598 | + * in the marker count that appears on a cluster marker (this count is the value of the |
| 599 | + * <code>text</code> property of the result returned by the default <code>calculator</code>). |
| 600 | + * If set to <code>true</code> and you change the visibility of a marker being clustered, be |
| 601 | + * sure to also call <code>MarkerClusterer.repaint()</code>. |
| 602 | + * @property {boolean} [printable=false] Whether to make the cluster icons printable. Do not |
| 603 | + * set to <code>true</code> if the <code>url</code> fields in the <code>styles</code> array |
| 604 | + * refer to image sprite files. |
| 605 | + * @property {string} [title=""] The tooltip to display when the mouse moves over a cluster |
| 606 | + * marker. |
| 607 | + * @property {function} [calculator=MarkerClusterer.CALCULATOR] The function used to determine |
| 608 | + * the text to be displayed on a cluster marker and the index indicating which style to use |
| 609 | + * for the cluster marker. The input parameters for the function are (1) the array of markers |
| 610 | + * represented by a cluster marker and (2) the number of cluster icon styles. It returns a |
| 611 | + * {@link ClusterIconInfo} object. The default <code>calculator</code> returns a |
| 612 | + * <code>text</code> property which is the number of markers in the cluster and an |
| 613 | + * <code>index</code> property which is one higher than the lowest integer such that |
| 614 | + * <code>10^i</code> exceeds the number of markers in the cluster, or the size of the styles |
| 615 | + * array, whichever is less. The <code>styles</code> array element used has an index of |
| 616 | + * <code>index</code> minus 1. For example, the default <code>calculator</code> returns a |
| 617 | + * <code>text</code> value of <code>"125"</code> and an <code>index</code> of <code>3</code> |
| 618 | + * for a cluster icon representing 125 markers so the element used in the <code>styles</code> |
| 619 | + * array is <code>2</code>. |
| 620 | + * @property {Array} [styles] An array of {@link ClusterIconStyle} elements defining the styles |
| 621 | + * of the cluster markers to be used. The element to be used to style a given cluster marker |
| 622 | + * is determined by the function defined by the <code>calculator</code> property. |
| 623 | + * The default is an array of {@link ClusterIconStyle} elements whose properties are derived |
| 624 | + * from the values for <code>imagePath</code>, <code>imageExtension</code>, and |
| 625 | + * <code>imageSizes</code>. |
| 626 | + * @property {number} [batchSize=MarkerClusterer.BATCH_SIZE] Set this property to the |
| 627 | + * number of markers to be processed in a single batch when using a browser other than |
| 628 | + * Internet Explorer (for Internet Explorer, use the batchSizeIE property instead). |
| 629 | + * @property {number} [batchSizeIE=MarkerClusterer.BATCH_SIZE_IE] When Internet Explorer is |
| 630 | + * being used, markers are processed in several batches with a small delay inserted between |
| 631 | + * each batch in an attempt to avoid Javascript timeout errors. Set this property to the |
| 632 | + * number of markers to be processed in a single batch; select as high a number as you can |
| 633 | + * without causing a timeout error in the browser. This number might need to be as low as 100 |
| 634 | + * if 15,000 markers are being managed, for example. |
| 635 | + * @property {string} [imagePath=MarkerClusterer.IMAGE_PATH] |
| 636 | + * The full URL of the root name of the group of image files to use for cluster icons. |
| 637 | + * The complete file name is of the form <code>imagePath</code>n.<code>imageExtension</code> |
| 638 | + * where n is the image file number (1, 2, etc.). |
| 639 | + * @property {string} [imageExtension=MarkerClusterer.IMAGE_EXTENSION] |
| 640 | + * The extension name for the cluster icon image files (e.g., <code>"png"</code> or |
| 641 | + * <code>"jpg"</code>). |
| 642 | + * @property {Array} [imageSizes=MarkerClusterer.IMAGE_SIZES] |
| 643 | + * An array of numbers containing the widths of the group of |
| 644 | + * <code>imagePath</code>n.<code>imageExtension</code> image files. |
| 645 | + * (The images are assumed to be square.) |
| 646 | + */ |
| 647 | +/** |
| 648 | + * Creates a MarkerClusterer object with the options specified in {@link MarkerClustererOptions}. |
| 649 | + * @constructor |
| 650 | + * @extends google.maps.OverlayView |
| 651 | + * @param {google.maps.Map} map The Google map to attach to. |
| 652 | + * @param {Array.<google.maps.Marker>} [opt_markers] The markers to be added to the cluster. |
| 653 | + * @param {MarkerClustererOptions} [opt_options] The optional parameters. |
| 654 | + */ |
| 655 | +function MarkerClusterer(map, opt_markers, opt_options) { |
| 656 | + // MarkerClusterer implements google.maps.OverlayView interface. We use the |
| 657 | + // extend function to extend MarkerClusterer with google.maps.OverlayView |
| 658 | + // because it might not always be available when the code is defined so we |
| 659 | + // look for it at the last possible moment. If it doesn't exist now then |
| 660 | + // there is no point going ahead :) |
| 661 | + this.extend(MarkerClusterer, google.maps.OverlayView); |
| 662 | + |
| 663 | + opt_markers = opt_markers || []; |
| 664 | + opt_options = opt_options || {}; |
| 665 | + |
| 666 | + this.markers_ = []; |
| 667 | + this.clusters_ = []; |
| 668 | + this.listeners_ = []; |
| 669 | + this.activeMap_ = null; |
| 670 | + this.ready_ = false; |
| 671 | + |
| 672 | + this.gridSize_ = opt_options.gridSize || 60; |
| 673 | + this.minClusterSize_ = opt_options.minimumClusterSize || 2; |
| 674 | + this.maxZoom_ = opt_options.maxZoom || null; |
| 675 | + this.styles_ = opt_options.styles || []; |
| 676 | + this.title_ = opt_options.title || ""; |
| 677 | + this.zoomOnClick_ = true; |
| 678 | + if (opt_options.zoomOnClick !== undefined) { |
| 679 | + this.zoomOnClick_ = opt_options.zoomOnClick; |
| 680 | + } |
| 681 | + this.averageCenter_ = false; |
| 682 | + if (opt_options.averageCenter !== undefined) { |
| 683 | + this.averageCenter_ = opt_options.averageCenter; |
| 684 | + } |
| 685 | + this.ignoreHidden_ = false; |
| 686 | + if (opt_options.ignoreHidden !== undefined) { |
| 687 | + this.ignoreHidden_ = opt_options.ignoreHidden; |
| 688 | + } |
| 689 | + this.printable_ = false; |
| 690 | + if (opt_options.printable !== undefined) { |
| 691 | + this.printable_ = opt_options.printable; |
| 692 | + } |
| 693 | + this.imagePath_ = opt_options.imagePath || MarkerClusterer.IMAGE_PATH; |
| 694 | + this.imageExtension_ = opt_options.imageExtension || MarkerClusterer.IMAGE_EXTENSION; |
| 695 | + this.imageSizes_ = opt_options.imageSizes || MarkerClusterer.IMAGE_SIZES; |
| 696 | + this.calculator_ = opt_options.calculator || MarkerClusterer.CALCULATOR; |
| 697 | + this.batchSize_ = opt_options.batchSize || MarkerClusterer.BATCH_SIZE; |
| 698 | + this.batchSizeIE_ = opt_options.batchSizeIE || MarkerClusterer.BATCH_SIZE_IE; |
| 699 | + |
| 700 | + if (navigator.userAgent.toLowerCase().indexOf("msie") !== -1) { |
| 701 | + // Try to avoid IE timeout when processing a huge number of markers: |
| 702 | + this.batchSize_ = this.batchSizeIE_; |
| 703 | + } |
| 704 | + |
| 705 | + this.setupStyles_(); |
| 706 | + |
| 707 | + this.addMarkers(opt_markers, true); |
| 708 | + this.setMap(map); // Note: this causes onAdd to be called |
| 709 | +} |
| 710 | + |
| 711 | + |
| 712 | +/** |
| 713 | + * Implementation of the onAdd interface method. |
| 714 | + * @ignore |
| 715 | + */ |
| 716 | +MarkerClusterer.prototype.onAdd = function () { |
| 717 | + var cMarkerClusterer = this; |
| 718 | + |
| 719 | + this.activeMap_ = this.getMap(); |
| 720 | + this.ready_ = true; |
| 721 | + |
| 722 | + this.repaint(); |
| 723 | + |
| 724 | + // Add the map event listeners |
| 725 | + this.listeners_ = [ |
| 726 | + google.maps.event.addListener(this.getMap(), "zoom_changed", function () { |
| 727 | + cMarkerClusterer.resetViewport_(false); |
| 728 | + // Workaround for this Google bug: when map is at level 0 and "-" of |
| 729 | + // zoom slider is clicked, a "zoom_changed" event is fired even though |
| 730 | + // the map doesn't zoom out any further. In this situation, no "idle" |
| 731 | + // event is triggered so the cluster markers that have been removed |
| 732 | + // do not get redrawn. |
| 733 | + if (this.getZoom() === 0) { |
| 734 | + google.maps.event.trigger(this, "idle"); |
| 735 | + } |
| 736 | + }), |
| 737 | + google.maps.event.addListener(this.getMap(), "idle", function () { |
| 738 | + cMarkerClusterer.redraw_(); |
| 739 | + }) |
| 740 | + ]; |
| 741 | +}; |
| 742 | + |
| 743 | + |
| 744 | +/** |
| 745 | + * Implementation of the onRemove interface method. |
| 746 | + * Removes map event listeners and all cluster icons from the DOM. |
| 747 | + * All managed markers are also put back on the map. |
| 748 | + * @ignore |
| 749 | + */ |
| 750 | +MarkerClusterer.prototype.onRemove = function () { |
| 751 | + var i; |
| 752 | + |
| 753 | + // Put all the managed markers back on the map: |
| 754 | + for (i = 0; i < this.markers_.length; i++) { |
| 755 | + this.markers_[i].setMap(this.activeMap_); |
| 756 | + } |
| 757 | + |
| 758 | + // Remove all clusters: |
| 759 | + for (i = 0; i < this.clusters_.length; i++) { |
| 760 | + this.clusters_[i].remove(); |
| 761 | + } |
| 762 | + this.clusters_ = []; |
| 763 | + |
| 764 | + // Remove map event listeners: |
| 765 | + for (i = 0; i < this.listeners_.length; i++) { |
| 766 | + google.maps.event.removeListener(this.listeners_[i]); |
| 767 | + } |
| 768 | + this.listeners_ = []; |
| 769 | + |
| 770 | + this.activeMap_ = null; |
| 771 | + this.ready_ = false; |
| 772 | +}; |
| 773 | + |
| 774 | + |
| 775 | +/** |
| 776 | + * Implementation of the draw interface method. |
| 777 | + * @ignore |
| 778 | + */ |
| 779 | +MarkerClusterer.prototype.draw = function () {}; |
| 780 | + |
| 781 | + |
| 782 | +/** |
| 783 | + * Sets up the styles object. |
| 784 | + */ |
| 785 | +MarkerClusterer.prototype.setupStyles_ = function () { |
| 786 | + var i, size; |
| 787 | + if (this.styles_.length > 0) { |
| 788 | + return; |
| 789 | + } |
| 790 | + |
| 791 | + for (i = 0; i < this.imageSizes_.length; i++) { |
| 792 | + size = this.imageSizes_[i]; |
| 793 | + this.styles_.push({ |
| 794 | + url: this.imagePath_ + (i + 1) + "." + this.imageExtension_, |
| 795 | + height: size, |
| 796 | + width: size |
| 797 | + }); |
| 798 | + } |
| 799 | +}; |
| 800 | + |
| 801 | + |
| 802 | +/** |
| 803 | + * Fits the map to the bounds of the markers managed by the clusterer. |
| 804 | + */ |
| 805 | +MarkerClusterer.prototype.fitMapToMarkers = function () { |
| 806 | + var i; |
| 807 | + var markers = this.getMarkers(); |
| 808 | + var bounds = new google.maps.LatLngBounds(); |
| 809 | + for (i = 0; i < markers.length; i++) { |
| 810 | + bounds.extend(markers[i].getPosition()); |
| 811 | + } |
| 812 | + |
| 813 | + this.getMap().fitBounds(bounds); |
| 814 | +}; |
| 815 | + |
| 816 | + |
| 817 | +/** |
| 818 | + * Returns the value of the <code>gridSize</code> property. |
| 819 | + * |
| 820 | + * @return {number} The grid size. |
| 821 | + */ |
| 822 | +MarkerClusterer.prototype.getGridSize = function () { |
| 823 | + return this.gridSize_; |
| 824 | +}; |
| 825 | + |
| 826 | + |
| 827 | +/** |
| 828 | + * Sets the value of the <code>gridSize</code> property. |
| 829 | + * |
| 830 | + * @param {number} gridSize The grid size. |
| 831 | + */ |
| 832 | +MarkerClusterer.prototype.setGridSize = function (gridSize) { |
| 833 | + this.gridSize_ = gridSize; |
| 834 | +}; |
| 835 | + |
| 836 | + |
| 837 | +/** |
| 838 | + * Returns the value of the <code>minimumClusterSize</code> property. |
| 839 | + * |
| 840 | + * @return {number} The minimum cluster size. |
| 841 | + */ |
| 842 | +MarkerClusterer.prototype.getMinimumClusterSize = function () { |
| 843 | + return this.minClusterSize_; |
| 844 | +}; |
| 845 | + |
| 846 | +/** |
| 847 | + * Sets the value of the <code>minimumClusterSize</code> property. |
| 848 | + * |
| 849 | + * @param {number} minimumClusterSize The minimum cluster size. |
| 850 | + */ |
| 851 | +MarkerClusterer.prototype.setMinimumClusterSize = function (minimumClusterSize) { |
| 852 | + this.minClusterSize_ = minimumClusterSize; |
| 853 | +}; |
| 854 | + |
| 855 | + |
| 856 | +/** |
| 857 | + * Returns the value of the <code>maxZoom</code> property. |
| 858 | + * |
| 859 | + * @return {number} The maximum zoom level. |
| 860 | + */ |
| 861 | +MarkerClusterer.prototype.getMaxZoom = function () { |
| 862 | + return this.maxZoom_; |
| 863 | +}; |
| 864 | + |
| 865 | + |
| 866 | +/** |
| 867 | + * Sets the value of the <code>maxZoom</code> property. |
| 868 | + * |
| 869 | + * @param {number} maxZoom The maximum zoom level. |
| 870 | + */ |
| 871 | +MarkerClusterer.prototype.setMaxZoom = function (maxZoom) { |
| 872 | + this.maxZoom_ = maxZoom; |
| 873 | +}; |
| 874 | + |
| 875 | + |
| 876 | +/** |
| 877 | + * Returns the value of the <code>styles</code> property. |
| 878 | + * |
| 879 | + * @return {Array} The array of styles defining the cluster markers to be used. |
| 880 | + */ |
| 881 | +MarkerClusterer.prototype.getStyles = function () { |
| 882 | + return this.styles_; |
| 883 | +}; |
| 884 | + |
| 885 | + |
| 886 | +/** |
| 887 | + * Sets the value of the <code>styles</code> property. |
| 888 | + * |
| 889 | + * @param {Array.<ClusterIconStyle>} styles The array of styles to use. |
| 890 | + */ |
| 891 | +MarkerClusterer.prototype.setStyles = function (styles) { |
| 892 | + this.styles_ = styles; |
| 893 | +}; |
| 894 | + |
| 895 | + |
| 896 | +/** |
| 897 | + * Returns the value of the <code>title</code> property. |
| 898 | + * |
| 899 | + * @return {string} The content of the title text. |
| 900 | + */ |
| 901 | +MarkerClusterer.prototype.getTitle = function () { |
| 902 | + return this.title_; |
| 903 | +}; |
| 904 | + |
| 905 | + |
| 906 | +/** |
| 907 | + * Sets the value of the <code>title</code> property. |
| 908 | + * |
| 909 | + * @param {string} title The value of the title property. |
| 910 | + */ |
| 911 | +MarkerClusterer.prototype.setTitle = function (title) { |
| 912 | + this.title_ = title; |
| 913 | +}; |
| 914 | + |
| 915 | + |
| 916 | +/** |
| 917 | + * Returns the value of the <code>zoomOnClick</code> property. |
| 918 | + * |
| 919 | + * @return {boolean} True if zoomOnClick property is set. |
| 920 | + */ |
| 921 | +MarkerClusterer.prototype.getZoomOnClick = function () { |
| 922 | + return this.zoomOnClick_; |
| 923 | +}; |
| 924 | + |
| 925 | + |
| 926 | +/** |
| 927 | + * Sets the value of the <code>zoomOnClick</code> property. |
| 928 | + * |
| 929 | + * @param {boolean} zoomOnClick The value of the zoomOnClick property. |
| 930 | + */ |
| 931 | +MarkerClusterer.prototype.setZoomOnClick = function (zoomOnClick) { |
| 932 | + this.zoomOnClick_ = zoomOnClick; |
| 933 | +}; |
| 934 | + |
| 935 | + |
| 936 | +/** |
| 937 | + * Returns the value of the <code>averageCenter</code> property. |
| 938 | + * |
| 939 | + * @return {boolean} True if averageCenter property is set. |
| 940 | + */ |
| 941 | +MarkerClusterer.prototype.getAverageCenter = function () { |
| 942 | + return this.averageCenter_; |
| 943 | +}; |
| 944 | + |
| 945 | + |
| 946 | +/** |
| 947 | + * Sets the value of the <code>averageCenter</code> property. |
| 948 | + * |
| 949 | + * @param {boolean} averageCenter The value of the averageCenter property. |
| 950 | + */ |
| 951 | +MarkerClusterer.prototype.setAverageCenter = function (averageCenter) { |
| 952 | + this.averageCenter_ = averageCenter; |
| 953 | +}; |
| 954 | + |
| 955 | + |
| 956 | +/** |
| 957 | + * Returns the value of the <code>ignoreHidden</code> property. |
| 958 | + * |
| 959 | + * @return {boolean} True if ignoreHidden property is set. |
| 960 | + */ |
| 961 | +MarkerClusterer.prototype.getIgnoreHidden = function () { |
| 962 | + return this.ignoreHidden_; |
| 963 | +}; |
| 964 | + |
| 965 | + |
| 966 | +/** |
| 967 | + * Sets the value of the <code>ignoreHidden</code> property. |
| 968 | + * |
| 969 | + * @param {boolean} ignoreHidden The value of the ignoreHidden property. |
| 970 | + */ |
| 971 | +MarkerClusterer.prototype.setIgnoreHidden = function (ignoreHidden) { |
| 972 | + this.ignoreHidden_ = ignoreHidden; |
| 973 | +}; |
| 974 | + |
| 975 | + |
| 976 | +/** |
| 977 | + * Returns the value of the <code>imageExtension</code> property. |
| 978 | + * |
| 979 | + * @return {string} The value of the imageExtension property. |
| 980 | + */ |
| 981 | +MarkerClusterer.prototype.getImageExtension = function () { |
| 982 | + return this.imageExtension_; |
| 983 | +}; |
| 984 | + |
| 985 | + |
| 986 | +/** |
| 987 | + * Sets the value of the <code>imageExtension</code> property. |
| 988 | + * |
| 989 | + * @param {string} imageExtension The value of the imageExtension property. |
| 990 | + */ |
| 991 | +MarkerClusterer.prototype.setImageExtension = function (imageExtension) { |
| 992 | + this.imageExtension_ = imageExtension; |
| 993 | +}; |
| 994 | + |
| 995 | + |
| 996 | +/** |
| 997 | + * Returns the value of the <code>imagePath</code> property. |
| 998 | + * |
| 999 | + * @return {string} The value of the imagePath property. |
| 1000 | + */ |
| 1001 | +MarkerClusterer.prototype.getImagePath = function () { |
| 1002 | + return this.imagePath_; |
| 1003 | +}; |
| 1004 | + |
| 1005 | + |
| 1006 | +/** |
| 1007 | + * Sets the value of the <code>imagePath</code> property. |
| 1008 | + * |
| 1009 | + * @param {string} imagePath The value of the imagePath property. |
| 1010 | + */ |
| 1011 | +MarkerClusterer.prototype.setImagePath = function (imagePath) { |
| 1012 | + this.imagePath_ = imagePath; |
| 1013 | +}; |
| 1014 | + |
| 1015 | + |
| 1016 | +/** |
| 1017 | + * Returns the value of the <code>imageSizes</code> property. |
| 1018 | + * |
| 1019 | + * @return {Array} The value of the imageSizes property. |
| 1020 | + */ |
| 1021 | +MarkerClusterer.prototype.getImageSizes = function () { |
| 1022 | + return this.imageSizes_; |
| 1023 | +}; |
| 1024 | + |
| 1025 | + |
| 1026 | +/** |
| 1027 | + * Sets the value of the <code>imageSizes</code> property. |
| 1028 | + * |
| 1029 | + * @param {Array} imageSizes The value of the imageSizes property. |
| 1030 | + */ |
| 1031 | +MarkerClusterer.prototype.setImageSizes = function (imageSizes) { |
| 1032 | + this.imageSizes_ = imageSizes; |
| 1033 | +}; |
| 1034 | + |
| 1035 | + |
| 1036 | +/** |
| 1037 | + * Returns the value of the <code>calculator</code> property. |
| 1038 | + * |
| 1039 | + * @return {function} the value of the calculator property. |
| 1040 | + */ |
| 1041 | +MarkerClusterer.prototype.getCalculator = function () { |
| 1042 | + return this.calculator_; |
| 1043 | +}; |
| 1044 | + |
| 1045 | + |
| 1046 | +/** |
| 1047 | + * Sets the value of the <code>calculator</code> property. |
| 1048 | + * |
| 1049 | + * @param {function(Array.<google.maps.Marker>, number)} calculator The value |
| 1050 | + * of the calculator property. |
| 1051 | + */ |
| 1052 | +MarkerClusterer.prototype.setCalculator = function (calculator) { |
| 1053 | + this.calculator_ = calculator; |
| 1054 | +}; |
| 1055 | + |
| 1056 | + |
| 1057 | +/** |
| 1058 | + * Returns the value of the <code>printable</code> property. |
| 1059 | + * |
| 1060 | + * @return {boolean} the value of the printable property. |
| 1061 | + */ |
| 1062 | +MarkerClusterer.prototype.getPrintable = function () { |
| 1063 | + return this.printable_; |
| 1064 | +}; |
| 1065 | + |
| 1066 | + |
| 1067 | +/** |
| 1068 | + * Sets the value of the <code>printable</code> property. |
| 1069 | + * |
| 1070 | + * @param {boolean} printable The value of the printable property. |
| 1071 | + */ |
| 1072 | +MarkerClusterer.prototype.setPrintable = function (printable) { |
| 1073 | + this.printable_ = printable; |
| 1074 | +}; |
| 1075 | + |
| 1076 | + |
| 1077 | +/** |
| 1078 | + * Returns the value of the <code>batchSizeIE</code> property. |
| 1079 | + * |
| 1080 | + * @return {number} the value of the batchSizeIE property. |
| 1081 | + */ |
| 1082 | +MarkerClusterer.prototype.getBatchSizeIE = function () { |
| 1083 | + return this.batchSizeIE_; |
| 1084 | +}; |
| 1085 | + |
| 1086 | + |
| 1087 | +/** |
| 1088 | + * Sets the value of the <code>batchSizeIE</code> property. |
| 1089 | + * |
| 1090 | + * @param {number} batchSizeIE The value of the batchSizeIE property. |
| 1091 | + */ |
| 1092 | +MarkerClusterer.prototype.setBatchSizeIE = function (batchSizeIE) { |
| 1093 | + this.batchSizeIE_ = batchSizeIE; |
| 1094 | +}; |
| 1095 | + |
| 1096 | + |
| 1097 | +/** |
| 1098 | + * Returns the array of markers managed by the clusterer. |
| 1099 | + * |
| 1100 | + * @return {Array} The array of markers managed by the clusterer. |
| 1101 | + */ |
| 1102 | +MarkerClusterer.prototype.getMarkers = function () { |
| 1103 | + return this.markers_; |
| 1104 | +}; |
| 1105 | + |
| 1106 | + |
| 1107 | +/** |
| 1108 | + * Returns the number of markers managed by the clusterer. |
| 1109 | + * |
| 1110 | + * @return {number} The number of markers. |
| 1111 | + */ |
| 1112 | +MarkerClusterer.prototype.getTotalMarkers = function () { |
| 1113 | + return this.markers_.length; |
| 1114 | +}; |
| 1115 | + |
| 1116 | + |
| 1117 | +/** |
| 1118 | + * Returns the current array of clusters formed by the clusterer. |
| 1119 | + * |
| 1120 | + * @return {Array} The array of clusters formed by the clusterer. |
| 1121 | + */ |
| 1122 | +MarkerClusterer.prototype.getClusters = function () { |
| 1123 | + return this.clusters_; |
| 1124 | +}; |
| 1125 | + |
| 1126 | + |
| 1127 | +/** |
| 1128 | + * Returns the number of clusters formed by the clusterer. |
| 1129 | + * |
| 1130 | + * @return {number} The number of clusters formed by the clusterer. |
| 1131 | + */ |
| 1132 | +MarkerClusterer.prototype.getTotalClusters = function () { |
| 1133 | + return this.clusters_.length; |
| 1134 | +}; |
| 1135 | + |
| 1136 | + |
| 1137 | +/** |
| 1138 | + * Adds a marker to the clusterer. The clusters are redrawn unless |
| 1139 | + * <code>opt_nodraw</code> is set to <code>true</code>. |
| 1140 | + * |
| 1141 | + * @param {google.maps.Marker} marker The marker to add. |
| 1142 | + * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing. |
| 1143 | + */ |
| 1144 | +MarkerClusterer.prototype.addMarker = function (marker, opt_nodraw) { |
| 1145 | + this.pushMarkerTo_(marker); |
| 1146 | + if (!opt_nodraw) { |
| 1147 | + this.redraw_(); |
| 1148 | + } |
| 1149 | +}; |
| 1150 | + |
| 1151 | + |
| 1152 | +/** |
| 1153 | + * Adds an array of markers to the clusterer. The clusters are redrawn unless |
| 1154 | + * <code>opt_nodraw</code> is set to <code>true</code>. |
| 1155 | + * |
| 1156 | + * @param {Array.<google.maps.Marker>} markers The markers to add. |
| 1157 | + * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing. |
| 1158 | + */ |
| 1159 | +MarkerClusterer.prototype.addMarkers = function (markers, opt_nodraw) { |
| 1160 | + var i; |
| 1161 | + for (i = 0; i < markers.length; i++) { |
| 1162 | + this.pushMarkerTo_(markers[i]); |
| 1163 | + } |
| 1164 | + if (!opt_nodraw) { |
| 1165 | + this.redraw_(); |
| 1166 | + } |
| 1167 | +}; |
| 1168 | + |
| 1169 | + |
| 1170 | +/** |
| 1171 | + * Pushes a marker to the clusterer. |
| 1172 | + * |
| 1173 | + * @param {google.maps.Marker} marker The marker to add. |
| 1174 | + */ |
| 1175 | +MarkerClusterer.prototype.pushMarkerTo_ = function (marker) { |
| 1176 | + // If the marker is draggable add a listener so we can update the clusters on the dragend: |
| 1177 | + if (marker.getDraggable()) { |
| 1178 | + var cMarkerClusterer = this; |
| 1179 | + google.maps.event.addListener(marker, "dragend", function () { |
| 1180 | + if (cMarkerClusterer.ready_) { |
| 1181 | + this.isAdded = false; |
| 1182 | + cMarkerClusterer.repaint(); |
| 1183 | + } |
| 1184 | + }); |
| 1185 | + } |
| 1186 | + marker.isAdded = false; |
| 1187 | + this.markers_.push(marker); |
| 1188 | +}; |
| 1189 | + |
| 1190 | + |
| 1191 | +/** |
| 1192 | + * Removes a marker from the cluster. The clusters are redrawn unless |
| 1193 | + * <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if the |
| 1194 | + * marker was removed from the clusterer. |
| 1195 | + * |
| 1196 | + * @param {google.maps.Marker} marker The marker to remove. |
| 1197 | + * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing. |
| 1198 | + * @return {boolean} True if the marker was removed from the clusterer. |
| 1199 | + */ |
| 1200 | +MarkerClusterer.prototype.removeMarker = function (marker, opt_nodraw) { |
| 1201 | + var removed = this.removeMarker_(marker); |
| 1202 | + |
| 1203 | + if (!opt_nodraw && removed) { |
| 1204 | + this.repaint(); |
| 1205 | + } |
| 1206 | + |
| 1207 | + return removed; |
| 1208 | +}; |
| 1209 | + |
| 1210 | + |
| 1211 | +/** |
| 1212 | + * Removes an array of markers from the cluster. The clusters are redrawn unless |
| 1213 | + * <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if markers |
| 1214 | + * were removed from the clusterer. |
| 1215 | + * |
| 1216 | + * @param {Array.<google.maps.Marker>} markers The markers to remove. |
| 1217 | + * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing. |
| 1218 | + * @return {boolean} True if markers were removed from the clusterer. |
| 1219 | + */ |
| 1220 | +MarkerClusterer.prototype.removeMarkers = function (markers, opt_nodraw) { |
| 1221 | + var i, r; |
| 1222 | + var removed = false; |
| 1223 | + |
| 1224 | + for (i = 0; i < markers.length; i++) { |
| 1225 | + r = this.removeMarker_(markers[i]); |
| 1226 | + removed = removed || r; |
| 1227 | + } |
| 1228 | + |
| 1229 | + if (!opt_nodraw && removed) { |
| 1230 | + this.repaint(); |
| 1231 | + } |
| 1232 | + |
| 1233 | + return removed; |
| 1234 | +}; |
| 1235 | + |
| 1236 | + |
| 1237 | +/** |
| 1238 | + * Removes a marker and returns true if removed, false if not. |
| 1239 | + * |
| 1240 | + * @param {google.maps.Marker} marker The marker to remove |
| 1241 | + * @return {boolean} Whether the marker was removed or not |
| 1242 | + */ |
| 1243 | +MarkerClusterer.prototype.removeMarker_ = function (marker) { |
| 1244 | + var i; |
| 1245 | + var index = -1; |
| 1246 | + if (this.markers_.indexOf) { |
| 1247 | + index = this.markers_.indexOf(marker); |
| 1248 | + } else { |
| 1249 | + for (i = 0; i < this.markers_.length; i++) { |
| 1250 | + if (marker === this.markers_[i]) { |
| 1251 | + index = i; |
| 1252 | + break; |
| 1253 | + } |
| 1254 | + } |
| 1255 | + } |
| 1256 | + |
| 1257 | + if (index === -1) { |
| 1258 | + // Marker is not in our list of markers, so do nothing: |
| 1259 | + return false; |
| 1260 | + } |
| 1261 | + |
| 1262 | + marker.setMap(null); |
| 1263 | + this.markers_.splice(index, 1); // Remove the marker from the list of managed markers |
| 1264 | + return true; |
| 1265 | +}; |
| 1266 | + |
| 1267 | + |
| 1268 | +/** |
| 1269 | + * Removes all clusters and markers from the map and also removes all markers |
| 1270 | + * managed by the clusterer. |
| 1271 | + */ |
| 1272 | +MarkerClusterer.prototype.clearMarkers = function () { |
| 1273 | + this.resetViewport_(true); |
| 1274 | + this.markers_ = []; |
| 1275 | +}; |
| 1276 | + |
| 1277 | + |
| 1278 | +/** |
| 1279 | + * Recalculates and redraws all the marker clusters from scratch. |
| 1280 | + * Call this after changing any properties. |
| 1281 | + */ |
| 1282 | +MarkerClusterer.prototype.repaint = function () { |
| 1283 | + var oldClusters = this.clusters_.slice(); |
| 1284 | + this.clusters_ = []; |
| 1285 | + this.resetViewport_(false); |
| 1286 | + this.redraw_(); |
| 1287 | + |
| 1288 | + // Remove the old clusters. |
| 1289 | + // Do it in a timeout to prevent blinking effect. |
| 1290 | + setTimeout(function () { |
| 1291 | + var i; |
| 1292 | + for (i = 0; i < oldClusters.length; i++) { |
| 1293 | + oldClusters[i].remove(); |
| 1294 | + } |
| 1295 | + }, 0); |
| 1296 | +}; |
| 1297 | + |
| 1298 | + |
| 1299 | +/** |
| 1300 | + * Returns the current bounds extended by the grid size. |
| 1301 | + * |
| 1302 | + * @param {google.maps.LatLngBounds} bounds The bounds to extend. |
| 1303 | + * @return {google.maps.LatLngBounds} The extended bounds. |
| 1304 | + * @ignore |
| 1305 | + */ |
| 1306 | +MarkerClusterer.prototype.getExtendedBounds = function (bounds) { |
| 1307 | + var projection = this.getProjection(); |
| 1308 | + |
| 1309 | + // Turn the bounds into latlng. |
| 1310 | + var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), |
| 1311 | + bounds.getNorthEast().lng()); |
| 1312 | + var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), |
| 1313 | + bounds.getSouthWest().lng()); |
| 1314 | + |
| 1315 | + // Convert the points to pixels and the extend out by the grid size. |
| 1316 | + var trPix = projection.fromLatLngToDivPixel(tr); |
| 1317 | + trPix.x += this.gridSize_; |
| 1318 | + trPix.y -= this.gridSize_; |
| 1319 | + |
| 1320 | + var blPix = projection.fromLatLngToDivPixel(bl); |
| 1321 | + blPix.x -= this.gridSize_; |
| 1322 | + blPix.y += this.gridSize_; |
| 1323 | + |
| 1324 | + // Convert the pixel points back to LatLng |
| 1325 | + var ne = projection.fromDivPixelToLatLng(trPix); |
| 1326 | + var sw = projection.fromDivPixelToLatLng(blPix); |
| 1327 | + |
| 1328 | + // Extend the bounds to contain the new bounds. |
| 1329 | + bounds.extend(ne); |
| 1330 | + bounds.extend(sw); |
| 1331 | + |
| 1332 | + return bounds; |
| 1333 | +}; |
| 1334 | + |
| 1335 | + |
| 1336 | +/** |
| 1337 | + * Redraws all the clusters. |
| 1338 | + */ |
| 1339 | +MarkerClusterer.prototype.redraw_ = function () { |
| 1340 | + this.createClusters_(0); |
| 1341 | +}; |
| 1342 | + |
| 1343 | + |
| 1344 | +/** |
| 1345 | + * Removes all clusters from the map. The markers are also removed from the map |
| 1346 | + * if <code>opt_hide</code> is set to <code>true</code>. |
| 1347 | + * |
| 1348 | + * @param {boolean} [opt_hide] Set to <code>true</code> to also remove the markers |
| 1349 | + * from the map. |
| 1350 | + */ |
| 1351 | +MarkerClusterer.prototype.resetViewport_ = function (opt_hide) { |
| 1352 | + var i, marker; |
| 1353 | + // Remove all the clusters |
| 1354 | + for (i = 0; i < this.clusters_.length; i++) { |
| 1355 | + this.clusters_[i].remove(); |
| 1356 | + } |
| 1357 | + this.clusters_ = []; |
| 1358 | + |
| 1359 | + // Reset the markers to not be added and to be removed from the map. |
| 1360 | + for (i = 0; i < this.markers_.length; i++) { |
| 1361 | + marker = this.markers_[i]; |
| 1362 | + marker.isAdded = false; |
| 1363 | + if (opt_hide) { |
| 1364 | + marker.setMap(null); |
| 1365 | + } |
| 1366 | + } |
| 1367 | +}; |
| 1368 | + |
| 1369 | + |
| 1370 | +/** |
| 1371 | + * Calculates the distance between two latlng locations in km. |
| 1372 | + * |
| 1373 | + * @param {google.maps.LatLng} p1 The first lat lng point. |
| 1374 | + * @param {google.maps.LatLng} p2 The second lat lng point. |
| 1375 | + * @return {number} The distance between the two points in km. |
| 1376 | + * @see http://www.movable-type.co.uk/scripts/latlong.html |
| 1377 | + */ |
| 1378 | +MarkerClusterer.prototype.distanceBetweenPoints_ = function (p1, p2) { |
| 1379 | + var R = 6371; // Radius of the Earth in km |
| 1380 | + var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; |
| 1381 | + var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; |
| 1382 | + var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + |
| 1383 | + Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * |
| 1384 | + Math.sin(dLon / 2) * Math.sin(dLon / 2); |
| 1385 | + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); |
| 1386 | + var d = R * c; |
| 1387 | + return d; |
| 1388 | +}; |
| 1389 | + |
| 1390 | + |
| 1391 | +/** |
| 1392 | + * Determines if a marker is contained in a bounds. |
| 1393 | + * |
| 1394 | + * @param {google.maps.Marker} marker The marker to check. |
| 1395 | + * @param {google.maps.LatLngBounds} bounds The bounds to check against. |
| 1396 | + * @return {boolean} True if the marker is in the bounds. |
| 1397 | + */ |
| 1398 | +MarkerClusterer.prototype.isMarkerInBounds_ = function (marker, bounds) { |
| 1399 | + return bounds.contains(marker.getPosition()); |
| 1400 | +}; |
| 1401 | + |
| 1402 | + |
| 1403 | +/** |
| 1404 | + * Adds a marker to a cluster, or creates a new cluster. |
| 1405 | + * |
| 1406 | + * @param {google.maps.Marker} marker The marker to add. |
| 1407 | + */ |
| 1408 | +MarkerClusterer.prototype.addToClosestCluster_ = function (marker) { |
| 1409 | + var i, d, cluster, center; |
| 1410 | + var distance = 40000; // Some large number |
| 1411 | + var clusterToAddTo = null; |
| 1412 | + for (i = 0; i < this.clusters_.length; i++) { |
| 1413 | + cluster = this.clusters_[i]; |
| 1414 | + center = cluster.getCenter(); |
| 1415 | + if (center) { |
| 1416 | + d = this.distanceBetweenPoints_(center, marker.getPosition()); |
| 1417 | + if (d < distance) { |
| 1418 | + distance = d; |
| 1419 | + clusterToAddTo = cluster; |
| 1420 | + } |
| 1421 | + } |
| 1422 | + } |
| 1423 | + |
| 1424 | + if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) { |
| 1425 | + clusterToAddTo.addMarker(marker); |
| 1426 | + } else { |
| 1427 | + cluster = new Cluster(this); |
| 1428 | + cluster.addMarker(marker); |
| 1429 | + this.clusters_.push(cluster); |
| 1430 | + } |
| 1431 | +}; |
| 1432 | + |
| 1433 | + |
| 1434 | +/** |
| 1435 | + * Creates the clusters. This is done in batches to avoid timeout errors |
| 1436 | + * in some browsers when there is a huge number of markers. |
| 1437 | + * |
| 1438 | + * @param {number} iFirst The index of the first marker in the batch of |
| 1439 | + * markers to be added to clusters. |
| 1440 | + */ |
| 1441 | +MarkerClusterer.prototype.createClusters_ = function (iFirst) { |
| 1442 | + var i, marker; |
| 1443 | + var mapBounds; |
| 1444 | + var cMarkerClusterer = this; |
| 1445 | + if (!this.ready_) { |
| 1446 | + return; |
| 1447 | + } |
| 1448 | + |
| 1449 | + // Cancel previous batch processing if we're working on the first batch: |
| 1450 | + if (iFirst === 0) { |
| 1451 | + /** |
| 1452 | + * This event is fired when the <code>MarkerClusterer</code> begins |
| 1453 | + * clustering markers. |
| 1454 | + * @name MarkerClusterer#clusteringbegin |
| 1455 | + * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered. |
| 1456 | + * @event |
| 1457 | + */ |
| 1458 | + google.maps.event.trigger(this, "clusteringbegin", this); |
| 1459 | + |
| 1460 | + if (typeof this.timerRefStatic !== "undefined") { |
| 1461 | + clearTimeout(this.timerRefStatic); |
| 1462 | + delete this.timerRefStatic; |
| 1463 | + } |
| 1464 | + } |
| 1465 | + |
| 1466 | + // Get our current map view bounds. |
| 1467 | + // Create a new bounds object so we don't affect the map. |
| 1468 | + // |
| 1469 | + // See Comments 9 & 11 on Issue 3651 relating to this workaround for a Google Maps bug: |
| 1470 | + if (this.getMap().getZoom() > 3) { |
| 1471 | + mapBounds = new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest(), |
| 1472 | + this.getMap().getBounds().getNorthEast()); |
| 1473 | + } else { |
| 1474 | + mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472, -178.48388434375), new google.maps.LatLng(-85.08136444384544, 178.00048865625)); |
| 1475 | + } |
| 1476 | + var bounds = this.getExtendedBounds(mapBounds); |
| 1477 | + |
| 1478 | + var iLast = Math.min(iFirst + this.batchSize_, this.markers_.length); |
| 1479 | + |
| 1480 | + for (i = iFirst; i < iLast; i++) { |
| 1481 | + marker = this.markers_[i]; |
| 1482 | + if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) { |
| 1483 | + if (!this.ignoreHidden_ || (this.ignoreHidden_ && marker.getVisible())) { |
| 1484 | + this.addToClosestCluster_(marker); |
| 1485 | + } |
| 1486 | + } |
| 1487 | + } |
| 1488 | + |
| 1489 | + if (iLast < this.markers_.length) { |
| 1490 | + this.timerRefStatic = setTimeout(function () { |
| 1491 | + cMarkerClusterer.createClusters_(iLast); |
| 1492 | + }, 0); |
| 1493 | + } else { |
| 1494 | + delete this.timerRefStatic; |
| 1495 | + |
| 1496 | + /** |
| 1497 | + * This event is fired when the <code>MarkerClusterer</code> stops |
| 1498 | + * clustering markers. |
| 1499 | + * @name MarkerClusterer#clusteringend |
| 1500 | + * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered. |
| 1501 | + * @event |
| 1502 | + */ |
| 1503 | + google.maps.event.trigger(this, "clusteringend", this); |
| 1504 | + } |
| 1505 | +}; |
| 1506 | + |
| 1507 | + |
| 1508 | +/** |
| 1509 | + * Extends an object's prototype by another's. |
| 1510 | + * |
| 1511 | + * @param {Object} obj1 The object to be extended. |
| 1512 | + * @param {Object} obj2 The object to extend with. |
| 1513 | + * @return {Object} The new extended object. |
| 1514 | + * @ignore |
| 1515 | + */ |
| 1516 | +MarkerClusterer.prototype.extend = function (obj1, obj2) { |
| 1517 | + return (function (object) { |
| 1518 | + var property; |
| 1519 | + for (property in object.prototype) { |
| 1520 | + this.prototype[property] = object.prototype[property]; |
| 1521 | + } |
| 1522 | + return this; |
| 1523 | + }).apply(obj1, [obj2]); |
| 1524 | +}; |
| 1525 | + |
| 1526 | + |
| 1527 | +/** |
| 1528 | + * The default function for determining the label text and style |
| 1529 | + * for a cluster icon. |
| 1530 | + * |
| 1531 | + * @param {Array.<google.maps.Marker>} markers The array of markers represented by the cluster. |
| 1532 | + * @param {number} numStyles The number of marker styles available. |
| 1533 | + * @return {ClusterIconInfo} The information resource for the cluster. |
| 1534 | + * @constant |
| 1535 | + * @ignore |
| 1536 | + */ |
| 1537 | +MarkerClusterer.CALCULATOR = function (markers, numStyles) { |
| 1538 | + var index = 0; |
| 1539 | + var count = markers.length.toString(); |
| 1540 | + |
| 1541 | + var dv = count; |
| 1542 | + while (dv !== 0) { |
| 1543 | + dv = parseInt(dv / 10, 10); |
| 1544 | + index++; |
| 1545 | + } |
| 1546 | + |
| 1547 | + index = Math.min(index, numStyles); |
| 1548 | + return { |
| 1549 | + text: count, |
| 1550 | + index: index |
| 1551 | + }; |
| 1552 | +}; |
| 1553 | + |
| 1554 | + |
| 1555 | +/** |
| 1556 | + * The number of markers to process in one batch. |
| 1557 | + * |
| 1558 | + * @type {number} |
| 1559 | + * @constant |
| 1560 | + */ |
| 1561 | +MarkerClusterer.BATCH_SIZE = 2000; |
| 1562 | + |
| 1563 | + |
| 1564 | +/** |
| 1565 | + * The number of markers to process in one batch (IE only). |
| 1566 | + * |
| 1567 | + * @type {number} |
| 1568 | + * @constant |
| 1569 | + */ |
| 1570 | +MarkerClusterer.BATCH_SIZE_IE = 500; |
| 1571 | + |
| 1572 | + |
| 1573 | +/** |
| 1574 | + * The default root name for the marker cluster images. |
| 1575 | + * |
| 1576 | + * @type {string} |
| 1577 | + * @constant |
| 1578 | + */ |
| 1579 | +MarkerClusterer.IMAGE_PATH = "http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/images/m"; |
| 1580 | + |
| 1581 | + |
| 1582 | +/** |
| 1583 | + * The default extension name for the marker cluster images. |
| 1584 | + * |
| 1585 | + * @type {string} |
| 1586 | + * @constant |
| 1587 | + */ |
| 1588 | +MarkerClusterer.IMAGE_EXTENSION = "png"; |
| 1589 | + |
| 1590 | + |
| 1591 | +/** |
| 1592 | + * The default array of sizes for the marker cluster images. |
| 1593 | + * |
| 1594 | + * @type {Array.<number>} |
| 1595 | + * @constant |
| 1596 | + */ |
| 1597 | +MarkerClusterer.IMAGE_SIZES = [53, 56, 66, 78, 90]; |
\ No newline at end of file |
Index: trunk/extensions/Maps/includes/services/GoogleMaps3/GoogleMaps3.php |
— | — | @@ -27,8 +27,9 @@ |
28 | 28 | 'remoteBasePath' => $egMapsScriptPath . '/includes/services/GoogleMaps3', |
29 | 29 | 'group' => 'ext.maps', |
30 | 30 | 'scripts' => array( |
| 31 | + 'markerclusterer.js', |
31 | 32 | 'jquery.googlemap.js', |
32 | | - 'ext.maps.googlemaps3.js', |
| 33 | + 'ext.maps.googlemaps3.js' |
33 | 34 | ), |
34 | 35 | 'messages' => array( |
35 | 36 | 'maps-googlemaps3-incompatbrowser' |
— | — | @@ -75,7 +76,8 @@ |
76 | 77 | MapsMappingServices::registerService( 'googlemaps3', 'MapsGoogleMaps3' ); |
77 | 78 | $googleMaps = MapsMappingServices::getServiceInstance( 'googlemaps3' ); |
78 | 79 | $googleMaps->addFeature( 'display_map', 'MapsBaseMap' ); |
79 | | - $googleMaps->addFeature( 'display_point', 'MapsBasePointMap' ); |
| 80 | + $googleMaps->addFeature( 'display_point', 'MapsBasePointMap' ); |
| 81 | + $googleMaps->addFeature( 'display_line', 'MapsBasePointLineMap' ); |
80 | 82 | |
81 | 83 | return true; |
82 | 84 | } |
Index: trunk/extensions/Maps/includes/services/OpenLayers/OpenLayers.php |
— | — | @@ -51,7 +51,8 @@ |
52 | 52 | 'MapsOpenLayers', |
53 | 53 | array( |
54 | 54 | 'display_point' => 'MapsBasePointMap', |
55 | | - 'display_map' => 'MapsBaseMap' |
| 55 | + 'display_map' => 'MapsBaseMap', |
| 56 | + 'display_line' => 'MapsBasePointLineMap' |
56 | 57 | ) |
57 | 58 | ); |
58 | 59 | |
Index: trunk/extensions/Maps/includes/services/OpenLayers/jquery.openlayers.js |
— | — | @@ -7,7 +7,7 @@ |
8 | 8 | */ |
9 | 9 | |
10 | 10 | (function( $ ){ $.fn.openlayers = function( mapElementId, options ) { |
11 | | - |
| 11 | + |
12 | 12 | this.getOLMarker = function( markerLayer, markerData ) { |
13 | 13 | var marker; |
14 | 14 | |
— | — | @@ -103,6 +103,28 @@ |
104 | 104 | |
105 | 105 | map.addControl( new OpenLayers.Control.Attribution() ); |
106 | 106 | } |
| 107 | + |
| 108 | + this.addLine = function(properties){ |
| 109 | + var pos = new Array(); |
| 110 | + for(var x = 0; x < properties.pos.length; x++){ |
| 111 | + var point = new OpenLayers.Geometry.Point(properties.pos[x].lon,properties.pos[x].lat); |
| 112 | + point.transform( |
| 113 | + new OpenLayers.Projection("EPSG:4326"), // transform from WGS 1984 |
| 114 | + map.getProjectionObject() // to Spherical Mercator Projection |
| 115 | + ); |
| 116 | + pos.push(point); |
| 117 | + } |
| 118 | + |
| 119 | + var style = { |
| 120 | + 'strokeColor':properties.strokeColor, |
| 121 | + 'strokeWidth': properties.strokeWeight, |
| 122 | + 'strokeOpacity': properties.strokeOpacity |
| 123 | + } |
| 124 | + |
| 125 | + var line = new OpenLayers.Geometry.LineString(pos); |
| 126 | + var lineFeature = new OpenLayers.Feature.Vector(line, null, style); |
| 127 | + this.lineLayer.addFeatures([lineFeature]); |
| 128 | + } |
107 | 129 | |
108 | 130 | /** |
109 | 131 | * Gets a valid control name (with excat lower and upper case letters), |
— | — | @@ -154,7 +176,7 @@ |
155 | 177 | mapOptions.maxResolution = 156543.0339; |
156 | 178 | mapOptions.maxExtent = new OpenLayers.Bounds(-20037508, -20037508, 20037508, 20037508.34); |
157 | 179 | } |
158 | | - |
| 180 | + |
159 | 181 | this.map = new OpenLayers.Map( mapElementId, mapOptions ); |
160 | 182 | var map = this.map; |
161 | 183 | this.addControls( map, options.controls, this.get( 0 ) ); |
— | — | @@ -195,7 +217,59 @@ |
196 | 218 | mw.loader.using( 'ext.maps.resizable', function() { |
197 | 219 | _this.resizable(); |
198 | 220 | } ); |
199 | | - } |
| 221 | + } |
| 222 | + |
| 223 | + if(options.lines){ |
| 224 | + this.lineLayer = new OpenLayers.Layer.Vector("Line Layer"); |
| 225 | + map.addLayer(this.lineLayer); |
| 226 | + map.addControl(new OpenLayers.Control.DrawFeature(this.lineLayer, OpenLayers.Handler.Path)); |
| 227 | + |
| 228 | + for ( var i = 0; i < options.lines.length; i++ ) { |
| 229 | + this.addLine(options.lines[i]); |
| 230 | + } |
| 231 | + } |
| 232 | + |
| 233 | + if(options.copycoords){ |
| 234 | + map.div.oncontextmenu = function(){return false;}; |
| 235 | + OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, { |
| 236 | + defaultHandlerOptions: { |
| 237 | + 'single': true, |
| 238 | + 'double': false, |
| 239 | + 'pixelTolerance': 0, |
| 240 | + 'stopSingle': false, |
| 241 | + 'stopDouble': false |
| 242 | + }, |
| 243 | + handleRightClicks:true, |
| 244 | + |
| 245 | + initialize: function(options) { |
| 246 | + this.handlerOptions = OpenLayers.Util.extend( |
| 247 | + {}, this.defaultHandlerOptions |
| 248 | + ); |
| 249 | + OpenLayers.Control.prototype.initialize.apply( |
| 250 | + this, arguments |
| 251 | + ); |
| 252 | + this.handler = new OpenLayers.Handler.Click( |
| 253 | + this, this.eventMethods, this.handlerOptions |
| 254 | + ); |
| 255 | + } |
| 256 | + |
| 257 | + }) |
| 258 | + var click = new OpenLayers.Control.Click({ |
| 259 | + eventMethods:{ |
| 260 | + 'rightclick': function(e){ |
| 261 | + var lonlat = map.getLonLatFromViewPortPx(e.xy); |
| 262 | + lonlat = lonlat.transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326")); |
| 263 | + prompt("CTRL+C, ENTER",lonlat.lat+','+lonlat.lon); |
| 264 | + } |
| 265 | + } |
| 266 | + }); |
| 267 | + map.addControl(click); |
| 268 | + click.activate(); |
| 269 | + } |
| 270 | + |
| 271 | + if(options.markercluster){ |
| 272 | + alert('no support for clustering in openlayers'); |
| 273 | + } |
200 | 274 | |
201 | 275 | return this; |
202 | 276 | |
Index: trunk/extensions/Maps/includes/manipulations/Maps_ParamLine.php |
— | — | @@ -0,0 +1,38 @@ |
| 2 | +<?php |
| 3 | +class MapsParamLine extends ItemParameterManipulation { |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | + /** |
| 8 | + * Constructor |
| 9 | + */ |
| 10 | + public function __construct() { |
| 11 | + parent::__construct(); |
| 12 | + } |
| 13 | + |
| 14 | + /** |
| 15 | + * Manipulate an actual value. |
| 16 | + * |
| 17 | + * @param string $value |
| 18 | + * @param Parameter $parameter |
| 19 | + * @param array $parameters |
| 20 | + * |
| 21 | + * @since 0.4 |
| 22 | + * |
| 23 | + * @return mixed |
| 24 | + */ |
| 25 | + public function doManipulation(&$value, Parameter $parameter, array &$parameters) |
| 26 | + { |
| 27 | + $parts = preg_split('/[\|]+/',$value); |
| 28 | + $lineCoords = preg_split('/[:]/',$parts[0]); |
| 29 | + |
| 30 | + $value = new MapsLine($lineCoords); |
| 31 | + $value->setTitle( isset($parts[1]) ? $parts[1] : '' ); |
| 32 | + $value->setText( isset($parts[2]) ? $parts[2] : '' ); |
| 33 | + $value->setStrokeColor( isset($parts[3]) ? $parts[3] : '' ); |
| 34 | + $value->setStrokeOpacity( isset($parts[4]) ? $parts[4] : '' ); |
| 35 | + $value->setStrokeWeight( isset($parts[5]) ? $parts[5] : '' ); |
| 36 | + |
| 37 | + $value = $value->getJSONObject(); |
| 38 | + } |
| 39 | +} |
\ No newline at end of file |