Package muntjac :: Package addon :: Package google_maps :: Module google_map
[hide private]
[frames] | no frames]

Source Code for Module muntjac.addon.google_maps.google_map

  1  # Copyright (C) 2012 Vaadin Ltd.  
  2  # Copyright (C) 2012 Richard Lincoln 
  3  #  
  4  # Licensed under the Apache License, Version 2.0 (the "License");  
  5  # you may not use this file except in compliance with the License.  
  6  # You may obtain a copy of the License at  
  7  #  
  8  #     http://www.apache.org/licenses/LICENSE-2.0  
  9  #  
 10  # Unless required by applicable law or agreed to in writing, software  
 11  # distributed under the License is distributed on an "AS IS" BASIS,  
 12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
 13  # See the License for the specific language governing permissions and  
 14  # limitations under the License. 
 15   
 16  try: 
 17      from cStringIO import StringIO 
 18  except ImportError: 
 19      from StringIO import StringIO 
 20   
 21  from muntjac.ui.abstract_component \ 
 22      import AbstractComponent 
 23   
 24  from muntjac.terminal.download_stream \ 
 25      import DownloadStream 
 26   
 27  from muntjac.terminal.application_resource \ 
 28      import IApplicationResource 
 29   
 30  from muntjac.addon.google_maps.overlay.polygon \ 
 31      import Polygon 
 32   
 33  from muntjac.addon.google_maps.overlay.basic_marker_source \ 
 34      import BasicMarkerSource 
