diff --git a/django/contrib/gis/maps/google/__init__.py b/django/contrib/gis/maps/google/__init__.py index 2d168a2cf0..dfbbedc3d6 100644 --- a/django/contrib/gis/maps/google/__init__.py +++ b/django/contrib/gis/maps/google/__init__.py @@ -57,5 +57,5 @@ version. """ from django.contrib.gis.maps.google.gmap import GoogleMap -from django.contrib.gis.maps.google.overlays import GPolygon, GPolyline +from django.contrib.gis.maps.google.overlays import GEvent, GMarker, GPolygon, GPolyline from django.contrib.gis.maps.google.zoom import GoogleZoom diff --git a/django/contrib/gis/maps/google/gmap.py b/django/contrib/gis/maps/google/gmap.py index 09f2ec87e4..580ab7ad8e 100644 --- a/django/contrib/gis/maps/google/gmap.py +++ b/django/contrib/gis/maps/google/gmap.py @@ -4,7 +4,7 @@ from django.template.loader import render_to_string from django.utils.safestring import mark_safe class GoogleMapException(Exception): pass -from django.contrib.gis.maps.google.overlays import GPolygon, GPolyline +from django.contrib.gis.maps.google.overlays import GPolygon, GPolyline, GMarker # The default Google Maps URL (for the API javascript) # TODO: Internationalize for Japan, UK, etc. @@ -20,7 +20,7 @@ class GoogleMap(object): def __init__(self, key=None, api_url=None, version=None, center=None, zoom=None, dom_id='map', load_func='gmap_load', - kml_urls=[], polygons=[], polylines=[], + kml_urls=[], polygons=[], polylines=[], markers=[], template='gis/google/js/google-map.js', extra_context={}): @@ -55,8 +55,14 @@ class GoogleMap(object): self.template = template self.kml_urls = kml_urls - # Does the user want any GPolygon or GPolyline overlays? - self.polygons, self.polylines = [], [] + # Does the user want any GMarker, GPolygon, and/or GPolyline overlays? + self.polygons, self.polylines, self.markers = [], [], [] + if markers: + for point in markers: + if isinstance(point, GMarker): + self.markers.append(point) + else: + self.markers.append(GMarker(point)) if polygons: for poly in polygons: if isinstance(poly, GPolygon): @@ -70,12 +76,13 @@ class GoogleMap(object): else: self.polylines.append(GPolyline(pline)) - # If GPolygons and/or GPolylines are used the zoom will be automatically + # If GMarker, GPolygons, and/or GPolylines + # are used the zoom will be automatically # calculated via the Google Maps API. If both a zoom level and a # center coordinate are provided with polygons/polylines, no automatic # determination will occur. self.calc_zoom = False - if self.polygons or self.polylines: + if self.polygons or self.polylines or self.markers: if center is None or zoom is None: self.calc_zoom = True @@ -95,6 +102,7 @@ class GoogleMap(object): 'zoom' : self.zoom, 'polygons' : self.polygons, 'polylines' : self.polylines, + 'markers' : self.markers, } params.update(extra_context) self.js = render_to_string(self.template, params) diff --git a/django/contrib/gis/maps/google/overlays.py b/django/contrib/gis/maps/google/overlays.py index 921fe34f3a..0efedf3632 100644 --- a/django/contrib/gis/maps/google/overlays.py +++ b/django/contrib/gis/maps/google/overlays.py @@ -1,10 +1,66 @@ -from django.contrib.gis.geos import LineString, LinearRing, Polygon from django.utils.safestring import mark_safe +from django.contrib.gis.geos import fromstr, Point, LineString, LinearRing, Polygon + +class GEvent(object): + """ + A Python wrapper for the Google GEvent object. + + Events can be attached to any object derived from GOverlayBase with the + add_event() call. + + For more information please see the Google Maps API Reference: + http://code.google.com/apis/maps/documentation/reference.html#GEvent + + Example: + + from django.shortcuts import render_to_response + from django.contrib.gis.maps.google import GoogleMap, GEvent, GPolyline + + def sample_request(request): + polyline = GPolyline('LINESTRING(101 26, 112 26, 102 31)') + event = GEvent('click', + 'function() { location.href = "http://www.google.com"}') + polyline.add_event(event) + return render_to_response('mytemplate.html', + {'google' : GoogleMap(polylines=[polyline])}) + """ + + def __init__(self, event, action): + """ + Initializes a GEvent object. + + Parameters: + + event: + string for the event, such as 'click'. The event must be a valid + event for the object in the Google Maps API. + There is no validation of the event type within Django. + + action: + string containing a Javascript function, such as + 'function() { location.href = "newurl";}' + The string must be a valid Javascript function. Again there is no + validation fo the function within Django. + """ + self.event = event + self.action = action + + def __unicode__(self): + "Returns the parameter part of a GEvent." + return mark_safe('"%s", %s' %(self.event, self.action)) class GOverlayBase(object): + def __init__(self): + self.events = [] + def latlng_from_coords(self, coords): + "Generates a JavaScript array of GLatLng objects for the given coordinates." return '[%s]' % ','.join(['new GLatLng(%s,%s)' % (y, x) for x, y in coords]) + def add_event(self, event): + "Attaches a GEvent to the overlay object." + self.events.append(event) + def __unicode__(self): "The string representation is the JavaScript API call." return mark_safe('%s(%s)' % (self.__class__.__name__, self.js_params)) @@ -19,8 +75,9 @@ class GPolygon(GOverlayBase): stroke_color='#0000ff', stroke_weight=2, stroke_opacity=1, fill_color='#0000ff', fill_opacity=0.4): """ - The GPolygon object initializes on a GEOS Polygon. Please note that - this will not depict Polygons with internal rings. + The GPolygon object initializes on a GEOS Polygon or a parameter that + may be instantiated into GEOS Polygon. Please note that this will not + depict a Polygon's internal rings. Keyword Options: @@ -39,8 +96,8 @@ class GPolygon(GOverlayBase): fill_opacity: The opacity of the polygon fill. Defaults to 0.4. """ - - # TODO: Take other types of geometries. + if isinstance(poly, basestring): poly = fromstr(poly) + if isinstance(poly, (tuple, list)): poly = Polygon(poly) if not isinstance(poly, Polygon): raise TypeError('GPolygon may only initialize on GEOS Polygons.') @@ -57,7 +114,9 @@ class GPolygon(GOverlayBase): # Fill settings. self.fill_color, self.fill_opacity = fill_color, fill_opacity - + + super(GPolygon, self).__init__() + @property def js_params(self): return '%s, "%s", %s, %s, "%s", %s' % (self.points, self.stroke_color, self.stroke_weight, self.stroke_opacity, @@ -71,8 +130,9 @@ class GPolyline(GOverlayBase): """ def __init__(self, geom, color='#0000ff', weight=2, opacity=1): """ - The GPolyline object may initialize on GEOS LineStirng, LinearRing, - and Polygon objects (internal rings not supported). + The GPolyline object may be initialized on GEOS LineStirng, LinearRing, + and Polygon objects (internal rings not supported) or a parameter that + may instantiated into one of the above geometries. Keyword Options: @@ -85,6 +145,10 @@ class GPolyline(GOverlayBase): opacity: The opacity of the polyline, between 0 and 1. Defaults to 1. """ + # If a GEOS geometry isn't passed in, try to contsruct one. + if isinstance(geom, basestring): geom = fromstr(geom) + if isinstance(geom, (tuple, list)): geom = Polygon(geom) + # Generating the lat/lng coordinate pairs. if isinstance(geom, (LineString, LinearRing)): self.latlngs = self.latlng_from_coords(geom.coords) elif isinstance(geom, Polygon): @@ -95,7 +159,62 @@ class GPolyline(GOverlayBase): # Getting the envelope for automatic zoom determination. self.envelope = geom.envelope self.color, self.weight, self.opacity = color, weight, opacity + super(GPolyline, self).__init__() @property def js_params(self): return '%s, "%s", %s, %s' % (self.latlngs, self.color, self.weight, self.opacity) + +class GMarker(GOverlayBase): + """ + A Python wrapper for the Google GMarker object. For more information + please see the Google Maps API Reference: + http://code.google.com/apis/maps/documentation/reference.html#GMarker + + Example: + + from django.shortcuts import render_to_response + from django.contrib.gis.maps.google.overlays import GMarker, GEvent + + def sample_request(request): + marker = GMarker('POINT(101 26)') + event = GEvent('click', + 'function() { location.href = "http://www.google.com"}') + marker.add_event(event) + return render_to_response('mytemplate.html', + {'google' : GoogleMap(markers=[marker])}) + """ + def __init__(self, geom, title=None): + """ + The GMarker object may initialize on GEOS Points or a parameter + that may be instantiated into a GEOS point. Keyword options map to + GMarkerOptions -- so far only the title option is supported. + + Keyword Options: + title: + Title option for GMarker, will be displayed as a tooltip. + """ + # If a GEOS geometry isn't passed in, try to construct one. + if isinstance(geom, basestring): geom = fromstr(geom) + if isinstance(geom, (tuple, list)): geom = Point(geom) + if isinstance(geom, Point): + self.latlng = self.latlng_from_coords(geom.coords) + else: + raise TypeError('GMarker may only initialize on GEOS Point geometry.') + # Getting the envelope for automatic zoom determination. + self.envelope = geom.envelope + # TODO: Add support for more GMarkerOptions + self.title = title + super(GMarker, self).__init__() + + def latlng_from_coords(self, coords): + return 'new GLatLng(%s,%s)' %(coords[1], coords[0]) + + def options(self): + result = [] + if self.title: result.append('title: "%s"' % self.title) + return '{%s}' % ','.join(result) + + @property + def js_params(self): + return '%s, %s' % (self.latlng, self.options()) diff --git a/django/contrib/gis/templates/gis/google/js/google-map.js b/django/contrib/gis/templates/gis/google/js/google-map.js index e6af197d66..5efdf9045e 100644 --- a/django/contrib/gis/templates/gis/google/js/google-map.js +++ b/django/contrib/gis/templates/gis/google/js/google-map.js @@ -3,17 +3,28 @@ {% block load %}function {{ load_func }}(){ if (GBrowserIsCompatible()) { map = new GMap2(document.getElementById("{{ dom_id }}")); + map.setCenter(new GLatLng({{ center.1 }}, {{ center.0 }}), {{ zoom }}); {% block controls %}map.addControl(new GSmallMapControl()); map.addControl(new GMapTypeControl());{% endblock %} - {% if calc_zoom %}var bounds = new GLatLngBounds(); var tmp_bounds = new GLatLngBounds();{% else %}map.setCenter(new GLatLng({{ center.1 }}, {{ center.0 }}), {{ zoom }});{% endif %} + {% if calc_zoom %}var bounds = new GLatLngBounds(); var tmp_bounds = new GLatLngBounds();{% endif %} {% for kml_url in kml_urls %}var kml{{ forloop.counter }} = new GGeoXml("{{ kml_url }}"); map.addOverlay(kml{{ forloop.counter }});{% endfor %} + {% for polygon in polygons %}var poly{{ forloop.counter }} = new {{ polygon }}; - map.addOverlay(poly{{ forloop.counter }});{% if calc_zoom %} - tmp_bounds = poly{{ forloop.counter }}.getBounds(); bounds.extend(tmp_bounds.getSouthWest()); bounds.extend(tmp_bounds.getNorthEast());{% endif %}{% endfor %} + map.addOverlay(poly{{ forloop.counter }}); + {% for event in polygon.events %}GEvent.addListener(poly{{ forloop.parentloop.counter }}, {{ event }});{% endfor %} + {% if calc_zoom %}tmp_bounds = poly{{ forloop.counter }}.getBounds(); bounds.extend(tmp_bounds.getSouthWest()); bounds.extend(tmp_bounds.getNorthEast());{% endif %}{% endfor %} + {% for polyline in polylines %}var polyline{{ forloop.counter }} = new {{ polyline }}; - map.addOverlay(polyline{{ forloop.counter }});{% if calc_zoom %} - tmp_bounds = polyline{{ forloop.counter }}.getBounds(); bounds.extend(tmp_bounds.getSouthWest()); bounds.extend(tmp_bounds.getNorthEast());{% endif %}{% endfor %} + map.addOverlay(polyline{{ forloop.counter }}); + {% for event in polyline.events %}GEvent.addListener(polyline{{ forloop.parentloop.counter }}, {{ event }}); {% endfor %} + {% if calc_zoom %}tmp_bounds = polyline{{ forloop.counter }}.getBounds(); bounds.extend(tmp_bounds.getSouthWest()); bounds.extend(tmp_bounds.getNorthEast());{% endif %}{% endfor %} + + {% for marker in markers %}var marker{{ forloop.counter }} = new {{ marker }}; + map.addOverlay(marker{{ forloop.counter }}); + {% for event in marker.events %}GEvent.addListener(marker{{ forloop.parentloop.counter }}, {{ event }}); {% endfor %} + {% if calc_zoom %}bounds.extend(marker{{ forloop.counter }}.getLatLng()); {% endif %}{% endfor %} + {% if calc_zoom %}map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));{% endif %} {% block load_extra %}{% endblock %} }else {