From ae7d0ec0f0affa0d30fcbbd0d2edd7ad81cf6c01 Mon Sep 17 00:00:00 2001 From: Thierry Parmentelat Date: Fri, 5 Apr 2013 10:14:41 +0200 Subject: [PATCH] pour googlemap in the mix fix prelude so we can refer to full url's in {js,css}_files --- plugins/googlemap/__init__.py | 0 plugins/googlemap/googlemap.css | 15 + plugins/googlemap/googlemap.html | 2 + plugins/googlemap/googlemap.js | 278 ++++ plugins/googlemap/googlemap.py | 34 + plugins/googlemap/markerclusterer.js | 1137 +++++++++++++++++ plugins/googlemap/markerclusterer_compiled.js | 1 + plugins/googlemap/markerclusterer_packed.js | 1 + trash/dashboard.py | 2 +- trash/sliceview.py | 16 +- unfold/plugin.py | 2 +- unfold/prelude.py | 21 +- unfold/templates/prelude.html | 4 +- 13 files changed, 1500 insertions(+), 13 deletions(-) create mode 100644 plugins/googlemap/__init__.py create mode 100644 plugins/googlemap/googlemap.css create mode 100644 plugins/googlemap/googlemap.html create mode 100644 plugins/googlemap/googlemap.js create mode 100644 plugins/googlemap/googlemap.py create mode 100644 plugins/googlemap/markerclusterer.js create mode 100644 plugins/googlemap/markerclusterer_compiled.js create mode 100644 plugins/googlemap/markerclusterer_packed.js diff --git a/plugins/googlemap/__init__.py b/plugins/googlemap/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/plugins/googlemap/googlemap.css b/plugins/googlemap/googlemap.css new file mode 100644 index 00000000..6a828547 --- /dev/null +++ b/plugins/googlemap/googlemap.css @@ -0,0 +1,15 @@ +#map-container { + padding: 6px; + border-width: 1px; + border-style: solid; + border-color: #ccc #ccc #999 #ccc; + -webkit-box-shadow: rgba(64, 64, 64, 0.5) 0 2px 5px; + -moz-box-shadow: rgba(64, 64, 64, 0.5) 0 2px 5px; + box-shadow: rgba(64, 64, 64, 0.1) 0 2px 5px; + /*width: 800px;*/ +} + +#map { + /*width: 800px;*/ + height: 500px; +} diff --git a/plugins/googlemap/googlemap.html b/plugins/googlemap/googlemap.html new file mode 100644 index 00000000..d0bb5d67 --- /dev/null +++ b/plugins/googlemap/googlemap.html @@ -0,0 +1,2 @@ +{# keep the original skeleton but this probably won't support several instances in the same page #} +
diff --git a/plugins/googlemap/googlemap.js b/plugins/googlemap/googlemap.js new file mode 100644 index 00000000..2ab99daf --- /dev/null +++ b/plugins/googlemap/googlemap.js @@ -0,0 +1,278 @@ +/** + * MySlice GoogleMap plugin + * URL: http://trac.myslice.info + * Description: display a query result in a googlemap + * Author: The MySlice Team + * Copyright (c) 2012 UPMC Sorbonne Universite - INRIA + * License: GPLv3 + */ + + +(function( jQuery ){ + + var methods = { + init : function( options ) { + + return this.each(function(){ + + var $this = jQuery(this), + data = $this.data('GoogleMap'), GoogleMap = jQuery('
', {text : $this.attr('title')}); + + // If the plugin hasn't been initialized yet + if ( ! data ) { + + /* Plugin initialization */ + + //google.load('maps', '3', { other_params: 'sensor=false' }); + //google.setOnLoadCallback(initialize); + + $this.data('map', null); + $this.data('markerCluster', null); + $this.data('markers', []); + + var myLatlng = new google.maps.LatLng(34.397, 150.644); + var myOptions = { + zoom: 2, + center: myLatlng, + mapTypeId: google.maps.MapTypeId.ROADMAP + } + + var map = new google.maps.Map(document.getElementById("map"), myOptions); + $this.data('map', map); + + /* End of plugin initialization */ + + jQuery(this).data('GoogleMap', { + plugin_uuid: options.plugin_uuid, + query_uuid: options.query_uuid, + target : $this, + current_resources: Array(), + GoogleMap : GoogleMap + }); + + /* Subscribe to query updates */ + jQuery.subscribe('/results/' + options.query_uuid + '/changed', {instance: $this}, update_map); + jQuery.subscribe('/update-set/' + options.query_uuid, {instance: $this}, on_resource_changed); + jQuery.subscribe('/query/' + options.query_uuid + '/changed', {instance: $this}, query_changed); + + //data = jQuery(this).data(); + + // TODO: Change the status of a node based on the actions in GoogleMap plugin or in other plugins (e.g. DataTables) + // Can we publish a value in results row['sliver'] ??? + // Today, the value is attached or undefined + // But can we think about a added/removed status ??? + // This plugin would update the map based on the results published + + } + }); + }, + destroy : function( ) { + + return this.each(function(){ + var $this = jQuery(this), data = $this.data('GoogleMap'); + jQuery(window).unbind('GoogleMap'); + data.GoogleMap.remove(); + $this.removeData('GoogleMap'); + }) + + }, +/* + reposition : function( ) { // ... }, + update : function( ) { // ... }, + hide : function( ) { // ... }, +*/ + show : function( content ) { + google.maps.event.trigger(map, 'resize'); + } + }; + + jQuery.fn.GoogleMap = function( method ) { + /* Method calling logic */ + if ( methods[method] ) { + return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 )); + } else if ( typeof method === 'object' || ! method ) { + return methods.init.apply( this, arguments ); + } else { + jQuery.error( 'Method ' + method + ' does not exist on jQuery.GoogleMap' ); + } + + }; + + /* Private methods */ + function query_changed(e,query){ + var data = e.data.instance.data(); + /* Compare current and advertised query to get added and removed fields */ + previous_query=data.current_query; + /* Save the query as the current query */ + data.current_query=query; + + var rows=[]; + if(typeof(data.results)!="undefined" && data.results.length>0){ + jQuery.each(data.results, function(i, row){ + jQuery.each(query.filter, function (idx, filter){ + if(get_value(row[filter[0]])==filter[2]){ + rows.push(row); + } + }); + }); + data.markerCluster=[]; + data.markers=[]; + var myLatlng = new google.maps.LatLng(34.397, 150.644); + var myOptions = { + zoom: 2, + center: myLatlng, + mapTypeId: google.maps.MapTypeId.ROADMAP + } + map = new google.maps.Map(jQuery('#map')[0],myOptions); + data.map=map; + //map.clearMarkers(); + update_map(e, rows); + } + } + function update_map(e, rows) { + var data = e.data.instance.data(); + var instance_ = e.data.instance; + //$plugindiv.closest('.need-spin').spin(false); + instance_.closest('.need-spin').spin(false); + + + if (!rows) { + alert('error'); + return; + } + + if(rows.length==0) { + rows=data.results; + } + + if(typeof(data.results)=="undefined" || data.results==null){ + data.results=rows; + } + var map = data.map; + var markerCluster = data.markerCluster; + var markers = data.markers; + var coords = new Array(); + var infowindow = new google.maps.InfoWindow(); + /* + if(typeof(markers)!="undefined" && markers.length>0){ + map.clearMarkers(); + }*/ + + data.current_resources = Array(); + + jQuery.each(data.results, function(i, result){ + // get the coordinates + var latitude=get_value(result['latitude']); + var longitude=get_value(result['longitude']); + var hash = latitude + longitude; + + // check to see if we've seen this hash before + if(coords[hash] == null) { + // get coordinate object + var myLatlng = new google.maps.LatLng(latitude, longitude); + // store an indicator that we've seen this point before + coords[hash] = 1; + } else { + // add some randomness to this point 1500 = 100 meters, 15000 = 10 meters + var lat = latitude + (Math.random() -.5) / 1500; + var lng = longitude + (Math.random() -.5) / 1500; + + // get the coordinate object + var myLatlng = new google.maps.LatLng(lat, lng); + } + // If the node is attached to the slice, action will be Remove; else action will be add to slice + if (typeof(result['sliver']) != 'undefined') { + data.current_resources.push(result['urn']); + action="del"; + action_class="ui-icon-circle-minus"; + action_message="Remove from slice"; + }else{ + action="add"; + action_class="ui-icon-circle-plus"; + action_message="Add to slice"; + } + // XXX not working + if (!(result['latitude'])) { + return true; + } + + //jQuery(".map-button").click(button_click); + if(jQuery.inArray(result,rows)>-1){ + var marker = new google.maps.Marker({ + position: myLatlng, + title: get_value(result['ip']), + // This should be done by the rendering + content: '

Agent: ' + get_value(result['ip']) + ' (' + get_value(result['urn']) + ')
Platform: ' + get_value(result['platform'])+'

' + + '
'+ + ''+action_message+ + '
' + }); + + google.maps.event.addListener(marker, 'click', function() { + infowindow.content = this.content; + infowindow.open(map, this); + // onload of the infowindow on the map, bind a click on a button + google.maps.event.addListener(infowindow, 'domready', function() { + jQuery('.map-button').unbind('click'); + jQuery(".map-button").click({instance: instance_}, button_click); + }); + }); + markers.push(marker); + } + }); + markerCluster = new MarkerClusterer(map, markers, {zoomOnClick: false}); + google.maps.event.addListener(markerCluster, "clusterclick", function (cluster) { + var markers = cluster.getMarkers(); + var bounds = new google.maps.LatLngBounds(); + /* + * date: 24/05/2012 + * author: lbaron + * Firefox JS Error - replaced $.each by JQuery.each + */ + jQuery.each(markers, function(i, marker){ + bounds.extend(marker.getPosition()); + }); + //map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds)); + map.fitBounds(bounds); + }); + data.markerCluster=markerCluster; + } + function button_click(e){ + var data = e.data.instance.data().GoogleMap; + var op_value=this.id.split("/"); + if(op_value.length>0){ + jQuery.publish('selected', op_value[0]+'/'+op_value[1]); + var value = op_value[1]; + + if (op_value[0] == 'add') { + data.current_resources.push(value); + } else { + tmp = jQuery.grep(data.current_resources, function(x) { return x != value; }); + data.current_resources = tmp; + } + + /* inform slice that our selected resources have changed */ + jQuery.publish('/update-set/' + data.query_uuid, [data.current_resources, true, e.data.instance]); + } + } + + function on_resource_changed(e, resources, instance) + { + /* TODO OPENLAB : this query determines which checkboxes must be checked */ + if (instance == e.data.instance) + return; + data = e.data.instance.data().GoogleMap; + + previous_resources = data.current_resources; + data.current_resources = resources; + + /* TODO We uncheck all checkboxes ... */ + //jQuery('datatables-checkbox-' + data.options.plugin_uuid).attr('checked', false); + /* ... and check the ones specified in the resource list */ + //jQuery.each(data.current_resources, function(index, urn) { + // jQuery('#datatables-checkbox-' + data.options.plugin_uuid + "-" + urn).attr('checked', true) + //}); + + } + +})( jQuery ); diff --git a/plugins/googlemap/googlemap.py b/plugins/googlemap/googlemap.py new file mode 100644 index 00000000..ac2c7982 --- /dev/null +++ b/plugins/googlemap/googlemap.py @@ -0,0 +1,34 @@ +from unfold.plugin import Plugin + +class GoogleMap (Plugin): + + # set checkboxes if a final column with checkboxes is desired + # pass columns as the initial set of columns + # if None then this is taken from the query's fields + def __init__ (self, query, **settings): + Plugin.__init__ (self, **settings) + self.query=query + + def template_file (self): + return "googlemap.html" + + def template_env (self, request): + env={} + return env + + def requirements (self): + reqs = { + 'js_files' : [ "https://maps.googleapis.com/maps/api/js?sensor=false", + "/js/googlemap.js", + "/js/markerclusterer.js", + "js/manifold.js", "js/manifold-query.js", + "js/spin.presets.js", "js/spin.min.js", "js/jquery.spin.js", + "js/unfold-helper.js", + ], + 'css_files' : [ "css/googlemap.css", + ], + } + return reqs + + # the list of things passed to the js plugin + def json_settings_list (self): return ['plugin_uuid','query_uuid'] diff --git a/plugins/googlemap/markerclusterer.js b/plugins/googlemap/markerclusterer.js new file mode 100644 index 00000000..8ac6d8fe --- /dev/null +++ b/plugins/googlemap/markerclusterer.js @@ -0,0 +1,1137 @@ +// ==ClosureCompiler== +// @compilation_level ADVANCED_OPTIMIZATIONS +// @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3.js +// ==/ClosureCompiler== + +/** + * @name MarkerClusterer for Google Maps v3 + * @version version 1.0 + * @author Luke Mahe + * @fileoverview + * The library creates and manages per-zoom-level clusters for large amounts of + * markers. + *
+ * This is a v3 implementation of the + * v2 MarkerClusterer. + */ + +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +/** + * A Marker Clusterer that clusters markers. + * + * @param {google.maps.Map} map The Google map to attach to. + * @param {Array.} opt_markers Optional markers to add to + * the cluster. + * @param {Object} opt_options support the following options: + * 'gridSize': (number) The grid size of a cluster in pixels. + * 'maxZoom': (number) The maximum zoom level that a marker can be part of a + * cluster. + * 'zoomOnClick': (boolean) Whether the default behaviour of clicking on a + * cluster is to zoom into it. + * 'averageCenter': (boolean) Wether the center of each cluster should be + * the average of all markers in the cluster. + * 'styles': (object) An object that has style properties: + * 'url': (string) The image url. + * 'height': (number) The image height. + * 'width': (number) The image width. + * 'anchor': (Array) The anchor position of the label text. + * 'textColor': (string) The text color. + * 'textSize': (number) The text size. + * @constructor + * @extends google.maps.OverlayView + */ +function MarkerClusterer(map, opt_markers, opt_options) { + // MarkerClusterer implements google.maps.OverlayView interface. We use the + // extend function to extend MarkerClusterer with google.maps.OverlayView + // because it might not always be available when the code is defined so we + // look for it at the last possible moment. If it doesn't exist now then + // there is no point going ahead :) + this.extend(MarkerClusterer, google.maps.OverlayView); + this.map_ = map; + + /** + * @type {Array.} + * @private + */ + this.markers_ = []; + + /** + * @type {Array.} + */ + this.clusters_ = []; + + this.sizes = [53, 56, 66, 78, 90]; + + /** + * @private + */ + this.styles_ = []; + + /** + * @type {boolean} + * @private + */ + this.ready_ = false; + + var options = opt_options || {}; + + /** + * @type {number} + * @private + */ + this.gridSize_ = options['gridSize'] || 60; + + /** + * @type {?number} + * @private + */ + this.maxZoom_ = options['maxZoom'] || null; + + this.styles_ = options['styles'] || []; + + /** + * @type {string} + * @private + */ + this.imagePath_ = options['imagePath'] || + this.MARKER_CLUSTER_IMAGE_PATH_; + + /** + * @type {string} + * @private + */ + this.imageExtension_ = options['imageExtension'] || + this.MARKER_CLUSTER_IMAGE_EXTENSION_; + + /** + * @type {boolean} + * @private + */ + this.zoomOnClick_ = true; + + if (options['zoomOnClick'] != undefined) { + this.zoomOnClick_ = options['zoomOnClick']; + } + + /** + * @type {boolean} + * @private + */ + this.averageCenter_ = false; + + if (options['averageCenter'] != undefined) { + this.averageCenter_ = options['averageCenter']; + } + + this.setupStyles_(); + + this.setMap(map); + + /** + * @type {number} + * @private + */ + this.prevZoom_ = this.map_.getZoom(); + + // Add the map event listeners + var that = this; + google.maps.event.addListener(this.map_, 'zoom_changed', function() { + var maxZoom = that.map_.mapTypes[that.map_.getMapTypeId()].maxZoom; + var zoom = that.map_.getZoom(); + if (zoom < 0 || zoom > maxZoom) { + return; + } + + if (that.prevZoom_ != zoom) { + that.prevZoom_ = that.map_.getZoom(); + that.resetViewport(); + } + }); + + google.maps.event.addListener(this.map_, 'idle', function() { + that.redraw(); + }); + + // Finally, add the markers + if (opt_markers && opt_markers.length) { + this.addMarkers(opt_markers, false); + } +} + + +/** + * The marker cluster image path. + * + * @type {string} + * @private + */ +MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_ = + 'http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/' + + 'images/m'; + + +/** + * The marker cluster image path. + * + * @type {string} + * @private + */ +MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png'; + + +/** + * Extends a objects prototype by anothers. + * + * @param {Object} obj1 The object to be extended. + * @param {Object} obj2 The object to extend with. + * @return {Object} The new extended object. + * @ignore + */ +MarkerClusterer.prototype.extend = function(obj1, obj2) { + return (function(object) { + for (property in object.prototype) { + this.prototype[property] = object.prototype[property]; + } + return this; + }).apply(obj1, [obj2]); +}; + + +/** + * Implementaion of the interface method. + * @ignore + */ +MarkerClusterer.prototype.onAdd = function() { + this.setReady_(true); +}; + + +/** + * Implementation of the interface. + * @ignore + */ +MarkerClusterer.prototype.draw = function() {}; + + +/** + * Sets up the styles object. + * + * @private + */ +MarkerClusterer.prototype.setupStyles_ = function() { + if (this.styles_.length) { + return; + } + + for (var i = 0, size; size = this.sizes[i]; i++) { + this.styles_.push({ + url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_, + height: size, + width: size + }); + } +}; + + +/** + * Sets the styles. + * + * @param {Object} styles The style to set. + */ +MarkerClusterer.prototype.setStyles = function(styles) { + this.styles_ = styles; +}; + + +/** + * Gets the styles. + * + * @return {Object} The styles object. + */ +MarkerClusterer.prototype.getStyles = function() { + return this.styles_; +}; + + +/** + * Whether zoom on click is set. + * + * @return {boolean} True if zoomOnClick_ is set. + */ +MarkerClusterer.prototype.isZoomOnClick = function() { + return this.zoomOnClick_; +}; + +/** + * Whether average center is set. + * + * @return {boolean} True if averageCenter_ is set. + */ +MarkerClusterer.prototype.isAverageCenter = function() { + return this.averageCenter_; +}; + + +/** + * Returns the array of markers in the clusterer. + * + * @return {Array.} The markers. + */ +MarkerClusterer.prototype.getMarkers = function() { + return this.markers_; +}; + + +/** + * Returns the array of markers in the clusterer. + * + * @return {Array.} The number of markers. + */ +MarkerClusterer.prototype.getTotalMarkers = function() { + return this.markers_; +}; + + +/** + * Sets the max zoom for the clusterer. + * + * @param {number} maxZoom The max zoom level. + */ +MarkerClusterer.prototype.setMaxZoom = function(maxZoom) { + this.maxZoom_ = maxZoom; +}; + + +/** + * Gets the max zoom for the clusterer. + * + * @return {number} The max zoom level. + */ +MarkerClusterer.prototype.getMaxZoom = function() { + return this.maxZoom_ || this.map_.mapTypes[this.map_.getMapTypeId()].maxZoom; +}; + + +/** + * The function for calculating the cluster icon image. + * + * @param {Array.} markers The markers in the clusterer. + * @param {number} numStyles The number of styles available. + * @return {Object} A object properties: 'text' (string) and 'index' (number). + * @private + */ +MarkerClusterer.prototype.calculator_ = function(markers, numStyles) { + var index = 0; + var count = markers.length; + var dv = count; + while (dv !== 0) { + dv = parseInt(dv / 10, 10); + index++; + } + + index = Math.min(index, numStyles); + return { + text: count, + index: index + }; +}; + + +/** + * Set the calculator function. + * + * @param {function(Array, number)} calculator The function to set as the + * calculator. The function should return a object properties: + * 'text' (string) and 'index' (number). + * + */ +MarkerClusterer.prototype.setCalculator = function(calculator) { + this.calculator_ = calculator; +}; + + +/** + * Get the calculator function. + * + * @return {function(Array, number)} the calculator function. + */ +MarkerClusterer.prototype.getCalculator = function() { + return this.calculator_; +}; + + +/** + * Add an array of markers to the clusterer. + * + * @param {Array.} markers The markers to add. + * @param {boolean} opt_nodraw Whether to redraw the clusters. + */ +MarkerClusterer.prototype.addMarkers = function(markers, opt_nodraw) { + for (var i = 0, marker; marker = markers[i]; i++) { + this.pushMarkerTo_(marker); + } + if (!opt_nodraw) { + this.redraw(); + } +}; + + +/** + * Pushes a marker to the clusterer. + * + * @param {google.maps.Marker} marker The marker to add. + * @private + */ +MarkerClusterer.prototype.pushMarkerTo_ = function(marker) { + marker.setVisible(false); + marker.setMap(null); + marker.isAdded = false; + if (marker['draggable']) { + // If the marker is draggable add a listener so we update the clusters on + // the drag end. + var that = this; + google.maps.event.addListener(marker, 'dragend', function() { + marker.isAdded = false; + that.resetViewport(); + that.redraw(); + }); + } + this.markers_.push(marker); +}; + + +/** + * Adds a marker to the clusterer and redraws if needed. + * + * @param {google.maps.Marker} marker The marker to add. + * @param {boolean} opt_nodraw Whether to redraw the clusters. + */ +MarkerClusterer.prototype.addMarker = function(marker, opt_nodraw) { + this.pushMarkerTo_(marker); + if (!opt_nodraw) { + this.redraw(); + } +}; + + +/** + * Remove a marker from the cluster. + * + * @param {google.maps.Marker} marker The marker to remove. + * @return {boolean} True if the marker was removed. + */ +MarkerClusterer.prototype.removeMarker = function(marker) { + var index = -1; + if (this.markers_.indexOf) { + index = this.markers_.indexOf(marker); + } else { + for (var i = 0, m; m = this.markers_[i]; i++) { + if (m == marker) { + index = i; + continue; + } + } + } + + if (index == -1) { + // Marker is not in our list of markers. + return false; + } + + this.markers_.splice(index, 1); + marker.setVisible(false); + marker.setMap(null); + + this.resetViewport(); + this.redraw(); + return true; +}; + + +/** + * Sets the clusterer's ready state. + * + * @param {boolean} ready The state. + * @private + */ +MarkerClusterer.prototype.setReady_ = function(ready) { + if (!this.ready_) { + this.ready_ = ready; + this.createClusters_(); + } +}; + + +/** + * Returns the number of clusters in the clusterer. + * + * @return {number} The number of clusters. + */ +MarkerClusterer.prototype.getTotalClusters = function() { + return this.clusters_.length; +}; + + +/** + * Returns the google map that the clusterer is associated with. + * + * @return {google.maps.Map} The map. + */ +MarkerClusterer.prototype.getMap = function() { + return this.map_; +}; + + +/** + * Sets the google map that the clusterer is associated with. + * + * @param {google.maps.Map} map The map. + */ +MarkerClusterer.prototype.setMap = function(map) { + this.map_ = map; +}; + + +/** + * Returns the size of the grid. + * + * @return {number} The grid size. + */ +MarkerClusterer.prototype.getGridSize = function() { + return this.gridSize_; +}; + + +/** + * Returns the size of the grid. + * + * @param {number} size The grid size. + */ +MarkerClusterer.prototype.setGridSize = function(size) { + this.gridSize_ = size; +}; + + +/** + * Extends a bounds object by the grid size. + * + * @param {google.maps.LatLngBounds} bounds The bounds to extend. + * @return {google.maps.LatLngBounds} The extended bounds. + */ +MarkerClusterer.prototype.getExtendedBounds = function(bounds) { + var projection = this.getProjection(); + + // Turn the bounds into latlng. + var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), + bounds.getNorthEast().lng()); + var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), + bounds.getSouthWest().lng()); + + // Convert the points to pixels and the extend out by the grid size. + var trPix = projection.fromLatLngToDivPixel(tr); + trPix.x += this.gridSize_; + trPix.y -= this.gridSize_; + + var blPix = projection.fromLatLngToDivPixel(bl); + blPix.x -= this.gridSize_; + blPix.y += this.gridSize_; + + // Convert the pixel points back to LatLng + var ne = projection.fromDivPixelToLatLng(trPix); + var sw = projection.fromDivPixelToLatLng(blPix); + + // Extend the bounds to contain the new bounds. + bounds.extend(ne); + bounds.extend(sw); + + return bounds; +}; + + +/** + * Determins if a marker is contained in a bounds. + * + * @param {google.maps.Marker} marker The marker to check. + * @param {google.maps.LatLngBounds} bounds The bounds to check against. + * @return {boolean} True if the marker is in the bounds. + * @private + */ +MarkerClusterer.prototype.isMarkerInBounds_ = function(marker, bounds) { + return bounds.contains(marker.getPosition()); +}; + + +/** + * Clears all clusters and markers from the clusterer. + */ +MarkerClusterer.prototype.clearMarkers = function() { + this.resetViewport(); + + // Set the markers a empty array. + this.markers_ = []; +}; + + +/** + * Clears all existing clusters and recreates them. + */ +MarkerClusterer.prototype.resetViewport = function() { + // Remove all the clusters + for (var i = 0, cluster; cluster = this.clusters_[i]; i++) { + cluster.remove(); + } + + // Reset the markers to not be added and to be invisible. + for (var i = 0, marker; marker = this.markers_[i]; i++) { + marker.isAdded = false; + marker.setMap(null); + marker.setVisible(false); + } + + this.clusters_ = []; +}; + + +/** + * Redraws the clusters. + */ +MarkerClusterer.prototype.redraw = function() { + this.createClusters_(); +}; + + +/** + * Creates the clusters. + * + * @private + */ +MarkerClusterer.prototype.createClusters_ = function() { + if (!this.ready_) { + return; + } + + // Get our current map view bounds. + // Create a new bounds object so we don't affect the map. + var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(), + this.map_.getBounds().getNorthEast()); + var bounds = this.getExtendedBounds(mapBounds); + + for (var i = 0, marker; marker = this.markers_[i]; i++) { + var added = false; + if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) { + for (var j = 0, cluster; cluster = this.clusters_[j]; j++) { + if (!added && cluster.getCenter() && + cluster.isMarkerInClusterBounds(marker)) { + added = true; + cluster.addMarker(marker); + break; + } + } + + if (!added) { + // Create a new cluster. + var cluster = new Cluster(this); + cluster.addMarker(marker); + this.clusters_.push(cluster); + } + } + } +}; + + +/** + * A cluster that contains markers. + * + * @param {MarkerClusterer} markerClusterer The markerclusterer that this + * cluster is associated with. + * @constructor + * @ignore + */ +function Cluster(markerClusterer) { + this.markerClusterer_ = markerClusterer; + this.map_ = markerClusterer.getMap(); + this.gridSize_ = markerClusterer.getGridSize(); + this.averageCenter_ = markerClusterer.isAverageCenter(); + this.center_ = null; + this.markers_ = []; + this.bounds_ = null; + this.clusterIcon_ = new ClusterIcon(this, markerClusterer.getStyles(), + markerClusterer.getGridSize()); +} + +/** + * Determins if a marker is already added to the cluster. + * + * @param {google.maps.Marker} marker The marker to check. + * @return {boolean} True if the marker is already added. + */ +Cluster.prototype.isMarkerAlreadyAdded = function(marker) { + if (this.markers_.indexOf) { + return this.markers_.indexOf(marker) != -1; + } else { + for (var i = 0, m; m = this.markers_[i]; i++) { + if (m == marker) { + return true; + } + } + } + return false; +}; + + +/** + * Add a marker the cluster. + * + * @param {google.maps.Marker} marker The marker to add. + * @return {boolean} True if the marker was added. + */ +Cluster.prototype.addMarker = function(marker) { + if (this.isMarkerAlreadyAdded(marker)) { + return false; + } + + if (!this.center_) { + this.center_ = marker.getPosition(); + this.calculateBounds_(); + } else { + if (this.averageCenter_) { + var lat = (this.center_.lat() + marker.getPosition().lat()) / 2; + var lng = (this.center_.lng() + marker.getPosition().lng()) / 2; + this.center_ = new google.maps.LatLng(lat, lng); + this.calculateBounds_(); + } + } + + + if (this.markers_.length == 0) { + // Only 1 marker in this cluster so show the marker. + marker.setMap(this.map_); + marker.setVisible(true); + } else if (this.markers_.length == 1) { + // Hide the 1 marker that was showing. + this.markers_[0].setMap(null); + this.markers_[0].setVisible(false); + } + + marker.isAdded = true; + this.markers_.push(marker); + + this.updateIcon(); + return true; +}; + + +/** + * Returns the marker clusterer that the cluster is associated with. + * + * @return {MarkerClusterer} The associated marker clusterer. + */ +Cluster.prototype.getMarkerClusterer = function() { + return this.markerClusterer_; +}; + + +/** + * Returns the bounds of the cluster. + * + * @return {google.maps.LatLngBounds} the cluster bounds. + */ +Cluster.prototype.getBounds = function() { + this.calculateBounds_(); + return this.bounds_; +}; + + +/** + * Removes the cluster + */ +Cluster.prototype.remove = function() { + this.clusterIcon_.remove(); + this.markers_.length = 0; + delete this.markers_; +}; + + +/** + * Returns the center of the cluster. + * + * @return {number} The cluster center. + */ +Cluster.prototype.getSize = function() { + return this.markers_.length; +}; + + +/** + * Returns the center of the cluster. + * + * @return {Array.} The cluster center. + */ +Cluster.prototype.getMarkers = function() { + return this.markers_; +}; + + +/** + * Returns the center of the cluster. + * + * @return {google.maps.LatLng} The cluster center. + */ +Cluster.prototype.getCenter = function() { + return this.center_; +}; + + +/** + * Calculated the bounds of the cluster with the grid. + * + * @private + */ +Cluster.prototype.calculateBounds_ = function() { + var bounds = new google.maps.LatLngBounds(this.center_, this.center_); + this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); +}; + + +/** + * Determines if a marker lies in the clusters bounds. + * + * @param {google.maps.Marker} marker The marker to check. + * @return {boolean} True if the marker lies in the bounds. + */ +Cluster.prototype.isMarkerInClusterBounds = function(marker) { + return this.bounds_.contains(marker.getPosition()); +}; + + +/** + * Returns the map that the cluster is associated with. + * + * @return {google.maps.Map} The map. + */ +Cluster.prototype.getMap = function() { + return this.map_; +}; + + +/** + * Updates the cluster icon + */ +Cluster.prototype.updateIcon = function() { + var zoom = this.map_.getZoom(); + var mz = this.markerClusterer_.getMaxZoom(); + + if (zoom > mz) { + // The zoom is greater than our max zoom so show all the markers in cluster. + for (var i = 0, marker; marker = this.markers_[i]; i++) { + marker.setMap(this.map_); + marker.setVisible(true); + } + return; + } + + if (this.markers_.length < 2) { + // We have 0 or 1 markers so hide the icon. + this.clusterIcon_.hide(); + return; + } + + var numStyles = this.markerClusterer_.getStyles().length; + var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); + this.clusterIcon_.setCenter(this.center_); + this.clusterIcon_.setSums(sums); + this.clusterIcon_.show(); +}; + + +/** + * A cluster icon + * + * @param {Cluster} cluster The cluster to be associated with. + * @param {Object} styles An object that has style properties: + * 'url': (string) The image url. + * 'height': (number) The image height. + * 'width': (number) The image width. + * 'anchor': (Array) The anchor position of the label text. + * 'textColor': (string) The text color. + * 'textSize': (number) The text size. + * @param {number} opt_padding Optional padding to apply to the cluster icon. + * @constructor + * @extends google.maps.OverlayView + * @ignore + */ +function ClusterIcon(cluster, styles, opt_padding) { + cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); + + this.styles_ = styles; + this.padding_ = opt_padding || 0; + this.cluster_ = cluster; + this.center_ = null; + this.map_ = cluster.getMap(); + this.div_ = null; + this.sums_ = null; + this.visible_ = false; + + this.setMap(this.map_); +} + + +/** + * Triggers the clusterclick event and zoom's if the option is set. + */ +ClusterIcon.prototype.triggerClusterClick = function() { + var markerClusterer = this.cluster_.getMarkerClusterer(); + + // Trigger the clusterclick event. + google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_); + + if (markerClusterer.isZoomOnClick()) { + // Center the map on this cluster. + this.map_.panTo(this.cluster_.getCenter()); + + // Zoom into the cluster. + this.map_.fitBounds(this.cluster_.getBounds()); + } +}; + + +/** + * Adding the cluster icon to the dom. + * @ignore + */ +ClusterIcon.prototype.onAdd = function() { + this.div_ = document.createElement('DIV'); + if (this.visible_) { + var pos = this.getPosFromLatLng_(this.center_); + this.div_.style.cssText = this.createCss(pos); + this.div_.innerHTML = this.sums_.text; + } + + var panes = this.getPanes(); + panes.overlayImage.appendChild(this.div_); + + var that = this; + google.maps.event.addDomListener(this.div_, 'click', function() { + that.triggerClusterClick(); + }); +}; + + +/** + * Returns the position to place the div dending on the latlng. + * + * @param {google.maps.LatLng} latlng The position in latlng. + * @return {google.maps.Point} The position in pixels. + * @private + */ +ClusterIcon.prototype.getPosFromLatLng_ = function(latlng) { + var pos = this.getProjection().fromLatLngToDivPixel(latlng); + pos.x -= parseInt(this.width_ / 2, 10); + pos.y -= parseInt(this.height_ / 2, 10); + return pos; +}; + + +/** + * Draw the icon. + * @ignore + */ +ClusterIcon.prototype.draw = function() { + if (this.visible_) { + var pos = this.getPosFromLatLng_(this.center_); + this.div_.style.top = pos.y + 'px'; + this.div_.style.left = pos.x + 'px'; + } +}; + + +/** + * Hide the icon. + */ +ClusterIcon.prototype.hide = function() { + if (this.div_) { + this.div_.style.display = 'none'; + } + this.visible_ = false; +}; + + +/** + * Position and show the icon. + */ +ClusterIcon.prototype.show = function() { + if (this.div_) { + var pos = this.getPosFromLatLng_(this.center_); + this.div_.style.cssText = this.createCss(pos); + this.div_.style.display = ''; + } + this.visible_ = true; +}; + + +/** + * Remove the icon from the map + */ +ClusterIcon.prototype.remove = function() { + this.setMap(null); +}; + + +/** + * Implementation of the onRemove interface. + * @ignore + */ +ClusterIcon.prototype.onRemove = function() { + if (this.div_ && this.div_.parentNode) { + this.hide(); + this.div_.parentNode.removeChild(this.div_); + this.div_ = null; + } +}; + + +/** + * Set the sums of the icon. + * + * @param {Object} sums The sums containing: + * 'text': (string) The text to display in the icon. + * 'index': (number) The style index of the icon. + */ +ClusterIcon.prototype.setSums = function(sums) { + this.sums_ = sums; + this.text_ = sums.text; + this.index_ = sums.index; + if (this.div_) { + this.div_.innerHTML = sums.text; + } + + this.useStyle(); +}; + + +/** + * Sets the icon to the the styles. + */ +ClusterIcon.prototype.useStyle = function() { + var index = Math.max(0, this.sums_.index - 1); + index = Math.min(this.styles_.length - 1, index); + var style = this.styles_[index]; + this.url_ = style['url']; + this.height_ = style['height']; + this.width_ = style['width']; + this.textColor_ = style['textColor']; + this.anchor = style['anchor']; + this.textSize_ = style['textSize']; +}; + + +/** + * Sets the center of the icon. + * + * @param {google.maps.LatLng} center The latlng to set as the center. + */ +ClusterIcon.prototype.setCenter = function(center) { + this.center_ = center; +}; + + +/** + * Create the css text based on the position of the icon. + * + * @param {google.maps.Point} pos The position. + * @return {string} The css style text. + */ +ClusterIcon.prototype.createCss = function(pos) { + var style = []; + if (document.all) { + style.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(' + + 'sizingMethod=scale,src="' + this.url_ + '");'); + } else { + style.push('background:url(' + this.url_ + ');'); + } + + if (typeof this.anchor_ === 'object') { + if (typeof this.anchor_[0] === 'number' && this.anchor_[0] > 0 && + this.anchor_[0] < this.height_) { + style.push('height:' + (this.height_ - this.anchor_[0]) + + 'px; padding-top:' + this.anchor_[0] + 'px;'); + } else { + style.push('height:' + this.height_ + 'px; line-height:' + this.height_ + + 'px;'); + } + if (typeof this.anchor_[1] === 'number' && this.anchor_[1] > 0 && + this.anchor_[1] < this.width_) { + style.push('width:' + (this.width_ - this.anchor_[1]) + + 'px; padding-left:' + this.anchor_[1] + 'px;'); + } else { + style.push('width:' + this.width_ + 'px; text-align:center;'); + } + } else { + style.push('height:' + this.height_ + 'px; line-height:' + + this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;'); + } + + var txtColor = this.textColor_ ? this.textColor_ : 'black'; + var txtSize = this.textSize_ ? this.textSize_ : 11; + + style.push('cursor:pointer; top:' + pos.y + 'px; left:' + + pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' + + txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold'); + return style.join(''); +}; + + +// Export Symbols for Closure +// If you are not going to compile with closure then you can remove the +// code below. +window['MarkerClusterer'] = MarkerClusterer; +MarkerClusterer.prototype['addMarker'] = MarkerClusterer.prototype.addMarker; +MarkerClusterer.prototype['addMarkers'] = MarkerClusterer.prototype.addMarkers; +MarkerClusterer.prototype['clearMarkers'] = + MarkerClusterer.prototype.clearMarkers; +MarkerClusterer.prototype['getCalculator'] = + MarkerClusterer.prototype.getCalculator; +MarkerClusterer.prototype['getGridSize'] = + MarkerClusterer.prototype.getGridSize; +MarkerClusterer.prototype['getMap'] = MarkerClusterer.prototype.getMap; +MarkerClusterer.prototype['getMarkers'] = MarkerClusterer.prototype.getMarkers; +MarkerClusterer.prototype['getMaxZoom'] = MarkerClusterer.prototype.getMaxZoom; +MarkerClusterer.prototype['getStyles'] = MarkerClusterer.prototype.getStyles; +MarkerClusterer.prototype['getTotalClusters'] = + MarkerClusterer.prototype.getTotalClusters; +MarkerClusterer.prototype['getTotalMarkers'] = + MarkerClusterer.prototype.getTotalMarkers; +MarkerClusterer.prototype['redraw'] = MarkerClusterer.prototype.redraw; +MarkerClusterer.prototype['removeMarker'] = + MarkerClusterer.prototype.removeMarker; +MarkerClusterer.prototype['resetViewport'] = + MarkerClusterer.prototype.resetViewport; +MarkerClusterer.prototype['setCalculator'] = + MarkerClusterer.prototype.setCalculator; +MarkerClusterer.prototype['setGridSize'] = + MarkerClusterer.prototype.setGridSize; +MarkerClusterer.prototype['onAdd'] = MarkerClusterer.prototype.onAdd; +MarkerClusterer.prototype['draw'] = MarkerClusterer.prototype.draw; + +Cluster.prototype['getCenter'] = Cluster.prototype.getCenter; +Cluster.prototype['getSize'] = Cluster.prototype.getSize; +Cluster.prototype['getMarkers'] = Cluster.prototype.getMarkers; + +ClusterIcon.prototype['onAdd'] = ClusterIcon.prototype.onAdd; +ClusterIcon.prototype['draw'] = ClusterIcon.prototype.draw; +ClusterIcon.prototype['onRemove'] = ClusterIcon.prototype.onRemove; diff --git a/plugins/googlemap/markerclusterer_compiled.js b/plugins/googlemap/markerclusterer_compiled.js new file mode 100644 index 00000000..ad74d1bb --- /dev/null +++ b/plugins/googlemap/markerclusterer_compiled.js @@ -0,0 +1 @@ +function d(a){return function(b){this[a]=b}}function e(a){return function(){return this[a]}}var h; function i(a,b,c){this.extend(i,google.maps.OverlayView);this.b=a;this.a=[];this.l=[];this.Y=[53,56,66,78,90];this.h=[];this.z=false;c=c||{};this.f=c.gridSize||60;this.U=c.maxZoom||null;this.h=c.styles||[];this.T=c.imagePath||this.M;this.S=c.imageExtension||this.L;this.K=true;if(c.zoomOnClick!=undefined)this.K=c.zoomOnClick;this.p=false;if(c.averageCenter!=undefined)this.p=c.averageCenter;j(this);this.setMap(a);this.G=this.b.getZoom();var f=this;google.maps.event.addListener(this.b,"zoom_changed", function(){var g=f.b.mapTypes[f.b.getMapTypeId()].maxZoom,l=f.b.getZoom();if(!(l<0||l>g))if(f.G!=l){f.G=f.b.getZoom();f.m()}});google.maps.event.addListener(this.b,"idle",function(){f.j()});b&&b.length&&this.B(b,false)}h=i.prototype;h.M="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m";h.L="png";h.extend=function(a,b){return function(c){for(property in c.prototype)this.prototype[property]=c.prototype[property];return this}.apply(a,[b])}; h.onAdd=function(){if(!this.z){this.z=true;m(this)}};h.R=function(){};h.draw=function(){};function j(a){if(!a.h.length)for(var b=0,c;c=a.Y[b];b++)a.h.push({url:a.T+(b+1)+"."+a.S,height:c,width:c})}h.w=e("h");h.v=e("a");h.Q=e("a");h.F=function(){return this.U||this.b.mapTypes[this.b.getMapTypeId()].maxZoom};h.C=function(a,b){for(var c=0,f=a.length,g=f;g!==0;){g=parseInt(g/10,10);c++}c=Math.min(c,b);return{text:f,index:c}};h.W=d("C");h.D=e("C"); h.B=function(a,b){for(var c=0,f;f=a[c];c++)n(this,f);b||this.j()};function n(a,b){b.setVisible(false);b.setMap(null);b.r=false;b.draggable&&google.maps.event.addListener(b,"dragend",function(){b.r=false;a.m();a.j()});a.a.push(b)}h.o=function(a,b){n(this,a);b||this.j()};h.V=function(a){var b=-1;if(this.a.indexOf)b=this.a.indexOf(a);else for(var c=0,f;f=this.a[c];c++)if(f==a)b=c;if(b==-1)return false;this.a.splice(b,1);a.setVisible(false);a.setMap(null);this.m();this.j();return true};h.P=function(){return this.l.length}; h.getMap=e("b");h.setMap=d("b");h.u=e("f");h.X=d("f");function o(a,b){var c=a.getProjection(),f=new google.maps.LatLng(b.getNorthEast().lat(),b.getNorthEast().lng()),g=new google.maps.LatLng(b.getSouthWest().lat(),b.getSouthWest().lng());f=c.fromLatLngToDivPixel(f);f.x+=a.f;f.y-=a.f;g=c.fromLatLngToDivPixel(g);g.x-=a.f;g.y+=a.f;f=c.fromDivPixelToLatLng(f);c=c.fromDivPixelToLatLng(g);b.extend(f);b.extend(c);return b}h.N=function(){this.m();this.a=[]}; h.m=function(){for(var a=0,b;b=this.l[a];a++)b.remove();for(a=0;b=this.a[a];a++){b.r=false;b.setMap(null);b.setVisible(false)}this.l=[]};h.j=function(){m(this)};function m(a){if(a.z)for(var b=o(a,new google.maps.LatLngBounds(a.b.getBounds().getSouthWest(),a.b.getBounds().getNorthEast())),c=0,f;f=a.a[c];c++){var g=false;if(!f.r&&b.contains(f.getPosition())){for(var l=0,k;k=a.l[l];l++)if(!g&&k.getCenter()&&k.t.contains(f.getPosition())){g=true;k.o(f);break}if(!g){k=new p(a);k.o(f);a.l.push(k)}}}} function p(a){this.i=a;this.b=a.getMap();this.f=a.u();this.p=a.p;this.d=null;this.a=[];this.t=null;this.k=new q(this,a.w(),a.u())}h=p.prototype; h.o=function(a){var b;a:if(this.a.indexOf)b=this.a.indexOf(a)!=-1;else{b=0;for(var c;c=this.a[b];b++)if(c==a){b=true;break a}b=false}if(b)return false;if(this.d){if(this.p){b=(this.d.lat()+a.getPosition().lat())/2;c=(this.d.lng()+a.getPosition().lng())/2;this.d=new google.maps.LatLng(b,c);r(this)}}else{this.d=a.getPosition();r(this)}if(this.a.length==0){a.setMap(this.b);a.setVisible(true)}else if(this.a.length==1){this.a[0].setMap(null);this.a[0].setVisible(false)}a.r=true;this.a.push(a);if(this.b.getZoom()> this.i.F())for(a=0;b=this.a[a];a++){b.setMap(this.b);b.setVisible(true)}else if(this.a.length<2)s(this.k);else{a=this.i.w().length;b=this.i.D()(this.a,a);this.k.setCenter(this.d);a=this.k;a.A=b;a.aa=b.text;a.Z=b.index;if(a.c)a.c.innerHTML=b.text;b=Math.max(0,a.A.index-1);b=Math.min(a.h.length-1,b);b=a.h[b];a.J=b.url;a.g=b.height;a.n=b.width;a.H=b.textColor;a.anchor=b.anchor;a.I=b.textSize;this.k.show()}return true};h.getBounds=function(){r(this);return this.t}; h.remove=function(){this.k.remove();this.a.length=0;delete this.a};h.O=function(){return this.a.length};h.v=e("a");h.getCenter=e("d");function r(a){a.t=o(a.i,new google.maps.LatLngBounds(a.d,a.d))}h.getMap=e("b");function q(a,b,c){a.i.extend(q,google.maps.OverlayView);this.h=b;this.$=c||0;this.q=a;this.d=null;this.b=a.getMap();this.A=this.c=null;this.s=false;this.setMap(this.b)}h=q.prototype; h.onAdd=function(){this.c=document.createElement("DIV");if(this.s){this.c.style.cssText=t(this,u(this,this.d));this.c.innerHTML=this.A.text}this.getPanes().overlayImage.appendChild(this.c);var a=this;google.maps.event.addDomListener(this.c,"click",function(){var b=a.q.i;google.maps.event.trigger(b,"clusterclick",a.q);if(b.K){a.b.panTo(a.q.getCenter());a.b.fitBounds(a.q.getBounds())}})}; function u(a,b){var c=a.getProjection().fromLatLngToDivPixel(b);c.x-=parseInt(a.n/2,10);c.y-=parseInt(a.g/2,10);return c}h.draw=function(){if(this.s){var a=u(this,this.d);this.c.style.top=a.y+"px";this.c.style.left=a.x+"px"}};function s(a){if(a.c)a.c.style.display="none";a.s=false}h.show=function(){if(this.c){this.c.style.cssText=t(this,u(this,this.d));this.c.style.display=""}this.s=true};h.remove=function(){this.setMap(null)}; h.onRemove=function(){if(this.c&&this.c.parentNode){s(this);this.c.parentNode.removeChild(this.c);this.c=null}};h.setCenter=d("d"); function t(a,b){var c=[];document.all?c.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="'+a.J+'");'):c.push("background:url("+a.J+");");if(typeof a.e==="object"){typeof a.e[0]==="number"&&a.e[0]>0&&a.e[0]0&&a.e[1]35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('6 5(c,d,e){3.Z(5,u.v.23);3.l=c;3.k=[];3.N=[];3.24=[2H,2I,2J,2K,2L];3.F=[];3.1d=r;7 f=e||{};3.G=f[\'2M\']||2N;3.1s=f[\'1t\']||w;3.F=f[\'2O\']||[];3.25=f[\'2P\']||3.26;3.27=f[\'2Q\']||3.28;3.1u=C;8(f[\'29\']!=2a){3.1u=f[\'29\']}3.12=r;8(f[\'2b\']!=2a){3.12=f[\'2b\']}3.2c();3.D(c);3.1v=3.l.1e();7 g=3;u.v.13.1w(3.l,\'2R\',6(){7 a=g.l.2d[g.l.2e()].1t;7 b=g.l.1e();8(b<0||b>a){9}8(g.1v!=b){g.1v=g.l.1e();g.K()}});u.v.13.1w(3.l,\'1f\',6(){g.H()});8(d&&d.A){3.1g(d,r)}}5.4.26=\'2S://u-v-2T-2U-2V.2W.2X/2Y/2Z/30/\'+\'31/m\';5.4.28=\'32\';5.4.Z=6(b,c){9(6(a){E(1x 33 a.4){3.4[1x]=a.4[1x]}9 3}).34(b,[c])};5.4.O=6(){3.2f(C)};5.4.1f=6(){};5.4.P=6(){};5.4.2c=6(){8(3.F.A){9}E(7 i=0,14;14=3.24[i];i++){3.F.z({1y:3.25+(i+1)+\'.\'+3.27,L:14,15:14})}};5.4.35=6(a){3.F=a};5.4.16=6(){9 3.F};5.4.2g=6(){9 3.1u};5.4.2h=6(){9 3.12};5.4.Q=6(){9 3.k};5.4.1z=6(){9 3.k};5.4.36=6(a){3.1s=a};5.4.1h=6(){9 3.1s||3.l.2d[3.l.2e()].1t};5.4.1A=6(a,b){7 c=0;7 d=a.A;7 e=d;37(e!==0){e=1B(e/10,10);c++}c=1C.2i(c,b);9{R:d,1D:c}};5.4.1E=6(a){3.1A=a};5.4.1i=6(){9 3.1A};5.4.1g=6(a,b){E(7 i=0,q;q=a[i];i++){3.1F(q)}8(!b){3.H()}};5.4.1F=6(a){a.S(r);a.D(w);a.17=r;8(a[\'38\']){7 b=3;u.v.13.1w(a,\'39\',6(){a.17=r;b.K();b.H()})}3.k.z(a)};5.4.T=6(a,b){3.1F(a);8(!b){3.H()}};5.4.1G=6(a){7 b=-1;8(3.k.1j){b=3.k.1j(a)}I{E(7 i=0,m;m=3.k[i];i++){8(m==a){b=i;3a}}}8(b==-1){9 r}3.k.3b(b,1);a.S(r);a.D(w);3.K();3.H();9 C};5.4.2f=6(a){8(!3.1d){3.1d=a;3.1H()}};5.4.1I=6(){9 3.N.A};5.4.U=6(){9 3.l};5.4.D=6(a){3.l=a};5.4.18=6(){9 3.G};5.4.1J=6(a){3.G=a};5.4.1K=6(a){7 b=3.2j();7 c=M u.v.1L(a.1M().1k(),a.1M().1l());7 d=M u.v.1L(a.1N().1k(),a.1N().1l());7 e=b.1O(c);e.x+=3.G;e.y-=3.G;7 f=b.1O(d);f.x-=3.G;f.y+=3.G;7 g=b.2k(e);7 h=b.2k(f);a.Z(g);a.Z(h);9 a};5.4.2l=6(a,b){9 b.2m(a.19())};5.4.1P=6(){3.K();3.k=[]};5.4.K=6(){E(7 i=0,1Q;1Q=3.N[i];i++){1Q.1m()}E(7 i=0,q;q=3.k[i];i++){q.17=r;q.D(w);q.S(r)}3.N=[]};5.4.H=6(){3.1H()};5.4.1H=6(){8(!3.1d){9}7 a=M u.v.2n(3.l.1n().1N(),3.l.1n().1M());7 b=3.1K(a);E(7 i=0,q;q=3.k[i];i++){7 c=r;8(!q.17&&3.2l(q,b)){E(7 j=0,d;d=3.N[j];j++){8(!c&&d.1a()&&d.2o(q)){c=C;d.T(q);3c}}8(!c){7 d=M o(3);d.T(q);3.N.z(d)}}}};6 o(a){3.V=a;3.l=a.U();3.G=a.18();3.12=a.2h();3.s=w;3.k=[];3.1o=w;3.W=M n(3,a.16(),a.18())}o.4.2p=6(a){8(3.k.1j){9 3.k.1j(a)!=-1}I{E(7 i=0,m;m=3.k[i];i++){8(m==a){9 C}}}9 r};o.4.T=6(a){8(3.2p(a)){9 r}8(!3.s){3.s=a.19();3.1p()}I{8(3.12){7 b=(3.s.1k()+a.19().1k())/2;7 c=(3.s.1l()+a.19().1l())/2;3.s=M u.v.1L(b,c);3.1p()}}8(3.k.A==0){a.D(3.l);a.S(C)}I 8(3.k.A==1){3.k[0].D(w);3.k[0].S(r)}a.17=C;3.k.z(a);3.2q();9 C};o.4.1R=6(){9 3.V};o.4.1n=6(){3.1p();9 3.1o};o.4.1m=6(){3.W.1m();3.k.A=0;3d 3.k};o.4.1S=6(){9 3.k.A};o.4.Q=6(){9 3.k};o.4.1a=6(){9 3.s};o.4.1p=6(){7 a=M u.v.2n(3.s,3.s);3.1o=3.V.1K(a)};o.4.2o=6(a){9 3.1o.2m(a.19())};o.4.U=6(){9 3.l};o.4.2q=6(){7 a=3.l.1e();7 b=3.V.1h();8(a>b){E(7 i=0,q;q=3.k[i];i++){q.D(3.l);q.S(C)}9}8(3.k.A<2){3.W.1T();9}7 c=3.V.16().A;7 d=3.V.1i()(3.k,c);3.W.2r(3.s);3.W.2s(d);3.W.2t()};6 n(a,b,c){a.1R().Z(n,u.v.23);3.F=b;3.3e=c||0;3.1b=a;3.s=w;3.l=a.U();3.p=w;3.1q=w;3.1c=r;3.D(3.l)}n.4.2u=6(){7 a=3.1b.1R();u.v.13.3f(a,\'3g\',3.1b);8(a.2g()){3.l.3h(3.1b.1a());3.l.3i(3.1b.1n())}};n.4.O=6(){3.p=2v.3j(\'3k\');8(3.1c){7 a=3.1r(3.s);3.p.X.2w=3.1U(a);3.p.2x=3.1q.R}7 b=3.3l();b.3m.3n(3.p);7 c=3;u.v.13.3o(3.p,\'3p\',6(){c.2u()})};n.4.1r=6(a){7 b=3.2j().1O(a);b.x-=1B(3.Y/2,10);b.y-=1B(3.J/2,10);9 b};n.4.P=6(){8(3.1c){7 a=3.1r(3.s);3.p.X.1V=a.y+\'t\';3.p.X.1W=a.x+\'t\'}};n.4.1T=6(){8(3.p){3.p.X.2y=\'3q\'}3.1c=r};n.4.2t=6(){8(3.p){7 a=3.1r(3.s);3.p.X.2w=3.1U(a);3.p.X.2y=\'\'}3.1c=C};n.4.1m=6(){3.D(w)};n.4.1X=6(){8(3.p&&3.p.2z){3.1T();3.p.2z.3r(3.p);3.p=w}};n.4.2s=6(a){3.1q=a;3.3s=a.R;3.3t=a.1D;8(3.p){3.p.2x=a.R}3.2A()};n.4.2A=6(){7 a=1C.3u(0,3.1q.1D-1);a=1C.2i(3.F.A-1,a);7 b=3.F[a];3.1Y=b[\'1y\'];3.J=b[\'L\'];3.Y=b[\'15\'];3.1Z=b[\'3v\'];3.2B=b[\'2B\'];3.20=b[\'3w\']};n.4.2r=6(a){3.s=a};n.4.1U=6(a){7 b=[];8(2v.3x){b.z(\'3y:3z:3A.3B.3C(\'+\'3D=3E,3F="\'+3.1Y+\'");\')}I{b.z(\'3G:1y(\'+3.1Y+\');\')}8(21 3.B===\'3H\'){8(21 3.B[0]===\'2C\'&&3.B[0]>0&&3.B[0]<3.J){b.z(\'L:\'+(3.J-3.B[0])+\'t; 2D-1V:\'+3.B[0]+\'t;\')}I{b.z(\'L:\'+3.J+\'t; 2E-L:\'+3.J+\'t;\')}8(21 3.B[1]===\'2C\'&&3.B[1]>0&&3.B[1]<3.Y){b.z(\'15:\'+(3.Y-3.B[1])+\'t; 2D-1W:\'+3.B[1]+\'t;\')}I{b.z(\'15:\'+3.Y+\'t; R-2F:2G;\')}}I{b.z(\'L:\'+3.J+\'t; 2E-L:\'+3.J+\'t; 15:\'+3.Y+\'t; R-2F:2G;\')}7 c=3.1Z?3.1Z:\'3I\';7 d=3.20?3.20:11;b.z(\'3J:3K; 1V:\'+a.y+\'t; 1W:\'+a.x+\'t; 3L:\'+c+\'; 3M:3N; 22-14:\'+d+\'t; 22-3O:3P,3Q-3R; 22-3S:3T\');9 b.3U(\'\')};3V[\'5\']=5;5.4[\'T\']=5.4.T;5.4[\'1g\']=5.4.1g;5.4[\'1P\']=5.4.1P;5.4[\'1i\']=5.4.1i;5.4[\'18\']=5.4.18;5.4[\'U\']=5.4.U;5.4[\'Q\']=5.4.Q;5.4[\'1h\']=5.4.1h;5.4[\'16\']=5.4.16;5.4[\'1I\']=5.4.1I;5.4[\'1z\']=5.4.1z;5.4[\'H\']=5.4.H;5.4[\'1G\']=5.4.1G;5.4[\'K\']=5.4.K;5.4[\'1E\']=5.4.1E;5.4[\'1J\']=5.4.1J;5.4[\'O\']=5.4.O;5.4[\'P\']=5.4.P;5.4[\'1f\']=5.4.1f;o.4[\'1a\']=o.4.1a;o.4[\'1S\']=o.4.1S;o.4[\'Q\']=o.4.Q;n.4[\'O\']=n.4.O;n.4[\'P\']=n.4.P;n.4[\'1X\']=n.4.1X;',62,244,'|||this|prototype|MarkerClusterer|function|var|if|return|||||||||||markers_|map_||ClusterIcon|Cluster|div_|marker|false|center_|px|google|maps|null|||push|length|anchor_|true|setMap|for|styles_|gridSize_|redraw|else|height_|resetViewport|height|new|clusters_|onAdd|draw|getMarkers|text|setVisible|addMarker|getMap|markerClusterer_|clusterIcon_|style|width_|extend|||averageCenter_|event|size|width|getStyles|isAdded|getGridSize|getPosition|getCenter|cluster_|visible_|ready_|getZoom|idle|addMarkers|getMaxZoom|getCalculator|indexOf|lat|lng|remove|getBounds|bounds_|calculateBounds_|sums_|getPosFromLatLng_|maxZoom_|maxZoom|zoomOnClick_|prevZoom_|addListener|property|url|getTotalMarkers|calculator_|parseInt|Math|index|setCalculator|pushMarkerTo_|removeMarker|createClusters_|getTotalClusters|setGridSize|getExtendedBounds|LatLng|getNorthEast|getSouthWest|fromLatLngToDivPixel|clearMarkers|cluster|getMarkerClusterer|getSize|hide|createCss|top|left|onRemove|url_|textColor_|textSize_|typeof|font|OverlayView|sizes|imagePath_|MARKER_CLUSTER_IMAGE_PATH_|imageExtension_|MARKER_CLUSTER_IMAGE_EXTENSION_|zoomOnClick|undefined|averageCenter|setupStyles_|mapTypes|getMapTypeId|setReady_|isZoomOnClick|isAverageCenter|min|getProjection|fromDivPixelToLatLng|isMarkerInBounds_|contains|LatLngBounds|isMarkerInClusterBounds|isMarkerAlreadyAdded|updateIcon|setCenter|setSums|show|triggerClusterClick|document|cssText|innerHTML|display|parentNode|useStyle|anchor|number|padding|line|align|center|53|56|66|78|90|gridSize|60|styles|imagePath|imageExtension|zoom_changed|http|utility|library|v3|googlecode|com|svn|trunk|markerclusterer|images|png|in|apply|setStyles|setMaxZoom|while|draggable|dragend|continue|splice|break|delete|padding_|trigger|clusterclick|panTo|fitBounds|createElement|DIV|getPanes|overlayImage|appendChild|addDomListener|click|none|removeChild|text_|index_|max|textColor|textSize|all|filter|progid|DXImageTransform|Microsoft|AlphaImageLoader|sizingMethod|scale|src|background|object|black|cursor|pointer|color|position|absolute|family|Arial|sans|serif|weight|bold|join|window'.split('|'),0,{})) diff --git a/trash/dashboard.py b/trash/dashboard.py index f1ce6981..6f97a0a7 100644 --- a/trash/dashboard.py +++ b/trash/dashboard.py @@ -43,7 +43,7 @@ def dashboard_view (request): sons=[ QueryCode ( page=page, - title="Vizualize your query (no syntax highlight for now)", + title="Vizualize your query", query=slices_query, toggled=False, ), diff --git a/trash/sliceview.py b/trash/sliceview.py index 9139454e..f62208ba 100644 --- a/trash/sliceview.py +++ b/trash/sliceview.py @@ -11,6 +11,7 @@ from manifold.manifoldquery import ManifoldQuery from plugins.stack.stack import Stack from plugins.tabs.tabs import Tabs from plugins.hazelnut.hazelnut import Hazelnut +from plugins.googlemap.googlemap import GoogleMap from plugins.lists.slicelist import SliceList from plugins.querycode.querycode import QueryCode from plugins.quickfilter.quickfilter import QuickFilter @@ -43,12 +44,14 @@ def slice_view (request, slicename=tmp_default_slice): page=page, title="2 tabs : w/ and w/o checkboxes", domid='thetabs', - active_domid='checkboxes', + # active_domid='checkboxes', + active_domid='gmap', sons=[ Hazelnut ( page=page, title='a sample and simple hazelnut', domid='simple', + # tab's sons preferably turn this off togglable=False, # this is the query at the core of the slice list query=main_query, @@ -57,10 +60,11 @@ def slice_view (request, slicename=tmp_default_slice): page=page, title='with checkboxes', domid='checkboxes', + # tab's sons preferably turn this off togglable=False, - checkboxes=True, # this is the query at the core of the slice list query=main_query, + checkboxes=True, datatables_options = { # for now we turn off sorting on the checkboxes columns this way # this of course should be automatic in hazelnut @@ -69,6 +73,14 @@ def slice_view (request, slicename=tmp_default_slice): 'bLengthChange' : True, }, ), + GoogleMap ( + page=page, + title='geographic view', + domid='gmap', + # tab's sons preferably turn this off + togglable=False, + query=main_query, + ), ]), Hazelnut ( page=page, diff --git a/unfold/plugin.py b/unfold/plugin.py index 9189e716..b7ad65b8 100644 --- a/unfold/plugin.py +++ b/unfold/plugin.py @@ -17,7 +17,7 @@ from unfold.prelude import Prelude DEBUG= False #DEBUG= [ 'SimpleList' ] -DEBUG=True +#DEBUG=True # decorator to deflect calls on Plugin to its Prelude through self.page.prelude def to_prelude (method): diff --git a/unfold/prelude.py b/unfold/prelude.py index f576ed3d..8c90a19b 100644 --- a/unfold/prelude.py +++ b/unfold/prelude.py @@ -48,25 +48,32 @@ class Prelude: # env['js_chunks']= '\n'.join(self.js_chunks) # env['css_chunks']='\n'.join(self.css_chunks) # return env - # together with this in layout-unfold2.html + # together with this in prelude.html # {% for js_file in js_files %} {% insert_str prelude js_file %} {% endfor %} # {% for css_file in css_files %} {% insert_str prelude css_file %} {% endfor %} # somehow however this would not work too well, # probably insert_above is not powerful enough to handle that # - # so a much simpler and safer approach is for use to compute the html header directly + # so a much simpler and safer approach is for us to compute the html header directly + # this requires to filter on full urls + # + @staticmethod + def full_url (input): + if input.startswith("http://") or input.startswith("https://"): + return input + else: + from myslice.settings import STATIC_URL + return "%s%s"%(STATIC_URL,input) + def prelude_env (self): env={} - env['js_files']= self.js_files - env['css_files']= self.css_files + env['js_urls'] = [ Prelude.full_url (js_file) for js_file in self.js_files ] + env['css_urls'] = [ Prelude.full_url (css_file) for css_file in self.css_files ] env['js_chunks']= self.js_chunks env['css_chunks']=self.css_chunks if debug: print "prelude has %d js_files, %d css files, %d js chunks and %d css_chunks"%\ (len(self.js_files),len(self.css_files),len(self.js_chunks),len(self.css_chunks),) - # not sure how this should be done more cleanly - from myslice.settings import STATIC_URL - env ['STATIC_URL'] = STATIC_URL # render this with prelude.html and put the result in header_prelude header_prelude = render_to_string ('prelude.html',env) return { 'header_prelude' : header_prelude } diff --git a/unfold/templates/prelude.html b/unfold/templates/prelude.html index 37e0e597..898624ea 100644 --- a/unfold/templates/prelude.html +++ b/unfold/templates/prelude.html @@ -1,6 +1,6 @@ -{% for js_file in js_files %} +{% for js_url in js_urls %} {% endfor %} -{% for css_file in css_files %} +{% for css_url in css_urls %} {% endfor %} -- 2.43.0