overlapping-marker-spiderfier
Advanced tools
Comparing version
@@ -1,2 +0,2 @@ | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.OverlappingMarkerSpiderfier=t():e.OverlappingMarkerSpiderfier=t()}(this,function(){return function(e){function t(i){if(r[i])return r[i].exports;var n=r[i]={exports:{},id:i,loaded:!1};return e[i].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var r={};return t.m=e,t.c=r,t.p="/static/",t(0)}([function(e,t,r){e.exports=r(1)},function(e,t){/** @preserve OverlappingMarkerSpiderfier | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.OverlappingMarkerSpiderfier=t():e.OverlappingMarkerSpiderfier=t()}(this,function(){return function(e){function t(r){if(i[r])return i[r].exports;var n=i[r]={exports:{},id:r,loaded:!1};return e[r].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var i={};return t.m=e,t.c=i,t.p="/static/",t(0)}([function(e,t,i){e.exports=i(1)},function(e,t){/** @preserve OverlappingMarkerSpiderfier | ||
https://github.com/jawj/OverlappingMarkerSpiderfier | ||
@@ -7,2 +7,2 @@ Copyright (c) 2011 - 2013 George MacKerron | ||
*/ | ||
var r={}.hasOwnProperty,i=[].slice;this.OverlappingMarkerSpiderfier=function(){function e(e,i){var n,s,o,l,a,h;this.map=e,null==i&&(i={});for(s in i)r.call(i,s)&&(h=i[s],this[s]=h);for(this.projHelper=new this.constructor.ProjHelper(this.map),this.initMarkerArrays(),this.listeners={},a=["click","zoom_changed","maptypeid_changed"],o=0,l=a.length;l>o;o++)n=a[o],t.addListener(this.map,n,function(e){return function(){return e.unspiderfy()}}(this))}var t,n,s,o,l,a,h,u,p,f,g;for(u=e.prototype,p=[e,u],s=0,a=p.length;a>s;s++)g=p[s],g.VERSION="0.3.3";return n=google.maps,t=n.event,h=n.MapTypeId,f=2*Math.PI,u.keepSpiderfied=!1,u.markersWontHide=!1,u.markersWontMove=!1,u.nearbyDistance=20,u.circleSpiralSwitchover=9,u.circleFootSeparation=23,u.circleStartAngle=f/12,u.spiralFootSeparation=26,u.spiralLengthStart=11,u.spiralLengthFactor=4,u.spiderfiedZIndex=1e3,u.usualLegZIndex=10,u.highlightedLegZIndex=20,u.event="click",u.minZoomLevel=!1,u.lineToCenter=!0,u.legWeight=1.5,u.legColors={usual:{},highlighted:{}},l=u.legColors.usual,o=u.legColors.highlighted,l[h.HYBRID]=l[h.SATELLITE]="#fff",o[h.HYBRID]=o[h.SATELLITE]="#f00",l[h.TERRAIN]=l[h.ROADMAP]="#444",o[h.TERRAIN]=o[h.ROADMAP]="#f00",u.initMarkerArrays=function(){return this.markers=[],this.markerListenerRefs=[]},u.addMarker=function(e){var r;return null!=e._oms?this:(e._oms=!0,r=[t.addListener(e,this.event,function(t){return function(r){return t.spiderListener(e,r)}}(this))],this.markersWontHide||r.push(t.addListener(e,"visible_changed",function(t){return function(){return t.markerChangeListener(e,!1)}}(this))),this.markersWontMove||r.push(t.addListener(e,"position_changed",function(t){return function(){return t.markerChangeListener(e,!0)}}(this))),this.markerListenerRefs.push(r),this.markers.push(e),this)},u.markerChangeListener=function(e,t){return null==e._omsData||!t&&e.getVisible()||null!=this.spiderfying||null!=this.unspiderfying?void 0:this.unspiderfy(t?e:null)},u.getMarkers=function(){return this.markers.slice(0)},u.removeMarker=function(e){var r,i,n,s,o;if(null!=e._omsData&&this.unspiderfy(),r=this.arrIndexOf(this.markers,e),0>r)return this;for(o=this.markerListenerRefs.splice(r,1)[0],i=0,n=o.length;n>i;i++)s=o[i],t.removeListener(s);return delete e._oms,this.markers.splice(r,1),this},u.clearMarkers=function(){var e,r,i,n,s,o,l,a,h;for(this.unspiderfy(),h=this.markers,e=r=0,i=h.length;i>r;e=++r){for(l=h[e],o=this.markerListenerRefs[e],a=0,n=o.length;n>a;a++)s=o[a],t.removeListener(s);delete l._oms}return this.initMarkerArrays(),this},u.addListener=function(e,t){var r;return(null!=(r=this.listeners)[e]?r[e]:r[e]=[]).push(t),this},u.removeListener=function(e,t){var r;return r=this.arrIndexOf(this.listeners[e],t),0>r||this.listeners[e].splice(r,1),this},u.clearListeners=function(e){return this.listeners[e]=[],this},u.trigger=function(){var e,t,r,n,s,o,l,a;for(t=arguments[0],e=arguments.length>=2?i.call(arguments,1):[],l=null!=(o=this.listeners[t])?o:[],a=[],n=0,s=l.length;s>n;n++)r=l[n],a.push(r.apply(null,e));return a},u.generatePtsCircle=function(e,t){var r,i,s,o,l,a,h,u;for(s=this.circleFootSeparation*(2+e),a=s/f,i=f/e,u=[],o=l=0,h=e;h>=0?h>l:l>h;o=h>=0?++l:--l)r=this.circleStartAngle+o*i,u.push(new n.Point(t.x+a*Math.cos(r),t.y+a*Math.sin(r)));return u},u.generatePtsSpiral=function(e,t){var r,i,s,o,l,a,h;for(o=this.spiralLengthStart,r=0,h=[],i=s=0,a=e;a>=0?a>s:s>a;i=a>=0?++s:--s)r+=this.spiralFootSeparation/o+5e-4*i,l=new n.Point(t.x+o*Math.cos(r),t.y+o*Math.sin(r)),o+=f*this.spiralLengthFactor/r,h.push(l);return h},u.spiderListener=function(e,t){var r,i,n,s,o,l,a,h,p,f,g,c,d;if(h=null!=e._omsData,h&&this.keepSpiderfied||("mouseover"===this.event?(r=this,i=function(){return r.unspiderfy()},window.clearTimeout(u.timeout),u.timeout=setTimeout(i,3e3)):this.unspiderfy()),h||this.map.getStreetView().getVisible()||"GoogleEarthAPI"===this.map.getMapTypeId())return this.trigger("click",e,t);for(f=[],g=[],p=this.nearbyDistance,c=p*p,a=this.llToPt(e.position),d=this.markers,n=0,s=d.length;s>n;n++)o=d[n],null!=o.map&&o.getVisible()&&(l=this.llToPt(o.position),this.ptDistanceSq(l,a)<c?f.push({marker:o,markerPt:l}):g.push(o));return 1===f.length?this.trigger("click",e,t):this.spiderfy(f,g)},u.markersNearMarker=function(e,t){var r,i,n,s,o,l,a,h,u,p,f;if(null==t&&(t=!1),null==this.projHelper.getProjection())throw"Must wait for 'idle' event on map before calling markersNearMarker";for(a=this.nearbyDistance,h=a*a,o=this.llToPt(e.position),l=[],u=this.markers,r=0,i=u.length;i>r&&(n=u[r],!(n!==e&&null!=n.map&&n.getVisible()&&(s=this.llToPt(null!=(p=null!=(f=n._omsData)?f.usualPosition:void 0)?p:n.position),this.ptDistanceSq(s,o)<h&&(l.push(n),t))));r++);return l},u.markersNearAnyOtherMarker=function(){var e,t,r,i,n,s,o,l,a,h,u,p,f,g,c,d,m,k,v,y,L;if(null==this.projHelper.getProjection())throw"Must wait for 'idle' event on map before calling markersNearAnyOtherMarker";for(c=this.nearbyDistance,d=c*c,f=function(){var e,t,r,i,n,s;for(r=this.markers,s=[],e=0,t=r.length;t>e;e++)l=r[e],s.push({pt:this.llToPt(null!=(i=null!=(n=l._omsData)?n.usualPosition:void 0)?i:l.position),willSpiderfy:!1});return s}.call(this),k=this.markers,t=i=0,n=k.length;n>i;t=++i)if(a=k[t],null!=a.map&&a.getVisible()&&(h=f[t],!h.willSpiderfy))for(v=this.markers,r=g=0,s=v.length;s>g;r=++g)if(u=v[r],r!==t&&null!=u.map&&u.getVisible()&&(p=f[r],(!(t>r)||p.willSpiderfy)&&this.ptDistanceSq(h.pt,p.pt)<d)){h.willSpiderfy=p.willSpiderfy=!0;break}for(y=this.markers,L=[],e=m=0,o=y.length;o>m;e=++m)l=y[e],f[e].willSpiderfy&&L.push(l);return L},u.makeHighlightListenerFuncs=function(e){return{highlight:function(t){return function(){return e._omsData.leg.setOptions({strokeColor:t.legColors.highlighted[t.map.mapTypeId],zIndex:t.highlightedLegZIndex})}}(this),unhighlight:function(t){return function(){return e._omsData.leg.setOptions({strokeColor:t.legColors.usual[t.map.mapTypeId],zIndex:t.usualLegZIndex})}}(this)}},u.spiderfy=function(e,r){var i,s,o,l,a,h,u,p,f,g,c,d,m;return this.minZoomLevel&&this.map.getZoom()<this.minZoomLevel?!1:(this.spiderfying=!0,d=e.length,i=this.ptAverage(function(){var t,r,i;for(i=[],t=0,r=e.length;r>t;t++)g=e[t],i.push(g.markerPt);return i}()),a=d>=this.circleSpiralSwitchover?this.generatePtsSpiral(d,i).reverse():this.generatePtsCircle(d,i),s=this.ptToLl(i),m=function(){var r,i,g;for(g=[],r=0,i=a.length;i>r;r++)l=a[r],o=this.ptToLl(l),c=this.minExtract(e,function(e){return function(t){return e.ptDistanceSq(t.markerPt,l)}}(this)),f=c.marker,p=this.lineToCenter?s:f.position,u=new n.Polyline({map:this.map,path:[p,o],strokeColor:this.legColors.usual[this.map.mapTypeId],strokeWeight:this.legWeight,zIndex:this.usualLegZIndex}),f._omsData={usualPosition:f.position,leg:u},this.legColors.highlighted[this.map.mapTypeId]!==this.legColors.usual[this.map.mapTypeId]&&(h=this.makeHighlightListenerFuncs(f),f._omsData.hightlightListeners={highlight:t.addListener(f,"mouseover",h.highlight),unhighlight:t.addListener(f,"mouseout",h.unhighlight)}),f.setPosition(o),f.setZIndex(Math.round(this.spiderfiedZIndex+l.y)),g.push(f);return g}.call(this),delete this.spiderfying,this.spiderfied=!0,this.trigger("spiderfy",m,r))},u.unspiderfy=function(e){var r,i,n,s,o,l,a;if(null==e&&(e=null),null==this.spiderfied)return this;for(this.unspiderfying=!0,a=[],o=[],l=this.markers,r=0,i=l.length;i>r;r++)s=l[r],null!=s._omsData?(s._omsData.leg.setMap(null),s!==e&&s.setPosition(s._omsData.usualPosition),s.setZIndex(null),n=s._omsData.hightlightListeners,null!=n&&(t.removeListener(n.highlight),t.removeListener(n.unhighlight)),delete s._omsData,a.push(s)):o.push(s);return delete this.unspiderfying,delete this.spiderfied,this.trigger("unspiderfy",a,o),this},u.ptDistanceSq=function(e,t){var r,i;return r=e.x-t.x,i=e.y-t.y,r*r+i*i},u.ptAverage=function(e){var t,r,i,s,o,l;for(o=l=0,t=0,r=e.length;r>t;t++)s=e[t],o+=s.x,l+=s.y;return i=e.length,new n.Point(o/i,l/i)},u.llToPt=function(e){return this.projHelper.getProjection().fromLatLngToDivPixel(e)},u.ptToLl=function(e){return this.projHelper.getProjection().fromDivPixelToLatLng(e)},u.minExtract=function(e,t){var r,i,n,s,o,l,a;for(n=o=0,l=e.length;l>o;n=++o)s=e[n],a=t(s),("undefined"==typeof r||null===r||i>a)&&(i=a,r=n);return e.splice(r,1)[0]},u.arrIndexOf=function(e,t){var r,i,n,s;if(null!=e.indexOf)return e.indexOf(t);for(r=i=0,n=e.length;n>i;r=++i)if(s=e[r],s===t)return r;return-1},e.ProjHelper=function(e){return this.setMap(e)},e.ProjHelper.prototype=new n.OverlayView,e.ProjHelper.prototype.draw=function(){},e}(),e.exports=this.OverlappingMarkerSpiderfier}])}); | ||
var i={}.hasOwnProperty,r=[].slice;this.OverlappingMarkerSpiderfier=function(){function e(e,r){var n,s,o,a,l,h;this.map=e,null==r&&(r={});for(s in r)i.call(r,s)&&(h=r[s],this[s]=h);for(this.projHelper=new this.constructor.ProjHelper(this.map),this.initMarkerArrays(),this.listeners={},l=["click","zoom_changed","maptypeid_changed"],o=0,a=l.length;a>o;o++)n=l[o],t.addListener(this.map,n,function(e){return function(){return e.unspiderfy()}}(this));this.nudgeStackedMarkers&&t.addListenerOnce(this.map,"idle",function(e){return function(){return t.addListener(e.map,"zoom_changed",function(){return e.mapZoomChangeListener()}),e.mapZoomChangeListener()}}(this))}var t,n,s,o,a,l,h,u,p,d,g;for(u=e.prototype,p=[e,u],s=0,l=p.length;l>s;s++)g=p[s],g.VERSION="0.3.3";return n=google.maps,t=n.event,h=n.MapTypeId,d=2*Math.PI,u.keepSpiderfied=!1,u.markersWontHide=!1,u.markersWontMove=!1,u.spiderfiedShadowColor="white",u.nudgeStackedMarkers=!0,u.minNudgeZoomLevel=8,u.nudgeRadius=1,u.markerCountInBaseNudgeLevel=9,u.nudgeBucketSize=12,u.nearbyDistance=20,u.circleSpiralSwitchover=9,u.circleFootSeparation=23,u.circleStartAngle=d/12,u.spiralFootSeparation=26,u.spiralLengthStart=11,u.spiralLengthFactor=4,u.spiderfiedZIndex=1e3,u.usualLegZIndex=10,u.highlightedLegZIndex=20,u.event="click",u.minZoomLevel=!1,u.lineToCenter=!0,u.legWeight=1.5,u.legColors={usual:{},highlighted:{}},a=u.legColors.usual,o=u.legColors.highlighted,a[h.HYBRID]=a[h.SATELLITE]="#fff",o[h.HYBRID]=o[h.SATELLITE]="#f00",a[h.TERRAIN]=a[h.ROADMAP]="#444",o[h.TERRAIN]=o[h.ROADMAP]="#f00",u.initMarkerArrays=function(){return this.markers=[],this.markerListenerRefs=[]},u.addMarker=function(e){var i;return null!=e._oms?this:(e._oms=!0,i=[t.addListener(e,this.event,function(t){return function(i){return t.spiderListener(e,i)}}(this))],this.markersWontHide||i.push(t.addListener(e,"visible_changed",function(t){return function(){return t.markerChangeListener(e,!1)}}(this))),this.markersWontMove||i.push(t.addListener(e,"position_changed",function(t){return function(){return t.markerChangeListener(e,!0)}}(this))),this.markerListenerRefs.push(i),this.markers.push(e),this)},u.markerChangeListener=function(e,t){return null==e._omsData||e._omsData.nudged||!t&&e.getVisible()||null!=this.spiderfying||null!=this.unspiderfying?void 0:this.unspiderfy(t?e:null)},u.countsPerLevel=[1,1],u.levelsByCount=[],u.getCountPerNudgeLevel=function(e){return null!=this.countsPerLevel[e]?this.countsPerLevel[e]:this.countsPerLevel[e]=this.getCountPerNudgeLevel(e-1)+Math.pow(2,e-2)*this.markerCountInBaseNudgeLevel},u.getNudgeLevel=function(e){var t;if(null!=this.levelsByCount[e])return this.levelsByCount[e];for(t=0;e>=this.countsPerLevel[t];)t+1>=this.countsPerLevel.length&&this.getCountPerNudgeLevel(t+1),t++;return this.levelsByCount[e]=t-1},u.nudgeAllMarkers=function(){var e,t,i,r,n,s,o,a,l,h,u,p,g,f,c,m,v,k,y;for(u={},e=1/((1+this.nudgeBucketSize)*this.nudgeRadius),i=function(t){return function(t){return Math.floor(t.x*e)+","+Math.floor(t.y*e)}}(this),g=this.markers,k=[],r=n=0,s=g.length;s>n;r=++n){for(o=g[r],a=!1,h=this.llToPt(null!=(f=null!=(c=o._omsData)?c.usualPosition:void 0)?f:o.position),l={x:h.x,y:h.y};u.hasOwnProperty(i(h));)t=u[i(h)],u.hasOwnProperty(i(h))?u[i(h)]+=1:u[i(h)]=1,y=this.getNudgeLevel(t),p=20*this.nudgeRadius*y,h.x=l.x+Math.sin(d*t/this.markerCountInBaseNudgeLevel/y)*p,h.y=l.y+Math.cos(d*t/this.markerCountInBaseNudgeLevel/y)*p,this.nudged=!0,a=!0;a?(o._omsData={usualPosition:null!=(m=null!=(v=o._omsData)?v.usualPosition:void 0)?m:o.position,nudged:!0},o.setPosition(this.ptToLl(h))):null!=o._omsData&&o._omsData.nudged&&(o.setPosition(o._omsData.usualPosition),delete o._omsData),k.push(u.hasOwnProperty(i(h))?void 0:u[i(h)]=1)}return k},u.resetNudgedMarkers=function(){var e,t,i,r;if(this.nudged){for(r=this.markers,e=0,t=r.length;t>e;e++)i=r[e],null!=i._omsData&&i._omsData.nudged&&(i.setPosition(i._omsData.usualPosition),delete i._omsData);return delete this.nudged}},u.mapZoomChangeListener=function(){return this.minNudgeZoomLevel&&this.map.getZoom()<this.minNudgeZoomLevel?this.resetNudgedMarkers():this.nudgeAllMarkers(this.markers)},u.getMarkers=function(){return this.markers.slice(0)},u.removeMarker=function(e){var i,r,n,s,o;if(null!=e._omsData&&this.unspiderfy(),i=this.arrIndexOf(this.markers,e),0>i)return this;for(o=this.markerListenerRefs.splice(i,1)[0],r=0,n=o.length;n>r;r++)s=o[r],t.removeListener(s);return delete e._oms,this.markers.splice(i,1),this},u.clearMarkers=function(){var e,i,r,n,s,o,a,l,h;for(this.unspiderfy(),h=this.markers,e=i=0,r=h.length;r>i;e=++i){for(a=h[e],o=this.markerListenerRefs[e],l=0,n=o.length;n>l;l++)s=o[l],t.removeListener(s);delete a._oms}return this.initMarkerArrays(),this},u.addListener=function(e,t){var i;return(null!=(i=this.listeners)[e]?i[e]:i[e]=[]).push(t),this},u.removeListener=function(e,t){var i;return i=this.arrIndexOf(this.listeners[e],t),0>i||this.listeners[e].splice(i,1),this},u.clearListeners=function(e){return this.listeners[e]=[],this},u.trigger=function(){var e,t,i,n,s,o,a,l;for(t=arguments[0],e=arguments.length>=2?r.call(arguments,1):[],a=null!=(o=this.listeners[t])?o:[],l=[],n=0,s=a.length;s>n;n++)i=a[n],l.push(i.apply(null,e));return l},u.generatePtsCircle=function(e,t){var i,r,s,o,a,l,h,u;for(s=this.circleFootSeparation*(2+e),l=s/d,r=d/e,u=[],o=a=0,h=e;h>=0?h>a:a>h;o=h>=0?++a:--a)i=this.circleStartAngle+o*r,u.push(new n.Point(t.x+l*Math.cos(i),t.y+l*Math.sin(i)));return u},u.generatePtsSpiral=function(e,t){var i,r,s,o,a,l,h;for(o=this.spiralLengthStart,i=0,h=[],r=s=0,l=e;l>=0?l>s:s>l;r=l>=0?++s:--s)i+=this.spiralFootSeparation/o+5e-4*r,a=new n.Point(t.x+o*Math.cos(i),t.y+o*Math.sin(i)),o+=d*this.spiralLengthFactor/i,h.push(a);return h},u.spiderListener=function(e,t){var i,r,n,s,o,a,l,h,p,d,g,f,c;if(h=null!=e._omsData&&!e._omsData.nudged,h&&this.keepSpiderfied||("mouseover"===this.event?(i=this,r=function(){return i.unspiderfy()},window.clearTimeout(u.timeout),u.timeout=setTimeout(r,3e3)):this.unspiderfy()),h||this.map.getStreetView().getVisible()||"GoogleEarthAPI"===this.map.getMapTypeId())return this.trigger("click",e,t);for(d=[],g=[],p=this.nearbyDistance,f=p*p,l=this.llToPt(e.position),c=this.markers,n=0,s=c.length;s>n;n++)o=c[n],null!=o.map&&o.getVisible()&&(a=this.llToPt(o.position),this.ptDistanceSq(a,l)<f?d.push({marker:o,markerPt:a}):g.push(o));return 1===d.length?this.trigger("click",e,t):this.spiderfy(d,g)},u.markersNearMarker=function(e,t){var i,r,n,s,o,a,l,h,u,p,d;if(null==t&&(t=!1),null==this.projHelper.getProjection())throw"Must wait for 'idle' event on map before calling markersNearMarker";for(l=this.nearbyDistance,h=l*l,o=this.llToPt(e.position),a=[],u=this.markers,i=0,r=u.length;r>i&&(n=u[i],!(n!==e&&null!=n.map&&n.getVisible()&&(s=this.llToPt(null!=(p=null!=(d=n._omsData)?d.usualPosition:void 0)?p:n.position),this.ptDistanceSq(s,o)<h&&(a.push(n),t))));i++);return a},u.markersNearAnyOtherMarker=function(){var e,t,i,r,n,s,o,a,l,h,u,p,d,g,f,c,m,v,k,y,L;if(null==this.projHelper.getProjection())throw"Must wait for 'idle' event on map before calling markersNearAnyOtherMarker";for(f=this.nearbyDistance,c=f*f,d=function(){var e,t,i,r,n,s;for(i=this.markers,s=[],e=0,t=i.length;t>e;e++)a=i[e],s.push({pt:this.llToPt(null!=(r=null!=(n=a._omsData)?n.usualPosition:void 0)?r:a.position),willSpiderfy:!1});return s}.call(this),v=this.markers,t=r=0,n=v.length;n>r;t=++r)if(l=v[t],null!=l.map&&l.getVisible()&&(h=d[t],!h.willSpiderfy))for(k=this.markers,i=g=0,s=k.length;s>g;i=++g)if(u=k[i],i!==t&&null!=u.map&&u.getVisible()&&(p=d[i],(!(t>i)||p.willSpiderfy)&&this.ptDistanceSq(h.pt,p.pt)<c)){h.willSpiderfy=p.willSpiderfy=!0;break}for(y=this.markers,L=[],e=m=0,o=y.length;o>m;e=++m)a=y[e],d[e].willSpiderfy&&L.push(a);return L},u.makeHighlightListenerFuncs=function(e){return{highlight:function(t){return function(){var i;return e._omsData.leg.setOptions({strokeColor:t.legColors.highlighted[t.map.mapTypeId],zIndex:t.highlightedLegZIndex}),null!=e._omsData.shadow?(i=e._omsData.shadow.getIcon(),i.fillOpacity=.8,e._omsData.shadow.setOptions({icon:i})):void 0}}(this),unhighlight:function(t){return function(){var i;return e._omsData.leg.setOptions({strokeColor:t.legColors.usual[t.map.mapTypeId],zIndex:t.usualLegZIndex}),null!=e._omsData.shadow?(i=e._omsData.shadow.getIcon(),i.fillOpacity=.3,e._omsData.shadow.setOptions({icon:i})):void 0}}(this)}},u.spiderfy=function(e,i){var r,s,o,a,l,h,u,p,d,g,f,c,m;return this.minZoomLevel&&this.map.getZoom()<this.minZoomLevel?!1:(this.spiderfying=!0,c=e.length,r=this.ptAverage(function(){var t,i,r;for(r=[],t=0,i=e.length;i>t;t++)g=e[t],r.push(g.markerPt);return r}()),l=c>=this.circleSpiralSwitchover?this.generatePtsSpiral(c,r).reverse():this.generatePtsCircle(c,r),s=this.ptToLl(r),m=function(){var i,r,g,c,m;for(m=[],i=0,r=l.length;r>i;i++)a=l[i],o=this.ptToLl(a),f=this.minExtract(e,function(e){return function(t){return e.ptDistanceSq(t.markerPt,a)}}(this)),d=f.marker,p=this.lineToCenter?s:d.position,u=new n.Polyline({map:this.map,path:[p,o],strokeColor:this.legColors.usual[this.map.mapTypeId],strokeWeight:this.legWeight,zIndex:this.usualLegZIndex}),d._omsData={usualPosition:null!=(g=null!=(c=d._omsData)?c.usualPosition:void 0)?g:d.position,leg:u},this.spiderfiedShadowColor&&(d._omsData.shadow=new n.Marker({position:o,map:this.map,clickable:!1,zIndex:-2,icon:{path:google.maps.SymbolPath.CIRCLE,fillOpacity:.3,fillColor:this.spiderfiedShadowColor,strokeWeight:0,scale:20}})),this.legColors.highlighted[this.map.mapTypeId]!==this.legColors.usual[this.map.mapTypeId]&&(h=this.makeHighlightListenerFuncs(d),d._omsData.hightlightListeners={highlight:t.addListener(d,"mouseover",h.highlight),unhighlight:t.addListener(d,"mouseout",h.unhighlight)}),d.setPosition(o),d.setZIndex(Math.round(this.spiderfiedZIndex+a.y)),m.push(d);return m}.call(this),delete this.spiderfying,this.spiderfied=!0,this.trigger("spiderfy",m,i))},u.unspiderfy=function(e){var i,r,n,s,o,a,l,h;if(null==e&&(e=null),null==this.spiderfied)return this;for(this.unspiderfying=!0,h=[],o=[],a=this.markers,i=0,r=a.length;r>i;i++)s=a[i],null==s._omsData||s._omsData.nudged?o.push(s):(s._omsData.leg.setMap(null),null!=(l=s._omsData.shadow)&&l.setMap(null),s!==e&&s.setPosition(s._omsData.usualPosition),s.setZIndex(null),n=s._omsData.hightlightListeners,null!=n&&(t.removeListener(n.highlight),t.removeListener(n.unhighlight)),delete s._omsData,h.push(s));return delete this.unspiderfying,delete this.spiderfied,this.trigger("unspiderfy",h,o),this.nudged&&this.nudgeAllMarkers(),this},u.ptDistanceSq=function(e,t){var i,r;return i=e.x-t.x,r=e.y-t.y,i*i+r*r},u.ptAverage=function(e){var t,i,r,s,o,a;for(o=a=0,t=0,i=e.length;i>t;t++)s=e[t],o+=s.x,a+=s.y;return r=e.length,new n.Point(o/r,a/r)},u.llToPt=function(e){return this.projHelper.getProjection().fromLatLngToDivPixel(e)},u.ptToLl=function(e){return this.projHelper.getProjection().fromDivPixelToLatLng(e)},u.minExtract=function(e,t){var i,r,n,s,o,a,l;for(n=o=0,a=e.length;a>o;n=++o)s=e[n],l=t(s),("undefined"==typeof i||null===i||r>l)&&(r=l,i=n);return e.splice(i,1)[0]},u.arrIndexOf=function(e,t){var i,r,n,s;if(null!=e.indexOf)return e.indexOf(t);for(i=r=0,n=e.length;n>r;i=++r)if(s=e[i],s===t)return i;return-1},e.ProjHelper=function(e){return this.setMap(e)},e.ProjHelper.prototype=new n.OverlayView,e.ProjHelper.prototype.draw=function(){},e}(),e.exports=this.OverlappingMarkerSpiderfier}])}); |
178
lib/oms.js
@@ -37,2 +37,14 @@ // Generated by CoffeeScript 1.10.0 | ||
p['spiderfiedShadowColor'] = 'white'; | ||
p['nudgeStackedMarkers'] = true; | ||
p['minNudgeZoomLevel'] = 8; | ||
p['nudgeRadius'] = 1; | ||
p['markerCountInBaseNudgeLevel'] = 9; | ||
p['nudgeBucketSize'] = 12; | ||
p['nearbyDistance'] = 20; | ||
@@ -106,2 +118,12 @@ | ||
} | ||
if (this['nudgeStackedMarkers']) { | ||
ge.addListenerOnce(this.map, 'idle', (function(_this) { | ||
return function() { | ||
ge.addListener(_this.map, 'zoom_changed', function() { | ||
return _this.mapZoomChangeListener(); | ||
}); | ||
return _this.mapZoomChangeListener(); | ||
}; | ||
})(this)); | ||
} | ||
} | ||
@@ -147,3 +169,3 @@ | ||
p.markerChangeListener = function(marker, positionChanged) { | ||
if ((marker['_omsData'] != null) && (positionChanged || !marker.getVisible()) && !((this.spiderfying != null) || (this.unspiderfying != null))) { | ||
if ((marker['_omsData'] != null) && !marker['_omsData'].nudged && (positionChanged || !marker.getVisible()) && !((this.spiderfying != null) || (this.unspiderfying != null))) { | ||
return this['unspiderfy'](positionChanged ? marker : null); | ||
@@ -153,2 +175,105 @@ } | ||
p.countsPerLevel = [1, 1]; | ||
p.levelsByCount = []; | ||
p.getCountPerNudgeLevel = function(level) { | ||
if (this.countsPerLevel[level] != null) { | ||
return this.countsPerLevel[level]; | ||
} | ||
this.countsPerLevel[level] = this.getCountPerNudgeLevel(level - 1) + Math.pow(2, level - 2) * this['markerCountInBaseNudgeLevel']; | ||
return this.countsPerLevel[level]; | ||
}; | ||
p.getNudgeLevel = function(markerIndex) { | ||
var level; | ||
if (this.levelsByCount[markerIndex] != null) { | ||
return this.levelsByCount[markerIndex]; | ||
} | ||
level = 0; | ||
while (markerIndex >= this.countsPerLevel[level]) { | ||
if (level + 1 >= this.countsPerLevel.length) { | ||
this.getCountPerNudgeLevel(level + 1); | ||
} | ||
level++; | ||
} | ||
this.levelsByCount[markerIndex] = level - 1; | ||
return this.levelsByCount[markerIndex]; | ||
}; | ||
p.nudgeAllMarkers = function() { | ||
var bucketSize, direction, getKey, i, l, len1, m, needsNudge, originalPos, pos, positions, radius, ref1, ref2, ref3, ref4, ref5, results, ringLevel; | ||
positions = {}; | ||
bucketSize = 1 / ((1 + this['nudgeBucketSize']) * this['nudgeRadius']); | ||
getKey = (function(_this) { | ||
return function(pos) { | ||
return Math.floor(pos.x * bucketSize) + ',' + Math.floor(pos.y * bucketSize); | ||
}; | ||
})(this); | ||
ref1 = this.markers; | ||
results = []; | ||
for (i = l = 0, len1 = ref1.length; l < len1; i = ++l) { | ||
m = ref1[i]; | ||
needsNudge = false; | ||
pos = this.llToPt((ref2 = (ref3 = m['_omsData']) != null ? ref3.usualPosition : void 0) != null ? ref2 : m.position); | ||
originalPos = { | ||
x: pos.x, | ||
y: pos.y | ||
}; | ||
while (positions.hasOwnProperty(getKey(pos))) { | ||
direction = positions[getKey(pos)]; | ||
if (positions.hasOwnProperty(getKey(pos))) { | ||
positions[getKey(pos)] += 1; | ||
} else { | ||
positions[getKey(pos)] = 1; | ||
} | ||
ringLevel = this.getNudgeLevel(direction); | ||
radius = 20 * this['nudgeRadius'] * ringLevel; | ||
pos.x = originalPos.x + Math.sin(twoPi * direction / this['markerCountInBaseNudgeLevel'] / ringLevel) * radius; | ||
pos.y = originalPos.y + Math.cos(twoPi * direction / this['markerCountInBaseNudgeLevel'] / ringLevel) * radius; | ||
this.nudged = true; | ||
needsNudge = true; | ||
} | ||
if (needsNudge) { | ||
m['_omsData'] = { | ||
usualPosition: (ref4 = (ref5 = m['_omsData']) != null ? ref5.usualPosition : void 0) != null ? ref4 : m.position, | ||
nudged: true | ||
}; | ||
m.setPosition(this.ptToLl(pos)); | ||
} else if ((m['_omsData'] != null) && m['_omsData'].nudged) { | ||
m.setPosition(m['_omsData'].usualPosition); | ||
delete m['_omsData']; | ||
} | ||
if (!positions.hasOwnProperty(getKey(pos))) { | ||
results.push(positions[getKey(pos)] = 1); | ||
} else { | ||
results.push(void 0); | ||
} | ||
} | ||
return results; | ||
}; | ||
p.resetNudgedMarkers = function() { | ||
var l, len1, m, ref1; | ||
if (!this.nudged) { | ||
return; | ||
} | ||
ref1 = this.markers; | ||
for (l = 0, len1 = ref1.length; l < len1; l++) { | ||
m = ref1[l]; | ||
if ((m['_omsData'] != null) && m['_omsData'].nudged) { | ||
m.setPosition(m['_omsData'].usualPosition); | ||
delete m['_omsData']; | ||
} | ||
} | ||
return delete this.nudged; | ||
}; | ||
p.mapZoomChangeListener = function() { | ||
if (this['minNudgeZoomLevel'] && this.map.getZoom() < this['minNudgeZoomLevel']) { | ||
return this.resetNudgedMarkers(); | ||
} | ||
return this.nudgeAllMarkers(this.markers); | ||
}; | ||
p['getMarkers'] = function() { | ||
@@ -255,3 +380,3 @@ return this.markers.slice(0); | ||
var $this, clear, l, len1, m, mPt, markerPt, markerSpiderfied, nDist, nearbyMarkerData, nonNearbyMarkers, pxSq, ref1; | ||
markerSpiderfied = marker['_omsData'] != null; | ||
markerSpiderfied = (marker['_omsData'] != null) && !marker['_omsData'].nudged; | ||
if (!(markerSpiderfied && this['keepSpiderfied'])) { | ||
@@ -394,6 +519,14 @@ if (this['event'] === 'mouseover') { | ||
return function() { | ||
return marker['_omsData'].leg.setOptions({ | ||
var icon; | ||
marker['_omsData'].leg.setOptions({ | ||
strokeColor: _this['legColors']['highlighted'][_this.map.mapTypeId], | ||
zIndex: _this['highlightedLegZIndex'] | ||
}); | ||
if (marker['_omsData'].shadow != null) { | ||
icon = marker['_omsData'].shadow.getIcon(); | ||
icon.fillOpacity = 0.8; | ||
return marker['_omsData'].shadow.setOptions({ | ||
icon: icon | ||
}); | ||
} | ||
}; | ||
@@ -403,6 +536,14 @@ })(this), | ||
return function() { | ||
return marker['_omsData'].leg.setOptions({ | ||
var icon; | ||
marker['_omsData'].leg.setOptions({ | ||
strokeColor: _this['legColors']['usual'][_this.map.mapTypeId], | ||
zIndex: _this['usualLegZIndex'] | ||
}); | ||
if (marker['_omsData'].shadow != null) { | ||
icon = marker['_omsData'].shadow.getIcon(); | ||
icon.fillOpacity = 0.3; | ||
return marker['_omsData'].shadow.setOptions({ | ||
icon: icon | ||
}); | ||
} | ||
}; | ||
@@ -432,3 +573,3 @@ })(this) | ||
spiderfiedMarkers = (function() { | ||
var l, len1, results; | ||
var l, len1, ref1, ref2, results; | ||
results = []; | ||
@@ -453,5 +594,20 @@ for (l = 0, len1 = footPts.length; l < len1; l++) { | ||
marker['_omsData'] = { | ||
usualPosition: marker.position, | ||
usualPosition: (ref1 = (ref2 = marker['_omsData']) != null ? ref2.usualPosition : void 0) != null ? ref1 : marker.position, | ||
leg: leg | ||
}; | ||
if (this['spiderfiedShadowColor']) { | ||
marker['_omsData'].shadow = new gm.Marker({ | ||
position: footLl, | ||
map: this.map, | ||
clickable: false, | ||
zIndex: -2, | ||
icon: { | ||
path: google.maps.SymbolPath.CIRCLE, | ||
fillOpacity: 0.3, | ||
fillColor: this['spiderfiedShadowColor'], | ||
strokeWeight: 0, | ||
scale: 20 | ||
} | ||
}); | ||
} | ||
if (this['legColors']['highlighted'][this.map.mapTypeId] !== this['legColors']['usual'][this.map.mapTypeId]) { | ||
@@ -476,3 +632,3 @@ highlightListenerFuncs = this.makeHighlightListenerFuncs(marker); | ||
p['unspiderfy'] = function(markerNotToMove) { | ||
var l, len1, listeners, marker, nonNearbyMarkers, ref1, unspiderfiedMarkers; | ||
var l, len1, listeners, marker, nonNearbyMarkers, ref1, ref2, unspiderfiedMarkers; | ||
if (markerNotToMove == null) { | ||
@@ -490,4 +646,7 @@ markerNotToMove = null; | ||
marker = ref1[l]; | ||
if (marker['_omsData'] != null) { | ||
if ((marker['_omsData'] != null) && !marker['_omsData'].nudged) { | ||
marker['_omsData'].leg.setMap(null); | ||
if ((ref2 = marker['_omsData'].shadow) != null) { | ||
ref2.setMap(null); | ||
} | ||
if (marker !== markerNotToMove) { | ||
@@ -511,2 +670,5 @@ marker.setPosition(marker['_omsData'].usualPosition); | ||
this.trigger('unspiderfy', unspiderfiedMarkers, nonNearbyMarkers); | ||
if (this.nudged) { | ||
this.nudgeAllMarkers(); | ||
} | ||
return this; | ||
@@ -513,0 +675,0 @@ }; |
{ | ||
"name": "overlapping-marker-spiderfier", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"description": "Deals with overlapping markers in Google Maps JS API v3, Google Earth-style", | ||
@@ -5,0 +5,0 @@ "main": "lib/oms.js", |
125
README.md
Overlapping Marker Spiderfier for Google Maps API v3 | ||
==================================================== | ||
**Ever noticed how, in [Google Earth](http://earth.google.com), marker pins that overlap each other spring apart gracefully when you click them, so you can pick the one you meant?** | ||
Customized fork of [jawj/OverlappingMarkerSpiderfier](https://github.com/jawj/OverlappingMarkerSpiderfier) | ||
**And ever noticed how, when using the [Google Maps API](http://code.google.com/apis/maps/documentation/javascript/), the same thing doesn't happen?** | ||
This code makes Google Maps API **version 3** map markers behave in that Google Earth way (minus the animation). Small numbers of markers (yes, up to 8) spiderfy into a circle. Larger numbers fan out into a more space-efficient spiral. | ||
The compiled code has no dependencies beyond Google Maps. And it's under 3K when compiled out of [CoffeeScript](http://jashkenas.github.com/coffee-script/), minified with Google's [Closure Compiler](http://code.google.com/closure/compiler/) and gzipped. | ||
I wrote it as part of the data download feature for [Mappiness](http://www.mappiness.org.uk/maps/). | ||
**There's now also [a port for the Leaflet maps API](https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet)** | ||
### Doesn't clustering solve this problem? | ||
You may have seen the [marker clustering library](http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/docs/reference.html), which also helps deal with markers that are close together. | ||
That might be what you want. However, it probably **isn't** what you want (or isn't the only thing you want) if you have markers that could be in the exact same location, or close enough to overlap even at the maximum zoom level. In that case, clustering won't help your users see and/or click on the marker they're looking for. | ||
(I'm told that the OverlappingMarkerSpiderfier also plays nice with clustering -- i.e. once you get down to a zoom level where individual markers are shown, these markers then spiderfy happily -- but I haven't yet tried it myself). | ||
Demo | ||
@@ -45,6 +27,4 @@ ---- | ||
```js | ||
const gm = google.maps; | ||
const map = new gm.Map(document.getElementById('map_canvas'), { | ||
mapTypeId: gm.MapTypeId.SATELLITE, | ||
center: new gm.LatLng(50, 0), | ||
const map = new google.maps.Map(document.getElementById('map_canvas'), { | ||
center: {lat: 50, lng: 0}, | ||
zoom: 6 | ||
@@ -59,3 +39,4 @@ }); | ||
// ... | ||
const oms = new OverlappingMarkerSpiderfier(map); | ||
const options = { legWeight: 3 }; // Just an example of options - please set your own if necessary | ||
const oms = new OverlappingMarkerSpiderfier(map, options); | ||
``` | ||
@@ -66,3 +47,3 @@ | ||
```js | ||
const iw = new gm.InfoWindow(); | ||
const iw = new google.maps.InfoWindow(); | ||
oms.addListener('click', function(marker, event) { | ||
@@ -85,11 +66,7 @@ iw.setContent(marker.desc); | ||
```js | ||
for (let i = 0; i < window.mapData.length; i ++) { | ||
const datum = window.mapData[i]; | ||
const loc = new gm.LatLng(datum.lat, datum.lon); | ||
const marker = new gm.Marker({ | ||
position: loc, | ||
title: datum.h, | ||
for (let i = 0; i < markerPositions.length; i ++) { | ||
const marker = new google.maps.Marker({ | ||
position: markerPositions[i], | ||
map: map | ||
}); | ||
marker.desc = datum.d; | ||
oms.addMarker(marker); // <-- here | ||
@@ -106,4 +83,2 @@ } | ||
The Google Maps API code changes frequently. Some earlier versions had broken support for z-indices, and the 'frozen' versions appear not to be as frozen as you'd like. At this moment, the 'stable' version 3.7 seems to work well, but do test with whatever version you fix on. | ||
### Construction | ||
@@ -115,45 +90,25 @@ | ||
The `options` argument is an optional `Object` specifying any options you want changed from their defaults. The available options are: | ||
The `options` argument is an optional `Object` specifying any options you want changed from their defaults. | ||
**markersWontMove** and **markersWontHide** (defaults: `false`) | ||
## Options | ||
By default, change events for each added marker's `position` and `visibility` are observed (so that, if a spiderfied marker is moved or hidden, all spiderfied markers are unspiderfied, and the new position is respected where applicable). | ||
Property | Type | Default | Description | ||
:---------------------------|:-----------------------:|:---------:|:------------ | ||
keepSpiderfied | bool | `false` | By default, the OverlappingMarkerSpiderfier works like Google Earth, in that when you click a spiderfied marker, the markers unspiderfy before any other action takes place. | ||
minZoomLevel | number or `false` | `false` | Minimum zoom level necessary to trigger spiderify | ||
nearbyDistance | number | `20` | This is the pixel radius within which a marker is considered to be overlapping a clicked marker. | ||
circleSpiralSwitchover | number | `9` | This is the lowest number of markers that will be fanned out into a spiral instead of a circle. Set this to `0` to always get spirals, or `Infinity` for all circles. | ||
legWeight | number | `1.5` | This determines the thickness of the lines joining spiderfied markers to their original locations. | ||
circleFootSeparation | number | `23` | This is the pixel distance between each marker in a circle shape. | ||
spiralFootSeparation | number | `26` | This is the pixel distance between each marker in a spiral shape. | ||
nudgeStackedMarkers | bool | `true` | Nudge markers that are stacked right on top of each other, so markers aren't overlooked | ||
minNudgeZoomLevel | number | `8` | The minimum zoom level at which to nudge markers | ||
nudgeRadius | number | `1` | The distance of the nudged marker from its original position | ||
markerCountInBaseNudgeLevel | number | `9` | The number of markers in the closest ring to the original marker | ||
nudgeBucketSize | number | `12` | The size of the buckets arranged in a grid to use in determining which markers need to be nudged (0 means nudging only occurs when icons are perfectly overlapped) | ||
lineToCenter | bool | `true` | When true, all lines point to the averaged center of the markers. When false, point the lines to the original positions of each marker. | ||
spiderfiedShadowColor | color string or `false` | `'white'` | Set the color of the shadow underneath the spiderfied markers, or to false to disable | ||
markersWontMove | bool | `false` | See [Optimizations](#optimizations) | ||
markersWontHide | bool | `false` | See [Optimizations](#optimizations) | ||
However, if you know that you won't be moving and/or hiding any of the markers you add to this instance, you can save memory (a closure per marker in each case) by setting the options named `markersWontMove` and/or `markersWontHide` to `true` (or anything [truthy](http://isolani.co.uk/blog/javascript/TruthyFalsyAndTypeCasting)). | ||
For example, | ||
```js | ||
const oms = new OverlappingMarkerSpiderfier(map, {markersWontMove: true, markersWontHide: true}); | ||
``` | ||
**keepSpiderfied** (default: `false`) | ||
By default, the OverlappingMarkerSpiderfier works like Google Earth, in that when you click a spiderfied marker, the markers unspiderfy before any other action takes place. | ||
Since this can make it tricky for the user to work through a set of markers one by one, you can override this behaviour by setting the `keepSpiderfied` option to `true`. | ||
**nearbyDistance** (default: `20`). | ||
This is the pixel radius within which a marker is considered to be overlapping a clicked marker. | ||
**circleSpiralSwitchover** (default: `9`) | ||
This is the lowest number of markers that will be fanned out into a spiral instead of a circle. Set this to `0` to always get spirals, or `Infinity` for all circles. | ||
**legWeight** (default: `1.5`) | ||
This determines the thickness of the lines joining spiderfied markers to their original locations. | ||
**circleFootSeparation** (default: `23`). | ||
This is the pixel distance between each marker in a circle shape. | ||
**spiralFootSeparation** (default: `26`). | ||
This is the pixel distance between each marker in a spiral shape. | ||
**lineToCenter** (default: `true`). | ||
When true, all lines point to the averaged center of the markers. When false, point the lines to the original positions of each marker. | ||
### Instance methods: managing markers | ||
@@ -248,16 +203,18 @@ | ||
How to build locally | ||
==================== | ||
### Optimizations | ||
By default, change events for each added marker's `position` and `visibility` are observed (so that, if a spiderfied marker is moved or hidden, all spiderfied markers are unspiderfied, and the new position is respected where applicable). | ||
npm install | ||
npm install -g bower | ||
bower install | ||
npm install -g gulp | ||
gulp | ||
However, if you know that you won't be moving and/or hiding any of the markers you add to this instance, you can save memory (a closure per marker in each case) by setting the options named `markersWontMove` and/or `markersWontHide` to `true`. | ||
Licence | ||
### Local dev | ||
```sh | ||
npm install | ||
npm start # Starts a webpack dev server with livereload | ||
``` | ||
License | ||
------- | ||
This software is released under the [MIT licence](http://www.opensource.org/licenses/mit-license.php). | ||
This software is released under the [MIT License](http://www.opensource.org/licenses/mit-license.php). | ||
Finally, if you want to say thanks, I am on [Gittip](https://www.gittip.com/jawj). | ||
Finally, if you want to say thanks, the original author is on [Gittip](https://www.gittip.com/jawj). |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
339629
5.37%8311
3.72%0
-100%214
-16.73%