35 36 37 -class MapControl(object):
38 39 SmallMapControl = 'SmallMapControl' 40 HierarchicalMapTypeControl = 'HierarchicalMapTypeControl' 41 LargeMapControl = 'LargeMapControl' 42 MapTypeControl = 'MapTypeControl' 43 MenuMapTypeControl = 'MenuMapTypeControl' 44 OverviewMapControl = 'OverviewMapControl' 45 ScaleControl = 'ScaleControl' 46 SmallZoomControl = 'SmallZoomControl'
47
48 49 -class GoogleMap(AbstractComponent):
50 """Server side component for the VGoogleMap widget.""" 51 52 CLIENT_WIDGET = None # ClientWidget(VGoogleMap) 53 54 TYPE_MAPPING = 'org.vaadin.hezamu.googlemapwidget.GoogleMap' 55
56 - def __init__(self, application, apiKey_or_center=None, zoom=None, 57 apiKey=None):
58 """Construct a new instance of the map with given parameters. 59 60 @param application: 61 L{Application} owning this instance. 62 @param apiKey_or_center: 63 the API key to be used for Google Maps or the center of 64 the map as a 2-tuple 65 @param zoom: 66 initial zoom level of the map 67 @param apiKey: 68 the API key to be used for Google Maps 69 """ 70 super(GoogleMap, self).__init__() 71 72 self._center = None 73 74 self._boundsNE = None 75 76 self._boundsSW = None 77 78 self._zoom = None 79 80 self._moveListeners = list() 81 82 self._mapClickListeners = list() 83 84 self._markerListeners = list() 85 86 self._markerMovedListeners = list() 87 88 self._markerSource = None 89 90 self._clickedMarker = None 91 92 self._closeInfoWindow = False 93 94 self._overlays = dict() 95 96 self._scrollWheelZoomEnabled = True 97 98 self._clearMapTypes = False 99 100 self._controls = list() 101 102 self._mapTypes = list() 103 104 self._mapTypesChanged = False 105 106 self._reportMapBounds = False 107 108 self._markerResource = \ 109 MarkerApplicationResource(self, self._markerSource) 110 111 self._apiKey = '' 112 self._clientLogLevel = 0 113 114 application.addResource(self._markerResource) 115 116 if apiKey_or_center is None: 117 # Greewich Royal Observatory 118 self._center = (-0.001475, 51.477811) 119 self._zoom = 14 120 elif zoom is None: 121 self._apiKey = apiKey_or_center 122 # Greewich Royal Observatory 123 self._center = (-0.001475, 51.477811) 124 self._zoom = 14 125 elif apiKey is None: 126 self._center = apiKey_or_center 127 self._zoom = zoom 128 else: 129 self._apiKey = apiKey 130 self._center = apiKey_or_center 131 self._zoom = zoom
132 133
134 - def paintContent(self, target):
135 super(GoogleMap, self).paintContent(target) 136 target.addVariable(self, 'center_lat', self._center[1]) 137 target.addVariable(self, 'center_lng', self._center[0]) 138 target.addVariable(self, 'zoom', self._zoom) 139 target.addVariable(self, 'swze', self._scrollWheelZoomEnabled) 140 target.addAttribute('apikey', self._apiKey) 141 target.addAttribute('loglevel', self._clientLogLevel) 142 143 for control in self._controls: 144 target.addAttribute(control, True) 145 146 # TODO: this feels like a kludge, but unsure how to implement correctly 147 if self._clickedMarker is not None: 148 target.addAttribute('marker', str(self._clickedMarker.getId())) 149 target.startTag('tabs') 150 tabs = self._clickedMarker.getInfoWindowContent() 151 for i in range(len(tabs)): 152 target.startTag('tab') 153 if len(tabs) > 1: 154 target.addAttribute('selected', tabs[i].isSelected()) 155 target.addAttribute('label', tabs[i].getLabel()) 156 157 tabs[i].getContent().paint(target) 158 159 target.endTag('tab') 160 161 target.endTag('tabs') 162 163 self._clickedMarker = None 164 elif self._markerSource is not None: 165 target.addAttribute('markerRes', self._markerResource) 166 167 if self._closeInfoWindow: 168 target.addAttribute('closeInfoWindow', True) 169 self._closeInfoWindow = False 170 171 target.startTag('overlays') 172 173 for poly in self._overlays.values(): 174 target.startTag('o') 175 target.addAttribute('id', poly.getId()) 176 177 # Encode polyline points as a string attribute 178 sb = StringIO() 179 points = poly.getPoints() 180 for i in range(len(points)): 181 if i > 0: 182 sb.write(' ') 183 sb.write(str(points[i][1]) + ',' + str(points[i][0])) 184 185 target.addAttribute('points', sb.getvalue()) 186 sb.close() 187 188 target.addAttribute('color', poly.getColor()) 189 target.addAttribute('weight', poly.getWeight()) 190 target.addAttribute('opacity', poly.getOpacity()) 191 target.addAttribute('clickable', poly.isClickable()) 192 193 if isinstance(poly, Polygon): 194 polygon = poly 195 target.addAttribute('fillcolor', polygon.getFillColor()) 196 target.addAttribute('fillopacity', polygon.getFillOpacity()) 197 198 target.endTag('o') 199 200 target.endTag('overlays') 201 202 if self._clearMapTypes: 203 target.addAttribute('clearMapTypes', True) 204 self._clearMapTypes = False 205 206 if self._mapTypesChanged: 207 target.startTag('mapTypes') 208 209 for mapType in self._mapTypes: 210 mapType.paintContent(target) 211 212 target.endTag('mapTypes') 213 214 self._mapTypesChanged = False 215 216 if self._reportMapBounds: 217 target.addAttribute('reportBounds', True) 218 self._reportMapBounds = False
219 220
221 - def changeVariables(self, source, variables):
222 """Receive and handle events and other variable changes from the 223 client. 224 """ 225 super(GoogleMap, self).changeVariables(source, variables) 226 227 if 'click_pos' in variables: 228 self.fireClickEvent(variables.get('click_pos')) 229 self.requestRepaint() 230 231 moveEvent = False 232 intVar = variables.get('zoom') 233 if intVar is not None: 234 self._zoom = intVar 235 moveEvent = True 236 237 stringVar = variables.get('center') 238 if stringVar is not None and not (stringVar.strip() == ''): 239 self._center = GoogleMap.strToLL(stringVar) 240 moveEvent = True 241 242 stringVar = variables.get('bounds_ne') 243 if stringVar is not None and not (stringVar.strip() == ''): 244 self._boundsNE = GoogleMap.strToLL(stringVar) 245 moveEvent = True 246 247 stringVar = variables.get('bounds_sw') 248 if stringVar is not None and not (stringVar.strip() == ''): 249 self._boundsSW = GoogleMap.strToLL(stringVar) 250 moveEvent = True 251 252 if moveEvent: 253 self.fireMoveEvent() 254 255 if 'marker' in variables: 256 self._clickedMarker = self._markerSource.getMarker( 257 str(variables['marker'])) 258 if self._clickedMarker is not None: 259 self.fireMarkerClickedEvent(self._clickedMarker) 260 if self._clickedMarker.getInfoWindowContent() is not None: 261 self.requestRepaint() 262 263 if 'markerMovedId' in variables: 264 markerID = str(variables['markerMovedId']).replace('\"', '') 265 markers = self._markerSource.getMarkers() 266 267 for mark in markers: 268 if mark.getId() == int(markerID): 269 270 lat = float(variables['markerMovedLat']) 271 lng = float(variables['markerMovedLong']) 272 mark.setLatLng((lng, lat)) 273 274 self.fireMarkerMovedEvent(mark) 275 break
276 277
278 - def fireMoveEvent(self):
279 for listener in self._moveListeners: 280 listener.mapMoved(self._zoom, self._center, self._boundsNE, 281 self._boundsSW)
282 283
284 - def fireClickEvent(self, obj):
285 clickPos = GoogleMap.strToLL(str(obj)) 286 for listener in self._mapClickListeners: 287 listener.mapClicked(clickPos)
288 289
290 - def fireMarkerClickedEvent(self, clickedMarker):
291 for m in self._markerListeners: 292 m.markerClicked(clickedMarker)
293 294
295 - def fireMarkerMovedEvent(self, movedMarker):
296 for m in self._markerMovedListeners: 297 m.markerMoved(movedMarker)
298 299 300
301 - def addListener(self, listener, iface=None):
302 """Register a new map listener. 303 304 @param listener: 305 new L{IMapClickListener}, L{IMapMoveListener}, 306 L{IMarkerMovedListener} or L{IMarkerClickListener} 307 to register 308 309 NOTE!! The marker that is clicked MUST have some information window 310 content! This is due to the implementation of the Widget, as the marker 311 click events do not propagate if there is not a information window 312 opened. 313 """ 314 if (isinstance(listener, IMapClickListener) and 315 (iface is None or issubclass(iface, IMapClickListener))): 316 if listener not in self._mapClickListeners.contains(): 317 self._mapClickListeners.append(listener) 318 elif (isinstance(listener, IMapMoveListener) and 319 (iface is None or issubclass(iface, IMapMoveListener))): 320 if listener not in self._moveListeners: 321 self._moveListeners.append(listener) 322 elif (isinstance(listener, IMarkerClickListener) and 323 (iface is None or issubclass(iface, IMarkerClickListener))): 324 if listener not in self._markerListeners: 325 self._markerListeners.append(listener) 326 elif (isinstance(listener, IMarkerMovedListener) and 327 (iface is None or issubclass(iface, IMarkerMovedListener))): 328 if listener not in self._markerMovedListeners: 329 self._markerMovedListeners.append(listener) 330 331 super(GoogleMap, self).addListener(listener, iface)
332 333 334 # TODO: add callback support 335 336
337 - def removeListener(self, listener, iface=None):
338 """Remove a map listener. 339 340 @param listener: 341 L{IMapClickListener}, L{IMapMoveListener}, 342 L{IMarkerMovedListener} or L{IMarkerClickListener} 343 to remove 344 """ 345 if (isinstance(listener, IMapClickListener) and 346 (iface is None or issubclass(iface, IMapClickListener))): 347 if listener in self._mapClickListeners: 348 self._mapClickListeners.remove(listener) 349 elif (isinstance(listener, IMapMoveListener) and 350 (iface is None or issubclass(iface, IMapMoveListener))): 351 if listener in self._moveListeners: 352 self._moveListeners.remove(listener) 353 elif (isinstance(listener, IMarkerClickListener) and 354 (iface is None or issubclass(iface, IMarkerClickListener))): 355 if listener in self._markerListeners: 356 self._markerListeners.remove(listener) 357 elif (isinstance(listener, IMarkerMovedListener) and 358 (iface is None or issubclass(iface, IMarkerMovedListener))): 359 if listener in self._markerMovedListeners: 360 self._markerMovedListeners.remove(listener) 361 362 super(GoogleMap, self).removeListener(listener, iface)
363 364
365 - def getCenter(self):
366 """Get current center coordinates of the map. 367 """ 368 return self._center
369 370
371 - def setCenter(self, center):
372 """Set the current center coordinates of the map. This method can be 373 used to pan the map programmatically. 374 375 @param center: 376 the new center coordinates 377 """ 378 self._center = center 379 self.requestRepaint()
380 381
382 - def getZoom(self):
383 """Get the current zoom level of the map. 384 385 @return: the current zoom level 386 """ 387 return self._zoom
388 389
390 - def setZoom(self, zoom):
391 """Set the zoom level of the map. This method can be used to zoom the 392 map programmatically. 393 """ 394 self._zoom = zoom 395 self.requestRepaint()
396 397
398 - def setClientLogLevel(self, level):
399 """Set the level of verbosity the client side uses for tracing or 400 displaying error messages. 401 """ 402 self._clientLogLevel = level 403 self.requestRepaint()
404 405
406 - def getClientLogLevel(self):
407 """Get the level of verbosity the client side uses for tracing or 408 displaying error messages. 409 """ 410 return self._clientLogLevel
411 412
413 - def getBoundsNE(self):
414 """Get the coordinates of the north-east corner of the map. 415 """ 416 return self._boundsNE
417 418
419 - def getBoundsSW(self):
420 """Get the coordinates of the south-west corner of the map. 421 """ 422 return self._boundsSW
423 424
425 - def setMarkerSource(self, markerSource):
426 """Set the L{MarkerSource} for the map. 427 """ 428 self._markerSource = markerSource 429 self._markerResource._markerSource = markerSource
430 431
432 - def closeInfoWindow(self):
433 """Close the currently open info window, if any.""" 434 self._closeInfoWindow = True 435 self.requestRepaint()
436 437
438 - def addPolyOverlay(self, overlay):
439 """Add a new {@link PolyOverlay} to the map. Does nothing if the 440 overlay already exist on the map. 441 442 @param overlay: 443 L{PolyOverlay} to add 444 445 @return: True if the overlay was added. 446 """ 447 if not (overlay.getId() in self._overlays): 448 self._overlays[overlay.getId()] = overlay 449 self.requestRepaint() 450 return True 451 return False
452 453
454 - def updateOverlay(self, overlay):
455 """Update a L{PolyOverlay} on the map. Does nothing if the overlay 456 does not exist on the map. 457 458 @param overlay: 459 L{PolyOverlay} to update 460 461 @return: True if the overlay was updated. 462 """ 463 if overlay.getId() in self._overlays: 464 self._overlays[overlay.getId()] = overlay 465 self.requestRepaint() 466 return True 467 return False
468 469
470 - def removeOverlay(self, overlay):
471 """Remove a L{PolyOverlay} from the map. Does nothing if the overlay 472 does not exist on the map. 473 474 @param overlay: 475 L{PolyOverlay} to remove 476 477 @return: True if the overlay was removed. 478 """ 479 if overlay.getId() in self._overlays: 480 del self._overlays[overlay.getId()] 481 self.requestRepaint() 482 return True 483 return False
484 485
486 - def getOverlays(self):
487 """Get the collection of L{PolyOverlay}s currently in the map. 488 489 @return: a list of overlays. 490 """ 491 return self._overlays.values()
492 493 494 @classmethod
495 - def strToLL(cls, latLngStr):
496 if latLngStr is None: 497 return None 498 499 nums = latLngStr.split(', ') 500 if len(nums) != 2: 501 return None 502 503 lat = float(nums[0][1:]) 504 lng = float(nums[1][:len(nums[1]) - 1]) 505 return (lng, lat)
506 507
508 - def addMarker(self, marker):
509 """Add a Marker to the current MarkerSource. If the map has no marker 510 source a new L{BasicMarkerSource} is created. 511 512 @param marker: 513 Marker to add 514 """ 515 if self._markerSource is None: 516 self.setMarkerSource(BasicMarkerSource()) 517 self._markerSource.addMarker(marker) 518 self.requestRepaint()
519 520
521 - def removeMarker(self, marker):
522 """Removes the marker from the map 523 """ 524 if self._markerSource is not None: 525 if marker in self._markerSource.getMarkers(): 526 self._markerSource.getMarkers().remove(marker) 527 self.requestRepaint()
528 529
530 - def removeAllMarkers(self):
531 if self._markerSource is not None: 532 del self._markerSource.getMarkers()[:] 533 self.requestRepaint()
534 535
536 - def setScrollWheelZoomEnabled(self, isEnabled):
537 self._scrollWheelZoomEnabled = isEnabled
538 539
540 - def isScrollWheelZoomEnabled(self):
541 return self._scrollWheelZoomEnabled
542 543
544 - def addControl(self, control):
545 if control not in self._controls: 546 self._controls.append(control) 547 return True 548 return False
549 550
551 - def hasControl(self, control):
552 return control in self._controls
553 554
555 - def removeControl(self, control):
556 if control in self._controls: 557 self._controls.remove(control) 558 return True 559 return False
560 561
562 - def addMapType(self, name, minZoom, maxZoom, copyright_, tileUrl, isPng, 563 opacity):
564 self._mapTypes.append(CustomMapType(name, minZoom, maxZoom, 565 copyright_, tileUrl, isPng, opacity)) 566 self._mapTypesChanged = True 567 self.requestRepaint()
568 569
570 - def clearMapTypes(self):
571 self._mapTypes.clear() 572 self._clearMapTypes = True 573 self.requestRepaint()
574 575
576 - def reportMapBounds(self):
577 self._reportMapBounds = True 578 self.requestRepaint()
579
580 581 -class MarkerApplicationResource(IApplicationResource):
582
583 - def __init__(self, gmap, markerSource):
584 self._gmap = gmap 585 self._markerSource = markerSource
586
587 - def getApplication(self):
588 return self._gmap.getApplication()
589
590 - def getBufferSize(self):
591 return len(self._markerSource.getMarkerJSON())
592
593 - def getCacheTime(self):
594 return -1
595
596 - def getFilename(self):
597 return "markersource.txt"
598
599 - def getStream(self):
600 return DownloadStream(StringIO(self._markerSource.getMarkerJSON()), 601 self.getMIMEType(), self.getFilename())
602
603 - def getMIMEType(self):
604 return "text/plain"
605
606 607 608 -class IMapMoveListener(object):
609 """Interface for listening map move and zoom events. 610 611 @author: Henri Muurimaa 612 @author: Richard Lincoln 613 """ 614
615 - def mapMoved(self, newZoomLevel, newCenter, boundsNE, boundsSW):
616 """Handle a MapMoveEvent. 617 618 @param newZoomLevel: 619 New zoom level 620 @param newCenter: 621 New center coordinates 622 @param boundsNE: 623 Coordinates of the north-east corner of the map 624 @param boundsSW: 625 Coordinates of the south-west corner of the map 626 """ 627 raise NotImplementedError
628
629 630 -class IMapClickListener(object):
631 """Interface for listening map click events. 632 633 @author Henri Muurimaa 634 """ 635
636 - def mapClicked(self, clickPos):
637 """Handle a MapClickEvent. 638 639 @param clickPos: 640 coordinates of the click event. 641 """ 642 raise NotImplementedError
643
644 645 -class IMarkerClickListener(object):
646 """Interface for listening marker click events. 647 """ 648
649 - def markerClicked(self, clickedMarker):
650 """Handle a MarkerClickEvent. 651 652 @param clickedMarker: 653 the marker that was clicked. 654 """ 655 raise NotImplementedError
656
657 658 -class IMarkerMovedListener(object):
659 """Interface for listening marker move events. 660 """ 661
662 - def markerMoved(self, movedMarker):
663 """Handle a MarkerMovedEvent. 664 665 @param movedMarker: 666 the marker that was moved. 667 """ 668 raise NotImplementedError
669
670 671 -class CustomMapType(object):
672
673 - def __init__(self, name, minZoom, maxZoom, copyright_, tileUrl, isPng, 674 opacity):
675 self._name = name 676 self._minZoom = minZoom 677 self._maxZoom = maxZoom 678 self._copyright = copyright_ 679 self._tileUrl = tileUrl 680 self._isPng = isPng 681 self._opacity = opacity
682 683
684 - def paintContent(self, target):
685 target.startTag('maptype') 686 target.addAttribute('name', self._name) 687 target.addAttribute('minZoom', self._minZoom) 688 target.addAttribute('maxZoom', self._maxZoom) 689 target.addAttribute('copyright', self._copyright) 690 target.addAttribute('tileUrl', self._tileUrl) 691 target.addAttribute('isPng', self._isPng) 692 target.addAttribute('opacity', self._opacity) 693 target.endTag('maptype')
694 695
696 - def getOpacity(self):
697 return self._opacity
698 699
700 - def getTileUrl(self):
701 return self._tileUrl
702 703
704 - def isPng(self):
705 return self._isPng
706 707
708 - def getMinZoom(self):
709 return self._minZoom
710 711
712 - def getMaxZoom(self):
713 return self._maxZoom
714 715
716 - def getCopyright(self):
717 return self._copyright
718