From bf760eef743bc7472dc913a4ae24fc166dd00c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月28日 14:50:48 +0200 Subject: [PATCH 01/39] new cluster image location (Google switched from googlecode to github) --- src/datalayerclusterer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index d49a9d4..71734c2 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -1012,7 +1012,7 @@ FeatureClusterIcon.prototype.setSums = function(sums) { * @type {string} */ DataLayerClusterer.MARKER_CLUSTER_IMAGE_PATH_ = - 'http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m'; + 'https://raw.githubusercontent.com/googlemaps/v3-utility-library/master/markerclustererplus/images/m'; DataLayerClusterer.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png'; /** From 4118eb5044b06fbbc3642b0090cc66b7a9ef8a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月28日 17:52:16 +0200 Subject: [PATCH 02/39] fix errors being thrown due to the new .extend() notation for the options introduced with commit 7e86e3a4f731d58a30dbb88a40bf60fb2a939bbc by jesusr (will have to look into this later...) --- src/datalayerclusterer.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 71734c2..220e0eb 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -58,22 +58,22 @@ function DataLayerClusterer(optOptions) { DataLayerClusterer.extend(DataLayerClusterer, google.maps.OverlayView); var options = optOptions || {}; - DataLayerClusterer.extend(DataLayerClusterer, { - clusters_: [], - sizes: [53, 56, 66, 78, 90], - ready_: false, - map: options.map || null, - gridSize_: options.gridSize || 60, - minClusterSize_: options.minimumClusterSize || 2, - maxZoom_: options.maxZoom || null, - className_: options.className || 'cluster', - styles_: options.styles || [], - imagePath_: options.imagePath || DataLayerClusterer.MARKER_CLUSTER_IMAGE_PATH_, - imageExtension_: options.imageExtension || DataLayerClusterer.MARKER_CLUSTER_IMAGE_EXTENSION_, - zoomOnClick_: options.zoomOnClick !== undefined ? options.zoomOnClick : true, - averageCenter_: options.averageCenter !== undefined ? options.averageCenter : true, - _dataLayer: new google.maps.Data() - }); + + this.clusters_ = []; + this.sizes = [53, 56, 66, 78, 90]; + this.ready_ = false; + this.map = options.map || null; + this.gridSize_ = options.gridSize || 60; + this.minClusterSize_ = options.minimumClusterSize || 2; + this.maxZoom_ = options.maxZoom || null; + this.className_ = options.className || 'cluster'; + this.styles_ = options.styles || []; + this.imagePath_ = options.imagePath || DataLayerClusterer.MARKER_CLUSTER_IMAGE_PATH_; + this.imageExtension_ = options.imageExtension || DataLayerClusterer.MARKER_CLUSTER_IMAGE_EXTENSION_; + this.zoomOnClick_ = options.zoomOnClick !== undefined ? options.zoomOnClick : true; + this.averageCenter_ = options.averageCenter !== undefined ? options.averageCenter : true; + this._dataLayer = new google.maps.Data(); + this.setupStyles_(); this._dataLayer.setStyle(DataLayerClusterer.HIDDEN_FEATURE); if (this.map !== null) { From f52d0492fc331e00419f0393d59f7ad7f5ed6d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月28日 17:55:28 +0200 Subject: [PATCH 03/39] features of type LineString are now working --- src/datalayerclusterer.js | 71 ++++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 220e0eb..b163fe9 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -367,7 +367,19 @@ DataLayerClusterer.prototype.setReady_ = function(ready) { * @private */ DataLayerClusterer.prototype.isFeatureInBounds_ = function(f, bounds) { - return bounds.contains(f.getGeometry().get()); + var geom = f.getGeometry(), + inBounds = false; + + if (geom.getType() == 'Point') { + inBounds = bounds.contains(geom.get()); + } else { + geom.getArray().forEach(function(g) { + inBounds = bounds.contains(g); + return !inBounds; + }); + } + + return inBounds; }; /** @@ -395,6 +407,48 @@ DataLayerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) { return d; }; +/** + * Calculates the bounds of a feature + * + * @private + */ +DataLayerClusterer.prototype.featureBounds_ = function(feature, extendBounds) { + var geom = feature.getGeometry(), + geom_bounds = extendBounds || new google.maps.LatLngBounds(); + + if (geom.getType() == 'Point') { + geom_bounds.extend(geom.get()); + } else { + geom.getArray().forEach(function(latLng){ + geom_bounds.extend(latLng); + /*console.log(geom.getType()); + console.log(latLng);*/ + //iterate over the points in the path + /*path.getArray().forEach(function(latLng){ + + //extend the bounds + + });*/ + }); + } + + return geom_bounds; +}; + +/** + * Calculates the center point of the bounds of a feature + * + * @private + */ +DataLayerClusterer.prototype.featureCenter_ = function(feature) { + var geom = feature.getGeometry(); + if (geom.getType() == 'Point') { + return geom.get(); + } + + return this.featureBounds_(feature).getCenter(); +}; + /** * Add a feature to a cluster, or creates a new cluster. * @@ -404,8 +458,7 @@ DataLayerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) { DataLayerClusterer.prototype.addToClosestCluster_ = function(feature) { var distance = 40000; // Some large number - var pos = feature.getGeometry().get(); - + var pos = this.featureCenter_(feature); var cluster; var csize = this.clusters_.length; @@ -608,14 +661,16 @@ FeatureCluster.prototype.addFeature = function(feature) { return false; } + var geom = feature.getGeometry(), centerPoint = this.featureClusterer_.featureCenter_(feature); + if (!this.center_) { - this.center_ = feature.getGeometry().get(); + this.center_ = centerPoint; this.calculateBounds_(); } else { if (this.averageCenter_) { var l = this.features_.length + 1; - var lat = (this.center_.lat() * (l - 1) + feature.getGeometry().get().lat()) / l; - var lng = (this.center_.lng() * (l - 1) + feature.getGeometry().get().lng()) / l; + var lat = (this.center_.lat() * (l - 1) + centerPoint.lat()) / l; + var lng = (this.center_.lng() * (l - 1) + centerPoint.lng()) / l; this.center_ = new google.maps.LatLng(lat, lng); this.calculateBounds_(); } @@ -665,7 +720,7 @@ FeatureCluster.prototype.getBounds = function() { var fsize = this.features_.length; for (var i = 0; i !== fsize; ++i) { - bounds.extend(this.features_[i].getGeometry().get()); + bounds = DataLayerClusterer.prototype.featureBounds_(this.features_[i], bounds); } return bounds; @@ -725,7 +780,7 @@ FeatureCluster.prototype.calculateBounds_ = function() { * @return {boolean} True if the feature lies in the bounds. */ FeatureCluster.prototype.isFeatureInClusterBounds = function(feature) { - return this.bounds_.contains(feature.getGeometry().get()); + return this.bounds_.contains(this.featureClusterer_.featureCenter_(feature)); }; /** From 3e0d6f118569f06d403a9e05a603114bf037a8d9 Mon Sep 17 00:00:00 2001 From: Connum Date: 2016年9月28日 17:58:01 +0200 Subject: [PATCH 04/39] Update bower.json Changed project homepage, description text and keywords --- bower.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index f539b13..1ede1b7 100644 --- a/bower.json +++ b/bower.json @@ -1,17 +1,18 @@ { "name": "data-layer-clusterer", "version": "0.7.3", - "homepage": "https://github.com/nantunes/data-layer-clusterer", + "homepage": "https://github.com/Connum/data-layer-clusterer", "authors": [ "Nelson Antunes" ], - "description": "The library creates and manages per-zoom-level clusters large amounts of data layer features. Google API v3.", + "description": "The library creates and manages per-zoom-level clusters for large amounts of data layer features. Google API v3.", "main": "src/datalayerclusterer.js", "keywords": [ "google", "maps", "data", "layer", + "features", "marker", "cluster", "clusterer", From 137363b0df133f0e765d2ca79ada6c8a7f816bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月28日 18:05:25 +0200 Subject: [PATCH 05/39] added cluster marker images and newly created svg versions --- images/m1.png | Bin 0 -> 3003 bytes images/m1.svg | 75 ++++++++++++++++++++++++++++++ images/m2.png | Bin 0 -> 3259 bytes images/m2.svg | 75 ++++++++++++++++++++++++++++++ images/m3.png | Bin 0 -> 3956 bytes images/m3.svg | 75 ++++++++++++++++++++++++++++++ images/m4.png | Bin 0 -> 5705 bytes images/m4.svg | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++ images/m5.png | Bin 0 -> 6839 bytes images/m5.svg | 75 ++++++++++++++++++++++++++++++ 10 files changed, 423 insertions(+) create mode 100644 images/m1.png create mode 100644 images/m1.svg create mode 100644 images/m2.png create mode 100644 images/m2.svg create mode 100644 images/m3.png create mode 100644 images/m3.svg create mode 100644 images/m4.png create mode 100644 images/m4.svg create mode 100644 images/m5.png create mode 100644 images/m5.svg diff --git a/images/m1.png b/images/m1.png new file mode 100644 index 0000000000000000000000000000000000000000..329ff524c59e4ac6ba91db135e2257106253d7b9 GIT binary patch literal 3003 zcmV;s3qPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXJ| z2r3bh0E3kP01G=wL_t(&-o2Vzj9q7S$AABQX2xTW$M(c=Vw^w|nxv@{1u6ドルLp-Ky> z+Heu!fwodn5%56Z1ca1j>?!8rnn2T)Z6sCkHxDm5yJkb+93CZRwCC@~?$Zb;HN z#N*iG8IR|F&i?bT*WTxwiOs&+2FL&b4WNMy(g=2e z)B%iLzLtLldDgqXsSQnG{dczt>WP%^M}bkKp)e#v%EEwnng|2I7E*!TMe1U&AT7|; z%0|~!>+(cXrBNWHF<@sh0k)ckac$snh>vOm;ljUz72qteR4-cp0&c2NJ%ci&o!C3V z8w^8GT>98JxyH7vE}aBCIYk#+-Fgi;k=l}C&{2h1aN zE;^C6!QO_n1ABXKMu~CdSOwB)n(7Q{nAYSS*aK__iu(T2J00u=qie}4FNogkQ4Ay z?5luB!h8GTc~dmi*=YC?n{x;DB5*5kT_0E!VZ zM}Q}>hk=`ru8y&tB+K9Uhx8EkKBRvNuZ$%7r%B_!>|ozS zo;|asXjw&*%DQHEpjekyu@qEigr*iRTE3S99svGj)Anb%D8opf2i`x>qPmW>k>IH- zqU|hrRU>{h7Z3OgU@p+?ccCS?_GGyjpFnDFx_DFZ#gb>PHmnkMN@9C0O+7{1`+{bH zv?$tcMERgdelG^)c}+0#A*B0(_eA0MH1H(SaqK6M4sNnRRT$q(Q-2>`7z6$^82?O^ zI=iC2dmswS9g!xAO3KQTO}>nDRr3BUO|4+x2D}F8y-0tFy&E{V>0av9Vt*F~eF69? z(no>6#$Jl@zl*ejeHC`6mKGq(fZ?P7>xQdGlE+lxcufMoPXm9;R$W#8Y8AI2HKKkh zkZwUbSM$r#kg;I>bsEVM$%>?q8w)fmnL^uR*bVH84xL5%GbFauuyU_YMKz#!{&*5a z4z1DYT(XM{=)~AAupg<2syhb8vndie2wn12lypikbv2k>=8|0&h01}?B?Auw<55sk z#g~KCyASp+=Gic6wQ{Np6mi?gv&ke>GhkaNJ6mj3ドル*Z`{sOzt$POnmZtNpjiQ{|1T zJy*h}!c#lqyE8F}frHrh#)5cDJXB8d2-3FjPKGp1+oQ?iE5w}!eMWSV6q6OLVhET? z+&LbvM`I8p-3T0uB2io5AtC)XRI1Tf=mzju%%@uX4Cz#Ci3&)_!nK{4oFkE)NhIYF zTEztR(OL?I8-ab;-`+yx`v~@-$Vm>B_h-P~B$pX`7HPQF@ulS~;INwvxgC2x#7f0? zHvmu6pOq5x`+@HQOSPoDd7e5#Q$I%fo205fL7qJaT%8y`MV=Mmsp{V4jHZm;tt~HO zxPyYsmV531(&>P5qMvjFFpd2{qS|i8Ug{z>X%%k+u8eUr;77EIUBKQX(H^H&4A(id zG<30o^rzwyjlb(fvwe0bm6u(zlmewx<{yb&fs+u5z}nks*rg+tbxrexakasvn#gmc z*GZ)Dh&Bb&=u}3{C%|A}2XHKuoyvnRBhMbg{&k|sT^D-9U&ZI?1lNXi$x%afY(paq-bt&t75HbQ*}8=zy&CvO z;CAf)NSJ^J@J`we`YiA@?2pwvv*dlhqp5qbZ>!Uf)WCiU7_Zq+Io2Y1MwZkn(@6pr za0T!T(%E=lAiX>k)bA&BWf?dW8pZpt?+hT(MU!){R|W7Z-~je_kvRy4&XgVO9?P6V1Fx`a<5gk-vv}(*gi>juw0|Fl`O5k z@_Yn)Z}RY}ZF30wn!3YLhU$MJ9mf7p7*naU0!{(X0{eL0qn$ZZg1APjVv7ZIL5#fI*SVgrMFOX-C()LUG_~{w)Z1H@S-mHo3 zR2<-_xvzjr@4|j45^c3t3gfgy+mswveiizr7>$j4GL88Xip1}D<|klcd6qhk1ycr@lp1t?=}{?0_l;utyfzl1kc7h?gue948t(dz!$^6^ z$aBE|)70`MsL*f2+b;ku^6WDq^4y7ZZD03f8tLb>y)RDA5Mka0ruubDT|&WXB}MC0u+($&#Ss~+44#&IkNi&sdbt z=>X4>XFrHA-k&tatNmOW)FTe-I2=-f!4*`ezRFN^Alyu7a4(JK+bH%=$+P1Fj6DI| z9(!X6I10QFIcZP5e|il$M^btxgXrsnJ-IpxmuKDB2UEcXRr4;#0V)_7SY;}`xf6RFdpKDr z)m?de5@~i&gErRlEhjY=q;j~9`M&HvfMM0)tn?Jx64HF#by;IaaqYdwvWZ=Ja4z=R xi_!k+Bn*4dYdJi9ドルNNgq0E6-n$U0i2e*pgzX-8pBlM(;`002ovPDHLkV1iGktbG6g literal 0 HcmV?d00001 diff --git a/images/m1.svg b/images/m1.svg new file mode 100644 index 0000000..cd368eb --- /dev/null +++ b/images/m1.svg @@ -0,0 +1,75 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/images/m2.png b/images/m2.png new file mode 100644 index 0000000000000000000000000000000000000000..b999cbcf69441f194768157d79a113b0fae03e3c GIT binary patch literal 3259 zcmV;s3`FyZP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXJ| z2q`s%E;NS#01P`xL_t(&-o=_*Y^7&)hM)KM_F)`*4xUNE_0hLw-_KzSa zVYuKVxBwCf3AG4Ss^F#)>P1gdE2Q>!-(Glqdp_^j9((Lbf63a`KK%c;zV)tmt@W-?T!O1=fP`!muQfmteL&4- zS)#J3Bsw;hu#Jmyf2w7w)5lOMYu^nFc?Pk1AP*G_W7?!5QeY3vGQ3*q75tq z%Qmf|q`ecHt?hUz+wEbS%Ya(CZy1t{TK73Nr0<5&25hqo%ozib?rzq%@scdnu=l8n zWlXmCrK~UBG2FgK5XJg)9DV0ZRe9woR3s z4;1088qjAmATa>+r_IQ`nG9XDnJr3|-CWs_grIF3*kjz7%}^FoibYAH1I*ei6eUy* z`hoqxUWt(ui{Y^fn3b5cnJP-Es(~a%WAl{Vp9}%>Maj&Lc`zAQHKqIa)e3N$qno~BXB*uXSdj%y~TsvFyqQtc7R2PD*HbW#;gF#?l zK+CbX0%%E0*%T5t*t`nZ8~3fW34A2)XU4W|CV}5eoDA6RjpsRAb6a9UbvilWZP|n2 zxhpfTIY?!uD4BJW>uqj{l%O86=`JY6X7|Av`DEm>+IF)j@l#;hX1~;p`Sjh&X1plz zMR_o}-%WM{BN<6d22|f5anpm{p3nb*xtn+ovkxcfclos#>x8%WNQ?oyfL)RNbITr~vpHLooZD6m_8J=3L7O`gv=UiJ%mK$# zXWC8v#OA%g?hLkh^6!PHfZy4CvnYAeO|G#y90ZdXIuT6+SV%;5p;V!*RxVY8TO4B`8?1CQ(EhXh*DF8I0Km+F8ykfMtoNZN3T& zOT0M>hDTLrd?VJ}cBz(m^7RtG1&-L7gcZbHM#AK&`ia zwV49`Av`xyl$?4+YSCn*n_Lh4WmZQPVc?Sz57?Xn?g;4q0k9jm8Tg6B0`Q2<4<()g zZU_F@=7#XHqa&FC{-P-H#T4AzDYjFxKWcL{*mNHFGVl`cd5ODiUY*6-syrC)ElK*%XSw}>{Z-m%SK-%aW^jdg0uiwf~r%b}v=#KlS|0E*^uA21}*iol)& z28)uV3*Dee?vy-n_ga3Hc4f9+b(dM)rj2oy!hT-D&MRf7)j zlEf+1nai5q+|bVgySEy1n}qG%Hn*wHvGoj#_^$;qodL!|=qiB4WLV#-(rZO;8H+M( zpUqWOgX0o!w|T^7+)dii!rYy!WIO4^DBnH<+?%dq8qlxyoi3rlfvy7(bi($@rz0x6 zFi=Y^N%U5dSO%_0ZA-+}1aPg*yMd8ドルHu^0iU=HK;f>LCtXgO9dWy}^b=t9v zzNh16gZ8DjF2bN4{Uw=n()+!aiCx7y2GO(dcI3vCgs+R=&hQFdA&>NDZ!NDC8sUuY zRm;n*jBFNq=z5pUe1d2|<^b^grg{v1^s}mk)?%~!fm-k@c98dx*nvars>)`CRhKde zo=~RGWOUrIc?0l3c>7WTHPQY3zztdFF&QapPvVOxHJ9wwVN(Q_ongHn7!Q>!$q!?p zyCkKn9bNd-5|fvLMT4y+@ucd^0XHQ@&St`9G166H)(V*Koop3xqjq-Vn(8dXR3~Yi zBfxm%m;}+A!r%ulNq$gwp?_U9xCyu>^XLl_wU}Wf)vXmoRAj1MtB`PfkW1ドルz7&;#D zmTJ_&P?`I(vDTfyzs79hQo!_S;C|p{{ z3`*?~ijD$zNj#irdXrU-vjU?6z8H$!M}ap+SEEP)|KcVGZLW?Gk;HU#H+wlh-#4&S zlq=0`a_LN#3vIK@X2#~l#7tq#5uj=F3yHyiPAlZT_u0G)_=L@5CJlO{R1h)!Gl^2- zuYgb6yfemUL~(zAR8wz?J~eI537bBNfz)(oUC;TlM)eh4GgStW2S=_l%h?&RktV!+^<@xzvt*0%jfo(sd>_+=T zmpNf`V|ce^bF0m$#8+%ei4R3PQ%Nk_+$?bm@GaH(AK-u2P{D2l{yd)7g;hYVWAinM zM{WKn^{5Hbz_AF8EAoR15@*+JmsIZxNwZl3b?nfX>Kq47r}=|K74pIDz;nM z2}fd}YOuQ~`B5lW3Eq}C0Ng1tC-HgUzhhQd#ow>i(=dyYP9*4|I63i@&3{PzO~mj! z0^Z5^0yqi61ドルY4I8O7vxHgnY4QbRU58jh0{&J?!=;H=G?fa~I1On$)NY_w3PVu9Bd zC68<*9#;+cbc*tn35nuln63{ptflll@n?jkv1laj9;;`hl2)%ystxj{_k_5uj1+ka zbtc{9CY#r-HAIQ;KPT~o>MNUYf%*Sl;QBa<(t8ly9t|bhc){j!icr`<6z86ohi?lp z3|1tuFHS&I*{r=4d2Wx*>*Mf1)iWuoNql1?1GCM%E84V9f_~J&RUA?{9!4I>DwwpE z8P%CvZ%9*Yw7W%#Lo%Gt6}UKCc10xq9|I3Z67J7fdE8z(q_c_P&5J#oGXR{CcqsDZ zP;6q69bD)GrlVI}cLKGpfyvbpsLt77S0~jim~>R)fX$TZG;JQTIU70Zv~=OoS3rLv zEBhuSzE_ky3@idy2Gf$CxUiWhO6IfEA3JggVL0T&B=zTqf0lsxnCT4xouXuUllR>f zaG#b~w7E=TIFRCOc9@_=WWsY| + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/images/m3.png b/images/m3.png new file mode 100644 index 0000000000000000000000000000000000000000..9f30b3092b237db28bfd382cbbad51908287ecd1 GIT binary patch literal 3956 zcmV-)4~y`LP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXJ| z2r4YfXwQTI01os?L_t(|+QppRkDT{a3ドルN$p**Dv}w&S-XPE*@SlQd3B1ag};2>KdS z3R0`Kf(8}!h8x;)!zEG5ドル*PdhhzklW6{ScNDMCV2qzDpHq7qaJPU0kXV!KJ~#36p$ z*O{H?aB;rBea<`{$ghuydn9xixj?-0_dcchzuopUNOgc;$jOTr?a^X%GF|X4NdJap#A~2|uv$fZ8(>%hV156SpKOs+WZTyP; zmlwRXm{ITd!Wnvc^p@M2^E6wi|3Ir=7h$o&2K1tc4qw@Y(LLgYcrs6h7qw z3&5Pi1%pclwTv?fZ1s5d>~U!ywZmKmm)D9LwpuT+VKBUf0KwwE)(VGt;F3^&o5OyC zy&lG^lDlJ9$Blvo7l1c_GxDeoFk>*4LDhy2V$s5ozFLFW8oglL;xxMj#PVqr56~ud z0e2J6c4ドル;48^&dmW?}r=;4R>U!+9BeTA9h(I0E!gsI0~qw%U-OWrWsgEFpJvxD~j| zV24PNd$Yi71hZD+@D@~LJfPtv9pG)?6@xcL#-H+O z5~xb2_K0%5B^NKJaU6I_?*3teUjlZCes_fav@oI=bD6-G8hG8|-+&*hSi8^RVDh|t z&pC%5iHdu*h7`JHLX`}1zeQH?#2rlF^sMObqYj^z2YHKZdGey1o--EYQ@;#6Zt$cB z5dS=|tr#R>)-i+Q^=1;P5ux%R;j(4XCxDj>4mmt#a6mPIw@y4W&OypR-4?AT5iaux z#TOmEYjD=#FmOk@)^USl>nXt2U27x|eSnDf7d-O(CxI_0WesR*j7qDW6^LC1=7`wt zS%lhc4pRc<8d)yxojmkwwrub{ho@!il%^#qytljbbr){}iu80fgi@ zEidR2IsOTU9}^V&QHKu!cMu!grj5R#!tAU^mU-IY34!K^9X=tiS@uTdS_J;g;OI>k zW)fbEJABsQ<19ドルq;ip7a&j4r{c+}va2y3!uoe6g(sqsd09qaci^~(fj>8bqw2ZJA4 zW1O4&@Zq0-vxdK{;q!GECgz%35(?jh^8iMcc1!=mC3dbjsn58uoeA1Kafa zmU={LuCH=&heI{IQg4C*T840kf0y=g&4a2(@4NC)+qc#*R%sIxdVhzeAAtphe%RrD zhkdnr6jjRriA#J9xO20kR`)&P@I|gdqVnd&zPfdzmQAYw+#xc!rEvQ$!+E@?5A4x);nBzx*dp5B>If}>qq6)t=RU;EV z;BcqGQy#?Y?o{&krJVO7ドルaQx^a7U@s(#tM4N~~|uqYl`wHeSa(J;ZI|;EThqYCtrmP;z+DylWZ@groAl+6hti{ag z{RgcnuQ&Z+P4DEeYLLFgzhy$vy4}Kq2dz*YC*Ct8=vGMsET`{Pgu%|1r}Wi8naqyj zaq}?`&GKf@`}-vEEhhEawWivKH*9JWUD0=~u9jo%%B-_2w4Ep?q89x-@~Y}4*a4k* zN17$cQ>O``xo*`$KFVd+vUs;htDVFfYQ@|5h!DGx#y}#^s5b4W!3%Gr`C4VYIfX)b zp(Ay9K>&5Ls$VzQVJ!g6&FE41@~3ドル{$dR?MV@wKC&xgBFr3rJx^#E&h(Ar^NR>M6) z_F_t%D?Qaw*Q%_=5Jf#reT96}aW!k^8CIjW3N&BzCO&V77wAU+rKVH2MPLSM6n9(U zeL1V=*(3U?G2=9YUM-1%31S%Jw1`Q$=@g+ppX3In&1ドルbV7u3+n#o}dypJ~0F)`FJQ zYw23kidbpU*D0o)NImC#ILl1R@~@`pH}u*ej|R*;egmX}g#5307~=hw$^`Xd-}DfM z$C9U(Hf1+8hRkAcrKv44>8K(%_q<_ol`mbs8(=j+a+sjnm{r?&(aajbhbihp)%`n( z+ssbP12vzx5?jgPIf)~ zB7vG&h(ezMo&mn%F-THd(^`DiVx;Jvmg^hvd>z==E%;Uj-;5A#R|Y^!JFOT;Ma!HA zcjzRdvR}Hy_vEIfEbmF2`F-G@fiF0`-k!W$s{!q*82QWPOJRwb<_{zx;r)4DBUeA}X_{6HxKw2z*_F`B{E%!=ogT~muCo68WmSs?MVz$Xnpr_@{G)+CIaa(G(O*&Yj__aTFaTr(7UbQJ$TC1U-8-uJN7 z@ea+xI|QzM-(o!Ah{L|LwyqB0S*6!)3{Tt50+-S_RFgLJ;-vUOl?=2j`n;DAOUGOG zJ6XZe8a`$rv3LB+A@ZJ>>ho1JamT!I)yxx}UDF2tOJoA~Cch5|wEhS&=T^Wv;6;JwTf(RV z7K5w3RDe|yaZeF*T3b|k7BQxz?Mqd(AFkNAt)f%DUB;+~g)A;9e;*>cNn6SWb)(WM zI8UJWS-Isk=b|<4jr^p7)t7u#;8rsho=?%pilnrknso!vx@+s7qvc=r)rl^*t0j!_ zYNsaP>Xe6FIsBq@io*(xfen9or4&8`i>cTPt7Mjw9!1p@ac@g1T3JI^zdaY67Tqzu+jV^q=&wveX% zX@=4J;w7gf74AvwHhaPx@l7DF-{s`HtLd~F3Is=s!*~_}V;yzJg2DW1reo{j`G(shv7Hk@OsbtX-s)zRHL;4CM|OH zc&jd1=`u;}UeM|E*K;rJbV73tlSNNp*!Tq(dKLv>Z>>Wb>r$K3iB-TB4Uc!z6uvXe z?H7o6_q7_Mr0>KDh9><}m}ahsi`vpuxli?ph?i+kw`9zL(iai!PM6w;n-PVdbqj zFSOhA1(#XLjLT~Yg$*%8%NQ8+Ykf6Yt$=Mxt3}xDuhmsk#w!0yK$XI-UoeRDzXx2; zywbW2LDut|QyA1?jCmPitNygS);LU?>nqc^hC$}a5PH?>|6fm6>Hh&6nXpifC1hg& O0000 + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/images/m4.png b/images/m4.png new file mode 100644 index 0000000000000000000000000000000000000000..0d3f8263bd0528e34388c722242b9c8bd250fe18 GIT binary patch literal 5705 zcmV-P7Pje$P)KLZ*U+=)p!fv7f#TG` zAxLl%!EgG`&*5<32cu%wory0{l9a7~=}6b}76Isk~1IN~P)K3@?4 z&zpALY4A7Z!>t0&I7qECf*j`WHIYAjW_h^ivJu4lvb8y9VL`DD`rG0ZKvcZ$L@8 zo)6*!Fng}&gE0q~LGCMnsiR8`P)pL0I_sTkS+y)n+TA3G@GlDCw3FVN#n+KY{!yU zS&J#rq84&EoSA2ドルF#kgyUX3UYMT+SK2vM9n_kQ31JL~_P@3WGNtmiTe$FKX_peydn z->Lo%)PMpo{aZL*2YQBe0oP9tDD!h~G@d*r8ドルR|1_-vS~&emK7Fak6*hPnW=1c;@H z`-*kw|8(`!(NFJoPT+O{R~XiCykS@)q|307tRk;?`rViQv=TUTydlXefzt7wVI9W< z$nyUyaD@PB7#7l1OVZ`(r>v@U0?Y?UT-;Er%%LmE>!hFVW`J1VM8HOqI@TOF!`$%_ za0^WYOTRCHBoIph87ge?d#JFU7x&bFx?v-Z2Y_w;^fnT}jRY=i)VM%v>i?zVAyomM z$soHvbHhwdpckL%yXJV27Nz012Y~Tj$IFU6EMgpvF9Hkt=~X0nOMx>1H5say0EtNk zSYgk4K-;iIU{MmXLM^MFP~R&It2sUbjK~bsWh-5PXX&+c&p2=uX#F$*R|6&u8%;(& zO9YOGN_HKe2j+opvSp*d6fmLR8x^%O*{T*W@Ax&t<{y0bscc1}4i2}nuh%rxa&y~f zjyHihsq&TpH>$~wgbkBqS@NcC*n$SymU@l@2Z8O5j~g~3uUk*?oL6Eiml{VHvZ!ay z0v8>>V%QZ){)Ay;6)zm>U(;uW7IEQr0%yReVcX;Y2qT?huu{^|k~$W^USOwTJArME zk4cRHN=?jf6vRquEd|MohRp+49KQ_A8g|L?SxM)ZY+TK-G8~Sc5#X7?^UVgX&`Y=J z`%AHw22o0~7J!c9b;tJ`whPz~?2uF~#=Yse*~tu=NLl%}CcC;zs(Bf>09=$LUeoi( zr2d7z>&Y(H_4n2=Ue|_9xkA#WG-5ドル?R|>F}{=U=kBfvq~pRPP-Db+4K`vO8-S4f^~ zL5dW=SxTh>h;SNB+0b)_odzz+D~G4ArBJ2_+Y4OL_tpl4Gc&BTO-Rj(v_d9hKL;#0 zz704E95!qM=nB;Ewg|L{$rgcj3L837r<^&a$nza=z(|bqs_qoudm1=%!_enj%b|>D zY>n{v;kX3c+*+m1){*4b3=8{I(@WdHtm7TSjskZ&J{7AQph-ePH5LrJ2+TNs*|2#T zYu9&nDZ#Hfenk$!lINW=Y&WpS@iE!1ZY)f)gI%fZ8Q@)wd54}+DukJL{Ms=2?s|Yj zli4Oaw*XucNXH#N2y9ax8j-z4j_UXf@Qz_;C7qMNe#7=Tz6Tf=$m0T`2Fw}O1Lhn* zC&@l5DQN69S8P0zFT&4D!g<~?2~%81$@h}oty(?p90>Iq#gwBaD1=q z*kT0TvcZdnO*{T3aMiG;<88xwk4da+d{)-jq%&g*;j8#ajch8-e5i?3;u3^4!2q6t zvNxpfdDW{X+t?JiOW_Fc9`Iw}B=EL8>QUehsdeP2rbW41&wh0nXRsNWL&Qm~WXEb6 z>}3IV2G|4qg5!?@hoq)$!`kV4IbO>Iqf6~qP)<_cdky<2fbzbhykoxyvrtk0?j^np zy<ZwDiiSd$!z zgfs_JG9p`Ll@u<2ui^njvp_qg+oqy3sq1bc6?)zo;0<}u8#2sig25#gx0>A>035Ox zClrF50e<56chnw~a|$ua1v-ejcmlsc@t>E`zo_ptfZdMoko}x+d_r-d%kXDASd;yI z4tUD(ONQO=_#wr1Cj+##05^zIUpH{8Tw`+eErlh&2z*>@Z712UQAy7<@fwu8<7wk) ziz#j@q`53F*wts-fCGkoL=I`Mq_35N<#^vkz;|rwrhp4d(o3&`tjp430`6uao$s+5 z;MajqDO+gkd!s;868T-nUy@WEa{MmffYPTOhHZCzpJCIol~J>Yo(aRT@_@M+*@L;`G6 zeAP(L49+f8_zmDif${_3bxHSwiUY&w&nb6pNn$sSwH;RCZl=QNp1r_hhCM3k=#t~l z%k%9p>D6 z0)MN_;i`DL)d8qT_m%@!c~K4cbHl!@u%H;W za=r%XbE)K$z@JKW*DPtn0#|IP!5sXe8t_|&eZ}!3saV4XzuQEVeg*hVdGJjw8s`bt zHu(7?z!zoH_H!F9u`R!+aHzi7zy(2>D@_~m(oX55brR`)cNq3* z;Mr778+45WJH-k|as8@NJPfOgat~^&9_GQ*~gEfu0=J=h;Xo^IVHWH%~})oZpxiFBFDe~W58xZAKpVp|Uyb`02O*xQaD7rSfg2yQn@>VkznD&q7s@I{%O zNlDm@a^GpGVI!3&>mos~M6o?NxT1ao?-_LSXbMqGe_v4F#%_7wXrYSgQA2?4TqiZP z(bqP^jye8{^4PnQ+SbLuye*quCOcS@3eU$nBQ4Ng{`!^lo^MK4Q{ZOEO#LFewu)j z%^sDsWmS%TJQQjTvXxe`e=C8jc6~=TPi56=@<361j#abgwku^j%(hr#wo`-;e|m}p zMm27eYF-n?zoHN>EQ+SFI|j3<}|n+ zY85;pdm8BB3S|j-g-lXqY7OnAm9VsDVXl&|np#vFj-PgXMxyUqq?)ex&jLSIEw+|a z(hcj-@9G>^=#f>VRJk%z((AlxH^57iT0+$RsC(s%ZjwL}^R614M7Kk(;UYfA91{pq(;ztB|1k_%Lay((MsB_B;rfe zaCJ+PB(e@nIet+M%eiRMq~C?~p5s4I<21kbu1f*yjn>mDb{@D-@#m6#bNs5bV?sH3 zfVz}kvfN-DrpkeC7{hMGV4?>RhTgDR@{+ss(DRO8t88LBCY61Oerv<+&r4u|e%-l| zD<3_qm($y11;z{nzmy=zo`j|=8}3t{b2#ftczy&_xw1!1?7^(#t(1dx68h)feikxg z;-eRL2k{MI}t>gv3QB zQI$F#5j!v^Pa6?-6F6$v8N+_U^>xmNwA1BD!@g^`TT}bjx9Jz8qraq0ドルG;%vv{&!fvzh>CG8la1H*ati&p6idp z!`{Zk{<7nl;rl0zz5wh~-dpi34}o{z=<{w=og_;+ekawq3ksegc+@{hw&9lxyzt*f-j6ga- z4SUJ3{}I8tTVT0Dk2wKLA~HP9y93MH8F7439~3E}t%+Ls(D7*nw z`h13d*JcnIQBJ5yV%yb9er$~OO3pg27f&k+54wHPu)Shbrw#jOrD4%Dz2Nvx;Fo|$ zfWHF1>iEgTvf6s$ybM-)#{0nUJN|XYk2wCzh8;3&L7&$Y=syj-CTjlx{mm!`_n@L7Br_p;?BF zrV!1fqV2H%RX#5EvDcG53hw=i8k6^H!a*O6s?ya`{c4V&}aW;*gL8& zeNH9DBLe`r*>#?Nd;YV)-zYSCSfK4!bK#=K(F0z0{3+S#k1FAB$syDgub$T5b_wWS z8rNbjF;@3-#n$lc*}LlRSEb)~8Fp{V_aX}a(6AplzQ?dHI{rzi^xckAzEDg#bC{(btpX1+wf zcchc{a>f4oYlgk9d*75jIV2!IY1mN(p(O8i7WlsEqaPS{S+&rgH|%~%buU$_qHW=n z+EG1dFPCW|lPRfUVDM{Kv-5W7m2*hjjTlvR;iR2tj^S&A>*lINg7s9!LIr=J^@+{}HGO-7~ zBXH~VcMcYkjBiL%ekIW~q25iFd)Ou&{}0)v)dLrYmWfN~uaPBjb9|I}-Ujbx*04tSdyR z13v-&!SN5&n|qh;?WC>fHTrF+?KOlUU8(sVwdQwInX@6gHtqPczzgz%Cqz6TK*Hgb zX+vD0dDYV%`&H@RV%kk5) zS1kqKXXtlKz3lkBGJ^ZmX%)u=6)D7+*0^Wok()`YBA{NRzfX2xaK4s}zM~2lo9QG4 zlL4OBN;f1)ccc|vYF<%5jticqj0s(iuoi3?8tpo8s~sk?re5f=%^>>)z-tOmN_ot~ zh8;*Yb*W;dN>|WHR}(#!k1Z->*DkZXS#Mpy1$f&s%cO-+1SwlUhdCQ?A2om5X5()`Wx=e4##grO{SNJ}j-B)R2bo__>@%f5dF@`NtRCKRKDV0Rxp82_8IVR-Vud0|cEe7R)80Bb2UNG#Ea#_Dg{ouOlyf*Oele9+{ z)n#Qj(~`0U+2`n-_q40pQnoV`XzKwkKnj~QA!cEz3KXhHzY%#slX+M4`d)GTdmV4< zypi5ICGM&gvCw$pNR2aT47+5TBV`OyGE%9IBzNd`{-Mqt8L$m<=`=5v4jME zwhV4i*rPJ&tDdHHld|Ht!j4kvIIs6cr$MH@z0y_SlG-^ruYr5nHWRq8D`TRBa!b2c zCok!WPNyC>IX&WO@;59qqG#5nh~VDoX<-+f+%)on-7(nij69#iscky=b8 zWsTEU>yprcIpC1*j;&P9^!z0tB7PJZ&^2s9pxyHEw;Q!dETpF^rX&uH%APlPyEp)J zBZ{pN*3@I6Qmz>tMm+`C!Qgf_V!|7}yDnM>79<5tqb(d70~1amt5=!w&?uh1jytn@ zEmCwt4i()j;6jDEqJUZga7^!t{*&%nRAmjeCf?J~q69eIbdFuQ8NDl;ee!e!s;$K3 vjj+ZudDk%EUh0hRsTA1SD0N)z`o94Hp>J(tm=v(I00000NkvXXu0mjfCr9_Z literal 0 HcmV?d00001 diff --git a/images/m4.svg b/images/m4.svg new file mode 100644 index 0000000..d35c719 --- /dev/null +++ b/images/m4.svg @@ -0,0 +1,123 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/images/m5.png b/images/m5.png new file mode 100644 index 0000000000000000000000000000000000000000..61387d2ab5c8d22efef4846617567a3b1ae6d72e GIT binary patch literal 6839 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ドルX+V;;WNlHg-V_t6gnJY`v2*dP{B5p;yK; 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 literal 0 HcmV?d00001 diff --git a/images/m5.svg b/images/m5.svg new file mode 100644 index 0000000..dd0600d --- /dev/null +++ b/images/m5.svg @@ -0,0 +1,75 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + From ba80d93eb4a7153647ee4fbf3daf2c00efec7e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月28日 18:10:36 +0200 Subject: [PATCH 06/39] minified marker svg versions --- images/m1.svg | 76 +------------------------------ images/m2.svg | 76 +------------------------------ images/m3.svg | 76 +------------------------------ images/m4.svg | 124 +------------------------------------------------- images/m5.svg | 76 +------------------------------ 5 files changed, 5 insertions(+), 423 deletions(-) diff --git a/images/m1.svg b/images/m1.svg index cd368eb..c471f17 100644 --- a/images/m1.svg +++ b/images/m1.svg @@ -1,75 +1 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/images/m2.svg b/images/m2.svg index b401f69..104fb04 100644 --- a/images/m2.svg +++ b/images/m2.svg @@ -1,75 +1 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/images/m3.svg b/images/m3.svg index 59a6cf9..3b9e7a2 100644 --- a/images/m3.svg +++ b/images/m3.svg @@ -1,75 +1 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/images/m4.svg b/images/m4.svg index d35c719..26c3dab 100644 --- a/images/m4.svg +++ b/images/m4.svg @@ -1,123 +1 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/images/m5.svg b/images/m5.svg index dd0600d..4f1c981 100644 --- a/images/m5.svg +++ b/images/m5.svg @@ -1,75 +1 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file From 6f4b39d35799de32fe363d635d24f470a91ba9b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月28日 18:17:53 +0200 Subject: [PATCH 07/39] new marker image path using rawgit CDN, uses SVG depending on feature detection --- src/datalayerclusterer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index b163fe9..b90245f 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -1067,8 +1067,8 @@ FeatureClusterIcon.prototype.setSums = function(sums) { * @type {string} */ DataLayerClusterer.MARKER_CLUSTER_IMAGE_PATH_ = - 'https://raw.githubusercontent.com/googlemaps/v3-utility-library/master/markerclustererplus/images/m'; -DataLayerClusterer.MARKER_CLUSTER_IMAGE_EXTENSION_ = 'png'; + 'img/markercluster/m'; +DataLayerClusterer.MARKER_CLUSTER_IMAGE_EXTENSION_ = document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image", "1.1") ? 'svg' : 'png'; /** * Sets up the styles object. From 400a073a07b099b4039d934d247e744cae9ae22a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月28日 19:26:38 +0200 Subject: [PATCH 08/39] new marker image path using rawgit CDN, uses SVG depending on feature detection --- src/datalayerclusterer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index b90245f..d014b4d 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -1067,7 +1067,7 @@ FeatureClusterIcon.prototype.setSums = function(sums) { * @type {string} */ DataLayerClusterer.MARKER_CLUSTER_IMAGE_PATH_ = - 'img/markercluster/m'; + 'https://cdn.rawgit.com/Connum/data-layer-clusterer/master/images/m'; DataLayerClusterer.MARKER_CLUSTER_IMAGE_EXTENSION_ = document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image", "1.1") ? 'svg' : 'png'; /** From 6fb3272865debdfb162fbbe0e5a2e7e4d0770dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月28日 19:26:53 +0200 Subject: [PATCH 09/39] removed TODO --- src/datalayerclusterer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index d014b4d..542bceb 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -1058,7 +1058,6 @@ FeatureClusterIcon.prototype.setSums = function(sums) { /* ---- To remove soon ---- */ /* * TODO: Allow the styling using a similar interface than google.map.Data. - * Use SVG icon by default, remove dependency of google-maps-utility-library-v3.googlecode.com. */ /** From 351999439f2c63e3f32ced8e72507fe895195f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月28日 19:28:34 +0200 Subject: [PATCH 10/39] override addListener() inherited by google.maps.OverlayView --- src/datalayerclusterer.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 542bceb..181cc78 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -57,6 +57,10 @@ */ function DataLayerClusterer(optOptions) { DataLayerClusterer.extend(DataLayerClusterer, google.maps.OverlayView); + this.addListener = function(type, callback) { + return this._dataLayer.addListener(type, callback); + }; + var options = optOptions || {}; this.clusters_ = []; From 3381c4925aae60e6084c4559a0aaaba3faa9d401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月29日 09:56:04 +0200 Subject: [PATCH 11/39] style --- src/datalayerclusterer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 181cc78..8c5492e 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -724,7 +724,7 @@ FeatureCluster.prototype.getBounds = function() { var fsize = this.features_.length; for (var i = 0; i !== fsize; ++i) { - bounds = DataLayerClusterer.prototype.featureBounds_(this.features_[i], bounds); + bounds = this.featureClusterer_.featureBounds_(this.features_[i], bounds); } return bounds; From 5868b96ea2645a93c7576b71c5d58c08f7bb6e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月29日 09:56:49 +0200 Subject: [PATCH 12/39] clustering working for polygons as well --- src/datalayerclusterer.js | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 8c5492e..41a7ce3 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -377,8 +377,13 @@ DataLayerClusterer.prototype.isFeatureInBounds_ = function(f, bounds) { if (geom.getType() == 'Point') { inBounds = bounds.contains(geom.get()); } else { + var self = this; geom.getArray().forEach(function(g) { - inBounds = bounds.contains(g); + if (g instanceof google.maps.LatLng) { + inBounds = bounds.contains(g); + } else { + inBounds = bounds.contains(self.featureCenter_(g)); + } return !inBounds; }); } @@ -417,22 +422,20 @@ DataLayerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) { * @private */ DataLayerClusterer.prototype.featureBounds_ = function(feature, extendBounds) { - var geom = feature.getGeometry(), + var geom = feature.getGeometry ? feature.getGeometry() : feature, geom_bounds = extendBounds || new google.maps.LatLngBounds(); if (geom.getType() == 'Point') { geom_bounds.extend(geom.get()); } else { - geom.getArray().forEach(function(latLng){ - geom_bounds.extend(latLng); - /*console.log(geom.getType()); - console.log(latLng);*/ - //iterate over the points in the path - /*path.getArray().forEach(function(latLng){ - - //extend the bounds - - });*/ + geom.getArray().forEach(function(g){ + if (g instanceof google.maps.LatLng) { + geom_bounds.extend(g); + } else { + g.getArray().forEach(function(LatLng) { + geom_bounds.extend(LatLng); + }); + } }); } @@ -445,12 +448,12 @@ DataLayerClusterer.prototype.featureBounds_ = function(feature, extendBounds) { * @private */ DataLayerClusterer.prototype.featureCenter_ = function(feature) { - var geom = feature.getGeometry(); + var geom = feature.getGeometry ? feature.getGeometry() : feature; if (geom.getType() == 'Point') { return geom.get(); + } else { + return this.featureBounds_(feature).getCenter(); } - - return this.featureBounds_(feature).getCenter(); }; /** From 86fa57b5067ac23e52d2b13a604f8e318d75139a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月29日 09:59:25 +0200 Subject: [PATCH 13/39] short writing style (might be a bit more performant than if/else) --- src/datalayerclusterer.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 41a7ce3..c069df6 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -379,11 +379,7 @@ DataLayerClusterer.prototype.isFeatureInBounds_ = function(f, bounds) { } else { var self = this; geom.getArray().forEach(function(g) { - if (g instanceof google.maps.LatLng) { - inBounds = bounds.contains(g); - } else { - inBounds = bounds.contains(self.featureCenter_(g)); - } + inBounds = g instanceof google.maps.LatLng ? bounds.contains(g) : bounds.contains(self.featureCenter_(g)); return !inBounds; }); } From 17f2e60954c39f20051a02a5626e814d74ce9178 Mon Sep 17 00:00:00 2001 From: Connum Date: 2016年9月29日 10:45:55 +0200 Subject: [PATCH 14/39] Update README.md Added a description about the reasons for this fork and what has been changed so far. --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 4c50d6b..b414d69 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,20 @@ A Google Maps JavaScript API v3 library to create and manage per-zoom-level clus Based on [Marker Clusterer – A Google Maps JavaScript API utility library](https://github.com/googlemaps/js-marker-clusterer) by Luke Mehe (Google Inc.). +## About this fork + +While working with the data layer feature was fun because of the simplicity of adding and getting map content via GeoJSON, I soon encountered the problem of too many markers, lines and polygons being displayed when zooming out of the map. I knew about the marker clusterer for normal layers, but it took me browsing through several StackOverflow posts and pages of search results to stumble upon nantunes' approach to data layers. + +Seeing that there hadn't been any work done on the project for almost a year, I tried out jesusr's fork, which included some fixes/optimizations, but out-of-the-box it would just throw JS errors in the console. After forking it and getting it to work, I had to find out that just like the version in the initial repo, there was no support for LineStrings or Polygons, which I needed. Also, the URL to the cluster marker icons was no longer valid, so that had to be fixed as well, and so I did. + +After a day of working on it, the current implementation now includes the following changes: +- Fixed cluster marker image URLs +- Added SVG versions of the marker images which will be used by default if supported by the browser and falls back to the PNG versions +- LineStrings and Polygons are being clustered as well, using the center point of their bounding rectangles + +## More to come +- Cluster LineStrings and Polygons not only based on their bounding rectangle center points, but also if they are becoming too small according to a threshold value in pixels for either width or height + ## License Licensed under the Apache License, Version 2.0 (the "License"); From 1713adde052c17fe43b53e1eba5c8c207663cc3a Mon Sep 17 00:00:00 2001 From: Connum Date: 2016年9月29日 13:45:57 +0200 Subject: [PATCH 15/39] Update README.md added blog post URL --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b414d69..54ca68f 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ After a day of working on it, the current implementation now includes the follow - Added SVG versions of the marker images which will be used by default if supported by the browser and falls back to the PNG versions - LineStrings and Polygons are being clustered as well, using the center point of their bounding rectangles +To read more and view a working example, see my blog post at www.constantinmedia.com/2016/09/google-maps-javascript-api-v3-handling-large-amounts-of-features-using-clustering-in-data-layers/ + ## More to come - Cluster LineStrings and Polygons not only based on their bounding rectangle center points, but also if they are becoming too small according to a threshold value in pixels for either width or height From b50b4e26aa7ac2b62858371fc184801796fe5976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月29日 17:49:05 +0200 Subject: [PATCH 16/39] new option 'setProperty': Instead of changing the StyleOption attribute 'visible' of the features directly, a property 'in_cluster' is set on the features, which can then be used to toggle visibility, taking into account other properties (useful for additonal filtering, for example) --- src/datalayerclusterer.js | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index c069df6..77f6d43 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -44,6 +44,11 @@ * 'minimumClusterSize': (number) The minimum number of features to be in a * cluster before the features are hidden and a count * is shown. + * 'setProperty': (boolean) when true, the features will not be hidden, but + * the property 'in_cluster' will be set to a boolean value, + * indicating whether the feature is currently being clustered or not + * This allows to handle hiding/showing manually, taking other factors + * (like filtering) into account. * 'styles': (object) An object that has style properties: * 'url': (string) The image url. * 'height': (number) The image height. @@ -69,6 +74,7 @@ function DataLayerClusterer(optOptions) { this.map = options.map || null; this.gridSize_ = options.gridSize || 60; this.minClusterSize_ = options.minimumClusterSize || 2; + this.setProperty_ = options.setProperty || false; this.maxZoom_ = options.maxZoom || null; this.className_ = options.className || 'cluster'; this.styles_ = options.styles || []; @@ -79,7 +85,13 @@ function DataLayerClusterer(optOptions) { this._dataLayer = new google.maps.Data(); this.setupStyles_(); - this._dataLayer.setStyle(DataLayerClusterer.HIDDEN_FEATURE); + if (this.setProperty_) { + this._dataLayer.forEach(function(feature) { + feature.setProperty('in_cluster', true); + }); + } else { + this._dataLayer.setStyle(DataLayerClusterer.HIDDEN_FEATURE); + } if (this.map !== null) { this.setMap(this.map); } @@ -684,19 +696,31 @@ FeatureCluster.prototype.addFeature = function(feature) { var len = this.features_.length; if (len < this.minClusterSize_) { // Min cluster size not reached so show the feature. - this.featureClusterer_.overrideStyle(feature, DataLayerClusterer.VISIBLE_FEATURE); + if (this.featureClusterer_.setProperty_) { + feature.setProperty('in_cluster', false); + } else { + this.featureClusterer_.overrideStyle(feature, DataLayerClusterer.VISIBLE_FEATURE); + } } if (len === this.minClusterSize_) { // Hide the features that were showing. for (var i = 0; i < len; i++) { - this.featureClusterer_.overrideStyle(this.features_[i], DataLayerClusterer.HIDDEN_FEATURE); + if (this.featureClusterer_.setProperty_) { + this.features_[i].setProperty('in_cluster', true); + } else { + this.featureClusterer_.overrideStyle(this.features_[i], DataLayerClusterer.HIDDEN_FEATURE); + } } } if (len>= this.minClusterSize_) { for (var j = 0; j < len; j++) { - this.featureClusterer_.overrideStyle(this.features_[j], DataLayerClusterer.HIDDEN_FEATURE); + if (this.featureClusterer_.setProperty_) { + this.features_[j].setProperty('in_cluster', true); + } else { + this.featureClusterer_.overrideStyle(this.features_[j], DataLayerClusterer.HIDDEN_FEATURE); + } } } @@ -806,7 +830,11 @@ FeatureCluster.prototype.updateIcon = function() { // The zoom is greater than our max zoom so show all the features in cluster. var fsize = this.features_.length; for (var i = 0; i !== fsize; ++i) { - this.featureClusterer_.overrideStyle(this.features_[i], DataLayerClusterer.VISIBLE_FEATURE); + if (this.featureClusterer_.setProperty_) { + this.features_[i].setProperty('in_cluster', false); + } else { + this.featureClusterer_.overrideStyle(this.features_[i], DataLayerClusterer.VISIBLE_FEATURE); + } } return; From 4f70884cd5cba42e3bd7b0800e0fb296ca310176 Mon Sep 17 00:00:00 2001 From: Connum Date: 2016年9月29日 17:51:46 +0200 Subject: [PATCH 17/39] Update README.md added setProperty option description --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 54ca68f..f45414a 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,11 @@ While working with the data layer feature was fun because of the simplicity of a Seeing that there hadn't been any work done on the project for almost a year, I tried out jesusr's fork, which included some fixes/optimizations, but out-of-the-box it would just throw JS errors in the console. After forking it and getting it to work, I had to find out that just like the version in the initial repo, there was no support for LineStrings or Polygons, which I needed. Also, the URL to the cluster marker icons was no longer valid, so that had to be fixed as well, and so I did. -After a day of working on it, the current implementation now includes the following changes: +My current implementation now includes the following changes: - Fixed cluster marker image URLs - Added SVG versions of the marker images which will be used by default if supported by the browser and falls back to the PNG versions - LineStrings and Polygons are being clustered as well, using the center point of their bounding rectangles +- new option 'setProperty': If set to true, instead of changing the StyleOption attribute 'visible' of the features directly, a boolean property 'in_cluster' is set on the features, which can then be used to toggle visibility (for example in order to take into account other properties for additonal filtering) To read more and view a working example, see my blog post at www.constantinmedia.com/2016/09/google-maps-javascript-api-v3-handling-large-amounts-of-features-using-clustering-in-data-layers/ From c1737ba9aa2b36947fedb8ebb8c142e5cad7b784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月29日 18:01:37 +0200 Subject: [PATCH 18/39] make the property name 'in_cluster' configurable with the constant DataLayerClusterer.CLUSTER_PROPERTY_NAME --- src/datalayerclusterer.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 77f6d43..c2d8417 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -45,10 +45,12 @@ * cluster before the features are hidden and a count * is shown. * 'setProperty': (boolean) when true, the features will not be hidden, but - * the property 'in_cluster' will be set to a boolean value, - * indicating whether the feature is currently being clustered or not - * This allows to handle hiding/showing manually, taking other factors - * (like filtering) into account. + * the property 'in_cluster' (or a configurable property name defined + * in the constant DataLayerClusterer.CLUSTER_PROPERTY_NAME) + * will be set to a boolean value, indicating whether the feature is + * currently being clustered or not. This allows to handle + * hiding/showing manually, taking other factors (like filtering) + * into account. * 'styles': (object) An object that has style properties: * 'url': (string) The image url. * 'height': (number) The image height. @@ -99,6 +101,8 @@ function DataLayerClusterer(optOptions) { /* ---- Constants ---- */ +DataLayerClusterer.CLUSTER_PROPERTY_NAME = 'in_cluster'; + DataLayerClusterer.VISIBLE_FEATURE = { visible: true }; @@ -697,7 +701,7 @@ FeatureCluster.prototype.addFeature = function(feature) { if (len < this.minClusterSize_) { // Min cluster size not reached so show the feature. if (this.featureClusterer_.setProperty_) { - feature.setProperty('in_cluster', false); + feature.setProperty(DataLayerClusterer.CLUSTER_PROPERTY_NAME, false); } else { this.featureClusterer_.overrideStyle(feature, DataLayerClusterer.VISIBLE_FEATURE); } @@ -707,7 +711,7 @@ FeatureCluster.prototype.addFeature = function(feature) { // Hide the features that were showing. for (var i = 0; i < len; i++) { if (this.featureClusterer_.setProperty_) { - this.features_[i].setProperty('in_cluster', true); + this.features_[i].setProperty(DataLayerClusterer.CLUSTER_PROPERTY_NAME, true); } else { this.featureClusterer_.overrideStyle(this.features_[i], DataLayerClusterer.HIDDEN_FEATURE); } @@ -717,7 +721,7 @@ FeatureCluster.prototype.addFeature = function(feature) { if (len>= this.minClusterSize_) { for (var j = 0; j < len; j++) { if (this.featureClusterer_.setProperty_) { - this.features_[j].setProperty('in_cluster', true); + this.features_[j].setProperty(DataLayerClusterer.CLUSTER_PROPERTY_NAME, true); } else { this.featureClusterer_.overrideStyle(this.features_[j], DataLayerClusterer.HIDDEN_FEATURE); } @@ -831,7 +835,7 @@ FeatureCluster.prototype.updateIcon = function() { var fsize = this.features_.length; for (var i = 0; i !== fsize; ++i) { if (this.featureClusterer_.setProperty_) { - this.features_[i].setProperty('in_cluster', false); + this.features_[i].setProperty(DataLayerClusterer.CLUSTER_PROPERTY_NAME, false); } else { this.featureClusterer_.overrideStyle(this.features_[i], DataLayerClusterer.VISIBLE_FEATURE); } From 8fe39a59fafad467b955518bfc1f3011eaa464c3 Mon Sep 17 00:00:00 2001 From: Connum Date: 2016年9月29日 18:02:35 +0200 Subject: [PATCH 19/39] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f45414a..f0831b6 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ My current implementation now includes the following changes: - Fixed cluster marker image URLs - Added SVG versions of the marker images which will be used by default if supported by the browser and falls back to the PNG versions - LineStrings and Polygons are being clustered as well, using the center point of their bounding rectangles -- new option 'setProperty': If set to true, instead of changing the StyleOption attribute 'visible' of the features directly, a boolean property 'in_cluster' is set on the features, which can then be used to toggle visibility (for example in order to take into account other properties for additonal filtering) +- new option 'setProperty': If set to true, instead of changing the StyleOption attribute 'visible' of the features directly, a boolean property 'in_cluster' (or a configurable property name defined in the constant DataLayerClusterer.CLUSTER_PROPERTY_NAME) is set on the features, which can then be used to toggle visibility (for example in order to take into account other properties for additonal filtering) To read more and view a working example, see my blog post at www.constantinmedia.com/2016/09/google-maps-javascript-api-v3-handling-large-amounts-of-features-using-clustering-in-data-layers/ From 4c16f5894bb4265299ae8dac46d4208f71509a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月30日 11:15:56 +0200 Subject: [PATCH 20/39] fix: it was not possible to set setReady(false) --- src/datalayerclusterer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index c2d8417..3bedae9 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -372,8 +372,8 @@ DataLayerClusterer.prototype.resetViewport = function() { * @private */ DataLayerClusterer.prototype.setReady_ = function(ready) { - if (!this.ready_) { - this.ready_ = ready; + this.ready_ = ready; + if (ready) { this.createClusters_(); } }; From 1d8591f66e597b7d06b062c059ee0e22102aebdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月30日 11:17:56 +0200 Subject: [PATCH 21/39] performance optimization: redrawing was triggered too often initially --- src/datalayerclusterer.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 3bedae9..dd8ca80 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -85,6 +85,7 @@ function DataLayerClusterer(optOptions) { this.zoomOnClick_ = options.zoomOnClick !== undefined ? options.zoomOnClick : true; this.averageCenter_ = options.averageCenter !== undefined ? options.averageCenter : true; this._dataLayer = new google.maps.Data(); + this.firstIdle_ = true; this.setupStyles_(); if (this.setProperty_) { @@ -558,11 +559,13 @@ DataLayerClusterer.prototype.onAdd = function() { }); this._idle = google.maps.event.addListener(this.map_, 'idle', function() { - self.redraw(); + if (!self.firstIdle_) { + self.redraw(); + } + self.firstIdle_ = false; }); this.setReady_(true); - this.redraw(); } else { this.setReady_(false); } From 8aa83bfae8dd0f9b05a43a3ea247c4dcbb70e32b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月30日 11:21:28 +0200 Subject: [PATCH 22/39] fix: if setProperty is true, ignore all markers that are hidden even if not in a cluster --- src/datalayerclusterer.js | 42 +++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index dd8ca80..bbd5d89 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -481,25 +481,37 @@ DataLayerClusterer.prototype.addToClosestCluster_ = function(feature) { var pos = this.featureCenter_(feature); var cluster; - var csize = this.clusters_.length; - for (var i = 0; i !== csize; ++i) { - var center = this.clusters_[i].getCenter(); + var isVisible = true; + if (this.setProperty_) { + var propBefore = feature.getProperty(DataLayerClusterer.CLUSTER_PROPERTY_NAME); + feature.setProperty(DataLayerClusterer.CLUSTER_PROPERTY_NAME, false); + var fStyle = this.getStyle(feature); + if (typeof fStyle == 'function') fStyle = fStyle(feature); + isVisible = typeof fStyle.visible == 'undefined' || fStyle.visible; + feature.setProperty(DataLayerClusterer.CLUSTER_PROPERTY_NAME, propBefore); + } + + if (isVisible) { + var csize = this.clusters_.length; + for (var i = 0; i !== csize; ++i) { + var center = this.clusters_[i].getCenter(); - if (center) { - var d = this.distanceBetweenPoints_(center, pos); - if (d < distance) { - distance = d; - cluster = this.clusters_[i]; + if (center) { + var d = this.distanceBetweenPoints_(center, pos); + if (d < distance) { + distance = d; + cluster = this.clusters_[i]; + } } } - } - if (cluster && cluster.isFeatureInClusterBounds(feature)) { - cluster.addFeature(feature); - } else { - cluster = new FeatureCluster(this); - cluster.addFeature(feature); - this.clusters_.push(cluster); + if (cluster && cluster.isFeatureInClusterBounds(feature)) { + cluster.addFeature(feature); + } else { + cluster = new FeatureCluster(this); + cluster.addFeature(feature); + this.clusters_.push(cluster); + } } }; From 3b7d6044457afb14cd8747ca0b9152d7ba58a16c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月30日 11:22:18 +0200 Subject: [PATCH 23/39] if the setProperty option is true, trigger a redraw on setStyle() --- src/datalayerclusterer.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index bbd5d89..fca1a2f 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -3,9 +3,11 @@ 'use strict'; /** - * @name DataLayerClusterer for Google Maps v3 - * @version version 0.7.2 + * @name DataLayerClusterer for Google Maps v3 (Connum's Fork) + * @version version 1.0.0 * @author Nelson Antunes + * @author Jesús R Peinado + * @author Constantin Groß * * The library creates and manages per-zoom-level clusters for large amounts of * data layer features. @@ -346,7 +348,11 @@ DataLayerClusterer.prototype.setDrawingMode = function(drawingMode) { }; DataLayerClusterer.prototype.setStyle = function(style) { - return this._dataLayer.setStyle(style); + var returnVal = this._dataLayer.setStyle(style); + if (this.setProperty_) { + this.redraw(); + } + return returnVal; }; DataLayerClusterer.prototype.toGeoJson = function(callback) { From 6844fe3c09709b935fd46d38e31ea58454825c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月30日 11:27:45 +0200 Subject: [PATCH 24/39] updated version to version in the jsdocs section (1.0.0) to make it different from the original repo --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f539b13..2c84224 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "data-layer-clusterer", - "version": "0.7.3", + "version": "1.0.0", "homepage": "https://github.com/nantunes/data-layer-clusterer", "authors": [ "Nelson Antunes" From 1cdb09703ae82a2068a807f07d114a8539386824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月30日 16:21:57 +0200 Subject: [PATCH 25/39] new recolorSvg option --- src/datalayerclusterer.js | 52 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index fca1a2f..6929f84 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -35,7 +35,7 @@ * * @param {google.maps.Map} map The Google map to attach to. * @param {Object=} optOptions support the following options: - * 'map': (google.maps.Map) The Google map to attach to. + * 'map': (google.maps.Map) The Google map to attach to. * 'gridSize': (number) The grid size of a cluster in pixels. * 'maxZoom': (number) The maximum zoom level that a feature can be part of a * cluster. @@ -53,6 +53,10 @@ * currently being clustered or not. This allows to handle * hiding/showing manually, taking other factors (like filtering) * into account. + * 'recolorSvg': (string) only takes action if SVG is being used: + * a selector for an SVG element in the set imagePath that can be used + * for re-coloring the cluster marker image. This saves requests and + * prevents the different marker images popping up after loading. * 'styles': (object) An object that has style properties: * 'url': (string) The image url. * 'height': (number) The image height. @@ -74,6 +78,7 @@ function DataLayerClusterer(optOptions) { this.clusters_ = []; this.sizes = [53, 56, 66, 78, 90]; + this.colors = ['#008cff','#ffbf00','#ff0000','#ff00ed','#9c00ff']; this.ready_ = false; this.map = options.map || null; this.gridSize_ = options.gridSize || 60; @@ -88,8 +93,11 @@ function DataLayerClusterer(optOptions) { this.averageCenter_ = options.averageCenter !== undefined ? options.averageCenter : true; this._dataLayer = new google.maps.Data(); this.firstIdle_ = true; + this.recolorSVG_ = options.recolorSvg || 'g:first-child'; + this.baseSVG_ = null; this.setupStyles_(); + if (this.setProperty_) { this._dataLayer.forEach(function(feature) { feature.setProperty('in_cluster', true); @@ -1043,6 +1051,7 @@ FeatureClusterIcon.prototype.getPosFromLatLng_ = function(latlng) { FeatureClusterIcon.prototype.createCss = function(pos) { var style = []; style.push('background-image:url(' + this.url_ + ');'); + if (this.cluster_.featureClusterer_.recolorSVG_) style.push('background-size: contain;'); var backgroundPosition = this.backgroundPosition_ ? this.backgroundPosition_ : '0 0'; style.push('background-position:' + backgroundPosition + ';'); @@ -1135,12 +1144,47 @@ DataLayerClusterer.prototype.setupStyles_ = function() { return; } + if (!this.baseSVG_ && this.recolorSVG_ && this.imageExtension_ == 'svg') { + var self = this, + xhr = new XMLHttpRequest(); + xhr.open("GET",/\.svg$/.test(this.imagePath_) ? this.imagePath_ : this.imagePath_ + '1.' + this.imageExtension_); + // Following line is just to be on the safe side; + // not needed if your server delivers SVG with correct MIME type + xhr.overrideMimeType("image/svg+xml"); + xhr.send(""); + + xhr.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + self.baseSVG_ = { + 'document': xhr.responseXML.documentElement, + 'colorElement': xhr.responseXML.documentElement.querySelector(self.recolorSVG_) + }; + if (!self.baseSVG_.document || !self.baseSVG_.colorElement) { + self.recolorSVG_ = false; + } + } else { + self.recolorSVG_ = false; + } + self.setupStyles_(); + } + }; + return; + } + var ssizes = this.sizes.length; for (var i = 0; i !== ssizes; ++i) { + var thisSize = this.sizes[i], + thisColor = this.colors[i], + markerUrl = this.imagePath_ + (i + 1) + '.' + this.imageExtension_; + if (this.recolorSVG_) { + this.baseSVG_.colorElement.style.fill = thisColor; + markerUrl = 'data:image/svg+xml;base64,' + btoa(this.baseSVG_.document.outerHTML); + } this.styles_.push({ - url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_, - height: this.sizes[i], - width: this.sizes[i] + url: markerUrl, + height: thisSize, + width: thisSize }); } }; From a9af7151842dc5c2dc496449fb1dd69887de4e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月30日 19:03:08 +0200 Subject: [PATCH 26/39] fixed handling of recolorSVG option (could only be turned off by an empty string value before) --- src/datalayerclusterer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 6929f84..299dd37 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -93,7 +93,7 @@ function DataLayerClusterer(optOptions) { this.averageCenter_ = options.averageCenter !== undefined ? options.averageCenter : true; this._dataLayer = new google.maps.Data(); this.firstIdle_ = true; - this.recolorSVG_ = options.recolorSvg || 'g:first-child'; + this.recolorSVG_ = typeof options.recolorSVG !== "undefined" && (typeof options.recolorSVG === "string" || options.recolorSVG instanceof String || options.recolorSVG === false) ? options.recolorSVG : 'g:first-child'; this.baseSVG_ = null; this.setupStyles_(); From dd75189c6a3a3a040ea5d530b1cf071f97507c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月30日 19:03:35 +0200 Subject: [PATCH 27/39] fixed case in recolorSVG option in the jsdocs --- src/datalayerclusterer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 299dd37..5f54dca 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -53,7 +53,7 @@ * currently being clustered or not. This allows to handle * hiding/showing manually, taking other factors (like filtering) * into account. - * 'recolorSvg': (string) only takes action if SVG is being used: + * 'recolorSVG': (string) only takes action if SVG is being used: * a selector for an SVG element in the set imagePath that can be used * for re-coloring the cluster marker image. This saves requests and * prevents the different marker images popping up after loading. From 2ef00e0a1ad2839aa1de3144428cb1e35a736022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年9月30日 19:04:36 +0200 Subject: [PATCH 28/39] restructured initialization in order to prevent js erros when recolorSVG was used but the marker svg had not been loaded by the time the clusters were created --- src/datalayerclusterer.js | 85 +++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 5f54dca..9ef2388 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -96,18 +96,32 @@ function DataLayerClusterer(optOptions) { this.recolorSVG_ = typeof options.recolorSVG !== "undefined" && (typeof options.recolorSVG === "string" || options.recolorSVG instanceof String || options.recolorSVG === false) ? options.recolorSVG : 'g:first-child'; this.baseSVG_ = null; - this.setupStyles_(); + if (this.recolorSVG_ && this.imageExtension_ == 'svg') { + var self = this, + xhr = new XMLHttpRequest(); + xhr.open("GET",/\.svg$/.test(this.imagePath_) ? this.imagePath_ : this.imagePath_ + '1.' + this.imageExtension_); + // Following line is just to be on the safe side; + // not needed if your server delivers SVG with correct MIME type + xhr.overrideMimeType("image/svg+xml"); + xhr.send(""); - if (this.setProperty_) { - this._dataLayer.forEach(function(feature) { - feature.setProperty('in_cluster', true); - }); - } else { - this._dataLayer.setStyle(DataLayerClusterer.HIDDEN_FEATURE); - } - if (this.map !== null) { - this.setMap(this.map); - } + xhr.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + self.baseSVG_ = { + 'document': xhr.responseXML.documentElement, + 'colorElement': xhr.responseXML.documentElement.querySelector(self.recolorSVG_) + }; + if (!self.baseSVG_.document || !self.baseSVG_.colorElement) { + self.recolorSVG_ = false; + } + } else { + self.recolorSVG_ = false; + } + self.init_(); + } + }; + } else this.init_(); } /* ---- Constants ---- */ @@ -1134,6 +1148,27 @@ DataLayerClusterer.MARKER_CLUSTER_IMAGE_PATH_ = 'https://cdn.rawgit.com/Connum/data-layer-clusterer/master/images/m'; DataLayerClusterer.MARKER_CLUSTER_IMAGE_EXTENSION_ = document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image", "1.1") ? 'svg' : 'png'; + +/** + * Initialises the clustering when everything is ready + * + * @private + */ +DataLayerClusterer.prototype.init_ = function() { + this.setupStyles_(); + + if (this.setProperty_) { + this._dataLayer.forEach(function(feature) { + feature.setProperty('in_cluster', true); + }); + } else { + this._dataLayer.setStyle(DataLayerClusterer.HIDDEN_FEATURE); + } + if (this.map !== null) { + this.setMap(this.map); + } +} + /** * Sets up the styles object. * @@ -1144,34 +1179,6 @@ DataLayerClusterer.prototype.setupStyles_ = function() { return; } - if (!this.baseSVG_ && this.recolorSVG_ && this.imageExtension_ == 'svg') { - var self = this, - xhr = new XMLHttpRequest(); - xhr.open("GET",/\.svg$/.test(this.imagePath_) ? this.imagePath_ : this.imagePath_ + '1.' + this.imageExtension_); - // Following line is just to be on the safe side; - // not needed if your server delivers SVG with correct MIME type - xhr.overrideMimeType("image/svg+xml"); - xhr.send(""); - - xhr.onreadystatechange = function() { - if (this.readyState == 4) { - if (this.status == 200) { - self.baseSVG_ = { - 'document': xhr.responseXML.documentElement, - 'colorElement': xhr.responseXML.documentElement.querySelector(self.recolorSVG_) - }; - if (!self.baseSVG_.document || !self.baseSVG_.colorElement) { - self.recolorSVG_ = false; - } - } else { - self.recolorSVG_ = false; - } - self.setupStyles_(); - } - }; - return; - } - var ssizes = this.sizes.length; for (var i = 0; i !== ssizes; ++i) { var thisSize = this.sizes[i], From b4c31fd35048c6197e92e23d061cc08990d40a9b Mon Sep 17 00:00:00 2001 From: Connum Date: Sat, 1 Oct 2016 09:45:51 +0200 Subject: [PATCH 29/39] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f0831b6..b8b6a8a 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ My current implementation now includes the following changes: - Added SVG versions of the marker images which will be used by default if supported by the browser and falls back to the PNG versions - LineStrings and Polygons are being clustered as well, using the center point of their bounding rectangles - new option 'setProperty': If set to true, instead of changing the StyleOption attribute 'visible' of the features directly, a boolean property 'in_cluster' (or a configurable property name defined in the constant DataLayerClusterer.CLUSTER_PROPERTY_NAME) is set on the features, which can then be used to toggle visibility (for example in order to take into account other properties for additonal filtering) +- new option 'recolorSvg': (string) only takes action if SVG is supported and being used: a selector string for an SVG element in the set imagePath that can be used for re-coloring the cluster marker image. This saves requests and prevents the different marker images popping up after loading. To read more and view a working example, see my blog post at www.constantinmedia.com/2016/09/google-maps-javascript-api-v3-handling-large-amounts-of-features-using-clustering-in-data-layers/ From 2590a170ef67d2f8f2253df4c2511d4defab2855 Mon Sep 17 00:00:00 2001 From: Connum Date: Sat, 1 Oct 2016 10:36:05 +0200 Subject: [PATCH 30/39] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b8b6a8a..5cfb6f8 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ A Google Maps JavaScript API v3 library to create and manage per-zoom-level clus Based on [Marker Clusterer – A Google Maps JavaScript API utility library](https://github.com/googlemaps/js-marker-clusterer) by Luke Mehe (Google Inc.). +You can find the minified JS file [in the releases tab](https://github.com/Connum/data-layer-clusterer/releases). + ## About this fork While working with the data layer feature was fun because of the simplicity of adding and getting map content via GeoJSON, I soon encountered the problem of too many markers, lines and polygons being displayed when zooming out of the map. I knew about the marker clusterer for normal layers, but it took me browsing through several StackOverflow posts and pages of search results to stumble upon nantunes' approach to data layers. From d826cf9e6ee8c42f4c8cc115622ba9889d3ea5eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: 2016年10月10日 12:59:53 +0200 Subject: [PATCH 31/39] new option minimumPolySize, updated package.json, increased version number to 1.0.1 --- bower.json | 2 +- package.json | 7 ++++--- src/datalayerclusterer.js | 38 +++++++++++++++++++++++++++++++++----- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/bower.json b/bower.json index 1ede1b7..22934d0 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "data-layer-clusterer", - "version": "0.7.3", + "version": "1.0.1", "homepage": "https://github.com/Connum/data-layer-clusterer", "authors": [ "Nelson Antunes" diff --git a/package.json b/package.json index 2c84224..22934d0 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,18 @@ { "name": "data-layer-clusterer", - "version": "1.0.0", - "homepage": "https://github.com/nantunes/data-layer-clusterer", + "version": "1.0.1", + "homepage": "https://github.com/Connum/data-layer-clusterer", "authors": [ "Nelson Antunes" ], - "description": "The library creates and manages per-zoom-level clusters large amounts of data layer features. Google API v3.", + "description": "The library creates and manages per-zoom-level clusters for large amounts of data layer features. Google API v3.", "main": "src/datalayerclusterer.js", "keywords": [ "google", "maps", "data", "layer", + "features", "marker", "cluster", "clusterer", diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 9ef2388..b2f5ae5 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -4,7 +4,7 @@ /** * @name DataLayerClusterer for Google Maps v3 (Connum's Fork) - * @version version 1.0.0 + * @version version 1.0.1 * @author Nelson Antunes * @author Jesús R Peinado * @author Constantin Groß @@ -46,6 +46,11 @@ * 'minimumClusterSize': (number) The minimum number of features to be in a * cluster before the features are hidden and a count * is shown. + * 'minimumPolySize': (number) The minimum width or height of the bounding box + * of a feature (other than type 'Point') in pixels before + * it is forced into a cluster, even if the cluster ends up + * containing only this one feature. 0 or false to disable + * this functionality. * 'setProperty': (boolean) when true, the features will not be hidden, but * the property 'in_cluster' (or a configurable property name defined * in the constant DataLayerClusterer.CLUSTER_PROPERTY_NAME) @@ -83,6 +88,7 @@ function DataLayerClusterer(optOptions) { this.map = options.map || null; this.gridSize_ = options.gridSize || 60; this.minClusterSize_ = options.minimumClusterSize || 2; + this.minPolySize_ = options.minimumPolySize || 50; this.setProperty_ = options.setProperty || false; this.maxZoom_ = options.maxZoom || null; this.className_ = options.className || 'cluster'; @@ -521,6 +527,7 @@ DataLayerClusterer.prototype.addToClosestCluster_ = function(feature) { if (isVisible) { var csize = this.clusters_.length; + for (var i = 0; i !== csize; ++i) { var center = this.clusters_[i].getCenter(); @@ -688,6 +695,8 @@ function FeatureCluster(featureClusterer) { this.clusterIcon_ = new FeatureClusterIcon(this, featureClusterer.getStyles(), featureClusterer.getGridSize(), this.classId); + + this.forced_ = false; } /** @@ -741,7 +750,26 @@ FeatureCluster.prototype.addFeature = function(feature) { this.features_.push(feature); var len = this.features_.length; - if (len < this.minClusterSize_) { + + if (len == 1 && !!this.featureClusterer_.minPolySize_ && feature.getGeometry().getType() != 'Point') { + var polyMinSize = this.featureClusterer_.minPolySize_; + var bounds = this.featureClusterer_.featureBounds_(feature); + var SW = bounds.getSouthWest(); + var NE = bounds.getNorthEast(); + var proj = this.map_.getProjection(); + var swPx = proj.fromLatLngToPoint(SW); + var nePx = proj.fromLatLngToPoint(NE); + var pixelWidth = Math.round(Math.abs((nePx.x - swPx.x)* Math.pow(2, this.map_.getZoom()))); + var pixelHeight = Math.round(Math.abs((swPx.y - nePx.y)* Math.pow(2, this.map_.getZoom()))); + + if (pixelWidth < polyMinSize && pixelHeight < polyMinSize) { + this.forced_ = true; + } else { + this.forced_ = false; + } + } + + if (len < this.minClusterSize_ && !this.forced_) { // Min cluster size not reached so show the feature. if (this.featureClusterer_.setProperty_) { feature.setProperty(DataLayerClusterer.CLUSTER_PROPERTY_NAME, false); @@ -750,7 +778,7 @@ FeatureCluster.prototype.addFeature = function(feature) { } } - if (len === this.minClusterSize_) { + if (len === this.minClusterSize_ || this.forced_) { // Hide the features that were showing. for (var i = 0; i < len; i++) { if (this.featureClusterer_.setProperty_) { @@ -761,7 +789,7 @@ FeatureCluster.prototype.addFeature = function(feature) { } } - if (len>= this.minClusterSize_) { + if (len>= this.minClusterSize_ || this.forced_) { for (var j = 0; j < len; j++) { if (this.featureClusterer_.setProperty_) { this.features_[j].setProperty(DataLayerClusterer.CLUSTER_PROPERTY_NAME, true); @@ -887,7 +915,7 @@ FeatureCluster.prototype.updateIcon = function() { return; } - if (this.features_.length < this.minClusterSize_) { + if (this.features_.length < this.minClusterSize_ && !this.forced_) { // Min cluster size not yet reached. this.clusterIcon_.hide(); return; From 0c6c03ac440b3ce2bafe6377e55e441f3838daca Mon Sep 17 00:00:00 2001 From: Connum Date: 2016年10月10日 13:03:12 +0200 Subject: [PATCH 32/39] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cfb6f8..435d68b 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,12 @@ My current implementation now includes the following changes: - LineStrings and Polygons are being clustered as well, using the center point of their bounding rectangles - new option 'setProperty': If set to true, instead of changing the StyleOption attribute 'visible' of the features directly, a boolean property 'in_cluster' (or a configurable property name defined in the constant DataLayerClusterer.CLUSTER_PROPERTY_NAME) is set on the features, which can then be used to toggle visibility (for example in order to take into account other properties for additonal filtering) - new option 'recolorSvg': (string) only takes action if SVG is supported and being used: a selector string for an SVG element in the set imagePath that can be used for re-coloring the cluster marker image. This saves requests and prevents the different marker images popping up after loading. +- new option: 'minimumPolySize': (number) The minimum width or height of the bounding box of a feature (other than type 'Point') in pixels before it is forced into a cluster, even if the cluster ends up containing only this one feature. 0 or false to disable this functionality. To read more and view a working example, see my blog post at www.constantinmedia.com/2016/09/google-maps-javascript-api-v3-handling-large-amounts-of-features-using-clustering-in-data-layers/ ## More to come -- Cluster LineStrings and Polygons not only based on their bounding rectangle center points, but also if they are becoming too small according to a threshold value in pixels for either width or height +- When LineStrings and Polygons are becoming too small according to the minimumPolySize option, display a marker instead for better visibility. ## License From b5ef48ca4bc1ca1678dd86686297b7b346a6611b Mon Sep 17 00:00:00 2001 From: Connum Date: 2016年10月10日 13:18:03 +0200 Subject: [PATCH 33/39] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 435d68b..e514c57 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ My current implementation now includes the following changes: - LineStrings and Polygons are being clustered as well, using the center point of their bounding rectangles - new option 'setProperty': If set to true, instead of changing the StyleOption attribute 'visible' of the features directly, a boolean property 'in_cluster' (or a configurable property name defined in the constant DataLayerClusterer.CLUSTER_PROPERTY_NAME) is set on the features, which can then be used to toggle visibility (for example in order to take into account other properties for additonal filtering) - new option 'recolorSvg': (string) only takes action if SVG is supported and being used: a selector string for an SVG element in the set imagePath that can be used for re-coloring the cluster marker image. This saves requests and prevents the different marker images popping up after loading. -- new option: 'minimumPolySize': (number) The minimum width or height of the bounding box of a feature (other than type 'Point') in pixels before it is forced into a cluster, even if the cluster ends up containing only this one feature. 0 or false to disable this functionality. +- new option: 'minimumPolySize': (number) The minimum width or height of the bounding box of a feature (other than type 'Point') in pixels before it is forced into a cluster, even if the cluster ends up containing only this one feature. 0 or false to disable this functionality. Defaults to 50. To read more and view a working example, see my blog post at www.constantinmedia.com/2016/09/google-maps-javascript-api-v3-handling-large-amounts-of-features-using-clustering-in-data-layers/ From 7da81f66e3aa124cc798f2b9189277782e425076 Mon Sep 17 00:00:00 2001 From: Miguel Sainz Jr Date: 2016年12月27日 09:33:28 -0500 Subject: [PATCH 34/39] FeatureCluster.addFeature - Avoid doing twice the work --- src/datalayerclusterer.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index b2f5ae5..2d46760 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -778,7 +778,7 @@ FeatureCluster.prototype.addFeature = function(feature) { } } - if (len === this.minClusterSize_ || this.forced_) { + if (len>= this.minClusterSize_ || this.forced_) { // Hide the features that were showing. for (var i = 0; i < len; i++) { if (this.featureClusterer_.setProperty_) { @@ -789,16 +789,6 @@ FeatureCluster.prototype.addFeature = function(feature) { } } - if (len>= this.minClusterSize_ || this.forced_) { - for (var j = 0; j < len; j++) { - if (this.featureClusterer_.setProperty_) { - this.features_[j].setProperty(DataLayerClusterer.CLUSTER_PROPERTY_NAME, true); - } else { - this.featureClusterer_.overrideStyle(this.features_[j], DataLayerClusterer.HIDDEN_FEATURE); - } - } - } - this.updateIcon(); return true; }; From 10da8be5c8dfb98fc12e860397f7367b7e20fed6 Mon Sep 17 00:00:00 2001 From: Miguel Sainz Jr Date: 2016年12月27日 15:22:43 -0500 Subject: [PATCH 35/39] Remove setStyle within init which did not allow our own setStyle Looks to have no negative effects... --- src/datalayerclusterer.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index b2f5ae5..fb515aa 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -1189,8 +1189,6 @@ DataLayerClusterer.prototype.init_ = function() { this._dataLayer.forEach(function(feature) { feature.setProperty('in_cluster', true); }); - } else { - this._dataLayer.setStyle(DataLayerClusterer.HIDDEN_FEATURE); } if (this.map !== null) { this.setMap(this.map); From 07f331b87f77bb1ee3f6aac46319a94835a54342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constantin=20Gro=C3=9F?= Date: Fri, 3 Feb 2017 16:25:05 +0100 Subject: [PATCH 36/39] coding style --- src/datalayerclusterer.js | 263 +++++++++++++++++++------------------- 1 file changed, 130 insertions(+), 133 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 6aea481..6f218a1 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -73,9 +73,9 @@ * @constructor * @extends google.maps.OverlayView */ -function DataLayerClusterer(optOptions) { +function DataLayerClusterer (optOptions) { DataLayerClusterer.extend(DataLayerClusterer, google.maps.OverlayView); - this.addListener = function(type, callback) { + this.addListener = function (type, callback) { return this._dataLayer.addListener(type, callback); }; @@ -111,7 +111,7 @@ function DataLayerClusterer(optOptions) { xhr.overrideMimeType("image/svg+xml"); xhr.send(""); - xhr.onreadystatechange = function() { + xhr.onreadystatechange = function () { if (this.readyState == 4) { if (this.status == 200) { self.baseSVG_ = { @@ -150,7 +150,7 @@ DataLayerClusterer.HIDDEN_FEATURE = { * @param {bool} v * @return {void} */ -DataLayerClusterer.prototype.setVisible = function(v) { +DataLayerClusterer.prototype.setVisible = function (v) { if (!v) { this.map__ = this.getMap(); google.maps.event.removeListener(this._idle); @@ -175,7 +175,7 @@ DataLayerClusterer.prototype.setVisible = function(v) { * * @return {number} The number of clusters. */ -DataLayerClusterer.prototype.getTotalClusters = function() { +DataLayerClusterer.prototype.getTotalClusters = function () { return this.clusters_.length; }; @@ -185,7 +185,7 @@ DataLayerClusterer.prototype.getTotalClusters = function() { * @param {google.maps.LatLngBounds} bounds The bounds to extend. * @return {google.maps.LatLngBounds} The extended bounds. */ -DataLayerClusterer.prototype.getExtendedBounds = function(bounds) { +DataLayerClusterer.prototype.getExtendedBounds = function (bounds) { var projection = this.getProjection(); // Turn the bounds into latlng. @@ -204,8 +204,8 @@ DataLayerClusterer.prototype.getExtendedBounds = function(bounds) { blPix.y += this.gridSize_; // Convert the pixel points back to LatLng - var ne = projection.fromDivPixelToLatLng(trPix); - var sw = projection.fromDivPixelToLatLng(blPix); + var ne = projection.fromDivPixelToLatLng(trPix), + sw = projection.fromDivPixelToLatLng(blPix); // Extend the bounds to contain the new bounds. bounds.extend(ne); @@ -217,7 +217,7 @@ DataLayerClusterer.prototype.getExtendedBounds = function(bounds) { /** * Redraws the clusters. */ -DataLayerClusterer.prototype.redraw = function() { +DataLayerClusterer.prototype.redraw = function () { var oldClusters = this.clusters_.slice(); this.clusters_.length = 0; @@ -225,7 +225,7 @@ DataLayerClusterer.prototype.redraw = function() { // Remove the old clusters. // Do it in a timeout so the other clusters have been drawn first. - window.requestAnimationFrame(function() { + window.requestAnimationFrame(function () { var oldSize = oldClusters.length; for (var i = 0; i !== oldSize; ++i) { oldClusters[i].remove(); @@ -241,7 +241,7 @@ DataLayerClusterer.prototype.redraw = function() { * * @return {boolean} True if zoomOnClick_ is set. */ -DataLayerClusterer.prototype.isZoomOnClick = function() { +DataLayerClusterer.prototype.isZoomOnClick = function () { return this.zoomOnClick_; }; @@ -250,7 +250,7 @@ DataLayerClusterer.prototype.isZoomOnClick = function() { * * @return {boolean} True if averageCenter_ is set. */ -DataLayerClusterer.prototype.isAverageCenter = function() { +DataLayerClusterer.prototype.isAverageCenter = function () { return this.averageCenter_; }; @@ -259,7 +259,7 @@ DataLayerClusterer.prototype.isAverageCenter = function() { * * @param {number} maxZoom The max zoom level. */ -DataLayerClusterer.prototype.setMaxZoom = function(maxZoom) { +DataLayerClusterer.prototype.setMaxZoom = function (maxZoom) { this.maxZoom_ = maxZoom; }; @@ -268,7 +268,7 @@ DataLayerClusterer.prototype.setMaxZoom = function(maxZoom) { * * @return {number} The max zoom level. */ -DataLayerClusterer.prototype.getMaxZoom = function() { +DataLayerClusterer.prototype.getMaxZoom = function () { return this.maxZoom_; }; @@ -277,7 +277,7 @@ DataLayerClusterer.prototype.getMaxZoom = function() { * * @return {number} The grid size. */ -DataLayerClusterer.prototype.getGridSize = function() { +DataLayerClusterer.prototype.getGridSize = function () { return this.gridSize_; }; @@ -286,7 +286,7 @@ DataLayerClusterer.prototype.getGridSize = function() { * * @param {number} size The grid size. */ -DataLayerClusterer.prototype.setGridSize = function(size) { +DataLayerClusterer.prototype.setGridSize = function (size) { this.gridSize_ = size; }; @@ -295,7 +295,7 @@ DataLayerClusterer.prototype.setGridSize = function(size) { * * @return {number} The grid size. */ -DataLayerClusterer.prototype.getMinClusterSize = function() { +DataLayerClusterer.prototype.getMinClusterSize = function () { return this.minClusterSize_; }; @@ -304,78 +304,78 @@ DataLayerClusterer.prototype.getMinClusterSize = function() { * * @param {number} size The grid size. */ -DataLayerClusterer.prototype.setMinClusterSize = function(size) { +DataLayerClusterer.prototype.setMinClusterSize = function (size) { this.minClusterSize_ = size; }; /* ---- google.maps.Data interface ---- */ -DataLayerClusterer.prototype.add = function(feature) { +DataLayerClusterer.prototype.add = function (feature) { return this._dataLayer.add(feature); }; -DataLayerClusterer.prototype.addGeoJson = function(geoJson, options) { +DataLayerClusterer.prototype.addGeoJson = function (geoJson, options) { return this._dataLayer.addGeoJson(geoJson, options); }; -DataLayerClusterer.prototype.contains = function(feature) { +DataLayerClusterer.prototype.contains = function (feature) { return this._dataLayer.contains(feature); }; -DataLayerClusterer.prototype.forEach = function(callback) { +DataLayerClusterer.prototype.forEach = function (callback) { return this._dataLayer.forEach(callback); }; -DataLayerClusterer.prototype.getControlPosition = function() { +DataLayerClusterer.prototype.getControlPosition = function () { return this._dataLayer.getControlPosition(); }; -DataLayerClusterer.prototype.getControls = function() { +DataLayerClusterer.prototype.getControls = function () { return this._dataLayer.getControls(); }; -DataLayerClusterer.prototype.getDrawingMode = function() { +DataLayerClusterer.prototype.getDrawingMode = function () { return this._dataLayer.getDrawingMode(); }; -DataLayerClusterer.prototype.getFeatureById = function(id) { +DataLayerClusterer.prototype.getFeatureById = function (id) { return this._dataLayer.getFeatureById(id); }; -DataLayerClusterer.prototype.getStyle = function() { +DataLayerClusterer.prototype.getStyle = function () { return this._dataLayer.getStyle(); }; -DataLayerClusterer.prototype.loadGeoJson = function(url, options, callback) { +DataLayerClusterer.prototype.loadGeoJson = function (url, options, callback) { return this._dataLayer.loadGeoJson(url, options, callback); }; -DataLayerClusterer.prototype.overrideStyle = function(feature, style) { +DataLayerClusterer.prototype.overrideStyle = function (feature, style) { return this._dataLayer.overrideStyle(feature, style); }; -DataLayerClusterer.prototype.remove = function(feature) { +DataLayerClusterer.prototype.remove = function (feature) { return this._dataLayer.remove(feature); }; -DataLayerClusterer.prototype.revertStyle = function(feature) { +DataLayerClusterer.prototype.revertStyle = function (feature) { return this._dataLayer.revertStyle(feature); }; -DataLayerClusterer.prototype.setControlPosition = function(controlPosition) { +DataLayerClusterer.prototype.setControlPosition = function (controlPosition) { return this._dataLayer.setControlPosition(controlPosition); }; -DataLayerClusterer.prototype.setControls = function(controls) { +DataLayerClusterer.prototype.setControls = function (controls) { return this._dataLayer.setControls(controls); }; -DataLayerClusterer.prototype.setDrawingMode = function(drawingMode) { +DataLayerClusterer.prototype.setDrawingMode = function (drawingMode) { return this._dataLayer.setDrawingMode(drawingMode); }; -DataLayerClusterer.prototype.setStyle = function(style) { +DataLayerClusterer.prototype.setStyle = function (style) { var returnVal = this._dataLayer.setStyle(style); if (this.setProperty_) { this.redraw(); @@ -383,14 +383,14 @@ DataLayerClusterer.prototype.setStyle = function(style) { return returnVal; }; -DataLayerClusterer.prototype.toGeoJson = function(callback) { +DataLayerClusterer.prototype.toGeoJson = function (callback) { return this._dataLayer.toGeoJson(callback); }; /* ---- Private methods ---- */ -DataLayerClusterer.prototype.resetViewport = function() { +DataLayerClusterer.prototype.resetViewport = function () { // Remove all the clusters var csize = this.clusters_.length; for (var i = 0; i !== csize; ++i) { @@ -406,7 +406,7 @@ DataLayerClusterer.prototype.resetViewport = function() { * @param {boolean} ready The state. * @private */ -DataLayerClusterer.prototype.setReady_ = function(ready) { +DataLayerClusterer.prototype.setReady_ = function (ready) { this.ready_ = ready; if (ready) { this.createClusters_(); @@ -421,7 +421,7 @@ DataLayerClusterer.prototype.setReady_ = function(ready) { * @return {boolean} True if the feature is in the bounds. * @private */ -DataLayerClusterer.prototype.isFeatureInBounds_ = function(f, bounds) { +DataLayerClusterer.prototype.isFeatureInBounds_ = function (f, bounds) { var geom = f.getGeometry(), inBounds = false; @@ -429,7 +429,7 @@ DataLayerClusterer.prototype.isFeatureInBounds_ = function(f, bounds) { inBounds = bounds.contains(geom.get()); } else { var self = this; - geom.getArray().forEach(function(g) { + geom.getArray().forEach(function (g) { inBounds = g instanceof google.maps.LatLng ? bounds.contains(g) : bounds.contains(self.featureCenter_(g)); return !inBounds; }); @@ -447,19 +447,19 @@ DataLayerClusterer.prototype.isFeatureInBounds_ = function(f, bounds) { * @return {number} The distance between the two points in km. * @private */ -DataLayerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) { +DataLayerClusterer.prototype.distanceBetweenPoints_ = function (p1, p2) { if (!p1 || !p2) { return 0; } - var R = 6371; // Radius of the Earth in km - var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; - var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; - var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * - Math.sin(dLon / 2) * Math.sin(dLon / 2); - var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - var d = R * c; + var R = 6371, // Radius of the Earth in km + dLat = (p2.lat() - p1.lat()) * Math.PI / 180, + dLon = (p2.lng() - p1.lng()) * Math.PI / 180, + a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * + Math.sin(dLon / 2) * Math.sin(dLon / 2), + c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)), + d = R * c; return d; }; @@ -468,18 +468,18 @@ DataLayerClusterer.prototype.distanceBetweenPoints_ = function(p1, p2) { * * @private */ -DataLayerClusterer.prototype.featureBounds_ = function(feature, extendBounds) { +DataLayerClusterer.prototype.featureBounds_ = function (feature, extendBounds) { var geom = feature.getGeometry ? feature.getGeometry() : feature, geom_bounds = extendBounds || new google.maps.LatLngBounds(); if (geom.getType() == 'Point') { geom_bounds.extend(geom.get()); } else { - geom.getArray().forEach(function(g){ + geom.getArray().forEach(function (g) { if (g instanceof google.maps.LatLng) { geom_bounds.extend(g); } else { - g.getArray().forEach(function(LatLng) { + g.getArray().forEach(function (LatLng) { geom_bounds.extend(LatLng); }); } @@ -494,7 +494,7 @@ DataLayerClusterer.prototype.featureBounds_ = function(feature, extendBounds) { * * @private */ -DataLayerClusterer.prototype.featureCenter_ = function(feature) { +DataLayerClusterer.prototype.featureCenter_ = function (feature) { var geom = feature.getGeometry ? feature.getGeometry() : feature; if (geom.getType() == 'Point') { return geom.get(); @@ -509,13 +509,12 @@ DataLayerClusterer.prototype.featureCenter_ = function(feature) { * @param {google.maps.Data.Feature} feature The feature to add. * @private */ -DataLayerClusterer.prototype.addToClosestCluster_ = function(feature) { - var distance = 40000; // Some large number +DataLayerClusterer.prototype.addToClosestCluster_ = function (feature) { + var distance = 40000, // Some large number + pos = this.featureCenter_(feature), + cluster, + isVisible = true; - var pos = this.featureCenter_(feature); - var cluster; - - var isVisible = true; if (this.setProperty_) { var propBefore = feature.getProperty(DataLayerClusterer.CLUSTER_PROPERTY_NAME); feature.setProperty(DataLayerClusterer.CLUSTER_PROPERTY_NAME, false); @@ -555,17 +554,15 @@ DataLayerClusterer.prototype.addToClosestCluster_ = function(feature) { * * @private */ -DataLayerClusterer.prototype.createClusters_ = function() { +DataLayerClusterer.prototype.createClusters_ = function () { if (!this.ready_ || !this.map_) { return; } - var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(), - this.map_.getBounds().getNorthEast()); - var bounds = this.getExtendedBounds(mapBounds); - - var self = this; - this.forEach(function(feature) { + var mapBounds = new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(), this.map_.getBounds().getNorthEast()), + bounds = this.getExtendedBounds(mapBounds), + self = this; + this.forEach(function (feature) { if (self.isFeatureInBounds_(feature, bounds)) { self.addToClosestCluster_(feature); } @@ -580,7 +577,7 @@ DataLayerClusterer.prototype.createClusters_ = function() { * * Adds the data layer to the map and setup the events listeners. */ -DataLayerClusterer.prototype.onAdd = function() { +DataLayerClusterer.prototype.onAdd = function () { var map = this.getMap(); if (this.map_ !== map) { @@ -596,7 +593,7 @@ DataLayerClusterer.prototype.onAdd = function() { // Add the map event listeners var self = this; - this._zoomchanged = google.maps.event.addListener(this.map_, 'zoom_changed', function() { + this._zoomchanged = google.maps.event.addListener(this.map_, 'zoom_changed', function () { var zoom = self.map_.getZoom(); if (self.prevZoom_ !== zoom) { @@ -605,7 +602,7 @@ DataLayerClusterer.prototype.onAdd = function() { } }); - this._idle = google.maps.event.addListener(this.map_, 'idle', function() { + this._idle = google.maps.event.addListener(this.map_, 'idle', function () { if (!self.firstIdle_) { self.redraw(); } @@ -623,7 +620,7 @@ DataLayerClusterer.prototype.onAdd = function() { * * Removes the data layer from the map and cleans the events listeners. */ -DataLayerClusterer.prototype.onRemove = function() { +DataLayerClusterer.prototype.onRemove = function () { if (this.map_ !== null) { if (this._zoomchanged !== null) { try { @@ -648,7 +645,7 @@ DataLayerClusterer.prototype.onRemove = function() { /** * Empty implementation of the interface method. */ -DataLayerClusterer.prototype.draw = function() {}; +DataLayerClusterer.prototype.draw = function () {}; /* ---- Utils ---- */ @@ -660,8 +657,8 @@ DataLayerClusterer.prototype.draw = function() {}; * @param {Object} obj2 The object to extend with. * @return {Object} The new extended object. */ -DataLayerClusterer.extend = function(obj1, obj2) { - return (function(object) { +DataLayerClusterer.extend = function (obj1, obj2) { + return (function (object) { for (var property in object.prototype) { if (object.prototype[property]) { this.prototype[property] = object.prototype[property]; @@ -680,7 +677,7 @@ DataLayerClusterer.extend = function(obj1, obj2) { * @constructor * @ignore */ -function FeatureCluster(featureClusterer) { +function FeatureCluster (featureClusterer) { this.featureClusterer_ = featureClusterer; this.map_ = featureClusterer.getMap(); @@ -705,7 +702,7 @@ function FeatureCluster(featureClusterer) { * @param {google.maps.Data.Feature} feature The feature to check. * @return {boolean} True if the feature is already added. */ -FeatureCluster.prototype.isFeatureAlreadyAdded = function(feature) { +FeatureCluster.prototype.isFeatureAlreadyAdded = function (feature) { if (this.features_.indexOf) { return this.features_.indexOf(feature) !== -1; } else { @@ -727,7 +724,7 @@ FeatureCluster.prototype.isFeatureAlreadyAdded = function(feature) { * @param {google.maps.Data.Feature} feature The feature to add. * @return {boolean} True if the feature was added. */ -FeatureCluster.prototype.addFeature = function(feature) { +FeatureCluster.prototype.addFeature = function (feature) { if (this.isFeatureAlreadyAdded(feature)) { return false; } @@ -739,9 +736,9 @@ FeatureCluster.prototype.addFeature = function(feature) { this.calculateBounds_(); } else { if (this.averageCenter_) { - var l = this.features_.length + 1; - var lat = (this.center_.lat() * (l - 1) + centerPoint.lat()) / l; - var lng = (this.center_.lng() * (l - 1) + centerPoint.lng()) / l; + var l = this.features_.length + 1, + lat = (this.center_.lat() * (l - 1) + centerPoint.lat()) / l, + lng = (this.center_.lng() * (l - 1) + centerPoint.lng()) / l; this.center_ = new google.maps.LatLng(lat, lng); this.calculateBounds_(); } @@ -752,15 +749,15 @@ FeatureCluster.prototype.addFeature = function(feature) { var len = this.features_.length; if (len == 1 && !!this.featureClusterer_.minPolySize_ && feature.getGeometry().getType() != 'Point') { - var polyMinSize = this.featureClusterer_.minPolySize_; - var bounds = this.featureClusterer_.featureBounds_(feature); - var SW = bounds.getSouthWest(); - var NE = bounds.getNorthEast(); - var proj = this.map_.getProjection(); - var swPx = proj.fromLatLngToPoint(SW); - var nePx = proj.fromLatLngToPoint(NE); - var pixelWidth = Math.round(Math.abs((nePx.x - swPx.x)* Math.pow(2, this.map_.getZoom()))); - var pixelHeight = Math.round(Math.abs((swPx.y - nePx.y)* Math.pow(2, this.map_.getZoom()))); + var polyMinSize = this.featureClusterer_.minPolySize_, + bounds = this.featureClusterer_.featureBounds_(feature), + SW = bounds.getSouthWest(), + NE = bounds.getNorthEast(), + proj = this.map_.getProjection(), + swPx = proj.fromLatLngToPoint(SW), + nePx = proj.fromLatLngToPoint(NE), + pixelWidth = Math.round(Math.abs((nePx.x - swPx.x)* Math.pow(2, this.map_.getZoom()))), + pixelHeight = Math.round(Math.abs((swPx.y - nePx.y)* Math.pow(2, this.map_.getZoom()))); if (pixelWidth < polyMinSize && pixelHeight < polyMinSize) { this.forced_ = true; @@ -798,7 +795,7 @@ FeatureCluster.prototype.addFeature = function(feature) { * * @return {DataLayerClusterer} The associated feature clusterer. */ -FeatureCluster.prototype.getDataLayerClusterer = function() { +FeatureCluster.prototype.getDataLayerClusterer = function () { return this.featureClusterer_; }; @@ -807,10 +804,10 @@ FeatureCluster.prototype.getDataLayerClusterer = function() { * * @return {google.maps.LatLngBounds} the cluster bounds. */ -FeatureCluster.prototype.getBounds = function() { - var bounds = new google.maps.LatLngBounds(this.center_, this.center_); +FeatureCluster.prototype.getBounds = function () { + var bounds = new google.maps.LatLngBounds(this.center_, this.center_), + fsize = this.features_.length; - var fsize = this.features_.length; for (var i = 0; i !== fsize; ++i) { bounds = this.featureClusterer_.featureBounds_(this.features_[i], bounds); } @@ -821,7 +818,7 @@ FeatureCluster.prototype.getBounds = function() { /** * Removes the cluster */ -FeatureCluster.prototype.remove = function() { +FeatureCluster.prototype.remove = function () { this.clusterIcon_.remove(); this.features_.length = 0; delete this.features_; @@ -832,7 +829,7 @@ FeatureCluster.prototype.remove = function() { * * @return {number} The cluster size. */ -FeatureCluster.prototype.getSize = function() { +FeatureCluster.prototype.getSize = function () { return this.features_.length; }; @@ -841,7 +838,7 @@ FeatureCluster.prototype.getSize = function() { * * @return {Array.} The cluster's features. */ -FeatureCluster.prototype.getFeatures = function() { +FeatureCluster.prototype.getFeatures = function () { return this.features_; }; @@ -850,7 +847,7 @@ FeatureCluster.prototype.getFeatures = function() { * * @return {google.maps.LatLng} The cluster center. */ -FeatureCluster.prototype.getCenter = function() { +FeatureCluster.prototype.getCenter = function () { return this.center_; }; @@ -860,7 +857,7 @@ FeatureCluster.prototype.getCenter = function() { * * @private */ -FeatureCluster.prototype.calculateBounds_ = function() { +FeatureCluster.prototype.calculateBounds_ = function () { var bounds = new google.maps.LatLngBounds(this.center_, this.center_); this.bounds_ = this.featureClusterer_.getExtendedBounds(bounds); }; @@ -871,7 +868,7 @@ FeatureCluster.prototype.calculateBounds_ = function() { * @param {google.maps.Data.Feature} feature The feature to check. * @return {boolean} True if the feature lies in the bounds. */ -FeatureCluster.prototype.isFeatureInClusterBounds = function(feature) { +FeatureCluster.prototype.isFeatureInClusterBounds = function (feature) { return this.bounds_.contains(this.featureClusterer_.featureCenter_(feature)); }; @@ -880,16 +877,16 @@ FeatureCluster.prototype.isFeatureInClusterBounds = function(feature) { * * @return {google.maps.Map} The map. */ -FeatureCluster.prototype.getMap = function() { +FeatureCluster.prototype.getMap = function () { return this.map_; }; /** * Updates the cluster icon */ -FeatureCluster.prototype.updateIcon = function() { - var zoom = this.map_.getZoom(); - var mz = this.featureClusterer_.getMaxZoom(); +FeatureCluster.prototype.updateIcon = function () { + var zoom = this.map_.getZoom(), + mz = this.featureClusterer_.getMaxZoom(); if (mz && zoom> mz) { // The zoom is greater than our max zoom so show all the features in cluster. @@ -911,8 +908,8 @@ FeatureCluster.prototype.updateIcon = function() { return; } - var numStyles = this.featureClusterer_.getStyles().length; - var sums = this.featureClusterer_.getCalculator()(this.features_, numStyles); + var numStyles = this.featureClusterer_.getStyles().length, + sums = this.featureClusterer_.getCalculator()(this.features_, numStyles); this.clusterIcon_.setSums(sums); @@ -937,7 +934,7 @@ FeatureCluster.prototype.updateIcon = function() { * @constructor * @extends google.maps.OverlayView */ -function FeatureClusterIcon(cluster, styles, optpadding, classId) { +function FeatureClusterIcon (cluster, styles, optpadding, classId) { DataLayerClusterer.extend(FeatureClusterIcon, google.maps.OverlayView); this.styles_ = styles; @@ -958,7 +955,7 @@ function FeatureClusterIcon(cluster, styles, optpadding, classId) { /** * Hide the icon. */ -FeatureClusterIcon.prototype.hide = function() { +FeatureClusterIcon.prototype.hide = function () { if (this.div_) { this.div_.style.display = 'none'; } @@ -968,7 +965,7 @@ FeatureClusterIcon.prototype.hide = function() { /** * Position and show the icon. */ -FeatureClusterIcon.prototype.show = function() { +FeatureClusterIcon.prototype.show = function () { if (this.div_) { var pos = this.getPosFromLatLng_(this.center_); this.div_.style.cssText = this.createCss(pos); @@ -980,7 +977,7 @@ FeatureClusterIcon.prototype.show = function() { /** * Remove the icon from the map */ -FeatureClusterIcon.prototype.remove = function() { +FeatureClusterIcon.prototype.remove = function () { this.setMap(null); }; @@ -989,7 +986,7 @@ FeatureClusterIcon.prototype.remove = function() { * * @param {google.maps.LatLng} center The latlng to set as the center. */ -FeatureClusterIcon.prototype.setCenter = function(center) { +FeatureClusterIcon.prototype.setCenter = function (center) { this.center_ = center; }; @@ -1000,7 +997,7 @@ FeatureClusterIcon.prototype.setCenter = function(center) { * Adding the cluster icon to the dom. * @ignore */ -FeatureClusterIcon.prototype.onAdd = function() { +FeatureClusterIcon.prototype.onAdd = function () { this.div_ = document.createElement('DIV'); if (this.visible_) { var pos = this.getPosFromLatLng_(this.center_); @@ -1013,7 +1010,7 @@ FeatureClusterIcon.prototype.onAdd = function() { panes.overlayMouseTarget.appendChild(this.div_); var self = this; - google.maps.event.addDomListener(this.div_, 'click', function() { + google.maps.event.addDomListener(this.div_, 'click', function () { self.triggerClusterClick(); }); }; @@ -1022,7 +1019,7 @@ FeatureClusterIcon.prototype.onAdd = function() { * Draw the icon. * @ignore */ -FeatureClusterIcon.prototype.draw = function() { +FeatureClusterIcon.prototype.draw = function () { if (this.visible_) { var pos = this.getPosFromLatLng_(this.center_); this.div_.style.top = pos.y + 'px'; @@ -1034,7 +1031,7 @@ FeatureClusterIcon.prototype.draw = function() { * Implementation of the onRemove interface. * @ignore */ -FeatureClusterIcon.prototype.onRemove = function() { +FeatureClusterIcon.prototype.onRemove = function () { if (this.div_ && this.div_.parentNode) { this.hide(); this.div_.parentNode.removeChild(this.div_); @@ -1048,7 +1045,7 @@ FeatureClusterIcon.prototype.onRemove = function() { /** * Triggers the clusterclick event and zoom's if the option is set. */ -FeatureClusterIcon.prototype.triggerClusterClick = function() { +FeatureClusterIcon.prototype.triggerClusterClick = function () { var featureClusterer = this.cluster_.getDataLayerClusterer(); // Trigger the clusterclick event. @@ -1067,7 +1064,7 @@ FeatureClusterIcon.prototype.triggerClusterClick = function() { * @return {google.maps.Point} The position in pixels. * @private */ -FeatureClusterIcon.prototype.getPosFromLatLng_ = function(latlng) { +FeatureClusterIcon.prototype.getPosFromLatLng_ = function (latlng) { var pos = this.getProjection().fromLatLngToDivPixel(latlng); pos.x -= parseInt(this.width_ / 2, 10); pos.y -= parseInt(this.height_ / 2, 10); @@ -1080,7 +1077,7 @@ FeatureClusterIcon.prototype.getPosFromLatLng_ = function(latlng) { * @param {google.maps.Point} pos The position. * @return {string} The css style text. */ -FeatureClusterIcon.prototype.createCss = function(pos) { +FeatureClusterIcon.prototype.createCss = function (pos) { var style = []; style.push('background-image:url(' + this.url_ + ');'); if (this.cluster_.featureClusterer_.recolorSVG_) style.push('background-size: contain;'); @@ -1108,8 +1105,8 @@ FeatureClusterIcon.prototype.createCss = function(pos) { this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;'); } - var txtColor = this.textColor_ ? this.textColor_ : 'black'; - var txtSize = this.textSize_ ? this.textSize_ : 11; + var txtColor = this.textColor_ ? this.textColor_ : 'black', + txtSize = this.textSize_ ? this.textSize_ : 11; style.push('cursor:pointer; top:' + pos.y + 'px; left:' + pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' + @@ -1120,7 +1117,7 @@ FeatureClusterIcon.prototype.createCss = function(pos) { /** * Sets the icon to the the styles. */ -FeatureClusterIcon.prototype.useStyle = function() { +FeatureClusterIcon.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]; @@ -1140,7 +1137,7 @@ FeatureClusterIcon.prototype.useStyle = function() { * 'text': (string) The text to display in the icon. * 'index': (number) The style index of the icon. */ -FeatureClusterIcon.prototype.setSums = function(sums) { +FeatureClusterIcon.prototype.setSums = function (sums) { this.sums_ = sums; this.text_ = sums.text; this.index_ = sums.index; @@ -1172,11 +1169,11 @@ DataLayerClusterer.MARKER_CLUSTER_IMAGE_EXTENSION_ = document.implementation.has * * @private */ -DataLayerClusterer.prototype.init_ = function() { +DataLayerClusterer.prototype.init_ = function () { this.setupStyles_(); if (this.setProperty_) { - this._dataLayer.forEach(function(feature) { + this._dataLayer.forEach(function (feature) { feature.setProperty('in_cluster', true); }); } @@ -1190,7 +1187,7 @@ DataLayerClusterer.prototype.init_ = function() { * * @private */ -DataLayerClusterer.prototype.setupStyles_ = function() { +DataLayerClusterer.prototype.setupStyles_ = function () { if (this.styles_.length) { return; } @@ -1217,7 +1214,7 @@ DataLayerClusterer.prototype.setupStyles_ = function() { * * @param {Object} styles The style to set. */ -DataLayerClusterer.prototype.setStyles = function(styles) { +DataLayerClusterer.prototype.setStyles = function (styles) { this.styles_ = styles; }; @@ -1226,28 +1223,28 @@ DataLayerClusterer.prototype.setStyles = function(styles) { * * @return {Object} The styles object. */ -DataLayerClusterer.prototype.getStyles = function() { +DataLayerClusterer.prototype.getStyles = function () { return this.styles_; }; /** * Set the calculator function. * - * @param {function(Array, number)} calculator The function to set as the + * @param {function (Array, number)} calculator The function to set as the * calculator. The function should return a object properties: * 'text' (string) and 'index' (number). * */ -DataLayerClusterer.prototype.setCalculator = function(calculator) { +DataLayerClusterer.prototype.setCalculator = function (calculator) { this.calculator_ = calculator; }; /** * Get the calculator function. * - * @return {function(Array, number)} the calculator function. + * @return {function (Array, number)} the calculator function. */ -DataLayerClusterer.prototype.getCalculator = function() { +DataLayerClusterer.prototype.getCalculator = function () { return this.calculator_; }; @@ -1259,10 +1256,10 @@ DataLayerClusterer.prototype.getCalculator = function() { * @return {Object} A object properties: 'text' (string) and 'index' (number). * @private */ -DataLayerClusterer.prototype.calculator_ = function(features, numStyles) { - var index = 0; - var count = features.length; - var dv = count; +DataLayerClusterer.prototype.calculator_ = function (features, numStyles) { + var index = 0, + count = features.length, + dv = count; while (dv !== 0) { dv = parseInt(dv / 10, 10); index++; From acda46baea0488b5773617d7a5464a79eca587f1 Mon Sep 17 00:00:00 2001 From: Connum Date: 2017年2月24日 12:00:27 +0100 Subject: [PATCH 37/39] performance optimization: do not recalculate center if _center is already set --- src/datalayerclusterer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 6f218a1..09c6d42 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -729,7 +729,7 @@ FeatureCluster.prototype.addFeature = function (feature) { return false; } - var geom = feature.getGeometry(), centerPoint = this.featureClusterer_.featureCenter_(feature); + var geom = feature.getGeometry(), centerPoint = this.center_ || this.featureClusterer_.featureCenter_(feature); if (!this.center_) { this.center_ = centerPoint; From 8884026dc53a14c24baaf25855cea6031030acd6 Mon Sep 17 00:00:00 2001 From: Connum Date: 2017年2月24日 13:21:45 +0100 Subject: [PATCH 38/39] performance: removed unnecessary expensive loop over features --- src/datalayerclusterer.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 09c6d42..8ae0da6 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -1172,11 +1172,6 @@ DataLayerClusterer.MARKER_CLUSTER_IMAGE_EXTENSION_ = document.implementation.has DataLayerClusterer.prototype.init_ = function () { this.setupStyles_(); - if (this.setProperty_) { - this._dataLayer.forEach(function (feature) { - feature.setProperty('in_cluster', true); - }); - } if (this.map !== null) { this.setMap(this.map); } From e792ca6727d297a2f3a21dd84dfd8af714593402 Mon Sep 17 00:00:00 2001 From: Connum Date: 2017年2月24日 14:09:39 +0100 Subject: [PATCH 39/39] some minor performance improvement (prevents flickering): do not redraw if neither bounding box nor zoom level has changed on idle or zoom_changed (for example, if zooming out beyond a specific zoom level has been disabled on the map) --- src/datalayerclusterer.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/datalayerclusterer.js b/src/datalayerclusterer.js index 8ae0da6..432d758 100755 --- a/src/datalayerclusterer.js +++ b/src/datalayerclusterer.js @@ -99,6 +99,7 @@ function DataLayerClusterer (optOptions) { this.averageCenter_ = options.averageCenter !== undefined ? options.averageCenter : true; this._dataLayer = new google.maps.Data(); this.firstIdle_ = true; + this.prevBounds_ = null; this.recolorSVG_ = typeof options.recolorSVG !== "undefined" && (typeof options.recolorSVG === "string" || options.recolorSVG instanceof String || options.recolorSVG === false) ? options.recolorSVG : 'g:first-child'; this.baseSVG_ = null; @@ -231,6 +232,10 @@ DataLayerClusterer.prototype.redraw = function () { oldClusters[i].remove(); } }); + + if (this.map_) { + this.prevBounds_ = this.map_.getBounds(); + } }; @@ -594,16 +599,17 @@ DataLayerClusterer.prototype.onAdd = function () { // Add the map event listeners var self = this; this._zoomchanged = google.maps.event.addListener(this.map_, 'zoom_changed', function () { - var zoom = self.map_.getZoom(); - - if (self.prevZoom_ !== zoom) { + var zoom = self.map_.getZoom(), + nothingChanged = (self.prevBounds_ && self.prevBounds_.equals(self.map_.getBounds())); + if (self.prevZoom_ !== zoom && nothingChanged !== true) { self.prevZoom_ = zoom; self.resetViewport(); } }); this._idle = google.maps.event.addListener(this.map_, 'idle', function () { - if (!self.firstIdle_) { + var nothingChanged = (self.map_ && self.prevZoom_ && self.prevZoom_ === self.map_.getZoom() && self.prevBounds_ && self.prevBounds_.equals(self.map_.getBounds())); + if (!self.firstIdle_ && nothingChanged !== true) { self.redraw(); } self.firstIdle_ = false;

AltStyle によって変換されたページ (->オリジナル) /