Making WordPress.org

Changeset 1566


Ignore:
Timestamp:
05/12/2015 10:41:20 PM (10 years ago)
Author:
iandunn
Message:

Central Theme: Add the [wcc_map] shortcode to show a Google Map of WordCamps.

Location:
sites/trunk/wordcamp.org/public_html/wp-content/themes/wordcamp-central-2012
Files:
4 added
3 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordcamp.org/public_html/wp-content/themes/wordcamp-central-2012/functions.php

    r1545 r1566  
    4444        add_filter( 'wp_nav_menu_items', array( __CLASS__, 'add_rss_links_to_footer_menu' ), 10, 2 );
    4545
     46        add_shortcode( 'wcc_map',         array( __CLASS__, 'shortcode_map'         ) );
    4647        add_shortcode( 'wcc_about_stats', array( __CLASS__, 'shortcode_about_stats' ) );
    4748    }
     
    184185     */
    185186    static function enqueue_scripts() {
    186         wp_enqueue_style( 'central', get_stylesheet_uri(), array(), 5 );
    187         wp_enqueue_script( 'wordcamp-central', get_stylesheet_directory_uri() . '/js/central.js', array( 'jquery', 'underscore' ), 1, true );
    188 
    189         wp_localize_script( 'wordcamp-central', 'wordcampCentralOptions', array( 'ajaxURL' => admin_url( 'admin-ajax.php' ) ) );
     187        wp_enqueue_style( 'central', get_stylesheet_uri(), array(), 6 );
     188        wp_enqueue_script( 'wordcamp-central', get_stylesheet_directory_uri() . '/js/central.js', array( 'jquery', 'underscore' ), 2, true );
     189
     190        wp_localize_script( 'wordcamp-central', 'wordcampCentralOptions', self::get_javascript_options() );
    190191
    191192        /* We add some JavaScript to pages with the comment form
     
    200201        }
    201202
     203        if ( is_page( 'about' ) || is_page( 'schedule' ) ) {
     204            wp_enqueue_script( 'google-maps', 'https://maps.googleapis.com/maps/api/js', array(), false, true );
     205        }
     206    }
     207
     208    /**
     209     * Build the array of options to pass to the client side
     210     *
     211     * @return array
     212     */
     213    protected static function get_javascript_options() {
     214        global $post;
     215
     216        $options = array( 'ajaxURL' => admin_url( 'admin-ajax.php' ) );
     217
     218        if ( $map_id = self::get_map_id( $post->post_content ) ) {
     219            $options['mapContainer']            = "wcc-map-$map_id";
     220            $options['markerIconBaseURL']       = get_stylesheet_directory_uri() . '/images/';
     221            $options['markerClusterIcon']       = 'icon-marker-clustered.png';
     222            $options['markerIconAnchorXOffset'] = 24;
     223            $options['markerIconHeight']        = 94;
     224            $options['markerIconWidth']         = 122;
     225
     226            if ( $map_markers = self::get_map_markers( $map_id ) ) {
     227                $options['mapMarkers'] = $map_markers;
     228            }
     229        }
     230
     231        return $options;
     232    }
     233
     234    /**
     235     * Get the ID of the map called in the given page
     236     *
     237     * @param string $post_content
     238     *
     239     * @return mixed A string of the map name on success, or false on failure
     240     */
     241    protected static function get_map_id( $post_content ) {
     242        $map_id = false;
     243
     244        if ( has_shortcode( $post_content, 'wcc_map' ) ) {
     245            preg_match_all( '/' . get_shortcode_regex() . '/s', $post_content, $shortcodes, PREG_SET_ORDER );
     246
     247            foreach ( $shortcodes as $shortcode ) {
     248                if ( 'wcc_map' == $shortcode[2] ) {
     249                    $attributes = shortcode_parse_atts( $shortcode[3] );
     250                    $map_id     = sanitize_text_field( $attributes['id'] );
     251                    break;
     252                }
     253            }
     254        }
     255
     256        return $map_id;
     257    }
     258
     259    /**
     260     * Get the markers assigned to the given map
     261     *
     262     * @param string $map_id
     263     *
     264     * @return array
     265     */
     266    protected static function get_map_markers( $map_id ) {
     267        $transient_key = "wcc_map_markers_$map_id";
     268
     269        if ( $markers = get_transient( $transient_key ) ) {
     270            return $markers;
     271        } else {
     272            $markers = array();
     273        }
     274
     275        // Get the raw marker posts for the given map
     276        $parameters = array(
     277            'post_type'      => 'wordcamp',
     278            'posts_per_page' => -1,
     279        );
     280
     281        switch( $map_id ) {
     282            case 'schedule':
     283                $parameters['post_status'][] = array( 'publish', 'pending' );
     284                $parameters['meta_query'][] = array(
     285                    'key'     => 'Start Date (YYYY-mm-dd)',
     286                    'value'   => strtotime( '-2 days' ),
     287                    'compare' => '>',
     288                );
     289                break;
     290        }
     291
     292        $raw_markers = get_posts( $parameters );
     293
     294        // Convert the raw markers into prepared objects that are ready to be used on the JavaScript side
     295        foreach ( $raw_markers as $marker ) {
     296            if ( 'schedule' == $map_id ) {
     297                $marker_type = 'upcoming';
     298            } else {
     299                $marker_type = get_post_meta( $marker->ID, 'Start Date (YYYY-mm-dd)', true ) > strtotime( '-2 days' ) ? 'upcoming' : 'past';
     300            }
     301
     302            if ( ! $coordinates = get_post_meta( $marker->ID, '_venue_coordinates', true ) ) {
     303                continue;
     304            }
     305
     306            $markers[ $marker->ID ] = array(
     307                'id'          => $marker->ID,
     308                'name'        => wcpt_get_wordcamp_title( $marker->ID ),
     309                'dates'       => wcpt_get_wordcamp_start_date( $marker->ID ),
     310                'location'    => get_post_meta( $marker->ID, 'Location', true ),
     311                'venueName'   => get_post_meta( $marker->ID, 'Venue Name', true ),
     312                'url'         => self::get_best_wordcamp_url( $marker->ID ),
     313                'latitude'    => $coordinates['latitude'],
     314                'longitude'   => $coordinates['longitude'],
     315                'iconURL'     => "icon-marker-{$marker_type}-2x.png",
     316            );
     317        }
     318
     319        $markers = apply_filters( 'wcc_get_map_markers', $markers );
     320
     321        set_transient( $transient_key, $markers, WEEK_IN_SECONDS );
     322
     323        return $markers;
    202324    }
    203325
     
    649771
    650772    /**
     773     * Render the [wcc_map] shortcode
     774     *
     775     * @param array $attributes
     776     *
     777     * @return string
     778     */
     779    public static function shortcode_map( $attributes ) {
     780        $attributes = shortcode_atts( array( 'id' => '' ), $attributes );
     781
     782        ob_start();
     783        require( __DIR__ . '/shortcode-about-map.php' );
     784        return ob_get_clean();
     785    }
     786
     787    /**
    651788     * Render the [wcc_about_stats] shortcode
    652789     *
  • sites/trunk/wordcamp.org/public_html/wp-content/themes/wordcamp-central-2012/js/central.js

    r1405 r1566  
     1/**
     2 * @name MarkerClusterer for Google Maps v3
     3 * @version version 1.0
     4 * @author Luke Mahe
     5 *
     6 * The library creates and manages per-zoom-level clusters for large amounts of
     7 * markers.
     8 */
     9(function(){var d=null;function e(a){return function(b){this[a]=b}}function h(a){return function(){return this[a]}}var j;
     10    function k(a,b,c){this.extend(k,google.maps.OverlayView);this.c=a;this.a=[];this.f=[];this.ca=[53,56,66,78,90];this.j=[];this.A=!1;c=c||{};this.g=c.gridSize||60;this.l=c.minimumClusterSize||2;this.J=c.maxZoom||d;this.j=c.styles||[];this.X=c.imagePath||this.Q;this.W=c.imageExtension||this.P;this.O=!0;if(c.zoomOnClick!=void 0)this.O=c.zoomOnClick;this.r=!1;if(c.averageCenter!=void 0)this.r=c.averageCenter;l(this);this.setMap(a);this.K=this.c.getZoom();var f=this;google.maps.event.addListener(this.c,
     11        "zoom_changed",function(){var a=f.c.getZoom();if(f.K!=a)f.K=a,f.m()});google.maps.event.addListener(this.c,"idle",function(){f.i()});b&&b.length&&this.C(b,!1)}j=k.prototype;j.Q="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m";j.P="png";j.extend=function(a,b){return function(a){for(var b in a.prototype)this.prototype[b]=a.prototype[b];return this}.apply(a,[b])};j.onAdd=function(){if(!this.A)this.A=!0,n(this)};j.draw=function(){};
     12    function l(a){if(!a.j.length)for(var b=0,c;c=a.ca[b];b++)a.j.push({url:a.X+(b+1)+"."+a.W,height:c,width:c})}j.S=function(){for(var a=this.o(),b=new google.maps.LatLngBounds,c=0,f;f=a[c];c++)b.extend(f.getPosition());this.c.fitBounds(b)};j.z=h("j");j.o=h("a");j.V=function(){return this.a.length};j.ba=e("J");j.I=h("J");j.G=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}};j.$=e("G");j.H=h("G");
     13    j.C=function(a,b){for(var c=0,f;f=a[c];c++)q(this,f);b||this.i()};function q(a,b){b.s=!1;b.draggable&&google.maps.event.addListener(b,"dragend",function(){b.s=!1;a.L()});a.a.push(b)}j.q=function(a,b){q(this,a);b||this.i()};function r(a,b){var c=-1;if(a.a.indexOf)c=a.a.indexOf(b);else for(var f=0,g;g=a.a[f];f++)if(g==b){c=f;break}if(c==-1)return!1;b.setMap(d);a.a.splice(c,1);return!0}j.Y=function(a,b){var c=r(this,a);return!b&&c?(this.m(),this.i(),!0):!1};
     14    j.Z=function(a,b){for(var c=!1,f=0,g;g=a[f];f++)g=r(this,g),c=c||g;if(!b&&c)return this.m(),this.i(),!0};j.U=function(){return this.f.length};j.getMap=h("c");j.setMap=e("c");j.w=h("g");j.aa=e("g");
     15    j.v=function(a){var b=this.getProjection(),c=new google.maps.LatLng(a.getNorthEast().lat(),a.getNorthEast().lng()),f=new google.maps.LatLng(a.getSouthWest().lat(),a.getSouthWest().lng()),c=b.fromLatLngToDivPixel(c);c.x+=this.g;c.y-=this.g;f=b.fromLatLngToDivPixel(f);f.x-=this.g;f.y+=this.g;c=b.fromDivPixelToLatLng(c);b=b.fromDivPixelToLatLng(f);a.extend(c);a.extend(b);return a};j.R=function(){this.m(!0);this.a=[]};
     16    j.m=function(a){for(var b=0,c;c=this.f[b];b++)c.remove();for(b=0;c=this.a[b];b++)c.s=!1,a&&c.setMap(d);this.f=[]};j.L=function(){var a=this.f.slice();this.f.length=0;this.m();this.i();window.setTimeout(function(){for(var b=0,c;c=a[b];b++)c.remove()},0)};j.i=function(){n(this)};
     17    function n(a){if(a.A)for(var b=a.v(new google.maps.LatLngBounds(a.c.getBounds().getSouthWest(),a.c.getBounds().getNorthEast())),c=0,f;f=a.a[c];c++)if(!f.s&&b.contains(f.getPosition())){for(var g=a,u=4E4,o=d,v=0,m=void 0;m=g.f[v];v++){var i=m.getCenter();if(i){var p=f.getPosition();if(!i||!p)i=0;else var w=(p.lat()-i.lat())*Math.PI/180,x=(p.lng()-i.lng())*Math.PI/180,i=Math.sin(w/2)*Math.sin(w/2)+Math.cos(i.lat()*Math.PI/180)*Math.cos(p.lat()*Math.PI/180)*Math.sin(x/2)*Math.sin(x/2),i=6371*2*Math.atan2(Math.sqrt(i),
     18            Math.sqrt(1-i));i<u&&(u=i,o=m)}}o&&o.F.contains(f.getPosition())?o.q(f):(m=new s(g),m.q(f),g.f.push(m))}}function s(a){this.k=a;this.c=a.getMap();this.g=a.w();this.l=a.l;this.r=a.r;this.d=d;this.a=[];this.F=d;this.n=new t(this,a.z(),a.w())}j=s.prototype;
     19    j.q=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=!0;break a}b=!1}if(b)return!1;if(this.d){if(this.r)c=this.a.length+1,b=(this.d.lat()*(c-1)+a.getPosition().lat())/c,c=(this.d.lng()*(c-1)+a.getPosition().lng())/c,this.d=new google.maps.LatLng(b,c),y(this)}else this.d=a.getPosition(),y(this);a.s=!0;this.a.push(a);b=this.a.length;b<this.l&&a.getMap()!=this.c&&a.setMap(this.c);if(b==this.l)for(c=0;c<b;c++)this.a[c].setMap(d);b>=this.l&&a.setMap(d);
     20        a=this.c.getZoom();if((b=this.k.I())&&a>b)for(a=0;b=this.a[a];a++)b.setMap(this.c);else if(this.a.length<this.l)z(this.n);else{b=this.k.H()(this.a,this.k.z().length);this.n.setCenter(this.d);a=this.n;a.B=b;a.ga=b.text;a.ea=b.index;if(a.b)a.b.innerHTML=b.text;b=Math.max(0,a.B.index-1);b=Math.min(a.j.length-1,b);b=a.j[b];a.da=b.url;a.h=b.height;a.p=b.width;a.M=b.textColor;a.e=b.anchor;a.N=b.textSize;a.D=b.backgroundPosition;this.n.show()}return!0};
     21    j.getBounds=function(){for(var a=new google.maps.LatLngBounds(this.d,this.d),b=this.o(),c=0,f;f=b[c];c++)a.extend(f.getPosition());return a};j.remove=function(){this.n.remove();this.a.length=0;delete this.a};j.T=function(){return this.a.length};j.o=h("a");j.getCenter=h("d");function y(a){a.F=a.k.v(new google.maps.LatLngBounds(a.d,a.d))}j.getMap=h("c");
     22    function t(a,b,c){a.k.extend(t,google.maps.OverlayView);this.j=b;this.fa=c||0;this.u=a;this.d=d;this.c=a.getMap();this.B=this.b=d;this.t=!1;this.setMap(this.c)}j=t.prototype;
     23    j.onAdd=function(){this.b=document.createElement("DIV");if(this.t)this.b.style.cssText=A(this,B(this,this.d)),this.b.innerHTML=this.B.text;this.getPanes().overlayMouseTarget.appendChild(this.b);var a=this;google.maps.event.addDomListener(this.b,"click",function(){var b=a.u.k;google.maps.event.trigger(b,"clusterclick",a.u);b.O&&a.c.fitBounds(a.u.getBounds())})};function B(a,b){var c=a.getProjection().fromLatLngToDivPixel(b);c.x-=parseInt(a.p/2,10);c.y-=parseInt(a.h/2,10);return c}
     24    j.draw=function(){if(this.t){var a=B(this,this.d);this.b.style.top=a.y+"px";this.b.style.left=a.x+"px"}};function z(a){if(a.b)a.b.style.display="none";a.t=!1}j.show=function(){if(this.b)this.b.style.cssText=A(this,B(this,this.d)),this.b.style.display="";this.t=!0};j.remove=function(){this.setMap(d)};j.onRemove=function(){if(this.b&&this.b.parentNode)z(this),this.b.parentNode.removeChild(this.b),this.b=d};j.setCenter=e("d");
     25    function A(a,b){var c=[];c.push("background-image:url("+a.da+");");c.push("background-position:"+(a.D?a.D:"0 0")+";");typeof a.e==="object"?(typeof a.e[0]==="number"&&a.e[0]>0&&a.e[0]<a.h?c.push("height:"+(a.h-a.e[0])+"px; padding-top:"+a.e[0]+"px;"):c.push("height:"+a.h+"px; line-height:"+a.h+"px;"),typeof a.e[1]==="number"&&a.e[1]>0&&a.e[1]<a.p?c.push("width:"+(a.p-a.e[1])+"px; padding-left:"+a.e[1]+"px;"):c.push("width:"+a.p+"px; text-align:center;")):c.push("height:"+a.h+"px; line-height:"+a.h+
     26    "px; width:"+a.p+"px; text-align:center;");c.push("cursor:pointer; top:"+b.y+"px; left:"+b.x+"px; color:"+(a.M?a.M:"black")+"; position:absolute; font-size:"+(a.N?a.N:11)+"px; font-family:Arial,sans-serif; font-weight:bold");return c.join("")}window.MarkerClusterer=k;k.prototype.addMarker=k.prototype.q;k.prototype.addMarkers=k.prototype.C;k.prototype.clearMarkers=k.prototype.R;k.prototype.fitMapToMarkers=k.prototype.S;k.prototype.getCalculator=k.prototype.H;k.prototype.getGridSize=k.prototype.w;
     27    k.prototype.getExtendedBounds=k.prototype.v;k.prototype.getMap=k.prototype.getMap;k.prototype.getMarkers=k.prototype.o;k.prototype.getMaxZoom=k.prototype.I;k.prototype.getStyles=k.prototype.z;k.prototype.getTotalClusters=k.prototype.U;k.prototype.getTotalMarkers=k.prototype.V;k.prototype.redraw=k.prototype.i;k.prototype.removeMarker=k.prototype.Y;k.prototype.removeMarkers=k.prototype.Z;k.prototype.resetViewport=k.prototype.m;k.prototype.repaint=k.prototype.L;k.prototype.setCalculator=k.prototype.$;
     28    k.prototype.setGridSize=k.prototype.aa;k.prototype.setMaxZoom=k.prototype.ba;k.prototype.onAdd=k.prototype.onAdd;k.prototype.draw=k.prototype.draw;s.prototype.getCenter=s.prototype.getCenter;s.prototype.getSize=s.prototype.T;s.prototype.getMarkers=s.prototype.o;t.prototype.onAdd=t.prototype.onAdd;t.prototype.draw=t.prototype.draw;t.prototype.onRemove=t.prototype.onRemove;
     29})();
     30
     31
     32/**
     33 * WordCampCentral
     34 *
     35 * Custom client-side behaviors for the Central theme
     36 */
    137var WordCampCentral = ( function( $ ) {
    2 
    338    // templateOptions is copied from Core in order to avoid an extra HTTP request just to get wp.template
    439    var options,
     
    1146    /**
    1247     * Initialization that runs as soon as this file has loaded
     48     *
     49     * @param {object} initOptions
    1350     */
    1451    function immediateInit( initOptions ) {
     
    2360     */
    2461    function documentReadyInit() {
     62        try {
     63            if ( options.hasOwnProperty( 'mapContainer' ) && options.hasOwnProperty( 'mapMarkers' ) ) {
     64                loadMap( options.mapContainer, options.mapMarkers );
     65            }
     66        } catch ( exception ) {
     67            log( exception );
     68        }
    2569    }
    2670
     
    104148    }
    105149
     150    /**
     151     * Build a Google Map in the given container with the given marker data
     152     *
     153     * @param {string} container
     154     * @param {object} markers
     155     */
     156    function loadMap( container, markers ) {
     157        if ( ! $( '#' + container ).length ) {
     158            throw "Map container element isn't present in the DOM.";
     159        }
     160
     161        if ( 'undefined' === typeof( google ) || ! google.hasOwnProperty( 'maps' ) ) {
     162            throw 'Google Maps library is not loaded.';
     163        }
     164
     165        var map, markerCluster,
     166            mapOptions = {
     167                center            : new google.maps.LatLng( 15.000, 7.000 ),
     168                zoom              : 2,
     169                zoomControl       : true,
     170                mapTypeControl    : false,
     171                streetViewControl : false
     172        };
     173
     174        map     = new google.maps.Map( document.getElementById( container ), mapOptions );
     175        markers = createMarkers(  map, markers );
     176
     177        /*
     178         * The About map contains all camps, past and present, so there will be camps from different years that
     179         * are located in the same venue, and their markers will overlap.
     180         */
     181        if ( 'wcc-map-about' == container ) {
     182            markers = repositionOverlappingMarkers( markers );
     183        }
     184
     185        markerCluster = clusterMarkers( map, markers );
     186    }
     187
     188    /**
     189     * Create markers on a map with the given marker data
     190     *
     191     * Normally the markers would be assigned to the map at this point, but we'll run them through MarkerClusterer
     192     * later on, so adding them to the map now is unnecessary and negatively affects performance.
     193     *
     194     * @param {google.maps.Map} map
     195     * @param {object}          markers
     196     *
     197     * @return {object}
     198     */
     199    function createMarkers( map, markers ) {
     200        var markerID,
     201            infoWindowTemplate = _.template( $( '#tmpl-wcc-map-marker' ).html(), null, templateOptions ),
     202            infoWindow         = new google.maps.InfoWindow( {
     203                pixelOffset: new google.maps.Size( -options.markerIconAnchorXOffset, 0 )
     204            } );
     205
     206        for ( markerID in markers ) {
     207            if ( ! markers.hasOwnProperty( markerID ) ) {
     208                continue;
     209            }
     210
     211            markers[ markerID ] = new google.maps.Marker( {
     212                wccID     : markerID,
     213                wccURL    : markers[ markerID ].url,
     214                wccDates  : markers[ markerID ].dates,
     215                location  : markers[ markerID ].location,
     216                venueName : markers[ markerID ].venueName,
     217                title     : markers[ markerID ].name,
     218
     219                icon : {
     220                    url        : options.markerIconBaseURL + markers[ markerID ].iconURL,
     221                    size       : new google.maps.Size(  options.markerIconHeight,        options.markerIconWidth ),
     222                    anchor     : new google.maps.Point( options.markerIconAnchorXOffset, options.markerIconWidth / 2 ),
     223                    scaledSize : new google.maps.Size(  options.markerIconHeight / 2,    options.markerIconWidth / 2 )
     224                },
     225
     226                position : new google.maps.LatLng(
     227                    markers[ markerID ].latitude,
     228                    markers[ markerID ].longitude
     229                )
     230            } );
     231
     232            google.maps.event.addListener( markers[ markerID ], 'click', function() {
     233                try {
     234                    infoWindow.setContent( infoWindowTemplate( { wordcamp: markers[ this.wccID ] } ) );
     235                    infoWindow.open( map, markers[ this.wccID ] );
     236                } catch ( exception ) {
     237                    log( exception );
     238                }
     239            } );
     240        }
     241
     242        return markers;
     243    }
     244
     245    /**
     246     * Offset the position of overlapping markers
     247     *
     248     * Often camps will use the same venue for multiple years, and those map pins would be placed in the exact same
     249     * spot by default, making it impossible to open the overlaid pins, or even know that they're there. This
     250     * function will spread overlapping pins out in a circle around the original point.
     251     *
     252     * @todo It'd be nice to adjust the distance on the fly based on the zoom level, so that you could always see
     253     *       that there are multiple markers at a given location, rather than having to zoom in to the neighborhood
     254     *       level to know that.
     255     *
     256     * @param {object} markers
     257     *
     258     * @returns {object}
     259     */
     260    function repositionOverlappingMarkers( markers ) {
     261        var groupedMarkers = groupMarkersByCoordinates( markers );
     262
     263        _.each( groupedMarkers, function( markerGroup ) {
     264            var markerGroupSize = _.size( markerGroup );
     265
     266            if ( markerGroupSize > 1 ) {
     267                var currentMarkerIndex = 1,
     268                    distance           = markerGroupSize == 2 ? .1 : .5;  // when there are only 2 markers, it's not as obvious that they're centered around a point in the middle
     269
     270                _.each( markerGroup, function( marker, markerID ) {
     271                    var bearing     = currentMarkerIndex / markerGroupSize * 360 + 90,
     272                        newPosition = calculateDestinationPoint( marker.getPosition(), bearing, distance );
     273
     274                    markers[ markerID ].setPosition( newPosition );
     275                    currentMarkerIndex++;
     276                } );
     277            }
     278        } );
     279
     280        return markers;
     281    }
     282
     283    /**
     284     * Group markers by their coordinates
     285     *
     286     * @param {object} markers
     287     *
     288     * @returns {object}
     289     */
     290    function groupMarkersByCoordinates( markers ) {
     291        var groupedMarkers = {};
     292
     293        _.each( markers, function( marker, markerID ) {
     294            var position    = marker.getPosition(),
     295                coordinates = position.lat() + '|' + position.lng();
     296
     297            if ( ! groupedMarkers.hasOwnProperty( coordinates ) ) {
     298                groupedMarkers[ coordinates ] = {};
     299            }
     300
     301            groupedMarkers[ coordinates ][ markerID ] = marker;
     302        } );
     303
     304        return groupedMarkers;
     305    }
     306
     307    /**
     308     * Calculate the destination point after moving a distance with a bearing from a starting point
     309     *
     310     * Based on http://www.movable-type.co.uk/scripts/latlong.html
     311     *
     312     * @param {google.maps.LatLng} startPosition
     313     * @param {number}             bearing in degrees
     314     * @param {number}             distance in kilometers
     315     *
     316     * @returns {google.maps.LatLng}
     317     */
     318    function calculateDestinationPoint( startPosition, bearing, distance ) {
     319        var startLatitude, startLongitude, newLatitude, newLongitude,
     320            earthRadius = 6371; // in kilometers
     321
     322        startLatitude  = startPosition.lat().toRadians();
     323        startLongitude = startPosition.lng().toRadians();
     324        bearing        = bearing.toRadians();
     325
     326        newLatitude = Math.asin(
     327            Math.sin( startLatitude )          *
     328            Math.cos( distance / earthRadius ) +
     329            Math.cos( startLatitude )          *
     330            Math.sin( distance / earthRadius ) *
     331            Math.cos( bearing )
     332        );
     333
     334        newLongitude = startLongitude + Math.atan2(
     335            Math.sin( bearing )                *
     336            Math.sin( distance / earthRadius ) *
     337            Math.cos( startLatitude ),
     338
     339            Math.cos( distance / earthRadius ) -
     340            Math.sin( startLatitude )          *
     341            Math.sin( newLatitude )
     342        );
     343
     344        return new google.maps.LatLng( newLatitude.toDegrees(), newLongitude.toDegrees() );
     345    }
     346
     347    /**
     348     * Cluster the markers into groups for improved performance and UX
     349     *
     350     * options.markerClusterIcon is just 1x size, because MarkerClusterer doesn't support retina images.
     351     * MarkerClusterer Plus does, but it doesn't seem as official, so I'm not as confident that it's secure,
     352     * stable, etc.
     353     *
     354     * @todo the location of clustered pins is shifted off the center of where it should be when zoomed out,
     355     *       and shifts closer to the actual location each time you zoom in
     356     *
     357     * @param {google.maps.Map} map
     358     * @param {object}          markers
     359     *
     360     * @return MarkerClusterer
     361     */
     362    function clusterMarkers( map, markers ) {
     363        var clusterOptions,
     364            markersArray = [];
     365
     366        /*
     367         * We're storing markers in an object so that they can be accessed directly by ID, rather than having to
     368         * loop through them to find one. MarkerClusterer requires them to be passed in as an object, though, so
     369         * we need to convert them here.
     370         */
     371        for ( var m in markers ) {
     372            markersArray.push( markers[ m ] );
     373        }
     374
     375        clusterOptions = {
     376            maxZoom:  13,
     377            gridSize: 40,
     378            styles:   [
     379                {
     380                    url:       options.markerIconBaseURL + options.markerClusterIcon,
     381                    height:    options.markerIconWidth  / 2,
     382                    width:     options.markerIconHeight / 2,
     383                    anchor:    [ 5, -0 ],
     384                    textColor: '#ffffff',
     385                    textSize:  22
     386                },
     387
     388                {
     389                    url:       options.markerIconBaseURL + options.markerClusterIcon,
     390                    height:    options.markerIconWidth  / 2,
     391                    width:     options.markerIconHeight / 2,
     392                    anchor:    [ 5, -5 ],
     393                    textColor: '#ffffff',
     394                    textSize:  18
     395                },
     396
     397                {
     398                    url:       options.markerIconBaseURL + options.markerClusterIcon,
     399                    height:    options.markerIconWidth  / 2,
     400                    width:     options.markerIconHeight / 2,
     401                    anchor:    [ 5, -5 ],
     402                    textColor: '#ffffff',
     403                    textSize:  18
     404                }
     405            ]
     406        };
     407
     408        return new MarkerClusterer( map, markersArray, clusterOptions );
     409    }
     410
     411    /**
     412     * Log a message to the console
     413     *
     414     * @param {*} message
     415     */
     416    function log( message ) {
     417        if ( ! window.console ) {
     418            return;
     419        }
     420
     421        if ( 'string' == typeof( message ) ) {
     422            console.log( 'WordCampCentral: ' + message );
     423        } else {
     424            console.log( 'WordCampCentral: ', message );
     425        }
     426    }
     427
    106428    return {
    107429        immediateInit:     immediateInit,
     
    110432} )( jQuery );
    111433
     434if ( 'undefined' === typeof( Number.prototype.toRadians ) ) {
     435    /**
     436     * Convert degrees to radians
     437     *
     438     * @returns {number}
     439     */
     440    Number.prototype.toRadians = function() {
     441        return this * Math.PI / 180;
     442    };
     443}
     444
     445if ( 'undefined' === typeof( Number.prototype.toDegrees ) ) {
     446    /**
     447     * Convert radians to degrees
     448     *
     449     * @returns {number}
     450     */
     451    Number.prototype.toDegrees = function() {
     452        return this * 180 / Math.PI;
     453    };
     454}
     455
    112456WordCampCentral.immediateInit( wordcampCentralOptions );
    113457jQuery( document ).ready( WordCampCentral.documentReadyInit );
  • sites/trunk/wordcamp.org/public_html/wp-content/themes/wordcamp-central-2012/style.css

    r1545 r1566  
    19661966}
    19671967
    1968 .about-page .about-map {
    1969     float: left;
    1970     margin: -4px 20px 40px 0;
    1971     padding: 12px;
    1972     box-shadow: 0 1px 3px 1px rgba(0,0,0,.3);
    1973     -webkit-box-shadow: 0 1px 3px 1px rgba(0,0,0,.3);
    1974 }
    1975 
    19761968#content .about-stats {
    19771969    margin: 0;
     
    20402032    display: inline-block;
    20412033    margin-top: 5px;
     2034}
     2035
     2036/* !03f. Maps */
     2037/* ------------------------------------- */
     2038
     2039.wcc-map {
     2040    position: relative;
     2041    height: 325px;
     2042    background-color: #E5E3DF;
     2043}
     2044
     2045#wcc-map-spinner {
     2046    position: absolute;
     2047    float: none;
     2048    top: 50%;
     2049    left: 50%;
     2050    margin: -10px 0 0 0;    /* take half the height of the spinner off the top, so that it's perfectly centered */
     2051}
     2052
     2053.wcc-map-marker ul {
     2054    margin-left: 0;
     2055}
     2056
     2057.wcc-map-marker ul li {
     2058    list-style-type: none;
    20422059}
    20432060
Note: See TracChangeset for help on using the changeset viewer.